1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

wasm: add basic debuginfo through name section (#18130)

This commit is contained in:
l-m 2023-05-08 06:31:36 +00:00 committed by GitHub
parent 5bdf94a7bc
commit 3a06b55388
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 541 additions and 109 deletions

View File

@ -28,15 +28,15 @@ fn main() {
mut ifexpr := m.new_function('if_expr', [.i32_t], [.i64_t]) mut ifexpr := m.new_function('if_expr', [.i32_t], [.i64_t])
{ {
ifexpr.local_get(0) ifexpr.local_get(0)
ifexpr.c_if([], [.i64_t]) if_blk := ifexpr.c_if([], [.i64_t])
{ {
ifexpr.i64_const(5000) ifexpr.i64_const(5000)
} }
ifexpr.c_else() ifexpr.c_else(if_blk)
{ {
ifexpr.i64_const(-5000) ifexpr.i64_const(-5000)
} }
ifexpr.c_end_if() ifexpr.c_end(if_blk)
} }
m.commit(ifexpr, true) m.commit(ifexpr, true)
print(m.compile().bytestr()) print(m.compile().bytestr())

View File

@ -6,11 +6,11 @@ fn main() {
{ {
fac.local_get(0) fac.local_get(0)
fac.eqz(.i64_t) fac.eqz(.i64_t)
fac.c_if([], [.i64_t]) bif := fac.c_if([], [.i64_t])
{ {
fac.i64_const(1) fac.i64_const(1)
} }
fac.c_else() fac.c_else(bif)
{ {
{ {
fac.local_get(0) fac.local_get(0)
@ -23,7 +23,7 @@ fn main() {
} }
fac.mul(.i64_t) fac.mul(.i64_t)
} }
fac.c_end_if() fac.c_end(bif)
} }
m.commit(fac, true) m.commit(fac, true)
print(m.compile().bytestr()) print(m.compile().bytestr())

View File

@ -2,14 +2,15 @@ import wasm
fn main() { fn main() {
mut m := wasm.Module{} mut m := wasm.Module{}
m.enable_debug('vlang')
m.new_function_import('wasi_unstable', 'proc_exit', [.i32_t], []) 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], m.new_function_import('wasi_unstable', 'fd_write', [.i32_t, .i32_t, .i32_t, .i32_t],
[.i32_t]) [.i32_t])
m.assign_memory('memory', true, 1, none) m.assign_memory('memory', true, 1, none)
m.new_data_segment(0, [u8(8), 0, 0, 0]) // pointer to string m.new_data_segment('CIOVec.str', 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('CIOVec.len', 4, [u8(13), 0, 0, 0]) // length of string
m.new_data_segment(8, 'Hello, WASI!\n'.bytes()) m.new_data_segment(none, 8, 'Hello, WASI!\n'.bytes())
mut func := m.new_function('_start', [], []) mut func := m.new_function('_start', [], [])
{ {

View File

@ -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 The module is designed to generate a `[]u8`, which can be written to a `.wasm` file
or executed in memory. or executed in memory.
Examples are present in `examples/wasm_codegen`.
```v ```v
import wasm import wasm
import os import os

View File

@ -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 module wasm
import encoding.leb128 import encoding.leb128
@ -21,6 +24,34 @@ pub fn constexpr_value[T](v T) ConstExpression {
return expr 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. // constexpr_ref_null returns a constant expression that evaluates to a null reference.
pub fn constexpr_ref_null(rt RefType) ConstExpression { pub fn constexpr_ref_null(rt RefType) ConstExpression {
mut expr := ConstExpression{} mut expr := ConstExpression{}

View File

@ -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 module wasm
import encoding.leb128 import encoding.leb128
@ -89,16 +92,16 @@ fn (mod &Module) get_function_idx(patch CallPatch) int {
fn (mut mod Module) patch(ft Function) { fn (mut mod Module) patch(ft Function) {
mut ptr := 0 mut ptr := 0
for patch in ft.call_patches { for patch in ft.patches {
idx := mod.get_function_idx(patch) mut idx := 0
match patch {
mod.buf << ft.code[ptr..patch.pos] CallPatch {
mod.u32(u32(idx)) idx = mod.get_function_idx(patch)
ptr = patch.pos }
} FunctionGlobalPatch {
idx = mod.global_imports.len + patch.idx
for patch in ft.global_patches { }
idx := mod.global_imports.len + patch.idx }
mod.buf << ft.code[ptr..patch.pos] mod.buf << ft.code[ptr..patch.pos]
mod.u32(u32(idx)) mod.u32(u32(idx))
ptr = patch.pos ptr = patch.pos
@ -107,6 +110,29 @@ fn (mut mod Module) patch(ft Function) {
mod.buf << ft.code[ptr..] 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. // compile serialises the WebAssembly module into a byte array.
// The returned byte array can be written out into a `.wasm`, or executed in memory. // The returned byte array can be written out into a `.wasm`, or executed in memory.
pub fn (mut mod Module) compile() []u8 { 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 // https://webassembly.github.io/spec/core/binary/modules.html#type-section
// //
if mod.functypes.len > 0 { if mod.functypes.len > 0 {
// Types tpatch := mod.start_section(.type_section)
mod.buf << u8(Section.type_section)
tpatch := mod.patch_start()
{ {
mod.u32(u32(mod.functypes.len)) mod.u32(u32(mod.functypes.len))
for ft in mod.functypes { for ft in mod.functypes {
mod.function_type(ft) mod.function_type(ft)
} }
} }
mod.patch_len(tpatch) mod.end_section(tpatch)
} }
// https://webassembly.github.io/spec/core/binary/modules.html#import-section // https://webassembly.github.io/spec/core/binary/modules.html#import-section
// //
if mod.fn_imports.len > 0 || mod.global_imports.len > 0 { if mod.fn_imports.len > 0 || mod.global_imports.len > 0 {
mod.buf << u8(Section.import_section) tpatch := mod.start_section(.import_section)
tpatch := mod.patch_start()
{ {
mod.u32(u32(mod.fn_imports.len + mod.global_imports.len)) mod.u32(u32(mod.fn_imports.len + mod.global_imports.len))
for ft in mod.fn_imports { 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.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 // https://webassembly.github.io/spec/core/binary/modules.html#binary-funcsec
// //
if mod.functions.len > 0 { if mod.functions.len > 0 {
mod.buf << u8(Section.function_section) tpatch := mod.start_section(.function_section)
tpatch := mod.patch_start()
{ {
mod.u32(u32(mod.functions.len)) mod.u32(u32(mod.functions.len))
for _, ft in mod.functions { for _, ft in mod.functions {
mod.u32(u32(ft.tidx)) mod.u32(u32(ft.tidx))
} }
} }
mod.patch_len(tpatch) mod.end_section(tpatch)
} }
// https://webassembly.github.io/spec/core/binary/modules.html#binary-memsec // https://webassembly.github.io/spec/core/binary/modules.html#binary-memsec
// //
if memory := mod.memory { if memory := mod.memory {
mod.buf << u8(Section.memory_section) tpatch := mod.start_section(.memory_section)
tpatch := mod.patch_start()
{ {
mod.u32(1) mod.u32(1)
if max := memory.max { if max := memory.max {
@ -184,13 +205,12 @@ pub fn (mut mod Module) compile() []u8 {
mod.u32(memory.min) mod.u32(memory.min)
} }
} }
mod.patch_len(tpatch) mod.end_section(tpatch)
} }
// https://webassembly.github.io/spec/core/binary/modules.html#global-section // https://webassembly.github.io/spec/core/binary/modules.html#global-section
// //
if mod.globals.len > 0 { if mod.globals.len > 0 {
mod.buf << u8(Section.global_section) tpatch := mod.start_section(.global_section)
tpatch := mod.patch_start()
{ {
mod.u32(u32(mod.globals.len)) mod.u32(u32(mod.globals.len))
for gt in mod.globals { for gt in mod.globals {
@ -210,13 +230,12 @@ pub fn (mut mod Module) compile() []u8 {
mod.buf << 0x0B // END expression opcode 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 // https://webassembly.github.io/spec/core/binary/modules.html#export-section
// //
if mod.functions.len > 0 { {
mod.buf << u8(Section.export_section) tpatch := mod.start_section(.export_section)
tpatch := mod.patch_start()
{ {
lpatch := mod.patch_start() lpatch := mod.patch_start()
mut lsz := 0 mut lsz := 0
@ -225,59 +244,64 @@ pub fn (mut mod Module) compile() []u8 {
continue continue
} }
lsz++ lsz++
mod.u32(u32(ft.name.len)) mod.name(ft.name)
mod.buf << ft.name.bytes()
mod.buf << 0x00 // function mod.buf << 0x00 // function
mod.u32(u32(ft.idx + mod.fn_imports.len)) mod.u32(u32(ft.idx + mod.fn_imports.len))
} }
if memory := mod.memory { if memory := mod.memory {
if memory.export { if memory.export {
lsz++ lsz++
mod.u32(u32(memory.name.len)) mod.name(memory.name)
mod.buf << memory.name.bytes()
mod.buf << 0x02 // function mod.buf << 0x02 // function
mod.u32(0) 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_u32(lpatch, u32(lsz))
} }
mod.patch_len(tpatch) mod.end_section(tpatch)
} }
// https://webassembly.github.io/spec/core/binary/modules.html#binary-startsec // https://webassembly.github.io/spec/core/binary/modules.html#binary-startsec
// //
if start := mod.start { if start := mod.start {
ftt := mod.functions[start] or { panic('start function ${start} does not exist') } ftt := mod.functions[start] or { panic('start function ${start} does not exist') }
mod.buf << u8(Section.start_section) tpatch := mod.start_section(.start_section)
tpatch := mod.patch_start()
{ {
mod.u32(u32(ftt.idx + mod.fn_imports.len)) 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 // https://webassembly.github.io/spec/core/binary/modules.html#data-count-section
// //
if mod.segments.len > 0 { if mod.segments.len > 0 {
mod.buf << u8(Section.data_count_section) tpatch := mod.start_section(.data_count_section)
tpatch := mod.patch_start()
{ {
mod.u32(u32(mod.segments.len)) 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 // https://webassembly.github.io/spec/core/binary/modules.html#binary-codesec
// //
if mod.functions.len > 0 { if mod.functions.len > 0 {
mod.buf << u8(Section.code_section) tpatch := mod.start_section(.code_section)
tpatch := mod.patch_start()
{ {
mod.u32(u32(mod.functions.len)) mod.u32(u32(mod.functions.len))
for _, ft in mod.functions { for _, ft in mod.functions {
fpatch := mod.patch_start() fpatch := mod.patch_start()
rloc := ft.locals[mod.functypes[ft.tidx].parameters.len..]
{ {
mod.u32(u32(ft.locals.len)) mod.u32(u32(rloc.len))
for lt in ft.locals { for lt in rloc {
mod.u32(1) mod.u32(1)
mod.buf << u8(lt) mod.buf << u8(lt.typ)
} }
mod.patch(ft) mod.patch(ft)
mod.buf << 0x0B // END expression opcode mod.buf << 0x0B // END expression opcode
@ -285,13 +309,12 @@ pub fn (mut mod Module) compile() []u8 {
mod.patch_len(fpatch) mod.patch_len(fpatch)
} }
} }
mod.patch_len(tpatch) mod.end_section(tpatch)
} }
// https://webassembly.github.io/spec/core/binary/modules.html#data-section // https://webassembly.github.io/spec/core/binary/modules.html#data-section
// //
if mod.segments.len > 0 { if mod.segments.len > 0 {
mod.buf << u8(Section.data_section) tpatch := mod.start_section(.data_section)
tpatch := mod.patch_start()
{ {
mod.u32(u32(mod.segments.len)) mod.u32(u32(mod.segments.len))
for _, seg in mod.segments { for _, seg in mod.segments {
@ -308,7 +331,129 @@ pub fn (mut mod Module) compile() []u8 {
mod.buf << seg.data 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 return mod.buf

View File

@ -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 module wasm
struct ImportCallPatch { struct ImportCallPatch {
mod string mod string
name string name string
pos int mut:
pos int
} }
struct FunctionCallPatch { struct FunctionCallPatch {
name string name string
pos int mut:
pos int
} }
type CallPatch = FunctionCallPatch | ImportCallPatch type CallPatch = FunctionCallPatch | ImportCallPatch
struct FunctionGlobalPatch { struct FunctionGlobalPatch {
idx GlobalIndex idx GlobalIndex
mut:
pos int pos int
} }
type FunctionPatch = CallPatch | FunctionGlobalPatch
struct FunctionLocal {
typ ValType
name ?string
}
pub struct Function { pub struct Function {
tidx int tidx int
idx int idx int
mut: mut:
call_patches []CallPatch patches []FunctionPatch // sorted
global_patches []FunctionGlobalPatch label int
label int export bool
export bool mod &Module = unsafe { nil }
mod &Module = unsafe { nil } code []u8
code []u8 locals []FunctionLocal
locals []ValType
pub: pub:
name string name string
} }

View File

@ -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 module wasm
import encoding.leb128 import encoding.leb128
@ -22,13 +25,85 @@ fn (mut func Function) blocktype(typ FuncType) {
func.code << leb128.encode_i32(tidx) 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. // new_local creates a function local and returns it's index.
// See `local_get`, `local_set`, `local_tee`. // See `local_get`, `local_set`, `local_tee`.
pub fn (mut func Function) new_local(v ValType) LocalIndex { pub fn (mut func Function) new_local(v ValType) LocalIndex {
ldiff := func.mod.functypes[func.tidx].parameters.len ret := func.locals.len
ret := func.locals.len + ldiff 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 return ret
} }
@ -89,7 +164,7 @@ pub fn (mut func Function) global_get(global GlobalIndices) {
func.code << 0x23 // global.get func.code << 0x23 // global.get
match global { match global {
GlobalIndex { GlobalIndex {
func.global_patches << FunctionGlobalPatch{ func.patches << FunctionGlobalPatch{
idx: global idx: global
pos: func.code.len pos: func.code.len
} }
@ -106,7 +181,7 @@ pub fn (mut func Function) global_set(global GlobalIndices) {
func.code << 0x24 // global.set func.code << 0x24 // global.set
match global { match global {
GlobalIndex { GlobalIndex {
func.global_patches << FunctionGlobalPatch{ func.patches << FunctionGlobalPatch{
idx: global idx: global
pos: func.code.len pos: func.code.len
} }
@ -798,20 +873,22 @@ pub fn (mut func Function) nop() {
func.code << 0x01 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`. // 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`. // 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. // 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.label++
func.code << 0x02 func.code << 0x02
func.blocktype(parameters: parameters, results: results) func.blocktype(parameters: parameters, results: results)
return func.label 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`. // 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. // 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.label++
func.code << 0x03 func.code << 0x03
func.blocktype(parameters: parameters, results: results) 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. // 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`. // 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`. // 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. // All if expressions must be ended, see the `c_end` function.
pub fn (mut func Function) c_if(parameters []ValType, results []ValType) { pub fn (mut func Function) c_if(parameters []ValType, results []ValType) LabelIndex {
func.label++
func.code << 0x04 func.code << 0x04
func.blocktype(parameters: parameters, results: results) 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`. // 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() { pub fn (mut func Function) c_else(label LabelIndex) {
assert func.label == label, 'c_else: called with an invalid label ${label}'
func.code << 0x05 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`. // 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}' assert func.label == label, 'c_end: called with an invalid label ${label}'
func.label-- func.label--
assert func.label >= 0, 'c_end: negative label index, unbalanced calls' 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`. // c_br branches to a loop or block with the label passed in at `label`.
// WebAssembly instruction: `br`. // 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 v := func.label - label
assert v >= 0, 'c_br: malformed label index' assert v >= 0, 'c_br: malformed label index'
func.code << 0x0C // br 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. // 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`. // 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 v := func.label - label
assert v >= 0, 'c_br_if: malformed label index' assert v >= 0, 'c_br_if: malformed label index'
func.code << 0x0D // br_if func.code << 0x0D // br_if
@ -874,10 +955,10 @@ pub fn (mut func Function) c_end_if() {
// WebAssembly instruction: `call`. // WebAssembly instruction: `call`.
pub fn (mut func Function) call(name string) { pub fn (mut func Function) call(name string) {
func.code << 0x10 // call func.code << 0x10 // call
func.call_patches << FunctionCallPatch{ func.patches << CallPatch(FunctionCallPatch{
name: name name: name
pos: func.code.len pos: func.code.len
} })
} }
// call calls an imported function. // call calls an imported function.
@ -885,11 +966,11 @@ pub fn (mut func Function) call(name string) {
// WebAssembly instruction: `call`. // WebAssembly instruction: `call`.
pub fn (mut func Function) call_import(mod string, name string) { pub fn (mut func Function) call_import(mod string, name string) {
func.code << 0x10 // call func.code << 0x10 // call
func.call_patches << ImportCallPatch{ func.patches << CallPatch(ImportCallPatch{
mod: mod mod: mod
name: name name: name
pos: func.code.len pos: func.code.len
} })
} }
// load loads a value with type `typ` from memory. // 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`. // WebAssembly instruction: `ref.func`.
pub fn (mut func Function) ref_func(name string) { pub fn (mut func Function) ref_func(name string) {
func.code << 0xD2 // ref.func func.code << 0xD2 // ref.func
func.call_patches << FunctionCallPatch{ func.patches << CallPatch(FunctionCallPatch{
name: name name: name
pos: func.code.len 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. // If the imported function does not exist when calling `compile` on the module, it will panic.
// WebAssembly instruction: `ref.func`. // WebAssembly instruction: `ref.func`.
pub fn (mut func Function) ref_func_import(mod string, name string) { pub fn (mut func Function) ref_func_import(mod string, name string) {
func.code << 0xD2 // ref.func func.code << 0xD2 // ref.func
func.call_patches << ImportCallPatch{ func.patches << CallPatch(ImportCallPatch{
mod: mod mod: mod
name: name name: name
pos: func.code.len pos: func.code.len
} })
} }

View File

@ -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 module wasm
enum Section as u8 { enum Section as u8 {
@ -16,6 +19,20 @@ enum Section as u8 {
data_count_section 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 { pub enum NumType as u8 {
i32_t = 0x7f i32_t = 0x7f
i64_t = 0x7e i64_t = 0x7e
@ -52,13 +69,17 @@ mut:
fn_imports []FunctionImport fn_imports []FunctionImport
global_imports []GlobalImport global_imports []GlobalImport
segments []DataSegment segments []DataSegment
debug bool
mod_name ?string
} }
struct Global { struct Global {
typ ValType typ ValType
is_mut bool is_mut bool
export_name ?string name string
init ConstExpression export bool
mut:
init ConstExpression
} }
struct GlobalImport { struct GlobalImport {
@ -84,6 +105,7 @@ struct Memory {
struct DataSegment { struct DataSegment {
idx ?int idx ?int
data []u8 data []u8
name ?string
} }
pub type LocalIndex = int pub type LocalIndex = int
@ -91,11 +113,11 @@ pub type GlobalIndex = int
pub type GlobalImportIndex = int pub type GlobalImportIndex = int
pub type DataSegmentIndex = int pub type DataSegmentIndex = int
[params]
pub struct FuncType { pub struct FuncType {
pub: pub:
parameters []ValType parameters []ValType
results []ValType results []ValType
name ?string
} }
fn (mut mod Module) new_functype(ft FuncType) int { 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() assert name !in mod.functions.keys()
idx := mod.functions.len idx := mod.functions.len
tidx := mod.new_functype(FuncType{parameters, results}) tidx := mod.new_functype(FuncType{parameters, results, none})
return Function{ return Function{
name: name name: name
tidx: tidx tidx: tidx
idx: idx idx: idx
mod: mod 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. // assign_memory assigns memory to the current module.
pub fn (mut mod Module) assign_memory(name string, export bool, min u32, max ?u32) { pub fn (mut mod Module) assign_memory(name string, export bool, min u32, max ?u32) {
mod.memory = Memory{ 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) { 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) 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.fn_imports << FunctionImport{
mod: modn 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`. // 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 len := mod.segments.len
mod.segments << DataSegment{ mod.segments << DataSegment{
idx: pos idx: pos
data: data data: data
name: name
} }
return len return len
} }
// new_passive_data_segment inserts a new passive data segment. // 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{ mod.segments << DataSegment{
data: data data: data
name: name
} }
} }
// new_global creates a global and returns it's index. // 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`. // 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 len := mod.globals.len
mod.globals << Global{ mod.globals << Global{
typ: typ typ: typ
is_mut: is_mut is_mut: is_mut
export_name: export_name name: name
export: export
init: init init: init
} }
return len return len
} }
// new_global_import imports a new global into the current module and returns it's index. // 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 { 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) 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 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
}

View File

@ -61,11 +61,11 @@ fn test_call() {
{ {
fac.local_get(0) fac.local_get(0)
fac.eqz(.i64_t) fac.eqz(.i64_t)
fac.c_if([], [.i64_t]) ifs := fac.c_if([], [.i64_t])
{ {
fac.i64_const(1) fac.i64_const(1)
} }
fac.c_else() fac.c_else(ifs)
{ {
{ {
fac.local_get(0) fac.local_get(0)
@ -78,7 +78,7 @@ fn test_call() {
} }
fac.mul(.i64_t) fac.mul(.i64_t)
} }
fac.c_end_if() fac.c_end(ifs)
} }
m.commit(fac, true) m.commit(fac, true)

View File

@ -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_int>_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())
}

View File

@ -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) }
}

View File

@ -4,8 +4,9 @@ import wasm
fn test_globals() { fn test_globals() {
mut m := wasm.Module{} 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]) mut func := m.new_function('vsp', [], [.i32_t])
{ {
func.global_get(vsp) func.global_get(vsp)
@ -16,7 +17,7 @@ fn test_globals() {
} }
m.commit(func, true) 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', [], []) mut func1 := m.new_function('ref', [], [])
{ {
func1.ref_func('vsp') func1.ref_func('vsp')