kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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