Skip to content

Commit 6f71923

Browse files
authored
wasm: bug fixes and memory based changes (#17497)
1 parent 72cbca9 commit 6f71923

8 files changed

Lines changed: 149 additions & 93 deletions

File tree

vlib/builtin/wasm/alloc.v

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,7 @@ module builtin
44
// Shitty `sbrk` basic `malloc` and `free` impl
55
// TODO: implement pure V `walloc` later
66

7-
const wasm_page_size = 64 * 1024
8-
9-
__global g_heap_base = isize(0)
10-
11-
fn init() {
12-
g_heap_base = __memory_grow(3)
13-
if g_heap_base == -1 {
14-
panic('g_heap_base: malloc() == nil')
15-
}
16-
g_heap_base *= wasm_page_size
17-
}
7+
__global g_heap_base = usize(__heap_base())
188

199
// malloc dynamically allocates a `n` bytes block of memory on the heap.
2010
// malloc returns a `byteptr` pointing to the memory address of the allocated space.
@@ -26,7 +16,7 @@ pub fn malloc(n isize) &u8 {
2616
}
2717

2818
res := g_heap_base
29-
g_heap_base += n
19+
g_heap_base += usize(n)
3020

3121
return &u8(res)
3222
}
@@ -37,45 +27,3 @@ pub fn malloc(n isize) &u8 {
3727
pub fn free(ptr voidptr) {
3828
_ := ptr
3929
}
40-
41-
// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap.
42-
// vcalloc returns a `byteptr` pointing to the memory address of the allocated space.
43-
// Unlike `v_calloc` vcalloc checks for negative values given in `n`.
44-
[unsafe]
45-
pub fn vcalloc(n isize) &u8 {
46-
if n <= 0 {
47-
panic('vcalloc(n <= 0)')
48-
} else if n == 0 {
49-
return &u8(0)
50-
}
51-
52-
res := unsafe { malloc(n) }
53-
54-
__memory_fill(res, 0, n)
55-
56-
return res
57-
}
58-
59-
// vmemcpy copies n bytes from memory area src to memory area dest.
60-
// The memory areas **CAN** overlap. vmemcpy returns a pointer to `dest`.
61-
[unsafe]
62-
pub fn vmemcpy(dest voidptr, const_src voidptr, n isize) voidptr {
63-
__memory_copy(dest, const_src, n)
64-
return dest
65-
}
66-
67-
// vmemmove copies n bytes from memory area src to memory area dest.
68-
// The memory areas **CAN** overlap. vmemmove returns a pointer to `dest`.
69-
[unsafe]
70-
pub fn vmemmove(dest voidptr, const_src voidptr, n isize) voidptr {
71-
__memory_copy(dest, const_src, n)
72-
return dest
73-
}
74-
75-
// vmemset fills the first `n` bytes of the memory area pointed to by `s`,
76-
// with the constant byte `c`. It returns a pointer to the memory area `s`.
77-
[unsafe]
78-
pub fn vmemset(s voidptr, c int, n isize) voidptr {
79-
__memory_fill(s, c, n)
80-
return s
81-
}

vlib/builtin/wasm/builtin.v

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,55 @@
11
module builtin
22

3-
fn __memory_grow(size isize) isize
3+
fn __heap_base() voidptr
4+
fn __memory_size() usize
5+
fn __memory_grow(size usize) usize
46
fn __memory_fill(dest &u8, value isize, size isize)
57
fn __memory_copy(dest &u8, src &u8, size isize)
8+
9+
// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap.
10+
// vcalloc returns a `byteptr` pointing to the memory address of the allocated space.
11+
// Unlike `v_calloc` vcalloc checks for negative values given in `n`.
12+
[unsafe]
13+
pub fn vcalloc(n isize) &u8 {
14+
if n <= 0 {
15+
panic('vcalloc(n <= 0)')
16+
} else if n == 0 {
17+
return &u8(0)
18+
}
19+
20+
res := unsafe { malloc(n) }
21+
22+
__memory_fill(res, 0, n)
23+
24+
return res
25+
}
26+
27+
// isnil returns true if an object is nil (only for C objects).
28+
[inline]
29+
pub fn isnil(v voidptr) bool {
30+
return v == 0
31+
}
32+
33+
// vmemcpy copies n bytes from memory area src to memory area dest.
34+
// The memory areas **CAN** overlap. vmemcpy returns a pointer to `dest`.
35+
[unsafe]
36+
pub fn vmemcpy(dest voidptr, const_src voidptr, n isize) voidptr {
37+
__memory_copy(dest, const_src, n)
38+
return dest
39+
}
40+
41+
// vmemmove copies n bytes from memory area src to memory area dest.
42+
// The memory areas **CAN** overlap. vmemmove returns a pointer to `dest`.
43+
[unsafe]
44+
pub fn vmemmove(dest voidptr, const_src voidptr, n isize) voidptr {
45+
__memory_copy(dest, const_src, n)
46+
return dest
47+
}
48+
49+
// vmemset fills the first `n` bytes of the memory area pointed to by `s`,
50+
// with the constant byte `c`. It returns a pointer to the memory area `s`.
51+
[unsafe]
52+
pub fn vmemset(s voidptr, c int, n isize) voidptr {
53+
__memory_fill(s, c, n)
54+
return s
55+
}

vlib/builtin/wasm/wasi/builtin.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,6 @@ pub fn exit(code int) {
5757
pub fn panic(s string) {
5858
eprint('V panic: ')
5959
eprintln(s)
60+
_ := *&u8(-1)
6061
exit(1)
6162
}

vlib/v/gen/wasm/gen.v

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ mut:
3636
stack_patches []BlockPatch
3737
needs_stack bool // If true, will use `memory` and `__vsp`
3838
constant_data []ConstantData
39-
constant_data_offset int
39+
constant_data_offset int = 1024 // Low 1KiB of data unused, for optimisations
4040
module_import_namespace string // `[wasm_import_namespace: 'wasi_snapshot_preview1']` else `env`
4141
globals map[string]GlobalData
4242
}
@@ -159,7 +159,7 @@ fn (mut g Gen) function_return_wasm_type(typ ast.Type) binaryen.Type {
159159
if typ == ast.void_type {
160160
return type_none
161161
}
162-
types := g.unpack_type(typ).filter(g.table.sym(it).info !is ast.Struct).map(g.get_wasm_type(it))
162+
types := g.unpack_type(typ).filter(it.is_real_pointer() || g.table.sym(it).info !is ast.Struct).map(g.get_wasm_type(it))
163163
if types.len == 0 {
164164
return type_none
165165
}
@@ -216,8 +216,10 @@ fn (mut g Gen) bare_function(name string, expr binaryen.Expression) binaryen.Fun
216216
temporaries << g.local_temporaries[idx].typ
217217
}
218218

219+
wasm_expr := g.setup_stack_frame(expr)
220+
219221
func := binaryen.addfunction(g.mod, name.str, type_none, type_none, temporaries.data,
220-
temporaries.len, expr)
222+
temporaries.len, wasm_expr)
221223

222224
g.local_temporaries.clear()
223225
g.local_addresses = map[string]Stack{}
@@ -272,7 +274,7 @@ fn (mut g Gen) fn_decl(node ast.FnDecl) {
272274

273275
for idx, typ in g.curr_ret {
274276
sym := g.table.sym(typ)
275-
if sym.info is ast.Struct {
277+
if sym.info is ast.Struct && !typ.is_real_pointer() {
276278
g.local_temporaries << Temporary{
277279
name: '__return${idx}'
278280
typ: type_i32 // pointer
@@ -318,7 +320,7 @@ fn (mut g Gen) fn_decl(node ast.FnDecl) {
318320
node.pos.col)
319321
}
320322

321-
if g.pref.printfn_list.len > 0 && node.name in g.pref.printfn_list {
323+
if g.pref.printfn_list.len > 0 && name in g.pref.printfn_list {
322324
binaryen.expressionprint(wasm_expr)
323325
}
324326

@@ -346,7 +348,7 @@ fn (mut g Gen) literalint(val i64, expected ast.Type) binaryen.Expression {
346348
type_i64 { return binaryen.constant(g.mod, binaryen.literalint64(val)) }
347349
else {}
348350
}
349-
g.w_error('literalint: bad type `${expected}`')
351+
g.w_error('literalint: bad type `${*g.table.sym(expected)}`')
350352
}
351353

352354
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 {
360362
g.w_error('literal: bad type `${expected}`')
361363
}
362364

365+
fn (mut g Gen) handle_ptr_arithmetic(typ ast.Type, expr binaryen.Expression) binaryen.Expression {
366+
return if typ.is_ptr() {
367+
size, _ := g.get_type_size_align(typ)
368+
binaryen.binary(g.mod, binaryen.mulint32(), expr, g.literalint(size, ast.voidptr_type))
369+
} else {
370+
expr
371+
}
372+
}
373+
363374
fn (mut g Gen) postfix_expr(node ast.PostfixExpr) binaryen.Expression {
364375
kind := if node.op == .inc { token.Kind.plus } else { token.Kind.minus }
365376

366377
var := g.get_var_from_expr(node.expr)
367378
op := g.infix_from_typ(node.typ, kind)
368379

369-
expr := binaryen.binary(g.mod, op, g.get_or_lea_lop(var, node.typ), g.literal('1',
370-
node.typ))
380+
expr := binaryen.binary(g.mod, op, g.get_or_lea_lop(var, node.typ), g.handle_ptr_arithmetic(node.typ,
381+
g.literal('0', node.typ)))
371382

372383
return g.set_var(var, expr, ast_typ: node.typ)
373384
}
@@ -395,8 +406,8 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr, expected ast.Type) binaryen.Expres
395406

396407
op := g.infix_from_typ(node.left_type, node.op)
397408

398-
infix := binaryen.binary(g.mod, op, g.expr(node.left, node.left_type), g.expr_with_cast(node.right,
399-
node.right_type, node.left_type))
409+
infix := binaryen.binary(g.mod, op, g.expr(node.left, node.left_type), g.handle_ptr_arithmetic(node.left_type,
410+
g.expr_with_cast(node.right, node.right_type, node.left_type)))
400411

401412
res_typ := if infix_kind_return_bool(node.op) {
402413
ast.bool_type
@@ -492,7 +503,8 @@ fn (mut g Gen) if_expr(ifexpr ast.IfExpr) binaryen.Expression {
492503
return g.if_branch(ifexpr, 0)
493504
}
494505

495-
const wasm_builtins = ['__memory_grow', '__memory_fill', '__memory_copy']
506+
const wasm_builtins = ['__memory_grow', '__memory_fill', '__memory_copy', '__memory_size',
507+
'__heap_base']
496508

497509
fn (mut g Gen) wasm_builtin(name string, node ast.CallExpr) binaryen.Expression {
498510
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
510522
'__memory_copy' {
511523
return binaryen.memorycopy(g.mod, args[0], args[1], args[2], c'memory', c'memory')
512524
}
525+
'__memory_size' {
526+
return binaryen.memorysize(g.mod, c'memory', false)
527+
}
528+
'__heap_base' {
529+
return binaryen.globalget(g.mod, c'__heap_base', type_i32)
530+
}
513531
else {
514532
panic('unreachable')
515533
}
@@ -666,6 +684,9 @@ fn (mut g Gen) expr_impl(node ast.Expr, expected ast.Type) binaryen.Expression {
666684
g.literalint(off, ast.u32_type)
667685
}
668686
ast.SizeOf {
687+
if !g.table.known_type_idx(node.typ) {
688+
g.v_error('unknown type `${*g.table.sym(node.typ)}`', node.pos)
689+
}
669690
size, _ := g.table.type_size(node.typ)
670691
g.literalint(size, ast.u32_type)
671692
}
@@ -705,6 +726,9 @@ fn (mut g Gen) expr_impl(node ast.Expr, expected ast.Type) binaryen.Expression {
705726
ast.IntegerLiteral, ast.FloatLiteral {
706727
g.literal(node.val, expected)
707728
}
729+
ast.Nil {
730+
g.literalint(0, expected)
731+
}
708732
ast.IfExpr {
709733
if node.branches.len == 2 && node.is_expr {
710734
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 {
755779
}
756780

757781
ret_types := g.unpack_type(node.return_type)
758-
structs := ret_types.filter(g.table.sym(it).info is ast.Struct)
782+
structs := ret_types.filter(g.table.sym(it).info is ast.Struct && !it.is_real_pointer())
759783
mut structs_addrs := []int{cap: structs.len}
760784

761785
// ABI: {return structs} {method `self`}, then {arguments}
@@ -926,20 +950,21 @@ fn (mut g Gen) expr_stmt(node ast.Stmt, expected ast.Type) binaryen.Expression {
926950
mut leave_expr_list := []binaryen.Expression{cap: node.exprs.len}
927951
mut exprs := []binaryen.Expression{cap: node.exprs.len}
928952
for idx, expr in node.exprs {
929-
if g.table.sym(g.curr_ret[idx]).info is ast.Struct {
953+
typ := g.curr_ret[idx]
954+
if g.table.sym(typ).info is ast.Struct && !typ.is_real_pointer() {
930955
// Could be adapted to use random pointers?
931956
/*
932957
if expr is ast.StructInit {
933958
var := g.local_temporaries[g.get_local_temporary('__return${idx}')]
934959
leave_expr_list << g.init_struct(var, expr)
935960
}*/
936961
var := g.local_temporaries[g.get_local_temporary('__return${idx}')]
937-
address := g.expr(expr, g.curr_ret[idx])
962+
address := g.expr(expr, typ)
938963

939-
leave_expr_list << g.blit(address, g.curr_ret[idx], binaryen.localget(g.mod,
940-
var.idx, var.typ))
964+
leave_expr_list << g.blit(address, typ, binaryen.localget(g.mod, var.idx,
965+
var.typ))
941966
} else {
942-
exprs << g.expr(expr, g.curr_ret[idx])
967+
exprs << g.expr(expr, typ)
943968
}
944969
}
945970

@@ -1179,9 +1204,11 @@ pub fn gen(files []&ast.File, table &ast.Table, out_name string, w_pref &pref.Pr
11791204
mod: binaryen.modulecreate()
11801205
}
11811206
g.table.pointer_size = 4
1182-
// Offset all pointers by 8, so that 0 never points to valid memory
1183-
g.constant_data_offset = 8
11841207
binaryen.modulesetfeatures(g.mod, binaryen.featureall())
1208+
binaryen.setlowmemoryunused(true) // Low 1KiB of memory is unused.
1209+
defer {
1210+
binaryen.moduledispose(g.mod)
1211+
}
11851212

11861213
if g.pref.os == .browser {
11871214
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
12021229
g.needs_stack = true
12031230
}
12041231
g.housekeeping()
1205-
if binaryen.modulevalidate(g.mod) {
1232+
1233+
mut valid := binaryen.modulevalidate(g.mod)
1234+
if valid {
12061235
binaryen.setdebuginfo(w_pref.is_debug)
12071236
if w_pref.is_prod {
12081237
binaryen.setoptimizelevel(3)
12091238
binaryen.moduleoptimize(g.mod)
12101239
}
1211-
if out_name == '-' {
1212-
if g.pref.is_verbose {
1213-
binaryen.moduleprint(g.mod)
1214-
} else {
1215-
binaryen.moduleprintstackir(g.mod, w_pref.is_prod)
1216-
}
1240+
}
1241+
1242+
if out_name == '-' {
1243+
if g.pref.is_verbose {
1244+
binaryen.moduleprint(g.mod)
12171245
} else {
1218-
bytes := binaryen.moduleallocateandwrite(g.mod, unsafe { nil })
1219-
str := unsafe { (&char(bytes.binary)).vstring_with_len(int(bytes.binaryBytes)) }
1220-
os.write_file(out_name, str) or { panic(err) }
1246+
binaryen.moduleprintstackir(g.mod, w_pref.is_prod)
12211247
}
1222-
} else {
1223-
binaryen.moduledispose(g.mod)
1248+
}
1249+
1250+
if !valid {
12241251
g.w_error('validation failed, this should not happen. report an issue with the above messages')
12251252
}
1226-
binaryen.moduledispose(g.mod)
1253+
1254+
if out_name != '-' {
1255+
bytes := binaryen.moduleallocateandwrite(g.mod, unsafe { nil })
1256+
str := unsafe { (&char(bytes.binary)).vstring_with_len(int(bytes.binaryBytes)) }
1257+
os.write_file(out_name, str) or { panic(err) }
1258+
}
12271259
}

vlib/v/gen/wasm/mem.v

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ fn (mut g Gen) get_or_lea_lop(lp LocalOrPointer, expected ast.Type) binaryen.Exp
142142
}
143143
}
144144

145-
if !is_expr && parent_typ == expected {
145+
if (!is_expr && parent_typ == expected) || !g.is_pure_type(expected) {
146146
return expr
147147
}
148148

@@ -235,6 +235,9 @@ fn (mut g Gen) get_var_from_expr(node ast.Expr) LocalOrPointer {
235235
ast.Ident {
236236
g.get_var_from_ident(node)
237237
}
238+
ast.ParExpr {
239+
g.get_var_from_expr(node.expr)
240+
}
238241
ast.SelectorExpr {
239242
address := g.get_var_from_expr(node.expr)
240243
offset := g.get_field_offset(node.expr_type, node.field_name)

0 commit comments

Comments
 (0)