// Copyright (c) 2023 l-m.dev. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module wasm import encoding.leb128 fn (mut func Function) u32(v u32) { func.code << leb128.encode_u32(v) } fn (mut func Function) blocktype(typ FuncType) { if typ.parameters.len == 0 { if typ.results.len == 0 { func.code << 0x40 // empty type return } else if typ.results.len == 1 { func.code << u8(typ.results[0]) // encode a single result type return } } // encode full type tidx := func.mod.new_functype(typ) func.code << leb128.encode_i32(tidx) } pub type PatchPos = int // patch_pos returns a `PatchPos` for use with `patch`. pub fn (func Function) patch_pos() PatchPos { return func.code.len } // patch "patches" the code generated starting from the last `patch_pos` call in `begin` to `loc`. // // ```v // start := func.patch_pos() // // ... // // patch_block := func.patch_pos() // { // func.i32_const(10) // func.local_set(idx) // } // func.patch(start, patch_block) // will patch code to the `start`. // // func.code[patch_block..] // ``` pub fn (mut func Function) patch(loc PatchPos, begin PatchPos) { if loc == begin { return } assert loc < begin v := func.code[begin..].clone() func.code.trim(begin) func.code.insert(int(loc), v) for mut patch in func.patches { if patch.pos >= begin { patch.pos -= begin - loc } else if patch.pos >= loc { patch.pos += func.code.len - begin } } func.patches.sort(a.pos < b.pos) /* lenn := begin diff := loc - begin for mut patch in func.patches { if patch.pos >= begin { patch.pos += diff } if patch.pos <= lenn { continue } delta := patch.pos - lenn patch.pos = loc + delta } func.patches.sort(a.pos < b.pos)*/ } // new_local creates a function local and returns it's index. // See `local_get`, `local_set`, `local_tee`. pub fn (mut func Function) new_local(v ValType) LocalIndex { ret := func.locals.len func.locals << FunctionLocal{ typ: v } return ret } // new_local_named creates a function local with a name and returns it's index. // The `name` is used in debug information, where applicable. // See `local_get`, `local_set`, `local_tee`. pub fn (mut func Function) new_local_named(v ValType, name string) LocalIndex { ret := func.locals.len func.locals << FunctionLocal{ typ: v name: name } return ret } // i32_const places a constant i32 value on the stack. // WebAssembly instruction: `i32.const`. pub fn (mut func Function) i32_const(v i32) { func.code << 0x41 // i32.const func.code << leb128.encode_i32(v) } // i64_const places a constant i64 value on the stack. // WebAssembly instruction: `i64.const`. pub fn (mut func Function) i64_const(v i64) { func.code << 0x42 // i64.const func.code << leb128.encode_i64(v) } // f32_const places a constant f32 value on the stack. // WebAssembly instruction: `f32.const`. pub fn (mut func Function) f32_const(v f32) { func.code << 0x43 // f32.const push_f32(mut func.code, v) } // f64_const places a constant f64 value on the stack. // WebAssembly instruction: `f64.const`. pub fn (mut func Function) f64_const(v f64) { func.code << 0x44 // f64.const push_f64(mut func.code, v) } // local_get places the value of the local at the index `local` on the stack. // WebAssembly instruction: `local.get`. pub fn (mut func Function) local_get(local LocalIndex) { func.code << 0x20 // local.get func.u32(u32(local)) } // local_get sets the local at the index `local` to the value on the stack. // WebAssembly instruction: `local.set`. pub fn (mut func Function) local_set(local LocalIndex) { func.code << 0x21 // local.set func.u32(u32(local)) } // local_tee sets the local at the index `local` to the value on the stack, then places it's value on the stack. // WebAssembly instruction: `local.tee`. pub fn (mut func Function) local_tee(local LocalIndex) { func.code << 0x22 // local.tee func.u32(u32(local)) } type GlobalIndices = GlobalImportIndex | GlobalIndex // global_get places the value of the global at the index `global` on the stack. // WebAssembly instruction: `global.get`. pub fn (mut func Function) global_get(global GlobalIndices) { func.code << 0x23 // global.get match global { GlobalIndex { func.patches << FunctionGlobalPatch{ idx: global pos: func.code.len } } GlobalImportIndex { func.u32(u32(global)) } } } // global_set sets the global at the index `global` to the value on the stack. // WebAssembly instruction: `global.set`. pub fn (mut func Function) global_set(global GlobalIndices) { func.code << 0x24 // global.set match global { GlobalIndex { func.patches << FunctionGlobalPatch{ idx: global pos: func.code.len } } GlobalImportIndex { func.u32(u32(global)) } } } // drop drops the value on the stack // WebAssembly instruction: `drop`. pub fn (mut func Function) drop() { func.code << 0x1A } // c_select selects one of its first two operands based on an i32 condition. // WebAssembly instruction: `select`. pub fn (mut func Function) c_select() { func.code << 0x1B } // add adds two values on the stack with type `typ`. // WebAssembly instructions: `i32|i64|f32|f64.add`. pub fn (mut func Function) add(typ NumType) { match typ { .i32_t { func.code << 0x6A } // i32.add .i64_t { func.code << 0x7C } // i64.add .f32_t { func.code << 0x92 } // f32.add .f64_t { func.code << 0xA0 } // f64.add } } // sub subtracts two values on the stack with type `typ`. // WebAssembly instructions: `i32|i64|f32|f64.sub`. pub fn (mut func Function) sub(typ NumType) { match typ { .i32_t { func.code << 0x6B } // i32.sub .i64_t { func.code << 0x7D } // i64.sub .f32_t { func.code << 0x93 } // f32.sub .f64_t { func.code << 0xA1 } // f64.sub } } // mul multiplies two values on the stack with type `typ`. // WebAssembly instructions: `i32|i64|f32|f64.mul`. pub fn (mut func Function) mul(typ NumType) { match typ { .i32_t { func.code << 0x6C } // i32.mul .i64_t { func.code << 0x7E } // i64.mul .f32_t { func.code << 0x94 } // f32.mul .f64_t { func.code << 0xA2 } // f64.mul } } // div divides two values on the stack with type `typ`, with respect to `is_signed`. // WebAssembly instructions: `i32|i64.div_s`, `i32|i64.div_u`, `f32|f64.div`. pub fn (mut func Function) div(typ NumType, is_signed bool) { match typ { .i32_t { if is_signed { func.code << 0x6D // i32.div_s } else { func.code << 0x6E // i32.div_u } } .i64_t { if is_signed { func.code << 0x7F // i64.div_s } else { func.code << 0x80 // i64.div_u } } .f32_t { func.code << 0x95 // f32.div } .f64_t { func.code << 0xA3 // f64.div } } } // rem takes the remainder of two values on the stack with type `typ`, with respect to `is_signed`. // WebAssembly instructions: `i32|i64.rem_s`, `i32|i64.rem_u`. pub fn (mut func Function) rem(typ NumType, is_signed bool) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { if is_signed { func.code << 0x6F // i32.rem_s } else { func.code << 0x70 // i32.rem_u } } .i64_t { if is_signed { func.code << 0x81 // i64.rem_s } else { func.code << 0x82 // i64.rem_u } } else {} } } // and takes the bitwise and of two values on the stack with type `typ`. // WebAssembly instruction: `i32|i64.and`. pub fn (mut func Function) b_and(typ NumType) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0x71 } // i32.and .i64_t { func.code << 0x83 } // i64.and else {} } } // or takes the bitwise or of two values on the stack with type `typ`. // WebAssembly instruction: `i32|i64.or`. pub fn (mut func Function) b_or(typ NumType) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0x72 } // i32.or .i64_t { func.code << 0x84 } // i64.or else {} } } // xor takes the bitwise xor of two values on the stack with type `typ`. // WebAssembly instruction: `i32|i64.xor`. pub fn (mut func Function) b_xor(typ NumType) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0x73 } // i32.xor .i64_t { func.code << 0x85 } // i64.xor else {} } } // shl performs bitwise left-shift on a value with type `typ`. // WebAssembly instruction: `i32|i64.shl`. pub fn (mut func Function) b_shl(typ NumType) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0x74 } // i32.shl .i64_t { func.code << 0x86 } // i64.shl else {} } } // shr performs bitwise right-shift on a value with type `typ`, with respect to `is_signed`. // WebAssembly instructions: `i32|i64.shr_s`, `i32|i64.shr_u`. pub fn (mut func Function) b_shr(typ NumType, is_signed bool) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { if is_signed { func.code << 0x75 // i32.shr_s } else { func.code << 0x76 // i32.shr_u } } .i64_t { if is_signed { func.code << 0x87 // i64.shr_s } else { func.code << 0x88 // i64.shr_u } } else {} } } // clz counts the amount of leading zeros in the numbers binary representation. // WebAssembly instruction: `i32|i64.clz`. pub fn (mut func Function) clz(typ NumType) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0x67 } // i32.clz .i64_t { func.code << 0x79 } // i64.clz else {} } } // ctz counts the amount of trailing zeros in the numbers binary representation. // WebAssembly instruction: `i32|i64.ctz`. pub fn (mut func Function) ctz(typ NumType) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0x68 } // i32.ctz .i64_t { func.code << 0x7A } // i64.ctz else {} } } // popcnt counts the amount of 1s in a numbers binary representation. // WebAssembly instruction: `i32|i64.popcnt`. pub fn (mut func Function) popcnt(typ NumType) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0x69 } // i32.popcnt .i64_t { func.code << 0x7B } // i64.popcnt else {} } } // rotl performs bitwise left-rotate on a value with type `typ`. // WebAssembly instruction: `i32|i64.rotl`. pub fn (mut func Function) rotl(typ NumType) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0x77 } // i32.rotl .i64_t { func.code << 0x89 } // i64.rotl else {} } } // rotr performs bitwise right-rotate on a value with type `typ`. // WebAssembly instruction: `i32|i64.rotr`. pub fn (mut func Function) rotr(typ NumType) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0x78 } // i32.rotr .i64_t { func.code << 0xA8 } // i64.rotr else {} } } // abs gets the absolute value of a float with type `typ`. // WebAssembly instruction: `f32|f64.abs`. pub fn (mut func Function) abs(typ NumType) { assert typ in [.f32_t, .f64_t] match typ { .f32_t { func.code << 0x8B } // f32.abs .f64_t { func.code << 0x99 } // f64.abs else {} } } // neg negates the value of a float with type `typ`. // WebAssembly instruction: `f32|f64.neg`. pub fn (mut func Function) neg(typ NumType) { assert typ in [.f32_t, .f64_t] match typ { .f32_t { func.code << 0x8C } // f32.neg .f64_t { func.code << 0x9A } // f64.neg else {} } } // ceil rounds up the value of a float with type `typ` to the nearest integer. // WebAssembly instruction: `f32|f64.ceil`. pub fn (mut func Function) ceil(typ NumType) { assert typ in [.f32_t, .f64_t] match typ { .f32_t { func.code << 0x8D } // f32.ceil .f64_t { func.code << 0x9B } // f64.ceil else {} } } // floor rounds down the value of a float with type `typ` to the nearest integer. // WebAssembly instruction: `f32|f64.floor`. pub fn (mut func Function) floor(typ NumType) { assert typ in [.f32_t, .f64_t] match typ { .f32_t { func.code << 0x8E } // f32.floor .f64_t { func.code << 0x9C } // f64.floor else {} } } // trunc discards the fractional part of the value of a float with type `typ`. // WebAssembly instruction: `f32|f64.trunc`. pub fn (mut func Function) trunc(typ NumType) { assert typ in [.f32_t, .f64_t] match typ { .f32_t { func.code << 0x8F } // f32.trunc .f64_t { func.code << 0x9D } // f64.trunc else {} } } // nearest rounds the value of a float with type `typ` to the nearest integer. // WebAssembly instruction: `f32|f64.nearest`. pub fn (mut func Function) nearest(typ NumType) { assert typ in [.f32_t, .f64_t] match typ { .f32_t { func.code << 0x90 } // f32.nearest .f64_t { func.code << 0x9E } // f64.nearest else {} } } // sqrt performs the square root on the value of a float with type `typ`. // WebAssembly instruction: `f32|f64.sqrt`. pub fn (mut func Function) sqrt(typ NumType) { assert typ in [.f32_t, .f64_t] match typ { .f32_t { func.code << 0x91 } // f32.sqrt .f64_t { func.code << 0x9F } // f64.sqrt else {} } } // min gets the smaller value of two floats with type `typ`. // WebAssembly instruction: `f32|f64.min`. pub fn (mut func Function) min(typ NumType) { assert typ in [.f32_t, .f64_t] match typ { .f32_t { func.code << 0x96 } // f32.min .f64_t { func.code << 0xA4 } // f64.min else {} } } // max gets the higher value of two floats with type `typ`. // WebAssembly instruction: `f32|f64.max`. pub fn (mut func Function) max(typ NumType) { assert typ in [.f32_t, .f64_t] match typ { .f32_t { func.code << 0x97 } // f32.max .f64_t { func.code << 0xA5 } // f64.max else {} } } // copysign copies the sign bit of one float value to another float, both with type `typ`. // WebAssembly instruction: `f32|f64.copysign`. pub fn (mut func Function) copysign(typ NumType) { assert typ in [.f32_t, .f64_t] match typ { .f32_t { func.code << 0x98 } // f32.copysign .f64_t { func.code << 0xA6 } // f64.copysign else {} } } // eqz checks if the value with type `typ` is equal to zero, places an i32 boolean value on the stack. // WebAssembly instruction: `i32|i64.eqz`. pub fn (mut func Function) eqz(typ NumType) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0x45 } // i32.eqz .i64_t { func.code << 0x50 } // i64.eqz else {} } } // eq checks if two values with type `typ` are equal, places an i32 boolean value on the stack. // WebAssembly instruction: `i32|i64|f32|f64.eq`. pub fn (mut func Function) eq(typ NumType) { match typ { .i32_t { func.code << 0x46 } // i32.eq .i64_t { func.code << 0x51 } // i64.eq .f32_t { func.code << 0x5B } // f32.eq .f64_t { func.code << 0x61 } // f64.eq } } // ne checks if two values with type `typ` are not equal, places an i32 boolean value on the stack. // WebAssembly instruction: `i32|i64|f32|f64.ne`. pub fn (mut func Function) ne(typ NumType) { match typ { .i32_t { func.code << 0x47 } // i32.ne .i64_t { func.code << 0x52 } // i64.ne .f32_t { func.code << 0x5C } // f32.ne .f64_t { func.code << 0x62 } // f64.ne } } // lt checks if two values with type `typ` with respect to `is_signed` are less than another, places an i32 boolean value on the stack. // WebAssembly instructions: `i32|i64.lt_s`, `i32|i64.lt_u`, `f32|f64.lt`. pub fn (mut func Function) lt(typ NumType, is_signed bool) { match typ { .i32_t { if is_signed { func.code << 0x48 // i32.lt_s } else { func.code << 0x49 // i32.lt_u } } .i64_t { if is_signed { func.code << 0x53 // i64.lt_s } else { func.code << 0x54 // i64.lt_u } } .f32_t { func.code << 0x5D // f32.lt } .f64_t { func.code << 0x63 // f64.lt } } } // gt checks if two values with type `typ` with respect to `is_signed` are greater than another, places an i32 boolean value on the stack. // WebAssembly instructions: `i32|i64.gt_s`, `i32|i64.gt_u`, `f32|f64.gt`. pub fn (mut func Function) gt(typ NumType, is_signed bool) { match typ { .i32_t { if is_signed { func.code << 0x4A // i32.gt_s } else { func.code << 0x4B // i32.gt_u } } .i64_t { if is_signed { func.code << 0x55 // i64.gt_s } else { func.code << 0x56 // i64.gt_u } } .f32_t { func.code << 0x5E } // f32.gt .f64_t { func.code << 0x64 } // f64.gt } } // le checks if two values with type `typ` with respect to `is_signed` are less than or equal to another, places an i32 boolean value on the stack. // WebAssembly instructions: `i32|i64.le_s`, `i32|i64.le_u`, `f32|f64.le`. pub fn (mut func Function) le(typ NumType, is_signed bool) { match typ { .i32_t { if is_signed { func.code << 0x4C // i32.le_s } else { func.code << 0x4D // i32.le_u } } .i64_t { if is_signed { func.code << 0x57 // i64.le_s } else { func.code << 0x58 // i64.le_u } } .f32_t { func.code << 0x5F } // f32.le .f64_t { func.code << 0x65 } // f64.le } } // ge checks if two values with type `typ` with respect to `is_signed` are greater than or equal to another, places an i32 boolean value on the stack. // WebAssembly instructions: `i32|i64.ge_s`, `i32|i64.ge_u`, `f32|f64.ge`. pub fn (mut func Function) ge(typ NumType, is_signed bool) { match typ { .i32_t { if is_signed { func.code << 0x4E // i32.ge_s } else { func.code << 0x4F // i32.ge_u } } .i64_t { if is_signed { func.code << 0x59 // i64.ge_s } else { func.code << 0x5A // i64.ge_u } } .f32_t { func.code << 0x60 } // f32.ge .f64_t { func.code << 0x66 } // f64.ge } } // sign_extend8 extends the value of a 8-bit integer of type `typ`. // WebAssembly instruction: `i32|i64.extend8_s`. pub fn (mut func Function) sign_extend8(typ ValType) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0xC0 } // i32.extend8_s .i64_t { func.code << 0xC2 } // i64.extend8_s else {} } } // sign_extend16 extends the value of a 16-bit integer of type `typ`. // WebAssembly instruction: `i32|i64.extend16_s`. pub fn (mut func Function) sign_extend16(typ ValType) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0xC1 } // i32.extend16_s .i64_t { func.code << 0xC3 } // i64.extend16_s else {} } } // sign_extend32_i64 extends the value of a 32-bit integer of type i64. // WebAssembly instruction: `i64.extend64_s`. pub fn (mut func Function) sign_extend32_i64() { func.code << 0xC4 // i64.extend64_s } // cast casts a value of type `a` with respect to `is_signed`, to type `b`. // A generic utility function over a large amount of WebAssembly instructions. // Note: This function uses non-trapping float conversion operators, see `cast_trapping` to use opcodes that cause a runtime exception. // WebAssembly instructions: // - `i32|i64.trunc_sat_f32_s`, `i32|i64.trunc_sat_f64_s`. // - `f32.demote_f64`, `f64.promote_f32`. // - `i32.wrap_i64`, `i64.extend_i32_s`, `i64.extend_i32_u`. // - `f32|f64.convert_i32_s`, `f32|f64.convert_i32_u`. // - `f32|f64.convert_i64_s`, `f32|f64.convert_i64_u`. pub fn (mut func Function) cast(a NumType, is_signed bool, b NumType) { if a in [.f32_t, .f64_t] { if a == .f32_t { match b { .i32_t { func.code << 0xFC // sat opcode func.code << 0x00 // i32.trunc_sat_f32_s } .i64_t { func.code << 0xFC // sat opcode func.code << 0x04 // i64.trunc_sat_f32_s } .f64_t { func.code << 0xBB // f64.promote_f32 } else {} } } else { match b { .i32_t { func.code << 0xFC // sat opcode func.code << 0x02 // i32.trunc_sat_f64_s } .i64_t { func.code << 0xFC // sat opcode func.code << 0x06 // i64.trunc_sat_f64_s } .f32_t { func.code << 0xB6 // f32.demote_f64 } else {} } } return } if a == .i64_t && b == .i32_t { func.code << 0xA7 // i32.wrap_i64 return } if is_signed { match a { .i32_t { match b { .i64_t { func.code << 0xAC // i64.extend_i32_s } .f32_t { func.code << 0xB2 // f32.convert_i32_s } .f64_t { func.code << 0xB7 // f64.convert_i32_s } else {} } } .i64_t { match b { .f32_t { func.code << 0xB4 // f32.convert_i64_s } .f64_t { func.code << 0xB9 // f64.convert_i64_s } else {} } } else {} } } else { match a { .i32_t { match b { .i64_t { func.code << 0xAD // i64.extend_i32_u } .f32_t { func.code << 0xB3 // f32.convert_i32_u } .f64_t { func.code << 0xB8 // f64.convert_i32_u } else {} } } .i64_t { match b { .f32_t { func.code << 0xB5 // f32.convert_i64_u } .f64_t { func.code << 0xBA // f64.convert_i64_u } else {} } } else {} } } } // cast_trapping casts a value of type `a` with respect to `is_signed`, to type `b`. // A generic utility function over a large amount of WebAssembly instructions. // Note: This function uses trapping float conversion operators, see `cast` to use opcodes that do NOT cause a runtime exception. // WebAssembly instructions: // - `i32|i64.trunc_f32_s`, `i32|i64.trunc_f64_s`. // - See function `cast` for the rest. pub fn (mut func Function) cast_trapping(a NumType, is_signed bool, b NumType) { if a in [.f32_t, .f64_t] { if a == .f32_t { match b { .i32_t { func.code << 0xA8 // i32.trunc_f32_s return } .i64_t { func.code << 0xAE // i64.trunc_f32_s return } else {} } } else { match b { .i32_t { func.code << 0xAA // i32.trunc_f64_s return } .i64_t { func.code << 0xB0 // i64.trunc_f64_s return } else {} } } } func.cast(a, is_signed, b) } // reinterpret returns a value which has the same bit-pattern as its operand value, in its result type. // WebAssembly instruction: `f32.reinterpret_i32`, `i32.reinterpret_f32`, `f64.reinterpret_i64`, `i64.reinterpret_f64`. pub fn (mut func Function) reinterpret(a NumType) { match a { .f32_t { func.code << 0xBC } // i32.reinterpret_f32 .i32_t { func.code << 0xBE } // f32.reinterpret_i32 .f64_t { func.code << 0xBD } // i64.reinterpret_f64 .i64_t { func.code << 0xBF } // f64.reinterpret_i64 } } // unreachable denotes a point in code that should not be reachable, it is an unconditional trap. // WebAssembly instruction: `unreachable`. pub fn (mut func Function) unreachable() { func.code << 0x00 } // nop instruction, does nothing. // WebAssembly instruction: `nop`. pub fn (mut func Function) nop() { func.code << 0x01 } pub type LabelIndex = int // c_block creates a label that can later be branched out of with `c_br` and `c_br_if`. // Blocks are strongly typed, you must supply a list of types for `parameters` and `results`. // All blocks must be ended, see the `c_end` function. pub fn (mut func Function) c_block(parameters []ValType, results []ValType) LabelIndex { func.label++ func.code << 0x02 func.blocktype(parameters: parameters, results: results) return func.label } // c_loop creates a label that can later be branched to with `c_br` and `c_br_if`. // Loops are strongly typed, you must supply a list of types for `parameters` and `results`. // All loops must be ended, see the `c_end` function. pub fn (mut func Function) c_loop(parameters []ValType, results []ValType) LabelIndex { func.label++ func.code << 0x03 func.blocktype(parameters: parameters, results: results) return func.label } // c_if opens an if expression. It executes a statement if the last item on the stack is true. // It creates a label that can later be branched out of with `c_br` and `c_br_if`. // If expressions are strongly typed, you must supply a list of types for `parameters` and `results`. // Call `c_else` to open the else case of an if expression, or close it by calling `c_end_if`. // All if expressions must be ended, see the `c_end` function. pub fn (mut func Function) c_if(parameters []ValType, results []ValType) LabelIndex { func.label++ func.code << 0x04 func.blocktype(parameters: parameters, results: results) return func.label } // c_else opens the else case of an if expression, it must be closed by calling `c_end`. pub fn (mut func Function) c_else(label LabelIndex) { assert func.label == label, 'c_else: called with an invalid label ${label}' func.code << 0x05 } // c_return returns from a function. // WebAssembly instruction: `return`. pub fn (mut func Function) c_return() { func.code << 0x0F // return } // c_end ends the block, loop or if expression with the label passed in at `label`. pub fn (mut func Function) c_end(label LabelIndex) { assert func.label == label, 'c_end: called with an invalid label ${label}' func.label-- assert func.label >= 0, 'c_end: negative label index, unbalanced calls' func.code << 0x0B // END expression opcode } // c_br branches to a loop or block with the label passed in at `label`. // WebAssembly instruction: `br`. pub fn (mut func Function) c_br(label LabelIndex) { v := func.label - label assert v >= 0, 'c_br: malformed label index' func.code << 0x0C // br func.u32(u32(v)) } // c_br_if branches to a loop or block with the label passed in at `label`, based on an i32 condition. // WebAssembly instruction: `br_if`. pub fn (mut func Function) c_br_if(label LabelIndex) { v := func.label - label assert v >= 0, 'c_br_if: malformed label index' func.code << 0x0D // br_if func.u32(u32(v)) } // call calls a locally defined function. // If this function does not exist when calling `compile` on the module, it will panic. // WebAssembly instruction: `call`. pub fn (mut func Function) call(name string) { func.code << 0x10 // call func.patches << CallPatch(FunctionCallPatch{ name: name pos: func.code.len }) } // call calls an imported function. // If the imported function does not exist when calling `compile` on the module, it will panic. // WebAssembly instruction: `call`. pub fn (mut func Function) call_import(mod string, name string) { func.code << 0x10 // call func.patches << CallPatch(ImportCallPatch{ mod: mod name: name pos: func.code.len }) } // load loads a value with type `typ` from memory. // WebAssembly instruction: `i32|i64|f32|f64.load`. pub fn (mut func Function) load(typ NumType, align int, offset int) { match typ { .i32_t { func.code << 0x28 } // i32.load .i64_t { func.code << 0x29 } // i64.load .f32_t { func.code << 0x2A } // f32.load .f64_t { func.code << 0x2B } // f64.load } func.u32(u32(align)) func.u32(u32(offset)) } // load8 loads a 8-bit value with type `typ` with respect to `is_signed` from memory. // WebAssembly instructions: `i32|i64.load8_s`, `i32|i64.load8_u`. pub fn (mut func Function) load8(typ NumType, is_signed bool, align int, offset int) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { if is_signed { func.code << 0x2C // i32.load8_s } else { func.code << 0x2D // i32.load8_u } } .i64_t { if is_signed { func.code << 0x30 // i64.load8_s } else { func.code << 0x31 // i64.load8_u } } else {} } func.u32(u32(align)) func.u32(u32(offset)) } // load16 loads a 16-bit value with type `typ` with respect to `is_signed` from memory. // WebAssembly instructions: `i32|i64.load16_s`, `i32|i64.load16_u`. pub fn (mut func Function) load16(typ NumType, is_signed bool, align int, offset int) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { if is_signed { func.code << 0x2E // i32.load16_s } else { func.code << 0x2F // i32.load16_u } } .i64_t { if is_signed { func.code << 0x32 // i64.load16_s } else { func.code << 0x33 // i64.load16_u } } else {} } func.u32(u32(align)) func.u32(u32(offset)) } // load32_i64 loads a 32-bit value of type i64 with respect to `is_signed` from memory. // WebAssembly instructions: `i64.load32_s`, `i64.load32_u`. pub fn (mut func Function) load32_i64(is_signed bool, align int, offset int) { if is_signed { func.code << 0x34 // i64.load32_s } else { func.code << 0x35 // i64.load32_u } func.u32(u32(align)) func.u32(u32(offset)) } // store stores a value with type `typ` into memory. // WebAssembly instruction: `i32|i64|f32|f64.store`. pub fn (mut func Function) store(typ NumType, align int, offset int) { match typ { .i32_t { func.code << 0x36 } // i32.store .i64_t { func.code << 0x37 } // i64.store .f32_t { func.code << 0x38 } // f32.store .f64_t { func.code << 0x39 } // f64.store } func.u32(u32(align)) func.u32(u32(offset)) } // store8 stores a 8-bit value with type `typ` into memory. // WebAssembly instruction: `i32|i64.store8`. pub fn (mut func Function) store8(typ NumType, align int, offset int) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0x3A } // i32.store8 .i64_t { func.code << 0x3C } // i64.store8 else {} } func.u32(u32(align)) func.u32(u32(offset)) } // store16 stores a 16-bit value with type `typ` into memory. // WebAssembly instruction: `i32|i64.store16`. pub fn (mut func Function) store16(typ NumType, align int, offset int) { assert typ in [.i32_t, .i64_t] match typ { .i32_t { func.code << 0x3B } // i32.store16 .i64_t { func.code << 0x3D } // i64.store16 else {} } func.u32(u32(align)) func.u32(u32(offset)) } // store16 stores a 32-bit value of type i64 into memory. // WebAssembly instruction: `i64.store32`. pub fn (mut func Function) store32_i64(align int, offset int) { func.code << 0x3E // i64.store32 func.u32(u32(align)) func.u32(u32(offset)) } // memory_size gets the size of the memory instance. // WebAssembly instruction: `memory.size`. pub fn (mut func Function) memory_size() { func.code << 0x3F func.code << 0x00 } // memory_grow increases the size of the memory instance. // WebAssembly instruction: `memory.grow`. pub fn (mut func Function) memory_grow() { func.code << 0x40 func.code << 0x00 } // memory_init copies from a passive memory segment to the memory instance. // WebAssembly instruction: `memory.init`. pub fn (mut func Function) memory_init(idx DataSegmentIndex) { func.code << 0xFC func.code << 0x08 func.u32(u32(idx)) func.code << 0x00 } // data_drop prevents further use of a passive memory segment. // WebAssembly instruction: `data.drop`. pub fn (mut func Function) data_drop(idx DataSegmentIndex) { func.code << 0xFC func.code << 0x09 func.u32(u32(idx)) } // memory_copy copies one region of memory to another. // Similar to `memcpy` and `memmove`, memory regions can overlap. // WebAssembly instruction: `memory.copy`. pub fn (mut func Function) memory_copy() { func.code << [u8(0xFC), 0x0A, 0x00, 0x00] } // memory_fill sets a memory region to a byte value. // Similar to `memset`. // WebAssembly instruction: `memory.copy`. pub fn (mut func Function) memory_fill() { func.code << [u8(0xFC), 0x0B, 0x00] } // ref_null places a null reference on the stack. // WebAssembly instruction: `ref.null`. pub fn (mut func Function) ref_null(rt RefType) { func.code << 0xD0 // ref.null func.code << u8(rt) } // ref_is_null checks if the reference value on the stack is null, places an i32 boolean value on the stack. // WebAssembly instruction: `ref_is_null`. pub fn (mut func Function) ref_is_null(rt RefType) { func.code << 0xD1 // ref_is_null } // ref_func places a reference to a function with `name` on the stack. // If this function does not exist when calling `compile` on the module, it will panic. // WebAssembly instruction: `ref.func`. pub fn (mut func Function) ref_func(name string) { func.code << 0xD2 // ref.func func.patches << CallPatch(FunctionCallPatch{ name: name pos: func.code.len }) } // ref_func_import places a reference to an imported function with `name` on the stack. // If the imported function does not exist when calling `compile` on the module, it will panic. // WebAssembly instruction: `ref.func`. pub fn (mut func Function) ref_func_import(mod string, name string) { func.code << 0xD2 // ref.func func.patches << CallPatch(ImportCallPatch{ mod: mod name: name pos: func.code.len }) }