| 1 | // Copyright (c) 2023 l-m.dev. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module wasm |
| 5 | |
| 6 | // Memory contract for the native WebAssembly backend |
| 7 | // --------------------------------------------------- |
| 8 | // `-b wasm` selects `-gc none` automatically (every non-C backend defaults to |
| 9 | // no_gc in v.pref.default), and autofree is off by default. Crucially, autofree |
| 10 | // code generation lives only in the C backend (v/gen/c/autofree.v); this backend |
| 11 | // never sees frontend-inserted free/drop calls, so the AST it lowers is "natural". |
| 12 | // |
| 13 | // Today the only memory mechanism is the shadow stack (__vsp, grows down). |
| 14 | // `g.func.drop()` calls in this backend are WebAssembly operand-stack drops, not |
| 15 | // memory frees. There is no malloc/free/GC. |
| 16 | // |
| 17 | // This invariant is load-bearing: a future manual allocator (__v_alloc/__v_free) |
| 18 | // and explicit dispose() will be the sole heap-management entry points, and must |
| 19 | // not collide with frontend-inserted frees. The no-frontend-free property is |
| 20 | // regression-guarded by tests_decompile/no_frontend_free_under_wasm.vv. |
| 21 | import wasm |
| 22 | import v.ast |
| 23 | import v.gen.wasm.serialise |
| 24 | import encoding.binary |
| 25 | |
| 26 | pub struct Var { |
| 27 | name string |
| 28 | mut: |
| 29 | typ ast.Type |
| 30 | idx wasm.LocalIndex |
| 31 | is_address bool |
| 32 | is_global bool |
| 33 | g_idx wasm.GlobalIndex |
| 34 | offset int |
| 35 | } |
| 36 | |
| 37 | pub fn (mut g Gen) get_var_from_ident(ident ast.Ident) Var { |
| 38 | mut obj := ident.obj |
| 39 | if obj !in [ast.Var, ast.ConstField, ast.GlobalField, ast.AsmRegister] { |
| 40 | obj = ident.scope.find(ident.name) or { g.w_error('unknown variable ${ident.name}') } |
| 41 | } |
| 42 | |
| 43 | match mut obj { |
| 44 | ast.Var { |
| 45 | if g.local_vars.len == 0 { |
| 46 | g.w_error('get_var_from_ident: g.local_vars.len == 0') |
| 47 | } |
| 48 | mut c := g.local_vars.len |
| 49 | for { |
| 50 | c-- |
| 51 | if g.local_vars[c].name == obj.name { |
| 52 | return g.local_vars[c] |
| 53 | } |
| 54 | if c == 0 { |
| 55 | break |
| 56 | } |
| 57 | } |
| 58 | g.w_error('get_var_from_ident: unreachable, variable not found') |
| 59 | } |
| 60 | ast.ConstField { |
| 61 | if gbl := g.global_vars[obj.name] { |
| 62 | return gbl.v |
| 63 | } |
| 64 | gbl := g.new_global(obj.name, obj.typ, obj.expr, false) |
| 65 | g.global_vars[obj.name] = gbl |
| 66 | return gbl.v |
| 67 | } |
| 68 | ast.GlobalField { |
| 69 | if gbl := g.global_vars[obj.name] { |
| 70 | return gbl.v |
| 71 | } |
| 72 | gbl := g.new_global(obj.name, obj.typ, obj.expr, true) |
| 73 | g.global_vars[obj.name] = gbl |
| 74 | return gbl.v |
| 75 | } |
| 76 | else { |
| 77 | g.w_error('unsupported variable type type:${obj} name:${ident.name}') |
| 78 | } |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | pub fn (mut g Gen) get_var_from_expr(node ast.Expr) ?Var { |
| 83 | match node { |
| 84 | ast.Ident { |
| 85 | return g.get_var_from_ident(node) |
| 86 | } |
| 87 | ast.ParExpr { |
| 88 | return g.get_var_from_expr(node.expr) |
| 89 | } |
| 90 | ast.CastExpr { |
| 91 | // For cast expressions like &u32(), we need to look through |
| 92 | // the cast to get the underlying variable because WASM don't have |
| 93 | // really pointers like in C |
| 94 | return g.get_var_from_expr(node.expr) |
| 95 | } |
| 96 | ast.SelectorExpr { |
| 97 | mut addr := g.get_var_from_expr(node.expr) or { |
| 98 | // if place { |
| 99 | // g.field_offset(node.expr_type, node.field_name) |
| 100 | // } |
| 101 | return none |
| 102 | } |
| 103 | addr.is_address = true |
| 104 | |
| 105 | offset := g.get_field_offset(node.expr_type, node.field_name) |
| 106 | return g.offset(addr, node.typ, offset) |
| 107 | } |
| 108 | else { |
| 109 | // g.w_error('get_var_from_expr: unexpected `${node.type_name()}`') |
| 110 | return none |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | // ONLY call this with the LHS of an assign, an lvalue |
| 116 | // use get_var_from_expr in all other cases |
| 117 | pub fn (mut g Gen) get_var_or_make_from_expr(node ast.Expr, typ ast.Type) Var { |
| 118 | if v := g.get_var_from_expr(node) { |
| 119 | return v |
| 120 | } |
| 121 | |
| 122 | mut v := g.new_local('__tmp', ast.voidptr_type) |
| 123 | g.needs_address = true |
| 124 | { |
| 125 | g.set_with_expr(node, v) |
| 126 | } |
| 127 | g.needs_address = false |
| 128 | |
| 129 | v.typ = typ |
| 130 | v.is_address = true |
| 131 | |
| 132 | return v |
| 133 | } |
| 134 | |
| 135 | pub fn (mut g Gen) bp() wasm.LocalIndex { |
| 136 | if g.bp_idx == -1 { |
| 137 | g.bp_idx = g.func.new_local_named(.i32_t, '__vbp') |
| 138 | } |
| 139 | return g.bp_idx |
| 140 | } |
| 141 | |
| 142 | pub fn (mut g Gen) sp() wasm.GlobalIndex { |
| 143 | if sp := g.sp_global { |
| 144 | return sp |
| 145 | } |
| 146 | // (64KiB - 1KiB) of stack space, grows downwards. |
| 147 | // Memory addresses from 0 to 1024 are forbidden. |
| 148 | g.sp_global = g.mod.new_global('__vsp', false, .i32_t, true, wasm.constexpr_value(0)) |
| 149 | return g.sp() |
| 150 | } |
| 151 | |
| 152 | pub fn (mut g Gen) hp() wasm.GlobalIndex { |
| 153 | if hp := g.heap_base { |
| 154 | return hp |
| 155 | } |
| 156 | hp := g.mod.new_global('__heap_base', false, .i32_t, false, wasm.constexpr_value(0)) |
| 157 | g.heap_base = hp |
| 158 | return hp |
| 159 | } |
| 160 | |
| 161 | pub fn (mut g Gen) new_local(name string, typ_ ast.Type) Var { |
| 162 | mut typ := typ_ |
| 163 | ts := g.table.sym(typ) |
| 164 | |
| 165 | match ts.info { |
| 166 | ast.Enum { |
| 167 | typ = ts.info.typ |
| 168 | } |
| 169 | ast.Alias { |
| 170 | typ = ts.info.parent_type |
| 171 | } |
| 172 | else {} |
| 173 | } |
| 174 | |
| 175 | is_address := !g.is_pure_type(typ) |
| 176 | wtyp := g.get_wasm_type(typ) |
| 177 | |
| 178 | mut v := Var{ |
| 179 | name: name |
| 180 | typ: typ |
| 181 | is_address: is_address |
| 182 | } |
| 183 | |
| 184 | if !is_address { |
| 185 | v.idx = g.func.new_local_named(wtyp, g.dbg_type_name(name, typ_)) |
| 186 | g.local_vars << v |
| 187 | return v |
| 188 | } |
| 189 | v.idx = g.bp() |
| 190 | |
| 191 | // allocate memory, then assign an offset |
| 192 | // |
| 193 | match ts.info { |
| 194 | ast.Struct, ast.ArrayFixed { |
| 195 | size, align := g.pool.type_size(typ) |
| 196 | padding := calc_padding(g.stack_frame, align) |
| 197 | address := g.stack_frame |
| 198 | g.stack_frame += size + padding |
| 199 | |
| 200 | v.offset = address |
| 201 | } |
| 202 | else { |
| 203 | g.w_error('new_local: type `${*ts}` (${ts.info.type_name()}) is not a supported local type') |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | g.local_vars << v |
| 208 | return v |
| 209 | } |
| 210 | |
| 211 | pub fn (mut g Gen) literal_to_constant_expression(typ_ ast.Type, init ast.Expr) ?wasm.ConstExpression { |
| 212 | typ := ast.mktyp(typ_) |
| 213 | match init { |
| 214 | ast.BoolLiteral { |
| 215 | return wasm.constexpr_value(int(init.val)) |
| 216 | } |
| 217 | ast.CharLiteral { |
| 218 | return wasm.constexpr_value(int(init.val.runes()[0])) |
| 219 | } |
| 220 | ast.FloatLiteral { |
| 221 | if typ == ast.f32_type { |
| 222 | return wasm.constexpr_value(init.val.f32()) |
| 223 | } else if typ == ast.f64_type { |
| 224 | return wasm.constexpr_value(init.val.f64()) |
| 225 | } |
| 226 | } |
| 227 | ast.IntegerLiteral { |
| 228 | if !typ.is_pure_int() { |
| 229 | return none |
| 230 | } |
| 231 | t := g.get_wasm_type(typ) |
| 232 | match t { |
| 233 | .i32_t { return wasm.constexpr_value(init.val.int()) } |
| 234 | .i64_t { return wasm.constexpr_value(init.val.i64()) } |
| 235 | else {} |
| 236 | } |
| 237 | } |
| 238 | ast.Ident { |
| 239 | mut obj := init.obj |
| 240 | if obj !in [ast.ConstField, ast.GlobalField] { |
| 241 | obj = init.scope.find(init.name) or { return none } |
| 242 | } |
| 243 | match mut obj { |
| 244 | ast.ConstField { |
| 245 | return g.literal_to_constant_expression(typ, obj.expr) |
| 246 | } |
| 247 | ast.GlobalField { |
| 248 | return g.literal_to_constant_expression(typ, obj.expr) |
| 249 | } |
| 250 | else { |
| 251 | return none |
| 252 | } |
| 253 | } |
| 254 | } |
| 255 | else {} |
| 256 | } |
| 257 | |
| 258 | return none |
| 259 | } |
| 260 | |
| 261 | pub fn (mut g Gen) new_global(name string, typ_ ast.Type, init ast.Expr, is_global_mut bool) Global { |
| 262 | mut typ := ast.mktyp(typ_) |
| 263 | ts := g.table.sym(typ) |
| 264 | |
| 265 | match ts.info { |
| 266 | ast.Enum { |
| 267 | typ = ts.info.typ |
| 268 | } |
| 269 | ast.Alias { |
| 270 | typ = ts.info.parent_type |
| 271 | } |
| 272 | else {} |
| 273 | } |
| 274 | |
| 275 | mut is_mut := false |
| 276 | is_address := !g.is_pure_type(typ) |
| 277 | mut init_expr := ?ast.Expr(none) |
| 278 | |
| 279 | cexpr := if cexpr_v := g.literal_to_constant_expression(unpack_literal_int(typ), init) { |
| 280 | is_mut = is_global_mut |
| 281 | cexpr_v |
| 282 | } else { |
| 283 | // Isn't a literal ... |
| 284 | if is_address { |
| 285 | // ... allocate memory and append |
| 286 | pos, is_init := g.pool.append(init, typ) |
| 287 | if !is_init { |
| 288 | // ... AND wait for init in `_vinit` |
| 289 | init_expr = init |
| 290 | } |
| 291 | wasm.constexpr_value(g.data_base + pos) |
| 292 | } else { |
| 293 | // ... wait for init in `_vinit` |
| 294 | init_expr = init |
| 295 | is_mut = true |
| 296 | |
| 297 | t := g.get_wasm_type(typ) |
| 298 | wasm.constexpr_value_zero(t) |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | mut glbl := Global{ |
| 303 | init: init_expr |
| 304 | v: Var{ |
| 305 | name: name |
| 306 | typ: typ |
| 307 | is_address: is_address |
| 308 | is_global: true |
| 309 | g_idx: g.mod.new_global(g.dbg_type_name(name, typ), false, |
| 310 | g.get_wasm_type_int_literal(typ), is_mut, cexpr) |
| 311 | } |
| 312 | } |
| 313 | |
| 314 | return glbl |
| 315 | } |
| 316 | |
| 317 | // is_pure_type(voidptr) == true |
| 318 | // is_pure_type(&Struct) == false |
| 319 | pub fn (g &Gen) is_pure_type(typ ast.Type) bool { |
| 320 | if typ.is_pure_int() || typ.is_pure_float() || typ == ast.char_type_idx |
| 321 | || typ.is_any_kind_of_pointer() || typ.is_bool() { |
| 322 | return true |
| 323 | } |
| 324 | ts := g.table.sym(typ) |
| 325 | match ts.info { |
| 326 | ast.Alias { |
| 327 | return g.is_pure_type(ts.info.parent_type) |
| 328 | } |
| 329 | ast.Enum { |
| 330 | return g.is_pure_type(ts.info.typ) |
| 331 | } |
| 332 | ast.FnType { |
| 333 | // a function value is an i32 index into the indirect function table |
| 334 | return true |
| 335 | } |
| 336 | else {} |
| 337 | } |
| 338 | |
| 339 | return false |
| 340 | } |
| 341 | |
| 342 | pub fn log2(size int) int { |
| 343 | return match size { |
| 344 | 1 { 0 } |
| 345 | 2 { 1 } |
| 346 | 4 { 2 } |
| 347 | 8 { 3 } |
| 348 | else { panic('unreachable') } |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | pub fn (mut g Gen) load(typ ast.Type, offset int) { |
| 353 | size, align := g.pool.type_size(typ) |
| 354 | wtyp := g.as_numtype(g.get_wasm_type(typ)) |
| 355 | |
| 356 | match size { |
| 357 | 1 { g.func.load8(wtyp, typ.is_signed(), log2(align), offset) } |
| 358 | 2 { g.func.load16(wtyp, typ.is_signed(), log2(align), offset) } |
| 359 | else { g.func.load(wtyp, log2(align), offset) } |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | pub fn (mut g Gen) store(typ ast.Type, offset int) { |
| 364 | size, align := g.pool.type_size(typ) |
| 365 | wtyp := g.as_numtype(g.get_wasm_type(typ)) |
| 366 | |
| 367 | match size { |
| 368 | 1 { g.func.store8(wtyp, log2(align), offset) } |
| 369 | 2 { g.func.store16(wtyp, log2(align), offset) } |
| 370 | else { g.func.store(wtyp, log2(align), offset) } |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | pub fn (mut g Gen) get(v Var) { |
| 375 | if v.is_global { |
| 376 | g.func.global_get(v.g_idx) |
| 377 | } else { |
| 378 | g.func.local_get(v.idx) |
| 379 | } |
| 380 | |
| 381 | if v.is_address && g.is_pure_type(v.typ) { |
| 382 | g.load(v.typ, v.offset) |
| 383 | } else if v.is_address && v.offset != 0 { |
| 384 | g.func.i32_const(i32(v.offset)) |
| 385 | g.func.add(.i32_t) |
| 386 | } |
| 387 | } |
| 388 | |
| 389 | pub fn (mut g Gen) mov(to Var, v Var) { |
| 390 | if !v.is_address || g.is_pure_type(v.typ) { |
| 391 | g.get(v) |
| 392 | g.cast(v.typ, to.typ) |
| 393 | g.set(to) |
| 394 | return |
| 395 | } |
| 396 | |
| 397 | size, _ := g.pool.type_size(v.typ) |
| 398 | |
| 399 | if size > 16 { |
| 400 | g.ref(to) |
| 401 | g.ref(v) |
| 402 | g.func.i32_const(i32(size)) |
| 403 | g.func.memory_copy() |
| 404 | return |
| 405 | } |
| 406 | |
| 407 | mut sz := size |
| 408 | mut oz := 0 |
| 409 | for sz > 0 { |
| 410 | g.ref_ignore_offset(to) |
| 411 | g.ref_ignore_offset(v) |
| 412 | if sz - 8 >= 0 { |
| 413 | g.load(ast.u64_type_idx, v.offset + oz) |
| 414 | g.store(ast.u64_type_idx, to.offset + oz) |
| 415 | sz -= 8 |
| 416 | oz += 8 |
| 417 | } else if sz - 4 >= 0 { |
| 418 | g.load(ast.u32_type_idx, v.offset + oz) |
| 419 | g.store(ast.u32_type_idx, to.offset + oz) |
| 420 | sz -= 4 |
| 421 | oz += 4 |
| 422 | } else if sz - 2 >= 0 { |
| 423 | g.load(ast.u16_type_idx, v.offset + oz) |
| 424 | g.store(ast.u16_type_idx, to.offset + oz) |
| 425 | sz -= 2 |
| 426 | oz += 2 |
| 427 | } else if sz - 1 >= 0 { |
| 428 | g.load(ast.u8_type_idx, v.offset + oz) |
| 429 | g.store(ast.u8_type_idx, to.offset + oz) |
| 430 | sz -= 1 |
| 431 | oz += 1 |
| 432 | } |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | pub fn (mut g Gen) set_prepare(v Var) { |
| 437 | if !v.is_address { |
| 438 | return |
| 439 | } |
| 440 | |
| 441 | if g.is_pure_type(v.typ) { |
| 442 | if v.is_global { |
| 443 | g.func.global_get(v.g_idx) |
| 444 | } else { |
| 445 | g.func.local_get(v.idx) |
| 446 | } |
| 447 | return |
| 448 | } |
| 449 | } |
| 450 | |
| 451 | pub fn (mut g Gen) set_set(v Var) { |
| 452 | if !v.is_address { |
| 453 | if v.is_global { |
| 454 | g.func.global_set(v.g_idx) |
| 455 | } else { |
| 456 | g.func.local_set(v.idx) |
| 457 | } |
| 458 | return |
| 459 | } |
| 460 | |
| 461 | if g.is_pure_type(v.typ) { |
| 462 | g.store(v.typ, v.offset) |
| 463 | return |
| 464 | } |
| 465 | |
| 466 | from := Var{ |
| 467 | typ: v.typ |
| 468 | idx: g.func.new_local_named(.i32_t, '__tmp<voidptr>') |
| 469 | is_address: v.is_address |
| 470 | } |
| 471 | |
| 472 | g.func.local_set(from.idx) |
| 473 | g.mov(v, from) |
| 474 | } |
| 475 | |
| 476 | // set structures with pointer, memcpy. |
| 477 | // set pointers with value, get local, store value. |
| 478 | // set value, set local. |
| 479 | // -- set works with a single value present on the stack beforehand |
| 480 | // -- not optimal for copying stack memory or shuffling structs |
| 481 | // -- use mov instead |
| 482 | pub fn (mut g Gen) set(v Var) { |
| 483 | if !v.is_address { |
| 484 | if v.is_global { |
| 485 | g.func.global_set(v.g_idx) |
| 486 | } else { |
| 487 | g.func.local_set(v.idx) |
| 488 | } |
| 489 | return |
| 490 | } |
| 491 | |
| 492 | if g.is_pure_type(v.typ) { |
| 493 | l := g.new_local('__tmp', v.typ) |
| 494 | g.func.local_set(l.idx) |
| 495 | |
| 496 | if v.is_global { |
| 497 | g.func.global_get(v.g_idx) |
| 498 | } else { |
| 499 | g.func.local_get(v.idx) |
| 500 | } |
| 501 | |
| 502 | g.func.local_get(l.idx) |
| 503 | |
| 504 | g.store(v.typ, v.offset) |
| 505 | return |
| 506 | } |
| 507 | |
| 508 | from := Var{ |
| 509 | typ: v.typ |
| 510 | idx: g.func.new_local_named(.i32_t, '__tmp<voidptr>') |
| 511 | is_address: v.is_address |
| 512 | } |
| 513 | |
| 514 | g.func.local_set(from.idx) |
| 515 | g.mov(v, from) |
| 516 | } |
| 517 | |
| 518 | // to satisfy inline assembly needs |
| 519 | // never used by actual codegen |
| 520 | pub fn (mut g Gen) tee(v Var) { |
| 521 | assert !v.is_global |
| 522 | |
| 523 | if !v.is_address { |
| 524 | g.func.local_tee(v.idx) |
| 525 | return |
| 526 | } |
| 527 | |
| 528 | if g.is_pure_type(v.typ) { |
| 529 | l := g.new_local('__tmp', v.typ) |
| 530 | g.func.local_tee(l.idx) // tee here, leave on stack |
| 531 | |
| 532 | g.func.local_get(v.idx) |
| 533 | g.func.local_get(l.idx) |
| 534 | g.store(v.typ, v.offset) |
| 535 | return |
| 536 | } |
| 537 | |
| 538 | from := Var{ |
| 539 | typ: v.typ |
| 540 | idx: g.func.new_local_named(.i32_t, '__tmp<voidptr>') |
| 541 | is_address: v.is_address |
| 542 | } |
| 543 | |
| 544 | g.func.local_tee(from.idx) // tee here, leave on stack |
| 545 | g.mov(v, from) |
| 546 | } |
| 547 | |
| 548 | pub fn (mut g Gen) ref(v Var) { |
| 549 | g.ref_ignore_offset(v) |
| 550 | |
| 551 | if v.offset != 0 { |
| 552 | g.func.i32_const(i32(v.offset)) |
| 553 | g.func.add(.i32_t) |
| 554 | } |
| 555 | } |
| 556 | |
| 557 | pub fn (mut g Gen) ref_ignore_offset(v Var) { |
| 558 | if !v.is_address { |
| 559 | panic('unreachable') |
| 560 | } |
| 561 | |
| 562 | if v.is_global { |
| 563 | g.func.global_get(v.g_idx) |
| 564 | } else { |
| 565 | g.func.local_get(v.idx) |
| 566 | } |
| 567 | } |
| 568 | |
| 569 | // creates a new pointer variable with the offset `offset` and type `typ` |
| 570 | pub fn (mut g Gen) offset(v Var, typ ast.Type, offset int) Var { |
| 571 | if !v.is_address { |
| 572 | panic('unreachable') |
| 573 | } |
| 574 | |
| 575 | nv := Var{ |
| 576 | ...v |
| 577 | typ: typ |
| 578 | offset: v.offset + offset |
| 579 | } |
| 580 | |
| 581 | return nv |
| 582 | } |
| 583 | |
| 584 | pub fn (mut g Gen) zero_fill(v Var, size int) { |
| 585 | assert size > 0 |
| 586 | |
| 587 | // TODO: support coalescing `zero_fill` calls together. |
| 588 | // maybe with some kind of context? |
| 589 | // |
| 590 | // ```v |
| 591 | // struct AA { |
| 592 | // a bool |
| 593 | // b int = 20 |
| 594 | // c int |
| 595 | // d int |
| 596 | // } |
| 597 | // ``` |
| 598 | // |
| 599 | // ```wast |
| 600 | // (i32.store8 |
| 601 | // (local.get $0) |
| 602 | // (i32.const 0) |
| 603 | // ) |
| 604 | // (i32.store offset=4 |
| 605 | // (i32.const 20) |
| 606 | // (local.get $0) |
| 607 | // ) ;; /- join these together. |
| 608 | // (i32.store offset=8 ;;-\ |
| 609 | // (local.get $0) ;; | |
| 610 | // (i32.const 0) ;; | |
| 611 | // ) ;; | |
| 612 | // (i32.store offset=12 ;; | |
| 613 | // (local.get $0) ;; | |
| 614 | // (i32.const 0) ;; | |
| 615 | // ) ;;-/ |
| 616 | // ``` |
| 617 | |
| 618 | if size > 16 { |
| 619 | g.ref(v) |
| 620 | g.func.i32_const(0) |
| 621 | g.func.i32_const(i32(size)) |
| 622 | g.func.memory_fill() |
| 623 | return |
| 624 | } |
| 625 | |
| 626 | mut sz := size |
| 627 | mut oz := 0 |
| 628 | for sz > 0 { |
| 629 | g.ref_ignore_offset(v) |
| 630 | if sz - 8 >= 0 { |
| 631 | g.func.i64_const(0) |
| 632 | g.store(ast.u64_type_idx, v.offset + oz) |
| 633 | sz -= 8 |
| 634 | oz += 8 |
| 635 | } else if sz - 4 >= 0 { |
| 636 | g.func.i32_const(0) |
| 637 | g.store(ast.u32_type_idx, v.offset + oz) |
| 638 | sz -= 4 |
| 639 | oz += 4 |
| 640 | } else if sz - 2 >= 0 { |
| 641 | g.func.i32_const(0) |
| 642 | g.store(ast.u16_type_idx, v.offset + oz) |
| 643 | sz -= 2 |
| 644 | oz += 2 |
| 645 | } else if sz - 1 >= 0 { |
| 646 | g.func.i32_const(0) |
| 647 | g.store(ast.u8_type_idx, v.offset + oz) |
| 648 | sz -= 1 |
| 649 | oz += 1 |
| 650 | } |
| 651 | } |
| 652 | } |
| 653 | |
| 654 | pub fn (g &Gen) is_param(v Var) bool { |
| 655 | if v.is_global { |
| 656 | return false |
| 657 | } |
| 658 | |
| 659 | return v.idx < g.fn_local_idx_end |
| 660 | } |
| 661 | |
| 662 | pub fn (mut g Gen) set_with_multi_expr(init ast.Expr, expected ast.Type, existing_rvars []Var) { |
| 663 | // misleading name: this doesn't really perform similar to `set_with_expr` |
| 664 | match init { |
| 665 | ast.ConcatExpr { |
| 666 | mut r := 0 |
| 667 | types := g.unpack_type(expected) |
| 668 | for idx, expr in init.vals { |
| 669 | typ := types[idx] |
| 670 | if g.is_param_type(typ) { |
| 671 | if rhs := g.get_var_from_expr(expr) { |
| 672 | g.mov(existing_rvars[r], rhs) |
| 673 | } else { |
| 674 | g.set_with_expr(expr, existing_rvars[r]) |
| 675 | } |
| 676 | r++ |
| 677 | } else { |
| 678 | g.expr(expr, typ) |
| 679 | } |
| 680 | } |
| 681 | } |
| 682 | ast.IfExpr { |
| 683 | g.if_expr(init, expected, existing_rvars) |
| 684 | } |
| 685 | ast.MatchExpr { |
| 686 | g.match_expr(init, expected, existing_rvars) |
| 687 | } |
| 688 | ast.CallExpr { |
| 689 | g.call_expr(init, expected, existing_rvars) |
| 690 | } |
| 691 | else { |
| 692 | if existing_rvars.len > 1 { |
| 693 | g.w_error('wasm.set_with_multi_expr(): (existing_rvars.len > 1) node: ' + |
| 694 | init.type_name()) |
| 695 | } |
| 696 | |
| 697 | if existing_rvars.len == 1 && g.is_param_type(expected) { |
| 698 | if rhs := g.get_var_from_expr(init) { |
| 699 | g.mov(existing_rvars[0], rhs) |
| 700 | } else { |
| 701 | g.set_with_expr(init, existing_rvars[0]) |
| 702 | } |
| 703 | } else { |
| 704 | g.expr(init, expected) |
| 705 | } |
| 706 | } |
| 707 | } |
| 708 | } |
| 709 | |
| 710 | pub fn (mut g Gen) set_with_expr(init ast.Expr, v Var) { |
| 711 | match init { |
| 712 | ast.StructInit { |
| 713 | size, _ := g.pool.type_size(v.typ) |
| 714 | ts := g.table.sym(v.typ) |
| 715 | ts_info := ts.info as ast.Struct |
| 716 | si := g.pool.type_struct_info(v.typ) or { panic('unreachable') } |
| 717 | |
| 718 | if init.init_fields.len == 0 && !(ts_info.fields.any(it.has_default_expr)) { |
| 719 | // Struct definition contains no default initialisers |
| 720 | // AND struct init contains no set values. |
| 721 | g.zero_fill(v, size) |
| 722 | return |
| 723 | } |
| 724 | |
| 725 | for i, f in ts_info.fields { |
| 726 | field_to_be_set := init.init_fields.map(it.name).contains(f.name) |
| 727 | |
| 728 | if !field_to_be_set { |
| 729 | offset := si.offsets[i] |
| 730 | offset_var := g.offset(v, f.typ, offset) |
| 731 | |
| 732 | fsize, _ := g.pool.type_size(f.typ) |
| 733 | |
| 734 | if f.has_default_expr { |
| 735 | g.set_with_expr(f.default_expr, offset_var) |
| 736 | } else { |
| 737 | g.zero_fill(offset_var, fsize) |
| 738 | } |
| 739 | } |
| 740 | } |
| 741 | |
| 742 | for f in init.init_fields { |
| 743 | field := ts.find_field(f.name) or { |
| 744 | g.w_error('could not find field `${f.name}` on init') |
| 745 | } |
| 746 | |
| 747 | offset := si.offsets[field.i] |
| 748 | offset_var := g.offset(v, f.expected_type, offset) |
| 749 | |
| 750 | g.set_with_expr(f.expr, offset_var) |
| 751 | } |
| 752 | } |
| 753 | ast.StringLiteral { |
| 754 | val := serialise.eval_escape_codes(init) or { panic('unreachable') } |
| 755 | str_pos := g.pool.append_string(val) |
| 756 | |
| 757 | if v.typ != ast.string_type { |
| 758 | // c'str' |
| 759 | g.set_prepare(v) |
| 760 | { |
| 761 | g.literalint(g.data_base + str_pos, ast.voidptr_type) |
| 762 | } |
| 763 | g.set_set(v) |
| 764 | return |
| 765 | } |
| 766 | |
| 767 | // init struct |
| 768 | // fields: str, len |
| 769 | g.ref(v) |
| 770 | g.literalint(g.data_base + str_pos, ast.voidptr_type) |
| 771 | g.store_field(ast.string_type, ast.voidptr_type, 'str') |
| 772 | g.ref(v) |
| 773 | g.literalint(val.len, ast.int_type) |
| 774 | g.store_field(ast.string_type, ast.int_type, 'len') |
| 775 | } |
| 776 | ast.CallExpr { |
| 777 | // `set_with_expr` is never called with a multireturn call expression |
| 778 | is_pt := g.is_param_type(v.typ) |
| 779 | |
| 780 | g.call_expr(init, v.typ, if is_pt { [v] } else { []Var{} }) |
| 781 | if !is_pt { |
| 782 | g.set(v) |
| 783 | } |
| 784 | } |
| 785 | ast.ArrayInit { |
| 786 | if !init.is_fixed { |
| 787 | g.v_error('wasm backend does not support non fixed arrays yet', init.pos) |
| 788 | } |
| 789 | |
| 790 | elm_typ := init.elem_type |
| 791 | elm_size, _ := g.pool.type_size(elm_typ) |
| 792 | |
| 793 | if !init.has_val { |
| 794 | arr_size, _ := g.pool.type_size(v.typ) |
| 795 | |
| 796 | g.zero_fill(v, arr_size) |
| 797 | return |
| 798 | } |
| 799 | |
| 800 | mut voff := g.offset(v, elm_typ, 0) // index zero |
| 801 | |
| 802 | for e in init.exprs { |
| 803 | g.set_with_expr(e, voff) |
| 804 | voff = g.offset(voff, elm_typ, elm_size) |
| 805 | } |
| 806 | } |
| 807 | else { |
| 808 | // impl of set but taken out |
| 809 | |
| 810 | if !v.is_address { |
| 811 | g.expr(init, v.typ) |
| 812 | if v.is_global { |
| 813 | g.func.global_set(v.g_idx) |
| 814 | } else { |
| 815 | g.func.local_set(v.idx) |
| 816 | } |
| 817 | return |
| 818 | } |
| 819 | |
| 820 | if g.is_pure_type(v.typ) { |
| 821 | if v.is_global { |
| 822 | g.func.global_get(v.g_idx) |
| 823 | } else { |
| 824 | g.func.local_get(v.idx) |
| 825 | } |
| 826 | g.expr(init, v.typ) |
| 827 | g.store(v.typ, v.offset) |
| 828 | return |
| 829 | } |
| 830 | |
| 831 | if var := g.get_var_from_expr(init) { |
| 832 | g.mov(v, var) |
| 833 | return |
| 834 | } |
| 835 | |
| 836 | from := Var{ |
| 837 | typ: v.typ |
| 838 | idx: g.func.new_local_named(.i32_t, '__tmp<voidptr>') |
| 839 | is_address: v.is_address // true |
| 840 | } |
| 841 | |
| 842 | g.expr(init, v.typ) |
| 843 | g.func.local_set(from.idx) |
| 844 | g.mov(v, from) |
| 845 | } |
| 846 | } |
| 847 | } |
| 848 | |
| 849 | pub fn calc_padding(value int, alignment int) int { |
| 850 | if alignment == 0 { |
| 851 | return value |
| 852 | } |
| 853 | return (alignment - value % alignment) % alignment |
| 854 | } |
| 855 | |
| 856 | pub fn calc_align(value int, alignment int) int { |
| 857 | if alignment == 0 { |
| 858 | return value |
| 859 | } |
| 860 | return (value + alignment - 1) / alignment * alignment |
| 861 | } |
| 862 | |
| 863 | pub fn (mut g Gen) make_vinit() { |
| 864 | g.func = g.mod.new_function('_vinit', [], []) |
| 865 | func_start := g.func.patch_pos() |
| 866 | { |
| 867 | for mod_name in g.table.modules { |
| 868 | if mod_name == 'v.reflection' { |
| 869 | g.w_error('the wasm backend does not implement `v.reflection` yet') |
| 870 | } |
| 871 | init_fn_name := if mod_name != 'builtin' { '${mod_name}.init' } else { 'init' } |
| 872 | if _ := g.table.find_fn(init_fn_name) { |
| 873 | g.func.call(init_fn_name) |
| 874 | } |
| 875 | cleanup_fn_name := if mod_name != 'builtin' { '${mod_name}.cleanup' } else { 'cleanup' } |
| 876 | if _ := g.table.find_fn(cleanup_fn_name) { |
| 877 | g.func.call(cleanup_fn_name) |
| 878 | } |
| 879 | } |
| 880 | for _, gv in g.global_vars { |
| 881 | if init := gv.init { |
| 882 | g.set_with_expr(init, gv.v) |
| 883 | } |
| 884 | } |
| 885 | g.bare_function_frame(func_start) |
| 886 | } |
| 887 | g.mod.commit(g.func, false) |
| 888 | g.bare_function_end() |
| 889 | } |
| 890 | |
| 891 | pub fn (mut g Gen) housekeeping() { |
| 892 | g.make_vinit() |
| 893 | |
| 894 | // Compile any pending non-capturing anonymous functions (their bodies may |
| 895 | // reference further anon fns, so loop until the queue drains), then declare |
| 896 | // and populate the indirect function table used by `call_indirect`. |
| 897 | for g.pending_anon_fns.len > 0 { |
| 898 | g.fn_decl(g.pending_anon_fns.pop()) |
| 899 | } |
| 900 | // Declare the indirect function table whenever a `call_indirect` was emitted, |
| 901 | // even if no function value was registered (e.g. a module that only consumes a |
| 902 | // callback), otherwise the emitted `call_indirect` references a table that does |
| 903 | // not exist and the module fails to validate. |
| 904 | if g.uses_call_indirect || g.fn_value_indices.len > 0 { |
| 905 | // names[i] holds the function for table slot `i + 1`; slot 0 is the |
| 906 | // reserved null/trap slot (left as `ref.null func`). |
| 907 | mut names := []string{len: g.fn_value_indices.len} |
| 908 | for name, idx in g.fn_value_indices { |
| 909 | names[idx - 1] = name |
| 910 | } |
| 911 | // +1 for the reserved null slot at index 0 |
| 912 | t := |
| 913 | g.mod.assign_table('__indirect_function_table', false, .funcref_t, u32(names.len + 1), none) |
| 914 | if names.len > 0 { |
| 915 | g.mod.new_active_element(t, 1, names) |
| 916 | } |
| 917 | } |
| 918 | |
| 919 | heap_base := calc_align(g.data_base + g.pool.buf.len, 16) // 16? |
| 920 | page_boundary := calc_align(g.data_base + g.pool.buf.len, 64 * 1024) |
| 921 | preallocated_pages := page_boundary / (64 * 1024) |
| 922 | |
| 923 | if g.pref.is_verbose { |
| 924 | eprintln('housekeeping(): acceptable addresses are > 1024') |
| 925 | eprintln('housekeeping(): stack top: ${g.stack_top}, data_base: ${g.data_base} (size: ${g.pool.buf.len}), heap_base: ${heap_base}') |
| 926 | eprintln('housekeeping(): preallocated pages: ${preallocated_pages}') |
| 927 | } |
| 928 | |
| 929 | if sp := g.sp_global { |
| 930 | g.mod.assign_global_init(sp, wasm.constexpr_value(g.stack_top)) |
| 931 | } |
| 932 | if g.sp_global != none || g.pool.buf.len > 0 { |
| 933 | g.mod.assign_memory('memory', true, u32(preallocated_pages), none) |
| 934 | if g.pool.buf.len > 0 { |
| 935 | mut buf := g.pool.buf.clone() |
| 936 | |
| 937 | for reloc in g.pool.relocs { |
| 938 | binary.little_endian_put_u32_at(mut buf, u32(g.data_base + reloc.offset), reloc.pos) |
| 939 | } |
| 940 | g.mod.new_data_segment(none, g.data_base, buf) |
| 941 | } |
| 942 | } |
| 943 | if hp := g.heap_base { |
| 944 | g.mod.assign_global_init(hp, wasm.constexpr_value(heap_base)) |
| 945 | } |
| 946 | |
| 947 | if g.pref.os == .wasi && !g.pref.is_shared { |
| 948 | mut fn_start := g.mod.new_function('_start', [], []) |
| 949 | { |
| 950 | fn_start.call('_vinit') |
| 951 | fn_start.call('main.main') |
| 952 | } |
| 953 | g.mod.commit(fn_start, true) |
| 954 | } else { |
| 955 | g.mod.assign_start('_vinit') |
| 956 | } |
| 957 | } |
| 958 | |