1///! Contains all constants and types representing the wasm
2///! binary format, as specified by:
3///! https://webassembly.github.io/spec/core/
4const std = @import("std.zig");
5const testing = std.testing;
6
7// TODO: Add support for multi-byte ops (e.g. table operations)
8
9/// Wasm instruction opcodes
10///
11/// All instructions are defined as per spec:
12/// https://webassembly.github.io/spec/core/appendix/index-instructions.html
13pub const Opcode = enum(u8) {
14    @"unreachable" = 0x00,
15    nop = 0x01,
16    block = 0x02,
17    loop = 0x03,
18    @"if" = 0x04,
19    @"else" = 0x05,
20    end = 0x0B,
21    br = 0x0C,
22    br_if = 0x0D,
23    br_table = 0x0E,
24    @"return" = 0x0F,
25    call = 0x10,
26    call_indirect = 0x11,
27    drop = 0x1A,
28    select = 0x1B,
29    local_get = 0x20,
30    local_set = 0x21,
31    local_tee = 0x22,
32    global_get = 0x23,
33    global_set = 0x24,
34    i32_load = 0x28,
35    i64_load = 0x29,
36    f32_load = 0x2A,
37    f64_load = 0x2B,
38    i32_load8_s = 0x2C,
39    i32_load8_u = 0x2D,
40    i32_load16_s = 0x2E,
41    i32_load16_u = 0x2F,
42    i64_load8_s = 0x30,
43    i64_load8_u = 0x31,
44    i64_load16_s = 0x32,
45    i64_load16_u = 0x33,
46    i64_load32_s = 0x34,
47    i64_load32_u = 0x35,
48    i32_store = 0x36,
49    i64_store = 0x37,
50    f32_store = 0x38,
51    f64_store = 0x39,
52    i32_store8 = 0x3A,
53    i32_store16 = 0x3B,
54    i64_store8 = 0x3C,
55    i64_store16 = 0x3D,
56    i64_store32 = 0x3E,
57    memory_size = 0x3F,
58    memory_grow = 0x40,
59    i32_const = 0x41,
60    i64_const = 0x42,
61    f32_const = 0x43,
62    f64_const = 0x44,
63    i32_eqz = 0x45,
64    i32_eq = 0x46,
65    i32_ne = 0x47,
66    i32_lt_s = 0x48,
67    i32_lt_u = 0x49,
68    i32_gt_s = 0x4A,
69    i32_gt_u = 0x4B,
70    i32_le_s = 0x4C,
71    i32_le_u = 0x4D,
72    i32_ge_s = 0x4E,
73    i32_ge_u = 0x4F,
74    i64_eqz = 0x50,
75    i64_eq = 0x51,
76    i64_ne = 0x52,
77    i64_lt_s = 0x53,
78    i64_lt_u = 0x54,
79    i64_gt_s = 0x55,
80    i64_gt_u = 0x56,
81    i64_le_s = 0x57,
82    i64_le_u = 0x58,
83    i64_ge_s = 0x59,
84    i64_ge_u = 0x5A,
85    f32_eq = 0x5B,
86    f32_ne = 0x5C,
87    f32_lt = 0x5D,
88    f32_gt = 0x5E,
89    f32_le = 0x5F,
90    f32_ge = 0x60,
91    f64_eq = 0x61,
92    f64_ne = 0x62,
93    f64_lt = 0x63,
94    f64_gt = 0x64,
95    f64_le = 0x65,
96    f64_ge = 0x66,
97    i32_clz = 0x67,
98    i32_ctz = 0x68,
99    i32_popcnt = 0x69,
100    i32_add = 0x6A,
101    i32_sub = 0x6B,
102    i32_mul = 0x6C,
103    i32_div_s = 0x6D,
104    i32_div_u = 0x6E,
105    i32_rem_s = 0x6F,
106    i32_rem_u = 0x70,
107    i32_and = 0x71,
108    i32_or = 0x72,
109    i32_xor = 0x73,
110    i32_shl = 0x74,
111    i32_shr_s = 0x75,
112    i32_shr_u = 0x76,
113    i32_rotl = 0x77,
114    i32_rotr = 0x78,
115    i64_clz = 0x79,
116    i64_ctz = 0x7A,
117    i64_popcnt = 0x7B,
118    i64_add = 0x7C,
119    i64_sub = 0x7D,
120    i64_mul = 0x7E,
121    i64_div_s = 0x7F,
122    i64_div_u = 0x80,
123    i64_rem_s = 0x81,
124    i64_rem_u = 0x82,
125    i64_and = 0x83,
126    i64_or = 0x84,
127    i64_xor = 0x85,
128    i64_shl = 0x86,
129    i64_shr_s = 0x87,
130    i64_shr_u = 0x88,
131    i64_rotl = 0x89,
132    i64_rotr = 0x8A,
133    f32_abs = 0x8B,
134    f32_neg = 0x8C,
135    f32_ceil = 0x8D,
136    f32_floor = 0x8E,
137    f32_trunc = 0x8F,
138    f32_nearest = 0x90,
139    f32_sqrt = 0x91,
140    f32_add = 0x92,
141    f32_sub = 0x93,
142    f32_mul = 0x94,
143    f32_div = 0x95,
144    f32_min = 0x96,
145    f32_max = 0x97,
146    f32_copysign = 0x98,
147    f64_abs = 0x99,
148    f64_neg = 0x9A,
149    f64_ceil = 0x9B,
150    f64_floor = 0x9C,
151    f64_trunc = 0x9D,
152    f64_nearest = 0x9E,
153    f64_sqrt = 0x9F,
154    f64_add = 0xA0,
155    f64_sub = 0xA1,
156    f64_mul = 0xA2,
157    f64_div = 0xA3,
158    f64_min = 0xA4,
159    f64_max = 0xA5,
160    f64_copysign = 0xA6,
161    i32_wrap_i64 = 0xA7,
162    i32_trunc_f32_s = 0xA8,
163    i32_trunc_f32_u = 0xA9,
164    i32_trunc_f64_s = 0xAA,
165    i32_trunc_f64_u = 0xAB,
166    i64_extend_i32_s = 0xAC,
167    i64_extend_i32_u = 0xAD,
168    i64_trunc_f32_s = 0xAE,
169    i64_trunc_f32_u = 0xAF,
170    i64_trunc_f64_s = 0xB0,
171    i64_trunc_f64_u = 0xB1,
172    f32_convert_i32_s = 0xB2,
173    f32_convert_i32_u = 0xB3,
174    f32_convert_i64_s = 0xB4,
175    f32_convert_i64_u = 0xB5,
176    f32_demote_f64 = 0xB6,
177    f64_convert_i32_s = 0xB7,
178    f64_convert_i32_u = 0xB8,
179    f64_convert_i64_s = 0xB9,
180    f64_convert_i64_u = 0xBA,
181    f64_promote_f32 = 0xBB,
182    i32_reinterpret_f32 = 0xBC,
183    i64_reinterpret_f64 = 0xBD,
184    f32_reinterpret_i32 = 0xBE,
185    f64_reinterpret_i64 = 0xBF,
186    i32_extend8_s = 0xC0,
187    i32_extend16_s = 0xC1,
188    i64_extend8_s = 0xC2,
189    i64_extend16_s = 0xC3,
190    i64_extend32_s = 0xC4,
191    _,
192};
193
194/// Returns the integer value of an `Opcode`. Used by the Zig compiler
195/// to write instructions to the wasm binary file
196pub fn opcode(op: Opcode) u8 {
197    return @enumToInt(op);
198}
199
200test "Wasm - opcodes" {
201    // Ensure our opcodes values remain intact as certain values are skipped due to them being reserved
202    const i32_const = opcode(.i32_const);
203    const end = opcode(.end);
204    const drop = opcode(.drop);
205    const local_get = opcode(.local_get);
206    const i64_extend32_s = opcode(.i64_extend32_s);
207
208    try testing.expectEqual(@as(u16, 0x41), i32_const);
209    try testing.expectEqual(@as(u16, 0x0B), end);
210    try testing.expectEqual(@as(u16, 0x1A), drop);
211    try testing.expectEqual(@as(u16, 0x20), local_get);
212    try testing.expectEqual(@as(u16, 0xC4), i64_extend32_s);
213}
214
215/// Enum representing all Wasm value types as per spec:
216/// https://webassembly.github.io/spec/core/binary/types.html
217pub const Valtype = enum(u8) {
218    i32 = 0x7F,
219    i64 = 0x7E,
220    f32 = 0x7D,
221    f64 = 0x7C,
222};
223
224/// Returns the integer value of a `Valtype`
225pub fn valtype(value: Valtype) u8 {
226    return @enumToInt(value);
227}
228
229/// Reference types, where the funcref references to a function regardless of its type
230/// and ref references an object from the embedder.
231pub const RefType = enum(u8) {
232    funcref = 0x70,
233    externref = 0x6F,
234};
235
236/// Returns the integer value of a `Reftype`
237pub fn reftype(value: RefType) u8 {
238    return @enumToInt(value);
239}
240
241test "Wasm - valtypes" {
242    const _i32 = valtype(.i32);
243    const _i64 = valtype(.i64);
244    const _f32 = valtype(.f32);
245    const _f64 = valtype(.f64);
246
247    try testing.expectEqual(@as(u8, 0x7F), _i32);
248    try testing.expectEqual(@as(u8, 0x7E), _i64);
249    try testing.expectEqual(@as(u8, 0x7D), _f32);
250    try testing.expectEqual(@as(u8, 0x7C), _f64);
251}
252
253/// Limits classify the size range of resizeable storage associated with memory types and table types.
254pub const Limits = struct {
255    min: u32,
256    max: ?u32,
257};
258
259/// Initialization expressions are used to set the initial value on an object
260/// when a wasm module is being loaded.
261pub const InitExpression = union(enum) {
262    i32_const: i32,
263    i64_const: i64,
264    f32_const: f32,
265    f64_const: f64,
266    global_get: u32,
267};
268
269///
270pub const Func = struct {
271    type_index: u32,
272};
273
274/// Tables are used to hold pointers to opaque objects.
275/// This can either by any function, or an object from the host.
276pub const Table = struct {
277    limits: Limits,
278    reftype: RefType,
279};
280
281/// Describes the layout of the memory where `min` represents
282/// the minimal amount of pages, and the optional `max` represents
283/// the max pages. When `null` will allow the host to determine the
284/// amount of pages.
285pub const Memory = struct {
286    limits: Limits,
287};
288
289/// Represents the type of a `Global` or an imported global.
290pub const GlobalType = struct {
291    valtype: Valtype,
292    mutable: bool,
293};
294
295pub const Global = struct {
296    global_type: GlobalType,
297    init: InitExpression,
298};
299
300/// Notates an object to be exported from wasm
301/// to the host.
302pub const Export = struct {
303    name: []const u8,
304    kind: ExternalKind,
305    index: u32,
306};
307
308/// Element describes the layout of the table that can
309/// be found at `table_index`
310pub const Element = struct {
311    table_index: u32,
312    offset: InitExpression,
313    func_indexes: []const u32,
314};
315
316/// Imports are used to import objects from the host
317pub const Import = struct {
318    module_name: []const u8,
319    name: []const u8,
320    kind: Kind,
321
322    pub const Kind = union(ExternalKind) {
323        function: u32,
324        table: Table,
325        memory: Limits,
326        global: GlobalType,
327    };
328};
329
330/// `Type` represents a function signature type containing both
331/// a slice of parameters as well as a slice of return values.
332pub const Type = struct {
333    params: []const Valtype,
334    returns: []const Valtype,
335
336    pub fn format(self: Type, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
337        _ = fmt;
338        _ = opt;
339        try writer.writeByte('(');
340        for (self.params) |param, i| {
341            try writer.print("{s}", .{@tagName(param)});
342            if (i + 1 != self.params.len) {
343                try writer.writeAll(", ");
344            }
345        }
346        try writer.writeAll(") -> ");
347        if (self.returns.len == 0) {
348            try writer.writeAll("nil");
349        } else {
350            for (self.returns) |return_ty, i| {
351                try writer.print("{s}", .{@tagName(return_ty)});
352                if (i + 1 != self.returns.len) {
353                    try writer.writeAll(", ");
354                }
355            }
356        }
357    }
358
359    pub fn eql(self: Type, other: Type) bool {
360        return std.mem.eql(Valtype, self.params, other.params) and
361            std.mem.eql(Valtype, self.returns, other.returns);
362    }
363
364    pub fn deinit(self: *Type, gpa: std.mem.Allocator) void {
365        gpa.free(self.params);
366        gpa.free(self.returns);
367        self.* = undefined;
368    }
369};
370
371/// Wasm module sections as per spec:
372/// https://webassembly.github.io/spec/core/binary/modules.html
373pub const Section = enum(u8) {
374    custom,
375    type,
376    import,
377    function,
378    table,
379    memory,
380    global,
381    @"export",
382    start,
383    element,
384    code,
385    data,
386    data_count,
387    _,
388};
389
390/// Returns the integer value of a given `Section`
391pub fn section(val: Section) u8 {
392    return @enumToInt(val);
393}
394
395/// The kind of the type when importing or exporting to/from the host environment
396/// https://webassembly.github.io/spec/core/syntax/modules.html
397pub const ExternalKind = enum(u8) {
398    function,
399    table,
400    memory,
401    global,
402};
403
404/// Returns the integer value of a given `ExternalKind`
405pub fn externalKind(val: ExternalKind) u8 {
406    return @enumToInt(val);
407}
408
409// type constants
410pub const element_type: u8 = 0x70;
411pub const function_type: u8 = 0x60;
412pub const result_type: u8 = 0x40;
413
414/// Represents a block which will not return a value
415pub const block_empty: u8 = 0x40;
416
417// binary constants
418pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm
419pub const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1 (MVP)
420
421// Each wasm page size is 64kB
422pub const page_size = 64 * 1024;
423