lib/choir/src/backends/wasm/binary/format.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const binary = @import("root.zig");
3
4 pub fn writeHeader(out: anytype) !void {
5 try out.writeAll(&.{ 0x00, 0x61, 0x73, 0x6d });
6 try out.writeAll(&.{ 0x01, 0x00, 0x00, 0x00 });
7 }
8
9 pub fn writeSectionHeader(out: anytype, section: binary.Section, body_len: usize) !void {
10 try out.writeByte(@backingInt(section));
11 try writeUleb(out, body_len);
12 }
13
14 pub fn writeName(out: anytype, name: []const u8) !void {
15 try writeUleb(out, name.len);
16 try out.writeAll(name);
17 }
18
19 pub fn writeValueType(out: anytype, value_type: binary.ValueType) !void {
20 try out.writeByte(@backingInt(value_type));
21 }
22
23 pub fn writeF32(out: anytype, value: f32) !void {
24 try writeLittleU32(out, @bitCast(value));
25 }
26
27 pub fn writeF64(out: anytype, value: f64) !void {
28 try writeLittleU64(out, @bitCast(value));
29 }
30
31 pub fn writeLittleU32(out: anytype, value: u32) !void {
32 try out.writeByte(@intCast(value & 0xff));
33 try out.writeByte(@intCast((value >> 8) & 0xff));
34 try out.writeByte(@intCast((value >> 16) & 0xff));
35 try out.writeByte(@intCast((value >> 24) & 0xff));
36 }
37
38 pub fn writeLittleU64(out: anytype, value: u64) !void {
39 try writeLittleU32(out, @intCast(value & 0xffff_ffff));
40 try writeLittleU32(out, @intCast(value >> 32));
41 }
42
43 pub fn writeUleb(out: anytype, value: anytype) !void {
44 var remaining: u64 = @intCast(value);
45 while (true) {
46 var byte: u8 = @intCast(remaining & 0x7f);
47 remaining >>= 7;
48 if (remaining != 0) byte |= 0x80;
49 try out.writeByte(byte);
50 if (remaining == 0) return;
51 }
52 }
53
54 pub fn writeSleb(out: anytype, value: i64) !void {
55 var remaining = value;
56 while (true) {
57 var byte: u8 = @intCast(@as(u64, @bitCast(remaining)) & 0x7f);
58 remaining >>= 7;
59 const sign_bit = (byte & 0x40) != 0;
60 const done = (remaining == 0 and !sign_bit) or (remaining == -1 and sign_bit);
61 if (!done) byte |= 0x80;
62 try out.writeByte(byte);
63 if (done) return;
64 }
65 }
66
67 test "wasm binary unsigned leb128 encoding" {
68 var bytes: [3]u8 = undefined;
69 var out = binary.Fixed.init(&bytes);
70 try writeUleb(&out, @as(u32, 624485));
71 try std.testing.expectEqualSlices(u8, &.{ 0xe5, 0x8e, 0x26 }, try out.finish());
72 }
73
74 test "wasm binary signed leb128 encoding" {
75 var bytes: [3]u8 = undefined;
76 var out = binary.Fixed.init(&bytes);
77 try writeSleb(&out, -624485);
78 try std.testing.expectEqualSlices(u8, &.{ 0x9b, 0xf1, 0x59 }, try out.finish());
79 }
80
81 test "wasm binary section header carries id and body size" {
82 var bytes: [2]u8 = undefined;
83 var out = binary.Fixed.init(&bytes);
84 try writeSectionHeader(&out, .type, 4);
85 try std.testing.expectEqualSlices(u8, &.{ 0x01, 0x04 }, try out.finish());
86 }
87
88 test "wasm binary counter and fixed sink share encodings" {
89 var count = binary.Count{};
90 try writeName(&count, "bounded");
91 var bytes: [8]u8 = undefined;
92 try std.testing.expectEqual(bytes.len, count.len);
93 var out = binary.Fixed.init(&bytes);
94 try writeName(&out, "bounded");
95 try std.testing.expectEqualSlices(u8, &.{ 7, 'b', 'o', 'u', 'n', 'd', 'e', 'd' }, try out.finish());
96 }
97
98 test "wasm binary unsigned lengths cross canonical boundaries exactly" {
99 const cases = [_]struct { value: u32, len: usize }{
100 .{ .value = 127, .len = 1 },
101 .{ .value = 128, .len = 2 },
102 .{ .value = 16_383, .len = 2 },
103 .{ .value = 16_384, .len = 3 },
104 .{ .value = std.math.maxInt(u32), .len = 5 },
105 };
106 for (cases) |case| {
107 var count = binary.Count{};
108 try writeUleb(&count, case.value);
109 try std.testing.expectEqual(case.len, count.len);
110 var bytes: [5]u8 = undefined;
111 var out = binary.Fixed.init(bytes[0..case.len]);
112 try writeUleb(&out, case.value);
113 _ = try out.finish();
114 }
115 }