From 3a06b55388559598c05e46ddb776d7443180b56b Mon Sep 17 00:00:00 2001 From: l-m Date: Mon, 8 May 2023 06:31:36 +0000 Subject: [PATCH] wasm: add basic debuginfo through `name` section (#18130) --- examples/wasm_codegen/control_flow.v | 6 +- examples/wasm_codegen/factorial.v | 6 +- examples/wasm_codegen/hello_wasi.v | 7 +- vlib/wasm/README.md | 2 + vlib/wasm/constant.v | 31 ++++ vlib/wasm/encoding.v | 243 +++++++++++++++++++++------ vlib/wasm/functions.v | 30 +++- vlib/wasm/instructions.v | 129 +++++++++++--- vlib/wasm/module.v | 96 +++++++++-- vlib/wasm/tests/call_test.v | 6 +- vlib/wasm/tests/debug_test.v | 59 +++++++ vlib/wasm/tests/patch_test.v | 30 ++++ vlib/wasm/tests/var_test.v | 5 +- 13 files changed, 541 insertions(+), 109 deletions(-) create mode 100644 vlib/wasm/tests/debug_test.v create mode 100644 vlib/wasm/tests/patch_test.v diff --git a/examples/wasm_codegen/control_flow.v b/examples/wasm_codegen/control_flow.v index 2971cf7ff2..db615d850a 100644 --- a/examples/wasm_codegen/control_flow.v +++ b/examples/wasm_codegen/control_flow.v @@ -28,15 +28,15 @@ fn main() { mut ifexpr := m.new_function('if_expr', [.i32_t], [.i64_t]) { ifexpr.local_get(0) - ifexpr.c_if([], [.i64_t]) + if_blk := ifexpr.c_if([], [.i64_t]) { ifexpr.i64_const(5000) } - ifexpr.c_else() + ifexpr.c_else(if_blk) { ifexpr.i64_const(-5000) } - ifexpr.c_end_if() + ifexpr.c_end(if_blk) } m.commit(ifexpr, true) print(m.compile().bytestr()) diff --git a/examples/wasm_codegen/factorial.v b/examples/wasm_codegen/factorial.v index c51329b6c5..41c3c372e2 100644 --- a/examples/wasm_codegen/factorial.v +++ b/examples/wasm_codegen/factorial.v @@ -6,11 +6,11 @@ fn main() { { fac.local_get(0) fac.eqz(.i64_t) - fac.c_if([], [.i64_t]) + bif := fac.c_if([], [.i64_t]) { fac.i64_const(1) } - fac.c_else() + fac.c_else(bif) { { fac.local_get(0) @@ -23,7 +23,7 @@ fn main() { } fac.mul(.i64_t) } - fac.c_end_if() + fac.c_end(bif) } m.commit(fac, true) print(m.compile().bytestr()) diff --git a/examples/wasm_codegen/hello_wasi.v b/examples/wasm_codegen/hello_wasi.v index dd5316f406..85df2932de 100644 --- a/examples/wasm_codegen/hello_wasi.v +++ b/examples/wasm_codegen/hello_wasi.v @@ -2,14 +2,15 @@ import wasm fn main() { mut m := wasm.Module{} + m.enable_debug('vlang') 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()) + m.new_data_segment('CIOVec.str', 0, [u8(8), 0, 0, 0]) // pointer to string + m.new_data_segment('CIOVec.len', 4, [u8(13), 0, 0, 0]) // length of string + m.new_data_segment(none, 8, 'Hello, WASI!\n'.bytes()) mut func := m.new_function('_start', [], []) { diff --git a/vlib/wasm/README.md b/vlib/wasm/README.md index a81391a031..c028968743 100644 --- a/vlib/wasm/README.md +++ b/vlib/wasm/README.md @@ -13,6 +13,8 @@ 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. +Examples are present in `examples/wasm_codegen`. + ```v import wasm import os diff --git a/vlib/wasm/constant.v b/vlib/wasm/constant.v index 029eee248c..11a07e5990 100644 --- a/vlib/wasm/constant.v +++ b/vlib/wasm/constant.v @@ -1,3 +1,6 @@ +// 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 @@ -21,6 +24,34 @@ pub fn constexpr_value[T](v T) ConstExpression { return expr } +// constexpr_value_zero returns a constant expression that evaluates to zero. +pub fn constexpr_value_zero(v ValType) ConstExpression { + mut expr := ConstExpression{} + + match v { + .i32_t { + expr.i32_const(0) + } + .i64_t { + expr.i64_const(0) + } + .f32_t { + expr.f32_const(0.0) + } + .f64_t { + expr.f64_const(0.0) + } + .funcref_t, .externref_t { + expr.ref_null(RefType(v)) + } + .v128_t { + panic('type `v128_t` not permitted in a constant expression') + } + } + + return expr +} + // constexpr_ref_null returns a constant expression that evaluates to a null reference. pub fn constexpr_ref_null(rt RefType) ConstExpression { mut expr := ConstExpression{} diff --git a/vlib/wasm/encoding.v b/vlib/wasm/encoding.v index 56d7684988..481537b957 100644 --- a/vlib/wasm/encoding.v +++ b/vlib/wasm/encoding.v @@ -1,3 +1,6 @@ +// 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 @@ -89,16 +92,16 @@ fn (mod &Module) get_function_idx(patch CallPatch) int { fn (mut mod Module) patch(ft Function) { mut ptr := 0 - for patch in ft.call_patches { - idx := mod.get_function_idx(patch) - - mod.buf << ft.code[ptr..patch.pos] - mod.u32(u32(idx)) - ptr = patch.pos - } - - for patch in ft.global_patches { - idx := mod.global_imports.len + patch.idx + for patch in ft.patches { + mut idx := 0 + match patch { + CallPatch { + idx = mod.get_function_idx(patch) + } + FunctionGlobalPatch { + idx = mod.global_imports.len + patch.idx + } + } mod.buf << ft.code[ptr..patch.pos] mod.u32(u32(idx)) ptr = patch.pos @@ -107,6 +110,29 @@ fn (mut mod Module) patch(ft Function) { mod.buf << ft.code[ptr..] } +// name +pub fn (mut mod Module) name(name string) { + mod.u32(u32(name.len)) + mod.buf << name.bytes() +} + +// start_subsection +pub fn (mut mod Module) start_subsection(sec Subsection) int { + mod.buf << u8(sec) + return mod.patch_start() +} + +// start_section +pub fn (mut mod Module) start_section(sec Section) int { + mod.buf << u8(sec) + return mod.patch_start() +} + +// end_section +pub fn (mut mod Module) end_section(tpatch int) { + mod.patch_len(tpatch) +} + // 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 { @@ -118,22 +144,19 @@ pub fn (mut mod Module) compile() []u8 { // 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() + tpatch := mod.start_section(.type_section) { mod.u32(u32(mod.functypes.len)) for ft in mod.functypes { mod.function_type(ft) } } - mod.patch_len(tpatch) + mod.end_section(tpatch) } // https://webassembly.github.io/spec/core/binary/modules.html#import-section // if mod.fn_imports.len > 0 || mod.global_imports.len > 0 { - mod.buf << u8(Section.import_section) - tpatch := mod.patch_start() + tpatch := mod.start_section(.import_section) { mod.u32(u32(mod.fn_imports.len + mod.global_imports.len)) for ft in mod.fn_imports { @@ -153,26 +176,24 @@ pub fn (mut mod Module) compile() []u8 { mod.global_type(gt.typ, gt.is_mut) } } - mod.patch_len(tpatch) + mod.end_section(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() + tpatch := mod.start_section(.function_section) { mod.u32(u32(mod.functions.len)) for _, ft in mod.functions { mod.u32(u32(ft.tidx)) } } - mod.patch_len(tpatch) + mod.end_section(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() + tpatch := mod.start_section(.memory_section) { mod.u32(1) if max := memory.max { @@ -184,13 +205,12 @@ pub fn (mut mod Module) compile() []u8 { mod.u32(memory.min) } } - mod.patch_len(tpatch) + mod.end_section(tpatch) } // https://webassembly.github.io/spec/core/binary/modules.html#global-section // if mod.globals.len > 0 { - mod.buf << u8(Section.global_section) - tpatch := mod.patch_start() + tpatch := mod.start_section(.global_section) { mod.u32(u32(mod.globals.len)) for gt in mod.globals { @@ -210,13 +230,12 @@ pub fn (mut mod Module) compile() []u8 { mod.buf << 0x0B // END expression opcode } } - mod.patch_len(tpatch) + mod.end_section(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() + { + tpatch := mod.start_section(.export_section) { lpatch := mod.patch_start() mut lsz := 0 @@ -225,59 +244,64 @@ pub fn (mut mod Module) compile() []u8 { continue } lsz++ - mod.u32(u32(ft.name.len)) - mod.buf << ft.name.bytes() + mod.name(ft.name) 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.name(memory.name) mod.buf << 0x02 // function mod.u32(0) } } + for idx, gbl in mod.globals { + if !gbl.export { + continue + } + lsz++ + mod.name(gbl.name) + mod.buf << 0x03 // global + mod.u32(u32(idx + mod.global_imports.len)) + } mod.patch_u32(lpatch, u32(lsz)) } - mod.patch_len(tpatch) + mod.end_section(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() + tpatch := mod.start_section(.start_section) { mod.u32(u32(ftt.idx + mod.fn_imports.len)) } - mod.patch_len(tpatch) + mod.end_section(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() + tpatch := mod.start_section(.data_count_section) { mod.u32(u32(mod.segments.len)) } - mod.patch_len(tpatch) + mod.end_section(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() + tpatch := mod.start_section(.code_section) { mod.u32(u32(mod.functions.len)) for _, ft in mod.functions { fpatch := mod.patch_start() + rloc := ft.locals[mod.functypes[ft.tidx].parameters.len..] { - mod.u32(u32(ft.locals.len)) - for lt in ft.locals { + mod.u32(u32(rloc.len)) + for lt in rloc { mod.u32(1) - mod.buf << u8(lt) + mod.buf << u8(lt.typ) } mod.patch(ft) mod.buf << 0x0B // END expression opcode @@ -285,13 +309,12 @@ pub fn (mut mod Module) compile() []u8 { mod.patch_len(fpatch) } } - mod.patch_len(tpatch) + mod.end_section(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() + tpatch := mod.start_section(.data_section) { mod.u32(u32(mod.segments.len)) for _, seg in mod.segments { @@ -308,7 +331,129 @@ pub fn (mut mod Module) compile() []u8 { mod.buf << seg.data } } - mod.patch_len(tpatch) + mod.end_section(tpatch) + } + // https://webassembly.github.io/spec/core/appendix/custom.html#name-section + // + if mod.debug { + tpatch := mod.start_section(.custom_section) + mod.name('name') + if mod_name := mod.mod_name { + mpatch := mod.start_subsection(.name_module) + { + mod.name(mod_name) + } + mod.end_section(mpatch) + } + { + mpatch := mod.start_subsection(.name_function) + { + mod.u32(u32(mod.functions.len + mod.fn_imports.len)) + mut idx := 0 + for f in mod.fn_imports { + mod.u32(u32(idx)) + mod.name('${f.mod}.${f.name}') + idx++ + } + for n, _ in mod.functions { + mod.u32(u32(idx)) + mod.name(n) + idx++ + } + } + mod.end_section(mpatch) + } + { + mpatch := mod.start_subsection(.name_local) + { + fpatch := mod.patch_start() + mut fcount := 0 + mut idx := mod.fn_imports.len // after imports + for _, ft in mod.functions { + // only add entry if it contains a local with an assigned name + if ft.locals.any(it.name != none) { + mod.u32(u32(idx)) // function idx + + mut lcount := 0 + lcpatch := mod.patch_start() + for lidx, loc in ft.locals { + if name := loc.name { + mod.u32(u32(lidx)) + mod.name(name) + lcount++ + } + } + mod.patch_u32(lcpatch, u32(lcount)) + fcount++ + } + idx++ + } + mod.patch_u32(fpatch, u32(fcount)) + } + mod.end_section(mpatch) + } + { + mpatch := mod.start_subsection(.name_type) + { + fpatch := mod.patch_start() + mut fcount := 0 + for idx, ft in mod.functypes { + if name := ft.name { + mod.u32(u32(idx)) + mod.name(name) + fcount++ + } + } + mod.patch_u32(fpatch, u32(fcount)) + } + mod.end_section(mpatch) + } + if memory := mod.memory { + mpatch := mod.start_subsection(.name_memory) + { + mod.u32(u32(1)) // one memory in vec + mod.u32(u32(0)) // 0 idx + mod.name(memory.name) // memory name + } + mod.end_section(mpatch) + } + if mod.globals.len != 0 || mod.global_imports.len != 0 { + mpatch := mod.start_subsection(.name_global) + { + fpatch := mod.patch_start() + mut fcount := 0 + for gbl in mod.global_imports { + mod.u32(u32(fcount)) + mod.name('${gbl.mod}.${gbl.name}') + fcount++ + } + for gbl in mod.globals { + mod.u32(u32(fcount)) + mod.name(gbl.name) + fcount++ + } + mod.patch_u32(fpatch, u32(fcount)) + } + mod.end_section(mpatch) + } + if mod.segments.any(it.name != none) { + mpatch := mod.start_subsection(.name_data) + { + fpatch := mod.patch_start() + mut fcount := 0 + for idx, ds in mod.segments { + if name := ds.name { + mod.u32(u32(idx)) + mod.name(name) + fcount++ + } + } + mod.patch_u32(fpatch, u32(fcount)) + } + mod.end_section(mpatch) + } + + mod.end_section(tpatch) } return mod.buf diff --git a/vlib/wasm/functions.v b/vlib/wasm/functions.v index 93de97850f..0896bd6173 100644 --- a/vlib/wasm/functions.v +++ b/vlib/wasm/functions.v @@ -1,34 +1,46 @@ +// 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 struct ImportCallPatch { mod string name string - pos int +mut: + pos int } struct FunctionCallPatch { name string - pos int +mut: + pos int } type CallPatch = FunctionCallPatch | ImportCallPatch struct FunctionGlobalPatch { idx GlobalIndex +mut: pos int } +type FunctionPatch = CallPatch | FunctionGlobalPatch + +struct FunctionLocal { + typ ValType + name ?string +} + pub struct Function { tidx int idx int mut: - call_patches []CallPatch - global_patches []FunctionGlobalPatch - label int - export bool - mod &Module = unsafe { nil } - code []u8 - locals []ValType + patches []FunctionPatch // sorted + label int + export bool + mod &Module = unsafe { nil } + code []u8 + locals []FunctionLocal pub: name string } diff --git a/vlib/wasm/instructions.v b/vlib/wasm/instructions.v index f0a4ef6ed5..4244bce024 100644 --- a/vlib/wasm/instructions.v +++ b/vlib/wasm/instructions.v @@ -1,3 +1,6 @@ +// 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 @@ -22,13 +25,85 @@ fn (mut func Function) blocktype(typ FuncType) { 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 { - ldiff := func.mod.functypes[func.tidx].parameters.len - ret := func.locals.len + ldiff + ret := func.locals.len + func.locals << FunctionLocal{ + typ: v + } + return ret +} - func.locals << v +// 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 } @@ -89,7 +164,7 @@ pub fn (mut func Function) global_get(global GlobalIndices) { func.code << 0x23 // global.get match global { GlobalIndex { - func.global_patches << FunctionGlobalPatch{ + func.patches << FunctionGlobalPatch{ idx: global pos: func.code.len } @@ -106,7 +181,7 @@ pub fn (mut func Function) global_set(global GlobalIndices) { func.code << 0x24 // global.set match global { GlobalIndex { - func.global_patches << FunctionGlobalPatch{ + func.patches << FunctionGlobalPatch{ idx: global pos: func.code.len } @@ -798,20 +873,22 @@ 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) int { +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 of with `c_br` and `c_br_if`. +// 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) int { +pub fn (mut func Function) c_loop(parameters []ValType, results []ValType) LabelIndex { func.label++ func.code << 0x03 func.blocktype(parameters: parameters, results: results) @@ -819,16 +896,20 @@ pub fn (mut func Function) c_loop(parameters []ValType, results []ValType) int { } // 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 by calling with `c_end_if. -pub fn (mut func Function) c_if(parameters []ValType, results []ValType) { +// 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 starts the else case of an if expression, it must be closed by calling `c_end_if`. -pub fn (mut func Function) c_else() { +// 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 } @@ -839,7 +920,7 @@ pub fn (mut func Function) c_return() { } // c_end ends the block or loop with the label passed in at `label`. -pub fn (mut func Function) c_end(label int) { +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' @@ -848,7 +929,7 @@ pub fn (mut func Function) c_end(label int) { // 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) { +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 @@ -857,7 +938,7 @@ pub fn (mut func Function) c_br(label int) { // 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) { +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 @@ -874,10 +955,10 @@ pub fn (mut func Function) c_end_if() { // WebAssembly instruction: `call`. pub fn (mut func Function) call(name string) { func.code << 0x10 // call - func.call_patches << FunctionCallPatch{ + func.patches << CallPatch(FunctionCallPatch{ name: name pos: func.code.len - } + }) } // call calls an imported function. @@ -885,11 +966,11 @@ pub fn (mut func Function) call(name string) { // WebAssembly instruction: `call`. pub fn (mut func Function) call_import(mod string, name string) { func.code << 0x10 // call - func.call_patches << ImportCallPatch{ + func.patches << CallPatch(ImportCallPatch{ mod: mod name: name pos: func.code.len - } + }) } // load loads a value with type `typ` from memory. @@ -1081,20 +1162,20 @@ pub fn (mut func Function) ref_is_null(rt RefType) { // WebAssembly instruction: `ref.func`. pub fn (mut func Function) ref_func(name string) { func.code << 0xD2 // ref.func - func.call_patches << FunctionCallPatch{ + func.patches << CallPatch(FunctionCallPatch{ name: name pos: func.code.len - } + }) } -// ref_func places a reference to an imported function with `name` on the stack. +// 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.call_patches << ImportCallPatch{ + func.patches << CallPatch(ImportCallPatch{ mod: mod name: name pos: func.code.len - } + }) } diff --git a/vlib/wasm/module.v b/vlib/wasm/module.v index 25705b11f3..243212208d 100644 --- a/vlib/wasm/module.v +++ b/vlib/wasm/module.v @@ -1,3 +1,6 @@ +// 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 enum Section as u8 { @@ -16,6 +19,20 @@ enum Section as u8 { data_count_section } +enum Subsection as u8 { + name_module + name_function + name_local + // see: https://github.com/WebAssembly/extended-name-section + name_label + name_type + name_table + name_memory + name_global + name_elem + name_data +} + pub enum NumType as u8 { i32_t = 0x7f i64_t = 0x7e @@ -52,13 +69,17 @@ mut: fn_imports []FunctionImport global_imports []GlobalImport segments []DataSegment + debug bool + mod_name ?string } struct Global { - typ ValType - is_mut bool - export_name ?string - init ConstExpression + typ ValType + is_mut bool + name string + export bool +mut: + init ConstExpression } struct GlobalImport { @@ -84,6 +105,7 @@ struct Memory { struct DataSegment { idx ?int data []u8 + name ?string } pub type LocalIndex = int @@ -91,11 +113,11 @@ pub type GlobalIndex = int pub type GlobalImportIndex = int pub type DataSegmentIndex = int -[params] pub struct FuncType { pub: parameters []ValType results []ValType + name ?string } fn (mut mod Module) new_functype(ft FuncType) int { @@ -115,16 +137,41 @@ pub fn (mut mod Module) new_function(name string, parameters []ValType, results assert name !in mod.functions.keys() idx := mod.functions.len - tidx := mod.new_functype(FuncType{parameters, results}) + tidx := mod.new_functype(FuncType{parameters, results, none}) return Function{ name: name tidx: tidx idx: idx mod: mod + locals: parameters.map(FunctionLocal{}) // specifying it's ValType doesn't matter } } +// new_debug_function creates a function struct with extra debug information. +// `argument_names` must be the same length as the parameters in the function type `typ`. +pub fn (mut mod Module) new_debug_function(name string, typ FuncType, argument_names []?string) Function { + assert name !in mod.functions.keys() + assert typ.parameters.len == argument_names.len + + idx := mod.functions.len + tidx := mod.new_functype(typ) + + return Function{ + name: name + tidx: tidx + idx: idx + mod: mod + locals: argument_names.map(FunctionLocal{ name: it }) // specifying it's ValType doesn't matter + } +} + +// enable_debug sets whether to emit debug information for not. +pub fn (mut mod Module) enable_debug(mod_name ?string) { + mod.debug = true + mod.mod_name = mod_name +} + // 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{ @@ -144,7 +191,20 @@ pub fn (mut mod Module) assign_start(name string) { 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}) + tidx := mod.new_functype(FuncType{parameters, results, none}) + + mod.fn_imports << FunctionImport{ + mod: modn + name: name + tidx: tidx + } +} + +// new_function_import_debug imports a new function into the current module with extra debug information. +pub fn (mut mod Module) new_function_import_debug(modn string, name string, typ FuncType) { + assert !mod.fn_imports.any(it.mod == modn && it.name == name) + + tidx := mod.new_functype(typ) mod.fn_imports << FunctionImport{ mod: modn @@ -164,38 +224,42 @@ pub fn (mut mod Module) commit(func Function, export bool) { } // 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) DataSegmentIndex { +// `name` is optional, it is used for debug info. +pub fn (mut mod Module) new_data_segment(name ?string, pos int, data []u8) DataSegmentIndex { len := mod.segments.len mod.segments << DataSegment{ idx: pos data: data + name: name } return len } // new_passive_data_segment inserts a new passive data segment. -pub fn (mut mod Module) new_passive_data_segment(data []u8) { +// `name` is optional, it is used for debug info. +pub fn (mut mod Module) new_passive_data_segment(name ?string, data []u8) { mod.segments << DataSegment{ data: data + name: name } } // new_global creates a global and returns it's index. -// If `export_name` is none, the global will not be exported. // See `global_get`, `global_set`. -pub fn (mut mod Module) new_global(export_name ?string, typ ValType, is_mut bool, init ConstExpression) GlobalIndex { +pub fn (mut mod Module) new_global(name string, export bool, typ ValType, is_mut bool, init ConstExpression) GlobalIndex { len := mod.globals.len mod.globals << Global{ typ: typ is_mut: is_mut - export_name: export_name + name: name + export: export init: init } return len } // new_global_import imports a new global into the current module and returns it's index. -// See `global_import_get`, `global_import_set`. +// See `global_get`, `global_set`. pub fn (mut mod Module) new_global_import(modn string, name string, typ ValType, is_mut bool) GlobalImportIndex { assert !mod.fn_imports.any(it.mod == modn && it.name == name) @@ -208,3 +272,9 @@ pub fn (mut mod Module) new_global_import(modn string, name string, typ ValType, } return len } + +// assign_global_init assigns a global with the constant expression `init`. +// See `new_global`. +pub fn (mut mod Module) assign_global_init(global GlobalIndex, init ConstExpression) { + mod.globals[global].init = init +} diff --git a/vlib/wasm/tests/call_test.v b/vlib/wasm/tests/call_test.v index 5d41e646fc..b411f3e251 100644 --- a/vlib/wasm/tests/call_test.v +++ b/vlib/wasm/tests/call_test.v @@ -61,11 +61,11 @@ fn test_call() { { fac.local_get(0) fac.eqz(.i64_t) - fac.c_if([], [.i64_t]) + ifs := fac.c_if([], [.i64_t]) { fac.i64_const(1) } - fac.c_else() + fac.c_else(ifs) { { fac.local_get(0) @@ -78,7 +78,7 @@ fn test_call() { } fac.mul(.i64_t) } - fac.c_end_if() + fac.c_end(ifs) } m.commit(fac, true) diff --git a/vlib/wasm/tests/debug_test.v b/vlib/wasm/tests/debug_test.v new file mode 100644 index 0000000000..8d3e2e60b7 --- /dev/null +++ b/vlib/wasm/tests/debug_test.v @@ -0,0 +1,59 @@ +module main + +import wasm + +fn test_debug() { + mut m := wasm.Module{} + m.enable_debug('hello VLANG!') + + // FuncType with debuginfo/name + func_type := wasm.FuncType{[.i32_t, .i32_t], [.i32_t], 'fn__int'} + + // []?string, named function parameters + param_names := [?string('a'), 'b'] + + mut a1 := m.new_debug_function('add', func_type, param_names) + { + loc := a1.new_local_named(.i32_t, 'intermediary') + a1.local_get(0) + a1.local_get(1) + a1.add(.i32_t) + a1.local_set(loc) + a1.local_get(loc) + } + m.commit(a1, false) + validate(m.compile()) or { panic(err) } +} + +fn test_wasi_debug() { + mut m := wasm.Module{} + m.enable_debug('hello!') + m.new_global('__vsp', true, .i32_t, true, wasm.constexpr_value(10)) + + m.new_global_import('env', 'glble', .i32_t, false) + + 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('CIOVec.str', 0, [u8(8), 0, 0, 0]) // pointer to string + m.new_data_segment('CIOVec.len', 4, [u8(13), 0, 0, 0]) // length of string + m.new_data_segment(none, 8, 'Hello, WASI!\n'.bytes()) + + mut func := m.new_function('_start', [], []) + { + func.new_local_named(.i32_t, 'loc') + 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/vlib/wasm/tests/patch_test.v b/vlib/wasm/tests/patch_test.v new file mode 100644 index 0000000000..3de2e1963c --- /dev/null +++ b/vlib/wasm/tests/patch_test.v @@ -0,0 +1,30 @@ +module main + +import wasm + +fn test_patch() { + mut m := wasm.Module{} + vsp := m.new_global('__vsp', false, .i32_t, true, wasm.constexpr_value(10)) + mut at := m.new_function('add', [], []) + { + pp := at.patch_pos() + at.drop() + at.i32_const(10) + at.i32_const(10) + at.i32_const(10) + at.drop() + at.drop() + at.drop() + pps := at.patch_pos() + { + at.i32_const(10) + at.i32_const(10) + at.global_set(vsp) + at.i32_const(10) + at.global_set(vsp) + } + at.patch(pp, pps) // move to start + } + m.commit(at, true) + validate(m.compile()) or { panic(err) } +} diff --git a/vlib/wasm/tests/var_test.v b/vlib/wasm/tests/var_test.v index 195c43150a..84acaecedc 100644 --- a/vlib/wasm/tests/var_test.v +++ b/vlib/wasm/tests/var_test.v @@ -4,8 +4,9 @@ import wasm fn test_globals() { mut m := wasm.Module{} + m.enable_debug('vlang') - vsp := m.new_global('__vsp', .i32_t, true, wasm.constexpr_value(10)) + vsp := m.new_global('__vsp', false, .i32_t, true, wasm.constexpr_value(10)) mut func := m.new_function('vsp', [], [.i32_t]) { func.global_get(vsp) @@ -16,7 +17,7 @@ fn test_globals() { } m.commit(func, true) - fref := m.new_global('__ref', .funcref_t, true, wasm.constexpr_ref_null(.funcref_t)) + fref := m.new_global('__ref', true, .funcref_t, true, wasm.constexpr_ref_null(.funcref_t)) mut func1 := m.new_function('ref', [], []) { func1.ref_func('vsp')