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