internal.h (16769B)
1 #ifndef KIT_ARCH_WASM_INTERNAL_H 2 #define KIT_ARCH_WASM_INTERNAL_H 3 4 /* Wasm CGTarget. 5 * 6 * Produces a tool-conventions-shaped Wasm module from a single Toy/C 7 * translation unit. Operates with virtual_regs=1: CG mints fresh Reg ids and 8 * the target assigns each Reg a Wasm local on first use, materializing values 9 * by `local.get`/`local.set`. The accumulating WasmModule is attached to the 10 * ObjBuilder under OBJ_EXT_WASM so emit_wasm can flush it at finalize time. 11 * 12 * Scope (initial): scalar i32/i64/f32/f64 with locals, params, return, direct 13 * calls, structured `scope_begin(LOOP)` + break/continue, label-based 14 * forward `if`/`if-else` via the kit_cg_if_begin/else/end pattern, basic 15 * binop/unop/cmp/convert, and OPK_IMM constants. Address-taken locals, 16 * aggregates, indirect calls, alloca, va_*, intrinsics other than 17 * trivial ones, inline asm, TLS, bitfields, switch_, indirect_branch, and 18 * load_label_addr all panic with a precise diagnostic; they will land as 19 * follow-ups against doc/WASM.md. Atomics (load/store/rmw/cmpxchg/fence) 20 * are lowered through the wasm-threads opcodes; the linear memory is 21 * promoted to shared on the first atomic emission. */ 22 23 #include <kit/core.h> 24 25 #include "arch/mc.h" 26 #include "cg/cgtarget.h" /* WTarget embeds CgTarget base */ 27 #include "core/core.h" 28 #include "obj/obj.h" 29 #include "opt/ir.h" 30 31 typedef CgTarget CGTarget; 32 typedef struct CgIrModule CgIrModule; 33 34 typedef struct WasmOptCGSwitchDesc { 35 Operand selector; 36 KitCgTypeId selector_type; 37 Label default_label; 38 const CGSwitchCase* cases; 39 u32 ncases; 40 u8 hint; 41 u8 opt_level; 42 u8 pad[2]; 43 } WasmOptCGSwitchDesc; 44 #define CGSwitchDesc WasmOptCGSwitchDesc 45 46 /* Forward references into the shared src/wasm module representation. The 47 * target reuses that model so emit_wasm can flush through wasm_encode. */ 48 struct WasmModule; 49 struct WasmFunc; 50 51 /* Per-instruction kinds we record, then linearize at func_end. Keeping the 52 * record list separate from the WasmFunc body lets us run the deferred 53 * label/jump → wasm-block resolution without baking forward-only structure 54 * into the recording side. */ 55 typedef enum WIROp { 56 WIR_LOAD_IMM, /* dst, imm, type */ 57 WIR_LOAD_CONST_F, /* dst, fp_imm, type */ 58 WIR_COPY, /* dst, src */ 59 WIR_BINOP, /* dst, a, b, BinOp */ 60 WIR_UNOP, /* dst, a, UnOp */ 61 WIR_CMP, /* dst, a, b, CmpOp */ 62 WIR_CONVERT, /* dst, src, ConvKind */ 63 WIR_LABEL, /* place label[0] */ 64 WIR_JUMP, /* jump to label[0] */ 65 WIR_CMP_BRANCH, /* cmp_branch op a b -> label[0] */ 66 WIR_CALL, /* callee_sym, nargs, args[], ret_reg (or REG_NONE) */ 67 WIR_CALL_INDIRECT, /* callee in `a` (i32 table index), typeidx in `imm`, 68 nargs/args[]/ret_reg same shape as WIR_CALL */ 69 WIR_RET, /* val (or REG_NONE) */ 70 WIR_SCOPE_OPEN, /* scope_id; kind == SCOPE_LOOP/BLOCK */ 71 WIR_SCOPE_CLOSE, /* scope_id */ 72 WIR_UNREACHABLE, 73 WIR_SWITCH, /* selector operand + dense label table at lowering */ 74 WIR_LOAD_LOCAL, /* dst, frame_slot in `imm`, type */ 75 WIR_STORE_LOCAL, /* frame_slot in `imm`, src (a/imm_a/imm_kind), type */ 76 WIR_LOAD_MEM, /* dst = load addr */ 77 WIR_STORE_MEM, /* store src -> addr */ 78 WIR_ADDR_OF, /* dst = address of addr */ 79 WIR_ALLOCA, /* dst = stack allocation of size operand */ 80 WIR_COPY_BYTES, /* memcpy-like byte copy */ 81 WIR_SET_BYTES, /* memset-like byte set */ 82 WIR_ATOMIC_LOAD, /* dst = atomic.load(addr_reg), mem holds access info */ 83 WIR_ATOMIC_STORE, /* atomic.store(addr_reg, src) */ 84 WIR_ATOMIC_RMW, /* dst = atomic.rmw_<op>(addr_reg, val); cgop = KitCgAtomicOp 85 */ 86 WIR_ATOMIC_CAS, /* dst = prior; dst2 = ok (i32 0/1); a=addr_reg, 87 b/imm_kind_b=expected, op_c/imm_kind_c=desired */ 88 WIR_FENCE, /* atomic.fence (memory order ignored, wasm has seq_cst) */ 89 WIR_VA_START, /* addr = ap_addr; stores va_ptr_param_local i32 at *ap */ 90 WIR_VA_ARG, /* dst = load of `type` from *(*addr); advance *addr by 8 */ 91 WIR_VA_COPY, /* addr = dst_ap_addr; call_sret_addr = src_ap_addr */ 92 WIR_INTRINSIC, /* cgop = IntrinKind; operand layout per kind: 93 - bit ops (CLZ/CTZ/POPCOUNT/BSWAP): one 94 register operand in `a`, single dst in `dst`, 95 `type` carries the operand/result type. 96 - overflow arith (S/U{ADD,SUB,MUL}_OVERFLOW): two 97 value operands captured into a/imm_a/imm_kind and 98 b/imm_b/imm_kind_b, dst = value reg, dst2 = i32 99 overflow flag reg, `type` = value type. */ 100 WIR_ASM_BLOCK, /* inline asm block. raw_insns[0..raw_ninsns) is the 101 pre-parsed body; local.get/set/tee indices in 102 [0, asm_nin) are remapped to freshly allocated wasm 103 locals at linearize time. Input bindings are 104 captured into asm_in_kinds/asm_in_imms/asm_in_regs; 105 output bindings into asm_out_regs. */ 106 } WIROp; 107 108 typedef struct WIR { 109 u8 op; 110 u8 cls; /* RegClass for the produced value or branch operand */ 111 u8 cgop; /* BinOp/UnOp/CmpOp/ConvKind discriminator */ 112 u8 pad; 113 KitCgTypeId type; /* type of dst (or operand type for cmp/store) */ 114 KitCgTypeId type2; /* operand type for convert/cmp */ 115 Reg dst; 116 Reg a; 117 Reg b; 118 i64 imm; 119 double fp_imm; 120 Label labels[1]; /* used by LABEL/JUMP/CMP_BRANCH */ 121 u32 scope_id; 122 u32 imm_kind; /* 0=reg, 1=imm — operand variant for a/b */ 123 u32 imm_kind_b; 124 i64 imm_a; 125 i64 imm_b; 126 ObjSymId call_sym; 127 u32 call_narg; 128 Operand addr; 129 MemAccess mem; 130 AggregateAccess agg; 131 /* Per-arg captured operand. kind: 0=REG (value=arg_regs[i]), 1=IMM 132 * (value=arg_imms[i]), 4=ADDR (the i32 address of an aggregate source — 133 * call_arg_addrs[i] holds the original Operand). Heap-allocated when 134 * nargs>0; freed at func_end. */ 135 Reg* call_args; 136 i64* call_arg_imms; 137 u8* call_arg_kinds; 138 KitCgTypeId* call_arg_types; 139 Operand* call_arg_addrs; 140 /* Sret return slot: when the called function returns indirectly, the caller 141 * prepends an i32 pointer to a caller-allocated buffer. call_sret_addr is 142 * that buffer's address operand; emit-time pushes its i32 first. */ 143 u8 call_has_sret; 144 u8 call_variadic; /* callee is variadic; pack call_var_* into linear-memory 145 * buffer and push its addr as hidden trailing i32 arg. */ 146 u8 call_tail; /* emit as return_call / return_call_indirect */ 147 u8 pad_call[1]; 148 Operand call_sret_addr; 149 /* Variadic args (those past d->abi->nparams when callee is variadic). Stored 150 * separately from call_args because they don't appear in the wasm signature 151 * at all — they go into a caller-packed linear-memory buffer, each in an 152 * 8-byte slot. kinds are WOP_REG / WOP_IMM only; aggregate variadic args 153 * diagnose in wasm_call. */ 154 u32 call_nvar; 155 Reg* call_var_regs; 156 i64* call_var_imms; 157 u8* call_var_kinds; 158 KitCgTypeId* call_var_types; 159 CGSwitchCase* switch_cases; 160 u32 switch_ncases; 161 /* Atomic CAS extras: third value-operand (desired) plus a second result reg 162 * (ok bool). For the other atomic ops these stay 0. */ 163 Reg dst2; 164 Reg op_c; 165 u32 imm_kind_c; 166 i64 imm_c; 167 /* WIR_ASM_BLOCK payload. Heap-allocated arrays owned by the WIR (freed at 168 * per-func teardown). raw_insns is the parsed body; the *_in_* arrays hold 169 * one entry per input operand (declaration order, including inout duplicates 170 * appended at the end); the *_out_* arrays hold one entry per output. */ 171 struct WasmInsn* raw_insns; 172 u32 raw_ninsns; 173 u32 asm_nin; 174 u32 asm_nout; 175 u8* asm_in_kinds; /* WOP_REG / WOP_IMM / WOP_LOCAL */ 176 i64* asm_in_imms; 177 Reg* asm_in_regs; 178 KitCgTypeId* asm_in_types; 179 /* For each input i: index of the matching output (numeric tieback "N" or 180 * +r inout duplicate); -1 if the input is independent. Inputs that share 181 * an output's wasm local materialize into the OUTPUT's local. */ 182 i32* asm_in_share_out; 183 Reg* asm_out_regs; 184 KitCgTypeId* asm_out_types; 185 } WIR; 186 187 typedef struct WScope { 188 u8 cg_kind; /* ScopeKind */ 189 u8 placed_in_wir; /* WIR_SCOPE_OPEN emitted */ 190 u8 break_seen; /* break_label_place arrived */ 191 u8 cont_seen; /* continue_label_place arrived */ 192 u32 id; 193 Label break_lbl; 194 Label cont_lbl; 195 KitCgTypeId result_type; 196 } WScope; 197 198 typedef enum WLabelKind { 199 WLBL_UNBOUND = 0, 200 WLBL_SCOPE_BREAK, 201 WLBL_SCOPE_CONT, 202 WLBL_FORWARD, /* placed via label_place but not tied to a scope */ 203 } WLabelKind; 204 205 typedef struct WLabel { 206 u8 kind; 207 u8 placed; 208 u8 pad[2]; 209 u32 scope_id; /* for SCOPE_BREAK/SCOPE_CONT */ 210 u32 wir_index; /* WIR_LABEL position once placed */ 211 } WLabel; 212 213 typedef struct WFunc { 214 ObjSymId sym; 215 u32 wasm_func_idx; /* index into WasmModule.funcs */ 216 KitCgTypeId fn_type; 217 u8 has_export_name; 218 u8 pad[3]; 219 } WFunc; 220 221 typedef enum WSlotKind { 222 W_SLOT_LOCAL = 0, 223 W_SLOT_STACK = 1, 224 } WSlotKind; 225 226 /* For each ABI_ARG_INDIRECT (byval) param the callee receives an i32 pointer 227 * in a wasm function param and must copy the pointed-to aggregate into a 228 * caller-isolated buffer in the linear-memory stack frame. We queue these at 229 * wasm_param time and emit the byte copies in the prologue, after frame setup 230 * and before the user body. */ 231 typedef struct WByvalCopy { 232 u32 ptr_wasm_local; /* wasm-local holding the source pointer (param slot) */ 233 u32 dst_slot_id; /* index into WTarget.slots; slot is W_SLOT_STACK */ 234 } WByvalCopy; 235 236 typedef struct WSlot { 237 u8 kind; 238 u8 pad[3]; 239 KitCgTypeId type; 240 u32 wasm_local; 241 u32 size; 242 u32 align; 243 u32 frame_offset; 244 } WSlot; 245 246 /* Deferred symbol-address fixup. emit_addr_operand for OPK_GLOBAL pushes 247 * `i32.const 0` placeholder and queues a WSymFixup. wasm_materialize_data 248 * computes the compact section base layout and rewrites in.imm with the 249 * absolute linear-memory address (section_base[sym->section_id] + sym->value 250 * + addend). Static-data relocations are applied directly to the linear 251 * memory image via the same section_base table. */ 252 typedef struct WSymFixup { 253 u32 wasm_func_idx; 254 u32 insn_idx; 255 ObjSymId sym; 256 i64 addend; 257 } WSymFixup; 258 259 /* Function-pointer fixup. emit_addr_operand for an OPK_GLOBAL pointing at a 260 * function symbol pushes `i32.const 0` and queues a WFuncTableFixup. At 261 * finalize, wasm_materialize_functable assigns each address-taken function a 262 * sequential index (starting at 1; slot 0 is reserved as null) into the 263 * single funcref table and rewrites in.imm with the assigned table index. */ 264 typedef struct WFuncTableFixup { 265 u32 wasm_func_idx; 266 u32 insn_idx; 267 ObjSymId sym; 268 } WFuncTableFixup; 269 270 typedef struct WTarget { 271 CgTarget base; 272 273 Compiler* c; 274 ObjBuilder* obj; 275 struct WasmModule* module; 276 277 /* TU-wide: ObjSymId -> (wasm_func_idx + 1); 0 means "not yet a wasm func". 278 * Lazily grown as ObjSymIds appear via call() or func_begin(). */ 279 u32* sym_to_func; 280 u32 sym_to_func_cap; 281 WFunc* funcs; 282 u32 nfuncs; 283 u32 funcs_cap; 284 285 /* Per-function state. Reset on func_begin. */ 286 const CGFuncDesc* cur_fn_desc; 287 /* Most recent SrcLoc the frontend told us about via wasm_set_loc. Used by 288 * cur_loc so diagnostics attribute to the actual failing statement 289 * rather than the function-definition location. Zeroed at func_begin; 290 * a zero line falls back to cur_fn_desc->loc. */ 291 SrcLoc cur_stmt_loc; 292 u32 cur_func_idx; 293 struct WasmFunc* cur_func; 294 295 /* SSA Reg -> Wasm local index (0..nparams=params, then locals). 0xffffffffu 296 * means "not assigned yet". reg_cap grows monotonically across the TU; 297 * reg_hwm tracks one past the highest Reg actually bound in any function so 298 * far, so func_begin only needs to wipe [0, reg_hwm) rather than reg_cap. */ 299 u32* reg_to_local; 300 KitCgTypeId* reg_type; 301 u8* reg_cls; 302 u32 reg_cap; 303 u32 reg_hwm; 304 305 /* WIR record list for the current function. */ 306 WIR* wir; 307 u32 nwir; 308 u32 wir_cap; 309 310 /* Labels minted by label_new. */ 311 WLabel* labels; 312 u32 nlabels; 313 u32 labels_cap; 314 315 /* CG scope stack. */ 316 WScope scopes[32]; 317 u32 nscopes; 318 u32 next_scope_id; 319 320 /* Per-function frame slots. Scalar slots stay as Wasm locals; addressable 321 * slots are assigned offsets in a downward-growing linear-memory frame. */ 322 WSlot* slots; 323 u32 nslots; 324 u32 slots_cap; 325 u32 frame_size; 326 u32 frame_align; 327 u32 frame_base_local; 328 u32 frame_saved_sp_local; 329 u8 has_stack_frame; 330 u8 has_memory; 331 u8 has_stack_pointer; 332 u8 cur_has_sret; 333 u32 stack_pointer_global; 334 u32 stack_size; 335 u32 data_end; 336 337 /* Compact section -> linear memory base. Populated lazily in 338 * wasm_materialize_data; 0xFFFFFFFFu means "not assigned yet". The 339 * compact layout reserves a small null guard at low memory and walks 340 * SF_ALLOC sections in id order, giving each section an aligned base. */ 341 u32* section_base; 342 u32 section_base_cap; 343 /* Common-symbol (SK_COMMON) -> linear memory base, indexed by ObjSymId. 344 * Common symbols have section_id == OBJ_SEC_NONE in ObjBuilder; for a 345 * single-TU final-module emit we lay them out BSS-style after sections. 346 * 0xFFFFFFFFu = no common base assigned. */ 347 u32* common_base; 348 u32 common_base_cap; 349 350 /* Deferred symbol-address fixups; see WSymFixup. */ 351 WSymFixup* sym_fixups; 352 u32 sym_fixups_count; 353 u32 sym_fixups_cap; 354 355 /* Function-pointer table state. ObjSymIds of every function whose address 356 * has been taken, in insertion order. The implied table index for entry 357 * func_table[i] is (i + 1); slot 0 stays unpopulated as a null/trap guard. 358 * Filled lazily by emit_addr_operand and patched into placeholder 359 * `i32.const 0` insns at finalize. */ 360 ObjSymId* func_table; 361 u32 func_table_count; 362 u32 func_table_cap; 363 WFuncTableFixup* func_table_fixups; 364 u32 func_table_fixups_count; 365 u32 func_table_fixups_cap; 366 /* Patched into the linear-memory image at apply_data_relocs time for 367 * R_ABS32 relocations whose target symbol is a function (so a static 368 * `static fn_t v = &foo;` initializer ends up holding the table index). */ 369 u8 has_func_table; 370 371 /* Per-function aggregate-lowering state. Populated by func_begin and used by 372 * wasm_param/ret/call to translate sret + byval through the wasm32 BasicCABI 373 * shape (sret pointer as hidden i32 leading param, byval args as i32 374 * pointers, callee copy-in into a stack-backed local buffer). */ 375 u32 sret_param_local; /* wasm-local idx of the sret pointer; 0xffffffffu when 376 none */ 377 u32 va_ptr_param_local; /* wasm-local idx of the hidden i32 va_ptr trailing 378 * param on variadic functions; 0xffffffffu when none. 379 * Read by wasm_va_start to seed the va_list. */ 380 u32* param_local_idx; /* per CG-param idx -> wasm-local idx (0xffffffffu for 381 IGNORE) */ 382 u32 param_local_idx_cap; 383 u32 nparams_cg; /* count of entries in param_local_idx */ 384 u8 cur_is_variadic; /* current function declared variadic (abi->variadic) */ 385 /* Per-function scratch locals for variadic-call packing. Lazily allocated 386 * on the first variadic call site; reused across all variadic calls in the 387 * same function (each call's save/restore window is self-contained, so a 388 * single pair of locals is safe). 0xffffffffu when not yet allocated. */ 389 u32 varcall_saved_sp_local; 390 u32 varcall_buf_local; 391 /* Scratch i32 local used by va_arg to remember the va_list address across 392 * the load-current-slot / advance-pointer sequence. Lazily allocated on 393 * first va_arg in the function. 0xffffffffu when not allocated. */ 394 u32 va_arg_tmp_addr_local; 395 WByvalCopy* byval_copies; 396 u32 nbyval_copies; 397 u32 byval_copies_cap; 398 399 /* Body has been terminated unconditionally — skip further insns until the 400 * next label_place. Lets us emit dead code that the parser may produce 401 * without breaking wasm validation. */ 402 u8 dead; 403 } WTarget; 404 405 CgTarget* wasm_cgtarget_new(Compiler* c, ObjBuilder* o, MCEmitter* mc); 406 WTarget* wasm_emit_target_new(Compiler* c, ObjBuilder* o, MCEmitter* mc); 407 void wasm_emit_ir_module(WTarget* t, const CgIrModule* module); 408 void wasm_finalize(CGTarget*); 409 void wasm_destroy(CGTarget*); 410 411 /* CFG structurer (src/arch/wasm/structure.c). Rewrites the recorded WIR 412 * list so every free WIR_LABEL becomes the break/continue of a synthetic 413 * SCOPE_BLOCK / SCOPE_LOOP. Called from emit.c's linearize() before the 414 * WIR walk; after this returns, br_to_label resolves every jump through 415 * the existing scope-bound machinery. Switch islands are pre-reordered 416 * (selector + WIR_SWITCH + case bodies) so their case labels become 417 * forward refs the same synthetic-scope structuring handles. */ 418 void wasm_structurize(WTarget* t); 419 420 #endif