emit.c (186276B)
1 /* Wasm CGTarget emission. 2 * 3 * Records CGTarget operations into a per-function WIR list, then linearizes 4 * to a WasmFunc body at func_end. Each SSA Reg becomes a Wasm local; control 5 * flow that fits the kit_cg_if_begin/else/end pattern lowers to 6 * if/else/end, while CG scopes (SCOPE_LOOP) lower to (block (loop ...)). 7 * 8 * TODO: complete IR-to-WASM coverage for: 9 * - bitfield load/store 10 * - multiple call/return results 11 * - address-taken parameters 12 * - ABI multipart params 13 * - dynamic memcpy/memset via memory.* calls 14 * - file-scope asm 15 * - atomics 16 * - intrinsics 17 */ 18 19 #include <stdarg.h> 20 #include <string.h> 21 22 #include "abi/abi.h" 23 #include "arch/wasm/internal.h" 24 #include "cg/type.h" 25 #include "core/arena.h" 26 #include "core/buf.h" 27 #include "core/heap.h" 28 #include "core/pool.h" 29 #include "obj/obj.h" 30 #include "obj/wasm_imports.h" 31 32 /* Shared Wasm core: in-memory WasmModule, helpers (wasm_add_func, 33 * wasm_intern_func_type, wasm_func_add_insn, ...), and wasm_encode for 34 * the final flush from emit_wasm. */ 35 #include "wasm/wasm.h" 36 37 /* ----------------------------------------------------------------- 38 * Helpers 39 * ----------------------------------------------------------------- */ 40 41 static SrcLoc cur_loc(WTarget* t) { 42 /* Prefer the most recent statement loc the frontend reported via 43 * wasm_set_loc — gives diagnostics the actual failing line, not the 44 * function definition's line. Fall back to the function loc when no 45 * statement loc has been set (line == 0). */ 46 if (t->cur_stmt_loc.line) return t->cur_stmt_loc; 47 if (t->cur_fn_desc) return t->cur_fn_desc->loc; 48 SrcLoc l = {0, 0, 0}; 49 return l; 50 } 51 52 static _Noreturn void wfail(WTarget* t, const char* fmt, ...) { 53 va_list ap; 54 va_start(ap, fmt); 55 compiler_panicv(t->c, cur_loc(t), fmt, ap); 56 } 57 58 static _Noreturn void wfail_at(WTarget* t, SrcLoc loc, const char* fmt, ...) { 59 va_list ap; 60 va_start(ap, fmt); 61 compiler_panicv(t->c, loc, fmt, ap); 62 } 63 64 static struct WasmModule* ensure_module(WTarget* t); 65 66 static const char* pool_sym_cstr(Pool* p, Sym sym, size_t* len_out) { 67 Slice sl = pool_slice(p, sym); 68 if (len_out) *len_out = sl.len; 69 return sl.s; 70 } 71 72 static WasmValType valtype_for_size_kind(WTarget* t, u32 size, u8 scalar_kind) { 73 if (scalar_kind == ABI_SC_FLOAT) { 74 if (size == 4) return WASM_VAL_F32; 75 if (size == 8) return WASM_VAL_F64; 76 /* The only C float wider than f64 is binary128 long double, which has 77 * no wasm value type. Report it specifically rather than as a generic 78 * size error. */ 79 if (size == 16) wfail(t, "wasm: long double not supported"); 80 wfail(t, "wasm: unsupported float size %u", size); 81 } 82 if (size <= 4) return WASM_VAL_I32; 83 if (size == 8) return WASM_VAL_I64; 84 wfail(t, "wasm: unsupported integer size %u", size); 85 } 86 87 static WasmValType valtype_for_type(WTarget* t, KitCgTypeId ty) { 88 ABITypeInfo ti = abi_cg_type_info(t->c->abi, ty); 89 if (ti.scalar_kind == ABI_SC_VOID) { 90 wfail(t, "wasm: void value type requested"); 91 } 92 if (ti.scalar_kind == ABI_SC_PTR) return WASM_VAL_I32; /* wasm32 ILP32 */ 93 return valtype_for_size_kind(t, ti.size, ti.scalar_kind); 94 } 95 96 static u32 align_to_u32(u32 v, u32 a) { 97 if (!a) return v; 98 return (v + a - 1u) & ~(a - 1u); 99 } 100 101 /* Low-memory guard: leaves the first NULL_GUARD bytes of linear memory 102 * unassigned so addr==0 never resolves to a real symbol. */ 103 #define WASM_DATA_NULL_GUARD 16u 104 105 static void type_size_align(WTarget* t, KitCgTypeId ty, u32 fallback_size, 106 u32 fallback_align, u32* size_out, u32* align_out) { 107 ABITypeInfo ti; 108 if (ty) { 109 ti = abi_cg_type_info(t->c->abi, ty); 110 *size_out = ti.size ? ti.size : fallback_size; 111 *align_out = ti.align ? ti.align : fallback_align; 112 } else { 113 *size_out = fallback_size; 114 *align_out = fallback_align; 115 } 116 if (!*size_out) *size_out = 1; 117 if (!*align_out) *align_out = 1; 118 } 119 120 static u32 add_wasm_local(WTarget* t, WasmValType vt) { 121 if (!t->cur_func) wfail(t, "wasm: local allocation outside a function"); 122 return wasm_func_push_local(t->c, t->module, t->cur_func, vt); 123 } 124 125 static void ensure_linear_memory(WTarget* t) { 126 ensure_module(t); 127 if (!t->has_memory) { 128 WasmMemory* mem = wasm_add_memory(t->c, t->module); 129 mem->min_pages = 1; 130 t->has_memory = 1; 131 } 132 } 133 134 /* Atomic memory ops require the linear memory to be declared shared and to 135 * carry a maximum size. We promote the (single) module memory to shared on 136 * first atomic emission. max_pages is provisionally set to the wasm32 limit 137 * (65536, i.e. 4 GiB); wasm_materialize_data tightens it to match min_pages 138 * after the final layout is known so embedders can pre-reserve a snug arena. */ 139 static void ensure_shared_memory(WTarget* t) { 140 ensure_linear_memory(t); 141 WasmMemory* mem = &t->module->memories[0]; 142 if (!mem->shared) { 143 mem->shared = 1; 144 } 145 if (!mem->has_max) { 146 mem->has_max = 1; 147 mem->max_pages = 65536u; 148 } 149 } 150 151 static void ensure_stack_pointer(WTarget* t) { 152 ensure_linear_memory(t); 153 if (!t->has_stack_pointer) { 154 WasmGlobal* g = wasm_add_global(t->c, t->module); 155 g->name = wasm_strdup(t->module->heap, "__stack_pointer", 156 sizeof("__stack_pointer") - 1u); 157 g->type = WASM_VAL_I32; 158 g->mutable_ = 1; 159 g->init.kind = WASM_INSN_I32_CONST; 160 g->init.imm = 65536; 161 t->stack_pointer_global = t->module->nglobals - 1u; 162 t->stack_size = 65536; 163 t->has_stack_pointer = 1; 164 } 165 } 166 167 /* Map an SSA Reg to its WasmFunc local index, allocating on first use. */ 168 static u32 reg_local(WTarget* t, Reg r, KitCgTypeId ty, RegClass cls) { 169 Heap* h = t->c->ctx->heap; 170 if (r == REG_NONE) wfail(t, "wasm: REG_NONE used as operand"); 171 if (r >= t->reg_cap) { 172 u32 nc = t->reg_cap ? t->reg_cap : 16u; 173 while (nc <= r) nc *= 2u; 174 u32* nl = (u32*)h->realloc(h, t->reg_to_local, sizeof(u32) * t->reg_cap, 175 sizeof(u32) * nc, _Alignof(u32)); 176 KitCgTypeId* nt = (KitCgTypeId*)h->realloc( 177 h, t->reg_type, sizeof(KitCgTypeId) * t->reg_cap, 178 sizeof(KitCgTypeId) * nc, _Alignof(KitCgTypeId)); 179 u8* nc_arr = (u8*)h->realloc(h, t->reg_cls, t->reg_cap, nc, 1); 180 if (!nl || !nt || !nc_arr) wfail(t, "wasm: out of memory"); 181 for (u32 i = t->reg_cap; i < nc; ++i) { 182 nl[i] = 0xffffffffu; 183 nt[i] = KIT_CG_TYPE_NONE; 184 nc_arr[i] = 0; 185 } 186 t->reg_to_local = nl; 187 t->reg_type = nt; 188 t->reg_cls = nc_arr; 189 t->reg_cap = nc; 190 } 191 /* CG may reuse the same Reg id with different value types: api_ensure_reg 192 * for an SV_CMP reuses one of the cmp operands' Regs (originally e.g. i64) 193 * to hold the i32 cmp result. Detect a type change and rebind to a fresh 194 * wasm local; the previous binding is dead from CG's point of view. */ 195 if (t->reg_to_local[r] != 0xffffffffu && t->reg_type[r]) { 196 WasmValType cached_vt = valtype_for_type(t, t->reg_type[r]); 197 WasmValType want_vt = valtype_for_type(t, ty); 198 if (cached_vt == want_vt) return t->reg_to_local[r]; 199 /* fall through to allocate fresh */ 200 } 201 { 202 WasmValType vt = valtype_for_type(t, ty); 203 t->reg_to_local[r] = add_wasm_local(t, vt); 204 t->reg_type[r] = ty; 205 t->reg_cls[r] = (u8)cls; 206 if (r + 1u > t->reg_hwm) t->reg_hwm = r + 1u; 207 } 208 return t->reg_to_local[r]; 209 } 210 211 /* ----------------------------------------------------------------- 212 * WIR appending 213 * ----------------------------------------------------------------- */ 214 215 static WIR* wir_push(WTarget* t) { 216 Heap* h = t->c->ctx->heap; 217 if (t->nwir == t->wir_cap) { 218 u32 nc = t->wir_cap ? t->wir_cap * 2u : 64u; 219 void* p = h->realloc(h, t->wir, sizeof(WIR) * t->wir_cap, sizeof(WIR) * nc, 220 _Alignof(WIR)); 221 if (!p) wfail(t, "wasm: out of memory"); 222 t->wir = (WIR*)p; 223 t->wir_cap = nc; 224 } 225 WIR* w = &t->wir[t->nwir++]; 226 memset(w, 0, sizeof *w); 227 return w; 228 } 229 230 /* Operand-kind encoding stored in WIR's imm_kind / imm_kind_b. */ 231 enum { 232 WOP_REG = 0, 233 WOP_IMM = 1, 234 WOP_LOCAL = 2, 235 WOP_WASM_LOCAL = 3, 236 WOP_ADDR = 4 237 }; 238 239 static void wir_capture_operand(WIR* w, int which, Operand op) { 240 u32 kind; 241 i64 ival; 242 Reg r = REG_NONE; 243 switch (op.kind) { 244 case OPK_REG: 245 kind = WOP_REG; 246 r = op.v.reg; 247 ival = 0; 248 break; 249 case OPK_IMM: 250 kind = WOP_IMM; 251 ival = op.v.imm; 252 break; 253 case OPK_LOCAL: 254 kind = WOP_LOCAL; 255 ival = (i64)op.v.frame_slot; 256 break; 257 default: 258 kind = 99u; 259 ival = 0; 260 break; 261 } 262 if (which == 0) { 263 w->imm_kind = kind; 264 w->imm_a = ival; 265 w->a = r; 266 } else { 267 w->imm_kind_b = kind; 268 w->imm_b = ival; 269 w->b = r; 270 } 271 } 272 273 /* ----------------------------------------------------------------- 274 * Labels 275 * ----------------------------------------------------------------- */ 276 277 Label wasm_label_new(CGTarget* tg) { 278 WTarget* t = (WTarget*)tg; 279 Heap* h = t->c->ctx->heap; 280 if (t->nlabels == t->labels_cap) { 281 u32 nc = t->labels_cap ? t->labels_cap * 2u : 16u; 282 void* p = h->realloc(h, t->labels, sizeof(WLabel) * t->labels_cap, 283 sizeof(WLabel) * nc, _Alignof(WLabel)); 284 if (!p) wfail(t, "wasm: out of memory"); 285 t->labels = (WLabel*)p; 286 t->labels_cap = nc; 287 } 288 u32 id = t->nlabels++; 289 memset(&t->labels[id], 0, sizeof t->labels[id]); 290 return (Label)(id + 1u); 291 } 292 293 static WLabel* lookup_label(WTarget* t, Label l) { 294 if (l == LABEL_NONE || l - 1u >= t->nlabels) return NULL; 295 return &t->labels[l - 1u]; 296 } 297 298 void wasm_label_place(CGTarget* tg, Label l) { 299 WTarget* t = (WTarget*)tg; 300 WLabel* lbl = lookup_label(t, l); 301 if (!lbl) wfail(t, "wasm: label_place on unknown label"); 302 /* If this label is registered to a scope, the scope ops drive the wasm 303 * structure — placement here is a no-op. */ 304 if (lbl->kind == WLBL_SCOPE_BREAK || lbl->kind == WLBL_SCOPE_CONT) { 305 lbl->placed = 1; 306 t->dead = 0; 307 return; 308 } 309 /* Free-standing label placement: record but emit nothing. The CG layer 310 * sometimes places scope continue/break labels just before/after a 311 * scope_begin/end pair; for wasm the structured scope ops drive the 312 * `br N` targets, so these placements are no-ops. A subsequent jump or 313 * cmp_branch that lands on an unbound label will diagnose. */ 314 lbl->placed = 1; 315 lbl->wir_index = t->nwir; 316 if (lbl->kind == WLBL_UNBOUND) lbl->kind = WLBL_FORWARD; 317 WIR* w = wir_push(t); 318 w->op = WIR_LABEL; 319 w->labels[0] = l; 320 t->dead = 0; 321 } 322 323 void wasm_jump(CGTarget* tg, Label l) { 324 WTarget* t = (WTarget*)tg; 325 if (t->dead) return; 326 WLabel* lbl = lookup_label(t, l); 327 if (!lbl) wfail(t, "wasm: jump to unknown label"); 328 WIR* w = wir_push(t); 329 w->op = WIR_JUMP; 330 w->labels[0] = l; 331 t->dead = 1; 332 } 333 334 void wasm_cmp_branch(CGTarget* tg, CmpOp op, Operand a, Operand b, Label l) { 335 WTarget* t = (WTarget*)tg; 336 if (t->dead) return; 337 if (!lookup_label(t, l)) wfail(t, "wasm: cmp_branch to unknown label"); 338 WIR* w = wir_push(t); 339 w->op = WIR_CMP_BRANCH; 340 w->cgop = (u8)op; 341 wir_capture_operand(w, 0, a); 342 wir_capture_operand(w, 1, b); 343 w->type = a.type ? a.type : b.type; 344 w->labels[0] = l; 345 } 346 347 void wasm_switch(CGTarget* tg, const CGSwitchDesc* d) { 348 WTarget* t = (WTarget*)tg; 349 WIR* w; 350 if (t->dead) return; 351 if (!d) wfail(t, "wasm: switch without descriptor"); 352 if (d->default_label == LABEL_NONE) 353 wfail(t, "wasm: switch without default label"); 354 if (d->ncases && !d->cases) wfail(t, "wasm: switch case count without cases"); 355 if (d->selector.kind != OPK_REG && d->selector.kind != OPK_IMM && 356 d->selector.kind != OPK_LOCAL) 357 wfail(t, "wasm: switch selector has unsupported operand kind"); 358 if (!lookup_label(t, d->default_label)) 359 wfail(t, "wasm: switch default label is unknown"); 360 for (u32 i = 0; i < d->ncases; ++i) { 361 if (!lookup_label(t, d->cases[i].label)) 362 wfail(t, "wasm: switch case label is unknown"); 363 } 364 365 w = wir_push(t); 366 w->op = WIR_SWITCH; 367 wir_capture_operand(w, 0, d->selector); 368 w->type = d->selector_type; 369 w->labels[0] = d->default_label; 370 w->switch_ncases = d->ncases; 371 if (d->ncases) { 372 Heap* h = t->c->ctx->heap; 373 w->switch_cases = (CGSwitchCase*)h->alloc( 374 h, sizeof(CGSwitchCase) * d->ncases, _Alignof(CGSwitchCase)); 375 if (!w->switch_cases) wfail(t, "wasm: out of memory"); 376 memcpy(w->switch_cases, d->cases, sizeof(CGSwitchCase) * d->ncases); 377 } 378 t->dead = 1; 379 } 380 381 /* ----------------------------------------------------------------- 382 * Scopes 383 * ----------------------------------------------------------------- */ 384 385 CGScope wasm_scope_begin(CGTarget* tg, const CGScopeDesc* d) { 386 WTarget* t = (WTarget*)tg; 387 if (t->nscopes >= 32u) wfail(t, "wasm: too many nested scopes (max 32)"); 388 WScope* s = &t->scopes[t->nscopes]; 389 memset(s, 0, sizeof *s); 390 s->id = ++t->next_scope_id; 391 s->cg_kind = d->kind; 392 s->break_lbl = d->break_label; 393 s->cont_lbl = d->continue_label; 394 s->result_type = d->result_type; 395 396 /* Wire scope's break/continue labels to this scope so jump()/cmp_branch() 397 * to them can lower to wasm `br`. */ 398 if (d->break_label != LABEL_NONE) { 399 WLabel* lbl = lookup_label(t, d->break_label); 400 if (lbl) { 401 lbl->kind = WLBL_SCOPE_BREAK; 402 lbl->scope_id = s->id; 403 } 404 } 405 if (d->continue_label != LABEL_NONE) { 406 WLabel* lbl = lookup_label(t, d->continue_label); 407 if (lbl) { 408 lbl->kind = WLBL_SCOPE_CONT; 409 lbl->scope_id = s->id; 410 } 411 } 412 413 WIR* open = wir_push(t); 414 open->op = WIR_SCOPE_OPEN; 415 open->scope_id = s->id; 416 open->cgop = d->kind; 417 open->dst = REG_NONE; 418 419 s->placed_in_wir = 1; 420 t->nscopes++; 421 return (CGScope)s->id; 422 } 423 424 static WScope* scope_by_id(WTarget* t, u32 id) { 425 for (u32 i = 0; i < t->nscopes; ++i) { 426 if (t->scopes[i].id == id) return &t->scopes[i]; 427 } 428 return NULL; 429 } 430 431 void wasm_scope_end(CGTarget* tg, CGScope sc) { 432 WTarget* t = (WTarget*)tg; 433 WScope* s = scope_by_id(t, (u32)sc); 434 if (!s) wfail(t, "wasm: scope_end on unknown scope"); 435 WIR* w = wir_push(t); 436 w->op = WIR_SCOPE_CLOSE; 437 w->scope_id = s->id; 438 /* Pop the scope from the stack. CG always closes in LIFO order. */ 439 if (t->nscopes == 0 || t->scopes[t->nscopes - 1u].id != s->id) 440 wfail(t, "wasm: scope_end out of LIFO order"); 441 t->nscopes--; 442 t->dead = 0; 443 } 444 445 void wasm_break_to(CGTarget* tg, CGScope sc) { 446 WTarget* t = (WTarget*)tg; 447 if (t->dead) return; 448 WScope* s = scope_by_id(t, (u32)sc); 449 if (!s) wfail(t, "wasm: break_to unknown scope"); 450 WIR* w = wir_push(t); 451 w->op = WIR_JUMP; 452 w->labels[0] = s->break_lbl; 453 t->dead = 1; 454 } 455 456 void wasm_continue_to(CGTarget* tg, CGScope sc) { 457 WTarget* t = (WTarget*)tg; 458 if (t->dead) return; 459 WScope* s = scope_by_id(t, (u32)sc); 460 if (!s) wfail(t, "wasm: continue_to unknown scope"); 461 WIR* w = wir_push(t); 462 w->op = WIR_JUMP; 463 w->labels[0] = s->cont_lbl; 464 t->dead = 1; 465 } 466 467 /* ----------------------------------------------------------------- 468 * Function lifecycle 469 * ----------------------------------------------------------------- */ 470 471 /* Forward decl — promotes an undefined function symbol's WasmFunc to an 472 * import using the supplied ABI to build the wasm signature. */ 473 static void promote_import_func(WTarget* t, ObjSymId sym, WasmFunc* f, 474 const ABIFuncInfo* abi); 475 476 /* Lookup or allocate a Wasm function index for an ObjSymId. Returns 477 * (wasm_func_idx, *out_func) on success. */ 478 static u32 sym_to_wasm_func(WTarget* t, ObjSymId sym, WasmFunc** out_func) { 479 Heap* h = t->c->ctx->heap; 480 if (sym >= t->sym_to_func_cap) { 481 u32 nc = t->sym_to_func_cap ? t->sym_to_func_cap : 16u; 482 while (nc <= sym) nc *= 2u; 483 u32* p = 484 (u32*)h->realloc(h, t->sym_to_func, sizeof(u32) * t->sym_to_func_cap, 485 sizeof(u32) * nc, _Alignof(u32)); 486 if (!p) wfail(t, "wasm: out of memory"); 487 for (u32 i = t->sym_to_func_cap; i < nc; ++i) p[i] = 0; 488 t->sym_to_func = p; 489 t->sym_to_func_cap = nc; 490 } 491 if (t->sym_to_func[sym]) { 492 u32 idx = t->sym_to_func[sym] - 1u; 493 if (out_func) *out_func = &t->module->funcs[idx]; 494 return idx; 495 } 496 /* Create a fresh WasmFunc and link. */ 497 WasmFunc* f = wasm_add_func(t->c, t->module); 498 u32 idx = t->module->nfuncs - 1u; 499 t->sym_to_func[sym] = idx + 1u; 500 if (out_func) *out_func = f; 501 return idx; 502 } 503 504 static WSlot* slot_push(WTarget* t) { 505 Heap* h = t->c->ctx->heap; 506 if (t->nslots == t->slots_cap) { 507 u32 nc = t->slots_cap ? t->slots_cap * 2u : 16u; 508 WSlot* ns = (WSlot*)h->realloc(h, t->slots, sizeof(WSlot) * t->slots_cap, 509 sizeof(WSlot) * nc, _Alignof(WSlot)); 510 if (!ns) wfail(t, "wasm: out of memory"); 511 t->slots = ns; 512 t->slots_cap = nc; 513 } 514 WSlot* s = &t->slots[t->nslots++]; 515 memset(s, 0, sizeof *s); 516 return s; 517 } 518 519 /* True iff `ty` maps to a single wasm value type (i32/i64/f32/f64) and so can 520 * live directly in a wasm local. Aggregates (records/arrays) and anything 521 * wider than 8 bytes must be homed in linear memory. */ 522 static int type_is_wasm_scalar(WTarget* t, KitCgTypeId ty) { 523 ABITypeInfo ti; 524 if (!ty) return 0; 525 ti = abi_cg_type_info(t->c->abi, ty); 526 if (ti.scalar_kind == ABI_SC_VOID) return 0; 527 if (ti.scalar_kind == ABI_SC_PTR) return 1; 528 return ti.size <= 8u; 529 } 530 531 static FrameSlot alloc_frame_slot_kind(WTarget* t, KitCgTypeId ty, u32 size, 532 u32 align, int stack_backed) { 533 WSlot* s; 534 u32 slot_id; 535 type_size_align(t, ty, size, align, &size, &align); 536 /* A non-scalar type has no wasm value type; force it into linear memory. */ 537 if (!stack_backed && !type_is_wasm_scalar(t, ty)) stack_backed = 1; 538 s = slot_push(t); 539 s->type = ty; 540 s->size = size; 541 s->align = align; 542 if (stack_backed) { 543 ensure_stack_pointer(t); 544 t->frame_size = align_to_u32(t->frame_size, align); 545 s->kind = W_SLOT_STACK; 546 s->frame_offset = t->frame_size; 547 t->frame_size += size; 548 if (align > t->frame_align) t->frame_align = align; 549 t->has_stack_frame = 1; 550 } else { 551 s->kind = W_SLOT_LOCAL; 552 s->wasm_local = add_wasm_local(t, valtype_for_type(t, ty)); 553 } 554 slot_id = t->nslots; 555 return (FrameSlot)slot_id; 556 } 557 558 static WSlot* slot_for(WTarget* t, FrameSlot fs) { 559 if (fs == FRAME_SLOT_NONE) wfail(t, "wasm: FRAME_SLOT_NONE used"); 560 u32 idx = fs - 1u; 561 if (idx >= t->nslots) wfail(t, "wasm: bad frame slot id"); 562 return &t->slots[idx]; 563 } 564 565 static void promote_slot_to_stack(WTarget* t, WSlot* s) { 566 if (s->kind == W_SLOT_STACK) return; 567 ensure_stack_pointer(t); 568 t->frame_size = align_to_u32(t->frame_size, s->align ? s->align : 1u); 569 s->frame_offset = t->frame_size; 570 t->frame_size += s->size ? s->size : 1u; 571 if (s->align > t->frame_align) t->frame_align = s->align; 572 s->kind = W_SLOT_STACK; 573 t->has_stack_frame = 1; 574 } 575 576 void wasm_func_begin(CGTarget* tg, const CGFuncDesc* d) { 577 WTarget* t = (WTarget*)tg; 578 WasmFunc* f; 579 u32 idx; 580 const CgType* fnty; 581 const ABIFuncInfo* abi; 582 Heap* h = t->c->ctx->heap; 583 584 ensure_module(t); 585 t->cur_fn_desc = d; 586 memset(&t->cur_stmt_loc, 0, sizeof t->cur_stmt_loc); 587 t->nwir = 0; 588 t->nlabels = 0; 589 t->nslots = 0; 590 t->nscopes = 0; 591 t->next_scope_id = 0; 592 t->frame_size = 0; 593 t->frame_align = 1; 594 t->frame_base_local = 0xffffffffu; 595 t->frame_saved_sp_local = 0xffffffffu; 596 t->has_stack_frame = 0; 597 t->dead = 0; 598 t->sret_param_local = 0xffffffffu; 599 t->va_ptr_param_local = 0xffffffffu; 600 t->cur_has_sret = 0; 601 t->cur_is_variadic = 0; 602 t->varcall_saved_sp_local = 0xffffffffu; 603 t->varcall_buf_local = 0xffffffffu; 604 t->va_arg_tmp_addr_local = 0xffffffffu; 605 t->nparams_cg = 0; 606 t->nbyval_copies = 0; 607 /* Wipe reg map. Only [0, reg_hwm) can hold a non-sentinel binding from a 608 * prior function — reg_cap grows monotonically but slots beyond the 609 * high-water mark are sentinel-initialized at grow time and never bound, so 610 * wiping the full reg_cap each function is wasted work on large TUs. */ 611 for (u32 i = 0; i < t->reg_hwm; ++i) t->reg_to_local[i] = 0xffffffffu; 612 613 idx = sym_to_wasm_func(t, d->sym, &f); 614 t->cur_func_idx = idx; 615 t->cur_func = f; 616 617 fnty = cg_type_get(t->c, d->fn_type); 618 if (!fnty || fnty->kind != KIT_CG_TYPE_FUNC) 619 wfail(t, "wasm: func_begin without function type"); 620 abi = d->abi; 621 if (!abi) wfail(t, "wasm: func_begin with no ABI info"); 622 623 /* Build the wasm function's param layout: 624 * [sret_ptr]? [param_0] [param_1] ... 625 * with IGNORE'd CG params dropped. Record per-CG-param the wasm-local 626 * index so wasm_param can place each frame slot on the right local. 627 * params/locals/local_names grow on demand — no fixed cap. */ 628 f->nparams = 0; 629 if (abi->has_sret) { 630 t->sret_param_local = 631 wasm_func_push_param(t->c, t->module, f, WASM_VAL_I32); 632 t->cur_has_sret = 1; 633 ensure_linear_memory(t); 634 } 635 if (fnty->func.nparams > t->param_local_idx_cap) { 636 u32 nc = t->param_local_idx_cap ? t->param_local_idx_cap : 4u; 637 while (nc < fnty->func.nparams) nc *= 2u; 638 u32* p = (u32*)h->realloc(h, t->param_local_idx, 639 sizeof(u32) * t->param_local_idx_cap, 640 sizeof(u32) * nc, _Alignof(u32)); 641 if (!p) wfail(t, "wasm: out of memory"); 642 t->param_local_idx = p; 643 t->param_local_idx_cap = nc; 644 } 645 t->nparams_cg = fnty->func.nparams; 646 for (u32 i = 0; i < fnty->func.nparams; ++i) { 647 const ABIArgInfo* ai = &abi->params[i]; 648 if (ai->kind == ABI_ARG_IGNORE) { 649 t->param_local_idx[i] = 0xffffffffu; 650 continue; 651 } 652 if (ai->kind == ABI_ARG_INDIRECT) { 653 t->param_local_idx[i] = 654 wasm_func_push_param(t->c, t->module, f, WASM_VAL_I32); 655 ensure_linear_memory(t); 656 } else { 657 if (ai->nparts != 1) 658 wfail(t, "wasm: multi-part DIRECT param %u not yet implemented", i); 659 const ABIArgPart* p = &ai->parts[0]; 660 WasmValType vt = valtype_for_size_kind( 661 t, p->size, p->cls == ABI_CLASS_FP ? ABI_SC_FLOAT : ABI_SC_INT); 662 t->param_local_idx[i] = wasm_func_push_param(t->c, t->module, f, vt); 663 } 664 } 665 /* Variadic: append hidden i32 va_ptr trailing param. Must match 666 * abi_to_wasm_func_type so indirect calls' signature interning agrees. */ 667 if (abi->variadic) { 668 t->va_ptr_param_local = 669 wasm_func_push_param(t->c, t->module, f, WASM_VAL_I32); 670 t->cur_is_variadic = 1; 671 ensure_linear_memory(t); 672 } 673 f->nresults = 0; 674 if (!abi->has_sret && abi->ret.kind == ABI_ARG_DIRECT && 675 abi->ret.nparts == 1) { 676 const ABIArgPart* p = &abi->ret.parts[0]; 677 wasm_func_push_result( 678 t->c, t->module, f, 679 valtype_for_size_kind( 680 t, p->size, p->cls == ABI_CLASS_FP ? ABI_SC_FLOAT : ABI_SC_INT)); 681 } 682 f->typeidx = wasm_intern_func_type(t->c, t->module, f); 683 684 /* Export under the symbol's name when the symbol is globally bound. */ 685 const ObjSym* sym = obj_symbol_get(t->obj, d->sym); 686 if (sym && sym->bind != SB_LOCAL) { 687 const char* name = pool_sym_cstr(t->c->global, sym->name, NULL); 688 if (name && *name) { 689 Heap* h = t->c->ctx->heap; 690 size_t nlen = strlen(name); 691 char* dup = (char*)h->alloc(h, nlen + 1u, 1); 692 memcpy(dup, name, nlen + 1u); 693 f->export_name = dup; 694 WasmExport* e = wasm_add_export(t->c, t->module); 695 char* exp_name = (char*)h->alloc(h, nlen + 1u, 1); 696 memcpy(exp_name, name, nlen + 1u); 697 e->name = exp_name; 698 e->kind = 0; /* function export */ 699 e->index = idx; 700 } 701 } 702 } 703 704 CGLocalStorage wasm_param(CGTarget* tg, const CGParamDesc* d) { 705 WTarget* t = (WTarget*)tg; 706 CGLocalStorage ls; 707 Heap* h = t->c->ctx->heap; 708 u32 wli; 709 WSlot* s; 710 memset(&ls, 0, sizeof ls); 711 if (d->index >= t->nparams_cg) 712 wfail(t, "wasm: param index %u out of range (nparams=%u)", d->index, 713 t->nparams_cg); 714 wli = t->param_local_idx[d->index]; 715 if (wli == 0xffffffffu) { 716 /* ABI_ARG_IGNORE — no wasm storage. Push a placeholder slot so the 717 * returned FrameSlot is meaningful to CG; it never gets emitted. */ 718 s = slot_push(t); 719 s->type = d->type; 720 s->size = d->size; 721 s->align = d->align ? d->align : 1u; 722 s->kind = W_SLOT_LOCAL; 723 s->wasm_local = 0; 724 ls.kind = CG_LOCAL_STORAGE_FRAME; 725 ls.v.frame_slot = (FrameSlot)t->nslots; 726 return ls; 727 } 728 if (d->abi && d->abi->kind == ABI_ARG_INDIRECT) { 729 /* byval: callee receives an i32 pointer; copy the aggregate into a 730 * caller-isolated stack-backed slot at function entry. */ 731 u32 size = d->size; 732 u32 align = d->align ? d->align : 1u; 733 type_size_align(t, d->type, size, align, &size, &align); 734 ensure_stack_pointer(t); 735 s = slot_push(t); 736 s->type = d->type; 737 s->size = size; 738 s->align = align; 739 s->kind = W_SLOT_STACK; 740 t->frame_size = align_to_u32(t->frame_size, align); 741 s->frame_offset = t->frame_size; 742 t->frame_size += size; 743 if (align > t->frame_align) t->frame_align = align; 744 t->has_stack_frame = 1; 745 /* Queue prologue copy-in from the pointer's wasm-local into &slot. */ 746 if (t->nbyval_copies == t->byval_copies_cap) { 747 u32 nc = t->byval_copies_cap ? t->byval_copies_cap * 2u : 4u; 748 WByvalCopy* nb = (WByvalCopy*)h->realloc( 749 h, t->byval_copies, sizeof(WByvalCopy) * t->byval_copies_cap, 750 sizeof(WByvalCopy) * nc, _Alignof(WByvalCopy)); 751 if (!nb) wfail(t, "wasm: out of memory"); 752 t->byval_copies = nb; 753 t->byval_copies_cap = nc; 754 } 755 WByvalCopy* bc = &t->byval_copies[t->nbyval_copies++]; 756 bc->ptr_wasm_local = wli; 757 bc->dst_slot_id = t->nslots - 1u; 758 ls.kind = CG_LOCAL_STORAGE_FRAME; 759 ls.v.frame_slot = (FrameSlot)t->nslots; 760 return ls; 761 } 762 if (d->flags & CG_LOCAL_ADDR_TAKEN) { 763 wfail(t, "wasm: address-taken parameter not yet implemented"); 764 } 765 s = slot_push(t); 766 s->type = d->type; 767 s->size = d->size; 768 s->align = d->align ? d->align : 1u; 769 s->kind = W_SLOT_LOCAL; 770 s->wasm_local = wli; 771 ls.kind = CG_LOCAL_STORAGE_FRAME; 772 ls.v.frame_slot = (FrameSlot)t->nslots; 773 return ls; 774 } 775 776 /* Allocate a frame slot backed by a fresh wasm local. */ 777 static FrameSlot alloc_frame_slot(WTarget* t, KitCgTypeId ty) { 778 return alloc_frame_slot_kind(t, ty, 0, 0, 0); 779 } 780 781 FrameSlot wasm_frame_slot(CGTarget* tg, const FrameSlotDesc* d) { 782 WTarget* t = (WTarget*)tg; 783 if (!d->type && !d->size) wfail(t, "wasm: frame slot without type/size"); 784 return alloc_frame_slot_kind( 785 t, d->type, d->size, d->align, 786 (d->flags & FSF_ADDR_TAKEN) != 0 || d->kind == FS_ALLOCA); 787 } 788 789 CGLocalStorage wasm_local(CGTarget* tg, const CGLocalDesc* d) { 790 WTarget* t = (WTarget*)tg; 791 CGLocalStorage ls; 792 memset(&ls, 0, sizeof ls); 793 if (d->flags & (CG_LOCAL_ADDR_TAKEN | CG_LOCAL_MEMORY_REQUIRED)) { 794 ls.kind = CG_LOCAL_STORAGE_FRAME; 795 ls.v.frame_slot = alloc_frame_slot_kind(t, d->type, d->size, d->align, 1); 796 return ls; 797 } 798 ls.kind = CG_LOCAL_STORAGE_FRAME; 799 ls.v.frame_slot = alloc_frame_slot(t, d->type); 800 return ls; 801 } 802 803 /* ----------------------------------------------------------------- 804 * Data-movement records 805 * ----------------------------------------------------------------- */ 806 807 void wasm_load_imm(CGTarget* tg, Operand dst, i64 imm) { 808 WTarget* t = (WTarget*)tg; 809 if (t->dead) return; 810 if (dst.kind != OPK_REG) wfail(t, "wasm: load_imm dst must be REG"); 811 WIR* w = wir_push(t); 812 w->op = WIR_LOAD_IMM; 813 w->dst = dst.v.reg; 814 w->imm = imm; 815 w->type = dst.type; 816 w->cls = dst.cls; 817 } 818 819 void wasm_load_const(CGTarget* tg, Operand dst, ConstBytes cb) { 820 WTarget* t = (WTarget*)tg; 821 if (t->dead) return; 822 if (dst.kind != OPK_REG) wfail(t, "wasm: load_const dst must be REG"); 823 WasmValType vt = valtype_for_type(t, cb.type); 824 WIR* w = wir_push(t); 825 w->dst = dst.v.reg; 826 w->type = cb.type; 827 w->cls = dst.cls; 828 if (vt == WASM_VAL_F32) { 829 if (cb.size != 4) wfail(t, "wasm: f32 const must be 4 bytes"); 830 float f; 831 memcpy(&f, cb.bytes, 4); 832 w->op = WIR_LOAD_CONST_F; 833 w->fp_imm = (double)f; 834 } else if (vt == WASM_VAL_F64) { 835 if (cb.size != 8) wfail(t, "wasm: f64 const must be 8 bytes"); 836 double v; 837 memcpy(&v, cb.bytes, 8); 838 w->op = WIR_LOAD_CONST_F; 839 w->fp_imm = v; 840 } else { 841 i64 v = 0; 842 memcpy(&v, cb.bytes, cb.size < 8 ? cb.size : 8u); 843 /* Sign-extend for small signed types so the immediate has the expected 844 * bit pattern. */ 845 if (cb.size == 1) 846 v = (i64)(i8)v; 847 else if (cb.size == 2) 848 v = (i64)(i16)v; 849 else if (cb.size == 4) 850 v = (i64)(i32)v; 851 w->op = WIR_LOAD_IMM; 852 w->imm = v; 853 } 854 } 855 856 void wasm_copy(CGTarget* tg, Operand dst, Operand src) { 857 WTarget* t = (WTarget*)tg; 858 if (t->dead) return; 859 if (dst.kind != OPK_REG || src.kind != OPK_REG) 860 wfail(t, "wasm: copy operands must both be REG"); 861 WIR* w = wir_push(t); 862 w->op = WIR_COPY; 863 w->dst = dst.v.reg; 864 w->a = src.v.reg; 865 w->type = dst.type; 866 } 867 868 void wasm_binop(CGTarget* tg, BinOp op, Operand dst, Operand a, Operand b) { 869 WTarget* t = (WTarget*)tg; 870 if (t->dead) return; 871 if (dst.kind != OPK_REG) wfail(t, "wasm: binop dst must be REG"); 872 WIR* w = wir_push(t); 873 w->op = WIR_BINOP; 874 w->cgop = (u8)op; 875 w->dst = dst.v.reg; 876 wir_capture_operand(w, 0, a); 877 wir_capture_operand(w, 1, b); 878 w->type = dst.type; 879 w->cls = dst.cls; 880 } 881 882 void wasm_unop(CGTarget* tg, UnOp op, Operand dst, Operand a) { 883 WTarget* t = (WTarget*)tg; 884 if (t->dead) return; 885 if (dst.kind != OPK_REG) wfail(t, "wasm: unop dst must be REG"); 886 WIR* w = wir_push(t); 887 w->op = WIR_UNOP; 888 w->cgop = (u8)op; 889 w->dst = dst.v.reg; 890 wir_capture_operand(w, 0, a); 891 w->type = dst.type; 892 w->cls = dst.cls; 893 } 894 895 void wasm_cmp(CGTarget* tg, CmpOp op, Operand dst, Operand a, Operand b) { 896 WTarget* t = (WTarget*)tg; 897 if (t->dead) return; 898 if (dst.kind != OPK_REG) wfail(t, "wasm: cmp dst must be REG"); 899 WIR* w = wir_push(t); 900 w->op = WIR_CMP; 901 w->cgop = (u8)op; 902 w->dst = dst.v.reg; 903 wir_capture_operand(w, 0, a); 904 wir_capture_operand(w, 1, b); 905 w->type = dst.type; 906 w->type2 = a.type ? a.type : b.type; 907 w->cls = dst.cls; 908 } 909 910 void wasm_convert(CGTarget* tg, ConvKind ck, Operand dst, Operand src) { 911 WTarget* t = (WTarget*)tg; 912 if (t->dead) return; 913 if (dst.kind != OPK_REG) wfail(t, "wasm: convert dst must be REG"); 914 WIR* w = wir_push(t); 915 w->op = WIR_CONVERT; 916 w->cgop = (u8)ck; 917 w->dst = dst.v.reg; 918 wir_capture_operand(w, 0, src); 919 w->type = dst.type; 920 w->type2 = src.type; 921 w->cls = dst.cls; 922 } 923 924 /* Build (or reuse) the wasm typeidx for a function-typed indirect call. The 925 * signature shape must exactly match a direct call to the same C type: 926 * - hidden i32 sret pointer prepended when ABI has_sret 927 * - per-param: i32 pointer for ABI_ARG_INDIRECT, else the DIRECT scalar 928 * produced by the wasm32 BasicCABI classifier (IGNORE params dropped) 929 * - result: empty when has_sret, else the DIRECT scalar 930 * 931 * call_indirect's runtime type check compares this typeidx against the 932 * funcref's recorded type, so any mismatch with the direct-call path would 933 * trap. The temporary WasmFunc is stack-allocated; wasm_intern_func_type 934 * copies the param array on insertion. */ 935 /* Translate an ABI function signature into the wasm-level param/result list. 936 * Used both for indirect-call signature interning and for import-function 937 * type synthesis. `what` names the call site in diagnostics. Returns the 938 * interned type index. The caller-provided buffer `params` (length `cap`) is 939 * filled from index 0; *nparams_out is the count written. */ 940 static u32 abi_to_wasm_func_type(WTarget* t, const ABIFuncInfo* abi, 941 WasmValType* params, u32 cap, u32* nparams_out, 942 WasmValType* result_out, u32* nresults_out, 943 const char* what) { 944 WasmFunc tmp; 945 /* results is a pointer now (multi-value); back it with a small stack buffer 946 * just like the caller-supplied params buffer, so tmp.results[0] = ... below 947 * is not a NULL deref. A direct return lowers to at most one result. */ 948 WasmValType results[4]; 949 memset(&tmp, 0, sizeof tmp); 950 tmp.params = params; 951 tmp.cap_params = cap; 952 tmp.results = results; 953 tmp.cap_results = 4u; 954 if (abi->has_sret) { 955 if (tmp.nparams >= cap) wfail(t, "wasm: %s has too many params", what); 956 params[tmp.nparams++] = WASM_VAL_I32; 957 } 958 for (u32 i = 0; i < abi->nparams; ++i) { 959 const ABIArgInfo* ai = &abi->params[i]; 960 if (ai->kind == ABI_ARG_IGNORE) continue; 961 if (tmp.nparams >= cap) wfail(t, "wasm: %s has too many params", what); 962 if (ai->kind == ABI_ARG_INDIRECT) { 963 params[tmp.nparams++] = WASM_VAL_I32; 964 } else { 965 if (ai->nparts != 1) 966 wfail(t, "wasm: %s has multi-part DIRECT param (unsupported)", what); 967 const ABIArgPart* p = &ai->parts[0]; 968 params[tmp.nparams++] = valtype_for_size_kind( 969 t, p->size, p->cls == ABI_CLASS_FP ? ABI_SC_FLOAT : ABI_SC_INT); 970 } 971 } 972 /* Variadic functions take one hidden trailing i32 va_ptr — the address of 973 * the caller-packed varargs buffer in linear memory. See wasm_call's 974 * variadic packing and wasm_va_start in this file. */ 975 if (abi->variadic) { 976 if (tmp.nparams >= cap) wfail(t, "wasm: %s has too many params", what); 977 params[tmp.nparams++] = WASM_VAL_I32; 978 } 979 tmp.nresults = 0; 980 if (!abi->has_sret && abi->ret.kind == ABI_ARG_DIRECT && 981 abi->ret.nparts == 1) { 982 const ABIArgPart* p = &abi->ret.parts[0]; 983 tmp.results[0] = valtype_for_size_kind( 984 t, p->size, p->cls == ABI_CLASS_FP ? ABI_SC_FLOAT : ABI_SC_INT); 985 tmp.nresults = 1; 986 } 987 if (nparams_out) *nparams_out = tmp.nparams; 988 if (result_out && tmp.nresults) *result_out = tmp.results[0]; 989 if (nresults_out) *nresults_out = tmp.nresults; 990 return wasm_intern_func_type(t->c, t->module, &tmp); 991 } 992 993 static u32 intern_indirect_signature(WTarget* t, const ABIFuncInfo* abi) { 994 WasmValType params[64]; 995 return abi_to_wasm_func_type(t, abi, params, 64u, NULL, NULL, NULL, 996 "indirect call"); 997 } 998 999 /* Promote `f` (already allocated for `sym` via sym_to_wasm_func) into a wasm 1000 * `(import "<module>" "<field>" (func ...))` entry. The signature is 1001 * synthesized from the supplied ABIFuncInfo, mirroring the layout the 1002 * caller-side WIR_CALL pushes onto the stack: hidden i32 sret-pointer when 1003 * has_sret, followed by lowered params, with a single i32/i64/f32/f64 1004 * result for direct, non-sret returns. Module/field default to "env" / the 1005 * symbol's name; either may be overridden by 1006 * `__attribute__((import_module/import_name))`. */ 1007 static void promote_import_func(WTarget* t, ObjSymId sym, WasmFunc* f, 1008 const ABIFuncInfo* abi) { 1009 Heap* h = t->c->ctx->heap; 1010 const ObjSym* os; 1011 const char* sym_name; 1012 size_t sym_name_len = 0; 1013 Sym attr_module = 0; 1014 Sym attr_name = 0; 1015 const char* mod_str = "env"; 1016 size_t mod_len = sizeof("env") - 1u; 1017 if (!t->module) return; 1018 if (f->is_import) return; 1019 os = obj_symbol_get(t->obj, sym); 1020 if (!os) return; 1021 if (os->section_id != OBJ_SEC_NONE) return; /* already defined locally */ 1022 if (os->kind != SK_UNDEF && os->kind != SK_FUNC) return; 1023 if (!abi) 1024 wfail(t, 1025 "wasm: cannot synthesize import signature for '%s' " 1026 "(missing ABI info)", 1027 pool_sym_cstr(t->c->global, os->name, NULL)); 1028 if (f->ninsns != 0) 1029 wfail(t, "wasm: cannot promote function with emitted body to import"); 1030 /* Synthesize the wasm type from the ABI. Diagnoses unsupported shapes 1031 * (varargs => multi-part DIRECT or extra parts; by-value aggregates that 1032 * the ABI didn't already lower to ABI_ARG_INDIRECT) by naming the import 1033 * symbol so the error points at the C declaration. */ 1034 WasmValType params[64]; 1035 u32 nparams = 0; 1036 WasmValType result_vt = 0; 1037 u32 nresults = 0; 1038 char what[160]; 1039 sym_name = pool_sym_cstr(t->c->global, os->name, &sym_name_len); 1040 if (!sym_name) sym_name = "(anonymous)"; 1041 /* Snprintf-free: build a short context string by hand to avoid pulling in 1042 * stdio. The buffer is large enough for any plausible C identifier. */ 1043 { 1044 const char* prefix = "import '"; 1045 const char* suffix = "'"; 1046 size_t plen = 8u; /* strlen(prefix) */ 1047 size_t slen = 1u; /* strlen(suffix) */ 1048 size_t nlen = sym_name_len ? sym_name_len : strlen(sym_name); 1049 if (nlen > sizeof(what) - plen - slen - 1u) 1050 nlen = sizeof(what) - plen - slen - 1u; 1051 memcpy(what, prefix, plen); 1052 memcpy(what + plen, sym_name, nlen); 1053 memcpy(what + plen + nlen, suffix, slen); 1054 what[plen + nlen + slen] = 0; 1055 } 1056 f->typeidx = abi_to_wasm_func_type(t, abi, params, 64u, &nparams, &result_vt, 1057 &nresults, what); 1058 f->has_typeidx = 1; 1059 /* Mirror the synthesized params/results onto the WasmFunc so the import 1060 * encoder writes the matching signature. */ 1061 wasm_func_set_params(t->c, t->module, f, params, nparams); 1062 wasm_func_set_results(t->c, t->module, f, &result_vt, nresults); 1063 /* Resolve module/name overrides set via __attribute__((import_module/ 1064 * import_name)) on the C declaration. */ 1065 (void)wasm_imports_get(t->obj, os->name, &attr_module, &attr_name); 1066 if (attr_module) mod_str = pool_sym_cstr(t->c->global, attr_module, &mod_len); 1067 const char* name_str = 1068 attr_name ? pool_sym_cstr(t->c->global, attr_name, &sym_name_len) 1069 : sym_name; 1070 size_t name_len = attr_name 1071 ? sym_name_len 1072 : (sym_name_len ? sym_name_len : strlen(name_str)); 1073 f->is_import = 1; 1074 { 1075 char* m = (char*)h->alloc(h, mod_len + 1u, 1); 1076 if (!m) wfail(t, "wasm: out of memory"); 1077 memcpy(m, mod_str, mod_len); 1078 m[mod_len] = 0; 1079 f->import_module = m; 1080 } 1081 { 1082 char* n = (char*)h->alloc(h, name_len + 1u, 1); 1083 if (!n) wfail(t, "wasm: out of memory"); 1084 memcpy(n, name_str, name_len); 1085 n[name_len] = 0; 1086 f->import_name = n; 1087 } 1088 } 1089 1090 void wasm_call(CGTarget* tg, const CGCallDesc* d) { 1091 WTarget* t = (WTarget*)tg; 1092 if (t->dead) return; 1093 int is_indirect = (d->callee.kind != OPK_GLOBAL); 1094 if (is_indirect && d->callee.kind != OPK_REG) 1095 wfail(t, "wasm: indirect call callee must be a register (got opkind %u)", 1096 (unsigned)d->callee.kind); 1097 Heap* h = t->c->ctx->heap; 1098 int callee_has_sret = (d->abi && d->abi->has_sret) ? 1 : 0; 1099 int callee_variadic = (d->abi && d->abi->variadic) ? 1 : 0; 1100 int is_tail = (d->flags & CG_CALL_TAIL) ? 1 : 0; 1101 if (is_tail) { 1102 /* Realizability is decided by CG via wasm_ir_tail_call_unrealizable_reason 1103 * (target.c) before CG_CALL_TAIL is set: variadic tails are rejected there, 1104 * and sret tails forward the incoming sret pointer (handled in WIR emit). 1105 */ 1106 ensure_module(t); 1107 t->module->features |= WASM_FEATURE_TAIL_CALLS; 1108 } 1109 u32 nfixed = (u32)d->nargs; 1110 u32 nvar = 0u; 1111 if (callee_variadic) { 1112 if (d->nargs < d->abi->nparams) 1113 wfail(t, "wasm: variadic call has fewer args (%u) than fixed params (%u)", 1114 (unsigned)d->nargs, (unsigned)d->abi->nparams); 1115 nfixed = d->abi->nparams; 1116 nvar = (u32)d->nargs - nfixed; 1117 } 1118 WIR* w = wir_push(t); 1119 if (is_indirect) { 1120 if (!d->abi) wfail(t, "wasm: indirect call without ABIFuncInfo"); 1121 ensure_module(t); 1122 w->op = WIR_CALL_INDIRECT; 1123 w->a = d->callee.v.reg; 1124 w->imm = (i64)intern_indirect_signature(t, d->abi); 1125 } else { 1126 w->op = WIR_CALL; 1127 w->call_sym = d->callee.v.global.sym; 1128 /* Direct calls into externally-defined functions become wasm imports. 1129 * Synthesize the import signature now while the ABI is available — the 1130 * WIR emit loop only has the symbol index. The C frontend mints SK_FUNC 1131 * for `extern foo(...)` declarations; the "undefined" signal is 1132 * `section_id == OBJ_SEC_NONE`. SK_UNDEF can appear when a symbol's 1133 * kind hasn't been pinned down yet. */ 1134 { 1135 const ObjSym* os = obj_symbol_get(t->obj, d->callee.v.global.sym); 1136 if (os && os->section_id == OBJ_SEC_NONE && 1137 (os->kind == SK_UNDEF || os->kind == SK_FUNC)) { 1138 WasmFunc* f; 1139 ensure_module(t); 1140 (void)sym_to_wasm_func(t, d->callee.v.global.sym, &f); 1141 if (!f->is_import) 1142 promote_import_func(t, d->callee.v.global.sym, f, d->abi); 1143 } 1144 } 1145 } 1146 w->call_narg = nfixed; 1147 w->type = d->ret.type; 1148 w->call_has_sret = (u8)callee_has_sret; 1149 w->call_variadic = (u8)callee_variadic; 1150 w->call_tail = (u8)is_tail; 1151 w->call_nvar = nvar; 1152 if (callee_variadic) ensure_linear_memory(t); 1153 if (callee_has_sret) { 1154 /* Caller allocated a frame slot for the aggregate result via 1155 * api_alloc_call_ret_storage; pass its address as the prepended i32. */ 1156 w->call_sret_addr = d->ret.storage; 1157 ensure_linear_memory(t); 1158 } 1159 if (nfixed) { 1160 w->call_args = (Reg*)h->alloc(h, sizeof(Reg) * nfixed, _Alignof(Reg)); 1161 w->call_arg_imms = (i64*)h->alloc(h, sizeof(i64) * nfixed, _Alignof(i64)); 1162 w->call_arg_kinds = (u8*)h->alloc(h, nfixed, 1); 1163 w->call_arg_types = (KitCgTypeId*)h->alloc(h, sizeof(KitCgTypeId) * nfixed, 1164 _Alignof(KitCgTypeId)); 1165 w->call_arg_addrs = 1166 (Operand*)h->alloc(h, sizeof(Operand) * nfixed, _Alignof(Operand)); 1167 memset(w->call_arg_addrs, 0, sizeof(Operand) * nfixed); 1168 for (u32 i = 0; i < nfixed; ++i) { 1169 const CGABIValue* av = &d->args[i]; 1170 w->call_arg_types[i] = av->type; 1171 int is_indirect = (av->abi && av->abi->kind == ABI_ARG_INDIRECT); 1172 if (is_indirect) { 1173 if (av->storage.kind != OPK_LOCAL && av->storage.kind != OPK_INDIRECT && 1174 av->storage.kind != OPK_GLOBAL) { 1175 wfail(t, "wasm: byval call arg %u storage kind %u must be an address", 1176 i, (unsigned)av->storage.kind); 1177 } 1178 w->call_arg_kinds[i] = WOP_ADDR; 1179 w->call_args[i] = REG_NONE; 1180 w->call_arg_imms[i] = 0; 1181 w->call_arg_addrs[i] = av->storage; 1182 ensure_linear_memory(t); 1183 } else if (av->storage.kind == OPK_REG) { 1184 w->call_arg_kinds[i] = 0; 1185 w->call_args[i] = av->storage.v.reg; 1186 w->call_arg_imms[i] = 0; 1187 } else if (av->storage.kind == OPK_IMM) { 1188 w->call_arg_kinds[i] = 1; 1189 w->call_args[i] = REG_NONE; 1190 w->call_arg_imms[i] = av->storage.v.imm; 1191 } else { 1192 wfail(t, 1193 "wasm: call arg %u has unsupported operand kind %u (only " 1194 "REG/IMM scalar args are supported in v1)", 1195 i, (unsigned)av->storage.kind); 1196 } 1197 } 1198 } 1199 if (nvar) { 1200 w->call_var_regs = (Reg*)h->alloc(h, sizeof(Reg) * nvar, _Alignof(Reg)); 1201 w->call_var_imms = (i64*)h->alloc(h, sizeof(i64) * nvar, _Alignof(i64)); 1202 w->call_var_kinds = (u8*)h->alloc(h, nvar, 1); 1203 w->call_var_types = (KitCgTypeId*)h->alloc(h, sizeof(KitCgTypeId) * nvar, 1204 _Alignof(KitCgTypeId)); 1205 for (u32 i = 0; i < nvar; ++i) { 1206 const CGABIValue* av = &d->args[nfixed + i]; 1207 const CgType* aty = av->type ? cg_type_get(t->c, av->type) : NULL; 1208 w->call_var_types[i] = av->type; 1209 if (aty && 1210 (aty->kind == KIT_CG_TYPE_RECORD || aty->kind == KIT_CG_TYPE_ARRAY)) { 1211 wfail(t, "wasm target: aggregate variadic arg %u not yet supported", i); 1212 } 1213 if (av->storage.kind == OPK_REG) { 1214 w->call_var_kinds[i] = WOP_REG; 1215 w->call_var_regs[i] = av->storage.v.reg; 1216 w->call_var_imms[i] = 0; 1217 } else if (av->storage.kind == OPK_IMM) { 1218 w->call_var_kinds[i] = WOP_IMM; 1219 w->call_var_regs[i] = REG_NONE; 1220 w->call_var_imms[i] = av->storage.v.imm; 1221 } else { 1222 wfail(t, 1223 "wasm target: variadic arg %u has unsupported operand kind %u " 1224 "(only REG/IMM scalar args supported in v1)", 1225 i, (unsigned)av->storage.kind); 1226 } 1227 } 1228 } 1229 if (callee_has_sret) { 1230 /* The call has no wasm result; the buffer pointed to by the sret arg 1231 * holds the aggregate. */ 1232 w->dst = REG_NONE; 1233 } else if (d->ret.storage.kind == OPK_REG && 1234 d->ret.storage.v.reg != REG_NONE) { 1235 w->dst = d->ret.storage.v.reg; 1236 } else { 1237 w->dst = REG_NONE; 1238 } 1239 } 1240 1241 void wasm_ret(CGTarget* tg, const CGABIValue* v) { 1242 WTarget* t = (WTarget*)tg; 1243 if (t->dead) return; 1244 WIR* w = wir_push(t); 1245 w->op = WIR_RET; 1246 if (t->cur_has_sret && v && v->abi && v->abi->kind == ABI_ARG_INDIRECT) { 1247 /* Aggregate sret return: emit a memcpy from av.storage to the buffer 1248 * pointed to by the hidden sret parameter, then a void return. */ 1249 w->addr = v->storage; 1250 w->type = v->type; 1251 w->agg.size = (u32)abi_cg_sizeof(t->c->abi, v->type); 1252 w->agg.align = 1u; 1253 w->cgop = 1; /* tag: sret copy */ 1254 w->dst = REG_NONE; 1255 } else if (v && v->storage.kind == OPK_REG && v->storage.v.reg != REG_NONE) { 1256 w->dst = v->storage.v.reg; 1257 w->type = v->type; 1258 } else if (v && v->storage.kind == OPK_IMM) { 1259 w->imm_kind = 1; 1260 w->imm_a = v->storage.v.imm; 1261 w->type = v->type; 1262 w->dst = REG_NONE; 1263 } else { 1264 w->dst = REG_NONE; 1265 } 1266 t->dead = 1; 1267 } 1268 1269 void wasm_load(CGTarget* tg, Operand dst, Operand addr, MemAccess mem) { 1270 WTarget* t = (WTarget*)tg; 1271 if (t->dead) return; 1272 if (dst.kind != OPK_REG) wfail(t, "wasm: load dst must be REG"); 1273 if (addr.kind != OPK_LOCAL || 1274 slot_for(t, addr.v.frame_slot)->kind == W_SLOT_STACK) 1275 ensure_linear_memory(t); 1276 WIR* w = wir_push(t); 1277 w->op = (addr.kind == OPK_LOCAL && 1278 slot_for(t, addr.v.frame_slot)->kind == W_SLOT_LOCAL) 1279 ? WIR_LOAD_LOCAL 1280 : WIR_LOAD_MEM; 1281 w->dst = dst.v.reg; 1282 w->addr = addr; 1283 if (addr.kind == OPK_LOCAL) { 1284 WSlot* s = slot_for(t, addr.v.frame_slot); 1285 w->imm = 1286 (w->op == WIR_LOAD_LOCAL) ? (i64)s->wasm_local : (i64)addr.v.frame_slot; 1287 } 1288 w->mem = mem; 1289 w->type = dst.type; 1290 w->cls = dst.cls; 1291 } 1292 1293 void wasm_store(CGTarget* tg, Operand addr, Operand src, MemAccess mem) { 1294 WTarget* t = (WTarget*)tg; 1295 if (t->dead) return; 1296 if (addr.kind != OPK_LOCAL || 1297 slot_for(t, addr.v.frame_slot)->kind == W_SLOT_STACK) 1298 ensure_linear_memory(t); 1299 WIR* w = wir_push(t); 1300 w->op = (addr.kind == OPK_LOCAL && 1301 slot_for(t, addr.v.frame_slot)->kind == W_SLOT_LOCAL) 1302 ? WIR_STORE_LOCAL 1303 : WIR_STORE_MEM; 1304 w->addr = addr; 1305 if (addr.kind == OPK_LOCAL) { 1306 WSlot* s = slot_for(t, addr.v.frame_slot); 1307 w->imm = (w->op == WIR_STORE_LOCAL) ? (i64)s->wasm_local 1308 : (i64)addr.v.frame_slot; 1309 } 1310 wir_capture_operand(w, 0, src); 1311 w->mem = mem; 1312 /* The store's value type is the accessed type. When storing through a 1313 * pointer register, addr.type is the (possibly void) pointer rvalue type, 1314 * not the pointee — so prefer the MemAccess type, which always describes 1315 * the element being written, before falling back to the operands. */ 1316 w->type = mem.type ? mem.type : (addr.type ? addr.type : src.type); 1317 } 1318 1319 /* Variadic CG hooks. va_list on wasm32 is a single i32 pointer into a 1320 * caller-packed buffer of 8-byte slots (see wasm_call's variadic packing). 1321 * va_start writes the hidden va_ptr param into *ap; va_arg loads T from 1322 * *ap and advances *ap by 8; va_end is a no-op; va_copy copies the i32. */ 1323 void wasm_va_start(CGTarget* tg, Operand ap_addr) { 1324 WTarget* t = (WTarget*)tg; 1325 if (t->dead) return; 1326 if (!t->cur_is_variadic || t->va_ptr_param_local == 0xffffffffu) 1327 wfail(t, "wasm: va_start in non-variadic function"); 1328 ensure_linear_memory(t); 1329 WIR* w = wir_push(t); 1330 w->op = WIR_VA_START; 1331 w->addr = ap_addr; 1332 } 1333 1334 void wasm_va_arg(CGTarget* tg, Operand dst, Operand ap_addr, KitCgTypeId type) { 1335 WTarget* t = (WTarget*)tg; 1336 if (t->dead) return; 1337 if (dst.kind != OPK_REG) wfail(t, "wasm: va_arg dst must be REG"); 1338 const CgType* aty = type ? cg_type_get(t->c, type) : NULL; 1339 if (aty && 1340 (aty->kind == KIT_CG_TYPE_RECORD || aty->kind == KIT_CG_TYPE_ARRAY)) { 1341 wfail(t, "wasm target: va_arg of aggregate type not yet supported"); 1342 } 1343 ensure_linear_memory(t); 1344 WIR* w = wir_push(t); 1345 w->op = WIR_VA_ARG; 1346 w->dst = dst.v.reg; 1347 w->addr = ap_addr; 1348 w->type = type; 1349 w->cls = dst.cls; 1350 } 1351 1352 void wasm_va_end(CGTarget* tg, Operand ap_addr) { 1353 WTarget* t = (WTarget*)tg; 1354 (void)ap_addr; 1355 if (t->dead) return; 1356 /* No-op: nothing to release. */ 1357 } 1358 1359 void wasm_va_copy(CGTarget* tg, Operand dst_ap_addr, Operand src_ap_addr) { 1360 WTarget* t = (WTarget*)tg; 1361 if (t->dead) return; 1362 ensure_linear_memory(t); 1363 WIR* w = wir_push(t); 1364 w->op = WIR_VA_COPY; 1365 w->addr = dst_ap_addr; 1366 w->call_sret_addr = src_ap_addr; /* reused slot — see WIR comment */ 1367 } 1368 1369 void wasm_addr_of(CGTarget* tg, Operand dst, Operand lv) { 1370 WTarget* t = (WTarget*)tg; 1371 if (t->dead) return; 1372 if (dst.kind != OPK_REG) wfail(t, "wasm: addr_of dst must be REG"); 1373 if (lv.kind == OPK_LOCAL) { 1374 WSlot* s = slot_for(t, lv.v.frame_slot); 1375 if (s->kind == W_SLOT_LOCAL) { 1376 u32 old_local = s->wasm_local; 1377 promote_slot_to_stack(t, s); 1378 WIR* st = wir_push(t); 1379 st->op = WIR_STORE_MEM; 1380 st->addr = lv; 1381 st->type = s->type; 1382 st->mem.type = s->type; 1383 st->mem.size = s->size; 1384 st->mem.align = s->align; 1385 st->imm_kind = WOP_WASM_LOCAL; 1386 st->imm_a = old_local; 1387 } 1388 } else { 1389 ensure_linear_memory(t); 1390 } 1391 WIR* w = wir_push(t); 1392 w->op = WIR_ADDR_OF; 1393 w->dst = dst.v.reg; 1394 w->addr = lv; 1395 w->type = dst.type; 1396 w->cls = dst.cls; 1397 } 1398 1399 void wasm_alloca(CGTarget* tg, Operand dst, Operand size, u32 align) { 1400 WTarget* t = (WTarget*)tg; 1401 if (t->dead) return; 1402 ensure_linear_memory(t); 1403 if (dst.kind != OPK_REG) wfail(t, "wasm: alloca dst must be REG"); 1404 ensure_stack_pointer(t); 1405 t->has_stack_frame = 1; 1406 WIR* w = wir_push(t); 1407 w->op = WIR_ALLOCA; 1408 w->dst = dst.v.reg; 1409 wir_capture_operand(w, 0, size); 1410 w->type = dst.type; 1411 w->type2 = size.type; 1412 w->cls = dst.cls; 1413 w->imm = align ? align : 16u; 1414 } 1415 1416 void wasm_copy_bytes(CGTarget* tg, Operand dst, Operand src, 1417 AggregateAccess a) { 1418 WTarget* t = (WTarget*)tg; 1419 if (t->dead) return; 1420 ensure_linear_memory(t); 1421 WIR* w = wir_push(t); 1422 w->op = WIR_COPY_BYTES; 1423 w->addr = dst; 1424 wir_capture_operand(w, 0, src); 1425 w->agg = a; 1426 } 1427 1428 void wasm_set_bytes(CGTarget* tg, Operand dst, Operand byte, 1429 AggregateAccess a) { 1430 WTarget* t = (WTarget*)tg; 1431 if (t->dead) return; 1432 WIR* w = wir_push(t); 1433 w->op = WIR_SET_BYTES; 1434 w->addr = dst; 1435 wir_capture_operand(w, 0, byte); 1436 w->agg = a; 1437 } 1438 1439 /* Atomic ops. CG forces `addr` to a REG and accepts reg-or-imm for value 1440 * operands. Wasm only models seq_cst; the KitCgMemOrder argument is captured 1441 * but not encoded — every emitted atomic op is sequentially consistent. The 1442 * caller-provided MemAccess carries the type and natural alignment we need 1443 * for the memarg width. */ 1444 static void atomic_require_addr_reg(WTarget* t, Operand addr, 1445 const char* what) { 1446 if (addr.kind != OPK_REG) 1447 wfail(t, "wasm: %s address must be in a register (got opkind %u)", what, 1448 (unsigned)addr.kind); 1449 } 1450 1451 void wasm_atomic_load(CGTarget* tg, Operand dst, Operand addr, MemAccess mem, 1452 KitCgMemOrder mo) { 1453 WTarget* t = (WTarget*)tg; 1454 (void)mo; 1455 if (t->dead) return; 1456 if (dst.kind != OPK_REG) wfail(t, "wasm: atomic_load dst must be REG"); 1457 atomic_require_addr_reg(t, addr, "atomic_load"); 1458 ensure_shared_memory(t); 1459 WIR* w = wir_push(t); 1460 w->op = WIR_ATOMIC_LOAD; 1461 w->dst = dst.v.reg; 1462 w->a = addr.v.reg; 1463 w->mem = mem; 1464 w->type = dst.type ? dst.type : mem.type; 1465 w->cls = dst.cls; 1466 } 1467 1468 void wasm_atomic_store(CGTarget* tg, Operand addr, Operand src, MemAccess mem, 1469 KitCgMemOrder mo) { 1470 WTarget* t = (WTarget*)tg; 1471 (void)mo; 1472 if (t->dead) return; 1473 atomic_require_addr_reg(t, addr, "atomic_store"); 1474 if (src.kind != OPK_REG && src.kind != OPK_IMM) 1475 wfail(t, "wasm: atomic_store value must be REG or IMM"); 1476 ensure_shared_memory(t); 1477 WIR* w = wir_push(t); 1478 w->op = WIR_ATOMIC_STORE; 1479 w->a = addr.v.reg; 1480 wir_capture_operand(w, 1, src); 1481 w->mem = mem; 1482 w->type = mem.type ? mem.type : src.type; 1483 } 1484 1485 void wasm_atomic_rmw(CGTarget* tg, KitCgAtomicOp op, Operand dst, Operand addr, 1486 Operand val, MemAccess mem, KitCgMemOrder mo) { 1487 WTarget* t = (WTarget*)tg; 1488 (void)mo; 1489 if (t->dead) return; 1490 if (dst.kind != OPK_REG) wfail(t, "wasm: atomic_rmw dst must be REG"); 1491 atomic_require_addr_reg(t, addr, "atomic_rmw"); 1492 if (val.kind != OPK_REG && val.kind != OPK_IMM) 1493 wfail(t, "wasm: atomic_rmw value must be REG or IMM"); 1494 /* KIT_CG_ATOMIC_NAND has no native wasm-threads opcode; the linearizer 1495 * expands it into an atomic cmpxchg retry loop (see WIR_ATOMIC_RMW). */ 1496 ensure_shared_memory(t); 1497 WIR* w = wir_push(t); 1498 w->op = WIR_ATOMIC_RMW; 1499 w->cgop = (u8)op; 1500 w->dst = dst.v.reg; 1501 w->a = addr.v.reg; 1502 wir_capture_operand(w, 1, val); 1503 w->mem = mem; 1504 w->type = dst.type ? dst.type : mem.type; 1505 w->cls = dst.cls; 1506 } 1507 1508 void wasm_atomic_cas(CGTarget* tg, Operand prior, Operand ok, Operand addr, 1509 Operand expected, Operand desired, MemAccess mem, 1510 KitCgMemOrder success, KitCgMemOrder failure) { 1511 WTarget* t = (WTarget*)tg; 1512 (void)success; 1513 (void)failure; 1514 if (t->dead) return; 1515 if (prior.kind != OPK_REG) wfail(t, "wasm: atomic_cas prior must be REG"); 1516 if (ok.kind != OPK_REG) wfail(t, "wasm: atomic_cas ok must be REG"); 1517 atomic_require_addr_reg(t, addr, "atomic_cas"); 1518 if (expected.kind != OPK_REG && expected.kind != OPK_IMM) 1519 wfail(t, "wasm: atomic_cas expected must be REG or IMM"); 1520 if (desired.kind != OPK_REG && desired.kind != OPK_IMM) 1521 wfail(t, "wasm: atomic_cas desired must be REG or IMM"); 1522 ensure_shared_memory(t); 1523 WIR* w = wir_push(t); 1524 w->op = WIR_ATOMIC_CAS; 1525 w->dst = prior.v.reg; 1526 w->dst2 = ok.v.reg; 1527 w->a = addr.v.reg; 1528 wir_capture_operand(w, 1, expected); 1529 /* Capture desired into op_c/imm_kind_c/imm_c (third operand slot). */ 1530 if (desired.kind == OPK_REG) { 1531 w->imm_kind_c = WOP_REG; 1532 w->op_c = desired.v.reg; 1533 } else { 1534 w->imm_kind_c = WOP_IMM; 1535 w->imm_c = desired.v.imm; 1536 } 1537 w->mem = mem; 1538 w->type = prior.type ? prior.type : mem.type; 1539 w->cls = prior.cls; 1540 w->type2 = ok.type; 1541 } 1542 1543 void wasm_fence(CGTarget* tg, KitCgMemOrder mo) { 1544 WTarget* t = (WTarget*)tg; 1545 (void)mo; 1546 if (t->dead) return; 1547 /* Wasm atomic.fence does not require a memory to exist, but in practice it 1548 * is meaningful only inside a module that has shared memory. We don't 1549 * force-create memory here to avoid producing a bogus memory for fence-only 1550 * modules. */ 1551 WIR* w = wir_push(t); 1552 w->op = WIR_FENCE; 1553 } 1554 1555 /* Forward decls: defined further down. */ 1556 static WasmValType type_valtype(WTarget* t, KitCgTypeId ty); 1557 void wasm_emit_unreachable(WTarget* t); 1558 1559 /* Per-intrinsic-name diagnostic text. Used both by the recorder for 1560 * SETJMP/LONGJMP (which we still reject) and for the fallback panic so 1561 * users see "wasm target: __builtin_clz ..." instead of a numeric kind. */ 1562 static const char* intrin_name(IntrinKind k) { 1563 switch (k) { 1564 case INTRIN_NONE: 1565 return "<none>"; 1566 case INTRIN_POPCOUNT: 1567 return "__builtin_popcount"; 1568 case INTRIN_CTZ: 1569 return "__builtin_ctz"; 1570 case INTRIN_CLZ: 1571 return "__builtin_clz"; 1572 case INTRIN_BSWAP: 1573 return "__builtin_bswap"; 1574 case INTRIN_SMUL_HIGH: 1575 return "smul_high"; 1576 case INTRIN_UMUL_HIGH: 1577 return "umul_high"; 1578 case INTRIN_MEMMOVE: 1579 return "memmove"; 1580 case INTRIN_PREFETCH: 1581 return "__builtin_prefetch"; 1582 case INTRIN_ASSUME_ALIGNED: 1583 return "__builtin_assume_aligned"; 1584 case INTRIN_EXPECT: 1585 return "__builtin_expect"; 1586 case INTRIN_TRAP: 1587 return "__builtin_trap"; 1588 case INTRIN_SYSCALL: 1589 return "__kit_syscall"; 1590 case INTRIN_SETJMP: 1591 return "setjmp"; 1592 case INTRIN_LONGJMP: 1593 return "longjmp"; 1594 case INTRIN_SADD_OVERFLOW: 1595 return "__builtin_sadd_overflow"; 1596 case INTRIN_UADD_OVERFLOW: 1597 return "__builtin_uadd_overflow"; 1598 case INTRIN_SSUB_OVERFLOW: 1599 return "__builtin_ssub_overflow"; 1600 case INTRIN_USUB_OVERFLOW: 1601 return "__builtin_usub_overflow"; 1602 case INTRIN_SMUL_OVERFLOW: 1603 return "__builtin_smul_overflow"; 1604 case INTRIN_UMUL_OVERFLOW: 1605 return "__builtin_umul_overflow"; 1606 case INTRIN_CPU_NOP: 1607 return "cpu_nop"; 1608 case INTRIN_CPU_YIELD: 1609 return "cpu_yield"; 1610 case INTRIN_WFI: 1611 return "wfi"; 1612 case INTRIN_WFE: 1613 return "wfe"; 1614 case INTRIN_SEV: 1615 return "sev"; 1616 case INTRIN_ISB: 1617 return "isb"; 1618 case INTRIN_DMB: 1619 return "dmb"; 1620 case INTRIN_DSB: 1621 return "dsb"; 1622 case INTRIN_IRQ_SAVE: 1623 return "irq_save"; 1624 case INTRIN_IRQ_RESTORE: 1625 return "irq_restore"; 1626 case INTRIN_IRQ_ENABLE: 1627 return "irq_enable"; 1628 case INTRIN_IRQ_DISABLE: 1629 return "irq_disable"; 1630 case INTRIN_FRAME_ADDRESS: 1631 return "frame_address"; 1632 case INTRIN_RETURN_ADDRESS: 1633 return "return_address"; 1634 case INTRIN_READCYCLECOUNTER: 1635 return "readcyclecounter"; 1636 case INTRIN_STACK_GUARD: 1637 return "stack_guard"; 1638 } 1639 return "<unknown>"; 1640 } 1641 1642 void wasm_intrinsic(CGTarget* tg, IntrinKind k, Operand* dst, u32 ndst, 1643 const Operand* args, u32 nargs) { 1644 WTarget* t = (WTarget*)tg; 1645 if (t->dead) return; 1646 1647 switch (k) { 1648 case INTRIN_TRAP: 1649 wasm_emit_unreachable(t); 1650 return; 1651 1652 case INTRIN_PREFETCH: 1653 /* No-op hint. */ 1654 return; 1655 1656 case INTRIN_CPU_YIELD: 1657 /* Portable spin-loop hint; wasm has no corresponding core instruction. */ 1658 return; 1659 1660 case INTRIN_EXPECT: 1661 case INTRIN_ASSUME_ALIGNED: 1662 /* Pass-through hint: result = first argument. CG always allocates a 1663 * dst reg for these; copy arg[0] there so downstream uses see the 1664 * expected value. CG keeps the first arg as OPK_IMM when it was a 1665 * literal so the constant flows through unchanged. */ 1666 if (ndst == 1 && nargs >= 1) { 1667 if (args[0].kind == OPK_IMM) { 1668 wasm_load_imm(tg, dst[0], args[0].v.imm); 1669 } else { 1670 wasm_copy(tg, dst[0], args[0]); 1671 } 1672 } 1673 return; 1674 1675 case INTRIN_MEMMOVE: { 1676 /* memmove lowers to memory.copy, which is spec-defined to handle overlap 1677 * correctly. CG forces (dst, src) to REG and passes size as OPK_IMM 1678 * (kit_cg_memmove). */ 1679 if (nargs != 3 || args[0].kind != OPK_REG || args[1].kind != OPK_REG) { 1680 compiler_panic(t->c, cur_loc(t), 1681 "wasm target: %s requires register pointers", 1682 intrin_name(k)); 1683 return; 1684 } 1685 if (args[2].kind != OPK_IMM) { 1686 compiler_panic(t->c, cur_loc(t), 1687 "wasm target: %s with non-constant size is not yet " 1688 "supported", 1689 intrin_name(k)); 1690 return; 1691 } 1692 ensure_linear_memory(t); 1693 AggregateAccess a; 1694 memset(&a, 0, sizeof a); 1695 a.size = (u32)args[2].v.imm; 1696 a.align = 1; 1697 WIR* w = wir_push(t); 1698 w->op = WIR_COPY_BYTES; 1699 w->addr = args[0]; 1700 wir_capture_operand(w, 0, args[1]); 1701 w->agg = a; 1702 return; 1703 } 1704 1705 case INTRIN_CLZ: 1706 case INTRIN_CTZ: 1707 case INTRIN_POPCOUNT: 1708 case INTRIN_BSWAP: { 1709 if (ndst != 1 || nargs != 1 || dst[0].kind != OPK_REG || 1710 args[0].kind != OPK_REG) { 1711 compiler_panic(t->c, cur_loc(t), 1712 "wasm target: %s requires single REG operand", 1713 intrin_name(k)); 1714 return; 1715 } 1716 WIR* w = wir_push(t); 1717 w->op = WIR_INTRINSIC; 1718 w->cgop = (u8)k; 1719 w->dst = dst[0].v.reg; 1720 w->a = args[0].v.reg; 1721 w->type = dst[0].type; 1722 /* clz/ctz/popcount return int (i32) but operate at the operand's width 1723 * (e.g. __builtin_ctzl over an i64). The wasm op width must follow the 1724 * operand, with a wrap to the i32 dst afterward. type2 carries it. */ 1725 w->type2 = args[0].type; 1726 w->cls = dst[0].cls; 1727 return; 1728 } 1729 1730 case INTRIN_SADD_OVERFLOW: 1731 case INTRIN_UADD_OVERFLOW: 1732 case INTRIN_SSUB_OVERFLOW: 1733 case INTRIN_USUB_OVERFLOW: 1734 case INTRIN_SMUL_OVERFLOW: 1735 case INTRIN_UMUL_OVERFLOW: { 1736 if (ndst != 2 || nargs != 2 || dst[0].kind != OPK_REG || 1737 dst[1].kind != OPK_REG) { 1738 compiler_panic(t->c, cur_loc(t), 1739 "wasm target: %s requires 2 args + 2 result regs", 1740 intrin_name(k)); 1741 return; 1742 } 1743 /* Reject i64 mul-overflow for now: wasm core has no widening 64x64 1744 * multiply, so the standard expansion would need partial-product 1745 * synthesis. 32-bit (the common shape on wasm32) is supported. */ 1746 WasmValType vt = type_valtype(t, dst[0].type); 1747 if (vt == WASM_VAL_I64 && 1748 (k == INTRIN_SMUL_OVERFLOW || k == INTRIN_UMUL_OVERFLOW)) { 1749 compiler_panic(t->c, cur_loc(t), 1750 "wasm target: 64-bit checked-overflow multiply is " 1751 "not yet supported"); 1752 return; 1753 } 1754 WIR* w = wir_push(t); 1755 w->op = WIR_INTRINSIC; 1756 w->cgop = (u8)k; 1757 w->dst = dst[0].v.reg; 1758 w->dst2 = dst[1].v.reg; 1759 w->type = dst[0].type; 1760 w->cls = dst[0].cls; 1761 wir_capture_operand(w, 0, args[0]); 1762 wir_capture_operand(w, 1, args[1]); 1763 return; 1764 } 1765 1766 case INTRIN_SMUL_HIGH: 1767 case INTRIN_UMUL_HIGH: { 1768 if (ndst != 1 || nargs != 2 || dst[0].kind != OPK_REG) { 1769 compiler_panic(t->c, cur_loc(t), 1770 "wasm target: %s requires 2 args + 1 result reg", 1771 intrin_name(k)); 1772 return; 1773 } 1774 WIR* w = wir_push(t); 1775 w->op = WIR_INTRINSIC; 1776 w->cgop = (u8)k; 1777 w->dst = dst[0].v.reg; 1778 w->type = dst[0].type; 1779 w->cls = dst[0].cls; 1780 wir_capture_operand(w, 0, args[0]); 1781 wir_capture_operand(w, 1, args[1]); 1782 return; 1783 } 1784 1785 case INTRIN_SETJMP: 1786 case INTRIN_LONGJMP: 1787 compiler_panic(t->c, cur_loc(t), 1788 "wasm target: %s is not yet supported (no exception/" 1789 "stack-unwind runtime)", 1790 intrin_name(k)); 1791 return; 1792 1793 /* Baremetal/CPU-control intrinsics have no wasm lowering; 1794 * kit_cg_target_supports_intrinsic reports them false so frontends 1795 * diagnose before reaching here. Fall through to the generic panic. */ 1796 case INTRIN_CPU_NOP: 1797 case INTRIN_WFI: 1798 case INTRIN_WFE: 1799 case INTRIN_SEV: 1800 case INTRIN_ISB: 1801 case INTRIN_DMB: 1802 case INTRIN_DSB: 1803 case INTRIN_IRQ_SAVE: 1804 case INTRIN_IRQ_RESTORE: 1805 case INTRIN_IRQ_ENABLE: 1806 case INTRIN_IRQ_DISABLE: 1807 case INTRIN_SYSCALL: 1808 case INTRIN_READCYCLECOUNTER: 1809 case INTRIN_STACK_GUARD: 1810 /* No frame-pointer chain in wasm; reported unsupported up front. */ 1811 case INTRIN_FRAME_ADDRESS: 1812 case INTRIN_RETURN_ADDRESS: 1813 case INTRIN_NONE: 1814 break; 1815 } 1816 compiler_panic(t->c, cur_loc(t), 1817 "wasm target: intrinsic %s not yet implemented", 1818 intrin_name(k)); 1819 } 1820 1821 /* Inline asm v1 — see doc/WASM.md "Inline asm" for the surface contract. 1822 * 1823 * Template syntax: WAT instruction sequence. Inputs pre-pushed to the value 1824 * stack (via local.get on synthetic input locals); outputs popped from the 1825 * stack into synthetic output locals after the body. The snippet's 1826 * local.get/set/tee with index < nin refers to the i-th input. 1827 * 1828 * Constraints: "r" → wasm local; "i" → const-folded; "m" → i32 address. 1829 * Numeric tie-back constraints ("0","1",...) reuse the referenced output's 1830 * slot (cg/asm.c expands them into duplicate input entries at the end). 1831 * 1832 * Disallowed in v1: escaping br/br_if/br_table, return/return_call*, 1833 * call_indirect, snippet-internal locals, output count > 1, register 1834 * clobbers (only `memory` is accepted). */ 1835 static WasmValType wasm_asm_operand_vt(WTarget* t, KitCgTypeId ty, 1836 const char* what, SrcLoc loc) { 1837 WasmValType vt; 1838 if (!ty) wfail_at(t, loc, "wasm target: asm %s with no CG type", what); 1839 vt = valtype_for_type(t, ty); 1840 if (vt != WASM_VAL_I32 && vt != WASM_VAL_I64 && vt != WASM_VAL_F32 && 1841 vt != WASM_VAL_F64) 1842 wfail_at(t, loc, "wasm target: asm %s of non-scalar type not supported", 1843 what); 1844 return vt; 1845 } 1846 1847 void wasm_asm_block(CGTarget* tg, const char* tmpl, const AsmConstraint* outs, 1848 u32 nout, Operand* out_ops, const AsmConstraint* ins, 1849 u32 nin, const Operand* in_ops, const Sym* clob, 1850 u32 nclob) { 1851 WTarget* t = (WTarget*)tg; 1852 Heap* h = t->c->ctx->heap; 1853 Sym sym_memory; 1854 WasmFunc scratch; 1855 SrcLoc loc = cur_loc(t); 1856 u32 depth; 1857 u32 i; 1858 1859 if (t->dead) return; 1860 1861 /* Clobber policy: only `memory` is meaningful on wasm (effective no-op 1862 * because cg/asm.c already spilled live SSA values). Reject named-register 1863 * clobbers explicitly. */ 1864 sym_memory = pool_intern_slice(t->c->global, SLICE_LIT("memory")); 1865 for (i = 0; i < nclob; ++i) { 1866 if (clob[i] != sym_memory) 1867 wfail_at(t, loc, "wasm target: asm register clobbers not yet supported"); 1868 } 1869 for (i = 0; i < nout; ++i) { 1870 if (outs[i].reg) 1871 wfail_at(t, loc, "wasm target: asm hard-register operands not supported"); 1872 } 1873 for (i = 0; i < nin; ++i) { 1874 if (ins[i].reg) 1875 wfail_at(t, loc, "wasm target: asm hard-register operands not supported"); 1876 } 1877 1878 /* Build a scratch WasmFunc with the synthetic signature. Layout is: 1879 * params = input types (indices 0 .. nin-1) 1880 * locals = output types (indices nin .. nin+nout-1) 1881 * results = empty 1882 * Snippets use local.get/set/tee N to read/write either side. The author 1883 * is responsible for writing each output via local.set N (>= nin); empty 1884 * snippets paired with `+r` / numeric tieback constraints get identity 1885 * behavior because the input and output share a wasm local at lowering. */ 1886 memset(&scratch, 0, sizeof scratch); 1887 for (i = 0; i < nin; ++i) { 1888 WasmValType vt = wasm_asm_operand_vt(t, ins[i].type, "input operand", loc); 1889 /* Constraint-specific checks: "i" requires an immediate operand; "m" 1890 * requires an indirect (i32 address). */ 1891 if (ins[i].str && ins[i].str[0] == 'i' && in_ops[i].kind != OPK_IMM) 1892 wfail_at(t, loc, "wasm target: asm 'i' input must be an immediate"); 1893 if (ins[i].str && ins[i].str[0] == 'm') { 1894 if (in_ops[i].kind != OPK_INDIRECT) 1895 wfail_at(t, loc, "wasm target: asm 'm' input must be indirect"); 1896 vt = WASM_VAL_I32; 1897 } 1898 wasm_func_push_param(t->c, t->module, &scratch, vt); 1899 } 1900 for (i = 0; i < nout; ++i) { 1901 WasmValType vt = 1902 wasm_asm_operand_vt(t, outs[i].type, "output operand", loc); 1903 wasm_func_push_local(t->c, t->module, &scratch, vt); 1904 } 1905 /* No declared result — outputs are read from locals at the end of the 1906 * lowering, not popped from the value stack. */ 1907 1908 /* Parse the template into scratch.insns. */ 1909 wasm_parse_wat_body(t->c, t->module, &scratch, tmpl, strlen(tmpl), loc); 1910 1911 /* Walk the parsed body to reject constructs that escape or aren't 1912 * supported in v1. Track control depth so br/br_if/br_table with imm >= 1913 * depth (i.e. would branch out of the snippet) are rejected. */ 1914 depth = 0; 1915 for (i = 0; i < scratch.ninsns; ++i) { 1916 WasmInsn* in = &scratch.insns[i]; 1917 switch (in->kind) { 1918 case WASM_INSN_BLOCK: 1919 case WASM_INSN_LOOP: 1920 case WASM_INSN_IF: 1921 depth++; 1922 break; 1923 case WASM_INSN_END: 1924 if (depth) depth--; 1925 break; 1926 case WASM_INSN_BR: 1927 case WASM_INSN_BR_IF: 1928 if (in->imm < 0 || (u64)in->imm >= depth) 1929 wfail_at(t, in->loc, 1930 "wasm target: asm template branch escapes snippet"); 1931 break; 1932 case WASM_INSN_BR_TABLE: { 1933 u32 k; 1934 for (k = 0; k < in->ntargets; ++k) 1935 if (in->targets[k] >= depth) 1936 wfail_at(t, in->loc, 1937 "wasm target: asm template br_table escapes snippet"); 1938 break; 1939 } 1940 case WASM_INSN_RETURN: 1941 case WASM_INSN_RETURN_CALL: 1942 case WASM_INSN_RETURN_CALL_INDIRECT: 1943 case WASM_INSN_RETURN_CALL_REF: 1944 wfail_at(t, in->loc, 1945 "wasm target: return/tail-call in asm template not " 1946 "supported"); 1947 break; 1948 case WASM_INSN_LOCAL_GET: 1949 case WASM_INSN_LOCAL_SET: 1950 case WASM_INSN_LOCAL_TEE: 1951 if (in->imm < 0 || (u64)in->imm >= (u64)(nin + nout)) 1952 wfail_at(t, in->loc, 1953 "wasm target: asm template references local beyond " 1954 "declared operands (snippet-internal locals not " 1955 "supported)"); 1956 break; 1957 default: 1958 break; 1959 } 1960 } 1961 1962 /* Validate the body against the synthetic signature. */ 1963 wasm_validate_func(t->c, t->module, &scratch); 1964 1965 { 1966 /* Build the WIR_ASM_BLOCK payload. raw_insns is owned by the WIR; the 1967 * other arrays are too. Sizes are zero-when-empty per the WIR teardown 1968 * conventions. */ 1969 WIR* w = wir_push(t); 1970 w->op = WIR_ASM_BLOCK; 1971 w->raw_ninsns = scratch.ninsns; 1972 if (scratch.ninsns) { 1973 w->raw_insns = (WasmInsn*)h->alloc(h, sizeof(WasmInsn) * scratch.ninsns, 1974 _Alignof(WasmInsn)); 1975 if (!w->raw_insns) wfail(t, "wasm: out of memory"); 1976 memcpy(w->raw_insns, scratch.insns, sizeof(WasmInsn) * scratch.ninsns); 1977 } 1978 w->asm_nin = nin; 1979 w->asm_nout = nout; 1980 if (nin) { 1981 w->asm_in_kinds = (u8*)h->alloc(h, nin, 1); 1982 w->asm_in_imms = (i64*)h->alloc(h, sizeof(i64) * nin, _Alignof(i64)); 1983 w->asm_in_regs = (Reg*)h->alloc(h, sizeof(Reg) * nin, _Alignof(Reg)); 1984 w->asm_in_types = (KitCgTypeId*)h->alloc(h, sizeof(KitCgTypeId) * nin, 1985 _Alignof(KitCgTypeId)); 1986 w->asm_in_share_out = (i32*)h->alloc(h, sizeof(i32) * nin, _Alignof(i32)); 1987 if (!w->asm_in_kinds || !w->asm_in_imms || !w->asm_in_regs || 1988 !w->asm_in_types || !w->asm_in_share_out) 1989 wfail(t, "wasm: out of memory"); 1990 for (i = 0; i < nin; ++i) w->asm_in_share_out[i] = -1; 1991 for (i = 0; i < nin; ++i) { 1992 Operand op = in_ops[i]; 1993 /* Numeric tieback constraints ("0".."9") share the matching 1994 * output's wasm local. cg/asm.c also rewrites +r inout outputs 1995 * into duplicate inputs at the tail of ins[], using the same 1996 * numeric encoding. */ 1997 const char* s = ins[i].str; 1998 if (s && s[0] >= '0' && s[0] <= '9' && s[1] == '\0') { 1999 int idx = s[0] - '0'; 2000 if ((u32)idx < nout) w->asm_in_share_out[i] = idx; 2001 } 2002 switch (op.kind) { 2003 case OPK_REG: 2004 w->asm_in_kinds[i] = WOP_REG; 2005 w->asm_in_regs[i] = op.v.reg; 2006 w->asm_in_imms[i] = 0; 2007 w->asm_in_types[i] = op.type ? op.type : ins[i].type; 2008 break; 2009 case OPK_IMM: 2010 w->asm_in_kinds[i] = WOP_IMM; 2011 w->asm_in_regs[i] = REG_NONE; 2012 w->asm_in_imms[i] = op.v.imm; 2013 w->asm_in_types[i] = op.type ? op.type : ins[i].type; 2014 break; 2015 case OPK_INDIRECT: 2016 /* For "m" constraint: the input local holds the i32 address 2017 * `base + ofs` of the lvalue. We re-use asm_in_imms (unused 2018 * for WOP_REG operands) to carry the displacement so the 2019 * linearizer can splice in `i32.const ofs; i32.add` after 2020 * pushing the base local. */ 2021 w->asm_in_kinds[i] = WOP_REG; 2022 w->asm_in_regs[i] = op.v.ind.base; 2023 w->asm_in_imms[i] = (i64)op.v.ind.ofs; 2024 w->asm_in_types[i] = builtin_id(KIT_CG_BUILTIN_I32); 2025 break; 2026 default: 2027 wfail_at(t, loc, "wasm target: unsupported asm input operand kind"); 2028 } 2029 } 2030 } 2031 if (nout) { 2032 w->asm_out_regs = (Reg*)h->alloc(h, sizeof(Reg) * nout, _Alignof(Reg)); 2033 w->asm_out_types = (KitCgTypeId*)h->alloc(h, sizeof(KitCgTypeId) * nout, 2034 _Alignof(KitCgTypeId)); 2035 if (!w->asm_out_regs || !w->asm_out_types) 2036 wfail(t, "wasm: out of memory"); 2037 for (i = 0; i < nout; ++i) { 2038 if (out_ops[i].kind != OPK_REG) 2039 wfail_at(t, loc, "wasm target: asm output must be a register"); 2040 w->asm_out_regs[i] = out_ops[i].v.reg; 2041 w->asm_out_types[i] = out_ops[i].type ? out_ops[i].type : outs[i].type; 2042 } 2043 } 2044 } 2045 2046 /* Free scratch func storage. The parsed insns have been copied into the 2047 * WIR payload. */ 2048 if (scratch.params) 2049 h->free(h, scratch.params, sizeof(WasmValType) * scratch.cap_params); 2050 if (scratch.locals) 2051 h->free(h, scratch.locals, sizeof(WasmValType) * scratch.cap_locals); 2052 if (scratch.insns) 2053 h->free(h, scratch.insns, sizeof(WasmInsn) * scratch.cap_insns); 2054 } 2055 2056 void wasm_file_scope_asm(CGTarget* tg, const char* src, size_t len) { 2057 WTarget* t = (WTarget*)tg; 2058 (void)src; 2059 (void)len; 2060 compiler_panic(t->c, cur_loc(t), 2061 "wasm target: file-scope asm not yet supported"); 2062 } 2063 2064 void wasm_set_loc(CGTarget* tg, SrcLoc loc) { 2065 WTarget* t = (WTarget*)tg; 2066 /* No debug info in v1, but we stash the most recent loc so cur_loc / 2067 * diagnostics attribute to the actual statement rather than the 2068 * function-definition location. */ 2069 t->cur_stmt_loc = loc; 2070 } 2071 2072 void wasm_emit_unreachable(WTarget* t) { 2073 if (t->dead) return; 2074 WIR* w = wir_push(t); 2075 w->op = WIR_UNREACHABLE; 2076 t->dead = 1; 2077 } 2078 2079 /* Control terminator (the C __builtin_unreachable point): emit the Wasm 2080 * `unreachable` opcode, which traps if reached. Ends the current block. */ 2081 void wasm_unreachable(CGTarget* tg) { wasm_emit_unreachable((WTarget*)tg); } 2082 2083 /* ----------------------------------------------------------------- 2084 * WIR -> WasmFunc lowering 2085 * ----------------------------------------------------------------- */ 2086 2087 static void emit_insn(WTarget* t, WasmInsnKind k, i64 imm) { 2088 wasm_func_add_insn(t->c, t->module, t->cur_func, k, imm); 2089 } 2090 static void emit_fp(WTarget* t, WasmInsnKind k, double v) { 2091 wasm_func_add_fp_insn(t->c, t->module, t->cur_func, k, v); 2092 } 2093 2094 /* Push an operand onto the wasm value stack. */ 2095 static void emit_push_operand_reg(WTarget* t, Reg r) { 2096 if (r == REG_NONE) wfail(t, "wasm: push of REG_NONE"); 2097 /* The reg must already have a local. */ 2098 if (r >= t->reg_cap || t->reg_to_local[r] == 0xffffffffu) { 2099 wfail(t, "wasm: reg %u used before being defined", (unsigned)r); 2100 } 2101 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->reg_to_local[r]); 2102 } 2103 2104 static WasmValType type_valtype(WTarget* t, KitCgTypeId ty) { 2105 return valtype_for_type(t, ty); 2106 } 2107 2108 static void emit_push_imm(WTarget* t, WasmValType vt, i64 imm) { 2109 WasmInsnKind k = 2110 (vt == WASM_VAL_I64) ? WASM_INSN_I64_CONST : WASM_INSN_I32_CONST; 2111 emit_insn(t, k, imm); 2112 } 2113 2114 static u32 memarg_align_log2(u32 align, u32 width); 2115 static WasmInsnKind load_kind_for(WTarget* t, KitCgTypeId ty, MemAccess ma); 2116 2117 static void emit_push_operand(WTarget* t, u32 kind, i64 imm, Reg r, 2118 KitCgTypeId ty) { 2119 if (kind == WOP_IMM) { 2120 WasmValType vt = type_valtype(t, ty); 2121 if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64) { 2122 wfail(t, "wasm: float immediate operand not supported"); 2123 } 2124 emit_push_imm(t, vt, imm); 2125 } else if (kind == WOP_LOCAL) { 2126 FrameSlot fs = (FrameSlot)imm; 2127 WSlot* s = slot_for(t, fs); 2128 if (s->kind == W_SLOT_LOCAL) { 2129 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)s->wasm_local); 2130 } else { 2131 MemAccess ma; 2132 memset(&ma, 0, sizeof ma); 2133 ma.type = ty; 2134 ma.size = (u32)abi_cg_sizeof(t->c->abi, ty); 2135 ma.align = s->align; 2136 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_base_local); 2137 WasmInsnKind k = load_kind_for(t, ty, ma); 2138 wasm_func_add_mem_insn( 2139 t->c, t->module, t->cur_func, k, 2140 memarg_align_log2(ma.align, wasm_mem_width((uint8_t)k)), 2141 s->frame_offset, 0); 2142 } 2143 } else { 2144 emit_push_operand_reg(t, r); 2145 } 2146 } 2147 2148 static void emit_local_set(WTarget* t, Reg dst, KitCgTypeId ty, RegClass cls) { 2149 u32 idx = reg_local(t, dst, ty, cls); 2150 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)idx); 2151 } 2152 2153 static u32 memarg_align_log2(u32 align, u32 width) { 2154 u32 a = align ? align : width; 2155 u32 lg = 0; 2156 if (a > width) a = width; 2157 while (a > 1u) { 2158 a >>= 1u; 2159 lg++; 2160 } 2161 return lg; 2162 } 2163 2164 static WasmInsnKind load_kind_for(WTarget* t, KitCgTypeId ty, MemAccess ma) { 2165 WasmValType vt = type_valtype(t, ty); 2166 u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty); 2167 if (vt == WASM_VAL_F32) return WASM_INSN_F32_LOAD; 2168 if (vt == WASM_VAL_F64) return WASM_INSN_F64_LOAD; 2169 if (vt == WASM_VAL_I64) { 2170 if (size == 1) return WASM_INSN_I64_LOAD8_U; 2171 if (size == 2) return WASM_INSN_I64_LOAD16_U; 2172 if (size == 4) return WASM_INSN_I64_LOAD32_U; 2173 return WASM_INSN_I64_LOAD; 2174 } 2175 if (size == 1) return WASM_INSN_I32_LOAD8_U; 2176 if (size == 2) return WASM_INSN_I32_LOAD16_U; 2177 return WASM_INSN_I32_LOAD; 2178 } 2179 2180 static WasmInsnKind store_kind_for(WTarget* t, KitCgTypeId ty, MemAccess ma) { 2181 WasmValType vt = type_valtype(t, ty); 2182 u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty); 2183 if (vt == WASM_VAL_F32) return WASM_INSN_F32_STORE; 2184 if (vt == WASM_VAL_F64) return WASM_INSN_F64_STORE; 2185 if (vt == WASM_VAL_I64) { 2186 if (size == 1) return WASM_INSN_I64_STORE8; 2187 if (size == 2) return WASM_INSN_I64_STORE16; 2188 if (size == 4) return WASM_INSN_I64_STORE32; 2189 return WASM_INSN_I64_STORE; 2190 } 2191 if (size == 1) return WASM_INSN_I32_STORE8; 2192 if (size == 2) return WASM_INSN_I32_STORE16; 2193 return WASM_INSN_I32_STORE; 2194 } 2195 2196 /* Atomic op selection. Wasm threads gives natural-width atomic load/store for 2197 * i32/i64 (with 8/16/32 subword variants) but only full-width (i32/i64) RMW 2198 * and cmpxchg in kit's wasm core. Sub-word RMW/cmpxchg therefore diagnose 2199 * rather than silently widening. */ 2200 static WasmInsnKind atomic_load_kind_for(WTarget* t, KitCgTypeId ty, 2201 MemAccess ma) { 2202 WasmValType vt = type_valtype(t, ty); 2203 u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty); 2204 if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64) 2205 wfail(t, 2206 "wasm target: atomic load of floating-point value is not " 2207 "representable in wasm threads"); 2208 if (vt == WASM_VAL_I64) { 2209 if (size == 1) return WASM_INSN_I64_ATOMIC_LOAD8_U; 2210 if (size == 2) return WASM_INSN_I64_ATOMIC_LOAD16_U; 2211 if (size == 4) return WASM_INSN_I64_ATOMIC_LOAD32_U; 2212 if (size == 8) return WASM_INSN_I64_ATOMIC_LOAD; 2213 wfail(t, "wasm: atomic load i64 size %u not supported", size); 2214 } 2215 if (size == 1) return WASM_INSN_I32_ATOMIC_LOAD8_U; 2216 if (size == 2) return WASM_INSN_I32_ATOMIC_LOAD16_U; 2217 if (size == 4) return WASM_INSN_I32_ATOMIC_LOAD; 2218 wfail(t, "wasm: atomic load i32 size %u not supported", size); 2219 } 2220 2221 static WasmInsnKind atomic_store_kind_for(WTarget* t, KitCgTypeId ty, 2222 MemAccess ma) { 2223 WasmValType vt = type_valtype(t, ty); 2224 u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty); 2225 if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64) 2226 wfail(t, 2227 "wasm target: atomic store of floating-point value is not " 2228 "representable in wasm threads"); 2229 if (vt == WASM_VAL_I64) { 2230 if (size == 1) return WASM_INSN_I64_ATOMIC_STORE8; 2231 if (size == 2) return WASM_INSN_I64_ATOMIC_STORE16; 2232 if (size == 4) return WASM_INSN_I64_ATOMIC_STORE32; 2233 if (size == 8) return WASM_INSN_I64_ATOMIC_STORE; 2234 wfail(t, "wasm: atomic store i64 size %u not supported", size); 2235 } 2236 if (size == 1) return WASM_INSN_I32_ATOMIC_STORE8; 2237 if (size == 2) return WASM_INSN_I32_ATOMIC_STORE16; 2238 if (size == 4) return WASM_INSN_I32_ATOMIC_STORE; 2239 wfail(t, "wasm: atomic store i32 size %u not supported", size); 2240 } 2241 2242 static WasmInsnKind atomic_rmw_kind_for(WTarget* t, KitCgAtomicOp op, 2243 KitCgTypeId ty, MemAccess ma) { 2244 WasmValType vt = type_valtype(t, ty); 2245 u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty); 2246 int is64 = (vt == WASM_VAL_I64); 2247 if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64) 2248 wfail(t, 2249 "wasm target: atomic RMW on floating-point value is not " 2250 "representable in wasm threads"); 2251 if (!(size == 4 || size == 8) || (is64 && size != 8) || 2252 (!is64 && size != 4)) { 2253 wfail(t, 2254 "wasm target: atomic RMW size %u not yet supported (only " 2255 "full-width i32 and i64 atomic RMW are wired)", 2256 size); 2257 } 2258 switch (op) { 2259 case KIT_CG_ATOMIC_ADD: 2260 return is64 ? WASM_INSN_I64_ATOMIC_RMW_ADD : WASM_INSN_I32_ATOMIC_RMW_ADD; 2261 case KIT_CG_ATOMIC_SUB: 2262 return is64 ? WASM_INSN_I64_ATOMIC_RMW_SUB : WASM_INSN_I32_ATOMIC_RMW_SUB; 2263 case KIT_CG_ATOMIC_AND: 2264 return is64 ? WASM_INSN_I64_ATOMIC_RMW_AND : WASM_INSN_I32_ATOMIC_RMW_AND; 2265 case KIT_CG_ATOMIC_OR: 2266 return is64 ? WASM_INSN_I64_ATOMIC_RMW_OR : WASM_INSN_I32_ATOMIC_RMW_OR; 2267 case KIT_CG_ATOMIC_XOR: 2268 return is64 ? WASM_INSN_I64_ATOMIC_RMW_XOR : WASM_INSN_I32_ATOMIC_RMW_XOR; 2269 case KIT_CG_ATOMIC_XCHG: 2270 return is64 ? WASM_INSN_I64_ATOMIC_RMW_XCHG 2271 : WASM_INSN_I32_ATOMIC_RMW_XCHG; 2272 case KIT_CG_ATOMIC_NAND: 2273 wfail(t, "wasm target: atomic NAND has no native wasm-threads opcode"); 2274 } 2275 wfail(t, "wasm: unsupported atomic RMW op %d", (int)op); 2276 } 2277 2278 static WasmInsnKind atomic_cmpxchg_kind_for(WTarget* t, KitCgTypeId ty, 2279 MemAccess ma) { 2280 WasmValType vt = type_valtype(t, ty); 2281 u32 size = ma.size ? ma.size : (u32)abi_cg_sizeof(t->c->abi, ty); 2282 if (vt == WASM_VAL_F32 || vt == WASM_VAL_F64) 2283 wfail(t, 2284 "wasm target: atomic cmpxchg on floating-point value is not " 2285 "representable in wasm threads"); 2286 if (vt == WASM_VAL_I64) { 2287 if (size != 8) 2288 wfail(t, "wasm target: atomic cmpxchg i64 size %u not yet supported", 2289 size); 2290 return WASM_INSN_I64_ATOMIC_RMW_CMPXCHG; 2291 } 2292 if (size != 4) 2293 wfail(t, "wasm target: atomic cmpxchg i32 size %u not yet supported", size); 2294 return WASM_INSN_I32_ATOMIC_RMW_CMPXCHG; 2295 } 2296 2297 /* Look up (or assign) `sym`'s slot in the funcref table. Returned index is 2298 * the wasm table index (>= 1, with 0 reserved as the null/trap slot). */ 2299 static u32 func_table_index_for(WTarget* t, ObjSymId sym) { 2300 Heap* h = t->c->ctx->heap; 2301 for (u32 i = 0; i < t->func_table_count; ++i) { 2302 if (t->func_table[i] == sym) return i + 1u; 2303 } 2304 if (t->func_table_count == t->func_table_cap) { 2305 u32 nc = t->func_table_cap ? t->func_table_cap * 2u : 8u; 2306 void* p = h->realloc(h, t->func_table, sizeof(ObjSymId) * t->func_table_cap, 2307 sizeof(ObjSymId) * nc, _Alignof(ObjSymId)); 2308 if (!p) wfail(t, "wasm: out of memory"); 2309 t->func_table = (ObjSymId*)p; 2310 t->func_table_cap = nc; 2311 } 2312 t->func_table[t->func_table_count] = sym; 2313 t->has_func_table = 1; 2314 return ++t->func_table_count; /* slot 0 reserved; first sym -> index 1 */ 2315 } 2316 2317 /* Defer function-pointer materialization to wasm_materialize_functable. 2318 * Emits `i32.const 0` and queues a WFuncTableFixup keyed by the placeholder's 2319 * (cur_func_idx, ninsns-1). */ 2320 static void queue_func_table_fixup(WTarget* t, ObjSymId sym) { 2321 Heap* h = t->c->ctx->heap; 2322 if (!t->cur_func) wfail(t, "wasm: function address outside a function"); 2323 /* Ensure the function gets a slot and force the WasmFunc shell to exist so 2324 * the table's element segment can resolve its wasm-func index later. */ 2325 (void)func_table_index_for(t, sym); 2326 (void)sym_to_wasm_func(t, sym, NULL); 2327 emit_insn(t, WASM_INSN_I32_CONST, 0); 2328 if (t->func_table_fixups_count == t->func_table_fixups_cap) { 2329 u32 nc = t->func_table_fixups_cap ? t->func_table_fixups_cap * 2u : 16u; 2330 void* p = 2331 h->realloc(h, t->func_table_fixups, 2332 sizeof(WFuncTableFixup) * t->func_table_fixups_cap, 2333 sizeof(WFuncTableFixup) * nc, _Alignof(WFuncTableFixup)); 2334 if (!p) wfail(t, "wasm: out of memory"); 2335 t->func_table_fixups = (WFuncTableFixup*)p; 2336 t->func_table_fixups_cap = nc; 2337 } 2338 WFuncTableFixup* fx = &t->func_table_fixups[t->func_table_fixups_count++]; 2339 fx->wasm_func_idx = t->cur_func_idx; 2340 fx->insn_idx = t->cur_func->ninsns - 1u; 2341 fx->sym = sym; 2342 } 2343 2344 /* Defer symbol-address resolution to wasm_materialize_data. Emits an 2345 * i32.const placeholder into the current function and queues a WSymFixup 2346 * keyed by (cur_func_idx, ninsns-1). The compact section layout is only 2347 * known once every section's final size is settled, which doesn't happen 2348 * until finalize. */ 2349 static void queue_symbol_addr_fixup(WTarget* t, ObjSymId sym, i64 addend) { 2350 Heap* h = t->c->ctx->heap; 2351 const ObjSym* os = obj_symbol_get(t->obj, sym); 2352 /* Function-symbol addresses route through the funcref table, not linear 2353 * memory. CG occasionally takes the address of an extern function before 2354 * the function body is seen (forward declarations, indirect-call 2355 * setup); the SK_FUNC kind is set by the frontend at sym creation. */ 2356 if (os && os->kind == SK_FUNC) { 2357 if (addend != 0) 2358 wfail(t, "wasm: nonzero addend on function-pointer reference"); 2359 queue_func_table_fixup(t, sym); 2360 return; 2361 } 2362 if (!os) 2363 wfail(t, "wasm target: address of unresolved symbol not yet implemented"); 2364 if (os->section_id == OBJ_SEC_NONE && os->kind != SK_COMMON) 2365 wfail(t, "wasm target: address of undefined symbol not yet implemented"); 2366 /* SK_COMMON falls through: apply_sym_fixups allocates a BSS-style base for 2367 * it in wasm_materialize_data and patches the i32.const at finalize. */ 2368 if (addend < INT32_MIN || addend > INT32_MAX) 2369 wfail(t, "wasm: symbol addend out of range"); 2370 if (!t->cur_func) wfail(t, "wasm: symbol address outside a function"); 2371 emit_insn(t, WASM_INSN_I32_CONST, 0); 2372 if (t->sym_fixups_count == t->sym_fixups_cap) { 2373 u32 nc = t->sym_fixups_cap ? t->sym_fixups_cap * 2u : 16u; 2374 void* p = 2375 h->realloc(h, t->sym_fixups, sizeof(WSymFixup) * t->sym_fixups_cap, 2376 sizeof(WSymFixup) * nc, _Alignof(WSymFixup)); 2377 if (!p) wfail(t, "wasm: out of memory"); 2378 t->sym_fixups = (WSymFixup*)p; 2379 t->sym_fixups_cap = nc; 2380 } 2381 WSymFixup* fx = &t->sym_fixups[t->sym_fixups_count++]; 2382 fx->wasm_func_idx = t->cur_func_idx; 2383 fx->insn_idx = t->cur_func->ninsns - 1u; 2384 fx->sym = sym; 2385 fx->addend = addend; 2386 } 2387 2388 /* Push the value of an OPK_INDIRECT base/index component. The CG defers loading 2389 * the pointer value of an address-taken (frame-resident) pointer local to the 2390 * backend: the deref of such a local arrives as an OPK_INDIRECT whose base 2391 * names the local itself, not a materialized register (see 2392 * fold_ea_into_operand, and native_direct_target's nd_cache_reg_for, which 2393 * loads it from the home). In the wasm backend each id is either a register 2394 * (reg_to_local set) or a frame slot, never both — so dispatch on that: a 2395 * register is fetched directly; a frame-resident local is read from its home 2396 * like any other WOP_LOCAL operand. */ 2397 static void emit_push_addr_component(WTarget* t, Reg id) { 2398 if (id < t->reg_cap && t->reg_to_local[id] != 0xffffffffu) { 2399 emit_push_operand_reg(t, id); 2400 } else { 2401 WSlot* s = slot_for(t, id); 2402 emit_push_operand(t, WOP_LOCAL, (i64)id, REG_NONE, s->type); 2403 } 2404 } 2405 2406 /* Value type of an indirect component, whether it lives in a register or a 2407 * frame slot (used to decide i64->i32 address narrowing). */ 2408 static WasmValType addr_component_valtype(WTarget* t, Reg id) { 2409 if (id < t->reg_cap && t->reg_to_local[id] != 0xffffffffu && t->reg_type[id]) 2410 return type_valtype(t, t->reg_type[id]); 2411 return type_valtype(t, slot_for(t, id)->type); 2412 } 2413 2414 static void emit_addr_operand(WTarget* t, Operand addr, uint64_t* offset_out) { 2415 *offset_out = 0; 2416 if (addr.kind == OPK_LOCAL) { 2417 WSlot* s = slot_for(t, addr.v.frame_slot); 2418 if (s->kind != W_SLOT_STACK) 2419 wfail(t, "wasm: address of non-addressable local"); 2420 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_base_local); 2421 *offset_out = s->frame_offset; 2422 return; 2423 } 2424 if (addr.kind == OPK_INDIRECT) { 2425 emit_push_addr_component(t, addr.v.ind.base); 2426 if (addr.v.ind.index != REG_NONE) { 2427 emit_push_addr_component(t, addr.v.ind.index); 2428 if (addr_component_valtype(t, addr.v.ind.index) == WASM_VAL_I64) { 2429 emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 2430 } 2431 if (addr.v.ind.log2_scale != 0) { 2432 emit_insn(t, WASM_INSN_I32_CONST, (i64)addr.v.ind.log2_scale); 2433 emit_insn(t, WASM_INSN_I32_SHL, 0); 2434 } 2435 emit_insn(t, WASM_INSN_I32_ADD, 0); 2436 } 2437 if (addr.v.ind.ofs >= 0) { 2438 *offset_out = (uint32_t)addr.v.ind.ofs; 2439 } else { 2440 emit_insn(t, WASM_INSN_I32_CONST, (i64)addr.v.ind.ofs); 2441 emit_insn(t, WASM_INSN_I32_ADD, 0); 2442 } 2443 return; 2444 } 2445 if (addr.kind == OPK_GLOBAL) { 2446 queue_symbol_addr_fixup(t, addr.v.global.sym, addr.v.global.addend); 2447 return; 2448 } 2449 if (addr.kind == OPK_REG) { 2450 /* An i32 address already materialized in a register: just push it. */ 2451 emit_push_operand_reg(t, addr.v.reg); 2452 return; 2453 } 2454 wfail(t, "wasm: unsupported address operand kind %u", (unsigned)addr.kind); 2455 } 2456 2457 /* Push a complete i32 address value (folding any positive offset into the base 2458 * via i32.add). Used by bulk-memory ops (memory.copy / memory.fill) which take 2459 * the address as a stack operand and carry no memarg offset. */ 2460 static void emit_push_addr_value(WTarget* t, Operand addr) { 2461 uint64_t off; 2462 emit_addr_operand(t, addr, &off); 2463 if (off != 0) { 2464 emit_insn(t, WASM_INSN_I32_CONST, (i64)(uint32_t)off); 2465 emit_insn(t, WASM_INSN_I32_ADD, 0); 2466 } 2467 } 2468 2469 static void emit_load_addr(WTarget* t, Operand addr, KitCgTypeId ty, 2470 MemAccess ma) { 2471 uint64_t offset; 2472 WasmInsnKind k = load_kind_for(t, ty, ma); 2473 u32 width = wasm_mem_width((uint8_t)k); 2474 emit_addr_operand(t, addr, &offset); 2475 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, 2476 memarg_align_log2(ma.align, width), offset, 0); 2477 } 2478 2479 static void emit_store_addr(WTarget* t, Operand addr, KitCgTypeId ty, 2480 Operand src, MemAccess ma, u32 src_kind, 2481 i64 src_imm, Reg src_reg) { 2482 uint64_t offset; 2483 WasmInsnKind k = store_kind_for(t, ty, ma); 2484 u32 width = wasm_mem_width((uint8_t)k); 2485 emit_addr_operand(t, addr, &offset); 2486 if (src_kind == WOP_IMM) { 2487 emit_push_imm(t, type_valtype(t, ty), src_imm); 2488 } else if (src_kind == WOP_WASM_LOCAL) { 2489 emit_insn(t, WASM_INSN_LOCAL_GET, src_imm); 2490 } else if (src.kind == OPK_IMM) { 2491 emit_push_imm(t, type_valtype(t, ty), src.v.imm); 2492 } else if (src_kind == WOP_LOCAL) { 2493 emit_push_operand(t, WOP_LOCAL, src_imm, REG_NONE, ty); 2494 } else { 2495 emit_push_operand_reg(t, src_reg); 2496 } 2497 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, 2498 memarg_align_log2(ma.align, width), offset, 0); 2499 } 2500 2501 /* Map (BinOp, valtype) to wasm opcode. */ 2502 static WasmInsnKind binop_kind(WTarget* t, BinOp op, WasmValType vt) { 2503 switch (op) { 2504 case BO_IADD: 2505 return vt == WASM_VAL_I64 ? WASM_INSN_I64_ADD : WASM_INSN_I32_ADD; 2506 case BO_ISUB: 2507 return vt == WASM_VAL_I64 ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB; 2508 case BO_IMUL: 2509 return vt == WASM_VAL_I64 ? WASM_INSN_I64_MUL : WASM_INSN_I32_MUL; 2510 case BO_SDIV: 2511 return vt == WASM_VAL_I64 ? WASM_INSN_I64_DIV_S : WASM_INSN_I32_DIV_S; 2512 case BO_UDIV: 2513 return vt == WASM_VAL_I64 ? WASM_INSN_I64_DIV_U : WASM_INSN_I32_DIV_U; 2514 case BO_SREM: 2515 return vt == WASM_VAL_I64 ? WASM_INSN_I64_REM_S : WASM_INSN_I32_REM_S; 2516 case BO_UREM: 2517 return vt == WASM_VAL_I64 ? WASM_INSN_I64_REM_U : WASM_INSN_I32_REM_U; 2518 case BO_AND: 2519 return vt == WASM_VAL_I64 ? WASM_INSN_I64_AND : WASM_INSN_I32_AND; 2520 case BO_OR: 2521 return vt == WASM_VAL_I64 ? WASM_INSN_I64_OR : WASM_INSN_I32_OR; 2522 case BO_XOR: 2523 return vt == WASM_VAL_I64 ? WASM_INSN_I64_XOR : WASM_INSN_I32_XOR; 2524 case BO_SHL: 2525 return vt == WASM_VAL_I64 ? WASM_INSN_I64_SHL : WASM_INSN_I32_SHL; 2526 case BO_SHR_S: 2527 return vt == WASM_VAL_I64 ? WASM_INSN_I64_SHR_S : WASM_INSN_I32_SHR_S; 2528 case BO_SHR_U: 2529 return vt == WASM_VAL_I64 ? WASM_INSN_I64_SHR_U : WASM_INSN_I32_SHR_U; 2530 case BO_FADD: 2531 return vt == WASM_VAL_F64 ? WASM_INSN_F64_ADD : WASM_INSN_F32_ADD; 2532 case BO_FSUB: 2533 return vt == WASM_VAL_F64 ? WASM_INSN_F64_SUB : WASM_INSN_F32_SUB; 2534 case BO_FMUL: 2535 return vt == WASM_VAL_F64 ? WASM_INSN_F64_MUL : WASM_INSN_F32_MUL; 2536 case BO_FDIV: 2537 return vt == WASM_VAL_F64 ? WASM_INSN_F64_DIV : WASM_INSN_F32_DIV; 2538 } 2539 wfail(t, "wasm: unsupported binop %d", (int)op); 2540 } 2541 2542 static WasmInsnKind cmp_kind(WTarget* t, CmpOp op, WasmValType vt) { 2543 int is64 = (vt == WASM_VAL_I64); 2544 switch (op) { 2545 case CMP_EQ: 2546 return is64 ? WASM_INSN_I64_EQ : WASM_INSN_I32_EQ; 2547 case CMP_NE: 2548 return is64 ? WASM_INSN_I64_NE : WASM_INSN_I32_NE; 2549 case CMP_LT_S: 2550 return is64 ? WASM_INSN_I64_LT_S : WASM_INSN_I32_LT_S; 2551 case CMP_LE_S: 2552 return is64 ? WASM_INSN_I64_LE_S : WASM_INSN_I32_LE_S; 2553 case CMP_GT_S: 2554 return is64 ? WASM_INSN_I64_GT_S : WASM_INSN_I32_GT_S; 2555 case CMP_GE_S: 2556 return is64 ? WASM_INSN_I64_GE_S : WASM_INSN_I32_GE_S; 2557 case CMP_LT_U: 2558 return is64 ? WASM_INSN_I64_LT_U : WASM_INSN_I32_LT_U; 2559 case CMP_LE_U: 2560 return is64 ? WASM_INSN_I64_LE_U : WASM_INSN_I32_LE_U; 2561 case CMP_GT_U: 2562 return is64 ? WASM_INSN_I64_GT_U : WASM_INSN_I32_GT_U; 2563 case CMP_GE_U: 2564 return is64 ? WASM_INSN_I64_GE_U : WASM_INSN_I32_GE_U; 2565 /* FP compares are lowered by emit_fp_cmp (they may need multiple wasm 2566 * instructions) and never reach cmp_kind. Listed so -Wswitch stays useful. 2567 */ 2568 case CMP_OEQ_F: 2569 case CMP_ONE_F: 2570 case CMP_OLT_F: 2571 case CMP_OLE_F: 2572 case CMP_OGT_F: 2573 case CMP_OGE_F: 2574 case CMP_UEQ_F: 2575 case CMP_UNE_F: 2576 case CMP_ULT_F: 2577 case CMP_ULE_F: 2578 case CMP_UGT_F: 2579 case CMP_UGE_F: 2580 break; 2581 } 2582 wfail(t, "wasm: unsupported cmp %d", (int)op); 2583 } 2584 2585 /* Push both compare operands (a then b) onto the wasm stack. */ 2586 static void push_cmp_operands(WTarget* t, WIR* w, KitCgTypeId opty) { 2587 emit_push_operand(t, w->imm_kind, w->imm_a, w->a, opty); 2588 emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, opty); 2589 } 2590 2591 /* Lower an FP compare to the wasm stack, leaving an i32 0/1 result. wasm's 2592 * f.eq/f.lt/f.le/f.gt/f.ge are ordered (false on NaN) and f.ne is unordered 2593 * (true on NaN), so the 12 IEEE predicates compose from those plus i32.eqz / 2594 * i32.or, using unordered-R == !(ordered-not-R). ONE/UEQ need both operands 2595 * twice, so push_cmp_operands runs again for the second relation. */ 2596 static void emit_fp_cmp(WTarget* t, CmpOp op, WIR* w, KitCgTypeId opty) { 2597 int d = (type_valtype(t, opty) == WASM_VAL_F64); 2598 WasmInsnKind EQ = d ? WASM_INSN_F64_EQ : WASM_INSN_F32_EQ; 2599 WasmInsnKind NE = d ? WASM_INSN_F64_NE : WASM_INSN_F32_NE; 2600 WasmInsnKind LT = d ? WASM_INSN_F64_LT : WASM_INSN_F32_LT; 2601 WasmInsnKind LE = d ? WASM_INSN_F64_LE : WASM_INSN_F32_LE; 2602 WasmInsnKind GT = d ? WASM_INSN_F64_GT : WASM_INSN_F32_GT; 2603 WasmInsnKind GE = d ? WASM_INSN_F64_GE : WASM_INSN_F32_GE; 2604 switch (op) { 2605 case CMP_OEQ_F: 2606 push_cmp_operands(t, w, opty); 2607 emit_insn(t, EQ, 0); 2608 return; 2609 case CMP_UNE_F: 2610 push_cmp_operands(t, w, opty); 2611 emit_insn(t, NE, 0); 2612 return; 2613 case CMP_OLT_F: 2614 push_cmp_operands(t, w, opty); 2615 emit_insn(t, LT, 0); 2616 return; 2617 case CMP_OLE_F: 2618 push_cmp_operands(t, w, opty); 2619 emit_insn(t, LE, 0); 2620 return; 2621 case CMP_OGT_F: 2622 push_cmp_operands(t, w, opty); 2623 emit_insn(t, GT, 0); 2624 return; 2625 case CMP_OGE_F: 2626 push_cmp_operands(t, w, opty); 2627 emit_insn(t, GE, 0); 2628 return; 2629 case CMP_UGE_F: /* !(OLT) */ 2630 push_cmp_operands(t, w, opty); 2631 emit_insn(t, LT, 0); 2632 emit_insn(t, WASM_INSN_I32_EQZ, 0); 2633 return; 2634 case CMP_UGT_F: /* !(OLE) */ 2635 push_cmp_operands(t, w, opty); 2636 emit_insn(t, LE, 0); 2637 emit_insn(t, WASM_INSN_I32_EQZ, 0); 2638 return; 2639 case CMP_ULE_F: /* !(OGT) */ 2640 push_cmp_operands(t, w, opty); 2641 emit_insn(t, GT, 0); 2642 emit_insn(t, WASM_INSN_I32_EQZ, 0); 2643 return; 2644 case CMP_ULT_F: /* !(OGE) */ 2645 push_cmp_operands(t, w, opty); 2646 emit_insn(t, GE, 0); 2647 emit_insn(t, WASM_INSN_I32_EQZ, 0); 2648 return; 2649 case CMP_ONE_F: /* ordered & !=: (a<b) | (a>b) */ 2650 push_cmp_operands(t, w, opty); 2651 emit_insn(t, LT, 0); 2652 push_cmp_operands(t, w, opty); 2653 emit_insn(t, GT, 0); 2654 emit_insn(t, WASM_INSN_I32_OR, 0); 2655 return; 2656 case CMP_UEQ_F: /* unordered | ==: !((a<b) | (a>b)) */ 2657 push_cmp_operands(t, w, opty); 2658 emit_insn(t, LT, 0); 2659 push_cmp_operands(t, w, opty); 2660 emit_insn(t, GT, 0); 2661 emit_insn(t, WASM_INSN_I32_OR, 0); 2662 emit_insn(t, WASM_INSN_I32_EQZ, 0); 2663 return; 2664 default: 2665 wfail(t, "wasm: unsupported fp cmp %d", (int)op); 2666 } 2667 } 2668 2669 static void emit_convert(WTarget* t, ConvKind ck, WasmValType src, 2670 WasmValType dst, u32 sw, u32 dw) { 2671 (void)dw; 2672 /* Integer sign/zero extension. Sub-i32 logical widths (i8/i16) share the i32 2673 * valtype, so a "same valtype" SEXT/ZEXT is NOT a no-op — the high bits must 2674 * be filled per the source's logical width (sw). The CG IR keeps narrow 2675 * immediates as truncated bit patterns, so without this an i8 value like 2676 * (signed char)-128 reads back as 128. */ 2677 if (ck == CV_SEXT && src != WASM_VAL_F32 && src != WASM_VAL_F64) { 2678 if (src == WASM_VAL_I32) { 2679 if (sw == 8u) 2680 emit_insn(t, WASM_INSN_I32_EXTEND8_S, 0); 2681 else if (sw == 16u) 2682 emit_insn(t, WASM_INSN_I32_EXTEND16_S, 0); 2683 } else { 2684 if (sw == 8u) 2685 emit_insn(t, WASM_INSN_I64_EXTEND8_S, 0); 2686 else if (sw == 16u) 2687 emit_insn(t, WASM_INSN_I64_EXTEND16_S, 0); 2688 else if (sw == 32u) 2689 emit_insn(t, WASM_INSN_I64_EXTEND32_S, 0); 2690 } 2691 if (src == WASM_VAL_I32 && dst == WASM_VAL_I64) 2692 emit_insn(t, WASM_INSN_I64_EXTEND_I32_S, 0); 2693 else if (src == WASM_VAL_I64 && dst == WASM_VAL_I32) 2694 emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 2695 return; 2696 } 2697 if (ck == CV_ZEXT && src != WASM_VAL_F32 && src != WASM_VAL_F64) { 2698 if (src == WASM_VAL_I32) { 2699 if (sw > 0u && sw < 32u) { 2700 emit_insn(t, WASM_INSN_I32_CONST, (i64)(((u32)1 << sw) - 1u)); 2701 emit_insn(t, WASM_INSN_I32_AND, 0); 2702 } 2703 if (dst == WASM_VAL_I64) emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0); 2704 } else { 2705 if (sw > 0u && sw < 64u) { 2706 emit_push_imm(t, WASM_VAL_I64, (i64)(((u64)1 << sw) - 1u)); 2707 emit_insn(t, WASM_INSN_I64_AND, 0); 2708 } 2709 if (dst == WASM_VAL_I32) emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 2710 } 2711 return; 2712 } 2713 if (src == dst && (ck == CV_BITCAST || ck == CV_TRUNC)) { 2714 /* No-op conversion. */ 2715 return; 2716 } 2717 if (ck == CV_BITCAST) { 2718 if (src == WASM_VAL_I32 && dst == WASM_VAL_F32) { 2719 emit_insn(t, WASM_INSN_F32_REINTERPRET_I32, 0); 2720 return; 2721 } 2722 if (src == WASM_VAL_F32 && dst == WASM_VAL_I32) { 2723 emit_insn(t, WASM_INSN_I32_REINTERPRET_F32, 0); 2724 return; 2725 } 2726 if (src == WASM_VAL_I64 && dst == WASM_VAL_F64) { 2727 emit_insn(t, WASM_INSN_F64_REINTERPRET_I64, 0); 2728 return; 2729 } 2730 if (src == WASM_VAL_F64 && dst == WASM_VAL_I64) { 2731 emit_insn(t, WASM_INSN_I64_REINTERPRET_F64, 0); 2732 return; 2733 } 2734 /* Width-changing ptr/int bitcasts: kit_cg_ptr_to_int and 2735 * kit_cg_int_to_ptr route through CV_BITCAST, and on wasm32 a pointer 2736 * is i32 while the frontend integer side may be i64. Lower as 2737 * wrap/extend (zero-extend; pointers are non-negative addresses). */ 2738 if (src == WASM_VAL_I64 && dst == WASM_VAL_I32) { 2739 emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 2740 return; 2741 } 2742 if (src == WASM_VAL_I32 && dst == WASM_VAL_I64) { 2743 emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0); 2744 return; 2745 } 2746 wfail(t, "wasm: unsupported bitcast"); 2747 } 2748 if (ck == CV_TRUNC) { 2749 if (src == WASM_VAL_I64 && dst == WASM_VAL_I32) { 2750 emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 2751 return; 2752 } 2753 } 2754 if (ck == CV_FEXT && src == WASM_VAL_F32 && dst == WASM_VAL_F64) { 2755 emit_insn(t, WASM_INSN_F64_PROMOTE_F32, 0); 2756 return; 2757 } 2758 if (ck == CV_FTRUNC && src == WASM_VAL_F64 && dst == WASM_VAL_F32) { 2759 emit_insn(t, WASM_INSN_F32_DEMOTE_F64, 0); 2760 return; 2761 } 2762 if (ck == CV_ITOF_S) { 2763 if (src == WASM_VAL_I32 && dst == WASM_VAL_F32) { 2764 emit_insn(t, WASM_INSN_F32_CONVERT_I32_S, 0); 2765 return; 2766 } 2767 if (src == WASM_VAL_I32 && dst == WASM_VAL_F64) { 2768 emit_insn(t, WASM_INSN_F64_CONVERT_I32_S, 0); 2769 return; 2770 } 2771 if (src == WASM_VAL_I64 && dst == WASM_VAL_F32) { 2772 emit_insn(t, WASM_INSN_F32_CONVERT_I64_S, 0); 2773 return; 2774 } 2775 if (src == WASM_VAL_I64 && dst == WASM_VAL_F64) { 2776 emit_insn(t, WASM_INSN_F64_CONVERT_I64_S, 0); 2777 return; 2778 } 2779 } 2780 if (ck == CV_ITOF_U) { 2781 if (src == WASM_VAL_I32 && dst == WASM_VAL_F32) { 2782 emit_insn(t, WASM_INSN_F32_CONVERT_I32_U, 0); 2783 return; 2784 } 2785 if (src == WASM_VAL_I32 && dst == WASM_VAL_F64) { 2786 emit_insn(t, WASM_INSN_F64_CONVERT_I32_U, 0); 2787 return; 2788 } 2789 if (src == WASM_VAL_I64 && dst == WASM_VAL_F32) { 2790 emit_insn(t, WASM_INSN_F32_CONVERT_I64_U, 0); 2791 return; 2792 } 2793 if (src == WASM_VAL_I64 && dst == WASM_VAL_F64) { 2794 emit_insn(t, WASM_INSN_F64_CONVERT_I64_U, 0); 2795 return; 2796 } 2797 } 2798 if (ck == CV_FTOI_S) { 2799 if (src == WASM_VAL_F32 && dst == WASM_VAL_I32) { 2800 emit_insn(t, WASM_INSN_I32_TRUNC_F32_S, 0); 2801 return; 2802 } 2803 if (src == WASM_VAL_F64 && dst == WASM_VAL_I32) { 2804 emit_insn(t, WASM_INSN_I32_TRUNC_F64_S, 0); 2805 return; 2806 } 2807 if (src == WASM_VAL_F32 && dst == WASM_VAL_I64) { 2808 emit_insn(t, WASM_INSN_I64_TRUNC_F32_S, 0); 2809 return; 2810 } 2811 if (src == WASM_VAL_F64 && dst == WASM_VAL_I64) { 2812 emit_insn(t, WASM_INSN_I64_TRUNC_F64_S, 0); 2813 return; 2814 } 2815 } 2816 if (ck == CV_FTOI_U) { 2817 if (src == WASM_VAL_F32 && dst == WASM_VAL_I32) { 2818 emit_insn(t, WASM_INSN_I32_TRUNC_F32_U, 0); 2819 return; 2820 } 2821 if (src == WASM_VAL_F64 && dst == WASM_VAL_I32) { 2822 emit_insn(t, WASM_INSN_I32_TRUNC_F64_U, 0); 2823 return; 2824 } 2825 if (src == WASM_VAL_F32 && dst == WASM_VAL_I64) { 2826 emit_insn(t, WASM_INSN_I64_TRUNC_F32_U, 0); 2827 return; 2828 } 2829 if (src == WASM_VAL_F64 && dst == WASM_VAL_I64) { 2830 emit_insn(t, WASM_INSN_I64_TRUNC_F64_U, 0); 2831 return; 2832 } 2833 } 2834 wfail(t, "wasm: unsupported convert kind %d (%d -> %d)", (int)ck, (int)src, 2835 (int)dst); 2836 } 2837 2838 /* During lowering we keep a running active-scope stack so we can compute 2839 * br depths. */ 2840 typedef struct LoweringScope { 2841 u32 id; 2842 u8 kind; 2843 /* Depth at which break/continue targets are reached. */ 2844 u32 break_depth; 2845 u32 cont_depth; 2846 } LoweringScope; 2847 2848 typedef struct LoweringState { 2849 WTarget* t; 2850 /* Bounded by the deepest synthetic + CG scope nesting we'll emit. 2851 * Switch islands wrap one block per case, so the limit is roughly 2852 * (max cases + max user nesting). 1024 leaves room for very wide 2853 * switches without forcing future per-case-count caps. */ 2854 LoweringScope stack[1024]; 2855 u32 nstack; 2856 u32 cur_depth; 2857 } LoweringState; 2858 2859 static u32 br_to_label(LoweringState* L, Label l) { 2860 WLabel* lbl = lookup_label(L->t, l); 2861 if (!lbl) wfail(L->t, "wasm: br to unknown label"); 2862 if (lbl->kind == WLBL_SCOPE_BREAK) { 2863 for (u32 i = L->nstack; i > 0; --i) { 2864 if (L->stack[i - 1u].id == lbl->scope_id) { 2865 return L->cur_depth - L->stack[i - 1u].break_depth; 2866 } 2867 } 2868 wfail(L->t, "wasm: br to break label of inactive scope"); 2869 } 2870 if (lbl->kind == WLBL_SCOPE_CONT) { 2871 for (u32 i = L->nstack; i > 0; --i) { 2872 if (L->stack[i - 1u].id == lbl->scope_id) { 2873 return L->cur_depth - L->stack[i - 1u].cont_depth; 2874 } 2875 } 2876 wfail(L->t, "wasm: br to continue label of inactive scope"); 2877 } 2878 /* wasm_structurize wraps every reachable forward label in a synthetic 2879 * SCOPE_BLOCK (forward goto) or SCOPE_LOOP (backward goto), and 2880 * unroll_switch_islands reorders the WIR so switch case labels are 2881 * forward refs from WIR_SWITCH. Arriving here means the structurer 2882 * missed a shape — a bug, not a feature gap. */ 2883 wfail(L->t, 2884 "wasm: br to free label whose synthetic scope was not " 2885 "emitted; structurer bug"); 2886 } 2887 2888 static i64 wasm_switch_sign_extend(u64 v, u32 width) { 2889 if (width == 0u || width >= 64u) return (i64)v; 2890 { 2891 u64 bit = 1ull << (width - 1u); 2892 u64 mask = (1ull << width) - 1u; 2893 v &= mask; 2894 return (i64)((v ^ bit) - bit); 2895 } 2896 } 2897 2898 static int wasm_switch_extents(WTarget* t, const WIR* w, i64* out_vmin, 2899 u64* out_span) { 2900 u32 width; 2901 i64 vmin = INT64_MAX; 2902 i64 vmax = INT64_MIN; 2903 if (w->switch_ncases == 0) return 0; 2904 width = kit_cg_type_int_width((KitCompiler*)t->c, w->type); 2905 if (!width || width > 64u) return 0; 2906 for (u32 i = 0; i < w->switch_ncases; ++i) { 2907 i64 vi = wasm_switch_sign_extend(w->switch_cases[i].value, width); 2908 if (vi < vmin) vmin = vi; 2909 if (vi > vmax) vmax = vi; 2910 } 2911 if (vmax < vmin) return 0; 2912 { 2913 u64 delta = (u64)vmax - (u64)vmin; 2914 if (delta == UINT64_MAX) return 0; 2915 *out_span = delta + 1u; 2916 } 2917 *out_vmin = vmin; 2918 return 1; 2919 } 2920 2921 static void emit_br_table(WTarget* t, const u32* targets, u32 ntargets) { 2922 WasmInsn* in; 2923 if (ntargets == 0) 2924 wfail(t, "wasm: br_table needs at least the default target"); 2925 wasm_func_add_insn(t->c, t->module, t->cur_func, WASM_INSN_BR_TABLE, 0); 2926 in = &t->cur_func->insns[t->cur_func->ninsns - 1u]; 2927 wasm_insn_set_targets(t->c, t->module, in, targets, ntargets); 2928 } 2929 2930 /* A switch lowers to a dense br_table when its case values span a range that 2931 * isn't pathologically sparse relative to the number of cases; otherwise an 2932 * `eq`/`br_if` comparison chain. Small ranges always take the table (cheap 2933 * either way); larger ranges only when at least ~half the table slots carry a 2934 * real case, so a sparse switch (e.g. `case 0`, `case 1000000`) doesn't 2935 * materialize a giant mostly-default table. There is no range-splitting yet, 2936 * so a switch that fails this test is a linear scan. */ 2937 static int switch_use_br_table(const WIR* w, u64 span) { 2938 if (span <= 64u) return 1; 2939 return span <= (u64)w->switch_ncases * 2u; 2940 } 2941 2942 static void emit_switch_br_table(WTarget* t, LoweringState* L, const WIR* w) { 2943 i64 vmin; 2944 u64 span; 2945 u32* targets; 2946 Label* labels; 2947 u32 ntargets; 2948 WasmValType vt; 2949 Heap* h = t->c->ctx->heap; 2950 2951 if (w->switch_ncases == 0) { 2952 emit_insn(t, WASM_INSN_BR, (i64)br_to_label(L, w->labels[0])); 2953 return; 2954 } 2955 if (!wasm_switch_extents(t, w, &vmin, &span)) 2956 wfail(t, "wasm: unsupported switch selector type"); 2957 vt = type_valtype(t, w->type); 2958 if (vt != WASM_VAL_I32 && vt != WASM_VAL_I64) 2959 wfail(t, "wasm: switch selector must be integer"); 2960 if (!switch_use_br_table(w, span)) { 2961 for (u32 i = 0; i < w->switch_ncases; ++i) { 2962 u32 width = kit_cg_type_int_width((KitCompiler*)t->c, w->type); 2963 i64 vi = wasm_switch_sign_extend(w->switch_cases[i].value, width); 2964 emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); 2965 emit_push_imm(t, vt, vi); 2966 emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_EQ : WASM_INSN_I32_EQ, 0); 2967 emit_insn(t, WASM_INSN_BR_IF, 2968 (i64)br_to_label(L, w->switch_cases[i].label)); 2969 } 2970 emit_insn(t, WASM_INSN_BR, (i64)br_to_label(L, w->labels[0])); 2971 return; 2972 } 2973 2974 /* Dense table: one slot per value in [vmin, vmin+span), default-filled, with 2975 * the default appended as the trailing out-of-range target. */ 2976 ntargets = (u32)span + 1u; 2977 labels = (Label*)h->alloc(h, sizeof(Label) * span, _Alignof(Label)); 2978 targets = (u32*)h->alloc(h, sizeof(u32) * ntargets, _Alignof(u32)); 2979 if (!labels || !targets) wfail(t, "wasm: out of memory for switch table"); 2980 for (u64 i = 0; i < span; ++i) labels[i] = w->labels[0]; 2981 for (u32 i = 0; i < w->switch_ncases; ++i) { 2982 u32 width = kit_cg_type_int_width((KitCompiler*)t->c, w->type); 2983 i64 vi = wasm_switch_sign_extend(w->switch_cases[i].value, width); 2984 u64 slot = (u64)(vi - vmin); 2985 if (slot >= span) wfail(t, "wasm: switch case outside span"); 2986 labels[slot] = w->switch_cases[i].label; 2987 } 2988 2989 emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); 2990 if (vmin != 0) { 2991 emit_push_imm(t, vt, vmin); 2992 emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB, 0); 2993 } 2994 if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 2995 2996 for (u32 i = 0; i < (u32)span; ++i) targets[i] = br_to_label(L, labels[i]); 2997 targets[ntargets - 1u] = br_to_label(L, w->labels[0]); 2998 emit_br_table(t, targets, ntargets); 2999 h->free(h, targets, sizeof(u32) * ntargets); 3000 h->free(h, labels, sizeof(Label) * span); 3001 } 3002 3003 /* ----------------------------------------------------------------- 3004 * Intrinsics (bit ops / bswap / overflow arith) 3005 * 3006 * MEMCPY/MEMMOVE/MEMSET don't appear here: the recorder funnels them 3007 * into WIR_COPY_BYTES / WIR_SET_BYTES which already lower to 3008 * memory.copy / memory.fill. Hints (PREFETCH/EXPECT/ASSUME_ALIGNED) 3009 * also don't reach the linearizer — the recorder either drops them or 3010 * emits a plain copy. ----------------------------------------------- */ 3011 3012 static void emit_intrinsic_bit_op(WTarget* t, const WIR* w) { 3013 /* clz/ctz/popcount instruction width follows the operand (type2), not the 3014 * i32 result. i64 forms produce an i64 count that we wrap to the i32 dst. */ 3015 WasmValType vt = type_valtype(t, w->type2 ? w->type2 : w->type); 3016 WasmValType dvt = type_valtype(t, w->type); 3017 WasmInsnKind op; 3018 switch ((IntrinKind)w->cgop) { 3019 case INTRIN_CLZ: 3020 op = (vt == WASM_VAL_I64) ? WASM_INSN_I64_CLZ : WASM_INSN_I32_CLZ; 3021 break; 3022 case INTRIN_CTZ: 3023 op = (vt == WASM_VAL_I64) ? WASM_INSN_I64_CTZ : WASM_INSN_I32_CTZ; 3024 break; 3025 case INTRIN_POPCOUNT: 3026 op = (vt == WASM_VAL_I64) ? WASM_INSN_I64_POPCNT : WASM_INSN_I32_POPCNT; 3027 break; 3028 default: 3029 wfail(t, "wasm: unexpected bit-op intrinsic %d", (int)w->cgop); 3030 return; 3031 } 3032 emit_push_operand_reg(t, w->a); 3033 emit_insn(t, op, 0); 3034 if (vt == WASM_VAL_I64 && dvt == WASM_VAL_I32) 3035 emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 3036 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3037 } 3038 3039 static void emit_intrinsic_bswap(WTarget* t, const WIR* w) { 3040 /* Width-by-type: the recorded result type fixes the byte width. */ 3041 u32 width = (u32)abi_cg_sizeof(t->c->abi, w->type); 3042 if (width <= 4) { 3043 /* Both 16- and 32-bit forms operate over i32. The 16-bit form only 3044 * touches the low 16 bits; any extra high bits in the input are 3045 * discarded by the AND mask. */ 3046 u32 tmp = add_wasm_local(t, WASM_VAL_I32); 3047 emit_push_operand_reg(t, w->a); 3048 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)tmp); 3049 if (width <= 2) { 3050 /* (x & 0xff) << 8 */ 3051 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); 3052 emit_insn(t, WASM_INSN_I32_CONST, 0xff); 3053 emit_insn(t, WASM_INSN_I32_AND, 0); 3054 emit_insn(t, WASM_INSN_I32_CONST, 8); 3055 emit_insn(t, WASM_INSN_I32_SHL, 0); 3056 /* (x >> 8) & 0xff */ 3057 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); 3058 emit_insn(t, WASM_INSN_I32_CONST, 8); 3059 emit_insn(t, WASM_INSN_I32_SHR_U, 0); 3060 emit_insn(t, WASM_INSN_I32_CONST, 0xff); 3061 emit_insn(t, WASM_INSN_I32_AND, 0); 3062 emit_insn(t, WASM_INSN_I32_OR, 0); 3063 } else { 3064 /* Four-byte shuffle. */ 3065 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); 3066 emit_insn(t, WASM_INSN_I32_CONST, 24); 3067 emit_insn(t, WASM_INSN_I32_SHR_U, 0); 3068 emit_insn(t, WASM_INSN_I32_CONST, 0xff); 3069 emit_insn(t, WASM_INSN_I32_AND, 0); 3070 3071 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); 3072 emit_insn(t, WASM_INSN_I32_CONST, 8); 3073 emit_insn(t, WASM_INSN_I32_SHR_U, 0); 3074 emit_insn(t, WASM_INSN_I32_CONST, 0xff00); 3075 emit_insn(t, WASM_INSN_I32_AND, 0); 3076 emit_insn(t, WASM_INSN_I32_OR, 0); 3077 3078 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); 3079 emit_insn(t, WASM_INSN_I32_CONST, 8); 3080 emit_insn(t, WASM_INSN_I32_SHL, 0); 3081 emit_insn(t, WASM_INSN_I32_CONST, 0xff0000); 3082 emit_insn(t, WASM_INSN_I32_AND, 0); 3083 emit_insn(t, WASM_INSN_I32_OR, 0); 3084 3085 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); 3086 emit_insn(t, WASM_INSN_I32_CONST, 24); 3087 emit_insn(t, WASM_INSN_I32_SHL, 0); 3088 emit_insn(t, WASM_INSN_I32_OR, 0); 3089 } 3090 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3091 return; 3092 } 3093 /* 8-byte form: byte reverse over i64. */ 3094 u32 tmp = add_wasm_local(t, WASM_VAL_I64); 3095 emit_push_operand_reg(t, w->a); 3096 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)tmp); 3097 for (int i = 0; i < 8; ++i) { 3098 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)tmp); 3099 if (i > 0) { 3100 emit_insn(t, WASM_INSN_I64_CONST, (i64)(i * 8)); 3101 emit_insn(t, WASM_INSN_I64_SHR_U, 0); 3102 } 3103 emit_insn(t, WASM_INSN_I64_CONST, 0xff); 3104 emit_insn(t, WASM_INSN_I64_AND, 0); 3105 int shift = (7 - i) * 8; 3106 if (shift > 0) { 3107 emit_insn(t, WASM_INSN_I64_CONST, (i64)shift); 3108 emit_insn(t, WASM_INSN_I64_SHL, 0); 3109 } 3110 if (i > 0) emit_insn(t, WASM_INSN_I64_OR, 0); 3111 } 3112 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3113 } 3114 3115 static void emit_intrinsic_mul_high(WTarget* t, const WIR* w) { 3116 IntrinKind k = (IntrinKind)w->cgop; 3117 WasmValType vt = type_valtype(t, w->type); 3118 u32 a_loc = add_wasm_local(t, vt); 3119 u32 b_loc = add_wasm_local(t, vt); 3120 emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); 3121 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)a_loc); 3122 emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); 3123 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)b_loc); 3124 3125 if (vt == WASM_VAL_I32) { 3126 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3127 emit_insn(t, k == INTRIN_SMUL_HIGH ? WASM_INSN_I64_EXTEND_I32_S 3128 : WASM_INSN_I64_EXTEND_I32_U, 3129 0); 3130 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3131 emit_insn(t, k == INTRIN_SMUL_HIGH ? WASM_INSN_I64_EXTEND_I32_S 3132 : WASM_INSN_I64_EXTEND_I32_U, 3133 0); 3134 emit_insn(t, WASM_INSN_I64_MUL, 0); 3135 emit_insn(t, WASM_INSN_I64_CONST, 32); 3136 emit_insn(t, k == INTRIN_SMUL_HIGH ? WASM_INSN_I64_SHR_S 3137 : WASM_INSN_I64_SHR_U, 3138 0); 3139 emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 3140 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3141 return; 3142 } 3143 3144 /* Core wasm has no widening i64 multiply. Split into 32-bit limbs: 3145 * high = p11 + (p01>>32) + (p10>>32) + 3146 * (((p00>>32) + lo32(p01) + lo32(p10)) >> 32). */ 3147 { 3148 u32 p00 = add_wasm_local(t, WASM_VAL_I64); 3149 u32 p01 = add_wasm_local(t, WASM_VAL_I64); 3150 u32 p10 = add_wasm_local(t, WASM_VAL_I64); 3151 u32 p11 = add_wasm_local(t, WASM_VAL_I64); 3152 u32 middle = add_wasm_local(t, WASM_VAL_I64); 3153 u32 high = add_wasm_local(t, WASM_VAL_I64); 3154 3155 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3156 emit_insn(t, WASM_INSN_I64_CONST, 0xffffffffull); 3157 emit_insn(t, WASM_INSN_I64_AND, 0); 3158 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3159 emit_insn(t, WASM_INSN_I64_CONST, 0xffffffffull); 3160 emit_insn(t, WASM_INSN_I64_AND, 0); 3161 emit_insn(t, WASM_INSN_I64_MUL, 0); 3162 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)p00); 3163 3164 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3165 emit_insn(t, WASM_INSN_I64_CONST, 0xffffffffull); 3166 emit_insn(t, WASM_INSN_I64_AND, 0); 3167 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3168 emit_insn(t, WASM_INSN_I64_CONST, 32); 3169 emit_insn(t, WASM_INSN_I64_SHR_U, 0); 3170 emit_insn(t, WASM_INSN_I64_MUL, 0); 3171 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)p01); 3172 3173 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3174 emit_insn(t, WASM_INSN_I64_CONST, 32); 3175 emit_insn(t, WASM_INSN_I64_SHR_U, 0); 3176 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3177 emit_insn(t, WASM_INSN_I64_CONST, 0xffffffffull); 3178 emit_insn(t, WASM_INSN_I64_AND, 0); 3179 emit_insn(t, WASM_INSN_I64_MUL, 0); 3180 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)p10); 3181 3182 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3183 emit_insn(t, WASM_INSN_I64_CONST, 32); 3184 emit_insn(t, WASM_INSN_I64_SHR_U, 0); 3185 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3186 emit_insn(t, WASM_INSN_I64_CONST, 32); 3187 emit_insn(t, WASM_INSN_I64_SHR_U, 0); 3188 emit_insn(t, WASM_INSN_I64_MUL, 0); 3189 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)p11); 3190 3191 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)p00); 3192 emit_insn(t, WASM_INSN_I64_CONST, 32); 3193 emit_insn(t, WASM_INSN_I64_SHR_U, 0); 3194 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)p01); 3195 emit_insn(t, WASM_INSN_I64_CONST, 0xffffffffull); 3196 emit_insn(t, WASM_INSN_I64_AND, 0); 3197 emit_insn(t, WASM_INSN_I64_ADD, 0); 3198 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)p10); 3199 emit_insn(t, WASM_INSN_I64_CONST, 0xffffffffull); 3200 emit_insn(t, WASM_INSN_I64_AND, 0); 3201 emit_insn(t, WASM_INSN_I64_ADD, 0); 3202 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)middle); 3203 3204 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)p11); 3205 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)p01); 3206 emit_insn(t, WASM_INSN_I64_CONST, 32); 3207 emit_insn(t, WASM_INSN_I64_SHR_U, 0); 3208 emit_insn(t, WASM_INSN_I64_ADD, 0); 3209 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)p10); 3210 emit_insn(t, WASM_INSN_I64_CONST, 32); 3211 emit_insn(t, WASM_INSN_I64_SHR_U, 0); 3212 emit_insn(t, WASM_INSN_I64_ADD, 0); 3213 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)middle); 3214 emit_insn(t, WASM_INSN_I64_CONST, 32); 3215 emit_insn(t, WASM_INSN_I64_SHR_U, 0); 3216 emit_insn(t, WASM_INSN_I64_ADD, 0); 3217 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)high); 3218 3219 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)high); 3220 if (k == INTRIN_SMUL_HIGH) { 3221 /* signed_high(a,b) = unsigned_high(a,b) - (a<0 ? b : 0) 3222 * - (b<0 ? a : 0). */ 3223 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3224 emit_insn(t, WASM_INSN_I64_CONST, 0); 3225 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3226 emit_insn(t, WASM_INSN_I64_CONST, 0); 3227 emit_insn(t, WASM_INSN_I64_LT_S, 0); 3228 emit_insn(t, WASM_INSN_SELECT, 0); 3229 emit_insn(t, WASM_INSN_I64_SUB, 0); 3230 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3231 emit_insn(t, WASM_INSN_I64_CONST, 0); 3232 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3233 emit_insn(t, WASM_INSN_I64_CONST, 0); 3234 emit_insn(t, WASM_INSN_I64_LT_S, 0); 3235 emit_insn(t, WASM_INSN_SELECT, 0); 3236 emit_insn(t, WASM_INSN_I64_SUB, 0); 3237 } 3238 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3239 } 3240 } 3241 3242 static void emit_intrinsic_overflow(WTarget* t, const WIR* w) { 3243 IntrinKind k = (IntrinKind)w->cgop; 3244 WasmValType vt = type_valtype(t, w->type); 3245 WasmInsnKind k_add = 3246 (vt == WASM_VAL_I64) ? WASM_INSN_I64_ADD : WASM_INSN_I32_ADD; 3247 WasmInsnKind k_sub = 3248 (vt == WASM_VAL_I64) ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB; 3249 WasmInsnKind k_and = 3250 (vt == WASM_VAL_I64) ? WASM_INSN_I64_AND : WASM_INSN_I32_AND; 3251 WasmInsnKind k_xor = 3252 (vt == WASM_VAL_I64) ? WASM_INSN_I64_XOR : WASM_INSN_I32_XOR; 3253 WasmInsnKind k_shr_u = 3254 (vt == WASM_VAL_I64) ? WASM_INSN_I64_SHR_U : WASM_INSN_I32_SHR_U; 3255 WasmInsnKind k_lt_u = 3256 (vt == WASM_VAL_I64) ? WASM_INSN_I64_LT_U : WASM_INSN_I32_LT_U; 3257 WasmInsnKind k_const = 3258 (vt == WASM_VAL_I64) ? WASM_INSN_I64_CONST : WASM_INSN_I32_CONST; 3259 KitCgTypeId bool_ty = builtin_id(KIT_CG_BUILTIN_BOOL); 3260 3261 /* Stash both operands in scratch locals so each side of the expansion can 3262 * re-load them without re-evaluating immediates or relying on the wasm 3263 * value stack shape. */ 3264 u32 a_loc = add_wasm_local(t, vt); 3265 u32 b_loc = add_wasm_local(t, vt); 3266 emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); 3267 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)a_loc); 3268 emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); 3269 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)b_loc); 3270 3271 switch (k) { 3272 case INTRIN_UADD_OVERFLOW: 3273 /* r = a + b; ovf = (r <u a) */ 3274 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3275 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3276 emit_insn(t, k_add, 0); 3277 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3278 emit_push_operand_reg(t, w->dst); 3279 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3280 emit_insn(t, k_lt_u, 0); 3281 emit_local_set(t, w->dst2, bool_ty, RC_INT); 3282 break; 3283 case INTRIN_USUB_OVERFLOW: 3284 /* r = a - b; ovf = (a <u b) */ 3285 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3286 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3287 emit_insn(t, k_sub, 0); 3288 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3289 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3290 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3291 emit_insn(t, k_lt_u, 0); 3292 emit_local_set(t, w->dst2, bool_ty, RC_INT); 3293 break; 3294 case INTRIN_SADD_OVERFLOW: 3295 /* r = a + b; ovf = ((r ^ a) & (r ^ b)) >>u (W-1) */ 3296 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3297 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3298 emit_insn(t, k_add, 0); 3299 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3300 emit_push_operand_reg(t, w->dst); 3301 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3302 emit_insn(t, k_xor, 0); 3303 emit_push_operand_reg(t, w->dst); 3304 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3305 emit_insn(t, k_xor, 0); 3306 emit_insn(t, k_and, 0); 3307 emit_insn(t, k_const, (i64)(vt == WASM_VAL_I64 ? 63 : 31)); 3308 emit_insn(t, k_shr_u, 0); 3309 if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 3310 emit_local_set(t, w->dst2, bool_ty, RC_INT); 3311 break; 3312 case INTRIN_SSUB_OVERFLOW: 3313 /* r = a - b; ovf = ((a ^ b) & (a ^ r)) >>u (W-1) */ 3314 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3315 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3316 emit_insn(t, k_sub, 0); 3317 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3318 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3319 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3320 emit_insn(t, k_xor, 0); 3321 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3322 emit_push_operand_reg(t, w->dst); 3323 emit_insn(t, k_xor, 0); 3324 emit_insn(t, k_and, 0); 3325 emit_insn(t, k_const, (i64)(vt == WASM_VAL_I64 ? 63 : 31)); 3326 emit_insn(t, k_shr_u, 0); 3327 if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 3328 emit_local_set(t, w->dst2, bool_ty, RC_INT); 3329 break; 3330 case INTRIN_UMUL_OVERFLOW: { 3331 /* i32 only (i64 rejected in recorder). Widen to i64, multiply, 3332 * low 32 = result, ovf = (wide >> 32) != 0. */ 3333 u32 wide = add_wasm_local(t, WASM_VAL_I64); 3334 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3335 emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0); 3336 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3337 emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0); 3338 emit_insn(t, WASM_INSN_I64_MUL, 0); 3339 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)wide); 3340 3341 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wide); 3342 emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 3343 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3344 3345 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wide); 3346 emit_insn(t, WASM_INSN_I64_CONST, 32); 3347 emit_insn(t, WASM_INSN_I64_SHR_U, 0); 3348 emit_insn(t, WASM_INSN_I64_CONST, 0); 3349 emit_insn(t, WASM_INSN_I64_NE, 0); 3350 emit_local_set(t, w->dst2, bool_ty, RC_INT); 3351 break; 3352 } 3353 case INTRIN_SMUL_OVERFLOW: { 3354 /* i32 only. Sign-extend, multiply, low 32 = result, ovf if 3355 * sext(result) != wide product. */ 3356 u32 wide = add_wasm_local(t, WASM_VAL_I64); 3357 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)a_loc); 3358 emit_insn(t, WASM_INSN_I64_EXTEND_I32_S, 0); 3359 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)b_loc); 3360 emit_insn(t, WASM_INSN_I64_EXTEND_I32_S, 0); 3361 emit_insn(t, WASM_INSN_I64_MUL, 0); 3362 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)wide); 3363 3364 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wide); 3365 emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 3366 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3367 3368 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wide); 3369 emit_push_operand_reg(t, w->dst); 3370 emit_insn(t, WASM_INSN_I64_EXTEND_I32_S, 0); 3371 emit_insn(t, WASM_INSN_I64_NE, 0); 3372 emit_local_set(t, w->dst2, bool_ty, RC_INT); 3373 break; 3374 } 3375 default: 3376 wfail(t, "wasm: overflow intrinsic dispatch reached default (%d)", 3377 (int)k); 3378 } 3379 } 3380 3381 static void emit_intrinsic(WTarget* t, const WIR* w) { 3382 IntrinKind k = (IntrinKind)w->cgop; 3383 switch (k) { 3384 case INTRIN_CLZ: 3385 case INTRIN_CTZ: 3386 case INTRIN_POPCOUNT: 3387 emit_intrinsic_bit_op(t, w); 3388 return; 3389 case INTRIN_BSWAP: 3390 emit_intrinsic_bswap(t, w); 3391 return; 3392 case INTRIN_SMUL_HIGH: 3393 case INTRIN_UMUL_HIGH: 3394 emit_intrinsic_mul_high(t, w); 3395 return; 3396 case INTRIN_SADD_OVERFLOW: 3397 case INTRIN_UADD_OVERFLOW: 3398 case INTRIN_SSUB_OVERFLOW: 3399 case INTRIN_USUB_OVERFLOW: 3400 case INTRIN_SMUL_OVERFLOW: 3401 case INTRIN_UMUL_OVERFLOW: 3402 emit_intrinsic_overflow(t, w); 3403 return; 3404 default: 3405 wfail(t, "wasm: unexpected intrinsic kind %d in linearizer", (int)k); 3406 } 3407 } 3408 3409 static void linearize_range(WTarget* t, LoweringState* L, u32 start, u32 end) { 3410 for (u32 i = start; i < end; ++i) { 3411 WIR* w = &t->wir[i]; 3412 switch (w->op) { 3413 case WIR_LOAD_IMM: { 3414 WasmValType vt = type_valtype(t, w->type); 3415 emit_push_imm(t, vt, w->imm); 3416 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3417 break; 3418 } 3419 case WIR_LOAD_CONST_F: { 3420 WasmValType vt = type_valtype(t, w->type); 3421 emit_fp(t, 3422 vt == WASM_VAL_F64 ? WASM_INSN_F64_CONST : WASM_INSN_F32_CONST, 3423 w->fp_imm); 3424 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3425 break; 3426 } 3427 case WIR_COPY: { 3428 emit_push_operand_reg(t, w->a); 3429 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3430 break; 3431 } 3432 case WIR_BINOP: { 3433 emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); 3434 emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); 3435 WasmValType vt = type_valtype(t, w->type); 3436 emit_insn(t, binop_kind(t, (BinOp)w->cgop, vt), 0); 3437 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3438 break; 3439 } 3440 case WIR_UNOP: { 3441 WasmValType vt = type_valtype(t, w->type); 3442 switch ((UnOp)w->cgop) { 3443 case UO_NEG: { 3444 /* 0 - a */ 3445 emit_push_imm(t, vt, 0); 3446 emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); 3447 emit_insn( 3448 t, vt == WASM_VAL_I64 ? WASM_INSN_I64_SUB : WASM_INSN_I32_SUB, 3449 0); 3450 break; 3451 } 3452 case UO_FNEG: { 3453 emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); 3454 emit_insn( 3455 t, vt == WASM_VAL_F64 ? WASM_INSN_F64_NEG : WASM_INSN_F32_NEG, 3456 0); 3457 break; 3458 } 3459 case UO_BNOT: { 3460 /* a XOR -1 */ 3461 emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); 3462 emit_push_imm(t, vt, -1); 3463 emit_insn( 3464 t, vt == WASM_VAL_I64 ? WASM_INSN_I64_XOR : WASM_INSN_I32_XOR, 3465 0); 3466 break; 3467 } 3468 case UO_NOT: { 3469 /* a == 0 — i{32,64}.eqz always produces an i32 0/1. When the CG 3470 * destination is i64 (e.g. !x where x was zext'd to i64 before the 3471 * negation), widen the i32 boolean back to i64 so the following 3472 * local.set is well-typed. */ 3473 emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); 3474 emit_insn( 3475 t, vt == WASM_VAL_I64 ? WASM_INSN_I64_EQZ : WASM_INSN_I32_EQZ, 3476 0); 3477 if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0); 3478 break; 3479 } 3480 } 3481 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3482 break; 3483 } 3484 case WIR_CMP: { 3485 CmpOp cop = (CmpOp)w->cgop; 3486 if (cop >= CMP_OEQ_F) { 3487 emit_fp_cmp(t, cop, w, w->type2); 3488 } else { 3489 push_cmp_operands(t, w, w->type2); 3490 emit_insn(t, cmp_kind(t, cop, type_valtype(t, w->type2)), 0); 3491 } 3492 /* cmp result is i32 (0/1). dst type may be wider — but cg generally 3493 * stores cmp results into i32. */ 3494 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3495 break; 3496 } 3497 case WIR_CONVERT: { 3498 WasmValType src = type_valtype(t, w->type2); 3499 WasmValType dst = type_valtype(t, w->type); 3500 u32 sw = kit_cg_type_int_width((KitCompiler*)t->c, w->type2); 3501 u32 dw = kit_cg_type_int_width((KitCompiler*)t->c, w->type); 3502 emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type2); 3503 emit_convert(t, (ConvKind)w->cgop, src, dst, sw, dw); 3504 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3505 break; 3506 } 3507 case WIR_CALL: 3508 case WIR_CALL_INDIRECT: { 3509 /* Tail calls tear down the caller's wasm frame (return_call / 3510 * return_call_indirect have polymorphic-unreachable type after the 3511 * call). Mirror the WIR_RET linear-stack epilogue before pushing 3512 * args so the linear-memory stack frame is released. Operands 3513 * below come from wasm locals (incoming params or reg-locals), 3514 * not from the linear stack we just freed. Variadic tail calls are 3515 * rejected upstream; sret tail calls forward the incoming pointer. */ 3516 if (w->call_tail && t->has_stack_frame) { 3517 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_saved_sp_local); 3518 emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global); 3519 } 3520 if (w->call_has_sret) { 3521 if (w->call_tail) { 3522 /* Forward this function's own incoming sret pointer: the callee 3523 * writes the same buffer (in our caller's frame, which outlives 3524 * the sibling call) and return_calls back. The pointer is a wasm 3525 * local, unaffected by the linear-frame teardown above. */ 3526 if (t->sret_param_local == 0xffffffffu) 3527 wfail(t, "wasm: sret tail call without an incoming sret pointer"); 3528 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->sret_param_local); 3529 } else { 3530 /* Push sret pointer (address of caller-allocated buffer). */ 3531 uint64_t off; 3532 emit_addr_operand(t, w->call_sret_addr, &off); 3533 if (off) { 3534 emit_insn(t, WASM_INSN_I32_CONST, (i64)off); 3535 emit_insn(t, WASM_INSN_I32_ADD, 0); 3536 } 3537 } 3538 } 3539 for (u32 a = 0; a < w->call_narg; ++a) { 3540 if (w->call_arg_kinds[a] == WOP_REG) { 3541 emit_push_operand_reg(t, w->call_args[a]); 3542 } else if (w->call_arg_kinds[a] == WOP_IMM) { 3543 WasmValType vt = type_valtype(t, w->call_arg_types[a]); 3544 emit_push_imm(t, vt, w->call_arg_imms[a]); 3545 } else if (w->call_arg_kinds[a] == WOP_ADDR) { 3546 uint64_t off; 3547 emit_addr_operand(t, w->call_arg_addrs[a], &off); 3548 if (off) { 3549 emit_insn(t, WASM_INSN_I32_CONST, (i64)off); 3550 emit_insn(t, WASM_INSN_I32_ADD, 0); 3551 } 3552 } else { 3553 wfail(t, "wasm: bad call-arg kind %u", w->call_arg_kinds[a]); 3554 } 3555 } 3556 /* Variadic packing. Each variadic arg occupies an 8-byte slot in a 3557 * caller-allocated linear-memory buffer; the buffer's address is 3558 * passed as the hidden trailing i32. We save __stack_pointer to a 3559 * scratch local before allocating the buffer and restore it after 3560 * the call returns, so a variadic call in a loop doesn't grow the 3561 * linear stack. See wasm_va_start / wasm_va_arg for the callee side. 3562 */ 3563 if (w->call_variadic) { 3564 if (w->call_nvar == 0u) { 3565 /* No varargs: still pass a hidden i32. NULL is fine — the callee 3566 * must not deref va_list without a matching @va_arg, which a 3567 * well-typed program won't do. */ 3568 emit_insn(t, WASM_INSN_I32_CONST, 0); 3569 } else { 3570 ensure_stack_pointer(t); 3571 if (t->varcall_saved_sp_local == 0xffffffffu) 3572 t->varcall_saved_sp_local = add_wasm_local(t, WASM_VAL_I32); 3573 if (t->varcall_buf_local == 0xffffffffu) 3574 t->varcall_buf_local = add_wasm_local(t, WASM_VAL_I32); 3575 u32 buf_size = w->call_nvar * 8u; 3576 /* Save SP, allocate aligned buffer, set SP = buf. */ 3577 emit_insn(t, WASM_INSN_GLOBAL_GET, (i64)t->stack_pointer_global); 3578 emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->varcall_saved_sp_local); 3579 emit_insn(t, WASM_INSN_I32_CONST, (i64)buf_size); 3580 emit_insn(t, WASM_INSN_I32_SUB, 0); 3581 emit_insn(t, WASM_INSN_I32_CONST, -(i64)8); 3582 emit_insn(t, WASM_INSN_I32_AND, 0); 3583 emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->varcall_buf_local); 3584 emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global); 3585 /* Pack each variadic arg at offset i*8. Store width is the 3586 * value's natural width (i32/i64/f32/f64); the unused high 3587 * bytes of i32/f32 slots are left as whatever __stack_pointer 3588 * pointed at, which @va_arg won't read for those slots. */ 3589 for (u32 v = 0; v < w->call_nvar; ++v) { 3590 KitCgTypeId vty = w->call_var_types[v]; 3591 WasmValType vvt = type_valtype(t, vty); 3592 WasmInsnKind store_op; 3593 u32 width; 3594 if (vvt == WASM_VAL_I64) { 3595 store_op = WASM_INSN_I64_STORE; 3596 width = 8u; 3597 } else if (vvt == WASM_VAL_F32) { 3598 store_op = WASM_INSN_F32_STORE; 3599 width = 4u; 3600 } else if (vvt == WASM_VAL_F64) { 3601 store_op = WASM_INSN_F64_STORE; 3602 width = 8u; 3603 } else { 3604 store_op = WASM_INSN_I32_STORE; 3605 width = 4u; 3606 } 3607 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->varcall_buf_local); 3608 if (w->call_var_kinds[v] == WOP_REG) { 3609 emit_push_operand_reg(t, w->call_var_regs[v]); 3610 } else if (w->call_var_kinds[v] == WOP_IMM) { 3611 if (vvt == WASM_VAL_F32 || vvt == WASM_VAL_F64) 3612 wfail(t, "wasm: float immediate variadic arg unsupported"); 3613 emit_push_imm(t, vvt, w->call_var_imms[v]); 3614 } else { 3615 wfail(t, "wasm: bad variadic-arg kind %u", 3616 w->call_var_kinds[v]); 3617 } 3618 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, store_op, 3619 memarg_align_log2(width, width), 3620 (u64)(v * 8u), 0u); 3621 } 3622 /* Push buf addr as hidden last arg. */ 3623 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->varcall_buf_local); 3624 } 3625 } 3626 if (w->op == WIR_CALL_INDIRECT) { 3627 /* Callee: push the i32 table index. */ 3628 emit_push_operand_reg(t, w->a); 3629 /* call_indirect / return_call_indirect both encode 3630 * (typeidx, tableidx). The encoder reads `imm` as typeidx and 3631 * `align` as tableidx. */ 3632 wasm_func_add_insn(t->c, t->module, t->cur_func, 3633 w->call_tail ? WASM_INSN_RETURN_CALL_INDIRECT 3634 : WASM_INSN_CALL_INDIRECT, 3635 w->imm); 3636 t->cur_func->insns[t->cur_func->ninsns - 1u].align = 0u; 3637 } else { 3638 u32 idx = sym_to_wasm_func(t, w->call_sym, NULL); 3639 emit_insn(t, w->call_tail ? WASM_INSN_RETURN_CALL : WASM_INSN_CALL, 3640 (i64)idx); 3641 } 3642 /* Tail calls never return to this function: the operand stack is 3643 * polymorphic-unreachable after return_call*, so writing dst or 3644 * restoring the variadic stack pointer would be dead and would 3645 * also corrupt stack typing. (Variadic tail calls are rejected 3646 * upstream, so the variadic SP-restore guard is defensive.) */ 3647 if (!w->call_tail) { 3648 if (w->dst != REG_NONE) { 3649 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3650 } 3651 /* Restore SP after variadic call so loop-resident variadic calls 3652 * don't accumulate stack usage. Done after stashing the return 3653 * value into its local. */ 3654 if (w->call_variadic && w->call_nvar) { 3655 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->varcall_saved_sp_local); 3656 emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global); 3657 } 3658 } 3659 break; 3660 } 3661 case WIR_RET: { 3662 if (w->cgop == 1) { 3663 /* Aggregate sret return: memcpy w->addr -> *sret_param, then 3664 * void return. The sret pointer was the hidden first wasm param. 3665 * NOTE: this still uses a byte loop rather than memory.copy so 3666 * the produced module remains loadable by the kit runtime 3667 * before the wasm-core default-feature change lands. The path 3668 * will collapse to memory.copy once the core's default feature 3669 * set includes WASM_FEATURE_BULK_MEMORY (subagent A). */ 3670 if (t->sret_param_local == 0xffffffffu) 3671 wfail(t, "wasm: sret return without hidden sret param"); 3672 for (u32 n = 0; n < w->agg.size; ++n) { 3673 /* Push destination address (sret_ptr) onto stack. The memarg 3674 * offset on the i32.store8 carries the per-byte offset. */ 3675 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->sret_param_local); 3676 /* Load src byte at (w->addr + n). */ 3677 Operand src = w->addr; 3678 if (src.kind == OPK_INDIRECT) 3679 src.v.ind.ofs += (i32)n; 3680 else if (src.kind == OPK_GLOBAL) 3681 src.v.global.addend += n; 3682 uint64_t src_off; 3683 emit_addr_operand(t, src, &src_off); 3684 if (src.kind == OPK_LOCAL) src_off += n; 3685 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, 3686 WASM_INSN_I32_LOAD8_U, 0, src_off, 0); 3687 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, 3688 WASM_INSN_I32_STORE8, 0, n, 0); 3689 } 3690 } else if (w->dst != REG_NONE) 3691 emit_push_operand_reg(t, w->dst); 3692 else if (w->imm_kind == 1) { 3693 WasmValType vt = type_valtype(t, w->type); 3694 emit_push_imm(t, vt, w->imm_a); 3695 } 3696 if (t->has_stack_frame) { 3697 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_saved_sp_local); 3698 emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global); 3699 } 3700 emit_insn(t, WASM_INSN_RETURN, 0); 3701 break; 3702 } 3703 case WIR_UNREACHABLE: { 3704 emit_insn(t, WASM_INSN_UNREACHABLE, 0); 3705 break; 3706 } 3707 case WIR_LOAD_LOCAL: { 3708 u32 wli = (u32)w->imm; 3709 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)wli); 3710 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3711 break; 3712 } 3713 case WIR_STORE_LOCAL: { 3714 u32 wli = (u32)w->imm; 3715 if (w->imm_kind == 1) { 3716 WasmValType vt = type_valtype(t, w->type); 3717 emit_push_imm(t, vt, w->imm_a); 3718 } else { 3719 emit_push_operand_reg(t, w->a); 3720 } 3721 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)wli); 3722 break; 3723 } 3724 case WIR_LOAD_MEM: { 3725 emit_load_addr(t, w->addr, w->type, w->mem); 3726 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3727 break; 3728 } 3729 case WIR_STORE_MEM: { 3730 Operand src; 3731 memset(&src, 0, sizeof src); 3732 src.kind = w->imm_kind == WOP_IMM ? OPK_IMM : OPK_REG; 3733 src.type = w->type; 3734 if (src.kind == OPK_IMM) 3735 src.v.imm = w->imm_a; 3736 else 3737 src.v.reg = w->a; 3738 emit_store_addr(t, w->addr, w->type, src, w->mem, w->imm_kind, w->imm_a, 3739 w->a); 3740 break; 3741 } 3742 case WIR_ADDR_OF: { 3743 uint64_t offset; 3744 emit_addr_operand(t, w->addr, &offset); 3745 if (offset) { 3746 emit_insn(t, WASM_INSN_I32_CONST, (i64)offset); 3747 emit_insn(t, WASM_INSN_I32_ADD, 0); 3748 } 3749 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3750 break; 3751 } 3752 case WIR_ALLOCA: { 3753 u32 align = (u32)w->imm; 3754 emit_insn(t, WASM_INSN_GLOBAL_GET, (i64)t->stack_pointer_global); 3755 emit_push_operand(t, w->imm_kind, w->imm_a, w->a, 3756 w->type2 ? w->type2 : builtin_id(KIT_CG_BUILTIN_I32)); 3757 if (w->type2 && type_valtype(t, w->type2) == WASM_VAL_I64) 3758 emit_insn(t, WASM_INSN_I32_WRAP_I64, 0); 3759 emit_insn(t, WASM_INSN_I32_SUB, 0); 3760 if (align > 1u) { 3761 emit_insn(t, WASM_INSN_I32_CONST, -(i64)align); 3762 emit_insn(t, WASM_INSN_I32_AND, 0); 3763 } 3764 emit_insn(t, WASM_INSN_LOCAL_TEE, 3765 (i64)reg_local(t, w->dst, w->type, (RegClass)w->cls)); 3766 emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global); 3767 break; 3768 } 3769 case WIR_COPY_BYTES: { 3770 /* memory.copy: stack = dst_addr, src_addr, n; both memidx fields = 0. 3771 */ 3772 Operand src_addr; 3773 if (w->imm_kind != WOP_REG) 3774 wfail(t, "wasm: copy_bytes source must be a register pointer"); 3775 memset(&src_addr, 0, sizeof src_addr); 3776 src_addr.kind = OPK_INDIRECT; 3777 src_addr.type = w->addr.type; 3778 src_addr.v.ind.base = w->a; 3779 src_addr.v.ind.index = REG_NONE; 3780 src_addr.v.ind.log2_scale = 0; 3781 src_addr.v.ind.ofs = 0; 3782 if (w->agg.size == 0) break; 3783 emit_push_addr_value(t, w->addr); 3784 emit_push_addr_value(t, src_addr); 3785 emit_insn(t, WASM_INSN_I32_CONST, (i64)(uint32_t)w->agg.size); 3786 wasm_func_add_insn(t->c, t->module, t->cur_func, WASM_INSN_MEMORY_COPY, 3787 0); 3788 /* dst memidx = 0, src memidx = 0 (kit-cc single-memory module). */ 3789 t->cur_func->insns[t->cur_func->ninsns - 1u].memidx = 0; 3790 t->cur_func->insns[t->cur_func->ninsns - 1u].aux_idx = 0; 3791 break; 3792 } 3793 case WIR_SET_BYTES: { 3794 /* memory.fill: stack = dst_addr, value_i32, n; memidx = 0. */ 3795 if (w->imm_kind != WOP_IMM) 3796 wfail(t, "wasm: set_bytes value must be immediate in v1"); 3797 if (w->agg.size == 0) break; 3798 emit_push_addr_value(t, w->addr); 3799 emit_insn(t, WASM_INSN_I32_CONST, (i64)(w->imm_a & 0xff)); 3800 emit_insn(t, WASM_INSN_I32_CONST, (i64)(uint32_t)w->agg.size); 3801 wasm_func_add_insn(t->c, t->module, t->cur_func, WASM_INSN_MEMORY_FILL, 3802 0); 3803 t->cur_func->insns[t->cur_func->ninsns - 1u].memidx = 0; 3804 break; 3805 } 3806 case WIR_ATOMIC_LOAD: { 3807 WasmInsnKind k = atomic_load_kind_for(t, w->type, w->mem); 3808 u32 width = wasm_mem_width((uint8_t)k); 3809 emit_push_operand_reg(t, w->a); 3810 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, 3811 memarg_align_log2(w->mem.align, width), 0, 0); 3812 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3813 break; 3814 } 3815 case WIR_ATOMIC_STORE: { 3816 WasmInsnKind k = atomic_store_kind_for(t, w->type, w->mem); 3817 u32 width = wasm_mem_width((uint8_t)k); 3818 emit_push_operand_reg(t, w->a); 3819 emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); 3820 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, 3821 memarg_align_log2(w->mem.align, width), 0, 0); 3822 break; 3823 } 3824 case WIR_ATOMIC_RMW: { 3825 if ((KitCgAtomicOp)w->cgop == KIT_CG_ATOMIC_NAND) { 3826 /* wasm-threads has no atomic.rmw.nand. Expand to a cmpxchg retry 3827 * loop computing desired = ~(old & val): 3828 * loop 3829 * old = atomic.load(addr) ; tee into old_local 3830 * desired = (old & val) ^ -1 3831 * got = atomic.rmw.cmpxchg(addr, old, desired) 3832 * br_if loop (got != old) ; lost the race, retry 3833 * end 3834 * dst = old_local ; fetch returns prior value 3835 */ 3836 WasmValType vt = type_valtype(t, w->type); 3837 WasmInsnKind load_k = atomic_load_kind_for(t, w->type, w->mem); 3838 WasmInsnKind cas_k = atomic_cmpxchg_kind_for(t, w->type, w->mem); 3839 u32 load_w = wasm_mem_width((uint8_t)load_k); 3840 u32 cas_w = wasm_mem_width((uint8_t)cas_k); 3841 int is64 = (vt == WASM_VAL_I64); 3842 u32 old_local = add_wasm_local(t, vt); 3843 emit_insn(t, WASM_INSN_LOOP, 0); 3844 /* addr (cmpxchg arg0) */ 3845 emit_push_operand_reg(t, w->a); 3846 /* expected = atomic.load(addr), tee into old_local */ 3847 emit_push_operand_reg(t, w->a); 3848 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, load_k, 3849 memarg_align_log2(w->mem.align, load_w), 0, 0); 3850 emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)old_local); 3851 /* desired = (old & val) ^ -1 */ 3852 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)old_local); 3853 emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); 3854 emit_insn(t, is64 ? WASM_INSN_I64_AND : WASM_INSN_I32_AND, 0); 3855 emit_push_imm(t, vt, -1); 3856 emit_insn(t, is64 ? WASM_INSN_I64_XOR : WASM_INSN_I32_XOR, 0); 3857 /* cmpxchg -> value previously in memory */ 3858 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, cas_k, 3859 memarg_align_log2(w->mem.align, cas_w), 0, 0); 3860 /* retry if memory had changed (got != expected) */ 3861 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)old_local); 3862 emit_insn(t, is64 ? WASM_INSN_I64_NE : WASM_INSN_I32_NE, 0); 3863 emit_insn(t, WASM_INSN_BR_IF, 0); /* 0 = innermost loop */ 3864 emit_insn(t, WASM_INSN_END, 0); 3865 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)old_local); 3866 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3867 break; 3868 } 3869 WasmInsnKind k = 3870 atomic_rmw_kind_for(t, (KitCgAtomicOp)w->cgop, w->type, w->mem); 3871 u32 width = wasm_mem_width((uint8_t)k); 3872 emit_push_operand_reg(t, w->a); 3873 emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); 3874 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, 3875 memarg_align_log2(w->mem.align, width), 0, 0); 3876 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 3877 break; 3878 } 3879 case WIR_ATOMIC_CAS: { 3880 WasmInsnKind k = atomic_cmpxchg_kind_for(t, w->type, w->mem); 3881 WasmValType vt = type_valtype(t, w->type); 3882 u32 width = wasm_mem_width((uint8_t)k); 3883 /* Save expected into a fresh wasm local before consuming inputs. CG 3884 * may reuse one of (addr, expected, desired) regs for prior or ok; 3885 * reg_local() for w->dst/w->dst2 would then rebind that reg's local 3886 * mid-stream, and re-pushing expected via the (now-stale) mapping 3887 * would read an uninitialized local. The temp sidesteps that. */ 3888 u32 saved_expected = add_wasm_local(t, vt); 3889 /* push addr; expected (tee into saved-expected, leaves on stack); 3890 * desired; cmpxchg -> prior on stack. */ 3891 emit_push_operand_reg(t, w->a); 3892 emit_push_operand(t, w->imm_kind_b, w->imm_b, w->b, w->type); 3893 emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)saved_expected); 3894 emit_push_operand(t, w->imm_kind_c, w->imm_c, w->op_c, w->type); 3895 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, k, 3896 memarg_align_log2(w->mem.align, width), 0, 0); 3897 /* All input regs have been consumed; safe to rebind. */ 3898 u32 prior_local = reg_local(t, w->dst, w->type, (RegClass)w->cls); 3899 emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)prior_local); 3900 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)saved_expected); 3901 emit_insn(t, vt == WASM_VAL_I64 ? WASM_INSN_I64_EQ : WASM_INSN_I32_EQ, 3902 0); 3903 emit_local_set(t, w->dst2, 3904 w->type2 ? w->type2 : builtin_id(KIT_CG_BUILTIN_BOOL), 3905 RC_INT); 3906 break; 3907 } 3908 case WIR_FENCE: { 3909 emit_insn(t, WASM_INSN_ATOMIC_FENCE, 0); 3910 break; 3911 } 3912 case WIR_JUMP: { 3913 u32 d = br_to_label(L, w->labels[0]); 3914 emit_insn(t, WASM_INSN_BR, (i64)d); 3915 break; 3916 } 3917 case WIR_CMP_BRANCH: { 3918 CmpOp cop = (CmpOp)w->cgop; 3919 if (cop >= CMP_OEQ_F) { 3920 emit_fp_cmp(t, cop, w, w->type); 3921 } else { 3922 push_cmp_operands(t, w, w->type); 3923 emit_insn(t, cmp_kind(t, cop, type_valtype(t, w->type)), 0); 3924 } 3925 u32 d = br_to_label(L, w->labels[0]); 3926 emit_insn(t, WASM_INSN_BR_IF, (i64)d); 3927 break; 3928 } 3929 case WIR_SWITCH: { 3930 emit_switch_br_table(t, L, w); 3931 break; 3932 } 3933 case WIR_SCOPE_OPEN: { 3934 if (L->nstack >= 1024u) 3935 wfail(t, "wasm: scope nesting too deep (max 1024)"); 3936 LoweringScope* s = &L->stack[L->nstack++]; 3937 s->id = w->scope_id; 3938 s->kind = w->cgop; 3939 if (w->cgop == SCOPE_LOOP) { 3940 /* (block (loop ...)); inside the body: 3941 * br to loop top (cur_depth+1) = continue 3942 * br to past block (cur_depth) = break (one more level out) */ 3943 emit_insn(t, WASM_INSN_BLOCK, 0); 3944 L->cur_depth++; 3945 s->break_depth = L->cur_depth; /* `br N` lands AFTER block */ 3946 emit_insn(t, WASM_INSN_LOOP, 0); 3947 L->cur_depth++; 3948 s->cont_depth = L->cur_depth; /* `br N` lands at LOOP top */ 3949 } else if (w->cgop == SCOPE_BLOCK) { 3950 emit_insn(t, WASM_INSN_BLOCK, 0); 3951 L->cur_depth++; 3952 s->break_depth = L->cur_depth; 3953 s->cont_depth = L->cur_depth; /* unused */ 3954 } else { 3955 wfail(t, "wasm: unknown scope kind %d", (int)w->cgop); 3956 } 3957 break; 3958 } 3959 case WIR_SCOPE_CLOSE: { 3960 if (L->nstack == 0) wfail(t, "wasm: scope_close without open scope"); 3961 LoweringScope* s = &L->stack[L->nstack - 1u]; 3962 if (s->kind == SCOPE_LOOP) { 3963 emit_insn(t, WASM_INSN_END, 0); /* close loop */ 3964 L->cur_depth--; 3965 emit_insn(t, WASM_INSN_END, 0); /* close outer block */ 3966 L->cur_depth--; 3967 } else { 3968 emit_insn(t, WASM_INSN_END, 0); 3969 L->cur_depth--; 3970 } 3971 L->nstack--; 3972 break; 3973 } 3974 case WIR_VA_START: { 3975 /* *ap_addr = va_ptr_param_local */ 3976 emit_push_addr_value(t, w->addr); 3977 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->va_ptr_param_local); 3978 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, 3979 WASM_INSN_I32_STORE, 2u, 0u, 0u); 3980 break; 3981 } 3982 case WIR_VA_ARG: { 3983 if (t->va_arg_tmp_addr_local == 0xffffffffu) 3984 t->va_arg_tmp_addr_local = add_wasm_local(t, WASM_VAL_I32); 3985 WasmValType vt = type_valtype(t, w->type); 3986 WasmInsnKind load_op; 3987 u32 width; 3988 if (vt == WASM_VAL_I64) { 3989 load_op = WASM_INSN_I64_LOAD; 3990 width = 8u; 3991 } else if (vt == WASM_VAL_F32) { 3992 load_op = WASM_INSN_F32_LOAD; 3993 width = 4u; 3994 } else if (vt == WASM_VAL_F64) { 3995 load_op = WASM_INSN_F64_LOAD; 3996 width = 8u; 3997 } else { 3998 load_op = WASM_INSN_I32_LOAD; 3999 width = 4u; 4000 } 4001 /* Load T from current *ap and stash into dst. */ 4002 emit_push_addr_value(t, w->addr); 4003 emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->va_arg_tmp_addr_local); 4004 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, WASM_INSN_I32_LOAD, 4005 2u, 0u, 0u); 4006 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, load_op, 4007 memarg_align_log2(width, width), 0u, 0u); 4008 emit_local_set(t, w->dst, w->type, (RegClass)w->cls); 4009 /* Advance: *ap = *ap + 8. */ 4010 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->va_arg_tmp_addr_local); 4011 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->va_arg_tmp_addr_local); 4012 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, WASM_INSN_I32_LOAD, 4013 2u, 0u, 0u); 4014 emit_insn(t, WASM_INSN_I32_CONST, (i64)8); 4015 emit_insn(t, WASM_INSN_I32_ADD, 0); 4016 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, 4017 WASM_INSN_I32_STORE, 2u, 0u, 0u); 4018 break; 4019 } 4020 case WIR_VA_COPY: { 4021 /* *dst_ap = *src_ap (single i32). */ 4022 emit_push_addr_value(t, w->addr); 4023 emit_push_addr_value(t, w->call_sret_addr); 4024 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, WASM_INSN_I32_LOAD, 4025 2u, 0u, 0u); 4026 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, 4027 WASM_INSN_I32_STORE, 2u, 0u, 0u); 4028 break; 4029 } 4030 case WIR_INTRINSIC: { 4031 emit_intrinsic(t, w); 4032 break; 4033 } 4034 case WIR_ASM_BLOCK: { 4035 Heap* h_blk = t->c->ctx->heap; 4036 u32 nin = w->asm_nin; 4037 u32 nout = w->asm_nout; 4038 u32* in_locals = NULL; 4039 u32* out_locals = NULL; 4040 if (nin) { 4041 in_locals = 4042 (u32*)h_blk->alloc(h_blk, sizeof(u32) * nin, _Alignof(u32)); 4043 if (!in_locals) wfail(t, "wasm: out of memory"); 4044 /* defer per-input allocation until after output locals are known 4045 * so numeric tieback ("+r", "0".."9") can share. */ 4046 } 4047 if (nout) { 4048 out_locals = 4049 (u32*)h_blk->alloc(h_blk, sizeof(u32) * nout, _Alignof(u32)); 4050 if (!out_locals) wfail(t, "wasm: out of memory"); 4051 for (u32 i = 0; i < nout; ++i) 4052 out_locals[i] = 4053 add_wasm_local(t, valtype_for_type(t, w->asm_out_types[i])); 4054 } 4055 if (nin) { 4056 for (u32 i = 0; i < nin; ++i) { 4057 i32 share = w->asm_in_share_out[i]; 4058 if (share >= 0 && (u32)share < nout) { 4059 in_locals[i] = out_locals[share]; 4060 } else { 4061 in_locals[i] = 4062 add_wasm_local(t, valtype_for_type(t, w->asm_in_types[i])); 4063 } 4064 } 4065 } 4066 /* Input materialization: push source operand, then for OPK_INDIRECT 4067 * inputs ("m" constraint with displacement) splice in 4068 * `i32.const ofs; i32.add` so the input local holds base+ofs. 4069 * Finally local.set into the input's local (which may be a shared 4070 * output local). */ 4071 for (u32 i = 0; i < nin; ++i) { 4072 emit_push_operand(t, w->asm_in_kinds[i], w->asm_in_imms[i], 4073 w->asm_in_regs[i], w->asm_in_types[i]); 4074 if (w->asm_in_kinds[i] == WOP_REG && w->asm_in_imms[i] != 0) { 4075 emit_push_imm(t, WASM_VAL_I32, w->asm_in_imms[i]); 4076 emit_insn(t, WASM_INSN_I32_ADD, 0); 4077 } 4078 emit_insn(t, WASM_INSN_LOCAL_SET, (i64)in_locals[i]); 4079 } 4080 /* Splice body, remapping local indices < nin+nout to the actual 4081 * wasm local table. */ 4082 for (u32 i = 0; i < w->raw_ninsns; ++i) { 4083 WasmInsn in = w->raw_insns[i]; 4084 if (in.kind == WASM_INSN_LOCAL_GET || 4085 in.kind == WASM_INSN_LOCAL_SET || 4086 in.kind == WASM_INSN_LOCAL_TEE) { 4087 if (in.imm >= 0 && (u64)in.imm < (u64)nin) 4088 in.imm = (i64)in_locals[in.imm]; 4089 else if (in.imm >= (i64)nin && (u64)in.imm < (u64)(nin + nout)) 4090 in.imm = (i64)out_locals[in.imm - (i64)nin]; 4091 } 4092 t->module->current_loc = in.loc; 4093 wasm_func_add_insn(t->c, t->module, t->cur_func, 4094 (WasmInsnKind)in.kind, in.imm); 4095 t->cur_func->insns[t->cur_func->ninsns - 1u] = in; 4096 } 4097 /* Output extraction: copy each output local into the destination 4098 * Reg's wasm local. */ 4099 for (u32 i = 0; i < nout; ++i) { 4100 WasmValType ovt = valtype_for_type(t, w->asm_out_types[i]); 4101 RegClass cls = 4102 (ovt == WASM_VAL_F32 || ovt == WASM_VAL_F64) ? RC_FP : RC_INT; 4103 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)out_locals[i]); 4104 emit_local_set(t, w->asm_out_regs[i], w->asm_out_types[i], cls); 4105 } 4106 if (in_locals) h_blk->free(h_blk, in_locals, sizeof(u32) * nin); 4107 if (out_locals) h_blk->free(h_blk, out_locals, sizeof(u32) * nout); 4108 break; 4109 } 4110 case WIR_LABEL: { 4111 break; 4112 } 4113 } 4114 } 4115 } 4116 4117 static void linearize(WTarget* t) { 4118 LoweringState L; 4119 /* Rewrite WIR so every free label is bound to a synthetic SCOPE_BLOCK 4120 * (forward goto) or SCOPE_LOOP (backward goto). Switch islands are 4121 * pre-reordered into selector + WIR_SWITCH + case bodies, turning their 4122 * case labels into forward refs the same structuring covers. After this, 4123 * br_to_label resolves every jump through scope-bound machinery. */ 4124 wasm_structurize(t); 4125 memset(&L, 0, sizeof L); 4126 L.t = t; 4127 4128 if (t->has_stack_frame) { 4129 t->frame_saved_sp_local = add_wasm_local(t, WASM_VAL_I32); 4130 t->frame_base_local = add_wasm_local(t, WASM_VAL_I32); 4131 emit_insn(t, WASM_INSN_GLOBAL_GET, (i64)t->stack_pointer_global); 4132 emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->frame_saved_sp_local); 4133 if (t->frame_size) { 4134 emit_insn(t, WASM_INSN_I32_CONST, 4135 (i64)align_to_u32(t->frame_size, t->frame_align)); 4136 emit_insn(t, WASM_INSN_I32_SUB, 0); 4137 } 4138 emit_insn(t, WASM_INSN_LOCAL_TEE, (i64)t->frame_base_local); 4139 emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global); 4140 } 4141 4142 /* Byval copy-in: for each ABI_ARG_INDIRECT param, copy the aggregate from 4143 * the caller's pointer into the callee's stack-frame buffer so callee 4144 * mutations are isolated (wasm32 BasicCABI). Byte-by-byte for v1; can be 4145 * promoted to wider chunks later. */ 4146 for (u32 i = 0; i < t->nbyval_copies; ++i) { 4147 const WByvalCopy* bc = &t->byval_copies[i]; 4148 const WSlot* s = &t->slots[bc->dst_slot_id]; 4149 for (u32 n = 0; n < s->size; ++n) { 4150 /* dst: frame_base */ 4151 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_base_local); 4152 /* src byte: i32.load8_u (ptr_local) offset=n */ 4153 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)bc->ptr_wasm_local); 4154 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, 4155 WASM_INSN_I32_LOAD8_U, 0, n, 0); 4156 wasm_func_add_mem_insn(t->c, t->module, t->cur_func, WASM_INSN_I32_STORE8, 4157 0, s->frame_offset + n, 0); 4158 } 4159 } 4160 4161 linearize_range(t, &L, 0, t->nwir); 4162 if (L.nstack != 0) 4163 wfail(t, "wasm: function ended with %u open scopes", L.nstack); 4164 /* If the body's last real WIR is a terminator (return / br / switch / 4165 * unreachable / tail call) buried inside nested blocks, kit's wasm 4166 * validator does not propagate the unreachable flag across enclosing 4167 * ENDs and would complain about a missing result at the implicit 4168 * function exit. Emit an explicit trailing `unreachable` so control[0] 4169 * is marked unreachable independent of the validator's propagation 4170 * rules. Also: when the terminator is a tail call we've already 4171 * emitted the linear-stack SP restore inline (see WIR_CALL handler), 4172 * and the function never reaches the post-body epilogue at runtime — 4173 * skip it to avoid emitting dead GLOBAL_GET/GLOBAL_SET pairs after the 4174 * return_call. */ 4175 int last_is_tail_call = 0; 4176 { 4177 int needs_unreachable = 0; 4178 for (u32 i = t->nwir; i > 0; --i) { 4179 WIR* w = &t->wir[i - 1u]; 4180 if (w->op == WIR_LABEL || w->op == WIR_SCOPE_OPEN || 4181 w->op == WIR_SCOPE_CLOSE) 4182 continue; 4183 if (w->op == WIR_RET || w->op == WIR_JUMP || w->op == WIR_SWITCH || 4184 w->op == WIR_UNREACHABLE) { 4185 needs_unreachable = 1; 4186 } else if ((w->op == WIR_CALL || w->op == WIR_CALL_INDIRECT) && 4187 w->call_tail) { 4188 needs_unreachable = 1; 4189 last_is_tail_call = 1; 4190 } 4191 break; 4192 } 4193 if (needs_unreachable) emit_insn(t, WASM_INSN_UNREACHABLE, 0); 4194 } 4195 if (t->has_stack_frame && !t->dead && !last_is_tail_call) { 4196 emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->frame_saved_sp_local); 4197 emit_insn(t, WASM_INSN_GLOBAL_SET, (i64)t->stack_pointer_global); 4198 } 4199 } 4200 4201 void wasm_func_end(CGTarget* tg) { 4202 WTarget* t = (WTarget*)tg; 4203 if (!t->cur_func) return; 4204 /* Linearize WIR into the WasmFunc body. */ 4205 linearize(t); 4206 t->cur_fn_desc = NULL; 4207 t->cur_func = NULL; 4208 /* Free per-function WIR arg arrays. */ 4209 Heap* h = t->c->ctx->heap; 4210 for (u32 i = 0; i < t->nwir; ++i) { 4211 WIR* w = &t->wir[i]; 4212 if (w->call_args) { 4213 h->free(h, w->call_args, sizeof(Reg) * w->call_narg); 4214 h->free(h, w->call_arg_imms, sizeof(i64) * w->call_narg); 4215 h->free(h, w->call_arg_kinds, w->call_narg); 4216 h->free(h, w->call_arg_types, sizeof(KitCgTypeId) * w->call_narg); 4217 if (w->call_arg_addrs) 4218 h->free(h, w->call_arg_addrs, sizeof(Operand) * w->call_narg); 4219 w->call_args = NULL; 4220 w->call_arg_imms = NULL; 4221 w->call_arg_kinds = NULL; 4222 w->call_arg_types = NULL; 4223 w->call_arg_addrs = NULL; 4224 } 4225 if (w->switch_cases) { 4226 h->free(h, w->switch_cases, sizeof(CGSwitchCase) * w->switch_ncases); 4227 w->switch_cases = NULL; 4228 w->switch_ncases = 0; 4229 } 4230 if (w->raw_insns) { 4231 h->free(h, w->raw_insns, sizeof(WasmInsn) * w->raw_ninsns); 4232 w->raw_insns = NULL; 4233 w->raw_ninsns = 0; 4234 } 4235 if (w->asm_in_kinds) { 4236 h->free(h, w->asm_in_kinds, w->asm_nin); 4237 h->free(h, w->asm_in_imms, sizeof(i64) * w->asm_nin); 4238 h->free(h, w->asm_in_regs, sizeof(Reg) * w->asm_nin); 4239 h->free(h, w->asm_in_types, sizeof(KitCgTypeId) * w->asm_nin); 4240 h->free(h, w->asm_in_share_out, sizeof(i32) * w->asm_nin); 4241 w->asm_in_kinds = NULL; 4242 w->asm_in_imms = NULL; 4243 w->asm_in_regs = NULL; 4244 w->asm_in_types = NULL; 4245 w->asm_in_share_out = NULL; 4246 w->asm_nin = 0; 4247 } 4248 if (w->asm_out_regs) { 4249 h->free(h, w->asm_out_regs, sizeof(Reg) * w->asm_nout); 4250 h->free(h, w->asm_out_types, sizeof(KitCgTypeId) * w->asm_nout); 4251 w->asm_out_regs = NULL; 4252 w->asm_out_types = NULL; 4253 w->asm_nout = 0; 4254 } 4255 } 4256 t->nwir = 0; 4257 } 4258 4259 /* CGTarget alias hook. cg/session.c has already shared (section_id, value) 4260 * between alias_sym and target_sym at the ObjBuilder layer, which covers 4261 * data aliases (apply_sym_fixups reads section_id/value directly off the 4262 * ObjSym). Function aliases need extra wiring: the wasm function payload 4263 * lives in a target-side side-table (sym_to_func), not in obj sections, 4264 * and the alias's external linker name needs its own WasmExport entry. */ 4265 void wasm_alias(CGTarget* tg, ObjSymId alias_sym, ObjSymId target_sym, 4266 KitCgTypeId type) { 4267 WTarget* t = (WTarget*)tg; 4268 const ObjSym* tsym; 4269 const ObjSym* asym; 4270 (void)type; 4271 if (t->dead) return; 4272 /* Aliases are processed before any function body is emitted, so the module 4273 * may not exist yet; sym_to_wasm_func / wasm_add_export both need it. */ 4274 ensure_module(t); 4275 tsym = obj_symbol_get(t->obj, target_sym); 4276 if (!tsym) wfail(t, "wasm: alias against unknown target symbol"); 4277 if (tsym->kind == SK_FUNC) { 4278 /* Mirror sym_to_func so any later WIR_CALL against the alias resolves 4279 * to the target's wasm function index. */ 4280 u32 idx = sym_to_wasm_func(t, target_sym, NULL); 4281 if (alias_sym >= t->sym_to_func_cap) { 4282 Heap* h = t->c->ctx->heap; 4283 u32 nc = t->sym_to_func_cap ? t->sym_to_func_cap : 16u; 4284 while (nc <= alias_sym) nc *= 2u; 4285 u32* p = 4286 (u32*)h->realloc(h, t->sym_to_func, sizeof(u32) * t->sym_to_func_cap, 4287 sizeof(u32) * nc, _Alignof(u32)); 4288 if (!p) wfail(t, "wasm: out of memory"); 4289 for (u32 i = t->sym_to_func_cap; i < nc; ++i) p[i] = 0; 4290 t->sym_to_func = p; 4291 t->sym_to_func_cap = nc; 4292 } 4293 t->sym_to_func[alias_sym] = idx + 1u; 4294 /* Export under the alias's linker name when non-local. Mirrors the 4295 * export logic at the end of wasm_func_begin. */ 4296 asym = obj_symbol_get(t->obj, alias_sym); 4297 if (asym && asym->bind != SB_LOCAL) { 4298 const char* name = pool_sym_cstr(t->c->global, asym->name, NULL); 4299 if (name && *name) { 4300 Heap* h = t->c->ctx->heap; 4301 size_t nlen = strlen(name); 4302 char* exp_name = (char*)h->alloc(h, nlen + 1u, 1); 4303 WasmExport* e; 4304 memcpy(exp_name, name, nlen + 1u); 4305 e = wasm_add_export(t->c, t->module); 4306 e->name = exp_name; 4307 e->kind = 0; /* function export */ 4308 e->index = idx; 4309 } 4310 } 4311 return; 4312 } 4313 if (tsym->kind == SK_OBJ) { 4314 /* Data aliases: obj_symbol_define has already shared (section_id, 4315 * value), and apply_sym_fixups reads those directly. Nothing more 4316 * to do here — but diagnose if the target hasn't been defined yet 4317 * (it would produce a bogus address at finalize). */ 4318 if (tsym->section_id == OBJ_SEC_NONE) { 4319 wfail(t, "wasm: data alias against undefined target symbol"); 4320 } 4321 return; 4322 } 4323 wfail(t, "wasm target: alias of symbol kind %u not yet supported", 4324 (unsigned)tsym->kind); 4325 } 4326 4327 /* Assign each SF_ALLOC (non-EXEC) ObjBuilder section a compact base in 4328 * linear memory. Walks sections in id order so the layout is deterministic. 4329 * Each base is aligned to the section's required alignment and lives in 4330 * t->section_base[sid]. Returns the next unused offset (end of data image). */ 4331 static u32 assign_section_bases(WTarget* t) { 4332 Heap* h = t->c->ctx->heap; 4333 u32 nsec = obj_section_count(t->obj); 4334 if (nsec > t->section_base_cap) { 4335 u32 nc = t->section_base_cap ? t->section_base_cap : 4u; 4336 while (nc < nsec) nc *= 2u; 4337 void* p = h->realloc(h, t->section_base, sizeof(u32) * t->section_base_cap, 4338 sizeof(u32) * nc, _Alignof(u32)); 4339 if (!p) wfail(t, "wasm: out of memory"); 4340 t->section_base = (u32*)p; 4341 for (u32 i = t->section_base_cap; i < nc; ++i) 4342 t->section_base[i] = 0xFFFFFFFFu; 4343 t->section_base_cap = nc; 4344 } 4345 u32 next = WASM_DATA_NULL_GUARD; 4346 for (ObjSecId sid = 0; sid < nsec; ++sid) t->section_base[sid] = 0xFFFFFFFFu; 4347 for (ObjSecId sid = 1; sid < nsec; ++sid) { 4348 const Section* s = obj_section_get(t->obj, sid); 4349 if (!s || s->removed || !(s->flags & SF_ALLOC) || s->flags & SF_EXEC) 4350 continue; 4351 u32 align = s->align ? s->align : 1u; 4352 if (align < 1u) align = 1u; 4353 next = align_to_u32(next, align); 4354 t->section_base[sid] = next; 4355 u32 sz = (s->kind == SEC_BSS || s->sem == SSEM_NOBITS) 4356 ? s->bss_size 4357 : (u32)s->bytes.total; 4358 if (sz > UINT32_MAX - next) wfail(t, "wasm: linear memory image too large"); 4359 next += sz; 4360 } 4361 return next; 4362 } 4363 4364 /* Patch a single i32/i64 value into the linear-memory image buffer at 4365 * `offset`. Wasm is little-endian. */ 4366 static void mem_write_le(u8* buf, u32 offset, u64 value, u32 width) { 4367 for (u32 i = 0; i < width; ++i) buf[offset + i] = (u8)(value >> (i * 8u)); 4368 } 4369 4370 /* Allocate aligned BSS-style space in linear memory for every SK_COMMON 4371 * symbol the ObjBuilder knows about. Called after assign_section_bases so 4372 * common storage sits past the last SF_ALLOC section. Records the assigned 4373 * base in t->common_base[id]; returns the next free cursor. */ 4374 static u32 assign_common_bases(WTarget* t, u32 next) { 4375 Heap* h = t->c->ctx->heap; 4376 ObjSymIter* it = obj_symiter_new(t->obj); 4377 ObjSymEntry e; 4378 while (obj_symiter_next(it, &e)) { 4379 const ObjSym* os = e.sym; 4380 if (!os || os->removed) continue; 4381 if (os->kind != SK_COMMON) continue; 4382 u32 align = os->common_align ? (u32)os->common_align : 1u; 4383 if (align < 1u) align = 1u; 4384 if (e.id >= t->common_base_cap) { 4385 u32 nc = t->common_base_cap ? t->common_base_cap : 8u; 4386 while (nc <= e.id) nc *= 2u; 4387 void* p = h->realloc(h, t->common_base, sizeof(u32) * t->common_base_cap, 4388 sizeof(u32) * nc, _Alignof(u32)); 4389 if (!p) wfail(t, "wasm: out of memory"); 4390 t->common_base = (u32*)p; 4391 for (u32 i = t->common_base_cap; i < nc; ++i) 4392 t->common_base[i] = 0xFFFFFFFFu; 4393 t->common_base_cap = nc; 4394 } 4395 next = align_to_u32(next, align); 4396 t->common_base[e.id] = next; 4397 u32 sz = (u32)os->size; 4398 if (sz > UINT32_MAX - next) 4399 wfail(t, "wasm: linear memory image too large (common symbols)"); 4400 next += sz; 4401 } 4402 obj_symiter_free(it); 4403 return next; 4404 } 4405 4406 /* Resolve `sym + addend` to a linear-memory address. Handles both 4407 * section-defined symbols (via t->section_base[sym->section_id]) and 4408 * common symbols (via t->common_base[sym]). Returns 0 and sets *ok=0 if 4409 * the symbol can't be resolved here; callers diagnose. */ 4410 static u32 wasm_sym_linear_addr(WTarget* t, ObjSymId sym, i64 addend, int* ok) { 4411 const ObjSym* os = obj_symbol_get(t->obj, sym); 4412 *ok = 0; 4413 if (!os) return 0; 4414 if (os->kind == SK_COMMON) { 4415 if (sym >= t->common_base_cap || t->common_base[sym] == 0xFFFFFFFFu) 4416 return 0; 4417 *ok = 1; 4418 return t->common_base[sym] + (u32)addend; 4419 } 4420 if (os->section_id == OBJ_SEC_NONE) return 0; 4421 if (os->section_id >= t->section_base_cap || 4422 t->section_base[os->section_id] == 0xFFFFFFFFu) 4423 return 0; 4424 *ok = 1; 4425 return t->section_base[os->section_id] + (u32)os->value + (u32)addend; 4426 } 4427 4428 /* Apply each ObjBuilder relocation to the linear-memory image. Only 4429 * absolute (R_ABS32/R_ABS64) relocations are supported for now; PC-relative 4430 * and other kinds diagnose. */ 4431 static void apply_data_relocs(WTarget* t, u8* mem) { 4432 u32 ntotal = obj_reloc_total(t->obj); 4433 for (u32 i = 0; i < ntotal; ++i) { 4434 const Reloc* r = obj_reloc_at(t->obj, i); 4435 if (!r || r->removed) continue; 4436 if (r->section_id == OBJ_SEC_NONE) continue; 4437 if (r->section_id >= t->section_base_cap || 4438 t->section_base[r->section_id] == 0xFFFFFFFFu) 4439 continue; 4440 const Section* rs = obj_section_get(t->obj, r->section_id); 4441 if (!rs || rs->flags & SF_EXEC) continue; 4442 const ObjSym* tos = obj_symbol_get(t->obj, r->sym); 4443 if (!tos) 4444 wfail(t, "wasm: data relocation against unresolved symbol not supported"); 4445 /* Function-symbol references in data sections (e.g. a static vtable 4446 * `static fn_t v = &foo;`) resolve to wasm function-table indices, not 4447 * linear-memory addresses. The funcref table is built before 4448 * apply_data_relocs runs, so the index is already known. */ 4449 u32 width; 4450 u64 value; 4451 if (tos->kind == SK_FUNC) { 4452 if (r->kind != R_ABS32) 4453 wfail(t, 4454 "wasm: function-pointer data relocation kind %u not supported " 4455 "(only R_ABS32 on wasm32 target)", 4456 (unsigned)r->kind); 4457 if (r->addend != 0) 4458 wfail(t, "wasm: nonzero addend on function-pointer data relocation"); 4459 u32 tbl_idx = func_table_index_for(t, r->sym); 4460 width = 4; 4461 value = (u64)tbl_idx; 4462 u32 dst_off = t->section_base[r->section_id] + r->offset; 4463 mem_write_le(mem, dst_off, value, width); 4464 continue; 4465 } 4466 if (tos->section_id == OBJ_SEC_NONE && tos->kind != SK_COMMON) 4467 wfail(t, "wasm: data relocation against unresolved symbol not supported"); 4468 { 4469 int ok = 0; 4470 /* The addend is already added by `value = sym_addr + r->addend` below; 4471 * pass 0 here so we don't double-count. */ 4472 u32 sym_addr = wasm_sym_linear_addr(t, r->sym, 0, &ok); 4473 if (!ok) 4474 wfail(t, 4475 "wasm: data relocation target symbol has no linear-memory " 4476 "address"); 4477 switch (r->kind) { 4478 case R_ABS32: 4479 width = 4; 4480 value = (u64)(u32)((i64)sym_addr + r->addend); 4481 break; 4482 case R_ABS64: 4483 wfail(t, 4484 "wasm: R_ABS64 data relocation not supported on wasm32 target"); 4485 default: 4486 wfail(t, "wasm: unsupported data relocation kind %u", 4487 (unsigned)r->kind); 4488 } 4489 } 4490 u32 dst_off = t->section_base[r->section_id] + r->offset; 4491 mem_write_le(mem, dst_off, value, width); 4492 } 4493 } 4494 4495 /* Walk the deferred WSymFixup queue and patch the placeholder i32.const 4496 * imm in each WasmFunc.insns[] with the resolved absolute address. */ 4497 static void apply_sym_fixups(WTarget* t) { 4498 for (u32 i = 0; i < t->sym_fixups_count; ++i) { 4499 WSymFixup fx = t->sym_fixups[i]; 4500 int ok = 0; 4501 u32 addr = wasm_sym_linear_addr(t, fx.sym, fx.addend, &ok); 4502 if (!ok) wfail(t, "wasm: deferred symbol fixup against unresolved symbol"); 4503 WasmFunc* f = &t->module->funcs[fx.wasm_func_idx]; 4504 if (fx.insn_idx >= f->ninsns) 4505 wfail(t, "wasm: deferred symbol fixup insn_idx out of range"); 4506 f->insns[fx.insn_idx].imm = (i64)addr; 4507 } 4508 } 4509 4510 static void wasm_materialize_data(WTarget* t) { 4511 if (!t->has_memory) { 4512 /* No linear memory was needed by any function body or addr_of, so 4513 * symbol fixups should be empty by construction. */ 4514 return; 4515 } 4516 u32 image_end = assign_section_bases(t); 4517 image_end = assign_common_bases(t, image_end); 4518 u32 stack_size = t->has_stack_pointer ? t->stack_size : 0u; 4519 u32 image = image_end ? align_to_u32(image_end, 16u) : WASM_DATA_NULL_GUARD; 4520 if (image > UINT32_MAX - stack_size) 4521 wfail(t, "wasm: linear memory image too large"); 4522 /* Build a single active data segment covering 0..image. Passive segments 4523 * + memory.init would be needed for multi-TU linking; single-TU output 4524 * stays with the simpler shape. */ 4525 WasmDataSegment* seg = NULL; 4526 if (image) { 4527 seg = wasm_add_data(t->c, t->module); 4528 seg->mode = WASM_SEG_ACTIVE; 4529 seg->memidx = 0; 4530 seg->offset = 0; 4531 wasm_data_set_bytes(t->c, t->module, seg, NULL, (u64)image); 4532 } 4533 u32 nsec = obj_section_count(t->obj); 4534 for (ObjSecId sid = 1; sid < nsec; ++sid) { 4535 const Section* s = obj_section_get(t->obj, sid); 4536 if (!s || s->removed || !(s->flags & SF_ALLOC) || s->flags & SF_EXEC) 4537 continue; 4538 if (s->kind == SEC_BSS || s->sem == SSEM_NOBITS || !s->bytes.total) 4539 continue; 4540 buf_flatten(&s->bytes, seg->bytes + t->section_base[sid]); 4541 } 4542 if (seg) apply_data_relocs(t, seg->bytes); 4543 apply_sym_fixups(t); 4544 t->data_end = image; 4545 u32 stack_top = (u32)align_to_u32(image + stack_size, 16u); 4546 t->module->memories[0].min_pages = (stack_top + 65535u) / 65536u; 4547 /* Shared memory requires has_max and max >= min. ensure_shared_memory set a 4548 * provisional wasm32-ceiling cap (65536 pages = 4 GiB); now that the final 4549 * layout is known, tighten max down to min so the module declares a snug, 4550 * fixed shared memory. The backend never emits memory.grow, so the memory is 4551 * non-growable regardless, and a 4 GiB declared max would otherwise force an 4552 * embedder (e.g. `kit run`) to reserve the full ceiling up front. */ 4553 if (t->module->memories[0].shared) { 4554 t->module->memories[0].has_max = 1; 4555 t->module->memories[0].max_pages = t->module->memories[0].min_pages; 4556 } 4557 if (t->has_stack_pointer && t->stack_pointer_global < t->module->nglobals) { 4558 t->module->globals[t->stack_pointer_global].init.imm = stack_top; 4559 } 4560 } 4561 4562 /* Static-data initializers (e.g. `static fn_t v[] = {&foo, &bar};`) go 4563 * through ObjBuilder relocations rather than wasm_addr_of, so they never 4564 * touch queue_func_table_fixup. Scan the reloc table once before building 4565 * the funcref table so every function whose address is referenced from data 4566 * also gets a table slot. apply_data_relocs then patches the linear-memory 4567 * image with the assigned index. */ 4568 static void wasm_collect_func_data_refs(WTarget* t) { 4569 u32 ntotal = obj_reloc_total(t->obj); 4570 for (u32 i = 0; i < ntotal; ++i) { 4571 const Reloc* r = obj_reloc_at(t->obj, i); 4572 const ObjSym* tos; 4573 if (!r || r->removed) continue; 4574 if (r->section_id == OBJ_SEC_NONE) continue; 4575 { 4576 const Section* rs = obj_section_get(t->obj, r->section_id); 4577 if (!rs || rs->flags & SF_EXEC) continue; /* code-section relocs */ 4578 } 4579 tos = obj_symbol_get(t->obj, r->sym); 4580 if (!tos || tos->kind != SK_FUNC) continue; 4581 (void)func_table_index_for(t, r->sym); 4582 (void)sym_to_wasm_func(t, r->sym, NULL); 4583 } 4584 } 4585 4586 /* Build the single funcref table and its active element segment, then patch 4587 * every queued WFuncTableFixup's placeholder `i32.const 0` with the assigned 4588 * table index. Slot 0 stays reserved (call_indirect through index 0 traps on 4589 * the type check), so the first recorded function lands at index 1. Each 4590 * WasmElemSegment caps its funcs array at 64 entries; we chunk across 4591 * multiple segments when the address-taken set is larger. */ 4592 static void wasm_materialize_functable(WTarget* t) { 4593 wasm_collect_func_data_refs(t); 4594 if (!t->has_func_table || t->func_table_count == 0) return; 4595 ensure_module(t); 4596 /* Table: non-growable, sized to hold the reserved null slot plus every 4597 * assigned entry. */ 4598 WasmTable* tbl = wasm_add_table(t->c, t->module); 4599 tbl->elem_type = WASM_VAL_FUNCREF; 4600 tbl->min = 1u + t->func_table_count; 4601 tbl->max = tbl->min; 4602 tbl->has_max = 1; 4603 /* Active element segment populates table 0 starting at offset 1 (slot 0 4604 * stays null). Element segments are now heap-grown — no chunking needed. */ 4605 { 4606 WasmElemSegment* seg = wasm_add_elem(t->c, t->module); 4607 seg->mode = WASM_SEG_ACTIVE; 4608 seg->elem_type = WASM_VAL_FUNCREF; 4609 seg->tableidx = 0; 4610 seg->offset = 1; 4611 for (u32 i = 0; i < t->func_table_count; ++i) { 4612 ObjSymId sym = t->func_table[i]; 4613 wasm_elem_push_func(t->c, t->module, seg, sym_to_wasm_func(t, sym, NULL)); 4614 } 4615 } 4616 /* Patch placeholders. */ 4617 for (u32 i = 0; i < t->func_table_fixups_count; ++i) { 4618 WFuncTableFixup fx = t->func_table_fixups[i]; 4619 u32 tbl_idx = func_table_index_for(t, fx.sym); 4620 WasmFunc* f = &t->module->funcs[fx.wasm_func_idx]; 4621 if (fx.insn_idx >= f->ninsns) 4622 wfail(t, "wasm: function-pointer fixup insn_idx out of range"); 4623 f->insns[fx.insn_idx].imm = (i64)tbl_idx; 4624 } 4625 } 4626 4627 /* Wasm requires every import to occupy a lower function index than any 4628 * defined function. The backend, however, allocates a WasmFunc for any 4629 * direct-call target in walk order — so a defined function may end up at a 4630 * lower array index than an import created later by promote_import_func. 4631 * Reorder m->funcs so all imports precede all definitions, then walk every 4632 * function-index reference in the module and apply the old->new mapping. */ 4633 static void wasm_reorder_funcs_imports_first(WTarget* t) { 4634 WasmModule* m = t->module; 4635 if (!m || m->nfuncs == 0) return; 4636 Heap* h = m->heap; 4637 u32 n = m->nfuncs; 4638 /* Quick check: bail out if imports are already before all definitions. */ 4639 int seen_def = 0; 4640 int needs_reorder = 0; 4641 for (u32 i = 0; i < n; ++i) { 4642 if (m->funcs[i].is_import) { 4643 if (seen_def) { 4644 needs_reorder = 1; 4645 break; 4646 } 4647 } else { 4648 seen_def = 1; 4649 } 4650 } 4651 if (!needs_reorder) return; 4652 u32* old_to_new = (u32*)h->alloc(h, sizeof(u32) * n, _Alignof(u32)); 4653 WasmFunc* new_funcs = 4654 (WasmFunc*)h->alloc(h, sizeof(WasmFunc) * n, _Alignof(WasmFunc)); 4655 if (!old_to_new || !new_funcs) wfail(t, "wasm: out of memory"); 4656 u32 w_idx = 0; 4657 for (u32 i = 0; i < n; ++i) { 4658 if (m->funcs[i].is_import) { 4659 new_funcs[w_idx] = m->funcs[i]; 4660 old_to_new[i] = w_idx++; 4661 } 4662 } 4663 for (u32 i = 0; i < n; ++i) { 4664 if (!m->funcs[i].is_import) { 4665 new_funcs[w_idx] = m->funcs[i]; 4666 old_to_new[i] = w_idx++; 4667 } 4668 } 4669 /* Swap arrays. Old buffer is freed via the module's heap-tracked 4670 * realloc bookkeeping when wasm_module_free runs; we just overwrite the 4671 * pointer + length here. */ 4672 h->free(h, m->funcs, sizeof(WasmFunc) * m->cap_funcs); 4673 m->funcs = new_funcs; 4674 m->cap_funcs = n; 4675 /* Remap every funcidx-bearing slot in the module. */ 4676 for (u32 fi = 0; fi < n; ++fi) { 4677 WasmFunc* f = &m->funcs[fi]; 4678 for (u32 j = 0; j < f->ninsns; ++j) { 4679 WasmInsn* in = &f->insns[j]; 4680 if (in->kind == WASM_INSN_CALL || in->kind == WASM_INSN_RETURN_CALL || 4681 in->kind == WASM_INSN_REF_FUNC) { 4682 u32 old = (u32)in->imm; 4683 if (old < n) in->imm = (int64_t)old_to_new[old]; 4684 } 4685 } 4686 } 4687 for (u32 i = 0; i < m->nexports; ++i) { 4688 if (m->exports[i].kind == 0u && m->exports[i].index < n) 4689 m->exports[i].index = old_to_new[m->exports[i].index]; 4690 } 4691 for (u32 i = 0; i < m->nelems; ++i) { 4692 WasmElemSegment* seg = &m->elems[i]; 4693 for (u32 j = 0; j < seg->nfuncs; ++j) { 4694 if (seg->funcs[j] < n) seg->funcs[j] = old_to_new[seg->funcs[j]]; 4695 } 4696 } 4697 if (m->has_start && m->start_func < n) 4698 m->start_func = old_to_new[m->start_func]; 4699 /* Update the backend's sym_to_func reverse map so any post-finalize lookups 4700 * (e.g. data-reloc fixups) resolve to the new indices. The map stores 4701 * idx+1 so 0 = "unassigned"; preserve that convention. */ 4702 for (ObjSymId sym = 0; sym < t->sym_to_func_cap; ++sym) { 4703 if (t->sym_to_func[sym]) { 4704 u32 old = t->sym_to_func[sym] - 1u; 4705 if (old < n) t->sym_to_func[sym] = old_to_new[old] + 1u; 4706 } 4707 } 4708 h->free(h, old_to_new, sizeof(u32) * n); 4709 } 4710 4711 /* Export the module's linear memory under the conventional name "memory" so 4712 * standard runtimes (browser/wasmtime/wasmer/Node) can find it. Only emits 4713 * when the module has at least one defined (non-import) memory and no 4714 * memory export already exists. */ 4715 static void wasm_export_memory(WTarget* t) { 4716 WasmModule* m = t->module; 4717 if (!m) return; 4718 ensure_linear_memory(t); 4719 m = t->module; 4720 /* Find the first defined (non-import) memory. */ 4721 u32 mem_idx = 0; 4722 int found = 0; 4723 for (u32 i = 0; i < m->nmemories; ++i) { 4724 if (!m->memories[i].is_import) { 4725 mem_idx = i; 4726 found = 1; 4727 break; 4728 } 4729 } 4730 if (!found) return; 4731 /* Skip if the user already added a memory export (e.g. via the WAT path 4732 * or future explicit-export hook). */ 4733 for (u32 i = 0; i < m->nexports; ++i) { 4734 if (m->exports[i].kind == 2u && m->exports[i].index == mem_idx) return; 4735 } 4736 Heap* h = t->c->ctx->heap; 4737 WasmExport* e = wasm_add_export(t->c, m); 4738 static const char kName[] = "memory"; 4739 char* dup = (char*)h->alloc(h, sizeof(kName), 1); 4740 if (!dup) wfail(t, "wasm: out of memory"); 4741 memcpy(dup, kName, sizeof(kName)); 4742 e->name = dup; 4743 e->kind = 2u; /* memory export */ 4744 e->index = mem_idx; 4745 } 4746 4747 /* Diagnose any WasmFunc that has neither a body nor import status — that's a 4748 * declaration with no definition, e.g. a function-pointer reference to an 4749 * extern whose call site never appeared (so we never saw an ABI to synthesize 4750 * an import signature from). Emitting such a function would produce a 4751 * malformed module. Diagnose by sym name so users can fix the source. */ 4752 static void wasm_diagnose_unresolved_funcs(WTarget* t) { 4753 if (!t->module) return; 4754 for (ObjSymId sym = 1; sym < t->sym_to_func_cap; ++sym) { 4755 if (!t->sym_to_func[sym]) continue; 4756 u32 idx = t->sym_to_func[sym] - 1u; 4757 if (idx >= t->module->nfuncs) continue; 4758 WasmFunc* f = &t->module->funcs[idx]; 4759 if (f->is_import) continue; 4760 if (f->ninsns != 0) continue; 4761 const ObjSym* os = obj_symbol_get(t->obj, sym); 4762 if (!os || os->section_id != OBJ_SEC_NONE) continue; 4763 const char* name = pool_sym_cstr(t->c->global, os->name, NULL); 4764 wfail(t, 4765 "wasm: undefined function '%s' has its address taken but no direct " 4766 "call was seen — cannot synthesize import signature; add a direct " 4767 "call or annotate the declaration", 4768 name ? name : "(anonymous)"); 4769 } 4770 } 4771 4772 void wasm_finalize(CGTarget* tg) { 4773 WTarget* t = (WTarget*)tg; 4774 wasm_materialize_functable(t); 4775 wasm_materialize_data(t); 4776 if (t->module) { 4777 wasm_diagnose_unresolved_funcs(t); 4778 wasm_export_memory(t); 4779 wasm_reorder_funcs_imports_first(t); 4780 } 4781 /* WasmModule remains attached to ObjBuilder via OBJ_EXT_WASM; emit_wasm 4782 * flushes it. */ 4783 } 4784 4785 static void wasm_module_freefn(Compiler* c, void* p) { 4786 (void)c; 4787 WasmModule* m = (WasmModule*)p; 4788 Heap* h = m->heap; 4789 wasm_module_free(m); 4790 h->free(h, m, sizeof *m); 4791 } 4792 4793 WTarget* wasm_emit_target_new(Compiler* c, ObjBuilder* o, MCEmitter* mc) { 4794 Heap* h; 4795 WTarget* t; 4796 if (!c) return NULL; 4797 h = (Heap*)c->ctx->heap; 4798 t = (WTarget*)h->alloc(h, sizeof *t, _Alignof(WTarget)); 4799 if (!t) return NULL; 4800 memset(t, 0, sizeof *t); 4801 t->base.c = c; 4802 t->base.obj = o; 4803 t->c = c; 4804 t->obj = o; 4805 (void)mc; 4806 return t; 4807 } 4808 4809 void wasm_destroy(CGTarget* tg) { 4810 WTarget* t = (WTarget*)tg; 4811 Heap* h = t->c->ctx->heap; 4812 if (t->reg_to_local) h->free(h, t->reg_to_local, sizeof(u32) * t->reg_cap); 4813 if (t->reg_type) h->free(h, t->reg_type, sizeof(KitCgTypeId) * t->reg_cap); 4814 if (t->reg_cls) h->free(h, t->reg_cls, t->reg_cap); 4815 if (t->wir) h->free(h, t->wir, sizeof(WIR) * t->wir_cap); 4816 if (t->labels) h->free(h, t->labels, sizeof(WLabel) * t->labels_cap); 4817 if (t->slots) h->free(h, t->slots, sizeof(WSlot) * t->slots_cap); 4818 if (t->param_local_idx) 4819 h->free(h, t->param_local_idx, sizeof(u32) * t->param_local_idx_cap); 4820 if (t->byval_copies) 4821 h->free(h, t->byval_copies, sizeof(WByvalCopy) * t->byval_copies_cap); 4822 if (t->sym_to_func) 4823 h->free(h, t->sym_to_func, sizeof(u32) * t->sym_to_func_cap); 4824 if (t->funcs) h->free(h, t->funcs, sizeof(WFunc) * t->funcs_cap); 4825 if (t->section_base) 4826 h->free(h, t->section_base, sizeof(u32) * t->section_base_cap); 4827 if (t->common_base) 4828 h->free(h, t->common_base, sizeof(u32) * t->common_base_cap); 4829 if (t->sym_fixups) 4830 h->free(h, t->sym_fixups, sizeof(WSymFixup) * t->sym_fixups_cap); 4831 if (t->func_table) 4832 h->free(h, t->func_table, sizeof(ObjSymId) * t->func_table_cap); 4833 if (t->func_table_fixups) 4834 h->free(h, t->func_table_fixups, 4835 sizeof(WFuncTableFixup) * t->func_table_fixups_cap); 4836 h->free(h, t, sizeof *t); 4837 } 4838 4839 /* ----------------------------------------------------------------- 4840 * Module bootstrap: attach a WasmModule to the ObjBuilder so emit_wasm 4841 * can find it. Lazily on first func_begin. 4842 * ----------------------------------------------------------------- */ 4843 4844 static struct WasmModule* ensure_module(WTarget* t) { 4845 if (t->module) return t->module; 4846 Heap* h = t->c->ctx->heap; 4847 WasmModule* m = (WasmModule*)h->alloc(h, sizeof *m, _Alignof(WasmModule)); 4848 if (!m) wfail(t, "wasm: out of memory"); 4849 wasm_module_init(m, h); 4850 /* kit-produced modules always declare bulk-memory support: WIR_COPY_BYTES 4851 * / WIR_SET_BYTES lower to memory.copy / memory.fill unconditionally, and 4852 * the sret-return path emits memory.copy too. */ 4853 m->features |= WASM_FEATURE_BULK_MEMORY; 4854 t->module = m; 4855 obj_ext_set(t->obj, OBJ_EXT_WASM, m, wasm_module_freefn); 4856 return m; 4857 }