diff --git a/examples/wasm_codegen/add.v b/examples/wasm_codegen/add.v new file mode 100644 index 0000000000..09e2ae429a --- /dev/null +++ b/examples/wasm_codegen/add.v @@ -0,0 +1,13 @@ +import wasm + +fn main() { + mut m := wasm.Module{} + mut func := m.new_function('add', [.i32_t, .i32_t], [.i32_t]) + { + func.local_get(0) + func.local_get(1) + func.add(.i32_t) + } + m.commit(func, true) // `export: true` + print(m.compile().bytestr()) +} diff --git a/examples/wasm_codegen/control_flow.v b/examples/wasm_codegen/control_flow.v new file mode 100644 index 0000000000..2971cf7ff2 --- /dev/null +++ b/examples/wasm_codegen/control_flow.v @@ -0,0 +1,43 @@ +import wasm + +fn main() { + mut m := wasm.Module{} + mut bif := m.new_function('block_if', [.i32_t], [.i32_t]) + { + loc := bif.new_local(.i32_t) + + // loc = i32.const 10 + bif.i32_const(10) + bif.local_set(loc) + + blk := bif.c_block([], []) + { + // if argument[0], break + bif.local_get(0) + bif.c_br_if(blk) + // loc = i32.const 11 + bif.i32_const(11) + bif.local_set(loc) + } + bif.c_end(blk) + + // return loc + bif.local_get(loc) + } + m.commit(bif, true) + mut ifexpr := m.new_function('if_expr', [.i32_t], [.i64_t]) + { + ifexpr.local_get(0) + ifexpr.c_if([], [.i64_t]) + { + ifexpr.i64_const(5000) + } + ifexpr.c_else() + { + ifexpr.i64_const(-5000) + } + ifexpr.c_end_if() + } + m.commit(ifexpr, true) + print(m.compile().bytestr()) +} diff --git a/examples/wasm_codegen/factorial.v b/examples/wasm_codegen/factorial.v new file mode 100644 index 0000000000..c51329b6c5 --- /dev/null +++ b/examples/wasm_codegen/factorial.v @@ -0,0 +1,33 @@ +import wasm + +fn main() { + mut m := wasm.Module{} + mut fac := m.new_function('fac', [.i64_t], [.i64_t]) + { + fac.local_get(0) + fac.eqz(.i64_t) + fac.c_if([], [.i64_t]) + { + fac.i64_const(1) + } + fac.c_else() + { + { + fac.local_get(0) + } + { + fac.local_get(0) + fac.i64_const(1) + fac.sub(.i64_t) + fac.call('fac') + } + fac.mul(.i64_t) + } + fac.c_end_if() + } + m.commit(fac, true) + print(m.compile().bytestr()) + + // v run factorial.v > a.wasm + // wasmer a.wasm -i fac 5 +} diff --git a/examples/wasm_codegen/functions.v b/examples/wasm_codegen/functions.v new file mode 100644 index 0000000000..478040fe86 --- /dev/null +++ b/examples/wasm_codegen/functions.v @@ -0,0 +1,29 @@ +import wasm + +fn main() { + mut m := wasm.Module{} + mut pyth := m.new_function('pythagoras', [.f32_t, .f32_t], [ + .f32_t, + ]) + { + pyth.local_get(0) + pyth.local_get(0) + pyth.mul(.f32_t) + pyth.local_get(1) + pyth.local_get(1) + pyth.mul(.f32_t) + pyth.add(.f32_t) + pyth.sqrt(.f32_t) + pyth.cast(.f32_t, true, .f64_t) + } + m.commit(pyth, true) + mut test := m.new_function('test', [.f32_t], [.f64_t]) + { + test.local_get(0) + test.f32_const(10.0) + test.call('pythagoras') + test.cast(.f32_t, true, .f64_t) + } + m.commit(test, true) + print(m.compile().bytestr()) +} diff --git a/examples/wasm_codegen/hello_wasi.v b/examples/wasm_codegen/hello_wasi.v new file mode 100644 index 0000000000..dd5316f406 --- /dev/null +++ b/examples/wasm_codegen/hello_wasi.v @@ -0,0 +1,28 @@ +import wasm + +fn main() { + mut m := wasm.Module{} + m.new_function_import('wasi_unstable', 'proc_exit', [.i32_t], []) + m.new_function_import('wasi_unstable', 'fd_write', [.i32_t, .i32_t, .i32_t, .i32_t], + [.i32_t]) + m.assign_memory('memory', true, 1, none) + + m.new_data_segment(0, [u8(8), 0, 0, 0]) // pointer to string + m.new_data_segment(4, [u8(13), 0, 0, 0]) // length of string + m.new_data_segment(8, 'Hello, WASI!\n'.bytes()) + + mut func := m.new_function('_start', [], []) + { + func.i32_const(1) // stdout + func.i32_const(0) // *iovs + func.i32_const(1) // 1 iov + func.i32_const(-1) // *retptrs + func.call_import('wasi_unstable', 'fd_write') + func.drop() + func.i32_const(0) + func.call_import('wasi_unstable', 'proc_exit') + } + m.commit(func, true) + + print(m.compile().bytestr()) +} diff --git a/examples/wasm_codegen/memory.v b/examples/wasm_codegen/memory.v new file mode 100644 index 0000000000..48b000e766 --- /dev/null +++ b/examples/wasm_codegen/memory.v @@ -0,0 +1,12 @@ +import wasm + +fn main() { + mut m := wasm.Module{} + mut mtest := m.new_function('mload', [.i32_t], [.i32_t]) + { + mtest.local_get(0) + mtest.load(.i32_t, 2, 0) + } + m.commit(mtest, false) + print(m.compile().bytestr()) +} diff --git a/vlib/wasm/README.md b/vlib/wasm/README.md new file mode 100644 index 0000000000..a81391a031 --- /dev/null +++ b/vlib/wasm/README.md @@ -0,0 +1,37 @@ +## Description: + +The `wasm` module is a pure V implementation of the WebAssembly bytecode module format, +available in the form of a builder. + +It allows users to generate WebAssembly modules in memory. + +With the V wasm module, users can create functions, opcodes, and utilize the entire wasm +specification without the need for a large dependency like binaryen. All of this +functionality is available within V itself, making the module a valuable resource for +V developers seeking to build high-performance web applications. + +The module is designed to generate a `[]u8`, which can be written to a `.wasm` file +or executed in memory. + +```v +import wasm +import os + +fn main() { + mut m := wasm.Module{} + mut func := m.new_function('add', [.i32_t, .i32_t], [.i32_t]) + { + func.local_get(0) // | local.get 0 + func.local_get(1) // | local.get 1 + func.add(.i32_t) // | i32.add + } + m.commit(func, true) // `export: true` + + mod := m.compile() // []u8 + + os.write_file_array('add.wasm', mod)! +} +``` + +This module does not perform verification of the WebAssembly output. +Use a tool like `wasm-validate` to validate, and `wasm-dis` to show a decompiled form. diff --git a/vlib/wasm/encoding.v b/vlib/wasm/encoding.v new file mode 100644 index 0000000000..f89c7a82b8 --- /dev/null +++ b/vlib/wasm/encoding.v @@ -0,0 +1,244 @@ +module wasm + +import encoding.leb128 + +fn (mut mod Module) u32(v u32) { + mod.buf << leb128.encode_u32(v) +} + +fn (mut mod Module) patch_start() int { + return mod.buf.len +} + +fn (mut mod Module) patch_len(pos int) { + len := mod.buf.len - pos + data := leb128.encode_u32(u32(len)) + mod.buf.insert(pos, data) +} + +fn (mut mod Module) patch_u32(pos int, val u32) { + data := leb128.encode_u32(val) + mod.buf.insert(pos, data) +} + +fn (mut mod Module) result_type(results []ValType) { + mod.u32(u32(results.len)) + for r in results { + mod.buf << u8(r) + } +} + +fn (mut mod Module) function_type(ft FuncType) { + mod.buf << 0x60 // function type indicator + mod.result_type(ft.parameters) + mod.result_type(ft.results) +} + +fn (mut mod Module) patch_calls(ft Function) { + mut ptr := 0 + + for patch in ft.call_patches { + mut idx := -1 + + match patch { + FunctionCallPatch { + ftt := mod.functions[patch.name] or { + panic('called function ${patch.name} does not exist') + } + idx = ftt.idx + mod.fn_imports.len + } + ImportCallPatch { + for fnidx, c in mod.fn_imports { + if c.mod == patch.mod && c.name == patch.name { + idx = fnidx + break + } + } + if idx == -1 { + panic('called imported function ${patch.mod}.${patch.name} does not exist') + } + } + } + + mod.buf << ft.code[ptr..patch.pos] + mod.buf << 0x10 // call + mod.u32(u32(idx)) + ptr = patch.pos + } + + mod.buf << ft.code[ptr..] +} + +// compile serialises the WebAssembly module into a byte array. +// The returned byte array can be written out into a `.wasm`, or executed in memory. +pub fn (mut mod Module) compile() []u8 { + mod.buf = []u8{cap: 128} + + // WASM_BINARY_MAGIC, WASM_BINARY_VERSION + mod.buf << [u8(0x00), 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00] + + // https://webassembly.github.io/spec/core/binary/modules.html#type-section + // + if mod.functypes.len > 0 { + // Types + mod.buf << u8(Section.type_section) + tpatch := mod.patch_start() + { + mod.u32(u32(mod.functypes.len)) + for ft in mod.functypes { + mod.function_type(ft) + } + } + mod.patch_len(tpatch) + } + // https://webassembly.github.io/spec/core/binary/modules.html#import-section + // + if mod.fn_imports.len > 0 { + // Types + mod.buf << u8(Section.import_section) + tpatch := mod.patch_start() + { + mod.u32(u32(mod.fn_imports.len)) + for ft in mod.fn_imports { + mod.u32(u32(ft.mod.len)) + mod.buf << ft.mod.bytes() + mod.u32(u32(ft.name.len)) + mod.buf << ft.name.bytes() + mod.buf << 0x00 // function + mod.u32(u32(ft.tidx)) + } + } + mod.patch_len(tpatch) + } + // https://webassembly.github.io/spec/core/binary/modules.html#binary-funcsec + // + if mod.functions.len > 0 { + mod.buf << u8(Section.function_section) + tpatch := mod.patch_start() + { + mod.u32(u32(mod.functions.len)) + for _, ft in mod.functions { + mod.u32(u32(ft.tidx)) + } + } + mod.patch_len(tpatch) + } + // https://webassembly.github.io/spec/core/binary/modules.html#binary-memsec + // + if memory := mod.memory { + mod.buf << u8(Section.memory_section) + tpatch := mod.patch_start() + { + mod.u32(1) + if max := memory.max { + mod.buf << 0x01 // limit, max present + mod.u32(memory.min) + mod.u32(max) + } else { + mod.buf << 0x00 // limit, max not present + mod.u32(memory.min) + } + } + mod.patch_len(tpatch) + } + // https://webassembly.github.io/spec/core/binary/modules.html#export-section + // + if mod.functions.len > 0 { + mod.buf << u8(Section.export_section) + tpatch := mod.patch_start() + { + lpatch := mod.patch_start() + mut lsz := 0 + for _, ft in mod.functions { + if !ft.export { + continue + } + lsz++ + mod.u32(u32(ft.name.len)) + mod.buf << ft.name.bytes() + mod.buf << 0x00 // function + mod.u32(u32(ft.idx + mod.fn_imports.len)) + } + if memory := mod.memory { + if memory.export { + lsz++ + mod.u32(u32(memory.name.len)) + mod.buf << memory.name.bytes() + mod.buf << 0x02 // function + mod.u32(0) + } + } + mod.patch_u32(lpatch, u32(lsz)) + } + mod.patch_len(tpatch) + } + // https://webassembly.github.io/spec/core/binary/modules.html#binary-startsec + // + if start := mod.start { + ftt := mod.functions[start] or { panic('start function ${start} does not exist') } + mod.buf << u8(Section.start_section) + tpatch := mod.patch_start() + { + mod.u32(u32(ftt.idx + mod.fn_imports.len)) + } + mod.patch_len(tpatch) + } + // https://webassembly.github.io/spec/core/binary/modules.html#data-count-section + // + if mod.segments.len > 0 { + mod.buf << u8(Section.data_count_section) + tpatch := mod.patch_start() + { + mod.u32(u32(mod.segments.len)) + } + mod.patch_len(tpatch) + } + // https://webassembly.github.io/spec/core/binary/modules.html#binary-codesec + // + if mod.functions.len > 0 { + mod.buf << u8(Section.code_section) + tpatch := mod.patch_start() + { + mod.u32(u32(mod.functions.len)) + for _, ft in mod.functions { + fpatch := mod.patch_start() + { + mod.u32(u32(ft.locals.len)) + for lt in ft.locals { + mod.u32(1) + mod.buf << u8(lt) + } + mod.patch_calls(ft) + mod.buf << 0x0B // END expression opcode + } + mod.patch_len(fpatch) + } + } + mod.patch_len(tpatch) + } + // https://webassembly.github.io/spec/core/binary/modules.html#data-section + // + if mod.segments.len > 0 { + mod.buf << u8(Section.data_section) + tpatch := mod.patch_start() + { + mod.u32(u32(mod.segments.len)) + for _, seg in mod.segments { + if idx := seg.idx { + mod.buf << 0x00 // active + // constant expr + mod.buf << 0x41 // i32.const + mod.buf << leb128.encode_i32(idx) + mod.buf << 0x0B // END expression opcode + } else { + mod.buf << 0x01 // passive + } + mod.u32(u32(seg.data.len)) + mod.buf << seg.data + } + } + mod.patch_len(tpatch) + } + + return mod.buf +} diff --git a/vlib/wasm/functions.v b/vlib/wasm/functions.v new file mode 100644 index 0000000000..814a3e0896 --- /dev/null +++ b/vlib/wasm/functions.v @@ -0,0 +1,28 @@ +module wasm + +struct ImportCallPatch { + mod string + name string + pos int +} + +struct FunctionCallPatch { + name string + pos int +} + +type CallPatch = FunctionCallPatch | ImportCallPatch + +struct Function { + tidx int + idx int +mut: + call_patches []CallPatch + label int + export bool + mod &Module + code []u8 + locals []ValType +pub: + name string +} diff --git a/vlib/wasm/instructions.v b/vlib/wasm/instructions.v new file mode 100644 index 0000000000..40b9ae94b0 --- /dev/null +++ b/vlib/wasm/instructions.v @@ -0,0 +1,1039 @@ +module wasm + +import encoding.leb128 +import math.bits + +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) +} + +// 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) int { + ldiff := func.mod.functypes[func.tidx].parameters.len + ret := func.locals.len + ldiff + + func.locals << v + 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 + rv := bits.f32_bits(v) + func.code << u8(rv >> u32(0)) + func.code << u8(rv >> u32(8)) + func.code << u8(rv >> u32(16)) + func.code << u8(rv >> u32(24)) +} + +// 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 + rv := bits.f64_bits(v) + func.code << u8(rv >> u32(0)) + func.code << u8(rv >> u32(8)) + func.code << u8(rv >> u32(16)) + func.code << u8(rv >> u32(24)) + func.code << u8(rv >> u32(32)) + func.code << u8(rv >> u32(40)) + func.code << u8(rv >> u32(48)) + func.code << u8(rv >> u32(56)) +} + +// 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 int) { + 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 int) { + 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 int) { + func.code << 0x22 // local.tee + func.u32(u32(local)) +} + +// 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) +} + +// 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 +} + +// 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) int { + 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 of 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) int { + 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. +// 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 by calling with `c_end_if. +pub fn (mut func Function) c_if(parameters []ValType, results []ValType) { + func.code << 0x04 + func.blocktype(parameters: parameters, results: results) +} + +// c_else starts the else case of an if expression, it must be closed by calling `c_end_if`. +pub fn (mut func Function) c_else() { + 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 or loop with the label passed in at `label`. +pub fn (mut func Function) c_end(label int) { + 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 int) { + 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 int) { + v := func.label - label + assert v >= 0, 'c_br_if: malformed label index' + func.code << 0x0D // br_if + func.u32(u32(v)) +} + +// c_end_if closes the current if expression. +pub fn (mut func Function) c_end_if() { + func.code << 0x0B // END expression opcode +} + +// call calls a locally defined function. +// If this function does not exist when calling `compile` on the module, it panic. +// WebAssembly instruction: `call`. +pub fn (mut func Function) call(name string) { + func.call_patches << 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 panic. +// WebAssembly instruction: `call`. +pub fn (mut func Function) call_import(mod string, name string) { + func.call_patches << 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 int) { + 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 int) { + 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] +} diff --git a/vlib/wasm/module.v b/vlib/wasm/module.v new file mode 100644 index 0000000000..49fcbb41fb --- /dev/null +++ b/vlib/wasm/module.v @@ -0,0 +1,155 @@ +module wasm + +enum Section as u8 { + custom_section + type_section + import_section + function_section + table_section + memory_section + global_section + export_section + start_section + element_section + code_section + data_section + data_count_section +} + +pub enum NumType as u8 { + i32_t = 0x7f + i64_t = 0x7e + f32_t = 0x7d + f64_t = 0x7c +} + +pub enum ValType as u8 { + i32_t = 0x7f + i64_t = 0x7e + f32_t = 0x7d + f64_t = 0x7c + v128_t = 0x7b + funcref_t = 0x70 + externref_t = 0x6f +} + +// Module contains the WebAssembly module. +// Use the `compile` method to compile the module into a pure byte array. +[heap] +pub struct Module { +mut: + buf []u8 + functypes []FuncType + functions map[string]Function + memory ?Memory + start ?string + fn_imports []FunctionImport + segments []DataSegment +} + +struct FunctionImport { + mod string + name string + tidx int +} + +struct Memory { + name string + export bool + min u32 + max ?u32 +} + +struct DataSegment { + idx ?int + data []u8 +} + +[params] +pub struct FuncType { +pub: + parameters []ValType + results []ValType +} + +fn (mut mod Module) new_functype(ft FuncType) int { + // interns existing types + mut idx := mod.functypes.index(ft) + + if idx == -1 { + idx = mod.functypes.len + mod.functypes << ft + } + + return idx +} + +// new_function creates a function struct. +pub fn (mut mod Module) new_function(name string, parameters []ValType, results []ValType) Function { + assert name !in mod.functions.keys() + + idx := mod.functions.len + tidx := mod.new_functype(FuncType{parameters, results}) + + return Function{ + name: name + tidx: tidx + idx: idx + mod: mod + } +} + +// assign_memory assigns memory to the current module. +pub fn (mut mod Module) assign_memory(name string, export bool, min u32, max ?u32) { + mod.memory = Memory{ + name: name + export: export + min: min + max: max + } +} + +// assign_start assigns the start function to the current module. +pub fn (mut mod Module) assign_start(name string) { + mod.start = name +} + +// new_function_import imports a new function into the current module. +pub fn (mut mod Module) new_function_import(modn string, name string, parameters []ValType, results []ValType) { + assert !mod.fn_imports.any(it.mod == modn && it.name == name) + + tidx := mod.new_functype(FuncType{parameters, results}) + + mod.fn_imports << FunctionImport{ + mod: modn + name: name + tidx: tidx + } +} + +// commit commits a function to the module, use `export` to export the function. +pub fn (mut mod Module) commit(func Function, export bool) { + assert func.name !in mod.functions.keys() + + mod.functions[func.name] = Function{ + ...func + export: export + } +} + +// new_data_segment inserts a new data segment at the memory index `pos`. +pub fn (mut mod Module) new_data_segment(pos int, data []u8) int { + len := mod.segments.len + mod.segments << DataSegment{ + idx: pos + data: data + } + return len +} + +// new_passive_data_segment inserts a new passive data segment. +pub fn (mut mod Module) new_passive_data_segment(data []u8) { + mod.segments << DataSegment{ + data: data + } +} diff --git a/vlib/wasm/tests/arith_test.v b/vlib/wasm/tests/arith_test.v new file mode 100644 index 0000000000..8f601cf4f0 --- /dev/null +++ b/vlib/wasm/tests/arith_test.v @@ -0,0 +1,56 @@ +import wasm +import os + +const exe = os.find_abs_path_of_executable('wasm-validate') or { exit(0) } + +fn validate(mod []u8) ! { + mut proc := os.new_process(exe) + proc.set_args(['-']) + proc.set_redirect_stdio() + proc.run() + { + os.fd_write(proc.stdio_fd[0], mod.bytestr()) + os.fd_close(proc.stdio_fd[0]) + } + proc.wait() + if proc.status != .exited { + return error('wasm-validate exited abormally') + } + if proc.code != 0 { + return error('wasm-validate exited with a non zero exit code') + } + proc.close() +} + +fn test_add() { + mut m := wasm.Module{} + mut a1 := m.new_function('add', [.i32_t, .i32_t], [.i32_t]) + { + a1.local_get(0) + a1.local_get(1) + a1.add(.i32_t) + } + m.commit(a1, true) + mut a2 := m.new_function('sub', [.i32_t, .i32_t], [.i32_t]) + { + a2.local_get(0) + a2.local_get(1) + a2.sub(.i32_t) + } + m.commit(a2, true) + mut a3 := m.new_function('mul', [.i32_t, .i32_t], [.i32_t]) + { + a3.local_get(0) + a3.local_get(1) + a3.mul(.i32_t) + } + m.commit(a3, true) + mut a4 := m.new_function('div', [.i32_t, .i32_t], [.i32_t]) + { + a4.local_get(0) + a4.local_get(1) + a4.div(.i32_t, true) + } + m.commit(a4, true) + validate(m.compile()) or { panic(err) } +} diff --git a/vlib/wasm/tests/block_test.v b/vlib/wasm/tests/block_test.v new file mode 100644 index 0000000000..743e974163 --- /dev/null +++ b/vlib/wasm/tests/block_test.v @@ -0,0 +1,143 @@ +import wasm +import os + +const exe = os.find_abs_path_of_executable('wasm-validate') or { exit(0) } + +fn validate(mod []u8) ! { + mut proc := os.new_process(exe) + proc.set_args(['-']) + proc.set_redirect_stdio() + proc.run() + { + os.fd_write(proc.stdio_fd[0], mod.bytestr()) + os.fd_close(proc.stdio_fd[0]) + } + proc.wait() + if proc.status != .exited { + return error('wasm-validate exited abormally') + } + if proc.code != 0 { + return error('wasm-validate exited with a non zero exit code') + } + proc.close() +} + +fn test_add() { + mut m := wasm.Module{} + mut a1 := m.new_function('param', [], [.i32_t]) + { + a1.i32_const(1) + blk := a1.c_block([.i32_t], [.i32_t]) + { + a1.i32_const(2) + a1.add(.i32_t) + } + a1.c_end(blk) + } + m.commit(a1, true) + mut a2 := m.new_function('params', [], [.i32_t]) + { + a2.i32_const(1) + a2.i32_const(2) + blk := a2.c_block([.i32_t, .i32_t], [.i32_t]) + { + a2.add(.i32_t) + } + a2.c_end(blk) + } + m.commit(a2, true) + mut a3 := m.new_function('params-id', [], [.i32_t]) + { + a3.i32_const(1) + a3.i32_const(2) + blk := a3.c_block([.i32_t, .i32_t], [.i32_t, .i32_t]) + { + } + a3.c_end(blk) + a3.add(.i32_t) + } + m.commit(a3, true) + + mut b1 := m.new_function('param-break', [], [.i32_t]) + { + b1.i32_const(1) + blk := b1.c_block([.i32_t], [.i32_t]) + { + b1.i32_const(2) + b1.add(.i32_t) + b1.c_br(blk) + } + b1.c_end(blk) + } + m.commit(b1, true) + mut b2 := m.new_function('params-break', [], [.i32_t]) + { + b2.i32_const(1) + b2.i32_const(2) + blk := b2.c_block([.i32_t, .i32_t], [.i32_t]) + { + b2.add(.i32_t) + b2.c_br(blk) + } + b2.c_end(blk) + } + m.commit(b2, true) + mut b3 := m.new_function('params-id-break', [], [.i32_t]) + { + b3.i32_const(1) + b3.i32_const(2) + blk := b3.c_block([.i32_t, .i32_t], [.i32_t, .i32_t]) + { + b3.c_br(blk) + } + b3.c_end(blk) + b3.add(.i32_t) + } + m.commit(b3, true) + + mut dummy := m.new_function('dummy', [], []) + { + } + m.commit(dummy, false) + + mut c1 := m.new_function('singular', [], [.i32_t]) + { + blk1 := c1.c_block([], []) + { + c1.nop() + } + c1.c_end(blk1) + blk2 := c1.c_block([], [.i32_t]) + { + c1.i32_const(7) + } + c1.c_end(blk2) + } + m.commit(c1, true) + mut c2 := m.new_function('nested', [], [.i32_t]) + { + blk := c2.c_block([], [.i32_t]) + { + blk1 := c2.c_block([], []) + { + c2.call('dummy') + blk2 := c2.c_block([], []) + { + } + c2.c_end(blk2) + c2.nop() + } + c2.c_end(blk1) + blk2 := c2.c_block([], [.i32_t]) + { + c2.call('dummy') + c2.i32_const(9) + } + c2.c_end(blk2) + } + c2.c_end(blk) + } + m.commit(c2, true) + + validate(m.compile()) or { panic(err) } +} diff --git a/vlib/wasm/tests/call_test.v b/vlib/wasm/tests/call_test.v new file mode 100644 index 0000000000..7ee31dcd0e --- /dev/null +++ b/vlib/wasm/tests/call_test.v @@ -0,0 +1,106 @@ +import wasm +import os + +const exe = os.find_abs_path_of_executable('wasm-validate') or { exit(0) } + +fn validate(mod []u8) ! { + mut proc := os.new_process(exe) + proc.set_args(['-']) + proc.set_redirect_stdio() + proc.run() + { + os.fd_write(proc.stdio_fd[0], mod.bytestr()) + os.fd_close(proc.stdio_fd[0]) + } + proc.wait() + if proc.status != .exited { + return error('wasm-validate exited abormally') + } + if proc.code != 0 { + return error('wasm-validate exited with a non zero exit code') + } + proc.close() +} + +fn test_add() { + mut m := wasm.Module{} + mut a1 := m.new_function('const-i32', [], [.i32_t]) + { + a1.i32_const(0x132) + } + m.commit(a1, false) + mut a2 := m.new_function('const-i64', [], [.i64_t]) + { + a2.i64_const(0x164) + } + m.commit(a2, false) + mut a3 := m.new_function('const-f32', [], [.f32_t]) + { + a3.f32_const(0.2) + } + m.commit(a3, false) + mut a4 := m.new_function('const-f64', [], [.f64_t]) + { + a4.f64_const(0.4) + } + m.commit(a4, false) + mut a5 := m.new_function('const-i32-i64', [], [.i32_t, .i64_t]) + { + a5.i32_const(0x132) + a5.i64_const(0x164) + } + m.commit(a5, false) + + mut b1 := m.new_function('type-i32', [], [.i32_t]) + { + b1.call('const-i32') + } + m.commit(b1, true) + mut b2 := m.new_function('type-i64', [], [.i64_t]) + { + b2.call('const-i64') + } + m.commit(b2, true) + mut b3 := m.new_function('type-f32', [], [.f32_t]) + { + b3.call('const-f32') + } + m.commit(b3, true) + mut b4 := m.new_function('type-f64', [], [.f64_t]) + { + b4.call('const-f64') + } + m.commit(b4, true) + mut b5 := m.new_function('type-i32-i64', [], [.i32_t, .i64_t]) + { + b5.call('const-i32-i64') + } + m.commit(b5, true) + + mut fac := m.new_function('fac', [.i64_t], [.i64_t]) + { + fac.local_get(0) + fac.eqz(.i64_t) + fac.c_if([], [.i64_t]) + { + fac.i64_const(1) + } + fac.c_else() + { + { + fac.local_get(0) + } + { + fac.local_get(0) + fac.i64_const(1) + fac.sub(.i64_t) + fac.call('fac') + } + fac.mul(.i64_t) + } + fac.c_end_if() + } + m.commit(fac, true) + + validate(m.compile()) or { panic(err) } +}