diff --git a/vlib/builtin/wasm/alloc.v b/vlib/builtin/wasm/alloc.v index 6c1d6b9a53..de8497d2b1 100644 --- a/vlib/builtin/wasm/alloc.v +++ b/vlib/builtin/wasm/alloc.v @@ -4,17 +4,7 @@ module builtin // Shitty `sbrk` basic `malloc` and `free` impl // TODO: implement pure V `walloc` later -const wasm_page_size = 64 * 1024 - -__global g_heap_base = isize(0) - -fn init() { - g_heap_base = __memory_grow(3) - if g_heap_base == -1 { - panic('g_heap_base: malloc() == nil') - } - g_heap_base *= wasm_page_size -} +__global g_heap_base = usize(__heap_base()) // malloc dynamically allocates a `n` bytes block of memory on the heap. // malloc returns a `byteptr` pointing to the memory address of the allocated space. @@ -26,7 +16,7 @@ pub fn malloc(n isize) &u8 { } res := g_heap_base - g_heap_base += n + g_heap_base += usize(n) return &u8(res) } @@ -37,45 +27,3 @@ pub fn malloc(n isize) &u8 { pub fn free(ptr voidptr) { _ := ptr } - -// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap. -// vcalloc returns a `byteptr` pointing to the memory address of the allocated space. -// Unlike `v_calloc` vcalloc checks for negative values given in `n`. -[unsafe] -pub fn vcalloc(n isize) &u8 { - if n <= 0 { - panic('vcalloc(n <= 0)') - } else if n == 0 { - return &u8(0) - } - - res := unsafe { malloc(n) } - - __memory_fill(res, 0, n) - - return res -} - -// vmemcpy copies n bytes from memory area src to memory area dest. -// The memory areas **CAN** overlap. vmemcpy returns a pointer to `dest`. -[unsafe] -pub fn vmemcpy(dest voidptr, const_src voidptr, n isize) voidptr { - __memory_copy(dest, const_src, n) - return dest -} - -// vmemmove copies n bytes from memory area src to memory area dest. -// The memory areas **CAN** overlap. vmemmove returns a pointer to `dest`. -[unsafe] -pub fn vmemmove(dest voidptr, const_src voidptr, n isize) voidptr { - __memory_copy(dest, const_src, n) - return dest -} - -// vmemset fills the first `n` bytes of the memory area pointed to by `s`, -// with the constant byte `c`. It returns a pointer to the memory area `s`. -[unsafe] -pub fn vmemset(s voidptr, c int, n isize) voidptr { - __memory_fill(s, c, n) - return s -} diff --git a/vlib/builtin/wasm/builtin.v b/vlib/builtin/wasm/builtin.v index 079c2c1907..4f4eb11f2a 100644 --- a/vlib/builtin/wasm/builtin.v +++ b/vlib/builtin/wasm/builtin.v @@ -1,5 +1,55 @@ module builtin -fn __memory_grow(size isize) isize +fn __heap_base() voidptr +fn __memory_size() usize +fn __memory_grow(size usize) usize fn __memory_fill(dest &u8, value isize, size isize) fn __memory_copy(dest &u8, src &u8, size isize) + +// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap. +// vcalloc returns a `byteptr` pointing to the memory address of the allocated space. +// Unlike `v_calloc` vcalloc checks for negative values given in `n`. +[unsafe] +pub fn vcalloc(n isize) &u8 { + if n <= 0 { + panic('vcalloc(n <= 0)') + } else if n == 0 { + return &u8(0) + } + + res := unsafe { malloc(n) } + + __memory_fill(res, 0, n) + + return res +} + +// isnil returns true if an object is nil (only for C objects). +[inline] +pub fn isnil(v voidptr) bool { + return v == 0 +} + +// vmemcpy copies n bytes from memory area src to memory area dest. +// The memory areas **CAN** overlap. vmemcpy returns a pointer to `dest`. +[unsafe] +pub fn vmemcpy(dest voidptr, const_src voidptr, n isize) voidptr { + __memory_copy(dest, const_src, n) + return dest +} + +// vmemmove copies n bytes from memory area src to memory area dest. +// The memory areas **CAN** overlap. vmemmove returns a pointer to `dest`. +[unsafe] +pub fn vmemmove(dest voidptr, const_src voidptr, n isize) voidptr { + __memory_copy(dest, const_src, n) + return dest +} + +// vmemset fills the first `n` bytes of the memory area pointed to by `s`, +// with the constant byte `c`. It returns a pointer to the memory area `s`. +[unsafe] +pub fn vmemset(s voidptr, c int, n isize) voidptr { + __memory_fill(s, c, n) + return s +} diff --git a/vlib/builtin/wasm/wasi/builtin.v b/vlib/builtin/wasm/wasi/builtin.v index e0313cf555..fd14f293e9 100644 --- a/vlib/builtin/wasm/wasi/builtin.v +++ b/vlib/builtin/wasm/wasi/builtin.v @@ -57,5 +57,6 @@ pub fn exit(code int) { pub fn panic(s string) { eprint('V panic: ') eprintln(s) + _ := *&u8(-1) exit(1) } diff --git a/vlib/v/gen/wasm/gen.v b/vlib/v/gen/wasm/gen.v index 867232061d..070399895e 100644 --- a/vlib/v/gen/wasm/gen.v +++ b/vlib/v/gen/wasm/gen.v @@ -36,7 +36,7 @@ mut: stack_patches []BlockPatch needs_stack bool // If true, will use `memory` and `__vsp` constant_data []ConstantData - constant_data_offset int + constant_data_offset int = 1024 // Low 1KiB of data unused, for optimisations module_import_namespace string // `[wasm_import_namespace: 'wasi_snapshot_preview1']` else `env` globals map[string]GlobalData } @@ -159,7 +159,7 @@ fn (mut g Gen) function_return_wasm_type(typ ast.Type) binaryen.Type { if typ == ast.void_type { return type_none } - types := g.unpack_type(typ).filter(g.table.sym(it).info !is ast.Struct).map(g.get_wasm_type(it)) + types := g.unpack_type(typ).filter(it.is_real_pointer() || g.table.sym(it).info !is ast.Struct).map(g.get_wasm_type(it)) if types.len == 0 { return type_none } @@ -216,8 +216,10 @@ fn (mut g Gen) bare_function(name string, expr binaryen.Expression) binaryen.Fun temporaries << g.local_temporaries[idx].typ } + wasm_expr := g.setup_stack_frame(expr) + func := binaryen.addfunction(g.mod, name.str, type_none, type_none, temporaries.data, - temporaries.len, expr) + temporaries.len, wasm_expr) g.local_temporaries.clear() g.local_addresses = map[string]Stack{} @@ -272,7 +274,7 @@ fn (mut g Gen) fn_decl(node ast.FnDecl) { for idx, typ in g.curr_ret { sym := g.table.sym(typ) - if sym.info is ast.Struct { + if sym.info is ast.Struct && !typ.is_real_pointer() { g.local_temporaries << Temporary{ name: '__return${idx}' typ: type_i32 // pointer @@ -318,7 +320,7 @@ fn (mut g Gen) fn_decl(node ast.FnDecl) { node.pos.col) } - if g.pref.printfn_list.len > 0 && node.name in g.pref.printfn_list { + if g.pref.printfn_list.len > 0 && name in g.pref.printfn_list { binaryen.expressionprint(wasm_expr) } @@ -346,7 +348,7 @@ fn (mut g Gen) literalint(val i64, expected ast.Type) binaryen.Expression { type_i64 { return binaryen.constant(g.mod, binaryen.literalint64(val)) } else {} } - g.w_error('literalint: bad type `${expected}`') + g.w_error('literalint: bad type `${*g.table.sym(expected)}`') } fn (mut g Gen) literal(val string, expected ast.Type) binaryen.Expression { @@ -360,14 +362,23 @@ fn (mut g Gen) literal(val string, expected ast.Type) binaryen.Expression { g.w_error('literal: bad type `${expected}`') } +fn (mut g Gen) handle_ptr_arithmetic(typ ast.Type, expr binaryen.Expression) binaryen.Expression { + return if typ.is_ptr() { + size, _ := g.get_type_size_align(typ) + binaryen.binary(g.mod, binaryen.mulint32(), expr, g.literalint(size, ast.voidptr_type)) + } else { + expr + } +} + fn (mut g Gen) postfix_expr(node ast.PostfixExpr) binaryen.Expression { kind := if node.op == .inc { token.Kind.plus } else { token.Kind.minus } var := g.get_var_from_expr(node.expr) op := g.infix_from_typ(node.typ, kind) - expr := binaryen.binary(g.mod, op, g.get_or_lea_lop(var, node.typ), g.literal('1', - node.typ)) + expr := binaryen.binary(g.mod, op, g.get_or_lea_lop(var, node.typ), g.handle_ptr_arithmetic(node.typ, + g.literal('0', node.typ))) return g.set_var(var, expr, ast_typ: node.typ) } @@ -395,8 +406,8 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr, expected ast.Type) binaryen.Expres op := g.infix_from_typ(node.left_type, node.op) - infix := binaryen.binary(g.mod, op, g.expr(node.left, node.left_type), g.expr_with_cast(node.right, - node.right_type, node.left_type)) + infix := binaryen.binary(g.mod, op, g.expr(node.left, node.left_type), g.handle_ptr_arithmetic(node.left_type, + g.expr_with_cast(node.right, node.right_type, node.left_type))) res_typ := if infix_kind_return_bool(node.op) { ast.bool_type @@ -492,7 +503,8 @@ fn (mut g Gen) if_expr(ifexpr ast.IfExpr) binaryen.Expression { return g.if_branch(ifexpr, 0) } -const wasm_builtins = ['__memory_grow', '__memory_fill', '__memory_copy'] +const wasm_builtins = ['__memory_grow', '__memory_fill', '__memory_copy', '__memory_size', + '__heap_base'] fn (mut g Gen) wasm_builtin(name string, node ast.CallExpr) binaryen.Expression { mut args := []binaryen.Expression{cap: node.args.len} @@ -510,6 +522,12 @@ fn (mut g Gen) wasm_builtin(name string, node ast.CallExpr) binaryen.Expression '__memory_copy' { return binaryen.memorycopy(g.mod, args[0], args[1], args[2], c'memory', c'memory') } + '__memory_size' { + return binaryen.memorysize(g.mod, c'memory', false) + } + '__heap_base' { + return binaryen.globalget(g.mod, c'__heap_base', type_i32) + } else { panic('unreachable') } @@ -666,6 +684,9 @@ fn (mut g Gen) expr_impl(node ast.Expr, expected ast.Type) binaryen.Expression { g.literalint(off, ast.u32_type) } ast.SizeOf { + if !g.table.known_type_idx(node.typ) { + g.v_error('unknown type `${*g.table.sym(node.typ)}`', node.pos) + } size, _ := g.table.type_size(node.typ) g.literalint(size, ast.u32_type) } @@ -705,6 +726,9 @@ fn (mut g Gen) expr_impl(node ast.Expr, expected ast.Type) binaryen.Expression { ast.IntegerLiteral, ast.FloatLiteral { g.literal(node.val, expected) } + ast.Nil { + g.literalint(0, expected) + } ast.IfExpr { if node.branches.len == 2 && node.is_expr { left := g.expr_stmts(node.branches[0].stmts, expected) @@ -755,7 +779,7 @@ fn (mut g Gen) expr_impl(node ast.Expr, expected ast.Type) binaryen.Expression { } ret_types := g.unpack_type(node.return_type) - structs := ret_types.filter(g.table.sym(it).info is ast.Struct) + structs := ret_types.filter(g.table.sym(it).info is ast.Struct && !it.is_real_pointer()) mut structs_addrs := []int{cap: structs.len} // ABI: {return structs} {method `self`}, then {arguments} @@ -926,7 +950,8 @@ fn (mut g Gen) expr_stmt(node ast.Stmt, expected ast.Type) binaryen.Expression { mut leave_expr_list := []binaryen.Expression{cap: node.exprs.len} mut exprs := []binaryen.Expression{cap: node.exprs.len} for idx, expr in node.exprs { - if g.table.sym(g.curr_ret[idx]).info is ast.Struct { + typ := g.curr_ret[idx] + if g.table.sym(typ).info is ast.Struct && !typ.is_real_pointer() { // Could be adapted to use random pointers? /* if expr is ast.StructInit { @@ -934,12 +959,12 @@ fn (mut g Gen) expr_stmt(node ast.Stmt, expected ast.Type) binaryen.Expression { leave_expr_list << g.init_struct(var, expr) }*/ var := g.local_temporaries[g.get_local_temporary('__return${idx}')] - address := g.expr(expr, g.curr_ret[idx]) + address := g.expr(expr, typ) - leave_expr_list << g.blit(address, g.curr_ret[idx], binaryen.localget(g.mod, - var.idx, var.typ)) + leave_expr_list << g.blit(address, typ, binaryen.localget(g.mod, var.idx, + var.typ)) } else { - exprs << g.expr(expr, g.curr_ret[idx]) + exprs << g.expr(expr, typ) } } @@ -1179,9 +1204,11 @@ pub fn gen(files []&ast.File, table &ast.Table, out_name string, w_pref &pref.Pr mod: binaryen.modulecreate() } g.table.pointer_size = 4 - // Offset all pointers by 8, so that 0 never points to valid memory - g.constant_data_offset = 8 binaryen.modulesetfeatures(g.mod, binaryen.featureall()) + binaryen.setlowmemoryunused(true) // Low 1KiB of memory is unused. + defer { + binaryen.moduledispose(g.mod) + } if g.pref.os == .browser { eprintln('`-os browser` is experimental and will not live up to expectations...') @@ -1202,26 +1229,31 @@ pub fn gen(files []&ast.File, table &ast.Table, out_name string, w_pref &pref.Pr g.needs_stack = true } g.housekeeping() - if binaryen.modulevalidate(g.mod) { + + mut valid := binaryen.modulevalidate(g.mod) + if valid { binaryen.setdebuginfo(w_pref.is_debug) if w_pref.is_prod { binaryen.setoptimizelevel(3) binaryen.moduleoptimize(g.mod) } - if out_name == '-' { - if g.pref.is_verbose { - binaryen.moduleprint(g.mod) - } else { - binaryen.moduleprintstackir(g.mod, w_pref.is_prod) - } + } + + if out_name == '-' { + if g.pref.is_verbose { + binaryen.moduleprint(g.mod) } else { - bytes := binaryen.moduleallocateandwrite(g.mod, unsafe { nil }) - str := unsafe { (&char(bytes.binary)).vstring_with_len(int(bytes.binaryBytes)) } - os.write_file(out_name, str) or { panic(err) } + binaryen.moduleprintstackir(g.mod, w_pref.is_prod) } - } else { - binaryen.moduledispose(g.mod) + } + + if !valid { g.w_error('validation failed, this should not happen. report an issue with the above messages') } - binaryen.moduledispose(g.mod) + + if out_name != '-' { + bytes := binaryen.moduleallocateandwrite(g.mod, unsafe { nil }) + str := unsafe { (&char(bytes.binary)).vstring_with_len(int(bytes.binaryBytes)) } + os.write_file(out_name, str) or { panic(err) } + } } diff --git a/vlib/v/gen/wasm/mem.v b/vlib/v/gen/wasm/mem.v index 9b2b8c3c30..fdff8fbac9 100644 --- a/vlib/v/gen/wasm/mem.v +++ b/vlib/v/gen/wasm/mem.v @@ -142,7 +142,7 @@ fn (mut g Gen) get_or_lea_lop(lp LocalOrPointer, expected ast.Type) binaryen.Exp } } - if !is_expr && parent_typ == expected { + if (!is_expr && parent_typ == expected) || !g.is_pure_type(expected) { return expr } @@ -235,6 +235,9 @@ fn (mut g Gen) get_var_from_expr(node ast.Expr) LocalOrPointer { ast.Ident { g.get_var_from_ident(node) } + ast.ParExpr { + g.get_var_from_expr(node.expr) + } ast.SelectorExpr { address := g.get_var_from_expr(node.expr) offset := g.get_field_offset(node.expr_type, node.field_name) diff --git a/vlib/v/gen/wasm/ops.v b/vlib/v/gen/wasm/ops.v index 7d9c1fa14f..9fab29ad59 100644 --- a/vlib/v/gen/wasm/ops.v +++ b/vlib/v/gen/wasm/ops.v @@ -21,6 +21,7 @@ fn (mut g Gen) get_wasm_type(typ_ ast.Type) binaryen.Type { return wasm.type_none } if typ.is_real_pointer() { + g.needs_stack = true return wasm.type_i32 } if typ in ast.number_type_idxs { @@ -64,6 +65,9 @@ fn (mut g Gen) get_wasm_type(typ_ ast.Type) binaryen.Type { ast.ArrayFixed { return wasm.type_i32 } + ast.Enum { + return g.get_wasm_type(ts.info.typ) + } else {} } diff --git a/vlib/v/gen/wasm/serialisation.v b/vlib/v/gen/wasm/serialisation.v index 5cdde92167..a809664b1d 100644 --- a/vlib/v/gen/wasm/serialisation.v +++ b/vlib/v/gen/wasm/serialisation.v @@ -130,6 +130,13 @@ fn (mut g Gen) housekeeping() { // `_vinit` should be used to initialise the WASM module, // then `main.main` can be called safely. vinit := g.make_vinit() + stack_base := round_up_to_multiple(g.constant_data_offset, 1024) + heap_base := if g.needs_stack { + stack_base + 1024 * 16 // 16KiB of stack + } else { + stack_base + } + pages_needed := heap_base / (1024 * 64) + 1 if g.needs_stack || g.constant_data.len != 0 { data := g.constant_data.map(it.data.data) @@ -137,14 +144,14 @@ fn (mut g Gen) housekeeping() { data_offsets := g.constant_data.map(binaryen.constant(g.mod, binaryen.literalint32(it.offset))) passive := []bool{len: g.constant_data.len, init: false} - binaryen.setmemory(g.mod, 1, 4, c'memory', data.data, passive.data, data_offsets.data, - data_len.data, data.len, false, false, c'memory') + binaryen.setmemory(g.mod, pages_needed, pages_needed + 4, c'memory', data.data, + passive.data, data_offsets.data, data_len.data, data.len, false, false, c'memory') + binaryen.addglobal(g.mod, c'__heap_base', type_i32, false, g.literalint(heap_base, + ast.int_type)) } if g.needs_stack { // `g.constant_data_offset` rounded up to a multiple of 1024 - offset := round_up_to_multiple(g.constant_data_offset, 1024) - - binaryen.addglobal(g.mod, c'__vsp', type_i32, true, g.literalint(offset, ast.int_type)) + binaryen.addglobal(g.mod, c'__vsp', type_i32, true, g.literalint(stack_base, ast.int_type)) } if g.pref.os == .wasi { main_expr := g.mkblock([binaryen.call(g.mod, c'_vinit', unsafe { nil }, 0, type_none), diff --git a/vlib/v/gen/wasm/tests/arrays.vv b/vlib/v/gen/wasm/tests/arrays.vv index 7ae783e2b9..8fbb60623c 100644 --- a/vlib/v/gen/wasm/tests/arrays.vv +++ b/vlib/v/gen/wasm/tests/arrays.vv @@ -26,3 +26,14 @@ fn test_this(index int) int { } return 10 } + +struct AA { + a [10]&int +} + +fn main() { + a := AA{} + + mut b := &int(0) + b = a.a[2] +}