cgir.h (18729B)
1 #ifndef KIT_CG_CGIR_H 2 #define KIT_CG_CGIR_H 3 4 /* The CG codegen vocabulary: the operand model, the op enums (BinOp/UnOp/CmpOp/ 5 * ConvKind/IntrinKind), memory-access descriptors, and the call/func/switch/ 6 * scope descriptors. These are the data types the CgTarget vtable (cgtarget.h) 7 * and the recorder IR (cg/ir.h) are both built from. Split out of cgtarget.h so 8 * a consumer that only speaks operands (the native backends, the recorder IR, 9 * the interpreter) need not depend on the backend contract. */ 10 11 #include <kit/cg.h> 12 13 #include "core/core.h" 14 #include "obj/obj.h" 15 16 typedef u32 CGLocal; 17 #define CG_LOCAL_NONE 0u 18 19 /* Vector / SIMD forward compat: vector ops will arrive as new variants in 20 * the BinOp, UnOp, CmpOp, ConvKind families. Backend switches over these 21 * enums must use `default:` (unreachable / panic) rather than exhaustive 22 * case lists, so adding a new variant later does not silently mis-handle on 23 * backends that haven't been taught about it. Vector loads/stores reuse the 24 * existing load/store methods with vector-typed Operands and appropriate 25 * MemAccess. */ 26 27 /* Integer/float binary ops. Edge-case semantics are fully defined (no undefined 28 * behavior) in doc/IR.md: iadd/isub/imul (and UO_NEG) wrap modulo 2^width; 29 * sdiv/udiv/srem/urem and the shifts have a portable default plus an opt-in 30 * target-defined mode selected per instruction via CgIrInstFlag (src/cg/ir.h). 31 * FP ops are strict IEEE-754 in the target's default rounding/exception 32 * environment; there is no FP remainder op (the frontend calls fmod). */ 33 typedef enum BinOp { 34 BO_IADD, 35 BO_ISUB, 36 BO_IMUL, 37 BO_SDIV, 38 BO_UDIV, 39 BO_SREM, 40 BO_UREM, 41 BO_FADD, 42 BO_FSUB, 43 BO_FMUL, 44 BO_FDIV, 45 BO_AND, 46 BO_OR, 47 BO_XOR, 48 BO_SHL, 49 BO_SHR_S, 50 BO_SHR_U, 51 } BinOp; 52 53 typedef enum UnOp { 54 UO_NEG, 55 UO_FNEG, 56 UO_NOT, /* logical: 0/1 */ 57 UO_BNOT, /* bitwise ~ */ 58 } UnOp; 59 60 /* Compares producing i1. The 10 integer members (CMP_EQ..CMP_GE_U) are total 61 * and 1:1 with KitCgIntCmpOp; on integers CMP_EQ/CMP_NE are plain equality. 62 * 63 * The 12 floating-point members form a disjoint block laid out *after* the 64 * integer block, in the same order as the public KitCgFpCmpOp, and are 65 * IEEE-complete: each predicate encodes ordered (NaN -> false) vs unordered 66 * (NaN -> true) explicitly, so the distinction reaches every backend. The 67 * identity used throughout the backends is unordered-R == NOT(ordered-not-R) 68 * (e.g. ULT == !(OGE), UNE == !(OEQ)). CMP_OEQ_F is the FP boundary: an op is a 69 * floating compare iff op >= CMP_OEQ_F. */ 70 typedef enum CmpOp { 71 CMP_EQ, 72 CMP_NE, 73 CMP_LT_S, 74 CMP_LE_S, 75 CMP_GT_S, 76 CMP_GE_S, 77 CMP_LT_U, 78 CMP_LE_U, 79 CMP_GT_U, 80 CMP_GE_U, 81 /* Ordered FP relationals (NaN -> false). */ 82 CMP_OEQ_F, 83 CMP_ONE_F, 84 CMP_OLT_F, 85 CMP_OLE_F, 86 CMP_OGT_F, 87 CMP_OGE_F, 88 /* Unordered FP relationals (NaN -> true). */ 89 CMP_UEQ_F, 90 CMP_UNE_F, 91 CMP_ULT_F, 92 CMP_ULE_F, 93 CMP_UGT_F, 94 CMP_UGE_F, 95 } CmpOp; 96 97 /* Conversions. Widths must order correctly (sext/zext widen, trunc narrows, 98 * bitcast preserves byte size). itof, fext, and ftrunc round to nearest-even; 99 * ftoi_s/ftoi_u round toward zero with a portable saturating out-of-range 100 * default (NaN -> 0) and an opt-in target-defined mode 101 * (CG_IR_INST_TARGET_FPTOINT_EDGES in src/cg/ir.h). Full rules in doc/IR.md. */ 102 typedef enum ConvKind { 103 CV_SEXT, 104 CV_ZEXT, 105 CV_TRUNC, 106 CV_ITOF_S, 107 CV_ITOF_U, 108 CV_FTOI_S, 109 CV_FTOI_U, 110 CV_FEXT, 111 CV_FTRUNC, 112 CV_BITCAST, 113 } ConvKind; 114 115 /* Atomic op kinds (KitCgAtomicOp) and memory orders (KitCgMemOrder) come 116 * straight from the public API. Which orders are legal depends on the atomic 117 * op: load excludes release/acq_rel; store excludes acquire/consume/acq_rel; 118 * CAS failure order is one of relaxed/consume/acquire/seq_cst and no stronger 119 * than success. See the Atomics edge-case rules in doc/IR.md (mirrored by 120 * kit_cg_atomic_is_legal). */ 121 122 /* Compiler-intrinsic kinds dispatched through CgTarget.intrinsic and carried 123 * on IR_INTRINSIC via IRIntrinAux.kind. The set is bounded: a backend 124 * must know each one to choose inline-vs-libcall. Hint intrinsics 125 * (EXPECT/TRAP/PREFETCH/ASSUME_ALIGNED) ride the same dispatch: 126 * the backend decides whether they emit an instruction or a no-op. 127 * `unreachable` is NOT here: it is a first-class control terminator with 128 * its own CgTarget hook (see below), not an intrinsic. 129 * 130 * Not every C builtin lives here. Parser-evaluated builtins 131 * (__builtin_offsetof, __builtin_constant_p, __builtin_choose_expr, 132 * __builtin_types_compatible_p) fold at parse and never reach IR. Builtins 133 * that already have dedicated CgTarget methods (alloca, va_*, atomics) keep 134 * them. Returns-twice and no-return control intrinsics use this dispatch so 135 * opt can preserve their CFG effects without growing backend vtable hooks. */ 136 typedef enum IntrinKind { 137 INTRIN_NONE = 0, 138 139 /* bit ops */ 140 INTRIN_POPCOUNT, 141 INTRIN_CTZ, 142 INTRIN_CLZ, 143 INTRIN_BSWAP, 144 145 /* memory. memcpy/memset are the dedicated copy_bytes/set_bytes hooks 146 * (kit_cg_memcpy/_memset); only memmove flows through the intrinsic path. */ 147 INTRIN_MEMMOVE, 148 INTRIN_PREFETCH, 149 INTRIN_ASSUME_ALIGNED, 150 151 /* hints */ 152 INTRIN_EXPECT, 153 INTRIN_TRAP, 154 155 /* OS trap: args[0] is the syscall number, args[1..6] are integer/pointer 156 * payloads; dsts[0] receives the target long result. */ 157 INTRIN_SYSCALL, 158 159 /* non-local control */ 160 INTRIN_SETJMP, 161 INTRIN_LONGJMP, 162 163 /* checked arith — multi-result (value, overflow_flag) */ 164 INTRIN_SADD_OVERFLOW, 165 INTRIN_UADD_OVERFLOW, 166 INTRIN_SSUB_OVERFLOW, 167 INTRIN_USUB_OVERFLOW, 168 INTRIN_SMUL_OVERFLOW, 169 INTRIN_UMUL_OVERFLOW, 170 171 /* baremetal CPU control — single-instruction, no operands unless noted. 172 * dsts/args empty except IRQ_SAVE (dsts[0] = saved interrupt state) and 173 * IRQ_RESTORE (args[0] = state to restore). Privileged forms (WFI/WFE/SEV 174 * and the IRQ family) trap at user level; backends still emit the one 175 * instruction and frontends gate any runtime use behind a capability test. */ 176 INTRIN_CPU_NOP, 177 INTRIN_CPU_YIELD, 178 INTRIN_WFI, 179 INTRIN_WFE, 180 INTRIN_SEV, 181 INTRIN_ISB, 182 INTRIN_DMB, 183 INTRIN_DSB, 184 INTRIN_IRQ_SAVE, 185 INTRIN_IRQ_RESTORE, 186 INTRIN_IRQ_ENABLE, 187 INTRIN_IRQ_DISABLE, 188 189 /* frame-pointer-chain introspection — value-producing, single immediate 190 * operand (the constant level). args[0] is the level (OPK_IMM); dsts[0] is 191 * the void* result. Lowered as an unrolled FP walk; modeled as an ordinary 192 * frame-dependent memory read (IR_INTRINSIC is already conservatively 193 * side-effecting in opt, so it is never hoisted, CSE'd, or eliminated). */ 194 INTRIN_FRAME_ADDRESS, 195 INTRIN_RETURN_ADDRESS, 196 197 /* Read the target cycle/timestamp counter. No operands; dsts[0] receives the 198 * unsigned 64-bit result. Modeled like a frame-dependent read (IR_INTRINSIC 199 * is conservatively side-effecting, so it is never hoisted/CSE'd/removed). */ 200 INTRIN_READCYCLECOUNTER, 201 202 /* High half of a full-width two-operand product. */ 203 INTRIN_SMUL_HIGH, 204 INTRIN_UMUL_HIGH, 205 206 /* Internal stack-protector guard read. No operands; dsts[0] receives one 207 * pointer-width word. This is emitted only for target ABIs whose guard has 208 * no linkable object symbol (Linux/Android x86-64 use fs:0x28). */ 209 INTRIN_STACK_GUARD, 210 } IntrinKind; 211 212 typedef enum OpKind { 213 OPK_IMM, 214 OPK_LOCAL, /* typed semantic local */ 215 OPK_GLOBAL, /* address: symbol+addend, not a load */ 216 OPK_INDIRECT, /* [local + ofs], with optional indexed local */ 217 } OpKind; 218 219 /* Per-operand hint flags carried in Operand.flags. Additive: set by the cg layer 220 * at a specific op's call site on a transient copy of the operand (never on a 221 * value-stack entry), read only by the -O0 NativeDirectTarget; every other 222 * backend (opt recorder, c_target, wasm, check) ignores them. */ 223 typedef enum OpFlag { 224 OPK_FLAG_NONE = 0, 225 /* This OPK_LOCAL operand names a transient that is provably dead after the op 226 * consuming it (confirmed by api_temp_dead). The -O0 backend may then rename 227 * the local's live cache register to the op's destination (transfer ownership) 228 * instead of materializing the source and emitting a routing mov / in-place 229 * convert into a fresh register. Set only on a copy/convert source. */ 230 OPK_FLAG_KILL = 1u << 0, 231 } OpFlag; 232 233 typedef enum CGLocalFlag { 234 CG_LOCAL_FLAG_NONE = 0, 235 CG_LOCAL_ADDR_TAKEN = 1u << 0, 236 CG_LOCAL_MEMORY_REQUIRED = 1u << 1, 237 /* A transient compiler temporary minted per-subexpression (api_alloc_temp_local 238 * is its sole producer). Its frame home is recyclable: at a statement boundary 239 * the value stack holds no live reference to it, so the single-pass backend 240 * returns its slot to a free list and reuses it for the next statement's temps 241 * (kit_cg_reclaim_temps). Declared locals/params never carry this flag and keep 242 * their home for the whole function. */ 243 CG_LOCAL_TRANSIENT = 1u << 2, 244 } CGLocalFlag; 245 246 typedef struct CGLocalDesc { 247 KitCgTypeId type; 248 Sym name; 249 SrcLoc loc; 250 u32 size; 251 u32 align; 252 u32 flags; /* CGLocalFlag */ 253 } CGLocalDesc; 254 255 typedef enum MemFlag { 256 MF_NONE = 0, 257 MF_VOLATILE = 1u << 0, 258 MF_ATOMIC = 1u << 1, 259 MF_RESTRICT = 1u << 2, 260 MF_READONLY = 1u << 3, 261 MF_WRITEONLY = 1u << 4, 262 MF_UNALIGNED = 1u << 5, 263 /* Sign-extending narrow load (the C frontend's widening-signed-load lever): 264 * the value comes from a signed char/short/int lvalue whose only widening is a 265 * sign-extension to a wider type, so a backend that honors this flag emits a 266 * single sign-extending load (aa64 ldrsb/ldrsh into the X register, filling 267 * the whole register) instead of a zero-extending load plus a CV_SEXT convert. 268 * Non-bitfield integer loads only. A backend that does not honor it ignores 269 * the bit and emits a plain load; the shared -O0 NDT only elides the following 270 * CV_SEXT when the backend advertises NativeRegInfo.load_sext, so leaving 271 * the convert in place stays correct there. */ 272 MF_SEXT_LOAD = 1u << 6, 273 } MemFlag; 274 275 typedef enum AliasKind { 276 ALIAS_UNKNOWN, 277 ALIAS_LOCAL, 278 ALIAS_GLOBAL, 279 ALIAS_PARAM, 280 ALIAS_HEAP, 281 ALIAS_STRING, 282 } AliasKind; 283 284 typedef struct AliasRoot { 285 u8 kind; /* AliasKind */ 286 u8 pad[3]; 287 union { 288 i32 local_id; 289 ObjSymId global; 290 u32 param_idx; 291 Sym string_id; 292 } v; 293 } AliasRoot; 294 295 typedef struct MemAccess { 296 KitCgTypeId type; /* codegen object type accessed */ 297 u32 size; /* ABI byte size of this access (storage-unit size for a 298 * bit-field) */ 299 u32 align; /* known byte alignment; 0 means unknown */ 300 u16 flags; /* MemFlag */ 301 u16 addr_space; 302 /* Bit-field rider: when bf_width != 0 this access is a bit-field, so `load` 303 * extracts (shift+mask+extend) and `store` inserts (read-modify-write) within 304 * the storage unit described by {type,size}. The CgTarget impls translate 305 * this to the physical NativeTarget bitfield_load/store (or the recorder IR 306 * op); the semantic CgTarget no longer carries a separate bit-field method. 307 */ 308 u16 bf_offset; /* target-endian bit offset within the storage unit */ 309 u16 bf_width; /* 0 => not a bit-field access */ 310 u8 bf_signed; /* signed extraction on load */ 311 u8 bf_pad[3]; 312 AliasRoot alias; 313 } MemAccess; 314 315 typedef struct ConstBytes { 316 KitCgTypeId type; 317 const u8* bytes; /* ABI representation, little/big endian per target */ 318 u32 size; 319 u32 align; 320 } ConstBytes; 321 322 typedef struct AggregateAccess { 323 KitCgTypeId type; 324 u32 size; 325 u32 align; 326 MemAccess mem; 327 } AggregateAccess; 328 329 typedef struct BitFieldAccess { 330 KitCgTypeId field_type; 331 MemAccess storage; 332 u32 storage_offset; /* byte offset from record base */ 333 u16 bit_offset; /* target-endian bit offset within storage unit */ 334 u16 bit_width; /* may be 0 for zero-width layout barriers */ 335 u8 signed_; 336 u8 pad[3]; 337 } BitFieldAccess; 338 339 /* Reconstruct the BitFieldAccess a CgTarget impl needs from the bit-field 340 * MemAccess that rides the generic load/store (bf_width != 0). The storage unit 341 * is {m.type, m.size}; the bit geometry is the bf_* rider. */ 342 static inline BitFieldAccess bf_from_mem(MemAccess m) { 343 BitFieldAccess bf = {0}; 344 bf.field_type = m.type; 345 bf.storage = m; 346 bf.storage.bf_offset = 0; 347 bf.storage.bf_width = 0; 348 bf.storage.bf_signed = 0; 349 bf.bit_offset = m.bf_offset; 350 bf.bit_width = m.bf_width; 351 bf.signed_ = m.bf_signed; 352 return bf; 353 } 354 355 typedef struct Operand { 356 u8 kind; 357 u8 flags; /* OpFlag bitset; 0 except where a cg op sets a per-call-site hint */ 358 u8 pad[2]; 359 KitCgTypeId type; 360 union { 361 i64 imm; 362 CGLocal local; 363 struct { 364 ObjSymId sym; 365 i64 addend; 366 } global; 367 struct { 368 CGLocal base; 369 CGLocal index; /* CG_LOCAL_NONE when no index operand */ 370 u8 log2_scale; /* 0..3 -> 1/2/4/8 bytes; ignored when no index */ 371 i32 ofs; 372 } ind; 373 } v; 374 } Operand; 375 376 typedef struct CGParamDesc { 377 u32 index; 378 Sym name; 379 KitCgTypeId type; 380 u32 size; 381 u32 align; 382 u32 flags; /* CGLocalFlag */ 383 SrcLoc loc; 384 } CGParamDesc; 385 386 /* text_section_id and group_id are per-function so that -ffunction-sections, 387 * __attribute__((section)) on functions, and COMDAT for C11 inline-with- 388 * external-definition all work with no extra plumbing. Decl.section_id already 389 * carries the user's request; CG/decl decides the section name policy 390 * (default .text, vs .text.<sym> under -ffunction-sections, vs explicit 391 * attribute). The backend just writes to the named section. */ 392 /* Phase 2 attribute-derived hints. The backends are free to ignore these; 393 * they exist so the parser can communicate _Noreturn / __attribute__ 394 * info down to CG without forcing every backend to consult the Decl. */ 395 typedef enum CGFuncDescFlag { 396 CGFD_NONE = 0, 397 CGFD_NORETURN = 1u << 0, 398 } CGFuncDescFlag; 399 400 typedef struct CGFuncDesc { 401 ObjSymId sym; 402 ObjSecId text_section_id; 403 ObjGroupId group_id; /* OBJ_GROUP_NONE if none */ 404 KitCgTypeId fn_type; 405 KitCgTypeId result_type; /* KIT_CG_TYPE_NONE/void == no result */ 406 const CGParamDesc* params; 407 u32 nparams; 408 SrcLoc loc; 409 u32 flags; /* CGFuncDescFlag */ 410 KitCgInlinePolicy inline_policy; 411 u16 sym_bind; /* SymBind */ 412 u16 sym_kind; /* SymKind */ 413 u8 sym_vis; /* SymVis */ 414 u8 atomize; 415 u8 pad[2]; 416 } CGFuncDesc; 417 418 typedef enum CGCallFlag { 419 CG_CALL_NONE = 0, 420 /* Sibling call. The target emits a tail-position call and does NOT emit a 421 * return-style continuation. CG will not invoke target->ret afterwards. 422 * 423 * Realizability is verified before this flag is set: CG only sets it after 424 * tail_call_unrealizable_reason() returns NULL for the same desc and call 425 * state, so the target can emit the sibling call unconditionally. The 426 * target may assert/compiler_panic if the flag is set on an unrealizable 427 * desc, but that is an internal-consistency check — fallback and 428 * diagnostics for unrealizable tail calls are CG's responsibility, not the 429 * target's. */ 430 CG_CALL_TAIL = 1u << 0, 431 } CGCallFlag; 432 433 typedef struct CGCallDesc { 434 KitCgTypeId fn_type; 435 Operand callee; 436 const CGLocal* args; 437 CGLocal result; /* CG_LOCAL_NONE == void callee (no result) */ 438 u32 nargs; 439 u16 flags; /* CGCallFlag */ 440 u8 tail_policy; /* KitCgTailPolicy; meaningful when CG_CALL_TAIL is set. 441 * The opt recorder accepts every tail and preserves this so 442 * the replay can pick: emit tail (realizable), fall back to 443 * call+ret (ALLOWED), or diagnose (MUST). */ 444 u8 pad; 445 KitCgInlinePolicy inline_policy; 446 /* Bit i set => args[i] is a transient that is provably dead after this call 447 * (no live value-stack reference). The -O0 NativeDirectTarget may then source 448 * such an arg from its live register and drop it without writing back, 449 * instead of the spill-to-home + reload round-trip; a live (clear-bit) arg is 450 * spilled like any other live-across value. Bits >= 64 are 0 (treated as 451 * live: correct, just unoptimized). Other backends ignore it. */ 452 u64 arg_dead_mask; 453 } CGCallDesc; 454 455 typedef u32 Label; 456 #define LABEL_NONE 0 457 458 typedef enum ScopeKind { 459 SCOPE_BLOCK, /* break exits forward */ 460 SCOPE_LOOP, /* break exits forward; continue uses explicit target */ 461 } ScopeKind; 462 463 typedef u32 CGScope; 464 #define CG_SCOPE_NONE 0u 465 466 typedef struct CGScopeDesc { 467 u8 kind; /* ScopeKind */ 468 u8 pad[3]; 469 Label break_label; /* explicit target for break; LABEL_NONE => target creates 470 one */ 471 Label continue_label; /* explicit target for continue; LABEL_NONE for 472 non-loops */ 473 KitCgTypeId result_type; /* reserved for structured expression results */ 474 } CGScopeDesc; 475 476 typedef struct AsmConstraint { 477 const char* str; /* GCC-style: "r", "=&r", "+m", "i", "0" ... */ 478 Sym name; /* GCC `[name]` symbolic operand; 0 if absent */ 479 KitCgTypeId type; /* codegen type of the bound expression (output lvalue or 480 input rvalue). Drives type width for the binder. 481 NULL only for hand-built test constraints (binder 482 falls back to a 64-bit int default). */ 483 Sym reg; /* Explicit hard-register name ("r10"/"x8"/...) this operand 484 must occupy — a GNU local register variable bound as an 485 operand; 0 = unconstrained. Only the target's register 486 file resolves the name to a physical register. */ 487 u8 dir; /* KitCgAsmDir */ 488 u8 pad[3]; 489 } AsmConstraint; 490 491 typedef struct CGSwitchCase { 492 /* Bit pattern matched against the selector; interpreted using 493 * selector_type's width and signedness (signed comparison uses 494 * sign-extension to selector_type's width). */ 495 u64 value; 496 Label label; 497 } CGSwitchCase; 498 499 typedef struct CGSwitchDesc { 500 Operand selector; /* OPK_LOCAL or OPK_IMM */ 501 KitCgTypeId selector_type; 502 Label default_label; /* LABEL_NONE means "fall through past the switch" */ 503 const CGSwitchCase* cases; 504 u32 ncases; 505 u8 hint; /* KitCgSwitchHint */ 506 u8 opt_level; /* 0 direct, 1 optimized; public 2 is normalized to 1 */ 507 u8 pad[2]; 508 } CGSwitchDesc; 509 510 typedef struct CGLocalStaticDataDesc { 511 ObjSymId sym; 512 KitCgTypeId type; 513 KitCgDataDefAttrs attrs; 514 u32 align; 515 } CGLocalStaticDataDesc; 516 517 typedef enum CGDebugLocKind { 518 CG_DEBUG_LOC_NONE, 519 CG_DEBUG_LOC_FRAME, 520 CG_DEBUG_LOC_REG, 521 CG_DEBUG_LOC_GLOBAL, 522 } CGDebugLocKind; 523 524 typedef struct CGDebugLoc { 525 u8 kind; /* CGDebugLocKind */ 526 u8 pad[3]; 527 union { 528 /* Offset in the same target-defined frame-base coordinate system that the 529 * target/debugger pair uses to materialize frame-relative variables. CG 530 * treats this as opaque target data and only maps it into the debug 531 * producer's generic frame-location form. */ 532 i32 frame_ofs; 533 u32 reg; 534 ObjSymId global; 535 } v; 536 } CGDebugLoc; 537 #endif