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:
parent
5bdf94a7bc
commit
3a06b55388
@ -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())
|
||||
|
@ -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())
|
||||
|
@ -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', [], [])
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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{}
|
||||
|
@ -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.patches {
|
||||
mut idx := 0
|
||||
match patch {
|
||||
CallPatch {
|
||||
idx = mod.get_function_idx(patch)
|
||||
}
|
||||
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.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
|
||||
|
@ -1,13 +1,18 @@
|
||||
// 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
|
||||
mut:
|
||||
pos int
|
||||
}
|
||||
|
||||
struct FunctionCallPatch {
|
||||
name string
|
||||
mut:
|
||||
pos int
|
||||
}
|
||||
|
||||
@ -15,20 +20,27 @@ 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
|
||||
patches []FunctionPatch // sorted
|
||||
label int
|
||||
export bool
|
||||
mod &Module = unsafe { nil }
|
||||
code []u8
|
||||
locals []ValType
|
||||
locals []FunctionLocal
|
||||
pub:
|
||||
name string
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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,12 +69,16 @@ mut:
|
||||
fn_imports []FunctionImport
|
||||
global_imports []GlobalImport
|
||||
segments []DataSegment
|
||||
debug bool
|
||||
mod_name ?string
|
||||
}
|
||||
|
||||
struct Global {
|
||||
typ ValType
|
||||
is_mut bool
|
||||
export_name ?string
|
||||
name string
|
||||
export bool
|
||||
mut:
|
||||
init ConstExpression
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
59
vlib/wasm/tests/debug_test.v
Normal file
59
vlib/wasm/tests/debug_test.v
Normal 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())
|
||||
}
|
30
vlib/wasm/tests/patch_test.v
Normal file
30
vlib/wasm/tests/patch_test.v
Normal 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) }
|
||||
}
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user