/* ---- begin common.h ---- */ // zig1 -- a C port of the Zig 0.17.1 frontend plus an AIR interpreter, for // bootstrapping Zig from a C compiler alone. // // zig1.c is `LC_ALL=C cat stage1/*.h stage1/*.c`, so this header comes first. // Build: `cc -O3 zig1.c -o zig1 -lm` (or `cc -O3 stage1/*.c -o zig1 -lm`) // with gcc, clang or tcc on a POSIX host, or on Windows with MSVC or mingw // (AGENTS.txt "Platforms"). Run: // `zig1 ` is upstream's zig1.wasm command line: zig1 // lowers the compiler's own src/main.zig with wasm32-wasi semantics and // interprets it, so the stock compiler's frontend and C backend write zig2.c // and compiler_rt.c, byte-identical to upstream's. // // Exit status: the compiler's own; 1 for a usage error or an unusable lib // dir, as upstream's; 126 when zig1 refuses to compile the compiler (a parse, // AstGen or Sema compile error); 2 for a driver failure, out of memory // included; SIGABRT for an abort below. // // Files and the upstream code they port (each file's head maps it further): // air.c src/Air.zig // arith.c src/Sema/arith.zig // ast.c lib/std/zig/Ast.zig // ast_rl_annotate.c lib/std/zig/AstRlAnnotate.zig // astgen.c lib/std/zig/AstGen.zig // big_int.c lib/std/math/big/int.zig // builtin.c src/Builtin.zig: the `builtin` module source // builtin_fn.c lib/std/zig/BuiltinFn.zig // common.c zig1's own: the abort path and the error source // compilation.c src/Compilation.zig, lib/std/start.zig: a root module // from source to its interpreted main // comptime_ptr_access.c src/Sema/comptime_ptr_access.zig // intern_pool.c src/InternPool.zig // interp.c zig1's own: executes AIR as a wasm32-wasi machine // lower_zon.c src/Sema/LowerZon.zig // main.c the command line, upstream's stage1/wasi.c main // mutable_value.c src/mutable_value.zig // number_literal.c lib/std/zig/number_literal.zig // parse_float.c lib/std/fmt/parse_float.zig, at f128 // parser.c lib/std/zig/Parse.zig // sema.c src/Sema.zig, src/Sema/reinterpret.zig: ZIR to AIR, // analyzeBodyInner // sema_comptime.c zig1's own: the comptime evaluator ctEvalBody and // the declaration-level Sema it drives // softfloat.c lib/compiler_rt/: f16/f80/f128 soft float and musl's // f32/f64 functions, for comptime math // string_literal.c lib/std/zig/string_literal.zig // tokenizer.c lib/std/zig/tokenizer.zig // type.c src/Type.zig // type_resolution.c src/Sema/type_resolution.zig // value.c src/Value.zig // wasi.c upstream's stage1/wasi.c: the WASI host // wyhash.c lib/std/hash/wyhash.zig // zcu.c src/Zcu.zig // zcu_per_thread.c src/Zcu/PerThread.zig: files, imports, Navs, // namespaces and function bodies // zir.c lib/std/zig/Zir.zig // zon_gen.c lib/std/zig/ZonGen.zig, lib/std/zig/Zoir.zig // // Aborts (the macros below): UNIMPLEMENTED is a limitation of zig1: valid Zig // it does not implement, and its message leads with the upstream function or // feature to port. UNREACHABLE is a broken zig1 invariant or an upstream // `unreachable`, a bug in zig1. An error in the program that zig1 detects is // not an abort but a compile error: COMPILE_ERROR_UPSTREAM in Sema, // failNode/failTok in AstGen. zig1 may accept a program upstream rejects, or // abort on it. No abort fires while generating zig2.c or compiler_rt.c. Each // abort names the declaration being analyzed. // // State: one thread, not reentrant. Each phase keeps its state in its own // structs (Ast, Zir, InternPool, Zcu); the only file-scope state is the // interpreter's instance g_interp and the abort path's zig1_error_source. The // interpreter keeps the guest's calls on heap stacks (interpExecute), so the // process's default stack holds the native recursion. // // Values: Sema names every comptime value by its InternPool index (zig1.h, // "Value encodings"). The // interpreter's Value is {bits, InternPool type}: the scalar itself up to 64 // bits, else the address of its bytes in the guest's wasm32 memory (zig1.h, // "Runtime memory"). #ifndef ZIG1_COMMON_H #define ZIG1_COMMON_H // Before any system header: POSIX.1-2008, so that every -std mode sees the // same libc declarations, among them the ones the WASI host calls (wasi.c: // pread, ftruncate, readv). #define _POSIX_C_SOURCE 200809L // No floating-point contraction: every operation rounds to its own type, as // Zig's do. This is the first thing in zig1.c and in every translation unit, // so it covers all of them. clang honours the C99 pragma; gcc ignores it and // defaults to -ffp-contract=fast in GNU modes, so it takes its own; MSVC's // spelling is with ZIG1_ATTRIBUTE below; tcc never contracts. #if defined(__clang__) #pragma STDC FP_CONTRACT OFF #elif defined(__GNUC__) #pragma GCC optimize("fp-contract=off") #endif #include #include #include #include #include #include // Win32 and the CRT (wasi.c's rename and process id), once for every file; // AGENTS.txt §4c lists the macros that meet zig1 identifiers. #ifdef _WIN32 #define NOMINMAX #define WIN32_LEAN_AND_MEAN #include #include #undef NOMINMAX #undef WIN32_LEAN_AND_MEAN #endif // GNU attributes: gcc, clang and tcc accept them, and MSVC, which does not, // builds without them. zig1's file layer (wasi.c's host calls) is POSIX, or // the Windows CRT and Win32 on Windows. #if defined(_MSC_VER) && !defined(__clang__) #define ZIG1_ATTRIBUTE(x) #pragma fp_contract(off) #else #define ZIG1_ATTRIBUTE(x) __attribute__(x) #endif // Exit status of a program zig1 refuses to compile: a parse error, an AstGen // or Sema compile error, or no entry point (main.c has the full contract). #define ZIG1_EXIT_COMPILE_ERROR 126 // The abort path: prints `kind: msg (file:line)`, the declaration being // analyzed (zig1_error_source) and what the kind means, then aborts. Declared // noreturn so a function ending in one does not fall off its end; cold and out // of line so an abort costs its caller one call. ZIG1_ATTRIBUTE((noreturn, cold, noinline)) void zig1AbortLoudly( const char* kind, const char* msg, const char* file, int line); #define ZIG1_ABORT_LOUDLY(kind, msg) \ zig1AbortLoudly(kind, (msg), __FILE__, __LINE__) // ZIG1_TCC_RETURN: the host tcc 0.9.27 ignores noreturn in its return-path // check, so a function ending in an abort ends in this return of a value // that is never used; no other compiler sees it. #ifdef __TINYC__ #define ZIG1_TCC_RETURN(value) return (value) #else #define ZIG1_TCC_RETURN(value) #endif // Where a compile error is reported: the file being parsed, lowered or // analyzed (`path`, below module root `dir` or NULL) and a 1-based line and // column, 0 when only the file is known. A NULL path reports no location. typedef struct { const char* dir; const char* path; uint32_t line; uint32_t column; } Zig1ErrorSource; extern Zig1ErrorSource zig1_error_source; // Resets zig1_error_source to no location. void zig1ErrorSourceClear(void); // Sets zig1_error_source's line and column to byte `offset` of `source`. void zig1ErrorSourceAt(const char* source, uint32_t offset); // Reports `path:line:column: error: msg` at zig1_error_source and exits // ZIG1_EXIT_COMPILE_ERROR. ZIG1_ATTRIBUTE((noreturn, cold, noinline)) void zig1CompileError(const char* msg); // zig1CompileError of a printf-formatted message. ZIG1_ATTRIBUTE((noreturn, cold, noinline, format(printf, 1, 2))) void zig1CompileErrorFmt(const char* fmt, ...); // The formatted message for zig1CompileError, in a buffer the next call // reuses; the caller ends `args`. ZIG1_ATTRIBUTE((cold, format(printf, 1, 0))) const char* zig1CompileErrorMessageV(const char* fmt, va_list args); // Prints "zig1: " and exits 2, a driver failure. ZIG1_ATTRIBUTE((noreturn, cold, noinline, format(printf, 1, 2))) void zig1Fatal(const char* fmt, ...); // Prints "zig1: out of memory" and exits 2, a driver failure. ZIG1_ATTRIBUTE((noreturn, cold, noinline)) void zig1Oom(void); // malloc, calloc and realloc that exit through zig1Oom on failure. static inline void* zig1Malloc(size_t size) { void* p = malloc(size); if (!p) zig1Oom(); return p; } static inline void* zig1Calloc(size_t count, size_t size) { void* p = calloc(count, size); if (!p) zig1Oom(); return p; } static inline void* zig1Realloc(void* old, size_t size) { void* p = realloc(old, size); if (!p) zig1Oom(); return p; } // The capacity a full growable array grows to: double, or `initial` when it // has none yet. static inline uint32_t zig1GrowCap(uint32_t cap, uint32_t initial) { return cap ? cap * 2u : initial; } // Growable lists of u32 and of bytes (std.ArrayList); zero-initialize // (`{0}`), free `.items`. typedef struct { uint32_t* items; uint32_t len; uint32_t cap; } U32List; typedef struct { uint8_t* items; uint32_t len; uint32_t cap; } U8List; static inline void u32ListAppend(U32List* l, uint32_t v) { if (l->len == l->cap) { l->cap = zig1GrowCap(l->cap, 8u); l->items = zig1Realloc(l->items, l->cap * sizeof(uint32_t)); } l->items[l->len++] = v; } static inline void u8ListAppend(U8List* l, uint8_t b) { if (l->len == l->cap) { l->cap = zig1GrowCap(l->cap, 64u); l->items = zig1Realloc(l->items, l->cap); } l->items[l->len++] = b; } // std.hash_map.StringIndexContext with StringIndexAdapter: a set of // offsets into a byte list, each the start of a NUL-terminated, NUL-free // string, looked up by content (AstGen's and ZonGen's string_table). typedef struct { uint32_t str_index; // UINT32_MAX: empty uint32_t hash; } Zig1StringTableSlot; typedef struct { Zig1StringTableSlot* slots; uint32_t cap; uint32_t count; } Zig1StringTable; // The offset of the string `key[0..len]` in `bytes`, or UINT32_MAX. uint32_t zig1StringTableFind(const Zig1StringTable* table, const uint8_t* bytes, const char* key, uint32_t len); // Adds `str_index`, a string `key[0..len]` the table lacks. void zig1StringTableInsert( Zig1StringTable* table, uint32_t str_index, const char* key, uint32_t len); // A heap copy of `s[0..len]` with a NUL after it. char* zig1Strndup(const char* s, size_t len); static inline char* zig1Strdup(const char* s) { return zig1Strndup(s, strlen(s)); } // `dir/sub[0..sub_len]` on the heap; just the copy of `sub` when `dir` is NULL // or empty. char* zig1PathJoin(const char* dir, const char* sub, size_t sub_len); // The part of `path` after its last '/'. static inline const char* zig1PathBasename(const char* path) { const char* slash = strrchr(path, '/'); return slash == NULL ? path : slash + 1; } // The length of `path`'s directory, the bytes before its last '/'; 0 // without one. static inline size_t zig1PathDirLen(const char* path) { const char* slash = strrchr(path, '/'); return slash == NULL ? 0 : (size_t)(slash - path); } // The length of `name[0..len]` without a trailing ".zig" (std.fs.path.stem // of a Zig source file). static inline size_t zig1ZigStemLen(const char* name, size_t len) { return len > 4 && memcmp(name + len - 4, ".zig", 4) == 0 ? len - 4 : len; } // A whole file in a heap buffer with a NUL after it, its length in // `*out_len`; NULL when it cannot be opened. A read failure after the open // exits 2. char* zig1ReadFile(const char* path, size_t* out_len); // Exact IEEE equality, Zig's float `==`: false when either side is NaN, true // for +0 against -0, as the conjunction of the two ordered comparisons. The // C99 classification macros are no substitute: libc headers spell them with // `==` or C11 `_Generic`. static inline bool f64EqExact(double a, double b) { return a <= b && a >= b; } // +0 or -0. static inline bool f64IsZero(double x) { return f64EqExact(x, 0.0); } // ZIG1_INLINE: forced inlining for the small accessors the interpreter asks // on every load, store and constant. `static inline` alone is only a hint, // and a compiler may keep such helpers out of line. #define ZIG1_INLINE ZIG1_ATTRIBUTE((always_inline)) static inline // ZIG1_NOINLINE: the out-of-line rest of such a helper, kept out of its // inline fast path (and out of a recursion through it). #define ZIG1_NOINLINE ZIG1_ATTRIBUTE((noinline)) // The abort taxonomy (the overview at the top). The aborts go // through zig1AbortLoudly, so the printed prefix tells them apart: // // UNIMPLEMENTED(msg) valid Zig zig1 does not implement: `msg` leads with // the upstream function or feature to port (or wasm32, // for a limit of the interpreter's machine). astgen.c's // ASTGEN_UNIMPLEMENTED is the same, prefixed `astgen`. // UNREACHABLE(msg) a state no input can reach: a broken zig1 invariant, // or upstream's `unreachable`. A bug in zig1. // // COMPILE_ERROR_UPSTREAM(msg) is a shape stock Zig rejects, reported as a // compile error (exit 126), never fixed by accepting the shape. `msg` usually // names the zig1 handler, not upstream's text; only helpers such as // failWithBadMemberAccess reproduce upstream's message. #define UNIMPLEMENTED(msg) ZIG1_ABORT_LOUDLY("UNIMPLEMENTED", msg) #define UNREACHABLE(msg) ZIG1_ABORT_LOUDLY("UNREACHABLE", msg) #define COMPILE_ERROR_UPSTREAM(msg) zig1CompileError(msg) #define SLICE(Type) \ struct Type##Slice { \ uint32_t len; \ uint32_t cap; \ Type* arr; \ } #define ARR_INIT(Type, initial_cap) \ ((Type*)zig1Calloc((initial_cap), sizeof(Type))) #define SLICE_INIT(Type, initial_cap) \ { .len = 0, .cap = (initial_cap), .arr = ARR_INIT(Type, initial_cap) } // Each evaluates `slice` and its count once. #define SLICE_RESIZE(Type, slice, new_cap) \ do { \ struct Type##Slice* sr_ = (slice); \ const uint32_t cap_ = (new_cap); \ sr_->arr = zig1Realloc(sr_->arr, cap_ * sizeof(Type)); \ sr_->cap = cap_; \ } while (0) #define SLICE_ENSURE_CAPACITY(Type, slice, additional) \ do { \ struct Type##Slice* se_ = (slice); \ const uint32_t need_ = se_->len + (additional); \ if (need_ > se_->cap) \ SLICE_RESIZE( \ Type, se_, se_->cap * 2 > need_ ? se_->cap * 2 : need_); \ } while (0) #define SLICE_APPEND(Type, slice, item) \ do { \ struct Type##Slice* sa_ = (slice); \ SLICE_ENSURE_CAPACITY(Type, sa_, 1); \ sa_->arr[sa_->len++] = (item); \ } while (0) // ── One bit field inside a 128-bit pair ───────────────────────────────────── // The single transcription of that placement, LSB-first: a packed struct's // field at its structPackedFieldBitOffset and a packed array/vector's lane at // `i * elem_bits` are the same rule on the same carrier, so every packed // insert and extract into a {lo, hi} pair goes through these two -- the // comptime evaluator's and the interpreter's alike, which have to agree bit // for bit. The other carrier is the byte buffer `Sema.bitCastVal` allocates, // whose twin (`packedMemWrite`/`packedMemRead`, below) places the same // LSB-first field in a buffer wider than 128 bits; the two rules are the // same rule and any change here belongs there as well. // Callers own the range check that keeps `off + width` inside 128 bits, and // the sign: an extract returns the field's bits, and a signed element type is // sign-extended by the caller afterwards. // // `packedBitsInsert` ORs into its carrier, which is the whole job when the // carrier starts at zero (every init path). A store through a bit pointer // overwrites a field in place, which is `packedMemWrite`'s job. static inline void packedBitsInsert( uint64_t* lo, uint64_t* hi, uint32_t off, uint32_t width, uint64_t v) { uint64_t mask = width >= 64u ? ~UINT64_C(0) : ((UINT64_C(1) << width) - 1u); v &= mask; if (off >= 64u) { *hi |= v << (off - 64u); return; } *lo |= v << off; if (off != 0u && width > 64u - off) *hi |= v >> (64u - off); } static inline uint64_t packedBitsExtract( uint64_t lo, uint64_t hi, uint32_t off, uint32_t width) { uint64_t mask = width >= 64u ? ~UINT64_C(0) : ((UINT64_C(1) << width) - 1u); if (off >= 64u) return (hi >> (off - 64u)) & mask; uint64_t v = lo >> off; if (off != 0u && width > 64u - off) v |= hi << (64u - off); return v & mask; } // ── One bit field inside a byte buffer ────────────────────────────────────── // std.mem.writeVarPackedInt / readVarPackedInt for the little-endian target // zig1 lowers (wasm32-wasi), so bit `n` of the buffer is bit `n % 8` of byte // `n / 8` in both directions. The scalar carrier is a {lo, hi} pair, so 128 // bits is its ceiling, not the language's: a wider integer leaf takes // `writeToPackedMemory`'s big-int arm (big_int.c's // `bigIntConstWritePackedTwosComplement` and its reader) instead, and any // other wider leaf declines here and the caller keeps its loud abort. Without // this bound the loops shift a `uint64_t` by `bits - 64 >= 64` -- UB, and // silent garbage under gcc/tcc. Both also decline rather than run off the // end, since the caller sizes the buffer from the type's bit size. // Both frames' bit pointers use them over the host's bytes: the comptime // one's `semaPackedMemBuf`, the interpreter's arena. #define PACKED_MEM_CARRIER_BITS 128u static inline bool packedMemWrite(uint8_t* buf, uint64_t buf_bits, uint64_t bit_off, uint32_t bits, uint64_t lo, uint64_t hi) { if (bits > PACKED_MEM_CARRIER_BITS) return false; if (bit_off > buf_bits || buf_bits - bit_off < bits) return false; // One byte per step: the field's bits that land in byte `abs / 8`. for (uint32_t i = 0; i < bits;) { uint64_t abs = bit_off + i; uint32_t shift = (uint32_t)(abs & 7u); uint32_t n = 8u - shift < bits - i ? 8u - shift : bits - i; uint32_t mask = ((1u << n) - 1u) << shift; uint32_t chunk = (uint32_t)packedBitsExtract(lo, hi, i, n) << shift; buf[abs >> 3] = (uint8_t)((buf[abs >> 3] & ~mask) | chunk); i += n; } return true; } static inline bool packedMemRead(const uint8_t* buf, uint64_t buf_bits, uint64_t bit_off, uint32_t bits, uint64_t* lo, uint64_t* hi) { *lo = 0; *hi = 0; if (bits > PACKED_MEM_CARRIER_BITS) return false; if (bit_off > buf_bits || buf_bits - bit_off < bits) return false; for (uint32_t i = 0; i < bits;) { uint64_t abs = bit_off + i; uint32_t shift = (uint32_t)(abs & 7u); uint32_t n = 8u - shift < bits - i ? 8u - shift : bits - i; // packedBitsInsert masks the byte's bits above the field. packedBitsInsert(lo, hi, i, n, (uint64_t)(buf[abs >> 3] >> shift)); i += n; } return true; } #endif /* ---- end common.h ---- */ /* ---- begin compilation.h ---- */ // compilation.h — the compilation config, after src/Compilation.zig. zig1 // targets one fixed backend (wasm32-wasi), so most of upstream's config // surface is constant. #ifndef ZIG1_COMPILATION_H #define ZIG1_COMPILATION_H #include #include // One module definition in the fixed compilation graph. `sub_path` is its // root file, below `root_dir` (Module.root; NULL: the cwd) when `source` is // NULL. A non-NULL `source` is synthesized source text and is copied into // Zcu-owned storage when first imported. Identity is the index in // CompilationConfig.modules, so two dependency names can intentionally alias // the same module without loading two roots. typedef struct { // Module.fully_qualified_name, distinct from its root type/file stem. const char* name; char* root_dir; const char* sub_path; const char* source; } CompilationModule; // One dependency edge from the compilation root module. Only root // dependencies are modeled, not module-local tables. `module_index` indexes // CompilationConfig.modules, or is COMPILATION_MODULE_ROOT, the root itself. typedef struct { const char* name; uint32_t module_index; } CompilationDependency; #define COMPILATION_MODULE_ROOT UINT32_MAX // Selects the synthesized `builtin` source for a compilation. AIRGEN is the // profile stage1/test/air_gen.zig's AIR references are made with. The // bootstrap profile is zig1.wasm's (build.zig addWasiBuildOptions). The test // profile is the upstream behavior-test invocation: wasm32-wasi-none, // ReleaseSmall, self-hosted, and `is_test`. typedef enum { COMPILATION_PROFILE_AIRGEN = 0, COMPILATION_PROFILE_BOOTSTRAP_WASI, COMPILATION_PROFILE_TEST_WASI, } CompilationProfile; typedef struct { const char* module_root; // filesystem root of the module (NULL = cwd) const char* std_root; // what std's lib/std/ is under (NULL = cwd) // Root Module.fully_qualified_name. Kept separate from root_name because // @src().module names the module while type FQNs use the root type name. const char* module_name; // Root type name of the module's root source file: the file's sub-path // with the extension dropped and path separators turned into dots (see // Zcu.File.renderFullyQualifiedName). Used to build fully-qualified // declaration names, e.g. "export_void" -> "export_void.f". const char* root_name; // `zig test` has two distinct source modules: root is the upstream test // runner executable and main is the user source containing tests. NULL // means the ordinary one-module arrangement where main == root. const char* main_module_root; const char* main_module_name; const char* main_root_name; const char* target_cpu_arch; const char* target_cpu_model; const char* object_format; const char* link_mode; bool is_test; CompilationProfile profile; const CompilationModule* modules; uint32_t modules_len; const CompilationDependency* root_dependencies; uint32_t root_dependencies_len; // WASI guest argv for the interpreted program: the exact strings a WASI // `args_sizes_get`/`args_get` pair would return. For the bootstrap command // this is bootstrap.c's zig1 argv with the lib-dir argv[1] dropped, // mirroring upstream's wasi.c (its args_get skips src_i == 1 and uses that // argument as the /lib preopen root instead). On wasm32-wasi the entry's // `Init.Minimal` value is argv-independent (`args = .{ .vector = {} }`), // so nothing reads these until the interpreted program executes the WASI // syscalls. const char* const* wasi_argv; uint32_t wasi_argv_len; // The host directory the guest sees as /lib (NULL: `lib`), upstream's // wasi.c's argv[1], and the one it sees as /cache (NULL: none). const char* wasi_lib_dir; const char* wasi_global_cache; } CompilationConfig; typedef struct { CompilationConfig config; } Compilation; #endif /* ---- end compilation.h ---- */ /* ---- begin softfloat.h ---- */ // softfloat.h — zig1's float library, transcribed to C from // lib/compiler_rt/: the soft-float routines (f16, f80 and f128 arithmetic, // conversion, comparison, sqrt, fmod, rounding and the elementary functions) // and the f32/f64 elementary functions it ports from musl (sin, cos, tan, // exp, exp2, log, log2, log10). It is not the compiler_rt.c that zig1 // generates. Each name is crt and the lib/compiler_rt/ function it ports // (addf3.zig's addf3 is crtAddf3, sin.zig's sinf is crtSinf). // The soft-float routines are the one exact carrier for f80, f128 and this // pool's f128-stored `comptime_float`: Sema's comptime fold and the // AIR interpreter both call them, exactly as upstream's comptime `Value` // arithmetic (`lhs.toFloat(f128, zcu) + rhs.toFloat(f128, zcu)`) and the // wasm backend's libcalls (`__addtf3`, `__addxf3`, ...) reach the same // compiler_rt bodies. // // A value is its raw bit pattern as a `CrtU128`: an f128 fills all 128 bits, // an f80 the low 80 (`hi` holds sign+exponent in its low 16 bits). There is // no `__uint128_t` here (TCC lacks it), so upstream's `u128`/`u80` integer // arithmetic is spelled on the pair; every routine truncates to the float's // own width wherever upstream's `Z` would wrap. #ifndef ZIG1_SOFTFLOAT_H #define ZIG1_SOFTFLOAT_H #include #include typedef struct { uint64_t lo, hi; } CrtU128; // 128-bit unsigned arithmetic on the pair, for these routines and for // AstGen's float-literal parsing (astgen.c). static inline CrtU128 u128Make(uint64_t hi, uint64_t lo) { CrtU128 r = { lo, hi }; return r; } static inline bool u128IsZero(CrtU128 a) { return (a.lo | a.hi) == 0; } static inline int u128Cmp(CrtU128 a, CrtU128 b) { if (a.hi != b.hi) return a.hi < b.hi ? -1 : 1; if (a.lo != b.lo) return a.lo < b.lo ? -1 : 1; return 0; } static inline CrtU128 u128Add(CrtU128 a, CrtU128 b) { CrtU128 r; r.lo = a.lo + b.lo; r.hi = a.hi + b.hi + (r.lo < a.lo ? 1u : 0u); return r; } static inline CrtU128 u128Sub(CrtU128 a, CrtU128 b) { CrtU128 r; r.lo = a.lo - b.lo; r.hi = a.hi - b.hi - (a.lo < b.lo ? 1u : 0u); return r; } static inline CrtU128 u128And(CrtU128 a, CrtU128 b) { return u128Make(a.hi & b.hi, a.lo & b.lo); } static inline CrtU128 u128Or(CrtU128 a, CrtU128 b) { return u128Make(a.hi | b.hi, a.lo | b.lo); } static inline CrtU128 u128Xor(CrtU128 a, CrtU128 b) { return u128Make(a.hi ^ b.hi, a.lo ^ b.lo); } // `a << n` in 128 bits; n >= 128 shifts everything out. static inline CrtU128 u128Shl(CrtU128 a, uint32_t n) { if (n == 0) return a; if (n >= 128) return u128Make(0, 0); if (n >= 64) return u128Make(a.lo << (n - 64), 0); return u128Make((a.hi << n) | (a.lo >> (64 - n)), a.lo << n); } static inline CrtU128 u128Shr(CrtU128 a, uint32_t n) { if (n == 0) return a; if (n >= 128) return u128Make(0, 0); if (n >= 64) return u128Make(0, a.hi >> (n - 64)); // `n` is in [1, 63]; the mask spells that out for static analysis. return u128Make(a.hi >> n, (a.lo >> n) | (a.hi << ((64 - n) & 63u))); } static inline CrtU128 u128Bit(uint32_t n) { return u128Shl(u128Make(0, 1), n); } // The low `n` bits set, n < 128. static inline CrtU128 u128LowMask(uint32_t n) { return u128Sub(u128Bit(n), u128Make(0, 1)); } // The exact 64x64 -> 128 product, as `@as(u128, a) * b`. static inline CrtU128 u128Mul64(uint64_t a, uint64_t b) { uint64_t hi, lo; const uint64_t plolo = (a & 0xFFFFFFFFu) * (b & 0xFFFFFFFFu); const uint64_t plohi = (a & 0xFFFFFFFFu) * (b >> 32); const uint64_t philo = (a >> 32) * (b & 0xFFFFFFFFu); const uint64_t phihi = (a >> 32) * (b >> 32); const uint64_t r0 = plolo & 0xFFFFFFFFu; const uint64_t r1 = (plolo >> 32) + (plohi & 0xFFFFFFFFu) + (philo & 0xFFFFFFFFu); lo = r0 + (r1 << 32); hi = (plohi >> 32) + (philo >> 32) + (r1 >> 32) + phihi; return u128Make(hi, lo); } // `addf3(T, a, b)` (lib/compiler_rt/addf3.zig) for T = f80 (`float_bits` 80, // `__addxf3`) or f128 (128, `__addtf3`). CrtU128 crtAddf3(uint32_t float_bits, CrtU128 a, CrtU128 b); // `__subtf3` / `__subxf3`: `addf3(T, a, -b)`, the sign bit of b flipped. CrtU128 crtSubf3(uint32_t float_bits, CrtU128 a, CrtU128 b); // comparef.zig `cmpf2(f128, ...)` / `cmp_f80(...)`: -1/0/1 for a <, ==, > b. // An unordered pair answers `LE.Unordered` (1) or, with `ge`, // `GE.Unordered` (-1) -- the `__lttf2`/`__letf2`/`__eqtf2`/`__netf2` and // `__getf2`/`__gttf2` flavours CodeGen.floatCmp calls. int crtCmp(uint32_t float_bits, bool ge, CrtU128 a, CrtU128 b); // float_from_int.zig `floatFromInt(T, x)`: a <=128-bit magnitude and sign to // any float width, rounded to nearest-even. CrtU128 crtFloatFromInt(uint32_t float_bits, CrtU128 abs_val, bool negative); // `mulf3(T, a, b)` (lib/compiler_rt/mulf3.zig): `__mulxf3` / `__multf3`. CrtU128 crtMulf3(uint32_t float_bits, CrtU128 a, CrtU128 b); // divtf3.zig `div` (`__divtf3`) and divxf3.zig `__divxf3`. CrtU128 crtDivtf3(CrtU128 a, CrtU128 b); CrtU128 crtDivxf3(CrtU128 a, CrtU128 b); // extendf.zig `extendf` / truncf.zig `truncf` between the implicit-bit // formats (16, 32, 64, 128 bits): `__extendhftf2`, `__trunctfdf2`, ... CrtU128 crtExtendf(uint32_t dst_bits, uint32_t src_bits, CrtU128 a); CrtU128 crtTruncf(uint32_t dst_bits, uint32_t src_bits, CrtU128 a); // extendf.zig `extend_f80(src_t, a)` from f16/f32/f64 (`__extenddfxf2`, ...). CrtU128 crtExtendF80(uint32_t src_bits, CrtU128 a); // truncf.zig `trunc_f80(dst_t, a)` for f16, f32, f64 (`__truncxfhf2`, // `__truncxfsf2`, `__truncxfdf2`). CrtU128 crtTruncF80(uint32_t dst_bits, CrtU128 a); // extendxftf2.zig `__extendxftf2` (f80 -> f128) and trunctfxf2.zig // `__trunctfxf2` (f128 -> f80). CrtU128 crtExtendxftf2(CrtU128 a); CrtU128 crtTrunctfxf2(CrtU128 a); // floor_ceil.zig `__floorx`/`floorq` (`ceil` false) and `__ceilx`/`ceilq`. CrtU128 crtFloorCeil(uint32_t float_bits, bool ceil, CrtU128 x); // trunc.zig `truncq` and `__truncx` (`@floatCast(truncq(x))`). CrtU128 crtTruncq(CrtU128 x); CrtU128 crtTruncx(CrtU128 x); // round.zig `roundq` and `__roundx` (`@floatCast(roundq(x))`). CrtU128 crtRoundq(CrtU128 x); CrtU128 crtRoundx(CrtU128 x); // fmod.zig `__fmodx` and `fmodq`: the remainder with the dividend's sign. CrtU128 crtFmodx(CrtU128 a, CrtU128 b); CrtU128 crtFmodq(CrtU128 a, CrtU128 b); // fmin.zig / fmax.zig at f80 and f128 (`__fminx`, `fminq`, `__fmaxx`, // `fmaxq`): a NaN operand yields the other; -0 orders below +0. CrtU128 crtFminFmax(uint32_t float_bits, bool max, CrtU128 x, CrtU128 y); // sqrt.zig `__sqrtx` and `sqrtq`: correctly rounded square roots. CrtU128 crtSqrtx(CrtU128 x); CrtU128 crtSqrtq(CrtU128 x); // log.zig `logq`, log2.zig `log2q`, log10.zig `log10q` (log_f128.zig's // table-driven proc1/proc2); `__logx` and friends round these to f80. CrtU128 crtLogq(CrtU128 x); CrtU128 crtLog2q(CrtU128 x); CrtU128 crtLog10q(CrtU128 x); // exp_f128.zig `exp` (`expq`) and `exp2` (`exp2q`); `__expx` and `__exp2x` // round these to f80. CrtU128 crtExpq(CrtU128 x); CrtU128 crtExp2q(CrtU128 x); // sin.zig / cos.zig / tan.zig at f80 (`float_bits` 80: `__sinx`, `__cosx`, // `__tanx`) and f128 (128: `sinq`, `cosq`, `tanq`), with rem_pio2l.zig's // reduction. CrtU128 crtSinT(uint32_t float_bits, CrtU128 x); CrtU128 crtCosT(uint32_t float_bits, CrtU128 x); CrtU128 crtTanT(uint32_t float_bits, CrtU128 x); // The f32/f64 elementary functions of lib/compiler_rt/ (sin.zig `sinf` / // `sin`, cos.zig, tan.zig, exp.zig, exp2.zig, log.zig, log2.zig, log10.zig): // what upstream's comptime `Value.sin` ... `Value.log10` reach through the // host compiler's libcalls, and what the wasm backend's `floatSin` ... // `floatLog10` call at runtime. `want_float_exceptions` is // `compiler_rt.want_float_exceptions` (`!cpu.arch.isWasm()`): the logs of a // negative number answer the host's `(x - x) / 0.0` when it is set and // `math.nan` when it is not. float crtSinf(float x); double crtSin(double x); float crtCosf(float x); double crtCos(double x); float crtTanf(float x); double crtTan(double x); float crtExpf(float x); double crtExp(double x); float crtExp2f(float x); double crtExp2(double x); float crtLogf(float x, bool want_float_exceptions); double crtLog(double x, bool want_float_exceptions); float crtLog2f(float x, bool want_float_exceptions); double crtLog2(double x, bool want_float_exceptions); float crtLog10f(float x, bool want_float_exceptions); double crtLog10(double x, bool want_float_exceptions); #endif /* ---- end softfloat.h ---- */ /* ---- begin upstream_gen.h ---- */ // Generated by stage1/upstream_gen.zig from the pinned upstream // sources; do not edit. Regenerate: stage1/test/mem_build.sh gen-zig1 #ifndef ZIG1_UPSTREAM_GEN_H #define ZIG1_UPSTREAM_GEN_H // clang-format off // stage1/upstream.zig: version #define ZIG1_UPSTREAM_VERSION "0.17.1" #define ZIG1_UPSTREAM_SEMVER " .major = 0,\n .minor = 17,\n .patch = 1,\n" // lib/std/zig/Zir.zig: Inst.Tag #define ZIR_INST_FOREACH_TAG(TAG) \ TAG(ZIR_INST_ADD) \ TAG(ZIR_INST_ADDWRAP) \ TAG(ZIR_INST_ADD_SAT) \ TAG(ZIR_INST_ADD_UNSAFE) \ TAG(ZIR_INST_SUB) \ TAG(ZIR_INST_SUBWRAP) \ TAG(ZIR_INST_SUB_SAT) \ TAG(ZIR_INST_MUL) \ TAG(ZIR_INST_MULWRAP) \ TAG(ZIR_INST_MUL_SAT) \ TAG(ZIR_INST_DIV_EXACT) \ TAG(ZIR_INST_DIV_FLOOR) \ TAG(ZIR_INST_DIV_CEIL) \ TAG(ZIR_INST_DIV_TRUNC) \ TAG(ZIR_INST_MOD) \ TAG(ZIR_INST_REM) \ TAG(ZIR_INST_MOD_REM) \ TAG(ZIR_INST_SHL) \ TAG(ZIR_INST_SHL_EXACT) \ TAG(ZIR_INST_SHL_SAT) \ TAG(ZIR_INST_SHR) \ TAG(ZIR_INST_SHR_EXACT) \ TAG(ZIR_INST_PARAM) \ TAG(ZIR_INST_PARAM_COMPTIME) \ TAG(ZIR_INST_PARAM_ANYTYPE) \ TAG(ZIR_INST_PARAM_ANYTYPE_COMPTIME) \ TAG(ZIR_INST_ARRAY_CAT) \ TAG(ZIR_INST_ARRAY_TYPE) \ TAG(ZIR_INST_ARRAY_TYPE_SENTINEL) \ TAG(ZIR_INST_REIFY_INT) \ TAG(ZIR_INST_VECTOR_TYPE) \ TAG(ZIR_INST_ELEM_TYPE) \ TAG(ZIR_INST_INDEXABLE_PTR_ELEM_TYPE) \ TAG(ZIR_INST_SPLAT_OP_RESULT_TY) \ TAG(ZIR_INST_FROM_BACKING_INT_ARG_TY) \ TAG(ZIR_INST_INDEXABLE_PTR_LEN) \ TAG(ZIR_INST_ANYFRAME_TYPE) \ TAG(ZIR_INST_AS_NODE) \ TAG(ZIR_INST_AS_SHIFT_OPERAND) \ TAG(ZIR_INST_BIT_AND) \ TAG(ZIR_INST_BITCAST) \ TAG(ZIR_INST_BIT_NOT) \ TAG(ZIR_INST_BIT_OR) \ TAG(ZIR_INST_BLOCK) \ TAG(ZIR_INST_BLOCK_COMPTIME) \ TAG(ZIR_INST_BLOCK_INLINE) \ TAG(ZIR_INST_DECLARATION) \ TAG(ZIR_INST_SUSPEND_BLOCK) \ TAG(ZIR_INST_BOOL_NOT) \ TAG(ZIR_INST_BOOL_BR_AND) \ TAG(ZIR_INST_BOOL_BR_OR) \ TAG(ZIR_INST_BREAK) \ TAG(ZIR_INST_BREAK_INLINE) \ TAG(ZIR_INST_SWITCH_CONTINUE) \ TAG(ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW) \ TAG(ZIR_INST_CALL) \ TAG(ZIR_INST_FIELD_CALL) \ TAG(ZIR_INST_BUILTIN_CALL) \ TAG(ZIR_INST_CMP_LT) \ TAG(ZIR_INST_CMP_LTE) \ TAG(ZIR_INST_CMP_EQ) \ TAG(ZIR_INST_CMP_GTE) \ TAG(ZIR_INST_CMP_GT) \ TAG(ZIR_INST_CMP_NEQ) \ TAG(ZIR_INST_CONDBR) \ TAG(ZIR_INST_CONDBR_INLINE) \ TAG(ZIR_INST_TRY) \ TAG(ZIR_INST_TRY_PTR) \ TAG(ZIR_INST_ERROR_SET_DECL) \ TAG(ZIR_INST_DBG_STMT) \ TAG(ZIR_INST_DBG_VAR_PTR) \ TAG(ZIR_INST_DBG_VAR_VAL) \ TAG(ZIR_INST_DECL_REF) \ TAG(ZIR_INST_DECL_VAL) \ TAG(ZIR_INST_LOAD) \ TAG(ZIR_INST_DIV) \ TAG(ZIR_INST_ELEM_PTR_NODE) \ TAG(ZIR_INST_ELEM_PTR) \ TAG(ZIR_INST_ELEM_PTR_LOAD) \ TAG(ZIR_INST_ELEM_VAL) \ TAG(ZIR_INST_ELEM_VAL_IMM) \ TAG(ZIR_INST_ENSURE_RESULT_USED) \ TAG(ZIR_INST_ENSURE_RESULT_NON_ERROR) \ TAG(ZIR_INST_ENSURE_ERR_UNION_PAYLOAD_VOID) \ TAG(ZIR_INST_ERROR_UNION_TYPE) \ TAG(ZIR_INST_ERROR_VALUE) \ TAG(ZIR_INST_EXPORT) \ TAG(ZIR_INST_FIELD_PTR) \ TAG(ZIR_INST_FIELD_PTR_LOAD) \ TAG(ZIR_INST_FIELD_PTR_NAMED) \ TAG(ZIR_INST_FIELD_PTR_NAMED_LOAD) \ TAG(ZIR_INST_FUNC) \ TAG(ZIR_INST_FUNC_INFERRED) \ TAG(ZIR_INST_FUNC_FANCY) \ TAG(ZIR_INST_IMPORT) \ TAG(ZIR_INST_INT) \ TAG(ZIR_INST_INT_BIG) \ TAG(ZIR_INST_FLOAT) \ TAG(ZIR_INST_FLOAT128) \ TAG(ZIR_INST_INT_TYPE) \ TAG(ZIR_INST_IS_NON_NULL) \ TAG(ZIR_INST_IS_NON_NULL_PTR) \ TAG(ZIR_INST_IS_NON_ERR) \ TAG(ZIR_INST_IS_NON_ERR_PTR) \ TAG(ZIR_INST_RET_IS_NON_ERR) \ TAG(ZIR_INST_LOOP) \ TAG(ZIR_INST_REPEAT) \ TAG(ZIR_INST_REPEAT_INLINE) \ TAG(ZIR_INST_FOR_LEN) \ TAG(ZIR_INST_MERGE_ERROR_SETS) \ TAG(ZIR_INST_REF) \ TAG(ZIR_INST_DEREF) \ TAG(ZIR_INST_REF_DEREF) \ TAG(ZIR_INST_RET_NODE) \ TAG(ZIR_INST_RET_LOAD) \ TAG(ZIR_INST_RET_IMPLICIT) \ TAG(ZIR_INST_RET_ERR_VALUE) \ TAG(ZIR_INST_RET_PTR) \ TAG(ZIR_INST_RET_TYPE) \ TAG(ZIR_INST_PTR_TYPE) \ TAG(ZIR_INST_SLICE_START) \ TAG(ZIR_INST_SLICE_END) \ TAG(ZIR_INST_SLICE_SENTINEL) \ TAG(ZIR_INST_SLICE_LENGTH) \ TAG(ZIR_INST_SLICE_SENTINEL_TY) \ TAG(ZIR_INST_STORE_NODE) \ TAG(ZIR_INST_STORE_TO_INFERRED_PTR) \ TAG(ZIR_INST_STR) \ TAG(ZIR_INST_NEGATE) \ TAG(ZIR_INST_NEGATE_WRAP) \ TAG(ZIR_INST_TYPEOF) \ TAG(ZIR_INST_TYPEOF_BUILTIN) \ TAG(ZIR_INST_TYPEOF_LOG2_INT_TYPE) \ TAG(ZIR_INST_UNREACHABLE) \ TAG(ZIR_INST_XOR) \ TAG(ZIR_INST_OPTIONAL_TYPE) \ TAG(ZIR_INST_OPTIONAL_PAYLOAD_SAFE) \ TAG(ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE) \ TAG(ZIR_INST_OPTIONAL_PAYLOAD_SAFE_PTR) \ TAG(ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE_PTR) \ TAG(ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE) \ TAG(ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE_PTR) \ TAG(ZIR_INST_ERR_UNION_CODE) \ TAG(ZIR_INST_ERR_UNION_CODE_PTR) \ TAG(ZIR_INST_ENUM_LITERAL) \ TAG(ZIR_INST_DECL_LITERAL) \ TAG(ZIR_INST_DECL_LITERAL_NO_COERCE) \ TAG(ZIR_INST_SWITCH_BLOCK) \ TAG(ZIR_INST_SWITCH_BLOCK_REF) \ TAG(ZIR_INST_SWITCH_BLOCK_ERR_UNION) \ TAG(ZIR_INST_VALIDATE_DESTRUCTURE) \ TAG(ZIR_INST_FIELD_TYPE_REF) \ TAG(ZIR_INST_OPT_EU_BASE_PTR_INIT) \ TAG(ZIR_INST_COERCE_PTR_ELEM_TY) \ TAG(ZIR_INST_VALIDATE_REF_TY) \ TAG(ZIR_INST_VALIDATE_CONST) \ TAG(ZIR_INST_STRUCT_INIT_EMPTY) \ TAG(ZIR_INST_STRUCT_INIT_EMPTY_RESULT) \ TAG(ZIR_INST_STRUCT_INIT_EMPTY_REF_RESULT) \ TAG(ZIR_INST_STRUCT_INIT_ANON) \ TAG(ZIR_INST_STRUCT_INIT) \ TAG(ZIR_INST_STRUCT_INIT_REF) \ TAG(ZIR_INST_VALIDATE_STRUCT_INIT_TY) \ TAG(ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY) \ TAG(ZIR_INST_VALIDATE_PTR_STRUCT_INIT) \ TAG(ZIR_INST_STRUCT_INIT_FIELD_TYPE) \ TAG(ZIR_INST_STRUCT_INIT_FIELD_PTR) \ TAG(ZIR_INST_ARRAY_INIT_ANON) \ TAG(ZIR_INST_ARRAY_INIT) \ TAG(ZIR_INST_ARRAY_INIT_REF) \ TAG(ZIR_INST_VALIDATE_ARRAY_INIT_TY) \ TAG(ZIR_INST_VALIDATE_ARRAY_INIT_RESULT_TY) \ TAG(ZIR_INST_VALIDATE_ARRAY_INIT_REF_TY) \ TAG(ZIR_INST_VALIDATE_PTR_ARRAY_INIT) \ TAG(ZIR_INST_ARRAY_INIT_ELEM_TYPE) \ TAG(ZIR_INST_ARRAY_INIT_ELEM_PTR) \ TAG(ZIR_INST_UNION_INIT) \ TAG(ZIR_INST_TYPE_INFO) \ TAG(ZIR_INST_SIZE_OF) \ TAG(ZIR_INST_BIT_SIZE_OF) \ TAG(ZIR_INST_INT_FROM_PTR) \ TAG(ZIR_INST_COMPILE_ERROR) \ TAG(ZIR_INST_SET_EVAL_BRANCH_QUOTA) \ TAG(ZIR_INST_INT_FROM_ENUM) \ TAG(ZIR_INST_BACKING_INT) \ TAG(ZIR_INST_ALIGN_OF) \ TAG(ZIR_INST_INT_FROM_BOOL) \ TAG(ZIR_INST_EMBED_FILE) \ TAG(ZIR_INST_ERROR_NAME) \ TAG(ZIR_INST_PANIC) \ TAG(ZIR_INST_TRAP) \ TAG(ZIR_INST_SET_RUNTIME_SAFETY) \ TAG(ZIR_INST_SQRT) \ TAG(ZIR_INST_SIN) \ TAG(ZIR_INST_COS) \ TAG(ZIR_INST_TAN) \ TAG(ZIR_INST_EXP) \ TAG(ZIR_INST_EXP2) \ TAG(ZIR_INST_LOG) \ TAG(ZIR_INST_LOG2) \ TAG(ZIR_INST_LOG10) \ TAG(ZIR_INST_ABS) \ TAG(ZIR_INST_FLOOR) \ TAG(ZIR_INST_CEIL) \ TAG(ZIR_INST_TRUNC) \ TAG(ZIR_INST_ROUND) \ TAG(ZIR_INST_TAG_NAME) \ TAG(ZIR_INST_TYPE_NAME) \ TAG(ZIR_INST_FRAME_TYPE) \ TAG(ZIR_INST_INT_FROM_FLOAT) \ TAG(ZIR_INST_FLOAT_FROM_INT) \ TAG(ZIR_INST_PTR_FROM_INT) \ TAG(ZIR_INST_ENUM_FROM_INT) \ TAG(ZIR_INST_FROM_BACKING_INT) \ TAG(ZIR_INST_FLOAT_CAST) \ TAG(ZIR_INST_INT_CAST) \ TAG(ZIR_INST_PTR_CAST) \ TAG(ZIR_INST_TRUNCATE) \ TAG(ZIR_INST_HAS_DECL) \ TAG(ZIR_INST_HAS_FIELD) \ TAG(ZIR_INST_CLZ) \ TAG(ZIR_INST_CTZ) \ TAG(ZIR_INST_POP_COUNT) \ TAG(ZIR_INST_BYTE_SWAP) \ TAG(ZIR_INST_BIT_REVERSE) \ TAG(ZIR_INST_BIT_OFFSET_OF) \ TAG(ZIR_INST_OFFSET_OF) \ TAG(ZIR_INST_SPLAT) \ TAG(ZIR_INST_REDUCE) \ TAG(ZIR_INST_SHUFFLE) \ TAG(ZIR_INST_ATOMIC_LOAD) \ TAG(ZIR_INST_ATOMIC_RMW) \ TAG(ZIR_INST_ATOMIC_STORE) \ TAG(ZIR_INST_MUL_ADD) \ TAG(ZIR_INST_MEMCPY) \ TAG(ZIR_INST_MEMMOVE) \ TAG(ZIR_INST_MEMSET) \ TAG(ZIR_INST_MIN) \ TAG(ZIR_INST_MAX) \ TAG(ZIR_INST_ALLOC) \ TAG(ZIR_INST_ALLOC_MUT) \ TAG(ZIR_INST_ALLOC_COMPTIME_MUT) \ TAG(ZIR_INST_ALLOC_INFERRED) \ TAG(ZIR_INST_ALLOC_INFERRED_MUT) \ TAG(ZIR_INST_ALLOC_INFERRED_COMPTIME) \ TAG(ZIR_INST_ALLOC_INFERRED_COMPTIME_MUT) \ TAG(ZIR_INST_RESOLVE_INFERRED_ALLOC) \ TAG(ZIR_INST_MAKE_PTR_CONST) \ TAG(ZIR_INST_RESUME) \ TAG(ZIR_INST_DEFER) \ TAG(ZIR_INST_SAVE_ERR_RET_INDEX) \ TAG(ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL) \ TAG(ZIR_INST_RESTORE_ERR_RET_INDEX_FN_ENTRY) \ TAG(ZIR_INST_EXTENDED) // lib/std/zig/Zir.zig: Inst.Extended #define ZIR_EXT_FOREACH_TAG(TAG) \ TAG(ZIR_EXT_STRUCT_DECL) \ TAG(ZIR_EXT_ENUM_DECL) \ TAG(ZIR_EXT_UNION_DECL) \ TAG(ZIR_EXT_OPAQUE_DECL) \ TAG(ZIR_EXT_TUPLE_DECL) \ TAG(ZIR_EXT_THIS) \ TAG(ZIR_EXT_RET_ADDR) \ TAG(ZIR_EXT_BUILTIN_SRC) \ TAG(ZIR_EXT_ERROR_RETURN_TRACE) \ TAG(ZIR_EXT_FRAME) \ TAG(ZIR_EXT_FRAME_ADDRESS) \ TAG(ZIR_EXT_ALLOC) \ TAG(ZIR_EXT_BUILTIN_EXTERN) \ TAG(ZIR_EXT_ASM) \ TAG(ZIR_EXT_ASM_EXPR) \ TAG(ZIR_EXT_COMPILE_LOG) \ TAG(ZIR_EXT_TYPEOF_PEER) \ TAG(ZIR_EXT_MIN_MULTI) \ TAG(ZIR_EXT_MAX_MULTI) \ TAG(ZIR_EXT_ADD_WITH_OVERFLOW) \ TAG(ZIR_EXT_SUB_WITH_OVERFLOW) \ TAG(ZIR_EXT_MUL_WITH_OVERFLOW) \ TAG(ZIR_EXT_SHL_WITH_OVERFLOW) \ TAG(ZIR_EXT_ROUND_OP) \ TAG(ZIR_EXT_ROUND_OP_TY) \ TAG(ZIR_EXT_WASM_MEMORY_SIZE) \ TAG(ZIR_EXT_WASM_MEMORY_GROW) \ TAG(ZIR_EXT_PREFETCH) \ TAG(ZIR_EXT_SET_FLOAT_MODE) \ TAG(ZIR_EXT_ERROR_CAST) \ TAG(ZIR_EXT_BREAKPOINT) \ TAG(ZIR_EXT_DISABLE_INSTRUMENTATION) \ TAG(ZIR_EXT_DISABLE_INTRINSICS) \ TAG(ZIR_EXT_SELECT) \ TAG(ZIR_EXT_INT_FROM_ERROR) \ TAG(ZIR_EXT_ERROR_FROM_INT) \ TAG(ZIR_EXT_REIFY_SLICE_ARG_TY) \ TAG(ZIR_EXT_REIFY_ENUM_VALUE_SLICE_TY) \ TAG(ZIR_EXT_REIFY_POINTER_SENTINEL_TY) \ TAG(ZIR_EXT_REIFY_TUPLE) \ TAG(ZIR_EXT_REIFY_POINTER) \ TAG(ZIR_EXT_REIFY_FN) \ TAG(ZIR_EXT_REIFY_STRUCT) \ TAG(ZIR_EXT_REIFY_UNION) \ TAG(ZIR_EXT_REIFY_ENUM) \ TAG(ZIR_EXT_REIFY_SPIRV_TYPE) \ TAG(ZIR_EXT_CMPXCHG) \ TAG(ZIR_EXT_C_VA_ARG) \ TAG(ZIR_EXT_C_VA_COPY) \ TAG(ZIR_EXT_C_VA_END) \ TAG(ZIR_EXT_C_VA_START) \ TAG(ZIR_EXT_PTR_CAST_FULL) \ TAG(ZIR_EXT_PTR_CAST_NO_DEST) \ TAG(ZIR_EXT_WORK_ITEM_ID) \ TAG(ZIR_EXT_WORK_GROUP_SIZE) \ TAG(ZIR_EXT_WORK_GROUP_ID) \ TAG(ZIR_EXT_IN_COMPTIME) \ TAG(ZIR_EXT_RESTORE_ERR_RET_INDEX) \ TAG(ZIR_EXT_CLOSURE_GET) \ TAG(ZIR_EXT_VALUE_PLACEHOLDER) \ TAG(ZIR_EXT_FIELD_PARENT_PTR) \ TAG(ZIR_EXT_STD_LANG_VALUE) \ TAG(ZIR_EXT_BRANCH_HINT) \ TAG(ZIR_EXT_INPLACE_ARITH_RESULT_TY) \ TAG(ZIR_EXT_DBG_EMPTY_STMT) \ TAG(ZIR_EXT_ASTGEN_ERROR) \ TAG(ZIR_EXT_FLOAT_OP_RESULT_TY) // lib/std/zig/Zir.zig: Inst.Ref #define ZIR_REF_U0_TYPE 0u #define ZIR_REF_U1_TYPE 1u #define ZIR_REF_U8_TYPE 2u #define ZIR_REF_I8_TYPE 3u #define ZIR_REF_U16_TYPE 4u #define ZIR_REF_I16_TYPE 5u #define ZIR_REF_U29_TYPE 6u #define ZIR_REF_U32_TYPE 7u #define ZIR_REF_I32_TYPE 8u #define ZIR_REF_U64_TYPE 9u #define ZIR_REF_I64_TYPE 10u #define ZIR_REF_U80_TYPE 11u #define ZIR_REF_U128_TYPE 12u #define ZIR_REF_I128_TYPE 13u #define ZIR_REF_U256_TYPE 14u #define ZIR_REF_USIZE_TYPE 15u #define ZIR_REF_ISIZE_TYPE 16u #define ZIR_REF_C_CHAR_TYPE 17u #define ZIR_REF_C_SHORT_TYPE 18u #define ZIR_REF_C_USHORT_TYPE 19u #define ZIR_REF_C_INT_TYPE 20u #define ZIR_REF_C_UINT_TYPE 21u #define ZIR_REF_C_LONG_TYPE 22u #define ZIR_REF_C_ULONG_TYPE 23u #define ZIR_REF_C_LONGLONG_TYPE 24u #define ZIR_REF_C_ULONGLONG_TYPE 25u #define ZIR_REF_C_LONGDOUBLE_TYPE 26u #define ZIR_REF_F16_TYPE 27u #define ZIR_REF_F32_TYPE 28u #define ZIR_REF_F64_TYPE 29u #define ZIR_REF_F80_TYPE 30u #define ZIR_REF_F128_TYPE 31u #define ZIR_REF_ANYOPAQUE_TYPE 32u #define ZIR_REF_BOOL_TYPE 33u #define ZIR_REF_VOID_TYPE 34u #define ZIR_REF_TYPE_TYPE 35u #define ZIR_REF_ANYERROR_TYPE 36u #define ZIR_REF_COMPTIME_INT_TYPE 37u #define ZIR_REF_COMPTIME_FLOAT_TYPE 38u #define ZIR_REF_NORETURN_TYPE 39u #define ZIR_REF_ANYFRAME_TYPE 40u #define ZIR_REF_NULL_TYPE 41u #define ZIR_REF_UNDEFINED_TYPE 42u #define ZIR_REF_ENUM_LITERAL_TYPE 43u #define ZIR_REF_PTR_USIZE_TYPE 44u #define ZIR_REF_PTR_CONST_COMPTIME_INT_TYPE 45u #define ZIR_REF_MANYPTR_U8_TYPE 46u #define ZIR_REF_MANYPTR_CONST_U8_TYPE 47u #define ZIR_REF_MANYPTR_CONST_U8_SENTINEL_0_TYPE 48u #define ZIR_REF_SLICE_CONST_U8_TYPE 49u #define ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE 50u #define ZIR_REF_MANYPTR_CONST_SLICE_CONST_U8_TYPE 51u #define ZIR_REF_SLICE_CONST_SLICE_CONST_U8_TYPE 52u #define ZIR_REF_OPTIONAL_TYPE_TYPE 53u #define ZIR_REF_MANYPTR_CONST_TYPE_TYPE 54u #define ZIR_REF_SLICE_CONST_TYPE_TYPE 55u // ZIR_REF_VECTOR_8_I8_TYPE 56u // ZIR_REF_VECTOR_16_I8_TYPE 57u // ZIR_REF_VECTOR_32_I8_TYPE 58u // ZIR_REF_VECTOR_64_I8_TYPE 59u // ZIR_REF_VECTOR_1_U8_TYPE 60u // ZIR_REF_VECTOR_2_U8_TYPE 61u // ZIR_REF_VECTOR_4_U8_TYPE 62u // ZIR_REF_VECTOR_8_U8_TYPE 63u // ZIR_REF_VECTOR_16_U8_TYPE 64u // ZIR_REF_VECTOR_32_U8_TYPE 65u // ZIR_REF_VECTOR_64_U8_TYPE 66u // ZIR_REF_VECTOR_2_I16_TYPE 67u // ZIR_REF_VECTOR_4_I16_TYPE 68u // ZIR_REF_VECTOR_8_I16_TYPE 69u // ZIR_REF_VECTOR_16_I16_TYPE 70u // ZIR_REF_VECTOR_32_I16_TYPE 71u // ZIR_REF_VECTOR_4_U16_TYPE 72u // ZIR_REF_VECTOR_8_U16_TYPE 73u // ZIR_REF_VECTOR_16_U16_TYPE 74u // ZIR_REF_VECTOR_32_U16_TYPE 75u // ZIR_REF_VECTOR_2_I32_TYPE 76u // ZIR_REF_VECTOR_4_I32_TYPE 77u // ZIR_REF_VECTOR_8_I32_TYPE 78u // ZIR_REF_VECTOR_16_I32_TYPE 79u // ZIR_REF_VECTOR_4_U32_TYPE 80u // ZIR_REF_VECTOR_8_U32_TYPE 81u // ZIR_REF_VECTOR_16_U32_TYPE 82u // ZIR_REF_VECTOR_2_I64_TYPE 83u // ZIR_REF_VECTOR_4_I64_TYPE 84u // ZIR_REF_VECTOR_8_I64_TYPE 85u // ZIR_REF_VECTOR_2_U64_TYPE 86u // ZIR_REF_VECTOR_4_U64_TYPE 87u // ZIR_REF_VECTOR_8_U64_TYPE 88u // ZIR_REF_VECTOR_1_U128_TYPE 89u // ZIR_REF_VECTOR_2_U128_TYPE 90u // ZIR_REF_VECTOR_1_U256_TYPE 91u // ZIR_REF_VECTOR_4_F16_TYPE 92u // ZIR_REF_VECTOR_8_F16_TYPE 93u // ZIR_REF_VECTOR_16_F16_TYPE 94u // ZIR_REF_VECTOR_32_F16_TYPE 95u // ZIR_REF_VECTOR_2_F32_TYPE 96u // ZIR_REF_VECTOR_4_F32_TYPE 97u // ZIR_REF_VECTOR_8_F32_TYPE 98u // ZIR_REF_VECTOR_16_F32_TYPE 99u // ZIR_REF_VECTOR_2_F64_TYPE 100u // ZIR_REF_VECTOR_4_F64_TYPE 101u // ZIR_REF_VECTOR_8_F64_TYPE 102u // ZIR_REF_OPTIONAL_NORETURN_TYPE 103u #define ZIR_REF_ANYERROR_VOID_ERROR_UNION_TYPE 104u #define ZIR_REF_ADHOC_INFERRED_ERROR_SET_TYPE 105u #define ZIR_REF_GENERIC_POISON_TYPE 106u #define ZIR_REF_EMPTY_TUPLE_TYPE 107u #define ZIR_REF_UNDEF 108u #define ZIR_REF_UNDEF_BOOL 109u #define ZIR_REF_UNDEF_USIZE 110u #define ZIR_REF_UNDEF_U1 111u #define ZIR_REF_ZERO 112u #define ZIR_REF_ZERO_USIZE 113u #define ZIR_REF_ZERO_U1 114u #define ZIR_REF_ZERO_U8 115u #define ZIR_REF_ONE 116u #define ZIR_REF_ONE_USIZE 117u #define ZIR_REF_ONE_U1 118u #define ZIR_REF_ONE_U8 119u #define ZIR_REF_FOUR_U8 120u #define ZIR_REF_NEGATIVE_ONE 121u #define ZIR_REF_VOID_VALUE 122u #define ZIR_REF_UNREACHABLE_VALUE 123u #define ZIR_REF_NULL_VALUE 124u #define ZIR_REF_BOOL_TRUE 125u #define ZIR_REF_BOOL_FALSE 126u #define ZIR_REF_EMPTY_TUPLE 127u #define ZIR_REF_NONE 4294967295u // lib/std/zig/Zir.zig: ExtraIndex enum { ZIR_EXTRA_COMPILE_ERRORS = 0, ZIR_EXTRA_IMPORTS = 1, }; // lib/std/zig/Zir.zig: Inst.StdLangValue enum { STD_LANG_VALUE_ATOMIC_ORDER = 0, STD_LANG_VALUE_ATOMIC_RMW_OP = 1, STD_LANG_VALUE_CALLING_CONVENTION = 2, STD_LANG_VALUE_ADDRESS_SPACE = 3, STD_LANG_VALUE_FLOAT_MODE = 4, STD_LANG_VALUE_SIGNEDNESS = 5, STD_LANG_VALUE_REDUCE_OP = 6, STD_LANG_VALUE_CALL_MODIFIER = 7, STD_LANG_VALUE_PREFETCH_OPTIONS = 8, STD_LANG_VALUE_EXPORT_OPTIONS = 9, STD_LANG_VALUE_EXTERN_OPTIONS = 10, STD_LANG_VALUE_BRANCH_HINT = 11, STD_LANG_VALUE_CLOBBERS = 12, STD_LANG_VALUE_POINTER_SIZE = 13, STD_LANG_VALUE_POINTER_ATTRIBUTES = 14, STD_LANG_VALUE_FN_ATTRIBUTES = 15, STD_LANG_VALUE_CONTAINER_LAYOUT = 16, STD_LANG_VALUE_ENUM_MODE = 17, STD_LANG_VALUE_SPIRV_TYPE_OPTIONS = 18, STD_LANG_VALUE_CALLING_CONVENTION_C = 19, STD_LANG_VALUE_CALLING_CONVENTION_INLINE = 20, }; // lib/std/zig/Zir.zig: Inst.Declaration.Flags #define ZIR_DECL_FLAGS_SRC_LINE_SHIFT 0u #define ZIR_DECL_FLAGS_SRC_LINE_MASK 0x3FFFFFFFu #define ZIR_DECL_FLAGS_SRC_COLUMN_SHIFT 30u #define ZIR_DECL_FLAGS_SRC_COLUMN_MASK 0x1FFFFFFFu #define ZIR_DECL_FLAGS_ID_SHIFT 59u #define ZIR_DECL_FLAGS_ID_MASK 0x1Fu // lib/std/zig/Zir.zig: Inst.Declaration.Flags.Id typedef enum { DECL_ID_UNNAMED_TEST = 0, DECL_ID_TEST = 1, DECL_ID_DECLTEST = 2, DECL_ID_COMPTIME = 3, DECL_ID_CONST_SIMPLE = 4, DECL_ID_CONST_TYPED = 5, DECL_ID_CONST = 6, DECL_ID_PUB_CONST_SIMPLE = 7, DECL_ID_PUB_CONST_TYPED = 8, DECL_ID_PUB_CONST = 9, DECL_ID_EXTERN_CONST_SIMPLE = 10, DECL_ID_EXTERN_CONST = 11, DECL_ID_PUB_EXTERN_CONST_SIMPLE = 12, DECL_ID_PUB_EXTERN_CONST = 13, DECL_ID_EXPORT_CONST = 14, DECL_ID_PUB_EXPORT_CONST = 15, DECL_ID_VAR_SIMPLE = 16, DECL_ID_VAR = 17, DECL_ID_VAR_THREADLOCAL = 18, DECL_ID_PUB_VAR_SIMPLE = 19, DECL_ID_PUB_VAR = 20, DECL_ID_PUB_VAR_THREADLOCAL = 21, DECL_ID_EXTERN_VAR = 22, DECL_ID_EXTERN_VAR_THREADLOCAL = 23, DECL_ID_PUB_EXTERN_VAR = 24, DECL_ID_PUB_EXTERN_VAR_THREADLOCAL = 25, DECL_ID_EXPORT_VAR = 26, DECL_ID_EXPORT_VAR_THREADLOCAL = 27, DECL_ID_PUB_EXPORT_VAR = 28, DECL_ID_PUB_EXPORT_VAR_THREADLOCAL = 29, } DeclId; // lib/std/zig/Zir.zig: Inst.Data.ptr_type.flags // ZIR_PTR_TYPE_FLAGS_IS_ALLOWZERO_SHIFT 0u #define ZIR_PTR_TYPE_FLAGS_IS_ALLOWZERO 0x1u // ZIR_PTR_TYPE_FLAGS_IS_MUTABLE_SHIFT 1u #define ZIR_PTR_TYPE_FLAGS_IS_MUTABLE 0x2u // ZIR_PTR_TYPE_FLAGS_IS_VOLATILE_SHIFT 2u #define ZIR_PTR_TYPE_FLAGS_IS_VOLATILE 0x4u // ZIR_PTR_TYPE_FLAGS_HAS_SENTINEL_SHIFT 3u #define ZIR_PTR_TYPE_FLAGS_HAS_SENTINEL 0x8u // ZIR_PTR_TYPE_FLAGS_HAS_ALIGN_SHIFT 4u #define ZIR_PTR_TYPE_FLAGS_HAS_ALIGN 0x10u // ZIR_PTR_TYPE_FLAGS_HAS_ADDRSPACE_SHIFT 5u #define ZIR_PTR_TYPE_FLAGS_HAS_ADDRSPACE 0x20u // ZIR_PTR_TYPE_FLAGS_HAS_BIT_RANGE_SHIFT 6u #define ZIR_PTR_TYPE_FLAGS_HAS_BIT_RANGE 0x40u // lib/std/zig/Zir.zig: Inst.Func.RetTy // ZIR_FUNC_RET_TY_BODY_LEN_SHIFT 0u #define ZIR_FUNC_RET_TY_BODY_LEN_MASK 0x7FFFFFFFu // ZIR_FUNC_RET_TY_IS_GENERIC_SHIFT 31u #define ZIR_FUNC_RET_TY_IS_GENERIC 0x80000000u // lib/std/zig/Zir.zig: Inst.FuncFancy.Bits // ZIR_FUNC_FANCY_BITS_IS_VAR_ARGS_SHIFT 0u #define ZIR_FUNC_FANCY_BITS_IS_VAR_ARGS 0x1u // ZIR_FUNC_FANCY_BITS_IS_INFERRED_ERROR_SHIFT 1u #define ZIR_FUNC_FANCY_BITS_IS_INFERRED_ERROR 0x2u // ZIR_FUNC_FANCY_BITS_IS_NOINLINE_SHIFT 2u #define ZIR_FUNC_FANCY_BITS_IS_NOINLINE 0x4u // ZIR_FUNC_FANCY_BITS_HAS_CC_REF_SHIFT 3u #define ZIR_FUNC_FANCY_BITS_HAS_CC_REF 0x8u // ZIR_FUNC_FANCY_BITS_HAS_CC_BODY_SHIFT 4u #define ZIR_FUNC_FANCY_BITS_HAS_CC_BODY 0x10u // ZIR_FUNC_FANCY_BITS_HAS_RET_TY_REF_SHIFT 5u #define ZIR_FUNC_FANCY_BITS_HAS_RET_TY_REF 0x20u // ZIR_FUNC_FANCY_BITS_HAS_RET_TY_BODY_SHIFT 6u #define ZIR_FUNC_FANCY_BITS_HAS_RET_TY_BODY 0x40u // ZIR_FUNC_FANCY_BITS_HAS_ANY_NOALIAS_SHIFT 7u #define ZIR_FUNC_FANCY_BITS_HAS_ANY_NOALIAS 0x80u // ZIR_FUNC_FANCY_BITS_RET_TY_IS_GENERIC_SHIFT 8u #define ZIR_FUNC_FANCY_BITS_RET_TY_IS_GENERIC 0x100u // lib/std/zig/Zir.zig: Inst.Param.Type // ZIR_PARAM_TYPE_BODY_LEN_SHIFT 0u #define ZIR_PARAM_TYPE_BODY_LEN_MASK 0x7FFFFFFFu // ZIR_PARAM_TYPE_IS_GENERIC_SHIFT 31u #define ZIR_PARAM_TYPE_IS_GENERIC 0x80000000u // lib/std/zig/Zir.zig: Inst.Call.Flags #define ZIR_CALL_FLAGS_PACKED_MODIFIER_SHIFT 0u #define ZIR_CALL_FLAGS_PACKED_MODIFIER_MASK 0x7u // ZIR_CALL_FLAGS_ENSURE_RESULT_USED_SHIFT 3u #define ZIR_CALL_FLAGS_ENSURE_RESULT_USED 0x8u // ZIR_CALL_FLAGS_POP_ERROR_RETURN_TRACE_SHIFT 4u #define ZIR_CALL_FLAGS_POP_ERROR_RETURN_TRACE 0x10u #define ZIR_CALL_FLAGS_ARGS_LEN_SHIFT 5u #define ZIR_CALL_FLAGS_ARGS_LEN_MASK 0x7FFFFFFu // lib/std/zig/Zir.zig: Inst.BuiltinCall.Flags // ZIR_BUILTIN_CALL_FLAGS_IS_NOSUSPEND_SHIFT 0u // ZIR_BUILTIN_CALL_FLAGS_IS_NOSUSPEND 0x1u // ZIR_BUILTIN_CALL_FLAGS_ENSURE_RESULT_USED_SHIFT 1u #define ZIR_BUILTIN_CALL_FLAGS_ENSURE_RESULT_USED 0x2u // lib/std/zig/Zir.zig: Inst.SwitchBlock.Bits // ZIR_SWITCH_BITS_HAS_MULTI_CASES_SHIFT 0u #define ZIR_SWITCH_BITS_HAS_MULTI_CASES 0x1u // ZIR_SWITCH_BITS_ANY_RANGES_SHIFT 1u #define ZIR_SWITCH_BITS_ANY_RANGES 0x2u // ZIR_SWITCH_BITS_HAS_ELSE_SHIFT 2u #define ZIR_SWITCH_BITS_HAS_ELSE 0x4u // ZIR_SWITCH_BITS_HAS_UNDER_SHIFT 3u #define ZIR_SWITCH_BITS_HAS_UNDER 0x8u // ZIR_SWITCH_BITS_HAS_CONTINUE_SHIFT 4u #define ZIR_SWITCH_BITS_HAS_CONTINUE 0x10u // ZIR_SWITCH_BITS_ANY_MAYBE_RUNTIME_CAPTURE_SHIFT 5u #define ZIR_SWITCH_BITS_ANY_MAYBE_RUNTIME_CAPTURE 0x20u // ZIR_SWITCH_BITS_PAYLOAD_CAPTURE_INST_IS_PLACEHOLDER_SHIFT 6u #define ZIR_SWITCH_BITS_PAYLOAD_CAPTURE_INST_IS_PLACEHOLDER 0x40u // ZIR_SWITCH_BITS_TAG_CAPTURE_INST_IS_PLACEHOLDER_SHIFT 7u #define ZIR_SWITCH_BITS_TAG_CAPTURE_INST_IS_PLACEHOLDER 0x80u #define ZIR_SWITCH_BITS_SCALAR_CASES_LEN_SHIFT 8u // ZIR_SWITCH_BITS_SCALAR_CASES_LEN_MASK 0xFFFFFFu // lib/std/zig/Zir.zig: Inst.SwitchBlock.ProngInfo #define ZIR_PRONG_INFO_BODY_LEN_SHIFT 0u #define ZIR_PRONG_INFO_BODY_LEN_MASK 0x7FFFFFFu #define ZIR_PRONG_INFO_CAPTURE_SHIFT 27u #define ZIR_PRONG_INFO_CAPTURE_MASK 0x3u // ZIR_PRONG_INFO_IS_INLINE_SHIFT 29u #define ZIR_PRONG_INFO_IS_INLINE 0x20000000u // ZIR_PRONG_INFO_HAS_TAG_CAPTURE_SHIFT 30u #define ZIR_PRONG_INFO_HAS_TAG_CAPTURE 0x40000000u // ZIR_PRONG_INFO_IS_COMPTIME_UNREACH_SHIFT 31u #define ZIR_PRONG_INFO_IS_COMPTIME_UNREACH 0x80000000u // lib/std/zig/Zir.zig: Inst.SwitchBlock.ProngInfo.Capture enum { ZIR_PRONG_CAPTURE_NONE = 0, ZIR_PRONG_CAPTURE_BY_VAL = 1, ZIR_PRONG_CAPTURE_BY_REF = 2, }; // lib/std/zig/Zir.zig: Inst.SwitchBlock.ProngInfo.NonErr #define ZIR_PRONG_NON_ERR_BODY_LEN_SHIFT 0u #define ZIR_PRONG_NON_ERR_BODY_LEN_MASK 0x1FFFFFFFu #define ZIR_PRONG_NON_ERR_CAPTURE_SHIFT 29u #define ZIR_PRONG_NON_ERR_CAPTURE_MASK 0x3u // ZIR_PRONG_NON_ERR_OPERAND_IS_REF_SHIFT 31u #define ZIR_PRONG_NON_ERR_OPERAND_IS_REF 0x80000000u // lib/std/zig/Zir.zig: Inst.SwitchBlock.ProngInfo.Else #define ZIR_PRONG_ELSE_BODY_LEN_SHIFT 0u #define ZIR_PRONG_ELSE_BODY_LEN_MASK 0x7FFFFFFu #define ZIR_PRONG_ELSE_CAPTURE_SHIFT 27u #define ZIR_PRONG_ELSE_CAPTURE_MASK 0x3u // ZIR_PRONG_ELSE_IS_INLINE_SHIFT 29u #define ZIR_PRONG_ELSE_IS_INLINE 0x20000000u // ZIR_PRONG_ELSE_HAS_TAG_CAPTURE_SHIFT 30u #define ZIR_PRONG_ELSE_HAS_TAG_CAPTURE 0x40000000u // ZIR_PRONG_ELSE_IS_SIMPLE_NORETURN_SHIFT 31u #define ZIR_PRONG_ELSE_IS_SIMPLE_NORETURN 0x80000000u // lib/std/zig/Zir.zig: Inst.SwitchBlock.ItemInfo #define ZIR_ITEM_INFO_KIND_SHIFT 0u #define ZIR_ITEM_INFO_KIND_MASK 0x3u #define ZIR_ITEM_INFO_DATA_SHIFT 2u #define ZIR_ITEM_INFO_DATA_MASK 0x3FFFFFFFu // lib/std/zig/Zir.zig: Inst.SwitchBlock.ItemInfo.Kind enum { ZIR_ITEM_INFO_KIND_ENUM_LITERAL = 0, ZIR_ITEM_INFO_KIND_ERROR_VALUE = 1, ZIR_ITEM_INFO_KIND_BODY_LEN = 2, ZIR_ITEM_INFO_KIND_UNDER = 3, }; // lib/std/zig/Zir.zig: Inst.StructDecl.Small // ZIR_STRUCT_SMALL_HAS_CAPTURES_LEN_SHIFT 0u #define ZIR_STRUCT_SMALL_HAS_CAPTURES_LEN 0x1u // ZIR_STRUCT_SMALL_HAS_DECLS_LEN_SHIFT 1u #define ZIR_STRUCT_SMALL_HAS_DECLS_LEN 0x2u // ZIR_STRUCT_SMALL_HAS_FIELDS_LEN_SHIFT 2u #define ZIR_STRUCT_SMALL_HAS_FIELDS_LEN 0x4u #define ZIR_STRUCT_SMALL_NAME_STRATEGY_SHIFT 3u #define ZIR_STRUCT_SMALL_NAME_STRATEGY_MASK 0x3u #define ZIR_STRUCT_SMALL_LAYOUT_SHIFT 5u #define ZIR_STRUCT_SMALL_LAYOUT_MASK 0x3u // ZIR_STRUCT_SMALL_HAS_BACKING_INT_TYPE_SHIFT 7u #define ZIR_STRUCT_SMALL_HAS_BACKING_INT_TYPE 0x80u // ZIR_STRUCT_SMALL_ANY_FIELD_ALIGNS_SHIFT 8u #define ZIR_STRUCT_SMALL_ANY_FIELD_ALIGNS 0x100u // ZIR_STRUCT_SMALL_ANY_FIELD_DEFAULTS_SHIFT 9u #define ZIR_STRUCT_SMALL_ANY_FIELD_DEFAULTS 0x200u // ZIR_STRUCT_SMALL_ANY_COMPTIME_FIELDS_SHIFT 10u #define ZIR_STRUCT_SMALL_ANY_COMPTIME_FIELDS 0x400u // lib/std/zig/Zir.zig: Inst.EnumDecl.Small // ZIR_ENUM_SMALL_HAS_CAPTURES_LEN_SHIFT 0u #define ZIR_ENUM_SMALL_HAS_CAPTURES_LEN 0x1u // ZIR_ENUM_SMALL_HAS_DECLS_LEN_SHIFT 1u #define ZIR_ENUM_SMALL_HAS_DECLS_LEN 0x2u // ZIR_ENUM_SMALL_HAS_FIELDS_LEN_SHIFT 2u #define ZIR_ENUM_SMALL_HAS_FIELDS_LEN 0x4u #define ZIR_ENUM_SMALL_NAME_STRATEGY_SHIFT 3u // ZIR_ENUM_SMALL_NAME_STRATEGY_MASK 0x3u // ZIR_ENUM_SMALL_HAS_TAG_TYPE_SHIFT 5u #define ZIR_ENUM_SMALL_HAS_TAG_TYPE 0x20u // ZIR_ENUM_SMALL_NONEXHAUSTIVE_SHIFT 6u #define ZIR_ENUM_SMALL_NONEXHAUSTIVE 0x40u // ZIR_ENUM_SMALL_ANY_FIELD_VALUES_SHIFT 7u #define ZIR_ENUM_SMALL_ANY_FIELD_VALUES 0x80u // lib/std/zig/Zir.zig: Inst.UnionDecl.Small // ZIR_UNION_SMALL_HAS_CAPTURES_LEN_SHIFT 0u #define ZIR_UNION_SMALL_HAS_CAPTURES_LEN 0x1u // ZIR_UNION_SMALL_HAS_DECLS_LEN_SHIFT 1u #define ZIR_UNION_SMALL_HAS_DECLS_LEN 0x2u // ZIR_UNION_SMALL_HAS_FIELDS_LEN_SHIFT 2u #define ZIR_UNION_SMALL_HAS_FIELDS_LEN 0x4u #define ZIR_UNION_SMALL_NAME_STRATEGY_SHIFT 3u // ZIR_UNION_SMALL_NAME_STRATEGY_MASK 0x3u #define ZIR_UNION_SMALL_KIND_SHIFT 5u #define ZIR_UNION_SMALL_KIND_MASK 0x7u // ZIR_UNION_SMALL_ANY_FIELD_ALIGNS_SHIFT 8u #define ZIR_UNION_SMALL_ANY_FIELD_ALIGNS 0x100u // ZIR_UNION_SMALL_ANY_FIELD_VALUES_SHIFT 9u #define ZIR_UNION_SMALL_ANY_FIELD_VALUES 0x200u // lib/std/zig/Zir.zig: Inst.UnionDecl.Kind enum { ZIR_UNION_KIND_AUTO = 0, ZIR_UNION_KIND_TAGGED_EXPLICIT = 1, ZIR_UNION_KIND_TAGGED_ENUM = 2, ZIR_UNION_KIND_TAGGED_ENUM_EXPLICIT = 3, ZIR_UNION_KIND_EXTERN = 4, ZIR_UNION_KIND_PACKED = 5, ZIR_UNION_KIND_PACKED_EXPLICIT = 6, }; // lib/std/zig/Zir.zig: Inst.OpaqueDecl.Small // ZIR_OPAQUE_SMALL_HAS_CAPTURES_LEN_SHIFT 0u #define ZIR_OPAQUE_SMALL_HAS_CAPTURES_LEN 0x1u // ZIR_OPAQUE_SMALL_HAS_DECLS_LEN_SHIFT 1u #define ZIR_OPAQUE_SMALL_HAS_DECLS_LEN 0x2u #define ZIR_OPAQUE_SMALL_NAME_STRATEGY_SHIFT 2u #define ZIR_OPAQUE_SMALL_NAME_STRATEGY_MASK 0x3u // lib/std/zig/Zir.zig: Inst.Asm.Small // ZIR_ASM_SMALL_IS_VOLATILE_SHIFT 0u #define ZIR_ASM_SMALL_IS_VOLATILE 0x1u #define ZIR_ASM_SMALL_OUTPUTS_LEN_SHIFT 1u #define ZIR_ASM_SMALL_OUTPUTS_LEN_MASK 0x7Fu #define ZIR_ASM_SMALL_INPUTS_LEN_SHIFT 8u #define ZIR_ASM_SMALL_INPUTS_LEN_MASK 0xFFu // lib/std/zig/Zir.zig: Inst.AllocExtended.Small // ZIR_ALLOC_SMALL_HAS_TYPE_SHIFT 0u #define ZIR_ALLOC_SMALL_HAS_TYPE 0x1u // ZIR_ALLOC_SMALL_HAS_ALIGN_SHIFT 1u #define ZIR_ALLOC_SMALL_HAS_ALIGN 0x2u // ZIR_ALLOC_SMALL_IS_CONST_SHIFT 2u #define ZIR_ALLOC_SMALL_IS_CONST 0x4u // ZIR_ALLOC_SMALL_IS_COMPTIME_SHIFT 3u #define ZIR_ALLOC_SMALL_IS_COMPTIME 0x8u // lib/std/zig/Zir.zig: Inst.Capture #define ZIR_CAPTURE_TAG_SHIFT 0u #define ZIR_CAPTURE_TAG_MASK 0x7u #define ZIR_CAPTURE_DATA_SHIFT 3u // ZIR_CAPTURE_DATA_MASK 0x1FFFFFFFu // lib/std/zig/Zir.zig: Inst.Capture.tag enum { ZIR_CAPTURE_TAG_NESTED = 0, ZIR_CAPTURE_TAG_INSTRUCTION = 1, ZIR_CAPTURE_TAG_INSTRUCTION_LOAD = 2, ZIR_CAPTURE_TAG_DECL_VAL = 3, ZIR_CAPTURE_TAG_DECL_REF = 4, }; // lib/std/zig/Zir.zig: Inst.NameStrategy enum { ZIR_NAME_STRATEGY_PARENT = 0, ZIR_NAME_STRATEGY_FUNC = 1, ZIR_NAME_STRATEGY_ANON = 2, ZIR_NAME_STRATEGY_DBG_VAR = 3, }; // lib/std/zig/Zir.zig: Inst.FullPtrCastFlags // ZIR_PTR_CAST_FLAG_PTR_CAST_SHIFT 0u #define ZIR_PTR_CAST_FLAG_PTR_CAST 0x1u // ZIR_PTR_CAST_FLAG_ALIGN_CAST_SHIFT 1u #define ZIR_PTR_CAST_FLAG_ALIGN_CAST 0x2u // ZIR_PTR_CAST_FLAG_ADDRSPACE_CAST_SHIFT 2u #define ZIR_PTR_CAST_FLAG_ADDRSPACE_CAST 0x4u // ZIR_PTR_CAST_FLAG_CONST_CAST_SHIFT 3u #define ZIR_PTR_CAST_FLAG_CONST_CAST 0x8u // ZIR_PTR_CAST_FLAG_VOLATILE_CAST_SHIFT 4u #define ZIR_PTR_CAST_FLAG_VOLATILE_CAST 0x10u // lib/std/zig/Zir.zig: Inst.RoundOp enum { ZIR_ROUND_OP_ROUND = 0, ZIR_ROUND_OP_FLOOR = 1, ZIR_ROUND_OP_CEIL = 2, ZIR_ROUND_OP_TRUNC = 3, }; // lib/std/zig/Zir.zig: Inst.ReifySliceArgInfo enum { REIFY_SLICE_ARG_TYPE_TO_FN_PARAM_ATTRS = 0, REIFY_SLICE_ARG_STRING_TO_STRUCT_FIELD_TYPE = 1, REIFY_SLICE_ARG_STRING_TO_UNION_FIELD_TYPE = 2, REIFY_SLICE_ARG_STRING_TO_STRUCT_FIELD_ATTRS = 3, REIFY_SLICE_ARG_STRING_TO_UNION_FIELD_ATTRS = 4, }; // lib/std/zig/Zir.zig: Inst.InplaceOp typedef enum { ZIR_INPLACE_OP_ADD_EQ = 0, ZIR_INPLACE_OP_SUB_EQ = 1, } ZirInplaceOp; // lib/std/zig/Zir.zig: Inst.Break typedef struct { uint32_t operand_src_node; uint32_t block_inst; } ZirInstBreak; // lib/std/zig/Zir.zig: Inst.Asm typedef struct { uint32_t src_node; uint32_t asm_source; uint32_t output_type_bits; uint32_t clobbers; } ZirInstAsm; // lib/std/zig/Zir.zig: Inst.Asm.Output typedef struct { uint32_t name; uint32_t constraint; uint32_t operand; } ZirInstAsmOutput; // lib/std/zig/Zir.zig: Inst.Asm.Input typedef struct { uint32_t name; uint32_t constraint; uint32_t operand; } ZirInstAsmInput; // lib/std/zig/Zir.zig: Inst.Func typedef struct { uint32_t ret_ty; uint32_t param_block; uint32_t body_len; } ZirInstFunc; // lib/std/zig/Zir.zig: Inst.Func.SrcLocs typedef struct { uint32_t lbrace_line; uint32_t rbrace_line; uint32_t columns; } ZirInstFuncSrcLocs; // lib/std/zig/Zir.zig: Inst.FuncFancy typedef struct { uint32_t param_block; uint32_t body_len; uint32_t bits; } ZirInstFuncFancy; // lib/std/zig/Zir.zig: Inst.MultiOp typedef struct { uint32_t operands_len; } ZirInstMultiOp; // lib/std/zig/Zir.zig: Inst.NodeMultiOp typedef struct { uint32_t src_node; } ZirInstNodeMultiOp; // lib/std/zig/Zir.zig: Inst.Block typedef struct { uint32_t body_len; } ZirInstBlock; // lib/std/zig/Zir.zig: Inst.BlockComptime typedef struct { uint32_t reason; uint32_t body_len; } ZirInstBlockComptime; // lib/std/zig/Zir.zig: Inst.BoolBr typedef struct { uint32_t lhs; uint32_t body_len; } ZirInstBoolBr; // lib/std/zig/Zir.zig: Inst.Declaration typedef struct { uint32_t src_hash_0; uint32_t src_hash_1; uint32_t src_hash_2; uint32_t src_hash_3; uint32_t flags_0; uint32_t flags_1; } ZirInstDeclaration; // lib/std/zig/Zir.zig: Inst.Call typedef struct { uint32_t flags; uint32_t callee; } ZirInstCall; // lib/std/zig/Zir.zig: Inst.FieldCall typedef struct { uint32_t flags; uint32_t obj_ptr; uint32_t field_name_start; } ZirInstFieldCall; // lib/std/zig/Zir.zig: Inst.TypeOfPeer typedef struct { uint32_t src_node; uint32_t body_len; uint32_t body_index; } ZirInstTypeOfPeer; // lib/std/zig/Zir.zig: Inst.BuiltinCall typedef struct { uint32_t flags; uint32_t modifier; uint32_t callee; uint32_t args; } ZirInstBuiltinCall; // lib/std/zig/Zir.zig: Inst.CondBr typedef struct { uint32_t condition; uint32_t then_body_len; uint32_t else_body_len; } ZirInstCondBr; // lib/std/zig/Zir.zig: Inst.Try typedef struct { uint32_t operand; uint32_t body_len; } ZirInstTry; // lib/std/zig/Zir.zig: Inst.PtrType typedef struct { uint32_t elem_type; uint32_t src_node; } ZirInstPtrType; // lib/std/zig/Zir.zig: Inst.ArrayTypeSentinel typedef struct { uint32_t len; uint32_t sentinel; uint32_t elem_type; } ZirInstArrayTypeSentinel; // lib/std/zig/Zir.zig: Inst.SliceStart typedef struct { uint32_t lhs; uint32_t start; } ZirInstSliceStart; // lib/std/zig/Zir.zig: Inst.SliceEnd typedef struct { uint32_t lhs; uint32_t start; uint32_t end; } ZirInstSliceEnd; // lib/std/zig/Zir.zig: Inst.SliceSentinel typedef struct { uint32_t lhs; uint32_t start; uint32_t end; uint32_t sentinel; } ZirInstSliceSentinel; // lib/std/zig/Zir.zig: Inst.SliceLength typedef struct { uint32_t lhs; uint32_t start; uint32_t len; uint32_t sentinel; uint32_t start_src_node_offset; } ZirInstSliceLength; // lib/std/zig/Zir.zig: Inst.Bin typedef struct { uint32_t lhs; uint32_t rhs; } ZirInstBin; // lib/std/zig/Zir.zig: Inst.BinNode typedef struct { uint32_t node; uint32_t lhs; uint32_t rhs; } ZirInstBinNode; // lib/std/zig/Zir.zig: Inst.UnNode typedef struct { uint32_t node; uint32_t operand; } ZirInstUnNode; // lib/std/zig/Zir.zig: Inst.ElemPtrImm typedef struct { uint32_t ptr; uint32_t index; } ZirInstElemPtrImm; // lib/std/zig/Zir.zig: Inst.ReifyPointer typedef struct { uint32_t node; uint32_t size; uint32_t attrs; uint32_t elem_ty; uint32_t sentinel; } ZirInstReifyPointer; // lib/std/zig/Zir.zig: Inst.ReifyFn typedef struct { uint32_t node; uint32_t param_types; uint32_t param_attrs; uint32_t ret_ty; uint32_t fn_attrs; } ZirInstReifyFn; // lib/std/zig/Zir.zig: Inst.ReifyStruct typedef struct { uint32_t src_line; uint32_t src_column; uint32_t node; uint32_t layout; uint32_t backing_ty; uint32_t field_names; uint32_t field_types; uint32_t field_attrs; } ZirInstReifyStruct; // lib/std/zig/Zir.zig: Inst.ReifyUnion typedef struct { uint32_t src_line; uint32_t src_column; uint32_t node; uint32_t layout; uint32_t arg_ty; uint32_t field_names; uint32_t field_types; uint32_t field_attrs; } ZirInstReifyUnion; // lib/std/zig/Zir.zig: Inst.ReifyEnum typedef struct { uint32_t src_line; uint32_t src_column; uint32_t node; uint32_t tag_ty; uint32_t mode; uint32_t field_names; uint32_t field_values; } ZirInstReifyEnum; // lib/std/zig/Zir.zig: Inst.ReifySpirvType typedef struct { uint32_t src_line; uint32_t src_column; uint32_t node; uint32_t operand; } ZirInstReifySpirvType; // lib/std/zig/Zir.zig: Inst.SwitchBlock typedef struct { uint32_t raw_operand; uint32_t bits; } ZirInstSwitchBlock; // lib/std/zig/Zir.zig: Inst.ArrayInitRefTy typedef struct { uint32_t ptr_ty; uint32_t elem_count; } ZirInstArrayInitRefTy; // lib/std/zig/Zir.zig: Inst.Field typedef struct { uint32_t lhs; uint32_t field_name_start; } ZirInstField; // lib/std/zig/Zir.zig: Inst.FieldNamed typedef struct { uint32_t lhs; uint32_t field_name; } ZirInstFieldNamed; // lib/std/zig/Zir.zig: Inst.As typedef struct { uint32_t dest_type; uint32_t operand; } ZirInstAs; // lib/std/zig/Zir.zig: Inst.StructDecl typedef struct { uint32_t fields_hash_0; uint32_t fields_hash_1; uint32_t fields_hash_2; uint32_t fields_hash_3; uint32_t src_line; uint32_t src_column; uint32_t src_node; } ZirInstStructDecl; // lib/std/zig/Zir.zig: Inst.EnumDecl typedef struct { uint32_t fields_hash_0; uint32_t fields_hash_1; uint32_t fields_hash_2; uint32_t fields_hash_3; uint32_t src_line; uint32_t src_column; uint32_t src_node; } ZirInstEnumDecl; // lib/std/zig/Zir.zig: Inst.UnionDecl typedef struct { uint32_t fields_hash_0; uint32_t fields_hash_1; uint32_t fields_hash_2; uint32_t fields_hash_3; uint32_t src_line; uint32_t src_column; uint32_t src_node; } ZirInstUnionDecl; // lib/std/zig/Zir.zig: Inst.OpaqueDecl typedef struct { uint32_t src_line; uint32_t src_column; uint32_t src_node; } ZirInstOpaqueDecl; // lib/std/zig/Zir.zig: Inst.TupleDecl typedef struct { uint32_t src_node; } ZirInstTupleDecl; // lib/std/zig/Zir.zig: Inst.ErrorSetDecl typedef struct { uint32_t fields_len; } ZirInstErrorSetDecl; // lib/std/zig/Zir.zig: Inst.Float128 typedef struct { uint32_t piece0; uint32_t piece1; uint32_t piece2; uint32_t piece3; } ZirInstFloat128; // lib/std/zig/Zir.zig: Inst.StructInit typedef struct { uint32_t abs_node; uint32_t src_line; uint32_t src_column; uint32_t fields_len; } ZirInstStructInit; // lib/std/zig/Zir.zig: Inst.StructInit.Item typedef struct { uint32_t field_type; uint32_t init; } ZirInstStructInitItem; // lib/std/zig/Zir.zig: Inst.StructInitAnon typedef struct { uint32_t abs_node; uint32_t src_line; uint32_t src_column; uint32_t fields_len; } ZirInstStructInitAnon; // lib/std/zig/Zir.zig: Inst.StructInitAnon.Item typedef struct { uint32_t field_name; uint32_t init; } ZirInstStructInitAnonItem; // lib/std/zig/Zir.zig: Inst.FieldType typedef struct { uint32_t container_type; uint32_t name_start; } ZirInstFieldType; // lib/std/zig/Zir.zig: Inst.FieldTypeRef typedef struct { uint32_t container_type; uint32_t field_name; } ZirInstFieldTypeRef; // lib/std/zig/Zir.zig: Inst.Cmpxchg typedef struct { uint32_t node; uint32_t ptr; uint32_t expected_value; uint32_t new_value; uint32_t success_order; uint32_t failure_order; } ZirInstCmpxchg; // lib/std/zig/Zir.zig: Inst.AtomicRmw typedef struct { uint32_t ptr; uint32_t operation; uint32_t operand; uint32_t ordering; } ZirInstAtomicRmw; // lib/std/zig/Zir.zig: Inst.UnionInit typedef struct { uint32_t union_type; uint32_t field_name; uint32_t init; } ZirInstUnionInit; // lib/std/zig/Zir.zig: Inst.AtomicStore typedef struct { uint32_t ptr; uint32_t operand; uint32_t ordering; } ZirInstAtomicStore; // lib/std/zig/Zir.zig: Inst.AtomicLoad typedef struct { uint32_t elem_type; uint32_t ptr; uint32_t ordering; } ZirInstAtomicLoad; // lib/std/zig/Zir.zig: Inst.MulAdd typedef struct { uint32_t mulend1; uint32_t mulend2; uint32_t addend; } ZirInstMulAdd; // lib/std/zig/Zir.zig: Inst.FieldParentPtr typedef struct { uint32_t src_node; uint32_t parent_ptr_type; uint32_t field_name; uint32_t field_ptr; } ZirInstFieldParentPtr; // lib/std/zig/Zir.zig: Inst.Shuffle typedef struct { uint32_t elem_type; uint32_t a; uint32_t b; uint32_t mask; } ZirInstShuffle; // lib/std/zig/Zir.zig: Inst.Select typedef struct { uint32_t node; uint32_t elem_type; uint32_t pred; uint32_t a; uint32_t b; } ZirInstSelect; // lib/std/zig/Zir.zig: Inst.Param typedef struct { uint32_t name; uint32_t type; } ZirInstParam; // lib/std/zig/Zir.zig: Inst.AllocExtended typedef struct { uint32_t src_node; } ZirInstAllocExtended; // lib/std/zig/Zir.zig: Inst.Export typedef struct { uint32_t exported; uint32_t options; } ZirInstExport; // lib/std/zig/Zir.zig: Inst.CompileErrors typedef struct { uint32_t items_len; } ZirInstCompileErrors; // lib/std/zig/Zir.zig: Inst.CompileErrors.Item typedef struct { uint32_t msg; uint32_t node; uint32_t token; uint32_t byte_offset; uint32_t notes; } ZirInstCompileErrorsItem; // lib/std/zig/Zir.zig: Inst.Imports typedef struct { uint32_t imports_len; } ZirInstImports; // lib/std/zig/Zir.zig: Inst.Imports.Item typedef struct { uint32_t name; uint32_t token; } ZirInstImportsItem; // lib/std/zig/Zir.zig: Inst.LineColumn typedef struct { uint32_t line; uint32_t column; } ZirInstLineColumn; // lib/std/zig/Zir.zig: Inst.ArrayInit typedef struct { uint32_t ty; uint32_t init_count; } ZirInstArrayInit; // lib/std/zig/Zir.zig: Inst.Src typedef struct { uint32_t node; uint32_t line; uint32_t column; } ZirInstSrc; // lib/std/zig/Zir.zig: Inst.ValidateDestructure typedef struct { uint32_t operand; uint32_t destructure_node; uint32_t expect_len; } ZirInstValidateDestructure; // lib/std/zig/Zir.zig: Inst.RestoreErrRetIndex typedef struct { uint32_t src_node; uint32_t block; uint32_t operand; } ZirInstRestoreErrRetIndex; // lib/std/zig/Zir.zig: Inst.Import typedef struct { uint32_t res_ty; uint32_t path; } ZirInstImport; // src/Type.zig: Class enum { TYPE_CLASS_NO_POSSIBLE_VALUE = 0, TYPE_CLASS_ONE_POSSIBLE_VALUE = 1, TYPE_CLASS_RUNTIME = 2, TYPE_CLASS_PARTIALLY_COMPTIME = 3, TYPE_CLASS_FULLY_COMPTIME = 4, }; // lib/std/zig/tokenizer.zig: Token.Tag #define TOKEN_FOREACH_TAG(TAG) \ TAG(TOKEN_INVALID) \ TAG(TOKEN_IDENTIFIER) \ TAG(TOKEN_STRING_LITERAL) \ TAG(TOKEN_MULTILINE_STRING_LITERAL_LINE) \ TAG(TOKEN_CHAR_LITERAL) \ TAG(TOKEN_EOF) \ TAG(TOKEN_BUILTIN) \ TAG(TOKEN_BANG) \ TAG(TOKEN_PIPE) \ TAG(TOKEN_PIPE_PIPE) \ TAG(TOKEN_PIPE_EQUAL) \ TAG(TOKEN_EQUAL) \ TAG(TOKEN_EQUAL_EQUAL) \ TAG(TOKEN_EQUAL_ANGLE_BRACKET_RIGHT) \ TAG(TOKEN_BANG_EQUAL) \ TAG(TOKEN_L_PAREN) \ TAG(TOKEN_R_PAREN) \ TAG(TOKEN_SEMICOLON) \ TAG(TOKEN_PERCENT) \ TAG(TOKEN_PERCENT_EQUAL) \ TAG(TOKEN_L_BRACE) \ TAG(TOKEN_R_BRACE) \ TAG(TOKEN_L_BRACKET) \ TAG(TOKEN_R_BRACKET) \ TAG(TOKEN_PERIOD) \ TAG(TOKEN_PERIOD_ASTERISK) \ TAG(TOKEN_ELLIPSIS2) \ TAG(TOKEN_ELLIPSIS3) \ TAG(TOKEN_CARET) \ TAG(TOKEN_CARET_EQUAL) \ TAG(TOKEN_PLUS) \ TAG(TOKEN_PLUS_PLUS) \ TAG(TOKEN_PLUS_EQUAL) \ TAG(TOKEN_PLUS_PERCENT) \ TAG(TOKEN_PLUS_PERCENT_EQUAL) \ TAG(TOKEN_PLUS_PIPE) \ TAG(TOKEN_PLUS_PIPE_EQUAL) \ TAG(TOKEN_MINUS) \ TAG(TOKEN_MINUS_EQUAL) \ TAG(TOKEN_MINUS_PERCENT) \ TAG(TOKEN_MINUS_PERCENT_EQUAL) \ TAG(TOKEN_MINUS_PIPE) \ TAG(TOKEN_MINUS_PIPE_EQUAL) \ TAG(TOKEN_ASTERISK) \ TAG(TOKEN_ASTERISK_EQUAL) \ TAG(TOKEN_ASTERISK_PERCENT) \ TAG(TOKEN_ASTERISK_PERCENT_EQUAL) \ TAG(TOKEN_ASTERISK_PIPE) \ TAG(TOKEN_ASTERISK_PIPE_EQUAL) \ TAG(TOKEN_ARROW) \ TAG(TOKEN_COLON) \ TAG(TOKEN_SLASH) \ TAG(TOKEN_SLASH_EQUAL) \ TAG(TOKEN_COMMA) \ TAG(TOKEN_AMPERSAND) \ TAG(TOKEN_AMPERSAND_EQUAL) \ TAG(TOKEN_QUESTION_MARK) \ TAG(TOKEN_ANGLE_BRACKET_LEFT) \ TAG(TOKEN_ANGLE_BRACKET_LEFT_EQUAL) \ TAG(TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT) \ TAG(TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_EQUAL) \ TAG(TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE) \ TAG(TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE_EQUAL) \ TAG(TOKEN_ANGLE_BRACKET_RIGHT) \ TAG(TOKEN_ANGLE_BRACKET_RIGHT_EQUAL) \ TAG(TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT) \ TAG(TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT_EQUAL) \ TAG(TOKEN_TILDE) \ TAG(TOKEN_NUMBER_LITERAL) \ TAG(TOKEN_DOC_COMMENT) \ TAG(TOKEN_CONTAINER_DOC_COMMENT) \ TAG(TOKEN_KEYWORD_ADDRSPACE) \ TAG(TOKEN_KEYWORD_ALIGN) \ TAG(TOKEN_KEYWORD_ALLOWZERO) \ TAG(TOKEN_KEYWORD_AND) \ TAG(TOKEN_KEYWORD_ANYFRAME) \ TAG(TOKEN_KEYWORD_ANYTYPE) \ TAG(TOKEN_KEYWORD_ASM) \ TAG(TOKEN_KEYWORD_BREAK) \ TAG(TOKEN_KEYWORD_CALLCONV) \ TAG(TOKEN_KEYWORD_CATCH) \ TAG(TOKEN_KEYWORD_COMPTIME) \ TAG(TOKEN_KEYWORD_CONST) \ TAG(TOKEN_KEYWORD_CONTINUE) \ TAG(TOKEN_KEYWORD_DEFER) \ TAG(TOKEN_KEYWORD_ELSE) \ TAG(TOKEN_KEYWORD_ENUM) \ TAG(TOKEN_KEYWORD_ERRDEFER) \ TAG(TOKEN_KEYWORD_ERROR) \ TAG(TOKEN_KEYWORD_EXPORT) \ TAG(TOKEN_KEYWORD_EXTERN) \ TAG(TOKEN_KEYWORD_FN) \ TAG(TOKEN_KEYWORD_FOR) \ TAG(TOKEN_KEYWORD_IF) \ TAG(TOKEN_KEYWORD_INLINE) \ TAG(TOKEN_KEYWORD_NOALIAS) \ TAG(TOKEN_KEYWORD_NOINLINE) \ TAG(TOKEN_KEYWORD_NOSUSPEND) \ TAG(TOKEN_KEYWORD_OPAQUE) \ TAG(TOKEN_KEYWORD_OR) \ TAG(TOKEN_KEYWORD_ORELSE) \ TAG(TOKEN_KEYWORD_PACKED) \ TAG(TOKEN_KEYWORD_PUB) \ TAG(TOKEN_KEYWORD_RESUME) \ TAG(TOKEN_KEYWORD_RETURN) \ TAG(TOKEN_KEYWORD_LINKSECTION) \ TAG(TOKEN_KEYWORD_STRUCT) \ TAG(TOKEN_KEYWORD_SUSPEND) \ TAG(TOKEN_KEYWORD_SWITCH) \ TAG(TOKEN_KEYWORD_TEST) \ TAG(TOKEN_KEYWORD_THREADLOCAL) \ TAG(TOKEN_KEYWORD_TRY) \ TAG(TOKEN_KEYWORD_UNION) \ TAG(TOKEN_KEYWORD_UNREACHABLE) \ TAG(TOKEN_KEYWORD_VAR) \ TAG(TOKEN_KEYWORD_VOLATILE) \ TAG(TOKEN_KEYWORD_WHILE) // lib/std/zig/tokenizer.zig: Tokenizer.State #define TOKENIZER_STATE_FOREACH_TAG(TAG) \ TAG(TOKENIZER_STATE_START) \ TAG(TOKENIZER_STATE_EXPECT_NEWLINE) \ TAG(TOKENIZER_STATE_IDENTIFIER) \ TAG(TOKENIZER_STATE_BUILTIN) \ TAG(TOKENIZER_STATE_STRING_LITERAL) \ TAG(TOKENIZER_STATE_STRING_LITERAL_BACKSLASH) \ TAG(TOKENIZER_STATE_MULTILINE_STRING_LITERAL_LINE) \ TAG(TOKENIZER_STATE_CHAR_LITERAL) \ TAG(TOKENIZER_STATE_CHAR_LITERAL_BACKSLASH) \ TAG(TOKENIZER_STATE_BACKSLASH) \ TAG(TOKENIZER_STATE_EQUAL) \ TAG(TOKENIZER_STATE_BANG) \ TAG(TOKENIZER_STATE_PIPE) \ TAG(TOKENIZER_STATE_MINUS) \ TAG(TOKENIZER_STATE_MINUS_PERCENT) \ TAG(TOKENIZER_STATE_MINUS_PIPE) \ TAG(TOKENIZER_STATE_ASTERISK) \ TAG(TOKENIZER_STATE_ASTERISK_PERCENT) \ TAG(TOKENIZER_STATE_ASTERISK_PIPE) \ TAG(TOKENIZER_STATE_SLASH) \ TAG(TOKENIZER_STATE_LINE_COMMENT_START) \ TAG(TOKENIZER_STATE_LINE_COMMENT) \ TAG(TOKENIZER_STATE_DOC_COMMENT_START) \ TAG(TOKENIZER_STATE_DOC_COMMENT) \ TAG(TOKENIZER_STATE_INT) \ TAG(TOKENIZER_STATE_INT_EXPONENT) \ TAG(TOKENIZER_STATE_INT_PERIOD) \ TAG(TOKENIZER_STATE_FLOAT) \ TAG(TOKENIZER_STATE_FLOAT_EXPONENT) \ TAG(TOKENIZER_STATE_AMPERSAND) \ TAG(TOKENIZER_STATE_CARET) \ TAG(TOKENIZER_STATE_PERCENT) \ TAG(TOKENIZER_STATE_PLUS) \ TAG(TOKENIZER_STATE_PLUS_PERCENT) \ TAG(TOKENIZER_STATE_PLUS_PIPE) \ TAG(TOKENIZER_STATE_ANGLE_BRACKET_LEFT) \ TAG(TOKENIZER_STATE_ANGLE_BRACKET_ANGLE_BRACKET_LEFT) \ TAG(TOKENIZER_STATE_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE) \ TAG(TOKENIZER_STATE_ANGLE_BRACKET_RIGHT) \ TAG(TOKENIZER_STATE_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT) \ TAG(TOKENIZER_STATE_PERIOD) \ TAG(TOKENIZER_STATE_PERIOD_2) \ TAG(TOKENIZER_STATE_SAW_AT_SIGN) \ TAG(TOKENIZER_STATE_INVALID) // lib/std/zig.zig: SimpleComptimeReason enum { REASON_OPERAND_SET_EVAL_BRANCH_QUOTA = 0, REASON_OPERAND_SET_FLOAT_MODE = 1, REASON_OPERAND_BRANCH_HINT = 2, REASON_OPERAND_SET_RUNTIME_SAFETY = 3, REASON_OPERAND_EMBED_FILE = 4, REASON_OPERAND_SHUFFLE_MASK = 5, REASON_OPERAND_ATOMIC_RMW_OPERATION = 6, REASON_OPERAND_REDUCE_OPERATION = 7, REASON_EXPORT_TARGET = 8, REASON_EXPORT_OPTIONS = 9, REASON_EXTERN_OPTIONS = 10, REASON_PREFETCH_OPTIONS = 11, REASON_CALL_MODIFIER = 12, REASON_COMPILE_ERROR_STRING = 13, REASON_INLINE_ASSEMBLY_CODE = 14, REASON_ATOMIC_ORDER = 15, REASON_SLICE_CAT_OPERAND = 16, REASON_INLINE_CALL_TARGET = 17, REASON_GENERIC_CALL_TARGET = 18, REASON_WASM_MEMORY_INDEX = 19, REASON_WORK_GROUP_DIM_INDEX = 20, REASON_CLOBBER = 21, REASON_TYPE = 22, REASON_INT_SIGNEDNESS = 23, REASON_INT_BIT_WIDTH = 24, REASON_ARRAY_SENTINEL = 25, REASON_ARRAY_LENGTH = 26, REASON_POINTER_SIZE = 27, REASON_POINTER_ATTRS = 28, REASON_POINTER_SENTINEL = 29, REASON_SLICE_SENTINEL = 30, REASON_VECTOR_LENGTH = 31, REASON_FN_RET_TY = 32, REASON_FN_PARAM_TYPES = 33, REASON_FN_PARAM_ATTRS = 34, REASON_FN_ATTRS = 35, REASON_STRUCT_LAYOUT = 36, REASON_STRUCT_FIELD_NAMES = 37, REASON_STRUCT_FIELD_TYPES = 38, REASON_STRUCT_FIELD_ATTRS = 39, REASON_UNION_LAYOUT = 40, REASON_UNION_FIELD_NAMES = 41, REASON_UNION_FIELD_TYPES = 42, REASON_UNION_FIELD_ATTRS = 43, REASON_TUPLE_FIELD_TYPES = 44, REASON_ENUM_FIELD_NAMES = 45, REASON_ENUM_FIELD_VALUES = 46, REASON_UNION_ENUM_TAG_TYPE = 47, REASON_ENUM_INT_TAG_TYPE = 48, REASON_PACKED_STRUCT_BACKING_INT_TYPE = 49, REASON_PACKED_UNION_BACKING_INT_TYPE = 50, REASON_DECL_NAME = 51, REASON_FIELD_NAME = 52, REASON_TUPLE_FIELD_INDEX = 53, REASON_CONTAINER_VAR_INIT = 54, REASON_CALLCONV = 55, REASON_ALIGN = 56, REASON_ADDRSPACE = 57, REASON_LINKSECTION = 58, REASON_COMPTIME_KEYWORD = 59, REASON_COMPTIME_CALL_MODIFIER = 60, REASON_INLINE_LOOP_OPERAND = 61, REASON_SWITCH_ITEM = 62, REASON_TUPLE_FIELD_DEFAULT_VALUE = 63, REASON_STRUCT_FIELD_DEFAULT_VALUE = 64, REASON_ENUM_FIELD_TAG_VALUE = 65, REASON_SLICE_SINGLE_ITEM_PTR_BOUNDS = 66, REASON_STORED_TO_COMPTIME_FIELD = 67, REASON_STORED_TO_COMPTIME_VAR = 68, REASON_CASTED_TO_COMPTIME_INT = 69, REASON_CASTED_TO_COMPTIME_FLOAT = 70, REASON_STD_LANG_DECL = 71, }; // lib/std/zig/number_literal.zig: Error typedef enum { NUMBER_LITERAL_ERROR_LEADING_ZERO = 0, NUMBER_LITERAL_ERROR_DIGIT_AFTER_BASE = 1, NUMBER_LITERAL_ERROR_UPPER_CASE_BASE = 2, NUMBER_LITERAL_ERROR_INVALID_FLOAT_BASE = 3, NUMBER_LITERAL_ERROR_REPEATED_UNDERSCORE = 4, NUMBER_LITERAL_ERROR_INVALID_UNDERSCORE_AFTER_SPECIAL = 5, NUMBER_LITERAL_ERROR_INVALID_DIGIT = 6, NUMBER_LITERAL_ERROR_INVALID_DIGIT_EXPONENT = 7, NUMBER_LITERAL_ERROR_DUPLICATE_PERIOD = 8, NUMBER_LITERAL_ERROR_DUPLICATE_EXPONENT = 9, NUMBER_LITERAL_ERROR_EXPONENT_AFTER_UNDERSCORE = 10, NUMBER_LITERAL_ERROR_SPECIAL_AFTER_UNDERSCORE = 11, NUMBER_LITERAL_ERROR_TRAILING_SPECIAL = 12, NUMBER_LITERAL_ERROR_TRAILING_UNDERSCORE = 13, NUMBER_LITERAL_ERROR_INVALID_CHARACTER = 14, NUMBER_LITERAL_ERROR_INVALID_EXPONENT_SIGN = 15, NUMBER_LITERAL_ERROR_PERIOD_AFTER_EXPONENT = 16, } NumberLiteralError; // src/Zcu.zig: StdLangDecl enum { STD_LANG_DECL_SIGNEDNESS = 0, STD_LANG_DECL_ADDRESS_SPACE = 1, STD_LANG_DECL_CALLING_CONVENTION = 2, STD_LANG_DECL_RETURN_ERROR = 3, STD_LANG_DECL_STACK_TRACE = 4, STD_LANG_DECL_SOURCE_LOCATION = 5, STD_LANG_DECL_CALL_MODIFIER = 6, STD_LANG_DECL_ATOMIC_ORDER = 7, STD_LANG_DECL_ATOMIC_RMW_OP = 8, STD_LANG_DECL_REDUCE_OP = 9, STD_LANG_DECL_FLOAT_MODE = 10, STD_LANG_DECL_PREFETCH_OPTIONS = 11, STD_LANG_DECL_EXPORT_OPTIONS = 12, STD_LANG_DECL_EXTERN_OPTIONS = 13, STD_LANG_DECL_BRANCH_HINT = 14, STD_LANG_DECL_TYPE = 15, STD_LANG_DECL_TYPE_FN = 16, STD_LANG_DECL_TYPE_FN_PARAM_ATTRIBUTES = 17, STD_LANG_DECL_TYPE_FN_ATTRIBUTES = 18, STD_LANG_DECL_TYPE_INT = 19, STD_LANG_DECL_TYPE_FLOAT = 20, STD_LANG_DECL_TYPE_POINTER = 21, STD_LANG_DECL_TYPE_POINTER_SIZE = 22, STD_LANG_DECL_TYPE_POINTER_ATTRIBUTES = 23, STD_LANG_DECL_TYPE_ARRAY = 24, STD_LANG_DECL_TYPE_VECTOR = 25, STD_LANG_DECL_TYPE_OPTIONAL = 26, STD_LANG_DECL_TYPE_ERROR_UNION = 27, STD_LANG_DECL_TYPE_ERROR_SET = 28, STD_LANG_DECL_TYPE_ENUM = 29, STD_LANG_DECL_TYPE_ENUM_MODE = 30, STD_LANG_DECL_TYPE_UNION = 31, STD_LANG_DECL_TYPE_UNION_FIELD_ATTRIBUTES = 32, STD_LANG_DECL_TYPE_STRUCT = 33, STD_LANG_DECL_TYPE_STRUCT_FIELD_ATTRIBUTES = 34, STD_LANG_DECL_TYPE_CONTAINER_LAYOUT = 35, STD_LANG_DECL_TYPE_OPAQUE = 36, STD_LANG_DECL_TYPE_SPIRV = 37, STD_LANG_DECL_TYPE_SPIRV_IMAGE = 38, STD_LANG_DECL_TYPE_SPIRV_IMAGE_USAGE = 39, STD_LANG_DECL_TYPE_SPIRV_IMAGE_FORMAT = 40, STD_LANG_DECL_TYPE_SPIRV_IMAGE_DIMENSIONALITY = 41, STD_LANG_DECL_TYPE_SPIRV_IMAGE_DEPTH = 42, STD_LANG_DECL_TYPE_SPIRV_IMAGE_ACCESS = 43, STD_LANG_DECL_PANIC = 44, STD_LANG_DECL_PANIC_CALL = 45, STD_LANG_DECL_PANIC_SENTINEL_MISMATCH = 46, STD_LANG_DECL_PANIC_UNWRAP_ERROR = 47, STD_LANG_DECL_PANIC_OUT_OF_BOUNDS = 48, STD_LANG_DECL_PANIC_START_GREATER_THAN_END = 49, STD_LANG_DECL_PANIC_INACTIVE_UNION_FIELD = 50, STD_LANG_DECL_PANIC_SLICE_CAST_LEN_REMAINDER = 51, STD_LANG_DECL_PANIC_REACHED_UNREACHABLE = 52, STD_LANG_DECL_PANIC_UNWRAP_NULL = 53, STD_LANG_DECL_PANIC_CAST_TO_NULL = 54, STD_LANG_DECL_PANIC_INCORRECT_ALIGNMENT = 55, STD_LANG_DECL_PANIC_INVALID_ERROR_CODE = 56, STD_LANG_DECL_PANIC_UNEXPECTED_ERROR_CODE = 57, STD_LANG_DECL_PANIC_INTEGER_OUT_OF_BOUNDS = 58, STD_LANG_DECL_PANIC_INTEGER_OVERFLOW = 59, STD_LANG_DECL_PANIC_SHL_OVERFLOW = 60, STD_LANG_DECL_PANIC_SHR_OVERFLOW = 61, STD_LANG_DECL_PANIC_DIVIDE_BY_ZERO = 62, STD_LANG_DECL_PANIC_EXACT_DIVISION_REMAINDER = 63, STD_LANG_DECL_PANIC_INTEGER_PART_OUT_OF_BOUNDS = 64, STD_LANG_DECL_PANIC_CORRUPT_SWITCH = 65, STD_LANG_DECL_PANIC_SHIFT_RHS_TOO_BIG = 66, STD_LANG_DECL_PANIC_INVALID_ENUM_VALUE = 67, STD_LANG_DECL_PANIC_FOR_LEN_MISMATCH = 68, STD_LANG_DECL_PANIC_COPY_LEN_MISMATCH = 69, STD_LANG_DECL_PANIC_MEMCPY_ALIAS = 70, STD_LANG_DECL_PANIC_NORETURN_RETURNED = 71, STD_LANG_DECL_PANIC_LOAD_UNINSTANTIABLE_TYPE = 72, STD_LANG_DECL_VA_LIST = 73, STD_LANG_DECL_ASSEMBLY = 74, STD_LANG_DECL_ASSEMBLY_CLOBBERS = 75, }; #define STD_LANG_DECL_NAMES(NAME) \ NAME(STD_LANG_DECL_SIGNEDNESS, "Signedness") \ NAME(STD_LANG_DECL_ADDRESS_SPACE, "AddressSpace") \ NAME(STD_LANG_DECL_CALLING_CONVENTION, "CallingConvention") \ NAME(STD_LANG_DECL_RETURN_ERROR, "returnError") \ NAME(STD_LANG_DECL_STACK_TRACE, "StackTrace") \ NAME(STD_LANG_DECL_SOURCE_LOCATION, "SourceLocation") \ NAME(STD_LANG_DECL_CALL_MODIFIER, "CallModifier") \ NAME(STD_LANG_DECL_ATOMIC_ORDER, "AtomicOrder") \ NAME(STD_LANG_DECL_ATOMIC_RMW_OP, "AtomicRmwOp") \ NAME(STD_LANG_DECL_REDUCE_OP, "ReduceOp") \ NAME(STD_LANG_DECL_FLOAT_MODE, "FloatMode") \ NAME(STD_LANG_DECL_PREFETCH_OPTIONS, "PrefetchOptions") \ NAME(STD_LANG_DECL_EXPORT_OPTIONS, "ExportOptions") \ NAME(STD_LANG_DECL_EXTERN_OPTIONS, "ExternOptions") \ NAME(STD_LANG_DECL_BRANCH_HINT, "BranchHint") \ NAME(STD_LANG_DECL_TYPE, "Type") \ NAME(STD_LANG_DECL_TYPE_FN, "Type.Fn") \ NAME(STD_LANG_DECL_TYPE_FN_PARAM_ATTRIBUTES, "Type.Fn.ParamAttributes") \ NAME(STD_LANG_DECL_TYPE_FN_ATTRIBUTES, "Type.Fn.Attributes") \ NAME(STD_LANG_DECL_TYPE_INT, "Type.Int") \ NAME(STD_LANG_DECL_TYPE_FLOAT, "Type.Float") \ NAME(STD_LANG_DECL_TYPE_POINTER, "Type.Pointer") \ NAME(STD_LANG_DECL_TYPE_POINTER_SIZE, "Type.Pointer.Size") \ NAME(STD_LANG_DECL_TYPE_POINTER_ATTRIBUTES, "Type.Pointer.Attributes") \ NAME(STD_LANG_DECL_TYPE_ARRAY, "Type.Array") \ NAME(STD_LANG_DECL_TYPE_VECTOR, "Type.Vector") \ NAME(STD_LANG_DECL_TYPE_OPTIONAL, "Type.Optional") \ NAME(STD_LANG_DECL_TYPE_ERROR_UNION, "Type.ErrorUnion") \ NAME(STD_LANG_DECL_TYPE_ERROR_SET, "Type.ErrorSet") \ NAME(STD_LANG_DECL_TYPE_ENUM, "Type.Enum") \ NAME(STD_LANG_DECL_TYPE_ENUM_MODE, "Type.Enum.Mode") \ NAME(STD_LANG_DECL_TYPE_UNION, "Type.Union") \ NAME(STD_LANG_DECL_TYPE_UNION_FIELD_ATTRIBUTES, "Type.Union.FieldAttributes") \ NAME(STD_LANG_DECL_TYPE_STRUCT, "Type.Struct") \ NAME(STD_LANG_DECL_TYPE_STRUCT_FIELD_ATTRIBUTES, "Type.Struct.FieldAttributes") \ NAME(STD_LANG_DECL_TYPE_CONTAINER_LAYOUT, "Type.ContainerLayout") \ NAME(STD_LANG_DECL_TYPE_OPAQUE, "Type.Opaque") \ NAME(STD_LANG_DECL_TYPE_SPIRV, "Type.Spirv") \ NAME(STD_LANG_DECL_TYPE_SPIRV_IMAGE, "Type.Spirv.Image") \ NAME(STD_LANG_DECL_TYPE_SPIRV_IMAGE_USAGE, "Type.Spirv.Image.Usage") \ NAME(STD_LANG_DECL_TYPE_SPIRV_IMAGE_FORMAT, "Type.Spirv.Image.Format") \ NAME(STD_LANG_DECL_TYPE_SPIRV_IMAGE_DIMENSIONALITY, "Type.Spirv.Image.Dimensionality") \ NAME(STD_LANG_DECL_TYPE_SPIRV_IMAGE_DEPTH, "Type.Spirv.Image.Depth") \ NAME(STD_LANG_DECL_TYPE_SPIRV_IMAGE_ACCESS, "Type.Spirv.Image.Access") \ NAME(STD_LANG_DECL_PANIC, "panic") \ NAME(STD_LANG_DECL_PANIC_CALL, "panic.call") \ NAME(STD_LANG_DECL_PANIC_SENTINEL_MISMATCH, "panic.sentinelMismatch") \ NAME(STD_LANG_DECL_PANIC_UNWRAP_ERROR, "panic.unwrapError") \ NAME(STD_LANG_DECL_PANIC_OUT_OF_BOUNDS, "panic.outOfBounds") \ NAME(STD_LANG_DECL_PANIC_START_GREATER_THAN_END, "panic.startGreaterThanEnd") \ NAME(STD_LANG_DECL_PANIC_INACTIVE_UNION_FIELD, "panic.inactiveUnionField") \ NAME(STD_LANG_DECL_PANIC_SLICE_CAST_LEN_REMAINDER, "panic.sliceCastLenRemainder") \ NAME(STD_LANG_DECL_PANIC_REACHED_UNREACHABLE, "panic.reachedUnreachable") \ NAME(STD_LANG_DECL_PANIC_UNWRAP_NULL, "panic.unwrapNull") \ NAME(STD_LANG_DECL_PANIC_CAST_TO_NULL, "panic.castToNull") \ NAME(STD_LANG_DECL_PANIC_INCORRECT_ALIGNMENT, "panic.incorrectAlignment") \ NAME(STD_LANG_DECL_PANIC_INVALID_ERROR_CODE, "panic.invalidErrorCode") \ NAME(STD_LANG_DECL_PANIC_UNEXPECTED_ERROR_CODE, "panic.unexpectedErrorCode") \ NAME(STD_LANG_DECL_PANIC_INTEGER_OUT_OF_BOUNDS, "panic.integerOutOfBounds") \ NAME(STD_LANG_DECL_PANIC_INTEGER_OVERFLOW, "panic.integerOverflow") \ NAME(STD_LANG_DECL_PANIC_SHL_OVERFLOW, "panic.shlOverflow") \ NAME(STD_LANG_DECL_PANIC_SHR_OVERFLOW, "panic.shrOverflow") \ NAME(STD_LANG_DECL_PANIC_DIVIDE_BY_ZERO, "panic.divideByZero") \ NAME(STD_LANG_DECL_PANIC_EXACT_DIVISION_REMAINDER, "panic.exactDivisionRemainder") \ NAME(STD_LANG_DECL_PANIC_INTEGER_PART_OUT_OF_BOUNDS, "panic.integerPartOutOfBounds") \ NAME(STD_LANG_DECL_PANIC_CORRUPT_SWITCH, "panic.corruptSwitch") \ NAME(STD_LANG_DECL_PANIC_SHIFT_RHS_TOO_BIG, "panic.shiftRhsTooBig") \ NAME(STD_LANG_DECL_PANIC_INVALID_ENUM_VALUE, "panic.invalidEnumValue") \ NAME(STD_LANG_DECL_PANIC_FOR_LEN_MISMATCH, "panic.forLenMismatch") \ NAME(STD_LANG_DECL_PANIC_COPY_LEN_MISMATCH, "panic.copyLenMismatch") \ NAME(STD_LANG_DECL_PANIC_MEMCPY_ALIAS, "panic.memcpyAlias") \ NAME(STD_LANG_DECL_PANIC_NORETURN_RETURNED, "panic.noreturnReturned") \ NAME(STD_LANG_DECL_PANIC_LOAD_UNINSTANTIABLE_TYPE, "panic.loadUninstantiableType") \ NAME(STD_LANG_DECL_VA_LIST, "VaList") \ NAME(STD_LANG_DECL_ASSEMBLY, "assembly") \ NAME(STD_LANG_DECL_ASSEMBLY_CLOBBERS, "assembly.Clobbers") // lib/std/zig/BuiltinFn.zig: Tag #define BUILTIN_FN_FOREACH_TAG(TAG) \ TAG(BFN_add_with_overflow) \ TAG(BFN_addrspace_cast) \ TAG(BFN_align_cast) \ TAG(BFN_align_of) \ TAG(BFN_as) \ TAG(BFN_atomic_load) \ TAG(BFN_atomic_rmw) \ TAG(BFN_atomic_store) \ TAG(BFN_bit_cast) \ TAG(BFN_bit_offset_of) \ TAG(BFN_int_from_bool) \ TAG(BFN_bit_size_of) \ TAG(BFN_branch_hint) \ TAG(BFN_breakpoint) \ TAG(BFN_disable_instrumentation) \ TAG(BFN_disable_intrinsics) \ TAG(BFN_mul_add) \ TAG(BFN_byte_swap) \ TAG(BFN_bit_reverse) \ TAG(BFN_offset_of) \ TAG(BFN_call) \ TAG(BFN_clz) \ TAG(BFN_cmpxchg_strong) \ TAG(BFN_cmpxchg_weak) \ TAG(BFN_compile_error) \ TAG(BFN_compile_log) \ TAG(BFN_const_cast) \ TAG(BFN_ctz) \ TAG(BFN_c_va_arg) \ TAG(BFN_c_va_copy) \ TAG(BFN_c_va_end) \ TAG(BFN_c_va_start) \ TAG(BFN_div_ceil) \ TAG(BFN_div_exact) \ TAG(BFN_div_floor) \ TAG(BFN_div_trunc) \ TAG(BFN_embed_file) \ TAG(BFN_int_from_enum) \ TAG(BFN_error_name) \ TAG(BFN_error_return_trace) \ TAG(BFN_int_from_error) \ TAG(BFN_error_cast) \ TAG(BFN_export) \ TAG(BFN_extern) \ TAG(BFN_field) \ TAG(BFN_field_parent_ptr) \ TAG(BFN_FieldType) \ TAG(BFN_float_cast) \ TAG(BFN_int_from_float) \ TAG(BFN_frame) \ TAG(BFN_Frame) \ TAG(BFN_frame_address) \ TAG(BFN_has_decl) \ TAG(BFN_has_field) \ TAG(BFN_import) \ TAG(BFN_in_comptime) \ TAG(BFN_int_cast) \ TAG(BFN_backing_int) \ TAG(BFN_from_backing_int) \ TAG(BFN_enum_from_int) \ TAG(BFN_error_from_int) \ TAG(BFN_float_from_int) \ TAG(BFN_ptr_from_int) \ TAG(BFN_max) \ TAG(BFN_memcpy) \ TAG(BFN_memset) \ TAG(BFN_memmove) \ TAG(BFN_min) \ TAG(BFN_wasm_memory_size) \ TAG(BFN_wasm_memory_grow) \ TAG(BFN_mod) \ TAG(BFN_mul_with_overflow) \ TAG(BFN_panic) \ TAG(BFN_pop_count) \ TAG(BFN_prefetch) \ TAG(BFN_ptr_cast) \ TAG(BFN_int_from_ptr) \ TAG(BFN_rem) \ TAG(BFN_return_address) \ TAG(BFN_select) \ TAG(BFN_set_eval_branch_quota) \ TAG(BFN_set_float_mode) \ TAG(BFN_set_runtime_safety) \ TAG(BFN_shl_exact) \ TAG(BFN_shl_with_overflow) \ TAG(BFN_shr_exact) \ TAG(BFN_shuffle) \ TAG(BFN_size_of) \ TAG(BFN_splat) \ TAG(BFN_reduce) \ TAG(BFN_src) \ TAG(BFN_sqrt) \ TAG(BFN_sin) \ TAG(BFN_cos) \ TAG(BFN_tan) \ TAG(BFN_exp) \ TAG(BFN_exp2) \ TAG(BFN_log) \ TAG(BFN_log2) \ TAG(BFN_log10) \ TAG(BFN_abs) \ TAG(BFN_floor) \ TAG(BFN_ceil) \ TAG(BFN_trunc) \ TAG(BFN_round) \ TAG(BFN_sub_with_overflow) \ TAG(BFN_tag_name) \ TAG(BFN_This) \ TAG(BFN_trap) \ TAG(BFN_truncate) \ TAG(BFN_EnumLiteral) \ TAG(BFN_Int) \ TAG(BFN_Tuple) \ TAG(BFN_Pointer) \ TAG(BFN_Fn) \ TAG(BFN_Struct) \ TAG(BFN_Union) \ TAG(BFN_Enum) \ TAG(BFN_SpirvType) \ TAG(BFN_type_info) \ TAG(BFN_type_name) \ TAG(BFN_TypeOf) \ TAG(BFN_union_init) \ TAG(BFN_Vector) \ TAG(BFN_volatile_cast) \ TAG(BFN_work_item_id) \ TAG(BFN_work_group_size) \ TAG(BFN_work_group_id) // lib/std/zig/BuiltinFn.zig: EvalToError typedef enum { BUILTIN_FN_EVAL_TO_ERROR_NEVER = 0, BUILTIN_FN_EVAL_TO_ERROR_ALWAYS = 1, BUILTIN_FN_EVAL_TO_ERROR_MAYBE = 2, } BuiltinFnEvalToError; // lib/std/zig/Zoir.zig: Node typedef enum { ZOIR_NODE_TRUE = 0, ZOIR_NODE_FALSE = 1, ZOIR_NODE_NULL = 2, ZOIR_NODE_POS_INF = 3, ZOIR_NODE_NEG_INF = 4, ZOIR_NODE_NAN = 5, ZOIR_NODE_INT_LITERAL = 6, ZOIR_NODE_FLOAT_LITERAL = 7, ZOIR_NODE_CHAR_LITERAL = 8, ZOIR_NODE_ENUM_LITERAL = 9, ZOIR_NODE_STRING_LITERAL = 10, ZOIR_NODE_EMPTY_LITERAL = 11, ZOIR_NODE_ARRAY_LITERAL = 12, ZOIR_NODE_STRUCT_LITERAL = 13, } ZoirNodeTag; // lib/std/zig/Zoir.zig: Node.Repr.Tag typedef enum { ZOIR_NODE_REPR_TRUE = 0, ZOIR_NODE_REPR_FALSE = 1, ZOIR_NODE_REPR_NULL = 2, ZOIR_NODE_REPR_POS_INF = 3, ZOIR_NODE_REPR_NEG_INF = 4, ZOIR_NODE_REPR_NAN = 5, ZOIR_NODE_REPR_INT_LITERAL_SMALL = 6, ZOIR_NODE_REPR_INT_LITERAL_POS = 7, ZOIR_NODE_REPR_INT_LITERAL_NEG = 8, ZOIR_NODE_REPR_FLOAT_LITERAL_SMALL = 9, ZOIR_NODE_REPR_FLOAT_LITERAL = 10, ZOIR_NODE_REPR_CHAR_LITERAL = 11, ZOIR_NODE_REPR_ENUM_LITERAL = 12, ZOIR_NODE_REPR_STRING_LITERAL = 13, ZOIR_NODE_REPR_STRING_LITERAL_NULL = 14, ZOIR_NODE_REPR_EMPTY_LITERAL = 15, ZOIR_NODE_REPR_ARRAY_LITERAL = 16, ZOIR_NODE_REPR_STRUCT_LITERAL = 17, } ZoirNodeReprTag; // lib/std/zig/Ast.zig: Mode typedef enum { AST_MODE_ZIG = 0, AST_MODE_ZON = 1, } AstMode; // lib/std/zig/Ast.zig: Node.Tag #define AST_NODE_FOREACH_TAG(TAG) \ TAG(AST_NODE_ROOT) \ TAG(AST_NODE_TEST_DECL) \ TAG(AST_NODE_GLOBAL_VAR_DECL) \ TAG(AST_NODE_LOCAL_VAR_DECL) \ TAG(AST_NODE_SIMPLE_VAR_DECL) \ TAG(AST_NODE_ALIGNED_VAR_DECL) \ TAG(AST_NODE_ERRDEFER) \ TAG(AST_NODE_DEFER) \ TAG(AST_NODE_CATCH) \ TAG(AST_NODE_FIELD_ACCESS) \ TAG(AST_NODE_UNWRAP_OPTIONAL) \ TAG(AST_NODE_EQUAL_EQUAL) \ TAG(AST_NODE_BANG_EQUAL) \ TAG(AST_NODE_LESS_THAN) \ TAG(AST_NODE_GREATER_THAN) \ TAG(AST_NODE_LESS_OR_EQUAL) \ TAG(AST_NODE_GREATER_OR_EQUAL) \ TAG(AST_NODE_ASSIGN_MUL) \ TAG(AST_NODE_ASSIGN_DIV) \ TAG(AST_NODE_ASSIGN_MOD) \ TAG(AST_NODE_ASSIGN_ADD) \ TAG(AST_NODE_ASSIGN_SUB) \ TAG(AST_NODE_ASSIGN_SHL) \ TAG(AST_NODE_ASSIGN_SHL_SAT) \ TAG(AST_NODE_ASSIGN_SHR) \ TAG(AST_NODE_ASSIGN_BIT_AND) \ TAG(AST_NODE_ASSIGN_BIT_XOR) \ TAG(AST_NODE_ASSIGN_BIT_OR) \ TAG(AST_NODE_ASSIGN_MUL_WRAP) \ TAG(AST_NODE_ASSIGN_ADD_WRAP) \ TAG(AST_NODE_ASSIGN_SUB_WRAP) \ TAG(AST_NODE_ASSIGN_MUL_SAT) \ TAG(AST_NODE_ASSIGN_ADD_SAT) \ TAG(AST_NODE_ASSIGN_SUB_SAT) \ TAG(AST_NODE_ASSIGN) \ TAG(AST_NODE_ASSIGN_DESTRUCTURE) \ TAG(AST_NODE_MERGE_ERROR_SETS) \ TAG(AST_NODE_MUL) \ TAG(AST_NODE_DIV) \ TAG(AST_NODE_MOD) \ TAG(AST_NODE_MUL_WRAP) \ TAG(AST_NODE_MUL_SAT) \ TAG(AST_NODE_ADD) \ TAG(AST_NODE_SUB) \ TAG(AST_NODE_ARRAY_CAT) \ TAG(AST_NODE_ADD_WRAP) \ TAG(AST_NODE_SUB_WRAP) \ TAG(AST_NODE_ADD_SAT) \ TAG(AST_NODE_SUB_SAT) \ TAG(AST_NODE_SHL) \ TAG(AST_NODE_SHL_SAT) \ TAG(AST_NODE_SHR) \ TAG(AST_NODE_BIT_AND) \ TAG(AST_NODE_BIT_XOR) \ TAG(AST_NODE_BIT_OR) \ TAG(AST_NODE_ORELSE) \ TAG(AST_NODE_BOOL_AND) \ TAG(AST_NODE_BOOL_OR) \ TAG(AST_NODE_BOOL_NOT) \ TAG(AST_NODE_NEGATION) \ TAG(AST_NODE_BIT_NOT) \ TAG(AST_NODE_NEGATION_WRAP) \ TAG(AST_NODE_ADDRESS_OF) \ TAG(AST_NODE_TRY) \ TAG(AST_NODE_OPTIONAL_TYPE) \ TAG(AST_NODE_ARRAY_TYPE) \ TAG(AST_NODE_ARRAY_TYPE_SENTINEL) \ TAG(AST_NODE_PTR_TYPE_ALIGNED) \ TAG(AST_NODE_PTR_TYPE_SENTINEL) \ TAG(AST_NODE_PTR_TYPE) \ TAG(AST_NODE_PTR_TYPE_BIT_RANGE) \ TAG(AST_NODE_SLICE_OPEN) \ TAG(AST_NODE_SLICE) \ TAG(AST_NODE_SLICE_SENTINEL) \ TAG(AST_NODE_DEREF) \ TAG(AST_NODE_ARRAY_ACCESS) \ TAG(AST_NODE_ARRAY_INIT_ONE) \ TAG(AST_NODE_ARRAY_INIT_ONE_COMMA) \ TAG(AST_NODE_ARRAY_INIT_DOT_TWO) \ TAG(AST_NODE_ARRAY_INIT_DOT_TWO_COMMA) \ TAG(AST_NODE_ARRAY_INIT_DOT) \ TAG(AST_NODE_ARRAY_INIT_DOT_COMMA) \ TAG(AST_NODE_ARRAY_INIT) \ TAG(AST_NODE_ARRAY_INIT_COMMA) \ TAG(AST_NODE_STRUCT_INIT_ONE) \ TAG(AST_NODE_STRUCT_INIT_ONE_COMMA) \ TAG(AST_NODE_STRUCT_INIT_DOT_TWO) \ TAG(AST_NODE_STRUCT_INIT_DOT_TWO_COMMA) \ TAG(AST_NODE_STRUCT_INIT_DOT) \ TAG(AST_NODE_STRUCT_INIT_DOT_COMMA) \ TAG(AST_NODE_STRUCT_INIT) \ TAG(AST_NODE_STRUCT_INIT_COMMA) \ TAG(AST_NODE_CALL_ONE) \ TAG(AST_NODE_CALL_ONE_COMMA) \ TAG(AST_NODE_CALL) \ TAG(AST_NODE_CALL_COMMA) \ TAG(AST_NODE_SWITCH) \ TAG(AST_NODE_SWITCH_COMMA) \ TAG(AST_NODE_SWITCH_CASE_ONE) \ TAG(AST_NODE_SWITCH_CASE_INLINE_ONE) \ TAG(AST_NODE_SWITCH_CASE) \ TAG(AST_NODE_SWITCH_CASE_INLINE) \ TAG(AST_NODE_SWITCH_RANGE) \ TAG(AST_NODE_WHILE_SIMPLE) \ TAG(AST_NODE_WHILE_CONT) \ TAG(AST_NODE_WHILE) \ TAG(AST_NODE_FOR_SIMPLE) \ TAG(AST_NODE_FOR) \ TAG(AST_NODE_FOR_RANGE) \ TAG(AST_NODE_IF_SIMPLE) \ TAG(AST_NODE_IF) \ TAG(AST_NODE_SUSPEND) \ TAG(AST_NODE_RESUME) \ TAG(AST_NODE_CONTINUE) \ TAG(AST_NODE_BREAK) \ TAG(AST_NODE_RETURN) \ TAG(AST_NODE_FN_PROTO_SIMPLE) \ TAG(AST_NODE_FN_PROTO_MULTI) \ TAG(AST_NODE_FN_PROTO_ONE) \ TAG(AST_NODE_FN_PROTO) \ TAG(AST_NODE_FN_DECL) \ TAG(AST_NODE_ANYFRAME_TYPE) \ TAG(AST_NODE_ANYFRAME_LITERAL) \ TAG(AST_NODE_CHAR_LITERAL) \ TAG(AST_NODE_NUMBER_LITERAL) \ TAG(AST_NODE_UNREACHABLE_LITERAL) \ TAG(AST_NODE_IDENTIFIER) \ TAG(AST_NODE_ENUM_LITERAL) \ TAG(AST_NODE_STRING_LITERAL) \ TAG(AST_NODE_MULTILINE_STRING_LITERAL) \ TAG(AST_NODE_GROUPED_EXPRESSION) \ TAG(AST_NODE_BUILTIN_CALL_TWO) \ TAG(AST_NODE_BUILTIN_CALL_TWO_COMMA) \ TAG(AST_NODE_BUILTIN_CALL) \ TAG(AST_NODE_BUILTIN_CALL_COMMA) \ TAG(AST_NODE_ERROR_SET_DECL) \ TAG(AST_NODE_CONTAINER_DECL) \ TAG(AST_NODE_CONTAINER_DECL_TRAILING) \ TAG(AST_NODE_CONTAINER_DECL_TWO) \ TAG(AST_NODE_CONTAINER_DECL_TWO_TRAILING) \ TAG(AST_NODE_CONTAINER_DECL_ARG) \ TAG(AST_NODE_CONTAINER_DECL_ARG_TRAILING) \ TAG(AST_NODE_TAGGED_UNION) \ TAG(AST_NODE_TAGGED_UNION_TRAILING) \ TAG(AST_NODE_TAGGED_UNION_TWO) \ TAG(AST_NODE_TAGGED_UNION_TWO_TRAILING) \ TAG(AST_NODE_TAGGED_UNION_ENUM_TAG) \ TAG(AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING) \ TAG(AST_NODE_CONTAINER_FIELD_INIT) \ TAG(AST_NODE_CONTAINER_FIELD_ALIGN) \ TAG(AST_NODE_CONTAINER_FIELD) \ TAG(AST_NODE_COMPTIME) \ TAG(AST_NODE_NOSUSPEND) \ TAG(AST_NODE_BLOCK_TWO) \ TAG(AST_NODE_BLOCK_TWO_SEMICOLON) \ TAG(AST_NODE_BLOCK) \ TAG(AST_NODE_BLOCK_SEMICOLON) \ TAG(AST_NODE_ASM_SIMPLE) \ TAG(AST_NODE_ASM) \ TAG(AST_NODE_ASM_OUTPUT) \ TAG(AST_NODE_ASM_INPUT) \ TAG(AST_NODE_ERROR_VALUE) \ TAG(AST_NODE_ERROR_UNION) // lib/std/zig/Ast.zig: Node.For // AST_FOR_INPUTS_SHIFT 0u #define AST_FOR_INPUTS_MASK 0x7FFFFFFFu // AST_FOR_HAS_ELSE_SHIFT 31u #define AST_FOR_HAS_ELSE 0x80000000u // lib/std/zig/Ast.zig: Node.LocalVarDecl #define AST_EXTRA_LOCAL_VAR_DECL_TYPE_NODE 0u #define AST_EXTRA_LOCAL_VAR_DECL_ALIGN_NODE 1u // AST_EXTRA_LOCAL_VAR_DECL_LEN 2u // lib/std/zig/Ast.zig: Node.ArrayTypeSentinel #define AST_EXTRA_ARRAY_TYPE_SENTINEL_SENTINEL 0u #define AST_EXTRA_ARRAY_TYPE_SENTINEL_ELEM_TYPE 1u // AST_EXTRA_ARRAY_TYPE_SENTINEL_LEN 2u // lib/std/zig/Ast.zig: Node.PtrType #define AST_EXTRA_PTR_TYPE_SENTINEL 0u #define AST_EXTRA_PTR_TYPE_ALIGN_NODE 1u #define AST_EXTRA_PTR_TYPE_ADDRSPACE_NODE 2u // AST_EXTRA_PTR_TYPE_LEN 3u // lib/std/zig/Ast.zig: Node.PtrTypeBitRange #define AST_EXTRA_PTR_TYPE_BIT_RANGE_SENTINEL 0u #define AST_EXTRA_PTR_TYPE_BIT_RANGE_ALIGN_NODE 1u #define AST_EXTRA_PTR_TYPE_BIT_RANGE_ADDRSPACE_NODE 2u #define AST_EXTRA_PTR_TYPE_BIT_RANGE_BIT_RANGE_START 3u #define AST_EXTRA_PTR_TYPE_BIT_RANGE_BIT_RANGE_END 4u // AST_EXTRA_PTR_TYPE_BIT_RANGE_LEN 5u // lib/std/zig/Ast.zig: Node.SubRange #define AST_EXTRA_SUB_RANGE_START 0u #define AST_EXTRA_SUB_RANGE_END 1u // AST_EXTRA_SUB_RANGE_LEN 2u // lib/std/zig/Ast.zig: Node.If #define AST_EXTRA_IF_THEN_EXPR 0u #define AST_EXTRA_IF_ELSE_EXPR 1u // AST_EXTRA_IF_LEN 2u // lib/std/zig/Ast.zig: Node.ContainerField #define AST_EXTRA_CONTAINER_FIELD_ALIGN_EXPR 0u #define AST_EXTRA_CONTAINER_FIELD_VALUE_EXPR 1u // AST_EXTRA_CONTAINER_FIELD_LEN 2u // lib/std/zig/Ast.zig: Node.GlobalVarDecl #define AST_EXTRA_GLOBAL_VAR_DECL_TYPE_NODE 0u #define AST_EXTRA_GLOBAL_VAR_DECL_ALIGN_NODE 1u #define AST_EXTRA_GLOBAL_VAR_DECL_ADDRSPACE_NODE 2u #define AST_EXTRA_GLOBAL_VAR_DECL_SECTION_NODE 3u // AST_EXTRA_GLOBAL_VAR_DECL_LEN 4u // lib/std/zig/Ast.zig: Node.Slice #define AST_EXTRA_SLICE_START 0u #define AST_EXTRA_SLICE_END 1u // AST_EXTRA_SLICE_LEN 2u // lib/std/zig/Ast.zig: Node.SliceSentinel #define AST_EXTRA_SLICE_SENTINEL_START 0u #define AST_EXTRA_SLICE_SENTINEL_END 1u #define AST_EXTRA_SLICE_SENTINEL_SENTINEL 2u // AST_EXTRA_SLICE_SENTINEL_LEN 3u // lib/std/zig/Ast.zig: Node.While #define AST_EXTRA_WHILE_CONT_EXPR 0u #define AST_EXTRA_WHILE_THEN_EXPR 1u #define AST_EXTRA_WHILE_ELSE_EXPR 2u // AST_EXTRA_WHILE_LEN 3u // lib/std/zig/Ast.zig: Node.WhileCont #define AST_EXTRA_WHILE_CONT_CONT_EXPR 0u #define AST_EXTRA_WHILE_CONT_THEN_EXPR 1u // AST_EXTRA_WHILE_CONT_LEN 2u // lib/std/zig/Ast.zig: Node.FnProtoOne #define AST_EXTRA_FN_PROTO_ONE_PARAM 0u #define AST_EXTRA_FN_PROTO_ONE_ALIGN_EXPR 1u #define AST_EXTRA_FN_PROTO_ONE_ADDRSPACE_EXPR 2u #define AST_EXTRA_FN_PROTO_ONE_SECTION_EXPR 3u #define AST_EXTRA_FN_PROTO_ONE_CALLCONV_EXPR 4u // AST_EXTRA_FN_PROTO_ONE_LEN 5u // lib/std/zig/Ast.zig: Node.FnProto #define AST_EXTRA_FN_PROTO_PARAMS_START 0u #define AST_EXTRA_FN_PROTO_PARAMS_END 1u #define AST_EXTRA_FN_PROTO_ALIGN_EXPR 2u #define AST_EXTRA_FN_PROTO_ADDRSPACE_EXPR 3u #define AST_EXTRA_FN_PROTO_SECTION_EXPR 4u #define AST_EXTRA_FN_PROTO_CALLCONV_EXPR 5u // AST_EXTRA_FN_PROTO_LEN 6u // lib/std/zig/Ast.zig: Node.Asm #define AST_EXTRA_ASM_ITEMS_START 0u #define AST_EXTRA_ASM_ITEMS_END 1u #define AST_EXTRA_ASM_CLOBBERS 2u #define AST_EXTRA_ASM_RPAREN 3u // AST_EXTRA_ASM_LEN 4u // src/Air.zig: Inst.Tag #define AIR_INST_FOREACH_TAG(TAG) \ TAG(AIR_INST_ARG) \ TAG(AIR_INST_ADD) \ TAG(AIR_INST_ADD_SAFE) \ TAG(AIR_INST_ADD_OPTIMIZED) \ TAG(AIR_INST_ADD_WRAP) \ TAG(AIR_INST_ADD_SAT) \ TAG(AIR_INST_SUB) \ TAG(AIR_INST_SUB_SAFE) \ TAG(AIR_INST_SUB_OPTIMIZED) \ TAG(AIR_INST_SUB_WRAP) \ TAG(AIR_INST_SUB_SAT) \ TAG(AIR_INST_MUL) \ TAG(AIR_INST_MUL_SAFE) \ TAG(AIR_INST_MUL_OPTIMIZED) \ TAG(AIR_INST_MUL_WRAP) \ TAG(AIR_INST_MUL_SAT) \ TAG(AIR_INST_DIV_FLOAT) \ TAG(AIR_INST_DIV_FLOAT_OPTIMIZED) \ TAG(AIR_INST_DIV_TRUNC) \ TAG(AIR_INST_DIV_TRUNC_OPTIMIZED) \ TAG(AIR_INST_DIV_FLOOR) \ TAG(AIR_INST_DIV_FLOOR_OPTIMIZED) \ TAG(AIR_INST_DIV_CEIL) \ TAG(AIR_INST_DIV_CEIL_OPTIMIZED) \ TAG(AIR_INST_DIV_EXACT) \ TAG(AIR_INST_DIV_EXACT_OPTIMIZED) \ TAG(AIR_INST_REM) \ TAG(AIR_INST_REM_OPTIMIZED) \ TAG(AIR_INST_MOD) \ TAG(AIR_INST_MOD_OPTIMIZED) \ TAG(AIR_INST_PTR_ADD) \ TAG(AIR_INST_PTR_SUB) \ TAG(AIR_INST_MAX) \ TAG(AIR_INST_MIN) \ TAG(AIR_INST_ADD_WITH_OVERFLOW) \ TAG(AIR_INST_SUB_WITH_OVERFLOW) \ TAG(AIR_INST_MUL_WITH_OVERFLOW) \ TAG(AIR_INST_SHL_WITH_OVERFLOW) \ TAG(AIR_INST_ALLOC) \ TAG(AIR_INST_INFERRED_ALLOC) \ TAG(AIR_INST_INFERRED_ALLOC_COMPTIME) \ TAG(AIR_INST_RET_PTR) \ TAG(AIR_INST_ASSEMBLY) \ TAG(AIR_INST_BIT_AND) \ TAG(AIR_INST_BIT_OR) \ TAG(AIR_INST_SHR) \ TAG(AIR_INST_SHR_EXACT) \ TAG(AIR_INST_SHL) \ TAG(AIR_INST_SHL_EXACT) \ TAG(AIR_INST_SHL_SAT) \ TAG(AIR_INST_XOR) \ TAG(AIR_INST_NOT) \ TAG(AIR_INST_BIT_CAST) \ TAG(AIR_INST_BIT_CAST_SAFE) \ TAG(AIR_INST_PTR_CAST) \ TAG(AIR_INST_PTR_FROM_INT) \ TAG(AIR_INST_INT_FROM_PTR) \ TAG(AIR_INST_ERROR_CAST) \ TAG(AIR_INST_ERROR_FROM_INT) \ TAG(AIR_INST_INT_FROM_ERROR) \ TAG(AIR_INST_UNION_FROM_ENUM) \ TAG(AIR_INST_BLOCK) \ TAG(AIR_INST_LOOP) \ TAG(AIR_INST_REPEAT) \ TAG(AIR_INST_BR) \ TAG(AIR_INST_TRAP) \ TAG(AIR_INST_BREAKPOINT) \ TAG(AIR_INST_RET_ADDR) \ TAG(AIR_INST_FRAME_ADDR) \ TAG(AIR_INST_CALL) \ TAG(AIR_INST_CALL_ALWAYS_TAIL) \ TAG(AIR_INST_CALL_NEVER_TAIL) \ TAG(AIR_INST_CALL_NEVER_INLINE) \ TAG(AIR_INST_CLZ) \ TAG(AIR_INST_CTZ) \ TAG(AIR_INST_POPCOUNT) \ TAG(AIR_INST_BYTE_SWAP) \ TAG(AIR_INST_BIT_REVERSE) \ TAG(AIR_INST_SQRT) \ TAG(AIR_INST_SIN) \ TAG(AIR_INST_COS) \ TAG(AIR_INST_TAN) \ TAG(AIR_INST_EXP) \ TAG(AIR_INST_EXP2) \ TAG(AIR_INST_LOG) \ TAG(AIR_INST_LOG2) \ TAG(AIR_INST_LOG10) \ TAG(AIR_INST_ABS) \ TAG(AIR_INST_FLOOR) \ TAG(AIR_INST_CEIL) \ TAG(AIR_INST_ROUND) \ TAG(AIR_INST_TRUNC_FLOAT) \ TAG(AIR_INST_NEG) \ TAG(AIR_INST_NEG_OPTIMIZED) \ TAG(AIR_INST_CMP_LT) \ TAG(AIR_INST_CMP_LT_OPTIMIZED) \ TAG(AIR_INST_CMP_LTE) \ TAG(AIR_INST_CMP_LTE_OPTIMIZED) \ TAG(AIR_INST_CMP_EQ) \ TAG(AIR_INST_CMP_EQ_OPTIMIZED) \ TAG(AIR_INST_CMP_GTE) \ TAG(AIR_INST_CMP_GTE_OPTIMIZED) \ TAG(AIR_INST_CMP_GT) \ TAG(AIR_INST_CMP_GT_OPTIMIZED) \ TAG(AIR_INST_CMP_NEQ) \ TAG(AIR_INST_CMP_NEQ_OPTIMIZED) \ TAG(AIR_INST_CMP_VECTOR) \ TAG(AIR_INST_CMP_VECTOR_OPTIMIZED) \ TAG(AIR_INST_COND_BR) \ TAG(AIR_INST_SWITCH_BR) \ TAG(AIR_INST_LOOP_SWITCH_BR) \ TAG(AIR_INST_SWITCH_DISPATCH) \ TAG(AIR_INST_TRY) \ TAG(AIR_INST_TRY_COLD) \ TAG(AIR_INST_TRY_PTR) \ TAG(AIR_INST_TRY_PTR_COLD) \ TAG(AIR_INST_DBG_STMT) \ TAG(AIR_INST_DBG_EMPTY_STMT) \ TAG(AIR_INST_DBG_INLINE_BLOCK) \ TAG(AIR_INST_DBG_VAR_PTR) \ TAG(AIR_INST_DBG_VAR_VAL) \ TAG(AIR_INST_DBG_ARG_INLINE) \ TAG(AIR_INST_IS_NULL) \ TAG(AIR_INST_IS_NON_NULL) \ TAG(AIR_INST_IS_NULL_PTR) \ TAG(AIR_INST_IS_NON_NULL_PTR) \ TAG(AIR_INST_IS_ERR) \ TAG(AIR_INST_IS_NON_ERR) \ TAG(AIR_INST_IS_ERR_PTR) \ TAG(AIR_INST_IS_NON_ERR_PTR) \ TAG(AIR_INST_LOAD) \ TAG(AIR_INST_RET) \ TAG(AIR_INST_RET_SAFE) \ TAG(AIR_INST_RET_LOAD) \ TAG(AIR_INST_STORE) \ TAG(AIR_INST_STORE_SAFE) \ TAG(AIR_INST_UNREACH) \ TAG(AIR_INST_FPTRUNC) \ TAG(AIR_INST_FPEXT) \ TAG(AIR_INST_INT_CAST) \ TAG(AIR_INST_INT_CAST_SAFE) \ TAG(AIR_INST_TRUNC) \ TAG(AIR_INST_OPTIONAL_PAYLOAD) \ TAG(AIR_INST_OPTIONAL_PAYLOAD_PTR) \ TAG(AIR_INST_OPTIONAL_PAYLOAD_PTR_SET) \ TAG(AIR_INST_WRAP_OPTIONAL) \ TAG(AIR_INST_UNWRAP_ERRUNION_PAYLOAD) \ TAG(AIR_INST_UNWRAP_ERRUNION_ERR) \ TAG(AIR_INST_UNWRAP_ERRUNION_PAYLOAD_PTR) \ TAG(AIR_INST_UNWRAP_ERRUNION_ERR_PTR) \ TAG(AIR_INST_ERRUNION_PAYLOAD_PTR_SET) \ TAG(AIR_INST_WRAP_ERRUNION_PAYLOAD) \ TAG(AIR_INST_WRAP_ERRUNION_ERR) \ TAG(AIR_INST_STRUCT_FIELD_PTR) \ TAG(AIR_INST_STRUCT_FIELD_PTR_INDEX_0) \ TAG(AIR_INST_STRUCT_FIELD_PTR_INDEX_1) \ TAG(AIR_INST_STRUCT_FIELD_PTR_INDEX_2) \ TAG(AIR_INST_STRUCT_FIELD_PTR_INDEX_3) \ TAG(AIR_INST_AGG_FIELD_VAL) \ TAG(AIR_INST_SET_UNION_TAG) \ TAG(AIR_INST_GET_UNION_TAG) \ TAG(AIR_INST_SLICE) \ TAG(AIR_INST_SLICE_LEN) \ TAG(AIR_INST_SLICE_PTR) \ TAG(AIR_INST_PTR_SLICE_LEN_PTR) \ TAG(AIR_INST_PTR_SLICE_PTR_PTR) \ TAG(AIR_INST_ARRAY_ELEM_VAL) \ TAG(AIR_INST_SLICE_ELEM_VAL) \ TAG(AIR_INST_SLICE_ELEM_PTR) \ TAG(AIR_INST_PTR_ELEM_VAL) \ TAG(AIR_INST_PTR_ELEM_PTR) \ TAG(AIR_INST_ARRAY_TO_SLICE) \ TAG(AIR_INST_ARRAY_TO_VECTOR) \ TAG(AIR_INST_INT_FROM_FLOAT) \ TAG(AIR_INST_INT_FROM_FLOAT_OPTIMIZED) \ TAG(AIR_INST_INT_FROM_FLOAT_SAFE) \ TAG(AIR_INST_INT_FROM_FLOAT_OPTIMIZED_SAFE) \ TAG(AIR_INST_FLOAT_FROM_INT) \ TAG(AIR_INST_REDUCE) \ TAG(AIR_INST_REDUCE_OPTIMIZED) \ TAG(AIR_INST_SPLAT) \ TAG(AIR_INST_SHUFFLE_ONE) \ TAG(AIR_INST_SHUFFLE_TWO) \ TAG(AIR_INST_SELECT) \ TAG(AIR_INST_MEMSET) \ TAG(AIR_INST_MEMSET_SAFE) \ TAG(AIR_INST_MEMCPY) \ TAG(AIR_INST_MEMMOVE) \ TAG(AIR_INST_CMPXCHG_WEAK) \ TAG(AIR_INST_CMPXCHG_STRONG) \ TAG(AIR_INST_ATOMIC_LOAD) \ TAG(AIR_INST_ATOMIC_STORE_UNORDERED) \ TAG(AIR_INST_ATOMIC_STORE_MONOTONIC) \ TAG(AIR_INST_ATOMIC_STORE_RELEASE) \ TAG(AIR_INST_ATOMIC_STORE_SEQ_CST) \ TAG(AIR_INST_ATOMIC_RMW) \ TAG(AIR_INST_IS_NAMED_ENUM_VALUE) \ TAG(AIR_INST_TAG_NAME) \ TAG(AIR_INST_ERROR_NAME) \ TAG(AIR_INST_ERROR_SET_HAS_VALUE) \ TAG(AIR_INST_AGGREGATE_INIT) \ TAG(AIR_INST_UNION_INIT) \ TAG(AIR_INST_PREFETCH) \ TAG(AIR_INST_MUL_ADD) \ TAG(AIR_INST_FIELD_PARENT_PTR) \ TAG(AIR_INST_WASM_MEMORY_SIZE) \ TAG(AIR_INST_WASM_MEMORY_GROW) \ TAG(AIR_INST_CMP_LTE_ERRORS_LEN) \ TAG(AIR_INST_ERR_RETURN_TRACE) \ TAG(AIR_INST_SET_ERR_RETURN_TRACE) \ TAG(AIR_INST_ADDRSPACE_CAST) \ TAG(AIR_INST_SAVE_ERR_RETURN_TRACE_INDEX) \ TAG(AIR_INST_RUNTIME_NAV_PTR) \ TAG(AIR_INST_C_VA_ARG) \ TAG(AIR_INST_C_VA_COPY) \ TAG(AIR_INST_C_VA_END) \ TAG(AIR_INST_C_VA_START) \ TAG(AIR_INST_SPIRV_RUNTIME_ARRAY_LEN) \ TAG(AIR_INST_WORK_ITEM_ID) \ TAG(AIR_INST_WORK_GROUP_SIZE) \ TAG(AIR_INST_WORK_GROUP_ID) \ TAG(AIR_INST_LEGALIZE_VEC_STORE_ELEM) \ TAG(AIR_INST_LEGALIZE_VEC_ELEM_VAL) \ TAG(AIR_INST_LEGALIZE_COMPILER_RT_CALL) // src/Air.zig: CondBr.BranchHints #define AIR_BRANCH_HINTS_TRUE_SHIFT 0u #define AIR_BRANCH_HINTS_TRUE_MASK 0x7u #define AIR_BRANCH_HINTS_FALSE_SHIFT 3u // AIR_BRANCH_HINTS_FALSE_MASK 0x7u #define AIR_BRANCH_HINTS_THEN_COV_SHIFT 6u // AIR_BRANCH_HINTS_THEN_COV 0x40u #define AIR_BRANCH_HINTS_ELSE_COV_SHIFT 7u // AIR_BRANCH_HINTS_ELSE_COV 0x80u // src/Air.zig: CoveragePoint enum { AIR_COVERAGE_POINT_NONE = 0, AIR_COVERAGE_POINT_POI = 1, }; // src/Air.zig: ShuffleOneMask // AIR_SHUFFLE_ONE_MASK_INDEX_SHIFT 0u #define AIR_SHUFFLE_ONE_MASK_INDEX_MASK 0x7FFFFFFFu // AIR_SHUFFLE_ONE_MASK_KIND_SHIFT 31u #define AIR_SHUFFLE_ONE_MASK_KIND 0x80000000u // src/InternPool.zig: Tag typedef enum { IP_TAG_REMOVED = 0, IP_TAG_SIMPLE_TYPE = 1, IP_TAG_TYPE_INT_SIGNED = 2, IP_TAG_TYPE_INT_UNSIGNED = 3, IP_TAG_TYPE_ARRAY_BIG = 4, IP_TAG_TYPE_ARRAY_SMALL = 5, IP_TAG_TYPE_VECTOR = 6, IP_TAG_TYPE_POINTER = 7, IP_TAG_TYPE_SLICE = 8, IP_TAG_TYPE_OPTIONAL = 9, IP_TAG_TYPE_ANYFRAME = 10, IP_TAG_TYPE_ERROR_UNION = 11, IP_TAG_TYPE_ANYERROR_UNION = 12, IP_TAG_TYPE_ERROR_SET = 13, IP_TAG_TYPE_INFERRED_ERROR_SET = 14, IP_TAG_TYPE_FUNCTION = 15, IP_TAG_TYPE_TUPLE = 16, IP_TAG_TYPE_STRUCT = 17, IP_TAG_TYPE_STRUCT_PACKED_AUTO = 18, IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT = 19, IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS = 20, IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS = 21, IP_TAG_TYPE_UNION = 22, IP_TAG_TYPE_UNION_PACKED_AUTO = 23, IP_TAG_TYPE_UNION_PACKED_EXPLICIT = 24, IP_TAG_TYPE_ENUM_AUTO = 25, IP_TAG_TYPE_ENUM_EXPLICIT = 26, IP_TAG_TYPE_ENUM_NONEXHAUSTIVE = 27, IP_TAG_TYPE_SPIRV = 28, IP_TAG_TYPE_OPAQUE = 29, IP_TAG_UNDEF = 30, IP_TAG_SIMPLE_VALUE = 31, IP_TAG_PTR_NAV = 32, IP_TAG_PTR_COMPTIME_ALLOC = 33, IP_TAG_PTR_UAV = 34, IP_TAG_PTR_UAV_ALIGNED = 35, IP_TAG_PTR_COMPTIME_FIELD = 36, IP_TAG_PTR_INT = 37, IP_TAG_PTR_EU_PAYLOAD = 38, IP_TAG_PTR_OPT_PAYLOAD = 39, IP_TAG_PTR_ELEM = 40, IP_TAG_PTR_FIELD = 41, IP_TAG_PTR_SLICE = 42, IP_TAG_OPT_PAYLOAD = 43, IP_TAG_OPT_NULL = 44, IP_TAG_INT_U8 = 45, IP_TAG_INT_U16 = 46, IP_TAG_INT_U32 = 47, IP_TAG_INT_I32 = 48, IP_TAG_INT_USIZE = 49, IP_TAG_INT_COMPTIME_INT_U32 = 50, IP_TAG_INT_COMPTIME_INT_I32 = 51, IP_TAG_INT_SMALL = 52, IP_TAG_INT_POSITIVE = 53, IP_TAG_INT_NEGATIVE = 54, IP_TAG_ERROR_SET_ERROR = 55, IP_TAG_ERROR_UNION_ERROR = 56, IP_TAG_ERROR_UNION_PAYLOAD = 57, IP_TAG_ENUM_LITERAL = 58, IP_TAG_ENUM_TAG = 59, IP_TAG_FLOAT_F16 = 60, IP_TAG_FLOAT_F32 = 61, IP_TAG_FLOAT_F64 = 62, IP_TAG_FLOAT_F80 = 63, IP_TAG_FLOAT_F128 = 64, IP_TAG_FLOAT_C_LONGDOUBLE_F80 = 65, IP_TAG_FLOAT_C_LONGDOUBLE_F128 = 66, IP_TAG_FLOAT_COMPTIME_FLOAT = 67, IP_TAG_EXTERN = 68, IP_TAG_FUNC_DECL = 69, IP_TAG_FUNC_INSTANCE = 70, IP_TAG_FUNC_COERCED = 71, IP_TAG_ONLY_POSSIBLE_VALUE = 72, IP_TAG_UNION_VALUE = 73, IP_TAG_BYTES = 74, IP_TAG_AGGREGATE = 75, IP_TAG_REPEATED = 76, IP_TAG_BITPACK = 77, IP_TAG_MEMOIZED_CALL = 78, } IpTag; #define IP_TAG_NAMES(NAME) \ NAME(IP_TAG_REMOVED, "removed") \ NAME(IP_TAG_SIMPLE_TYPE, "simple_type") \ NAME(IP_TAG_TYPE_INT_SIGNED, "type_int_signed") \ NAME(IP_TAG_TYPE_INT_UNSIGNED, "type_int_unsigned") \ NAME(IP_TAG_TYPE_ARRAY_BIG, "type_array_big") \ NAME(IP_TAG_TYPE_ARRAY_SMALL, "type_array_small") \ NAME(IP_TAG_TYPE_VECTOR, "type_vector") \ NAME(IP_TAG_TYPE_POINTER, "type_pointer") \ NAME(IP_TAG_TYPE_SLICE, "type_slice") \ NAME(IP_TAG_TYPE_OPTIONAL, "type_optional") \ NAME(IP_TAG_TYPE_ANYFRAME, "type_anyframe") \ NAME(IP_TAG_TYPE_ERROR_UNION, "type_error_union") \ NAME(IP_TAG_TYPE_ANYERROR_UNION, "type_anyerror_union") \ NAME(IP_TAG_TYPE_ERROR_SET, "type_error_set") \ NAME(IP_TAG_TYPE_INFERRED_ERROR_SET, "type_inferred_error_set") \ NAME(IP_TAG_TYPE_FUNCTION, "type_function") \ NAME(IP_TAG_TYPE_TUPLE, "type_tuple") \ NAME(IP_TAG_TYPE_STRUCT, "type_struct") \ NAME(IP_TAG_TYPE_STRUCT_PACKED_AUTO, "type_struct_packed_auto") \ NAME(IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT, "type_struct_packed_explicit") \ NAME(IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS, "type_struct_packed_auto_defaults") \ NAME(IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS, "type_struct_packed_explicit_defaults") \ NAME(IP_TAG_TYPE_UNION, "type_union") \ NAME(IP_TAG_TYPE_UNION_PACKED_AUTO, "type_union_packed_auto") \ NAME(IP_TAG_TYPE_UNION_PACKED_EXPLICIT, "type_union_packed_explicit") \ NAME(IP_TAG_TYPE_ENUM_AUTO, "type_enum_auto") \ NAME(IP_TAG_TYPE_ENUM_EXPLICIT, "type_enum_explicit") \ NAME(IP_TAG_TYPE_ENUM_NONEXHAUSTIVE, "type_enum_nonexhaustive") \ NAME(IP_TAG_TYPE_SPIRV, "type_spirv") \ NAME(IP_TAG_TYPE_OPAQUE, "type_opaque") \ NAME(IP_TAG_UNDEF, "undef") \ NAME(IP_TAG_SIMPLE_VALUE, "simple_value") \ NAME(IP_TAG_PTR_NAV, "ptr_nav") \ NAME(IP_TAG_PTR_COMPTIME_ALLOC, "ptr_comptime_alloc") \ NAME(IP_TAG_PTR_UAV, "ptr_uav") \ NAME(IP_TAG_PTR_UAV_ALIGNED, "ptr_uav_aligned") \ NAME(IP_TAG_PTR_COMPTIME_FIELD, "ptr_comptime_field") \ NAME(IP_TAG_PTR_INT, "ptr_int") \ NAME(IP_TAG_PTR_EU_PAYLOAD, "ptr_eu_payload") \ NAME(IP_TAG_PTR_OPT_PAYLOAD, "ptr_opt_payload") \ NAME(IP_TAG_PTR_ELEM, "ptr_elem") \ NAME(IP_TAG_PTR_FIELD, "ptr_field") \ NAME(IP_TAG_PTR_SLICE, "ptr_slice") \ NAME(IP_TAG_OPT_PAYLOAD, "opt_payload") \ NAME(IP_TAG_OPT_NULL, "opt_null") \ NAME(IP_TAG_INT_U8, "int_u8") \ NAME(IP_TAG_INT_U16, "int_u16") \ NAME(IP_TAG_INT_U32, "int_u32") \ NAME(IP_TAG_INT_I32, "int_i32") \ NAME(IP_TAG_INT_USIZE, "int_usize") \ NAME(IP_TAG_INT_COMPTIME_INT_U32, "int_comptime_int_u32") \ NAME(IP_TAG_INT_COMPTIME_INT_I32, "int_comptime_int_i32") \ NAME(IP_TAG_INT_SMALL, "int_small") \ NAME(IP_TAG_INT_POSITIVE, "int_positive") \ NAME(IP_TAG_INT_NEGATIVE, "int_negative") \ NAME(IP_TAG_ERROR_SET_ERROR, "error_set_error") \ NAME(IP_TAG_ERROR_UNION_ERROR, "error_union_error") \ NAME(IP_TAG_ERROR_UNION_PAYLOAD, "error_union_payload") \ NAME(IP_TAG_ENUM_LITERAL, "enum_literal") \ NAME(IP_TAG_ENUM_TAG, "enum_tag") \ NAME(IP_TAG_FLOAT_F16, "float_f16") \ NAME(IP_TAG_FLOAT_F32, "float_f32") \ NAME(IP_TAG_FLOAT_F64, "float_f64") \ NAME(IP_TAG_FLOAT_F80, "float_f80") \ NAME(IP_TAG_FLOAT_F128, "float_f128") \ NAME(IP_TAG_FLOAT_C_LONGDOUBLE_F80, "float_c_longdouble_f80") \ NAME(IP_TAG_FLOAT_C_LONGDOUBLE_F128, "float_c_longdouble_f128") \ NAME(IP_TAG_FLOAT_COMPTIME_FLOAT, "float_comptime_float") \ NAME(IP_TAG_EXTERN, "extern") \ NAME(IP_TAG_FUNC_DECL, "func_decl") \ NAME(IP_TAG_FUNC_INSTANCE, "func_instance") \ NAME(IP_TAG_FUNC_COERCED, "func_coerced") \ NAME(IP_TAG_ONLY_POSSIBLE_VALUE, "only_possible_value") \ NAME(IP_TAG_UNION_VALUE, "union_value") \ NAME(IP_TAG_BYTES, "bytes") \ NAME(IP_TAG_AGGREGATE, "aggregate") \ NAME(IP_TAG_REPEATED, "repeated") \ NAME(IP_TAG_BITPACK, "bitpack") \ NAME(IP_TAG_MEMOIZED_CALL, "memoized_call") // src/InternPool.zig: Key.PtrType.Flags #define IP_PTR_FLAGS_SIZE_SHIFT 0u #define IP_PTR_FLAGS_SIZE_MASK 0x3u #define IP_PTR_FLAGS_ALIGNMENT_SHIFT 2u #define IP_PTR_FLAGS_ALIGNMENT_MASK 0x3Fu // IP_PTR_FLAGS_IS_CONST_SHIFT 8u #define IP_PTR_FLAGS_IS_CONST 0x100u // IP_PTR_FLAGS_IS_VOLATILE_SHIFT 9u #define IP_PTR_FLAGS_IS_VOLATILE 0x200u // IP_PTR_FLAGS_IS_ALLOWZERO_SHIFT 10u #define IP_PTR_FLAGS_IS_ALLOWZERO 0x400u #define IP_PTR_FLAGS_ADDRESS_SPACE_SHIFT 11u #define IP_PTR_FLAGS_ADDRESS_SPACE_MASK 0x1Fu #define IP_PTR_FLAGS_VECTOR_INDEX_SHIFT 16u #define IP_PTR_FLAGS_VECTOR_INDEX_MASK 0xFFFFu // src/InternPool.zig: Key.PtrType.PackedOffset #define IP_PTR_PACKED_OFFSET_HOST_SIZE_SHIFT 0u #define IP_PTR_PACKED_OFFSET_HOST_SIZE_MASK 0xFFFFu #define IP_PTR_PACKED_OFFSET_BIT_OFFSET_SHIFT 16u #define IP_PTR_PACKED_OFFSET_BIT_OFFSET_MASK 0xFFFFu // src/InternPool.zig: Key.PtrType.VectorIndex enum { IP_VECTOR_INDEX_NONE = 65535, }; // src/InternPool.zig: Alignment enum { IP_ALIGNMENT_1 = 0, IP_ALIGNMENT_2 = 1, IP_ALIGNMENT_4 = 2, IP_ALIGNMENT_8 = 3, IP_ALIGNMENT_16 = 4, IP_ALIGNMENT_32 = 5, IP_ALIGNMENT_64 = 6, IP_ALIGNMENT_NONE = 63, }; // src/InternPool.zig: Tag.Extern.Flags #define IP_EXTERN_FLAGS_LINKAGE_SHIFT 0u // IP_EXTERN_FLAGS_LINKAGE 0x1u #define IP_EXTERN_FLAGS_VISIBILITY_SHIFT 1u // IP_EXTERN_FLAGS_VISIBILITY_MASK 0x3u // IP_EXTERN_FLAGS_IS_DLL_IMPORT_SHIFT 3u #define IP_EXTERN_FLAGS_IS_DLL_IMPORT 0x8u #define IP_EXTERN_FLAGS_RELOCATION_SHIFT 4u // IP_EXTERN_FLAGS_RELOCATION 0x10u // IP_EXTERN_FLAGS_SOURCE_SHIFT 5u #define IP_EXTERN_FLAGS_SOURCE 0x20u // IP_EXTERN_FLAGS_DECORATION_TYPE_SHIFT 6u // IP_EXTERN_FLAGS_DECORATION_TYPE_MASK 0x3u // src/InternPool.zig: Tag.TypeFunction.Flags // IP_FUNC_FLAGS_CC_SHIFT 0u #define IP_FUNC_FLAGS_CC_MASK 0x3FFFFu // IP_FUNC_FLAGS_IS_VAR_ARGS_SHIFT 18u #define IP_FUNC_FLAGS_IS_VAR_ARGS 0x40000u // IP_FUNC_FLAGS_HAS_COMPTIME_BITS_SHIFT 19u #define IP_FUNC_FLAGS_HAS_COMPTIME_BITS 0x80000u // IP_FUNC_FLAGS_HAS_NOALIAS_BITS_SHIFT 20u #define IP_FUNC_FLAGS_HAS_NOALIAS_BITS 0x100000u // IP_FUNC_FLAGS_IS_NOINLINE_SHIFT 21u #define IP_FUNC_FLAGS_IS_NOINLINE 0x200000u // src/InternPool.zig: PackedU64 // IP_PACKED_U64_A 0u // IP_PACKED_U64_B 1u #define IP_PACKED_U64_LEN 2u // src/InternPool.zig: MemoizedCall #define IP_MEMOIZED_CALL_FUNC 0u #define IP_MEMOIZED_CALL_ARGS_LEN 1u #define IP_MEMOIZED_CALL_RESULT 2u #define IP_MEMOIZED_CALL_BRANCH_COUNT 3u #define IP_MEMOIZED_CALL_BRANCH_QUOTA 4u #define IP_MEMOIZED_CALL_LEN 5u // src/InternPool.zig: Key.PtrType #define IP_TYPE_POINTER_CHILD 0u #define IP_TYPE_POINTER_SENTINEL 1u #define IP_TYPE_POINTER_FLAGS 2u #define IP_TYPE_POINTER_PACKED_OFFSET 3u #define IP_TYPE_POINTER_LEN 4u // src/InternPool.zig: Tag.TypeFunction #define IP_TYPE_FUNCTION_PARAMS_LEN 0u #define IP_TYPE_FUNCTION_RETURN_TYPE 1u #define IP_TYPE_FUNCTION_FLAGS 2u #define IP_TYPE_FUNCTION_LEN 3u // src/InternPool.zig: Tag.TypeStruct #define IP_TYPE_STRUCT_ZIR_INDEX 0u #define IP_TYPE_STRUCT_NAME 1u #define IP_TYPE_STRUCT_FQN 2u #define IP_TYPE_STRUCT_NAME_NAV 3u #define IP_TYPE_STRUCT_NAMESPACE 4u #define IP_TYPE_STRUCT_FIELDS_LEN 5u #define IP_TYPE_STRUCT_FIELD_NAME_MAP 6u #define IP_TYPE_STRUCT_SIZE 7u #define IP_TYPE_STRUCT_FLAGS 8u #define IP_TYPE_STRUCT_LEN 9u // src/InternPool.zig: Tag.TypeStructPacked #define IP_TYPE_STRUCT_PACKED_ZIR_INDEX 0u #define IP_TYPE_STRUCT_PACKED_BITS 1u #define IP_TYPE_STRUCT_PACKED_NAME 2u #define IP_TYPE_STRUCT_PACKED_FQN 3u #define IP_TYPE_STRUCT_PACKED_NAME_NAV 4u #define IP_TYPE_STRUCT_PACKED_NAMESPACE 5u #define IP_TYPE_STRUCT_PACKED_BACKING_INT_TYPE 6u #define IP_TYPE_STRUCT_PACKED_FIELDS_LEN 7u #define IP_TYPE_STRUCT_PACKED_FIELD_NAME_MAP 8u #define IP_TYPE_STRUCT_PACKED_LEN 9u // src/InternPool.zig: Tag.TypeUnion #define IP_TYPE_UNION_ZIR_INDEX 0u #define IP_TYPE_UNION_NAME 1u #define IP_TYPE_UNION_FQN 2u #define IP_TYPE_UNION_NAME_NAV 3u #define IP_TYPE_UNION_NAMESPACE 4u #define IP_TYPE_UNION_ENUM_TAG_TYPE 5u #define IP_TYPE_UNION_FIELDS_LEN 6u #define IP_TYPE_UNION_SIZE 7u #define IP_TYPE_UNION_PADDING 8u #define IP_TYPE_UNION_FLAGS 9u #define IP_TYPE_UNION_LEN 10u // src/InternPool.zig: Tag.TypeUnionPacked #define IP_TYPE_UNION_PACKED_ZIR_INDEX 0u #define IP_TYPE_UNION_PACKED_BITS 1u #define IP_TYPE_UNION_PACKED_NAME 2u #define IP_TYPE_UNION_PACKED_FQN 3u #define IP_TYPE_UNION_PACKED_NAME_NAV 4u #define IP_TYPE_UNION_PACKED_NAMESPACE 5u #define IP_TYPE_UNION_PACKED_BACKING_INT_TYPE 6u #define IP_TYPE_UNION_PACKED_ENUM_TAG_TYPE 7u #define IP_TYPE_UNION_PACKED_FIELDS_LEN 8u #define IP_TYPE_UNION_PACKED_LEN 9u // src/InternPool.zig: Tag.TypeEnum #define IP_TYPE_ENUM_BITS 0u #define IP_TYPE_ENUM_NAME 1u #define IP_TYPE_ENUM_FQN 2u #define IP_TYPE_ENUM_NAME_NAV 3u #define IP_TYPE_ENUM_NAMESPACE 4u #define IP_TYPE_ENUM_INT_TAG_TYPE 5u #define IP_TYPE_ENUM_FIELDS_LEN 6u #define IP_TYPE_ENUM_FIELD_NAME_MAP 7u #define IP_TYPE_ENUM_LEN 8u // src/InternPool.zig: Tag.TypeOpaque #define IP_TYPE_OPAQUE_ZIR_INDEX 0u #define IP_TYPE_OPAQUE_CAPTURES_LEN 1u #define IP_TYPE_OPAQUE_NAME 2u #define IP_TYPE_OPAQUE_FQN 3u #define IP_TYPE_OPAQUE_NAME_NAV 4u #define IP_TYPE_OPAQUE_NAMESPACE 5u #define IP_TYPE_OPAQUE_LEN 6u // src/InternPool.zig: Tag.TypeStruct.Flags #define IP_STRUCT_FLAGS_ANY_CAPTURES_SHIFT 0u #define IP_STRUCT_FLAGS_ANY_CAPTURES_MASK 0x3u // IP_STRUCT_FLAGS_LAYOUT_SHIFT 2u #define IP_STRUCT_FLAGS_LAYOUT 0x4u // IP_STRUCT_FLAGS_ANY_COMPTIME_FIELDS_SHIFT 3u #define IP_STRUCT_FLAGS_ANY_COMPTIME_FIELDS 0x8u // IP_STRUCT_FLAGS_ANY_FIELD_DEFAULTS_SHIFT 4u #define IP_STRUCT_FLAGS_ANY_FIELD_DEFAULTS 0x10u // IP_STRUCT_FLAGS_ANY_FIELD_ALIGNS_SHIFT 5u #define IP_STRUCT_FLAGS_ANY_FIELD_ALIGNS 0x20u #define IP_STRUCT_FLAGS_CLASS_SHIFT 6u #define IP_STRUCT_FLAGS_CLASS_MASK 0x7u #define IP_STRUCT_FLAGS_ALIGNMENT_SHIFT 9u #define IP_STRUCT_FLAGS_ALIGNMENT_MASK 0x3Fu // IP_STRUCT_FLAGS_WANT_LAYOUT_SHIFT 15u #define IP_STRUCT_FLAGS_WANT_LAYOUT 0x8000u // src/InternPool.zig: Tag.TypeStruct.Flags.any_captures enum { IP_STRUCT_ANY_CAPTURES_TRUE = 0, IP_STRUCT_ANY_CAPTURES_FALSE = 1, IP_STRUCT_ANY_CAPTURES_REIFIED = 2, }; // src/InternPool.zig: Tag.TypeStructPacked.Bits // IP_STRUCT_PACKED_BITS_CAPTURES_LEN_SHIFT 0u #define IP_STRUCT_PACKED_BITS_CAPTURES_LEN_MASK 0x7FFFFFFFu // IP_STRUCT_PACKED_BITS_WANT_LAYOUT_SHIFT 31u #define IP_STRUCT_PACKED_BITS_WANT_LAYOUT 0x80000000u // src/InternPool.zig: Tag.TypeStructPacked.Bits.captures_len enum { IP_PACKED_CAPTURES_LEN_REIFIED = 2147483647, }; // src/InternPool.zig: Tag.TypeUnion.Flags #define IP_UNION_FLAGS_ANY_CAPTURES_SHIFT 0u #define IP_UNION_FLAGS_ANY_CAPTURES_MASK 0x3u #define IP_UNION_FLAGS_ENUM_TAG_MODE_SHIFT 2u #define IP_UNION_FLAGS_ENUM_TAG_MODE 0x4u // IP_UNION_FLAGS_LAYOUT_SHIFT 3u #define IP_UNION_FLAGS_LAYOUT 0x8u // IP_UNION_FLAGS_ANY_FIELD_ALIGNS_SHIFT 4u #define IP_UNION_FLAGS_ANY_FIELD_ALIGNS 0x10u #define IP_UNION_FLAGS_TAG_USAGE_SHIFT 5u #define IP_UNION_FLAGS_TAG_USAGE_MASK 0x3u #define IP_UNION_FLAGS_CLASS_SHIFT 7u #define IP_UNION_FLAGS_CLASS_MASK 0x7u // IP_UNION_FLAGS_HAS_RUNTIME_TAG_SHIFT 10u #define IP_UNION_FLAGS_HAS_RUNTIME_TAG 0x400u #define IP_UNION_FLAGS_ALIGNMENT_SHIFT 11u #define IP_UNION_FLAGS_ALIGNMENT_MASK 0x3Fu // IP_UNION_FLAGS_WANT_LAYOUT_SHIFT 17u #define IP_UNION_FLAGS_WANT_LAYOUT 0x20000u // src/InternPool.zig: Tag.TypeUnion.Flags.any_captures enum { IP_UNION_ANY_CAPTURES_TRUE = 0, IP_UNION_ANY_CAPTURES_FALSE = 1, IP_UNION_ANY_CAPTURES_REIFIED = 2, }; // src/InternPool.zig: BackingTypeMode enum { IP_BACKING_TYPE_MODE_EXPLICIT = 0, IP_BACKING_TYPE_MODE_AUTO = 1, }; // src/InternPool.zig: LoadedUnionType.TagUsage typedef enum { IP_UNION_TAG_USAGE_NONE = 0, IP_UNION_TAG_USAGE_SAFETY = 1, IP_UNION_TAG_USAGE_TAGGED = 2, } IpUnionTagUsage; // src/InternPool.zig: Tag.TypeUnionPacked.Bits // IP_UNION_PACKED_BITS_CAPTURES_LEN_SHIFT 0u // IP_UNION_PACKED_BITS_CAPTURES_LEN_MASK 0x7FFFFFFFu // IP_UNION_PACKED_BITS_WANT_LAYOUT_SHIFT 31u #define IP_UNION_PACKED_BITS_WANT_LAYOUT 0x80000000u // src/InternPool.zig: Tag.TypeEnum.Bits #define IP_ENUM_BITS_CAPTURES_LEN_SHIFT 0u // IP_ENUM_BITS_CAPTURES_LEN_MASK 0x7FFFFFFFu // IP_ENUM_BITS_WANT_LAYOUT_SHIFT 31u #define IP_ENUM_BITS_WANT_LAYOUT 0x80000000u // src/InternPool.zig: Tag.TypeEnum.Bits.captures_len enum { IP_ENUM_CAPTURES_LEN_REIFIED = 2147483647, IP_ENUM_CAPTURES_LEN_GENERATED_UNION_TAG = 2147483646, }; // src/InternPool.zig: FuncAnalysis // IP_FUNC_ANALYSIS_WANT_RUNTIME_ANALYSIS_SHIFT 0u #define IP_FUNC_ANALYSIS_WANT_RUNTIME_ANALYSIS 0x1u // IP_FUNC_ANALYSIS_BRANCH_HINT_SHIFT 1u // IP_FUNC_ANALYSIS_BRANCH_HINT_MASK 0x7u // IP_FUNC_ANALYSIS_IS_NOINLINE_SHIFT 4u #define IP_FUNC_ANALYSIS_IS_NOINLINE 0x10u // IP_FUNC_ANALYSIS_HAS_ERROR_TRACE_SHIFT 5u #define IP_FUNC_ANALYSIS_HAS_ERROR_TRACE 0x20u // IP_FUNC_ANALYSIS_INFERRED_ERROR_SET_SHIFT 6u #define IP_FUNC_ANALYSIS_INFERRED_ERROR_SET 0x40u // IP_FUNC_ANALYSIS_DISABLE_INSTRUMENTATION_SHIFT 7u #define IP_FUNC_ANALYSIS_DISABLE_INSTRUMENTATION 0x80u // IP_FUNC_ANALYSIS_DISABLE_INTRINSICS_SHIFT 8u // IP_FUNC_ANALYSIS_DISABLE_INTRINSICS 0x100u // src/InternPool.zig: PackedCallingConvention #define IP_PACKED_CC_TAG_SHIFT 0u #define IP_PACKED_CC_TAG_MASK 0xFFu #define IP_PACKED_CC_INCOMING_STACK_ALIGNMENT_SHIFT 8u #define IP_PACKED_CC_INCOMING_STACK_ALIGNMENT_MASK 0x3Fu #define IP_PACKED_CC_EXTRA_SHIFT 14u #define IP_PACKED_CC_EXTRA_MASK 0xFu // lib/std/lang.zig: AtomicRmwOp enum { ATOMIC_RMW_OP_XCHG = 0, ATOMIC_RMW_OP_ADD = 1, ATOMIC_RMW_OP_SUB = 2, ATOMIC_RMW_OP_AND = 3, ATOMIC_RMW_OP_NAND = 4, ATOMIC_RMW_OP_OR = 5, ATOMIC_RMW_OP_XOR = 6, ATOMIC_RMW_OP_MAX = 7, ATOMIC_RMW_OP_MIN = 8, }; // lib/std/lang.zig: AtomicOrder enum { ATOMIC_ORDER_UNORDERED = 0, ATOMIC_ORDER_MONOTONIC = 1, ATOMIC_ORDER_ACQUIRE = 2, ATOMIC_ORDER_RELEASE = 3, ATOMIC_ORDER_ACQ_REL = 4, ATOMIC_ORDER_SEQ_CST = 5, }; // lib/std/lang.zig: GlobalLinkage enum { GLOBAL_LINKAGE_STRONG = 0, GLOBAL_LINKAGE_WEAK = 1, }; // lib/std/lang.zig: SymbolVisibility enum { SYMBOL_VISIBILITY_DEFAULT = 0, SYMBOL_VISIBILITY_HIDDEN = 1, SYMBOL_VISIBILITY_PROTECTED = 2, }; // lib/std/lang.zig: ExternOptions.Relocation enum { EXTERN_RELOCATION_ANY = 0, EXTERN_RELOCATION_PCREL = 1, }; // lib/std/lang.zig: CallModifier enum { CALL_MODIFIER_AUTO = 0, CALL_MODIFIER_NEVER_TAIL = 1, CALL_MODIFIER_NEVER_INLINE = 2, CALL_MODIFIER_NO_SUSPEND = 3, CALL_MODIFIER_ALWAYS_TAIL = 4, CALL_MODIFIER_ALWAYS_INLINE = 5, CALL_MODIFIER_COMPILE_TIME = 6, }; // lib/std/lang.zig: Type typedef enum { ZIG_TYPE_TAG_TYPE = 0, ZIG_TYPE_TAG_VOID = 1, ZIG_TYPE_TAG_BOOL = 2, ZIG_TYPE_TAG_NORETURN = 3, ZIG_TYPE_TAG_INT = 4, ZIG_TYPE_TAG_FLOAT = 5, ZIG_TYPE_TAG_POINTER = 6, ZIG_TYPE_TAG_ARRAY = 7, ZIG_TYPE_TAG_STRUCT = 8, ZIG_TYPE_TAG_COMPTIME_FLOAT = 9, ZIG_TYPE_TAG_COMPTIME_INT = 10, ZIG_TYPE_TAG_UNDEFINED = 11, ZIG_TYPE_TAG_NULL = 12, ZIG_TYPE_TAG_OPTIONAL = 13, ZIG_TYPE_TAG_ERROR_UNION = 14, ZIG_TYPE_TAG_ERROR_SET = 15, ZIG_TYPE_TAG_ENUM = 16, ZIG_TYPE_TAG_UNION = 17, ZIG_TYPE_TAG_FN = 18, ZIG_TYPE_TAG_OPAQUE = 19, ZIG_TYPE_TAG_FRAME = 20, ZIG_TYPE_TAG_ANYFRAME = 21, ZIG_TYPE_TAG_VECTOR = 22, ZIG_TYPE_TAG_ENUM_LITERAL = 23, ZIG_TYPE_TAG_SPIRV = 24, } ZigTypeTag; // lib/std/lang.zig: Type.Pointer.Size enum { PTR_SIZE_ONE = 0, PTR_SIZE_MANY = 1, PTR_SIZE_SLICE = 2, PTR_SIZE_C = 3, }; // lib/std/lang.zig: Type.ContainerLayout enum { CONTAINER_LAYOUT_AUTO = 0, CONTAINER_LAYOUT_EXTERN = 1, CONTAINER_LAYOUT_PACKED = 2, }; // lib/std/lang.zig: Signedness enum { SIGNEDNESS_SIGNED = 0, SIGNEDNESS_UNSIGNED = 1, }; // lib/std/lang.zig: BranchHint enum { BRANCH_HINT_NONE = 0, BRANCH_HINT_LIKELY = 1, BRANCH_HINT_UNLIKELY = 2, BRANCH_HINT_COLD = 3, BRANCH_HINT_UNPREDICTABLE = 4, }; // lib/std/lang.zig: CallingConvention enum { CC_TAG_AUTO = 0, CC_TAG_ASYNC = 1, CC_TAG_NAKED = 2, CC_TAG_INLINE = 3, CC_TAG_X86_64_SYSV = 4, CC_TAG_X86_64_X32 = 5, CC_TAG_X86_64_WIN = 6, CC_TAG_X86_64_REGCALL_V3_SYSV = 7, CC_TAG_X86_64_REGCALL_V4_WIN = 8, CC_TAG_X86_64_VECTORCALL = 9, CC_TAG_X86_64_INTERRUPT = 10, CC_TAG_X86_64_PRESERVE_NONE = 11, CC_TAG_X86_SYSV = 12, CC_TAG_X86_WIN = 13, CC_TAG_X86_MINGW = 14, CC_TAG_X86_STDCALL = 15, CC_TAG_X86_FASTCALL = 16, CC_TAG_X86_THISCALL = 17, CC_TAG_X86_THISCALL_MINGW = 18, CC_TAG_X86_REGCALL_V3 = 19, CC_TAG_X86_REGCALL_V4_WIN = 20, CC_TAG_X86_VECTORCALL = 21, CC_TAG_X86_INTERRUPT = 22, CC_TAG_X86_16_CDECL = 23, CC_TAG_X86_16_STDCALL = 24, CC_TAG_X86_16_REGPARMCALL = 25, CC_TAG_X86_16_INTERRUPT = 26, CC_TAG_AARCH64_AAPCS = 27, CC_TAG_AARCH64_AAPCS_DARWIN = 28, CC_TAG_AARCH64_AAPCS_WIN = 29, CC_TAG_AARCH64_VFABI = 30, CC_TAG_AARCH64_VFABI_SVE = 31, CC_TAG_AARCH64_PRESERVE_NONE = 32, CC_TAG_ALPHA_OSF = 33, CC_TAG_ARM_AAPCS = 34, CC_TAG_ARM_AAPCS_VFP = 35, CC_TAG_ARM_INTERRUPT = 36, CC_TAG_MIPS64_N64 = 37, CC_TAG_MIPS64_N32 = 38, CC_TAG_MIPS64_INTERRUPT = 39, CC_TAG_MIPS_O32 = 40, CC_TAG_MIPS_INTERRUPT = 41, CC_TAG_RISCV64_LP64 = 42, CC_TAG_RISCV64_LP64_V = 43, CC_TAG_RISCV64_INTERRUPT = 44, CC_TAG_RISCV32_ILP32 = 45, CC_TAG_RISCV32_ILP32_V = 46, CC_TAG_RISCV32_INTERRUPT = 47, CC_TAG_SPARC64_SYSV = 48, CC_TAG_SPARC_SYSV = 49, CC_TAG_POWERPC64_ELF = 50, CC_TAG_POWERPC64_ELF_ALTIVEC = 51, CC_TAG_POWERPC64_ELF_V2 = 52, CC_TAG_POWERPC_SYSV = 53, CC_TAG_POWERPC_SYSV_ALTIVEC = 54, CC_TAG_POWERPC_AIX = 55, CC_TAG_POWERPC_AIX_ALTIVEC = 56, CC_TAG_WASM_MVP = 57, CC_TAG_ARC_SYSV = 58, CC_TAG_ARC_INTERRUPT = 59, CC_TAG_AVR_GNU = 60, CC_TAG_AVR_BUILTIN = 61, CC_TAG_AVR_SIGNAL = 62, CC_TAG_AVR_INTERRUPT = 63, CC_TAG_BPF_STD = 64, CC_TAG_CSKY_SYSV = 65, CC_TAG_CSKY_INTERRUPT = 66, CC_TAG_HEXAGON_SYSV = 67, CC_TAG_HEXAGON_SYSV_HVX = 68, CC_TAG_HPPA_ELF = 69, CC_TAG_HPPA64_ELF = 70, CC_TAG_KVX_LP64 = 71, CC_TAG_KVX_ILP32 = 72, CC_TAG_LANAI_SYSV = 73, CC_TAG_LOONGARCH64_LP64 = 74, CC_TAG_LOONGARCH32_ILP32 = 75, CC_TAG_M68K_SYSV = 76, CC_TAG_M68K_GNU = 77, CC_TAG_M68K_RTD = 78, CC_TAG_M68K_INTERRUPT = 79, CC_TAG_M88K_SYSV = 80, CC_TAG_MICROBLAZE_STD = 81, CC_TAG_MICROBLAZE_INTERRUPT = 82, CC_TAG_MSP430_EABI = 83, CC_TAG_MSP430_INTERRUPT = 84, CC_TAG_OR1K_SYSV = 85, CC_TAG_PROPELLER_SYSV = 86, CC_TAG_S390X_SYSV = 87, CC_TAG_S390X_SYSV_VX = 88, CC_TAG_SH_GNU = 89, CC_TAG_SH_RENESAS = 90, CC_TAG_SH_INTERRUPT = 91, CC_TAG_VE_SYSV = 92, CC_TAG_XCORE_XS1 = 93, CC_TAG_XCORE_XS2 = 94, CC_TAG_XTENSA_CALL0 = 95, CC_TAG_XTENSA_WINDOWED = 96, CC_TAG_AMDGCN_DEVICE = 97, CC_TAG_AMDGCN_KERNEL = 98, CC_TAG_AMDGCN_CS = 99, CC_TAG_NVPTX_DEVICE = 100, CC_TAG_NVPTX_KERNEL = 101, CC_TAG_SPIRV_DEVICE = 102, CC_TAG_SPIRV_VERTEX = 103, CC_TAG_SPIRV_KERNEL = 104, CC_TAG_SPIRV_FRAGMENT = 105, CC_TAG_SPIRV_TASK = 106, CC_TAG_SPIRV_MESH = 107, CC_TAG_EZ80_CET = 108, CC_TAG_EZ80_TIFLAGS = 109, CC_TAG_SPORK8 = 110, }; // lib/std/lang.zig: AddressSpace enum { ADDRESS_SPACE_GENERIC = 0, ADDRESS_SPACE_GS = 1, ADDRESS_SPACE_FS = 2, ADDRESS_SPACE_SS = 3, ADDRESS_SPACE_FAR = 4, ADDRESS_SPACE_GLOBAL = 5, ADDRESS_SPACE_CONSTANT = 6, ADDRESS_SPACE_PARAM = 7, ADDRESS_SPACE_SHARED = 8, ADDRESS_SPACE_LOCAL = 9, ADDRESS_SPACE_PRIVATE = 10, ADDRESS_SPACE_INPUT = 11, ADDRESS_SPACE_OUTPUT = 12, ADDRESS_SPACE_UNIFORM = 13, ADDRESS_SPACE_PUSH_CONSTANT = 14, ADDRESS_SPACE_STORAGE_BUFFER = 15, ADDRESS_SPACE_PHYSICAL_STORAGE_BUFFER = 16, ADDRESS_SPACE_FLASH = 17, ADDRESS_SPACE_FLASH1 = 18, ADDRESS_SPACE_FLASH2 = 19, ADDRESS_SPACE_FLASH3 = 20, ADDRESS_SPACE_FLASH4 = 21, ADDRESS_SPACE_FLASH5 = 22, ADDRESS_SPACE_COG = 23, ADDRESS_SPACE_HUB = 24, ADDRESS_SPACE_LUT = 25, ADDRESS_SPACE_EXTERNREF = 26, ADDRESS_SPACE_FUNCREF = 27, }; // clang-format on #endif /* ---- end upstream_gen.h ---- */ /* ---- begin wasi.h ---- */ // wasi.h — the WASI host the interpreted program's wasi_snapshot_preview1 // imports call, after upstream's stage1/wasi.c (definitions in wasi.c). #ifndef ZIG1_WASI_H #define ZIG1_WASI_H #include #include // The guest's linear memory as the host sees it: the host pointer of the // guest range [addr, addr + len), which aborts when no memory backs it. typedef uint8_t* (*WasiMemory)(uint32_t addr, uint32_t len); // The most descriptors the host has open for the guest at once. #define WASI_FD_CAP 256u // The entries fd_readdir lists, read whole when it starts at cookie 0: an // entry's cookie is its index, so an entry the guest deletes while it // iterates moves no other. typedef struct { uint32_t len; char** names; // owned uint64_t* inos; uint8_t* types; // filetype_t } WasiDirEntries; // A descriptor the host opened for the guest (-1 for a directory, which // has none), the host path it was opened at (owned; NULL: the slot is // free), a directory's entries once fd_readdir lists it (owned), and // whether it holds the lock of the cache file (`dev`, `ino`). typedef struct { int host_fd; char* host_root; WasiDirEntries* entries; bool locked; uint64_t dev, ino; } WasiFd; // The preopened directories: `.`, /lib and /cache. #define WASI_PREOPEN_COUNT 3u // The host's state, borrowing: `argv`, the guest's argv; `lib_dir`, the host // directory the guest sees as /lib (NULL: `lib`); and `global_cache`, the one // it sees as /cache (NULL: none). Each preopen's entries once fd_readdir // lists it are owned. typedef struct { const char* const* argv; uint32_t argc; const char* lib_dir; const char* global_cache; WasiFd fds[WASI_FD_CAP]; WasiDirEntries* preopen_entries[WASI_PREOPEN_COUNT]; } Wasi; // The most wasm-level parameters an import takes (path_open's). #define WASI_MAX_PARAMS 9u // One call of an import: the host, the call's wasm-level arguments (i32 and // i64 alike as uint64_t) and the guest's memory. typedef struct { Wasi* wasi; const uint64_t* args; WasiMemory mem; } WasiCall; // One import the host serves: its name, its `params` arguments, and its // handler, which answers lib/std/os/wasi.zig's errno_t, except proc_exit's // (`returns_errno` false), which answers the exit status the program ends // with. A NULL handler has nothing to do and answers success. typedef struct { const char* name; uint32_t params; bool returns_errno; uint32_t (*call)(const WasiCall* c); } WasiImport; // The wasi_snapshot_preview1 import `name`, or NULL when the host does not // serve it. const WasiImport* wasiImportFind(const char* name); // lib/std/zig.zig resolveGlobalCacheDir: ZIG_GLOBAL_CACHE_DIR, else // %LOCALAPPDATA%\zig on Windows, else $XDG_CACHE_HOME/zig, else // $HOME/.cache/zig (caller frees); NULL for error.AppDataDirUnavailable. char* resolveGlobalCacheDir(void); // The standard streams in binary mode (for Windows), and the directory // `global_cache` created as Directories.init's createDirPathOpen makes it; // false, with errno set, when it cannot be made. bool wasiHostStart(const char* global_cache); // Free the preopens' fd_readdir entries at the end of a run. void wasiPreopenEntriesFree(Wasi* wasi); // 0 when host path `path` is a directory, else an errno (ENOTDIR when it is // not a directory). int wasiDirError(const char* path); #endif /* ---- end wasi.h ---- */ /* ---- begin wyhash.h ---- */ // wyhash.h — ported from lib/std/hash/wyhash.zig. #ifndef ZIG1_WYHASH_H #define ZIG1_WYHASH_H #include #include typedef struct { uint64_t a, b; uint64_t state[3]; uint64_t total_len; uint8_t buf[48]; uint64_t buf_len; } Wyhash; void wyhashInit(Wyhash* h, uint64_t seed); uint64_t wyhashFinal(const Wyhash* h); // Wyhash.hash: one-shot. uint64_t wyhashHash(uint64_t seed, const void* data, size_t len); // std.hash.autoHash-style helpers, feeding little-endian bytes. void wyhashUpdateU32(Wyhash* h, uint32_t val); void wyhashUpdateU64(Wyhash* h, uint64_t val); #endif /* ---- end wyhash.h ---- */ /* ---- begin zig1.h ---- */ // zig1.h — the declarations of every zig1 module that builds on another // zig1 header. Each section mirrors the upstream file its banner names, // except the AIR interpreter, which is zig1's own. // It is also the translate-c root for the zig1 test harness's `c` module. // // The separate headers beside it depend on no other zig1 header and sort // before it, so in `LC_ALL=C cat *.h *.c` each comes before its users. #ifndef ZIG1_H #define ZIG1_H #ifndef ZIG1_COMMON_H #include "common.h" #endif #ifndef ZIG1_COMPILATION_H #include "compilation.h" #endif #ifndef ZIG1_SOFTFLOAT_H #include "softfloat.h" #endif #ifndef ZIG1_UPSTREAM_GEN_H #include "upstream_gen.h" #endif #ifndef ZIG1_WASI_H #include "wasi.h" #endif #ifndef ZIG1_WYHASH_H #include "wyhash.h" #endif // ── big int: lib/std/math/big/int.zig ─────────────────────────────────────── // Upstream keeps every arbitrary-precision integer rule in that one file and // represents a value as `Const { limbs: []const Limb, positive: bool }` — a // magnitude in base 2^Limb, little-endian, plus a sign. There is no two's // complement in the representation; a width only ever enters through a // question asked about the value (`fitsInTwosComp`) or an operation that // truncates to one. This module is that file's home here, and it carries only // what the frontend reaches. // // `Limb` is the HOST's `usize` (`std.math.big.Limb`), 64 bits for the pinned // compiler — see the `limbs` arena comment in the InternPool section. // `std.math.big.int.Const`. typedef struct { const uint64_t* limbs; uint32_t limbs_len; bool positive; } BigIntConst; // `std.math.big.int.Mutable`: `"limbs: []Limb, len: usize, positive: bool"`. // Upstream asserts its callers sized `limbs` for the operation; zig1 cannot // abort inside this module, so the capacity travels with the buffer and every // operation returns false when it is short of the bound its doc comment // names. A false is the caller's cue to decline loudly, never to answer with // a truncated value. typedef struct { uint64_t* limbs; uint32_t limbs_cap; uint32_t len; bool positive; } BigIntMutable; // `Mutable.toConst`: `"self.limbs[0..self.len]"` with the same sign. ZIG1_INLINE BigIntConst bigIntMutableToConst(BigIntMutable r) { return (BigIntConst) { r.limbs, r.len, r.positive }; } // `Const.abs` / `Const.negate`: the same limbs wearing a different sign. ZIG1_INLINE BigIntConst bigIntConstAbs(BigIntConst a) { a.positive = true; return a; } ZIG1_INLINE BigIntConst bigIntConstNegate(BigIntConst a) { a.positive = !a.positive; return a; } // `Mutable.copy`: `"@memcpy(...); self.normalize(other.limbs.len); // self.positive = other.positive or other.eqlZero();"`. Needs // `a.limbs_len` limbs. bool bigIntMutableCopy(BigIntMutable* r, BigIntConst a); int bigIntOrder(BigIntConst a, BigIntConst b); // `Mutable.add`: `"if (r.addCarry(a, b)) { ... r.len = msl + 1; r.limbs[msl] // = 1; }"` -- exact, at any width. Needs // `@max(a.limbs_len, b.limbs_len) + 1` limbs. bool bigIntMutableAdd(BigIntMutable* r, BigIntConst a, BigIntConst b); // `Mutable.sub`: `"r.add(a, b.negate());"`. Same bound as `add`. bool bigIntMutableSub(BigIntMutable* r, BigIntConst a, BigIntConst b); // `Mutable.bitOr` / `bitAnd` / `bitXor`: `a op b` under TWO'S complement // semantics, over sign-and-magnitude storage -- which is why each has its own // `llsigned*` worker rather than one masked word. Upper bounds on `r`: // `@max(a, b)` for `bitOr`; for `bitAnd`, `@min` when both are positive, the // positive side's length when only one is, and `@max + 1` when both are // negative; for `bitXor`, `@max` when the signs agree and `@max + 1` when // they do not. This port hands each the largest of those, which is // `@max(a, b) + 1`. bool bigIntMutableBitOr(BigIntMutable* r, BigIntConst a, BigIntConst b); bool bigIntMutableBitAnd(BigIntMutable* r, BigIntConst a, BigIntConst b); bool bigIntMutableBitXor(BigIntMutable* r, BigIntConst a, BigIntConst b); // `Mutable.shiftLeft`: `"const new_len = llshl(r.limbs, a.limbs, shift); // r.normalize(new_len); r.positive = a.positive;"` -- the magnitude shifts // and the sign rides along. Needs `a.limbs_len + shift / 64 + 1` limbs. bool bigIntMutableShiftLeft(BigIntMutable* r, BigIntConst a, uint64_t shift); // `Mutable.shiftRight`: the magnitude shifts, the sign rides along, and a // nonzero bit shifted out of a negative value rounds its magnitude away from // zero (`"if (nonzero_negative_shiftout) r.addScalar(r.toConst(), -1);"`). // Shifting a negative value out entirely converges to `-1`, not to `0`. // Needs `a.limbs_len + 1` limbs (the `+1` is the `addScalar`'s). bool bigIntMutableShiftRight(BigIntMutable* r, BigIntConst a, uint64_t shift); // `Mutable.mulNoAlias`: `"llmulacc(.add, allocator, rma.limbs, a.limbs, // b.limbs); rma.normalize(a.limbs.len + b.limbs.len); rma.positive = // (a.positive == b.positive);"`, with upstream's one-limb-by-one-limb fast // path in front of it. `r` may not alias `a` or `b`; it needs // `a.limbs_len + b.limbs_len` limbs. bool bigIntMutableMulNoAlias(BigIntMutable* r, BigIntConst a, BigIntConst b); // `Mutable.addScalar`: `add` against a primitive integer, which upstream // builds by `init`ing a Mutable from it. Only the `+1` / `-1` steps inside // this file's own ports reach it. Same bound as `add`. bool bigIntMutableAddScalar(BigIntMutable* r, BigIntConst a, int64_t scalar); // `Const.eqlZero`: `"var d: Limb = 0; for (a.limbs) |limb| d |= limb; return // d == 0;"`. // `calcDivLimbsBufferLen`: `"return a_len + b_len + 4;"` -- the scratch // `divTrunc`/`divFloor` copy their operands into. uint32_t bigIntCalcDivLimbsBufferLen(uint32_t a_len, uint32_t b_len); // `Mutable.divTrunc` / `Mutable.divFloor`: `q = a / b (rem r)`, rounded // toward zero and toward negative infinity respectively, over // `div` -> `divmod` (Knuth D / HAC 14.20). `q` needs `a.limbs_len + 1` limbs // (`divFloor`'s `addScalar(-1)` can want the extra one), `r` needs // `b.limbs_len + 2` (its de-normalizing `shiftRight` and `divFloor`'s // correction want theirs), `q` and `r` may not share limbs, and `b` must be // nonzero -- false for any of those, as everywhere else in this module. bool bigIntMutableDivTrunc(BigIntMutable* q, BigIntMutable* r, BigIntConst a, BigIntConst b, uint64_t* limbs_buffer, uint32_t limbs_buffer_len); bool bigIntMutableDivFloor(BigIntMutable* q, BigIntMutable* r, BigIntConst a, BigIntConst b, uint64_t* limbs_buffer, uint32_t limbs_buffer_len); // `Mutable.divCeil`: as divFloor, rounded toward +inf. bool bigIntMutableDivCeil(BigIntMutable* q, BigIntMutable* r, BigIntConst a, BigIntConst b, uint64_t* limbs_buffer, uint32_t limbs_buffer_len); bool bigIntEqlZero(BigIntConst a); // `Const.clz` / `Const.popCount`: leading zeros and set bits of the // `bits`-wide two's complement form. A negative value has no leading zeros. uint64_t bigIntConstClz(BigIntConst a, uint64_t bits); uint64_t bigIntConstPopCount(BigIntConst a, uint64_t bit_count); // `Const.toFloat(Float, .nearest_even)` for the IEEE format of `float_bits` // (16, 32, 64, 80, 128): the bit pattern in `*out_lo` / `*out_hi`, rounded to // nearest with ties to even (an overflow is the infinity), at any width; // `*out_exact` is its Exactness. False for any other `float_bits`. bool bigIntToFloatBits(BigIntConst a, uint32_t float_bits, uint64_t* out_lo, uint64_t* out_hi, bool* out_exact); // `Const.bitCountAbs`: `"(self.limbs.len - 1) * limb_bits + (limb_bits - // @clz(self.limbs[self.limbs.len - 1]))"` — the bits the magnitude needs. uint64_t bigIntBitCountAbs(BigIntConst a); // `Const.bitCountTwosComp`: the magnitude's bit count, plus one for a // negative value unless that value is exactly a single set bit (`-2^n` needs // n+1 bits, not n+2). uint64_t bigIntBitCountTwosComp(BigIntConst a); // `Const.fitsInTwosComp`: `"if (self.eqlZero()) return true; if (signedness // == .unsigned and !self.positive) return false; return bit_count >= // self.bitCountTwosCompForSignedness(signedness);"`. bool bigIntFitsInTwosComp(BigIntConst a, bool is_signed, uint64_t bit_count); // `Const.toInt(u64)` / `Const.toInt(i64)`: the value as a primitive word, or // false for `error.TargetTooSmall` / `error.NegativeIntoUnsigned` -- the one // reader every "does this wide value fit a word" question asks. bool bigIntToU64(BigIntConst a, uint64_t* out); bool bigIntToI64(BigIntConst a, int64_t* out); // The 2s-complement width rules. // Everything above answers about a value; everything below narrows one to a // width. `truncate` is the only rule that does the narrowing, and `saturate`, // `addWrap`, `mulWrapNoAlias` and `bitNotWrap` are all written in terms of it // upstream -- which is why they arrive together. // `calcTwosCompLimbCount`: `"@max(divCeil(bit_count, @bitSizeOf(Limb)), 1)"`. // A zero-bit integer still needs the one limb this representation stores zero // in. uint32_t bigIntCalcTwosCompLimbCount(uint64_t bit_count); // `Mutable.setTwosCompIntLimit`'s `TwosCompIntLimit`. typedef enum { BIG_INT_LIMIT_MIN, BIG_INT_LIMIT_MAX } BigIntTwosCompLimit; // `Mutable.setTwosCompIntLimit`: the min or max value of an integer of // `bit_count` bits and the given signedness. Needs // `calcTwosCompLimbCount(bit_count)` limbs. bool bigIntMutableSetTwosCompIntLimit(BigIntMutable* r, BigIntTwosCompLimit limit, bool is_signed, uint64_t bit_count); // `Mutable.truncate`: `a` narrowed to `bit_count` bits under two's-complement // semantics, which is where a magnitude-plus-sign representation has to work // for its answer -- the low bits of `|a|` are the answer only when the result // keeps `a`'s sign, and otherwise the complement-and-increment is done // explicitly. `r` may alias `a`. Needs `calcTwosCompLimbCount(bit_count)` // limbs. bool bigIntMutableTruncate( BigIntMutable* r, BigIntConst a, bool is_signed, uint64_t bit_count); // `Mutable.truncate` read as unsigned into the {lo, hi} pair the pool's // 128-bit carriers and the interpreter's scalar both spell a two's-complement // pattern as -- unsigned is what makes the limbs that pattern, so there is no // signedness to pass (a signed `truncate` returns a magnitude beside a sign // this pair has no room for). The only reason it is not a bare `truncate` at // every call is the two-limb buffer, which at this width is a constant -- so // it lives here once instead of at each reader and writer. False for // `bit_count > 128` or a `truncate` that declined. bool bigIntTruncateToU128( BigIntConst a, uint64_t bit_count, uint64_t* lo, uint64_t* hi); // `Mutable.readTwosComplement` and `Const.writeTwosComplement` over a byte // buffer -- the real pair, for the interpreter's memory-backed scalars. Only // the little-endian arms are transcribed: the guest is wasm32, so upstream's // `endian` argument is `.little` at every call here. `read` needs // `calcTwosCompLimbCount(bit_count)` limbs and normalizes; `write` fills the // whole `byte_count` buffer, sign- or zero-extending into an over-wide ABI // slot's padding, and returns false where upstream asserts `fitsInTwosComp`. bool bigIntMutableReadTwosComplement(BigIntMutable* r, const uint8_t* buffer, uint32_t bit_count, bool is_signed); bool bigIntConstWriteTwosComplement( BigIntConst a, uint8_t* buffer, uint32_t byte_count); // `Mutable.readPackedTwosComplement` / `Const.writePackedTwosComplement`: // the same pair at a bit offset (`Value.readFromPackedMemory` / // `writeToPackedMemory`'s integer arms); the byte pair is these at offset 0. bool bigIntMutableReadPackedTwosComplement(BigIntMutable* r, const uint8_t* buffer, uint64_t bit_offset, uint32_t bit_count, bool is_signed); bool bigIntConstWritePackedTwosComplement( BigIntConst a, uint8_t* buffer, uint64_t bit_offset, uint64_t bit_count); // `Mutable.byteSwap`: `a`'s little-endian two's-complement image at // `byte_count` bytes, permuted in place over `r.limbs`' bytes, then read back // in signed-magnitude form. `r` needs `calcTwosCompLimbCount(8 * byte_count)` // limbs and must not alias `a`. bool bigIntMutableByteSwap( BigIntMutable* r, BigIntConst a, bool is_signed, uint32_t byte_count); // `Mutable.bitReverse`: the same image at `bit_count` bits, its low // `bit_count` bits reversed. `r` needs `calcTwosCompLimbCount(bit_count)`. bool bigIntMutableBitReverse( BigIntMutable* r, BigIntConst a, bool is_signed, uint32_t bit_count); // `Mutable.truncate`'s masking half for that same register pair: keep the low // `bits` of {lo, hi} and clear the rest. Exported for the frontend's // `{lo, hi}` writers; the rule is `big.int`'s, so it lives here. void bigIntMaskU128(uint64_t* lo, uint64_t* hi, uint32_t bits); // `Mutable.readTwosComplement`, for the one pattern this tree carries in a // register pair rather than a byte buffer: the low `bits` of {lo, hi} read as // a two's-complement value of `is_signed`, published as a `Const` over // `space` (two limbs). The exact inverse of `bigIntTruncateToU128` above, and // the only reason it is not a bare `readTwosComplement` at every call is that // same two-limb buffer. `bits > 128` has nothing to mask or sign-extend, so // the pair is read as an unsigned magnitude. void bigIntFromTwosCompU128(uint64_t lo, uint64_t hi, uint32_t bits, bool is_signed, uint64_t* space, BigIntConst* out); // `Mutable.saturate`: `"if (!a.fitsInTwosComp(signedness, bit_count)) // r.setTwosCompIntLimit(if (r.positive) .max else .min, signedness, // bit_count);"`. Note the clamp reads `r`'s sign, not `a`'s: upstream's // callers have already written the unsaturated result into `r` and pass // `r.toConst()` as `a`, so the two agree. Needs // `calcTwosCompLimbCount(bit_count)` limbs. bool bigIntMutableSaturate( BigIntMutable* r, BigIntConst a, bool is_signed, uint64_t bit_count); // `Mutable.addWrap`: `a + b` truncated to the type, with `*out_overflow` set // when the exact sum did not fit it. Needs // `calcTwosCompLimbCount(bit_count)` limbs. bool bigIntMutableAddWrap(BigIntMutable* r, BigIntConst a, BigIntConst b, bool is_signed, uint64_t bit_count, bool* out_overflow); // `Mutable.subWrap`: `"return r.addWrap(a, b.negate(), signedness, // bit_count);"`. Same bound. bool bigIntMutableSubWrap(BigIntMutable* r, BigIntConst a, BigIntConst b, bool is_signed, uint64_t bit_count, bool* out_overflow); // `Mutable.addSat` / `Mutable.subSat`: the same sum clamped to the type's own // limits instead of wrapped. Same bound. bool bigIntMutableAddSat(BigIntMutable* r, BigIntConst a, BigIntConst b, bool is_signed, uint64_t bit_count); bool bigIntMutableSubSat(BigIntMutable* r, BigIntConst a, BigIntConst b, bool is_signed, uint64_t bit_count); // `Mutable.setFloat(value, .trunc)` for an f128 `value` given as its four // low-word-first words: the one rounding `Sema.intFromFloatScalar` asks // (`"switch (big_int.setFloat(float, .trunc))"`), so the other `Round` modes // have no port. `*out_exact` is its `Exactness`. The value is finite (the // caller rejected NaN and Inf) and `r` holds `(exponent + 2) / 64 + 3` limbs: // upstream's `calcLimbLen`, plus the one this module's `shiftLeft` wants. bool bigIntMutableSetFloat128Trunc( BigIntMutable* r, const uint32_t words[4], bool* out_exact); // `Mutable.shiftLeftSat`: `a << shift` clamped to the type's own limits. // Needs `calcTwosCompLimbCount(bit_count)` limbs. bool bigIntMutableShiftLeftSat(BigIntMutable* r, BigIntConst a, uint64_t shift, bool is_signed, uint64_t bit_count); // `Const.ctz`: the value's trailing zero bits, clamped to `bits`. A negative // value's two's complement flips only bits above the lowest set one, so the // magnitude answers for either sign. uint64_t bigIntConstCtz(BigIntConst a, uint64_t bits); bool bigIntToU32(BigIntConst a, uint32_t* out); bool bigIntToBitCastI32(BigIntConst a, uint32_t* out); bool bigIntToF128Words(BigIntConst v, uint32_t out[4]); // ── Zir: lib/std/zig/Zir.zig ──────────────────────────────────────────────── // --- ZIR instruction tags (uint8_t) --- // ZIR_INST_FOREACH_TAG (upstream_gen.h) lists Zir.Inst.Tag in declaration // order, so the integer discriminants match `@intFromEnum` exactly. typedef struct { uint32_t name; // NullTerminatedString index into zir->string_bytes uint32_t lib_name; // same, or IP_INDEX_NONE without an explicit library uint32_t id; // Zir.Inst.Declaration.Flags.Id bool has_lib_name; bool has_value_body; bool is_export; // declaration has `export` linkage uint32_t value_body_off; // index into zir->extra of the value body uint32_t value_body_len; // number of instruction indices bool has_type_body; // declaration has an explicit type body (`: T`) uint32_t type_body_off; // index into zir->extra of the type body uint32_t type_body_len; // number of instruction indices bool has_align_body; uint32_t align_body_off; uint32_t align_body_len; bool has_linksection_body; uint32_t linksection_body_off; uint32_t linksection_body_len; bool has_addrspace_body; uint32_t addrspace_body_off; uint32_t addrspace_body_len; } DeclInfo; #define ZIR_GENERATE_ENUM(e) e, typedef enum { ZIR_INST_FOREACH_TAG(ZIR_GENERATE_ENUM) } ZirInstTag; // --- ZIR extended opcodes (uint16_t) --- // ZIR_EXT_FOREACH_TAG (upstream_gen.h) lists Zir.Inst.Extended in order. #define ZIR_EXT_GENERATE_ENUM(e) e, typedef enum { ZIR_EXT_FOREACH_TAG(ZIR_EXT_GENERATE_ENUM) } ZirInstExtended; // Zir.Inst.FullPtrCastFlags, the `small` word of `ptr_cast_full` / // `ptr_cast_no_dest`. #define ZIR_PTR_CAST_FLAG_PTR ZIR_PTR_CAST_FLAG_PTR_CAST #define ZIR_PTR_CAST_FLAG_ALIGN ZIR_PTR_CAST_FLAG_ALIGN_CAST #define ZIR_PTR_CAST_FLAG_ADDRSPACE ZIR_PTR_CAST_FLAG_ADDRSPACE_CAST #define ZIR_PTR_CAST_FLAG_CONST ZIR_PTR_CAST_FLAG_CONST_CAST #define ZIR_PTR_CAST_FLAG_VOLATILE ZIR_PTR_CAST_FLAG_VOLATILE_CAST // `Zir.Inst.StdLangValue`, the `small` of a `std_lang_value` extended // instruction, is STD_LANG_VALUE_* (upstream_gen.h). // Is `tag` one of the ZIR function-definition instructions? static inline bool zirIsFuncInst(ZirInstTag tag) { return tag == ZIR_INST_FUNC || tag == ZIR_INST_FUNC_INFERRED || tag == ZIR_INST_FUNC_FANCY; } // Zir.Inst.UnionDecl.Kind.hasArgType (Zir.zig): `union(T)` // (tagged_explicit=1), `union(enum(T))` (tagged_enum_explicit=3) and `packed // union(T)` (packed_explicit=6) carry an arg_type_body_len count word before // captures[]; the other four kinds do not. Both decoders of the UnionDecl // trailing order ask here rather than restating the kind set. static inline bool zirUnionKindHasArgType(uint32_t kind) { return kind == ZIR_UNION_KIND_TAGGED_EXPLICIT || kind == ZIR_UNION_KIND_TAGGED_ENUM_EXPLICIT || kind == ZIR_UNION_KIND_PACKED_EXPLICIT; } // --- ZIR instruction data (8-byte union) --- // Mirrors Zir.Inst.Data from Zir.zig. Zig's union has no guaranteed layout, so // the test harness compares field-by-field, keyed by tag. typedef uint32_t ZirInstIndex; typedef uint32_t ZirInstRef; typedef union { struct { uint16_t opcode; uint16_t small; uint32_t operand; } extended; struct { int32_t src_node; ZirInstRef operand; } un_node; struct { int32_t src_tok; ZirInstRef operand; } un_tok; struct { int32_t src_node; uint32_t payload_index; } pl_node; struct { int32_t src_tok; uint32_t payload_index; } pl_tok; struct { ZirInstRef lhs; ZirInstRef rhs; } bin; struct { uint32_t start; uint32_t len; } str; struct { uint32_t start; int32_t src_tok; } str_tok; int32_t tok; int32_t node; uint64_t int_val; double float_val; struct { // Field order matches Zig's in-memory layout (payload_index, a u32, // is placed first by alignment; flags/size are u8). The upper 16 bits // of the second word are undefined padding and are masked out by the // ZIR comparison harness. uint32_t payload_index; uint8_t flags; uint8_t size; uint16_t _pad; } ptr_type; struct { int32_t src_node; // Zig reorders fields for optimal packing: bit_count (u16, align 2) // before signedness (enum(u1)/u8, align 1). Values: signed=0, // unsigned=1. uint16_t bit_count; uint8_t signedness; uint8_t _pad; } int_type; struct { int32_t src_node; uint32_t _pad; } unreachable_data; struct { ZirInstRef operand; uint32_t payload_index; } break_data; struct { uint32_t line; uint32_t column; } dbg_stmt; struct { int32_t src_node; ZirInstIndex inst; } inst_node; struct { uint32_t str; ZirInstRef operand; } str_op; struct { uint32_t index; uint32_t len; } defer_data; struct { ZirInstRef operand; uint32_t _pad; } save_err_ret_index; struct { ZirInstRef operand; uint32_t idx; } elem_val_imm; struct { uint32_t src_node; uint32_t payload_index; } declaration; } ZirInstData; // --- ZIR built-in refs --- // Mirrors Zir.Inst.Ref from Zir.zig. Values below ZIR_REF_START_INDEX are // InternPool indices; values >= it are `instruction_index + // ZIR_REF_START_INDEX`. // Zir.Inst.Ref.static_len: the first instruction ref. #define ZIR_REF_START_INDEX (ZIR_REF_EMPTY_TUPLE + 1u) #define ZIR_MAIN_STRUCT_INST 0 // Zir.Inst.Ref's static values are ZIR_REF_* (upstream_gen.h). // Ast.Node.OptionalOffset.none = maxInt(i32). #define AST_NODE_OFFSET_NONE ((int32_t)0x7FFFFFFF) // --- Extra indices reserved at the start of extra[] --- // Zir.ExtraIndex is ZIR_EXTRA_* (upstream_gen.h); the reserved words are all // of its named fields. #define ZIR_EXTRA_RESERVED_COUNT (ZIR_EXTRA_IMPORTS + 1u) // --- Zir output structure --- // The field names/layout are consumed by the byte-identical test harness. typedef struct { uint32_t inst_len; uint32_t inst_cap; uint8_t* inst_tags; ZirInstData* inst_datas; uint32_t extra_len; uint32_t extra_cap; uint32_t* extra; uint32_t string_bytes_len; uint32_t string_bytes_cap; uint8_t* string_bytes; bool has_compile_errors; } Zir; void zirDeinit(Zir* zir); // Zir.zig:extraData // `"Returns the requested data, as well as the new index which is at the // start of the trailers for the object."` The payload at extra[index] into // `*data`, a ZirInst* struct (upstream_gen.h), one u32 word per field; // returns `.end`. #define zirExtraData(zir, index, data) \ zirExtraDataSized((zir), (index), (data), sizeof *(data)) static inline uint32_t zirExtraDataSized( const Zir* zir, uint32_t index, void* data, size_t size) { memcpy(data, &zir->extra[index], size); return index + (uint32_t)(size / sizeof(uint32_t)); } // Zir.zig:bodySlice // Also Zir.refSlice's and any trailing words' slice: all are `extra` from // `start`. static inline const uint32_t* zirBodySlice(const Zir* zir, uint32_t start) { return &zir->extra[start]; } // Zir.zig:nullTerminatedString static inline const char* zirNullTerminatedString( const Zir* zir, uint32_t index) { return (const char*)&zir->string_bytes[index]; } // (part of) Zir.zig:getDeclaration // The 64-bit Declaration.Flags of `declaration` instruction `inst`, after its // four src_hash words. static inline uint64_t zirDeclarationFlags(const Zir* zir, uint32_t inst) { uint32_t p = zir->inst_datas[inst].declaration.payload_index; return (uint64_t)zir->extra[p + 4] | ((uint64_t)zir->extra[p + 5] << 32); } // Its Declaration.Flags.Id. static inline uint32_t zirDeclarationId(const Zir* zir, uint32_t inst) { return ( uint32_t)((zirDeclarationFlags(zir, inst) >> ZIR_DECL_FLAGS_ID_SHIFT) & ZIR_DECL_FLAGS_ID_MASK); } // Argument `ai` of a call's trailing arg bodies at `abase` (Sema.CallArgsInfo // .analyzeArg: `"const arg_body = sema.code.bodySlice(start, end - start)"`), // which ends in the `break_inline` naming the argument. Returns its length. static inline uint32_t semaCallArgBody(const Zir* zir, uint32_t abase, uint32_t args_len, uint32_t ai, const uint32_t** out_body) { uint32_t start = ai == 0 ? args_len : zir->extra[abase + ai - 1]; uint32_t end = zir->extra[abase + ai]; if (end <= start) UNREACHABLE("call: empty arg body"); *out_body = &zir->extra[abase + start]; if (zir->inst_tags[(*out_body)[end - start - 1]] != ZIR_INST_BREAK_INLINE) UNREACHABLE("call: arg body not break_inline"); return end - start; } // Zir.UnwrappedSwitchBlock: the decoded trailing data of a `switch_block`, // `switch_block_ref` or `switch_block_err_union`. An absent placeholder is // UINT32_MAX (`.none`); `multi_case_ranges_lens` is NULL without ranges. // Case bodies (prong body, item bodies, range bodies) start at `end`. typedef struct { uint32_t main_operand; uint32_t payload_capture_placeholder; uint32_t tag_capture_placeholder; bool has_continue; bool any_maybe_runtime_capture; bool has_under; uint32_t non_err_info; // ProngInfo.NonErr (switch_block_err_union) const uint32_t* non_err_body; bool has_else; uint32_t else_info; // ProngInfo.Else: body_len u27, capture u2, ... const uint32_t* else_body; uint32_t else_body_len; uint32_t else_capture; bool else_is_inline; uint32_t scalar_cases_len; uint32_t multi_cases_len; const uint32_t* prong_infos; const uint32_t* multi_case_items_lens; const uint32_t* multi_case_ranges_lens; const uint32_t* item_infos; uint32_t item_infos_len; uint32_t end; } ZirSwitchBlock; ZirSwitchBlock zirGetSwitchBlock(const Zir* zir, uint32_t switch_inst); // The trailing count words of a struct, enum, union or opaque decl // (Zir.zig getStructDecl/getEnumDecl/getUnionDecl/getOpaqueDecl): the // lengths, the backing-int / tag type / union arg type body's length (0 // without one), and the extra index of captures[], which capture_names[] // and decls[] follow. typedef struct { uint32_t captures_len; uint32_t decls_len; uint32_t fields_len; uint32_t arg_body_len; uint32_t captures; } ZirContainerTrailing; ZirContainerTrailing zirContainerTrailingWords(const Zir* zir, uint32_t inst); ZIG1_ATTRIBUTE((noreturn)) void zirAbortUnhandled(uint32_t tag, bool extended); // The declaration kind (Declaration.Unwrapped.Kind), derived from its Id. typedef enum { ZIR_DECL_KIND_CONST, ZIR_DECL_KIND_VAR, ZIR_DECL_KIND_COMPTIME, ZIR_DECL_KIND_TEST, ZIR_DECL_KIND_DECLTEST, ZIR_DECL_KIND_UNNAMED_TEST, } ZirDeclKind; ZirDeclKind zirDeclIdKind(uint32_t id); bool zirDeclIdIsPub(uint32_t id); bool zirDeclIdIsThreadlocal(uint32_t id); bool zirDeclIdIsExtern(uint32_t id); uint32_t zirDeclIdName(const Zir* zir, uint32_t inst); // Zir.zig UnwrappedStructDecl's trailing arrays (extra indices, // UINT32_MAX for an absent optional one) and its FieldIterator, which // starts at field_bodies (`{ 0, d.field_bodies }`). typedef struct { uint32_t captures_len; uint32_t decls_len; uint32_t fields_len; uint32_t decls; uint32_t field_names; uint32_t field_type_body_lens; uint32_t field_align_body_lens; uint32_t field_default_body_lens; uint32_t field_comptime_bits; uint32_t backing_int_type_body; uint32_t backing_int_type_body_len; uint32_t field_bodies; } ZirStructDecl; typedef struct { uint32_t idx; uint32_t name; uint32_t type_body, type_body_len; uint32_t align_body, align_body_len; uint32_t default_body, default_body_len; bool is_comptime; } ZirStructField; typedef struct { uint32_t next_idx; uint32_t bodies; } ZirStructFieldIterator; ZirStructDecl zirGetStructDecl(const Zir* zir, uint32_t struct_decl); bool zirStructFieldIteratorNext(const Zir* zir, const ZirStructDecl* d, ZirStructFieldIterator* it, ZirStructField* out); // Zir.zig Inst.Declaration.Flags.Id's hasName, hasLibName, hasTypeBody, // hasValueBody and hasSpecialBodies, shared by AstGen and Sema. static inline bool zirDeclIdHasName(DeclId id) { return id != DECL_ID_UNNAMED_TEST && id != DECL_ID_COMPTIME; } static inline bool zirDeclIdHasLibName(DeclId id) { switch (id) { case DECL_ID_EXTERN_CONST: case DECL_ID_PUB_EXTERN_CONST: case DECL_ID_EXTERN_VAR: case DECL_ID_EXTERN_VAR_THREADLOCAL: case DECL_ID_PUB_EXTERN_VAR: case DECL_ID_PUB_EXTERN_VAR_THREADLOCAL: return true; default: return false; } } static inline bool zirDeclIdHasTypeBody(DeclId id) { switch (id) { case DECL_ID_UNNAMED_TEST: case DECL_ID_TEST: case DECL_ID_DECLTEST: case DECL_ID_COMPTIME: case DECL_ID_CONST_SIMPLE: case DECL_ID_PUB_CONST_SIMPLE: case DECL_ID_VAR_SIMPLE: case DECL_ID_PUB_VAR_SIMPLE: return false; default: return true; } } static inline bool zirDeclIdHasValueBody(DeclId id) { switch (id) { case DECL_ID_EXTERN_CONST_SIMPLE: case DECL_ID_EXTERN_CONST: case DECL_ID_PUB_EXTERN_CONST_SIMPLE: case DECL_ID_PUB_EXTERN_CONST: case DECL_ID_EXTERN_VAR: case DECL_ID_EXTERN_VAR_THREADLOCAL: case DECL_ID_PUB_EXTERN_VAR: case DECL_ID_PUB_EXTERN_VAR_THREADLOCAL: return false; default: return true; } } static inline bool zirDeclIdHasSpecialBodies(DeclId id) { switch (id) { case DECL_ID_UNNAMED_TEST: case DECL_ID_TEST: case DECL_ID_DECLTEST: case DECL_ID_COMPTIME: case DECL_ID_CONST_SIMPLE: case DECL_ID_CONST_TYPED: case DECL_ID_PUB_CONST_SIMPLE: case DECL_ID_PUB_CONST_TYPED: case DECL_ID_EXTERN_CONST_SIMPLE: case DECL_ID_PUB_EXTERN_CONST_SIMPLE: case DECL_ID_VAR_SIMPLE: case DECL_ID_PUB_VAR_SIMPLE: return false; default: return true; } } bool zirFuncInstIsInferred(const Zir* zir, uint32_t func_inst); bool zirDecodeDeclaration(const Zir* zir, uint32_t inst, DeclInfo* out); bool zirDecodeFunctionDeclaration( const Zir* zir, uint32_t inst, DeclInfo* out); bool zirRefIsThis(const Zir* zir, uint32_t ref); // Decode a ZIR `func_fancy` instruction's payload + trailing sections into // `out`. Defined in zir.c; shared by navFuncDecl (signature // analysis) and zptAnalyzeFuncBody (body lowering). // Zir.FnInfo: a function instruction's bodies and return type, with each // body's length beside it. typedef struct { const uint32_t* param_body; uint32_t param_body_len; uint32_t param_body_inst; const uint32_t* ret_ty_body; uint32_t ret_ty_body_len; const uint32_t* body; uint32_t body_len; uint32_t ret_ty_ref; bool ret_ty_is_generic; uint32_t total_params_len; bool inferred_error_set; } ZirFnInfo; const uint32_t* zirGetParamBody( const Zir* zir, uint32_t fn_inst, uint32_t* len); ZirFnInfo zirGetFnInfo(const Zir* zir, uint32_t fn_inst); // ── Air: src/Air.zig ──────────────────────────────────────────────────────── // Analyzed Intermediate Representation: the AIR instruction encoding // and the per-function AIR container produced by sema.c and byte-compared // against the reference compiler. // --- AIR instruction tags (uint8_t) --- // AIR_INST_FOREACH_TAG (upstream_gen.h) lists Air.Inst.Tag in declaration // order, so the integer discriminants match `@intFromEnum` exactly. // Air.CondBr.BranchHints with both coverage points `.poi`. #define AIR_COND_BR_HINTS(true_hint, false_hint) \ (((uint32_t)(true_hint) << AIR_BRANCH_HINTS_TRUE_SHIFT) \ | ((uint32_t)(false_hint) << AIR_BRANCH_HINTS_FALSE_SHIFT) \ | ((uint32_t)AIR_COVERAGE_POINT_POI \ << AIR_BRANCH_HINTS_THEN_COV_SHIFT) \ | ((uint32_t)AIR_COVERAGE_POINT_POI \ << AIR_BRANCH_HINTS_ELSE_COV_SHIFT)) // AIR `Ref` encoding (Air.zig): a Ref below this base is an InternPool value // index (a comptime-known constant); at/above it, `ref - AIR_REF_INST_BASE` is // an AIR instruction index (a runtime value, e.g. a function parameter's `arg` // instruction). `inst_index.toRef()` == AIR_REF_INST_BASE | inst_index. #define AIR_REF_INST_BASE 0x80000000u // Air.Cmpxchg.flags and Air.AtomicRmw.flags are hand-packed upstream: // "@as(u3, @truncate(self.flags))" is the ordering and "self.flags >> 3" // the failure ordering or the AtomicRmwOp. #define AIR_ATOMIC_FLAGS_HIGH_SHIFT 3u #define AIR_ATOMIC_RMW_OP_MASK 0xFu // Air.ShuffleTwoMask: "aElem(idx) = idx << 1", "bElem(idx) = idx << 1 | 1". #define AIR_SHUFFLE_TWO_MASK_INDEX_SHIFT 1u #define AIR_SHUFFLE_TWO_MASK_B_ELEM 1u // Air.Inst.Data.prefetch { rw, locality: u2, cache }, packed in its word. #define AIR_PREFETCH_RW_SHIFT 0u #define AIR_PREFETCH_LOCALITY_SHIFT 1u #define AIR_PREFETCH_CACHE_SHIFT 3u // Air.SwitchBr's trailing BranchHints: "bit-packed into `u32` elems such // that each `u32` contains up to 10x `BranchHint`". #define AIR_SWITCH_BR_HINT_BITS 3u #define AIR_SWITCH_BR_HINTS_PER_WORD 10u #define AIR_GENERATE_ENUM(name) name, typedef enum { AIR_INST_FOREACH_TAG(AIR_GENERATE_ENUM) } AirInstTag; #undef AIR_GENERATE_ENUM // Air.Inst.Data is an 8-byte union. sema.c writes concrete variants; the // harness compares the meaningful bytes per tag. We keep it as a raw 8-byte // blob here so byte-identical comparison against the reference is trivial; // typed accessors are added as sema.c grows. typedef union { uint8_t bytes[8]; struct { uint32_t a; uint32_t b; } pair; } AirInstData; // Per-function AIR arrays (mirrors the fields the reference dumps). typedef struct { uint32_t inst_len; // Air.Inst.Tag is `enum(u8)` upstream: one byte per instruction. Stored as // bytes so the byte-identical comparison against the reference AIR (which // dumps the u8 tag array) matches for functions with >1 instruction. uint8_t* inst_tags; // [inst_len], each a (uint8_t)AirInstTag AirInstData* inst_datas; // [inst_len] uint32_t extra_len; uint32_t* extra; // [extra_len] } Air; void airDeinit(Air* air); // `cmp_vector`'s Air.VectorCmp carries its comparison as a bare `op: u32` // rather than an AIR tag: `"pub fn encodeOp(compare_operator: // std.math.CompareOperator) u32 { return @intFromEnum(compare_operator); }"` // and `"pub fn compareOperator(self: VectorCmp) std.math.CompareOperator { // return @enumFromInt(@as(u3, @intCast(self.op))); }"` (src/Air.zig). The // ordinals are std.math.CompareOperator's declaration order (lib/std/math.zig: // `"pub const CompareOperator = enum { lt, lte, eq, gte, gt, neq };"`), which // is also the order each operator's scalar `cmp_*` tag appears in above -- so // the sema side encodes and the interpreter decodes through one table, and // the ordering is written down exactly once. bool airVectorCmpEncodeOp(AirInstTag scalar_cmp, uint32_t* out); bool airVectorCmpDecodeOp(uint32_t op, AirInstTag* out); // `reduce`'s operation travels as std.lang.ReduceOp's declaration field index // (lib/std/lang.zig: // `"pub const ReduceOp = enum { And, Or, Xor, Min, Max, Add, Mul };"`). // Sema.zirReduce's fold defines each one in terms of a scalar operation: // And/Or/Xor/Add/Mul combine the accumulator through that scalar AIR tag, // and Min/Max are `Value.numberMin`/`numberMax`, i.e. one comparison // selecting between the two. This table is the one place that order is // written down; every other site derives what it needs from `scalar_op` // (whether the operation is bitwise, for instance) rather than respelling it. typedef enum { AIR_REDUCE_COMBINE, // accumulate through `scalar_op` AIR_REDUCE_MIN, AIR_REDUCE_MAX, } AirReduceKind; bool airReduceOp(uint32_t op, AirReduceKind* kind, AirInstTag* scalar_op); // ── tokenizer: lib/std/zig/tokenizer.zig ──────────────────────────────────── // Token.Tag, in upstream's order (TOKEN_FOREACH_TAG, upstream_gen.h). A // token-tag column stores it as uint8_t, as upstream's u8-backed enum. #define TOKEN_GENERATE_ENUM(e) e, typedef enum { TOKEN_FOREACH_TAG(TOKEN_GENERATE_ENUM) } TokenTag; // Token.Loc. typedef struct { uint32_t start; uint32_t end; } TokenLoc; // Token. typedef struct { TokenTag tag; TokenLoc loc; } Token; // Tokenizer.State (TOKENIZER_STATE_FOREACH_TAG, upstream_gen.h). #define TOKENIZER_STATE_GENERATE_ENUM(e) e, typedef enum { TOKENIZER_STATE_FOREACH_TAG(TOKENIZER_STATE_GENERATE_ENUM) } TokenizerState; // Tokenizer: `buffer` is NUL-terminated at `buffer_len`. typedef struct { const char* buffer; const uint32_t buffer_len; uint32_t index; } Tokenizer; Tokenizer tokenizerInit(const char* buffer, uint32_t len); Token tokenizerNext(Tokenizer* self); // Token.Tag.lexeme: the fixed spelling of `tag`, or NULL. const char* tokenTagLexeme(TokenTag tag); // Token.Tag.symbol: the lexeme, or a description of a token that has none. const char* tokenTagSymbol(TokenTag tag); // ── number_literal: lib/std/zig/number_literal.zig ────────────────────────── // Result, as a tag and the payload fields its variants use. typedef enum { NUMBER_LITERAL_INT, NUMBER_LITERAL_BIG_INT, NUMBER_LITERAL_FLOAT, NUMBER_LITERAL_FAILURE, } NumberLiteralResultTag; typedef struct { NumberLiteralResultTag tag; uint64_t int_val; // valid when tag == NUMBER_LITERAL_INT uint8_t base; // Base or FloatBase: 2, 8, 10 or 16 // NUMBER_LITERAL_FAILURE: the Error and the byte it names. NumberLiteralError failure; uint32_t failure_index; } NumberLiteralResult; // parseNumberLiteral: a number literal token's value or its first error. NumberLiteralResult parseNumberLiteral(const char* bytes, uint32_t len); // ── parse_float: lib/std/fmt/parse_float/ ────────────────────────────────── // parse_float.zig:parseFloat at f128, of a number literal the tokenizer // accepted: the binary128 bit pattern of its positive value. CrtU128 parseFloatF128(const char* bytes, uint32_t len); // ── BuiltinFn: lib/std/zig/BuiltinFn.zig ──────────────────────────────────── // BuiltinFn.Tag (BUILTIN_FN_FOREACH_TAG, upstream_gen.h), spelled as upstream. #define BFN_GENERATE_ENUM(e) e, typedef enum { BUILTIN_FN_FOREACH_TAG(BFN_GENERATE_ENUM) } BuiltinFnTag; // BuiltinFn; a param_count of -1 is upstream's null (any number). typedef struct { BuiltinFnTag tag; BuiltinFnEvalToError eval_to_error; bool allows_lvalue; bool illegal_outside_function; int8_t param_count; } BuiltinFn; // BuiltinFn.list.get: NULL for a name that is no builtin. const BuiltinFn* builtinFnListGet(const char* name, uint32_t name_len); // ── Ast: lib/std/zig/Ast.zig ──────────────────────────────────────────────── // Node.Tag, in upstream's order (AST_NODE_FOREACH_TAG, upstream_gen.h); a // node-tag column stores it as uint8_t, as upstream's u8-backed enum. #define AST_NODE_GENERATE_ENUM(e) e, typedef enum { AST_NODE_FOREACH_TAG(AST_NODE_GENERATE_ENUM) } AstNodeTag; typedef uint32_t AstTokenIndex; typedef uint32_t AstNodeIndex; typedef uint32_t AstIndex; typedef struct { AstIndex lhs; AstIndex rhs; } AstData; typedef struct { uint32_t len; uint32_t cap; uint8_t* tags; AstTokenIndex* main_tokens; AstData* datas; } AstNodeList; typedef struct { AstNodeTag tag; AstTokenIndex main_token; AstData data; } AstNodeItem; typedef struct { uint32_t len; uint32_t cap; uint8_t* tags; AstIndex* starts; } AstTokenList; typedef SLICE(AstNodeIndex) AstNodeIndexSlice; typedef struct { const char* source; uint32_t source_len; AstTokenList tokens; AstNodeList nodes; AstNodeIndexSlice extra_data; AstMode mode; bool has_error; // The heap copy of `source` that astDeinit frees; NULL when the caller // owns `source`. char* owned_source; // The first parse error's message (NULL without one) and its token; with // `err_tok_is_prev` it is reported at the token's end. char* err_msg; AstTokenIndex err_tok; bool err_tok_is_prev; } Ast; // Node.SubRange. typedef struct AstSubRange { AstNodeIndex start; AstNodeIndex end; } AstSubRange; Ast astParse(const char* source, uint32_t len, AstMode mode); void astDeinit(Ast*); // Node.OptionalIndex.none and OptionalTokenIndex.none; also the parser's // null for `?Node.Index` and `?TokenIndex`. #define AST_NODE_NONE UINT32_MAX #define AST_TOKEN_NONE UINT32_MAX // `[]const Node.Index`. typedef struct { const AstNodeIndex* ptr; uint32_t len; } AstNodeSlice; // Ast.zig:tokenTag static inline TokenTag astTokenTag(const Ast* tree, AstTokenIndex token) { return tree->tokens.tags[token]; } // Ast.zig:tokenStart static inline uint32_t astTokenStart(const Ast* tree, AstTokenIndex token) { return tree->tokens.starts[token]; } // Ast.zig:nodeTag static inline AstNodeTag astNodeTag(const Ast* tree, AstNodeIndex node) { return tree->nodes.tags[node]; } // Ast.zig:nodeMainToken static inline AstTokenIndex astNodeMainToken( const Ast* tree, AstNodeIndex node) { return tree->nodes.main_tokens[node]; } // Ast.zig:nodeData static inline AstData astNodeData(const Ast* tree, AstNodeIndex node) { return tree->nodes.datas[node]; } bool astIsTokenPrecededByTags(const Ast* tree, AstTokenIndex token, const TokenTag* expected, uint32_t expected_len); // Ast.zig:tokenSlice, as a pointer into the source and a length. void astTokenSlice( const Ast* tree, AstTokenIndex token, const char** out, uint32_t* out_len); // BuiltinFn.list.get(tree.tokenSlice(token)) (builtin_fn.c). const BuiltinFn* builtinFnListGetToken(const Ast* tree, AstTokenIndex token); AstTokenIndex astFirstToken(const Ast* tree, AstNodeIndex node); AstNodeSlice astRootDecls(const Ast* tree); AstTokenIndex astLastToken(const Ast* tree, AstNodeIndex node); // Ast.full: each `?TokenIndex` is AST_TOKEN_NONE for null, and each // Node.OptionalIndex is AST_NODE_NONE for .none. typedef struct { AstTokenIndex mut_token; AstNodeIndex type_node; AstNodeIndex align_node; AstNodeIndex addrspace_node; AstNodeIndex section_node; AstNodeIndex init_node; } AstFullVarDeclComponents; typedef struct { AstTokenIndex visib_token; AstTokenIndex extern_export_token; AstTokenIndex lib_name; AstTokenIndex threadlocal_token; AstTokenIndex comptime_token; AstFullVarDeclComponents ast; } AstFullVarDecl; typedef struct { AstNodeSlice variables; AstTokenIndex equal_token; AstNodeIndex value_expr; } AstFullAssignDestructureComponents; typedef struct { AstTokenIndex comptime_token; AstFullAssignDestructureComponents ast; } AstFullAssignDestructure; typedef struct { AstTokenIndex if_token; AstNodeIndex cond_expr; AstNodeIndex then_expr; AstNodeIndex else_expr; } AstFullIfComponents; typedef struct { AstTokenIndex payload_token; AstTokenIndex error_token; // Undefined when else_expr is none. AstTokenIndex else_token; AstFullIfComponents ast; } AstFullIf; typedef struct { AstTokenIndex while_token; AstNodeIndex cond_expr; AstNodeIndex cont_expr; AstNodeIndex then_expr; AstNodeIndex else_expr; } AstFullWhileComponents; typedef struct { AstFullWhileComponents ast; AstTokenIndex inline_token; AstTokenIndex label_token; AstTokenIndex payload_token; AstTokenIndex error_token; // Undefined when else_expr is none. AstTokenIndex else_token; } AstFullWhile; typedef struct { AstTokenIndex for_token; AstNodeSlice inputs; AstNodeIndex then_expr; AstNodeIndex else_expr; } AstFullForComponents; typedef struct { AstFullForComponents ast; AstTokenIndex inline_token; AstTokenIndex label_token; AstTokenIndex payload_token; // Undefined when else_expr is none. AstTokenIndex else_token; } AstFullFor; typedef struct { AstTokenIndex main_token; AstNodeIndex type_expr; AstNodeIndex align_expr; AstNodeIndex value_expr; bool tuple_like; } AstFullContainerFieldComponents; typedef struct { AstTokenIndex comptime_token; AstFullContainerFieldComponents ast; } AstFullContainerField; typedef struct { AstNodeIndex proto_node; AstTokenIndex fn_token; AstNodeIndex return_type; AstNodeSlice params; AstNodeIndex align_expr; AstNodeIndex addrspace_expr; AstNodeIndex section_expr; AstNodeIndex callconv_expr; } AstFullFnProtoComponents; typedef struct { AstTokenIndex visib_token; AstTokenIndex extern_export_inline_token; AstTokenIndex lib_name; AstTokenIndex name_token; AstTokenIndex lparen; AstFullFnProtoComponents ast; } AstFullFnProto; // full.FnProto.Param; type_expr is AST_NODE_NONE for null. typedef struct { AstTokenIndex first_doc_comment; AstTokenIndex name_token; AstTokenIndex comptime_noalias; AstTokenIndex anytype_ellipsis3; AstNodeIndex type_expr; } AstFullFnProtoParam; // full.FnProto.Iterator. typedef struct { const Ast* tree; const AstFullFnProto* fn_proto; uint32_t param_i; AstTokenIndex tok_i; bool tok_flag; } AstFullFnProtoIterator; typedef struct { AstTokenIndex lbrace; AstNodeSlice fields; AstNodeIndex type_expr; } AstFullStructInitComponents; typedef struct { AstFullStructInitComponents ast; } AstFullStructInit; typedef struct { AstTokenIndex lbrace; AstNodeSlice elements; AstNodeIndex type_expr; } AstFullArrayInitComponents; typedef struct { AstFullArrayInitComponents ast; } AstFullArrayInit; typedef struct { AstTokenIndex lbracket; AstNodeIndex elem_count; AstNodeIndex sentinel; AstNodeIndex elem_type; } AstFullArrayTypeComponents; typedef struct { AstFullArrayTypeComponents ast; } AstFullArrayType; typedef struct { AstTokenIndex main_token; AstNodeIndex align_node; AstNodeIndex addrspace_node; AstNodeIndex sentinel; AstNodeIndex bit_range_start; AstNodeIndex bit_range_end; AstNodeIndex child_type; } AstFullPtrTypeComponents; typedef struct { // std.lang.Type.Pointer.Size, as PTR_SIZE_* (upstream_gen.h). uint32_t size; AstTokenIndex allowzero_token; AstTokenIndex const_token; AstTokenIndex volatile_token; AstTokenIndex duplicate_token; AstFullPtrTypeComponents ast; } AstFullPtrType; typedef struct { AstNodeIndex sliced; AstTokenIndex lbracket; AstNodeIndex start; AstNodeIndex end; AstNodeIndex sentinel; } AstFullSliceComponents; typedef struct { AstFullSliceComponents ast; } AstFullSlice; typedef struct { AstTokenIndex main_token; AstTokenIndex enum_token; AstNodeSlice members; AstNodeIndex arg; } AstFullContainerDeclComponents; typedef struct { AstTokenIndex layout_token; AstFullContainerDeclComponents ast; } AstFullContainerDecl; typedef struct { AstTokenIndex switch_token; AstNodeIndex condition; AstNodeSlice cases; } AstFullSwitchComponents; typedef struct { AstFullSwitchComponents ast; AstTokenIndex label_token; } AstFullSwitch; typedef struct { AstNodeSlice values; AstTokenIndex arrow_token; AstNodeIndex target_expr; } AstFullSwitchCaseComponents; typedef struct { AstTokenIndex inline_token; AstTokenIndex payload_token; AstFullSwitchCaseComponents ast; } AstFullSwitchCase; typedef struct { AstTokenIndex asm_token; AstNodeIndex template; AstNodeSlice items; AstNodeIndex clobbers; AstTokenIndex rparen; } AstFullAsmComponents; typedef struct { AstFullAsmComponents ast; AstTokenIndex volatile_token; AstNodeSlice outputs; AstNodeSlice inputs; } AstFullAsm; typedef struct { AstTokenIndex lparen; AstNodeIndex fn_expr; AstNodeSlice params; } AstFullCallComponents; typedef struct { AstFullCallComponents ast; } AstFullCall; AstFullAssignDestructure astAssignDestructure( const Ast* tree, AstNodeIndex node); AstFullContainerDecl astContainerDeclRoot(const Ast* tree); // The Ast.zig:full* decoders: false where upstream returns null. bool astFullVarDecl(const Ast* tree, AstNodeIndex node, AstFullVarDecl* out); bool astFullIf(const Ast* tree, AstNodeIndex node, AstFullIf* out); bool astFullWhile(const Ast* tree, AstNodeIndex node, AstFullWhile* out); bool astFullFor(const Ast* tree, AstNodeIndex node, AstFullFor* out); bool astFullContainerField( const Ast* tree, AstNodeIndex node, AstFullContainerField* out); bool astFullFnProto(const Ast* tree, AstNodeIndex buffer[1], AstNodeIndex node, AstFullFnProto* out); bool astFullStructInit(const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node, AstFullStructInit* out); bool astFullArrayInit(const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node, AstFullArrayInit* out); bool astFullArrayType( const Ast* tree, AstNodeIndex node, AstFullArrayType* out); bool astFullPtrType(const Ast* tree, AstNodeIndex node, AstFullPtrType* out); bool astFullSlice(const Ast* tree, AstNodeIndex node, AstFullSlice* out); bool astFullContainerDecl(const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node, AstFullContainerDecl* out); bool astFullSwitch(const Ast* tree, AstNodeIndex node, AstFullSwitch* out); bool astFullSwitchCase( const Ast* tree, AstNodeIndex node, AstFullSwitchCase* out); bool astFullAsm(const Ast* tree, AstNodeIndex node, AstFullAsm* out); bool astFullCall(const Ast* tree, AstNodeIndex buffer[1], AstNodeIndex node, AstFullCall* out); bool astBuiltinCallParams(const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node, AstNodeSlice* out); bool astBlockStatements(const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node, AstNodeSlice* out); AstFullFnProtoIterator astFullFnProtoIterate( const AstFullFnProto* fn_proto, const Ast* tree); // full.FnProto.Iterator.next: false where upstream returns null. bool astFullFnProtoIteratorNext( AstFullFnProtoIterator* it, AstFullFnProtoParam* out); void astFullContainerFieldConvertToNonTupleLike( AstFullContainerField* cf, const Ast* tree); // ── Parse: lib/std/zig/Parse.zig ──────────────────────────────────────────── #define ERR_BUF_SIZE 200 typedef struct { const char* source; uint32_t source_len; const uint8_t* token_tags; AstIndex* token_starts; uint32_t tokens_len; AstTokenIndex tok_i; AstNodeList nodes; AstNodeIndexSlice extra_data; AstNodeIndexSlice scratch; bool has_warn; bool has_compile_errors; // The first error or warning: its message ("" before one) and token, // and whether it is reported at the end of that token (Ast.Error's // token_is_prev). char err_buf[ERR_BUF_SIZE]; AstTokenIndex err_tok; bool err_tok_is_prev; } Parser; void parseRoot(Parser* parser); void parseZon(Parser* parser); // ── AstRlAnnotate: lib/std/zig/AstRlAnnotate.zig ──────────────────────────── // AstRlAnnotate.annotate: RlNeededSet as a heap array of tree->nodes.len // bools, true for each node in the set; the caller frees it. bool* astRlAnnotate(const Ast* tree); // ── AstGen: lib/std/zig/AstGen.zig ────────────────────────────────────────── // Lower a parsed AST into ZIR, mirroring `std.zig.AstGen.generate`. Zir astGen(const Ast* ast); // astGen of the file at `path` (below module root `dir`, or NULL): a parse // or AstGen error is reported as a compile error in that file. Zir astGenFile(const Ast* ast, const char* dir, const char* path); // ── string_literal: lib/std/zig/string_literal.zig ────────────────────────── // Each returns false on a malformed literal, with the byte offset of the // failure in `*err_offset`. bool stringLiteralParseEscapeSequence(const char* slice, uint32_t len, uint32_t* offset, uint32_t* out, uint32_t* err_offset); bool stringLiteralParseWrite( const char* bytes, uint32_t len, U8List* out, uint32_t* err_offset); bool stringLiteralParseCharLiteral( const char* slice, uint32_t len, uint32_t* out, uint32_t* err_offset); // ── Zoir and ZonGen: lib/std/zig/Zoir.zig, lib/std/zig/ZonGen.zig ─────────── // Zoir: `nodes` (a MultiArrayList of Node.Repr: tag, data, ast_node), // `extra`, `limbs` and `string_bytes`. zig1 stops at the first ZonGen error, // so a Zoir never carries compile errors. typedef struct { uint32_t nodes_len; uint8_t* node_tags; // ZoirNodeReprTag uint32_t* node_datas; AstNodeIndex* node_ast_nodes; uint32_t* extra; uint32_t extra_len; uint64_t* limbs; uint32_t limbs_len; uint8_t* string_bytes; uint32_t string_bytes_len; } Zoir; // Zoir.Node.Index.Range. typedef struct { uint32_t start; uint32_t len; } ZoirNodeRange; // Zoir.Node, decoded by zoirNodeGet. A NullTerminatedString is an offset into // `string_bytes`. typedef struct { ZoirNodeTag tag; union { struct { bool is_big; int32_t small; BigIntConst big; } int_literal; uint32_t float_literal[4]; // f128, low word first uint32_t char_literal; uint32_t enum_literal; struct { const uint8_t* ptr; uint32_t len; } string_literal; ZoirNodeRange array_literal; struct { const uint32_t* names; ZoirNodeRange vals; } struct_literal; } data; } ZoirNode; // Zoir.Node.Index.get. ZoirNode zoirNodeGet(const Zoir* zoir, uint32_t idx); // Zoir.NullTerminatedString.get. static inline const char* zoirNullTerminatedString( const Zoir* zoir, uint32_t nts) { return (const char*)&zoir->string_bytes[nts]; } // Zoir.Node.Index.Range.at. static inline uint32_t zoirNodeRangeAt(ZoirNodeRange r, uint32_t i) { return r.start + i; } void zoirDeinit(Zoir* zoir); // ZonGen.generate of the ZON file at `path` (below module root `dir`): a // parse or ZonGen error is reported as a compile error in that file. Zoir zonGenGenerateFile(const Ast* tree, const char* dir, const char* path); // ── InternPool: src/InternPool.zig ────────────────────────────────────────── // The deduplicated store of types and values. // // Storage: an items list (each item = { tag, data }) plus the `extra` word // arena and a `string_bytes` arena, mirroring the single-thread ("locals" // single shard) layout of a single-threaded upstream compiler. The 128 // statically-known preinterns (`static_keys` in src/InternPool.zig) occupy // fixed indices 0..127; dynamic interning appends from index 128 upward. The // airgen IP dump (stage1/test/air_gen.zig, air-compare-zig1) matches it entry // for entry. // // Every preintern's `extra` payload contents are upstream's `static_keys` // words, not placeholders, which matters because `ipDedupSeedStatic` indexes // those exact words — a placeholder makes a dynamic rebuild of the same shape // miss the preintern and append a duplicate item. typedef uint32_t InternPoolIndex; #define IP_INDEX_NONE ((InternPoolIndex)0xFFFFFFFF) // Static preinterned `generic_poison_type` // (InternPool.Index.generic_poison_type). A generic function's `[generic]` // parameter/return types are interned as this until the call site // monomorphizes the instance. #define IP_INDEX_GENERIC_POISON_TYPE ZIR_REF_GENERIC_POISON_TYPE // InternPool.Tag (src/InternPool.zig) is generated as IpTag in // upstream_gen.h, so `item_tags[]` holds upstream's byte for every tag. // zig1's payload notes for the tags it interns: // // IP_TAG_TYPE_INFERRED_ERROR_SET: The inferred error set type of a function // (stock // `.type_inferred_error_set`, a `DataIsIndex` tag). item.data = the owning // `func_decl` Index directly (no extra payload). Interned only as part of // the four-item `ipGetFuncDeclIes` block for a `!T` (inferred-error-set) // function declaration. // // IP_TAG_TYPE_STRUCT_PACKED_AUTO: The four `packed struct` tags (stock // `.type_struct_packed_auto` and // friends, all four sharing one `Tag.TypeStructPacked` payload). The tag // encodes the two mint-time discriminators upstream's // `getDeclaredStructType` switches on: `packed_backing_mode` (`.explicit` // iff the ZIR carried a backing-int-type body, else `.auto`) and // `any_field_defaults`. The payload is upstream's `Tag.TypeStructPacked` // header (8 words) plus upstream's trailing groups: // `field_name[]`+`field_type[]` for tags 18/19 (extent 8+2F), plus // `field_default[]` for tags 20/21 (extent 8+3F) — see. // // IP_TAG_TYPE_UNION_PACKED_AUTO: The two `packed union` tags (stock // `.type_union_packed_auto` / // `.type_union_packed_explicit`, one `Tag.TypeUnionPacked` payload): the // tag is the `packed_backing_mode` (`.explicit` iff `packed union(T)`). // Header, word for word: zir_index, bits, name, name_nav, namespace, // backing_int_type, enum_tag_type, fields_len; then `field_type[]`. // `bits.captures_len` is always 0 here (captures live in the namespace // table, as for packed structs), so the `capture[]` group has no words. // // IP_TAG_TYPE_ENUM_EXPLICIT: A declared enum with an explicit integer tag type // and explicit field // values, exhaustive (stock InternPool `.type_enum_explicit`). Same // `Tag.TypeEnum` header as `.type_enum_auto`, but the trailing data adds a // `field_value_map` (after `zir_index`) and a `field_value[fields_len]` // array (after `field_name[]`); `int_tag_type` is the user-specified type, // not inferred. Produced only by a run (compilationRun) when an // explicit-valued enum (e.g. a std helper's `enum(u8){ a=10, ... }`) is // analyzed. // // IP_TAG_TYPE_ENUM_NONEXHAUSTIVE: Like `.type_enum_explicit` but // non-exhaustive (has a trailing `_`; stock // `.type_enum_nonexhaustive`). Identical trailing layout. Produced only by // a run (e.g. `std.lang.CompilerBackend = enum(u64){ ..., _ }`). // // IP_TAG_PTR_COMPTIME_FIELD: A pointer to a comptime-only struct/tuple field. // The field value is // part of the pointer identity, exactly like upstream's // `PtrComptimeField { ty, field_val, byte_offset_a, byte_offset_b }`. // // IP_TAG_PTR_INT: A pointer whose base address is an integer. The integer // address is // stored in the PackedU64 byte-offset words of `PtrInt`. // // IP_TAG_PTR_EU_PAYLOAD: A pointer to the payload of an error union (stock // `.ptr_eu_payload`). // item.data = extra offset of PtrBase { ty, base, byte_offset_a, // byte_offset_b } (4 words); `base` is the interned error-union pointer // this one derives from. Minted by ipGetPtrEuPayload. // // IP_TAG_PTR_OPT_PAYLOAD: A pointer to the payload of an optional (stock // `.ptr_opt_payload`). // Same PtrBase payload shape as ptr_eu_payload above; minted by // ipGetPtrOptPayload. // // IP_TAG_PTR_ELEM: A pointer to an array element (stock `.ptr_elem`). // item.data = extra // offset of PtrBaseIndex { ty, base, index, byte_offset_a, byte_offset_b } // (5 words); `index` is an interned usize value, minted first (mirrors // upstream's `ip.get(.{ .int = ... }})` funnel). Minted by ipGetPtrElem. // // IP_TAG_PTR_FIELD: A pointer to a container field (stock `.ptr_field`). Same // PtrBaseIndex // payload shape and index convention as ptr_elem above; minted by // ipGetPtrField. // // IP_TAG_OPT_PAYLOAD: A comptime optional-payload value `(?T) x` (stock // `.opt_payload`). // item.data = extra offset of { opt_ty, payload_val } (2 words). Produced // by coercing a comptime value to an optional type (`const o: ?u8 = 5`). // // IP_TAG_OPT_NULL: A comptime optional-none value `(?T) null` (stock // InternPool // `.opt_null`). item.data = the optional type Index. Produced by `return // null` / coercing the `null` literal to an optional type. // // IP_TAG_INT_POSITIVE: An integer value whose exact magnitude does not fit a // narrower // carrier's data word (stock `.int_positive`). item.data is an index into // the `limbs` arena, not into `extra`: upstream `InternPool.addInt` // writes `Int { ty, limbs_len }` there as `Int.limbs_items_len` limbs and // follows it with `limbs_len` magnitude limbs, little-endian, base // 2^Limb. Because `Int` carries `ty`, this is the one carrier for every // such value of any integer type -- `comptime_int` and sized alike. // // IP_TAG_INT_NEGATIVE: The same payload for a negative value (stock // `.int_negative`). The // limbs are the magnitude, exactly as `std.math.big.int.Const` stores it // beside its `positive` flag; there is no two's complement here. // // IP_TAG_ERROR_UNION_PAYLOAD: A comptime error-union-payload value `(E!T) x` // (stock `.error_union` // with a `.payload`). item.data = extra offset of { eu_ty, payload_val } // (2 words). Produced by coercing a comptime value to an error-union type // (`return 1` in a `anyerror!u8` fn). // // IP_TAG_FLOAT_F16: A comptime `f16` value (stock `.float_f16`). item.data = // raw IEEE-754 // half-precision bits zero-extended to u32. Produced only by interp // comptime bitcasts. // // IP_TAG_FLOAT_F32: A comptime `f32` value (stock `.float_f32`). item.data = // raw IEEE-754 // single-precision bits. // // IP_TAG_FLOAT_F64: A comptime `f64` value (stock `.float_f64`). item.data = // extra offset of // Float64 { piece0, piece1 }, the raw IEEE-754 double bits split low word // first, matching upstream InternPool.Float64. // // IP_TAG_FLOAT_F80: A comptime `f80` value (stock `.float_f80`). item.data = // extra offset // of Float80 { piece0, piece1, piece2 }, with the high u16 zero-padded to // one word, matching upstream InternPool.Float80. // // IP_TAG_FLOAT_F128: A comptime `f128` value (stock `.float_f128`). item.data // = extra offset // of Float128 { piece0, piece1, piece2, piece3 }, matching upstream // InternPool.Float128. // // IP_TAG_FLOAT_COMPTIME_FLOAT: A comptime_float value (stock // `.float_comptime_float`). item.data = // extra offset of Float128 { piece0, piece1, piece2, piece3 }. Produced // by ZIR_INST_FLOAT. // // IP_TAG_EXTERN: A bodyless extern function declaration. item.data is an // `extra` offset // of { owner_nav, type_function, library_name }. Unlike func_decl this // must never be queued for source body analysis. // // IP_TAG_FUNC_COERCED: A function declaration/instance coerced to a different // function type // (stock `.func_coerced`). item.data = extra offset of { ty, func }. // // IP_TAG_BITPACK: An instance of a `packed struct` (stock InternPool // `.bitpack`). // item.data = extra offset of { ty, backing_int_val } (2 words): the // packed struct type and the interned backing-integer value holding the // bit-packed fields. The interp represents a packed struct as a scalar of // the backing integer width (LSB-first field bit offsets), so a bitpack // value decodes to InterpValue{ bits = backing magnitude, ty = struct type // }. Produced only by a run on a packed struct init / comptime // `@bitCast`. // True for the four `packed struct` tags — upstream's `loadStructType` arm // set (`"// Exiting this `switch` means this is a `packed struct`."`). ZIG1_INLINE bool ipTagIsStructPacked(uint8_t tag) { return tag >= IP_TAG_TYPE_STRUCT_PACKED_AUTO && tag <= IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS; } // True for any declared struct type: the auto/extern `type_struct` plus the // four packed tags. `item_tags[x] == IP_TAG_TYPE_STRUCT` means // "auto or extern struct" only — every "is this a struct type" test asks this // instead. ZIG1_INLINE bool ipTagIsAnyStruct(uint8_t tag) { return tag == IP_TAG_TYPE_STRUCT || ipTagIsStructPacked(tag); } // True for the two `packed union` tags. ZIG1_INLINE bool ipTagIsUnionPacked(uint8_t tag) { return tag == IP_TAG_TYPE_UNION_PACKED_AUTO || tag == IP_TAG_TYPE_UNION_PACKED_EXPLICIT; } // True for any declared union type -- upstream's `.union_type` key, which // `indexToKey` answers for `.type_union` and both packed tags alike. ZIG1_INLINE bool ipTagIsAnyUnion(uint8_t tag) { return tag == IP_TAG_TYPE_UNION || ipTagIsUnionPacked(tag); } // InternPool container `layout`, mirroring std.lang.Type.ContainerLayout as // consumed by `getDeclaredStructType`. typedef enum { IP_STRUCT_LAYOUT_AUTO = 0, IP_STRUCT_LAYOUT_EXTERN = 1, IP_STRUCT_LAYOUT_PACKED = 2, } IpContainerLayout; // A single `Nav` (InternPool.Nav): a named source declaration awaiting (or // holding) semantic analysis. Navs live in a separate arena from items/extra, // so creating them does not append IP items (matching upstream). Only the // fields zig1's scan and lookup use are modeled. typedef struct { uint32_t name; // NullTerminatedString (IP string index) uint32_t fqn; // fully-qualified name (IP string index) // Source storage class, captured when the declaration is scanned. These // are properties of the Nav even before semantic analysis resolves its // type/value, and mirror the corresponding InternPool.Nav.Resolved bits. // Function/test/generated instance Navs are const, non-threadlocal, // non-extern. A mutable non-extern/non-threadlocal Nav is an ordinary // program global; resolved_value remains its initializer, never its // runtime storage. bool is_const; bool is_threadlocal; bool is_extern; // analysis (present for decl Navs; the value/type is resolved later) bool has_analysis; uint32_t analysis_namespace; // NamespaceIndex uint32_t analysis_zir_index; // TrackedInst.Index (the tracked counter) // The concrete ZIR `declaration` instruction this Nav was scanned from, in // its owning file's ZIR. Upstream this is recovered from the TrackedInst; // the C tracked-inst arena models only a count, so we store the real inst // index directly here so the comptime interpreter can recover the decl's // value body (`@import`, `enum_decl`, field access, ...) from the Nav. uint32_t analysis_inst; // ZIR declaration inst index (in file_scope's ZIR) bool analysis_wanted; // Type resolution and value resolution are deliberately separate, like // upstream Nav.resolved: `has_resolved` publishes the declaration's type // and pointer modifiers, while `has_value_resolved` says the initializer // is available in `resolved_value`. A valid type-only Nav therefore has // `has_resolved=true`, `has_value_resolved=false`, and // `resolved_value=IP_INDEX_NONE`. This is required for `&decl`: forming a // direct Nav pointer must not recursively evaluate the initializer (self- // and mutually-addressed globals depend on that ordering). bool has_resolved; bool has_value_resolved; bool value_resolving; // True once declaration align/linksection/addrspace bodies have been // checked. Self-referential container construction may prepublish the // type before this transition; the final publication completes it once. bool modifiers_resolved; // InternPool.Alignment encoding: log2(byte alignment), or 63 (`.none`) // for the pointee type's natural ABI alignment. Explicit linksection and // addrspace modifiers are rejected during resolution until represented. uint8_t resolved_alignment; InternPoolIndex resolved_type; InternPoolIndex resolved_value; } IpNav; // PtrNav structural decode status. `ipDecodePtrNavAny` accepts every // upstream-valid non-slice pointer type; `ipDecodePtrNav` additionally limits // the result to the byte-addressable, sentinel-free `.one` form used by Sema's // field-projection helpers. Policy such as mutable/extern/TLS, // function-vs-data, bounds, and full initializer availability belongs to the // caller. typedef enum { IP_PN_OK = 0, IP_PN_NOT_PTR_NAV, IP_PN_PAYLOAD_OOB, IP_PN_BAD_PTR_TYPE, IP_PN_PTR_PAYLOAD_OOB, IP_PN_HAS_SENTINEL, IP_PN_NOT_ONE, IP_PN_PACKED, IP_PN_BAD_NAV, IP_PN_UNRESOLVED_TYPE, IP_PN_BAD_CHILD, } IpPtrNavStatus; typedef struct { uint32_t ptr_type; uint32_t nav_index; uint32_t nav_type; uint32_t child_type; uint32_t ptr_sentinel; uint32_t ptr_flags; uint32_t packed_offset; uint64_t byte_offset; } IpPtrNav; // A `ComptimeUnit` (InternPool.ComptimeUnit): an unnamed `comptime {}` block // discovered during namespace scanning. Also a separate arena (no IP items). typedef struct { uint32_t zir_index; // TrackedInst.Index uint32_t ns; // NamespaceIndex } IpComptimeUnit; // A `Namespace` (Zcu.Namespace): the declaration scope of a container type. // Stored in the InternPool's separate namespace arena (createNamespace lives // on InternPool upstream). Decl lists hold Nav indices; comptime_units hold // ComptimeUnit ids. typedef struct { uint32_t parent; // NamespaceIndex, or UINT32_MAX (.none) InternPoolIndex owner_type; // the container's type_struct/enum/union index uint32_t file_scope; // File.Index this namespace belongs to uint32_t* pub_navs; uint32_t pub_navs_len, pub_navs_cap; uint32_t* priv_navs; uint32_t priv_navs_len, priv_navs_cap; uint32_t* comptime_units; uint32_t comptime_units_len, comptime_units_cap; uint32_t* test_navs; uint32_t test_navs_len, test_navs_cap; } IpNamespace; // Metadata retained between declaration-time interning of a union and its // first layout/field demand. Upstream creates a generated hypothetical enum // tag during resolveUnionLayout, not while interning the union declaration. typedef struct { InternPoolIndex union_type; uint32_t fields_len; // For `union(enum(T))`, the generated enum has explicit integer backing // `T`. If `explicit_int_tag_ty` is `.none`, `explicit_int_tag_bits` is // either UINT32_MAX for inferred backing (`union(enum)`) or a simple // deferred int_type body to intern after the enum item itself. uint32_t explicit_int_tag_ty; uint32_t explicit_int_tag_bits; bool explicit_int_tag_signed; // `packed union(T)`: the evaluated `T`, which upstream's // `resolvePackedUnionLayout` validates and stores; `.none` otherwise. uint32_t packed_backing_ty; // `union(enum(T)) { a = v, ... }`: the generated enum's final field // values at `T`, owned here; NULL numbers the fields 0..N-1. uint32_t* field_values; // The value bodies are not evaluated yet: the enum cannot be made. bool values_pending; } IpPendingUnionTag; // A declared struct's, enum's or union's field names, not yet interned: // upstream populates them in resolveStructLayout / resolveEnumLayout // ("Declared structs do not yet have field information populated"); until // then a struct's name slot holds 0 (as upstream's) and an enum's `.none`. // `names` are the ZIR's // NullTerminatedString indexes into `strings`. typedef struct { uint32_t ty; const uint8_t* strings; const uint32_t* names; uint32_t len; } IpPendingFieldNames; // One slot of the dedup hash index (see the "Dedup hash index" comment block // in intern_pool.c for the full design). `item_idx` doubles as an occupancy // flag: IP_INDEX_NONE (0xFFFFFFFF) means the slot is empty. `hash` is the // full hash the entry was inserted under (not just its bucket), stored // alongside the index so table growth can re-bucket every live entry into a // larger table without re-deriving its (tag, key words) from live IP/string // content -- which matters for the variable-length keys (aggregate, tuple, // error-set) that have no fixed word count to re-read generically. `n` is // the key word count the entry was inserted with (item table only -- see // ipDedupFind's use of it in intern_pool.c). This guards a same-tag, // same-hash-bucket collision between two items whose keys have different // lengths (only possible for the variable-length-key tags: aggregate, // tuple, error-set): without it, `ipDedupFind` would blindly compare `n` // words starting at a shorter candidate item's `extra` payload, reading past // that item's own words into whatever follows -- a possible OOB / wrong-match // read. The string table doesn't need the check (`ipStrDedupFind` verifies // via the found string's own NUL-terminated bytes, which is self-bounding), // so it stores 0 here; the field is still populated (harmlessly) to keep the // slot layout shared. The same slot layout is reused for both the per-item // dedup table (`dedup_*`, keyed by {tag, key words}) and the string dedup // table (`str_dedup_*`, keyed by string bytes) -- only the hash function and // what `item_idx` indexes differ. typedef struct { uint32_t item_idx; uint32_t hash; uint32_t n; } IpDedupSlot; typedef struct { // items: parallel arrays of tag + data, mirroring the MultiArrayList of // Item in InternPool.Local.Items. uint8_t* item_tags; uint32_t* item_datas; uint32_t items_len; uint32_t items_cap; // Dedup hash index over interned items (tag + key words -> first item // index with that key), open-addressed with linear probing, power-of-two // capacity, grown (rehashed) before load exceeds 70%. Lazily allocated // (dedup_cap == 0 means "not yet built" -- ipDedupFind treats that as a // guaranteed miss, matching a linear scan over zero converted entries). // Not every tag is indexed here; see the per-tag ipGet* comments for // which tags are index-accelerated vs. left as a plain scan. IpDedupSlot* dedup_slots; uint32_t dedup_cap; uint32_t dedup_count; // Dedup hash index over interned strings (content hash -> first strings- // list index with those bytes), same open-addressing scheme as // `dedup_slots` above but keyed by string content instead of (tag, // words). `item_idx` here holds a strings-list index (see // `string_offsets` below), not an item index. IpDedupSlot* str_dedup_slots; uint32_t str_dedup_cap; uint32_t str_dedup_count; // Dedup hash index over `type_error_set` items specifically. Its key // {names_len, names[0..names_len)} is not a contiguous `extra` span // (Tag.ErrorSet's `names_map` word sits between names_len and the name // list -- see ipGetErrorSetType), so it can't share `dedup_slots`' // generic content-verify (which re-reads `n` contiguous words starting // at item_datas[idx]). Reuses the same slot layout/grow/place // primitives (they only need a precomputed hash + item_idx + a stored // guard length, never re-deriving the key from live content) with a // bespoke hash + verify pair. `n` here is the errset's `names_len`. IpDedupSlot* errset_dedup_slots; uint32_t errset_dedup_cap; uint32_t errset_dedup_count; // Dedup hash index over `memoized_call` items (byte-compared IP // dump). Key = {func, arg_values[0..args_len)} -- mirrors // upstream's `Key.MemoizedCall` hash/eql, InternPool.zig/ // 3143-3146, which explicitly excludes `result`/`branch_count` ("ignored // by hash+eql", Sema.zig). The `extra` layout // (ipGetMemoizedCall) is `{func, args_len, result, branch_count, // branch_quota}` then trailing arg_values -- the header words sit between // `func` and the arg list, so (like `errset_dedup_*`) the key is not a // contiguous span starting at item_datas[idx] and can't use the generic // `dedup_slots` helpers. `n` here is the call's `args_len`. Single // creator: `ipGetMemoizedCall` is the only `ipAddItem(ip, // IP_TAG_MEMOIZED_CALL, ...)` call site. IpDedupSlot* memo_dedup_slots; uint32_t memo_dedup_cap; uint32_t memo_dedup_count; // Dedup hash index over the declared-key family: `func_decl` // (`ipGetFuncDecl` and `ipGetFuncDeclIes`), keyed on `owner_nav`, a // simplification of upstream's `owner_nav`+`ty` `.func` key (the getter // says why `owner_nav` alone suffices). Struct and enum types stay out: // zig1 reuses one zir_index across capture-distinct instantiations, so // their dedup is one layer up (intern_pool.c, "declared-key dedup // table"). Generic instances go through `ipGetFuncInstance`, so these // getters see one Nav per declaration. // // One word key, at a tag-specific `extra` offset (see // `ipDeclDedupKeyWord`); discriminated by IpTag so a future additional // tag's key words can't collide with `func_decl`'s even if the bare // values coincide (mirrors `dedup_slots`' `item_tags[idx] == tag` // check). `ipGetFuncDecl` + `ipGetFuncDeclIes` share this table/tag // safely: a given Nav has at most one declared value, so no owner_nav is // ever legitimately offered to both getters. IpDedupSlot* decl_dedup_slots; uint32_t decl_dedup_cap; uint32_t decl_dedup_count; // Dedup hash index over `func_instance` items. The key is the complete // instance signature (generic owner, inferred-error-set return payload, // instance function flags/noalias bits, runtime parameter types, and // comptime arguments). It has a bespoke verifier because the key spans // the instance payload and its separately-created type_function item. IpDedupSlot* func_instance_dedup_slots; uint32_t func_instance_dedup_cap; uint32_t func_instance_dedup_count; // The `extra` word arena that composite items point into via `data`. uint32_t* extra; uint32_t extra_len; uint32_t extra_cap; // The `limbs` arena (upstream `InternPool.Local.Shared.limbs`), which the // `int_positive`/`int_negative` items' `data` indexes. A `Limb` is the // host compiler's `usize`, and `Local.Shared.getLimbs` picks this // separate list over `extra` exactly when `@sizeOf(Limb) == // @sizeOf(u64)`. The upstream compiler zig1 reproduces runs on a 64-bit // host, so a limb is 64 bits here -- the width astgen.c's `int_big` ZIR // payload is written in. uint64_t* limbs; uint32_t limbs_len; uint32_t limbs_cap; // Dedup index over the two limb-backed integer tags. It cannot share // `dedup_slots`: that table's verifier re-reads a candidate's key from // `ip->extra[item_datas[idx]]`, and these items' `data` is a limbs index. // Same open addressing, own verifier -- the arrangement // `func_instance_dedup_slots` already uses. IpDedupSlot* limbs_dedup_slots; uint32_t limbs_dedup_cap; uint32_t limbs_dedup_count; // The `string_bytes` arena. Index 0 is the empty string. char* string_bytes; uint32_t string_len; uint32_t string_cap; // The `strings` list: start byte offset of each interned string into // `string_bytes`. A NullTerminatedString handle is the index into this // list (matching upstream InternPool `strings`/`NullTerminatedString` // semantics), so `ipGetOrPutString` returns the list index and // `ipStringBytes` translates index -> offset. String 0 is the empty // string at offset 0. uint32_t* string_offsets; uint32_t strings_count; uint32_t strings_cap; // Count of allocated field-name maps (InternPool.maps). Maps live in a // separate arena from items/extra (so they do not perturb `extra` // offsets); we only need the running length to hand out MapIndex values // for `getDeclaredStructType`'s `field_name_map`. The first map allocated // is index 0. uint32_t maps_len; // Count of tracked ZIR instructions (InternPool.tracked_insts). Like // `maps`, a separate arena; we track only the length to hand out // TrackedInst.Index values for container `zir_index` fields. uint32_t tracked_insts_len; // `trackZir`'s {file, inst} key per TrackedInst.Index, recorded only by // ipTrackZirInst (a bare ipTrackZir entry keeps {none, none}), and the // hash index over them. uint32_t* tracked_keys; uint32_t tracked_keys_cap; IpDedupSlot* tracked_dedup_slots; uint32_t tracked_dedup_cap; uint32_t tracked_dedup_count; // Navs (InternPool.Nav storage) — separate arena, no IP items. IpNav* navs; uint32_t navs_len, navs_cap; // Namespaces (createNamespace lives on InternPool upstream) — separate // arena, no IP items. IpNamespace* namespaces; uint32_t namespaces_len, namespaces_cap; // ComptimeUnits (InternPool.ComptimeUnit storage) — separate arena. IpComptimeUnit* comptime_units; uint32_t comptime_units_len, comptime_units_cap; // Generated union-tag metadata. Separate from items/extra, just like the // upstream getOrPut key's declaration-to-layout lifecycle. IpPendingUnionTag* pending_union_tags; uint32_t pending_union_tags_len, pending_union_tags_cap; // Declared container field names awaiting their layout resolution. IpPendingFieldNames* pending_field_names; uint32_t pending_field_names_len, pending_field_names_cap; // resolveEnumLayout of a declared explicit-tag enum, which needs Sema: // run on the first read of its `int_tag_type` or a field value, as // `ensureLayoutResolved` precedes those reads upstream. NULL outside Sema. void (*resolve_enum_layout)(void* ctx, uint32_t enum_ty); void* resolve_enum_layout_ctx; // Sema.resolveInferredErrorSetTy's ensureFuncBodyUpToDate: analyzes the // function whose inferred error set is read while unresolved. Installed // by Compilation.update's queue (performAllTheWork), which owns the AIR // list; NULL outside it, where an unresolved inferred set stays loud. void (*resolve_ies)(void* ctx, uint32_t func_index); void* resolve_ies_ctx; // The pending-Nav list into which a function-value -> `*const fn` // coercion queues the target function's owner Nav for body analysis // (Sema.maybeQueueFuncBodyAnalysis): a function taken as a pointer // (`if (cond) f else g`, a vtable field `.alloc = alloc`) has no call // site to queue it. Installed by the same queue; NULL outside it. These // hooks are the InternPool's because the coercions that reach them may // carry no Zcu. U32List* fnptr_pending; // InternPool.GlobalErrorSet: `names` (value v names global_error_names[v // - 1]; 0 is `.none`) and its map, here indexed directly by string: // string_error_value[name] is the value, 0 when absent. uint32_t* global_error_names; uint32_t global_error_names_len, global_error_names_cap; uint32_t* string_error_value; uint32_t string_error_value_cap; } InternPool; #define IP_NAMESPACE_NONE ((uint32_t)0xFFFFFFFF) InternPool ipInit(void); void ipDeinit(InternPool* ip); // `InternPool.indexToKey`: an index's item tag says what it is. Which half of // the index space the entry landed in says nothing -- the preinterned tail of // `InternPool.Index` (int types 0..14, the pointer/slice/vector types, the // small int values) are real pool items with real tags. An arm gated on // `idx >= ZIR_REF_START_INDEX` is therefore blind to every preinterned shape // of the tag it names; ask this instead, which also bounds the index the // `item_tags` read needs. static inline bool ipItemTagIs(const InternPool* ip, uint32_t idx, IpTag tag) { return idx < ip->items_len && ip->item_tags[idx] == (uint8_t)tag; } // InternPool.zig:isUndef // Value.isUndef: "return val.unwrap(ip).getTag(ip) == .undef". An AIR ref or // `.none` is no InternPool value, so it is not undef. ZIG1_INLINE bool ipIsUndef(const InternPool* ip, uint32_t val) { return ipItemTagIs(ip, val, IP_TAG_UNDEF); } // The upstream `@tagName` for an item tag (for the IP dump diagnostic). const char* ipTagName(IpTag tag); // Append a tracked ZIR instruction, returning its TrackedInst.Index (mirrors // InternPool.trackZir for a first-time track). Tracked insts live outside // items/extra. uint32_t ipTrackZir(InternPool* ip); // `InternPool.trackZir`: the TrackedInst.Index of ZIR `inst` in file `file`, // the same index on every later track of that pair. uint32_t ipTrackZirInst(InternPool* ip, uint32_t file, uint32_t inst); // Intern a string into `string_bytes`, deduplicating (mirrors // InternPool.getOrPutString). Returns the NullTerminatedString index. `bytes` // need not be NUL-terminated; `len` is its length. Strings live in the // string_bytes arena (no IP items). uint32_t ipGetOrPutString(InternPool* ip, const char* bytes, uint32_t len); // Borrow the NUL-terminated bytes of an interned string by index. const char* ipStringBytes(const InternPool* ip, uint32_t str_idx); // Length in bytes of an interned string, not counting its NUL terminator. // Mirrors upstream `InternPool.NullTerminatedString.length`, whose body is // `return strings[unwrapped.index + 1] - 1 - strings[unwrapped.index];` — the // length is structural (a difference of adjacent string-table offsets), not a // NUL scan. The authority for "how long is this interned string": callers that // held only `ipStringBytes` had to re-materialize a length with `strlen`. uint32_t ipStringLen(const InternPool* ip, uint32_t str_idx); // `NullTerminatedString.toUnsigned` over a name's bytes: a decimal u32 with no // leading zero, no `_` and no overflow, else false ("if (slice.len > 1 and // slice[0] == '0') return null;"). bool ipBytesToUnsigned(const char* bytes, uint32_t len, uint32_t* out); // Create a namespace (mirrors PerThread.createNamespace -> // ip.createNamespace). Returns its NamespaceIndex. uint32_t ipCreateNamespace(InternPool* ip, uint32_t parent, InternPoolIndex owner_type, uint32_t file_scope); // Borrow a namespace by index. IpNamespace* ipGetNamespace(InternPool* ip, uint32_t ns_idx); // Create a decl Nav (mirrors InternPool.createDeclNav): a named source // declaration whose value is resolved later. Returns its Nav.Index. uint32_t ipCreateDeclNav(InternPool* ip, uint32_t name, uint32_t fqn, uint32_t zir_index, uint32_t zir_inst, uint32_t ns, bool is_const, bool is_threadlocal, bool is_extern); // Borrow a Nav by index. const IpNav* ipGetNav(const InternPool* ip, uint32_t nav_idx); // Create a ComptimeUnit (mirrors InternPool.createComptimeUnit). Returns its // id. uint32_t ipCreateComptimeUnit(InternPool* ip, uint32_t zir_index, uint32_t ns); // Look up a declaration by (unqualified) name index in a namespace's pub+priv // decl lists (mirrors Zcu.Namespace lookups). Returns the Nav.Index, or // IP_INDEX_NONE if not found. uint32_t ipNamespaceLookup(InternPool* ip, uint32_t ns_idx, uint32_t name); // The `pub` declaration `name` of a namespace, else IP_INDEX_NONE. uint32_t ipNamespaceLookupPub( const InternPool* ip, uint32_t ns_idx, uint32_t name); // Port of `InternPool.getDeclaredStructType` restricted to the `.auto` // (non-packed, non-extern) branch: interns a `type_struct` item whose // `Tag.TypeStruct` payload is pushed onto `extra`, returning the new item's // index. `name`/`fqn`/`name_nav`/`namespace` are written as placeholders // here, set later by ipSetTypeName and ipSetStructNamespace (upstream's // `WipContainerType.setName` and `finish`). Aborts (UNIMPLEMENTED) on the // packed/extern layouts. Dedup (getOrPutKey) is deferred: each call appends a // new item (correct while only the single root struct is created). uint32_t ipGetDeclaredStructType(InternPool* ip, uint32_t zir_index, uint32_t captures_len, uint32_t fields_len, IpContainerLayout layout, bool any_comptime_fields, bool any_field_defaults, bool any_field_aligns); // `InternPool.getReifiedStructType`: the struct type keyed `.reified = { // zir_index, type_hash }`, fresh (`*out_existing` false, field slots // placeholders for the caller to fill) or the one already minted. // `packed_backing_int_ty` is the explicit backing int of a packed one, else // IP_INDEX_NONE. uint32_t ipGetReifiedStructType(InternPool* ip, uint32_t zir_index, uint64_t type_hash, uint32_t fields_len, IpContainerLayout layout, bool any_comptime_fields, bool any_field_defaults, bool any_field_aligns, uint32_t packed_backing_int_ty, bool* out_existing); // `WipContainerType.setName`: backpatch a container type's `name`, `fqn` // (NullTerminatedString indices) and `name_nav` words. void ipSetTypeName(InternPool* ip, uint32_t ty, uint32_t type_name, uint32_t type_fqn, uint32_t name_nav); // `Type.containerTypeName(ip)`'s `.name` and `.fqn` of a struct, union, enum // or opaque type. uint32_t ipContainerTypeName(const InternPool* ip, uint32_t type_idx); void ipGeneratedUnionTagName( InternPool* ip, uint32_t union_ty, uint32_t* out_name, uint32_t* out_fqn); uint32_t ipContainerTypeFqn(const InternPool* ip, uint32_t type_idx); void ipSetWantTypeLayout(InternPool* ip, uint32_t ty); // Port of Namespace.internFullyQualifiedName (Zcu.zig): the // fully-qualified name of a decl is ".", or just the // container name when `name` is the empty string, or `name` unqualified when // the namespace owner has no interned name. uint32_t ipInternNamespacedName( InternPool* ip, uint32_t ns_idx, uint32_t name); // Read/backpatch a Tag.TypeStruct's per-field `field_name` // (NullTerminatedString index) and `field_type` (InternPool type index) // arrays. Field metadata is laid out after the header: // field_name[fields_len], then field_type[fields_len] (see // ipGetDeclaredStructType). Mirror the field-name half of // `WipContainerType.finish` and `resolveStructFieldTypes`. // Defer / resolve a declared container's field names (IpPendingFieldNames). void ipDeferFieldNames(InternPool* ip, uint32_t ty, const uint8_t* strings, const uint32_t* names, uint32_t len); void ipResolveFieldNames(InternPool* ip, uint32_t ty); // Set the struct type's owning `namespace` word (used for method / decl // lookup via ipNamespaceLookup). void ipSetStructNamespace(InternPool* ip, uint32_t struct_idx, uint32_t ns); // Namespace word with the 0-is-ambiguous resolution (0 = unset or the first // namespace, which is std's): returns the namespace index, or // IP_NAMESPACE_NONE if the struct has none. Use this for member lookup. uint32_t ipStructNamespaceOrNone(const InternPool* ip, uint32_t struct_idx); void ipSetStructFieldName( InternPool* ip, uint32_t struct_idx, uint32_t i, uint32_t name); uint32_t ipStructFieldName(InternPool* ip, uint32_t struct_idx, uint32_t i); uint32_t ipStructFieldIndex( InternPool* ip, uint32_t struct_idx, uint32_t name); bool ipStructFieldsPlaceholder(const InternPool* ip, uint32_t struct_idx); // `InternPool.getReifiedUnionType`: keyed `.reified = { zir_index, // type_hash }`. `enum_tag_type` is the explicit tag (else IP_INDEX_NONE, a // generated one); a packed union's backing is inferred at layout. uint32_t ipGetReifiedUnionType(InternPool* ip, uint32_t zir_index, uint64_t type_hash, uint32_t fields_len, IpContainerLayout layout, uint32_t tag_usage, uint32_t enum_tag_type, bool* out_existing); void ipSetUnionReifiedFieldName( InternPool* ip, uint32_t union_idx, uint32_t i, uint32_t name); void ipSetStructFieldType( InternPool* ip, uint32_t struct_idx, uint32_t i, uint32_t ty); uint32_t ipStructFieldType( const InternPool* ip, uint32_t struct_idx, uint32_t i); // Raw access to the serialized field_default slot. The accessor returns // IP_INDEX_NONE when the struct has no default array (ordinary TypeStruct // flags bit 4 clear, or either packed non-_DEFAULTS tag). Both functions // bounds-check the item, field index, and complete trailing array before // touching it; the setter additionally requires a real default array. uint32_t ipStructFieldDefault( const InternPool* ip, uint32_t struct_idx, uint32_t i); void ipSetStructFieldDefault( InternPool* ip, uint32_t struct_idx, uint32_t i, uint32_t value); // Read/backpatch the optional `field_is_comptime_bits` bag. A fixed/comptime // field is omitted from Type.comptimeOnly aggregate classification rather // than making the aggregate comptime-only by itself. void ipSetStructFieldIsComptime( InternPool* ip, uint32_t struct_idx, uint32_t i, bool is_comptime); bool ipStructFieldIsComptime( const InternPool* ip, uint32_t struct_idx, uint32_t i); // Read/backpatch the packed Alignment byte for one field. Returns/accepts // log2 byte units, with 63 == Alignment.none. void ipSetStructFieldAlignment( InternPool* ip, uint32_t struct_idx, uint32_t i, uint32_t alignment); uint32_t ipStructFieldAlignment( const InternPool* ip, uint32_t struct_idx, uint32_t i); // Overwrite the mint-time 0 placeholder with .none for every field, as // upstream's resolveStructLayout does when a struct's fields are resolved. void ipSetStructFieldAlignsNone(InternPool* ip, uint32_t struct_idx); // A Tag.TypeStruct's `size` word, and its packed `flags` word carries the // resolved `alignment` (Alignment as log2 units; .none == 63) and // `want_layout`. // `ipSetStructSize` and the alignment setter backpatch size + alignment once // the layout is computed (upstream's resolveStructLayout); before that `size // == 0` and alignment reads back as .none. `log2_align` is the log2 of the // byte alignment (align 4 -> 2). A Tag.TypeStructPacked has neither word (a // packed struct's size and alignment are its backing integer's), so every // accessor below stops loudly on a packed index rather than reading its // `fields_len` / `field_name_map`. void ipSetStructSize(InternPool* ip, uint32_t struct_idx, uint32_t size); void ipSetStructAlignment( InternPool* ip, uint32_t struct_idx, uint32_t log2_align); // Type.Class stored in struct flags bits 6..8: 0 NPV, 1 OPV, 2 runtime, // 3 partially-comptime, 4 fully-comptime. void ipSetStructClass( InternPool* ip, uint32_t struct_idx, uint32_t type_class); // The per-field `field_offset` array is the last trailing array of a // non-extern Tag.TypeStruct (after field_name, field_type, field_runtime_order // — see the layout in ipGetDeclaredStructType). Backpatch/read field i's byte // offset (mirrors `struct_type.field_offsets`). Valid only after layout // resolution. A packed struct has no such array — `Type.structFieldOffset` // asserts it (`"assert(struct_type.layout != .@\"packed\");"`) and so do // these; a packed field's bit offset is recomputed on demand instead // (zcuStructPackedFieldBitOffset). void ipSetStructFieldOffset( InternPool* ip, uint32_t struct_idx, uint32_t i, uint32_t off); uint32_t ipStructFieldOffset( const InternPool* ip, uint32_t struct_idx, uint32_t i); void ipStructFieldOffsets( const InternPool* ip, uint32_t struct_idx, uint32_t* out_offsets); // Backpatch one slot of field_runtime_order. `runtime_position` indexes the // order array; `field_index` is a declaration index, or 0xFFFFFFFE for an // omitted comptime field. void ipSetStructFieldRuntimeOrder(InternPool* ip, uint32_t struct_idx, uint32_t runtime_position, uint32_t field_index); // Packed structs (`packed struct { ... }`). // A packed struct carries one of the four stock packed tags, chosen // from `(explicit backing int, any_field_defaults)` exactly as upstream's // `getDeclaredStructType` chooses it, over upstream's `Tag.TypeStructPacked` // header: `{zir_index, bits, name, name_nav, namespace, // backing_int_type, fields_len, field_name_map}`. Trailing groups are // upstream's `struct_packed_encoding` / `struct_packed_defaults_encoding` // `type_hash?`, `captures?`, `field_name[]`, `field_type[]`, and — // for the two `_defaults` tags only — `field_default[]`. `captures_len` is 0 // and never `.reified` here (residual D3b), so the first two groups occupy no // words; the `field_default[]` group is minted all-`.none` and materialized by // the post-layout struct-default lifecycle. Because the header words move, the // shared name/namespace/fields_len accessors branch on the tag; the size/flags // and field_offset/field_runtime_order accessors have no packed counterpart at // all and stop loudly instead. uint32_t ipGetDeclaredStructTypePacked(InternPool* ip, uint32_t zir_index, uint32_t fields_len, uint32_t backing_int_ty, bool explicit_backing, bool any_field_defaults); // True iff `struct_idx` is a packed struct. Safe on any index (a tag test). ZIG1_INLINE bool ipStructIsPacked(const InternPool* ip, uint32_t struct_idx) { return ipTagIsStructPacked(ip->item_tags[struct_idx]); } ZIG1_INLINE uint32_t ipStructFieldsLen( const InternPool* ip, uint32_t struct_idx) { return ip->extra[ip->item_datas[struct_idx] + (ipStructIsPacked(ip, struct_idx) ? IP_TYPE_STRUCT_PACKED_FIELDS_LEN : IP_TYPE_STRUCT_FIELDS_LEN)]; } // True iff `struct_idx` is an `extern struct` (`Tag.TypeStruct.Flags.layout`, // flags word bit 2). // Extern structs keep declaration order + C ABI layout (no alignment reorder). bool ipStructIsExtern(const InternPool* ip, uint32_t struct_idx); // Patch a packed struct's backing int type after interning, as // `resolvePackedStructLayout` does (for a struct with a deferred field type // resolved post-namespace-scan). void ipSetStructPackedBacking( InternPool* ip, uint32_t struct_idx, uint32_t backing_int_ty); // Intern a `.bitpack` value: a packed struct instance whose contents are the // interned backing-integer value `backing_val`. Returns the new item index. uint32_t ipGetBitpack( InternPool* ip, uint32_t struct_ty, uint32_t backing_val); uint32_t ipBitpackType(const InternPool* ip, uint32_t idx); uint32_t ipBitpackBackingVal(const InternPool* ip, uint32_t idx); // --- PtrType.Flags bit layout -------------------------------------------- // // The packed `PtrType.Flags` u32 that word 2 of a `type_pointer`'s `extra` // carries: IP_PTR_FLAGS_* (upstream_gen.h), with these names for the field // values zig1 reads and writes. #define IP_PTR_SIZE_ONE ((uint32_t)PTR_SIZE_ONE) #define IP_PTR_SIZE_MANY ((uint32_t)PTR_SIZE_MANY) #define IP_PTR_SIZE_SLICE ((uint32_t)PTR_SIZE_SLICE) #define IP_PTR_SIZE_C ((uint32_t)PTR_SIZE_C) #define IP_PTR_FLAGS_ALIGN_SHIFT IP_PTR_FLAGS_ALIGNMENT_SHIFT #define IP_PTR_FLAGS_ALIGN_MASK IP_PTR_FLAGS_ALIGNMENT_MASK #define IP_PTR_ALIGN_NONE ((uint32_t)IP_ALIGNMENT_NONE) #define IP_PTR_FLAGS_ADDRSPACE_SHIFT IP_PTR_FLAGS_ADDRESS_SPACE_SHIFT #define IP_PTR_FLAGS_ADDRSPACE_MASK IP_PTR_FLAGS_ADDRESS_SPACE_MASK #define IP_PTR_ADDRSPACE_GENERIC ((uint32_t)ADDRESS_SPACE_GENERIC) #define IP_PTR_VECTOR_INDEX_NONE ((uint32_t)IP_VECTOR_INDEX_NONE) // Field reads out of a flags word. #define IP_PTR_FLAGS_SIZE(flags) ((flags) & IP_PTR_FLAGS_SIZE_MASK) #define IP_PTR_FLAGS_ALIGN(flags) \ (((flags) >> IP_PTR_FLAGS_ALIGN_SHIFT) & IP_PTR_FLAGS_ALIGN_MASK) #define IP_PTR_FLAGS_VECTOR_INDEX(flags) \ (((flags) >> IP_PTR_FLAGS_VECTOR_INDEX_SHIFT) & IP_PTR_VECTOR_INDEX_NONE) // The one write of that field: `vector_index = @enumFromInt(index)`, or // `.none` for every pointer that is not a vector element's. #define IP_PTR_FLAGS_WITH_VECTOR_INDEX(flags, index) \ (((flags) \ & ~(IP_PTR_VECTOR_INDEX_NONE << IP_PTR_FLAGS_VECTOR_INDEX_SHIFT)) \ | ((index) << IP_PTR_FLAGS_VECTOR_INDEX_SHIFT)) // The alignment field's write (log2 units, IP_PTR_ALIGN_NONE for `.none`). #define IP_PTR_FLAGS_WITH_ALIGN(flags, log2) \ (((flags) & ~(IP_PTR_FLAGS_ALIGN_MASK << IP_PTR_FLAGS_ALIGN_SHIFT)) \ | ((log2) << IP_PTR_FLAGS_ALIGN_SHIFT)) // `PackedOffset` is `packed struct(u32) { host_size: u16, bit_offset: u16 }`, // host_size in the low half. It counts bytes for a bit-packed host pointer and // elements for a vector element pointer (Sema.elemPtrVector). #define IP_PTR_PACKED_HOST_SIZE(packed_offset) \ (((packed_offset) >> IP_PTR_PACKED_OFFSET_HOST_SIZE_SHIFT) \ & IP_PTR_PACKED_OFFSET_HOST_SIZE_MASK) // A bit pointer's host: `ptr_info.packed_offset` read the way // `comptime_ptr_access` and Legalize's `.expand_packed_load` / // `.expand_packed_store` read it -- `host_size` bytes (0: the pointee is // byte-addressed) and `bit_offset` bits. False for a vector element's // pointer, whose `host_size` counts elements. static inline bool ipPtrPackedHost(uint32_t flags, uint32_t packed_offset, uint32_t* host_size, uint32_t* bit_offset) { if (IP_PTR_FLAGS_VECTOR_INDEX(flags) != IP_PTR_VECTOR_INDEX_NONE) return false; *host_size = IP_PTR_PACKED_HOST_SIZE(packed_offset); *bit_offset = (packed_offset >> IP_PTR_PACKED_OFFSET_BIT_OFFSET_SHIFT) & IP_PTR_PACKED_OFFSET_BIT_OFFSET_MASK; return true; } // The three composed flag words zig1 mints pointers with: vector_index // `.none`, address_space `.generic`, alignment `.none`, not volatile, not // allowzero — plus the // size and is_const bits each name states. `*T`, `*const T`, `[*]const T`. // `[*]T` too — the mutable many-pointer form upstream's canonical // slice-pointer bases carry (Value.canonicalizeBasePtr mints its bases // mutable). Slice types are minted via ipGetSliceType over this many-pointer, // never as a slice-size `type_pointer` item (InternPool.zig converts a // `.slice`-size PtrType into a many-pointer + type_slice at intern time). #define IP_PTR_FLAGS_MUT_ONE \ ((IP_PTR_VECTOR_INDEX_NONE << IP_PTR_FLAGS_VECTOR_INDEX_SHIFT) \ | (IP_PTR_ADDRSPACE_GENERIC << IP_PTR_FLAGS_ADDRSPACE_SHIFT) \ | (IP_PTR_ALIGN_NONE << IP_PTR_FLAGS_ALIGN_SHIFT) \ | (IP_PTR_SIZE_ONE << IP_PTR_FLAGS_SIZE_SHIFT)) #define IP_PTR_FLAGS_CONST_ONE (IP_PTR_FLAGS_MUT_ONE | IP_PTR_FLAGS_IS_CONST) #define IP_PTR_FLAGS_CONST_MANY (IP_PTR_FLAGS_CONST_ONE | IP_PTR_SIZE_MANY) #define IP_PTR_FLAGS_MUT_MANY (IP_PTR_FLAGS_MUT_ONE | IP_PTR_SIZE_MANY) // --- the typed accessor layer -------------------------- // // Upstream has an accessor layer: `src/Type.zig`'s `ptrInfo` / `arrayLen` / // `arrayLenIncludingSentinel` / `childType` / `sentinel` / // `structFieldCount` / `fieldType` / `structFieldIsComptime` / // `optionalChild` all funnel through one payload decoder, // `InternPool.indexToKey`, so a payload's word offsets are written down // exactly once. These accessors are that decoder for the payload families // they cover; code calls them instead of indexing `extra[]`, since a raw read // is how decoders drift apart (`type_array_small`'s child is word +1, // `type_array_big`'s word +2). // // Two calling conventions, matching how upstream's own callers behave: // * status-returning (`ipArrayInfo`, `ipPtrInfo`) — for the callers that // use "not this shape" as ordinary control flow (peer resolution, // coercion arm selection). They decline; they do not abort. Upstream // spells this `switch (ip.indexToKey(...)) { ... else => ... }`. // * asserting (`ipOptionalChild`, `ipTupleField*`) — for the callers that // have already established the shape, mirroring upstream's `else => // unreachable`. These abort (`UNIMPLEMENTED`) rather than read a wrong // word. // Every accessor bounds-checks the item index and the `extra` span before // touching a payload word. // Outcome of `ipArrayInfo`. Ordered by the precedence the decode applies: // kind first, then payload extent, then the 64-bit length. typedef enum { IP_ARR_OK = 0, // Not a sequence type: `ty` is out of range (this subsumes // `IP_INDEX_NONE`), or its tag is none of `type_array_small`, // `type_array_big`, `type_vector`. IP_ARR_NOT_SEQ, // The item's `extra` payload does not lie wholly inside the arena. A // well-formed pool never produces this; the check exists so a malformed // one is caught instead of read out of bounds. IP_ARR_MALFORMED, // A `type_array_big` whose PackedU64 length has a non-zero high word, // i.e. > 4Gi elements. zig1 models lengths as u32 throughout; // the status is surfaced rather than silently truncated. IP_ARR_LEN_TOO_BIG, } IpArrayStatus; // The decoded sequence-type payload. Mirrors what upstream reaches through // `Type.arrayLen` (`pub fn arrayLenIp(ty: Type, ip: *const InternPool) u64` // -> `return ip.aggregateTypeLen(ty.toIntern());`), `Type.childType` // (`pub fn childTypeIp(ty: Type, ip: *const InternPool) Type`), // `Type.sentinel` (`.array_type => |t| if (t.sentinel != .none)`) and // `Type.arrayLenIncludingSentinel` // (`return zcu.intern_pool.aggregateTypeLenIncludingSentinel(ty.toIntern());`) // — all of src/Type.zig. Vectors decode here too, exactly as upstream's // single `inline .array_type, .vector_type` arm shape does: a vector shares // the small array's {len, child} words and has no sentinel. typedef struct { // Language-visible length: `.len`, slicing and bounds all use this, and // it excludes the sentinel (Type.arrayLen). uint32_t len; // Element count actually stored, i.e. `len + 1` when there is a sentinel // (Type.arrayLenIncludingSentinel). Valid only when // `storage_len_overflowed` is false. uint32_t storage_len; // `len + 1` did not fit u32 (only possible for `len == UINT32_MAX`). // Reported rather than aborted here so the declining callers, which // never consult `storage_len`, keep their old behavior exactly. bool storage_len_overflowed; // The element type (Type.childType). uint32_t child; // The sentinel value index, or IP_INDEX_NONE (Type.sentinel's `?Value`). // Always IP_INDEX_NONE for `type_array_small` and `type_vector`. uint32_t sentinel; // True for `type_vector`. Callers that are arrays-only (upstream's // `.array_type`-only arms) reject on this. bool is_vector; } IpArrayInfo; // Decode `type_array_small` / `type_array_big` / `type_vector` into `*out`. // A not-OK answer leaves `*out` an empty sequence of no element type. static inline IpArrayStatus ipArrayInfoInline( const InternPool* ip, uint32_t ty, IpArrayInfo* out) { *out = (IpArrayInfo) { .child = IP_INDEX_NONE, .sentinel = IP_INDEX_NONE }; // Kind first. `ty >= items_len` subsumes IP_INDEX_NONE (0xFFFFFFFF). if (ty >= ip->items_len) return IP_ARR_NOT_SEQ; uint8_t tag = ip->item_tags[ty]; bool is_big = tag == IP_TAG_TYPE_ARRAY_BIG; if (!is_big && tag != IP_TAG_TYPE_ARRAY_SMALL && tag != IP_TAG_TYPE_VECTOR) return IP_ARR_NOT_SEQ; // The trap this layer exists to close: `type_array_big`'s payload is // `Array {len0, len1, child, sentinel}` (4 words, PackedU64 length), // while `type_array_small` and `type_vector` share `Vector {len, child}` // (2 words). So `child` is word +2 for BIG and word +1 for the other // two, and BIG alone has a sentinel word. See `ipGetArrayType` / // `ipGetVectorType` for the writer side. uint32_t base = ip->item_datas[ty]; uint32_t words = is_big ? 4u : 2u; if (base > ip->extra_len || ip->extra_len - base < words) return IP_ARR_MALFORMED; if (is_big && ip->extra[base + 1] != 0) return IP_ARR_LEN_TOO_BIG; out->len = ip->extra[base]; out->child = is_big ? ip->extra[base + 2] : ip->extra[base + 1]; out->sentinel = is_big ? ip->extra[base + 3] : IP_INDEX_NONE; out->is_vector = tag == IP_TAG_TYPE_VECTOR; // Type.arrayLenIncludingSentinel: the sentinel occupies one extra stored // element but is excluded from `len`. out->storage_len = out->len; out->storage_len_overflowed = false; if (out->sentinel != IP_INDEX_NONE) { if (out->len == UINT32_MAX) out->storage_len_overflowed = true; else out->storage_len = out->len + 1u; } return IP_ARR_OK; } IpArrayStatus ipArrayInfo(const InternPool* ip, uint32_t ty, IpArrayInfo* out); // `Type.scalarType`: `"return switch (ty.zigTypeTag(zcu)) { .vector => // ty.childType(zcu), else => ty };"` -- the lane type of a vector, and the // type itself otherwise. Every upstream arm that asks a question of // `_scalar_ty` asks it of this, which is what makes one rule cover a // vector and its scalar. A non-type or IP_INDEX_NONE answers itself. static inline uint32_t ipScalarType(const InternPool* ip, uint32_t ty) { IpArrayInfo vec; if (ipArrayInfoInline(ip, ty, &vec) == IP_ARR_OK && vec.is_vector) return vec.child; return ty; } // Value.elemValue's storage-vs-sentinel split -- the rule every element read // owes, and the reason `repeated` storage cannot simply answer any index: // `"const len = ip.aggregateTypeLen(aggregate.ty);"` // `"if (index < len) return Value.fromInterned(switch (aggregate.storage)"` // `"assert(index == len);"` // `"return Type.fromInterned(aggregate.ty).sentinel(zcu).?;"` // `repeated_elem` and `bytes` storage happily answer an index past `len` // (and `elems` reads a word past its payload), so a decoder that dispatches // on storage alone returns an element where upstream returns the TYPE's // sentinel. This classifies the index; the caller keeps its own storage // decode and its own abort flavour. typedef enum { // `index < aggregateTypeLen` -- the caller's storage decode answers. // Also the answer when the value's type is not a sequence at all (a // struct/tuple aggregate, whose fields the same decoders read). IP_AGG_ELEM_STORED = 0, // `index == len` on a sentineled sequence: `*out_sentinel` is the value. IP_AGG_ELEM_SENTINEL, // Past even the sentinel -- upstream's `assert` / `.?`. Caller aborts. IP_AGG_ELEM_OUT_OF_RANGE, // Not one of the three `aggregate.storage` forms (only ever returned by // ipAggregateElemValue, whose `.undef` / non-aggregate callers each have // their own abort flavour). Caller aborts. IP_AGG_ELEM_UNSUPPORTED_STORAGE, } IpAggElemKind; // Value.elemValue itself: the rule above, then `"switch (aggregate.storage)"`. // `*out` receives the element (or the type's sentinel) for STORED/SENTINEL. // The one element read; a caller supplies only its own abort flavour. IpAggElemKind ipAggregateElemValue( InternPool* ip, uint32_t agg, uint32_t index, uint32_t* out); // Value.elemValue: element `index` of an aggregate value in any storage // InternPool.get picks (ipAggregateElemValue), its sentinel at `len`. Any // other answer aborts with `ctx`. static inline uint32_t valElemValue( InternPool* ip, uint32_t aggregate, uint32_t index, const char* ctx) { uint32_t elem = IP_INDEX_NONE; switch (ipAggregateElemValue(ip, aggregate, index, &elem)) { case IP_AGG_ELEM_STORED: case IP_AGG_ELEM_SENTINEL: return elem; case IP_AGG_ELEM_OUT_OF_RANGE: case IP_AGG_ELEM_UNSUPPORTED_STORAGE: break; } UNIMPLEMENTED(ctx); ZIG1_TCC_RETURN(IP_INDEX_NONE); } typedef enum { IP_PTRI_OK = 0, // `ty` is out of range (subsumes IP_INDEX_NONE) or is not a // `type_pointer`. NOTE `type_slice` is a separate zig1 tag holding its // many-pointer index in `data`; it does not decode here (upstream folds // slices into `.ptr_type` with `size == .slice`). IP_PTRI_NOT_PTR, // The 4-word payload does not lie wholly inside the `extra` arena. IP_PTRI_MALFORMED, } IpPtrInfoStatus; // The decoded `Tag.TypePointer` payload == upstream's `Key.PtrType`, which // `pub fn ptrInfo(ty: Type, zcu: *const Zcu) InternPool.Key.PtrType` // (src/Type.zig) returns via `.ptr_type => |p| p,`. The four raw words are // {child, sentinel, flags, packed_offset} (see `ipGetPtrTypeFull`); the // remaining members are the `flags` bit-fields, decoded once here with the // IP_PTR_* constants above instead of at each caller. typedef struct { uint32_t child; uint32_t sentinel; // IP_INDEX_NONE when not sentinel-terminated uint32_t flags; // the raw packed PtrType.Flags word uint32_t packed_offset; uint32_t size; // IP_PTR_SIZE_{one,many,slice,C} uint32_t alignment; // log2 units; IP_PTR_ALIGN_NONE == natural bool is_const; bool is_volatile; bool is_allowzero; } IpPtrInfo; // A not-OK answer leaves `*out` a pointer to no child type. IpPtrInfoStatus ipPtrInfo(const InternPool* ip, uint32_t ty, IpPtrInfo* out); // Decode `type_pointer` into `*out` (ipPtrInfo, inline). static inline IpPtrInfoStatus ipPtrInfoInline( const InternPool* ip, uint32_t ty, IpPtrInfo* out) { *out = (IpPtrInfo) { .child = IP_INDEX_NONE, .sentinel = IP_INDEX_NONE }; if (ty >= ip->items_len || ip->item_tags[ty] != IP_TAG_TYPE_POINTER) return IP_PTRI_NOT_PTR; uint32_t base = ip->item_datas[ty]; if (base > ip->extra_len || ip->extra_len - base < IP_TYPE_POINTER_LEN) return IP_PTRI_MALFORMED; // Tag.TypePointer == Key.PtrType: child, sentinel, flags, packed_offset. out->child = ip->extra[base + IP_TYPE_POINTER_CHILD]; out->sentinel = ip->extra[base + IP_TYPE_POINTER_SENTINEL]; out->flags = ip->extra[base + IP_TYPE_POINTER_FLAGS]; out->packed_offset = ip->extra[base + IP_TYPE_POINTER_PACKED_OFFSET]; out->size = IP_PTR_FLAGS_SIZE(out->flags); out->alignment = IP_PTR_FLAGS_ALIGN(out->flags); out->is_const = (out->flags & IP_PTR_FLAGS_IS_CONST) != 0; out->is_volatile = (out->flags & IP_PTR_FLAGS_IS_VOLATILE) != 0; out->is_allowzero = (out->flags & IP_PTR_FLAGS_IS_ALLOWZERO) != 0; return IP_PTRI_OK; } // `indexToKey(ty).ptr_type` of a type known to be a pointer. static inline IpPtrInfo ipPtrInfoKnown(const InternPool* ip, uint32_t ptr_ty) { IpPtrInfo info; if (ipPtrInfoInline(ip, ptr_ty, &info) != IP_PTRI_OK) UNREACHABLE("ipPtrInfoKnown: not a pointer type"); return info; } static inline uint32_t ipPtrTypeFlags(const InternPool* ip, uint32_t ptr_ty) { return ipPtrInfoKnown(ip, ptr_ty).flags; } static inline uint32_t ipPtrChild(const InternPool* ip, uint32_t ptr_ty) { return ipPtrInfoKnown(ip, ptr_ty).child; } static inline uint32_t ipPtrSentinel(const InternPool* ip, uint32_t ptr_ty) { return ipPtrInfoKnown(ip, ptr_ty).sentinel; } static inline uint32_t ipPtrSize(const InternPool* ip, uint32_t ptr_ty) { return ipPtrInfoKnown(ip, ptr_ty).size; } static inline uint32_t ipPtrPackedOffset( const InternPool* ip, uint32_t ptr_ty) { return ipPtrInfoKnown(ip, ptr_ty).packed_offset; } // `indexToKey(v).opt.val` or `.error_union.val.payload` of a non-null optional // or payload error union value; both carry { ty, val }. static inline uint32_t ipPayloadVal(const InternPool* ip, uint32_t v) { if (v >= ip->items_len || (ip->item_tags[v] != IP_TAG_OPT_PAYLOAD && ip->item_tags[v] != IP_TAG_ERROR_UNION_PAYLOAD)) UNREACHABLE("ipPayloadVal: not a payload value"); return ip->extra[ip->item_datas[v] + 1]; } // `indexToKey(v).err.name` or `.error_union.val.err_name`; both carry // { ty, name }. static inline uint32_t ipErrorName(const InternPool* ip, uint32_t v) { if (v >= ip->items_len || (ip->item_tags[v] != IP_TAG_ERROR_SET_ERROR && ip->item_tags[v] != IP_TAG_ERROR_UNION_ERROR)) UNREACHABLE("ipErrorName: not an error value"); return ip->extra[ip->item_datas[v] + 1]; } // `indexToKey(v).un.tag` and `.val`: a union_value is { ty, tag, val }. static inline uint32_t ipUnionValueTag(const InternPool* ip, uint32_t v) { if (v >= ip->items_len || ip->item_tags[v] != IP_TAG_UNION_VALUE) UNREACHABLE("ipUnionValueTag: not a union value"); return ip->extra[ip->item_datas[v] + 1]; } static inline uint32_t ipUnionValuePayload(const InternPool* ip, uint32_t v) { if (v >= ip->items_len || ip->item_tags[v] != IP_TAG_UNION_VALUE) UNREACHABLE("ipUnionValuePayload: not a union value"); return ip->extra[ip->item_datas[v] + 2]; } // `indexToKey(ty).error_set_type.names` of a type_error_set: { names_len, // names_map, names[names_len] }. static inline uint32_t ipErrorSetNamesLen(const InternPool* ip, uint32_t ty) { if (ty >= ip->items_len || ip->item_tags[ty] != IP_TAG_TYPE_ERROR_SET) UNREACHABLE("ipErrorSetNamesLen: not an error set type"); return ip->extra[ip->item_datas[ty]]; } static inline uint32_t ipErrorSetName( const InternPool* ip, uint32_t ty, uint32_t i) { if (i >= ipErrorSetNamesLen(ip, ty)) UNREACHABLE("ipErrorSetName: index past the set"); return ip->extra[ip->item_datas[ty] + 2 + i]; } // The child of `?T`. Port of `pub fn optionalChild(ty: Type, zcu: *const // Zcu) Type` (src/Type.zig), whose optional arm is // `.opt_type => |child| return .fromInterned(child),` — asserting // (`else => unreachable`), so a non-optional argument aborts loudly here. // zig1 stores the child Index directly in `item_datas` (no `extra` // payload); this is the one place that fact is written down. // DIVERGENCE from upstream: `optionalChild` also accepts a C pointer and // returns it unmodified (`assert(ptr_type.flags.size == .c); return ty;`). // This helper remains optional-only; C-pointer callers reach the loud abort // until that separate caller surface is ported. uint32_t ipOptionalChild(const InternPool* ip, uint32_t ty); // Type.optEuBaseType: peel optional and error-union wrappers until the // aggregate base type is reached. uint32_t ipOptEuBaseType(const InternPool* ip, uint32_t ty); // Anonymous-tuple (`type_tuple`) field accessors. The payload is // `TypeTuple { fields_len, types[fields_len], values[fields_len] }` (see // `ipGetTupleType`), i.e. the values array starts at `+1 + fields_len` — the // offset arithmetic these three functions exist to centralize. All three // assert (tag, payload extent, field index), mirroring upstream's // `else => unreachable` arms. // // Upstream analogues, all src/Type.zig, all reached through the `.tuple_type` // arm of the corresponding struct-field accessor: // ipTupleFieldCount <- `pub fn structFieldCount`, // `.tuple_type => |tuple| return tuple.types.len,` // ipTupleFieldType <- `pub fn fieldType` (src/Type.zig), // `.tuple_type => |tuple| tuple.types,` // ipTupleFieldValue <- `pub fn structFieldDefaultValue`, // `const val = tuple.values.get(ip)[index];` // (`.none` == no stored value == a runtime field; // upstream's `structFieldIsComptime` is exactly // `.tuple_type => |tuple| return // tuple.values.get(ip)[index] != .none,`) // zig1 keeps these tuple-only rather than folding struct + tuple into one // call, because the struct half already has its own accessor family // (`ipStructField*`) with a different payload layout. uint32_t ipTupleFieldCount(const InternPool* ip, uint32_t ty); uint32_t ipTupleFieldType(const InternPool* ip, uint32_t ty, uint32_t i); uint32_t ipTupleFieldValue(const InternPool* ip, uint32_t ty, uint32_t i); // Is `v` a comptime-mutable-memory pointer value (`ptr_comptime_alloc`)? // Status-returning (a declining classifier: "not this shape" is ordinary // control flow), mirroring how upstream reaches the same question — the // `.ptr => |ptr| switch (ptr.base_addr) { .comptime_alloc => ..., else => // ... }` shape over `InternPool.indexToKey`, whose base-addr variant is // documented `"/// Points to the value of a single comptime alloc stored in // `Sema`."`. bool ipIsPtrComptimeAlloc(const InternPool* ip, uint32_t v); // The `alloc_index` of a `ptr_comptime_alloc` value — word +1 of // `PtrComptimeAlloc { ty, index, byte_offset_a, byte_offset_b }` (see // ipGetPtrComptimeAlloc), i.e. upstream's `Key.Ptr.BaseAddr` // `"comptime_alloc: ComptimeAllocIndex,"`. Asserting (upstream's `else => // unreachable`): same bounds / tag checks as // ipIsPtrComptimeAlloc, but a value failing any of them aborts loudly rather // than reading a wrong word. // // The index is returned raw: it indexes the analyzing Sema's // comptime_allocs (getComptimeAlloc). uint32_t ipPtrComptimeAllocIndex(const InternPool* ip, uint32_t v); uint64_t ipPtrComptimeAllocByteOffset(const InternPool* ip, uint32_t v); // Intern a single-child pointer type (mirrors PerThread.ptrType -> // InternPool.get for the `.ptr_type` key). Serializes the 4-word // `Tag.TypePointer` (== `Key.PtrType`: child, sentinel, flags, packed_offset) // onto `extra` and appends a `type_pointer` item, returning its index. `flags` // is the packed `PtrType.Flags` u32 (see the `*const type` computation in the // caller). Dedup (getOrPutKey) is deferred: each call appends a new item. uint32_t ipGetPtrType(InternPool* ip, uint32_t child, uint32_t flags); // Full PtrType-info variant used when deriving a field pointer from an // existing pointer. This preserves the parent's sentinel and packed-offset // words in addition to all flags; the caller replaces only child/alignment. uint32_t ipGetPtrTypeFull(InternPool* ip, uint32_t child, uint32_t flags, uint32_t sentinel, uint32_t packed_offset); // ipGetPtrType with an explicit sentinel value index (the `0` of // `[:0]const u8`), or IP_INDEX_NONE (== ipGetPtrType). Dedups on all four // Key.PtrType words. uint32_t ipGetPtrTypeSentinel( InternPool* ip, uint32_t child, uint32_t flags, uint32_t sentinel); // Intern a `.ptr` value whose base address is a `Nav` (mirrors // InternPool.get for `.ptr` with `base_addr == .nav`). Serializes the 4-word // `PtrNav` (ty, nav, byte_offset_hi, byte_offset_lo) and appends a `ptr_nav` // item, returning its index. `byte_offset` is 0 for a plain nav pointer. uint32_t ipGetPtrNav(InternPool* ip, uint32_t ty, uint32_t nav); uint32_t ipGetPtrNavOffset( InternPool* ip, uint32_t ty, uint32_t nav, uint64_t byte_offset); // Intern a function type (mirrors InternPool.getFuncType, InternPool.zig) // with no var-args. Serializes the 3-word `Tag.TypeFunction` header // (params_len, return_type, flags), the optional trailing comptime_bits / // noalias_bits words (setting flags bits 19/20 when non-zero), plus one // `param_type` Index per parameter onto `extra`, then appends a // `type_function` item, returning its index (deduping against existing // entries). `return_type` and each `param_types[i]` are InternPool type // indices (for primitives, equal to their ZIR ref). `flags` is the packed // Tag.TypeFunction.Flags word (cc, is_noinline bit 21, ...); use // `ipTypeFunctionFlags` for the cc base. // // Upstream has a third conditional group between `noalias_bits` and // `param_type[]` -- the SPIR-V options words, present iff `flags.cc.extraLen() // != 0` // (`Tag.encodings` `spirv_kernel_options`/`spirv_mesh_options`), true only for // the three SPIR-V cc tags -- and it is unmintable here, see // `ipTypeFunctionFlags`. If a SPIR-V cc is ever ported, four sites must grow // the `extraLen()` term: this writer, the reader `ipFuncTypeParamType`, and // the two out-of-getter creators that build their own header + dedup key, // `ipGetFuncDeclIes` and `ipGetFuncInstance` (see their decls below). uint32_t ipGetFuncType(InternPool* ip, uint32_t return_type, const uint32_t* param_types, uint32_t params_len, uint32_t flags, uint32_t comptime_bits, uint32_t noalias_bits); // The packed Tag.TypeFunction.Flags word for an exported (`.wasm_mvp`) or // non-exported (`.auto`) function (no var-args/comptime/noalias). See the // definition for the bit layout. An explicit `callconv(...)` mints its own // packed convention instead (sema.c's `ccPack`); the SPIR-V options // words never appear, because `extraLen() != 0` holds for exactly the four // SPIR-V conventions and sema.c's `ccValidate` rejects all of them as // `bad_arch` on this port's wasm32 target before `ccPack` runs. uint32_t ipTypeFunctionFlags(bool is_export); // Encode a byte count as an `InternPool.Alignment` (log2 units, `.none` == 63 // for a zero count). Mirrors `Alignment.fromByteUnits`; aborts with `ctx` on a // non-power-of-two or unrepresentable count. uint32_t ipAlignmentFromByteUnits(uint64_t bytes, const char* ctx); // Read the `return_type` Index from a `type_function` item. uint32_t ipFuncTypeReturnType(const InternPool* ip, uint32_t func_ty); // Read the `i`-th parameter type Index from a `type_function` item. Its // offset arithmetic is the only one that runs past every optional trailing // group, so it is the reader that would have to add `flags.cc.extraLen()` if // a the SPIR-V options words-bearing cc were ever ported (see // `ipGetFuncType`). uint32_t ipFuncTypeParamType( const InternPool* ip, uint32_t func_ty, uint32_t i); // Read the `comptime_bits` word from a `type_function` item (0 if the // has_comptime_bits flag is unset). Non-zero for a generic function with a // `comptime` parameter. uint32_t ipFuncTypeComptimeBits(const InternPool* ip, uint32_t func_ty); // `FuncType.paramIsComptime` / `paramIsNoalias`: `"@as(u1, @truncate( // self.comptime_bits >> i)) != 0"`, the u32 having no bit past index 31. #define IP_FUNC_TYPE_PARAM_BITS 32u static inline bool ipFuncTypeParamBit(uint32_t bits, uint32_t i) { return i < IP_FUNC_TYPE_PARAM_BITS && ((bits >> i) & 1u) != 0; } // True when a `type_function`'s calling convention is `.@"inline"` (an // `inline fn`): the low 8 Flags bits (PackedCallingConvention.tag) == 3. bool ipFuncTypeIsInline(const InternPool* ip, uint32_t func_ty); // `Flags.is_noinline` of a `type_function` item (a `noinline fn`). bool ipFuncTypeIsNoinline(const InternPool* ip, uint32_t func_ty); // `PackedCallingConvention` (u18), the low bits of a `type_function`'s Flags: // `tag` (u8), `incoming_stack_alignment` (Alignment, u6; IP_PTR_ALIGN_NONE is // `.none`), `extra` (u4). #define IP_PACKED_CC_ALIGN_SHIFT IP_PACKED_CC_INCOMING_STACK_ALIGNMENT_SHIFT #define IP_PACKED_CC_ALIGN_MASK IP_PACKED_CC_INCOMING_STACK_ALIGNMENT_MASK #define IP_PACKED_CC_MASK IP_FUNC_FLAGS_CC_MASK // Read a `type_function`'s PackedCallingConvention.tag — the low 8 Flags bits, // a `std.lang.CallingConvention.Tag` ordinal (InternPool.zig). uint32_t ipFuncTypeCcTag(const InternPool* ip, uint32_t func_ty); // A `type_function`'s whole PackedCallingConvention (Flags bits 0..17), the // form ipTypeFunctionFlags builds. uint32_t ipFuncTypeCcPacked(const InternPool* ip, uint32_t func_ty); // Read a `type_function`'s Flags.is_var_args (bit 18; see ipTypeFunctionFlags // for the layout). bool ipFuncTypeIsVarArgs(const InternPool* ip, uint32_t func_ty); // Read the `noalias_bits` word from a `type_function` item (0 if the // has_noalias_bits flag is unset). uint32_t ipFuncTypeNoaliasBits(const InternPool* ip, uint32_t func_ty); // Read the parameter count from a `type_function` item. uint32_t ipFuncTypeParamsLen(const InternPool* ip, uint32_t func_ty); // Read the `owner_nav` Nav.Index from a `func_decl` item. uint32_t ipFuncDeclOwnerNav(const InternPool* ip, uint32_t func_decl); // Read the `ty` (type_function Index) from a `func_decl` item. uint32_t ipFuncDeclType(const InternPool* ip, uint32_t func_decl); // InternPool.getExtern (function type only): dedups on the key, else interns // the 7-word Tag.Extern item, creating its owner Nav when `owner_nav` is // IP_INDEX_NONE. `lib_name` is an IP string index or IP_INDEX_NONE; `flags` // is Tag.Extern.Flags (linkage, visibility << 2, is_dll_import << 4, // relocation << 5, source << 6). uint32_t ipGetExtern(InternPool* ip, uint32_t name, uint32_t ty, uint32_t lib_name, uint32_t flags, uint32_t zir_index, bool is_const, uint32_t alignment, uint32_t owner_nav); uint32_t ipExternFunctionOwnerNav(const InternPool* ip, uint32_t extern_fn); uint32_t ipExternFunctionLibraryName(const InternPool* ip, uint32_t extern_fn); // Intern a monomorphized function instance (InternPool.getFuncInstance). See // the definition; creates the instance's `type_function`, `func_instance` // item, and its "__func_" Nav (`anon_name_counter` is // `&zcu.anon_name_counter`). Inferred-error-set functions get // instance-owned error-union / inferred-error-set types. Returns the // func_instance Index. // `noalias_bits` are the instance-signature bits (remapped to runtime arg // positions by the caller, mirroring Sema.analyzeCall). // Builds its own `type_function` header + dedup key; one of the four sites // that must grow the SPIR-V options words if a SPIR-V cc is ever ported (see // `ipGetFuncType`). uint32_t ipGetFuncInstance(InternPool* ip, uint32_t generic_owner_fdecl, uint32_t bare_return_type, const uint32_t* runtime_param_types, uint32_t runtime_params_len, const uint32_t* comptime_args, uint32_t comptime_args_len, uint32_t noalias_bits, uint32_t* anon_name_counter); // Readers for a `func_instance` item. uint32_t ipFuncInstanceGenericOwner(const InternPool* ip, uint32_t inst); uint32_t ipFuncInstanceOwnerNav(const InternPool* ip, uint32_t inst); uint32_t ipFuncInstanceComptimeArg( const InternPool* ip, uint32_t inst, uint32_t i); // Upstream's one `switch (ip.indexToKey(val)) { .func => |f| f.owner_nav }` // over a comptime-known function value: the owning Nav of a `func_decl` or a // `func_instance`, or IP_INDEX_NONE when the value is neither. uint32_t ipFuncValueOwnerNav(const InternPool* ip, uint32_t val); // Read the instance's `ty` (its monomorphized type_function Index). uint32_t ipFuncInstanceType(const InternPool* ip, uint32_t inst); // `Func.branchQuotaUnordered` / `Func.maxBranchQuota` on an instance's // `branch_quota` word (`"@max(branch_quota_ptr.*, new_branch_quota)"`). uint32_t ipFuncInstanceBranchQuota(const InternPool* ip, uint32_t inst); void ipFuncInstanceMaxBranchQuota(InternPool* ip, uint32_t inst, uint32_t q); uint32_t ipGetFuncCoerced(InternPool* ip, uint32_t func, uint32_t ty); // Return the `extra` index of a function's resolved inferred-error-set slot, // or UINT32_MAX if the function has no inferred error set. Accepts both // func_decl and func_instance indices. uint32_t ipFuncIesResolvedSlot(const InternPool* ip, uint32_t func_index); // What ipErrorSetHasName answers for an inferred error set. typedef enum { IP_IES_NAME_ASSUMED, IP_IES_NAME_NONE, IP_IES_NAME_RESOLVED, } IpInferredErrorSetName; uint32_t ipResolvedInferredErrorSet(const InternPool* ip, uint32_t error_set); // Type.errorSetHasFieldIp: whether `name` is in `error_set`; anyerror has // every name. `ies` says what an inferred error set answers: the frames // differ there, Sema assuming membership, the interpreter none, and the // comptime evaluator reading the resolved set (no name while unresolved). static inline bool ipErrorSetHasName(const InternPool* ip, uint32_t error_set, uint32_t name, IpInferredErrorSetName ies) { if (error_set == ZIR_REF_ANYERROR_TYPE) return true; if (error_set < ip->items_len && ip->item_tags[error_set] == IP_TAG_TYPE_INFERRED_ERROR_SET) { switch (ies) { case IP_IES_NAME_ASSUMED: return true; case IP_IES_NAME_NONE: return false; case IP_IES_NAME_RESOLVED: { uint32_t resolved = ipResolvedInferredErrorSet(ip, error_set); if (resolved == IP_INDEX_NONE) return false; return ipErrorSetHasName(ip, resolved, name, ies); } } } if (error_set >= ip->items_len || ip->item_tags[error_set] != IP_TAG_TYPE_ERROR_SET) return false; uint32_t n = ipErrorSetNamesLen(ip, error_set); for (uint32_t i = 0; i < n; i++) if (ipErrorSetName(ip, error_set, i) == name) return true; return false; } // Intern a function declaration value (mirrors InternPool.getFuncDecl, // InternPool.zig). Serializes the 8-word `Tag.FuncDecl` payload // (analysis, owner_nav, ty, zir_body_inst, lbrace_line, rbrace_line, // lbrace_column, rbrace_column) onto `extra` and appends a `func_decl` item, // returning its index. `analysis` is the default `FuncAnalysis` (all-zero: // no runtime analysis wanted, branch_hint .none, no inferred error set, etc.) // except is_noinline (bit 4) for a `noinline fn`. Dedup deferred; each call // appends a fresh item. uint32_t ipGetFuncDecl(InternPool* ip, uint32_t owner_nav, uint32_t ty, bool is_noinline, uint32_t zir_body_inst, uint32_t lbrace_line, uint32_t rbrace_line, uint32_t lbrace_column, uint32_t rbrace_column); // Intern a function declaration with an inferred error set (`fn f() !T`), // mirroring InternPool.getFuncDeclIes. Appends four consecutive items // (func_decl, type_error_union, type_inferred_error_set, type_function); see // the definition for the layout. Returns the `func_decl` index. `base_flags` // is the calling-convention/export flag word (as from ipTypeFunctionFlags); // `comptime_bits` marks generic comptime params. Dedup deferred (each inferred // error set is unique to its function). // Builds its own `type_function` header + dedup key (intern_pool.c,) // ; one of the four sites that must grow `flags.cc.extraLen()` if a // SPIR-V cc is ever ported (see `ipGetFuncType`). uint32_t ipGetFuncDeclIes(InternPool* ip, uint32_t owner_nav, uint32_t bare_return_type, const uint32_t* param_types, uint32_t params_len, uint32_t base_flags, uint32_t comptime_bits, bool is_noinline, uint32_t zir_body_inst, uint32_t lbrace_line, uint32_t rbrace_line, uint32_t lbrace_column, uint32_t rbrace_column); // Intern a memoized comptime-call result (mirrors the `.memoized_call` case of // InternPool.intern, InternPool.zig). Serializes the 4-word // `MemoizedCall` payload (func, args_len, result, branch_count) followed by // the `args_len` argument value indices, and appends a `memoized_call` item, // returning its index. Appends without consulting the item table; both // callers (analyzeComptimeCall1, ctAnalyzeComptimeCallTyped, sema_comptime.c) // reach it only after an `ipLookupMemoizedCall` miss. Also inserts the new // item into the `memo_dedup_*` index under the same key, but only if that key // isn't already indexed (find-before-insert on the index itself, checked // internally) -- so the index stays first-match-wins no matter which caller // path appended a given item. uint32_t ipGetMemoizedCall(InternPool* ip, uint32_t func, const uint32_t* arg_values, uint32_t args_len, uint32_t result, uint32_t branch_count, uint32_t branch_quota); // Look up an existing `memoized_call` item by {func, arg_values[0..args_len)} // (mirrors upstream `ip.getIfExists(.{.memoized_call = ...})`, // Sema.zig -- `result`/`branch_count` are not part of the key, they // aren't known yet at lookup time). Returns the item index on a hit (read its // stored result/branch_count with `ipMemoizedCallResult`/ // `ipMemoizedCallBranchCount` and skip re-evaluating the call body, mirroring // Sema.analyzeCall's memo-hit early return, Sema.zig), or // IP_INDEX_NONE on a miss (evaluate the call normally; a successful, // non-mutating evaluation then calls `ipGetMemoizedCall` with the same key to // record it). Callers must apply the same "does this call want memoization" // guard upstream applies before even trying the lookup (Sema.zig: // skip when block is not comptime, or any argument // `canMutateComptimeVarState`) -- an argument for which no // entry was ever written can still theoretically share a hash bucket, so the // guard is belt-and-suspenders, not load-bearing for correctness, but keeps // lookup semantics aligned with upstream's decision to not even attempt one. uint32_t ipLookupMemoizedCall(const InternPool* ip, uint32_t func, const uint32_t* arg_values, uint32_t args_len); // Read the stored `result`/`branch_count` word of a `memoized_call` item // found by `ipLookupMemoizedCall` (or any other memoized_call index). `idx` // must be a valid `IP_TAG_MEMOIZED_CALL` item index. uint32_t ipMemoizedCallResult(const InternPool* ip, uint32_t idx); uint32_t ipMemoizedCallBranchCount(const InternPool* ip, uint32_t idx); uint32_t ipMemoizedCallBranchQuota(const InternPool* ip, uint32_t idx); // Intern an integer type (mirrors PerThread.intType -> InternPool.get for the // `.int_type` key). For `type_int_signed`/`type_int_unsigned` the item `data` // holds the bit width directly (no `extra` payload). Deduplicates against // existing int-type items by (signedness tag, bits); returns the existing // index on a hit, else appends. `is_signed` selects signed vs unsigned. uint32_t ipGetIntType(InternPool* ip, bool is_signed, uint32_t bits); // Intern a declared `.auto`-tag-mode enum type (mirrors the `.type_enum_auto` // branch of InternPool.getEnumType, InternPool.zig, restricted to a // non-reified, exhaustive enum with no captures). Serializes the // `Tag.TypeEnum` header followed by the trailing `zir_index` // (TrackedInst.Index) and one `field_name` (NullTerminatedString) per field — // for auto enums there is no field_value_map / field_value. `int_tag_type` is // written as `.none` (IP_INDEX_NONE) here: it is resolved and backpatched // later via `ipSetEnumIntTagType` (the forward reference is load-bearing — // upstream the enum index is reserved before its int tag type is interned). // `field_names` are IP NullTerminatedString indices. Returns the new item's // index. uint32_t ipGetEnumTypeAuto(InternPool* ip, uint32_t zir_index, uint32_t name, uint32_t fqn, uint32_t name_nav, uint32_t namespace_idx, uint32_t fields_len, const uint32_t* field_names); // Intern a declared enum with an explicit integer tag type and explicit field // values (mirrors InternPool.getDeclaredEnumType for the // explicit/nonexhaustive cases, no captures/decls). Serializes the same // `Tag.TypeEnum` header as the auto case, but with `int_tag_type` set to the // user-specified type (`int_tag_ty`), followed by `zir_index`, a fresh // `field_value_map`, `field_name[fields_len]`, and `field_value[fields_len]`. // Each `field_values[i]` is an already-interned int Index of type // `int_tag_ty`. `nonexhaustive` selects `IP_TAG_TYPE_ENUM_NONEXHAUSTIVE` vs // `IP_TAG_TYPE_ENUM_EXPLICIT`. Returns the new item's index. uint32_t ipGetEnumTypeExplicit(InternPool* ip, uint32_t zir_index, uint32_t name, uint32_t fqn, uint32_t name_nav, uint32_t namespace_idx, uint32_t int_tag_ty, uint32_t fields_len, const uint32_t* field_names, const uint32_t* field_values, bool nonexhaustive); // True if `ty` is any interned enum type item (auto, explicit, or // nonexhaustive). Enum accessors (ipEnumTagFromFieldName, ipEnumFieldName, // ipUnionFieldIndex) branch on the tag to pick the correct trailing offsets. bool ipIsEnumType(const InternPool* ip, uint32_t ty); // Backpatch an enum type's `int_tag_type` header word (mirrors // `WipEnumType.setTagTy` / the deferred int-tag resolution). `enum_idx` is a // `type_enum_auto`/`type_enum_explicit` item; `int_tag_type` is the resolved // integer type index. void ipSetEnumIntTagType( InternPool* ip, uint32_t enum_idx, uint32_t int_tag_type); // Backpatch an enum type's `namespace` header word — for an // enum with member decls, whose namespace is created after the type is // interned (same pattern as ipSetStructNamespace). void ipSetEnumNamespace( InternPool* ip, uint32_t enum_idx, uint32_t namespace_idx); // An enum type's namespace header word, or IP_NAMESPACE_NONE if the enum has // no member decls. Resolves the raw word's 0-vs-unset ambiguity by namespace // ownership, like ipStructNamespaceOrNone/ipUnionNamespaceOrNone. uint32_t ipEnumNamespaceOrNone(const InternPool* ip, uint32_t enum_ty); // Intern a declared `opaque { ... }` type (mirrors // InternPool.getDeclaredOpaqueType, no captures). Serializes the stock // `Tag.TypeOpaque` payload (captures_len 0) and appends a `type_opaque` item, // returning its // index. No getOrPutKey dedup is applied (stock dedups on {zir_index, // captures}; each zig1 ipTrackZir index is fresh and the Nav value cache // prevents re-evaluating a declared opaque's body, matching the captureless // struct_decl/enum_decl handlers). uint32_t ipGetOpaqueType(InternPool* ip, uint32_t zir_index, uint32_t name, uint32_t fqn, uint32_t name_nav, uint32_t namespace_idx); // An opaque type's `namespace` word: backpatched after the type is interned // (the namespace's owner), and read back as IP_NAMESPACE_NONE when unset. void ipSetOpaqueNamespace( InternPool* ip, uint32_t opaque_idx, uint32_t namespace_idx); uint32_t ipOpaqueNamespaceOrNone(const InternPool* ip, uint32_t opaque_idx); // Intern a small integer value (mirrors InternPool.get for `.int` with a // value that fits `IntSmall`). Serializes the 2-word `IntSmall` payload // {ty, value} and appends an `int_small` item, returning its index. uint32_t ipGetIntSmall(InternPool* ip, uint32_t ty, uint32_t value); // Intern an enum tag value (mirrors InternPool.get for `.enum_tag`). // Serializes the 2-word `Tag.EnumTag` payload {ty, int} and appends an // `enum_tag` item, returning its index. `ty` is the enum type; `int` is the // integer tag value (an `int_small`/int item of the enum's int tag type). uint32_t ipGetEnumTag(InternPool* ip, uint32_t ty, uint32_t int_val); // Resolve `.` of an auto enum type to its `enum_tag` value: look up the // field index by name, intern `@as(int_tag_type, field_index)`, then the // `enum_tag`. Mirrors Zcu.PerThread.enumValueFieldIndex (auto case). Returns // IP_INDEX_NONE if `field_name_str` is not a field of `enum_ty`. uint32_t ipEnumTagFromFieldName( InternPool* ip, uint32_t enum_ty, uint32_t field_name_str); // The same field-name lookup without interning anything: `.` -> the // declaration field index of `enum_ty`, read straight out of its // `field_name[]` array (names are interned, so the string index is the // comparison). Returns UINT32_MAX on a miss or a non-enum type. Callers that // only need the index must use this: ipEnumTagFromFieldName appends an int // and an `enum_tag` item as a side effect. uint32_t ipEnumFieldIndexFromName( InternPool* ip, uint32_t enum_ty, uint32_t field_name_str); // The enum reverse mapping: backing-integer bits -> declaration field index. // `tag_bits` are the value's raw storage bits (zero-extended low words, the // fixed-int carrier convention — not sign-extended). Auto enums map bits == // index directly; explicit/nonexhaustive enums scan the stored field values // (skipping any that is not a decodable fixed-width int). Returns UINT32_MAX // on miss; the first matching index otherwise. `out_ambiguous` (optional) is // set when a second, distinct field matches the same bits (well-formed enums // never have duplicate values; the runtime interpreter aborts on it). uint32_t ipEnumFieldIndexFromTagBits(InternPool* ip, uint32_t enum_ty, uint64_t tag_bits, uint32_t width, bool* out_ambiguous); // Reverse an interned enum_tag value to its declaration field index (thin // wrapper over ipEnumFieldIndexFromTagBits). Explicit backing integers are // not declaration indices; this compares canonical tag values. Returns // UINT32_MAX when the value is not a declared field (or the tag int's type // is not the enum's int tag type). uint32_t ipEnumTagFieldIndex(InternPool* ip, uint32_t enum_tag_idx); // `pt.smallestUnsignedInt`: the narrowest unsigned int type holding `max`. uint32_t ipSmallestUnsignedInt(InternPool* ip, uint64_t max); // Backpatches an auto enum's (or generated union tag's) inferred tag type. void ipSetInferredEnumIntTagType( InternPool* ip, uint32_t enum_ty, uint32_t fields_len); // An enum type's backing integer tag type (the `int_tag_type` header word of // `type_enum_auto`/`type_enum_explicit`; see ipSetEnumIntTagType): the 5th // word (index 4) of the Tag.TypeEnum header. An auto enum's is resolved by // ipResolveEnumAutoTagType on the first read. // Sema/type_resolution.zig resolveEnumLayout's auto arm: the field names, // then "try pt.smallestUnsignedInt(fields_len - 1)" as the tag type; an // unresolved explicit one runs `resolve_enum_layout`. A no-op for a resolved // enum. void ipResolveEnumAutoTagType(InternPool* ip, uint32_t enum_ty); ZIG1_INLINE uint32_t ipEnumTypeIntTagType(InternPool* ip, uint32_t enum_ty) { uint32_t off = ip->item_datas[enum_ty] + IP_TYPE_ENUM_INT_TAG_TYPE; if (ip->extra[off] == IP_INDEX_NONE) ipResolveEnumAutoTagType(ip, enum_ty); return ip->extra[off]; } // The `field_index`th field name (a NullTerminatedString index) of an auto // enum type. Mirrors Type.enumFieldName. uint32_t ipEnumFieldName( InternPool* ip, uint32_t enum_ty, uint32_t field_index); // Explicit/nonexhaustive enum backing value for a field. Returns // IP_INDEX_NONE for an auto enum or an invalid field. uint32_t ipEnumFieldValue( InternPool* ip, uint32_t enum_ty, uint32_t field_index); void ipSetEnumFieldValue( InternPool* ip, uint32_t enum_ty, uint32_t i, uint32_t value); // The number of fields of an enum type (the `fields_len` header word of // Tag.TypeEnum — shared across auto/explicit/nonexhaustive). Mirrors // Type.enumFieldCount. uint32_t ipEnumFieldsLen(const InternPool* ip, uint32_t enum_ty); // The underlying integer value of an `enum_tag` item (the `int` word of // `Tag.EnumTag { ty, int }`, already an interned/typed int index at the // enum's int tag type). Mirrors `Value.intFromEnum`'s auto-enum case (the // tag's backing int is stored directly, no extra work needed). uint32_t ipEnumTagValue(const InternPool* ip, uint32_t enum_tag_idx); // The enum type of an `enum_tag` item (the `ty` word of `Tag.EnumTag { ty, // int }`). Mirrors InternPool.typeOf's `.enum_tag => enum_tag.ty` case. uint32_t ipEnumTagType(const InternPool* ip, uint32_t enum_tag_idx); // Intern an `enum_literal` value (mirrors InternPool.get for `.enum_literal`). // The `data` word is the NullTerminatedString index of the literal name (no // extra payload). Returns the item index. uint32_t ipGetEnumLiteral(InternPool* ip, uint32_t str_idx); // Intern a declared auto-layout union type (no captures/field aligns). Mirrors // InternPool.getDeclaredUnionType. `tag_usage` is the exact two-bit // LoadedUnionType.TagUsage value (`none`, `safety`, or `tagged`). // `enum_tag_type` and the `field_type` array start `.none`; backpatch via // ipSetUnionEnumTagType/ipSetUnionFieldType. Even `tag_usage == none` retains // the hypothetical enum tag type once populated, as upstream does. uint32_t ipGetDeclaredUnionType(InternPool* ip, uint32_t zir_index, uint32_t fields_len, uint32_t tag_usage, bool is_extern_layout, bool any_field_aligns); // `getDeclaredUnionType`'s `.@"packed"` arm: a `Tag.TypeUnionPacked` whose // `backing_int_type` and `enum_tag_type` start `.none` until // `ipResolvePackedUnionLayout`, and whose field types start `.none`. uint32_t ipGetDeclaredUnionTypePacked(InternPool* ip, uint32_t zir_index, uint32_t fields_len, bool explicit_backing); // True iff `union_idx` is a packed union. Safe on any index (a tag test). ZIG1_INLINE bool ipUnionIsPacked(const InternPool* ip, uint32_t union_idx) { return ipTagIsUnionPacked(ip->item_tags[union_idx]); } // `InternPool.resolvePackedUnionLayout`: store the hypothetical tag enum and // the backing integer type. void ipResolvePackedUnionLayout(InternPool* ip, uint32_t union_idx, uint32_t enum_tag_ty, uint32_t backing_int_ty); // `Type.containerLayout(zcu) == .@"packed"` for a struct or union type: the // types whose values are `.bitpack`s of their backing integer. ZIG1_INLINE bool ipTypeIsBitpack(const InternPool* ip, uint32_t ty) { return ipTagIsStructPacked(ip->item_tags[ty]) || ipTagIsUnionPacked(ip->item_tags[ty]); } // The struct and union layout accessors, inline because the interpreter // asks them on every load and store (interpAbiSize, interpAbiAlign, // Type.classify). A packed payload has no `size`/`flags` group, so reaching // one stops loudly, as upstream asserts (`"assert(struct_type.layout != // .@\"packed\");"`). ZIG1_INLINE void ipLayoutRejectPacked( const InternPool* ip, uint32_t idx, const char* what) { if (ipTypeIsBitpack(ip, idx)) UNREACHABLE(what); } // A packed struct's size is its backing integer's, never stored. ZIG1_INLINE uint32_t ipStructSize(const InternPool* ip, uint32_t struct_idx) { ipLayoutRejectPacked(ip, struct_idx, "ipStructSize"); return ip->extra[ip->item_datas[struct_idx] + IP_TYPE_STRUCT_SIZE]; } ZIG1_INLINE uint32_t ipStructAlignmentLog2( const InternPool* ip, uint32_t struct_idx) { ipLayoutRejectPacked(ip, struct_idx, "ipStructAlignmentLog2"); return (ip->extra[ip->item_datas[struct_idx] + IP_TYPE_STRUCT_FLAGS] >> IP_STRUCT_FLAGS_ALIGNMENT_SHIFT) & IP_STRUCT_FLAGS_ALIGNMENT_MASK; } ZIG1_INLINE uint32_t ipStructClass(const InternPool* ip, uint32_t struct_idx) { ipLayoutRejectPacked(ip, struct_idx, "ipStructClass"); return (ip->extra[ip->item_datas[struct_idx] + IP_TYPE_STRUCT_FLAGS] >> IP_STRUCT_FLAGS_CLASS_SHIFT) & IP_STRUCT_FLAGS_CLASS_MASK; } // Before resolution the alignment reads back as .none (63); a resolved // struct, an empty one included, has a concrete alignment. Packed never // reaches here: its callers branch packed away first, as // Sema.resolveStructLayout dispatches to `resolvePackedStructLayout`. ZIG1_INLINE bool ipStructLayoutResolved( const InternPool* ip, uint32_t struct_idx) { ipLayoutRejectPacked(ip, struct_idx, "ipStructLayoutResolved"); return ipStructAlignmentLog2(ip, struct_idx) != IP_ALIGNMENT_NONE; } ZIG1_INLINE uint32_t ipStructBackingInt( const InternPool* ip, uint32_t struct_idx) { return ip->extra[ip->item_datas[struct_idx] + IP_TYPE_STRUCT_PACKED_BACKING_INT_TYPE]; } ZIG1_INLINE uint32_t ipUnionBackingInt( const InternPool* ip, uint32_t union_idx) { if (!ipUnionIsPacked(ip, union_idx)) UNREACHABLE("ipUnionBackingInt on a non-packed union"); return ip->extra[ip->item_datas[union_idx] + IP_TYPE_UNION_PACKED_BACKING_INT_TYPE]; } ZIG1_INLINE uint32_t ipUnionSize(const InternPool* ip, uint32_t union_idx) { ipLayoutRejectPacked(ip, union_idx, "ipUnionSize"); return ip->extra[ip->item_datas[union_idx] + IP_TYPE_UNION_SIZE]; } ZIG1_INLINE uint32_t ipUnionClass(const InternPool* ip, uint32_t union_idx) { ipLayoutRejectPacked(ip, union_idx, "ipUnionClass"); return (ip->extra[ip->item_datas[union_idx] + IP_TYPE_UNION_FLAGS] >> IP_UNION_FLAGS_CLASS_SHIFT) & IP_UNION_FLAGS_CLASS_MASK; } ZIG1_INLINE uint32_t ipUnionAlignmentLog2( const InternPool* ip, uint32_t union_idx) { ipLayoutRejectPacked(ip, union_idx, "ipUnionAlignmentLog2"); return (ip->extra[ip->item_datas[union_idx] + IP_TYPE_UNION_FLAGS] >> IP_UNION_FLAGS_ALIGNMENT_SHIFT) & IP_UNION_FLAGS_ALIGNMENT_MASK; } ZIG1_INLINE bool ipUnionLayoutResolved( const InternPool* ip, uint32_t union_idx) { if (ipUnionIsPacked(ip, union_idx)) return ipUnionBackingInt(ip, union_idx) != IP_INDEX_NONE; return ipUnionAlignmentLog2(ip, union_idx) != IP_ALIGNMENT_NONE; } // `Type.bitpackBackingInt`: `.struct_type => loadStructType(ty) // .packed_backing_int_type, .union_type => loadUnionType(ty) // .packed_backing_int_type`. static inline uint32_t ipBitpackBackingInt(const InternPool* ip, uint32_t ty) { return ipTagIsUnionPacked(ip->item_tags[ty]) ? ipUnionBackingInt(ip, ty) : ipStructBackingInt(ip, ty); } void ipSetUnionEnumTagType( InternPool* ip, uint32_t union_idx, uint32_t tag_ty); uint32_t ipUnionEnumTagType(const InternPool* ip, uint32_t union_idx); // Read LoadedUnionType.TagUsage directly from TypeUnion.Flags. Keeping // runtime-layout decisions on the serialized flag avoids treating a plain // union's hypothetical enum as proof that it has a tag. uint32_t ipUnionTagUsage(const InternPool* ip, uint32_t union_idx); // `containerLayout(zcu) == .@"extern"` for a union type. bool ipUnionIsExtern(const InternPool* ip, uint32_t union_idx); // Complete non-packed union layout metadata (TypeUnion's enum_tag_type, size, // padding and flags words). // Resolution is idempotent: a repeated identical result is a no-op; a // conflicting result aborts loudly. void ipResolveUnionLayout(InternPool* ip, uint32_t union_idx, uint32_t enum_tag_ty, uint32_t type_class, bool has_runtime_tag, uint32_t size, uint32_t padding, uint32_t alignment_log2); uint32_t ipUnionPadding(const InternPool* ip, uint32_t union_idx); bool ipUnionHasRuntimeTag(const InternPool* ip, uint32_t union_idx); // Backpatch / read the union's member-decl namespace (same // 0-vs-unset ownership disambiguation as ipStructNamespaceOrNone). void ipSetUnionNamespace(InternPool* ip, uint32_t union_idx, uint32_t ns); uint32_t ipUnionNamespaceOrNone(const InternPool* ip, uint32_t union_idx); // Type.getNamespace over struct, union, enum and opaque types; // IP_NAMESPACE_NONE otherwise. static inline uint32_t ipContainerNamespaceOrNone( const InternPool* ip, uint32_t container_ty) { if (container_ty >= ip->items_len) return IP_NAMESPACE_NONE; if (ipTagIsAnyStruct(ip->item_tags[container_ty])) return ipStructNamespaceOrNone(ip, container_ty); if (ipTagIsAnyUnion(ip->item_tags[container_ty])) return ipUnionNamespaceOrNone(ip, container_ty); if (ipIsEnumType(ip, container_ty)) return ipEnumNamespaceOrNone(ip, container_ty); if (ip->item_tags[container_ty] == IP_TAG_TYPE_OPAQUE) return ipOpaqueNamespaceOrNone(ip, container_ty); return IP_NAMESPACE_NONE; } // `anyerror`, an error set or an inferred error set. static inline bool ipIsErrorSetType(const InternPool* ip, uint32_t ty) { if (ty == ZIR_REF_ANYERROR_TYPE) return true; if (ty >= ip->items_len) return false; return ip->item_tags[ty] == IP_TAG_TYPE_ERROR_SET || ip->item_tags[ty] == IP_TAG_TYPE_INFERRED_ERROR_SET; } uint32_t ipUnionFieldsLen(const InternPool* ip, uint32_t union_idx); void ipSetUnionFieldType( InternPool* ip, uint32_t union_idx, uint32_t i, uint32_t ty); uint32_t ipUnionFieldType( const InternPool* ip, uint32_t union_idx, uint32_t i); // `LoadedUnionType.field_aligns[i]` (log2 units, `.none` = 0x3F), or `.none` // for a union declared with no `align`; the setter needs that storage. void ipSetUnionFieldAlignment( InternPool* ip, uint32_t union_idx, uint32_t i, uint32_t alignment); uint32_t ipUnionFieldAlignment( const InternPool* ip, uint32_t union_idx, uint32_t i); // Whether the union carries that per-field storage (`any_field_aligns`). bool ipUnionHasFieldAligns(const InternPool* ip, uint32_t union_idx); // Record a union's generated-tag metadata without creating an IP item (its // ZIR field names are deferred with ipDeferFieldNames). The first layout or // field-name demand calls ipEnsureGeneratedUnionTag, which appends the enum // (getGeneratedEnumTagType), names it, interns the field names and only then // infers/backpatches its uN integer tag type, as resolveUnionLayout. Both are // idempotent. void ipRegisterGeneratedUnionTag(InternPool* ip, uint32_t union_ty, uint32_t fields_len, uint32_t explicit_int_tag_ty, uint32_t explicit_int_tag_bits, bool explicit_int_tag_signed); uint32_t ipEnsureGeneratedUnionTag(InternPool* ip, uint32_t union_ty); // The declared `packed union(T)` backing type recorded with the union's // pending tag metadata (`.none` for an inferred backing). void ipSetPendingPackedUnionBacking( InternPool* ip, uint32_t union_ty, uint32_t backing_ty); uint32_t ipPendingPackedUnionBacking(const InternPool* ip, uint32_t union_ty); // The explicit field values of a pending `union(enum(T))` tag; takes // ownership of the `fields_len`-long malloc'd `values`. Until then, a union // marked with ipDeferPendingUnionTagValues has no tag enum to make. void ipSetPendingUnionTagValues( InternPool* ip, uint32_t union_ty, uint32_t* values); void ipDeferPendingUnionTagValues(InternPool* ip, uint32_t union_ty); // Resolve a union field name (IP string index) to its declaration index (via // the generated tag enum's field_name array). UINT32_MAX if not found. uint32_t ipUnionFieldIndex( InternPool* ip, uint32_t union_ty, uint32_t field_name_str); // Declaration-order union field name retained in the generated-tag metadata // side table (also populated for an explicit tag enum). uint32_t ipUnionFieldName( InternPool* ip, uint32_t union_ty, uint32_t field_index); // Intern a `union_value` (Key.Union = {ty, tag, val}). Mirrors // InternPool.getUnion; dedups by scanning existing union_value items. uint32_t ipAggregateTypeLenIncludingSentinel( const InternPool* ip, uint32_t ty); bool ipIsStructOrTupleType(const InternPool* ip, uint32_t ty); uint32_t ipGetUnionValue( InternPool* ip, uint32_t ty, uint32_t tag, uint32_t val); // Intern an array type (mirrors InternPool.get for `.array_type`). `sentinel` // is an IP value index or `IP_INDEX_NONE`. Emits `type_array_small` (Vector // {len, child}) when there is no sentinel, else `type_array_big` (Array // {len0, len1, child, sentinel}). Dedups. Returns the item index. uint32_t ipGetArrayType( InternPool* ip, uint32_t len, uint32_t child, uint32_t sentinel); // Intern a vector type `@Vector(len, child)`: `type_vector` stores {len, // child} (2 words). Mirrors InternPool.get for `.vector_type`. Dedups. uint32_t ipGetVectorType(InternPool* ip, uint32_t len, uint32_t child); // Intern an optional type `?child`: `type_optional` stores the child type // Index directly in item.data. Mirrors InternPool.get for `.opt_type`. Dedups. uint32_t ipGetOptionalType(InternPool* ip, uint32_t child); // Intern the optional-none value `(?T) null` of optional type `opt_ty` // (item.data = opt_ty). Dedups on the optional type. uint32_t ipGetOptNull(InternPool* ip, uint32_t opt_ty); // Intern the optional-payload value `(?T) payload` (extra { opt_ty, payload // }). Dedups on (opt_ty, payload). uint32_t ipGetOptPayload(InternPool* ip, uint32_t opt_ty, uint32_t payload); // Intern a slice type `[]T` (or `[]const T`): `type_slice` stores the // corresponding many-item pointer type Index directly in item.data (a slice is // represented as its `[*]` pointer variant plus a length). Mirrors // InternPool.get for a `.ptr_type` with `.size == .slice`, which interns the // `.many` pointer first then the slice. `many_ptr` must already be the // interned // `[*]...T` pointer type. Dedups. uint32_t ipGetSliceType(InternPool* ip, uint32_t many_ptr); // Intern an error-union type `anyerror!payload`: `type_anyerror_union` stores // the payload type Index directly in item.data. Mirrors InternPool.get for // `.error_union_type` with `error_set == anyerror`. Dedups. uint32_t ipGetAnyerrorUnionType(InternPool* ip, uint32_t payload); // Intern a named error set type `error{...}`: `type_error_set` stores a // `Tag.ErrorSet` header { names_len, names_map } followed by `names_len` // trailing NullTerminatedString (IP string index) words. `names` must be // sorted by string index (mirrors InternPool.getErrorSetType). Dedups by // comparing names_len + the trailing name list. uint32_t ipErrorSetTypeIfExists( const InternPool* ip, uint32_t* names, uint32_t names_len); uint32_t ipGetErrorSetType( InternPool* ip, const uint32_t* names, uint32_t names_len); // Intern an error-union type `E!payload` for a non-anyerror error set: // `type_error_union` stores `Key.ErrorUnionType` = { error_set_type, // payload_type } (2 words). Mirrors InternPool.get for `.error_union_type`. // Dedups. uint32_t ipGetErrorUnionType( InternPool* ip, uint32_t error_set, uint32_t payload); // Intern an error value `error.Name` of an error set type: `error_set_error` // stores `Key.Error` = { ty, name } (2 words) where `ty` is the error set type // and `name` is an IP string index. Mirrors InternPool.get for `.err`. Dedups. uint32_t ipGetErrorSetError(InternPool* ip, uint32_t ty, uint32_t name); // InternPool.getErrorValue / getErrorValueIfExists: the error's integer // value in the global error set (Zcu.ErrorInt), 1-based; IfExists answers 0 // for a name never registered. uint32_t ipGetErrorValue(InternPool* ip, uint32_t name); uint32_t ipGetErrorValueIfExists(const InternPool* ip, uint32_t name); // Intern an error-union value holding an error `error.Name` of an error union // type: `error_union_error` stores `Key.Error` = { ty, name } (2 words) where // `ty` is the error union type. Mirrors InternPool.get for `.error_union` with // `.err_name`. Dedups. uint32_t ipGetErrorUnionError(InternPool* ip, uint32_t ty, uint32_t name); // Intern an error-union value holding a payload `(E!T) payload` (extra { // eu_ty, payload }). Mirrors InternPool.get for `.error_union` with // `.payload`. Dedups on (eu_ty, payload). uint32_t ipGetErrorUnionPayload( InternPool* ip, uint32_t eu_ty, uint32_t payload); // Return the payload type of an error-union type (either `type_anyerror_union` // or a named `type_error_union`). uint32_t ipErrorUnionPayload(const InternPool* ip, uint32_t eu_ty); // Return the error-set type of an error-union type: `anyerror` (index 36) for // `type_anyerror_union`, or the stored error set for a named // `type_error_union`. uint32_t ipErrorUnionErrorSet(const InternPool* ip, uint32_t eu_ty); // Intern an aggregate value from `.bytes` storage (mirrors InternPool.get's // `.aggregate` key, `.bytes` storage branch, e.g. Sema.addStrLit): `ty` is // the (already-interned) `[len:0]u8`/`[len]u8` array type, `bytes_str` an // already-interned String of exactly `len` bytes (the sentinel, if any, is // not included). If every byte equals the first (trivially true for // `len <= 1`), interns a single `int_u8` + a `repeated{ty, elem_val}` value // instead of the full byte string (upstream's small-aggregate optimization, // checked before the plain-bytes path). Otherwise interns a plain `bytes` // value via `ipGetBytes`. uint32_t ipInternBytesAggregate( InternPool* ip, uint32_t ty, uint32_t bytes_str, uint32_t len); // Intern a general `aggregate` value: `Aggregate` = { ty } (1 word) followed // by `len` trailing element IP value indices and, when `ty` carries a // sentinel, one more word holding it. `ty` is the aggregate (array/struct) // type; `elems` are the (already-interned) element values, with or without // the sentinel as a trailing element — both shapes are legal, exactly as // upstream. May return a `repeated`, a `bytes` (any u8-element sequence, // sentineled or not) or an `only_possible_value` instead. Dedups. uint32_t ipGetAggregate( InternPool* ip, uint32_t ty, const uint32_t* elems, uint32_t len); // Intern aggregate storage with one repeated element. This is the storage // form selected by InternPool.get(.aggregate) for any all-equal aggregate; // Zcu.PerThread.aggregateSplatValue reaches the same interner for arrays and // vectors. A zero-length `ty` answers its only_possible_value, as upstream. uint32_t ipGetRepeated(InternPool* ip, uint32_t ty, uint32_t elem); // Intern an anonymous-uav pointer value where the pointer alignment matches // the uav's original type: `ptr_uav` = `PtrUav` = { ty, val, boff_a, boff_b } // (byte_offset == 0). Dedups. uint32_t ipGetPtrUav(InternPool* ip, uint32_t ty, uint32_t val); // Intern an anonymous-uav pointer value that keeps a distinct original type // (differing alignment): `ptr_uav_aligned` = `PtrUavAligned` = // { ty, val, orig_ty, boff_a, boff_b } (byte_offset == 0). Dedups. uint32_t ipGetPtrUavAligned( InternPool* ip, uint32_t ty, uint32_t val, uint32_t orig_ty); uint32_t ipGetPtrUavAlignedOffset(InternPool* ip, uint32_t ty, uint32_t val, uint32_t orig_ty, uint64_t byte_offset); // Re-intern a UAV base at `dest_ty`, choosing ptr_uav versus // ptr_uav_aligned with upstream's ptrsHaveSameAlignment rule. This is the // single owner of that tag-selection rule for all pointer coercion paths. uint32_t ipGetPtrUavRetypedOffset(InternPool* ip, uint32_t dest_ty, uint32_t backing, uint32_t orig_ty, uint64_t byte_offset); // Decode PtrUav/PtrUavAligned.byteOffset. Pinned PackedU64 storage puts the // high word (`byte_offset_a`) before the low word (`byte_offset_b`). uint64_t ipPtrUavByteOffset(const InternPool* ip, uint32_t ptr_uav); uint32_t ipPtrUavValue(const InternPool* ip, uint32_t ptr_uav); // A uav pointer's `orig_ty`: stored for ptr_uav_aligned, the pointer's own // type for a plain ptr_uav. uint32_t ipPtrUavOrigTy(const InternPool* ip, uint32_t ptr_uav); // Intern/decode the two remaining upstream `.ptr` base-address variants. // Both use exact payloads and structural dedup. uint32_t ipGetPtrComptimeField( InternPool* ip, uint32_t ty, uint32_t field_val); bool ipIsPtrComptimeField(const InternPool* ip, uint32_t v); uint32_t ipPtrComptimeFieldValue(const InternPool* ip, uint32_t v); uint32_t ipGetPtrInt(InternPool* ip, uint32_t ty, uint64_t address); bool ipIsPtrInt(const InternPool* ip, uint32_t v); // (part of) InternPool.zig:indexToKey // A `.ptr` key whose `base_addr` is `.uav`: `ptr_uav` and `ptr_uav_aligned` // alike, the latter only adding `orig_ty`. ZIG1_INLINE bool ipIsPtrUav(const InternPool* ip, uint32_t v) { return ipItemTagIs(ip, v, IP_TAG_PTR_UAV) || ipItemTagIs(ip, v, IP_TAG_PTR_UAV_ALIGNED); } uint64_t ipPtrIntAddress(const InternPool* ip, uint32_t v); // Intern a slice value: `ptr_slice` = `PtrSlice` = { ty, ptr, len } (3 words). // `ptr` is a many-pointer value; `len` is a usize value. Dedups. uint32_t ipGetPtrSlice( InternPool* ip, uint32_t ty, uint32_t ptr, uint32_t len); // Decode accessors, the read-side counterpart // to `ipGetPtrSlice` above -- mirrors `ipIsPtrElem`'s own family (same // bounds / tag-check shape; `PtrSlice` carries no byte_offset word, so there // is no `ipPtrSliceByteOffset` counterpart). Cell-pointer slices are produced // by `ctCellSliceMake` and consumed by `ctCellPtrResolveDescriptorAt`'s // ptr_slice arm. bool ipIsPtrSlice(const InternPool* ip, uint32_t v); uint32_t ipPtrSlicePtr(const InternPool* ip, uint32_t v); uint32_t ipPtrSliceLen(const InternPool* ip, uint32_t v); // Mint helpers for upstream's pointer-chain tags (ptr_eu_payload, // ptr_opt_payload, ptr_elem, ptr_field). // // Intern a pointer to the payload of an error union (`ptr_eu_payload`). // Extra payload PtrBase = { ty, base, byte_offset_a, byte_offset_b } // (4 words); `base` is the interned error-union pointer this one derives // from. Dedups. uint32_t ipGetPtrEuPayload(InternPool* ip, uint32_t ty, uint32_t base); // Intern a pointer to the payload of an optional (`ptr_opt_payload`). Extra // payload PtrBase = { ty, base, byte_offset_a, byte_offset_b } (4 words). // Dedups. uint32_t ipGetPtrOptPayload(InternPool* ip, uint32_t ty, uint32_t base); // Decode accessors for an already-minted `ptr_eu_payload`/ // `ptr_opt_payload`, the read-side counterpart to the mint helpers above -- // mirrors ipIsPtrField/ipIsPtrElem's own families, minus the index // accessor neither payload tag carries (PtrBase has no index word). bool ipIsPtrEuPayload(const InternPool* ip, uint32_t v); uint32_t ipPtrEuPayloadBase(const InternPool* ip, uint32_t v); uint64_t ipPtrEuPayloadByteOffset(const InternPool* ip, uint32_t v); bool ipIsPtrOptPayload(const InternPool* ip, uint32_t v); uint32_t ipPtrOptPayloadBase(const InternPool* ip, uint32_t v); uint64_t ipPtrOptPayloadByteOffset(const InternPool* ip, uint32_t v); // Intern a pointer to an array element (`ptr_elem`). Extra payload // PtrBaseIndex = { ty, base, index, byte_offset_a, byte_offset_b } (5 words): // `index` is minted as an interned usize value first (via ipGetIntUsize), // mirroring upstream's `ip.get(.{ .int = .{ .ty = .usize_type, .storage = // .{ .u64 = ... } } })` funnel that runs before constructing the pointer // item. Dedups. uint32_t ipGetPtrElem( InternPool* ip, uint32_t ty, uint32_t base, uint32_t index); uint32_t ipGetPtrElemOffset(InternPool* ip, uint32_t ty, uint32_t base, uint32_t index, uint64_t byte_offset); // Decode accessors for an already-minted `ptr_elem`, the read-side // counterpart to ipGetPtrElem/ipGetPtrElemOffset above -- mirrors // ipIsPtrField's own family exactly (same PtrBaseIndex layout, same // defensive bounds checking). bool ipIsPtrElem(const InternPool* ip, uint32_t v); uint32_t ipPtrElemBase(const InternPool* ip, uint32_t v); uint32_t ipPtrElemIndex(const InternPool* ip, uint32_t v); uint64_t ipPtrElemByteOffset(const InternPool* ip, uint32_t v); // Intern a pointer to a container field (`ptr_field`). Extra payload // PtrBaseIndex = { ty, base, index, byte_offset_a, byte_offset_b } (5 words), // same index-minting convention as ipGetPtrElem. Dedups. uint32_t ipGetPtrField( InternPool* ip, uint32_t ty, uint32_t base, uint32_t index); // Decode accessors for an already-minted `ptr_field`, the read-side // counterpart to ipGetPtrField/ipGetPtrFieldOffset above -- mirrors // ipIsPtrComptimeAlloc/ipPtrComptimeAllocIndex/ipPtrComptimeAllocByteOffset's // own shape and defensive bounds checking exactly. `ipPtrFieldIndex` unwraps // the interned-usize `index` word back to a raw field position (the mint // side's own `ipGetIntUsize` funnel guarantees an `IP_TAG_INT_USIZE` item, an // invariant re-checked here rather than assumed). bool ipIsPtrField(const InternPool* ip, uint32_t v); // The base address a derived pointer roots at: `.field`, `.arr_elem`, // `.opt_payload` and `.eu_payload` bases peeled (all four keep `base` in // their second payload word). uint32_t ipPtrDerivedRoot(const InternPool* ip, uint32_t v); uint32_t ipPtrFieldBase(const InternPool* ip, uint32_t v); uint32_t ipPtrFieldIndex(const InternPool* ip, uint32_t v); uint64_t ipPtrFieldByteOffset(const InternPool* ip, uint32_t v); // Re-intern an arbitrary non-slice pointer value at `dest_ty`, preserving its // base address and byte offset exactly. This is the zig1 counterpart of // InternPool.getCoerced's single `.ptr` arm; zig1's per-base pointer tags // make the operation a carrier dispatch. Returns IP_INDEX_NONE only when // `value` is not one of the modeled pointer carriers. Callers remain // responsible for establishing that the type conversion itself is legal. bool ipPtrValueByteOffset( const InternPool* ip, uint32_t value, uint64_t* out_byte_offset); // Key.Ptr.BaseAddr.eql of two pointer values, without interning. bool ipPtrBaseAddrEql(const InternPool* ip, uint32_t a, uint32_t b); uint32_t ipRemintPtrValue( InternPool* ip, uint32_t value, uint32_t dest_ty, uint64_t byte_offset); uint32_t ipRetypePtrValue(InternPool* ip, uint32_t value, uint32_t dest_ty); uint32_t ipUnwrapCoercedFunc(const InternPool* ip, uint32_t func); // Intern the `[:0]const u8` value of an already-interned string. Mirrors the // string-value construction shared by Sema.zirBuiltinSrc and the reflection // field-name paths: `[len:0]u8` bytes, a `[*:0]const u8` uav pointer, and the // slice length. Dedups at every layer. uint32_t ipGetNullTerminatedStringSlice(InternPool* ip, uint32_t string_index); // Same value construction from raw bytes, preserving upstream's ordering: // intern the array type before inserting the string into the string table. uint32_t ipGetNullTerminatedBytesSlice( InternPool* ip, const char* bytes, uint32_t len); // Intern a `@as(usize, value)` int value whose magnitude fits u32 // (`int_usize`, value stored directly in item `data`). Dedups by value. uint32_t ipGetIntUsize(InternPool* ip, uint32_t value); // Intern a `@as(u8, value)` int value (`int_u8`, value stored directly in item // `data`). Dedups by value. uint32_t ipGetIntU8(InternPool* ip, uint32_t value); // Intern a comptime_int value whose magnitude fits u32 // (`int_comptime_int_u32`, value stored directly in item `data`). Dedups by // value (0/1 reuse statics). uint32_t ipGetIntComptimeIntU32(InternPool* ip, uint32_t value); // Intern a `@as(u16, value)` int value (`int_u16`, value stored directly in // item `data`). Dedups by value. uint32_t ipGetIntU16(InternPool* ip, uint32_t value); // Intern a comptime `f16` value. `bits` are the raw IEEE-754 half bits; // storage mirrors upstream `.float_f16` (zero-extended item data). uint32_t ipGetFloatF16(InternPool* ip, uint32_t bits); // Read raw IEEE-754 half bits from an interned comptime f16 value. bool ipReadFloatF16Bits(const InternPool* ip, uint32_t idx, uint32_t* out); // Intern/read a comptime `f32` value. `bits` are the raw IEEE-754 single bits. uint32_t ipGetFloatF32(InternPool* ip, uint32_t bits); bool ipReadFloatF32Bits(const InternPool* ip, uint32_t idx, uint32_t* out); // Intern/read a comptime `f64` value. `bits` are the raw IEEE-754 double bits. uint32_t ipGetFloatF64(InternPool* ip, uint64_t bits); bool ipReadFloatF64Bits(const InternPool* ip, uint32_t idx, uint64_t* out); // Intern/read a comptime `f80` value. `words` are the raw IEEE-754 extended // bits split low word first; words[2] holds only the high u16. uint32_t ipGetFloatF80(InternPool* ip, const uint32_t words[3]); bool ipReadFloatF80Bits(const InternPool* ip, uint32_t idx, uint32_t words[3]); void ipFloatF80WordsFromLow80Bits(uint64_t lo, uint64_t hi, uint32_t words[3]); void ipFloatF80WordsToLow80Bits( const uint32_t words[3], uint64_t* lo, uint64_t* hi); bool ipReadFloatF128Bits( const InternPool* ip, uint32_t idx, uint32_t words[4]); uint32_t ipGetFloatComptimeFloatFromDouble(InternPool* ip, double value); uint32_t ipGetFloatComptimeFloatFromWords( InternPool* ip, const uint32_t words[4]); bool ipReadFloatComptimeFloatAsF64Bits( const InternPool* ip, uint32_t idx, uint64_t* out); bool ipReadFloatComptimeFloatAsF128Bits( const InternPool* ip, uint32_t idx, uint32_t words[4]); // `Value.toFloat(f128, zcu)`'s `.float` arm, `"inline else => |x| // @floatCast(x)"`: any interned float value widened exactly to f128's // low-word-first words. False for anything that is not a float value. bool ipReadFloatAsF128Bits( const InternPool* ip, uint32_t idx, uint32_t words[4]); // Those low-word-first words as compiler_rt's pair, and back. CrtU128 ipFloat128FromWords(const uint32_t w[4]); void ipFloat128ToWords(CrtU128 v, uint32_t w[4]); // `pt.floatValue(ty, x)` for an f128 `x`: `"switch (ty.floatBits(target)) // { 16 => .{ .f16 = @floatCast(x) }, ... }"`, rounding to nearest-even. // The fixed float types and `comptime_float`; IP_INDEX_NONE for any other // type (`c_longdouble` has no storage tag here). uint32_t ipGetFloatFromF128Bits( InternPool* ip, uint32_t ty, const uint32_t words[4]); // Intern a `@as(u32, value)` int value (`int_u32`, value stored directly in // item `data`). Dedups by value. uint32_t ipGetIntU32(InternPool* ip, uint32_t value); // Intern an `undefined` value of type `ty` (`undef`). The type index is stored // directly in item_datas. Dedups against an existing undef of the same type. uint32_t ipGetUndef(InternPool* ip, uint32_t ty); // Intern a comptime-mutable-memory pointer value (`ptr_comptime_alloc`). Extra // payload PtrComptimeAlloc = { ty, index, byte_offset_a, byte_offset_b } (4 // words). Dedups by {ty, index, byte_offset}; `alloc_index` is the // analyzing Sema's ComptimeAllocIndex. uint32_t ipGetPtrComptimeAlloc( InternPool* ip, uint32_t ptr_ty, uint32_t alloc_index); uint32_t ipGetPtrComptimeAllocOffset(InternPool* ip, uint32_t ptr_ty, uint32_t alloc_index, uint64_t byte_offset); // Intern an anonymous tuple type (`type_tuple`). Extra payload TypeTuple = // { fields_len, types[fields_len], values[fields_len] }. `values` entries are // the interned per-field comptime default values (or IP_INDEX_NONE for a // runtime field). Dedups by (types, values). uint32_t ipGetTupleType(InternPool* ip, uint32_t fields_len, const uint32_t* types, const uint32_t* values); // Intern a wide comptime_int (magnitude > u32; extra {lo, hi}). Dedups. uint32_t ipGetIntComptimeIntU64(InternPool* ip, uint64_t value); // `InternPool.indexToKeyBigInt`: the type, the magnitude limbs and the sign // behind an `int_positive`/`int_negative` item. False for every other tag. // The returned limb pointer is borrowed from the pool and is invalidated by // the next `ipAddInt`. bool ipIntBigLimbs(const InternPool* ip, uint32_t idx, uint32_t* out_ty, const uint64_t** out_limbs, uint32_t* out_len, bool* out_positive); // `InternPool.get()`'s `.int` arm, whole: intern a `big.int.Const` -- the // magnitude and its sign, upstream's own operand -- at any integer type, // running the per-type rungs, then `int_small`, then `ipAddInt`'s limbs. // The minter: no caller may restate a rung, split the ladder by sign, or // pick a carrier itself. `ty` must be an integer type, and the `u8`/`u16`/ // `u32`/`i32` rungs abort loudly on a value their width cannot hold, which // is upstream's `catch unreachable`. uint32_t ipGetInt(InternPool* ip, uint32_t ty, BigIntConst big_int); // Read the low 64 bits of an interned integer's stored bit pattern. This is // for operations such as @truncate/@bitCast where high bits are intentionally // discarded; arbitrary-width comptime_int values return their low limb. bool ipTryReadIntLow64Bits(const InternPool* ip, uint32_t idx, uint64_t* out); // Read the low 128 raw bits of an interned integer value. Values wider than // 128 bits decline; narrower values are zero-extended in `hi`. bool ipTryReadIntLow128Bits( const InternPool* ip, uint32_t idx, uint64_t* lo, uint64_t* hi); // Intern a negative comptime_int below i32 range (extra {lo, hi} = // two's-complement i64, little-endian). `value` must be < INT32_MIN (narrower // negatives use the i32 carrier). Dedups. uint32_t ipGetIntComptimeIntI64(InternPool* ip, int64_t value); // Intern the single possible value of a fully-comptime type // (`only_possible_value`); item `data` = the type Index. Dedups by type. uint32_t ipGetOnlyPossibleValue(InternPool* ip, uint32_t ty); // True for the ten `.int` storage tags (`int_u8` .. `int_negative`). bool ipTagIsIntValue(IpTag tag); // True when item `idx` is a type (any of the type item tags, incl. the // preinterned simple types). Used by the runtime CLOSURE_GET handler to gate // to type captures. bool ipIndexIsType(const InternPool* ip, uint32_t idx); // Exact InternPool.typeOf for the value tags modeled by zig1. Returns // IP_INDEX_NONE for an invalid index or a non-value bookkeeping item. uint32_t ipTypeOf(const InternPool* ip, uint32_t idx); IpPtrNavStatus ipDecodePtrNav( const InternPool* ip, uint32_t value, IpPtrNav* out); IpPtrNavStatus ipDecodePtrNavAny( const InternPool* ip, uint32_t value, IpPtrNav* out); uint32_t ipTypeOfPreinterned(uint32_t val); bool ipTryReadIntValue(const InternPool* ip, uint32_t idx, uint64_t* out); // The raw magnitude of an interned integer value. coerceExtra uses it to // widen a fixed-width comptime int (e.g. an `@intFromEnum` tag) to a // different fixed-width destination type. uint64_t ipReadIntValue(const InternPool* ip, uint32_t idx); // The type Index of an interned integer value. uint32_t ipIntValueType(const InternPool* ip, uint32_t idx); // `pt.intValue_big`'s intern side: the two's-complement word `n` at // `dest_ty`, stored in whichever int representation that type uses. Every // frame that finishes an `arith.` ends here, so a signed result is // interned the same way wherever it was folded. Returns IP_INDEX_NONE for a // non-integer destination. uint32_t ipInternIntResult(InternPool* ip, uint64_t n, uint32_t dest_ty); uint32_t ipInternIntResultU128( InternPool* ip, uint64_t lo, uint64_t hi, uint32_t dest_ty); // Return the type of a comptime-known InternPool value index. This is the // native C counterpart of InternPool.typeOf for the value shapes zig1 // materializes (including dynamic ptr_nav values); it only reads the item and // never interns. Runtime AIR refs are not accepted. uint32_t ipComptimeValueType(const InternPool* ip, uint32_t value); // The sign rule for an interned integer value read into a 64-bit // two's-complement word: bit 63 alone cannot answer it, because an unsigned // carrier legitimately holds magnitudes at or above 2^63 (`maxInt(u64)` and // `-1` read identically). Every producer of a `CtNumOperand`'s `.neg` owes // this one function -- `semaCmpReadKnownInt` in this file and `ctReadInt`'s // callers in sema.c alike. bool ipIntValueIsNegative(const InternPool* ip, uint32_t idx, uint64_t n); // `InternPool.getOrPutStringFmt`: the formatted string, interned at its full // length (no fixed buffer). ZIG1_ATTRIBUTE((format(printf, 2, 3))) uint32_t ipGetOrPutStringFmt(InternPool* ip, const char* fmt, ...); // ── Type and Value: src/Type.zig, src/Value.zig ───────────────────────────── // The type and value queries Sema and the interpreter share (type.c). // Type.arrayInfo of an array type, the arrays-only view of ipArrayInfo: its // length, element type and sentinel (IP_INDEX_NONE without one). False for // any other type, a vector included, as upstream's `.array_type => ... else // => ...` arm selection reads it. A length past u32 is a zig1 limit. static inline bool ztArrayTypeInfo(const InternPool* ip, uint32_t array_ty, uint32_t* out_len, uint32_t* out_child, uint32_t* out_sentinel) { IpArrayInfo info; IpArrayStatus st = ipArrayInfoInline(ip, array_ty, &info); if (st == IP_ARR_LEN_TOO_BIG) UNIMPLEMENTED("Type.arrayLen: array length past u32"); if (st != IP_ARR_OK || info.is_vector) return false; *out_len = info.len; *out_child = info.child; *out_sentinel = info.sentinel; return true; } // Recognize the simple preinterned float type refs zig1 models directly and // return their bit width. Shared by runtime float casts and comptime // `@typeInfo()` materialization so the hard-coded ref mapping stays in // one place. ZIG1_INLINE bool ztSimpleFloatBits(uint32_t ty, uint32_t* out_bits) { switch (ty) { case ZIR_REF_C_LONGDOUBLE_TYPE: *out_bits = 128; return true; case ZIR_REF_F16_TYPE: *out_bits = 16; return true; case ZIR_REF_F32_TYPE: *out_bits = 32; return true; case ZIR_REF_F64_TYPE: *out_bits = 64; return true; case ZIR_REF_F80_TYPE: *out_bits = 80; return true; case ZIR_REF_F128_TYPE: *out_bits = 128; return true; default: return false; } } // Bounded Type.classify port. Type.comptimeOnly is true for the FULL/PARTIAL // classes only; retaining the other classes matters because an NPV field // makes a struct/tuple NPV, while an NPV union field is merely unavailable. // Unknown is never converted to runtime: malformed unresolved metadata and a // by-value recursive aggregate must fail loudly instead of changing call or // Nav-storage semantics. #define ZT_CLASSIFY_MAX_DEPTH 128u // Type.comptimeOnly for the type shapes modeled by zig1. Shared with Nav // modifier validation: storage annotations are illegal on a function alias // or any other comptime-only declaration value. // Result of the bounded, cycle-safe Type.classify port used by // ztTypeIsComptimeOnly. Unknown means malformed/unresolved aggregate metadata // or an illegal by-value recursive cycle; callers must not silently treat it // as runtime-representable. typedef enum { ZT_CLASS_NPV = TYPE_CLASS_NO_POSSIBLE_VALUE, ZT_CLASS_OPV = TYPE_CLASS_ONE_POSSIBLE_VALUE, ZT_CLASS_RUNTIME = TYPE_CLASS_RUNTIME, ZT_CLASS_PARTIAL = TYPE_CLASS_PARTIALLY_COMPTIME, ZT_CLASS_FULL = TYPE_CLASS_FULLY_COMPTIME, ZT_CLASS_UNKNOWN, } ZtTypeClass; typedef enum { ZT_COMPTIME_ONLY_NO = 0, ZT_COMPTIME_ONLY_YES, ZT_COMPTIME_ONLY_UNKNOWN, } ZtComptimeOnlyStatus; // Type.classify's arms that need no walk: a simple type, an integer, a // function, a pointer-like or error-set type, and a struct or union whose // resolved layout stored its class. False for every other type (none of these // tags is a packed container's). ZIG1_INLINE bool ztClassifyLeaf( const InternPool* ip, uint32_t ty, ZtTypeClass* out) { switch ((IpTag)ip->item_tags[ty]) { case IP_TAG_SIMPLE_TYPE: if (ty == ZIR_REF_TYPE_TYPE || ty == ZIR_REF_COMPTIME_INT_TYPE || ty == ZIR_REF_COMPTIME_FLOAT_TYPE || ty == ZIR_REF_ENUM_LITERAL_TYPE || ty == ZIR_REF_NULL_TYPE || ty == ZIR_REF_UNDEFINED_TYPE) *out = ZT_CLASS_FULL; else if (ty == ZIR_REF_ANYOPAQUE_TYPE || ty == ZIR_REF_NORETURN_TYPE) *out = ZT_CLASS_NPV; else if (ty == ZIR_REF_VOID_TYPE) *out = ZT_CLASS_OPV; else *out = ZT_CLASS_RUNTIME; return true; case IP_TAG_TYPE_INT_SIGNED: case IP_TAG_TYPE_INT_UNSIGNED: *out = ip->item_datas[ty] == 0 ? ZT_CLASS_OPV : ZT_CLASS_RUNTIME; return true; case IP_TAG_TYPE_FUNCTION: *out = ZT_CLASS_FULL; return true; case IP_TAG_TYPE_POINTER: case IP_TAG_TYPE_SLICE: case IP_TAG_TYPE_ANYFRAME: case IP_TAG_TYPE_ERROR_SET: case IP_TAG_TYPE_INFERRED_ERROR_SET: *out = ZT_CLASS_RUNTIME; return true; case IP_TAG_TYPE_STRUCT: // Type.classify: `"break struct_obj.class;"`, the class // resolveStructLayout stored. if (!ipStructLayoutResolved(ip, ty)) return false; *out = ipStructClass(ip, ty); return true; case IP_TAG_TYPE_UNION: // Type.classify: `"break union_obj.class;"` once the layout is // resolved. if (!ipUnionLayoutResolved(ip, ty)) return false; *out = ipUnionClass(ip, ty); return true; default: return false; } } ZtTypeClass ztTypeClassWalk(InternPool* ip, uint32_t ty); // Inline: the interpreter asks it for every argument it binds. A leaf at the // top needs neither the walk's depth bound nor its cycle check. ZIG1_INLINE ZtTypeClass ztTypeClass(InternPool* ip, uint32_t ty) { ZtTypeClass class; if (ty < ip->items_len && ztClassifyLeaf(ip, ty, &class)) return class; return ztTypeClassWalk(ip, ty); } // Type.optionalReprIsPayload for zig1's split pointer/slice IP encoding. // `opt_ty` must be a type_optional. True means the payload's own zero value // is the null niche and no trailing discriminator byte exists. bool ztOptionalReprIsPayload(const InternPool* ip, uint32_t opt_ty); // `std.math.compare(a, op, b)` for two unsigned words. ZIG1_INLINE bool valCmpApplyU(ZirInstTag op, uint64_t a, uint64_t b) { switch (op) { case ZIR_INST_CMP_LT: return a < b; case ZIR_INST_CMP_LTE: return a <= b; case ZIR_INST_CMP_EQ: return a == b; case ZIR_INST_CMP_GTE: return a >= b; case ZIR_INST_CMP_GT: return a > b; case ZIR_INST_CMP_NEQ: return a != b; default: return false; } } // `std.math.Order.compare(op)`: the six operators applied once to a // three-way order, so a comparator that has already produced -1/0/1 does not // restate them. `valCmpApplyU` of `(ord + 1)` against `1` is that comparison. ZIG1_INLINE bool valCmpApplyOrder(ZirInstTag op, int ord) { return valCmpApplyU(op, (uint64_t)(ord + 1), 1u); } // Integer bit-width + signedness for an int-like type: the fixed-width int // tags plus usize/isize and the C integer types (Type.intInfo's arms). bool ztIntInfo( const InternPool* ip, uint32_t ty, uint32_t* bits, bool* is_signed); // InternPool.isIntegerType: a fixed-width integer type or comptime_int. ZIG1_INLINE bool ipIsIntegerType(const InternPool* ip, uint32_t ty) { uint32_t bits; bool is_signed; return ty == ZIR_REF_COMPTIME_INT_TYPE || ztIntInfo(ip, ty, &bits, &is_signed); } // Target.ptrBitWidth of wasm32. #define ZT_PTR_BIT_WIDTH 32u // Type.isCPtr. bool ztIsCPtr(const InternPool* ip, uint32_t ty); uint32_t valFloatSignBits( InternPool* ip, uint32_t val, uint32_t ty, bool clear); // {bits, signedness} for one of the preinterned C ABI integer types // (`c_char` .. `c_ulonglong`), evaluated for the fixed wasm32-wasi outer // target; false for every other type. Mirrors the `.c_char_type` .. // `.c_ulonglong_type` arms of `Type.intInfo` (src/Type.zig) — // `Target.cCharSignedness` (lib/std/Target.zig) and // `Target.cTypeBitSize` (the `.wasi` / non-mips64 / non-x86_64 // branch: char 8, short 16, int/long 32 = ptrBitWidth, longlong 64). // Defined in type.c; shared with sema_comptime.c's comptime `@typeInfo` // evaluator. bool ztCIntTypeInfoFE(uint32_t ty, uint32_t* bits, bool* is_signed); // Type.isSelfComparable with is_equality_cmp=true for zig1's represented // types. Shared by equality lowering and explicit-sentinel validation. bool ztTypeIsSelfComparableEq(const InternPool* ip, uint32_t ty); // Type.isPtrLikeOptional. bool ztTypeIsPtrLikeOptional(const InternPool* ip, uint32_t ty); bool ztCmpOptionalTypeComparableEq(const InternPool* ip, uint32_t opt_ty); // ── arith: src/Sema/arith.zig ─────────────────────────────────────────────── // Comptime integer arithmetic. Upstream keeps every comptime // arithmetic rule in that one file and sends a `.vector` type to the same // `Scalar` routine per lane, so there is one rule and not two; this module // is that file's home here. // // Upstream computes on `std.math.big.int` limbs, and so does this: every // integer entry point below takes `BigIntConst` operands and writes a // caller-owned `BigIntMutable`, exactly as upstream's `int*` workers take // `Value.toBigInt` operands and write a `BigIntMutable` allocated from // `sema.arena`. There is no width at which this module stops answering. // f16 <-> f64 on compiler_rt's extendf/truncf (`__extendhfdf2`, // `__truncdfhf2`): the float policy keeps the transcribed f16 conversions. static inline double valF16BitsToDouble(uint32_t bits) { const uint64_t u = crtExtendf(64, 16, (CrtU128) { bits, 0 }).lo; double d; memcpy(&d, &u, sizeof d); return d; } static inline uint32_t valF16BitsFromDouble(double d) { uint64_t u; memcpy(&u, &d, sizeof u); return (uint32_t)crtTruncf(16, 64, (CrtU128) { u, 0 }).lo; } // The type an operation is evaluated at (`Type.intInfo`). `sized == false` is // `comptime_int`, which upstream treats as unbounded: it never overflows and // wrapping/saturating variants collapse onto the plain op. typedef struct { bool sized; uint32_t bits; bool is_signed; } ArithIntType; // How an operation ended. Every status names an upstream diagnostic except // the last, which names a tag this module does not evaluate. There is no // "the carrier cannot spell this" status: `std.math.big.int` is unbounded and // so is this port. typedef enum { ARITH_OK = 0, // `Sema.failWithIntegerOverflow`: "overflow of integer type". ARITH_OVERFLOW, // `"exact division produced remainder"` (divScalar's `.div_exact` arm) // and `"exact shift shifted out 1 bits"` (`intShr`'s `.shr_exact` arm). ARITH_REMAINDER, // `Sema.failWithDivideByZero`. ARITH_DIV_ZERO, // `Sema.failWithNegativeShiftAmount` / // `failWithUnsupportedComptimeShiftAmount`. ARITH_SHIFT_AMOUNT, // `Sema.failWithTooLargeShiftAmount`, which is its own diagnostic: an // amount at or past a sized type's width. Split out so a reader can // publish upstream's text without restating the `amt >= ty.bits` rule. ARITH_SHIFT_TOO_LARGE, // The caller handed over a tag this module does not evaluate. ARITH_UNSUPPORTED_OP, // Float only: an f80/f128 (or f128-stored `comptime_float`) operation // whose compiler_rt routine is not ported yet, declined rather than // answered at f64. The integer half has no such status -- big.int is // unbounded. ARITH_FLOAT_NO_CARRIER } ArithStatus; // `Type.intInfo`-shaped constructors so callers do not spell the struct. ArithIntType arithIntTypeSized(uint32_t bits, bool is_signed); ArithIntType arithIntTypeComptimeInt(void); // `Value.toBigInt` for a frame whose carrier is one two's-complement word -- // the AIR interpreter's scalar and the comptime folder's working word. The // caller decides `negative`, because a word alone cannot say whether bit 63 // is a sign or a magnitude bit (`ipIntValueIsNegative`). `space` must hold one // limb and outlive the returned value. BigIntConst arithIntFromWord(uint64_t* space, uint64_t word, bool negative); // `pt.intValue_big`'s word side, for those same frames: the two's-complement // word for `v` at `ty`, truncated and sign-extended to `ty.bits`. Returns // false when `v` has no such word -- past one limb, or a sign the word cannot // carry at a type wider than itself. That is the CALLER's boundary, not this // module's: a caller that interns instead (`ipGetInt` over the `BigIntConst`) // never asks. bool arithIntToWord(BigIntConst v, ArithIntType ty, uint64_t* out_word); // The shift family `arithIntBinOp` sends to `arith.shl`/`arith.shr`. Their // result type is the shifted operand's alone (`shlScalar`/`shrScalar`), never // a peer, so every caller that resolves a type asks this rather than spelling // the list again. bool arithTagIsShift(ZirInstTag tag); // Upper bound on the limbs `arithIntBinOp`'s result needs for this operand // pair at this type, so the caller can size `out` -- upstream allocates the // same bound inside each `int*` worker from `sema.arena` // (`calcTwosCompLimbCount(info.bits)`, `@max(a, b) + 1`, `a.len + b.len`, // `operand.len + shift / 64 + 1`). A shorter `out` is a zig1 bug and aborts // loudly. uint32_t arithIntBinOpLimbs( ZirInstTag tag, BigIntConst lhs, BigIntConst rhs, ArithIntType ty); // The single integer entry point: `arith.add`/`sub`/`mul` and their wrap and // saturating siblings, `arith.div`'s four `DivOp`s, `arith.modRem`'s two // `ModRemOp`s, `arith.shl`'s three `ShlOp`s, `arith.shr`'s two `ShrOp`s, and // `arith.bitwiseBin`, selected by the ZIR tag the caller already has. On // ARITH_OK `*out` holds the result -- a `big.int.Const` with its own sign, // which is the one representation upstream's `Value` is here. ArithStatus arithIntBinOp(ZirInstTag tag, BigIntConst lhs, BigIntConst rhs, ArithIntType ty, BigIntMutable* out); // `arith.addWithOverflow` / `subWithOverflow` / `mulWithOverflow`'s scalar // half (`intAddWithOverflowInner` and its two siblings): `"const overflowed = // result_bigint.addWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits); // return .{ .overflow_bit = try pt.intValue(.u1, @intFromBool(overflowed)), // .wrapped_result = try pt.intValue_big(ty, result_bigint.toConst()) };"` -- // the wrapped result at `ty` plus the one bit saying the exact result did not // fit. `tag` is ZIR_INST_ADD, ZIR_INST_SUB or ZIR_INST_MUL. `comptime_int` is // unbounded, so its bit is always 0 (`intAddWithOverflow`'s // `.comptime_int_type` arm). `out` is sized by `arithIntBinOpLimbs`. ArithStatus arithIntWithOverflow(ZirInstTag tag, BigIntConst lhs, BigIntConst rhs, ArithIntType ty, BigIntMutable* out, bool* out_overflow); // The single float entry point: `arith.add`/`sub`/`mul`'s float arm // (`addScalar`'s `"else { ... return floatAdd(sema, lhs_val, rhs_val, ty); // }"`), `arith.div`'s four `DivOp`s and `arith.modRem`'s two `ModRemOp`s, // selected by the same ZIR tag `arithIntBinOp` takes. A float cannot wrap, so // `.addwrap`/`.subwrap`/`.mulwrap` (the interpreter's key for AIR's // add/sub/mul) are `+ - *`. // // Upstream evaluates at the operand width -- `"switch (ty.floatBits(target)) { // 16 => .{ .f16 = lhs.toFloat(f16, zcu) + rhs.toFloat(f16, zcu) }, 32 => ..."` // -- so every intermediate rounds the way that width rounds, not the way some // wider carrier would. `lhs_bits`/`rhs_bits` are the IEEE patterns the pool // stores, zero-extended; `float_bits` is `Type.floatBits` (128 for // `comptime_float`). On ARITH_OK the result's pattern at the same width is // written to `*out_bits`. // // f80 and f128 compute on compiler_rt's soft-float routines (softfloat.h), // which is what upstream's host `f80`/`f128` operators lower to; an operation // whose routine is not ported returns ARITH_FLOAT_NO_CARRIER. ArithStatus arithFloatBinOp(ZirInstTag tag, CrtU128 lhs_bits, CrtU128 rhs_bits, uint32_t float_bits, CrtU128* out_bits); // `Value.sqrt` / `sin` / `cos` / `tan` / `exp` / `exp2` / `log` / `log2` / // `log10` / `floor` / `ceil` / `trunc` / `round` (src/Value.zig), each // `"switch (float_type.floatBits(target)) { ... 32 => .{ .f32 = @sqrt(f) } // ..."`: One rule the interpreter's runtime unary float ops and the comptime // fold both ask. The transcendental ops are compiler_rt's routines // (softfloat.h); f16 evaluates at f32. `runtime` selects the wasm program // the stage2_wasm backend emits over the comptime host: compiler_rt built // without `want_float_exceptions` and `__sqrth`'s NaN. f80 and f128 (and // `comptime_float`, 128) compute on compiler_rt's routines too, which both // frames reach; an op with no ported routine returns // ARITH_FLOAT_NO_CARRIER. Bit patterns are zero-extended, as // `arithFloatBinOp`'s are. ArithStatus arithFloatUnary(ZirInstTag tag, CrtU128 bits, uint32_t float_bits, bool runtime, CrtU128* out_bits); // An f16 / f32 / f64 bit pattern widened exactly to a double; false for f80 // and f128 (no exact carrier). bool arithFloatToDouble(uint64_t bits, uint32_t float_bits, double* out); // `d` rounded to `float_bits`: f16 through f32, as compiler_rt's `__h` // (`"@floatCast(sqrtf(x))"`) evaluates. uint64_t arithFloatFromDouble(double d, uint32_t float_bits); // `Value.mulAddScalar` (src/Value.zig), `@mulAdd` at the float's own // width, one rounding: f32 `fmaf`, f64 `fma`, f16 as compiler_rt's `__fmah` // (`"return @floatCast(fmaf(x, y, z));"`); f80 and f128 return // ARITH_FLOAT_NO_CARRIER. The rule both the comptime fold and the // interpreter's AIR `mul_add` ask. ArithStatus arithFloatMulAdd(uint32_t float_bits, uint64_t a, uint64_t b, uint64_t c, uint64_t* out_bits); // `Sema.zirOverflowArithmetic`'s fully-comptime fold: `arith.addWithOverflow` // / `subWithOverflow` / `mulWithOverflow` over `dest_ty` -- a sized integer or // a vector thereof, whose `.vector` arm maps the scalar routine per lane -- // returned as the `{ dest_ty, u1-or-@Vector(len, u1) }` tuple value // `pt.overflowArithmeticTupleType(dest_ty)` names. `tag` is ZIR_INST_ADD, // ZIR_INST_SUB or ZIR_INST_MUL. // `arith.bitwiseNot`: the complement of a bool, an integer, or a vector of // either, mapped per lane. `Sema.zirBitNot` (`~x`) and `Sema.zirBoolNot`'s // vector arm (`!v`) both end here through `analyzeBitNot`. uint32_t bitwiseNot(InternPool* ip, uint32_t ty, uint32_t val); uint32_t arithValueBitPermute( InternPool* ip, ZirInstTag tag, uint32_t val, uint32_t ty); uint32_t arithIntTruncateLane(InternPool* ip, uint32_t val, uint32_t dest_ty); uint32_t arithBitwiseBinBool( InternPool* ip, ZirInstTag tag, uint32_t lhs, uint32_t rhs); uint32_t arithWithOverflowScalar(InternPool* ip, ZirInstTag tag, uint32_t ty, uint32_t lhs, uint32_t rhs, uint32_t* out_ov); // ── Builtin: src/Builtin.zig ──────────────────────────────────────────────── // The in-memory generator for the synthesized `builtin.zig`. A `builtin` // module's root source is not read from disk; the compiler generates it from // the resolved compilation config (`Builtin.append`/`Builtin.generate`). // zig1's target is one of four `wasm32-wasi` profiles (CompilationProfile). // stages_test byte-compares each against upstream's `--show-builtin`. // Generate the source selected by the compilation profile. char* builtinGenerateForProfile(CompilationProfile profile); // Source for the bootstrap's named `build_options` module. The // declarations and values mirror build.zig addWasiUpdateStep: LLVM/debug/ // tracing disabled, `.bootstrap`, threaded I/O, and `.by_name` value // interpretation. The companion synthesized `builtin` module supplies the // ReleaseSmall wasm32-wasi target model. const char* builtinBootstrapBuildOptionsSource(void); // ── Zcu: src/Zcu.zig ──────────────────────────────────────────────────────── // The Zig Compilation Unit. Heap-allocated per compilation; owns the // InternPool, the files, namespaces and memoized state. // // Where Sema's state lives. zig1 is single-threaded and non-reentrant: one // analysis runs at a time, and nested analysis (a comptime call, a Nav // value, a type body) runs to completion inside its caller. // - Per compilation: Zcu fields, reached through the Zcu* (or the // PerThread's) every analysis function takes; zcuDeinit frees them. This is // where upstream keeps it (Zcu, its PerThread and InternPool), with zig1's // own tables beside it: Zcu.type_resolution (type_resolution.c's units), // Zcu.comptime_tables (sema_comptime.c's dedup tables, cell pointers and // captures), and the Nav-analysis guards. // - Per analysis on the Zcu: what upstream keeps in the active Sema but // every nested analysis must see without a frame argument -- // sema_branch (the active Sema's branch quota, comptime allocations // and arena) and sema_owner_nav (the function body's Sema.owner). Each is // set and restored by the entry that starts that analysis. // - Per InternPool: the Sema hooks reached from paths that carry only the // InternPool (resolve_enum_layout, resolve_ies, fnptr_pending). // - Per analysis frame: the frame's own struct (InlineCtx and its SemaBlock // chain for analyzeBodyInner), so it ends with the frame. // - The comptime evaluator's frame: a SemaCtx per evaluation (ctFrameInit). // The frame-free type-ref resolution reads Zcu.ct (CtEvaluator), which each // entry point sets and restores around its nested evaluation. // - Per rendering: TypeNameWriter's modes. // No other Sema state is mutable. Outside Sema, the mutable file-scope // variables are the interpreter's instance (interp.c's g_interp), the abort // path's error source (common.c's zig1_error_source), wasi.c's `seeded`, // compilation.c's `bootstrap_module` and the static message buffers of // zig1CompileErrorMessageV and ccValidate. // Module identities used to key loaded files. Configured module definitions // occupy `ZCU_MODULE_CONFIG_FIRST + module_index`; dependency names are edges // to these identities, not identities themselves. #define ZCU_MODULE_ROOT 0u #define ZCU_MODULE_STD 1u #define ZCU_MODULE_BUILTIN 2u #define ZCU_MODULE_MAIN 3u #define ZCU_MODULE_CONFIG_FIRST 4u // Per-source-file state (mirrors Zcu.File): the parsed AST + lowered ZIR, plus // the interned root struct type once `ensureFilePopulated` has run. A file's // source is either owned by the Zcu (demand-loaded modules like `std`, whose // AST/ZIR the Zcu allocates and must free) or borrowed (the root/test file, // whose ZIR is owned by the harness that drives compilationAnalyze). typedef struct { char* sub_path; // heap-owned module-relative path, or NULL (borrowed root) // The path a compile error in this file names: `sub_path`, or the // driver's path of a borrowed root. const char* error_path; // Source identity supplied by the file scope, not reconstructed at @src() // use sites. sub_file_path is heap-owned; module_name is borrowed from // CompilationConfig or a static module name for the Zcu lifetime. char* sub_file_path; const char* module_name; uint32_t module_id; // owning module; relative imports inherit this ID bool owns_source; // Zcu owns `ast`/`zir_storage` and must deinit them Ast ast; // valid iff owns_source // Heap-allocated (not inline in the files array): `zir` aliases it, and an // inline member would dangle when the files array reallocs on growth. The // stable address also keeps zir-pointer identity (zcuNamespaceOfZir) valid // across demand-loads. Zir* zir_storage; // valid iff owns_source const Zir* zir; // the file's ZIR (points at *zir_storage or a borrow) // A `.zon` file's Zoir (Zcu.File.zoir), NULL for a `.zig` file, whose // `zir` is then NULL. Owned with the Ast. Zoir* zoir; InternPoolIndex root_type; // fileRootType; IP_INDEX_NONE until populated uint32_t ns; // owning namespace index; UINT32_MAX until populated } ZcuFile; // One recorded `@export` (mirrors `Zcu.Export` / `Zcu.Export.Options`, // src/Zcu.zig). `status` is not modeled: upstream advances it from // `.in_progress` in the linker, and zig1 has no linker -- `Sema.zirExport`'s // analysis-time contract ends at `sema.exports.append(...)`. typedef struct { uint32_t name; // NullTerminatedString (InternPool string index) uint32_t section; // string index, or IP_INDEX_NONE for `.none` uint8_t linkage; // std.lang.GlobalLinkage field index uint8_t visibility; // std.lang.SymbolVisibility field index // `Zcu.Exported` is `union(enum) { nav, uav }`: one tag, one index. bool exported_is_uav; uint32_t exported; // a Nav index, or an InternPool value index if uav } ZcuExport; // One `@embedFile` file (mirrors Zcu.EmbedFile; `Zcu.embed_table` is keyed by // its path). `val` is the `*const [N:0]u8` pointer to the bytes, or // IP_INDEX_NONE when the file could not be read (`ef.val = .none`). typedef struct { uint32_t module_id; char* sub_path; // heap-owned, normalized module-relative path uint32_t val; } ZcuEmbedFile; // Sema.default_branch_quota (`"pub const default_branch_quota = 1000;"`). #define ZIG1_DEFAULT_BRANCH_QUOTA 1000 // MutableValue (src/mutable_value.zig): an interned value, or one level of // an error union or optional payload, an aggregate, a slice or a union // whose sub-values are mutated in place; for aggregates the sentinel is // stored. The tags are upstream's union fields in order. typedef enum { MV_INTERNED, MV_EU_PAYLOAD, MV_OPT_PAYLOAD, MV_REPEATED, MV_BYTES, MV_AGGREGATE, MV_SLICE, MV_UN, } MutableValueTag; struct MutableValue; typedef struct { uint32_t ty; struct MutableValue* child; } MutableValueSubValue; typedef struct { uint32_t ty; uint8_t* data; uint32_t len; } MutableValueBytes; typedef struct { uint32_t ty; struct MutableValue* elems; uint32_t len; } MutableValueAggregate; typedef struct { uint32_t ty; struct MutableValue* ptr; // the many-pointer struct MutableValue* len; // a usize } MutableValueSlice; typedef struct { uint32_t ty; uint32_t tag; struct MutableValue* payload; } MutableValueUnion; typedef struct MutableValue { MutableValueTag tag; union { uint32_t interned; MutableValueSubValue eu_payload; MutableValueSubValue opt_payload; MutableValueSubValue repeated; MutableValueBytes bytes; MutableValueAggregate aggregate; MutableValueSlice slice; MutableValueUnion un; } u; } MutableValue; // Sema.ComptimeAlloc; is_const, src, alignment (the pointer type carries // it) and runtime_index are not modeled. typedef struct { MutableValue val; } ComptimeAlloc; // The state of the Sema analyzing now: its `branch_count`, `branch_quota`, // `quota_request`, `allow_memoize`, `comptime_allocs` and `arena` (every // allocation freed with the Sema). `owner_func` is `sema.owner`'s `.func`, // IP_INDEX_NONE for any other owner. typedef struct ZcuSemaBranch { uint32_t count; uint32_t quota; uint32_t quota_request; bool allow_memoize; uint32_t owner_func; ComptimeAlloc* comptime_allocs; uint32_t comptime_allocs_len; uint32_t comptime_allocs_cap; void** arena; uint32_t arena_len; uint32_t arena_cap; } ZcuSemaBranch; // Sema.Block's `type_name_ctx` and `type_fqn_ctx` (IP string indices): what // setTypeName prefixes a new container's `name` and `fqn` with. Both // IP_INDEX_NONE where zig1 has no context. typedef struct { uint32_t name; uint32_t fqn; } TypeNameCtx; #define TYPE_NAME_CTX_NONE ((TypeNameCtx) { IP_INDEX_NONE, IP_INDEX_NONE }) // A Nav's own context: `.type_name_ctx = nav.name, .type_fqn_ctx = nav.fqn`. #define TYPE_NAME_CTX_NAV(nav) ((TypeNameCtx) { (nav)->name, (nav)->fqn }) // The Block a comptime evaluation starts with (SemaCtx.ns, type_name_ctx, // type_name_func, owner_nav, InlineCtx.ct_lexical): ctFrameInit's input. // `lexical` is AstGen's GenZir.is_comptime for the body: true for a // declaration's value, type, modifier, field and generic type bodies, false // for a callee's own body. typedef struct { uint32_t ns; TypeNameCtx type_name_ctx; const struct TypeNameFuncArgs* type_name_func; uint32_t owner_nav; bool lexical; } CtBlockScope; // The state of the frame-free type-ref resolution (sema_comptime.c, zig1's // own; Zcu.ct): ctResolveParamTypeRef and ctResolveFieldTypeRefInNs have no // SemaCtx to read it from. Each entry point that starts a nested evaluation // restores what it sets before it returns. typedef struct { // The namespace a type expression's decl_val/decl_ref searches (up its // parent chain) before the ZIR file's root namespace, as Sema's decl // lookup searches the enclosing container scopes (`arch: Arch` inside // `std.Target.Cpu` finds `Cpu.Arch`); IP_NAMESPACE_NONE searches the // file root only. uint32_t type_resolve_ns; // A generic type body's parameter types (IP_INDEX_NONE: not a bound // param) and the frame holding the bound values (a parameter's slot is // INST_VALUE_UNSET when unbound; NULL outside a bound call), indexed by // the callee's ZIR instruction, set around ctEvalTypeBody: the body // may name a runtime parameter under `@TypeOf(param)` (std.mem.sort's // comparator), whose monomorphic type the call site knows. NULL at // comptime-call entry points, so a nested callee never reads the outer // callee's table. const uint32_t* param_tys; } CtEvaluator; // Set Zcu.ct.type_resolve_ns to `ns`, returning the caller's value for the // restore. static inline uint32_t ctSwapNs(uint32_t* slot, uint32_t ns) { uint32_t old = *slot; *slot = ns; return old; } // InternPool.getStructType's `.wip` key, which semaDeclaredNamespaceType holds // while the struct is minted and ctDedupPublishWip publishes, so a field // naming its own instantiation finds the type (sema_comptime.c). typedef struct { const Zir* zir; uint32_t inst; const struct CtCap* caps; uint32_t caps_len; bool pending; } CtDedupWip; // type_resolution.c's units (Zcu.type_resolution; its file head). // semaDeinitTypeResolution frees it. typedef struct { // Nesting of layout demands; normal struct defaults drain when it returns // to 0, as upstream's `.struct_defaults` unit runs after `.type_layout`. uint32_t demand_depth; // Each field's default body: the `zir_field.default_body` that // resolveStructDefaultsInner reads from the struct's tracked zir_index. struct StructFieldDefault* field_defaults; uint32_t field_defaults_len; uint32_t field_defaults_cap; // Each struct's defaults: its comptime fields', which resolveStructLayout // resolves, then its `.struct_defaults` AnalUnit's for the rest. struct StructDefaultsResolution* struct_defaults; uint32_t struct_defaults_len; uint32_t struct_defaults_cap; // Zcu.outdated_ready's `.struct_defaults` units, drained in order. U32List pending_struct_defaults; uint32_t pending_struct_defaults_head; bool struct_defaults_draining; // A union's non-primitive field type bodies, which resolveUnionLayout // reads from zir_union while union_obj.field_types are `.none`. struct UnionFieldDeferred* union_deferred; uint32_t union_deferred_len; uint32_t union_deferred_cap; // A union's align and tag value bodies, and how far its `.type_layout` // unit has got (analysis_in_progress, for the dependency loop). struct UnionLayoutDeferred* union_layout; uint32_t union_layout_len; uint32_t union_layout_cap; // Declared explicit-tag enums whose resolveEnumLayout waits for their // first layout demand (want_layout; InternPool.resolve_enum_layout). struct PendingEnumLayout* pending_enum_layouts; uint32_t pending_enum_layouts_len; uint32_t pending_enum_layouts_cap; // Declared structs whose fields resolveStructLayout has not populated // ("Declared structs do not yet have field information populated"), // with struct_fields_slot mapping a struct to its entry + 1. struct StructFieldsPending* struct_fields; uint32_t struct_fields_len; uint32_t struct_fields_cap; uint32_t* struct_fields_slot; uint32_t struct_fields_slot_cap; } ZcuTypeResolution; // The comptime evaluator's tables (Zcu.comptime_tables; sema_comptime.c), // which live as long as the Zcu, unlike Zcu.ct's scoped fields: each // namespace's container captures (indexed by namespace), the declared-type // dedup table (keyed by {zir, decl inst, captures}, hashed into its buckets), // the reified-enum dedup table, the declared type whose dedup key is published // before its fields resolve. ctDeinitTables frees them. typedef struct { struct CtCaptureSet* ns_captures; uint32_t ns_captures_cap; struct StructInstDedup* struct_dedup; uint32_t struct_dedup_len; uint32_t struct_dedup_cap; uint32_t* struct_dedup_buckets; uint32_t struct_dedup_buckets_cap; struct ReifiedEnumDedup* reified_enum_dedup; uint32_t reified_enum_dedup_len; uint32_t reified_enum_dedup_cap; CtDedupWip dedup_wip; } CtTables; typedef struct Zcu { Compilation* comp; InternPool intern_pool; // Zcu.anon_name_counter: numbers anonymous type names and generic // function instance Navs, in creation order. uint32_t anon_name_counter; // Sema.branch_count / Sema.branch_quota (`"branch_count: u32 = 0,"`, // `"branch_quota: u32 = default_branch_quota,"`) of the Sema analyzing // now. Upstream keeps one counter and one quota per Sema, shared by every // inline and comptime callee it analyzes, so an entry that starts a new // Sema (a function body, a declaration value, a type body) points this at // a fresh budget and restores it after; `sema_branch_root` backs the rest. ZcuSemaBranch* sema_branch; ZcuSemaBranch sema_branch_root; // `Sema.owner` of the function body being analyzed, as its Nav (a generic // instance's generic owner's); IP_INDEX_NONE outside one or when unknown. // zptAnalyzeFuncBody sets and restores it. A declaration value it // resolves on the way has its own owner, SemaCtx.owner_nav. uint32_t sema_owner_nav; // Loaded files (mirrors Zcu.import_table / Zcu.File storage). Index 0 is // reserved for no particular file; entries are appended as modules are // demand-loaded. ZcuFile* files; uint32_t num_files; uint32_t files_cap; uint32_t std_file_idx; // the `std` module root file uint32_t root_file_idx; // the root/test file under analysis // Distinct user test source for `zig test`; equals root_file_idx for the // ordinary one-module compilation. uint32_t main_file_idx; uint32_t builtin_file_idx; // synthesized `builtin` module (0 = not loaded) // One root-file cache entry per CompilationConfig.modules definition. // Dependency aliases share an entry because they resolve to one module ID. uint32_t* module_root_file_indices; // Nav values requested by type-only address formation, in the order of // their first request. Upstream's `outdated_ready.other` is insertion // ordered; preserving that order matters because resolving a value can // append InternPool items. `analysis_wanted` on each IpNav deduplicates // entries, while `wanted_navs_head` permits later drains to continue from // work appended by a previous analysis unit. uint32_t* wanted_navs; uint32_t wanted_navs_len; uint32_t wanted_navs_cap; uint32_t wanted_navs_head; // `Sema.exports` (upstream flushes them into `Zcu.all_exports`): every // `@export` recorded by `Sema.zirExport`, in analysis order. ZcuExport* exports; uint32_t exports_len; uint32_t exports_cap; // `Zcu.embed_table`: every file loaded with `@embedFile`. ZcuEmbedFile* embed_table; uint32_t embed_table_len; uint32_t embed_table_cap; ZcuTypeResolution type_resolution; CtTables comptime_tables; // The comptime evaluator's ambient state. CtEvaluator ct; // Sema.comptime_break_inst: the `break`/`switch_continue` whose // CT_EVAL_BREAK ctEvalBody is propagating, so a switch can tell its own // `continue` from a `break` out of it. Set by the break, not scoped. uint32_t comptime_break_inst; // Nav analysis state (zcu_per_thread.c, sema.c), set by zptAnalyzeNavVal // when the Nav's whole value is a container declaration. // navstruct_resolving is that Nav (a deferred capture naming it before its // type exists is a self-reference, which ctMaterializeCap rejects). // container_self_nav is the same Nav until the declaration reads and // clears it (semaBodyStructType, semaBodyUnionType, semaBodyEnumType) and // publishes its type on the Nav as soon as it is interned, so a field // whose type is a slice or pointer of the container itself (`kids: []const // Node`) resolves `decl_val(Self)`; IP_INDEX_NONE otherwise. uint32_t navstruct_resolving; uint32_t container_self_nav; // Zcu.std_lang_decl_values' `.Type` and `.@"Type.Pointer"` // (Sema.getStdLangType), memoized when @typeInfo first produces them // (sema.c's @typeInfo value predicates); IP_INDEX_NONE before. // Zcu.std_lang_decl_values' `.main` stage // (Sema.ensureMemoizedStateResolved). enum { STD_LANG_STAGE_UNRESOLVED, STD_LANG_STAGE_RESOLVING, STD_LANG_STAGE_RESOLVED } std_lang_main_state; // Compilation.update's queue (compilation.c's performAllTheWork), NULL // outside it: // its context, which Nav analysis tells of each Nav it enters and // publishes; the observer of each completed namespace scan, which // applies upstream's analysis-root rules to nested namespaces and // comptime declarations. Its other two hooks are the InternPool's // resolve_ies and fnptr_pending. struct UpdateQueue* update_queue; // Zcu.test_functions: a test compilation's selected test Navs in // ScanDeclIter.scanDecl's order (populateTestFunctions). U32List test_functions; void (*namespace_scan_observer)(void* ctx, uint32_t ns_idx, const Zir* zir, const uint32_t* decls, uint32_t decls_len); void* namespace_scan_observer_ctx; } Zcu; // Append one recorded `@export` (Sema.zirExport's `sema.exports.append`). // Returns its index in `zcu->exports`. uint32_t zcuAppendExport(Zcu* zcu, uint32_t name, uint32_t section, uint8_t linkage, uint8_t visibility, bool exported_is_uav, uint32_t exported); // Allocate and initialize a Zcu for the given compilation. Zcu* zcuInit(Compilation* comp); void zcuDeinit(Zcu* zcu); // Register a file whose AST/ZIR the Zcu takes ownership of (a demand-loaded // module such as `std`). `sub_path` must be heap-allocated; the Zcu frees it. // Returns the new file index. uint32_t zcuAddFileOwned( Zcu* zcu, uint32_t module_id, char* sub_path, Ast ast, Zir zir); // Zcu.File.modeFromPath: false for a path that is neither `.zig` nor `.zon`. bool zcuFileModeFromPath(const char* path, AstMode* out); // The same for a `.zon` file and its Zoir. uint32_t zcuAddFileOwnedZon( Zcu* zcu, uint32_t module_id, char* sub_path, Ast ast, Zoir zoir); // Register a file whose ZIR is owned by the caller (the root/test file driven // by the harness). Returns the new file index. uint32_t zcuAddFileBorrowed( Zcu* zcu, uint32_t module_id, const Zir* zir, const char* path); // Set the upstream File.sub_file_path / Module.fully_qualified_name identity // at file-registration time. Duplicates sub_file_path; replaces any prior // value. module_name must outlive the Zcu. void zcuSetFileSourceIdentity(Zcu* zcu, uint32_t file_idx, const char* module_name, const char* sub_file_path); // Namespace of the file whose ZIR is `zir` (the file currently being // analyzed). A function/const/type body's decl_val/decl_ref must resolve // against its own file's root namespace — Sema resolves decls against the // analysis unit's namespace, not the root file's — so a callee imported from a // sibling file finds its siblings in the right namespace. For a root-file body // the owning file is the root file, so the result equals // `zcu->files[zcu->root_file_idx].ns`. // Falls back to the root namespace if no file matches (defensive; each file // has a distinct ZIR pointer, so a body under analysis always matches). uint32_t zcuNamespaceOfZir(Zcu* zcu, const Zir* zir); // Zcu.structPackedFieldBitOffset: a packed struct field's LSB-first bit // offset, recomputed on demand as `"bit_sum += field_ty.bitSize(zcu);"` over // the preceding fields. Upstream stores no such array anywhere and neither // does zig1; this is the single source for both `@bitOffsetOf` and // `@offsetOf` on a packed struct, as `Sema.bitOffsetOf` is upstream. uint32_t zcuStructPackedFieldBitOffset( Zcu* zcu, InternPool* ip, uint32_t struct_ty, uint32_t field_index); // ── Zcu.PerThread: src/Zcu/PerThread.zig ──────────────────────────────────── // The analysis state Sema and PerThread share, and zcu_per_thread.c: files // and imports, Nav analysis, namespace scans and function bodies. // Value encodings. Every value word is 32 bits: // - InternPool index: a comptime-known value or type, below the pool's // items_len (so below AIR_REF_INST_BASE). // - analyzeBodyInner's inst_values (upstream's Sema.inst_map) holds an // Air.Inst.Ref per ZIR instruction: an InternPool index, an AIR instruction // at AIR_REF_INST_BASE and up, or INST_VALUE_UNSET (Air.Inst.Ref.none) for // one not yet analyzed. // ctEvalBody's frame values[] is such a map; its one AIR instruction is the // inferred_alloc_comptime placeholder, in its frame's SemaCtx.insts. // Any AIR ref or `.none` is above any pool's items_len, so a `< items_len` // test needs no semaRefIsInterned test beside it. #define INST_VALUE_UNSET UINT32_MAX // (part of) Air.zig:Inst.Ref.toInterned // Is an inst_values entry an InternPool value: neither an AIR Ref nor `.none`? ZIG1_INLINE bool semaRefIsInterned(uint32_t val) { return val < AIR_REF_INST_BASE; } // A soft-float f80 (Type.abiAlignment/abiSize `.f80` when // compilerRtFloatAbi(80) is `.soft`, as on wasm32): a u64 mantissa and a u16 // exponent. #define SOFT_F80_MANTISSA_BITS 64u #define SOFT_F80_EXPONENT_BITS 16u // Type.zigTypeTag (src/InternPool.zig:zigTypeTag): the std.lang.TypeId of a // type, whatever InternPool storage variant holds it. ZigTypeTag ztZigTypeTag(const InternPool* ip, uint32_t ty); // `ty.zigTypeTag(zcu) == tag` where zig1 can hand `.none`, an AIR ref or // generic_poison for the type upstream has already checked (a value's type, // a result, return or parameter type); false for those. ZIG1_INLINE bool ztIsZigTypeTag( const InternPool* ip, uint32_t ty, ZigTypeTag tag) { return ty != IP_INDEX_GENERIC_POISON_TYPE && ipIndexIsType(ip, ty) && ztZigTypeTag(ip, ty) == tag; } // Type.zig:isSlice // "`.ptr_type => |ptr_type| ptr_type.flags.size == .slice`": a slice is its // own `type_slice` item; false for any other index. ZIG1_INLINE bool ztIsSlice(const InternPool* ip, uint32_t ty) { return ipItemTagIs(ip, ty, IP_TAG_TYPE_SLICE); } // Type.zig:isTuple // "`.tuple_type => true`", the `type_tuple` item; false for any other index. ZIG1_INLINE bool ztIsTuple(const InternPool* ip, uint32_t ty) { return ipItemTagIs(ip, ty, IP_TAG_TYPE_TUPLE); } // Type.zig:isNonexhaustiveEnum // "`.enum_type => ip.loadEnumType(ty.toIntern()).nonexhaustive`", the // `type_enum_nonexhaustive` item; false for any other index. ZIG1_INLINE bool ztIsNonexhaustiveEnum(const InternPool* ip, uint32_t ty) { return ipItemTagIs(ip, ty, IP_TAG_TYPE_ENUM_NONEXHAUSTIVE); } // Sema.zirPtrType (src/Sema.zig): a pointer type's element-type // restrictions, then the ZIR->IP flag packing every ptr_type port repeats. // `zir_flags` is Zir.Inst.PtrType.flags (bit0 allowzero, bit1 mutable, bit2 // volatile) and `zir_size` is Zir's size (One=0, Many=1, Slice=2, C=3). The // returned flags word still carries `.one` in its size bits and `.none` // alignment: the caller packs its own align clause and ORs in the size (or // wraps the `[*]` variant in a `type_slice`), exactly as // Zcu.PerThread.ptrType does. static inline uint32_t semaPtrTypeFlags(const InternPool* ip, uint8_t zir_flags, uint8_t zir_size, uint32_t elem_ty) { // "if (elem_ty.zigTypeTag(zcu) == .@"fn") { if (inst_data.size != .one) // ... 'function pointers must be single pointers' } else if // (inst_data.size != .one and elem_ty.zigTypeTag(zcu) == .@"opaque") ... // 'indexable pointer to opaque type ... not allowed'". `[*c]` is a size // like any other here, so `[*c]fn()` and `[*c]Opaque` are rejected too. if (elem_ty < ip->items_len && ip->item_tags[elem_ty] == IP_TAG_TYPE_FUNCTION) { if (zir_size != IP_PTR_SIZE_ONE) COMPILE_ERROR_UPSTREAM( "ptr_type: function pointers must be single pointers"); } else if (zir_size != IP_PTR_SIZE_ONE && ztIsZigTypeTag(ip, elem_ty, ZIG_TYPE_TAG_OPAQUE)) { COMPILE_ERROR_UPSTREAM( "ptr_type: indexable pointer to opaque type not allowed"); } uint32_t ip_flags = IP_PTR_FLAGS_MUT_ONE; if (!(zir_flags & ZIR_PTR_TYPE_FLAGS_IS_MUTABLE)) ip_flags |= IP_PTR_FLAGS_IS_CONST; if (zir_flags & ZIR_PTR_TYPE_FLAGS_IS_VOLATILE) ip_flags |= IP_PTR_FLAGS_IS_VOLATILE; if (zir_flags & ZIR_PTR_TYPE_FLAGS_IS_ALLOWZERO) ip_flags |= IP_PTR_FLAGS_IS_ALLOWZERO; return ip_flags; } // zirPtrType's closing `pt.ptrType(...)`: a `.slice` interns the `[*]` // many-pointer first (carrying the sentinel), then the type_slice over it. static inline uint32_t semaPtrTypeIntern(InternPool* ip, uint32_t elem_ty, uint32_t ip_flags, uint8_t zir_size, uint32_t sentinel, uint32_t packed_offset) { if (zir_size == IP_PTR_SIZE_SLICE) return ipGetSliceType(ip, ipGetPtrTypeFull(ip, elem_ty, (ip_flags & ~3u) | IP_PTR_SIZE_MANY, sentinel, packed_offset)); return ipGetPtrTypeFull( ip, elem_ty, (ip_flags & ~3u) | zir_size, sentinel, packed_offset); } // Per-thread analysis context (mirrors Zcu.PerThread). typedef struct { Zcu* zcu; const Zir* zir; } PerThread; // `Zcu.PerThread.overflowArithmeticTupleType`: the `{ ty, u1 }` tuple type // `@addWithOverflow` and friends answer with -- and `{ ty, @Vector(len, u1) }` // when `ty` is a vector. Both frames build their result type here, so the // overflow half's shape is one rule. `out_ov_ty` may be NULL. uint32_t zptOverflowArithmeticTupleType( InternPool* ip, uint32_t ty, uint32_t* out_ov_ty); // The pending-callee queue holds plain callee Navs (analyzed by re-scanning // their ZIR declaration) and generic function instances. An entry with this // bit set is a `func_instance` InternPool index (with the bit masked off); the // drain analyzes it via its generic owner's ZIR body + stored comptime_args. #define PENDING_INSTANCE_FLAG (1u << 31) // Sema.zirFunc for the `func` instruction of Nav `nav_idx`'s value body: // intern its `type_function` + `func_decl` and return the func_decl // (zcu_per_thread.c). Everything else reaches a function through // zptEnsureNavValUpToDate. uint32_t navFuncDecl(Zcu* zcu, uint32_t nav_idx); typedef struct CtCap { uint32_t v; // CAP_VALUE: value/sentinel. CAP_DECL_*: Nav. CAP_RUNTIME_TYPE: ty. uint32_t kind; // CtCapKind } CtCap; // The function a `.func`-strategy type name prints: its `param` // instructions and the comptime frame's values for them. typedef struct TypeNameFuncArgs { const uint32_t* param_insts; uint32_t nparams; const uint32_t* values; } TypeNameFuncArgs; // PerThread.errorSetFromUnsortedNames: sorts `names` in place, then interns. uint32_t zptErrorSetFromUnsortedNames( InternPool* ip, uint32_t* names, uint32_t names_len); // `Sema.zirErrorCast`'s type checks and `result` classification; // `semaErrorCastValue` is its comptime-known fold. typedef enum { ZPT_ERRCAST_DISJOINT, ZPT_ERRCAST_SUPERSET, ZPT_ERRCAST_OVERLAP, } SemaErrorCastResult; // PerThread.ensureNavValUpToDate: the Nav's resolved value, analyzing it // (analyzeNavVal, zcu_per_thread.c) on the first demand. Writes the resolved // type through `out_type`. uint32_t zptEnsureNavValUpToDate( Zcu* zcu, uint32_t nav_idx, uint32_t* out_type); // Sema.analyzeNavVal: the one entry point for what a `decl_val` reads. It is // zptEnsureNavValUpToDate plus analyzeNavRefInner's function-owner // canonicalization (see sema_comptime.c), and identity for everything that is // not a function alias. Every decl_val / `@TypeOf()` site calls this, // never zptEnsureNavValUpToDate, so an alias cannot be read at its own // declared type. uint32_t navAnalyzeVal(Zcu* zcu, uint32_t nav_idx, uint32_t* out_type); // `comptime_ptr_access.ComptimeLoadResult`, in upstream's order: `.success` // is the loaded value, `.ty` the type for NEEDED_WELL_DEFINED / // OUT_OF_BOUNDS. Unmodeled is zig1's own: a base address or a // reinterpretation (`castMemory`) not ported yet. typedef enum { ZPT_LOAD_SUCCESS, ZPT_LOAD_RUNTIME_LOAD, ZPT_LOAD_UNDEF, ZPT_LOAD_ERR_PAYLOAD, ZPT_LOAD_NULL_PAYLOAD, ZPT_LOAD_INACTIVE_UNION_FIELD, ZPT_LOAD_NEEDED_WELL_DEFINED, ZPT_LOAD_OUT_OF_BOUNDS, ZPT_LOAD_EXCEEDS_HOST_SIZE, ZPT_LOAD_UNMODELED, } ComptimeLoadTag; typedef struct { ComptimeLoadTag tag; MutableValue success; uint32_t ty; } ComptimeLoadResult; // `comptime_ptr_access.ComptimeStoreResult`, in upstream's order: `.val` is // the expected value for COMPTIME_FIELD_MISMATCH and the type for // NEEDED_WELL_DEFINED / OUT_OF_BOUNDS. Unmodeled is zig1's own, a failed // load ComptimeLoadResult has no store name for. typedef enum { ZPT_STORE_SUCCESS, ZPT_STORE_RUNTIME_STORE, ZPT_STORE_COMPTIME_FIELD_MISMATCH, ZPT_STORE_UNDEF, ZPT_STORE_ERR_PAYLOAD, ZPT_STORE_NULL_PAYLOAD, ZPT_STORE_INACTIVE_UNION_FIELD, ZPT_STORE_NEEDED_WELL_DEFINED, ZPT_STORE_OUT_OF_BOUNDS, ZPT_STORE_EXCEEDS_HOST_SIZE, ZPT_STORE_UNMODELED, } ComptimeStoreTag; typedef struct { ComptimeStoreTag tag; uint32_t val; } ComptimeStoreResult; // Resolve only enough of a declaration to form its exact direct Nav pointer: // publish an explicit declared type + modifiers without touching the value, // falling back to full value resolution only for inferred-type declarations. // Returns the ptr_nav value and writes its pointer type when requested. uint32_t navDeclRefResolved( Zcu* zcu, uint32_t nav_idx, uint32_t* out_ptr_type); // Type-only half of navDeclRefResolved for runtime_nav_ptr cases where a // comptime ptr_nav cannot be formed (threadlocal Navs) but stock // analyzeNavRefInner(is_ref=true) still must not force initializer analysis. uint32_t navDeclRefTypeOnlyResolved(Zcu* zcu, uint32_t nav_idx); // The semantic DECL_REF/address-taking variant. In addition to forming the // canonical pointer, schedules an address-taken function owner in the active // Sema pending queue. Type-only/internal pointer formation uses the // unscheduled navDeclRefResolved entry point above. uint32_t navDeclRefAddressResolved( Zcu* zcu, uint32_t nav_idx, uint32_t* out_ptr_type); // Sema.zirVectorType's tail (src/Sema.zig): the resolved length is // coerced to `u32` -- `sema.resolveInt(block, len_src, extra.lhs, .u32, ...)`, // which interns that coerced value, exactly as the array spelling interns its // `usize` -- and the resolved element type then builds // `pt.vectorType(.{ .len, .child })`. Each of the three frames that can reach // a `@Vector(len, T)` instruction resolves the two operands with its own // machinery (a declared-type ref, the comptime evaluator's `values`, a runtime // body's `inst_values`); this is the one place that turns them into the type, // so the coerced `u32` cannot go missing from one of them. Defined in // zcu_per_thread.c. // // Being the one place, it is also where `checkVectorElemType` // (src/Sema.zig, int/float/bool/ptr-at-runtime only) runs, so no frame // can accept an element type upstream rejects. (The ARRAY_TYPE family's own // `validateArrayElemType` remains unmodeled.) uint32_t zptVectorType(InternPool* ip, uint64_t len, uint32_t elem_ty); // `Type.print`'s writer: a growable byte buffer, NUL-terminated after every // append so a diagnostic can read it as a C string, and the rendering's // modes. Zero-initialize it; the caller frees `buf.arr`. typedef struct { SLICE(char) buf; // A compile-error message, which may render a container zig1 has not // named as "" (`@typeName` may not). bool diagnostic; // A `.func`-strategy argument rendering (ztTypeNameWriteArgValue), which // declines instead of stopping on a shape zig1 cannot name; `declined` // records it, so the type being named stays unnamed. bool soft; bool declined; } TypeNameWriter; // Ensure a registered file's root struct type is interned, its namespace is // created, and its top-level declarations are scanned into real Navs (mirrors // PerThread.ensureFilePopulated). Tracks the file's `main_struct_inst`, // interns the `type_struct` via `ipGetDeclaredStructType`, creates the file's // namespace and runs `zptScanNamespace`. Records the result as the file's // `root_type` and `namespace`. Idempotent: returns immediately if already // populated. Returns the root struct InternPool index. uint32_t zptEnsureFilePopulated(PerThread* pt, uint32_t file_idx); // Scan a container's declaration list into its namespace (mirrors // PerThread.scanNamespace): registers const/var decls as Navs (pass 1) and // unnamed comptime/test units (pass 2). Touches only the string/nav arenas — // no InternPool items. Used by semaBodyStructType and semaBodyEnumType // (zcu_per_thread.c). void zptScanNamespace(PerThread* pt, uint32_t ns_idx, const uint32_t* decls, uint32_t decls_len, const Zir* zir); // Sema.zirEmbedFile: the `*const [N:0]u8` pointer to the bytes of `name`, // resolved against the file whose ZIR is `zir`. Defined in zcu_per_thread.c. uint32_t zptEmbedFile( Zcu* zcu, const Zir* zir, const char* name, uint32_t name_len); // Lower one function body (identified by its ZIR `func` instruction) to AIR. // Mirrors PerThread.analyzeFuncBody. `param_insts`/`param_types` list the ZIR // parameter instructions (in `param_body` order) and their (primitive) // InternPool type indices; each runtime parameter is emitted as an AIR `arg` // instruction bound to its ZIR instruction, matching the "runtime semantic // analysis" prologue in PerThread.analyzeFuncBody. Aborts via UNIMPLEMENTED on // any construct not yet ported, so no wrong output is ever produced. // For a generic function instance, `comptime_bits` marks which of the // `nparams` parameters are comptime; `comptime_args[i]` is the bound comptime // value (an InternPool index) for a comptime parameter, unused otherwise. A // runtime parameter is emitted as an AIR `arg` whose src_index is its position // `i` in the original signature; a comptime parameter is bound directly to its // value (no AIR `arg`). Pass `comptime_args == NULL` // for a non-generic function (all params runtime, src_index 0..nparams-1). // `override_ret_ty` forces the function's return type (used for a generic // func_instance, whose ZIR return type is `[generic]` and cannot be re-derived // from the owner's ZIR); pass `IP_INDEX_NONE` to derive it from the ZIR. // `type_name_ctx` is the analyzed function's Nav fqn (an IP string index); a // container type declared inline in the body (e.g. `const S = struct {...}`) // derives its name from it (`{type_name_ctx}.{var}`, Sema.setTypeName // .dbg_var). Pass 0 (empty string) when no inline container naming is needed. // `owner_nav` is `Zcu.sema_owner_nav` for the body (IP_INDEX_NONE if unknown). Air zptAnalyzeFuncBody(const PerThread* pt, uint32_t func_index, uint32_t func_decl_inst, const uint32_t* param_insts, const uint32_t* param_types, uint32_t nparams, const uint32_t* comptime_args, uint32_t override_ret_ty, uint32_t fn_name_ctx, TypeNameCtx type_name_ctx, uint32_t owner_nav, uint32_t branch_quota, U32List* pending_navs); // Sema.zirFieldParentPtr's parent pointer type, field index and parent // kind, after its compile-error checks; then the comptime-known fold. typedef struct { uint32_t parent_ptr_ty, parent_ty, fidx; bool is_union; } SemaFieldParent; // Sema.castMemory (src/Sema/reinterpret.zig): ZPT_CAST_RUNTIME is its // `null` (a pointer that is not an integer address was reinterpreted), // ZPT_CAST_UNMODELED a value or type zig1 does not lay out. typedef enum { ZPT_CAST_OK, ZPT_CAST_RUNTIME, ZPT_CAST_UNMODELED, } SemaCastStatus; uint32_t zptSliceToArrayPtr(InternPool* ip, uint32_t slice_val); // pt.aggregateValue: ipGetAggregate after the all-undef collapse. uint32_t zptGetCoerced(Zcu* zcu, uint32_t val, uint32_t ty); uint32_t zptAggregateValue( InternPool* ip, uint32_t ty, const uint32_t* elems, uint32_t len); uint32_t zptIntFittingRange(InternPool* ip, uint32_t min, uint32_t max); // std's root file below its module root. #define STD_ROOT_SUB_PATH "lib/std/std.zig" const char* zptPathBelowDir(const char* path, const char* dir, size_t dir_len); char* zptImporterRelativePath( const char* importer_path, const char* operand, size_t olen); uint32_t zptDemandLoadFile(Zcu* zcu, uint32_t module_id, const char* sub_path); uint32_t zptDemandLoadNamedModule( Zcu* zcu, uint32_t importer_module_id, const char* name); uint32_t zptDemandLoadBuiltin(Zcu* zcu); uint32_t navAnalyzeRefLoad(Zcu* zcu, uint32_t nav); bool navValueBodyHasFunc(const Zir* zir, const DeclInfo* decl); uint32_t zptEnsureNavTypeUpToDate(Zcu* zcu, uint32_t nav_idx); bool navQueueValueAnalysis(Zcu* zcu, uint32_t nav_idx); void zptDeferEnumLayout(Zcu* zcu, const Zir* zir, uint32_t enum_inst, uint32_t enum_ty, uint32_t eval_ns, const CtCap* caps, uint32_t caps_len); void zptAnalyzeComptimeUnit(Zcu* zcu, const Zir* zir, uint32_t lookup_ns, uint32_t body_off, uint32_t body_len); // PerThread.zig:createNamespace, PerThread.zig:scanNamespace uint32_t zptCreateNamespace(Zcu* zcu, const Zir* zir, uint32_t container_ty, uint32_t parent_ns_opt, const uint32_t* decls_ptr, uint32_t decls_len); // A test compilation's builtin.test_functions value from Zcu.test_functions, // after the analysis that gave `func_airs`. typedef struct SemaFuncAirList SemaFuncAirList; void zptPopulateTestFunctions(Zcu* zcu, const SemaFuncAirList* func_airs); // ── Sema: src/Sema.zig ────────────────────────────────────────────────────── // Semantic analysis, ZIR -> AIR (sema.c). Compilation's compilationAnalyze // discovers the functions to analyze and lowers each through PerThread. // A declared explicit-tag enum whose resolveEnumLayout waits for its first // layout demand (`InternPool.resolve_enum_layout`): zirEnumDecl only interns // the type, so its tag type and field values come after whatever the // declaration's analysis interns next (the `ptr_nav` a `std.lang.OutputMode` // field access takes). typedef struct PendingEnumLayout { uint32_t enum_ty; const Zir* zir; uint32_t enum_inst; uint32_t eval_ns; CtCap* caps; uint32_t caps_len; } PendingEnumLayout; // A resolved `std.lang.CallingConvention` value, the form Sema.funcCommon // validates and only then hands to `PackedCallingConvention.pack` (ccPack). // `tag` is the active union field's ordinal (its `Tag`); the payload is the // options struct flattened to the two fields `pack` reads -- the byte count // of `incoming_stack_alignment` (0 == `null`, upstream's `orelse 0`) and the // `extra` discriminator. No name pointer: the intern pool's string arena // moves on every intern, so `@tagName(cc)` is fetched where it is printed // (ccTagName). typedef struct { uint32_t tag; uint64_t incoming_stack_alignment; uint32_t extra; } FuncCallconv; // One analyzed function's AIR, tagged with its fully-qualified name and the // InternPool item count at capture time (the reference for AIR Refs). Mirrors // what the reference dump (stage1/test/air_gen.zig) holds per function. typedef struct { char* name; // heap-allocated, NUL-terminated uint32_t func_ip; // InternPool item count when this AIR was produced uint32_t func_decl; // InternPool index of this function's `func_decl` // (or `func_instance` for a generic instance); used by // the interpreter to resolve a `call` callee by exact // identity rather than by (ambiguous) unqualified name. Air air; } SemaFuncAir; struct SemaFuncAirList { uint32_t len; uint32_t cap; SemaFuncAir* items; uint32_t index_len; uint32_t index_cap; uint32_t* index_keys; uint32_t* index_values; uint8_t* index_used; }; // One compilation (compilationInit): the Zcu, its InternPool and the root ZIR. // Upstream's Sema is SemaCtx plus InlineCtx, ZcuSemaBranch and Zcu.ct (the // Zcu's comment says where each kind of state lives). typedef struct { Zcu* zcu; InternPool* ip; const Zir* zir; // Repo-relative path of the root/test file under analysis, or NULL. Given // to the borrowed root file as its `sub_path` so a sibling `@import` // resolves relative to the file's own directory (against cwd = repo root). // NULL takes the root FQN from `comp.config.root_name`. const char* root_sub_path; // Optional distinct main module used by `zig test`. Both pointers are // borrowed for the Zcu lifetime, like `zir`/`root_sub_path`. const Zir* main_zir; const char* main_sub_path; } CompilationCtx; // Result of checking storage modifiers after a Nav's initializer is known. // Kept non-crashing and public so focused tests can cover the illegal cases; // semantic analysis reports a non-OK result as its compile error. typedef enum { SEMA_NAV_MODIFIERS_OK = 0, SEMA_NAV_MODIFIERS_MALFORMED, SEMA_NAV_MODIFIERS_ALIGN_FUNCTION_ALIAS, SEMA_NAV_MODIFIERS_ALIGN_COMPTIME_ONLY, SEMA_NAV_MODIFIERS_ALIGN_FUNCTION_TARGET_UNSUPPORTED, } SemaNavModifierStatus; // Sema's u32 hash: the function-AIR index and its u32 sets. ZIG1_INLINE uint32_t semaU32Hash(uint32_t x) { x ^= x >> 16; x *= 0x7feb352du; x ^= x >> 15; x *= 0x846ca68bu; x ^= x >> 16; return x; } // Inline because the interpreter resolves every call's callee through it. ZIG1_INLINE const SemaFuncAir* semaFuncAirListFind( const SemaFuncAirList* list, uint32_t func_decl) { if (list->index_cap != 0) { uint32_t mask = list->index_cap - 1u; uint32_t slot = semaU32Hash(func_decl) & mask; while (list->index_used[slot]) { if (list->index_keys[slot] == func_decl) return &list->items[list->index_values[slot]]; slot = (slot + 1u) & mask; } return NULL; } for (uint32_t i = 0; i < list->len; i++) if (list->items[i].func_decl == func_decl) return &list->items[i]; return NULL; } typedef struct { uint8_t* tags; // each a (uint8_t)AirInstTag (Air.Inst.Tag is enum(u8)) AirInstData* datas; uint32_t len; uint32_t cap; } AirInstList; uint32_t semaAirInstAppend(AirInstList* l, AirInstTag tag, AirInstData data); // Per-function-body comptime state for inline (comptime-unrolled) control // flow: the comptime-mutable cells backing `alloc_comptime_mut`, plus the // in-flight `break_inline` signal that `block_inline` / `condbr_inline` / // `repeat_inline` use to unroll a loop. Mirrors Sema's comptime_allocs + the // ComptimeBreak error mechanism (a break to an outer inline block propagates // up until the target block consumes it). // The comptime allocations are the analyzing Sema's (Zcu.sema_branch), so a // runtime inline call's frame shares its caller's. SemaCtx's comment maps // each Sema.Block field to its home here or elsewhere. // The deepest nest of capturing switch prongs one InlineCtx frame tracks. #define SWITCH_CAPTURE_DEPTH_MAX 64u // An enclosing capturing switch: its ZIR inst and its merge-block AIR index // (each runtime inline callee gets a fresh InlineCtx). typedef struct { uint32_t switch_inst; uint32_t merge_air; } SwitchCapture; // The Sema.Block fields `check_comptime_control_flow` walks: `parent`, // `runtime_index`, `inline_block` and the `label`'s `zir_block`. Frames live // on the C stack of the handler that makes the sub-block; NULL is the // function body's root block (runtime_index 0, no label, no inline_block). #define INLINE_POST_HOC_MAX 16u typedef struct SemaBlock { struct SemaBlock* parent; uint32_t runtime_index; uint32_t inline_block; // ZIR inst, or UINT32_MAX uint32_t label; // ZIR inst of the label's zir_block, or UINT32_MAX } SemaBlock; // Sema.SwitchOperand's `.loop` arm: the `alloc` holding the operand of the // active dispatch (UINT32_MAX when no capture needs it), at `raw_ty`, which // is a pointer when the switch is by-ref. typedef struct { uint32_t alloc; uint32_t raw_ty; bool is_ref; } SwitchLoopOperand; typedef struct { bool brk; // a comptime break_inline is propagating outward uint32_t brk_target; // ZIR inst index of the target block_inline uint32_t brk_operand; // ZIR ref of the break operand // While analyzing a switch prong that captures the union payload, the // switch's own ZIR inst is temporarily rebound (in inst_values) to the // payload-capture AIR value so `|x|` resolves to it. But a `break` inside // that prong still names the switch inst as its target block, so these let // the break handler recover the real merge-block AIR index: one entry per // enclosing capturing switch, innermost last, as upstream's `Block.label` // chain lets a break reach any enclosing labeled switch. SwitchCapture caps[SWITCH_CAPTURE_DEPTH_MAX]; uint32_t caps_len; // While analyzing a labeled switch (`sw: switch (...) { ... continue :sw // next ... }`), `switch_continue` ZIR emits a dummy AIR `br` before the // eventual loop_switch_br AIR instruction exists. The owning switch // records those placeholders and rewrites them to switch_dispatch after // assembling the SwitchBr payload (mirrors Sema.Merges.extra_insts). bool swc_active; uint32_t swc_switch_inst; // target ZIR switch_block[_ref] uint32_t swc_merge_air; // temporary dummy br target uint32_t swc_raw_ty; // the raw operand's type, the continue operand's U32List* swc_placeholders; // AIR br indices to rewrite // Inside a `comptime { ... }` / block_comptime scope (Sema: // block.semaBlockIsComptime()): calls are forced to comptime evaluation // (`comptime assert(...)` in std.mem.eqlBytes). Save/restore-scoped by // the BLOCK_COMPTIME handler. bool ct_scope; // Mirrors AstGen's `GenZir.is_comptime` (a lexical, AstGen-time-known // bit), not Sema's `block.semaBlockIsComptime()` (a dynamic, // Sema-time-known bit) -- the two differ. `ct_scope` above is zig1's only // mirror of the latter and is set by five writers (BLOCK_COMPTIME's own // body, plus four comptime-forced-call-ARGUMENT-body writers Sema itself // forces comptime with no matching AstGen-time marking -- upstream's // AstGen cannot know a callee's parameter is `comptime` without // resolving the callee's type, which is Sema's job). Only one of those // five -- BLOCK_COMPTIME, astgen.c's mirror of every stock ZIR shape // built via `AstGen.comptimeExpr`'s `"block_scope.is_comptime = true;"` // wrapper within A runtime body (explicit `comptime { }`, a // comptime-required builtin argument, a `comptime`-forced identifier // reference) -- corresponds to a lexical `gz.is_comptime = true` // upstream. `@inComptime()`'s runtime-body arm needs exactly this bit // reject only the lexical case (upstream's AstGen already // does, with `"redundant '@inComptime' in comptime scope"`), not the // other four, where `block.semaBlockIsComptime()` is genuinely `true` and // upstream raises no error at all. Save/restore-scoped only by // BLOCK_COMPTIME, so it is inherited (not reset) across the other four // writers -- exactly mirroring `comptimeExpr2`'s `if (gz.is_comptime) // return expr(...);` fast path, which keeps an already-comptime lexical // scope comptime rather than re-wrapping it. // // Scope, corrected: this bit covers only // lexically-comptime scopes nested inside an otherwise-runtime function // body -- `analyzeBodyInner`'s whole domain. It is not "every lexical // comptime scope upstream can construct": container-level decl bodies // (const/var initializers, decl modifiers), struct/union field-default // and enum tag-value bodies, and generic param/return-type bodies are // all lexically comptime upstream too (astgen.c marks their own GenZir // `is_comptime = true` structurally -- no `block_comptime` instruction, // because there is no runtime-body sibling for a decl's own value/type // body to nest inside), and none of them reach `analyzeBodyInner` at // all -- they are evaluated by `sema_comptime.c`'s `ctEvalBody`, which // carries the identical concept in its own frame's `ct_lexical` (see // its comment in `sema_comptime.c` for the full // classification of its ten entry points). bool ct_lexical; // Minimal `Sema.branch_hint` mirror. Runtime condbr analysis resets this // while analyzing each branch and packs the resulting hint into // Air.CondBr.BranchHints. Explicit @branchHint and implicit @panic cold // both use it; first hint in a branch wins, matching upstream. bool branch_hint_set; uint32_t branch_hint; // Runtime inline call frame (Sema.analyzeCall's is_inline_call with // runtime args): when active, a `ret*` in this body lowers to AIR // `br inline_ret_block, ` instead of AIR `ret` (the // callee body is being inlined into the caller; the result block merges // the returns). Set only by semaAnalyzeRuntimeInlineCall's fresh frame. bool inline_ret_active; uint32_t inline_ret_block; // AIR index of the result block // The unqualified name of the function whose body this frame analyzes, // as an InternPool string index. Sema.zirBuiltinSrc reads the active // function's owner Nav name rather than reconstructing it from an FQN. uint32_t fn_name_ctx; // Sema.Block.Merges (src/Sema.zig), flattened. Every open block's // `results` / `br_list` entries live in this one array, keyed by // `block_inst`; see the `BlockMerge` doc below. // `Sema.InferredAlloc.prongs` // is the same shape keyed by an inferred alloc's own AIR index, so // it shares this array. struct BlockMerge* merges; uint32_t merges_len; uint32_t merges_cap; SemaBlock* blk; // the current Sema.Block; NULL is the root // Sema.Block.is_typeof: inside a `@TypeOf` operand body // (semaAnalyzeTypeofBody), whose instructions are discarded. bool is_typeof; // Sema.post_hoc_blocks: the AIR block ensurePostHoc gives an inline // block a runtime break reaches, keyed by its ZIR inst. Entries live // while that inline block is being analyzed, so the table is bounded // by the nesting of inline blocks. uint32_t post_hoc_zir[INLINE_POST_HOC_MAX]; uint32_t post_hoc_air[INLINE_POST_HOC_MAX]; uint32_t post_hoc_len; } InlineCtx; uint32_t semaU1ShapedLike(InternPool* ip, uint32_t ty); // A pointer VALUE's `Key.Ptr` minus its type: the base address (its tag and // the one or two words that name it) and the byte offset. Every pointer tag // this pool mints carries exactly upstream's `base_addr` payload, so two // pointers with equal keys are the pair `getCoerced` -- which re-types a // pointer carrying `base_addr` and `byte_offset` through unchanged -- would // re-intern as one item at the peer type, and `Value.eql` (`"a.toIntern() // == b.toIntern()"`) calls equal. typedef struct { uint8_t tag; uint32_t a, b; uint64_t off; } IpPtrKey; bool ipPtrKey(const InternPool* ip, uint32_t v, IpPtrKey* k); // `ipIntValueType` for a resolved `inst_values[]` entry, whose single // unsigned range test also rejects every AIR ref and INST_VALUE_UNSET. uint32_t semaInternedIntValueType(const InternPool* ip, uint32_t val); uint32_t semaFloatFromBits( InternPool* ip, uint32_t ty, uint32_t width, CrtU128 res); // Decoded `struct_decl` extended-instruction `small` bitfield. // StructDecl.Small bits: 0 has_captures_len, 1 has_decls_len, 2 // has_fields_len, 3..4 name_strategy, 5..6 layout, 7 has_backing_int, 8 // any_field_aligns, 9 any_field_defaults, 10 any_comptime_fields. Shared by // the inline `struct_decl` handler (semaBodyStructType) and the file-root // populate path (zptEnsureFilePopulated) so the flag decode lives in exactly // one place. typedef struct { bool has_captures; bool has_decls; bool has_fields; uint32_t name_strategy; uint32_t layout; bool has_backing_int; bool any_field_aligns; bool any_field_defaults; bool any_comptime_fields; } StructDeclSmall; bool semaCmpVectorLane( const InternPool* ip, ZirInstTag op, uint32_t lhs, uint32_t rhs); // Sema.analyzeBodyInner's `sema: *Sema` for one body: the ZIR, the AIR and // the inst map it writes, with the per-body Block state zig1 keeps beside // them (type_name_ctx, ictx). A body analyzed against another ZIR or return // type gets its own copy. Where zig1 keeps each field of upstream's // Sema.Block (src/Sema.zig): // parent, runtime_index, inline_block, label.zir_block // SemaBlock; InlineCtx.blk is the innermost // sema the SemaCtx every handler takes // namespace SemaCtx.ns in a comptime frame (CtBlockScope.ns); // Zcu.ct.type_resolve_ns for runtime handlers and // ctResolveParamTypeRef // instructions analyzeBodyInner's `block` list // params the param_types array of the walk that meets them // (navFuncDecl, ctResolveFuncTypeFromBody) // label.merges, inlining.merges // InlineCtx.merges, keyed by the block's AIR inst; // Merges.extra_insts is InlineCtx.swc_placeholders // inlining InlineCtx.inline_ret_active and inline_ret_block for a // runtime inline call; a comptime call is a ctEvalBody // frame of its own // comptime_reason InlineCtx.ct_scope, whether it is set only // is_typeof InlineCtx.is_typeof // type_name_ctx, type_fqn_ctx // SemaCtx.type_name_ctx (CtBlockScope.type_name_ctx) // want_safety, float_mode // nowhere: @setRuntimeSafety and @setFloatMode only // check their operand // runtime_cond, runtime_loop, error_return_trace_index, need_debug_scope, // src_baseline nowhere; zig1_error_source is an abort's location // Sema's own fields: inst_map is inst_values, air_instructions and air_extra // are insts and air_extra, owner is owner_nav (Zcu.sema_owner_nav in a // function body), branch_count, branch_quota and comptime_allocs are // Zcu.sema_branch's, comptime_break_inst is the Zcu's, and post_hoc_blocks // and branch_hint are InlineCtx's. typedef struct { InternPool* ip; const Zir* zir; uint32_t fn_ret_ty; AirInstList* insts; uint32_t* inst_values; // The type of each inst_values entry, by ZIR instruction: zig1's stand-in // for upstream's `sema.typeOf(ref)`, which asks the InternPool for an // interned ref and Air.typeOfIndex for an AIR ref. An entry is // authoritative only where inst_values holds an AIR ref: a handler may // leave a comptime value's entry unset (zirInt), and ctAnalyzeInst fills // its operands' entries from their values. Which typeOf to use when: // - a ZIR ref and its resolved value: semaResolveRefValType, sema.typeOf // itself; semaCmpOperandTypeOrNone is the comparison handlers' partial // twin of it; // - an operand known to be an AIR ref, or one whose handler wrote its // entry: semaResolveRefType; // - an interned value: ipTypeOf, or ipComptimeValueType to abort rather // than answer IP_INDEX_NONE; // - a preinterned ref with no pool at hand: ipTypeOfPreinterned; // - a value that may be an interned integer: semaInternedIntValueType. uint32_t* inst_types; U32List* air_extra; Zcu* zcu; U32List* pending_navs; TypeNameCtx type_name_ctx; InlineCtx* ictx; // Block.namespace, Block's `.func`-strategy type-name arguments and // sema.owner's nav_val, in a comptime evaluation's frame (ctFrameInit). uint32_t ns; const struct TypeNameFuncArgs* type_name_func; uint32_t owner_nav; } SemaCtx; void analyzeBodyInner( SemaCtx* sema, U32List* block, const uint32_t* body, uint32_t body_len); // An analyzeBodyInner frame outside a function body, over the caller's // whole-ZIR maps: its own AIR, block and InlineCtx. `sema` points into the // struct, so it stays where Init put it until Deinit, which aborts on queued // pending Navs it has no list to hand to. typedef struct { AirInstList insts; U32List block; U32List extra; U32List scratch_pending; InlineCtx ictx; SemaCtx sema; } SemaDetachedFrame; void semaDetachedFrameInit(SemaDetachedFrame* f, Zcu* zcu, const Zir* zir, uint32_t* inst_values, uint32_t* inst_types, uint32_t fn_ret_ty, TypeNameCtx type_name_ctx); void semaDetachedFrameDeinit(SemaDetachedFrame* f); StructDeclSmall semaDecodeStructDeclSmall(uint16_t small); // zirPtrType's `has_bit_range` operands (bit_offset, then host_size, each // `resolveInt(..., .u16)`) and the host_size != 0 bounds checks, as the // pointer type's packed_offset word. uint32_t semaPtrTypeBitRange( Zcu* zcu, uint32_t elem_ty, uint64_t bit_offset, uint64_t host_size); // Sema.structInitAnon's reified anonymous struct type; `values[i]` is `.none` // (IP_INDEX_NONE) for a runtime field. Shared by both frames. uint32_t semaStructInitAnonType(Zcu* zcu, const Zir* zir, uint32_t inst, TypeNameCtx type_name_ctx, const uint32_t* types, const uint32_t* values, const uint32_t* names, uint32_t fields_len); // Queue one canonical address-taken function owner in the active pending list. // Dedups within the queue; a NULL active list is a no-op. void semaQueueFnPtrPending( InternPool* ip, uint32_t func_ty, uint32_t owner_nav); // `Sema.analyzeArithmetic`'s both-comptime integer branch over operands read // by `Value.toBigInt`: the result type (semaArithResultType), `arith.` // there and `pt.intValue_big`; the result's type is its `ipTypeOf`. uint32_t semaComptimeIntBinOpBig(Zcu* zcu, ZirInstTag tag, BigIntConst a, uint32_t a_ty, uint32_t a_val, BigIntConst b, uint32_t b_ty, uint32_t b_val); uint32_t semaBitpackFieldIndex(InternPool* ip, uint32_t ty, uint32_t name); uint32_t semaReadBitpackFieldValue( Zcu* zcu, uint32_t bitpack, uint32_t field_idx); uint32_t semaPackStructFields( Zcu* zcu, uint32_t struct_ty, const uint32_t* elems, uint32_t elems_len); // Sema.bitCastVal: reinterpret comptime value `val` as `dest_ty` by writing // it to a packed byte buffer and reading it back (Value.writeToPackedMemory / // Value.readFromPackedMemory). Returns IP_INDEX_NONE when the bit sizes // disagree or a leaf shape is not modeled; the caller keeps its loud abort. uint32_t bitCastVal(Zcu* zcu, uint32_t val, uint32_t dest_ty); // `ensureFuncIesResolved` then `funcIesResolvedUnordered`: an inferred error // set's resolved set (IP_INDEX_NONE while unresolved); any other type as is. uint32_t semaResolvedInferredErrorSet( const InternPool* ip, uint32_t error_set); // Sema.zirStructDecl: intern a declared `struct { ... }` type from its // `struct_decl` instruction, name it, create and scan its namespace (parented // at `parent_ns_opt`, else the file root) and store `captures` there; the // fields wait for resolveStructLayout (semaDeferStructFields), except a packed // struct's, which resolve here from `inst_values` (NULL when every field type // is a preinterned primitive). uint32_t semaBodyStructType(Zcu* zcu, const Zir* zir, uint32_t struct_inst, const uint32_t* inst_values, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func, uint32_t parent_ns_opt, const CtCap* captures, uint32_t captures_len); // Coerce a `*const [N]T` uav pointer to a `[]const T` slice value // (Sema.coerceArrayPtrToSlice). Used by the comptime evaluator for // `&.{...}` -> slice argument coercion. uint32_t coerceArrayPtrToSlice( InternPool* ip, uint32_t ptr_uav, uint32_t slice_ty); // `Sema.checkPtrAttributes` (src/Sema.zig) over two pointer type // indices: const / volatile / address-space / alignment / bit-range, with // upstream's own `len0` empty-pointee exception computed from the source type // rather than supplied by the caller. `zcu` is nullable (see // `ztAbiAlignLog2`). A slice type is accepted on either side. bool semaCheckPtrAttributesTy( Zcu* zcu, InternPool* ip, uint32_t dest_ty, uint32_t src_ty); // Retype a comptime `*const [N:s]T` pointer value as `*const [N]T`, preserving // its base address and byte offset (Sema.coerceInMemoryAllowedPtrs + // InternPool.getCoerced). Returns IP_INDEX_NONE when the two types are not // that sentinel-drop relation. Recognized-but-unported element, attribute or // carrier variants stop loudly. Shared by both semantic evaluators. uint32_t semaCoerceSentinelArrayPtrToPlain(InternPool* ip, uint32_t operand, uint32_t operand_ty, uint32_t dest_ty, const char* context); // Sema.zirForLen's "len" string for an indexable input with a length. void semaForLenInternLen(InternPool* ip, uint32_t obj_ty); // Intern an auto-layout `union {}` or `union(enum) {}` type from its // `union_decl` extended instruction. // `inst_values` may be NULL when all field types are preinterned primitives. // The fixed ReleaseSmall seed gives a plain union tag_usage `.none`; a tagged // union uses `.tagged`. Both retain the hypothetical generated enum tag type. // When the union declares members (std.builtin.Type's `pub const Pointer`), // its namespace is created (parented at `parent_ns_opt` when given, else the // file root) and scanned. Every non-primitive payload field stays unresolved // (`.none`) until field/layout demand, so the generated enum is interned // first. // `captures`/`captures_arg_len`: the union's capture descriptors, like // semaBodyStructType's — a `const U = union(E) { ... }` declared inside a body // closes over `E`. When the ZIR declares captures, semaBodyUnionType creates // the union's namespace and registers them there before the tag-argument type // body runs, so its `closure_get` resolves. A plain union carries none and // callers pass NULL/0. uint32_t semaBodyUnionType(Zcu* zcu, const Zir* zir, uint32_t union_inst, const uint32_t* inst_values, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func, uint32_t parent_ns_opt, const CtCap* captures, uint32_t captures_arg_len); // A `struct_decl` or `union_decl` in either frame (`ct` the comptime one, else // NULL): its captures, the {zir, inst, captures} dedup, else the body routine. uint32_t semaDeclaredNamespaceType(Zcu* zcu, const Zir* zir, uint32_t decl_inst, const SemaCtx* ct, const uint32_t* inst_values, const uint32_t* inst_types, uint32_t parent_ns, TypeNameCtx type_name_ctx, bool is_union); // Runtime-body `enum_decl` route: semaBodyEnumType under the active scope. uint32_t semaRuntimeEnumDeclType(Zcu* zcu, const Zir* zir, uint32_t enum_inst, const uint32_t* inst_values, const uint32_t* inst_types, TypeNameCtx type_name_ctx); // Publish declared container `ty` as Nav `nav`'s value before its fields // resolve (sema.c). void semaNavCacheType(InternPool* ip, uint32_t nav, uint32_t ty); // Sema.structFieldPtr's tuple `len`: a `*const usize` uav of the field // count; IP_INDEX_NONE unless `tuple_ty` is a tuple and `field` is "len". uint32_t semaTupleLenRef( InternPool* ip, uint32_t tuple_ty, const char* field, uint32_t* out_ty); // Sema.tupleFieldIndex: a tuple field name's index, bounds-checked. uint32_t tupleFieldIndex( const InternPool* ip, uint32_t tuple_ty, const char* name); // Sema.resolveStdLangEnum's field index (sema.c). uint32_t resolveStdLangEnum(Zcu* zcu, uint32_t value, const char* name); uint32_t resolveAddressSpace(Zcu* zcu, uint32_t value); // Sema.zirCmpxchg's ordering checks and AIR flags (sema.c). uint32_t semaCmpxchgFlags( Zcu* zcu, uint32_t success_val, uint32_t failure_val); // Sema.zirCmpxchg's element-type checks (float refusal + // checkAtomicOperandType). bool semaCmpxchgElemSupported(Zcu* zcu, uint32_t ty, uint64_t* out_align); // Resolve both operands of a ZIR_INST_MERGE_ERROR_SETS `lhs || rhs` before // calling `semaMergeResolvedErrorSetTypes`, mirroring Sema.zirMergeErrorSets // (src/Sema.zig): Both operands' shapes are validated first (each // must be ZIR_REF_ANYERROR_TYPE, IP_TAG_TYPE_ERROR_SET, or // IP_TAG_TYPE_INFERRED_ERROR_SET; anything else aborts via `context`) -- // upstream's analyzeAsType + zigTypeTag(.error_set) checks (Sema.zig: // 7708-7716) run before the "merged with anyerror is anyerror" // short-circuit, so `anyerror || ` must abort loudly here // rather than silently collapsing to anyerror. Only after that does the // anyerror short-circuit run, then forcing an inferred operand's owner // function body via `ensureFuncIesResolved` (Sema.zig). `*lhs`/ // `*rhs` are updated to their resolved forms in place; returns true when // either operand is (or resolves to) anyerror, in which case the caller // should use ZIR_REF_ANYERROR_TYPE directly rather than calling // `semaMergeResolvedErrorSetTypes`. Defined in sema.c (needs the // inferred-error-set resolver installed there); shared by sema_comptime.c's // ctResolveParamTypeRef ZIR_INST_MERGE_ERROR_SETS arm and this file's // analyzeBodyInner runtime-body arm. bool semaResolveMergeErrorSetOperands( const InternPool* ip, uint32_t* lhs, uint32_t* rhs, const char* context); // zirArrayCat's `resolved_elem_ty` and `res_sent_val`, both frames. uint32_t semaArrayCatResolvedElemType(Zcu* zcu, const uint32_t elem_ty[2]); uint32_t semaArrayCatResultSentinel( Zcu* zcu, const uint32_t sent[2], uint32_t res_elem); // Sema.analyzeErrUnionPayloadPtr's comptime half (`ptrEuPayload`); false // when `p` is not an interned pointer, `*out` IP_INDEX_NONE on an error. bool semaErrUnionPayloadPtrComptime( Zcu* zcu, InternPool* ip, uint32_t p, uint32_t* out); // Sema.addNullTerminatedStrLit: the `*const [len:0]u8` uav of a name string. uint32_t addNullTerminatedStrLit( InternPool* ip, uint32_t name_str, uint32_t* out_ptr_ty); // Sema.fieldVal's `.type` arm for a container type operand (an error set's // `.err`, an enum's tag or member, a namespace member). bool semaComptimeNamespaceFieldVal(Zcu* zcu, const Zir* access_zir, uint32_t container_ty, uint32_t field_name, uint32_t* out_value, uint32_t* out_type, uint32_t* out_nav, const char* no_namespace_msg); uint32_t semaSwitchEnumLiteralItem( Zcu* zcu, const Zir* zir, uint32_t item_ty, uint32_t name); // `unionFieldVal`'s comptime `tag_matches`: union value `un`'s payload when // `field_idx` is its active field, else upstream's compile error. uint32_t semaUnionActivePayload(Zcu* zcu, uint32_t un, uint32_t field_idx); // `Sema.unionFieldPtr`'s comptime half, not initializing: an `.auto` union's // field must be the active one, then `union_ptr_val.ptrField(field_index)`. uint32_t semaUnionFieldPtrComptime( Zcu* zcu, uint32_t optr, uint32_t union_ty, uint32_t name); // `Type.unionTagFieldIndex` of a union value's tag. uint32_t semaUnionFieldIndexFromTag( InternPool* ip, uint32_t union_ty, uint32_t tag_idx); // Sema.fieldVal's two pointer-shaped prongs (`.pointer` with size `.slice`, // and `.array` reached through a single pointer), shared by both evaluators' // comptime nav-deref dispatch. `value` is the dereferenced Nav value and // `value_ty` its type (upstream's `object_ty` inside the `fieldVal` call that // `fieldPtrLoad` makes on the loaded pointer); `value_ty` may be // IP_INDEX_NONE. Returns false when the value is not pointer-shaped, leaving // the caller's remaining arms to run; the three messages let each caller keep // its own abort wording. bool semaPtrValueFieldVal(InternPool* ip, uint32_t value, uint32_t value_ty, const char* field, uint32_t* out_value, uint32_t* out_type, const char* slice_field_msg, const char* array_ptr_msg, const char* array_field_msg); // True when `src_ty`/`dest_ty` are both slice types whose only difference is // a sentinel dropped on the destination (same child, same pointer flags, // source has a sentinel, destination has none). bool semaSliceTypesDifferOnlyBySentinel( const InternPool* ip, uint32_t src_ty, uint32_t dest_ty); // Sema.coerceExtra's array-to-array arm (anchor `"Array coercions are allowed // only if the child is IMC and the sentinel is unchanged or removed."`) into // Sema.coerceArrayLike, for a comptime array value. Shared by both evaluators' // coercion dispatch. Returns IP_INDEX_NONE when the shape does not match, so // each caller keeps its own fall-through. uint32_t semaCoerceArrayValueToArray(Zcu* zcu, InternPool* ip, uint32_t operand, uint32_t operand_ty, uint32_t dest_ty); bool semaTagIsFloat(uint8_t tag); // `Sema.validateArrayInitTy`'s element-COUNT rule, shared by the runtime // array-init arms and the comptime fold's (intern_pool.c). Aborts on a // mismatch: `ipGetAggregate` takes its element count from the type and indexes // the caller's `elems[]` to it, so a short operand list reads out of bounds. void validateArrayInitTy( const InternPool* ip, uint32_t init_count, uint32_t ty); // The file this ZIR belongs to, or the root file. uint32_t semaZcuFileIdxOfZir(const Zcu* zcu, const Zir* zir); // Coerce a non-negative comptime-int magnitude `n` to the fixed-width integer // destination type `dest_ty`, interning the resulting int value (dedup-safe). // Returns IP_INDEX_NONE for an unsupported destination. Defined in // sema.c; used by sema_comptime.c to resolve a typed const's value. uint32_t semaCoerceComptimeIntLiteral( InternPool* ip, uint64_t n, uint32_t dest_ty); // Sema.zirInplaceArithResultTy (`op` a ZirInplaceOp). uint32_t semaInplaceArithResultTy( const InternPool* ip, uint32_t lhs_ty, uint16_t op); // resolveEnumLayout's implicit field values: each IP_INDEX_NONE in // `values[0..n]` becomes 0 for the first field, else the previous plus one. void semaEnumImplicitValues( Zcu* zcu, uint32_t int_ty, uint32_t* values, uint32_t n); // `Sema.fieldType` for a container type and an interned field name. uint32_t fieldType(Zcu* zcu, uint32_t aggregate_ty, uint32_t field_name); uint32_t semaCoerceIntToFloat( InternPool* ip, uint32_t operand, uint32_t dest_ty); // Integer bit-width + signedness for an int-like type: fixed-width int tags, // usize/isize, and C ABI integer preinterned types. Defined in // sema.c; shared with sema_comptime.c's `@typeInfo` dispatcher. // Sema.resolvePeerTypes over the argument list of `@TypeOf(a, b, ...)`: // `PeerResolveStrategy.select` classifies every peer, `merge` collapses them // to one strategy, and `resolvePeerTypesInner`'s arm for that strategy // answers. `vals[i]` is peer `i`'s comptime value, or IP_INDEX_NONE when it // has none -- upstream's `peer_vals: []?Value`. Declines loudly on a strategy // zig1 has not ported and with COMPILE_ERROR_UPSTREAM on upstream's // `.conflict`. Shared by the comptime (sema_comptime.c ctEvalBody) and runtime // (analyzeBodyInner) arms. // `tys`/`vals` are the caller's scratch arrays and are mutated in place, as // upstream mutates its arena `peer_tys`/`peer_vals` slices. uint32_t semaTypeofPeerResolve( Zcu* zcu, uint32_t* tys, uint32_t* vals, uint32_t len); // The `comptime_int` carrier ladder for a folded word plus its sign; see // sema.c. Every frame that interns a comptime_int fold uses it, so // `-2` is one InternPool index and not two. uint32_t semaInternComptimeIntWord(InternPool* ip, uint64_t w, bool negative); // Signed coercion companion. Returns IP_INDEX_NONE when the destination cannot // represent the value or zig1 cannot intern that signed shape yet. uint32_t semaCoerceComptimeIntI64Literal( InternPool* ip, int64_t v, uint32_t dest_ty); // The same rendering for a compile-error message, where a container zig1 has // not named yet renders as "" instead of stopping. void semaFormatTypeNameDiagnostic( InternPool* ip, uint32_t ty, TypeNameWriter* w); // Sema.analyzeErrUnionCode's comptime fold. uint32_t semaErrUnionCodeComptime(InternPool* ip, uint32_t eu); // Pointer flags of a direct Nav pointer (Sema.analyzeNavRefInner). uint32_t semaNavDirectPtrFlags(const IpNav* nav); // `Sema.checkSentinelType`: `"if (!ty.isSelfComparable(zcu, true)) return // sema.fail(block, src, \"non-scalar sentinel type '{f}'\", ...)"`, asked by // every pointer and array sentinel of both frames. void checkSentinelType(const InternPool* ip, uint32_t elem_ty); // The sentinel of Sema.zirPtrType / zirArrayTypeSentinel: coerced to the // element type (the range/type check), defined, no comptime-var reference, // `checkSentinelType`. `zcu` is nullable, as coerceExtra's. uint32_t semaSentinelValue( Zcu* zcu, InternPool* ip, uint32_t raw, uint32_t elem_ty); // Sema.validateArrayElemType. void validateArrayElemType(const InternPool* ip, uint32_t elem_ty); // Sema.zirTypeInfo's `ret_ty_is_generic` (`T` or `!T` generic). bool semaFnRetTyIsGeneric(const InternPool* ip, uint32_t rt); // `Sema.analyzeCmp`'s comptime answer (`cmpNumeric` / `cmpSelf` -> // `compareAll` -> `compareScalar`) for two interned operands, shared by both // frames: `.bool_true`, `.bool_false` or `.undef_bool`; aborts loudly on a // pair it does not model. uint32_t semaCmpComptime(Zcu* zcu, ZirInstTag op, uint32_t lhs, uint32_t rhs); // `Sema.maybeConstantUnaryMath` for `@sqrt` ... `@round` on a comptime // operand (a vector per lane), shared with the interpreter. uint32_t maybeConstantUnaryMath( Zcu* zcu, ZirInstTag tag, uint32_t val, uint32_t ty); // Value.elemValue / fieldValue: element `index` of a comptime aggregate, or an // `undefined` of `elem_ty` when the whole aggregate is `undefined`. uint32_t semaAggregateElemValueOrUndef(InternPool* ip, uint32_t aggregate, uint32_t elem_ty, uint32_t index, const char* ctx); // Sema.resolveConstStringIntern: coerce a comptime value to `[]const u8` and // intern its bytes as a NullTerminatedString (sliceToIpString). uint32_t resolveConstStringIntern(Zcu* zcu, uint32_t val); // Sema.analyzeTupleCat of two comptime-known tuple values. uint32_t semaTupleCatComptime(InternPool* ip, uint32_t lhs, uint32_t rhs); // Sema.analyzeMinMax over comptime-known operands (ctEvalBody). uint32_t semaMinMaxComptime( Zcu* zcu, bool is_max, const uint32_t* ops, uint32_t n); // Sema.coerce's function arm: `operand` re-typed at the coercible function // type `dest_ty`, or IP_INDEX_NONE when that is not this coercion. uint32_t semaCoerceFuncValue( InternPool* ip, uint32_t operand, uint32_t dest_ty); // analyzeSwitchCaptures' error-set capture type for a prong's `items`. uint32_t semaSwitchErrCaptureTy( InternPool* ip, const uint32_t* items, uint32_t items_len); // Sema.validateSwitchBlock's duplicate-item/invalid-range pass, shared by // both evaluators' switch ports. `item_vals` holds one entry per resolved // item in ZIR order (IP_INDEX_NONE for a `_` item, which upstream does not // validate); `range_vals` holds `2 * ranges_len` bounds, first then last. // Aborts with `duplicate switch value` on a repeat. void semaSwitchValidateItems(Zcu* zcu, uint32_t item_ty, const uint32_t* item_vals, uint32_t items_len, const uint32_t* range_vals, uint32_t ranges_len); // Sema.validateSwitchBlock's "Validate for missing special prongs." pass, // run after semaSwitchValidateItems over the same resolved items; returns // `else_err_ty` (IP_INDEX_NONE: upstream's `null`, or not an error set). uint32_t semaSwitchValidateSpecialProngs(InternPool* ip, uint32_t item_ty, uint32_t operand_ty, const uint32_t* item_vals, uint32_t items_len, const uint32_t* range_vals, uint32_t ranges_len, bool has_else, bool has_under, bool else_is_simple_noreturn); // `Sema.coerceEnumToUnion`'s comptime-known branch: a comptime enum tag // coerced to a tagged union. Defined in sema.c; shared with sema_comptime.c's // decl_literal coercion. The union's payload field types must already be // resolved. uint32_t coerceEnumToUnion( InternPool* ip, uint32_t union_ty, uint32_t enum_tag); // Type.ptrInfo(...).flags.is_const for a resolved pointer value. Returns false // for non-pointer values so callers can retain their existing // routing. bool ztPtrIsConst(const InternPool* ip, uint32_t ptr); bool semaOpvPtrInfo( InternPool* ip, uint32_t ptr, uint32_t* out_child, uint32_t* out_value); bool semaOpvRootPtrInfo( InternPool* ip, uint32_t ptr, uint32_t* out_child, uint32_t* out_value); // Resolve lazily stored aggregate field types before applying the same // classifier. Unknown after this pass is a malformed/by-value-cycle result. ZtComptimeOnlyStatus ztComptimeOnlyStatus(Zcu* zcu, uint32_t ty); bool ztComptimeOnly(Zcu* zcu, uint32_t ty); // Sema.structFieldPtr on a comptime single pointer to a struct or tuple, by // name: a comptime field's `.comptime_field` pointer, else `Value.ptrField`. // IP_INDEX_NONE for any other pointee. uint32_t semaStructFieldPtrComptime(Zcu* zcu, uint32_t struct_ptr, const char* field, uint32_t field_ip, uint32_t* out_ty); // analyzeSwitchPayloadCaptureTaggedUnion's by-val capture of a comptime // operand's active `payload`: `capture_ty`'s one possible value, else the // payload coerced to it; typed `*out_ty`. uint32_t semaSwitchByValCapture(Zcu* zcu, uint32_t union_ty, const uint32_t* item_tags, uint32_t n_items, uint32_t payload, uint32_t* out_ty); // analyzeSwitchPayloadCaptureTaggedUnion's by-ref capture on a comptime // operand pointer, typed `*out_ty`. uint32_t semaSwitchByRefCapturePtr(Zcu* zcu, uint32_t operand_ptr, uint32_t operand_ptr_ty, uint32_t union_ty, const uint32_t* item_tags, uint32_t n_items, uint32_t* out_ty); // Sema.zirSliceSentinelTy over the lhs pointer type. uint32_t semaSliceSentinelTy(const InternPool* ip, uint32_t lhs_ptr_ty); // Sema.analyzeSlice on comptime-known operands (`end` / `sentinel` // IP_INDEX_NONE for `.none`). uint32_t semaAnalyzeSliceComptime(Zcu* zcu, uint32_t ptr_ptr, uint32_t start, uint32_t end, uint32_t sentinel, bool by_length); // `Sema.coerceExtra` for a comptime-known operand: the interned `operand` // coerced to `dest_ty`, declining loudly what it does not model. // `zcu` is nullable: without it the layout-resolving predicates decline. uint32_t semaCoerceComptime( Zcu* zcu, InternPool* ip, uint32_t val, uint32_t dest_ty); // `CoerceOpts.report_err = false`: IP_INDEX_NONE (upstream's // `error.NotCoercible`) where the plain entry aborts. uint32_t semaCoerceExtraOpts( Zcu* zcu, InternPool* ip, uint32_t val, uint32_t dest_ty, bool report_err); // `Sema.ptrCastFull`'s type validation ahead of both halves (`flags`: a // Zir.Inst.FullPtrCastFlags word); every refusal is COMPILE_ERROR_UPSTREAM. void semaPtrCastFullCheck(Zcu* zcu, InternPool* ip, uint32_t operand_ty, uint32_t dest_ty, uint32_t flags); // Sema.zirRefDeref for a comptime operand: a C pointer as the single // pointer; IP_INDEX_NONE for a null one. uint32_t semaRefDerefComptime( InternPool* ip, uint32_t operand, uint32_t operand_ty, uint32_t* out_ty); // Sema.zirCoercePtrElemTy's coercion destination; IP_INDEX_NONE for none. uint32_t semaCoercePtrElemTyWant( InternPool* ip, uint32_t ptr_ty, uint32_t val_ty); bool coerceInMemoryAllowed(Zcu* zcu, InternPool* ip, uint32_t dest_ty, uint32_t src_ty, bool dest_is_mut); // `Sema.ptrCastFull`'s `ct:` block for a comptime-known `@ptrCast` operand. uint32_t semaPtrCastComptime( Zcu* zcu, uint32_t operand, uint32_t operand_ty, uint32_t dest_ty); // Sema.makePtrTyConst: `ptr_ty` with `is_const` set and every other key field // (alignment, sentinel, packed offset, ...) kept. uint32_t makePtrTyConst(InternPool* ip, uint32_t ptr_ty); // Slice-only type derivation; `empty_window` is the established operation // bound proof that permits a non-sentineled array's one-past end pointer. uint32_t semaSliceElemPtrType(Zcu* zcu, uint32_t parent_ptr_ty, uint32_t index, uint32_t* out_elem_ty, bool empty_window); // Sema.elemPtrVector's own pointer-type derivation for `&v[index]`: the index // lives in the pointer type's `vector_index` and `host_size` counts elements. // See the definition for why Type.elemPtrType cannot serve a vector. uint32_t semaElemPtrVectorType( Zcu* zcu, uint32_t vector_ptr_ty, uint32_t index, uint32_t* out_elem_ty); uint32_t semaPayloadPtrType( InternPool* ip, uint32_t parent_ptr_ty, uint32_t payload_ty); uint32_t semaStructFieldComptimeValue( Zcu* zcu, uint32_t struct_ty, uint32_t field_index); // Sema.analyzeLoad for a comptime-known pointer; IP_INDEX_NONE for a runtime // load. uint32_t semaAnalyzeLoadComptime(Zcu* zcu, uint32_t ptr, uint32_t* out_ty); uint32_t maybeDerefSliceAsArray(Zcu* zcu, uint32_t slice_val); bool semaFieldValByType(InternPool* ip, uint32_t agg, const char* field, uint32_t* out_val, uint32_t* out_ty); uint32_t semaReinterpretStore(Zcu* zcu, uint32_t host_val, uint32_t host_ty, uint32_t store_ty, uint64_t offset, uint32_t store_val); SemaCastStatus castMemory(Zcu* zcu, uint32_t val, uint32_t dest_ty, uint64_t byte_offset, uint32_t* out); // `analyzeSlice`'s comptime result: `ptr_val.ptrElem(start)` then // `getCoerced(new_ptr_val, return_ty)`. uint32_t semaPtrElemCoerced( Zcu* zcu, uint32_t ptr, uint32_t start, uint32_t return_ty); uint32_t semaElemPtrComptime( Zcu* zcu, uint32_t ptr, uint64_t index, bool one_layer, uint32_t* out_ty); uint32_t semaElemValArrSent( const InternPool* ip, uint32_t ptr_ty, uint64_t index); // Sema.elemVal's pointer arm on a comptime pointer; IP_INDEX_NONE for a // runtime load. uint32_t semaElemValComptime( Zcu* zcu, uint32_t ptr, uint64_t index, uint32_t* out_ty); // `Zcu.sema_branch`: start a new Sema's state; semaBranchLeave frees its // comptime allocations and arena and restores the caller's. ZcuSemaBranch* semaBranchEnter(Zcu* zcu, ZcuSemaBranch* fresh); void semaBranchDeinit(ZcuSemaBranch* b); // `sema.arena.alloc`: memory freed when the analyzing Sema ends. void* semaArenaAlloc(Zcu* zcu, size_t size); // Sema.getComptimeAlloc: comptime allocation `idx` of the analyzing Sema. ComptimeAlloc* getComptimeAlloc(Zcu* zcu, uint32_t idx); void semaBranchLeave(Zcu* zcu, ZcuSemaBranch* saved); void semaChargeBranches(Zcu* zcu, uint32_t n); void zirDisableInstrumentation(Zcu* zcu); void semaSetEvalBranchQuota(Zcu* zcu, uint32_t quota); // Sema.zirTypeofBuiltin / zirTypeofPeer from the comptime frame: the body is // analyzed in the runtime frame's non-comptime block; returns the type. uint32_t semaTypeofRuntime(Zcu* zcu, const Zir* zir, uint32_t inst, const uint32_t* ct_values, const uint32_t* param_tys, uint32_t fn_ret_ty); bool semaPatternToBigInt(const InternPool* ip, uint64_t lo, uint64_t hi, uint32_t dest_ty, uint64_t* space, BigIntConst* out); // One lane of a comptime numeric cast: `val` to the scalar `dest_ty`; // IP_INDEX_NONE where the lane has no carrier. typedef uint32_t (*SemaLaneCast)( InternPool* ip, uint32_t val, uint32_t dest_ty); uint32_t semaCastLanes( InternPool* ip, uint32_t val, uint32_t dest_ty, SemaLaneCast lane); // resolveUnionLayout's field align bodies and the generated tag's value // bodies (resolveEnumLayout's, `zir_index` mapped to the tag type): recorded // at declaration, evaluated on the union's first layout demand. The state // names the phase under way, so a body that demands its own union's layout // is upstream's dependency loop. typedef enum { UNION_LAYOUT_PENDING, UNION_LAYOUT_VALUES, UNION_LAYOUT_TYPES, UNION_LAYOUT_ALIGNS, UNION_LAYOUT_DONE, } UnionLayoutState; typedef struct UnionLayoutDeferred { uint32_t union_ty; uint32_t ns; const Zir* zir; uint32_t decl_inst; uint32_t fields_len; // Per field: align body offset and length, value body offset and length // (length 0: none). uint32_t* bodies; bool any_aligns; bool any_values; // The tag's integer type, or `.none` for `tag_int_bits`/`signed` to make. uint32_t tag_int_ty; uint32_t tag_int_bits; bool tag_int_signed; UnionLayoutState state; } UnionLayoutDeferred; #define UNION_LAYOUT_BODY_WORDS 4u bool semaIsRuntimeMutableDataNavPtr(InternPool* ip, uint32_t value); void semaContainerScanDecls(Zcu* zcu, uint32_t parent_ns, uint32_t owner_ty, const uint32_t* decls_ptr, uint32_t decls_len, const Zir* zir, const CtCap* captures, uint32_t captures_len, void (*set_ns)(InternPool*, uint32_t, uint32_t)); uint32_t semaEnumDeclType(Zcu* zcu, const Zir* zir, uint32_t enum_inst, TypeNameCtx name, uint32_t name_nav, uint32_t eval_ns, uint32_t* out_fields_len, const uint32_t** out_decls_ptr, uint32_t* out_decls_len, const CtCap* captures, uint32_t captures_arg_len, uint32_t layout_ty); void semaEnumDeclResolveLayout(Zcu* zcu, const Zir* zir, uint32_t enum_inst, uint32_t enum_ty, uint32_t eval_ns, const CtCap* captures, uint32_t captures_arg_len); // InternPool.PackedCallingConvention.pack, which funcCommon applies to the // validated convention on its way into the interned `type_function`'s Flags. uint32_t ccPack(const FuncCallconv* cc); // Sema.funcCommon's calling-convention validation (checkParamType, // checkReturnTypeAndCallConv, Zcu.callconvSupported). void ccValidate(Zcu* zcu, const FuncCallconv* cc, bool is_noinline, uint32_t nparams, uint32_t comptime_bits, const uint32_t* param_types, uint32_t ret_ty, bool is_inferred_error, bool has_body); // Resolve a `func_fancy`'s explicit calling convention (Sema.zirFuncFancy's // `has_cc_body` / `has_cc_ref` arms); `live_values` is the evaluating frame // of a fn-proto type, NULL for a declaration. FuncCallconv ccResolveFuncFancy(Zcu* zcu, const Zir* zir, uint32_t func_inst, const uint32_t* block_body, uint32_t block_body_len, uint32_t ns, TypeNameCtx type_name_ctx, const uint32_t* live_values); // Sema.lookupInNamespace then namespaceLookup's access check: whether `name` // is in namespace `ns_idx` (its Nav in *out_nav); a private member looked up // from another file is upstream's "not marked 'pub'" compile error. bool semaNamespaceLookupAccessible(Zcu* zcu, const Zir* access_zir, uint32_t ns_idx, uint32_t name, uint32_t* out_nav); // Intern an inline enum_decl extended instruction as a type value, for the // type and comptime paths and runtime function-body lowering. uint32_t semaBodyEnumType(Zcu* zcu, const Zir* zir, uint32_t enum_inst, uint32_t ns, const uint32_t* values, const uint32_t* runtime_values, const uint32_t* runtime_types, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func); // Resolve an `error_set_decl` ZIR instruction (a bare `error{...}` literal) // to its named `type_error_set` IP value. Defined in sema.c and // shared by the comptime evaluator (ctEvalBody) and analyzeBodyInner's // runtime-body dispatcher — an inline error-set declaration is always a // comptime type, independent of the enclosing body's runtime/comptime-ness. uint32_t semaResolveErrorSetDeclInst( InternPool* ip, const Zir* zir, uint32_t inst); // Merge two already-resolved error-set IP values (each either a named // `type_error_set` or `ZIR_REF_ANYERROR_TYPE`) per Sema.errorSetMerge // (src/Sema.zig): anything merged with anyerror is anyerror; otherwise // dedup+sort the combined name list and intern. `context` names the // UNIMPLEMENTED abort when an operand is neither. Defined in sema.c // (used by the comptime param-type resolver) and shared by analyzeBodyInner's // runtime-body `merge_error_sets` case. uint32_t semaMergeResolvedErrorSetTypes( InternPool* ip, uint32_t lhs, uint32_t rhs, const char* context); // Resolve one `@import` ZIR instruction (`import_inst`, in `file_idx`'s ZIR) // to the imported file's populated root struct type, writing its namespace // through `out_ns` when non-NULL. Mirrors Sema.zirImport -> Zcu.importFile -> // ensureFilePopulated (src/Sema.zig). Defined in sema.c; shared by // the `const x = @import(...)` Nav path, the fn-signature type resolver, // ctEvalBody's comptime arm and analyzeBodyInner's runtime arm. uint32_t semaImportInstRootType( Zcu* zcu, uint32_t file_idx, uint32_t import_inst, uint32_t* out_ns); void checkVectorElemType(const InternPool* ip, uint32_t elem_ty); // Sema.zirReifyFn: the `@Fn(...)` function type from its four operands. uint32_t semaReifyFn(Zcu* zcu, uint32_t param_types, uint32_t param_attrs, uint32_t ret_ty, uint32_t fn_attrs); // Defined in sema.c; `Sema.zirOpaqueDecl` -- interns the `opaque { // ... }` type an extended opaque_decl instruction declares. Shared by the // comptime fold and runtime-body analysis. uint32_t semaOpaqueDeclType(Zcu* zcu, const Zir* zir, uint32_t inst, const SemaCtx* ct, const uint32_t* inst_values, const uint32_t* inst_types, TypeNameCtx type_name_ctx); // Defined in sema.c; `Sema.zirReifyEnum` over `@Enum`'s four // already-resolved operands plus the declaring ZIR instruction (the dedup // identity). Shared by the comptime fold and runtime-body analysis. uint32_t semaReifyEnumType(Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t r_tag, uint32_t r_mode, uint32_t r_names, uint32_t r_vals, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func, uint32_t parent_nav); // Defined in sema.c; `Sema.zirReifyStruct` over `@Struct`'s five // already-resolved operands. Shared by the comptime fold and runtime-body // analysis. uint32_t semaReifyStructType(Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t rs_layout, uint32_t rs_backing, uint32_t rs_names_ref, uint32_t rs_types_ref, uint32_t rs_attrs_ref, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func, uint32_t parent_nav); // Defined in sema.c; `Sema.zirReifyUnion` over `@Union`'s five // already-resolved operands. Shared by the comptime fold and runtime-body // analysis. uint32_t semaReifyUnionType(Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t r_layout, uint32_t r_arg_ty, uint32_t r_names, uint32_t r_types, uint32_t r_attrs, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func, uint32_t parent_nav); // Defined in sema.c; `Sema.zirStdLangValue`'s StdLangValue -> // std.lang decl path table. False for the two value cases and for an unknown // ordinal. bool semaStdLangValueTypePath( uint16_t value, const char* const** out_path, uint32_t* out_len); // Materialize `.{}` (empty struct init) for a struct type by folding every // field from its declared default into an interned aggregate value. All // defaults must be comptime-known. Defined in sema.c; used by // ctEvalBody's comptime `.{}` and analyzeBodyInner's runtime // STRUCT_INIT_EMPTY_RESULT. uint32_t semaStructInitEmptyAggregate(Zcu* zcu, uint32_t struct_ty); // Sema.zirStructInitEmptyResult's `.{}` of a result type's opt/eu base, // before the coercion to the result type. uint32_t semaStructInitEmptyResultValue(Zcu* zcu, uint32_t result_ty); // Sema.zirExport given its comptime `exported` pointer and `options` // aggregate: validates and records the export. Defined in sema.c. void semaExport(Zcu* zcu, uint32_t exported, uint32_t options); void ccCheckSupportsVarArgs(const FuncCallconv* cc); uint32_t semaTupleDecl(Zcu* zcu, const Zir* zir, uint32_t inst, const uint32_t* values, const uint32_t* runtime_values); uint32_t semaCmpEqRef(ZirInstTag tag, bool eq); uint8_t* semaPackedMemBuf(Zcu* zcu, uint32_t val, uint64_t bits); uint32_t intFromFloat( InternPool* ip, uint32_t val, uint32_t int_ty, bool exact_mode); // Sema.zig:failWithBadMemberAccess: " '' has no member named // ''", a compile error. ZIG1_ATTRIBUTE((noreturn, cold)) void failWithBadMemberAccess(Zcu* zcu, uint32_t agg_ty, uint32_t field_name); // fieldCallBind's "no field or member function named" compile error. ZIG1_ATTRIBUTE((noreturn, cold)) void semaFailWithNoFieldOrMemberFn( Zcu* zcu, uint32_t concrete_ty, uint32_t field_name); uint32_t semaCoerceArrayUavPtrToMany( InternPool* ip, uint32_t operand, uint32_t operand_ty, uint32_t dest_ty); bool semaTupleFieldValueComptime(InternPool* ip, uint32_t tuple_ty, uint32_t index, uint32_t* out_ty, uint32_t* out_value); // ── mutable_value: src/mutable_value.zig ──────────────────────────────────── MutableValue mutableValueInterned(uint32_t ip_index); uint32_t mutableValueIntern(Zcu* zcu, const MutableValue* mv); void mutableValueUnintern( Zcu* zcu, MutableValue* mv, bool allow_bytes, bool allow_repeated); MutableValue* mutableValueElem(Zcu* zcu, MutableValue* mv, uint32_t field_idx); void mutableValueSetElem( Zcu* zcu, MutableValue* mv, uint32_t field_idx, MutableValue field_val); MutableValue mutableValueGetElem( Zcu* zcu, const MutableValue* mv, uint32_t field_idx); uint32_t mutableValueTypeOf(const InternPool* ip, const MutableValue* mv); // The arms of MutableValue.unpackOptional and unpackErrorUnion. typedef enum { MV_UNPACK_UNDEF, MV_UNPACK_NONE, // `.null` or `.err` MV_UNPACK_PAYLOAD, } MutableValueUnpack; MutableValue mutableValueUnpack(const InternPool* ip, const MutableValue* mv, MutableValueUnpack* kind, uint32_t* err_name); // ── comptime_ptr_access: src/Sema/comptime_ptr_access.zig ─────────────────── // comptime_ptr_access.zig's packed arms: the element a packed field pointer // `ptr_ty` reads out of its host value, and the host value a store through // it leaves. IP_INDEX_NONE for an unmodeled leaf or `.exceeds_host_size`. uint32_t semaPackedPtrLoad( Zcu* zcu, uint32_t ptr_ty, uint32_t host_ty, uint32_t host_val); uint32_t semaPackedPtrStore(Zcu* zcu, uint32_t ptr_ty, uint32_t host_ty, uint32_t host_val, uint32_t store_val); // `Sema.loadComptimePtr`: load the comptime-known pointer value `ptr` at its // own pointer type's child. ComptimeLoadResult loadComptimePtr(Zcu* zcu, uint32_t ptr); // `comptime_ptr_access.storeComptimePtr`: store `store_val`, of the // pointer's child type, through the comptime pointer `ptr`. ComptimeStoreResult storeComptimePtr( Zcu* zcu, uint32_t ptr, uint32_t store_val); // `Sema.pointerDeref`: the loaded value, or IP_INDEX_NONE for // `.runtime_load`; every other failure is its compile error (or a loud // decline). The child type is written to `out_type` when non-NULL. uint32_t pointerDeref(Zcu* zcu, uint32_t ptr, uint32_t* out_type); // `Sema.storePtrVal`: store `operand_val` of `operand_ty` through the // comptime-mutable pointer `ptr`; semaStorePtr2Comptime first coerces the // operand to the pointer's child type, as storePtr2 does. void storePtrVal( Zcu* zcu, uint32_t ptr, uint32_t operand_val, uint32_t operand_ty); void semaStorePtr2Comptime(Zcu* zcu, uint32_t ptr, uint32_t operand); // ── type_resolution: src/Sema/type_resolution.zig ─────────────────────────── // Struct layout computation (mutual recursion with ztLayoutInfo for field // ABI sizes/alignments). bool semaComputeStructLayout(Zcu* zcu, InternPool* ip, uint32_t struct_idx, uint64_t* out_size, uint64_t* out_align, uint32_t* order_out, uint32_t* offsets_out); bool resolveStructLayout(Zcu* zcu, InternPool* ip, uint32_t struct_idx); // Resolve all deferred union payload field types in declaration order on first // use, then return the requested one. Exported for sema_comptime.c's comptime // evaluator. Returns IP_INDEX_NONE when no deferred registration exists. uint32_t semaResolveUnionFieldTypeLazy( Zcu* zcu, uint32_t union_ty, uint32_t field_idx); // Resolve every registered deferred payload field of `union_ty`, modeling the // `ensureLayoutResolved(..., .init)` performed before upstream // zirStructInitFieldType returns a field type. void semaResolveAllUnionDeferred(Zcu* zcu, uint32_t union_ty); bool resolvePackedUnionLayout(Zcu* zcu, uint32_t union_ty); uint32_t semaBackingIntType(InternPool* ip, uint32_t ty); // A declared struct's field names, types and aligns, which its first layout // or field demand resolves (resolveStructLayout's declared-struct branch): // semaDeferStructFields records the struct_decl at the mint, and // semaEnsureStructFieldsResolved, a no-op once resolved, resolves them. void semaDeferStructFields( Zcu* zcu, uint32_t struct_ty, const Zir* zir, uint32_t struct_inst); void semaEnsureStructFieldsResolved(Zcu* zcu, uint32_t struct_ty); // PerThread.ensureStructDefaultsUpToDate's analysis: the struct's layout, // then its normal field defaults. void ensureStructDefaultsResolved(Zcu* zcu, uint32_t struct_ty); void semaDeinitTypeResolution(ZcuTypeResolution* tr); // Fold + field-type-coerce struct_ty.field_idx's declared default value, // returning the interned value (or IP_INDEX_NONE if the field has no recorded // default — the caller decides whether that is an error). Wraps the // file-static default side table so ctEvalBody's `.{}` empty-struct-init // (sema_comptime.c) can fill omitted fields without duplicating the // fold/coerce logic. Defined in type_resolution.c. uint32_t semaFieldDefaultValue( Zcu* zcu, uint32_t struct_ty, uint32_t field_idx); // Sema/type_resolution.zig resolvePackedStructLayout's backing integer, once // every field type is set; `explicit_backing_ty` is IP_INDEX_NONE for an // inferred one. void resolvePackedStructLayout( Zcu* zcu, uint32_t struct_ty, uint32_t explicit_backing_ty); // Resolve and validate the complete concrete wasm32 storage layout for every // runtime-representable shape zig1's interpreter accepts. Comptime-only types // require no storage and succeed without forcing an ABI. bool ensureLayoutResolved(Zcu* zcu, uint32_t ty); void semaRegisterFieldDefault(Zcu* zcu, uint32_t struct_ty, uint32_t field_idx, uint32_t ns, const Zir* zir, uint32_t body_off, uint32_t body_len, uint32_t struct_inst); void semaResolveStructFieldAlignment(Zcu* zcu, uint32_t struct_ty, uint32_t field_idx, uint32_t ns, const Zir* zir, uint32_t body_off, uint32_t body_len, uint32_t struct_inst); void semaRegisterUnionFieldDeferred(Zcu* zcu, uint32_t union_ty, uint32_t field_idx, uint32_t ns, const Zir* zir, uint32_t body_off, uint32_t body_len); void semaRegisterUnionLayoutDeferred(Zcu* zcu, UnionLayoutDeferred rec); // ── LowerZon: src/Sema/LowerZon.zig ───────────────────────────────────────── // LowerZon.run: the value of the `.zon` file `file_index` at `res_ty`, or of // its own type when `res_ty` is IP_INDEX_NONE. uint32_t lowerZonRun(Zcu* zcu, uint32_t file_index, uint32_t res_ty); // ── Sema's comptime evaluator: zig1's own ─────────────────────────────────── // ctEvalBody, its cells and captures, and the signature type resolution it // drives (sema_comptime.c). // Value.compareHetero's float side, which every frame that folds a comptime // comparison owes: the NaN rule first (`"if (lhs.isNan(zcu) or // rhs.isNan(zcu)) return op == .neq;"`), then `Value.order` -- whose f128 // carrier is exact for every operand it is given, the INT one included. // `CtNumOperand` holds a float operand already widened to that carrier and an // int one as it was read; `valCmpNumApply` widens the int (exactly) and orders // the pair on compiler_rt's f128 comparison. typedef struct { bool is_float; CrtU128 f; // valid when is_float: the value's f128 bits // The int side in the one form both int readers already answer -- // `semaCmpReadKnownInt` and `ctReadInt`'s `(uint64_t)` -- a sign-extended // two's-complement word plus "is negative". Callers must not pre-convert // it to a magnitude: `valCmpApplySigned` orders this form directly, and // the one place that wants a magnitude derives it with // `valCmpIntMagnitude`. uint64_t n; // valid when !is_float bool neg; } CtNumOperand; // A generic callee's parameter types, `CallArgsInfo.analyzeArg`'s // `"const param_ty = ... sema.resolveInlineBody(block, param_body, ...)"`: // each argument's declared type evaluated with the comptime arguments // before it bound (sema_comptime.c). Init, then per argument ask the provided // type and bind the coerced value; free at the end. typedef struct { uint32_t* param_insts; uint32_t nparams; uint32_t nav_ns; TypeNameCtx type_name_ctx; const Zir* zir; uint32_t* values; } CtCallParamCtx; // A comptime container capture (Sema.getCaptures / InternPool.CaptureValue). // `instruction`/nested captures are resolved eagerly to a value when the // captured instruction is comptime-known (kind CAP_VALUE — `v` is the // interned value). A runtime instruction capture keeps only its // monomorphic type (CAP_RUNTIME_TYPE — `v` is the type), enough for // `@TypeOf(capture)` but not for direct value reads. `decl_val` / `decl_ref` // captures are deferred (kind CAP_DECL_VAL / CAP_DECL_REF — `v` is the // captured Nav index) and materialized lazily at the `closure_get` consumer, // mirroring stock, which stores `.nav_val`/`.nav_ref` (identity) and resolves // the value only when a member actually reads it. Deferral avoids eagerly // evaluating a captured decl that no analyzed member references (e.g. // `MultiArrayList.Slice`'s `Entry`, used only by the never-called `dbHelper`, // whose value hits comptime `array_cat` + `@Struct` reification). The Nav // identity (not the resolved value) is the generic-instantiation dedup key — // distinct decls keep distinct navs (no false merge), and identical // instantiations resolve the same nav (no false split), exactly as stock. typedef enum { CAP_VALUE = 0, CAP_DECL_VAL = 1, CAP_DECL_REF = 2, CAP_RUNTIME_TYPE = 3, } CtCapKind; uint32_t ctFuncTypeComptimeBit(uint32_t param_index); bool ctRetTypeHasComputedAlign(const Zir* zir, uint32_t ref); // Status of a comptime body evaluation (ctEvalBody). typedef enum CtEvalStatus { CT_EVAL_NORMAL, // body fell off the end (no terminator hit) CT_EVAL_RETURN, // `ret*` — function result in *ct_ret_val CT_EVAL_BREAK, // `break*` — target block in *ct_break_block, value in // *ct_break_val CT_EVAL_REPEAT, // `repeat` — re-run the enclosing comptime loop body } CtEvalStatus; bool ctReadInt(const InternPool* ip, uint32_t idx, int64_t* out_val, bool* out_is_ct, uint32_t* out_bits); // The aggregate (array/struct) type of a comptime aggregate value. uint32_t ctAggType(const InternPool* ip, uint32_t agg); bool valIsAggregate(const InternPool* ip, uint32_t v); bool valIsNull(const InternPool* ip, uint32_t val); uint32_t valOptionalValue(const InternPool* ip, uint32_t val); uint32_t ctCoerceToType(Zcu* zcu, uint32_t val, uint32_t dest_ty); // Resolve a container decl's capture descriptors (Sema.getCaptures). One // function for every container decl (zirContainerTrailingWords). CtCap* ctResolveCaptures(Zcu* zcu, const Zir* zir, uint32_t decl_inst, uint32_t lookup_ns, const uint32_t* values, const uint32_t* runtime_values, const uint32_t* runtime_types, uint32_t* out_len); uint32_t ctDedupLookup(const Zcu* zcu, const Zir* zir, uint32_t inst, const CtCap* caps, uint32_t caps_len); void ctDedupInsert(Zcu* zcu, const Zir* zir, uint32_t inst, const CtCap* caps, uint32_t caps_len, uint32_t struct_ty); CtEvalStatus ctEvalBody(SemaCtx* sema, const uint32_t* body, uint32_t body_len, uint32_t* ct_ret_val, uint32_t* ct_break_block, uint32_t* ct_break_val); void ctDeinitTables(CtTables* t); uint32_t ctFileIndexOfZir(const Zcu* zcu, const Zir* zir); // Publish a freshly minted declared struct type into the {zir, inst, // captures} dedup before its fields resolve (sema_comptime.c; InternPool's // `.wip`). void ctDedupPublishWip(Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t ty); void ctCallParamCtxInit( Zcu* zcu, uint32_t nav_idx, uint32_t func_ty, CtCallParamCtx* ctx); void ctCallParamCtxFree(CtCallParamCtx* ctx); uint32_t ctCallProvidedParamType( Zcu* zcu, uint32_t func_ty, uint32_t param_i, CtCallParamCtx* ctx); void ctCallParamCtxBind(CtCallParamCtx* ctx, uint32_t param_i, uint32_t val); // Look `name_str` up through the active analysis-namespace scope chain (the // per-pending-callee override namespace up its parents), then the ZIR file's // root namespace. With no override set this is the file-root lookup (pure). // Defined in sema_comptime.c; used by the runtime DECL_VAL handler so a method // body naming a sibling container member (`growCapacity` inside the ArrayList // instance) resolves. Returns a Nav index or IP_INDEX_NONE. uint32_t semaScopedDeclLookup( InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t name_str); // Resolve `@This()` for the active analysis namespace, falling back to the // owner of the ZIR file namespace when analyzing a root-file body. Mirrors // Sema.zirThis (`block.namespace.owner_type`). Defined in sema_comptime.c. uint32_t semaScopedThisType(Zcu* zcu, const Zir* zir); // Read capture[index] of the active analysis-namespace scope chain (walks // parents). Used by the runtime-body CLOSURE_GET handler to resolve a generic // instantiation's capture (`T` in an ArrayList(u8) method body). Defined in // sema_comptime.c. Returns IP_INDEX_NONE when unset/unresolved. uint32_t semaScopedCaptureGet(Zcu* zcu, uint32_t index); uint32_t semaScopedCaptureNavVal(Zcu* zcu, uint32_t index); uint32_t semaScopedCaptureTypeOfGet(Zcu* zcu, uint32_t index); // Resolve a ZIR type ref/instruction (preinterned ref, int_type, ptr_type, // optional_type, error_union_type) to its InternPool type index. Interns // composite types (dedup-safe). Used to resolve param/return types in // function signatures. uint32_t ctResolveParamTypeRef( InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t ref); // A fn-proto type block in a runtime body, its type refs read from the // frame's comptime-known values. uint32_t semaFuncTypeFromRuntimeBlock(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, const uint32_t* inst_values, TypeNameCtx type_name_ctx); // ctResolveParamTypeRef with `decl_val`/`decl_ref` lookups resolved by walking // `ns` up its parent-namespace chain before the file-root fallback — for a // struct field type naming a decl of the struct's own namespace (`arch: Arch` // inside `std.Target.Cpu`). Defined in sema_comptime.c. Save/restores the // override, so nested resolutions of other structs' fields are correctly // scoped. A container the ref declares is named in `type_name_ctx`. uint32_t ctResolveFieldTypeRefInNs(InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t ref, uint32_t ns, TypeNameCtx type_name_ctx); // Fold a comptime type-expression body (terminating break_inline yields a // type) to its interned type, scoping decl lookups to `ns`. Used for a // tag/param/return type that is a generic type-returning call // (`math.Log2Int(usize)`), and by semaBodyStructType for a generic-container // field type (`nodes: std.ArrayList(Node)`): ctResolveParamTypeRef cannot // handle either from a single ref. Defined in sema_comptime.c. uint32_t ctFoldNavTypeBody(Zcu* zcu, uint32_t nav_idx, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t ns); uint32_t ctFoldComptimeTypeBody(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t ns, TypeNameCtx type_name_ctx); // A struct or union field's type body: folded whole when // ctTypeBodyNeedsComptimeFold says so, else its break operand resolved in // `ns`. uint32_t semaResolveFieldTypeBody(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t ns, TypeNameCtx type_name_ctx); // True when a signature type body (fn parameter, fn return, or struct/union // field) cannot be resolved from its terminating break operand alone and must // be folded whole through the comptime evaluator. One predicate for all three // positions, beside the single-ref resolver whose reach it complements; the // rationale, the shape set, the align placement and the DECL_REF exclusion // are documented at the definition. bool ctTypeBodyNeedsComptimeFold(const Zir* zir, uint32_t ref); // Resolve a function return-type body, folding shapes whose final ref needs // earlier body instructions through the comptime evaluator (computed-align // pointers, direct generic type calls, and comptime-selected switch types). // zcu_per_thread.c's zptAnalyzeFuncBody uses it to re-resolve return types the // way navFuncDecl resolves them. uint32_t ctResolveRetTypeBody(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t break_ref, TypeNameCtx type_name_ctx); // Register a generic container's resolved captures (Sema.getCaptures) under // its namespace `ns`, so a `closure_get` inside a member type/body reads // capture[i] (keyed by the member's analysis namespace == `ns`). Makes an // owned copy of `caps`. Defined in sema_comptime.c; called by // semaBodyStructType after it creates the instantiated struct's namespace and // before it resolves capture-typed fields. void ctStoreStructCapturesCopy( Zcu* zcu, uint32_t ns, const CtCap* caps, uint32_t len); // Resolve `std.lang.....` to its interned type/value (the // std.builtin reflection types, e.g. Type.Pointer.Size). Defined in // sema_comptime.c. uint32_t getStdLangType(Zcu* zcu, const char* const* path, uint32_t n); // Materialize the modeled real `std.lang.Type` union value for `@typeInfo(T)`. // Defined in sema_comptime.c; shared with the runtime ZIR path. uint32_t ctTypeInfoValueForType(Zcu* zcu, uint32_t ti_ty); // Build selected `@typeInfo(S).@"struct"` / `@typeInfo(E).@"enum"` reflection // slices. Defined in sema_comptime.c; reused by sema.c's semaTypeInfoPtrField // AIR path. Loud abort on explicit struct field attrs / packed field_attrs // (see the sema_comptime.c definitions). uint32_t ctBuildFieldNamesSlice(Zcu* zcu, uint32_t ti_src); uint32_t ctBuildFieldTypesSlice(Zcu* zcu, uint32_t ti_src); uint32_t ctBuildFieldAttrsSlice(Zcu* zcu, uint32_t ti_src); uint32_t ctBuildEnumFieldNamesSlice(Zcu* zcu, uint32_t ti_src); uint32_t ctStructLayoutValue(Zcu* zcu, uint32_t ti_src); // Decode the { child, flags } of a pointer/slice type (a slice reads its // inner many-pointer's words). Defined in sema_comptime.c. void ctPtrTypeParts(const InternPool* ip, uint32_t ty, uint32_t* out_child, uint32_t* out_flags); // Materialize the real `std.builtin.Type.Pointer.Attributes` struct value for // a pointer/slice type (Sema.typeInfo's Pointer branch). Defined in // sema_comptime.c. uint32_t ctPtrAttrsValue(Zcu* zcu, uint32_t ptr_ty); uint32_t ctPtrNoDestCastType( InternPool* ip, uint32_t ptr_ty, uint16_t cast_flags); // Sema.coerceCompatiblePtrs' comptime tail: `"return Air.internedToRef((try // pt.getCoerced(val, dest_ty)).toIntern());"` -- re-type a pointer value at // `dest_ty`, keeping its base address and byte offset. Shared by both // evaluators' pointer coercions. uint32_t ctPtrValueRetype(InternPool* ip, uint32_t ptr_val, uint32_t ptr_ty, uint32_t dest_ty, const char* unsupported_msg); uint32_t ctPtrNoDestCastValue(InternPool* ip, uint32_t ptr_val, uint32_t ptr_ty, uint16_t cast_flags, const char* unsupported_msg); // For a generic callee (`func_decl` InternPool index), return the callee's // ZIR, its parameter instruction indices (param_body order), the parameter // count, and its `func`/`func_inferred` instruction. Defined in // sema_comptime.c. Used by the call handler to evaluate `[generic]` // parameter/return type bodies with the comptime arguments bound. Returns // false if not a decodable function decl. Shared Nav owner/declaration // decoders. The work queue and generic-call paths use this one source of // truth for the owning ZIR and func instruction. bool semaNavZir( Zcu* zcu, uint32_t nav_idx, const Zir** out_zir, uint32_t* out_decl_inst); // For a generic callee (`func_decl` InternPool index), return the callee's // ZIR, parameter instruction indices, count, and func instruction. This is a // func_decl wrapper around ctNavFuncZir for the call handler. bool semaCalleeFuncZir(Zcu* zcu, uint32_t func_decl, const Zir** out_zir, uint32_t* param_insts, uint32_t param_insts_cap, uint32_t* out_nparams, uint32_t* out_func_inst); // Comptime-evaluate a generic callee's return-type body with the call's // comptime arguments bound (e.g. `SliceAsBytesReturnType(@TypeOf(slice))`). // `values` = callee-ZIR-indexed map with comptime param values bound; // `param_tys` maps each bound param instruction to its monomorphic type (for // `@TypeOf(param)` on runtime params); `lookup_ns` scopes decl_val lookups. // Returns the interned result type; aborts loudly on unmodeled shapes. // Defined in sema_comptime.c; used by the generic-call return-type resolution. uint32_t ctEvalTypeBody(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t* values, const uint32_t* param_tys, uint32_t lookup_ns, TypeNameCtx type_name_ctx); // Fold a general comptime ZIR body to its terminating value, returning the raw // folded InternPool index (the caller interprets it — e.g. coerces a struct // field default to the field type). `ns` scopes decl_val/type lookups. // `seed_inst`/`seed_val` (UINT32_MAX = none) pre-map one instruction before // the body runs (Sema's resolveStructDefaultsInner result-type mapping for // field defaults). Defined in sema_comptime.c; used by the struct_init // partial-init (field defaults) path. uint32_t ctFoldComptimeBodyValue(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t ns, TypeNameCtx type_name_ctx, uint32_t seed_inst, uint32_t seed_val); // Sema.toConstString on an interned string pointer or slice: copies its bytes // to `buf` (NUL-terminated) and returns the IP string. Defined in // sema_comptime.c. uint32_t ctFieldNamedNameString(InternPool* ip, uint32_t fn_name_v, char* fn_buf, uint32_t fn_buf_cap, uint32_t* out_fn_len); // Sema.zirMemset's and Sema.zirMemcpy's comptime arms on a comptime-mutable // destination, shared by both frames. void semaMemsetComptime(Zcu* zcu, uint32_t dest, uint32_t fill_val); void semaMemcpyComptime( Zcu* zcu, uint32_t dest, uint32_t src, bool check_aliasing); // sema_comptime.c's comptime-cell pointer halves (`Value.ptrField`, // `pointerDeref`, `storePtrVal`) for the runtime frame's `ptr_comptime_alloc` // / `.field` pointers. uint32_t semaComptimeCellFieldPtr(Zcu* zcu, uint32_t ptr, uint32_t name); uint32_t semaComptimeCellStructInitFieldPtr( Zcu* zcu, uint32_t ptr, uint32_t name); uint32_t semaComptimeCellTupleElemPtr(Zcu* zcu, uint32_t ptr, uint64_t index); uint32_t semaComptimeOptionalPayloadPtr(Zcu* zcu, uint32_t ptr); // Sema.newComptimeAlloc and Sema.storeToInferredAllocComptime (sema.c), // shared by both evaluators' comptime allocations. uint32_t newComptimeAlloc(Zcu* zcu, uint32_t ty); uint32_t semaComptimeAllocPtrType( InternPool* ip, uint32_t elem_ty, uint32_t ptr_flags); uint32_t semaAllocExtendedPtrFlags(uint16_t small); // Sema.analyzeAsAlign of a comptime byte alignment, returning its log2. uint32_t analyzeAsAlign(InternPool* ip, uint64_t align, const char* context); uint32_t semaPtrFlagsWithExplicitAlign( InternPool* ip, uint32_t flags, uint64_t align, const char* context); // Sema.analyzeComptimeAlloc: a comptime alloc's root pointer. uint32_t analyzeComptimeAlloc(Zcu* zcu, InternPool* ip, uint32_t elem_ty, uint32_t ptr_flags, uint32_t* out_ptr_ty); uint32_t storeToInferredAllocComptime( Zcu* zcu, uint32_t ptr_flags, uint32_t operand_val); // Comptime-evaluate a call on Sema.analyzeCall's `is_comptime_call` path: // either the enclosing scope forces comptime, or the callee returns a // comptime-only value such as `type`. `arg_vals[0..nargs]` are the bound // argument values (a `type` InternPool index for a `comptime T: type` // parameter, or an interned comptime constant); each is bound to the // corresponding parameter instruction and the callee body is evaluated // (ctEvalBody). Returns the folded result's InternPool index. When supplied, // `out_ret_ty` receives the concrete return type after binding generic return // bodies. `call_fty` is the function type the call is made at (a coerced // function's), or IP_INDEX_NONE for the body's own. Defined in // sema_comptime.c. uint32_t ctAnalyzeComptimeCallTyped(Zcu* zcu, uint32_t func_decl_idx, uint32_t callee_nav, uint32_t call_fty, const uint32_t* arg_vals, uint32_t nargs, uint32_t* out_ret_ty); // Comptime-evaluate an `inline fn` call whose body folds to a comptime-known // value (Sema inlines an `inline fn` at the call site; when its result is // needed to fold a comptime gate the body is evaluated comptime). Binds each // (comptime-known) argument to its parameter and runs the callee body via // ctEvalBody. Aborts loudly if the body reaches a genuinely runtime // instruction. Returns the folded result's InternPool index / preinterned ref. // Writes the concrete return type through out_ret_ty when supplied. Defined in // sema_comptime.c. uint32_t ctAnalyzeInlineCall(Zcu* zcu, uint32_t func_decl_idx, const uint32_t* arg_vals, uint32_t nargs, uint32_t* out_ret_ty); SemaNavModifierStatus semaNavValueModifierStatus( Zcu* zcu, uint32_t nav_idx, bool has_explicit_alignment); // The comptime namespace captures a pushed scope saved (ctPushCaptureScope). typedef struct CtCaptureSet { CtCap* vals; // one CtCap per capture (deferred decls carry their Nav) uint32_t len; } CtCaptureSet; // An interned array, slice or array-pointer argument of a reify builtin as its // element container and range. typedef struct { uint32_t agg; // element container (aggregate/repeated/bytes value) uint32_t off; // first element's index within `agg` uint32_t len; // element count } CtReifyArgView; bool ctIsEnumLiteral(const InternPool* ip, uint32_t v); uint32_t ctValidatedTypeResult( const InternPool* ip, uint32_t rv, const char* ctx); uint32_t* semaInstMapAlloc(const Zir* zir); uint32_t semaResolveRef(uint32_t ref, const uint32_t* inst_values); bool ctIsTypeInfoValue(Zcu* zcu, uint32_t val); const char* semaTypeInfoActiveTagName(Zcu* zcu, uint32_t val); uint32_t semaTypeInfoPayload( Zcu* zcu, uint32_t val, const char* field, uint32_t* out_ty); bool ctValueConformsToType(const InternPool* ip, uint32_t val, uint32_t ty); void ctPushCaptureScope(Zcu* zcu, uint32_t ns, const CtCap* caps, uint32_t len, CtCaptureSet* saved); void ctPopCaptureScope(Zcu* zcu, uint32_t ns, CtCaptureSet saved); uint32_t ctReifiedEnumDedupLookup(const Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t tag_ty, bool nonexhaustive, uint32_t fields_len, const uint32_t* names, const uint32_t* vals); void ctReifiedEnumDedupInsert(Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t tag_ty, bool nonexhaustive, uint32_t fields_len, const uint32_t* names, const uint32_t* vals, uint32_t enum_ty); uint32_t ctReifyNameString(Zcu* zcu, InternPool* ip, uint32_t nslice); CtReifyArgView ctReifyArgView(Zcu* zcu, InternPool* ip, uint32_t v); uint32_t ctReifyArgViewElem( InternPool* ip, CtReifyArgView view, uint32_t index); uint32_t ctEvalBodyScoped(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t* values, uint32_t ns, TypeNameCtx type_name_ctx); TypeNameCtx semaContainerTypeNameCtx(const InternPool* ip, uint32_t ty); // ctEvalBody in a Sema of its own whose Block is `scope`; the break and return // values land in *br_val and *ret_val. CtEvalStatus ctEvalBodyIn(Zcu* zcu, CtBlockScope scope, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t* values, uint32_t* ret_val, uint32_t* br_val); uint32_t ctCoerceIntAnyWidth(InternPool* ip, uint32_t val, uint32_t ty); uint32_t valSliceField(const InternPool* ip, uint32_t slice, uint32_t i); bool ctProjectValueAtOffset(Zcu* zcu, uint32_t value, uint32_t value_ty, uint64_t byte_offset, uint32_t child_ty, uint32_t depth, uint32_t* out_value); // ── Type and Value over the Zcu: src/Type.zig, src/Value.zig ──────────────── // type.c's and value.c's queries that need the Zcu or Sema's state, so are // declared after them; the InternPool-level ones are in the Type and Value // section above, which comes before the Zcu's declarations. bool ztPtrInfoIsSlice(const InternPool* ip, uint32_t ty); bool ztIsNamespaceType(const InternPool* ip, uint32_t ty); bool ztIntInfoInRange( const InternPool* ip, uint32_t dest_ty, uint32_t* bits, bool* is_signed); bool ztSliceManyPtrExtraOff( const InternPool* ip, uint32_t slice_ty, uint32_t* out_off); bool ztPtrInfoAllowsZero(const IpPtrInfo* info); bool ztFnHasRuntimeBits(InternPool* ip, uint32_t fn_ty); bool ztPtrInfoAlignLog2( Zcu* zcu, InternPool* ip, const IpPtrInfo* info, uint32_t* out); bool ztPtrAllowsZero(const InternPool* ip, uint32_t ty); void ztTypeNameWriteStr(TypeNameWriter* w, const char* s); void ztIntTypeMinMax( InternPool* ip, uint32_t ty, uint32_t* out_min, uint32_t* out_max); uint32_t ztPtrFromIntDestInfo(InternPool* ip, uint32_t result_ty, bool* out_optional, uint32_t* out_flags); bool ztLayoutCheckedAdd(uint64_t a, uint64_t b, uint64_t* out); bool ztLayoutCheckedAlign(uint64_t value, uint64_t alignment, uint64_t* out); uint32_t ztLog2PowerOfTwoU64(uint64_t n); bool ztPtrOrSliceInfo(const InternPool* ip, uint32_t ty, IpPtrInfo* out); uint32_t ztElemPtrTypeInner(Zcu* zcu, uint32_t parent_ptr_ty, uint32_t index, uint32_t* out_elem_ty, bool allow_empty_one_past, bool has_index); bool ztIsPtrAtRuntime(const InternPool* ip, uint32_t ty); bool ztTypeNameWriteArgValue( InternPool* ip, uint32_t val, uint32_t level, TypeNameWriter* w); uint32_t ztPtrInfoType(const InternPool* ip, uint32_t ty); uint32_t ztFloatSignificandBits(uint32_t float_bits); uint32_t ztTypeToUnsigned(InternPool* ip, uint32_t ty); // `Type.abiAlignment` in log2 units. `zcu` is nullable: given one, the // aggregate arms resolve layout as upstream's `abiAlignment` does; given // NULL there is no layout pass to lean on, so tuple / union / non-packed // struct decline instead of answering from a possibly-unresolved header. bool ztAbiAlignLog2(Zcu* zcu, InternPool* ip, uint32_t ty, uint32_t* out_log2); bool ztTypeUnpackable(const InternPool* ip, uint32_t ty); // Type.unionTagTypeHypothetical (src/Type.zig): a union type's tag // enum, generated on demand for an untagged union. uint32_t ztUnionTagTypeHypothetical(Zcu* zcu, uint32_t union_ty); // Append a type's fully-qualified name to `w`. Ports the tractable arms of // Type.print (src/Type.zig), the formatter behind `'{f}'` in a diagnostic. // Defined in type.c; used by @typeName and by Sema's return-type // diagnostics. void ztFormatTypeName(InternPool* ip, uint32_t ty, TypeNameWriter* w); // Type.abiSize / abiAlignment / bitSize (wasm32 target) for the types // reachable from @sizeOf/@alignOf/@bitSizeOf. Writes size/align/bits and // returns true, or returns false for an unhandled type. Exposed so the // comptime evaluator (sema_comptime.c ctEvalBody) can fold these builtins over // a bound `comptime T: type`. bool ztLayoutInfo(Zcu* zcu, InternPool* ip, uint32_t ty, uint64_t* size, uint64_t* align, uint64_t* bits); // Type.errorAbiSize / errorAbiAlignment: wasm32 `errorSetBits()` with the // default `error_limit` (maxInt(u16) - 1) is 16, so an error code is a u16. #define ZT_ERROR_ABI_SIZE 2u #define ZT_ERROR_ABI_ALIGN 2u // (part of) Zcu.UnionLayout, alignments in bytes: the words a resolved union // stores. `has_runtime_tag` is upstream's `tag_align != .none`; without a tag, // tag_size is 0 and tag_align 1. abi_align is max(tag_align, payload_align), // so the payload is the more aligned half exactly when abi_align > tag_align, // and a tagged union's payload_size is abi_size - padding - tag_size. typedef struct { uint64_t abi_size; uint64_t abi_align; uint64_t tag_size; uint64_t tag_align; uint32_t padding; bool has_runtime_tag; } ZtUnionLayout; // Type.getUnionLayout of a non-packed union; false when a field or the tag // has no layout. `zcu` is nullable, as ztLayoutInfo's. bool ztGetUnionLayout( Zcu* zcu, InternPool* ip, uint32_t ty, ZtUnionLayout* out); // UnionLayout.tagOffset / payloadOffset: {Tag, Payload} when the tag is at // least as aligned as the payload, else {Payload, Tag}. ZIG1_INLINE uint64_t ztUnionLayoutTagOffset(const ZtUnionLayout* l) { return l->has_runtime_tag && l->tag_align < l->abi_align ? l->abi_size - l->padding - l->tag_size : 0u; } ZIG1_INLINE uint64_t ztUnionLayoutPayloadOffset(const ZtUnionLayout* l) { return l->has_runtime_tag && l->tag_align == l->abi_align ? l->tag_size : 0u; } // Type.structFieldOffset of a non-packed struct, a tuple or a union; aborts // when the type has no layout. `zcu` is nullable, as ztLayoutInfo's. uint64_t ztStructFieldOffset( Zcu* zcu, InternPool* ip, uint32_t ty, uint32_t index); bool ztTypeIsSelfComparable( const InternPool* ip, uint32_t ty, bool is_equality_cmp); // Type.onePossibleValue: the one typed value a ZT_CLASS_OPV type has, or // IP_INDEX_NONE for upstream's `null`. Interns, so `ip` is mutable. Shared by // both ports of Type.structFieldValueComptime's second answer. uint32_t ztOnePossibleValue(InternPool* ip, uint32_t ty); // Type.fieldPtrType for the aggregate/slice lowering paths. Public so focused // metadata tests can verify exact pointer-key preservation independently of // AIR execution. uint32_t ztFieldPtrType( Zcu* zcu, uint32_t parent_ptr_ty, uint32_t field_index); uint32_t ztElemPtrType( Zcu* zcu, uint32_t parent_ptr_ty, uint32_t index, uint32_t* out_elem_ty); // Type.elemPtrType with a NULL `index`: the element index is runtime-known, // so the alignment merge takes upstream's `index orelse 1` and `is_allowzero` // survives. The array's own `array_len_s == 0` rejection still applies. uint32_t ztElemPtrTypeRuntimeIndex( Zcu* zcu, uint32_t parent_ptr_ty, uint32_t* out_elem_ty); // Type.arrayBase and Value.ptrElem. Shared with sema_comptime.c's comptime // cell-pointer chains, which is why they live here: both read `ztLayoutInfo` // and `ztComptimeOnlyStatus`. uint32_t ztIndexableElem(const InternPool* ip, uint32_t ptr_ty); uint32_t ztFieldType(const InternPool* ip, uint32_t ty, uint32_t i); bool ztArrayBase( const InternPool* ip, uint32_t ty, uint32_t* out_base, uint64_t* out_len); bool ztHasWellDefinedLayout(const InternPool* ip, uint32_t ty); // Type.abiSize; UINT64_MAX for a type zig1 does not lay out. uint64_t ztAbiSize(Zcu* zcu, uint32_t ty); bool valCanMutateComptimeVarState(InternPool* ip, uint32_t v); // Value.doPointersOverlap: whether `elem_count` elements at the comptime // pointers `a` and `b` overlap. bool valDoPointersOverlap( Zcu* zcu, uint32_t a, uint32_t b, uint64_t elem_count); bool valCmpApplySigned( ZirInstTag op, uint64_t a, bool a_neg, uint64_t b, bool b_neg); bool valBinReadComptimeBigInt(const InternPool* ip, uint32_t val, uint64_t* space, BigIntConst* out, uint32_t* out_ty); bool valFloatOperandBits( InternPool* ip, uint32_t val, uint32_t width, CrtU128* out); bool valWriteToPackedMemory( Zcu* zcu, uint32_t val, uint8_t* buf, uint64_t buf_bits, uint64_t bit_off); uint32_t valReadFromPackedMemory(Zcu* zcu, uint32_t ty, const uint8_t* buf, uint64_t buf_bits, uint64_t bit_off); // Materialize a value still carrying zig1's untyped `comptime_float` shape at // a concrete float width. uint32_t valComptimeFloatAtWidth(InternPool* ip, uint32_t val, uint64_t bits); SemaCastStatus valWriteToMemory( Zcu* zcu, uint32_t val, uint8_t* buf, uint64_t size); bool valCompareAllWithZero(InternPool* ip, uint32_t val, bool eq); uint64_t valBitCount( const InternPool* ip, ZirInstTag tag, uint32_t val, uint32_t bits); uint32_t valComptimeIntBinOp( Zcu* zcu, ZirInstTag tag, uint32_t lhs, uint32_t rhs); bool valIsNan(const InternPool* ip, uint32_t idx); CtNumOperand valCmpFloatOperand( const InternPool* ip, uint32_t idx, const char* what); bool valCmpNumApply(ZirInstTag op, CtNumOperand a, CtNumOperand b); // `Value.toBigInt`: any interned integer value as a `std.math.big.int.Const`, // whatever carrier the pool chose. `space` is a two-limb scratch the // non-limb-backed carriers are read into (`Value.BigIntSpace` upstream). // False for a value that is not a readable integer. bool valToBigInt( const InternPool* ip, uint32_t idx, uint64_t* space, BigIntConst* out); uint32_t valFloatCast(InternPool* ip, uint32_t val, uint32_t dest_ty); uint32_t valAbs(InternPool* ip, uint32_t val, uint32_t result_ty); // `Sema.intFitsInType`, the gate on Sema.coerceExtra's `.int, .comptime_int` // arm: `"if (!val.intFitsInType(dest_ty, null, zcu))"`, whose failure is the // "type '{f}' cannot represent integer value '{f}'" compile error. The // interning helpers here truncate to the destination width, so a caller that // coerces a comptime-KNOWN integer into a fixed-width type owes this check // first. It is `big.int.Const.fitsInTwosComp` over `Value.toBigInt`: one // question at every carrier, sign and width alike. A non-integer destination, // an unreadable operand and a `comptime_int` destination all answer true. bool valIntFitsInType(InternPool* ip, uint32_t val, uint32_t dest_ty); // Value.intFitsInType's integer rule on a big.int.Const: true at // comptime_int, else fitsInTwosComp at the type's signedness and width; false // for a type that is not an integer type. static inline bool valBigIntFitsInType( const InternPool* ip, BigIntConst v, uint32_t ty) { if (ty == ZIR_REF_COMPTIME_INT_TYPE) return true; uint32_t bits; bool is_signed; return ztIntInfoInRange(ip, ty, &bits, &is_signed) && bigIntFitsInTwosComp(v, is_signed, bits); } // `Sema.zirMulAdd` with all three operands comptime-known (already coerced // to `ty`): undef `mulend2` / `addend` answer `undefRef(ty)`, else // `Value.mulAdd` (a vector per lane). uint32_t valMulAdd(Zcu* zcu, uint32_t ty, uint32_t mulend1, uint32_t mulend2, uint32_t addend); // `Value.numberMin` (`want_min`) / `numberMax` of two comptime scalars. uint32_t valNumberMinMax( const InternPool* ip, uint32_t lhs, uint32_t rhs, bool want_min); uint32_t valPtrElem(Zcu* zcu, InternPool* ip, uint32_t parent_ptr, uint32_t result_ptr_ty, uint32_t elem_ty, uint32_t index); uint32_t valCanonicalizeBasePtr( InternPool* ip, uint32_t base, uint32_t size, uint32_t want_child); // Value.ptrField on a comptime pointer. uint32_t valPtrField(Zcu* zcu, uint32_t parent_ptr, uint32_t field_idx); uint32_t valConstNavStructFieldPtr(Zcu* zcu, uint32_t optr, const char* field, uint32_t field_ip, uint32_t* out_ty); // ── arith over the Zcu: src/Sema/arith.zig ────────────────────────────────── // arith.c's folds that take the Zcu, so are declared after it; the arith // section above comes before the Zcu's declarations. // `Sema.arith.negateFloat`: `-val` for a comptime float or float vector, at // type `ty`. Upstream's `zirNegate` runs this before the `0 - x` integer path // "to ensure negative zero is represented in the bits". Returns IP_INDEX_NONE // for a shape this frame does not model; the caller keeps its loud abort. uint32_t negateFloat(Zcu* zcu, uint32_t ty, uint32_t val); uint32_t incrementDefinedInt(Zcu* zcu, uint32_t ty, uint32_t prev); // ── Compilation: src/Compilation.zig, lib/std/start.zig ───────────────────── // The config of a wasm32-wasi executable rooted at `module_root`, with std // read below `std_root` (NULL: the cwd), whose guest sees `wasi_lib_dir` as // /lib (NULL: `lib`) and whose guest argv is `wasi_argv`, on the bootstrap // profile: zig1.wasm's builtin module, and its `build_options` as a // dependency of the root. CompilationConfig compilationBootstrapConfig(const char* module_root, const char* std_root, const char* wasi_lib_dir, const char* const* wasi_argv, uint32_t wasi_argv_len); // Analyze `fname` as the root of the executable `config` describes, with // `main_fname` as a test compilation's main module (NULL otherwise), and // interpret its `main`; the exit code, or ZIG1_EXIT_COMPILE_ERROR without an // entry point. A NULL module or root name is the file's stem. int compilationRun( const char* fname, const char* main_fname, CompilationConfig config); // `zig1 --run` and `zig1 --test`'s options (main.c), the guest's argv and the // std root they select; zero-initialized, but for is_test, before the parse. typedef struct { const char* fname; const char* lib_dir; const char* module_root; bool is_test; // `--test`, which takes no --bootstrap-profile, --dep or -M bool bootstrap_profile; // The -M modules in order, the root first; the root's --dep edges, which // index the modules after it. CompilationModule* modules; uint32_t modules_len; CompilationDependency* root_dependencies; uint32_t root_dependencies_len; const char* const* wasi_argv; uint32_t wasi_argv_len; const char* global_cache; // the guest's /cache (NULL: none) char* std_root; // set by compilationRunOptionsConfig } CompilationRunOptions; // Takes the options from argv[i..argc) up to and including the root file, // or with -M modules up to and including `--`; returns the first untaken // index. It edits argv in place: the -M and --dep arguments are split at // their '=' and argv[i - 1] becomes the root file, the guest's argv[0]. int compilationRunOptionsParse( CompilationRunOptions* opts, int argc, char** argv, int i); // The config `opts` select: compilationBootstrapConfig with std under // --zig-lib-dir's parent (opts->std_root; NULL: the cwd), the bootstrap // profile with --bootstrap-profile and the airgen profile otherwise; the -M // modules, the root's rooted at its file's directory without --module-root, // or else the bootstrap profile's build_options or none. CompilationConfig compilationRunOptionsConfig(CompilationRunOptions* opts); // Frees what the parse and compilationRunOptionsConfig allocated. void compilationRunOptionsDeinit(CompilationRunOptions* opts); // `zig1 --test`, as `zig test `: the root module is upstream's // /compiler/test_runner.zig and the main module is `opts->fname`, rooted // at its directory or at --module-root, below which its dotted path names it. // Runs on the test profile; the exit code. int compilationRunTest(CompilationRunOptions* opts); // Compilation.update's work queue (performAllTheWork), which Nav // analysis tells of each Nav it enters and publishes; NULL outside one. typedef struct UpdateQueue UpdateQueue; void compilationUpdateQueueNavFirstWant(UpdateQueue* ctx, uint32_t nav); void compilationUpdateQueueNavValueEnter(UpdateQueue* ctx, uint32_t nav); void compilationUpdateQueueNavTypeEnter(UpdateQueue* ctx, uint32_t nav); void compilationUpdateQueueNavValuePrePublish(UpdateQueue* ctx, uint32_t nav); void compilationUpdateQueueNavValuePublished( UpdateQueue* ctx, uint32_t nav, uint32_t value); // A struct's `.struct_defaults` unit: queued on its first layout demand, // cleared when its defaults are analyzed. void compilationUpdateQueueStructLayoutWanted( UpdateQueue* ctx, uint32_t struct_ty); void compilationUpdateQueueStructDefaultsEnter( UpdateQueue* ctx, uint32_t struct_ty); void compilationInit( CompilationCtx* cctx, Zcu* zcu, const Zir* zir, const char* root_sub_path); void compilationDeinit(CompilationCtx* cctx); // Analyze a compilation to completion, returning the AIR of every analyzed // function. The analysis roots are upstream's: std (whose start.zig imports // the root file and exports its entry point), a test compilation's main, then // the root's optional compiler_rt/ubsan_rt/zigc dependencies. SemaFuncAirList compilationAnalyze(CompilationCtx* cctx); void compilationFuncAirListDeinit(SemaFuncAirList* list); // ── AIR interpreter: zig1's own ───────────────────────────────────────────── // The interpreter executes the AIR that sema_comptime.c lowers (byte-identical // to the reference compiler's) as a wasm32-wasi machine (interp.c), its WASI // imports served by the WASI host (wasi.h). Its boundary is one call: the Sema // result in, the exit code out (interpRunMain). // A runtime value. `ty` is its InternPool type index, which supplies the bit // width and signedness needed for wrapping, comparison, and return. // // There are exactly two shapes, and `src/codegen/wasm/CodeGen.zig`'s `isByRef` // picks between them -- `".int => return ty.intInfo(zcu).bits > 64"`, // `".float => return ty.floatBits(target) > 64"`, an aggregate always: // // * by value: `bits` holds an integer/bool/pointer/float zero- or // sign-extended to 64 bits (canonical form: the low `width` bits are the // two's-complement value, sign-extended into the high bits for signed // types, zero-filled for unsigned). // * by reference: `bits` holds the address of the value's `abiSize` // little-endian bytes in the interpreter's memory. Upstream's own wasm // backend has no wider register either -- `lowerConstant`'s `.int` arm is // `unreachable` past 64 bits and `WValue.imm128` is an index -- so every // aggregate and every scalar wider than 64 bits lives in memory here too. // An integer's bytes are canonical: the value sign- or zero-extended // across the whole slot, which is `Const.writeTwosComplement` filling it. typedef struct { uint64_t bits; uint32_t ty; } InterpValue; // The host of one program run: the guest's WASI argv (`argc` strings at // `argv`, which may be NULL only when `argc` is zero), the host directory // the guest sees as /lib (NULL: `lib`) and as /cache (NULL: none), and the // module's recorded `@export`s (Zcu.exports), which resolve extern data Navs // by name. Borrowed for the run. typedef struct { const char* const* argv; uint32_t argc; const char* lib_dir; const char* global_cache; const ZcuExport* exports; uint32_t exports_len; } InterpHost; // Interpret `entry`, the analyzed `main` of the compilation whose InternPool // is `ip` and whose every analyzed function is in `funcs` (a call resolves its // callee there by function identity), as lib/std/start.zig's wasm32-wasi // entry calls it, and return the process exit code (0..255). `entry_args` // holds `entry_nargs` interned constants, the entry's arguments (start.zig // `callMain`; the interpreter materializes them inside the fresh program). // The result is wrapMain's mapping of main's result, or the status the // program passed to proc_exit. The interpreter is single-threaded and not // reentrant: one program runs at a time; it may lazily resolve InternPool // entries (an auto enum's tag type, a union's generated tag) as Sema does. int interpRunMain(InternPool* ip, const SemaFuncAirList* funcs, const SemaFuncAir* entry, const uint32_t* entry_args, uint32_t entry_nargs, const InterpHost* host); // Runtime memory. // Four flat byte-addressed regions live inside the interpreter: frame memory // below 0x70000000, persistent mutable Nav globals in [0x70000000, // 0x78000000), persistent const objects in [0x78000000, 0x80000000), and // interpreted wasm linear memory at/above 0x80000000. A pointer // InterpValue carries one such address in `bits`. Frames are reclaimed per // call; global/const regions and the Nav-address cache survive every nested // call and reset together at the next program. Host buffer capacities are // retained. The frame arena is process-global across programs; each // activation restores only its own allocations. #endif /* ---- end zig1.h ---- */ /* ---- begin air.c ---- */ // air.c — ported from src/Air.zig (declarations in zig1.h). #ifndef ZIG1_H #include "zig1.h" #endif // The one transcription of std.math.CompareOperator's ordinals; see zig1.h. static const struct { AirInstTag scalar_cmp; uint32_t op; } air_vector_cmp_ops[] = { { AIR_INST_CMP_LT, 0 }, { AIR_INST_CMP_LTE, 1 }, { AIR_INST_CMP_EQ, 2 }, { AIR_INST_CMP_GTE, 3 }, { AIR_INST_CMP_GT, 4 }, { AIR_INST_CMP_NEQ, 5 }, }; bool airVectorCmpEncodeOp(AirInstTag scalar_cmp, uint32_t* out) { for (size_t i = 0; i < sizeof(air_vector_cmp_ops) / sizeof(*air_vector_cmp_ops); i++) { if (air_vector_cmp_ops[i].scalar_cmp == scalar_cmp) { *out = air_vector_cmp_ops[i].op; return true; } } return false; } bool airVectorCmpDecodeOp(uint32_t op, AirInstTag* out) { for (size_t i = 0; i < sizeof(air_vector_cmp_ops) / sizeof(*air_vector_cmp_ops); i++) { if (air_vector_cmp_ops[i].op == op) { *out = air_vector_cmp_ops[i].scalar_cmp; return true; } } return false; } // The one transcription of std.lang.ReduceOp's ordinals; see zig1.h. static const struct { AirReduceKind kind; AirInstTag scalar_op; } air_reduce_ops[] = { { AIR_REDUCE_COMBINE, AIR_INST_BIT_AND }, // And { AIR_REDUCE_COMBINE, AIR_INST_BIT_OR }, // Or { AIR_REDUCE_COMBINE, AIR_INST_XOR }, // Xor { AIR_REDUCE_MIN, AIR_INST_CMP_LT }, // Min { AIR_REDUCE_MAX, AIR_INST_CMP_LT }, // Max { AIR_REDUCE_COMBINE, AIR_INST_ADD }, // Add { AIR_REDUCE_COMBINE, AIR_INST_MUL }, // Mul }; bool airReduceOp(uint32_t op, AirReduceKind* kind, AirInstTag* scalar_op) { if (op >= sizeof(air_reduce_ops) / sizeof(*air_reduce_ops)) return false; *kind = air_reduce_ops[op].kind; *scalar_op = air_reduce_ops[op].scalar_op; return true; } // Air.zig:deinit void airDeinit(Air* air) { free(air->inst_tags); free(air->inst_datas); free(air->extra); air->inst_tags = NULL; air->inst_datas = NULL; air->extra = NULL; air->inst_len = 0; air->extra_len = 0; } /* ---- end air.c ---- */ /* ---- begin arith.c ---- */ // arith.c — src/Sema/arith.zig's integer half. One entry point, // `arithIntBinOp`, stands in for upstream's per-op `add`/`sub`/`mul`/`div`/ // `modRem`/`shl`/`shr`/`bitwiseBin` (and their `*Scalar` halves, which zig1 // has no vector frame inside arith to need): it selects on the ZIR tag the // caller already holds, exactly as `Sema.analyzeArithmetic`'s own switch does. // Below it, in upstream's order, sit the `int*` workers -- one per family, // because C cannot cheaply spell `intAdd`/`intSub`/`intMul` as three bodies // over one big-int call: `arithIntAddSubMul` is `intAdd`/`intSub`/`intMul`, // `arithIntWrap` is their `*WithOverflowInner` truncation, `arithIntSat` is // `intAddSat`/`intSubSat`/`intMulSat`, `arithIntDivRem` is // `intDivTrunc`/`intDivFloor`/`intDivExact`/`intMod`/`intRem`, // `arithIntShift` is `intShl`/`intShlSat`/`intShlWithOverflow`/ // `comptimeIntShl`/`intShr`, and `arithIntBitwise` is `bitwiseBinScalar`'s // integer arm. A version hop diffs this file against that one at the level of // those families. // // Every one of them computes on `std.math.big.int` -- `BigIntConst` operands // and a caller-owned `BigIntMutable` result, exactly as upstream's workers // take `Value.toBigInt` operands and write a `BigIntMutable` allocated from // `sema.arena`. There is no width at which this module stops answering. #ifndef ZIG1_H #include "zig1.h" #endif #include #include // The float policy: the host's `float` // and `double` are the f32/f64 carriers, which needs every operation rounded // to its own type. TCC defines no FLT_EVAL_METHOD; on x86_64 it computes on // SSE, which is method 0. Nor need MSVC, which computes on SSE2 on x64, // arm64 and x86 with /arch:SSE2 (_M_IX86_FP 2). // cppcheck's preprocessor has no values, so it skips the check. #if defined(__CPPCHECK__) #elif defined(FLT_EVAL_METHOD) #if FLT_EVAL_METHOD != 0 #error "zig1 needs FLT_EVAL_METHOD == 0" #endif #elif !(defined(__TINYC__) && defined(__x86_64__)) \ && !(defined(_MSC_VER) \ && (defined(_M_X64) || defined(_M_ARM64) \ || (defined(_M_IX86_FP) && _M_IX86_FP == 2))) #error "zig1 needs FLT_EVAL_METHOD == 0" #endif // ── the type an operation is evaluated at ─────────────────────────────────── ArithIntType arithIntTypeSized(uint32_t bits, bool is_signed) { ArithIntType t; t.sized = true; t.bits = bits; t.is_signed = is_signed; return t; } ArithIntType arithIntTypeComptimeInt(void) { ArithIntType t; t.sized = false; t.bits = 0; t.is_signed = true; return t; } // `bigint.fitsInTwosComp(signedness, bits)`; always true for `comptime_int`. static bool arithIntFits(BigIntConst v, ArithIntType ty) { // `comptime_int` is unbounded, so it fits everything. if (!ty.sized) return true; return bigIntFitsInTwosComp(v, ty.is_signed, ty.bits); } BigIntConst arithIntFromWord(uint64_t* space, uint64_t word, bool negative) { BigIntConst v; v.limbs = space; v.limbs_len = 1; if (negative && word != 0) { v.positive = false; // Two's complement -> magnitude. `~w + 1` rather than `-(int64_t)w`, // which is UB at INT64_MIN and is exactly the value that matters. space[0] = ~word + 1u; } else { v.positive = true; space[0] = word; } return v; } // The truncation rule at a word boundary: the low `bits` of `w`, // sign-extended when the type is signed, which is what every signed reader in // zig1 expects. `comptime_int` is unbounded and keeps the whole word. static uint64_t arithMaskToType(uint64_t w, ArithIntType ty) { if (!ty.sized || ty.bits >= 64) return w; if (ty.bits == 0) return 0; uint64_t mask = (UINT64_C(1) << ty.bits) - 1u; w &= mask; if (ty.is_signed && (w & (UINT64_C(1) << (ty.bits - 1u))) != 0) w |= ~mask; return w; } bool arithIntToWord(BigIntConst v, ArithIntType ty, uint64_t* out_word) { // `u0`/`i0` hold only zero, so a nonzero value has no word at that type. // The masking below would answer 0 for one, which is a value the type // cannot hold, not a truncation the type asked for. The zero is written: // leaving `*out_word` untouched hands the caller its own uninitialised // stack (`uint64_t aw;`, interp.c's binop arm). if (ty.sized && ty.bits == 0) { *out_word = 0; return bigIntEqlZero(v); } uint64_t mag; if (!bigIntToU64(bigIntConstAbs(v), &mag)) return false; // past one limb: this boundary has no word for it // A negative magnitude above 2^63 has no sign-extended 64-bit word, which // only an unbounded `comptime_int` can reach; every sized type's own // range check has already rejected it. if (!v.positive && mag > (UINT64_C(1) << 63)) return false; // At a type wider than the word `arithMaskToType` has nothing to truncate // to, so the word it returns is read back by a bit-63 sign rule -- and a // signed type's positive value at or above 2^63 would come back negative. // That value has no word here. if (ty.sized && ty.bits > 64 && ty.is_signed && v.positive && mag >= (UINT64_C(1) << 63)) return false; *out_word = arithMaskToType(v.positive ? mag : (~mag + 1u), ty); return true; } // ── arith.zig's int workers ───────────────────────────────────────────────── // A worker handed a result buffer smaller than `arithIntBinOpLimbs` promised // is a zig1 bug, not a language shape: upstream allocates each worker's // `limbs` from `sema.arena` at exactly this bound and cannot be short. #define ARITH_NEED(ok, what) \ do { \ if (!(ok)) \ UNIMPLEMENTED( \ "analyzeArithmetic: result buffer too small for " what); \ } while (0) // `intAdd` / `intSub` / `intMul`: `"result_bigint.add(lhs_bigint, // rhs_bigint)"` (and `sub` / `mulNoAlias`), then `fitsInTwosComp`. Upstream // returns the exact `comptime_int` beside the overflow flag and lets // `addScalar` raise `failWithIntegerOverflow`; here ARITH_OVERFLOW is that // flag and `*out` holds the same exact value. static ArithStatus arithIntAddSubMul(ZirInstTag op, BigIntConst lhs, BigIntConst rhs, ArithIntType ty, BigIntMutable* out) { switch (op) { case ZIR_INST_ADD: ARITH_NEED(bigIntMutableAdd(out, lhs, rhs), "add"); break; case ZIR_INST_SUB: ARITH_NEED(bigIntMutableSub(out, lhs, rhs), "sub"); break; default: // ZIR_INST_MUL ARITH_NEED(bigIntMutableMulNoAlias(out, lhs, rhs), "mul"); break; } return arithIntFits(bigIntMutableToConst(*out), ty) ? ARITH_OK : ARITH_OVERFLOW; } // `intAddWithOverflowInner` / `intSubWithOverflowInner` / // `intMulWithOverflowInner`: the exact result modulo 2^bits plus the bit // saying the exact result did not fit -- which is also what `+%`, `-%` and // `*%` keep (`addWrapScalar` and friends). `mul`'s pair is spelled // `mulNoAlias` + `fitsInTwosComp` + `truncate` upstream rather than one // `mulWrap`, and the MUL arm below is that sequence. static ArithStatus arithIntWrap(ZirInstTag op, BigIntConst lhs, BigIntConst rhs, ArithIntType ty, BigIntMutable* out, bool* out_overflow) { if (!ty.sized) { // `comptime_int` is unbounded: `+%` is `+` (`addWrapScalar` asserts a // sized type upstream, and `intAddWithOverflow`'s // `.comptime_int_type` arm answers `.zero_u1` with the plain sum). *out_overflow = false; return arithIntAddSubMul(op, lhs, rhs, ty, out); } switch (op) { case ZIR_INST_ADD: ARITH_NEED(bigIntMutableAddWrap( out, lhs, rhs, ty.is_signed, ty.bits, out_overflow), "addWrap"); break; case ZIR_INST_SUB: ARITH_NEED(bigIntMutableSubWrap( out, lhs, rhs, ty.is_signed, ty.bits, out_overflow), "subWrap"); break; default: // ZIR_INST_MUL ARITH_NEED(bigIntMutableMulNoAlias(out, lhs, rhs), "mulWrap"); *out_overflow = !bigIntFitsInTwosComp( bigIntMutableToConst(*out), ty.is_signed, ty.bits); if (*out_overflow) ARITH_NEED(bigIntMutableTruncate(out, bigIntMutableToConst(*out), ty.is_signed, ty.bits), "mulWrap truncate"); break; } return ARITH_OK; } // `intAddSat` / `intSubSat` / `intMulSat`: the exact result clamped to the // type's own limits. `intMulSat` is `mulNoAlias` then `saturate`, spelled out // upstream; `addSat`/`subSat` are one `Mutable` call each. static ArithStatus arithIntSat(ZirInstTag op, BigIntConst lhs, BigIntConst rhs, ArithIntType ty, BigIntMutable* out) { if (!ty.sized) // `comptime_int` is unbounded: there is no extreme to saturate at, so // `+|` is `+` (`addSatScalar` asserts a sized type upstream). return arithIntAddSubMul(op, lhs, rhs, ty, out); switch (op) { case ZIR_INST_ADD: ARITH_NEED(bigIntMutableAddSat(out, lhs, rhs, ty.is_signed, ty.bits), "addSat"); break; case ZIR_INST_SUB: ARITH_NEED(bigIntMutableSubSat(out, lhs, rhs, ty.is_signed, ty.bits), "subSat"); break; default: // ZIR_INST_MUL ARITH_NEED(bigIntMutableMulNoAlias(out, lhs, rhs), "mulSat"); ARITH_NEED(bigIntMutableSaturate( out, bigIntMutableToConst(*out), ty.is_signed, ty.bits), "mulSat saturate"); break; } return ARITH_OK; } // `intDivTrunc` / `intDivExact` / `intDivFloor` / `intRem` / `intMod`, which // upstream builds out of one `divTrunc`/`divFloor` pair: a quotient and its // remainder. `divTrunc`'s remainder takes the DIVIDEND's sign, `divFloor`'s // takes the DIVISOR's -- the whole difference between `@rem` and `@mod`. The // three scratch arrays upstream allocates per call (`limbs_q`, `limbs_r`, // `limbs_buf`) are this function's own; `*out` is the caller's carrier. static ArithStatus arithIntDivRem(ZirInstTag op, BigIntConst lhs, BigIntConst rhs, ArithIntType ty, BigIntMutable* out) { bool floor; bool ceil = false; bool want_rem; switch (op) { case ZIR_INST_DIV: case ZIR_INST_DIV_TRUNC: case ZIR_INST_DIV_EXACT: floor = false; want_rem = false; break; case ZIR_INST_DIV_FLOOR: floor = true; want_rem = false; break; case ZIR_INST_DIV_CEIL: // `intDivCeil`: `"result_q.divCeil(&result_r, ...)"`. floor = false; ceil = true; want_rem = false; break; case ZIR_INST_MOD: // `@mod` is `divFloor`'s remainder; `@rem` and `%` are `divTrunc`'s. // `%` folds as a plain `@rem` because `zirModRem`'s "ambiguous // between @mod and @rem" rejection is asked of the whole value by the // caller (`compareAllWithZero` over every vector lane), not one lane // at a time. floor = true; want_rem = true; break; default: // ZIR_INST_REM / ZIR_INST_MOD_REM floor = false; want_rem = true; break; } if (bigIntEqlZero(rhs)) return ARITH_DIV_ZERO; uint32_t q_cap = lhs.limbs_len + 2u; uint32_t r_cap = lhs.limbs_len + rhs.limbs_len + 3u; uint32_t buf_len = bigIntCalcDivLimbsBufferLen(lhs.limbs_len, rhs.limbs_len); uint64_t* mem = zig1Malloc(((size_t)q_cap + r_cap + buf_len) * sizeof(uint64_t)); BigIntMutable q = { mem, q_cap, 0, true }; BigIntMutable r = { mem + q_cap, r_cap, 0, true }; uint64_t* buf = mem + q_cap + r_cap; bool ok = floor ? bigIntMutableDivFloor(&q, &r, lhs, rhs, buf, buf_len) : ceil ? bigIntMutableDivCeil(&q, &r, lhs, rhs, buf, buf_len) : bigIntMutableDivTrunc(&q, &r, lhs, rhs, buf, buf_len); ArithStatus st = ARITH_OK; if (!ok) { free(mem); UNIMPLEMENTED("intDivFloor: division scratch too small"); } if (op == ZIR_INST_DIV_EXACT && !bigIntEqlZero(bigIntMutableToConst(r))) { st = ARITH_REMAINDER; } else { bool copied = bigIntMutableCopy(out, bigIntMutableToConst(want_rem ? r : q)); free(mem); ARITH_NEED(copied, "divide"); // `intRem`/`intMod` end at `pt.intValue_big` with no range test: a // remainder is smaller in magnitude than the divisor, so it cannot // leave `ty`'s range. A quotient can (`@divTrunc(minInt(i8), -1)`), // which is `intDivTrunc`'s `error.Overflow` retry at `comptime_int` // and then `failWithIntegerOverflow`. if (want_rem) return ARITH_OK; return arithIntFits(bigIntMutableToConst(*out), ty) ? ARITH_OK : ARITH_OVERFLOW; } free(mem); return st; } // `shlScalar` / `shrScalar` and the `int*` routines under them (`intShl`, // `intShlSat`, `intShlWithOverflow`, `comptimeIntShl`, `intShr`). Upstream // shifts a `big.int` value, which is sign and magnitude -- so a right shift // moves the magnitude and carries the sign, and `(-1) >> 1` is `-1`, not // `2^63 - 1`. static ArithStatus arithIntShift(ZirInstTag op, BigIntConst lhs, BigIntConst rhs, ArithIntType ty, BigIntMutable* out) { // `"switch (Value.order(rhs_val, .zero_comptime_int, zcu)) { .gt => {}, // .eq => return lhs_val, .lt => return sema.failWithNegativeShiftAmount( // ...) }"` -- shared by `shlScalar` and `shrScalar`. if (!rhs.positive && !bigIntEqlZero(rhs)) return ARITH_SHIFT_AMOUNT; uint64_t amt; if (!bigIntToU64(rhs, &amt) || amt / 64u > UINT64_C(0xFFFF)) // `failWithUnsupportedComptimeShiftAmount`: an amount no `usize` can // hold (and, here, one whose result no machine can allocate for). return ARITH_SHIFT_AMOUNT; if (amt == 0) { ARITH_NEED(bigIntMutableCopy(out, lhs), "shift by zero"); return ARITH_OK; } bool right = (op == ZIR_INST_SHR || op == ZIR_INST_SHR_EXACT); // `failWithTooLargeShiftAmount`: `"if (lhs_ty.toIntern() != // .comptime_int_type and shift_amt >= lhs_ty.intInfo(zcu).bits)"`, in // `intShl`, `intShlWithOverflow` and `intShr`. `comptime_int` is // unbounded and has no such bound -- and neither does `intShlSat`, which // has no bits test at all and lets `shiftLeftSat` clamp instead, so // `1 <<| 8` at u8 is 255 rather than an error. if (ty.sized && op != ZIR_INST_SHL_SAT && amt >= ty.bits) return ARITH_SHIFT_TOO_LARGE; if (right) { // `"if (op == .shr_exact and lhs_bigint.ctz(shift_amt) < // shift_amt)"` -> "exact shift shifted out 1 bits". if (op == ZIR_INST_SHR_EXACT && bigIntConstCtz(lhs, amt) < amt) return ARITH_REMAINDER; ARITH_NEED(bigIntMutableShiftRight(out, lhs, amt), "shr"); // `intShr` ends at `pt.intValue_big(lhs_ty, ...)` with no range test: // a right shift never leaves the type it started in. return ARITH_OK; } if (op == ZIR_INST_SHL_SAT) { // `intShlSat` -> `bigint.shiftLeftSat(lhs, amt, signedness, bits)`. // A `comptime_int` is unbounded and has nothing to clamp at, so it is // `comptimeIntShl`'s plain shift (`shlScalar` sends `.comptime_int` // to `comptimeIntShl` for every `ShlOp`). if (!ty.sized) { ARITH_NEED(bigIntMutableShiftLeft(out, lhs, amt), "shl"); return ARITH_OK; } ARITH_NEED( bigIntMutableShiftLeftSat(out, lhs, amt, ty.is_signed, ty.bits), "shlSat"); return ARITH_OK; } // `intShlInner`: `result.shiftLeft(operand, shift_amt)`, exact. ARITH_NEED(bigIntMutableShiftLeft(out, lhs, amt), "shl"); if (!ty.sized) return ARITH_OK; // `comptimeIntShl`: unbounded, nothing to truncate if (op == ZIR_INST_SHL_EXACT) // `intShlWithOverflow`'s `"!result_bigint.toConst().fitsInTwosComp( // info.signedness, info.bits)"`, which `shlScalar` turns into // `failWithIntegerOverflow`. return arithIntFits(bigIntMutableToConst(*out), ty) ? ARITH_OK : ARITH_OVERFLOW; // Plain `.shl`: `intShl` truncates the exact result // (`"result_bigint.truncate(result_bigint.toConst(), info.signedness, // info.bits)"`). ARITH_NEED(bigIntMutableTruncate( out, bigIntMutableToConst(*out), ty.is_signed, ty.bits), "shl truncate"); return ARITH_OK; } // `bitwiseBinScalar`'s integer arm: `bigint.bitAnd`/`bitOr`/`bitXor` over the // two's-complement reading of sign-and-magnitude storage, which is what each // `llsigned*` worker in big_int.c spells. There is no `truncate` after them // upstream and none here: both operands already fit `ty`, and and/or/xor of // two such values cannot leave it. static ArithStatus arithIntBitwise( ZirInstTag op, BigIntConst lhs, BigIntConst rhs, BigIntMutable* out) { switch (op) { case ZIR_INST_BIT_AND: ARITH_NEED(bigIntMutableBitAnd(out, lhs, rhs), "bitAnd"); break; case ZIR_INST_BIT_OR: ARITH_NEED(bigIntMutableBitOr(out, lhs, rhs), "bitOr"); break; default: // ZIR_INST_XOR ARITH_NEED(bigIntMutableBitXor(out, lhs, rhs), "bitXor"); break; } return ARITH_OK; } // ── the dispatcher ────────────────────────────────────────────────────────── bool arithTagIsShift(ZirInstTag tag) { switch (tag) { case ZIR_INST_SHL: case ZIR_INST_SHL_EXACT: case ZIR_INST_SHL_SAT: case ZIR_INST_SHR: case ZIR_INST_SHR_EXACT: return true; default: return false; } } uint32_t arithIntBinOpLimbs( ZirInstTag tag, BigIntConst lhs, BigIntConst rhs, ArithIntType ty) { uint32_t max_ab = lhs.limbs_len >= rhs.limbs_len ? lhs.limbs_len : rhs.limbs_len; // Each operation's own documented upper bound (zig1.h), plus the // type's own width for every rule that ends at `truncate` or a limit. uint32_t n = max_ab + 1u; // add / sub / bitwise / shiftRight (+1) uint32_t mul = lhs.limbs_len + rhs.limbs_len; if (mul > n) n = mul; // mul / mulWrap / mulSat compute the exact product first if (ty.sized) { uint32_t req = bigIntCalcTwosCompLimbCount(ty.bits); if (req > n) n = req; } if (tag == ZIR_INST_SHL || tag == ZIR_INST_SHL_EXACT || tag == ZIR_INST_SHL_SAT) { uint64_t amt; // A shift amount this module will reject anyway needs no room; the // worker returns ARITH_SHIFT_AMOUNT before writing a limb. if (bigIntToU64(rhs, &amt) && amt / 64u <= UINT64_C(0xFFFF)) { uint64_t need = (uint64_t)lhs.limbs_len + amt / 64u + 1u; if (need > (uint64_t)n) n = (uint32_t)need; } } // `arithIntDivRem`'s result bound; the quotient of a one-limb divisor is // as long as the dividend. uint32_t div = lhs.limbs_len + 2u; if (div > n) n = div; return n; } ArithStatus arithIntBinOp(ZirInstTag tag, BigIntConst lhs, BigIntConst rhs, ArithIntType ty, BigIntMutable* out) { bool overflow; if (arithTagIsShift(tag)) return arithIntShift(tag, lhs, rhs, ty, out); switch (tag) { case ZIR_INST_ADD: case ZIR_INST_ADD_UNSAFE: return arithIntAddSubMul(ZIR_INST_ADD, lhs, rhs, ty, out); case ZIR_INST_SUB: return arithIntAddSubMul(ZIR_INST_SUB, lhs, rhs, ty, out); case ZIR_INST_MUL: return arithIntAddSubMul(ZIR_INST_MUL, lhs, rhs, ty, out); case ZIR_INST_ADDWRAP: return arithIntWrap(ZIR_INST_ADD, lhs, rhs, ty, out, &overflow); case ZIR_INST_SUBWRAP: return arithIntWrap(ZIR_INST_SUB, lhs, rhs, ty, out, &overflow); case ZIR_INST_MULWRAP: return arithIntWrap(ZIR_INST_MUL, lhs, rhs, ty, out, &overflow); case ZIR_INST_ADD_SAT: return arithIntSat(ZIR_INST_ADD, lhs, rhs, ty, out); case ZIR_INST_SUB_SAT: return arithIntSat(ZIR_INST_SUB, lhs, rhs, ty, out); case ZIR_INST_MUL_SAT: return arithIntSat(ZIR_INST_MUL, lhs, rhs, ty, out); case ZIR_INST_DIV: case ZIR_INST_DIV_TRUNC: case ZIR_INST_DIV_FLOOR: case ZIR_INST_DIV_CEIL: case ZIR_INST_DIV_EXACT: case ZIR_INST_MOD: case ZIR_INST_REM: case ZIR_INST_MOD_REM: return arithIntDivRem(tag, lhs, rhs, ty, out); case ZIR_INST_BIT_AND: case ZIR_INST_BIT_OR: case ZIR_INST_XOR: return arithIntBitwise(tag, lhs, rhs, out); default: return ARITH_UNSUPPORTED_OP; } } ArithStatus arithIntWithOverflow(ZirInstTag tag, BigIntConst lhs, BigIntConst rhs, ArithIntType ty, BigIntMutable* out, bool* out_overflow) { switch (tag) { case ZIR_INST_ADD: case ZIR_INST_SUB: case ZIR_INST_MUL: break; case ZIR_INST_SHL: { // `intShlWithOverflow(.., truncate_result = true)`: the exact shift // (`shl_exact`'s arm, which reports a result that leaves the type), // the overflow bit, then `truncate`. ArithStatus st = arithIntShift(ZIR_INST_SHL_EXACT, lhs, rhs, ty, out); *out_overflow = st == ARITH_OVERFLOW; if (st != ARITH_OVERFLOW) return st; ARITH_NEED(bigIntMutableTruncate( out, bigIntMutableToConst(*out), ty.is_signed, ty.bits), "shlWithOverflow truncate"); return ARITH_OK; } default: return ARITH_UNSUPPORTED_OP; } return arithIntWrap(tag, lhs, rhs, ty, out, out_overflow); } // `arith.bitwiseNot`'s worker `intBitwiseNot`, general arm: // `"result_bigint.bitNotWrap(val_bigint, info.signedness, info.bits);"` -- // the complement truncated (and sign-extended) to the type's own width. // Upstream's two special cases stay with the caller, which holds the values // they return: `"switch (ty.toIntern()) { .bool_type => return // .makeBool(!val.toBool()), .u0_type => return val, else => {} }"`. `out` // needs `calcTwosCompLimbCount(ty.bits)` limbs. static ArithStatus arithIntBitwiseNot( BigIntConst v, ArithIntType ty, BigIntMutable* out) { // `intBitwiseNot`'s general arm: `"result_bigint.bitNotWrap(val_bigint, // info.signedness, info.bits);"` -- the complement truncated (and sign- // extended) to the type's own width. `bitNotWrap` is `~x = -x - 1` under // two's complement, then `truncate`. if (!ty.sized) // `comptime_int` has no width to complement to; upstream's // `bitwiseNot` reaches `intBitwiseNot` only for a sized `ty` (Sema // rejects `~` on a `comptime_int` with "unable to perform binary not // operation on type 'comptime_int'"). return ARITH_UNSUPPORTED_OP; ARITH_NEED( bigIntMutableAddScalar(out, bigIntConstNegate(v), -1), "bitNot"); ARITH_NEED(bigIntMutableTruncate( out, bigIntMutableToConst(*out), ty.is_signed, ty.bits), "bitNot truncate"); return ARITH_OK; } // ── src/Sema/arith.zig's float half ───────────────────────────────────────── // f16 arithmetic is done in f32 and rounded once to f16 (the float policy): // innocuous, because 24 >= 2*11+2 significand bits. static float arithRoundF16(float f) { return (float)valF16BitsToDouble(valF16BitsFromDouble((double)f)); } // The host type is the width for f32 and f64, so there is nothing to round; // the call still forces the value through the width, defeating any excess // intermediate precision. static float arithRoundF32(float f) { return f; } static double arithRoundF64(double d) { return d; } // One float width's worth of arith.zig's float half. `RND` rounds a primitive // back to the width; `FMOD_` is the host's `@rem` at the carrier type. // // `@divTrunc` / `@divFloor` of floats answer only where `trunc`/`floor` of // the rounded quotient is provably that of the exact one: a rounded quotient // that is not an integer lies strictly between the same two integers as the // exact one, and an integral one is exact when the (exact) remainder is 0 or // an operand is a zero, NaN or infinity. Anything else -- a quotient rounded // onto an integer -- aborts as unported. // `@divExact` needs neither: with a zero `@mod` the quotient // is an integer, and `a / b` is it correctly rounded. // // `@mod` is the SPEC's floored remainder, `@divFloor(a, b) * b + @mod(a, b) // == a`: the exact `fmod` (`@rem`, the dividend's sign) moved onto the // divisor's sign by one `+ b`, and a zero result takes the divisor's sign. // arith.zig's `floatMod` (`"const a = fmod(lhs, rhs); const b = a + rhs; // const c = fmod(b, rhs); return if (lhs < 0) c else a;"`) answers the // dividend's sign at a negative divisor; see "Upstream divergences". #define ARITH_FLOAT_WIDTH(NAME, T, RND, TRUNC_, FLOOR_, FMOD_) \ static T NAME##Mod(T l, T r) { \ T a = RND(FMOD_(l, r)); \ if (f64IsZero((double)a)) \ return r < (T)0 ? -(T)0 : (T)0; \ return (a < (T)0) != (r < (T)0) ? RND(a + r) : a; \ } \ static T NAME##DivInt(T l, T r, ZirInstTag tag) { \ const T q = RND(l / r); \ /* not an integer, or a NaN */ \ if (!f64EqExact((double)TRUNC_(q), (double)q)) \ return tag == ZIR_INST_DIV_FLOOR ? FLOOR_(q) \ : tag == ZIR_INST_DIV_CEIL ? -FLOOR_(-q) \ : TRUNC_(q); \ if (!isfinite(l) || !isfinite(r) || f64IsZero((double)l) \ || f64IsZero((double)r) || f64IsZero((double)(FMOD_(l, r)))) \ return q; \ UNIMPLEMENTED("analyzeArithmetic: float @divTrunc/@divFloor " \ "quotient rounded onto " \ "an integer"); \ ZIG1_TCC_RETURN(0); \ } \ static ArithStatus NAME(ZirInstTag tag, T a, T b, T* out) { \ switch (tag) { \ case ZIR_INST_ADD: \ case ZIR_INST_ADD_UNSAFE: \ case ZIR_INST_ADDWRAP: \ *out = RND(a + b); \ return ARITH_OK; \ case ZIR_INST_SUB: \ case ZIR_INST_SUBWRAP: \ *out = RND(a - b); \ return ARITH_OK; \ case ZIR_INST_MUL: \ case ZIR_INST_MULWRAP: \ *out = RND(a * b); \ return ARITH_OK; \ case ZIR_INST_DIV: \ *out = RND(a / b); \ return ARITH_OK; \ case ZIR_INST_DIV_TRUNC: \ case ZIR_INST_DIV_FLOOR: \ case ZIR_INST_DIV_CEIL: \ *out = NAME##DivInt(a, b, tag); \ return ARITH_OK; \ case ZIR_INST_DIV_EXACT: \ /* `divScalar`'s `.div_exact` arm is the ONE float division that \ forbids a zero divisor (`"allow_div_zero = ... .div_exact => \ false"`) and the one that checks its remainder \ (`floatDivIsExact`, which asks `@mod`, not `@rem`). */ \ if (f64IsZero((double)b)) \ return ARITH_DIV_ZERO; \ if (!f64IsZero((double)(NAME##Mod(a, b)))) \ return ARITH_REMAINDER; \ *out = RND(a / b); \ return ARITH_OK; \ case ZIR_INST_REM: \ case ZIR_INST_MOD_REM: \ *out = RND(FMOD_(a, b)); \ return ARITH_OK; \ case ZIR_INST_MOD: \ *out = NAME##Mod(a, b); \ return ARITH_OK; \ default: \ return ARITH_UNSUPPORTED_OP; \ } \ } ARITH_FLOAT_WIDTH(arithFloatF16, float, arithRoundF16, truncf, floorf, fmodf) ARITH_FLOAT_WIDTH(arithFloatF32, float, arithRoundF32, truncf, floorf, fmodf) ARITH_FLOAT_WIDTH(arithFloatF64, double, arithRoundF64, trunc, floor, fmod) #undef ARITH_FLOAT_WIDTH // The f80/f128 primitives ARITH_FLOAT_WIDTH composes, on compiler_rt. static CrtU128 arithCrtDiv(uint32_t float_bits, CrtU128 a, CrtU128 b) { return float_bits == 80 ? crtDivxf3(a, b) : crtDivtf3(a, b); } static CrtU128 arithCrtFmod(uint32_t float_bits, CrtU128 a, CrtU128 b) { return float_bits == 80 ? crtFmodx(a, b) : crtFmodq(a, b); } // `NAME##Mod` above at f80/f128, the same floored remainder. static CrtU128 arithCrtMod(uint32_t float_bits, CrtU128 l, CrtU128 r) { const CrtU128 zero = { 0, 0 }; const CrtU128 a = arithCrtFmod(float_bits, l, r); const bool r_neg = crtCmp(float_bits, false, r, zero) < 0; const int a_ord = crtCmp(float_bits, false, a, zero); if (a_ord == 0) return r_neg ? (float_bits == 80 ? (CrtU128) { 0, UINT64_C(0x8000) } : (CrtU128) { 0, UINT64_C(1) << 63 }) : zero; return (a_ord < 0) != r_neg ? crtAddf3(float_bits, a, r) : a; } // `NAME##DivInt` above at f80/f128, on `__truncx`/`truncq`, `__floorx`/ // `floorq` and `__fmodx`/`fmodq`. static CrtU128 arithCrtDivInt( uint32_t float_bits, CrtU128 l, CrtU128 r, ZirInstTag tag) { const CrtU128 zero = { 0, 0 }; const CrtU128 q = arithCrtDiv(float_bits, l, r); const CrtU128 t = float_bits == 80 ? crtTruncx(q) : crtTruncq(q); if (crtCmp(float_bits, false, t, q) != 0) // not an integer, or a NaN return tag == ZIR_INST_DIV_TRUNC ? t : crtFloorCeil(float_bits, tag == ZIR_INST_DIV_CEIL, q); const uint32_t exp_at = float_bits == 80 ? 0 : 48; // in `.hi` if (((l.hi >> exp_at) & 0x7FFFu) == 0x7FFFu // an infinity (or NaN) || ((r.hi >> exp_at) & 0x7FFFu) == 0x7FFFu || crtCmp(float_bits, false, l, zero) == 0 || crtCmp(float_bits, false, r, zero) == 0 || crtCmp(float_bits, false, arithCrtFmod(float_bits, l, r), zero) == 0) return q; UNIMPLEMENTED("analyzeArithmetic: float @divTrunc/@divFloor quotient " "rounded onto an integer"); ZIG1_TCC_RETURN((CrtU128) { 0 }); } ArithStatus arithFloatBinOp(ZirInstTag tag, CrtU128 lhs_bits, CrtU128 rhs_bits, uint32_t float_bits, CrtU128* out_bits) { out_bits->hi = 0; switch (float_bits) { case 16: { float r; ArithStatus st = arithFloatF16(tag, (float)valF16BitsToDouble((uint32_t)(lhs_bits.lo & 0xFFFFu)), (float)valF16BitsToDouble((uint32_t)(rhs_bits.lo & 0xFFFFu)), &r); if (st != ARITH_OK) return st; out_bits->lo = valF16BitsFromDouble((double)r); return ARITH_OK; } case 32: { uint32_t lw = (uint32_t)lhs_bits.lo, rw = (uint32_t)rhs_bits.lo, ow; float la, rb, r; memcpy(&la, &lw, sizeof(la)); memcpy(&rb, &rw, sizeof(rb)); ArithStatus st = arithFloatF32(tag, la, rb, &r); if (st != ARITH_OK) return st; memcpy(&ow, &r, sizeof(ow)); out_bits->lo = ow; return ARITH_OK; } case 64: { double la, rb, r; memcpy(&la, &lhs_bits.lo, sizeof(la)); memcpy(&rb, &rhs_bits.lo, sizeof(rb)); ArithStatus st = arithFloatF64(tag, la, rb, &r); if (st != ARITH_OK) return st; memcpy(&out_bits->lo, &r, sizeof(r)); return ARITH_OK; } default: { // f80, f128 and this pool's f128-stored `comptime_float`: the host // operator upstream uses is a compiler_rt libcall, and the division // family is the same composition ARITH_FLOAT_WIDTH spells above, // over `__divxf3`/`__divtf3`, `__truncx`/`truncq`, `__floorx`/ // `floorq` and `__fmodx`/`fmodq`. const CrtU128 zero = { 0, 0 }; const CrtU128 a = lhs_bits, b = rhs_bits; switch (tag) { case ZIR_INST_ADD: case ZIR_INST_ADD_UNSAFE: case ZIR_INST_ADDWRAP: *out_bits = crtAddf3(float_bits, a, b); return ARITH_OK; case ZIR_INST_SUB: case ZIR_INST_SUBWRAP: *out_bits = crtSubf3(float_bits, a, b); return ARITH_OK; case ZIR_INST_MUL: case ZIR_INST_MULWRAP: *out_bits = crtMulf3(float_bits, a, b); return ARITH_OK; case ZIR_INST_DIV: *out_bits = arithCrtDiv(float_bits, a, b); return ARITH_OK; case ZIR_INST_DIV_TRUNC: case ZIR_INST_DIV_FLOOR: case ZIR_INST_DIV_CEIL: *out_bits = arithCrtDivInt(float_bits, a, b, tag); return ARITH_OK; case ZIR_INST_DIV_EXACT: if (crtCmp(float_bits, false, b, zero) == 0) return ARITH_DIV_ZERO; if (crtCmp(float_bits, false, arithCrtMod(float_bits, a, b), zero) != 0) return ARITH_REMAINDER; *out_bits = arithCrtDiv(float_bits, a, b); return ARITH_OK; case ZIR_INST_REM: case ZIR_INST_MOD_REM: *out_bits = arithCrtFmod(float_bits, a, b); return ARITH_OK; case ZIR_INST_MOD: *out_bits = arithCrtMod(float_bits, a, b); return ARITH_OK; default: return ARITH_FLOAT_NO_CARRIER; } } } } // The f32 rule (`float_bits` 16 reaches it widened, as upstream's f16 math // runs at f32: `__sinh` is `@floatCast(sinf(a))` and LLVM promotes the // comptime host's f16 ops alike). static float arithFloatUnaryF32(ZirInstTag tag, float f, bool runtime) { switch (tag) { case ZIR_INST_SQRT: return sqrtf(f); // `f32.sqrt`, and the host's sqrt: correctly rounded case ZIR_INST_SIN: return crtSinf(f); case ZIR_INST_COS: return crtCosf(f); case ZIR_INST_TAN: return crtTanf(f); case ZIR_INST_EXP: return crtExpf(f); case ZIR_INST_EXP2: return crtExp2f(f); case ZIR_INST_LOG: return crtLogf(f, !runtime); case ZIR_INST_LOG2: return crtLog2f(f, !runtime); case ZIR_INST_LOG10: return crtLog10f(f, !runtime); case ZIR_INST_FLOOR: return floorf(f); case ZIR_INST_CEIL: return ceilf(f); case ZIR_INST_TRUNC: return truncf(f); default: return roundf(f); } } static double arithFloatUnaryF64(ZirInstTag tag, double d, bool runtime) { switch (tag) { case ZIR_INST_SQRT: return sqrt(d); case ZIR_INST_SIN: return crtSin(d); case ZIR_INST_COS: return crtCos(d); case ZIR_INST_TAN: return crtTan(d); case ZIR_INST_EXP: return crtExp(d); case ZIR_INST_EXP2: return crtExp2(d); case ZIR_INST_LOG: return crtLog(d, !runtime); case ZIR_INST_LOG2: return crtLog2(d, !runtime); case ZIR_INST_LOG10: return crtLog10(d, !runtime); case ZIR_INST_FLOOR: return floor(d); case ZIR_INST_CEIL: return ceil(d); case ZIR_INST_TRUNC: return trunc(d); default: // Half away from zero in both frames, as the language defines // `@round`; the wasm backend's `f64.nearest` deviates (see "Upstream // divergences"). return round(d); } } ArithStatus arithFloatUnary(ZirInstTag tag, CrtU128 bits, uint32_t float_bits, bool runtime, CrtU128* out_bits) { switch (tag) { case ZIR_INST_SQRT: case ZIR_INST_SIN: case ZIR_INST_COS: case ZIR_INST_TAN: case ZIR_INST_EXP: case ZIR_INST_EXP2: case ZIR_INST_LOG: case ZIR_INST_LOG2: case ZIR_INST_LOG10: case ZIR_INST_FLOOR: case ZIR_INST_CEIL: case ZIR_INST_TRUNC: case ZIR_INST_ROUND: break; default: return ARITH_UNSUPPORTED_OP; } out_bits->hi = 0; if (float_bits == 80 || float_bits == 128) { // f80, f128 and this pool's f128-stored `comptime_float`: the // compiler_rt routine upstream's host operator and the wasm // backend's libcall (`__floorx`, `floorq`, ...) both reach. switch (tag) { case ZIR_INST_FLOOR: case ZIR_INST_CEIL: *out_bits = crtFloorCeil(float_bits, tag == ZIR_INST_CEIL, bits); return ARITH_OK; case ZIR_INST_TRUNC: *out_bits = float_bits == 80 ? crtTruncx(bits) : crtTruncq(bits); return ARITH_OK; case ZIR_INST_ROUND: *out_bits = float_bits == 80 ? crtRoundx(bits) : crtRoundq(bits); return ARITH_OK; case ZIR_INST_SQRT: *out_bits = float_bits == 80 ? crtSqrtx(bits) : crtSqrtq(bits); return ARITH_OK; case ZIR_INST_SIN: *out_bits = crtSinT(float_bits, bits); return ARITH_OK; case ZIR_INST_COS: *out_bits = crtCosT(float_bits, bits); return ARITH_OK; case ZIR_INST_TAN: *out_bits = crtTanT(float_bits, bits); return ARITH_OK; case ZIR_INST_LOG: case ZIR_INST_LOG2: case ZIR_INST_LOG10: case ZIR_INST_EXP: case ZIR_INST_EXP2: { // `logq`/`log2q`/`log10q`/`expq`/`exp2q`; `__logx` and friends // are `@floatCast(logq(a))`. CrtU128 (*fq)(CrtU128) = tag == ZIR_INST_LOG ? crtLogq : tag == ZIR_INST_LOG2 ? crtLog2q : tag == ZIR_INST_LOG10 ? crtLog10q : tag == ZIR_INST_EXP ? crtExpq : crtExp2q; *out_bits = float_bits == 80 ? crtTrunctfxf2(fq(crtExtendxftf2(bits))) : fq(bits); return ARITH_OK; } default: return ARITH_FLOAT_NO_CARRIER; } } if (float_bits == 64) { double d; memcpy(&d, &bits.lo, sizeof d); d = arithFloatUnaryF64(tag, d, runtime); memcpy(&out_bits->lo, &d, sizeof d); return ARITH_OK; } if (float_bits == 32) { uint32_t raw = (uint32_t)bits.lo; float f; memcpy(&f, &raw, sizeof f); f = arithFloatUnaryF32(tag, f, runtime); memcpy(&raw, &f, sizeof raw); out_bits->lo = raw; return ARITH_OK; } if (float_bits != 16) return ARITH_FLOAT_NO_CARRIER; uint32_t h = (uint32_t)(bits.lo & 0xFFFFu); if (runtime && tag == ZIR_INST_SQRT && (h & 0x7FFFu) != 0 && h > 0x7C00u) { // compiler_rt `__sqrth`, the wasm backend's f16 `@sqrt`: `"if (ix > // 0x7C00) return math.nan(f16);"` for a negative number and NaN; // its body is correctly rounded, as the f32 route below is. out_bits->lo = 0x7E00u; return ARITH_OK; } float r = arithFloatUnaryF32(tag, (float)valF16BitsToDouble(h), runtime); out_bits->lo = valF16BitsFromDouble((double)r); return ARITH_OK; } bool arithFloatToDouble(uint64_t bits, uint32_t float_bits, double* out) { if (float_bits == 16) { *out = valF16BitsToDouble((uint32_t)(bits & 0xFFFFu)); } else if (float_bits == 32) { uint32_t raw = (uint32_t)bits; float f; memcpy(&f, &raw, sizeof f); *out = (double)f; } else if (float_bits == 64) { memcpy(out, &bits, sizeof *out); } else { return false; } return true; } uint64_t arithFloatFromDouble(double d, uint32_t float_bits) { uint64_t bits = 0; float f = (float)d; if (float_bits == 16) bits = valF16BitsFromDouble((double)f); else if (float_bits == 32) memcpy(&bits, &f, sizeof f); else memcpy(&bits, &d, sizeof d); return bits; } ArithStatus arithFloatMulAdd(uint32_t float_bits, uint64_t a, uint64_t b, uint64_t c, uint64_t* out_bits) { double x, y, z; if (!arithFloatToDouble(a, float_bits, &x) || !arithFloatToDouble(b, float_bits, &y) || !arithFloatToDouble(c, float_bits, &z)) return ARITH_FLOAT_NO_CARRIER; // `@mulAdd` rounds once. An f16 product is exact in a double and the // double fma's one rounding is innocuous, so f16 rounds straight from it; // `fmaf` would round to f32 first (compiler_rt's `__fmah` does, and // double-rounds). if (float_bits == 16) *out_bits = valF16BitsFromDouble(fma(x, y, z)); else *out_bits = arithFloatFromDouble(float_bits == 64 ? fma(x, y, z) : (double)fmaf((float)x, (float)y, (float)z), float_bits); return ARITH_OK; } // This file's macros end with it (zig1.c is one translation unit). // ── Value folds: negate, truncate, bitwise, permute, overflow ─────────────── // (part of) Value.zig:toBigInt, PerThread.zig:intValue_big // `src/Sema/arith.zig` `intTruncate`: `Value.toBigInt`, then // `Mutable.truncate(val, signedness, bits)`, then `pt.intValue_big`. One // rule at any width the pattern carrier reaches, where both frames' // comptime `@truncate` arms each masked a 64-bit word by hand and refused a // destination wider than that word. IP_INDEX_NONE for a non-integer // destination, an unreadable operand, or a width past the carrier. static uint32_t intTruncate( InternPool* ip, uint32_t operand, uint32_t dest_ty) { uint32_t bits; bool is_signed; if (!ztIntInfo(ip, dest_ty, &bits, &is_signed) || bits > 128u) return IP_INDEX_NONE; uint64_t space[2]; BigIntConst v; if (!valToBigInt(ip, operand, space, &v)) return IP_INDEX_NONE; uint64_t buf[2]; BigIntMutable r = { buf, 2, 0, true }; if (!bigIntMutableTruncate(&r, v, is_signed, bits)) return IP_INDEX_NONE; return ipGetInt(ip, dest_ty, bigIntMutableToConst(r)); } // arith.zig:truncate, PerThread.zig:undefValue // `arith.truncate`'s lane: "result_elem.* = if (elem_val.isUndef(zcu)) (try // pt.undefValue(dest_elem_ty)).toIntern() else (try intTruncate(...))". uint32_t arithIntTruncateLane(InternPool* ip, uint32_t val, uint32_t dest_ty) { return ipItemTagIs(ip, val, IP_TAG_UNDEF) ? ipGetUndef(ip, dest_ty) : intTruncate(ip, val, dest_ty); } static uint32_t arithNegateFloatLane( InternPool* ip, uint32_t val, uint32_t ty) { return ipItemTagIs(ip, val, IP_TAG_UNDEF) ? val : valFloatSignBits(ip, val, ty, false); } // arith.zig:negateFloat uint32_t negateFloat(Zcu* zcu, uint32_t ty, uint32_t val) { InternPool* ip = &zcu->intern_pool; if (val >= ip->items_len || ty >= ip->items_len) return IP_INDEX_NONE; // `"if (val.isUndef(zcu)) return val;"` if (ip->item_tags[val] == IP_TAG_UNDEF) return val; // `.float, .comptime_float => floatNeg`, or the `.vector` per-lane loop // (an undef lane kept). valFloatSignBits declines a non-float type, where // upstream is `unreachable`; the caller keeps a loud abort. return semaCastLanes(ip, val, ty, arithNegateFloatLane); } // (part of) arith.zig:bitwiseBinScalar // `arith.bitwiseBinScalar` at `bool` (`intBitwiseAnd`/`Or`/`Xor`'s // `.bool_type` line): `xor` with an undefined operand is undefined; one // undefined operand of `and`/`or` becomes `intValueAa`, which is `true` for // bool; both undefined is undefined. IP_INDEX_NONE for an operand that is // not a comptime bool. uint32_t arithBitwiseBinBool( InternPool* ip, ZirInstTag tag, uint32_t lhs, uint32_t rhs) { uint32_t undef = ipGetUndef(ip, ZIR_REF_BOOL_TYPE); bool lu = lhs == undef || lhs == ZIR_REF_UNDEF; bool ru = rhs == undef || rhs == ZIR_REF_UNDEF; if ((tag == ZIR_INST_XOR && (lu || ru)) || (lu && ru)) return undef; bool lb = lu || lhs == ZIR_REF_BOOL_TRUE; bool rb = ru || rhs == ZIR_REF_BOOL_TRUE; if ((!lu && lhs != ZIR_REF_BOOL_TRUE && lhs != ZIR_REF_BOOL_FALSE) || (!ru && rhs != ZIR_REF_BOOL_TRUE && rhs != ZIR_REF_BOOL_FALSE)) return IP_INDEX_NONE; bool res = tag == ZIR_INST_BIT_AND ? (lb && rb) : tag == ZIR_INST_BIT_OR ? (lb || rb) : (lb != rb); return res ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; } // arith.zig:incrementDefinedInt // `arith.incrementDefinedInt` as `resolveEnumLayout` asks it: `"if // (ty.toIntern() == .u0_type) return .{ .overflow = true, ... }"`, else // `intAdd(prev_val, 1)` at `ty`, an overflow being "enum tag value '{f}' too // large for type '{f}'". uint32_t incrementDefinedInt(Zcu* zcu, uint32_t ty, uint32_t prev) { InternPool* ip = &zcu->intern_pool; if (ty == ZIR_REF_U0_TYPE) COMPILE_ERROR_UPSTREAM("enum tag value too large for type"); // `intAdd`: the big-integer sum, then whether it fits `ty` -- the `1` // itself is never coerced to `ty` (it does not fit an `i1`). uint32_t bits; bool is_signed; uint64_t space[2]; BigIntConst v; if (!ztIntInfo(ip, ty, &bits, &is_signed) || !valToBigInt(ip, prev, space, &v)) UNIMPLEMENTED("incrementDefinedInt: operand not a readable integer"); uint32_t cap = v.limbs_len + 1u; uint64_t* limbs = zig1Malloc(cap * sizeof(uint64_t)); BigIntMutable sum = { limbs, cap, 0, true }; if (!bigIntMutableAddScalar(&sum, v, 1)) UNIMPLEMENTED("incrementDefinedInt: limb buffer too short"); BigIntConst c = bigIntMutableToConst(sum); if (!bigIntFitsInTwosComp(c, is_signed, bits)) COMPILE_ERROR_UPSTREAM("enum tag value too large for type"); uint32_t res = ipGetInt(ip, ty, c); free(limbs); return res; } // (part of) Value.zig:toBigInt, PerThread.zig:intValue_big // `intByteSwap` / `intBitReverse` (src/Sema/arith.zig): `Mutable.byteSwap` / // `Mutable.bitReverse` over `Value.toBigInt` at the type's width, then // `pt.intValue_big(ty, ..)`. static uint32_t arithIntBitPermute( InternPool* ip, ZirInstTag tag, uint32_t val, uint32_t ty) { uint32_t bits; bool is_signed; uint64_t space[2]; BigIntConst v; if (!ztIntInfo(ip, ty, &bits, &is_signed) || !valToBigInt(ip, val, space, &v)) return IP_INDEX_NONE; uint32_t cap = bigIntCalcTwosCompLimbCount(bits); if (cap == 0) cap = 1; uint64_t* limbs = zig1Malloc((size_t)cap * sizeof(uint64_t)); BigIntMutable r = { limbs, cap, 0, true }; bool ok = tag == ZIR_INST_BYTE_SWAP ? bigIntMutableByteSwap(&r, v, is_signed, bits / 8u) : bigIntMutableBitReverse(&r, v, is_signed, bits); uint32_t res = ok ? ipGetInt(ip, ty, bigIntMutableToConst(r)) : IP_INDEX_NONE; free(limbs); return res; } // (part of) arith.zig:byteSwap, arith.zig:bitReverse // `arith.byteSwap` / `arith.bitReverse`: an undef value passes through, an // int is the worker above, and a vector maps its lanes, keeping an undef // lane. IP_INDEX_NONE for a shape this pool cannot carry, which is each // caller's loud boundary. uint32_t arithValueBitPermute( InternPool* ip, ZirInstTag tag, uint32_t val, uint32_t ty) { if (val >= ip->items_len) return IP_INDEX_NONE; if (ipItemTagIs(ip, val, IP_TAG_UNDEF)) return val; IpArrayInfo vi; if (ipArrayInfo(ip, ty, &vi) == IP_ARR_OK && vi.is_vector) { uint32_t* elems = zig1Malloc((vi.len ? (size_t)vi.len : 1u) * sizeof(*elems)); bool ok = true; for (uint32_t i = 0; i < vi.len; i++) { uint32_t le = valElemValue(ip, val, i, "bitwiseBin: vector lane"); elems[i] = ipItemTagIs(ip, le, IP_TAG_UNDEF) ? le : arithIntBitPermute(ip, tag, le, vi.child); if (elems[i] == IP_INDEX_NONE) ok = false; } uint32_t res = ok ? ipGetAggregate(ip, ty, elems, vi.len) : IP_INDEX_NONE; free(elems); return res; } return arithIntBitPermute(ip, tag, val, ty); } // arith.zig:bitwiseNot // `arith.bitwiseNot`'s worker `intBitwiseNot`: `"switch (ty.toIntern()) { // .bool_type => return .makeBool(!val.toBool()), .u0_type => return val, // else => {} }"`, then `bitNotWrap` at the type's width, which `arith.c` // owns. static uint32_t intBitwiseNot(InternPool* ip, uint32_t ty, uint32_t val) { if (ty == ZIR_REF_BOOL_TYPE) { if (val == ZIR_REF_BOOL_TRUE) return ZIR_REF_BOOL_FALSE; if (val == ZIR_REF_BOOL_FALSE) return ZIR_REF_BOOL_TRUE; UNIMPLEMENTED( "intBitwiseNot: `~` - bool operand is not a comptime-known bool"); } uint32_t bits; bool is_signed; if (ty == IP_INDEX_NONE || !ztIntInfo(ip, ty, &bits, &is_signed)) UNIMPLEMENTED("intBitwiseNot: `~` - operand type is not an integer"); // `.u0_type => return val`: u0 holds one value and `~` cannot move it. if (bits == 0 && !is_signed) return val; uint64_t v_space[2]; BigIntConst v; if (!valToBigInt(ip, val, v_space, &v)) UNIMPLEMENTED( "intBitwiseNot: `~` - operand is not a readable integer"); // `bitNotWrap` writes `calcTwosCompLimbCount(bits)` limbs, and // `addScalar(-1)` on the way there can want one more. uint32_t n_cap = bigIntCalcTwosCompLimbCount(bits) + 1u; if (v.limbs_len + 1u > n_cap) n_cap = v.limbs_len + 1u; uint64_t* n_buf = zig1Malloc((size_t)n_cap * sizeof(uint64_t)); BigIntMutable n_res = { n_buf, n_cap, 0, true }; if (arithIntBitwiseNot(v, arithIntTypeSized(bits, is_signed), &n_res) != ARITH_OK) { free(n_buf); UNIMPLEMENTED("intBitwiseNot: `~` - unhandled operand type"); } uint32_t res = ipGetInt(ip, ty, bigIntMutableToConst(n_res)); free(n_buf); if (res == IP_INDEX_NONE) UNIMPLEMENTED("intBitwiseNot: `~` - result not internable"); return res; } // arith.zig:bitwiseNot, PerThread.zig:aggregateValue, Sema.zig:zirBitNot // `arith.bitwiseNot`: `"switch (ty.zigTypeTag(zcu)) { .bool, .int, // .comptime_int => return intBitwiseNot(sema, val, ty), .vector => { ... for // (elem_vals, 0..) |*result_elem, elem_idx| { const elem_val = try // val.elemValue(pt, elem_idx); result_elem.* = ... (try intBitwiseNot(sema, // elem_val, elem_ty)).toIntern(); } return pt.aggregateValue(ty, elem_vals); // } }"`. BOTH `~v` and `!v` end here: `Sema.zirBitNot` through // `analyzeBitNot`, and `Sema.zirBoolNot` for a vector operand through `"if // (uncasted_ty.isVector(zcu)) { ... return analyzeBitNot(sema, block, // uncasted_operand, src); }"` -- one rule, not two. static uint32_t arithBitwiseNotLane( InternPool* ip, uint32_t val, uint32_t ty) { return ipItemTagIs(ip, val, IP_TAG_UNDEF) ? val : intBitwiseNot(ip, ty, val); } // arith.zig:bitwiseNot uint32_t bitwiseNot(InternPool* ip, uint32_t ty, uint32_t val) { // `"if (val.isUndef(zcu)) return pt.undefValue(ty);"`, and an undef lane // stays one. if (ipItemTagIs(ip, val, IP_TAG_UNDEF)) return ipGetUndef(ip, ty); return semaCastLanes(ip, val, ty, arithBitwiseNotLane); } // (part of) arith.zig:addWithOverflow // `arith.addWithOverflow` / `subWithOverflow` / `mulWithOverflow`'s scalar // arm (`WithOverflowScalar` -> `intWithOverflow`), which answers a // `Value.OverflowArithmeticResult` -- the wrapped result at `ty` and the u1 // overflow bit, interned through `*out_ov`. `ty` is a sized integer: an // unsized `comptime_int` destination is rejected by `zirOverflowArithmetic` // itself (`"if (dest_ty.scalarType(zcu).zigTypeTag(zcu) != .int) return // sema.fail(...)"`), and `.comptime_int` is not `.int`. uint32_t arithWithOverflowScalar(InternPool* ip, ZirInstTag tag, uint32_t ty, uint32_t lhs, uint32_t rhs, uint32_t* out_ov) { uint64_t lw, rw; if (lhs >= ip->items_len || rhs >= ip->items_len || !ipTryReadIntValue(ip, lhs, &lw) || !ipTryReadIntValue(ip, rhs, &rw)) UNIMPLEMENTED("addWithOverflow: overflow-arith comptime - operand is " "not a readable integer"); uint32_t bits; bool is_signed; if (ty == IP_INDEX_NONE || !ztIntInfo(ip, ty, &bits, &is_signed)) UNIMPLEMENTED("addWithOverflow: overflow-arith comptime - result type " "not supported"); // `Sema.coerce`'s int->int range check, which `zirOverflowArithmetic` // runs on each operand (`"const lhs = try sema.coerce(block, dest_ty, // uncasted_lhs, lhs_src);"`) before the fold: a `comptime_int` literal // the destination cannot represent is a compile error, not a truncation. // A sized operand of the destination type is in range by construction. ArithIntType aty = arithIntTypeSized(bits, is_signed); uint64_t l_space[2], r_space[2]; BigIntConst la, ra; if (!valToBigInt(ip, lhs, l_space, &la) || !valToBigInt(ip, rhs, r_space, &ra)) UNIMPLEMENTED("addWithOverflow: overflow-arith comptime - operand is " "not a readable integer"); if (!arithIntFits(la, aty) || !arithIntFits(ra, aty)) COMPILE_ERROR_UPSTREAM( "zirOverflowArithmetic: comptime operand does not fit " "the destination type"); uint32_t o_cap = arithIntBinOpLimbs(tag, la, ra, aty); uint64_t* o_buf = zig1Malloc((size_t)o_cap * sizeof(uint64_t)); BigIntMutable o_res = { o_buf, o_cap, 0, true }; bool overflow = false; if (arithIntWithOverflow(tag, la, ra, aty, &o_res, &overflow) != ARITH_OK) { free(o_buf); UNIMPLEMENTED( "addWithOverflow: overflow-arith comptime - unhandled op"); } *out_ov = ipInternIntResult(ip, overflow ? 1u : 0u, ZIR_REF_U1_TYPE); uint32_t res = ipGetInt(ip, ty, bigIntMutableToConst(o_res)); free(o_buf); if (res == IP_INDEX_NONE || *out_ov == IP_INDEX_NONE) UNIMPLEMENTED( "addWithOverflow: overflow-arith comptime - result intern"); return res; } #undef ARITH_NEED /* ---- end arith.c ---- */ /* ---- begin ast.c ---- */ // ast.c — lib/std/zig/Ast.zig: `parse` (tokenize, then Parse.zig) and the // tree's accessors and `full` decoders that AstGen and AstRlAnnotate use, // as `ast`-prefixed functions in upstream's order. #ifndef ZIG1_H #include "zig1.h" #endif #include #include #include #include #define AST_INITIAL_CAPACITY 1024 static AstFullVarDecl astFullVarDeclComponents( const Ast* tree, AstFullVarDeclComponents info); static AstFullAssignDestructure astFullAssignDestructureComponents( const Ast* tree, AstFullAssignDestructureComponents info); static AstFullIf astFullIfComponents( const Ast* tree, AstFullIfComponents info); static AstFullContainerField astFullContainerFieldComponents( const Ast* tree, AstFullContainerFieldComponents info); static AstFullFnProto astFullFnProtoComponents( const Ast* tree, AstFullFnProtoComponents info); static AstFullPtrType astFullPtrTypeComponents( const Ast* tree, AstFullPtrTypeComponents info); static AstFullContainerDecl astFullContainerDeclComponents( const Ast* tree, AstFullContainerDeclComponents info); static AstFullSwitchCase astFullSwitchCaseComponents( const Ast* tree, AstFullSwitchCaseComponents info, AstNodeIndex node); static AstFullAsm astFullAsmComponents( const Ast* tree, AstFullAsmComponents info); static AstFullWhile astFullWhileComponents( const Ast* tree, AstFullWhileComponents info); static AstFullFor astFullForComponents( const Ast* tree, AstFullForComponents info); static void astTokenListEnsureCapacity( AstTokenList* list, uint32_t additional) { const uint32_t new_len = list->len + additional; if (new_len <= list->cap) { return; } const uint32_t new_cap = new_len > list->cap * 2 ? new_len : list->cap * 2; list->tags = zig1Realloc(list->tags, new_cap * sizeof(uint8_t)); list->starts = zig1Realloc(list->starts, new_cap * sizeof(AstIndex)); list->cap = new_cap; } // Ast.zig:parse Ast astParse(const char* source, const uint32_t len, const AstMode mode) { uint32_t estimated_token_count = len / 8; AstTokenList tokens = { .len = 0, .cap = estimated_token_count, .tags = ARR_INIT(uint8_t, estimated_token_count), .starts = ARR_INIT(AstIndex, estimated_token_count), }; Tokenizer tok = tokenizerInit(source, len); while (true) { astTokenListEnsureCapacity(&tokens, 1); Token token = tokenizerNext(&tok); tokens.tags[tokens.len] = (uint8_t)token.tag; tokens.starts[tokens.len++] = token.loc.start; if (token.tag == TOKEN_EOF) break; } uint32_t estimated_node_count = (tokens.len + 2) / 2; Parser p = { .source = source, .source_len = len, .token_tags = tokens.tags, .token_starts = tokens.starts, .tokens_len = tokens.len, .tok_i = 0, .nodes = { .len = 0, .cap = estimated_node_count, .tags = ARR_INIT(uint8_t, estimated_node_count), .main_tokens = ARR_INIT(AstTokenIndex, estimated_node_count), .datas = ARR_INIT(AstData, estimated_node_count), }, .extra_data = SLICE_INIT(AstNodeIndex, AST_INITIAL_CAPACITY), .scratch = SLICE_INIT(AstNodeIndex, AST_INITIAL_CAPACITY), .has_warn = false, .has_compile_errors = false, .err_buf = { 0 }, .err_tok = 0, .err_tok_is_prev = false, }; switch (mode) { case AST_MODE_ZIG: parseRoot(&p); break; case AST_MODE_ZON: parseZon(&p); break; } p.scratch.cap = p.scratch.len = 0; free(p.scratch.arr); char* err_msg = NULL; if (p.err_buf[0] != '\0') { const size_t len2 = strlen(p.err_buf); err_msg = zig1Malloc(len2 + 1); memcpy(err_msg, p.err_buf, len2 + 1); } return (Ast) { .source = source, .source_len = len, .tokens = tokens, .nodes = p.nodes, .extra_data = { .len = p.extra_data.len, .cap = p.extra_data.cap, .arr = p.extra_data.arr, }, .mode = mode, .has_error = p.has_compile_errors || p.has_warn, .err_msg = err_msg, .err_tok = p.err_tok, .err_tok_is_prev = p.err_tok_is_prev, }; } void astDeinit(Ast* tree) { free(tree->owned_source); free(tree->err_msg); tree->tokens.cap = tree->tokens.len = 0; free(tree->tokens.tags); free(tree->tokens.starts); tree->nodes.cap = 0; tree->nodes.len = 0; free(tree->nodes.tags); free(tree->nodes.main_tokens); free(tree->nodes.datas); tree->extra_data.cap = 0; tree->extra_data.len = 0; free(tree->extra_data.arr); } // Ast.zig:isTokenPrecededByTags bool astIsTokenPrecededByTags(const Ast* tree, AstTokenIndex token, const TokenTag* expected, uint32_t expected_len) { if (token < expected_len) return false; for (uint32_t i = 0; i < expected_len; i++) { if (astTokenTag(tree, token - expected_len + i) != expected[i]) return false; } return true; } // Ast.zig:tokenSlice void astTokenSlice(const Ast* tree, AstTokenIndex token, const char** out, uint32_t* out_len) { const TokenTag token_tag = astTokenTag(tree, token); const char* lexeme = tokenTagLexeme(token_tag); if (lexeme != NULL) { *out = lexeme; *out_len = (uint32_t)strlen(lexeme); return; } Tokenizer tokenizer = tokenizerInit(tree->source, tree->source_len); tokenizer.index = astTokenStart(tree, token); const Token t = tokenizerNext(&tokenizer); assert(t.tag == token_tag); *out = tree->source + t.loc.start; *out_len = t.loc.end - t.loc.start; } // Ast.zig:extraDataSlice static AstNodeSlice astExtraDataSlice( const Ast* tree, uint32_t start, uint32_t end) { return (AstNodeSlice) { tree->extra_data.arr + start, end - start }; } // Ast.zig:extraData of a Node.SubRange at `index`. static AstNodeSlice astExtraSubRange(const Ast* tree, uint32_t index) { const uint32_t* extra = tree->extra_data.arr + index; return astExtraDataSlice(tree, extra[AST_EXTRA_SUB_RANGE_START], extra[AST_EXTRA_SUB_RANGE_END]); } // Ast.zig:loadOptionalNodesIntoBuffer static AstNodeSlice astLoadOptionalNodesIntoBuffer( uint32_t size, AstNodeIndex* buffer, const AstNodeIndex* items) { uint32_t i = 0; while (i < size && items[i] != AST_NODE_NONE) { buffer[i] = items[i]; i++; } return (AstNodeSlice) { buffer, i }; } // Ast.zig:rootDecls AstNodeSlice astRootDecls(const Ast* tree) { switch (tree->mode) { case AST_MODE_ZIG: break; case AST_MODE_ZON: return (AstNodeSlice) { &tree->nodes.datas[0].lhs, 1 }; } const AstData data = astNodeData(tree, 0); return astExtraDataSlice(tree, data.lhs, data.rhs); } // Ast.zig:firstToken AstTokenIndex astFirstToken(const Ast* tree, AstNodeIndex node) { uint32_t end_offset = 0; AstNodeIndex n = node; while (true) { switch (astNodeTag(tree, n)) { case AST_NODE_ROOT: return 0; case AST_NODE_TEST_DECL: case AST_NODE_ERRDEFER: case AST_NODE_DEFER: case AST_NODE_BOOL_NOT: case AST_NODE_NEGATION: case AST_NODE_BIT_NOT: case AST_NODE_NEGATION_WRAP: case AST_NODE_ADDRESS_OF: case AST_NODE_TRY: case AST_NODE_OPTIONAL_TYPE: case AST_NODE_SWITCH: case AST_NODE_SWITCH_COMMA: case AST_NODE_IF_SIMPLE: case AST_NODE_IF: case AST_NODE_SUSPEND: case AST_NODE_RESUME: case AST_NODE_CONTINUE: case AST_NODE_BREAK: case AST_NODE_RETURN: case AST_NODE_ANYFRAME_TYPE: case AST_NODE_IDENTIFIER: case AST_NODE_ANYFRAME_LITERAL: case AST_NODE_CHAR_LITERAL: case AST_NODE_NUMBER_LITERAL: case AST_NODE_UNREACHABLE_LITERAL: case AST_NODE_STRING_LITERAL: case AST_NODE_MULTILINE_STRING_LITERAL: case AST_NODE_GROUPED_EXPRESSION: case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: case AST_NODE_BUILTIN_CALL: case AST_NODE_BUILTIN_CALL_COMMA: case AST_NODE_ERROR_SET_DECL: case AST_NODE_COMPTIME: case AST_NODE_NOSUSPEND: case AST_NODE_ASM_SIMPLE: case AST_NODE_ASM: case AST_NODE_ARRAY_TYPE: case AST_NODE_ARRAY_TYPE_SENTINEL: case AST_NODE_ERROR_VALUE: return astNodeMainToken(tree, n) - end_offset; case AST_NODE_ARRAY_INIT_DOT: case AST_NODE_ARRAY_INIT_DOT_COMMA: case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: case AST_NODE_STRUCT_INIT_DOT: case AST_NODE_STRUCT_INIT_DOT_COMMA: case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: case AST_NODE_ENUM_LITERAL: return astNodeMainToken(tree, n) - 1 - end_offset; case AST_NODE_CATCH: case AST_NODE_EQUAL_EQUAL: case AST_NODE_BANG_EQUAL: case AST_NODE_LESS_THAN: case AST_NODE_GREATER_THAN: case AST_NODE_LESS_OR_EQUAL: case AST_NODE_GREATER_OR_EQUAL: case AST_NODE_ASSIGN_MUL: case AST_NODE_ASSIGN_DIV: case AST_NODE_ASSIGN_MOD: case AST_NODE_ASSIGN_ADD: case AST_NODE_ASSIGN_SUB: case AST_NODE_ASSIGN_SHL: case AST_NODE_ASSIGN_SHL_SAT: case AST_NODE_ASSIGN_SHR: case AST_NODE_ASSIGN_BIT_AND: case AST_NODE_ASSIGN_BIT_XOR: case AST_NODE_ASSIGN_BIT_OR: case AST_NODE_ASSIGN_MUL_WRAP: case AST_NODE_ASSIGN_ADD_WRAP: case AST_NODE_ASSIGN_SUB_WRAP: case AST_NODE_ASSIGN_MUL_SAT: case AST_NODE_ASSIGN_ADD_SAT: case AST_NODE_ASSIGN_SUB_SAT: case AST_NODE_ASSIGN: case AST_NODE_MERGE_ERROR_SETS: case AST_NODE_MUL: case AST_NODE_DIV: case AST_NODE_MOD: case AST_NODE_MUL_WRAP: case AST_NODE_MUL_SAT: case AST_NODE_ADD: case AST_NODE_SUB: case AST_NODE_ARRAY_CAT: case AST_NODE_ADD_WRAP: case AST_NODE_SUB_WRAP: case AST_NODE_ADD_SAT: case AST_NODE_SUB_SAT: case AST_NODE_SHL: case AST_NODE_SHL_SAT: case AST_NODE_SHR: case AST_NODE_BIT_AND: case AST_NODE_BIT_XOR: case AST_NODE_BIT_OR: case AST_NODE_ORELSE: case AST_NODE_BOOL_AND: case AST_NODE_BOOL_OR: case AST_NODE_SLICE_OPEN: case AST_NODE_ARRAY_ACCESS: case AST_NODE_ARRAY_INIT_ONE: case AST_NODE_ARRAY_INIT_ONE_COMMA: case AST_NODE_SWITCH_RANGE: case AST_NODE_ERROR_UNION: case AST_NODE_FOR_RANGE: case AST_NODE_CALL_ONE: case AST_NODE_CALL_ONE_COMMA: case AST_NODE_STRUCT_INIT_ONE: case AST_NODE_STRUCT_INIT_ONE_COMMA: case AST_NODE_FIELD_ACCESS: case AST_NODE_UNWRAP_OPTIONAL: case AST_NODE_SLICE: case AST_NODE_SLICE_SENTINEL: case AST_NODE_ARRAY_INIT: case AST_NODE_ARRAY_INIT_COMMA: case AST_NODE_STRUCT_INIT: case AST_NODE_STRUCT_INIT_COMMA: case AST_NODE_CALL: case AST_NODE_CALL_COMMA: case AST_NODE_DEREF: n = astNodeData(tree, n).lhs; break; case AST_NODE_ASSIGN_DESTRUCTURE: n = astAssignDestructure(tree, n).ast.variables.ptr[0]; break; case AST_NODE_FN_DECL: case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: { AstTokenIndex i = astNodeMainToken(tree, n); // fn token while (i > 0) { i -= 1; switch (astTokenTag(tree, i)) { case TOKEN_KEYWORD_EXTERN: case TOKEN_KEYWORD_EXPORT: case TOKEN_KEYWORD_PUB: case TOKEN_KEYWORD_INLINE: case TOKEN_KEYWORD_NOINLINE: case TOKEN_STRING_LITERAL: continue; default: return i + 1 - end_offset; } } return i - end_offset; } case AST_NODE_CONTAINER_FIELD_INIT: case AST_NODE_CONTAINER_FIELD_ALIGN: case AST_NODE_CONTAINER_FIELD: { const AstTokenIndex name_token = astNodeMainToken(tree, n); const TokenTag comptime_tag = TOKEN_KEYWORD_COMPTIME; if (astIsTokenPrecededByTags(tree, name_token, &comptime_tag, 1)) end_offset += 1; return name_token - end_offset; } case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: { AstTokenIndex i = astNodeMainToken(tree, n); // mut token while (i > 0) { i -= 1; switch (astTokenTag(tree, i)) { case TOKEN_KEYWORD_EXTERN: case TOKEN_KEYWORD_EXPORT: case TOKEN_KEYWORD_COMPTIME: case TOKEN_KEYWORD_PUB: case TOKEN_KEYWORD_THREADLOCAL: case TOKEN_STRING_LITERAL: continue; default: return i + 1 - end_offset; } } return i - end_offset; } case AST_NODE_BLOCK: case AST_NODE_BLOCK_SEMICOLON: case AST_NODE_BLOCK_TWO: case AST_NODE_BLOCK_TWO_SEMICOLON: { // Look for a label. const AstTokenIndex lbrace = astNodeMainToken(tree, n); const TokenTag label[] = { TOKEN_IDENTIFIER, TOKEN_COLON }; if (astIsTokenPrecededByTags(tree, lbrace, label, 2)) end_offset += 2; return lbrace - end_offset; } case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: { const AstTokenIndex main_token = astNodeMainToken(tree, n); switch (astTokenTag(tree, main_token == 0 ? 0 : main_token - 1)) { case TOKEN_KEYWORD_PACKED: case TOKEN_KEYWORD_EXTERN: end_offset += 1; break; default: break; } return main_token - end_offset; } case AST_NODE_PTR_TYPE_ALIGNED: case AST_NODE_PTR_TYPE_SENTINEL: case AST_NODE_PTR_TYPE: case AST_NODE_PTR_TYPE_BIT_RANGE: return astNodeMainToken(tree, n) - end_offset; case AST_NODE_SWITCH_CASE_ONE: case AST_NODE_SWITCH_CASE_INLINE_ONE: case AST_NODE_SWITCH_CASE: case AST_NODE_SWITCH_CASE_INLINE: { AstFullSwitchCase full_switch; if (!astFullSwitchCase(tree, n, &full_switch)) UNREACHABLE("firstToken: fullSwitchCase(n).? is null"); if (full_switch.inline_token != AST_TOKEN_NONE) return full_switch.inline_token; if (full_switch.ast.values.len == 0) return full_switch.ast.arrow_token - 1 - end_offset; // else n = full_switch.ast.values.ptr[0]; break; } case AST_NODE_ASM_OUTPUT: case AST_NODE_ASM_INPUT: assert(astTokenTag(tree, astNodeMainToken(tree, n) - 1) == TOKEN_L_BRACKET); return astNodeMainToken(tree, n) - 1 - end_offset; case AST_NODE_WHILE_SIMPLE: case AST_NODE_WHILE_CONT: case AST_NODE_WHILE: case AST_NODE_FOR_SIMPLE: case AST_NODE_FOR: { // Look for a label and inline. const AstTokenIndex main_token = astNodeMainToken(tree, n); AstTokenIndex result = main_token; const TokenTag inline_tag = TOKEN_KEYWORD_INLINE; const TokenTag label[] = { TOKEN_IDENTIFIER, TOKEN_COLON }; if (astIsTokenPrecededByTags(tree, result, &inline_tag, 1)) result = result - 1; if (astIsTokenPrecededByTags(tree, result, label, 2)) result = result - 2; return result - end_offset; } } } } // Ast.zig:lastToken AstTokenIndex astLastToken(const Ast* tree, AstNodeIndex node) { AstNodeIndex n = node; uint32_t end_offset = 0; while (true) { const AstData data = astNodeData(tree, n); const uint32_t* extra = tree->extra_data.arr; switch (astNodeTag(tree, n)) { case AST_NODE_ROOT: return tree->tokens.len - 1; case AST_NODE_BOOL_NOT: case AST_NODE_NEGATION: case AST_NODE_BIT_NOT: case AST_NODE_NEGATION_WRAP: case AST_NODE_ADDRESS_OF: case AST_NODE_TRY: case AST_NODE_OPTIONAL_TYPE: case AST_NODE_SUSPEND: case AST_NODE_RESUME: case AST_NODE_NOSUSPEND: case AST_NODE_COMPTIME: n = data.lhs; break; case AST_NODE_CATCH: case AST_NODE_EQUAL_EQUAL: case AST_NODE_BANG_EQUAL: case AST_NODE_LESS_THAN: case AST_NODE_GREATER_THAN: case AST_NODE_LESS_OR_EQUAL: case AST_NODE_GREATER_OR_EQUAL: case AST_NODE_ASSIGN_MUL: case AST_NODE_ASSIGN_DIV: case AST_NODE_ASSIGN_MOD: case AST_NODE_ASSIGN_ADD: case AST_NODE_ASSIGN_SUB: case AST_NODE_ASSIGN_SHL: case AST_NODE_ASSIGN_SHL_SAT: case AST_NODE_ASSIGN_SHR: case AST_NODE_ASSIGN_BIT_AND: case AST_NODE_ASSIGN_BIT_XOR: case AST_NODE_ASSIGN_BIT_OR: case AST_NODE_ASSIGN_MUL_WRAP: case AST_NODE_ASSIGN_ADD_WRAP: case AST_NODE_ASSIGN_SUB_WRAP: case AST_NODE_ASSIGN_MUL_SAT: case AST_NODE_ASSIGN_ADD_SAT: case AST_NODE_ASSIGN_SUB_SAT: case AST_NODE_ASSIGN: case AST_NODE_MERGE_ERROR_SETS: case AST_NODE_MUL: case AST_NODE_DIV: case AST_NODE_MOD: case AST_NODE_MUL_WRAP: case AST_NODE_MUL_SAT: case AST_NODE_ADD: case AST_NODE_SUB: case AST_NODE_ARRAY_CAT: case AST_NODE_ADD_WRAP: case AST_NODE_SUB_WRAP: case AST_NODE_ADD_SAT: case AST_NODE_SUB_SAT: case AST_NODE_SHL: case AST_NODE_SHL_SAT: case AST_NODE_SHR: case AST_NODE_BIT_AND: case AST_NODE_BIT_XOR: case AST_NODE_BIT_OR: case AST_NODE_ORELSE: case AST_NODE_BOOL_AND: case AST_NODE_BOOL_OR: case AST_NODE_ERROR_UNION: case AST_NODE_IF_SIMPLE: case AST_NODE_WHILE_SIMPLE: case AST_NODE_FOR_SIMPLE: case AST_NODE_FN_DECL: case AST_NODE_ARRAY_TYPE: case AST_NODE_SWITCH_RANGE: case AST_NODE_TEST_DECL: case AST_NODE_ANYFRAME_TYPE: case AST_NODE_SWITCH_CASE_ONE: case AST_NODE_SWITCH_CASE_INLINE_ONE: case AST_NODE_PTR_TYPE_ALIGNED: case AST_NODE_PTR_TYPE_SENTINEL: case AST_NODE_ASSIGN_DESTRUCTURE: case AST_NODE_PTR_TYPE: case AST_NODE_PTR_TYPE_BIT_RANGE: case AST_NODE_SWITCH_CASE: case AST_NODE_SWITCH_CASE_INLINE: case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: n = data.rhs; break; case AST_NODE_DEFER: case AST_NODE_ERRDEFER: n = data.lhs; break; case AST_NODE_FOR_RANGE: if (data.rhs == AST_NODE_NONE) return astNodeMainToken(tree, n) + end_offset; n = data.rhs; break; case AST_NODE_FIELD_ACCESS: case AST_NODE_UNWRAP_OPTIONAL: case AST_NODE_ASM_SIMPLE: case AST_NODE_GROUPED_EXPRESSION: case AST_NODE_ASM_INPUT: case AST_NODE_MULTILINE_STRING_LITERAL: case AST_NODE_ERROR_SET_DECL: case AST_NODE_ASM_OUTPUT: return data.rhs + end_offset; case AST_NODE_ERROR_VALUE: return astNodeMainToken(tree, n) + 2 + end_offset; case AST_NODE_ANYFRAME_LITERAL: case AST_NODE_CHAR_LITERAL: case AST_NODE_NUMBER_LITERAL: case AST_NODE_UNREACHABLE_LITERAL: case AST_NODE_IDENTIFIER: case AST_NODE_DEREF: case AST_NODE_ENUM_LITERAL: case AST_NODE_STRING_LITERAL: return astNodeMainToken(tree, n) + end_offset; case AST_NODE_RETURN: if (data.lhs == AST_NODE_NONE) return astNodeMainToken(tree, n) + end_offset; n = data.lhs; break; case AST_NODE_CALL: { const AstNodeSlice params = astExtraSubRange(tree, data.rhs); assert(params.len != 0); end_offset += 1; // for the rparen n = params.ptr[params.len - 1]; // last parameter break; } case AST_NODE_TAGGED_UNION_ENUM_TAG: { const AstNodeSlice members = astExtraSubRange(tree, data.rhs); if (members.len == 0) { end_offset += 4; // for the rparen + rparen + lbrace + rbrace n = data.lhs; } else { end_offset += 1; // for the rbrace n = members.ptr[members.len - 1]; // last parameter } break; } case AST_NODE_CALL_COMMA: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: { const AstNodeSlice params = astExtraSubRange(tree, data.rhs); assert(params.len != 0); end_offset += 2; // for the comma/semicolon + rparen/rbrace n = params.ptr[params.len - 1]; // last parameter break; } case AST_NODE_SWITCH: { const AstNodeSlice cases = astExtraSubRange(tree, data.rhs); if (cases.len == 0) { end_offset += 3; // rparen, lbrace, rbrace n = data.lhs; } else { end_offset += 1; // for the rbrace n = cases.ptr[cases.len - 1]; // last case } break; } case AST_NODE_CONTAINER_DECL_ARG: { const AstNodeSlice members = astExtraSubRange(tree, data.rhs); if (members.len == 0) { end_offset += 3; // for the rparen + lbrace + rbrace n = data.lhs; } else { end_offset += 1; // for the rbrace n = members.ptr[members.len - 1]; // last parameter } break; } case AST_NODE_ASM: return extra[data.rhs + AST_EXTRA_ASM_RPAREN] + end_offset; case AST_NODE_ARRAY_INIT: case AST_NODE_STRUCT_INIT: { const AstNodeSlice elements = astExtraSubRange(tree, data.rhs); assert(elements.len != 0); end_offset += 1; // for the rbrace n = elements.ptr[elements.len - 1]; // last element break; } case AST_NODE_ARRAY_INIT_COMMA: case AST_NODE_STRUCT_INIT_COMMA: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_SWITCH_COMMA: { const AstNodeSlice members = astExtraSubRange(tree, data.rhs); assert(members.len != 0); end_offset += 2; // for the comma + rbrace n = members.ptr[members.len - 1]; // last parameter break; } case AST_NODE_ARRAY_INIT_DOT: case AST_NODE_STRUCT_INIT_DOT: case AST_NODE_BLOCK: case AST_NODE_CONTAINER_DECL: case AST_NODE_TAGGED_UNION: case AST_NODE_BUILTIN_CALL: assert(data.lhs != data.rhs); end_offset += 1; // for the rbrace n = extra[data.rhs - 1]; // last statement break; case AST_NODE_ARRAY_INIT_DOT_COMMA: case AST_NODE_STRUCT_INIT_DOT_COMMA: case AST_NODE_BLOCK_SEMICOLON: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_TAGGED_UNION_TRAILING: case AST_NODE_BUILTIN_CALL_COMMA: assert(data.lhs != data.rhs); end_offset += 2; // for the comma/semicolon + rbrace/rparen n = extra[data.rhs - 1]; // last member break; case AST_NODE_CALL_ONE: end_offset += 1; // for the rparen if (data.rhs == AST_NODE_NONE) return astNodeMainToken(tree, n) + end_offset; n = data.rhs; break; case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_BLOCK_TWO: case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_TAGGED_UNION_TWO: if (data.rhs != AST_NODE_NONE) { end_offset += 1; // for the rparen/rbrace n = data.rhs; } else if (data.lhs != AST_NODE_NONE) { end_offset += 1; // for the rparen/rbrace n = data.lhs; } else { switch (astNodeTag(tree, n)) { case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_BLOCK_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO: end_offset += 1; // rbrace break; case AST_NODE_BUILTIN_CALL_TWO: end_offset += 2; // lparen/lbrace + rparen/rbrace break; case AST_NODE_CONTAINER_DECL_TWO: { uint32_t i = 2; // lbrace + rbrace while (astTokenTag(tree, astNodeMainToken(tree, n) + i) == TOKEN_CONTAINER_DOC_COMMENT) i += 1; end_offset += i; break; } case AST_NODE_TAGGED_UNION_TWO: { uint32_t i = 5; // (enum) {} while (astTokenTag(tree, astNodeMainToken(tree, n) + i) == TOKEN_CONTAINER_DOC_COMMENT) i += 1; end_offset += i; break; } default: UNREACHABLE("lastToken: not a two-member node"); } return astNodeMainToken(tree, n) + end_offset; } break; case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: case AST_NODE_BUILTIN_CALL_TWO_COMMA: case AST_NODE_BLOCK_TWO_SEMICOLON: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_TAGGED_UNION_TWO_TRAILING: end_offset += 2; // for the comma/semicolon + rbrace/rparen if (data.rhs != AST_NODE_NONE) { n = data.rhs; } else if (data.lhs != AST_NODE_NONE) { n = data.lhs; } else { UNREACHABLE("lastToken: a trailing two-member node"); } break; case AST_NODE_SIMPLE_VAR_DECL: if (data.rhs != AST_NODE_NONE) { n = data.rhs; } else if (data.lhs != AST_NODE_NONE) { n = data.lhs; } else { end_offset += 1; // from mut token to name return astNodeMainToken(tree, n) + end_offset; } break; case AST_NODE_ALIGNED_VAR_DECL: if (data.rhs != AST_NODE_NONE) { n = data.rhs; } else { end_offset += 1; // for the rparen n = data.lhs; } break; case AST_NODE_GLOBAL_VAR_DECL: if (data.rhs != AST_NODE_NONE) { n = data.rhs; } else { const uint32_t* decl = extra + data.lhs; if (decl[AST_EXTRA_GLOBAL_VAR_DECL_SECTION_NODE] != AST_NODE_NONE) { end_offset += 1; // for the rparen n = decl[AST_EXTRA_GLOBAL_VAR_DECL_SECTION_NODE]; } else if (decl[AST_EXTRA_GLOBAL_VAR_DECL_ADDRSPACE_NODE] != AST_NODE_NONE) { end_offset += 1; // for the rparen n = decl[AST_EXTRA_GLOBAL_VAR_DECL_ADDRSPACE_NODE]; } else if (decl[AST_EXTRA_GLOBAL_VAR_DECL_ALIGN_NODE] != AST_NODE_NONE) { end_offset += 1; // for the rparen n = decl[AST_EXTRA_GLOBAL_VAR_DECL_ALIGN_NODE]; } else if (decl[AST_EXTRA_GLOBAL_VAR_DECL_TYPE_NODE] != AST_NODE_NONE) { n = decl[AST_EXTRA_GLOBAL_VAR_DECL_TYPE_NODE]; } else { end_offset += 1; // from mut token to name return astNodeMainToken(tree, n) + end_offset; } } break; case AST_NODE_LOCAL_VAR_DECL: if (data.rhs != AST_NODE_NONE) { n = data.rhs; } else { end_offset += 1; // for the rparen n = extra[data.lhs + AST_EXTRA_LOCAL_VAR_DECL_ALIGN_NODE]; } break; case AST_NODE_CONTAINER_FIELD_INIT: n = data.rhs != AST_NODE_NONE ? data.rhs : data.lhs; break; case AST_NODE_ARRAY_ACCESS: case AST_NODE_ARRAY_INIT_ONE: case AST_NODE_CONTAINER_FIELD_ALIGN: end_offset += 1; // for the rbracket/rbrace/rparen n = data.rhs; break; case AST_NODE_CONTAINER_FIELD: n = extra[data.rhs + AST_EXTRA_CONTAINER_FIELD_VALUE_EXPR]; break; case AST_NODE_STRUCT_INIT_ONE: end_offset += 1; // rbrace if (data.rhs == AST_NODE_NONE) return astNodeMainToken(tree, n) + end_offset; n = data.rhs; break; case AST_NODE_SLICE_OPEN: end_offset += 2; // ellipsis2 + rbracket, or comma + rparen n = data.rhs; break; case AST_NODE_ARRAY_INIT_ONE_COMMA: end_offset += 2; // comma + rbrace n = data.rhs; break; case AST_NODE_CALL_ONE_COMMA: case AST_NODE_STRUCT_INIT_ONE_COMMA: end_offset += 2; // ellipsis2 + rbracket, or comma + rparen assert(data.rhs != AST_NODE_NONE); n = data.rhs; break; case AST_NODE_SLICE: end_offset += 1; // rbracket n = extra[data.rhs + AST_EXTRA_SLICE_END]; break; case AST_NODE_SLICE_SENTINEL: end_offset += 1; // rbracket n = extra[data.rhs + AST_EXTRA_SLICE_SENTINEL_SENTINEL]; break; case AST_NODE_CONTINUE: case AST_NODE_BREAK: if (data.rhs != AST_NODE_NONE) n = data.rhs; else if (data.lhs != AST_TOKEN_NONE) return data.lhs + end_offset; else return astNodeMainToken(tree, n) + end_offset; break; case AST_NODE_WHILE_CONT: n = extra[data.rhs + AST_EXTRA_WHILE_CONT_THEN_EXPR]; break; case AST_NODE_WHILE: n = extra[data.rhs + AST_EXTRA_WHILE_ELSE_EXPR]; break; case AST_NODE_IF: n = extra[data.rhs + AST_EXTRA_IF_ELSE_EXPR]; break; case AST_NODE_FOR: { const uint32_t inputs = data.rhs & AST_FOR_INPUTS_MASK; const uint32_t has_else = (data.rhs & AST_FOR_HAS_ELSE) != 0; n = extra[data.lhs + inputs + has_else]; break; } case AST_NODE_ARRAY_TYPE_SENTINEL: n = extra[data.rhs + AST_EXTRA_ARRAY_TYPE_SENTINEL_ELEM_TYPE]; break; } } } // Ast.zig:globalVarDecl static AstFullVarDecl astGlobalVarDecl(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_GLOBAL_VAR_DECL); const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.lhs; return astFullVarDeclComponents(tree, (AstFullVarDeclComponents) { .type_node = extra[AST_EXTRA_GLOBAL_VAR_DECL_TYPE_NODE], .align_node = extra[AST_EXTRA_GLOBAL_VAR_DECL_ALIGN_NODE], .addrspace_node = extra[AST_EXTRA_GLOBAL_VAR_DECL_ADDRSPACE_NODE], .section_node = extra[AST_EXTRA_GLOBAL_VAR_DECL_SECTION_NODE], .init_node = data.rhs, .mut_token = astNodeMainToken(tree, node), }); } // Ast.zig:localVarDecl static AstFullVarDecl astLocalVarDecl(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_LOCAL_VAR_DECL); const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.lhs; return astFullVarDeclComponents(tree, (AstFullVarDeclComponents) { .type_node = extra[AST_EXTRA_LOCAL_VAR_DECL_TYPE_NODE], .align_node = extra[AST_EXTRA_LOCAL_VAR_DECL_ALIGN_NODE], .addrspace_node = AST_NODE_NONE, .section_node = AST_NODE_NONE, .init_node = data.rhs, .mut_token = astNodeMainToken(tree, node), }); } // Ast.zig:simpleVarDecl static AstFullVarDecl astSimpleVarDecl(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_SIMPLE_VAR_DECL); const AstData data = astNodeData(tree, node); return astFullVarDeclComponents(tree, (AstFullVarDeclComponents) { .type_node = data.lhs, .align_node = AST_NODE_NONE, .addrspace_node = AST_NODE_NONE, .section_node = AST_NODE_NONE, .init_node = data.rhs, .mut_token = astNodeMainToken(tree, node), }); } // Ast.zig:alignedVarDecl static AstFullVarDecl astAlignedVarDecl(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_ALIGNED_VAR_DECL); const AstData data = astNodeData(tree, node); return astFullVarDeclComponents(tree, (AstFullVarDeclComponents) { .type_node = AST_NODE_NONE, .align_node = data.lhs, .addrspace_node = AST_NODE_NONE, .section_node = AST_NODE_NONE, .init_node = data.rhs, .mut_token = astNodeMainToken(tree, node), }); } // Ast.zig:assignDestructure AstFullAssignDestructure astAssignDestructure( const Ast* tree, AstNodeIndex node) { const AstData data = astNodeData(tree, node); const uint32_t variable_count = tree->extra_data.arr[data.lhs]; return astFullAssignDestructureComponents(tree, (AstFullAssignDestructureComponents) { .variables = astExtraDataSlice( tree, data.lhs + 1, data.lhs + 1 + variable_count), .equal_token = astNodeMainToken(tree, node), .value_expr = data.rhs, }); } // Ast.zig:ifSimple static AstFullIf astIfSimple(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_IF_SIMPLE); const AstData data = astNodeData(tree, node); return astFullIfComponents(tree, (AstFullIfComponents) { .cond_expr = data.lhs, .then_expr = data.rhs, .else_expr = AST_NODE_NONE, .if_token = astNodeMainToken(tree, node), }); } // Ast.zig:ifFull static AstFullIf astIfFull(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_IF); const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.rhs; return astFullIfComponents(tree, (AstFullIfComponents) { .cond_expr = data.lhs, .then_expr = extra[AST_EXTRA_IF_THEN_EXPR], .else_expr = extra[AST_EXTRA_IF_ELSE_EXPR], .if_token = astNodeMainToken(tree, node), }); } // A container field `main_token` names is tuple-like unless it is // `name: type`. static bool astFieldIsTupleLike(const Ast* tree, AstTokenIndex main_token) { return astTokenTag(tree, main_token) != TOKEN_IDENTIFIER || astTokenTag(tree, main_token + 1) != TOKEN_COLON; } // Ast.zig:containerField static AstFullContainerField astContainerField( const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_CONTAINER_FIELD); const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.rhs; const AstTokenIndex main_token = astNodeMainToken(tree, node); return astFullContainerFieldComponents(tree, (AstFullContainerFieldComponents) { .main_token = main_token, .type_expr = data.lhs, .align_expr = extra[AST_EXTRA_CONTAINER_FIELD_ALIGN_EXPR], .value_expr = extra[AST_EXTRA_CONTAINER_FIELD_VALUE_EXPR], .tuple_like = astFieldIsTupleLike(tree, main_token), }); } // Ast.zig:containerFieldInit static AstFullContainerField astContainerFieldInit( const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_CONTAINER_FIELD_INIT); const AstData data = astNodeData(tree, node); const AstTokenIndex main_token = astNodeMainToken(tree, node); return astFullContainerFieldComponents(tree, (AstFullContainerFieldComponents) { .main_token = main_token, .type_expr = data.lhs, .align_expr = AST_NODE_NONE, .value_expr = data.rhs, .tuple_like = astFieldIsTupleLike(tree, main_token), }); } // Ast.zig:containerFieldAlign static AstFullContainerField astContainerFieldAlign( const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_CONTAINER_FIELD_ALIGN); const AstData data = astNodeData(tree, node); const AstTokenIndex main_token = astNodeMainToken(tree, node); return astFullContainerFieldComponents(tree, (AstFullContainerFieldComponents) { .main_token = main_token, .type_expr = data.lhs, .align_expr = data.rhs, .value_expr = AST_NODE_NONE, .tuple_like = astFieldIsTupleLike(tree, main_token), }); } // Ast.zig:fnProtoSimple static AstFullFnProto astFnProtoSimple( const Ast* tree, AstNodeIndex buffer[1], AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_FN_PROTO_SIMPLE); const AstData data = astNodeData(tree, node); return astFullFnProtoComponents(tree, (AstFullFnProtoComponents) { .proto_node = node, .fn_token = astNodeMainToken(tree, node), .return_type = data.rhs, .params = astLoadOptionalNodesIntoBuffer(1, buffer, &data.lhs), .align_expr = AST_NODE_NONE, .addrspace_expr = AST_NODE_NONE, .section_expr = AST_NODE_NONE, .callconv_expr = AST_NODE_NONE, }); } // Ast.zig:fnProtoMulti static AstFullFnProto astFnProtoMulti(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_FN_PROTO_MULTI); const AstData data = astNodeData(tree, node); return astFullFnProtoComponents(tree, (AstFullFnProtoComponents) { .proto_node = node, .fn_token = astNodeMainToken(tree, node), .return_type = data.rhs, .params = astExtraSubRange(tree, data.lhs), .align_expr = AST_NODE_NONE, .addrspace_expr = AST_NODE_NONE, .section_expr = AST_NODE_NONE, .callconv_expr = AST_NODE_NONE, }); } // Ast.zig:fnProtoOne static AstFullFnProto astFnProtoOne( const Ast* tree, AstNodeIndex buffer[1], AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_FN_PROTO_ONE); const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.lhs; return astFullFnProtoComponents(tree, (AstFullFnProtoComponents) { .proto_node = node, .fn_token = astNodeMainToken(tree, node), .return_type = data.rhs, .params = astLoadOptionalNodesIntoBuffer( 1, buffer, &extra[AST_EXTRA_FN_PROTO_ONE_PARAM]), .align_expr = extra[AST_EXTRA_FN_PROTO_ONE_ALIGN_EXPR], .addrspace_expr = extra[AST_EXTRA_FN_PROTO_ONE_ADDRSPACE_EXPR], .section_expr = extra[AST_EXTRA_FN_PROTO_ONE_SECTION_EXPR], .callconv_expr = extra[AST_EXTRA_FN_PROTO_ONE_CALLCONV_EXPR], }); } // Ast.zig:fnProto static AstFullFnProto astFnProto(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_FN_PROTO); const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.lhs; return astFullFnProtoComponents(tree, (AstFullFnProtoComponents) { .proto_node = node, .fn_token = astNodeMainToken(tree, node), .return_type = data.rhs, .params = astExtraDataSlice(tree, extra[AST_EXTRA_FN_PROTO_PARAMS_START], extra[AST_EXTRA_FN_PROTO_PARAMS_END]), .align_expr = extra[AST_EXTRA_FN_PROTO_ALIGN_EXPR], .addrspace_expr = extra[AST_EXTRA_FN_PROTO_ADDRSPACE_EXPR], .section_expr = extra[AST_EXTRA_FN_PROTO_SECTION_EXPR], .callconv_expr = extra[AST_EXTRA_FN_PROTO_CALLCONV_EXPR], }); } // Ast.zig:structInitOne static AstFullStructInit astStructInitOne( const Ast* tree, AstNodeIndex buffer[1], AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_STRUCT_INIT_ONE || astNodeTag(tree, node) == AST_NODE_STRUCT_INIT_ONE_COMMA); const AstData data = astNodeData(tree, node); return (AstFullStructInit) { .ast = { .lbrace = astNodeMainToken(tree, node), .fields = astLoadOptionalNodesIntoBuffer(1, buffer, &data.rhs), .type_expr = data.lhs, } }; } // Ast.zig:structInitDotTwo static AstFullStructInit astStructInitDotTwo( const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_STRUCT_INIT_DOT_TWO || astNodeTag(tree, node) == AST_NODE_STRUCT_INIT_DOT_TWO_COMMA); const AstData data = astNodeData(tree, node); const AstNodeIndex items[2] = { data.lhs, data.rhs }; return (AstFullStructInit) { .ast = { .lbrace = astNodeMainToken(tree, node), .fields = astLoadOptionalNodesIntoBuffer(2, buffer, items), .type_expr = AST_NODE_NONE, } }; } // Ast.zig:structInitDot static AstFullStructInit astStructInitDot(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_STRUCT_INIT_DOT || astNodeTag(tree, node) == AST_NODE_STRUCT_INIT_DOT_COMMA); const AstData data = astNodeData(tree, node); return (AstFullStructInit) { .ast = { .lbrace = astNodeMainToken(tree, node), .fields = astExtraDataSlice(tree, data.lhs, data.rhs), .type_expr = AST_NODE_NONE, } }; } // Ast.zig:structInit static AstFullStructInit astStructInit(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_STRUCT_INIT || astNodeTag(tree, node) == AST_NODE_STRUCT_INIT_COMMA); const AstData data = astNodeData(tree, node); return ( AstFullStructInit) { .ast = { .lbrace = astNodeMainToken(tree, node), .fields = astExtraSubRange(tree, data.rhs), .type_expr = data.lhs, } }; } // Ast.zig:arrayInitOne static AstFullArrayInit astArrayInitOne( const Ast* tree, AstNodeIndex buffer[1], AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_ARRAY_INIT_ONE || astNodeTag(tree, node) == AST_NODE_ARRAY_INIT_ONE_COMMA); const AstData data = astNodeData(tree, node); buffer[0] = data.rhs; return (AstFullArrayInit) { .ast = { .lbrace = astNodeMainToken(tree, node), .elements = { buffer, 1 }, .type_expr = data.lhs, } }; } // Ast.zig:arrayInitDotTwo static AstFullArrayInit astArrayInitDotTwo( const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_ARRAY_INIT_DOT_TWO || astNodeTag(tree, node) == AST_NODE_ARRAY_INIT_DOT_TWO_COMMA); const AstData data = astNodeData(tree, node); const AstNodeIndex items[2] = { data.lhs, data.rhs }; return (AstFullArrayInit) { .ast = { .lbrace = astNodeMainToken(tree, node), .elements = astLoadOptionalNodesIntoBuffer(2, buffer, items), .type_expr = AST_NODE_NONE, } }; } // Ast.zig:arrayInitDot static AstFullArrayInit astArrayInitDot(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_ARRAY_INIT_DOT || astNodeTag(tree, node) == AST_NODE_ARRAY_INIT_DOT_COMMA); const AstData data = astNodeData(tree, node); return (AstFullArrayInit) { .ast = { .lbrace = astNodeMainToken(tree, node), .elements = astExtraDataSlice(tree, data.lhs, data.rhs), .type_expr = AST_NODE_NONE, } }; } // Ast.zig:arrayInit static AstFullArrayInit astArrayInit(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_ARRAY_INIT || astNodeTag(tree, node) == AST_NODE_ARRAY_INIT_COMMA); const AstData data = astNodeData(tree, node); return ( AstFullArrayInit) { .ast = { .lbrace = astNodeMainToken(tree, node), .elements = astExtraSubRange(tree, data.rhs), .type_expr = data.lhs, } }; } // Ast.zig:arrayType static AstFullArrayType astArrayType(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_ARRAY_TYPE); const AstData data = astNodeData(tree, node); return (AstFullArrayType) { .ast = { .lbracket = astNodeMainToken(tree, node), .elem_count = data.lhs, .sentinel = AST_NODE_NONE, .elem_type = data.rhs, } }; } // Ast.zig:arrayTypeSentinel static AstFullArrayType astArrayTypeSentinel( const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_ARRAY_TYPE_SENTINEL); const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.rhs; return (AstFullArrayType) { .ast = { .lbracket = astNodeMainToken(tree, node), .elem_count = data.lhs, .sentinel = extra[AST_EXTRA_ARRAY_TYPE_SENTINEL_SENTINEL], .elem_type = extra[AST_EXTRA_ARRAY_TYPE_SENTINEL_ELEM_TYPE], } }; } // Ast.zig:ptrTypeAligned static AstFullPtrType astPtrTypeAligned(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_PTR_TYPE_ALIGNED); const AstData data = astNodeData(tree, node); return astFullPtrTypeComponents(tree, (AstFullPtrTypeComponents) { .main_token = astNodeMainToken(tree, node), .align_node = data.lhs, .addrspace_node = AST_NODE_NONE, .sentinel = AST_NODE_NONE, .bit_range_start = AST_NODE_NONE, .bit_range_end = AST_NODE_NONE, .child_type = data.rhs, }); } // Ast.zig:ptrTypeSentinel static AstFullPtrType astPtrTypeSentinel(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_PTR_TYPE_SENTINEL); const AstData data = astNodeData(tree, node); return astFullPtrTypeComponents(tree, (AstFullPtrTypeComponents) { .main_token = astNodeMainToken(tree, node), .align_node = AST_NODE_NONE, .addrspace_node = AST_NODE_NONE, .sentinel = data.lhs, .bit_range_start = AST_NODE_NONE, .bit_range_end = AST_NODE_NONE, .child_type = data.rhs, }); } // Ast.zig:ptrType static AstFullPtrType astPtrType(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_PTR_TYPE); const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.lhs; return astFullPtrTypeComponents(tree, (AstFullPtrTypeComponents) { .main_token = astNodeMainToken(tree, node), .align_node = extra[AST_EXTRA_PTR_TYPE_ALIGN_NODE], .addrspace_node = extra[AST_EXTRA_PTR_TYPE_ADDRSPACE_NODE], .sentinel = extra[AST_EXTRA_PTR_TYPE_SENTINEL], .bit_range_start = AST_NODE_NONE, .bit_range_end = AST_NODE_NONE, .child_type = data.rhs, }); } // Ast.zig:ptrTypeBitRange static AstFullPtrType astPtrTypeBitRange(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_PTR_TYPE_BIT_RANGE); const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.lhs; return astFullPtrTypeComponents(tree, (AstFullPtrTypeComponents) { .main_token = astNodeMainToken(tree, node), .align_node = extra[AST_EXTRA_PTR_TYPE_BIT_RANGE_ALIGN_NODE], .addrspace_node = extra[AST_EXTRA_PTR_TYPE_BIT_RANGE_ADDRSPACE_NODE], .sentinel = extra[AST_EXTRA_PTR_TYPE_BIT_RANGE_SENTINEL], .bit_range_start = extra[AST_EXTRA_PTR_TYPE_BIT_RANGE_BIT_RANGE_START], .bit_range_end = extra[AST_EXTRA_PTR_TYPE_BIT_RANGE_BIT_RANGE_END], .child_type = data.rhs, }); } // Ast.zig:sliceOpen static AstFullSlice astSliceOpen(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_SLICE_OPEN); const AstData data = astNodeData(tree, node); return (AstFullSlice) { .ast = { .sliced = data.lhs, .lbracket = astNodeMainToken(tree, node), .start = data.rhs, .end = AST_NODE_NONE, .sentinel = AST_NODE_NONE, } }; } // Ast.zig:slice static AstFullSlice astSlice(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_SLICE); const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.rhs; return (AstFullSlice) { .ast = { .sliced = data.lhs, .lbracket = astNodeMainToken(tree, node), .start = extra[AST_EXTRA_SLICE_START], .end = extra[AST_EXTRA_SLICE_END], .sentinel = AST_NODE_NONE, } }; } // Ast.zig:sliceSentinel static AstFullSlice astSliceSentinel(const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_SLICE_SENTINEL); const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.rhs; return (AstFullSlice) { .ast = { .sliced = data.lhs, .lbracket = astNodeMainToken(tree, node), .start = extra[AST_EXTRA_SLICE_SENTINEL_START], .end = extra[AST_EXTRA_SLICE_SENTINEL_END], .sentinel = extra[AST_EXTRA_SLICE_SENTINEL_SENTINEL], } }; } // Ast.zig:containerDeclTwo static AstFullContainerDecl astContainerDeclTwo( const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_CONTAINER_DECL_TWO || astNodeTag(tree, node) == AST_NODE_CONTAINER_DECL_TWO_TRAILING); const AstData data = astNodeData(tree, node); const AstNodeIndex items[2] = { data.lhs, data.rhs }; return astFullContainerDeclComponents(tree, (AstFullContainerDeclComponents) { .main_token = astNodeMainToken(tree, node), .enum_token = AST_TOKEN_NONE, .members = astLoadOptionalNodesIntoBuffer(2, buffer, items), .arg = AST_NODE_NONE, }); } // Ast.zig:containerDecl static AstFullContainerDecl astContainerDecl( const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_CONTAINER_DECL || astNodeTag(tree, node) == AST_NODE_CONTAINER_DECL_TRAILING); const AstData data = astNodeData(tree, node); return astFullContainerDeclComponents(tree, (AstFullContainerDeclComponents) { .main_token = astNodeMainToken(tree, node), .enum_token = AST_TOKEN_NONE, .members = astExtraDataSlice(tree, data.lhs, data.rhs), .arg = AST_NODE_NONE, }); } // Ast.zig:containerDeclArg static AstFullContainerDecl astContainerDeclArg( const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_CONTAINER_DECL_ARG || astNodeTag(tree, node) == AST_NODE_CONTAINER_DECL_ARG_TRAILING); const AstData data = astNodeData(tree, node); return astFullContainerDeclComponents(tree, (AstFullContainerDeclComponents) { .main_token = astNodeMainToken(tree, node), .enum_token = AST_TOKEN_NONE, .members = astExtraSubRange(tree, data.rhs), .arg = data.lhs, }); } // Ast.zig:containerDeclRoot AstFullContainerDecl astContainerDeclRoot(const Ast* tree) { return (AstFullContainerDecl) { .layout_token = AST_TOKEN_NONE, .ast = { .main_token = 0, .enum_token = AST_TOKEN_NONE, .members = astRootDecls(tree), .arg = AST_NODE_NONE, }, }; } // Ast.zig:taggedUnionTwo static AstFullContainerDecl astTaggedUnionTwo( const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_TAGGED_UNION_TWO || astNodeTag(tree, node) == AST_NODE_TAGGED_UNION_TWO_TRAILING); const AstData data = astNodeData(tree, node); const AstNodeIndex items[2] = { data.lhs, data.rhs }; const AstTokenIndex main_token = astNodeMainToken(tree, node); return astFullContainerDeclComponents(tree, (AstFullContainerDeclComponents) { .main_token = main_token, .enum_token = main_token + 2, // union lparen enum .members = astLoadOptionalNodesIntoBuffer(2, buffer, items), .arg = AST_NODE_NONE, }); } // Ast.zig:taggedUnion static AstFullContainerDecl astTaggedUnion( const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_TAGGED_UNION || astNodeTag(tree, node) == AST_NODE_TAGGED_UNION_TRAILING); const AstData data = astNodeData(tree, node); const AstTokenIndex main_token = astNodeMainToken(tree, node); return astFullContainerDeclComponents(tree, (AstFullContainerDeclComponents) { .main_token = main_token, .enum_token = main_token + 2, // union lparen enum .members = astExtraDataSlice(tree, data.lhs, data.rhs), .arg = AST_NODE_NONE, }); } // Ast.zig:taggedUnionEnumTag static AstFullContainerDecl astTaggedUnionEnumTag( const Ast* tree, AstNodeIndex node) { assert(astNodeTag(tree, node) == AST_NODE_TAGGED_UNION_ENUM_TAG || astNodeTag(tree, node) == AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING); const AstData data = astNodeData(tree, node); const AstTokenIndex main_token = astNodeMainToken(tree, node); return astFullContainerDeclComponents(tree, (AstFullContainerDeclComponents) { .main_token = main_token, .enum_token = main_token + 2, // union lparen enum .members = astExtraSubRange(tree, data.rhs), .arg = data.lhs, }); } // Ast.zig:switchFull static AstFullSwitch astSwitchFull(const Ast* tree, AstNodeIndex node) { const AstTokenIndex main_token = astNodeMainToken(tree, node); AstTokenIndex switch_token; AstTokenIndex label_token; switch (astTokenTag(tree, main_token)) { case TOKEN_IDENTIFIER: switch_token = main_token + 2; label_token = main_token; break; case TOKEN_KEYWORD_SWITCH: switch_token = main_token; label_token = AST_TOKEN_NONE; break; default: UNREACHABLE("switchFull: not a switch token"); } const AstData data = astNodeData(tree, node); return (AstFullSwitch) { .ast = { .switch_token = switch_token, .condition = data.lhs, .cases = astExtraSubRange(tree, data.rhs), }, .label_token = label_token, }; } // Ast.zig:switchCaseOne static AstFullSwitchCase astSwitchCaseOne(const Ast* tree, AstNodeIndex node) { const AstData* data = &tree->nodes.datas[node]; return astFullSwitchCaseComponents(tree, (AstFullSwitchCaseComponents) { .values = { &data->lhs, data->lhs == AST_NODE_NONE ? 0 : 1 }, .arrow_token = astNodeMainToken(tree, node), .target_expr = data->rhs, }, node); } // Ast.zig:switchCase static AstFullSwitchCase astSwitchCase(const Ast* tree, AstNodeIndex node) { const AstData data = astNodeData(tree, node); return astFullSwitchCaseComponents(tree, (AstFullSwitchCaseComponents) { .values = astExtraSubRange(tree, data.lhs), .arrow_token = astNodeMainToken(tree, node), .target_expr = data.rhs, }, node); } // Ast.zig:asmSimple static AstFullAsm astAsmSimple(const Ast* tree, AstNodeIndex node) { const AstData data = astNodeData(tree, node); return astFullAsmComponents(tree, (AstFullAsmComponents) { .asm_token = astNodeMainToken(tree, node), .template = data.lhs, .items = { NULL, 0 }, .rparen = data.rhs, .clobbers = AST_NODE_NONE, }); } // Ast.zig:asmFull static AstFullAsm astAsmFull(const Ast* tree, AstNodeIndex node) { const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.rhs; return astFullAsmComponents(tree, (AstFullAsmComponents) { .asm_token = astNodeMainToken(tree, node), .template = data.lhs, .items = astExtraDataSlice(tree, extra[AST_EXTRA_ASM_ITEMS_START], extra[AST_EXTRA_ASM_ITEMS_END]), .clobbers = extra[AST_EXTRA_ASM_CLOBBERS], .rparen = extra[AST_EXTRA_ASM_RPAREN], }); } // Ast.zig:whileSimple static AstFullWhile astWhileSimple(const Ast* tree, AstNodeIndex node) { const AstData data = astNodeData(tree, node); return astFullWhileComponents(tree, (AstFullWhileComponents) { .while_token = astNodeMainToken(tree, node), .cond_expr = data.lhs, .cont_expr = AST_NODE_NONE, .then_expr = data.rhs, .else_expr = AST_NODE_NONE, }); } // Ast.zig:whileCont static AstFullWhile astWhileCont(const Ast* tree, AstNodeIndex node) { const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.rhs; return astFullWhileComponents(tree, (AstFullWhileComponents) { .while_token = astNodeMainToken(tree, node), .cond_expr = data.lhs, .cont_expr = extra[AST_EXTRA_WHILE_CONT_CONT_EXPR], .then_expr = extra[AST_EXTRA_WHILE_CONT_THEN_EXPR], .else_expr = AST_NODE_NONE, }); } // Ast.zig:whileFull static AstFullWhile astWhileFull(const Ast* tree, AstNodeIndex node) { const AstData data = astNodeData(tree, node); const uint32_t* extra = tree->extra_data.arr + data.rhs; return astFullWhileComponents(tree, (AstFullWhileComponents) { .while_token = astNodeMainToken(tree, node), .cond_expr = data.lhs, .cont_expr = extra[AST_EXTRA_WHILE_CONT_EXPR], .then_expr = extra[AST_EXTRA_WHILE_THEN_EXPR], .else_expr = extra[AST_EXTRA_WHILE_ELSE_EXPR], }); } // Ast.zig:forSimple static AstFullFor astForSimple(const Ast* tree, AstNodeIndex node) { const AstData* data = &tree->nodes.datas[node]; return astFullForComponents(tree, (AstFullForComponents) { .for_token = astNodeMainToken(tree, node), .inputs = { &data->lhs, 1 }, .then_expr = data->rhs, .else_expr = AST_NODE_NONE, }); } // Ast.zig:forFull static AstFullFor astForFull(const Ast* tree, AstNodeIndex node) { const AstData data = astNodeData(tree, node); const uint32_t inputs = data.rhs & AST_FOR_INPUTS_MASK; const bool has_else = (data.rhs & AST_FOR_HAS_ELSE) != 0; const uint32_t* extra = tree->extra_data.arr + data.lhs; return astFullForComponents(tree, (AstFullForComponents) { .for_token = astNodeMainToken(tree, node), .inputs = { extra, inputs }, .then_expr = extra[inputs], .else_expr = has_else ? extra[inputs + 1] : AST_NODE_NONE, }); } // Ast.zig:callOne static AstFullCall astCallOne( const Ast* tree, AstNodeIndex buffer[1], AstNodeIndex node) { const AstData data = astNodeData(tree, node); return (AstFullCall) { .ast = { .lparen = astNodeMainToken(tree, node), .fn_expr = data.lhs, .params = astLoadOptionalNodesIntoBuffer(1, buffer, &data.rhs), } }; } // Ast.zig:callFull static AstFullCall astCallFull(const Ast* tree, AstNodeIndex node) { const AstData data = astNodeData(tree, node); return (AstFullCall) { .ast = { .lparen = astNodeMainToken(tree, node), .fn_expr = data.lhs, .params = astExtraSubRange(tree, data.rhs), } }; } // Ast.zig:fullVarDeclComponents static AstFullVarDecl astFullVarDeclComponents( const Ast* tree, AstFullVarDeclComponents info) { AstFullVarDecl result = { .visib_token = AST_TOKEN_NONE, .extern_export_token = AST_TOKEN_NONE, .lib_name = AST_TOKEN_NONE, .threadlocal_token = AST_TOKEN_NONE, .comptime_token = AST_TOKEN_NONE, .ast = info, }; AstTokenIndex i = info.mut_token; while (i > 0) { i -= 1; switch (astTokenTag(tree, i)) { case TOKEN_KEYWORD_EXTERN: case TOKEN_KEYWORD_EXPORT: result.extern_export_token = i; break; case TOKEN_KEYWORD_COMPTIME: result.comptime_token = i; break; case TOKEN_KEYWORD_PUB: result.visib_token = i; break; case TOKEN_KEYWORD_THREADLOCAL: result.threadlocal_token = i; break; case TOKEN_STRING_LITERAL: result.lib_name = i; break; default: return result; } } return result; } // Ast.zig:fullAssignDestructureComponents static AstFullAssignDestructure astFullAssignDestructureComponents( const Ast* tree, AstFullAssignDestructureComponents info) { AstFullAssignDestructure result = { .comptime_token = AST_TOKEN_NONE, .ast = info, }; const AstTokenIndex first_variable_token = astFirstToken(tree, info.variables.ptr[0]); AstTokenIndex maybe_comptime_token; switch (astNodeTag(tree, info.variables.ptr[0])) { case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: case AST_NODE_SIMPLE_VAR_DECL: maybe_comptime_token = first_variable_token; break; default: maybe_comptime_token = first_variable_token - 1; break; } if (astTokenTag(tree, maybe_comptime_token) == TOKEN_KEYWORD_COMPTIME) result.comptime_token = maybe_comptime_token; return result; } // Ast.zig:fullIfComponents static AstFullIf astFullIfComponents( const Ast* tree, AstFullIfComponents info) { AstFullIf result = { .payload_token = AST_TOKEN_NONE, .error_token = AST_TOKEN_NONE, .else_token = AST_TOKEN_NONE, .ast = info, }; // if (cond_expr) |x| // ^ ^ const AstTokenIndex payload_pipe = astLastToken(tree, info.cond_expr) + 2; if (astTokenTag(tree, payload_pipe) == TOKEN_PIPE) result.payload_token = payload_pipe + 1; if (info.else_expr != AST_NODE_NONE) { // then_expr else |x| // ^ ^ result.else_token = astLastToken(tree, info.then_expr) + 1; if (astTokenTag(tree, result.else_token + 1) == TOKEN_PIPE) result.error_token = result.else_token + 2; } return result; } // Ast.zig:fullContainerFieldComponents static AstFullContainerField astFullContainerFieldComponents( const Ast* tree, AstFullContainerFieldComponents info) { AstFullContainerField result = { .comptime_token = AST_TOKEN_NONE, .ast = info, }; const TokenTag comptime_tag = TOKEN_KEYWORD_COMPTIME; if (astIsTokenPrecededByTags(tree, info.main_token, &comptime_tag, 1)) { // comptime type = init, // ^ ^ // comptime name: type = init, // ^ ^ result.comptime_token = info.main_token - 1; } return result; } // Ast.zig:fullFnProtoComponents static AstFullFnProto astFullFnProtoComponents( const Ast* tree, AstFullFnProtoComponents info) { AstFullFnProto result = { .visib_token = AST_TOKEN_NONE, .extern_export_inline_token = AST_TOKEN_NONE, .lib_name = AST_TOKEN_NONE, .name_token = AST_TOKEN_NONE, .lparen = AST_TOKEN_NONE, .ast = info, }; AstTokenIndex i = info.fn_token; while (i > 0) { i -= 1; bool done = false; switch (astTokenTag(tree, i)) { case TOKEN_KEYWORD_EXTERN: case TOKEN_KEYWORD_EXPORT: case TOKEN_KEYWORD_INLINE: case TOKEN_KEYWORD_NOINLINE: result.extern_export_inline_token = i; break; case TOKEN_KEYWORD_PUB: result.visib_token = i; break; case TOKEN_STRING_LITERAL: result.lib_name = i; break; default: done = true; break; } if (done) break; } const AstTokenIndex after_fn_token = info.fn_token + 1; if (astTokenTag(tree, after_fn_token) == TOKEN_IDENTIFIER) { result.name_token = after_fn_token; result.lparen = after_fn_token + 1; } else { result.lparen = after_fn_token; } assert(astTokenTag(tree, result.lparen) == TOKEN_L_PAREN); return result; } // Ast.zig:fullPtrTypeComponents static AstFullPtrType astFullPtrTypeComponents( const Ast* tree, AstFullPtrTypeComponents info) { uint32_t size; switch (astTokenTag(tree, info.main_token)) { case TOKEN_ASTERISK: size = PTR_SIZE_ONE; break; case TOKEN_L_BRACKET: if (astTokenTag(tree, info.main_token + 1) == TOKEN_ASTERISK) size = astTokenTag(tree, info.main_token + 2) == TOKEN_IDENTIFIER ? PTR_SIZE_C : PTR_SIZE_MANY; else size = PTR_SIZE_SLICE; break; default: UNREACHABLE("fullPtrTypeComponents: not a pointer type token"); } AstFullPtrType result = { .size = size, .allowzero_token = AST_TOKEN_NONE, .const_token = AST_TOKEN_NONE, .volatile_token = AST_TOKEN_NONE, .duplicate_token = AST_TOKEN_NONE, .ast = info, }; // We need to be careful that we don't iterate over any sub-expressions // here while looking for modifiers as that could result in false // positives. Therefore, start after a sentinel if there is one and // skip over any align node and bit range nodes. AstTokenIndex i; if (info.sentinel != AST_NODE_NONE) { i = astLastToken(tree, info.sentinel) + 1; } else { switch (size) { case PTR_SIZE_ONE: i = info.main_token; break; case PTR_SIZE_SLICE: i = info.main_token + 1; break; case PTR_SIZE_MANY: i = info.main_token + 2; break; default: i = info.main_token + 3; break; } } i += 1; const AstTokenIndex end = astFirstToken(tree, info.child_type); for (; i < end; i += 1) { switch (astTokenTag(tree, i)) { case TOKEN_KEYWORD_ALLOWZERO: if (result.allowzero_token != AST_TOKEN_NONE) result.duplicate_token = i; result.allowzero_token = i; break; case TOKEN_KEYWORD_CONST: if (result.const_token != AST_TOKEN_NONE) result.duplicate_token = i; result.const_token = i; break; case TOKEN_KEYWORD_VOLATILE: if (result.volatile_token != AST_TOKEN_NONE) result.duplicate_token = i; result.volatile_token = i; break; case TOKEN_KEYWORD_ALIGN: if (info.bit_range_end != AST_NODE_NONE) { assert(info.bit_range_start != AST_NODE_NONE); i = astLastToken(tree, info.bit_range_end) + 1; } else if (info.align_node != AST_NODE_NONE) { i = astLastToken(tree, info.align_node) + 1; } else { UNREACHABLE("fullPtrTypeComponents: align without a node"); } break; case TOKEN_KEYWORD_ADDRSPACE: assert(info.addrspace_node != AST_NODE_NONE); i = astLastToken(tree, info.addrspace_node) + 1; break; default: UNREACHABLE("fullPtrTypeComponents: not a pointer modifier"); } } return result; } // Ast.zig:fullContainerDeclComponents static AstFullContainerDecl astFullContainerDeclComponents( const Ast* tree, AstFullContainerDeclComponents info) { AstFullContainerDecl result = { .layout_token = AST_TOKEN_NONE, .ast = info, }; if (info.main_token == 0) return result; // .root const AstTokenIndex previous_token = info.main_token - 1; switch (astTokenTag(tree, previous_token)) { case TOKEN_KEYWORD_EXTERN: case TOKEN_KEYWORD_PACKED: result.layout_token = previous_token; break; default: break; } return result; } // Ast.zig:fullSwitchCaseComponents static AstFullSwitchCase astFullSwitchCaseComponents( const Ast* tree, AstFullSwitchCaseComponents info, AstNodeIndex node) { AstFullSwitchCase result = { .inline_token = AST_TOKEN_NONE, .payload_token = AST_TOKEN_NONE, .ast = info, }; if (astTokenTag(tree, info.arrow_token + 1) == TOKEN_PIPE) result.payload_token = info.arrow_token + 2; switch (astNodeTag(tree, node)) { case AST_NODE_SWITCH_CASE_INLINE: case AST_NODE_SWITCH_CASE_INLINE_ONE: result.inline_token = result.ast.values.len == 0 ? info.arrow_token - 2 : astFirstToken(tree, result.ast.values.ptr[0]) - 1; break; default: break; } return result; } // Ast.zig:fullAsmComponents static AstFullAsm astFullAsmComponents( const Ast* tree, AstFullAsmComponents info) { AstFullAsm result = { .ast = info, .volatile_token = AST_TOKEN_NONE, .outputs = { info.items.ptr, 0 }, .inputs = { info.items.ptr, 0 }, }; if (astTokenTag(tree, info.asm_token + 1) == TOKEN_KEYWORD_VOLATILE) result.volatile_token = info.asm_token + 1; uint32_t outputs_end = 0; while (outputs_end < info.items.len && astNodeTag(tree, info.items.ptr[outputs_end]) == AST_NODE_ASM_OUTPUT) outputs_end += 1; result.outputs = (AstNodeSlice) { info.items.ptr, outputs_end }; result.inputs = (AstNodeSlice) { info.items.ptr + outputs_end, info.items.len - outputs_end }; return result; } // Ast.zig:fullWhileComponents static AstFullWhile astFullWhileComponents( const Ast* tree, AstFullWhileComponents info) { AstFullWhile result = { .ast = info, .inline_token = AST_TOKEN_NONE, .label_token = AST_TOKEN_NONE, .payload_token = AST_TOKEN_NONE, .error_token = AST_TOKEN_NONE, .else_token = AST_TOKEN_NONE, }; AstTokenIndex tok_i = info.while_token; const TokenTag inline_tag = TOKEN_KEYWORD_INLINE; const TokenTag label[] = { TOKEN_IDENTIFIER, TOKEN_COLON }; if (astIsTokenPrecededByTags(tree, tok_i, &inline_tag, 1)) { result.inline_token = tok_i - 1; tok_i = tok_i - 1; } if (astIsTokenPrecededByTags(tree, tok_i, label, 2)) result.label_token = tok_i - 2; const AstTokenIndex last_cond_token = astLastToken(tree, info.cond_expr); if (astTokenTag(tree, last_cond_token + 2) == TOKEN_PIPE) result.payload_token = last_cond_token + 3; if (info.else_expr != AST_NODE_NONE) { // then_expr else |x| // ^ ^ result.else_token = astLastToken(tree, info.then_expr) + 1; if (astTokenTag(tree, result.else_token + 1) == TOKEN_PIPE) result.error_token = result.else_token + 2; } return result; } // Ast.zig:fullForComponents static AstFullFor astFullForComponents( const Ast* tree, AstFullForComponents info) { AstFullFor result = { .ast = info, .inline_token = AST_TOKEN_NONE, .label_token = AST_TOKEN_NONE, .payload_token = AST_TOKEN_NONE, .else_token = AST_TOKEN_NONE, }; AstTokenIndex tok_i = info.for_token; const TokenTag inline_tag = TOKEN_KEYWORD_INLINE; const TokenTag label[] = { TOKEN_IDENTIFIER, TOKEN_COLON }; if (astIsTokenPrecededByTags(tree, tok_i, &inline_tag, 1)) { result.inline_token = tok_i - 1; tok_i = tok_i - 1; } if (astIsTokenPrecededByTags(tree, tok_i, label, 2)) result.label_token = tok_i - 2; const AstTokenIndex last_cond_token = astLastToken(tree, info.inputs.ptr[info.inputs.len - 1]); result.payload_token = last_cond_token + 3 + (astTokenTag(tree, last_cond_token + 1) == TOKEN_COMMA ? 1u : 0u); if (info.else_expr != AST_NODE_NONE) result.else_token = astLastToken(tree, info.then_expr) + 1; return result; } // Ast.zig:fullVarDecl bool astFullVarDecl(const Ast* tree, AstNodeIndex node, AstFullVarDecl* out) { switch (astNodeTag(tree, node)) { case AST_NODE_GLOBAL_VAR_DECL: *out = astGlobalVarDecl(tree, node); return true; case AST_NODE_LOCAL_VAR_DECL: *out = astLocalVarDecl(tree, node); return true; case AST_NODE_ALIGNED_VAR_DECL: *out = astAlignedVarDecl(tree, node); return true; case AST_NODE_SIMPLE_VAR_DECL: *out = astSimpleVarDecl(tree, node); return true; default: return false; } } // Ast.zig:fullIf bool astFullIf(const Ast* tree, AstNodeIndex node, AstFullIf* out) { switch (astNodeTag(tree, node)) { case AST_NODE_IF_SIMPLE: *out = astIfSimple(tree, node); return true; case AST_NODE_IF: *out = astIfFull(tree, node); return true; default: return false; } } // Ast.zig:fullWhile bool astFullWhile(const Ast* tree, AstNodeIndex node, AstFullWhile* out) { switch (astNodeTag(tree, node)) { case AST_NODE_WHILE_SIMPLE: *out = astWhileSimple(tree, node); return true; case AST_NODE_WHILE_CONT: *out = astWhileCont(tree, node); return true; case AST_NODE_WHILE: *out = astWhileFull(tree, node); return true; default: return false; } } // Ast.zig:fullFor bool astFullFor(const Ast* tree, AstNodeIndex node, AstFullFor* out) { switch (astNodeTag(tree, node)) { case AST_NODE_FOR_SIMPLE: *out = astForSimple(tree, node); return true; case AST_NODE_FOR: *out = astForFull(tree, node); return true; default: return false; } } // Ast.zig:fullContainerField bool astFullContainerField( const Ast* tree, AstNodeIndex node, AstFullContainerField* out) { switch (astNodeTag(tree, node)) { case AST_NODE_CONTAINER_FIELD_INIT: *out = astContainerFieldInit(tree, node); return true; case AST_NODE_CONTAINER_FIELD_ALIGN: *out = astContainerFieldAlign(tree, node); return true; case AST_NODE_CONTAINER_FIELD: *out = astContainerField(tree, node); return true; default: return false; } } // Ast.zig:fullFnProto bool astFullFnProto(const Ast* tree, AstNodeIndex buffer[1], AstNodeIndex node, AstFullFnProto* out) { switch (astNodeTag(tree, node)) { case AST_NODE_FN_PROTO: *out = astFnProto(tree, node); return true; case AST_NODE_FN_PROTO_MULTI: *out = astFnProtoMulti(tree, node); return true; case AST_NODE_FN_PROTO_ONE: *out = astFnProtoOne(tree, buffer, node); return true; case AST_NODE_FN_PROTO_SIMPLE: *out = astFnProtoSimple(tree, buffer, node); return true; case AST_NODE_FN_DECL: return astFullFnProto(tree, buffer, astNodeData(tree, node).lhs, out); default: return false; } } // Ast.zig:fullStructInit bool astFullStructInit(const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node, AstFullStructInit* out) { switch (astNodeTag(tree, node)) { case AST_NODE_STRUCT_INIT_ONE: case AST_NODE_STRUCT_INIT_ONE_COMMA: *out = astStructInitOne(tree, buffer, node); return true; case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: *out = astStructInitDotTwo(tree, buffer, node); return true; case AST_NODE_STRUCT_INIT_DOT: case AST_NODE_STRUCT_INIT_DOT_COMMA: *out = astStructInitDot(tree, node); return true; case AST_NODE_STRUCT_INIT: case AST_NODE_STRUCT_INIT_COMMA: *out = astStructInit(tree, node); return true; default: return false; } } // Ast.zig:fullArrayInit bool astFullArrayInit(const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node, AstFullArrayInit* out) { switch (astNodeTag(tree, node)) { case AST_NODE_ARRAY_INIT_ONE: case AST_NODE_ARRAY_INIT_ONE_COMMA: *out = astArrayInitOne(tree, buffer, node); return true; case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: *out = astArrayInitDotTwo(tree, buffer, node); return true; case AST_NODE_ARRAY_INIT_DOT: case AST_NODE_ARRAY_INIT_DOT_COMMA: *out = astArrayInitDot(tree, node); return true; case AST_NODE_ARRAY_INIT: case AST_NODE_ARRAY_INIT_COMMA: *out = astArrayInit(tree, node); return true; default: return false; } } // Ast.zig:fullArrayType bool astFullArrayType( const Ast* tree, AstNodeIndex node, AstFullArrayType* out) { switch (astNodeTag(tree, node)) { case AST_NODE_ARRAY_TYPE: *out = astArrayType(tree, node); return true; case AST_NODE_ARRAY_TYPE_SENTINEL: *out = astArrayTypeSentinel(tree, node); return true; default: return false; } } // Ast.zig:fullPtrType bool astFullPtrType(const Ast* tree, AstNodeIndex node, AstFullPtrType* out) { switch (astNodeTag(tree, node)) { case AST_NODE_PTR_TYPE_ALIGNED: *out = astPtrTypeAligned(tree, node); return true; case AST_NODE_PTR_TYPE_SENTINEL: *out = astPtrTypeSentinel(tree, node); return true; case AST_NODE_PTR_TYPE: *out = astPtrType(tree, node); return true; case AST_NODE_PTR_TYPE_BIT_RANGE: *out = astPtrTypeBitRange(tree, node); return true; default: return false; } } // Ast.zig:fullSlice bool astFullSlice(const Ast* tree, AstNodeIndex node, AstFullSlice* out) { switch (astNodeTag(tree, node)) { case AST_NODE_SLICE_OPEN: *out = astSliceOpen(tree, node); return true; case AST_NODE_SLICE: *out = astSlice(tree, node); return true; case AST_NODE_SLICE_SENTINEL: *out = astSliceSentinel(tree, node); return true; default: return false; } } // Ast.zig:fullContainerDecl bool astFullContainerDecl(const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node, AstFullContainerDecl* out) { switch (astNodeTag(tree, node)) { case AST_NODE_ROOT: *out = astContainerDeclRoot(tree); return true; case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: *out = astContainerDecl(tree, node); return true; case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: *out = astContainerDeclArg(tree, node); return true; case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: *out = astContainerDeclTwo(tree, buffer, node); return true; case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: *out = astTaggedUnion(tree, node); return true; case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: *out = astTaggedUnionEnumTag(tree, node); return true; case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: *out = astTaggedUnionTwo(tree, buffer, node); return true; default: return false; } } // Ast.zig:fullSwitch bool astFullSwitch(const Ast* tree, AstNodeIndex node, AstFullSwitch* out) { switch (astNodeTag(tree, node)) { case AST_NODE_SWITCH: case AST_NODE_SWITCH_COMMA: *out = astSwitchFull(tree, node); return true; default: return false; } } // Ast.zig:fullSwitchCase bool astFullSwitchCase( const Ast* tree, AstNodeIndex node, AstFullSwitchCase* out) { switch (astNodeTag(tree, node)) { case AST_NODE_SWITCH_CASE_ONE: case AST_NODE_SWITCH_CASE_INLINE_ONE: *out = astSwitchCaseOne(tree, node); return true; case AST_NODE_SWITCH_CASE: case AST_NODE_SWITCH_CASE_INLINE: *out = astSwitchCase(tree, node); return true; default: return false; } } // Ast.zig:fullAsm bool astFullAsm(const Ast* tree, AstNodeIndex node, AstFullAsm* out) { switch (astNodeTag(tree, node)) { case AST_NODE_ASM_SIMPLE: *out = astAsmSimple(tree, node); return true; case AST_NODE_ASM: *out = astAsmFull(tree, node); return true; default: return false; } } // Ast.zig:fullCall bool astFullCall(const Ast* tree, AstNodeIndex buffer[1], AstNodeIndex node, AstFullCall* out) { switch (astNodeTag(tree, node)) { case AST_NODE_CALL: case AST_NODE_CALL_COMMA: *out = astCallFull(tree, node); return true; case AST_NODE_CALL_ONE: case AST_NODE_CALL_ONE_COMMA: *out = astCallOne(tree, buffer, node); return true; default: return false; } } // Ast.zig:builtinCallParams bool astBuiltinCallParams(const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node, AstNodeSlice* out) { const AstData data = astNodeData(tree, node); switch (astNodeTag(tree, node)) { case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: { const AstNodeIndex items[2] = { data.lhs, data.rhs }; *out = astLoadOptionalNodesIntoBuffer(2, buffer, items); return true; } case AST_NODE_BUILTIN_CALL: case AST_NODE_BUILTIN_CALL_COMMA: *out = astExtraDataSlice(tree, data.lhs, data.rhs); return true; default: return false; } } // Ast.zig:blockStatements bool astBlockStatements(const Ast* tree, AstNodeIndex buffer[2], AstNodeIndex node, AstNodeSlice* out) { const AstData data = astNodeData(tree, node); switch (astNodeTag(tree, node)) { case AST_NODE_BLOCK_TWO: case AST_NODE_BLOCK_TWO_SEMICOLON: { const AstNodeIndex items[2] = { data.lhs, data.rhs }; *out = astLoadOptionalNodesIntoBuffer(2, buffer, items); return true; } case AST_NODE_BLOCK: case AST_NODE_BLOCK_SEMICOLON: *out = astExtraDataSlice(tree, data.lhs, data.rhs); return true; default: return false; } } // Ast.zig:full.ContainerField.convertToNonTupleLike void astFullContainerFieldConvertToNonTupleLike( AstFullContainerField* cf, const Ast* tree) { if (!cf->ast.tuple_like) return; if (astNodeTag(tree, cf->ast.type_expr) != AST_NODE_IDENTIFIER) return; cf->ast.type_expr = AST_NODE_NONE; cf->ast.tuple_like = false; } // Ast.zig:full.FnProto.iterate AstFullFnProtoIterator astFullFnProtoIterate( const AstFullFnProto* fn_proto, const Ast* tree) { return (AstFullFnProtoIterator) { .tree = tree, .fn_proto = fn_proto, .param_i = 0, .tok_i = fn_proto->lparen + 1, .tok_flag = true, }; } // Ast.zig:full.FnProto.Iterator.next bool astFullFnProtoIteratorNext( AstFullFnProtoIterator* it, AstFullFnProtoParam* out) { const Ast* tree = it->tree; while (true) { AstTokenIndex first_doc_comment = AST_TOKEN_NONE; AstTokenIndex comptime_noalias = AST_TOKEN_NONE; AstTokenIndex name_token = AST_TOKEN_NONE; if (!it->tok_flag) { if (it->param_i >= it->fn_proto->ast.params.len) return false; const AstNodeIndex param_type = it->fn_proto->ast.params.ptr[it->param_i]; AstTokenIndex tok_i = astFirstToken(tree, param_type) - 1; bool done = false; for (; !done; tok_i -= 1) { switch (astTokenTag(tree, tok_i)) { case TOKEN_COLON: continue; case TOKEN_IDENTIFIER: name_token = tok_i; break; case TOKEN_DOC_COMMENT: first_doc_comment = tok_i; break; case TOKEN_KEYWORD_COMPTIME: case TOKEN_KEYWORD_NOALIAS: comptime_noalias = tok_i; break; default: done = true; break; } } it->param_i += 1; it->tok_i = astLastToken(tree, param_type) + 1; // Look for anytype and ... params afterwards. if (astTokenTag(tree, it->tok_i) == TOKEN_COMMA) it->tok_i += 1; it->tok_flag = true; *out = (AstFullFnProtoParam) { .first_doc_comment = first_doc_comment, .name_token = name_token, .comptime_noalias = comptime_noalias, .anytype_ellipsis3 = AST_TOKEN_NONE, .type_expr = param_type, }; return true; } if (astTokenTag(tree, it->tok_i) == TOKEN_COMMA) it->tok_i += 1; if (astTokenTag(tree, it->tok_i) == TOKEN_R_PAREN) return false; if (astTokenTag(tree, it->tok_i) == TOKEN_DOC_COMMENT) { first_doc_comment = it->tok_i; while (astTokenTag(tree, it->tok_i) == TOKEN_DOC_COMMENT) it->tok_i += 1; } switch (astTokenTag(tree, it->tok_i)) { case TOKEN_ELLIPSIS3: it->tok_flag = false; // Next iteration should return null. *out = (AstFullFnProtoParam) { .first_doc_comment = first_doc_comment, .name_token = AST_TOKEN_NONE, .comptime_noalias = AST_TOKEN_NONE, .anytype_ellipsis3 = it->tok_i, .type_expr = AST_NODE_NONE, }; return true; case TOKEN_KEYWORD_NOALIAS: case TOKEN_KEYWORD_COMPTIME: comptime_noalias = it->tok_i; it->tok_i += 1; break; default: break; } if (astTokenTag(tree, it->tok_i) == TOKEN_IDENTIFIER && astTokenTag(tree, it->tok_i + 1) == TOKEN_COLON) { name_token = it->tok_i; it->tok_i += 2; } if (astTokenTag(tree, it->tok_i) == TOKEN_KEYWORD_ANYTYPE) { it->tok_i += 1; *out = (AstFullFnProtoParam) { .first_doc_comment = first_doc_comment, .name_token = name_token, .comptime_noalias = comptime_noalias, .anytype_ellipsis3 = it->tok_i - 1, .type_expr = AST_NODE_NONE, }; return true; } it->tok_flag = false; } } // This file's macros end with it (zig1.c is one translation unit). #undef AST_INITIAL_CAPACITY /* ---- end ast.c ---- */ /* ---- begin astgen.c ---- */ // astgen.c — AST to ZIR, ported from lib/std/zig/AstGen.zig: `generate` and // everything it reaches, with upstream's names, control flow and side-effect // order. AstGen's methods keep their names with the AstGen first // (identAsString(ag, ...)), GenZir's are gz-prefixed (GenZir.addPlNode is // gzAddPlNode), and Scope's and the string table's carry their type's // name (AGENTS.txt "Naming"). The tree is // read through ast.c's Ast.zig accessors; AstRlAnnotate is // ast_rl_annotate.c. A compile error is reported through failNode/failTok; a // shape zig1 does not lower aborts with ASTGEN_UNIMPLEMENTED. #ifndef ZIG1_H #include "zig1.h" #endif #include #include #include #include #include #define GZ_UNSTACKED UINT32_MAX // Zir.Inst.Declaration.Unwrapped.Kind typedef enum { DECL_KIND_UNNAMED_TEST = 0, DECL_KIND_TEST, DECL_KIND_DECLTEST, DECL_KIND_COMPTIME, DECL_KIND_CONST, DECL_KIND_VAR, } DeclKind; // Zir.Inst.Declaration.Unwrapped.Linkage typedef enum { LINKAGE_NORMAL = 0, LINKAGE_EXTERN, LINKAGE_EXPORT, } DeclLinkage; // --- Scopes --- typedef enum { SCOPE_TOP, SCOPE_GEN_ZIR, SCOPE_NAMESPACE, SCOPE_LOCAL_VAL, SCOPE_LOCAL_PTR, SCOPE_DEFER_NORMAL, SCOPE_DEFER_ERROR, } ScopeTag; typedef struct Scope { ScopeTag tag; struct Scope* parent; // NULL for SCOPE_TOP } Scope; typedef struct AstGen AstGen; // --- Result info --- typedef enum { RL_NONE, RL_DISCARD, RL_REF, RL_REF_CONST, RL_REF_COERCED_TY, RL_TY, RL_COERCED_TY, RL_PTR, RL_INFERRED_PTR, RL_DESTRUCTURE, } ResultLocTag; // Component of a destructure result location. // Mirrors ResultInfo.Loc.DestructureComponent from AstGen.zig. typedef enum { DC_TYPED_PTR, // .typed_ptr: { inst, src_node } DC_INFERRED_PTR, // .inferred_ptr: { inst } DC_DISCARD, // .discard } DestructureComponentTag; typedef struct { DestructureComponentTag tag; ZirInstRef inst; // TYPED_PTR / INFERRED_PTR: the alloc/ptr inst AstNodeIndex src_node; // TYPED_PTR: optional source node (0 = none) } DestructureComponent; typedef struct { ResultLocTag tag; ZirInstRef ty; // valid for RL_TY / RL_COERCED_TY / RL_REF_COERCED_TY ZirInstRef inst; // valid for RL_PTR / RL_INFERRED_PTR (the ptr/alloc) AstNodeIndex src_node; // valid for RL_PTR (0 = none -> use expr src_node) // RL_DESTRUCTURE: components (stored inline so nested destructures don't // overwrite each other's state via a global pointer). const DestructureComponent* ds_components; // NULL outside RL_DESTRUCTURE uint32_t ds_components_len; AstNodeIndex ds_src_node; // the assign_destructure node } ResultLoc; // ResultInfo.Context (zig1 needs none as 0). typedef enum { CTX_NONE = 0, CTX_ASSIGNMENT, CTX_RETURN, CTX_FN_ARG, CTX_CONST_INIT, CTX_ERROR_HANDLING_EXPR, CTX_SHIFT_OP, CTX_RETURN_ADDROF, } RiContext; typedef struct { ResultLoc rl; int ctx; // RiContext } ResultInfo; typedef struct GenZir { Scope base; // tag = SCOPE_GEN_ZIR bool is_comptime; // AstGen.GenZir.is_typeof: "Whether we're in an expression within a // `@TypeOf` operand. In this case, closure of runtime variables is // permitted where it is usually not." bool is_typeof; bool is_inline; AstNodeIndex src_baseline; uint32_t decl_line; AstGen* astgen; // Index into astgen->gz_stack; GZ_UNSTACKED if unstacked. uint32_t instructions_top; // For blocks that can be `break` targets (set via setBreakResultInfo). ResultInfo break_result_info; // Set when this gz (or an ancestor) is lowering a defer expression body; // 0 = none. Used to reject `try` inside a defer. AstNodeIndex any_defer_node; // Loop/block control-flow targets (set by whileExpr/blockExpr/etc). AstTokenIndex label_token; // 0 = none bool label_used; bool label_used_for_continue; // set when `continue :label` targets this // scope bool allow_unlabeled_control_flow; uint32_t break_target; // inst index, 0 = none uint32_t continue_target; // inst index (a block to break to), 0 = none // When continue_is_switch is true, continue_target is the switch_block // inst and continueExpr emits switch_continue instead of break. bool continue_is_switch; ResultInfo continue_result_info; // result info for switch_continue operand } GenZir; typedef struct NamespaceDecl { uint32_t name; // NullTerminatedString index AstNodeIndex node; } NamespaceDecl; typedef struct Namespace { Scope base; // tag = SCOPE_NAMESPACE AstNodeIndex node; uint32_t inst; // decl_inst GenZir* declaring_gz; NamespaceDecl* decls; uint32_t decls_len; uint32_t decls_cap; bool maybe_generic; // Closure captures, in insertion order (an ordered map keyed by capture). uint32_t* capture_keys; // Zir.Inst.Capture (packed u32) uint32_t* capture_names; // NullTerminatedString index uint32_t captures_len; uint32_t captures_cap; } Namespace; // Scope.IdCat: user-visible category of a local identifier. typedef enum { ID_CAT_FUNCTION_PARAMETER = 0, ID_CAT_LOCAL_CONSTANT, ID_CAT_LOCAL_VARIABLE, ID_CAT_SWITCH_TAG_CAPTURE, ID_CAT_CAPTURE, } IdCat; // `@tagName(id_cat)`. static const char* const id_cat_names[] = { "function parameter", "local constant", "local variable", "switch tag capture", "capture" }; // Scope.LocalVal: a const local (or function parameter). Holds a value Ref. typedef struct LocalVal { Scope base; // tag = SCOPE_LOCAL_VAL GenZir* gen_zir; ZirInstRef inst; AstTokenIndex token_src; bool used; bool discarded; bool* is_used_or_discarded; // nullable uint32_t name; // NullTerminatedString IdCat id_cat; } LocalVal; // Scope.LocalPtr: a const/var local. Holds a pointer Ref. typedef struct LocalPtr { Scope base; // tag = SCOPE_LOCAL_PTR GenZir* gen_zir; ZirInstRef ptr; AstTokenIndex token_src; bool used; bool discarded; bool used_as_lvalue; uint32_t name; // NullTerminatedString IdCat id_cat; bool maybe_comptime; } LocalPtr; // Scope.Defer: a normal/error defer. Holds the extra-array location of the // pre-lowered defer body, replayed via a `.defer` instruction in genDefers. typedef struct ScopeDefer { Scope base; // tag = SCOPE_DEFER_NORMAL or SCOPE_DEFER_ERROR uint32_t index; // extra index of the defer body uint32_t len; // body_len } ScopeDefer; // One slot of the ref_table open-addressing index (see the "ref_table" // section below for the full design comment). `state` distinguishes an // empty slot (never occupied), a live occupied slot, and a tombstone (a // deleted entry that must not stop later probes from continuing past it). typedef enum { REF_SLOT_EMPTY = 0, REF_SLOT_OCCUPIED = 1, REF_SLOT_TOMBSTONE = 2, } RefSlotState; typedef struct { uint32_t key; uint32_t val; RefSlotState state; } RefSlot; // AstGen: per-file lowering context. struct AstGen { const Ast* tree; // instructions (MultiArrayList of {tag, data}). uint32_t inst_len; uint32_t inst_cap; uint8_t* inst_tags; ZirInstData* inst_datas; // extra: the ZIR trailing-data array. uint32_t extra_len; uint32_t extra_cap; uint32_t* extra; // string_bytes: interned strings; index 0 is the empty string. U8List string_bytes; // string_table: the interned NUL-terminated strings (see "string // interning" below). Zig1StringTable string_table; // scratch: replicate ZIR trailing data for container decls/fields. uint32_t* scratch; uint32_t scratch_len; uint32_t scratch_cap; // WipDecls: reserved decls slice in scratch for the current container decl // (save/restored around nested container decls). uint32_t wip_decls_start; uint32_t wip_decls_index; // gz_stack: shared instruction-index stack for all GenZir scopes. uint32_t* gz_stack; uint32_t gz_len; uint32_t gz_cap; // ref_table: instruction index -> ref instruction index. Open-addressing // hash index with tombstone deletion (see "ref_table" below). ref_cap // == 0 means "not yet built" (lazily allocated on first insert). RefSlot* ref_slots; uint32_t ref_cap; uint32_t ref_count; uint32_t ref_tombstones; // imports: ordered set of (name, token), keyed on name. uint32_t* import_names; uint32_t* import_tokens; uint32_t imports_len; uint32_t imports_cap; // Source cursor tracking (absolute). uint32_t source_offset; uint32_t source_line; uint32_t source_column; bool within_fn; bool has_compile_errors; // Function-body lowering state (mirrors astgen.fn_block / fn_ret_ty). GenZir* fn_block; // NULL when not lowering a function body ZirInstRef fn_ret_ty; bool fn_var_args; // Heap-allocated local scopes (LocalVal/LocalPtr), freed at astGen end. void** scope_allocs; uint32_t scope_allocs_len; uint32_t scope_allocs_cap; // AstRlAnnotate's RlNeededSet (astRlAnnotate), a bool per node. bool* nodes_need_rl; }; // `astgen.nodes_need_rl.contains(node)`. static bool nodesNeedRl(const AstGen* ag, AstNodeIndex node) { return ag->nodes_need_rl[node]; } // astgen.c's own UNIMPLEMENTED, prefixed `astgen` so an AstGen gap greps // apart from a Sema or interpreter one. #define ASTGEN_UNIMPLEMENTED(msg) \ ZIG1_ABORT_LOUDLY("astgen UNIMPLEMENTED", msg) // AstGen.zig:failOff // Reports the compile error `byte_offset` bytes into `token` and exits: zig1 // stops at the first AstGen error, where upstream collects them all. ZIG1_ATTRIBUTE((noreturn, format(printf, 4, 5))) static void failOff(const AstGen* ag, AstTokenIndex token, uint32_t byte_offset, const char* fmt, ...) { zig1ErrorSourceAt( ag->tree->source, astTokenStart(ag->tree, token) + byte_offset); va_list args; va_start(args, fmt); const char* msg = zig1CompileErrorMessageV(fmt, args); va_end(args); zig1CompileError(msg); } // AstGen.zig:failTok #define failTok(ag, token, ...) failOff((ag), (token), 0, __VA_ARGS__) // AstGen.zig:failNode // The error at the node's main token, where upstream's span points. #define failNode(ag, node, ...) \ failTok((ag), astNodeMainToken((ag)->tree, (node)), __VA_ARGS__) // AstGen.zig:builtinCall, anchor `"expected {d} argument{s}, found {d}"`. ZIG1_ATTRIBUTE((noreturn)) static void failParamCount( const AstGen* ag, AstNodeIndex node, uint32_t expected, uint32_t found) { failNode(ag, node, "expected %u argument%s, found %u", expected, expected == 1 ? "" : "s", found); } // Track a heap-allocated scope for later free. static void* scopeAlloc(AstGen* ag, size_t size) { void* p = zig1Malloc(size); const uint32_t need = ag->scope_allocs_len + 1; if (need > ag->scope_allocs_cap) { uint32_t new_cap = ag->scope_allocs_cap * 2; if (new_cap < need) new_cap = need; if (new_cap < 8) new_cap = 8; void** n = zig1Realloc(ag->scope_allocs, (size_t)new_cap * sizeof(void*)); ag->scope_allocs = n; ag->scope_allocs_cap = new_cap; } ag->scope_allocs[ag->scope_allocs_len++] = p; return p; } // --- dynamic array growth helpers --- static void instEnsureUnused(AstGen* ag, uint32_t additional) { const uint32_t need = ag->inst_len + additional; if (need <= ag->inst_cap) return; uint32_t new_cap = ag->inst_cap * 2; if (new_cap < need) new_cap = need; uint8_t* new_tags = zig1Realloc(ag->inst_tags, (size_t)new_cap * sizeof(uint8_t)); ZirInstData* new_datas = zig1Realloc(ag->inst_datas, (size_t)new_cap * sizeof(ZirInstData)); ag->inst_tags = new_tags; ag->inst_datas = new_datas; ag->inst_cap = new_cap; } static void extraEnsureUnused(AstGen* ag, uint32_t additional) { const uint32_t need = ag->extra_len + additional; if (need <= ag->extra_cap) return; uint32_t new_cap = ag->extra_cap * 2; if (new_cap < need) new_cap = need; uint32_t* new_extra = zig1Realloc(ag->extra, (size_t)new_cap * sizeof(uint32_t)); ag->extra = new_extra; ag->extra_cap = new_cap; } static uint32_t extraAppend(AstGen* ag, uint32_t value) { extraEnsureUnused(ag, 1); const uint32_t index = ag->extra_len; ag->extra[index] = value; ag->extra_len += 1; return index; } // AstGen.zig:reserveExtra // astgen.reserveExtra: reserve `count` slots in extra[], returning the start // index. Caller fills the reserved slots by index. Mirrors // AstGen.reserveExtra. static uint32_t reserveExtra(AstGen* ag, uint32_t count) { extraEnsureUnused(ag, count); const uint32_t index = ag->extra_len; ag->extra_len += count; return index; } static void scratchAppend(AstGen* ag, uint32_t value) { const uint32_t need = ag->scratch_len + 1; if (need > ag->scratch_cap) { uint32_t new_cap = ag->scratch_cap * 2; if (new_cap < need) new_cap = need; if (new_cap < 8) new_cap = 8; uint32_t* n = zig1Realloc(ag->scratch, (size_t)new_cap * sizeof(uint32_t)); ag->scratch = n; ag->scratch_cap = new_cap; } ag->scratch[ag->scratch_len++] = value; } // WipDecls.nextDecl: record a decl instruction into the reserved decls slice. static void wipDeclsNext(AstGen* ag, uint32_t decl_inst) { ag->scratch[ag->wip_decls_start + ag->wip_decls_index] = decl_inst; ag->wip_decls_index += 1; } static void gzStackPush(AstGen* ag, uint32_t value) { const uint32_t need = ag->gz_len + 1; if (need > ag->gz_cap) { uint32_t new_cap = ag->gz_cap * 2; if (new_cap < need) new_cap = need; if (new_cap < 8) new_cap = 8; uint32_t* n = zig1Realloc(ag->gz_stack, (size_t)new_cap * sizeof(uint32_t)); ag->gz_stack = n; ag->gz_cap = new_cap; } ag->gz_stack[ag->gz_len++] = value; } // --- ref_table --- // // AstGen.zig `ref_table: AutoHashMapUnmanaged(Zir.Inst.Index, // Zir.Inst.Index)`: a const local coerced into a pointer result location // (rvalue's RL_REF / RL_REF_CONST / RL_REF_COERCED_TY) goes through a `ref` // remembered here, so a second address-of reuses it. `fetchRemove` takes an // entry out when its instruction is written into a body (the // appendPossiblyRefdBodyInst family splices the `ref` in after it), and when // the fn-proto lowering re-extracts a param's ref before the return-type // body. A key has at most one live entry, since `refTableGetOrPut` reuses a // hit and only `refTableFetchRemove` deletes. // // Open addressing, linear probing, power-of-two capacity, doubled (live // entries only) when live plus tombstone load would pass 70%. A tombstone // keeps a probe going and never matches. static uint32_t refTableHash(uint32_t key) { return (uint32_t)wyhashHash(0, &key, sizeof(key)); } // Double the table (or perform the initial allocation from cap==0), // re-bucketing every live entry (tombstones are dropped, i.e. reclaimed // here -- see the design comment above). static void refTableGrow(AstGen* ag) { const uint32_t old_cap = ag->ref_cap; RefSlot* old_slots = ag->ref_slots; if (old_cap > UINT32_MAX / 2) { UNIMPLEMENTED("AstGen.ref_table: ref_table capacity overflow"); } const uint32_t new_cap = zig1GrowCap(old_cap, 16u); // calloc: every slot starts REF_SLOT_EMPTY (0). RefSlot* new_slots = zig1Calloc(new_cap, sizeof(RefSlot)); const uint32_t mask = new_cap - 1; for (uint32_t i = 0; i < old_cap; i++) { if (old_slots[i].state != REF_SLOT_OCCUPIED) continue; uint32_t slot = refTableHash(old_slots[i].key) & mask; while (new_slots[slot].state == REF_SLOT_OCCUPIED) slot = (slot + 1) & mask; new_slots[slot] = old_slots[i]; } free(old_slots); ag->ref_slots = new_slots; ag->ref_cap = new_cap; ag->ref_tombstones = 0; } // Ensure room for one more insertion without exceeding 70% (live + // tombstone) load, growing (or performing the initial allocation) first if // needed. static void refTableEnsureCapacity(AstGen* ag) { if (ag->ref_cap == 0) { refTableGrow(ag); return; } const uint64_t used = (uint64_t)ag->ref_count + ag->ref_tombstones; if ((used + 1) * 10 > (uint64_t)ag->ref_cap * 7) refTableGrow(ag); } static uint32_t* refTableGet(AstGen* ag, uint32_t key) { if (ag->ref_cap == 0) return NULL; const uint32_t mask = ag->ref_cap - 1; uint32_t slot = refTableHash(key) & mask; for (;;) { switch (ag->ref_slots[slot].state) { case REF_SLOT_EMPTY: return NULL; case REF_SLOT_OCCUPIED: if (ag->ref_slots[slot].key == key) return &ag->ref_slots[slot].val; break; case REF_SLOT_TOMBSTONE: break; } slot = (slot + 1) & mask; } } // ref_table.getOrPut: returns a pointer to the value slot for `key`. Sets // `*found_existing` accordingly. The value is uninitialized on insertion. static uint32_t* refTableGetOrPut( AstGen* ag, uint32_t key, bool* found_existing) { uint32_t* existing = refTableGet(ag, key); if (existing != NULL) { *found_existing = true; return existing; } *found_existing = false; // Miss: ensure capacity (may grow + rehash the whole table, so the // insertion probe below must run after this, against the current // ag->ref_cap/ref_slots -- refTableGet above never held onto a raw slot // index across this call, only the (already-NULL) `existing` pointer). refTableEnsureCapacity(ag); const uint32_t mask = ag->ref_cap - 1; uint32_t slot = refTableHash(key) & mask; // First non-occupied slot found while probing (empty or tombstone) is a // valid insertion point: refTableGet just proved no occupied slot for // this key exists anywhere on this same probe chain, so it's safe to // claim either kind here (standard open-addressing insertion). while (ag->ref_slots[slot].state == REF_SLOT_OCCUPIED) slot = (slot + 1) & mask; if (ag->ref_slots[slot].state == REF_SLOT_TOMBSTONE) ag->ref_tombstones--; ag->ref_slots[slot].key = key; ag->ref_slots[slot].state = REF_SLOT_OCCUPIED; ag->ref_count++; return &ag->ref_slots[slot].val; } // ref_table.fetchRemove: if `key` is present, return true and set `*out`, // tombstoning the entry so later lookups miss. static bool refTableFetchRemove(AstGen* ag, uint32_t key, uint32_t* out) { if (ag->ref_cap == 0) return false; const uint32_t mask = ag->ref_cap - 1; uint32_t slot = refTableHash(key) & mask; for (;;) { switch (ag->ref_slots[slot].state) { case REF_SLOT_EMPTY: return false; case REF_SLOT_OCCUPIED: if (ag->ref_slots[slot].key == key) { *out = ag->ref_slots[slot].val; ag->ref_slots[slot].state = REF_SLOT_TOMBSTONE; ag->ref_count--; ag->ref_tombstones++; return true; } break; case REF_SLOT_TOMBSTONE: break; } slot = (slot + 1) & mask; } } // --- string interning --- // // AstGen.zig `string_table: std.HashMapUnmanaged(u32, void, // StringIndexContext, ...)`, keyed by the `string_bytes` content and used by // identAsString and strLitAsString (zig1StringTable). A // stored entry is NUL-terminated and NUL-free (strLitAsString returns before // the table on an embedded NUL, `findScalar(u8, key, 0)`, and identAsString // rejects one), so `strlen(cand) == len && memcmp(cand, query, len) == 0` is // the content equality, and a key has at most one entry. // The identifier token is `_` (as upstream's mem.eql(tokenSlice, "_")). static bool tokenIsUnderscore(const Ast* tree, AstTokenIndex token) { const char* s; uint32_t len; astTokenSlice(tree, token, &s, &len); return len == 1 && s[0] == '_'; } // AstGen's `string_table.getOrPutContextAdapted` on // `string_bytes[str_index..]` (identAsString, strLitAsString): an existing // equal string, with the bytes dropped again, or `str_index` NUL-terminated // and added. static uint32_t internStringAt(AstGen* ag, uint32_t str_index) { const uint32_t len = ag->string_bytes.len - str_index; // The NUL's room first, so even an empty string's bytes exist. u8ListAppend(&ag->string_bytes, 0); ag->string_bytes.len--; const char* key = (const char*)&ag->string_bytes.items[str_index]; const uint32_t found = zig1StringTableFind( &ag->string_table, ag->string_bytes.items, key, len); if (found != UINT32_MAX) { ag->string_bytes.len = str_index; return found; } zig1StringTableInsert(&ag->string_table, str_index, key, len); u8ListAppend(&ag->string_bytes, 0); return str_index; } // AstGen.zig:appendIdentStr // The identifier's bytes, an @"..." one's parsed by string_literal.parseWrite. static void appendIdentStr(AstGen* ag, AstTokenIndex ident_token) { const char* name; uint32_t name_len; astTokenSlice(ag->tree, ident_token, &name, &name_len); const uint32_t start = ag->string_bytes.len; if (name[0] != '@') { for (uint32_t i = 0; i < name_len; i++) u8ListAppend(&ag->string_bytes, (uint8_t)name[i]); return; } const uint32_t offset = 1; uint32_t err_offset; if (!stringLiteralParseWrite( name + offset, name_len - offset, &ag->string_bytes, &err_offset)) failOff( ag, ident_token, offset + err_offset, "invalid string literal"); const uint32_t len = ag->string_bytes.len - start; if (memchr(&ag->string_bytes.items[start], 0, len) != NULL) failTok(ag, ident_token, "identifier cannot contain null bytes"); if (len == 0) failTok(ag, ident_token, "identifier cannot be empty"); } // AstGen.zig:identAsString static uint32_t identAsString(AstGen* ag, AstTokenIndex ident_token) { const uint32_t str_index = ag->string_bytes.len; appendIdentStr(ag, ident_token); return internStringAt(ag, str_index); } // AstGen.zig:strLitAsString // The literal's parsed bytes: interned unless they hold a NUL, which leaves // them uninterned and unterminated. Returns the start index; *out_len // receives the parsed byte length. static uint32_t strLitAsString( AstGen* ag, AstTokenIndex str_lit_token, uint32_t* out_len) { const char* raw; uint32_t raw_len; astTokenSlice(ag->tree, str_lit_token, &raw, &raw_len); const uint32_t str_index = ag->string_bytes.len; uint32_t err_offset; if (!stringLiteralParseWrite(raw, raw_len, &ag->string_bytes, &err_offset)) failOff(ag, str_lit_token, err_offset, "invalid string literal"); const uint32_t len = ag->string_bytes.len - str_index; *out_len = len; if (memchr(&ag->string_bytes.items[str_index], 0, len) != NULL) return str_index; return internStringAt(ag, str_index); } // AstGen.zig:strLitNodeAsString // Process an AST_NODE_MULTILINE_STRING_LITERAL node into // string_bytes. Mirrors AstGen.strLitNodeAsString. // Each multiline_string_literal_line token has the form `\\`. // First line: append content. Subsequent lines: append '\n' then content. // A null terminator is always appended. Returns the start index; *out_len // receives the content length (not counting the null terminator). static uint32_t strLitNodeAsString( AstGen* ag, AstNodeIndex node, uint32_t* out_len) { const Ast* tree = ag->tree; const AstTokenIndex start = (AstTokenIndex)astNodeData(tree, node).lhs; const AstTokenIndex end = (AstTokenIndex)astNodeData(tree, node).rhs; const uint32_t str_index = ag->string_bytes.len; bool first = true; for (AstTokenIndex tok = start; tok <= end; tok++) { const char* raw; uint32_t raw_len; astTokenSlice(tree, tok, &raw, &raw_len); // raw starts with "\\" (two backslash characters); content follows. if (raw_len < 2) ASTGEN_UNIMPLEMENTED("strLitNodeAsString: token too short"); const char* content = raw + 2; uint32_t content_len = raw_len - 2; if (!first) u8ListAppend(&ag->string_bytes, '\n'); first = false; for (uint32_t i = 0; i < content_len; i++) u8ListAppend(&ag->string_bytes, (uint8_t)content[i]); } *out_len = ag->string_bytes.len - str_index; u8ListAppend(&ag->string_bytes, 0); // null terminator return str_index; } // --- source cursor save/restore --- typedef struct { uint32_t offset; uint32_t line; uint32_t column; } SourceCursor; // AstGen.zig:saveSourceCursor static SourceCursor saveSourceCursor(const AstGen* ag) { SourceCursor c = { ag->source_offset, ag->source_line, ag->source_column }; return c; } // AstGen.zig:restoreSourceCursor static void restoreSourceCursor(AstGen* ag, SourceCursor c) { ag->source_offset = c.offset; ag->source_line = c.line; ag->source_column = c.column; } // AstGen.zig:advanceSourceCursor static void advanceSourceCursor(AstGen* ag, uint32_t end) { const char* source = ag->tree->source; uint32_t i = ag->source_offset; uint32_t line = ag->source_line; uint32_t column = ag->source_column; while (i < end) { if (source[i] == '\n') { line += 1; column = 0; } else { column += 1; } i += 1; } ag->source_offset = i; ag->source_line = line; ag->source_column = column; } // AstGen.zig:advanceSourceCursorToNode static void advanceSourceCursorToNode(AstGen* ag, AstNodeIndex node) { const AstTokenIndex tok = astFirstToken(ag->tree, node); const uint32_t node_start = astTokenStart(ag->tree, tok); advanceSourceCursor(ag, node_start); } // --- Zir.Inst.Tag classification --- static bool tagIsNoReturn(ZirInstTag tag) { switch (tag) { case ZIR_INST_BREAK: case ZIR_INST_BREAK_INLINE: case ZIR_INST_CONDBR: case ZIR_INST_CONDBR_INLINE: case ZIR_INST_COMPILE_ERROR: case ZIR_INST_RET_NODE: case ZIR_INST_RET_LOAD: case ZIR_INST_RET_IMPLICIT: case ZIR_INST_RET_ERR_VALUE: case ZIR_INST_UNREACHABLE: case ZIR_INST_REPEAT: case ZIR_INST_REPEAT_INLINE: case ZIR_INST_PANIC: case ZIR_INST_TRAP: case ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW: case ZIR_INST_SWITCH_CONTINUE: return true; default: return false; } } static bool tagIsAlwaysVoid(ZirInstTag tag, ZirInstData data) { switch (tag) { case ZIR_INST_DBG_STMT: case ZIR_INST_DBG_VAR_PTR: case ZIR_INST_DBG_VAR_VAL: case ZIR_INST_ENSURE_RESULT_USED: case ZIR_INST_ENSURE_RESULT_NON_ERROR: case ZIR_INST_ENSURE_ERR_UNION_PAYLOAD_VOID: case ZIR_INST_SET_EVAL_BRANCH_QUOTA: case ZIR_INST_ATOMIC_STORE: case ZIR_INST_STORE_NODE: case ZIR_INST_STORE_TO_INFERRED_PTR: case ZIR_INST_VALIDATE_DESTRUCTURE: case ZIR_INST_EXPORT: case ZIR_INST_SET_RUNTIME_SAFETY: case ZIR_INST_MEMCPY: case ZIR_INST_MEMSET: case ZIR_INST_MEMMOVE: case ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW: case ZIR_INST_DEFER: case ZIR_INST_SAVE_ERR_RET_INDEX: case ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL: case ZIR_INST_RESTORE_ERR_RET_INDEX_FN_ENTRY: case ZIR_INST_VALIDATE_STRUCT_INIT_TY: case ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY: case ZIR_INST_VALIDATE_PTR_STRUCT_INIT: case ZIR_INST_VALIDATE_ARRAY_INIT_TY: case ZIR_INST_VALIDATE_ARRAY_INIT_RESULT_TY: case ZIR_INST_VALIDATE_PTR_ARRAY_INIT: case ZIR_INST_VALIDATE_REF_TY: case ZIR_INST_VALIDATE_CONST: return true; case ZIR_INST_EXTENDED: switch (data.extended.opcode) { case ZIR_EXT_BRANCH_HINT: case ZIR_EXT_BREAKPOINT: case ZIR_EXT_DISABLE_INSTRUMENTATION: case ZIR_EXT_DISABLE_INTRINSICS: return true; default: return false; } default: return false; } } // --- body fixups (ref_table) --- // AstGen.zig:countBodyLenAfterFixupsExtraRefs static uint32_t countBodyLenAfterFixupsExtraRefs(AstGen* ag, const uint32_t* body, uint32_t body_len, const uint32_t* extra_refs, uint32_t extra_refs_len) { uint32_t count = body_len; for (uint32_t i = 0; i < body_len; i++) { uint32_t check = body[i]; const uint32_t* ref; while ((ref = refTableGet(ag, check)) != NULL) { count += 1; check = *ref; } } for (uint32_t i = 0; i < extra_refs_len; i++) { uint32_t check = extra_refs[i]; const uint32_t* ref; while ((ref = refTableGet(ag, check)) != NULL) { count += 1; check = *ref; } } return count; } // AstGen.zig:countBodyLenAfterFixups static uint32_t countBodyLenAfterFixups( AstGen* ag, const uint32_t* body, uint32_t body_len) { return countBodyLenAfterFixupsExtraRefs(ag, body, body_len, NULL, 0); } // AstGen.zig:appendPossiblyRefdBodyInst static void appendPossiblyRefdBodyInst(AstGen* ag, uint32_t body_inst) { extraAppend(ag, body_inst); uint32_t ref; if (!refTableFetchRemove(ag, body_inst, &ref)) return; appendPossiblyRefdBodyInst(ag, ref); } static void appendBodyWithFixupsExtraRefs(AstGen* ag, const uint32_t* body, uint32_t body_len, const uint32_t* extra_refs, uint32_t extra_refs_len) { for (uint32_t i = 0; i < extra_refs_len; i++) { uint32_t ref; if (refTableFetchRemove(ag, extra_refs[i], &ref)) appendPossiblyRefdBodyInst(ag, ref); } for (uint32_t i = 0; i < body_len; i++) appendPossiblyRefdBodyInst(ag, body[i]); } // AstGen.zig:appendBodyWithFixups static void appendBodyWithFixups( AstGen* ag, const uint32_t* body, uint32_t body_len) { appendBodyWithFixupsExtraRefs(ag, body, body_len, NULL, 0); } // Scratch.appendBodyWithFixups: same as above but appends into ag->scratch. static void scratchAppendPossiblyRefdBodyInst(AstGen* ag, uint32_t body_inst) { scratchAppend(ag, body_inst); uint32_t ref; if (!refTableFetchRemove(ag, body_inst, &ref)) return; scratchAppendPossiblyRefdBodyInst(ag, ref); } static uint32_t scratchAppendBodyWithFixups( AstGen* ag, const uint32_t* body, uint32_t body_len) { const uint32_t len = countBodyLenAfterFixups(ag, body, body_len); for (uint32_t i = 0; i < body_len; i++) scratchAppendPossiblyRefdBodyInst(ag, body[i]); return len; } // Like scratchAppendBodyWithFixups but with extra forward-ref handling for // instructions that might be ref-table entries (e.g. switch_block as a // placeholder for captures). static uint32_t scratchAppendBodyWithFixupsExtraRefs(AstGen* ag, const uint32_t* body, uint32_t body_len, const uint32_t* extra_refs, uint32_t extra_refs_len) { const uint32_t len = countBodyLenAfterFixupsExtraRefs( ag, body, body_len, extra_refs, extra_refs_len); for (uint32_t i = 0; i < extra_refs_len; i++) { uint32_t ref; if (refTableFetchRemove(ag, extra_refs[i], &ref)) scratchAppendPossiblyRefdBodyInst(ag, ref); } for (uint32_t i = 0; i < body_len; i++) scratchAppendPossiblyRefdBodyInst(ag, body[i]); return len; } // --- GenZir helpers --- // AstGen.zig:GenZir.instructionsSlice static const uint32_t* gzInstructionsSlice( const GenZir* gz, uint32_t* out_len) { AstGen* ag = gz->astgen; if (gz->instructions_top == GZ_UNSTACKED) { *out_len = 0; return NULL; } *out_len = ag->gz_len - gz->instructions_top; return &ag->gz_stack[gz->instructions_top]; } // AstGen.zig:GenZir.instructionsSliceUpto // Slice of `gz` up to the top of the stacked child gz. static const uint32_t* gzInstructionsSliceUpto( const GenZir* gz, const GenZir* stacked, uint32_t* out_len) { AstGen* ag = gz->astgen; if (gz->instructions_top == GZ_UNSTACKED) { *out_len = 0; return NULL; } uint32_t end = ag->gz_len; if (stacked->instructions_top != GZ_UNSTACKED) end = stacked->instructions_top; *out_len = end - gz->instructions_top; return &ag->gz_stack[gz->instructions_top]; } // AstGen.zig:GenZir.unstack static void gzUnstack(GenZir* gz) { if (gz->instructions_top != GZ_UNSTACKED) { gz->astgen->gz_len = gz->instructions_top; gz->instructions_top = GZ_UNSTACKED; } } // AstGen.zig:GenZir.isEmpty static bool gzIsEmpty(const GenZir* gz) { return gz->instructions_top == GZ_UNSTACKED || gz->astgen->gz_len == gz->instructions_top; } // AstGen.zig:GenZir.endsWithNoReturn static bool gzEndsWithNoReturn(const GenZir* gz) { if (gzIsEmpty(gz)) return false; const uint32_t last = gz->astgen->gz_stack[gz->astgen->gz_len - 1]; return tagIsNoReturn(gz->astgen->inst_tags[last]); } // AstGen.zig:GenZir.makeSubBlock static GenZir gzMakeSubBlock(GenZir* gz, Scope* scope) { GenZir sub; memset(&sub, 0, sizeof(sub)); sub.base.tag = SCOPE_GEN_ZIR; sub.is_comptime = gz->is_comptime; sub.is_typeof = gz->is_typeof; sub.is_inline = gz->is_inline; sub.src_baseline = gz->src_baseline; sub.decl_line = gz->decl_line; sub.base.parent = scope; sub.astgen = gz->astgen; sub.instructions_top = gz->astgen->gz_len; sub.any_defer_node = gz->any_defer_node; return sub; } // AstGen.zig:GenZir.nodeIndexToRelative static int32_t gzNodeIndexToRelative(const GenZir* gz, AstNodeIndex node) { return (int32_t)node - (int32_t)gz->src_baseline; } // Append an instruction to the instructions array only (no gz stack push). static uint32_t agAppendInst(AstGen* ag, ZirInstTag tag, ZirInstData data) { instEnsureUnused(ag, 1); const uint32_t index = ag->inst_len; ag->inst_tags[index] = (uint8_t)tag; ag->inst_datas[index] = data; ag->inst_len += 1; return index; } // AstGen.zig:appendPlaceholder static uint32_t appendPlaceholder(AstGen* ag) { ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = ZIR_EXT_VALUE_PLACEHOLDER; return agAppendInst(ag, ZIR_INST_EXTENDED, data); } // AstGen.zig:GenZir.add // Append an instruction and push it onto the gz stack; returns a Ref. static ZirInstRef gzAdd(GenZir* gz, ZirInstTag tag, ZirInstData data) { const uint32_t index = agAppendInst(gz->astgen, tag, data); gzStackPush(gz->astgen, index); return index + ZIR_REF_START_INDEX; } // AstGen.zig:GenZir.reserveInstructionIndex static uint32_t gzReserveInstructionIndex(GenZir* gz) { instEnsureUnused(gz->astgen, 1); const uint32_t new_index = gz->astgen->inst_len; gz->astgen->inst_len += 1; gzStackPush(gz->astgen, new_index); return new_index; } // AstGen.zig:GenZir.makeDeclaration // Append a `declaration` instruction (not pushed on the gz // stack); payload_index is finalized later by setDeclaration. static uint32_t gzMakeDeclaration(GenZir* gz, AstNodeIndex node) { ZirInstData data; memset(&data, 0, sizeof(data)); data.declaration.src_node = (uint32_t)node; data.declaration.payload_index = 0; // undefined; set by setDeclaration return agAppendInst(gz->astgen, ZIR_INST_DECLARATION, data); } // AstGen.zig:GenZir.makeBreakCommon // Emit a break/break_inline // instruction. static uint32_t gzMakeBreakCommon(GenZir* gz, ZirInstTag tag, uint32_t block_inst, ZirInstRef operand, bool has_src_node, AstNodeIndex operand_src_node) { AstGen* ag = gz->astgen; // Zir.Inst.Break payload: { operand_src_node (i32 optional offset), // block_inst }. const uint32_t payload_index = ag->extra_len; extraEnsureUnused(ag, 2); if (has_src_node) extraAppend(ag, (uint32_t)gzNodeIndexToRelative(gz, operand_src_node)); else extraAppend(ag, (uint32_t)AST_NODE_OFFSET_NONE); extraAppend(ag, block_inst); ZirInstData data; memset(&data, 0, sizeof(data)); data.break_data.operand = operand; data.break_data.payload_index = payload_index; return agAppendInst(ag, tag, data); } // AstGen.zig:GenZir.addBreak static uint32_t gzAddBreak( GenZir* gz, ZirInstTag tag, uint32_t block_inst, ZirInstRef operand) { const uint32_t new_index = gzMakeBreakCommon(gz, tag, block_inst, operand, false, 0); gzStackPush(gz->astgen, new_index); return new_index; } // AstGen.zig:GenZir.addBreakWithSrcNode static uint32_t gzAddBreakWithSrcNode(GenZir* gz, ZirInstTag tag, uint32_t block_inst, ZirInstRef operand, AstNodeIndex operand_src_node) { const uint32_t new_index = gzMakeBreakCommon( gz, tag, block_inst, operand, true, operand_src_node); gzStackPush(gz->astgen, new_index); return new_index; } // AstGen.zig:GenZir.addInt static ZirInstRef gzAddInt(GenZir* gz, uint64_t integer) { ZirInstData data; memset(&data, 0, sizeof(data)); data.int_val = integer; return gzAdd(gz, ZIR_INST_INT, data); } // AstGen.zig:GenZir.addFloat // Emit a `float` instruction holding an f64 value. static ZirInstRef gzAddFloat(GenZir* gz, double number) { ZirInstData data; memset(&data, 0, sizeof(data)); data.float_val = number; return gzAdd(gz, ZIR_INST_FLOAT, data); } // AstGen.zig:GenZir.addIntBig // Emit an `int_big` instruction. `limbs64` holds the value in // base 2^64, little-endian, normalized (top limb nonzero). The limbs are // appended to string_bytes as little-endian bytes (matching Zig's // std.math.big.Limb == u64 layout on 64-bit hosts); the `str` data records the // byte start and the limb count. static ZirInstRef gzAddIntBig( GenZir* gz, const uint64_t* limbs64, uint32_t limbs_len) { AstGen* ag = gz->astgen; const uint32_t start = ag->string_bytes.len; for (uint32_t i = 0; i < limbs_len; i++) { uint64_t limb = limbs64[i]; for (int b = 0; b < 8; b++) { u8ListAppend(&ag->string_bytes, (uint8_t)(limb & 0xFF)); limb >>= 8; } } ZirInstData data; memset(&data, 0, sizeof(data)); data.str.start = start; data.str.len = limbs_len; return gzAdd(gz, ZIR_INST_INT_BIG, data); } // `std.math.big.int.Mutable.setString(base, bytes)`: every digit (an // underscore skipped) folded in as `limbs * base + digit`, into base-2^64 // little-endian limbs. Returns the normalized limb count. static uint32_t bigIntSetString(uint8_t base, const char* bytes, uint32_t len, uint64_t* limbs, uint32_t limbs_cap) { uint32_t limbs_len = 1; limbs[0] = 0; for (uint32_t i = 0; i < len; i++) { const char c = bytes[i]; if (c == '_') continue; uint64_t carry = (uint64_t)(c <= '9' ? c - '0' : c >= 'a' ? c - 'a' + 10 : c - 'A' + 10); for (uint32_t j = 0; j < limbs_len; j++) { const CrtU128 v = u128Add(u128Mul64(limbs[j], base), u128Make(0, carry)); limbs[j] = v.lo; carry = v.hi; } if (carry != 0) { assert(limbs_len < limbs_cap); limbs[limbs_len++] = carry; } } return limbs_len; } // AstGen.zig:GenZir.addExtendedNodeSmall // An `extended` instruction whose operand encodes a // relative node offset and whose `small` is a caller-supplied u16. static ZirInstRef gzAddExtendedNodeSmall( GenZir* gz, uint16_t opcode, AstNodeIndex src_node, uint16_t small) { ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = opcode; data.extended.small = small; data.extended.operand = (uint32_t)gzNodeIndexToRelative(gz, src_node); return gzAdd(gz, ZIR_INST_EXTENDED, data); } // AstGen.zig:GenZir.addExtendedPayloadSmall // Create an extended instruction with a struct // payload serialized as consecutive u32 words. static ZirInstRef gzAddExtendedPayloadSmall(GenZir* gz, uint16_t opcode, uint16_t small, const uint32_t* words, uint32_t words_len) { AstGen* ag = gz->astgen; const uint32_t payload_index = ag->extra_len; for (uint32_t i = 0; i < words_len; i++) extraAppend(ag, words[i]); ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = opcode; data.extended.small = small; data.extended.operand = payload_index; return gzAdd(gz, ZIR_INST_EXTENDED, data); } // AstGen.zig:GenZir.addExtendedPayload static ZirInstRef gzAddExtendedPayload( GenZir* gz, uint16_t opcode, const uint32_t* words, uint32_t words_len) { return gzAddExtendedPayloadSmall(gz, opcode, 0, words, words_len); } // AstGen.zig:GenZir.addExtendedMultiOpPayloadIndex static ZirInstRef gzAddExtendedMultiOpPayloadIndex(GenZir* gz, uint16_t opcode, uint32_t payload_index, uint32_t trailing_len) { ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = opcode; data.extended.small = (uint16_t)trailing_len; data.extended.operand = payload_index; return gzAdd(gz, ZIR_INST_EXTENDED, data); } // AstGen.zig:GenZir.addNodeExtended static ZirInstRef gzAddNodeExtended( GenZir* gz, uint16_t opcode, AstNodeIndex src_node) { return gzAddExtendedNodeSmall(gz, opcode, src_node, 0); } // AstGen.zig:GenZir.addAllocExtended // `GenZir.addAllocExtended` for an aligned allocation: the `AllocExtended` // payload is the relative node, then the type when `type_inst` is not // ZIR_REF_NONE (`u0_type` is ref 0), then the align ref; `small` is // `"has_type | (has_align << 1) | (is_const << 2) | (is_comptime << 3)"`. static ZirInstRef gzAddAllocExtended(GenZir* gz, AstNodeIndex node, ZirInstRef type_inst, ZirInstRef align_inst, bool is_const, bool is_comptime) { const bool has_type = type_inst != ZIR_REF_NONE; uint32_t words[3]; uint32_t len = 0; words[len++] = (uint32_t)gzNodeIndexToRelative(gz, node); if (has_type) words[len++] = (uint32_t)type_inst; words[len++] = (uint32_t)align_inst; const uint16_t small = (uint16_t)((has_type ? ZIR_ALLOC_SMALL_HAS_TYPE : 0u) | ZIR_ALLOC_SMALL_HAS_ALIGN | (is_const ? ZIR_ALLOC_SMALL_IS_CONST : 0u) | (is_comptime ? ZIR_ALLOC_SMALL_IS_COMPTIME : 0u)); return gzAddExtendedPayloadSmall(gz, ZIR_EXT_ALLOC, small, words, len); } // AstGen.zig:GenZir.addStdLangValue static ZirInstRef gzAddStdLangValue( GenZir* gz, AstNodeIndex src_node, uint16_t val) { return gzAddExtendedNodeSmall(gz, ZIR_EXT_STD_LANG_VALUE, src_node, val); } // AstGen.zig:GenZir.makeUnNode // Create a un_node instruction without pushing it onto the gz // instruction stack. Returns the raw instruction index. Used for deferred // instruction placement (e.g. while optional payload capture). static uint32_t gzMakeUnNode( GenZir* gz, ZirInstTag tag, ZirInstRef operand, AstNodeIndex src_node) { ZirInstData data; memset(&data, 0, sizeof(data)); data.un_node.src_node = gzNodeIndexToRelative(gz, src_node); data.un_node.operand = operand; return agAppendInst(gz->astgen, tag, data); } // AstGen.zig:GenZir.addUnNode static ZirInstRef gzAddUnNode( GenZir* gz, ZirInstTag tag, ZirInstRef operand, AstNodeIndex src_node) { const uint32_t inst = gzMakeUnNode(gz, tag, operand, src_node); gzStackPush(gz->astgen, inst); return inst + ZIR_REF_START_INDEX; } // AstGen.zig:GenZir.addPlNodePayloadIndex static ZirInstRef gzAddPlNodePayloadIndex(GenZir* gz, ZirInstTag tag, AstNodeIndex src_node, uint32_t payload_index) { ZirInstData data; memset(&data, 0, sizeof(data)); data.pl_node.src_node = gzNodeIndexToRelative(gz, src_node); data.pl_node.payload_index = payload_index; return gzAdd(gz, tag, data); } // AstGen.zig:GenZir.addPlNode // gz.addPlNode with an N-word extra payload (operands appended in order). static ZirInstRef gzAddPlNodeExtra(GenZir* gz, ZirInstTag tag, AstNodeIndex src_node, const uint32_t* words, uint32_t words_len) { AstGen* ag = gz->astgen; const uint32_t payload_index = ag->extra_len; for (uint32_t i = 0; i < words_len; i++) extraAppend(ag, words[i]); return gzAddPlNodePayloadIndex(gz, tag, src_node, payload_index); } // AstGen.zig:GenZir.addPlNode // gz.addPlNode with a Zir.Inst.Bin payload. static ZirInstRef gzAddPlNodeBin(GenZir* gz, ZirInstTag tag, AstNodeIndex src_node, ZirInstRef lhs, ZirInstRef rhs) { const uint32_t words[2] = { lhs, rhs }; return gzAddPlNodeExtra(gz, tag, src_node, words, 2); } // AstGen.zig:GenZir.addPlNode // gz.addPlNode(.float128, ...): `pieces` holds the four little-endian u32 // words of the f128 bit pattern (Zir.Inst.Float128). static ZirInstRef gzAddFloat128( GenZir* gz, AstNodeIndex node, const uint32_t pieces[4]) { return gzAddPlNodeExtra(gz, ZIR_INST_FLOAT128, node, pieces, 4); } // gz.addStrTok: append a str_tok instruction. static ZirInstRef gzAddStrTok( GenZir* gz, ZirInstTag tag, uint32_t str_index, AstTokenIndex abs_tok); // AstGen.zig:GenZir.refIsNoReturn // Whether a Ref refers to a noreturn instruction. static bool refIsNoReturn(const GenZir* gz, ZirInstRef ref) { if (ref == ZIR_REF_UNREACHABLE_VALUE) return true; if (ref >= ZIR_REF_START_INDEX) { const uint32_t idx = ref - ZIR_REF_START_INDEX; return tagIsNoReturn(gz->astgen->inst_tags[idx]); } return false; } static BuiltinFnEvalToError nodeMayEvalToError( const Ast* tree, AstNodeIndex node); // --- forward declarations --- static ZirInstRef gzAddNode(GenZir* gz, ZirInstTag tag, AstNodeIndex src_node); static ZirInstRef expr( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node); static ZirInstRef fnProtoExprInner(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstFullFnProto fn_proto, bool implicit_ccc); static ZirInstRef fnProtoExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstFullFnProto fn_proto); static ZirInstRef orelseCatchExpr(GenZir* parent_gz, Scope* scope, ResultInfo ri, AstNodeIndex node, ZirInstTag cond_op, ZirInstTag unwrap_op, ZirInstTag unwrap_code_op, AstTokenIndex payload_token); static ZirInstRef errorSetDecl(GenZir* gz, ResultInfo ri, AstNodeIndex node); static ZirInstRef simpleStrTok(GenZir* gz, ResultInfo ri, AstTokenIndex ident_token, AstNodeIndex node, ZirInstTag op_inst_tag); static ZirInstRef comptimeExprAst( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node); static ZirInstRef rvalue( GenZir* gz, ResultInfo ri, ZirInstRef raw_result, AstNodeIndex src_node); static ZirInstRef rvalueNoCoercePreRef( GenZir* gz, ResultInfo ri, ZirInstRef raw_result, AstNodeIndex src_node); static int32_t gzTokenIndexToRelative(const GenZir* gz, AstTokenIndex tok); static uint32_t gzMakeUnTok( GenZir* gz, ZirInstTag tag, ZirInstRef operand, AstTokenIndex abs_tok); static ZirInstRef gzAddUnTok( GenZir* gz, ZirInstTag tag, ZirInstRef operand, AstTokenIndex abs_tok); static ZirInstRef ret(GenZir* gz, Scope* scope, AstNodeIndex node); static ZirInstRef simpleBinOp(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, ZirInstTag op_inst_tag); static ZirInstRef boolBinOp(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, ZirInstTag zir_tag); typedef enum { BLOCK_KIND_NORMAL, BLOCK_KIND_ALLOW_BRANCH_HINT } BlockKind; static ZirInstRef fullBodyExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, BlockKind block_kind); static ZirInstRef blockExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex block_node, const AstNodeIndex* statements, uint32_t statements_len, BlockKind block_kind); static ZirInstRef labeledBlockExpr(GenZir* gz, Scope* parent_scope, ResultInfo ri, AstNodeIndex block_node, const AstNodeIndex* statements, uint32_t statements_len, bool force_comptime, BlockKind block_kind); static ZirInstRef breakExpr( GenZir* parent_gz, Scope* parent_scope, AstNodeIndex node); static ZirInstRef continueExpr( GenZir* parent_gz, Scope* parent_scope, AstNodeIndex node); static void blockExprStmts(GenZir* gz, Scope* parent_scope, const AstNodeIndex* statements, uint32_t statements_len, BlockKind block_kind); static void gzSetBreakResultInfo(GenZir* gz, ResultInfo parent_ri); static ZirInstRef ifExpr( GenZir* parent_gz, Scope* scope, ResultInfo ri, AstNodeIndex node); static ZirInstRef whileExpr(GenZir* parent_gz, Scope* scope, ResultInfo ri, AstNodeIndex node, bool is_statement); static void assign(GenZir* gz, Scope* scope, AstNodeIndex infix_node); static void assignOp( GenZir* gz, Scope* scope, AstNodeIndex infix_node, ZirInstTag op_inst_tag); static void assignShift( GenZir* gz, Scope* scope, AstNodeIndex infix_node, ZirInstTag op_inst_tag); static void assignShiftSat(GenZir* gz, Scope* scope, AstNodeIndex infix_node); static Scope* assignDestructureMaybeDecls( GenZir* gz, Scope* scope, AstNodeIndex node); static void assignDestructure(GenZir* gz, Scope* scope, AstNodeIndex node); static ZirInstRef ptrType(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstFullPtrType ptr_info); static ZirInstRef containerDecl(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstFullContainerDecl container_decl, uint8_t name_strat); // nameStratExpr: if `node` consumes a name strategy (container decl), evaluate // it with `name_strat`, store the result in *out, and return true. Otherwise // returns false and the caller should use `expr`. static bool nameStratExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, uint8_t name_strat, ZirInstRef* out); typedef struct { uint32_t line; uint32_t column; } LineColumn; static void emitDbgStmt(GenZir* gz, uint32_t line, uint32_t column); static void emitDbgStmtForceCurrentIndex( GenZir* gz, uint32_t line, uint32_t column); static LineColumn maybeAdvanceSourceCursorToMainToken( GenZir* gz, AstNodeIndex node); static ZirInstRef callExpr(GenZir* gz, Scope* scope, ResultInfo ri, ZirInstRef override_decl_literal_type, AstNodeIndex node); static ZirInstRef tryExpr( GenZir* parent_gz, Scope* scope, ResultInfo ri, AstNodeIndex node); static ZirInstRef typeExpr(GenZir* gz, Scope* scope, AstNodeIndex type_node); // SimpleComptimeReason's null, for reachableExprComptime. #define NO_COMPTIME_REASON UINT32_MAX static ZirInstRef reachableExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex reachable_node); static ZirInstRef reachableExprComptime(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex reachable_node, uint32_t comptime_reason); static ZirInstRef reachableTypeExpr(GenZir* gz, Scope* scope, AstNodeIndex type_node, AstNodeIndex reachable_node); static ZirInstRef riResultType(ResultInfo ri); static ZirInstRef resultLocType(GenZir* gz, ResultInfo ri, AstNodeIndex node); ZIG1_ATTRIBUTE((noreturn)) static void failNoResultType(const AstGen* ag, AstNodeIndex node); static ZirInstRef arrayType( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node); static ZirInstRef arrayTypeSentinel( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node); static ZirInstRef forExpr(GenZir* parent_gz, Scope* scope, ResultInfo ri, AstNodeIndex node, bool is_statement); // AstGen.zig:SwitchNonErr typedef enum { // A regular switch expression. Emits `switch_block[_ref]`. SWITCH_NON_ERR_NONE, // `eu catch |err| switch (err) { ... }`, unlabeled switch. // Emits `switch_block_err_union`. SWITCH_NON_ERR_CATCH, // `if (eu) |payload| { ... } else |err| switch (err) { ... }`, // unlabeled switch. Emits `switch_block_err_union`. SWITCH_NON_ERR_IF, // `eu catch |err| label: switch (err) { ... }` (or the `if` form): a // `condbr` on the non-error body and a regular switch whose `break`s // target `block_inst` as peers of the non-error prong. SWITCH_NON_ERR_PEER_BREAK_TARGET, } SwitchNonErrTag; typedef struct { SwitchNonErrTag tag; AstFullIf if_full; // SWITCH_NON_ERR_IF uint32_t block_inst; // SWITCH_NON_ERR_PEER_BREAK_TARGET ResultInfo block_ri; // SWITCH_NON_ERR_PEER_BREAK_TARGET } SwitchNonErr; static ZirInstRef switchExpr(GenZir* parent_gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstFullSwitch switch_full, SwitchNonErr non_err); static bool switchOnErrCapture(const Ast* tree, AstNodeIndex node, AstTokenIndex capture_token, bool labeled, AstFullSwitch* switch_full); static void emitDbgNode(GenZir* gz, AstNodeIndex node); static uint32_t gzMakeBlockInst(GenZir* gz, ZirInstTag tag, AstNodeIndex node); static void gzSetBlockBody(GenZir* gz, uint32_t inst); static void gzSetBlockComptimeBody(GenZir* gz, uint32_t inst, uint32_t reason); static uint32_t gzAddCondBr(GenZir* gz, ZirInstTag tag, AstNodeIndex node); static void setCondBrPayload(uint32_t condbr_inst, ZirInstRef cond, GenZir* then_scope, GenZir* else_scope); // comptimeExpr: evaluate `node` in a comptime context. // `src_node` is used as the source location for the block_comptime inst. // In most cases, src_node == node; the exception is AST_NODE_COMPTIME where // src_node = the comptime node and node = the inner expression. static ZirInstRef comptimeExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, uint32_t reason); static ZirInstRef comptimeExpr2(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex src_node, uint32_t reason); // varDecl: lower a local var decl statement; returns the new (child) scope. static Scope* varDecl(GenZir* gz, Scope* scope, AstNodeIndex node); static const ResultInfo coerced_type_ri = { { RL_COERCED_TY, ZIR_REF_TYPE_TYPE, 0, 0, NULL, 0, 0 }, 0 }; static const ResultInfo coerced_bool_ri = { { RL_COERCED_TY, ZIR_REF_BOOL_TYPE, 0, 0, NULL, 0, 0 }, 0 }; static const ResultInfo coerced_align_ri = { { RL_COERCED_TY, ZIR_REF_U29_TYPE, 0, 0, NULL, 0, 0 }, 0 }; // The `.{ .rl = ... }` literals upstream spells inline. static const ResultInfo none_ri = { { RL_NONE, 0, 0, 0, NULL, 0, 0 }, 0 }; static const ResultInfo ref_const_ri = { { RL_REF_CONST, 0, 0, 0, NULL, 0, 0 }, 0 }; static const ResultInfo coerced_u32_ri = { { RL_COERCED_TY, ZIR_REF_U32_TYPE, 0, 0, NULL, 0, 0 }, 0 }; static const ResultInfo ref_ri = { { RL_REF, 0, 0, 0, NULL, 0, 0 }, 0 }; static const ResultInfo discard_ri = { { RL_DISCARD, 0, 0, 0, NULL, 0, 0 }, 0 }; static const ResultInfo coerced_usize_ri = { { RL_COERCED_TY, ZIR_REF_USIZE_TYPE, 0, 0, NULL, 0, 0 }, 0 }; static const ResultInfo slice_const_u8_ri = { { RL_COERCED_TY, ZIR_REF_SLICE_CONST_U8_TYPE, 0, 0, NULL, 0, 0 }, 0 }; // ResultInfo.br(): result info for a break operand. static ResultInfo riBr(ResultInfo ri) { if (ri.rl.tag == RL_COERCED_TY) { ResultInfo out = { { RL_TY, ri.rl.ty, 0, 0, NULL, 0, 0 }, ri.ctx }; return out; } return ri; } // --- rvalue --- // AstGen.zig:rvalueInner static ZirInstRef rvalueInner(GenZir* gz, ResultInfo ri, ZirInstRef raw_result, AstNodeIndex src_node, bool allow_coerce_pre_ref) { ZirInstRef result = raw_result; if (raw_result >= ZIR_REF_START_INDEX) { const uint32_t result_index = raw_result - ZIR_REF_START_INDEX; const ZirInstTag tag = gz->astgen->inst_tags[result_index]; const ZirInstData data = gz->astgen->inst_datas[result_index]; if (tagIsAlwaysVoid(tag, data)) result = ZIR_REF_VOID_VALUE; } if (gzEndsWithNoReturn(gz)) return result; switch (ri.rl.tag) { case RL_NONE: case RL_COERCED_TY: return result; case RL_DISCARD: // Emit a compile error for discarding error values. gzAddUnNode(gz, ZIR_INST_ENSURE_RESULT_NON_ERROR, result, src_node); return ZIR_REF_VOID_VALUE; case RL_REF: case RL_REF_CONST: case RL_REF_COERCED_TY: { ZirInstRef coerced_result = result; if (allow_coerce_pre_ref && ri.rl.tag == RL_REF_COERCED_TY) { coerced_result = gzAddPlNodeBin( gz, ZIR_INST_COERCE_PTR_ELEM_TY, src_node, ri.rl.ty, result); } // We need a pointer but we have a value. Route const-local refs // through the ref_table so a subsequent address-of yields the same // address. const AstTokenIndex src_token = astFirstToken(gz->astgen->tree, src_node); if (coerced_result < ZIR_REF_START_INDEX) return gzAddUnTok(gz, ZIR_INST_REF, coerced_result, src_token); const uint32_t result_index = coerced_result - ZIR_REF_START_INDEX; bool found_existing; uint32_t* slot = refTableGetOrPut(gz->astgen, result_index, &found_existing); if (!found_existing) *slot = gzMakeUnTok(gz, ZIR_INST_REF, coerced_result, src_token); return *slot + ZIR_REF_START_INDEX; } case RL_TY: { // Quickly eliminate some common, unnecessary type coercion. const ZirInstRef ty_inst = ri.rl.ty; const uint64_t key = ((uint64_t)ty_inst << 32) | (uint64_t)(uint32_t)result; #define AS_TY(r) ((((uint64_t)ZIR_REF_TYPE_TYPE) << 32) | (uint64_t)(r)) #define AS_CI(r) \ ((((uint64_t)ZIR_REF_COMPTIME_INT_TYPE) << 32) | (uint64_t)(r)) #define AS_US(r) ((((uint64_t)ZIR_REF_USIZE_TYPE) << 32) | (uint64_t)(r)) #define AS_U1(r) ((((uint64_t)ZIR_REF_U1_TYPE) << 32) | (uint64_t)(r)) #define AS_U8(r) ((((uint64_t)ZIR_REF_U8_TYPE) << 32) | (uint64_t)(r)) #define AS_BOOL(r) ((((uint64_t)ZIR_REF_BOOL_TYPE) << 32) | (uint64_t)(r)) #define AS_VOID(r) ((((uint64_t)ZIR_REF_VOID_TYPE) << 32) | (uint64_t)(r)) switch (key) { case AS_TY(ZIR_REF_U1_TYPE): case AS_TY(ZIR_REF_U8_TYPE): case AS_TY(ZIR_REF_I8_TYPE): case AS_TY(ZIR_REF_U16_TYPE): case AS_TY(ZIR_REF_U29_TYPE): case AS_TY(ZIR_REF_I16_TYPE): case AS_TY(ZIR_REF_U32_TYPE): case AS_TY(ZIR_REF_I32_TYPE): case AS_TY(ZIR_REF_U64_TYPE): case AS_TY(ZIR_REF_I64_TYPE): case AS_TY(ZIR_REF_U128_TYPE): case AS_TY(ZIR_REF_I128_TYPE): case AS_TY(ZIR_REF_USIZE_TYPE): case AS_TY(ZIR_REF_ISIZE_TYPE): case AS_TY(ZIR_REF_C_CHAR_TYPE): case AS_TY(ZIR_REF_C_SHORT_TYPE): case AS_TY(ZIR_REF_C_USHORT_TYPE): case AS_TY(ZIR_REF_C_INT_TYPE): case AS_TY(ZIR_REF_C_UINT_TYPE): case AS_TY(ZIR_REF_C_LONG_TYPE): case AS_TY(ZIR_REF_C_ULONG_TYPE): case AS_TY(ZIR_REF_C_LONGLONG_TYPE): case AS_TY(ZIR_REF_C_ULONGLONG_TYPE): case AS_TY(ZIR_REF_C_LONGDOUBLE_TYPE): case AS_TY(ZIR_REF_F16_TYPE): case AS_TY(ZIR_REF_F32_TYPE): case AS_TY(ZIR_REF_F64_TYPE): case AS_TY(ZIR_REF_F80_TYPE): case AS_TY(ZIR_REF_F128_TYPE): case AS_TY(ZIR_REF_ANYOPAQUE_TYPE): case AS_TY(ZIR_REF_BOOL_TYPE): case AS_TY(ZIR_REF_VOID_TYPE): case AS_TY(ZIR_REF_TYPE_TYPE): case AS_TY(ZIR_REF_ANYERROR_TYPE): case AS_TY(ZIR_REF_COMPTIME_INT_TYPE): case AS_TY(ZIR_REF_COMPTIME_FLOAT_TYPE): case AS_TY(ZIR_REF_NORETURN_TYPE): case AS_TY(ZIR_REF_ANYFRAME_TYPE): case AS_TY(ZIR_REF_NULL_TYPE): case AS_TY(ZIR_REF_UNDEFINED_TYPE): case AS_TY(ZIR_REF_ENUM_LITERAL_TYPE): case AS_TY(ZIR_REF_PTR_USIZE_TYPE): case AS_TY(ZIR_REF_PTR_CONST_COMPTIME_INT_TYPE): case AS_TY(ZIR_REF_MANYPTR_U8_TYPE): case AS_TY(ZIR_REF_MANYPTR_CONST_U8_TYPE): case AS_TY(ZIR_REF_MANYPTR_CONST_U8_SENTINEL_0_TYPE): case AS_TY(ZIR_REF_SLICE_CONST_U8_TYPE): case AS_TY(ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE): case AS_TY(ZIR_REF_ANYERROR_VOID_ERROR_UNION_TYPE): case AS_TY(ZIR_REF_GENERIC_POISON_TYPE): case AS_TY(ZIR_REF_EMPTY_TUPLE_TYPE): case AS_CI(ZIR_REF_ZERO): case AS_CI(ZIR_REF_ONE): case AS_CI(ZIR_REF_NEGATIVE_ONE): case AS_US(ZIR_REF_UNDEF_USIZE): case AS_US(ZIR_REF_ZERO_USIZE): case AS_US(ZIR_REF_ONE_USIZE): case AS_U1(ZIR_REF_UNDEF_U1): case AS_U1(ZIR_REF_ZERO_U1): case AS_U1(ZIR_REF_ONE_U1): case AS_U8(ZIR_REF_ZERO_U8): case AS_U8(ZIR_REF_ONE_U8): case AS_U8(ZIR_REF_FOUR_U8): case AS_BOOL(ZIR_REF_UNDEF_BOOL): case AS_BOOL(ZIR_REF_BOOL_TRUE): case AS_BOOL(ZIR_REF_BOOL_FALSE): case AS_VOID(ZIR_REF_VOID_VALUE): return result; // type of result is already correct case AS_BOOL(ZIR_REF_UNDEF): return ZIR_REF_UNDEF_BOOL; case AS_US(ZIR_REF_UNDEF): return ZIR_REF_UNDEF_USIZE; case AS_US(ZIR_REF_UNDEF_U1): return ZIR_REF_UNDEF_USIZE; case AS_U1(ZIR_REF_UNDEF): return ZIR_REF_UNDEF_U1; case AS_US(ZIR_REF_ZERO): return ZIR_REF_ZERO_USIZE; case AS_U1(ZIR_REF_ZERO): return ZIR_REF_ZERO_U1; case AS_U8(ZIR_REF_ZERO): return ZIR_REF_ZERO_U8; case AS_US(ZIR_REF_ONE): return ZIR_REF_ONE_USIZE; case AS_U1(ZIR_REF_ONE): return ZIR_REF_ONE_U1; case AS_U8(ZIR_REF_ONE): return ZIR_REF_ONE_U8; case AS_CI(ZIR_REF_ZERO_USIZE): return ZIR_REF_ZERO; case AS_U1(ZIR_REF_ZERO_USIZE): return ZIR_REF_ZERO_U1; case AS_U8(ZIR_REF_ZERO_USIZE): return ZIR_REF_ZERO_U8; case AS_CI(ZIR_REF_ONE_USIZE): return ZIR_REF_ONE; case AS_U1(ZIR_REF_ONE_USIZE): return ZIR_REF_ONE_U1; case AS_U8(ZIR_REF_ONE_USIZE): return ZIR_REF_ONE_U8; case AS_CI(ZIR_REF_ZERO_U1): return ZIR_REF_ZERO; case AS_CI(ZIR_REF_ZERO_U8): return ZIR_REF_ZERO; case AS_US(ZIR_REF_ZERO_U1): return ZIR_REF_ZERO_USIZE; case AS_US(ZIR_REF_ZERO_U8): return ZIR_REF_ZERO_USIZE; case AS_CI(ZIR_REF_ONE_U1): return ZIR_REF_ONE; case AS_CI(ZIR_REF_ONE_U8): return ZIR_REF_ONE; case AS_US(ZIR_REF_ONE_U1): return ZIR_REF_ONE_USIZE; case AS_US(ZIR_REF_ONE_U8): return ZIR_REF_ONE_USIZE; default: { const ZirInstTag as_tag = ri.ctx == CTX_SHIFT_OP ? ZIR_INST_AS_SHIFT_OPERAND : ZIR_INST_AS_NODE; return gzAddPlNodeBin(gz, as_tag, src_node, ty_inst, result); } } #undef AS_TY #undef AS_CI #undef AS_US #undef AS_U1 #undef AS_U8 #undef AS_BOOL #undef AS_VOID } case RL_PTR: { const AstNodeIndex store_node = ri.rl.src_node != 0 ? ri.rl.src_node : src_node; gzAddPlNodeBin( gz, ZIR_INST_STORE_NODE, store_node, ri.rl.inst, result); return ZIR_REF_VOID_VALUE; } case RL_INFERRED_PTR: { gzAddPlNodeBin( gz, ZIR_INST_STORE_TO_INFERRED_PTR, src_node, ri.rl.inst, result); return ZIR_REF_VOID_VALUE; } case RL_DESTRUCTURE: { // Mirrors AstGen.zig rvalue .destructure case. // Emit validate_destructure, then elem_val_imm + store per component. // Components are stored in ri.rl so nested destructures don't clobber. AstGen* ag = gz->astgen; const uint32_t n = ri.rl.ds_components_len; const DestructureComponent* components = ri.rl.ds_components; const AstNodeIndex ds_node = ri.rl.ds_src_node; // validate_destructure: pl_node with { operand, destructure_node, // expect_len } const uint32_t payload_index = ag->extra_len; extraAppend(ag, (uint32_t)result); extraAppend(ag, (uint32_t)(int32_t)gzNodeIndexToRelative(gz, ds_node)); extraAppend(ag, n); ZirInstData vd_data; memset(&vd_data, 0, sizeof(vd_data)); vd_data.pl_node.src_node = gzNodeIndexToRelative(gz, src_node); vd_data.pl_node.payload_index = payload_index; gzAdd(gz, ZIR_INST_VALIDATE_DESTRUCTURE, vd_data); // For each component: elem_val_imm + store for (uint32_t i = 0; i < n; i++) { const DestructureComponent* comp = &components[i]; if (comp->tag == DC_DISCARD) continue; // elem_val_imm: { operand, idx } ZirInstData ev_data; memset(&ev_data, 0, sizeof(ev_data)); ev_data.elem_val_imm.operand = (uint32_t)result; ev_data.elem_val_imm.idx = i; const ZirInstRef elem_val = gzAdd(gz, ZIR_INST_ELEM_VAL_IMM, ev_data); if (comp->tag == DC_INFERRED_PTR) { gzAddPlNodeBin(gz, ZIR_INST_STORE_TO_INFERRED_PTR, src_node, comp->inst, elem_val); } else { // DC_TYPED_PTR: store_node const AstNodeIndex store_node = (comp->src_node != 0) ? comp->src_node : src_node; gzAddPlNodeBin( gz, ZIR_INST_STORE_NODE, store_node, comp->inst, elem_val); } } return ZIR_REF_VOID_VALUE; } } UNREACHABLE("rvalue: invalid result location tag"); ZIG1_TCC_RETURN(ZIR_REF_NONE); } // AstGen.zig:rvalue static ZirInstRef rvalue( GenZir* gz, ResultInfo ri, ZirInstRef raw_result, AstNodeIndex src_node) { return rvalueInner(gz, ri, raw_result, src_node, true); } // AstGen.zig:rvalueNoCoercePreRef static ZirInstRef rvalueNoCoercePreRef( GenZir* gz, ResultInfo ri, ZirInstRef raw_result, AstNodeIndex src_node) { return rvalueInner(gz, ri, raw_result, src_node, false); } // --- primitive identifier -> Zir.Inst.Ref --- static bool primitiveRef(const char* name, uint32_t len, ZirInstRef* out) { #define PRIM(s, v) \ if (len == sizeof(s) - 1 && memcmp(name, s, len) == 0) { \ *out = (v); \ return true; \ } PRIM("anyerror", ZIR_REF_ANYERROR_TYPE) PRIM("anyframe", ZIR_REF_ANYFRAME_TYPE) PRIM("anyopaque", ZIR_REF_ANYOPAQUE_TYPE) PRIM("bool", ZIR_REF_BOOL_TYPE) PRIM("c_int", ZIR_REF_C_INT_TYPE) PRIM("c_long", ZIR_REF_C_LONG_TYPE) PRIM("c_longdouble", ZIR_REF_C_LONGDOUBLE_TYPE) PRIM("c_longlong", ZIR_REF_C_LONGLONG_TYPE) PRIM("c_char", ZIR_REF_C_CHAR_TYPE) PRIM("c_short", ZIR_REF_C_SHORT_TYPE) PRIM("c_uint", ZIR_REF_C_UINT_TYPE) PRIM("c_ulong", ZIR_REF_C_ULONG_TYPE) PRIM("c_ulonglong", ZIR_REF_C_ULONGLONG_TYPE) PRIM("c_ushort", ZIR_REF_C_USHORT_TYPE) PRIM("comptime_float", ZIR_REF_COMPTIME_FLOAT_TYPE) PRIM("comptime_int", ZIR_REF_COMPTIME_INT_TYPE) PRIM("false", ZIR_REF_BOOL_FALSE) PRIM("noreturn", ZIR_REF_NORETURN_TYPE) PRIM("null", ZIR_REF_NULL_VALUE) PRIM("true", ZIR_REF_BOOL_TRUE) PRIM("type", ZIR_REF_TYPE_TYPE) PRIM("undefined", ZIR_REF_UNDEF) PRIM("void", ZIR_REF_VOID_TYPE) PRIM("f16", ZIR_REF_F16_TYPE) PRIM("f32", ZIR_REF_F32_TYPE) PRIM("f64", ZIR_REF_F64_TYPE) PRIM("f80", ZIR_REF_F80_TYPE) PRIM("f128", ZIR_REF_F128_TYPE) PRIM("u0", ZIR_REF_U0_TYPE) PRIM("u1", ZIR_REF_U1_TYPE) PRIM("u8", ZIR_REF_U8_TYPE) PRIM("i8", ZIR_REF_I8_TYPE) PRIM("u16", ZIR_REF_U16_TYPE) PRIM("i16", ZIR_REF_I16_TYPE) PRIM("u29", ZIR_REF_U29_TYPE) PRIM("u32", ZIR_REF_U32_TYPE) PRIM("i32", ZIR_REF_I32_TYPE) PRIM("u64", ZIR_REF_U64_TYPE) PRIM("i64", ZIR_REF_I64_TYPE) PRIM("u80", ZIR_REF_U80_TYPE) PRIM("u128", ZIR_REF_U128_TYPE) PRIM("i128", ZIR_REF_I128_TYPE) PRIM("u256", ZIR_REF_U256_TYPE) PRIM("usize", ZIR_REF_USIZE_TYPE) PRIM("isize", ZIR_REF_ISIZE_TYPE) #undef PRIM return false; } // --- closure tunnelling --- static uint32_t captureWrap(uint32_t tag, uint32_t data) { return ((tag & ZIR_CAPTURE_TAG_MASK) << ZIR_CAPTURE_TAG_SHIFT) | (data << ZIR_CAPTURE_DATA_SHIFT); } // Namespace.captures.getOrPut: dedup by capture key, returning its index. The // name is (re)written on every access, matching upstream `value_ptr.* = name`. static uint32_t nsCapturesGetOrPut( Namespace* ns, uint32_t key, uint32_t name) { for (uint32_t i = 0; i < ns->captures_len; i++) { if (ns->capture_keys[i] == key) { ns->capture_names[i] = name; return i; } } if (ns->captures_len == ns->captures_cap) { uint32_t nc = zig1GrowCap(ns->captures_cap, 4u); ns->capture_keys = zig1Realloc(ns->capture_keys, (size_t)nc * sizeof(uint32_t)); ns->capture_names = zig1Realloc(ns->capture_names, (size_t)nc * sizeof(uint32_t)); ns->captures_cap = nc; } const uint32_t idx = ns->captures_len++; ns->capture_keys[idx] = key; ns->capture_names[idx] = name; return idx; } typedef enum { TUNNEL_REF, TUNNEL_REF_LOAD, TUNNEL_DECL_VAL, TUNNEL_DECL_REF, } TunnelValueKind; // AstGen.zig:tunnelThroughClosure // Access a ZIR instruction / decl through `num_tunnels` // namespaces, adding closure captures as required. Returns a `closure_get`. static ZirInstRef tunnelThroughClosure(GenZir* gz, AstNodeIndex inner_ref_node, uint32_t num_tunnels, TunnelValueKind kind, uint32_t value, uint32_t name_str_index) { // A trivial ref value (no backing instruction) needs no tunnel. if (kind == TUNNEL_REF && value < ZIR_REF_START_INDEX) return value; // Find the path of namespaces we are tunnelling through: the outermost is // `root_ns`, then `intermediate[0..num_tunnels-1]` from outer to inner. Namespace** intermediate = zig1Malloc((num_tunnels ? num_tunnels : 1u) * sizeof(Namespace*)); Namespace* root_ns = NULL; { uint32_t i = num_tunnels - 1; Scope* s = gz->base.parent; while (i > 0) { if (s->tag == SCOPE_NAMESPACE) { i -= 1; intermediate[i] = (Namespace*)s; } s = s->parent; } for (;;) { if (s->tag == SCOPE_NAMESPACE) { root_ns = (Namespace*)s; break; } s = s->parent; } } uint32_t root_capture; switch (kind) { case TUNNEL_REF: root_capture = captureWrap( ZIR_CAPTURE_TAG_INSTRUCTION, value - ZIR_REF_START_INDEX); break; case TUNNEL_REF_LOAD: root_capture = captureWrap( ZIR_CAPTURE_TAG_INSTRUCTION_LOAD, value - ZIR_REF_START_INDEX); break; case TUNNEL_DECL_VAL: root_capture = captureWrap(ZIR_CAPTURE_TAG_DECL_VAL, value); break; default: // TUNNEL_DECL_REF root_capture = captureWrap(ZIR_CAPTURE_TAG_DECL_REF, value); break; } uint32_t cur_capture_index = nsCapturesGetOrPut(root_ns, root_capture, name_str_index); for (uint32_t i = 0; i < num_tunnels - 1; i++) { const uint32_t nested_key = captureWrap(ZIR_CAPTURE_TAG_NESTED, cur_capture_index); cur_capture_index = nsCapturesGetOrPut(intermediate[i], nested_key, name_str_index); } free(intermediate); // Upstream feeds `cur_capture_index` into src_hasher; the C port // zero-fills and masks source hashes, so that update is intentionally // omitted. return gzAddExtendedNodeSmall( gz, ZIR_EXT_CLOSURE_GET, inner_ref_node, (uint16_t)cur_capture_index); } // --- identifier / numberLiteral --- // AstGen.zig:localVarRef // Resolve an identifier against local scopes and namespaces. static ZirInstRef localVarRef(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex ident, AstTokenIndex ident_token) { AstGen* ag = gz->astgen; const uint32_t name_str_index = identAsString(ag, ident_token); bool have_found = false; bool found_needs_tunnel = false; uint32_t found_namespaces_out = 0; uint32_t num_namespaces_out = 0; Scope* s = scope; for (;;) { if (s->tag == SCOPE_LOCAL_VAL) { LocalVal* lv = (LocalVal*)s; if (lv->name == name_str_index) { if (ri.rl.tag == RL_DISCARD && ri.ctx == CTX_ASSIGNMENT) lv->discarded = true; else lv->used = true; if (lv->is_used_or_discarded) *lv->is_used_or_discarded = true; const ZirInstRef value_inst = num_namespaces_out != 0 ? tunnelThroughClosure(gz, ident, num_namespaces_out, TUNNEL_REF, lv->inst, name_str_index) : lv->inst; return rvalueNoCoercePreRef(gz, ri, value_inst, ident); } s = lv->base.parent; continue; } if (s->tag == SCOPE_LOCAL_PTR) { LocalPtr* lp = (LocalPtr*)s; if (lp->name == name_str_index) { if (ri.rl.tag == RL_DISCARD && ri.ctx == CTX_ASSIGNMENT) lp->discarded = true; else lp->used = true; // AstGen.localVarRef: "if (!local_ptr.maybe_comptime and // !gz.is_typeof)". Inside a `@TypeOf` operand the closure of // a runtime variable is permitted, so the two rejections // below do not apply and the tunnel arms run instead. Both // rejections are upstream compile errors, not gaps. if (!lp->maybe_comptime && !gz->is_typeof) { const char* name; uint32_t name_len; astTokenSlice(ag->tree, ident_token, &name, &name_len); if (num_namespaces_out != 0) failNode(ag, ident, "mutable '%.*s' not accessible from here", (int)name_len, name); if (ri.ctx == CTX_RETURN_ADDROF) failNode(ag, ident, "returning address of expired local variable " "'%.*s'", (int)name_len, name); } switch (ri.rl.tag) { case RL_REF: case RL_REF_CONST: case RL_REF_COERCED_TY: { const ZirInstRef ptr_inst = num_namespaces_out != 0 ? tunnelThroughClosure(gz, ident, num_namespaces_out, TUNNEL_REF, lp->ptr, name_str_index) : lp->ptr; if (ri.rl.tag != RL_REF_CONST) lp->used_as_lvalue = true; return ptr_inst; } default: { const ZirInstRef val_inst = num_namespaces_out != 0 ? tunnelThroughClosure(gz, ident, num_namespaces_out, TUNNEL_REF_LOAD, lp->ptr, name_str_index) : gzAddUnNode(gz, ZIR_INST_LOAD, lp->ptr, ident); return rvalueNoCoercePreRef(gz, ri, val_inst, ident); } } } s = lp->base.parent; continue; } if (s->tag == SCOPE_GEN_ZIR) { s = s->parent; continue; } if (s->tag == SCOPE_DEFER_NORMAL || s->tag == SCOPE_DEFER_ERROR) { s = s->parent; continue; } if (s->tag == SCOPE_NAMESPACE) { Namespace* ns = (Namespace*)s; for (uint32_t i = 0; i < ns->decls_len; i++) { if (ns->decls[i].name == name_str_index) { if (have_found) failNode(ag, ident, "ambiguous reference"); have_found = true; found_needs_tunnel = ns->maybe_generic; found_namespaces_out = num_namespaces_out; break; } } num_namespaces_out += 1; s = ns->base.parent; continue; } if (s->tag == SCOPE_TOP) break; ASTGEN_UNIMPLEMENTED("localVarRef: unhandled scope tag"); } if (!have_found) { const char* name; uint32_t name_len; astTokenSlice(ag->tree, ident_token, &name, &name_len); failNode(ag, ident, "use of undeclared identifier '%.*s'", (int)name_len, name); } // Decl references happen by name rather than ZIR index. if (found_namespaces_out > 0 && found_needs_tunnel) { switch (ri.rl.tag) { case RL_REF: case RL_REF_CONST: case RL_REF_COERCED_TY: return tunnelThroughClosure(gz, ident, found_namespaces_out, TUNNEL_DECL_REF, name_str_index, name_str_index); default: { const ZirInstRef result = tunnelThroughClosure(gz, ident, found_namespaces_out, TUNNEL_DECL_VAL, name_str_index, name_str_index); return rvalueNoCoercePreRef(gz, ri, result, ident); } } } switch (ri.rl.tag) { case RL_REF: case RL_REF_CONST: case RL_REF_COERCED_TY: return gzAddStrTok(gz, ZIR_INST_DECL_REF, name_str_index, ident_token); default: { const ZirInstRef result = gzAddStrTok(gz, ZIR_INST_DECL_VAL, name_str_index, ident_token); return rvalueNoCoercePreRef(gz, ri, result, ident); } } } static ZirInstRef identifierInner(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex ident, bool force_comptime, AstNodeIndex fc_src_node, uint32_t fc_reason) { AstGen* ag = gz->astgen; const AstTokenIndex ident_token = astNodeMainToken(ag->tree, ident); const char* name; uint32_t name_len; astTokenSlice(ag->tree, ident_token, &name, &name_len); if (name_len == 1 && name[0] == '_') failNode(ag, ident, "'_' used as an identifier without @\"_\" syntax"); if (name_len == 0 || name[0] != '@') { ZirInstRef prim; if (primitiveRef(name, name_len, &prim)) return rvalue(gz, ri, prim, ident); // Primitive integer type like `u7` / `i33`. if (name_len >= 2 && (name[0] == 'u' || name[0] == 'i')) { uint8_t signedness = name[0] == 'u' ? 1 : 0; // signed=0, unsigned=1 (std.lang.Signedness) bool all_digits = true; uint32_t bit_count = 0; for (uint32_t i = 1; i < name_len; i++) { if (name[i] < '0' || name[i] > '9') { all_digits = false; break; } bit_count = bit_count * 10 + (uint32_t)(name[i] - '0'); } if (all_digits) { if (name_len == 2 && name[0] == 'i' && name[1] == '0') failNode( ag, ident, "signed integer cannot have bit width 0"); if (name[1] == '0') ASTGEN_UNIMPLEMENTED( "identifier: integer type name with a leading zero"); if (bit_count > 65535) failNode(ag, ident, "primitive integer type '%.*s' exceeds maximum bit " "width of 65535", (int)name_len, name); ZirInstData data; memset(&data, 0, sizeof(data)); data.int_type.src_node = gzNodeIndexToRelative(gz, ident); data.int_type.signedness = signedness; data.int_type.bit_count = (uint16_t)bit_count; const ZirInstRef result = gzAdd(gz, ZIR_INST_INT_TYPE, data); return rvalue(gz, ri, result, ident); } } } // Local variables, including function parameters, and container-level // declarations. if (force_comptime) { // Wrap the reference in a `block_comptime` (mirrors comptimeExpr's // tail). const uint32_t block_inst = gzMakeBlockInst(gz, ZIR_INST_BLOCK_COMPTIME, fc_src_node); GenZir comptime_gz = gzMakeSubBlock(gz, scope); comptime_gz.is_comptime = true; ResultInfo sub_ri = { { RL_NONE, 0, 0, 0, NULL, 0, 0 }, ri.ctx }; const ZirInstRef block_result = localVarRef(&comptime_gz, scope, sub_ri, ident, ident_token); assert(!gzEndsWithNoReturn(&comptime_gz)); gzAddBreak( &comptime_gz, ZIR_INST_BREAK_INLINE, block_inst, block_result); gzSetBlockComptimeBody(&comptime_gz, block_inst, fc_reason); gzStackPush(ag, block_inst); return rvalue(gz, ri, block_inst + ZIR_REF_START_INDEX, fc_src_node); } return localVarRef(gz, scope, ri, ident, ident_token); } // AstGen.zig:identifier static ZirInstRef identifier( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex ident) { return identifierInner(gz, scope, ri, ident, false, 0, 0); } typedef enum { SIGN_POSITIVE, SIGN_NEGATIVE } Sign; // AstGen.zig:charLiteral // A char literal ('x') as a ZIR int, by std.zig.parseCharLiteral. static ZirInstRef charLiteral(GenZir* gz, ResultInfo ri, AstNodeIndex node) { const AstGen* ag = gz->astgen; const AstTokenIndex tok = astNodeMainToken(ag->tree, node); const char* slice; uint32_t slice_len; astTokenSlice(ag->tree, tok, &slice, &slice_len); uint32_t codepoint; uint32_t err_offset; if (!stringLiteralParseCharLiteral( slice, slice_len, &codepoint, &err_offset)) failOff(ag, tok, err_offset, "invalid character literal"); const ZirInstRef result = gzAddInt(gz, (uint64_t)codepoint); return rvalue(gz, ri, result, node); } // AstGen.zig:failWithNumberError ZIG1_ATTRIBUTE((noreturn)) // AstGen.zig:failWithNumberError ZIG1_ATTRIBUTE((noreturn)) static void failWithNumberError(const AstGen* ag, NumberLiteralResult lit, AstTokenIndex token, const char* bytes, uint32_t len) { const uint32_t i = lit.failure_index; switch (lit.failure) { case NUMBER_LITERAL_ERROR_LEADING_ZERO: failTok(ag, token, "number '%.*s' has leading zero", (int)len, bytes); case NUMBER_LITERAL_ERROR_DIGIT_AFTER_BASE: failTok(ag, token, "expected a digit after base prefix"); case NUMBER_LITERAL_ERROR_UPPER_CASE_BASE: failOff(ag, token, i, "base prefix must be lowercase"); case NUMBER_LITERAL_ERROR_INVALID_FLOAT_BASE: failOff(ag, token, i, "invalid base for float literal"); case NUMBER_LITERAL_ERROR_REPEATED_UNDERSCORE: failOff(ag, token, i, "repeated digit separator"); case NUMBER_LITERAL_ERROR_INVALID_UNDERSCORE_AFTER_SPECIAL: failOff(ag, token, i, "expected digit before digit separator"); case NUMBER_LITERAL_ERROR_INVALID_DIGIT: failOff(ag, token, i, "invalid digit '%c' for %s base", bytes[i], lit.base == 2 ? "binary" : lit.base == 8 ? "octal" : lit.base == 16 ? "hex" : "decimal"); case NUMBER_LITERAL_ERROR_INVALID_DIGIT_EXPONENT: failOff(ag, token, i, "invalid digit '%c' in exponent", bytes[i]); case NUMBER_LITERAL_ERROR_DUPLICATE_EXPONENT: failOff(ag, token, i, "duplicate exponent"); case NUMBER_LITERAL_ERROR_EXPONENT_AFTER_UNDERSCORE: failOff(ag, token, i, "expected digit before exponent"); case NUMBER_LITERAL_ERROR_SPECIAL_AFTER_UNDERSCORE: failOff(ag, token, i, "expected digit before '%c'", bytes[i]); case NUMBER_LITERAL_ERROR_TRAILING_SPECIAL: failOff(ag, token, i, "expected digit after '%c'", bytes[i - 1]); case NUMBER_LITERAL_ERROR_TRAILING_UNDERSCORE: failOff(ag, token, i, "trailing digit separator"); case NUMBER_LITERAL_ERROR_DUPLICATE_PERIOD: // Validated by tokenizer case NUMBER_LITERAL_ERROR_INVALID_CHARACTER: // Validated by tokenizer UNREACHABLE("failWithNumberError: tokenizer-validated failure"); case NUMBER_LITERAL_ERROR_INVALID_EXPONENT_SIGN: failOff(ag, token, i, "sign '%c' cannot follow digit '%c' in hex base", bytes[i], bytes[i - 1]); case NUMBER_LITERAL_ERROR_PERIOD_AFTER_EXPONENT: failOff(ag, token, i, "unexpected period after exponent"); } UNREACHABLE("failWithNumberError"); } // AstGen.zig:numberLiteral static ZirInstRef numberLiteral(GenZir* gz, ResultInfo ri, AstNodeIndex node, AstNodeIndex source_node, Sign sign) { const AstGen* ag = gz->astgen; const AstTokenIndex num_token = astNodeMainToken(ag->tree, node); const char* bytes; uint32_t bytes_len; astTokenSlice(ag->tree, num_token, &bytes, &bytes_len); ZirInstRef result; const NumberLiteralResult lit = parseNumberLiteral(bytes, bytes_len); if (lit.tag == NUMBER_LITERAL_FAILURE) failWithNumberError(ag, lit, num_token, bytes, bytes_len); if (lit.tag == NUMBER_LITERAL_INT) { if (lit.int_val == 0) { if (sign == SIGN_NEGATIVE) failTok(ag, num_token, "integer literal '-0' is ambiguous"); result = ZIR_REF_ZERO; } else if (lit.int_val == 1) { // Handle the negation here! return rvalue(gz, ri, sign == SIGN_POSITIVE ? ZIR_REF_ONE : ZIR_REF_NEGATIVE_ONE, source_node); } else { result = gzAddInt(gz, lit.int_val); } } else if (lit.tag == NUMBER_LITERAL_BIG_INT) { uint64_t limbs[64]; const uint32_t prefix_offset = lit.base == 10 ? 0 : 2; const uint32_t limbs_len = bigIntSetString(lit.base, bytes + prefix_offset, bytes_len - prefix_offset, limbs, 64); result = gzAddIntBig(gz, limbs, limbs_len); } else { CrtU128 float_number = parseFloatF128(bytes, bytes_len); if (sign == SIGN_NEGATIVE) float_number = u128Xor(float_number, u128Bit(127)); // If the value fits into a f64 without losing any precision, store // it that way. const CrtU128 smaller_float = crtTruncf(64, 128, float_number); const CrtU128 bigger_again = crtExtendf(128, 64, smaller_float); if (u128Cmp(bigger_again, float_number) == 0) { double d; memcpy(&d, &smaller_float.lo, sizeof(d)); return rvalue(gz, ri, gzAddFloat(gz, d), source_node); } // We need to use 128 bits. Break the float into 4 u32 values so we // can put it into the `extra` array. const uint32_t pieces[4] = { (uint32_t)float_number.lo, (uint32_t)(float_number.lo >> 32), (uint32_t)float_number.hi, (uint32_t)(float_number.hi >> 32) }; return rvalue(gz, ri, gzAddFloat128(gz, node, pieces), source_node); } if (sign == SIGN_POSITIVE) return rvalue(gz, ri, result, source_node); const ZirInstRef negated = gzAddUnNode(gz, ZIR_INST_NEGATE, result, source_node); return rvalue(gz, ri, negated, source_node); } static ZirInstRef hasDeclOrField(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex lhs_node, AstNodeIndex rhs_node, ZirInstTag tag); static ZirInstRef typeCast(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex operand_node, ZirInstTag tag); static ZirInstRef simpleUnOpType(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex operand_node, ZirInstTag tag); static ZirInstRef simpleUnOp(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, ResultInfo operand_ri, AstNodeIndex operand_node, ZirInstTag tag); static ZirInstRef floatRoundOp(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex operand_node, ZirInstTag float_tag); static ZirInstRef floatUnOp(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex operand_node, ZirInstTag tag); static ZirInstRef cmpxchg(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeSlice params, uint16_t small); static ZirInstRef bitBuiltin(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex operand_node, ZirInstTag tag); static ZirInstRef divBuiltin(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex lhs_node, AstNodeIndex rhs_node, ZirInstTag tag); static ZirInstRef offsetOf(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex lhs_node, AstNodeIndex rhs_node, ZirInstTag tag); static ZirInstRef shiftOp(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex lhs_node, AstNodeIndex rhs_node, ZirInstTag tag); static ZirInstRef overflowArithmetic(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeSlice params, uint16_t tag); static void importsPut(AstGen* ag, uint32_t name, AstTokenIndex token); // AstGen.zig:ResultInfo.Loc.resultTypeForCast static ZirInstRef resultTypeForCast( GenZir* gz, ResultInfo ri, AstNodeIndex node) { const ZirInstRef ty = resultLocType(gz, ri, node); if (ty == ZIR_REF_NONE) failNoResultType(gz->astgen, node); return ty; } // AstGen.zig:as static ZirInstRef as(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex lhs, AstNodeIndex rhs) { const ZirInstRef dest_type = typeExpr(gz, scope, lhs); const ResultInfo ty_ri = { { RL_TY, dest_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef result = reachableExpr(gz, scope, ty_ri, rhs, node); return rvalue(gz, ri, result, node); } // AstGen.zig:unionInit static ZirInstRef unionInit(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeSlice params) { const ZirInstRef union_type = typeExpr(gz, scope, params.ptr[0]); const ZirInstRef field_name = comptimeExpr( gz, scope, slice_const_u8_ri, params.ptr[1], REASON_UNION_FIELD_NAMES); const ZirInstRef field_type = gzAddPlNodeBin( gz, ZIR_INST_FIELD_TYPE_REF, node, union_type, field_name); const ResultInfo init_ri = { { RL_TY, field_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef init = reachableExpr(gz, scope, init_ri, params.ptr[2], node); // Zir.Inst.UnionInit. const uint32_t words[3] = { union_type, field_name, init }; const ZirInstRef result = gzAddPlNodeExtra(gz, ZIR_INST_UNION_INIT, node, words, 3); return rvalue(gz, ri, result, node); } // AstGen.zig:bitCast static ZirInstRef bitCast(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex operand_node) { const LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef dest_type = resultTypeForCast(gz, ri, node); const ZirInstRef operand = reachableExpr(gz, scope, none_ri, operand_node, node); emitDbgStmt(gz, cursor.line, cursor.column); const ZirInstRef result = gzAddPlNodeBin(gz, ZIR_INST_BITCAST, node, dest_type, operand); return rvalue(gz, ri, result, node); } // AstGen.zig:ptrCast // Handle one or more nested pointer cast builtins (@ptrCast, @alignCast, // @addrSpaceCast, @constCast, @volatileCast) as a single operation, so that // `@ptrCast(@alignCast(ptr))` works although the intermediate result type is // unknown. static ZirInstRef ptrCast( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex root_node) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; // Zir.Inst.FullPtrCastFlags. uint16_t flags = 0; // Note that all pointer cast builtins have one parameter, so we only need // to handle `builtin_call_two`. AstNodeIndex node = root_node; while (true) { switch (astNodeTag(tree, node)) { case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: break; case AST_NODE_GROUPED_EXPRESSION: // Handle the chaining even with redundant parentheses node = astNodeData(tree, node).lhs; continue; default: goto done; } AstNodeIndex buf[2]; AstNodeSlice args; astBuiltinCallParams(tree, buf, node, &args); assert(args.len <= 2); if (args.len == 0) break; // 0 args const char* builtin_name; uint32_t builtin_name_len; astTokenSlice(tree, astNodeMainToken(tree, node), &builtin_name, &builtin_name_len); const BuiltinFn* info = builtinFnListGet(builtin_name, builtin_name_len); if (info == NULL) break; if (args.len == 1) { if (info->param_count != 1) break; uint16_t flag; switch (info->tag) { case BFN_ptr_cast: flag = ZIR_PTR_CAST_FLAG_PTR_CAST; break; case BFN_align_cast: flag = ZIR_PTR_CAST_FLAG_ALIGN_CAST; break; case BFN_addrspace_cast: flag = ZIR_PTR_CAST_FLAG_ADDRSPACE_CAST; break; case BFN_const_cast: flag = ZIR_PTR_CAST_FLAG_CONST_CAST; break; case BFN_volatile_cast: flag = ZIR_PTR_CAST_FLAG_VOLATILE_CAST; break; default: goto done; } if ((flags & flag) != 0) failNode(ag, node, "redundant %.*s", (int)builtin_name_len, builtin_name); flags |= flag; node = args.ptr[0]; } else { assert(args.len == 2); if (info->param_count != 2) break; if (info->tag != BFN_field_parent_ptr) break; if ((flags & ZIR_PTR_CAST_FLAG_PTR_CAST) != 0) break; const LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, root_node); const ZirInstRef parent_ptr_type = resultTypeForCast(gz, ri, root_node); const ZirInstRef field_name = comptimeExpr( gz, scope, slice_const_u8_ri, args.ptr[0], REASON_FIELD_NAME); const ZirInstRef field_ptr = expr(gz, scope, none_ri, args.ptr[1]); emitDbgStmt(gz, cursor.line, cursor.column); // Zir.Inst.FieldParentPtr. const uint32_t words[4] = { (uint32_t)gzNodeIndexToRelative(gz, node), parent_ptr_type, field_name, field_ptr }; const ZirInstRef result = gzAddExtendedPayloadSmall( gz, ZIR_EXT_FIELD_PARENT_PTR, flags, words, 4); return rvalue(gz, ri, result, root_node); } } done: assert(flags != 0); if (flags == ZIR_PTR_CAST_FLAG_PTR_CAST) { // Special case: simpler representation return typeCast(gz, scope, ri, root_node, node, ZIR_INST_PTR_CAST); } const uint16_t no_result_ty_flags = ZIR_PTR_CAST_FLAG_CONST_CAST | ZIR_PTR_CAST_FLAG_VOLATILE_CAST; if ((flags & ~no_result_ty_flags) == 0) { // Result type not needed const LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, root_node); const ZirInstRef operand = expr(gz, scope, none_ri, node); emitDbgStmt(gz, cursor.line, cursor.column); const uint32_t words[2] = { (uint32_t)gzNodeIndexToRelative(gz, root_node), operand }; const ZirInstRef result = gzAddExtendedPayloadSmall( gz, ZIR_EXT_PTR_CAST_NO_DEST, flags, words, 2); return rvalue(gz, ri, result, root_node); } // Full cast including result type const LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, root_node); const ZirInstRef result_type = resultTypeForCast(gz, ri, root_node); const ZirInstRef operand = expr(gz, scope, none_ri, node); emitDbgStmt(gz, cursor.line, cursor.column); const uint32_t words[3] = { (uint32_t)gzNodeIndexToRelative(gz, root_node), result_type, operand }; const ZirInstRef result = gzAddExtendedPayloadSmall( gz, ZIR_EXT_PTR_CAST_FULL, flags, words, 3); return rvalue(gz, ri, result, root_node); } // AstGen.zig:typeOf static ZirInstRef typeOf(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeSlice args) { AstGen* ag = gz->astgen; if (args.len < 1) failNode(ag, node, "expected at least 1 argument, found 0"); if (args.len == 1) { const uint32_t typeof_inst = gzMakeBlockInst(gz, ZIR_INST_TYPEOF_BUILTIN, node); GenZir typeof_scope = gzMakeSubBlock(gz, scope); typeof_scope.is_comptime = false; typeof_scope.is_typeof = true; const ZirInstRef ty_expr = reachableExpr( &typeof_scope, &typeof_scope.base, none_ri, args.ptr[0], node); if (!refIsNoReturn(gz, ty_expr)) gzAddBreak( &typeof_scope, ZIR_INST_BREAK_INLINE, typeof_inst, ty_expr); gzSetBlockBody(&typeof_scope, typeof_inst); // typeof_scope unstacked now, can add new instructions to gz gzStackPush(ag, typeof_inst); return rvalue(gz, ri, typeof_inst + ZIR_REF_START_INDEX, node); } // Zir.Inst.TypeOfPeer: { src_node, body_len, body_index }. const uint32_t payload_size = 3; const uint32_t payload_index = reserveExtra(ag, payload_size + args.len); const uint32_t args_index = payload_index + payload_size; const ZirInstRef typeof_inst = gzAddExtendedMultiOpPayloadIndex( gz, ZIR_EXT_TYPEOF_PEER, payload_index, args.len); GenZir typeof_scope = gzMakeSubBlock(gz, scope); typeof_scope.is_comptime = false; for (uint32_t i = 0; i < args.len; i++) { const ZirInstRef param_ref = reachableExpr( &typeof_scope, &typeof_scope.base, none_ri, args.ptr[i], node); ag->extra[args_index + i] = param_ref; } gzAddBreak(&typeof_scope, ZIR_INST_BREAK_INLINE, typeof_inst - ZIR_REF_START_INDEX, ZIR_REF_VOID_VALUE); uint32_t body_insts; const uint32_t* body = gzInstructionsSlice(&typeof_scope, &body_insts); const uint32_t body_len = countBodyLenAfterFixups(ag, body, body_insts); ag->extra[payload_index + 0] = (uint32_t)gzNodeIndexToRelative(gz, node); ag->extra[payload_index + 1] = body_len; ag->extra[payload_index + 2] = ag->extra_len; // body_index appendBodyWithFixups(ag, body, body_insts); gzUnstack(&typeof_scope); return rvalue(gz, ri, typeof_inst, node); } // AstGen.zig:minMax static ZirInstRef minMax(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeSlice args, bool is_max) { AstGen* ag = gz->astgen; if (args.len < 2) failNode( ag, node, "expected at least 2 arguments, found %u", args.len); if (args.len == 2) { const ZirInstTag tag = is_max ? ZIR_INST_MAX : ZIR_INST_MIN; const ZirInstRef a = expr(gz, scope, none_ri, args.ptr[0]); const ZirInstRef b = expr(gz, scope, none_ri, args.ptr[1]); const ZirInstRef result = gzAddPlNodeBin(gz, tag, node, a, b); return rvalue(gz, ri, result, node); } // Zir.Inst.NodeMultiOp. const uint32_t payload_index = ag->extra_len; extraAppend(ag, (uint32_t)gzNodeIndexToRelative(gz, node)); uint32_t extra_index = reserveExtra(ag, args.len); for (uint32_t i = 0; i < args.len; i++) { const ZirInstRef arg_ref = expr(gz, scope, none_ri, args.ptr[i]); ag->extra[extra_index] = arg_ref; extra_index += 1; } const uint16_t tag = is_max ? ZIR_EXT_MAX_MULTI : ZIR_EXT_MIN_MULTI; const ZirInstRef result = gzAddExtendedMultiOpPayloadIndex(gz, tag, payload_index, args.len); return rvalue(gz, ri, result, node); } // AstGen.zig:builtinCall static ZirInstRef builtinCall(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeSlice params, bool allow_branch_hint, uint8_t reify_name_strat) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; const AstTokenIndex builtin_token = astNodeMainToken(tree, node); const char* builtin_name; uint32_t builtin_name_len; astTokenSlice(tree, builtin_token, &builtin_name, &builtin_name_len); // We handle the different builtins manually because they have different // semantics depending on the function. For example, `@as` and others // participate in result location semantics. Also, some builtins have a // variable number of parameters. const BuiltinFn* info = builtinFnListGet(builtin_name, builtin_name_len); if (info == NULL) failNode(ag, node, "invalid builtin function: '%.*s'", (int)builtin_name_len, builtin_name); if (info->param_count >= 0 && (uint32_t)info->param_count != params.len) failParamCount(ag, node, (uint32_t)info->param_count, params.len); // Check function scope-only builtins if (ag->fn_block == NULL && info->illegal_outside_function) failNode(ag, node, "'%.*s' outside function scope", (int)builtin_name_len, builtin_name); const AstNodeIndex* p = params.ptr; switch (info->tag) { case BFN_branch_hint: { if (!allow_branch_hint) failNode(ag, node, "'@branchHint' must appear as the first statement in a " "function or conditional branch"); const ZirInstRef hint_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_BRANCH_HINT); const ResultInfo hint_ri = { { RL_COERCED_TY, hint_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef hint_val = comptimeExpr( gz, scope, hint_ri, p[0], REASON_OPERAND_BRANCH_HINT); const uint32_t words[2] = { (uint32_t)gzNodeIndexToRelative(gz, node), hint_val }; gzAddExtendedPayload(gz, ZIR_EXT_BRANCH_HINT, words, 2); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); } case BFN_import: { const AstNodeIndex operand_node = p[0]; if (astNodeTag(tree, operand_node) != AST_NODE_STRING_LITERAL) { // Spec reference: https://github.com/ziglang/zig/issues/2206 failNode( ag, operand_node, "@import operand must be a string literal"); } const AstTokenIndex str_lit_token = astNodeMainToken(tree, operand_node); uint32_t str_len; const uint32_t str_index = strLitAsString(ag, str_lit_token, &str_len); if (memchr(ag->string_bytes.items + str_index, 0, str_len) != NULL) failTok( ag, str_lit_token, "import path cannot contain null bytes"); else if (str_len == 0) failTok(ag, str_lit_token, "import path cannot be empty"); const ZirInstRef res_ty = resultLocType(gz, ri, node); // Zir.Inst.Import. const uint32_t payload_index = ag->extra_len; extraAppend(ag, res_ty); extraAppend(ag, str_index); ZirInstData data; memset(&data, 0, sizeof(data)); data.pl_tok.src_tok = gzTokenIndexToRelative(gz, str_lit_token); data.pl_tok.payload_index = payload_index; const ZirInstRef result = gzAdd(gz, ZIR_INST_IMPORT, data); importsPut(ag, str_index, str_lit_token); return rvalue(gz, ri, result, node); } case BFN_compile_log: { // Zir.Inst.NodeMultiOp. const uint32_t payload_index = ag->extra_len; extraAppend(ag, (uint32_t)gzNodeIndexToRelative(gz, node)); uint32_t extra_index = reserveExtra(ag, params.len); for (uint32_t i = 0; i < params.len; i++) { const ZirInstRef param_ref = expr(gz, scope, none_ri, p[i]); ag->extra[extra_index] = param_ref; extra_index += 1; } const ZirInstRef result = gzAddExtendedMultiOpPayloadIndex( gz, ZIR_EXT_COMPILE_LOG, payload_index, params.len); return rvalue(gz, ri, result, node); } case BFN_field: { switch (ri.rl.tag) { case RL_REF: case RL_REF_COERCED_TY: { const ZirInstRef lhs = expr(gz, scope, ref_ri, p[0]); const ZirInstRef field_name = comptimeExpr( gz, scope, slice_const_u8_ri, p[1], REASON_FIELD_NAME); return gzAddPlNodeBin( gz, ZIR_INST_FIELD_PTR_NAMED, node, lhs, field_name); } case RL_REF_CONST: { const ZirInstRef lhs = expr(gz, scope, ref_const_ri, p[0]); const ZirInstRef field_name = comptimeExpr( gz, scope, slice_const_u8_ri, p[1], REASON_FIELD_NAME); return gzAddPlNodeBin( gz, ZIR_INST_FIELD_PTR_NAMED, node, lhs, field_name); } default: { const ZirInstRef lhs = expr(gz, scope, ref_const_ri, p[0]); const ZirInstRef field_name = comptimeExpr( gz, scope, slice_const_u8_ri, p[1], REASON_FIELD_NAME); const ZirInstRef result = gzAddPlNodeBin( gz, ZIR_INST_FIELD_PTR_NAMED_LOAD, node, lhs, field_name); return rvalue(gz, ri, result, node); } } } case BFN_FieldType: { const ZirInstRef ty_inst = typeExpr(gz, scope, p[0]); const ZirInstRef name_inst = comptimeExpr( gz, scope, slice_const_u8_ri, p[1], REASON_FIELD_NAME); const ZirInstRef result = gzAddPlNodeBin( gz, ZIR_INST_FIELD_TYPE_REF, node, ty_inst, name_inst); return rvalue(gz, ri, result, node); } case BFN_as: return as(gz, scope, ri, node, p[0], p[1]); case BFN_bit_cast: return bitCast(gz, scope, ri, node, p[0]); case BFN_TypeOf: return typeOf(gz, scope, ri, node, params); case BFN_union_init: return unionInit(gz, scope, ri, node, params); case BFN_min: return minMax(gz, scope, ri, node, params, false); case BFN_max: return minMax(gz, scope, ri, node, params, true); case BFN_export: { const ZirInstRef exported = expr(gz, scope, none_ri, p[0]); const ZirInstRef export_options_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_EXPORT_OPTIONS); const ResultInfo options_ri = { { RL_COERCED_TY, export_options_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef options = comptimeExpr(gz, scope, options_ri, p[1], REASON_EXPORT_OPTIONS); gzAddPlNodeBin(gz, ZIR_INST_EXPORT, node, exported, options); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); } case BFN_extern: { const ZirInstRef type_inst = typeExpr(gz, scope, p[0]); const ZirInstRef extern_options_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_EXTERN_OPTIONS); const ResultInfo options_ri = { { RL_COERCED_TY, extern_options_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef options = comptimeExpr(gz, scope, options_ri, p[1], REASON_EXTERN_OPTIONS); const uint32_t words[3] = { (uint32_t)gzNodeIndexToRelative(gz, node), type_inst, options }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_BUILTIN_EXTERN, words, 3); return rvalue(gz, ri, result, node); } case BFN_set_float_mode: { const ZirInstRef float_mode_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_FLOAT_MODE); const ResultInfo order_ri = { { RL_COERCED_TY, float_mode_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef order = expr(gz, scope, order_ri, p[0]); const uint32_t words[2] = { (uint32_t)gzNodeIndexToRelative(gz, node), order }; gzAddExtendedPayload(gz, ZIR_EXT_SET_FLOAT_MODE, words, 2); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); } case BFN_src: { // Upstream also feeds the source location to the source hash, which // zig1 leaves zero. const uint32_t node_start = astTokenStart(tree, astFirstToken(tree, node)); advanceSourceCursor(ag, node_start); const uint32_t words[3] = { (uint32_t)gzNodeIndexToRelative(gz, node), ag->source_line, ag->source_column }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_BUILTIN_SRC, words, 3); return rvalue(gz, ri, result, node); } case BFN_This: return rvalue(gz, ri, gzAddNodeExtended(gz, ZIR_EXT_THIS, node), node); case BFN_return_address: return rvalue( gz, ri, gzAddNodeExtended(gz, ZIR_EXT_RET_ADDR, node), node); case BFN_error_return_trace: return rvalue(gz, ri, gzAddNodeExtended(gz, ZIR_EXT_ERROR_RETURN_TRACE, node), node); case BFN_frame: return rvalue( gz, ri, gzAddNodeExtended(gz, ZIR_EXT_FRAME, node), node); case BFN_frame_address: return rvalue( gz, ri, gzAddNodeExtended(gz, ZIR_EXT_FRAME_ADDRESS, node), node); case BFN_breakpoint: return rvalue( gz, ri, gzAddNodeExtended(gz, ZIR_EXT_BREAKPOINT, node), node); case BFN_disable_instrumentation: return rvalue(gz, ri, gzAddNodeExtended(gz, ZIR_EXT_DISABLE_INSTRUMENTATION, node), node); case BFN_disable_intrinsics: return rvalue(gz, ri, gzAddNodeExtended(gz, ZIR_EXT_DISABLE_INTRINSICS, node), node); case BFN_type_info: return simpleUnOpType(gz, scope, ri, node, p[0], ZIR_INST_TYPE_INFO); case BFN_size_of: return simpleUnOpType(gz, scope, ri, node, p[0], ZIR_INST_SIZE_OF); case BFN_bit_size_of: return simpleUnOpType(gz, scope, ri, node, p[0], ZIR_INST_BIT_SIZE_OF); case BFN_align_of: return simpleUnOpType(gz, scope, ri, node, p[0], ZIR_INST_ALIGN_OF); case BFN_int_from_ptr: return simpleUnOp( gz, scope, ri, node, none_ri, p[0], ZIR_INST_INT_FROM_PTR); case BFN_compile_error: return simpleUnOp(gz, scope, ri, node, slice_const_u8_ri, p[0], ZIR_INST_COMPILE_ERROR); case BFN_set_eval_branch_quota: { const ResultInfo u32_ri = { { RL_COERCED_TY, ZIR_REF_U32_TYPE, 0, 0, NULL, 0, 0 }, CTX_NONE }; return simpleUnOp( gz, scope, ri, node, u32_ri, p[0], ZIR_INST_SET_EVAL_BRANCH_QUOTA); } case BFN_int_from_enum: return simpleUnOp( gz, scope, ri, node, none_ri, p[0], ZIR_INST_INT_FROM_ENUM); case BFN_int_from_bool: return simpleUnOp( gz, scope, ri, node, none_ri, p[0], ZIR_INST_INT_FROM_BOOL); case BFN_backing_int: return simpleUnOp( gz, scope, ri, node, none_ri, p[0], ZIR_INST_BACKING_INT); case BFN_embed_file: return simpleUnOp( gz, scope, ri, node, slice_const_u8_ri, p[0], ZIR_INST_EMBED_FILE); case BFN_error_name: { const ResultInfo anyerror_ri = { { RL_COERCED_TY, ZIR_REF_ANYERROR_TYPE, 0, 0, NULL, 0, 0 }, CTX_NONE }; return simpleUnOp( gz, scope, ri, node, anyerror_ri, p[0], ZIR_INST_ERROR_NAME); } case BFN_set_runtime_safety: return simpleUnOp(gz, scope, ri, node, coerced_bool_ri, p[0], ZIR_INST_SET_RUNTIME_SAFETY); case BFN_abs: return simpleUnOp(gz, scope, ri, node, none_ri, p[0], ZIR_INST_ABS); case BFN_tag_name: return simpleUnOp( gz, scope, ri, node, none_ri, p[0], ZIR_INST_TAG_NAME); case BFN_type_name: return simpleUnOp( gz, scope, ri, node, none_ri, p[0], ZIR_INST_TYPE_NAME); case BFN_Frame: return simpleUnOp( gz, scope, ri, node, none_ri, p[0], ZIR_INST_FRAME_TYPE); case BFN_sqrt: return floatUnOp(gz, scope, ri, node, p[0], ZIR_INST_SQRT); case BFN_sin: return floatUnOp(gz, scope, ri, node, p[0], ZIR_INST_SIN); case BFN_cos: return floatUnOp(gz, scope, ri, node, p[0], ZIR_INST_COS); case BFN_tan: return floatUnOp(gz, scope, ri, node, p[0], ZIR_INST_TAN); case BFN_exp: return floatUnOp(gz, scope, ri, node, p[0], ZIR_INST_EXP); case BFN_exp2: return floatUnOp(gz, scope, ri, node, p[0], ZIR_INST_EXP2); case BFN_log: return floatUnOp(gz, scope, ri, node, p[0], ZIR_INST_LOG); case BFN_log2: return floatUnOp(gz, scope, ri, node, p[0], ZIR_INST_LOG2); case BFN_log10: return floatUnOp(gz, scope, ri, node, p[0], ZIR_INST_LOG10); case BFN_floor: return floatRoundOp(gz, scope, ri, node, p[0], ZIR_INST_FLOOR); case BFN_ceil: return floatRoundOp(gz, scope, ri, node, p[0], ZIR_INST_CEIL); case BFN_trunc: return floatRoundOp(gz, scope, ri, node, p[0], ZIR_INST_TRUNC); case BFN_round: return floatRoundOp(gz, scope, ri, node, p[0], ZIR_INST_ROUND); case BFN_int_from_float: return typeCast(gz, scope, ri, node, p[0], ZIR_INST_INT_FROM_FLOAT); case BFN_float_from_int: return typeCast(gz, scope, ri, node, p[0], ZIR_INST_FLOAT_FROM_INT); case BFN_ptr_from_int: return typeCast(gz, scope, ri, node, p[0], ZIR_INST_PTR_FROM_INT); case BFN_enum_from_int: return typeCast(gz, scope, ri, node, p[0], ZIR_INST_ENUM_FROM_INT); case BFN_float_cast: return typeCast(gz, scope, ri, node, p[0], ZIR_INST_FLOAT_CAST); case BFN_int_cast: return typeCast(gz, scope, ri, node, p[0], ZIR_INST_INT_CAST); case BFN_truncate: return typeCast(gz, scope, ri, node, p[0], ZIR_INST_TRUNCATE); case BFN_from_backing_int: { const LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef result_ty = resultTypeForCast(gz, ri, node); const ZirInstRef backing_int_ty = gzAddUnNode( gz, ZIR_INST_FROM_BACKING_INT_ARG_TY, result_ty, node); const ResultInfo operand_ri = { { RL_COERCED_TY, backing_int_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef operand = expr(gz, scope, operand_ri, p[0]); emitDbgStmt(gz, cursor.line, cursor.column); const ZirInstRef result = gzAddPlNodeBin( gz, ZIR_INST_FROM_BACKING_INT, node, result_ty, operand); return rvalue(gz, ri, result, node); } case BFN_in_comptime: if (gz->is_comptime) failNode(ag, node, "redundant '@inComptime' in comptime scope"); return rvalue( gz, ri, gzAddNodeExtended(gz, ZIR_EXT_IN_COMPTIME, node), node); case BFN_EnumLiteral: return rvalue(gz, ri, ZIR_REF_ENUM_LITERAL_TYPE, node); case BFN_Int: { const ZirInstRef signedness_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_SIGNEDNESS); const ResultInfo signedness_ri = { { RL_COERCED_TY, signedness_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ResultInfo u16_ri = { { RL_COERCED_TY, ZIR_REF_U16_TYPE, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef lhs = comptimeExpr( gz, scope, signedness_ri, p[0], REASON_INT_SIGNEDNESS); const ZirInstRef rhs = comptimeExpr(gz, scope, u16_ri, p[1], REASON_INT_BIT_WIDTH); const ZirInstRef result = gzAddPlNodeBin(gz, ZIR_INST_REIFY_INT, node, lhs, rhs); return rvalue(gz, ri, result, node); } case BFN_Tuple: { const ResultInfo types_ri = { { RL_COERCED_TY, ZIR_REF_SLICE_CONST_TYPE_TYPE, 0, 0, NULL, 0, 0 }, CTX_NONE }; const uint32_t node_rel = (uint32_t)gzNodeIndexToRelative(gz, node); const uint32_t words[2] = { node_rel, comptimeExpr( gz, scope, types_ri, p[0], REASON_TUPLE_FIELD_TYPES) }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_REIFY_TUPLE, words, 2); return rvalue(gz, ri, result, node); } case BFN_Pointer: { const ZirInstRef ptr_size_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_POINTER_SIZE); const ZirInstRef ptr_attrs_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_POINTER_ATTRIBUTES); const ResultInfo size_ri = { { RL_COERCED_TY, ptr_size_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ResultInfo attrs_ri = { { RL_COERCED_TY, ptr_attrs_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef size = comptimeExpr(gz, scope, size_ri, p[0], REASON_POINTER_SIZE); const ZirInstRef attrs = comptimeExpr(gz, scope, attrs_ri, p[1], REASON_POINTER_ATTRS); const ZirInstRef elem_ty = typeExpr(gz, scope, p[2]); const uint32_t sentinel_ty_words[2] = { (uint32_t)gzNodeIndexToRelative(gz, p[2]), elem_ty }; const ZirInstRef sentinel_ty = gzAddExtendedPayload( gz, ZIR_EXT_REIFY_POINTER_SENTINEL_TY, sentinel_ty_words, 2); const ResultInfo sentinel_ri = { { RL_COERCED_TY, sentinel_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef sentinel = comptimeExpr( gz, scope, sentinel_ri, p[3], REASON_POINTER_SENTINEL); // Zir.Inst.ReifyPointer. const uint32_t words[5] = { (uint32_t)gzNodeIndexToRelative(gz, node), size, attrs, elem_ty, sentinel }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_REIFY_POINTER, words, 5); return rvalue(gz, ri, result, node); } case BFN_Fn: { const ZirInstRef fn_attrs_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_FN_ATTRIBUTES); const ResultInfo types_ri = { { RL_COERCED_TY, ZIR_REF_SLICE_CONST_TYPE_TYPE, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef param_types = comptimeExpr(gz, scope, types_ri, p[0], REASON_FN_PARAM_TYPES); const uint32_t attrs_ty_words[2] = { (uint32_t)gzNodeIndexToRelative(gz, p[0]), param_types }; const ZirInstRef param_attrs_ty = gzAddExtendedPayloadSmall(gz, ZIR_EXT_REIFY_SLICE_ARG_TY, REIFY_SLICE_ARG_TYPE_TO_FN_PARAM_ATTRS, attrs_ty_words, 2); const ResultInfo param_attrs_ri = { { RL_COERCED_TY, param_attrs_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef param_attrs = comptimeExpr( gz, scope, param_attrs_ri, p[1], REASON_FN_PARAM_ATTRS); const ZirInstRef ret_ty = comptimeExpr(gz, scope, coerced_type_ri, p[2], REASON_FN_RET_TY); const ResultInfo fn_attrs_ri = { { RL_COERCED_TY, fn_attrs_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef fn_attrs = comptimeExpr(gz, scope, fn_attrs_ri, p[3], REASON_FN_ATTRS); // Zir.Inst.ReifyFn. const uint32_t words[5] = { (uint32_t)gzNodeIndexToRelative(gz, node), param_types, param_attrs, ret_ty, fn_attrs }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_REIFY_FN, words, 5); return rvalue(gz, ri, result, node); } case BFN_Struct: case BFN_Union: { const bool is_struct = info->tag == BFN_Struct; const ZirInstRef container_layout_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_CONTAINER_LAYOUT); const ResultInfo layout_ri = { { RL_COERCED_TY, container_layout_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef layout = comptimeExpr(gz, scope, layout_ri, p[0], is_struct ? REASON_STRUCT_LAYOUT : REASON_UNION_LAYOUT); const ResultInfo optional_type_ri = { { RL_COERCED_TY, ZIR_REF_OPTIONAL_TYPE_TYPE, 0, 0, NULL, 0, 0 }, CTX_NONE }; // `backing_ty` for @Struct, `arg_ty` for @Union. const ZirInstRef arg_ty = comptimeExpr(gz, scope, optional_type_ri, p[1], REASON_TYPE); const ResultInfo names_ri = { { RL_COERCED_TY, ZIR_REF_SLICE_CONST_SLICE_CONST_U8_TYPE, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef field_names = comptimeExpr(gz, scope, names_ri, p[2], is_struct ? REASON_STRUCT_FIELD_NAMES : REASON_UNION_FIELD_NAMES); const uint32_t names_words[2] = { (uint32_t)gzNodeIndexToRelative(gz, p[2]), field_names }; const ZirInstRef field_types_ty = gzAddExtendedPayloadSmall(gz, ZIR_EXT_REIFY_SLICE_ARG_TY, is_struct ? REIFY_SLICE_ARG_STRING_TO_STRUCT_FIELD_TYPE : REIFY_SLICE_ARG_STRING_TO_UNION_FIELD_TYPE, names_words, 2); const ZirInstRef field_attrs_ty = gzAddExtendedPayloadSmall(gz, ZIR_EXT_REIFY_SLICE_ARG_TY, is_struct ? REIFY_SLICE_ARG_STRING_TO_STRUCT_FIELD_ATTRS : REIFY_SLICE_ARG_STRING_TO_UNION_FIELD_ATTRS, names_words, 2); const ResultInfo types_ri = { { RL_COERCED_TY, field_types_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef field_types = comptimeExpr(gz, scope, types_ri, p[3], is_struct ? REASON_STRUCT_FIELD_TYPES : REASON_UNION_FIELD_TYPES); const ResultInfo attrs_ri = { { RL_COERCED_TY, field_attrs_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef field_attrs = comptimeExpr(gz, scope, attrs_ri, p[4], is_struct ? REASON_STRUCT_FIELD_ATTRS : REASON_UNION_FIELD_ATTRS); // Zir.Inst.ReifyStruct / Zir.Inst.ReifyUnion. const uint32_t words[8] = { ag->source_line, ag->source_column, node, layout, arg_ty, field_names, field_types, field_attrs }; const ZirInstRef result = gzAddExtendedPayloadSmall(gz, is_struct ? ZIR_EXT_REIFY_STRUCT : ZIR_EXT_REIFY_UNION, reify_name_strat, words, 8); return rvalue(gz, ri, result, node); } case BFN_Enum: { const ZirInstRef enum_mode_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_ENUM_MODE); const ZirInstRef tag_ty = typeExpr(gz, scope, p[0]); const ResultInfo mode_ri = { { RL_COERCED_TY, enum_mode_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef mode = comptimeExpr(gz, scope, mode_ri, p[1], REASON_TYPE); const ResultInfo names_ri = { { RL_COERCED_TY, ZIR_REF_SLICE_CONST_SLICE_CONST_U8_TYPE, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef field_names = comptimeExpr(gz, scope, names_ri, p[2], REASON_ENUM_FIELD_NAMES); const uint32_t values_ty_words[3] = { (uint32_t)gzNodeIndexToRelative(gz, node), tag_ty, field_names }; const ZirInstRef field_values_ty = gzAddExtendedPayload( gz, ZIR_EXT_REIFY_ENUM_VALUE_SLICE_TY, values_ty_words, 3); const ResultInfo values_ri = { { RL_COERCED_TY, field_values_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef field_values = comptimeExpr( gz, scope, values_ri, p[3], REASON_ENUM_FIELD_VALUES); // Zir.Inst.ReifyEnum. const uint32_t words[7] = { ag->source_line, ag->source_column, node, tag_ty, mode, field_names, field_values }; const ZirInstRef result = gzAddExtendedPayloadSmall( gz, ZIR_EXT_REIFY_ENUM, reify_name_strat, words, 7); return rvalue(gz, ri, result, node); } case BFN_SpirvType: { const ZirInstRef spirv_type_options_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_SPIRV_TYPE_OPTIONS); const ResultInfo operand_ri = { { RL_COERCED_TY, spirv_type_options_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef operand = comptimeExpr(gz, scope, operand_ri, p[0], REASON_TYPE); // Zir.Inst.ReifySpirvType. const uint32_t words[4] = { ag->source_line, ag->source_column, node, operand }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_REIFY_SPIRV_TYPE, words, 4); return rvalue(gz, ri, result, node); } case BFN_panic: emitDbgNode(gz, node); return simpleUnOp( gz, scope, ri, node, slice_const_u8_ri, p[0], ZIR_INST_PANIC); case BFN_trap: emitDbgNode(gz, node); gzAddNode(gz, ZIR_INST_TRAP, node); return rvalue(gz, ri, ZIR_REF_UNREACHABLE_VALUE, node); case BFN_int_from_error: { const ZirInstRef operand = expr(gz, scope, none_ri, p[0]); const uint32_t words[2] = { (uint32_t)gzNodeIndexToRelative(gz, node), operand }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_INT_FROM_ERROR, words, 2); return rvalue(gz, ri, result, node); } case BFN_error_from_int: { const ZirInstRef operand = expr(gz, scope, none_ri, p[0]); const uint32_t words[2] = { (uint32_t)gzNodeIndexToRelative(gz, node), operand }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_ERROR_FROM_INT, words, 2); return rvalue(gz, ri, result, node); } case BFN_error_cast: { emitDbgNode(gz, node); const ZirInstRef lhs = resultTypeForCast(gz, ri, node); const ZirInstRef rhs = expr(gz, scope, none_ri, p[0]); const uint32_t words[3] = { (uint32_t)gzNodeIndexToRelative(gz, node), lhs, rhs }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_ERROR_CAST, words, 3); return rvalue(gz, ri, result, node); } case BFN_ptr_cast: case BFN_align_cast: case BFN_addrspace_cast: case BFN_const_cast: case BFN_volatile_cast: return ptrCast(gz, scope, ri, node); case BFN_has_decl: return hasDeclOrField( gz, scope, ri, node, p[0], p[1], ZIR_INST_HAS_DECL); case BFN_has_field: return hasDeclOrField( gz, scope, ri, node, p[0], p[1], ZIR_INST_HAS_FIELD); case BFN_clz: return bitBuiltin(gz, scope, ri, node, p[0], ZIR_INST_CLZ); case BFN_ctz: return bitBuiltin(gz, scope, ri, node, p[0], ZIR_INST_CTZ); case BFN_pop_count: return bitBuiltin(gz, scope, ri, node, p[0], ZIR_INST_POP_COUNT); case BFN_byte_swap: return bitBuiltin(gz, scope, ri, node, p[0], ZIR_INST_BYTE_SWAP); case BFN_bit_reverse: return bitBuiltin(gz, scope, ri, node, p[0], ZIR_INST_BIT_REVERSE); case BFN_div_exact: return divBuiltin(gz, scope, ri, node, p[0], p[1], ZIR_INST_DIV_EXACT); case BFN_div_floor: return divBuiltin(gz, scope, ri, node, p[0], p[1], ZIR_INST_DIV_FLOOR); case BFN_div_ceil: return divBuiltin(gz, scope, ri, node, p[0], p[1], ZIR_INST_DIV_CEIL); case BFN_div_trunc: return divBuiltin(gz, scope, ri, node, p[0], p[1], ZIR_INST_DIV_TRUNC); case BFN_mod: return divBuiltin(gz, scope, ri, node, p[0], p[1], ZIR_INST_MOD); case BFN_rem: return divBuiltin(gz, scope, ri, node, p[0], p[1], ZIR_INST_REM); case BFN_shl_exact: return shiftOp(gz, scope, ri, node, p[0], p[1], ZIR_INST_SHL_EXACT); case BFN_shr_exact: return shiftOp(gz, scope, ri, node, p[0], p[1], ZIR_INST_SHR_EXACT); case BFN_bit_offset_of: return offsetOf( gz, scope, ri, node, p[0], p[1], ZIR_INST_BIT_OFFSET_OF); case BFN_offset_of: return offsetOf(gz, scope, ri, node, p[0], p[1], ZIR_INST_OFFSET_OF); case BFN_cmpxchg_strong: return cmpxchg(gz, scope, ri, node, params, 1); case BFN_cmpxchg_weak: return cmpxchg(gz, scope, ri, node, params, 0); case BFN_wasm_memory_size: { const ZirInstRef operand = comptimeExpr( gz, scope, coerced_u32_ri, p[0], REASON_WASM_MEMORY_INDEX); const uint32_t words[2] = { (uint32_t)gzNodeIndexToRelative(gz, node), operand }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_WASM_MEMORY_SIZE, words, 2); return rvalue(gz, ri, result, node); } case BFN_wasm_memory_grow: { const ZirInstRef index_arg = comptimeExpr( gz, scope, coerced_u32_ri, p[0], REASON_WASM_MEMORY_INDEX); const ZirInstRef delta_arg = expr(gz, scope, coerced_usize_ri, p[1]); const uint32_t words[3] = { (uint32_t)gzNodeIndexToRelative(gz, node), index_arg, delta_arg }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_WASM_MEMORY_GROW, words, 3); return rvalue(gz, ri, result, node); } case BFN_splat: { const ZirInstRef result_type = resultTypeForCast(gz, ri, node); const ZirInstRef elem_type = gzAddUnNode(gz, ZIR_INST_SPLAT_OP_RESULT_TY, result_type, node); const ResultInfo scalar_ri = { { RL_TY, elem_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef scalar = expr(gz, scope, scalar_ri, p[0]); const ZirInstRef result = gzAddPlNodeBin(gz, ZIR_INST_SPLAT, node, result_type, scalar); return rvalue(gz, ri, result, node); } case BFN_reduce: { const ZirInstRef reduce_op_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_REDUCE_OP); const ResultInfo op_ri = { { RL_COERCED_TY, reduce_op_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef op = expr(gz, scope, op_ri, p[0]); const ZirInstRef scalar = expr(gz, scope, none_ri, p[1]); const ZirInstRef result = gzAddPlNodeBin(gz, ZIR_INST_REDUCE, node, op, scalar); return rvalue(gz, ri, result, node); } case BFN_add_with_overflow: return overflowArithmetic( gz, scope, ri, node, params, ZIR_EXT_ADD_WITH_OVERFLOW); case BFN_sub_with_overflow: return overflowArithmetic( gz, scope, ri, node, params, ZIR_EXT_SUB_WITH_OVERFLOW); case BFN_mul_with_overflow: return overflowArithmetic( gz, scope, ri, node, params, ZIR_EXT_MUL_WITH_OVERFLOW); case BFN_shl_with_overflow: return overflowArithmetic( gz, scope, ri, node, params, ZIR_EXT_SHL_WITH_OVERFLOW); case BFN_atomic_load: { const ZirInstRef atomic_order_type = gzAddStdLangValue(gz, node, STD_LANG_VALUE_ATOMIC_ORDER); const ResultInfo order_ri = { { RL_COERCED_TY, atomic_order_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; // Zir.Inst.AtomicLoad. const ZirInstRef elem_type = typeExpr(gz, scope, p[0]); const ZirInstRef ptr = expr(gz, scope, none_ri, p[1]); const ZirInstRef ordering = expr(gz, scope, order_ri, p[2]); const uint32_t words[3] = { elem_type, ptr, ordering }; const ZirInstRef result = gzAddPlNodeExtra(gz, ZIR_INST_ATOMIC_LOAD, node, words, 3); return rvalue(gz, ri, result, node); } case BFN_atomic_rmw: { const ZirInstRef atomic_order_type = gzAddStdLangValue(gz, node, STD_LANG_VALUE_ATOMIC_ORDER); const ZirInstRef atomic_rmw_op_type = gzAddStdLangValue(gz, node, STD_LANG_VALUE_ATOMIC_RMW_OP); const ZirInstRef int_type = typeExpr(gz, scope, p[0]); const ResultInfo op_ri = { { RL_COERCED_TY, atomic_rmw_op_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ResultInfo operand_ri = { { RL_TY, int_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ResultInfo order_ri = { { RL_COERCED_TY, atomic_order_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; // Zir.Inst.AtomicRmw. const ZirInstRef ptr = expr(gz, scope, none_ri, p[1]); const ZirInstRef operation = expr(gz, scope, op_ri, p[2]); const ZirInstRef operand = expr(gz, scope, operand_ri, p[3]); const ZirInstRef ordering = expr(gz, scope, order_ri, p[4]); const uint32_t words[4] = { ptr, operation, operand, ordering }; const ZirInstRef result = gzAddPlNodeExtra(gz, ZIR_INST_ATOMIC_RMW, node, words, 4); return rvalue(gz, ri, result, node); } case BFN_atomic_store: { const ZirInstRef atomic_order_type = gzAddStdLangValue(gz, node, STD_LANG_VALUE_ATOMIC_ORDER); const ZirInstRef int_type = typeExpr(gz, scope, p[0]); const ResultInfo operand_ri = { { RL_TY, int_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ResultInfo order_ri = { { RL_COERCED_TY, atomic_order_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; // Zir.Inst.AtomicStore. const ZirInstRef ptr = expr(gz, scope, none_ri, p[1]); const ZirInstRef operand = expr(gz, scope, operand_ri, p[2]); const ZirInstRef ordering = expr(gz, scope, order_ri, p[3]); const uint32_t words[3] = { ptr, operand, ordering }; gzAddPlNodeExtra(gz, ZIR_INST_ATOMIC_STORE, node, words, 3); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); } case BFN_mul_add: { const ZirInstRef float_type = typeExpr(gz, scope, p[0]); const ResultInfo coerced_ri = { { RL_COERCED_TY, float_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ResultInfo ty_ri = { { RL_TY, float_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef mulend1 = expr(gz, scope, coerced_ri, p[1]); const ZirInstRef mulend2 = expr(gz, scope, coerced_ri, p[2]); const ZirInstRef addend = expr(gz, scope, ty_ri, p[3]); // Zir.Inst.MulAdd. const uint32_t words[3] = { mulend1, mulend2, addend }; const ZirInstRef result = gzAddPlNodeExtra(gz, ZIR_INST_MUL_ADD, node, words, 3); return rvalue(gz, ri, result, node); } case BFN_call: { const ZirInstRef call_modifier_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_CALL_MODIFIER); const ResultInfo modifier_ri = { { RL_COERCED_TY, call_modifier_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef modifier = comptimeExpr(gz, scope, modifier_ri, p[0], REASON_CALL_MODIFIER); const ZirInstRef callee = expr(gz, scope, none_ri, p[1]); const ZirInstRef args = expr(gz, scope, none_ri, p[2]); // Zir.Inst.BuiltinCall: flags, modifier, callee, args. zig1 does not // track GenZir.nosuspend_node, so is_nosuspend is clear. const uint32_t words[4] = { 0, modifier, callee, args }; const ZirInstRef result = gzAddPlNodeExtra(gz, ZIR_INST_BUILTIN_CALL, node, words, 4); return rvalue(gz, ri, result, node); } case BFN_field_parent_ptr: { const ZirInstRef parent_ptr_type = resultTypeForCast(gz, ri, node); const ZirInstRef field_name = comptimeExpr( gz, scope, slice_const_u8_ri, p[0], REASON_FIELD_NAME); const ZirInstRef field_ptr = expr(gz, scope, none_ri, p[1]); // Zir.Inst.FieldParentPtr. const uint32_t words[4] = { (uint32_t)gzNodeIndexToRelative(gz, node), parent_ptr_type, field_name, field_ptr }; const ZirInstRef result = gzAddExtendedPayloadSmall( gz, ZIR_EXT_FIELD_PARENT_PTR, 0, words, 4); return rvalue(gz, ri, result, node); } case BFN_memcpy: { const ZirInstRef lhs = expr(gz, scope, none_ri, p[0]); const ZirInstRef rhs = expr(gz, scope, none_ri, p[1]); gzAddPlNodeBin(gz, ZIR_INST_MEMCPY, node, lhs, rhs); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); } case BFN_memset: { const ZirInstRef lhs = expr(gz, scope, none_ri, p[0]); const ZirInstRef lhs_ty = gzAddUnNode(gz, ZIR_INST_TYPEOF, lhs, p[0]); const ZirInstRef elem_ty = gzAddUnNode(gz, ZIR_INST_INDEXABLE_PTR_ELEM_TYPE, lhs_ty, p[0]); const ResultInfo elem_ri = { { RL_COERCED_TY, elem_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef rhs = expr(gz, scope, elem_ri, p[1]); gzAddPlNodeBin(gz, ZIR_INST_MEMSET, node, lhs, rhs); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); } case BFN_memmove: { const ZirInstRef lhs = expr(gz, scope, none_ri, p[0]); const ZirInstRef rhs = expr(gz, scope, none_ri, p[1]); gzAddPlNodeBin(gz, ZIR_INST_MEMMOVE, node, lhs, rhs); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); } case BFN_shuffle: { // Zir.Inst.Shuffle. const ZirInstRef elem_type = typeExpr(gz, scope, p[0]); const ZirInstRef a = expr(gz, scope, none_ri, p[1]); const ZirInstRef b = expr(gz, scope, none_ri, p[2]); const ZirInstRef mask = comptimeExpr( gz, scope, none_ri, p[3], REASON_OPERAND_SHUFFLE_MASK); const uint32_t words[4] = { elem_type, a, b, mask }; const ZirInstRef result = gzAddPlNodeExtra(gz, ZIR_INST_SHUFFLE, node, words, 4); return rvalue(gz, ri, result, node); } case BFN_select: { // Zir.Inst.Select. const uint32_t node_rel = (uint32_t)gzNodeIndexToRelative(gz, node); const ZirInstRef elem_type = typeExpr(gz, scope, p[0]); const ZirInstRef pred = expr(gz, scope, none_ri, p[1]); const ZirInstRef a = expr(gz, scope, none_ri, p[2]); const ZirInstRef b = expr(gz, scope, none_ri, p[3]); const uint32_t words[5] = { node_rel, elem_type, pred, a, b }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_SELECT, words, 5); return rvalue(gz, ri, result, node); } case BFN_Vector: { const ZirInstRef len = comptimeExpr(gz, scope, coerced_u32_ri, p[0], REASON_TYPE); const ZirInstRef elem_type = typeExpr(gz, scope, p[1]); const ZirInstRef result = gzAddPlNodeBin(gz, ZIR_INST_VECTOR_TYPE, node, len, elem_type); return rvalue(gz, ri, result, node); } case BFN_prefetch: { const ZirInstRef prefetch_options_ty = gzAddStdLangValue(gz, node, STD_LANG_VALUE_PREFETCH_OPTIONS); const ZirInstRef ptr = expr(gz, scope, none_ri, p[0]); const ResultInfo options_ri = { { RL_COERCED_TY, prefetch_options_ty, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef options = comptimeExpr( gz, scope, options_ri, p[1], REASON_PREFETCH_OPTIONS); const uint32_t words[3] = { (uint32_t)gzNodeIndexToRelative(gz, node), ptr, options }; gzAddExtendedPayload(gz, ZIR_EXT_PREFETCH, words, 3); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); } case BFN_c_va_arg: { const uint32_t node_rel = (uint32_t)gzNodeIndexToRelative(gz, node); const ZirInstRef lhs = expr(gz, scope, none_ri, p[0]); const ZirInstRef rhs = typeExpr(gz, scope, p[1]); const uint32_t words[3] = { node_rel, lhs, rhs }; const ZirInstRef result = gzAddExtendedPayload(gz, ZIR_EXT_C_VA_ARG, words, 3); return rvalue(gz, ri, result, node); } case BFN_c_va_copy: case BFN_c_va_end: { const uint32_t node_rel = (uint32_t)gzNodeIndexToRelative(gz, node); const uint32_t words[2] = { node_rel, expr(gz, scope, none_ri, p[0]) }; const ZirInstRef result = gzAddExtendedPayload(gz, info->tag == BFN_c_va_copy ? ZIR_EXT_C_VA_COPY : ZIR_EXT_C_VA_END, words, 2); return rvalue(gz, ri, result, node); } case BFN_c_va_start: if (!ag->fn_var_args) failNode(ag, node, "'@cVaStart' in a non-variadic function"); return rvalue( gz, ri, gzAddNodeExtended(gz, ZIR_EXT_C_VA_START, node), node); case BFN_work_item_id: case BFN_work_group_size: case BFN_work_group_id: { const ZirInstRef operand = comptimeExpr( gz, scope, coerced_u32_ri, p[0], REASON_WORK_GROUP_DIM_INDEX); const uint32_t words[2] = { (uint32_t)gzNodeIndexToRelative(gz, node), operand }; const uint16_t opcode = info->tag == BFN_work_item_id ? ZIR_EXT_WORK_ITEM_ID : info->tag == BFN_work_group_size ? ZIR_EXT_WORK_GROUP_SIZE : ZIR_EXT_WORK_GROUP_ID; const ZirInstRef result = gzAddExtendedPayload(gz, opcode, words, 2); return rvalue(gz, ri, result, node); } } UNREACHABLE("builtinCall: not a BuiltinFn.Tag"); ZIG1_TCC_RETURN(ZIR_REF_NONE); } // AstGen.zig:hasDeclOrField static ZirInstRef hasDeclOrField(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex lhs_node, AstNodeIndex rhs_node, ZirInstTag tag) { const ZirInstRef container_type = typeExpr(gz, scope, lhs_node); const ZirInstRef name = comptimeExpr(gz, scope, slice_const_u8_ri, rhs_node, tag == ZIR_INST_HAS_DECL ? REASON_DECL_NAME : REASON_FIELD_NAME); const ZirInstRef result = gzAddPlNodeBin(gz, tag, node, container_type, name); return rvalue(gz, ri, result, node); } // AstGen.zig:typeCast static ZirInstRef typeCast(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex operand_node, ZirInstTag tag) { const LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef result_type = resultTypeForCast(gz, ri, node); const ZirInstRef operand = expr(gz, scope, none_ri, operand_node); emitDbgStmt(gz, cursor.line, cursor.column); const ZirInstRef result = gzAddPlNodeBin(gz, tag, node, result_type, operand); return rvalue(gz, ri, result, node); } // AstGen.zig:simpleUnOpType static ZirInstRef simpleUnOpType(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex operand_node, ZirInstTag tag) { const ZirInstRef operand = typeExpr(gz, scope, operand_node); const ZirInstRef result = gzAddUnNode(gz, tag, operand, node); return rvalue(gz, ri, result, node); } // AstGen.zig:simpleUnOp static ZirInstRef simpleUnOp(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, ResultInfo operand_ri, AstNodeIndex operand_node, ZirInstTag tag) { const LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef operand = tag == ZIR_INST_COMPILE_ERROR ? comptimeExpr( gz, scope, operand_ri, operand_node, REASON_COMPILE_ERROR_STRING) : expr(gz, scope, operand_ri, operand_node); switch (tag) { case ZIR_INST_TAG_NAME: case ZIR_INST_ERROR_NAME: case ZIR_INST_INT_FROM_PTR: emitDbgStmt(gz, cursor.line, cursor.column); break; default: break; } const ZirInstRef result = gzAddUnNode(gz, tag, operand, node); return rvalue(gz, ri, result, node); } // AstGen.zig:floatRoundOp static ZirInstRef floatRoundOp(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex operand_node, ZirInstTag float_tag) { const ZirInstRef dest_type = resultLocType(gz, ri, node); if (dest_type == ZIR_REF_NONE) return floatUnOp(gz, scope, ri, node, operand_node, float_tag); const LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const uint32_t ty_words[2] = { (uint32_t)gzNodeIndexToRelative(gz, node), dest_type }; const ZirInstRef operand_ty_inst = gzAddExtendedPayload(gz, ZIR_EXT_ROUND_OP_TY, ty_words, 2); const ResultInfo operand_ri = { { RL_COERCED_TY, operand_ty_inst, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef operand = expr(gz, scope, operand_ri, operand_node); emitDbgStmt(gz, cursor.line, cursor.column); uint16_t round_op; switch (float_tag) { case ZIR_INST_ROUND: round_op = ZIR_ROUND_OP_ROUND; break; case ZIR_INST_FLOOR: round_op = ZIR_ROUND_OP_FLOOR; break; case ZIR_INST_CEIL: round_op = ZIR_ROUND_OP_CEIL; break; case ZIR_INST_TRUNC: round_op = ZIR_ROUND_OP_TRUNC; break; default: UNREACHABLE("floatRoundOp: not a rounding tag"); } const uint32_t words[3] = { (uint32_t)gzNodeIndexToRelative(gz, node), dest_type, operand }; const ZirInstRef result = gzAddExtendedPayloadSmall(gz, ZIR_EXT_ROUND_OP, round_op, words, 3); return rvalue(gz, ri, result, node); } // AstGen.zig:floatUnOp static ZirInstRef floatUnOp(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex operand_node, ZirInstTag tag) { const ZirInstRef result_type = resultLocType(gz, ri, node); ResultInfo operand_ri = none_ri; if (result_type != ZIR_REF_NONE) { const uint32_t words[2] = { (uint32_t)gzNodeIndexToRelative(gz, node), result_type }; operand_ri.rl.tag = RL_TY; operand_ri.rl.ty = gzAddExtendedPayload(gz, ZIR_EXT_FLOAT_OP_RESULT_TY, words, 2); } const ZirInstRef operand = expr(gz, scope, operand_ri, operand_node); const ZirInstRef result = gzAddUnNode(gz, tag, operand, node); return rvalue(gz, ri, result, node); } // AstGen.zig:negation static ZirInstRef negation( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node) { const Ast* tree = gz->astgen->tree; // Check for float literal as the sub-expression because we want to // preserve its negativity rather than having it go through comptime // subtraction. const AstNodeIndex operand_node = astNodeData(tree, node).lhs; if (astNodeTag(tree, operand_node) == AST_NODE_NUMBER_LITERAL) return numberLiteral(gz, ri, operand_node, node, SIGN_NEGATIVE); const ZirInstRef operand = expr(gz, scope, none_ri, operand_node); const ZirInstRef result = gzAddUnNode(gz, ZIR_INST_NEGATE, operand, node); return rvalue(gz, ri, result, node); } // AstGen.zig:cmpxchg static ZirInstRef cmpxchg(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeSlice params, uint16_t small) { const ZirInstRef int_type = typeExpr(gz, scope, params.ptr[0]); const ZirInstRef atomic_order_type = gzAddStdLangValue(gz, node, STD_LANG_VALUE_ATOMIC_ORDER); const ResultInfo ty_ri = { { RL_TY, int_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ResultInfo coerced_ri = { { RL_COERCED_TY, int_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ResultInfo order_ri = { { RL_COERCED_TY, atomic_order_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; // Zir.Inst.Cmpxchg. const uint32_t node_rel = (uint32_t)gzNodeIndexToRelative(gz, node); const ZirInstRef ptr = expr(gz, scope, none_ri, params.ptr[1]); const ZirInstRef expected_value = expr(gz, scope, ty_ri, params.ptr[2]); const ZirInstRef new_value = expr(gz, scope, coerced_ri, params.ptr[3]); const ZirInstRef success_order = expr(gz, scope, order_ri, params.ptr[4]); const ZirInstRef failure_order = expr(gz, scope, order_ri, params.ptr[5]); const uint32_t words[6] = { node_rel, ptr, expected_value, new_value, success_order, failure_order }; const ZirInstRef result = gzAddExtendedPayloadSmall(gz, ZIR_EXT_CMPXCHG, small, words, 6); return rvalue(gz, ri, result, node); } // AstGen.zig:bitBuiltin static ZirInstRef bitBuiltin(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex operand_node, ZirInstTag tag) { const ZirInstRef operand = expr(gz, scope, none_ri, operand_node); const ZirInstRef result = gzAddUnNode(gz, tag, operand, node); return rvalue(gz, ri, result, node); } // AstGen.zig:divBuiltin static ZirInstRef divBuiltin(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex lhs_node, AstNodeIndex rhs_node, ZirInstTag tag) { const LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef lhs = expr(gz, scope, none_ri, lhs_node); const ZirInstRef rhs = expr(gz, scope, none_ri, rhs_node); emitDbgStmt(gz, cursor.line, cursor.column); const ZirInstRef result = gzAddPlNodeBin(gz, tag, node, lhs, rhs); return rvalue(gz, ri, result, node); } // AstGen.zig:offsetOf static ZirInstRef offsetOf(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex lhs_node, AstNodeIndex rhs_node, ZirInstTag tag) { const ZirInstRef type_inst = typeExpr(gz, scope, lhs_node); const ZirInstRef field_name = comptimeExpr( gz, scope, slice_const_u8_ri, rhs_node, REASON_FIELD_NAME); const ZirInstRef result = gzAddPlNodeBin(gz, tag, node, type_inst, field_name); return rvalue(gz, ri, result, node); } // AstGen.zig:shiftOp static ZirInstRef shiftOp(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex lhs_node, AstNodeIndex rhs_node, ZirInstTag tag) { const Ast* tree = gz->astgen->tree; const ZirInstRef lhs = expr(gz, scope, none_ri, lhs_node); const bool is_infix = astNodeTag(tree, node) == AST_NODE_SHL || astNodeTag(tree, node) == AST_NODE_SHR; LineColumn cursor = { 0, 0 }; if (is_infix) cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef log2_int_type = gzAddUnNode(gz, ZIR_INST_TYPEOF_LOG2_INT_TYPE, lhs, lhs_node); const ResultInfo rhs_ri = { { RL_TY, log2_int_type, 0, 0, NULL, 0, 0 }, CTX_SHIFT_OP }; const ZirInstRef rhs = expr(gz, scope, rhs_ri, rhs_node); if (is_infix) emitDbgStmt(gz, cursor.line, cursor.column); const ZirInstRef result = gzAddPlNodeBin(gz, tag, node, lhs, rhs); return rvalue(gz, ri, result, node); } // AstGen.zig:overflowArithmetic static ZirInstRef overflowArithmetic(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeSlice params, uint16_t tag) { const ZirInstRef lhs = expr(gz, scope, none_ri, params.ptr[0]); const ZirInstRef rhs = expr(gz, scope, none_ri, params.ptr[1]); const uint32_t words[3] = { (uint32_t)gzNodeIndexToRelative(gz, node), lhs, rhs }; const ZirInstRef result = gzAddExtendedPayload(gz, tag, words, 3); return rvalue(gz, ri, result, node); } // AstGen.zig:stringLiteral static ZirInstRef stringLiteral(GenZir* gz, ResultInfo ri, AstNodeIndex node) { AstGen* ag = gz->astgen; const AstTokenIndex str_lit_token = astNodeMainToken(ag->tree, node); uint32_t str_len; const uint32_t str_index = strLitAsString(ag, str_lit_token, &str_len); ZirInstData data; memset(&data, 0, sizeof(data)); data.str.start = str_index; data.str.len = str_len; const ZirInstRef result = gzAdd(gz, ZIR_INST_STR, data); return rvalue(gz, ri, result, node); } // AstGen.zig:multilineStringLiteral // Emit a ZIR_INST_STR for a multiline string. // Mirrors AstGen.multilineStringLiteral. static ZirInstRef multilineStringLiteral( GenZir* gz, ResultInfo ri, AstNodeIndex node) { AstGen* ag = gz->astgen; uint32_t str_len; const uint32_t str_index = strLitNodeAsString(ag, node, &str_len); ZirInstData data; memset(&data, 0, sizeof(data)); data.str.start = str_index; data.str.len = str_len; const ZirInstRef result = gzAdd(gz, ZIR_INST_STR, data); return rvalue(gz, ri, result, node); } static void importsPut(AstGen* ag, uint32_t name, AstTokenIndex token) { for (uint32_t i = 0; i < ag->imports_len; i++) if (ag->import_names[i] == name) return; const uint32_t need = ag->imports_len + 1; if (need > ag->imports_cap) { uint32_t new_cap = ag->imports_cap * 2; if (new_cap < need) new_cap = need; if (new_cap < 8) new_cap = 8; uint32_t* nn = zig1Realloc( ag->import_names, (size_t)new_cap * sizeof(uint32_t)); uint32_t* nt = zig1Realloc( ag->import_tokens, (size_t)new_cap * sizeof(uint32_t)); ag->import_names = nn; ag->import_tokens = nt; ag->imports_cap = new_cap; } ag->import_names[ag->imports_len] = name; ag->import_tokens[ag->imports_len] = token; ag->imports_len += 1; } // AstGen.zig:arrayType // Lowers `[N]T` to `array_type` (Bin payload: len, elem_type). static ZirInstRef arrayType( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node) { const Ast* tree = gz->astgen->tree; const AstNodeIndex len_node = astNodeData(tree, node).lhs; const AstNodeIndex elem_type_node = astNodeData(tree, node).rhs; // Upstream's `_` inferred-length error is not reported: zig1 accepts // programs upstream rejects. const ZirInstRef len = reachableExprComptime( gz, scope, coerced_usize_ri, len_node, node, REASON_TYPE); const ZirInstRef elem_type = typeExpr(gz, scope, elem_type_node); const ZirInstRef result = gzAddPlNodeBin(gz, ZIR_INST_ARRAY_TYPE, node, len, elem_type); return rvalue(gz, ri, result, node); } // AstGen.zig:arrayTypeSentinel // Lowers // `[N:s]T` to `array_type_sentinel` (ArrayTypeSentinel payload: len, sentinel, // elem_type). static ZirInstRef arrayTypeSentinel( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node) { const Ast* tree = gz->astgen->tree; const AstNodeIndex len_node = astNodeData(tree, node).lhs; const uint32_t extra_index = astNodeData(tree, node).rhs; // fullArrayType layout for sentinel: extra[rhs] = sentinel, extra[rhs+1] = // elem_type const AstNodeIndex sentinel_node = tree->extra_data.arr[extra_index]; const AstNodeIndex elem_type_node = tree->extra_data.arr[extra_index + 1]; const ZirInstRef len = reachableExprComptime( gz, scope, coerced_usize_ri, len_node, node, REASON_ARRAY_LENGTH); const SourceCursor saved_cursor = saveSourceCursor(gz->astgen); const ZirInstRef elem_type = typeExpr(gz, scope, elem_type_node); restoreSourceCursor(gz->astgen, saved_cursor); const ResultInfo elem_ri = { { RL_COERCED_TY, elem_type, 0, 0, NULL, 0, 0 }, 0 }; const ZirInstRef sentinel = reachableExprComptime( gz, scope, elem_ri, sentinel_node, node, REASON_ARRAY_SENTINEL); // ArrayTypeSentinel payload: { len, sentinel, elem_type } const uint32_t words[3] = { (uint32_t)len, (uint32_t)sentinel, (uint32_t)elem_type }; const ZirInstRef result = gzAddPlNodeExtra(gz, ZIR_INST_ARRAY_TYPE_SENTINEL, node, words, 3); return rvalue(gz, ri, result, node); } // --- slice / field access --- // AstGen.zig:nodeIsTriviallyZero // AstGen.nodeIsTriviallyZero: a number literal `parseNumberLiteral` reads as // the integer 0. static bool nodeIsTriviallyZero(const Ast* tree, AstNodeIndex node) { if (astNodeTag(tree, node) != AST_NODE_NUMBER_LITERAL) return false; const char* bytes; uint32_t len; astTokenSlice(tree, astNodeMainToken(tree, node), &bytes, &len); const NumberLiteralResult lit = parseNumberLiteral(bytes, len); return lit.tag == NUMBER_LITERAL_INT && lit.int_val == 0; } // AstGen.zig:addFieldAccess static ZirInstRef addFieldAccess(ZirInstTag tag, GenZir* gz, Scope* scope, ResultInfo lhs_ri, AstNodeIndex node) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; const AstNodeIndex object_node = astNodeData(tree, node).lhs; const AstTokenIndex field_ident = astNodeData(tree, node).rhs; const uint32_t str_index = identAsString(ag, field_ident); const ZirInstRef lhs = expr(gz, scope, lhs_ri, object_node); LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); emitDbgStmt(gz, cursor.line, cursor.column); const uint32_t words[2] = { lhs, str_index }; return gzAddPlNodeExtra(gz, tag, node, words, 2); } // AstGen.zig:fieldAccess static ZirInstRef fieldAccess( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node) { switch (ri.rl.tag) { case RL_REF: case RL_REF_COERCED_TY: return addFieldAccess(ZIR_INST_FIELD_PTR, gz, scope, ref_ri, node); case RL_REF_CONST: return addFieldAccess( ZIR_INST_FIELD_PTR, gz, scope, ref_const_ri, node); default: { const ZirInstRef access = addFieldAccess( ZIR_INST_FIELD_PTR_LOAD, gz, scope, ref_const_ri, node); return rvalue(gz, ri, access, node); } } } // AstGen.zig:arrayAccess static ZirInstRef arrayAccess( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node) { const Ast* tree = gz->astgen->tree; const AstNodeIndex lhs_node = astNodeData(tree, node).lhs; const AstNodeIndex rhs_node = astNodeData(tree, node).rhs; switch (ri.rl.tag) { case RL_REF: case RL_REF_COERCED_TY: { const ZirInstRef lhs = expr(gz, scope, ref_ri, lhs_node); const LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef rhs = expr(gz, scope, coerced_usize_ri, rhs_node); emitDbgStmt(gz, cursor.line, cursor.column); return gzAddPlNodeBin(gz, ZIR_INST_ELEM_PTR_NODE, node, lhs, rhs); } case RL_REF_CONST: { const ZirInstRef lhs = expr(gz, scope, ref_const_ri, lhs_node); const LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef rhs = expr(gz, scope, coerced_usize_ri, rhs_node); emitDbgStmt(gz, cursor.line, cursor.column); return gzAddPlNodeBin(gz, ZIR_INST_ELEM_PTR_NODE, node, lhs, rhs); } default: { const ZirInstRef lhs = expr(gz, scope, ref_const_ri, lhs_node); const LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef rhs = expr(gz, scope, coerced_usize_ri, rhs_node); emitDbgStmt(gz, cursor.line, cursor.column); return rvalue(gz, ri, gzAddPlNodeBin(gz, ZIR_INST_ELEM_PTR_LOAD, node, lhs, rhs), node); } } } // AstGen.zig:simpleStrTok static ZirInstRef simpleStrTok(GenZir* gz, ResultInfo ri, AstTokenIndex ident_token, AstNodeIndex node, ZirInstTag op_inst_tag) { const uint32_t str_index = identAsString(gz->astgen, ident_token); const ZirInstRef result = gzAddStrTok(gz, op_inst_tag, str_index, ident_token); return rvalue(gz, ri, result, node); } // sliceExpr, ported from AstGen.zig (slice_open/slice/slice_sentinel). static ZirInstRef sliceExpr( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; AstFullSlice full; astFullSlice(tree, node, &full); if ((full.ast.end != AST_TOKEN_NONE) && astNodeTag(tree, full.ast.sliced) == AST_NODE_SLICE_OPEN && nodeIsTriviallyZero(tree, full.ast.start)) { AstFullSlice lhs_extra; astFullSlice(tree, full.ast.sliced, &lhs_extra); const ZirInstRef lhs = expr(gz, scope, ref_ri, lhs_extra.ast.sliced); const ZirInstRef start = expr(gz, scope, coerced_usize_ri, lhs_extra.ast.start); LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef len = expr(gz, scope, coerced_usize_ri, full.ast.end); ZirInstRef sentinel = ZIR_REF_NONE; if ((full.ast.sentinel != AST_TOKEN_NONE)) { const ZirInstRef sentinel_ty = gzAddUnNode(gz, ZIR_INST_SLICE_SENTINEL_TY, lhs, node); ResultInfo sentinel_ri = { { RL_COERCED_TY, sentinel_ty, 0, 0, NULL, 0, 0 }, 0 }; sentinel = expr(gz, scope, sentinel_ri, full.ast.sentinel); } emitDbgStmt(gz, cursor.line, cursor.column); const uint32_t words[5] = { lhs, start, len, sentinel, (uint32_t)gzNodeIndexToRelative(gz, full.ast.sliced) }; const ZirInstRef result = gzAddPlNodeExtra(gz, ZIR_INST_SLICE_LENGTH, node, words, 5); return rvalue(gz, ri, result, node); } const ZirInstRef lhs = expr(gz, scope, ref_ri, full.ast.sliced); LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef start = expr(gz, scope, coerced_usize_ri, full.ast.start); const ZirInstRef end = (full.ast.end != AST_TOKEN_NONE) ? expr(gz, scope, coerced_usize_ri, full.ast.end) : ZIR_REF_NONE; ZirInstRef sentinel = ZIR_REF_NONE; if ((full.ast.sentinel != AST_TOKEN_NONE)) { const ZirInstRef sentinel_ty = gzAddUnNode(gz, ZIR_INST_SLICE_SENTINEL_TY, lhs, node); ResultInfo sentinel_ri = { { RL_COERCED_TY, sentinel_ty, 0, 0, NULL, 0, 0 }, 0 }; sentinel = expr(gz, scope, sentinel_ri, full.ast.sentinel); } emitDbgStmt(gz, cursor.line, cursor.column); if (sentinel != ZIR_REF_NONE) { const uint32_t words[4] = { lhs, start, end, sentinel }; const ZirInstRef result = gzAddPlNodeExtra(gz, ZIR_INST_SLICE_SENTINEL, node, words, 4); return rvalue(gz, ri, result, node); } else if (end != ZIR_REF_NONE) { const uint32_t words[3] = { lhs, start, end }; const ZirInstRef result = gzAddPlNodeExtra(gz, ZIR_INST_SLICE_END, node, words, 3); return rvalue(gz, ri, result, node); } else { const uint32_t words[2] = { lhs, start }; const ZirInstRef result = gzAddPlNodeExtra(gz, ZIR_INST_SLICE_START, node, words, 2); return rvalue(gz, ri, result, node); } } // --- asm --- typedef struct { uint32_t name; uint32_t constraint; ZirInstRef operand; } AsmOperand; // AstGen.zig:GenZir.addAsm // Build the extended `asm`/`asm_expr` instruction. static ZirInstRef gzAddAsm(GenZir* gz, uint16_t tag, AstNodeIndex node, uint32_t asm_source, uint32_t output_type_bits, bool is_volatile, const AsmOperand* outputs, uint32_t outputs_len, const AsmOperand* inputs, uint32_t inputs_len, ZirInstRef clobbers) { AstGen* ag = gz->astgen; // Zir.Inst.Asm: { src_node, asm_source, output_type_bits, clobbers }, // then Output{name, constraint, operand} and Input{name, constraint, // operand} for each. const uint32_t payload_index = ag->extra_len; extraAppend(ag, (uint32_t)gzNodeIndexToRelative(gz, node)); extraAppend(ag, asm_source); extraAppend(ag, output_type_bits); extraAppend(ag, clobbers); for (uint32_t i = 0; i < outputs_len; i++) { extraAppend(ag, outputs[i].name); extraAppend(ag, outputs[i].constraint); extraAppend(ag, outputs[i].operand); } for (uint32_t i = 0; i < inputs_len; i++) { extraAppend(ag, inputs[i].name); extraAppend(ag, inputs[i].constraint); extraAppend(ag, inputs[i].operand); } unsigned small = 0; if (is_volatile) small |= ZIR_ASM_SMALL_IS_VOLATILE; small |= (outputs_len & ZIR_ASM_SMALL_OUTPUTS_LEN_MASK) << ZIR_ASM_SMALL_OUTPUTS_LEN_SHIFT; small |= (inputs_len & ZIR_ASM_SMALL_INPUTS_LEN_MASK) << ZIR_ASM_SMALL_INPUTS_LEN_SHIFT; ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = tag; data.extended.small = (uint16_t)small; data.extended.operand = payload_index; return gzAdd(gz, ZIR_INST_EXTENDED, data); } // AstGen.zig:asmExpr static ZirInstRef asmExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, const AstFullAsm* full) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; uint16_t tag; uint32_t tmpl; switch (astNodeTag(tree, full->ast.template)) { case AST_NODE_STRING_LITERAL: { uint32_t tmpl_len; tmpl = strLitAsString( ag, astNodeMainToken(tree, full->ast.template), &tmpl_len); tag = ZIR_EXT_ASM; break; } case AST_NODE_MULTILINE_STRING_LITERAL: { uint32_t tmpl_len; tmpl = strLitNodeAsString(ag, full->ast.template, &tmpl_len); tag = ZIR_EXT_ASM; break; } default: { // asm_expr: comptime template expression (not a string literal). // Mirrors AstGen.zig asmExpr else branch. tag = ZIR_EXT_ASM_EXPR; tmpl = (uint32_t)comptimeExpr(gz, scope, none_ri, full->ast.template, REASON_INLINE_ASSEMBLY_CODE); break; } } const bool is_container_asm = ag->fn_block == NULL; if (!is_container_asm) { if (full->outputs.len == 0 && !(full->volatile_token != AST_TOKEN_NONE)) failNode(ag, node, "assembly expression with no output must be marked volatile"); } if (full->outputs.len >= 16) failNode(ag, node, "too many asm outputs"); AsmOperand outputs[15]; uint32_t output_type_bits = 0; for (uint32_t i = 0; i < full->outputs.len; i++) { const AstNodeIndex output_node = full->outputs.ptr[i]; const AstTokenIndex symbolic_name = astNodeMainToken(tree, output_node); const uint32_t name = identAsString(ag, symbolic_name); const AstTokenIndex constraint_token = symbolic_name + 2; uint32_t constraint_len; const uint32_t constraint = strLitAsString(ag, constraint_token, &constraint_len); const bool has_arrow = astTokenTag(tree, symbolic_name + 4) == TOKEN_ARROW; if (has_arrow) { if (output_type_bits != 0) failNode( ag, node, "inline assembly allows up to one output value"); output_type_bits |= (uint32_t)1 << i; const AstNodeIndex out_type_node = astNodeData(tree, output_node).lhs; const ZirInstRef out_type_inst = typeExpr(gz, scope, out_type_node); outputs[i].name = name; outputs[i].constraint = constraint; outputs[i].operand = out_type_inst; } else { const AstTokenIndex ident_token = symbolic_name + 4; outputs[i].name = name; outputs[i].constraint = constraint; outputs[i].operand = localVarRef(gz, scope, ref_ri, node, ident_token); } } if (full->inputs.len >= 32) failNode(ag, node, "too many asm inputs"); AsmOperand inputs[31]; for (uint32_t i = 0; i < full->inputs.len; i++) { const AstNodeIndex input_node = full->inputs.ptr[i]; const AstTokenIndex symbolic_name = astNodeMainToken(tree, input_node); const uint32_t name = identAsString(ag, symbolic_name); const AstTokenIndex constraint_token = symbolic_name + 2; uint32_t constraint_len; const uint32_t constraint = strLitAsString(ag, constraint_token, &constraint_len); const ZirInstRef operand = expr(gz, scope, none_ri, astNodeData(tree, input_node).lhs); inputs[i].name = name; inputs[i].constraint = constraint; inputs[i].operand = operand; } ZirInstRef clobbers = ZIR_REF_NONE; if (full->ast.clobbers != AST_NODE_NONE) { const ZirInstRef clobbers_ty = gzAddStdLangValue( gz, full->ast.clobbers, STD_LANG_VALUE_CLOBBERS); ResultInfo clobber_ri = { { RL_COERCED_TY, clobbers_ty, 0, 0, NULL, 0, 0 }, 0 }; clobbers = comptimeExpr( gz, scope, clobber_ri, full->ast.clobbers, REASON_CLOBBER); } const ZirInstRef result = gzAddAsm(gz, tag, node, tmpl, output_type_bits, (full->volatile_token != AST_TOKEN_NONE), outputs, full->outputs.len, inputs, full->inputs.len, clobbers); return rvalue(gz, ri, result, node); } // --- array/struct init lowering --- // AstGen.zig:arrayInitExprAnon // Uses an `array_init_anon` instruction. static ZirInstRef arrayInitExprAnon(GenZir* gz, Scope* scope, AstNodeIndex node, const AstNodeIndex* elements, uint32_t elements_len) { AstGen* ag = gz->astgen; const uint32_t payload_index = ag->extra_len; extraAppend(ag, elements_len); // MultiOp.operands_len const uint32_t extra_start = reserveExtra(ag, elements_len); for (uint32_t i = 0; i < elements_len; i++) { const ZirInstRef elem_ref = expr(gz, scope, none_ri, elements[i]); ag->extra[extra_start + i] = elem_ref; } return gzAddPlNodePayloadIndex( gz, ZIR_INST_ARRAY_INIT_ANON, node, payload_index); } // AstGen.zig:arrayInitExprTyped // Uses an `array_init`/`array_init_ref` instruction. static ZirInstRef arrayInitExprTyped(GenZir* gz, Scope* scope, AstNodeIndex node, const AstNodeIndex* elements, uint32_t elements_len, ZirInstRef ty_inst, ZirInstRef maybe_elem_ty_inst, bool is_ref) { AstGen* ag = gz->astgen; const uint32_t len = elements_len + 1; // +1 for type const uint32_t payload_index = ag->extra_len; extraAppend(ag, len); // MultiOp.operands_len const uint32_t extra_start = reserveExtra(ag, len); ag->extra[extra_start] = ty_inst; uint32_t extra_index = extra_start + 1; if (maybe_elem_ty_inst != ZIR_REF_NONE) { ResultInfo elem_ri = { { RL_COERCED_TY, maybe_elem_ty_inst, 0, 0, NULL, 0, 0 }, 0 }; for (uint32_t i = 0; i < elements_len; i++) { const ZirInstRef elem_inst = expr(gz, scope, elem_ri, elements[i]); ag->extra[extra_index++] = elem_inst; } } else { for (uint32_t i = 0; i < elements_len; i++) { ZirInstData data; memset(&data, 0, sizeof(data)); data.bin.lhs = ty_inst; data.bin.rhs = (ZirInstRef)i; const ZirInstRef this_elem_ty = gzAdd(gz, ZIR_INST_ARRAY_INIT_ELEM_TYPE, data); ResultInfo ri = { { RL_COERCED_TY, this_elem_ty, 0, 0, NULL, 0, 0 }, 0 }; const ZirInstRef elem_inst = expr(gz, scope, ri, elements[i]); ag->extra[extra_index++] = elem_inst; } } const ZirInstTag tag = is_ref ? ZIR_INST_ARRAY_INIT_REF : ZIR_INST_ARRAY_INIT; return gzAddPlNodePayloadIndex(gz, tag, node, payload_index); } // arrayInitExprPtr: initialize an array directly through a pointer (RL_PTR). // Mirrors AstGen.zig:arrayInitExprPtr. static void arrayInitExprPtr(GenZir* gz, Scope* scope, AstNodeIndex node, const AstNodeIndex* elements, uint32_t elements_len, ZirInstRef ptr_inst) { AstGen* ag = gz->astgen; // opt_eu_base_ptr_init: unwrap optional/error-union base from ptr. const ZirInstRef array_ptr_inst = gzAddUnNode(gz, ZIR_INST_OPT_EU_BASE_PTR_INIT, ptr_inst, node); // Block payload: { body_len } followed by body_len elem ptr indices. const uint32_t payload_index = ag->extra_len; extraAppend(ag, elements_len); // Block.body_len const uint32_t extra_start = reserveExtra(ag, elements_len); for (uint32_t i = 0; i < elements_len; i++) { // array_init_elem_ptr: ElemPtrImm { ptr, index }. const AstNodeIndex elem_init = elements[i]; const uint32_t elem_words[2] = { array_ptr_inst, i }; const ZirInstRef elem_ptr_inst = gzAddPlNodeExtra( gz, ZIR_INST_ARRAY_INIT_ELEM_PTR, elem_init, elem_words, 2); ag->extra[extra_start + i] = (uint32_t)(elem_ptr_inst - ZIR_REF_START_INDEX); ResultInfo elem_ri; memset(&elem_ri, 0, sizeof(elem_ri)); elem_ri.rl.tag = RL_PTR; elem_ri.rl.inst = elem_ptr_inst; expr(gz, scope, elem_ri, elem_init); } gzAddPlNodePayloadIndex( gz, ZIR_INST_VALIDATE_PTR_ARRAY_INIT, node, payload_index); } // AstGen.zig:arrayInitExpr static ZirInstRef arrayInitExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, const AstFullArrayInit* array_init) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; assert(array_init->ast.elements.len != 0); ZirInstRef array_ty = ZIR_REF_NONE; ZirInstRef elem_ty = ZIR_REF_NONE; if (array_init->ast.type_expr != AST_NODE_NONE) { const AstNodeIndex type_expr = array_init->ast.type_expr; AstFullArrayType at; bool is_inferred = false; if (astFullArrayType(tree, type_expr, &at)) { if (astNodeTag(tree, at.ast.elem_count) == AST_NODE_IDENTIFIER) { if (tokenIsUnderscore( tree, astNodeMainToken(tree, at.ast.elem_count))) is_inferred = true; } } if (is_inferred) { const ZirInstRef len_inst = gzAddInt(gz, array_init->ast.elements.len); const SourceCursor saved_cursor = saveSourceCursor(ag); const ZirInstRef elem_type = typeExpr(gz, scope, at.ast.elem_type); if (at.ast.sentinel == AST_NODE_NONE) { array_ty = gzAddPlNodeBin( gz, ZIR_INST_ARRAY_TYPE, type_expr, len_inst, elem_type); } else { restoreSourceCursor(ag, saved_cursor); // Inferred-len array with sentinel: [_:sentinel]T{...} // Mirrors AstGen.zig arrayInitExpr infer block (line // 1471-1482). // Payload: ArrayTypeSentinel { len, sentinel, elem_type } // (Zir.zig: sentinel is second, elem_type third). const ResultInfo sent_ri = { { RL_TY, elem_type, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef sentinel = comptimeExpr(gz, scope, sent_ri, at.ast.sentinel, REASON_ARRAY_SENTINEL); const uint32_t words[3] = { len_inst, sentinel, elem_type }; array_ty = gzAddPlNodeExtra( gz, ZIR_INST_ARRAY_TYPE_SENTINEL, type_expr, words, 3); } elem_ty = elem_type; } else { const ZirInstRef array_type_inst = typeExpr(gz, scope, type_expr); const uint32_t words[2] = { array_type_inst, array_init->ast.elements.len }; gzAddPlNodeExtra( gz, ZIR_INST_VALIDATE_ARRAY_INIT_TY, node, words, 2); array_ty = array_type_inst; elem_ty = ZIR_REF_NONE; } } if (array_ty != ZIR_REF_NONE) { // Typed inits do not use RLS for language simplicity. switch (ri.rl.tag) { case RL_DISCARD: { // Mirrors AstGen.zig:arrayInitExpr .discard branch (typed). if (elem_ty != ZIR_REF_NONE) { const ResultInfo elem_ri = { { RL_TY, elem_ty, 0, 0, NULL, 0, 0 }, 0 }; for (uint32_t i = 0; i < array_init->ast.elements.len; i++) expr(gz, scope, elem_ri, array_init->ast.elements.ptr[i]); } else { for (uint32_t i = 0; i < array_init->ast.elements.len; i++) { ZirInstData data; memset(&data, 0, sizeof(data)); data.bin.lhs = array_ty; data.bin.rhs = (ZirInstRef)i; const ZirInstRef this_elem_ty = gzAdd(gz, ZIR_INST_ARRAY_INIT_ELEM_TYPE, data); const ResultInfo elem_ri = { { RL_TY, this_elem_ty, 0, 0, NULL, 0, 0 }, 0 }; expr(gz, scope, elem_ri, array_init->ast.elements.ptr[i]); } } return ZIR_REF_VOID_VALUE; } case RL_REF: case RL_REF_CONST: return arrayInitExprTyped(gz, scope, node, array_init->ast.elements.ptr, array_init->ast.elements.len, array_ty, elem_ty, true); default: { const ZirInstRef array_inst = arrayInitExprTyped(gz, scope, node, array_init->ast.elements.ptr, array_init->ast.elements.len, array_ty, elem_ty, false); return rvalue(gz, ri, array_inst, node); } } } switch (ri.rl.tag) { case RL_NONE: return arrayInitExprAnon(gz, scope, node, array_init->ast.elements.ptr, array_init->ast.elements.len); case RL_DISCARD: { // Mirrors AstGen.zig:arrayInitExpr .discard branch (untyped). for (uint32_t i = 0; i < array_init->ast.elements.len; i++) expr(gz, scope, discard_ri, array_init->ast.elements.ptr[i]); return ZIR_REF_VOID_VALUE; } case RL_REF: case RL_REF_CONST: { const ZirInstRef result = arrayInitExprAnon(gz, scope, node, array_init->ast.elements.ptr, array_init->ast.elements.len); return gzAddUnTok(gz, ZIR_INST_REF, result, astFirstToken(tree, node)); } case RL_TY: case RL_COERCED_TY: { const uint32_t words[2] = { ri.rl.ty, array_init->ast.elements.len }; gzAddPlNodeExtra( gz, ZIR_INST_VALIDATE_ARRAY_INIT_RESULT_TY, node, words, 2); return arrayInitExprTyped(gz, scope, node, array_init->ast.elements.ptr, array_init->ast.elements.len, ri.rl.ty, ZIR_REF_NONE, false); } case RL_INFERRED_PTR: { // Can't get elem pointers of untyped inferred alloc; do anon init + // rvalue. const ZirInstRef result = arrayInitExprAnon(gz, scope, node, array_init->ast.elements.ptr, array_init->ast.elements.len); return rvalue(gz, ri, result, node); } case RL_PTR: { arrayInitExprPtr(gz, scope, node, array_init->ast.elements.ptr, array_init->ast.elements.len, ri.rl.inst); return ZIR_REF_VOID_VALUE; } case RL_REF_COERCED_TY: { // validate_array_init_ref_ty then typed init into a ref. const uint32_t words[2] = { ri.rl.ty, array_init->ast.elements.len }; const ZirInstRef dest_arr_ty_inst = gzAddPlNodeExtra( gz, ZIR_INST_VALIDATE_ARRAY_INIT_REF_TY, node, words, 2); return arrayInitExprTyped(gz, scope, node, array_init->ast.elements.ptr, array_init->ast.elements.len, dest_arr_ty_inst, ZIR_REF_NONE, true); } case RL_DESTRUCTURE: { // Untyped init - destructure directly into result pointers. // Mirrors AstGen.zig:arrayInitExpr .destructure case. // Components are in ri.rl (stored inline to avoid nesting clobber). const uint32_t n = ri.rl.ds_components_len; const DestructureComponent* components = ri.rl.ds_components; if (array_init->ast.elements.len != n) failNode(gz->astgen, node, "expected %u elements for destructure, found %u", n, array_init->ast.elements.len); for (uint32_t i = 0; i < n; i++) { const DestructureComponent* comp = &components[i]; ResultInfo elem_ri; memset(&elem_ri, 0, sizeof(elem_ri)); switch (comp->tag) { case DC_TYPED_PTR: elem_ri.rl.tag = RL_PTR; elem_ri.rl.inst = comp->inst; elem_ri.rl.src_node = comp->src_node; break; case DC_INFERRED_PTR: elem_ri.rl.tag = RL_INFERRED_PTR; elem_ri.rl.inst = comp->inst; break; case DC_DISCARD: elem_ri.rl.tag = RL_DISCARD; break; } expr(gz, scope, elem_ri, array_init->ast.elements.ptr[i]); } return ZIR_REF_VOID_VALUE; } } fprintf(stderr, "arrayInitExpr: invalid rl.tag=%d\n", (int)ri.rl.tag); UNREACHABLE("arrayInitExpr: invalid result location tag"); ZIG1_TCC_RETURN(ZIR_REF_NONE); } // AstGen.zig:structInitExprAnon // Uses a `struct_init_anon` instruction. static ZirInstRef structInitExprAnon(GenZir* gz, Scope* scope, AstNodeIndex node, const AstFullStructInit* struct_init) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; const uint32_t payload_index = ag->extra_len; extraAppend(ag, node); // StructInitAnon.abs_node extraAppend(ag, ag->source_line); // StructInitAnon.src_line extraAppend(ag, ag->source_column); // StructInitAnon.src_column extraAppend(ag, struct_init->ast.fields.len); // fields_len const uint32_t field_size = 2; // Item { field_name, init } const uint32_t extra_start = reserveExtra(ag, struct_init->ast.fields.len * field_size); uint32_t extra_index = extra_start; for (uint32_t i = 0; i < struct_init->ast.fields.len; i++) { const AstNodeIndex field_init = struct_init->ast.fields.ptr[i]; const AstTokenIndex name_token = astFirstToken(tree, field_init) - 2; const uint32_t str_index = identAsString(ag, name_token); ag->extra[extra_index] = str_index; // field_name const ZirInstRef init = expr(gz, scope, none_ri, field_init); ag->extra[extra_index + 1] = init; // init extra_index += field_size; } return gzAddPlNodePayloadIndex( gz, ZIR_INST_STRUCT_INIT_ANON, node, payload_index); } // AstGen.zig:structInitExprTyped // Uses a `struct_init`/`struct_init_ref` instruction. static ZirInstRef structInitExprTyped(GenZir* gz, Scope* scope, AstNodeIndex node, const AstFullStructInit* struct_init, ZirInstRef ty_inst, bool is_ref) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; const uint32_t payload_index = ag->extra_len; extraAppend(ag, node); // StructInit.abs_node extraAppend(ag, ag->source_line); // StructInit.src_line extraAppend(ag, ag->source_column); // StructInit.src_column extraAppend(ag, struct_init->ast.fields.len); // fields_len const uint32_t field_size = 2; // Item { field_type, init } const uint32_t extra_start = reserveExtra(ag, struct_init->ast.fields.len * field_size); uint32_t extra_index = extra_start; for (uint32_t i = 0; i < struct_init->ast.fields.len; i++) { const AstNodeIndex field_init = struct_init->ast.fields.ptr[i]; const AstTokenIndex name_token = astFirstToken(tree, field_init) - 2; const uint32_t str_index = identAsString(ag, name_token); const uint32_t words[2] = { ty_inst, str_index }; const ZirInstRef field_ty_inst = gzAddPlNodeExtra( gz, ZIR_INST_STRUCT_INIT_FIELD_TYPE, field_init, words, 2); ag->extra[extra_index] = field_ty_inst - ZIR_REF_START_INDEX; ResultInfo field_ri = { { RL_COERCED_TY, field_ty_inst, 0, 0, NULL, 0, 0 }, 0 }; const ZirInstRef init = expr(gz, scope, field_ri, field_init); ag->extra[extra_index + 1] = init; extra_index += field_size; } const ZirInstTag tag = is_ref ? ZIR_INST_STRUCT_INIT_REF : ZIR_INST_STRUCT_INIT; return gzAddPlNodePayloadIndex(gz, tag, node, payload_index); } // AstGen.zig:structInitExprPtr // Struct initialization using field pointers (`.ptr` RL). static void structInitExprPtr(GenZir* gz, Scope* scope, AstNodeIndex node, const AstFullStructInit* struct_init, ZirInstRef ptr_inst) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; const ZirInstRef struct_ptr_inst = gzAddUnNode(gz, ZIR_INST_OPT_EU_BASE_PTR_INIT, ptr_inst, node); const uint32_t payload_index = ag->extra_len; extraAppend(ag, struct_init->ast.fields.len); // Block.body_len const uint32_t extra_start = reserveExtra(ag, struct_init->ast.fields.len); uint32_t extra_index = extra_start; for (uint32_t i = 0; i < struct_init->ast.fields.len; i++) { const AstNodeIndex field_init = struct_init->ast.fields.ptr[i]; const AstTokenIndex name_token = astFirstToken(tree, field_init) - 2; const uint32_t str_index = identAsString(ag, name_token); const uint32_t words[2] = { struct_ptr_inst, str_index }; const ZirInstRef field_ptr = gzAddPlNodeExtra( gz, ZIR_INST_STRUCT_INIT_FIELD_PTR, field_init, words, 2); ag->extra[extra_index] = field_ptr - ZIR_REF_START_INDEX; extra_index += 1; ResultInfo field_ri = { { RL_PTR, 0, field_ptr, 0, NULL, 0, 0 }, 0 }; expr(gz, scope, field_ri, field_init); } gzAddPlNodePayloadIndex( gz, ZIR_INST_VALIDATE_PTR_STRUCT_INIT, node, payload_index); } // AstGen.zig:structInitExpr static ZirInstRef structInitExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, const AstFullStructInit* struct_init) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; if (struct_init->ast.type_expr == AST_NODE_NONE) { if (struct_init->ast.fields.len == 0) { // Anonymous init with no fields. switch (ri.rl.tag) { case RL_DISCARD: return ZIR_REF_VOID_VALUE; case RL_REF_COERCED_TY: return gzAddUnNode( gz, ZIR_INST_STRUCT_INIT_EMPTY_REF_RESULT, ri.rl.ty, node); case RL_TY: case RL_COERCED_TY: return gzAddUnNode( gz, ZIR_INST_STRUCT_INIT_EMPTY_RESULT, ri.rl.ty, node); case RL_NONE: case RL_REF: case RL_REF_CONST: case RL_INFERRED_PTR: return rvalue(gz, ri, ZIR_REF_EMPTY_TUPLE, node); case RL_PTR: { // AstGen.zig:structInitExpr .ptr case: get elem type of ptr, // emit struct_init_empty_result, then rvalue to store. const ZirInstRef ty_inst = resultLocType(gz, ri, node); const ZirInstRef val = gzAddUnNode( gz, ZIR_INST_STRUCT_INIT_EMPTY_RESULT, ty_inst, node); return rvalue(gz, ri, val, node); } default: ASTGEN_UNIMPLEMENTED( "structInitExpr: empty init result location"); } } } else { // Typed with an array type_expr => "initializing array with struct // syntax" (error) or empty struct_init. Handle the empty-struct case // that fullArrayType does not match by falling through below. const AstNodeIndex type_expr = struct_init->ast.type_expr; AstFullArrayType at; if (!astFullArrayType(tree, type_expr, &at)) { if (struct_init->ast.fields.len == 0) { const ZirInstRef ty_inst = typeExpr(gz, scope, type_expr); const ZirInstRef result = gzAddUnNode( gz, ZIR_INST_STRUCT_INIT_EMPTY, ty_inst, node); return rvalue(gz, ri, result, node); } // fall through to typed struct init below } else { if (struct_init->ast.fields.len == 0) { // e.g. `[_]T{}` or `[N]T{}`: empty array init. // is_inferred_array_len: elem_count is `.identifier` with text // "_". const AstNodeIndex ec = at.ast.elem_count; bool is_inferred = false; if (astNodeTag(tree, ec) == AST_NODE_IDENTIFIER) { is_inferred = tokenIsUnderscore(tree, astNodeMainToken(tree, ec)); } ZirInstRef array_type_inst; if (is_inferred) { // Only compute elem_type explicitly for inferred-len // arrays (mirrors AstGen.zig; for known len, typeExpr // on the full array type node generates elem+array // together). const SourceCursor saved_cursor = saveSourceCursor(ag); const ZirInstRef elem_type_inst = typeExpr(gz, scope, at.ast.elem_type); if (at.ast.sentinel == AST_NODE_NONE) { array_type_inst = gzAddPlNodeBin(gz, ZIR_INST_ARRAY_TYPE, type_expr, ZIR_REF_ZERO_USIZE, elem_type_inst); } else { restoreSourceCursor(ag, saved_cursor); // Sentinel case: len=zero_usize, sentinel, elem_type // Mirrors AstGen.zig structInitExpr: uses // .ty (not .coerced_ty) so rvalue emits as_node for // the sentinel coercion. const ResultInfo elem_ri = { { RL_TY, elem_type_inst, 0, 0, NULL, 0, 0 }, 0 }; const ZirInstRef sentinel_inst = comptimeExpr(gz, scope, elem_ri, at.ast.sentinel, REASON_ARRAY_SENTINEL); const uint32_t words[3] = { (uint32_t)ZIR_REF_ZERO_USIZE, (uint32_t)sentinel_inst, (uint32_t)elem_type_inst }; array_type_inst = gzAddPlNodeExtra(gz, ZIR_INST_ARRAY_TYPE_SENTINEL, type_expr, words, 3); } } else { // Known length: typeExpr on the full array type node // generates both elem_type and array_type in one shot // (mirrors AstGen.zig). array_type_inst = typeExpr(gz, scope, type_expr); } const ZirInstRef result = gzAddUnNode( gz, ZIR_INST_STRUCT_INIT_EMPTY, array_type_inst, node); return rvalue(gz, ri, result, node); } else { failNode( ag, type_expr, "initializing array with struct syntax"); } } } // Duplicate field-name detection: the error itself is not reported (zig1 // accepts programs upstream rejects), but upstream interns every field // name here, before any field values are lowered. This ordering is // observable in string_bytes, so it must be reproduced. for (uint32_t i = 0; i < struct_init->ast.fields.len; i++) { const AstNodeIndex field_init = struct_init->ast.fields.ptr[i]; const AstTokenIndex name_token = astFirstToken(tree, field_init) - 2; identAsString(ag, name_token); } if (struct_init->ast.type_expr != AST_NODE_NONE) { // Typed inits do not use RLS for language simplicity. const ZirInstRef ty_inst = typeExpr(gz, scope, struct_init->ast.type_expr); gzAddUnNode(gz, ZIR_INST_VALIDATE_STRUCT_INIT_TY, ty_inst, node); switch (ri.rl.tag) { case RL_REF: case RL_REF_CONST: return structInitExprTyped( gz, scope, node, struct_init, ty_inst, true); default: { const ZirInstRef struct_inst = structInitExprTyped( gz, scope, node, struct_init, ty_inst, false); return rvalue(gz, ri, struct_inst, node); } } } switch (ri.rl.tag) { case RL_NONE: return structInitExprAnon(gz, scope, node, struct_init); case RL_REF: case RL_REF_CONST: { const ZirInstRef result = structInitExprAnon(gz, scope, node, struct_init); return gzAddUnTok(gz, ZIR_INST_REF, result, astFirstToken(tree, node)); } case RL_REF_COERCED_TY: { const ZirInstRef result_ty_inst = gzAddUnNode(gz, ZIR_INST_ELEM_TYPE, ri.rl.ty, node); gzAddUnNode( gz, ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY, result_ty_inst, node); return structInitExprTyped( gz, scope, node, struct_init, result_ty_inst, true); } case RL_TY: case RL_COERCED_TY: gzAddUnNode( gz, ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY, ri.rl.ty, node); return structInitExprTyped( gz, scope, node, struct_init, ri.rl.ty, false); case RL_DISCARD: { // Even if discarding we must perform side-effects. for (uint32_t i = 0; i < struct_init->ast.fields.len; i++) expr(gz, scope, discard_ri, struct_init->ast.fields.ptr[i]); return ZIR_REF_VOID_VALUE; } case RL_PTR: structInitExprPtr(gz, scope, node, struct_init, ri.rl.inst); return ZIR_REF_VOID_VALUE; case RL_INFERRED_PTR: { // We can't get field pointers of an untyped inferred alloc, so must // perform a standard anonymous init followed by an rvalue store. const ZirInstRef struct_inst = structInitExprAnon(gz, scope, node, struct_init); return rvalue(gz, ri, struct_inst, node); } default: ASTGEN_UNIMPLEMENTED("structInitExpr: result location not yet ported"); } ZIG1_TCC_RETURN(ZIR_REF_NONE); } // --- expr --- // AstGen.zig:expr static ZirInstRef expr( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node) { const Ast* tree = gz->astgen->tree; switch (astNodeTag(tree, node)) { case AST_NODE_IDENTIFIER: return identifier(gz, scope, ri, node); case AST_NODE_NUMBER_LITERAL: return numberLiteral(gz, ri, node, node, SIGN_POSITIVE); case AST_NODE_CHAR_LITERAL: return charLiteral(gz, ri, node); case AST_NODE_STRING_LITERAL: return stringLiteral(gz, ri, node); case AST_NODE_MULTILINE_STRING_LITERAL: return multilineStringLiteral(gz, ri, node); case AST_NODE_ENUM_LITERAL: { const ZirInstRef res_ty = resultLocType(gz, ri, node); if (res_ty == ZIR_REF_NONE) return simpleStrTok(gz, ri, astNodeMainToken(tree, node), node, ZIR_INST_ENUM_LITERAL); const uint32_t str_index = identAsString(gz->astgen, astNodeMainToken(tree, node)); // Zir.Inst.Field. const uint32_t words[2] = { res_ty, str_index }; const ZirInstRef res = gzAddPlNodeExtra(gz, ZIR_INST_DECL_LITERAL, node, words, 2); switch (ri.rl.tag) { case RL_TY: case RL_COERCED_TY: return res; // `decl_literal` does the coercion for us case RL_REF_COERCED_TY: case RL_PTR: return rvalue(gz, ri, res, node); default: UNREACHABLE("enum_literal: no result type"); } } case AST_NODE_ERROR_VALUE: return simpleStrTok(gz, ri, astNodeMainToken(tree, node) + 2, node, ZIR_INST_ERROR_VALUE); case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: case AST_NODE_BUILTIN_CALL: case AST_NODE_BUILTIN_CALL_COMMA: { AstNodeIndex buf[2]; AstNodeSlice params; astBuiltinCallParams(tree, buf, node, ¶ms); return builtinCall( gz, scope, ri, node, params, false, ZIR_NAME_STRATEGY_ANON); } case AST_NODE_GROUPED_EXPRESSION: return expr(gz, scope, ri, astNodeData(tree, node).lhs); case AST_NODE_ADD: return simpleBinOp(gz, scope, ri, node, ZIR_INST_ADD); case AST_NODE_SUB: return simpleBinOp(gz, scope, ri, node, ZIR_INST_SUB); case AST_NODE_MUL: return simpleBinOp(gz, scope, ri, node, ZIR_INST_MUL); case AST_NODE_ADD_WRAP: return simpleBinOp(gz, scope, ri, node, ZIR_INST_ADDWRAP); case AST_NODE_SUB_WRAP: return simpleBinOp(gz, scope, ri, node, ZIR_INST_SUBWRAP); case AST_NODE_MUL_WRAP: return simpleBinOp(gz, scope, ri, node, ZIR_INST_MULWRAP); case AST_NODE_ADD_SAT: return simpleBinOp(gz, scope, ri, node, ZIR_INST_ADD_SAT); case AST_NODE_SUB_SAT: return simpleBinOp(gz, scope, ri, node, ZIR_INST_SUB_SAT); case AST_NODE_MUL_SAT: return simpleBinOp(gz, scope, ri, node, ZIR_INST_MUL_SAT); case AST_NODE_SHL_SAT: return simpleBinOp(gz, scope, ri, node, ZIR_INST_SHL_SAT); case AST_NODE_DIV: return simpleBinOp(gz, scope, ri, node, ZIR_INST_DIV); case AST_NODE_MOD: return simpleBinOp(gz, scope, ri, node, ZIR_INST_MOD_REM); case AST_NODE_BIT_AND: return simpleBinOp(gz, scope, ri, node, ZIR_INST_BIT_AND); case AST_NODE_BIT_OR: return simpleBinOp(gz, scope, ri, node, ZIR_INST_BIT_OR); case AST_NODE_BIT_XOR: return simpleBinOp(gz, scope, ri, node, ZIR_INST_XOR); case AST_NODE_SHL: return shiftOp(gz, scope, ri, node, astNodeData(tree, node).lhs, astNodeData(tree, node).rhs, ZIR_INST_SHL); case AST_NODE_SHR: return shiftOp(gz, scope, ri, node, astNodeData(tree, node).lhs, astNodeData(tree, node).rhs, ZIR_INST_SHR); case AST_NODE_BANG_EQUAL: return simpleBinOp(gz, scope, ri, node, ZIR_INST_CMP_NEQ); case AST_NODE_EQUAL_EQUAL: return simpleBinOp(gz, scope, ri, node, ZIR_INST_CMP_EQ); case AST_NODE_GREATER_THAN: return simpleBinOp(gz, scope, ri, node, ZIR_INST_CMP_GT); case AST_NODE_GREATER_OR_EQUAL: return simpleBinOp(gz, scope, ri, node, ZIR_INST_CMP_GTE); case AST_NODE_LESS_THAN: return simpleBinOp(gz, scope, ri, node, ZIR_INST_CMP_LT); case AST_NODE_LESS_OR_EQUAL: return simpleBinOp(gz, scope, ri, node, ZIR_INST_CMP_LTE); case AST_NODE_ARRAY_CAT: return simpleBinOp(gz, scope, ri, node, ZIR_INST_ARRAY_CAT); case AST_NODE_RETURN: return ret(gz, scope, node); case AST_NODE_TRY: return tryExpr(gz, scope, ri, node); case AST_NODE_IF_SIMPLE: case AST_NODE_IF: { AstFullIf if_full; astFullIf(tree, node, &if_full); AstFullSwitch switch_full = { 0 }; if (if_full.error_token != AST_TOKEN_NONE && switchOnErrCapture(tree, if_full.ast.else_expr, if_full.error_token, false, &switch_full)) return switchExpr(gz, scope, riBr(ri), node, switch_full, (SwitchNonErr) { .tag = SWITCH_NON_ERR_IF, .if_full = if_full }); return ifExpr(gz, scope, riBr(ri), node); } case AST_NODE_PTR_TYPE_ALIGNED: case AST_NODE_PTR_TYPE_SENTINEL: case AST_NODE_PTR_TYPE: case AST_NODE_PTR_TYPE_BIT_RANGE: { AstFullPtrType ptr_info; astFullPtrType(tree, node, &ptr_info); return ptrType(gz, scope, ri, node, ptr_info); } case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: { AstNodeIndex buf[2]; AstFullContainerDecl container_decl; astFullContainerDecl(tree, buf, node, &container_decl); return containerDecl( gz, scope, ri, node, container_decl, ZIR_NAME_STRATEGY_ANON); } case AST_NODE_FIELD_ACCESS: return fieldAccess(gz, scope, ri, node); case AST_NODE_CALL_ONE: case AST_NODE_CALL_ONE_COMMA: case AST_NODE_CALL: case AST_NODE_CALL_COMMA: return callExpr(gz, scope, ri, ZIR_REF_NONE, node); case AST_NODE_SLICE_OPEN: case AST_NODE_SLICE: case AST_NODE_SLICE_SENTINEL: return sliceExpr(gz, scope, ri, node); case AST_NODE_DEREF: { const ZirInstRef lhs = expr(gz, scope, none_ri, astNodeData(tree, node).lhs); if (ri.rl.tag == RL_REF || ri.rl.tag == RL_REF_CONST || ri.rl.tag == RL_REF_COERCED_TY) return gzAddUnNode(gz, ZIR_INST_REF_DEREF, lhs, node); const ZirInstRef result = gzAddUnNode(gz, ZIR_INST_DEREF, lhs, node); return rvalue(gz, ri, result, node); } case AST_NODE_UNWRAP_OPTIONAL: { // `expr.?` — mirrors AstGen's unwrap_optional handler. // main_token is '.' (dot) in both Zig and C parsers; Zig uses // maybeAdvanceSourceCursorToMainToken which advances to main_token. const AstNodeIndex lhs_node = astNodeData(tree, node).lhs; const AstTokenIndex dot_tok = astNodeMainToken(tree, node); if (ri.rl.tag == RL_REF || ri.rl.tag == RL_REF_CONST || ri.rl.tag == RL_REF_COERCED_TY) { const ZirInstRef lhs = expr(gz, scope, ri.rl.tag == RL_REF_CONST ? ref_const_ri : ref_ri, lhs_node); LineColumn cursor; if (!gz->is_comptime) { advanceSourceCursor(gz->astgen, astTokenStart(tree, dot_tok)); cursor = (LineColumn) { gz->astgen->source_line - gz->decl_line, gz->astgen->source_column, }; } else { cursor = (LineColumn) { gz->astgen->source_line - gz->decl_line, gz->astgen->source_column, }; } emitDbgStmt(gz, cursor.line, cursor.column); return gzAddUnNode( gz, ZIR_INST_OPTIONAL_PAYLOAD_SAFE_PTR, lhs, node); } const ZirInstRef lhs = expr(gz, scope, none_ri, lhs_node); LineColumn cursor; if (!gz->is_comptime) { advanceSourceCursor(gz->astgen, astTokenStart(tree, dot_tok)); cursor = (LineColumn) { gz->astgen->source_line - gz->decl_line, gz->astgen->source_column, }; } else { cursor = (LineColumn) { gz->astgen->source_line - gz->decl_line, gz->astgen->source_column, }; } emitDbgStmt(gz, cursor.line, cursor.column); const ZirInstRef result = gzAddUnNode(gz, ZIR_INST_OPTIONAL_PAYLOAD_SAFE, lhs, node); return rvalue(gz, ri, result, node); } case AST_NODE_ARRAY_ACCESS: return arrayAccess(gz, scope, ri, node); case AST_NODE_ARRAY_TYPE: return arrayType(gz, scope, ri, node); case AST_NODE_ARRAY_TYPE_SENTINEL: return arrayTypeSentinel(gz, scope, ri, node); case AST_NODE_ADDRESS_OF: { const ZirInstRef res_ty_inst = resultLocType(gz, ri, node); ResultInfo operand_ri; if (res_ty_inst != ZIR_REF_NONE) { gzAddUnTok(gz, ZIR_INST_VALIDATE_REF_TY, res_ty_inst, astFirstToken(tree, node)); operand_ri = (ResultInfo) { { RL_REF_COERCED_TY, res_ty_inst, 0, 0, NULL, 0, 0 }, ri.ctx == CTX_RETURN ? CTX_RETURN_ADDROF : CTX_NONE }; } else { operand_ri = (ResultInfo) { { RL_REF, 0, 0, 0, NULL, 0, 0 }, ri.ctx == CTX_RETURN ? CTX_RETURN_ADDROF : CTX_NONE }; } const AstNodeIndex operand_node = astNodeData(tree, node).lhs; const ZirInstRef result = expr(gz, scope, operand_ri, operand_node); return rvalue(gz, ri, result, node); } case AST_NODE_BOOL_AND: return boolBinOp(gz, scope, ri, node, ZIR_INST_BOOL_BR_AND); case AST_NODE_BOOL_OR: return boolBinOp(gz, scope, ri, node, ZIR_INST_BOOL_BR_OR); case AST_NODE_NEGATION: return negation(gz, scope, ri, node); case AST_NODE_NEGATION_WRAP: { const ZirInstRef operand = expr(gz, scope, none_ri, astNodeData(tree, node).lhs); const ZirInstRef result = gzAddUnNode(gz, ZIR_INST_NEGATE_WRAP, operand, node); return rvalue(gz, ri, result, node); } case AST_NODE_BIT_NOT: { // simpleUnOp: operand_ri=none, tag=bit_not maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef operand = expr(gz, scope, none_ri, astNodeData(tree, node).lhs); const ZirInstRef result = gzAddUnNode(gz, ZIR_INST_BIT_NOT, operand, node); return rvalue(gz, ri, result, node); } case AST_NODE_BOOL_NOT: { // simpleUnOp: operand_ri=none, tag=bool_not maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef operand = expr(gz, scope, none_ri, astNodeData(tree, node).lhs); const ZirInstRef result = gzAddUnNode(gz, ZIR_INST_BOOL_NOT, operand, node); return rvalue(gz, ri, result, node); } case AST_NODE_OPTIONAL_TYPE: { const ZirInstRef operand = typeExpr(gz, scope, astNodeData(tree, node).lhs); const ZirInstRef result = gzAddUnNode(gz, ZIR_INST_OPTIONAL_TYPE, operand, node); return rvalue(gz, ri, result, node); } case AST_NODE_ANYFRAME_LITERAL: { // AstGen.expr's `.anyframe_literal` arm (lib/std/zig/AstGen.zig), // which upstream places before `.anyframe_type`: a bare `anyframe` is // the same instruction with a `void` result type, not the preinterned // `.anyframe_type` ref (the commented-out line there is the version // issue 6025 would restore). const ZirInstRef result = gzAddUnNode(gz, ZIR_INST_ANYFRAME_TYPE, ZIR_REF_VOID_TYPE, node); return rvalue(gz, ri, result, node); } case AST_NODE_ANYFRAME_TYPE: { // AstGen.expr's `.anyframe_type` arm (lib/std/zig/AstGen.zig): // const return_type = try typeExpr(gz, scope, // tree.nodeData(node).token_and_node[1]); // const result = try gz.addUnNode(.anyframe_type, return_type, // node); // return rvalue(gz, ri, result, node); // `lhs` is the `->` token, `rhs` the result-type node (parser.c's // parsePrefixTypeExpr, and lastToken's own arm above). const ZirInstRef return_type = typeExpr(gz, scope, astNodeData(tree, node).rhs); const ZirInstRef result = gzAddUnNode(gz, ZIR_INST_ANYFRAME_TYPE, return_type, node); return rvalue(gz, ri, result, node); } case AST_NODE_ASSIGN: assign(gz, scope, node); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_DESTRUCTURE: // Note that this variant does not declare any new var/const: that // variant is handled by `blockExprStmts`. assignDestructure(gz, scope, node); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SHL: assignShift(gz, scope, node, ZIR_INST_SHL); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SHR: assignShift(gz, scope, node, ZIR_INST_SHR); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_BIT_AND: assignOp(gz, scope, node, ZIR_INST_BIT_AND); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_BIT_OR: assignOp(gz, scope, node, ZIR_INST_BIT_OR); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_BIT_XOR: assignOp(gz, scope, node, ZIR_INST_XOR); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_DIV: assignOp(gz, scope, node, ZIR_INST_DIV); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SUB: assignOp(gz, scope, node, ZIR_INST_SUB); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SUB_WRAP: assignOp(gz, scope, node, ZIR_INST_SUBWRAP); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_MOD: assignOp(gz, scope, node, ZIR_INST_MOD_REM); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_ADD: assignOp(gz, scope, node, ZIR_INST_ADD); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_ADD_WRAP: assignOp(gz, scope, node, ZIR_INST_ADDWRAP); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_MUL: assignOp(gz, scope, node, ZIR_INST_MUL); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_MUL_WRAP: assignOp(gz, scope, node, ZIR_INST_MULWRAP); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_ADD_SAT: assignOp(gz, scope, node, ZIR_INST_ADD_SAT); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SUB_SAT: assignOp(gz, scope, node, ZIR_INST_SUB_SAT); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_MUL_SAT: assignOp(gz, scope, node, ZIR_INST_MUL_SAT); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SHL_SAT: assignShiftSat(gz, scope, node); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); case AST_NODE_ARRAY_INIT_ONE: case AST_NODE_ARRAY_INIT_ONE_COMMA: case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: case AST_NODE_ARRAY_INIT_DOT: case AST_NODE_ARRAY_INIT_DOT_COMMA: case AST_NODE_ARRAY_INIT: case AST_NODE_ARRAY_INIT_COMMA: { AstNodeIndex buf[2]; AstFullArrayInit array_init; if (!astFullArrayInit(tree, buf, node, &array_init)) ASTGEN_UNIMPLEMENTED("expr: fullArrayInit returned false"); return arrayInitExpr(gz, scope, ri, node, &array_init); } case AST_NODE_STRUCT_INIT_ONE: case AST_NODE_STRUCT_INIT_ONE_COMMA: case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: case AST_NODE_STRUCT_INIT_DOT: case AST_NODE_STRUCT_INIT_DOT_COMMA: case AST_NODE_STRUCT_INIT: case AST_NODE_STRUCT_INIT_COMMA: { AstNodeIndex buf[2]; AstFullStructInit struct_init; if (!astFullStructInit(tree, buf, node, &struct_init)) ASTGEN_UNIMPLEMENTED("expr: fullStructInit returned false"); return structInitExpr(gz, scope, ri, node, &struct_init); } case AST_NODE_ASM_SIMPLE: case AST_NODE_ASM: { AstFullAsm full; astFullAsm(tree, node, &full); return asmExpr(gz, scope, ri, node, &full); } case AST_NODE_SWITCH: case AST_NODE_SWITCH_COMMA: { AstFullSwitch switch_full = { 0 }; astFullSwitch(tree, node, &switch_full); return switchExpr(gz, scope, riBr(ri), node, switch_full, (SwitchNonErr) { .tag = SWITCH_NON_ERR_NONE }); } case AST_NODE_WHILE_SIMPLE: case AST_NODE_WHILE_CONT: case AST_NODE_WHILE: return whileExpr(gz, scope, riBr(ri), node, false); case AST_NODE_FOR_SIMPLE: case AST_NODE_FOR: return forExpr(gz, scope, riBr(ri), node, false); case AST_NODE_CATCH: { const AstTokenIndex catch_token = astNodeMainToken(tree, node); const AstTokenIndex payload_token = astTokenTag(tree, catch_token + 1) == TOKEN_PIPE ? catch_token + 2 : AST_TOKEN_NONE; // A labeled switch on the capture is handled in orelseCatchExpr. AstFullSwitch switch_full = { 0 }; if (payload_token != AST_TOKEN_NONE && switchOnErrCapture(tree, astNodeData(tree, node).rhs, payload_token, false, &switch_full)) return switchExpr(gz, scope, riBr(ri), node, switch_full, (SwitchNonErr) { .tag = SWITCH_NON_ERR_CATCH }); switch (ri.rl.tag) { case RL_REF: case RL_REF_CONST: case RL_REF_COERCED_TY: return orelseCatchExpr(gz, scope, ri, node, ZIR_INST_IS_NON_ERR_PTR, ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE_PTR, ZIR_INST_ERR_UNION_CODE_PTR, payload_token); default: return orelseCatchExpr(gz, scope, ri, node, ZIR_INST_IS_NON_ERR, ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE, ZIR_INST_ERR_UNION_CODE, payload_token); } } case AST_NODE_ORELSE: switch (ri.rl.tag) { case RL_REF: case RL_REF_CONST: case RL_REF_COERCED_TY: return orelseCatchExpr(gz, scope, ri, node, ZIR_INST_IS_NON_NULL_PTR, ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE_PTR, ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE_PTR, AST_TOKEN_NONE); default: return orelseCatchExpr(gz, scope, ri, node, ZIR_INST_IS_NON_NULL, ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE, ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE, AST_TOKEN_NONE); } case AST_NODE_BLOCK_TWO: case AST_NODE_BLOCK_TWO_SEMICOLON: case AST_NODE_BLOCK: case AST_NODE_BLOCK_SEMICOLON: { AstNodeIndex buf[2]; AstNodeSlice statements; astBlockStatements(tree, buf, node, &statements); return blockExpr(gz, scope, ri, node, statements.ptr, statements.len, BLOCK_KIND_NORMAL); } case AST_NODE_BREAK: return breakExpr(gz, scope, node); case AST_NODE_CONTINUE: return continueExpr(gz, scope, node); case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: { AstNodeIndex buf[1]; AstFullFnProto fn_proto; astFullFnProto(tree, buf, node, &fn_proto); return fnProtoExpr(gz, scope, ri, node, fn_proto); } case AST_NODE_ERROR_UNION: case AST_NODE_MERGE_ERROR_SETS: { // `Err!T` (error_union_type) or `A || B` (merge_error_sets). // Both operands are type expressions (reachableTypeExpr in Zig ref). const ZirInstTag inst_tag = astNodeTag(tree, node) == AST_NODE_ERROR_UNION ? ZIR_INST_ERROR_UNION_TYPE : ZIR_INST_MERGE_ERROR_SETS; const AstNodeIndex lhs_node = astNodeData(tree, node).lhs; const AstNodeIndex rhs_node = astNodeData(tree, node).rhs; const ZirInstRef lhs = reachableTypeExpr(gz, scope, lhs_node, node); const ZirInstRef rhs = reachableTypeExpr(gz, scope, rhs_node, node); const ZirInstRef result = gzAddPlNodeBin(gz, inst_tag, node, lhs, rhs); return rvalue(gz, ri, result, node); } case AST_NODE_ERROR_SET_DECL: return errorSetDecl(gz, ri, node); case AST_NODE_UNREACHABLE_LITERAL: { emitDbgNode(gz, node); ZirInstData data; memset(&data, 0, sizeof(data)); data.unreachable_data.src_node = gzNodeIndexToRelative(gz, node); gzAdd(gz, ZIR_INST_UNREACHABLE, data); return ZIR_REF_UNREACHABLE_VALUE; } case AST_NODE_COMPTIME: return comptimeExprAst(gz, scope, ri, node); default: ASTGEN_UNIMPLEMENTED("expr: unhandled node tag"); } ZIG1_TCC_RETURN(ZIR_REF_NONE); } // --- setDeclaration --- typedef struct { // src_hash is masked out by the test harness, so it is always zeroed. uint32_t src_line; uint32_t src_column; DeclKind kind; uint32_t name; // NullTerminatedString bool is_pub; bool is_threadlocal; DeclLinkage linkage; uint32_t lib_name; // NullTerminatedString; 0 = empty GenZir* type_gz; GenZir* align_gz; GenZir* addrspace_gz; GenZir* linksection_gz; GenZir* value_gz; } SetDeclArgs; // AstGen.zig:setDeclaration static void setDeclaration(uint32_t decl_inst, SetDeclArgs* args) { AstGen* ag = args->value_gz->astgen; uint32_t type_len_insts, align_len_insts, linksection_len_insts, addrspace_len_insts, value_len_insts; const uint32_t* type_body = gzInstructionsSliceUpto( args->type_gz, args->align_gz, &type_len_insts); const uint32_t* align_body = gzInstructionsSliceUpto( args->align_gz, args->addrspace_gz, &align_len_insts); const uint32_t* addrspace_body = gzInstructionsSliceUpto( args->addrspace_gz, args->linksection_gz, &addrspace_len_insts); const uint32_t* linksection_body = gzInstructionsSliceUpto( args->linksection_gz, args->value_gz, &linksection_len_insts); uint32_t value_body_len_insts; const uint32_t* value_body = gzInstructionsSlice(args->value_gz, &value_body_len_insts); value_len_insts = value_body_len_insts; const bool has_name = args->name != 0; const bool has_lib_name = args->lib_name != 0; const bool has_type_body = type_len_insts != 0; const bool has_special_body = align_len_insts != 0 || linksection_len_insts != 0 || addrspace_len_insts != 0; const bool has_value_body = value_len_insts != 0; // Any kind or linkage the switch below does not name keeps its default. DeclId id = DECL_ID_CONST_SIMPLE; switch (args->kind) { case DECL_KIND_UNNAMED_TEST: id = DECL_ID_UNNAMED_TEST; break; case DECL_KIND_TEST: id = DECL_ID_TEST; break; case DECL_KIND_DECLTEST: id = DECL_ID_DECLTEST; break; case DECL_KIND_COMPTIME: id = DECL_ID_COMPTIME; break; case DECL_KIND_CONST: switch (args->linkage) { case LINKAGE_NORMAL: if (args->is_pub) { if (has_special_body) id = DECL_ID_PUB_CONST; else if (has_type_body) id = DECL_ID_PUB_CONST_TYPED; else id = DECL_ID_PUB_CONST_SIMPLE; } else { if (has_special_body) id = DECL_ID_CONST; else if (has_type_body) id = DECL_ID_CONST_TYPED; else id = DECL_ID_CONST_SIMPLE; } break; case LINKAGE_EXTERN: if (args->is_pub) { if (has_lib_name || has_special_body) id = DECL_ID_PUB_EXTERN_CONST; else id = DECL_ID_PUB_EXTERN_CONST_SIMPLE; } else { if (has_lib_name || has_special_body) id = DECL_ID_EXTERN_CONST; else id = DECL_ID_EXTERN_CONST_SIMPLE; } break; case LINKAGE_EXPORT: id = args->is_pub ? DECL_ID_PUB_EXPORT_CONST : DECL_ID_EXPORT_CONST; break; } break; case DECL_KIND_VAR: switch (args->linkage) { case LINKAGE_NORMAL: if (args->is_pub) { if (args->is_threadlocal) id = DECL_ID_PUB_VAR_THREADLOCAL; else if (has_special_body || has_type_body) id = DECL_ID_PUB_VAR; else id = DECL_ID_PUB_VAR_SIMPLE; } else { if (args->is_threadlocal) id = DECL_ID_VAR_THREADLOCAL; else if (has_special_body || has_type_body) id = DECL_ID_VAR; else id = DECL_ID_VAR_SIMPLE; } break; case LINKAGE_EXTERN: if (args->is_pub) id = args->is_threadlocal ? DECL_ID_PUB_EXTERN_VAR_THREADLOCAL : DECL_ID_PUB_EXTERN_VAR; else id = args->is_threadlocal ? DECL_ID_EXTERN_VAR_THREADLOCAL : DECL_ID_EXTERN_VAR; break; case LINKAGE_EXPORT: if (args->is_pub) id = args->is_threadlocal ? DECL_ID_PUB_EXPORT_VAR_THREADLOCAL : DECL_ID_PUB_EXPORT_VAR; else id = args->is_threadlocal ? DECL_ID_EXPORT_VAR_THREADLOCAL : DECL_ID_EXPORT_VAR; break; } break; } assert(zirDeclIdHasTypeBody(id) || !has_type_body); assert(zirDeclIdHasSpecialBodies(id) || !has_special_body); assert(zirDeclIdHasValueBody(id) == has_value_body); assert(zirDeclIdHasName(id) == has_name); assert(zirDeclIdHasLibName(id) || !has_lib_name); const uint32_t type_len = countBodyLenAfterFixups(ag, type_body, type_len_insts); const uint32_t align_len = countBodyLenAfterFixups(ag, align_body, align_len_insts); const uint32_t linksection_len = countBodyLenAfterFixups(ag, linksection_body, linksection_len_insts); const uint32_t addrspace_len = countBodyLenAfterFixups(ag, addrspace_body, addrspace_len_insts); const uint32_t value_len = countBodyLenAfterFixups(ag, value_body, value_len_insts); const uint64_t flags = ((uint64_t)(args->src_line & ZIR_DECL_FLAGS_SRC_LINE_MASK) << ZIR_DECL_FLAGS_SRC_LINE_SHIFT) | ((uint64_t)(args->src_column & ZIR_DECL_FLAGS_SRC_COLUMN_MASK) << ZIR_DECL_FLAGS_SRC_COLUMN_SHIFT) | ((uint64_t)((uint32_t)id & ZIR_DECL_FLAGS_ID_MASK) << ZIR_DECL_FLAGS_ID_SHIFT); // Zir.Inst.Declaration payload: src_hash_0..3, flags_0, flags_1. const uint32_t payload_index = ag->extra_len; extraAppend(ag, 0); // src_hash_0 (masked) extraAppend(ag, 0); // src_hash_1 extraAppend(ag, 0); // src_hash_2 extraAppend(ag, 0); // src_hash_3 extraAppend(ag, (uint32_t)flags); // flags_0 extraAppend(ag, (uint32_t)(flags >> 32)); // flags_1 ag->inst_datas[decl_inst].declaration.payload_index = payload_index; if (zirDeclIdHasName(id)) extraAppend(ag, args->name); if (zirDeclIdHasLibName(id)) extraAppend(ag, args->lib_name); if (zirDeclIdHasTypeBody(id)) extraAppend(ag, type_len); if (zirDeclIdHasSpecialBodies(id)) { extraAppend(ag, align_len); extraAppend(ag, linksection_len); extraAppend(ag, addrspace_len); } if (zirDeclIdHasValueBody(id)) extraAppend(ag, value_len); appendBodyWithFixups(ag, type_body, type_len_insts); appendBodyWithFixups(ag, align_body, align_len_insts); appendBodyWithFixups(ag, linksection_body, linksection_len_insts); appendBodyWithFixups(ag, addrspace_body, addrspace_len_insts); appendBodyWithFixups(ag, value_body, value_len_insts); gzUnstack(args->value_gz); gzUnstack(args->linksection_gz); gzUnstack(args->addrspace_gz); gzUnstack(args->align_gz); gzUnstack(args->type_gz); } // --- globalVarDecl --- // AstGen.zig:globalVarDecl static void globalVarDecl(AstGen* ag, GenZir* gz, Scope* scope, AstNodeIndex node, AstFullVarDecl var_decl) { const Ast* tree = ag->tree; const bool is_mutable = astTokenTag(tree, var_decl.ast.mut_token) == TOKEN_KEYWORD_VAR; const AstTokenIndex name_token = var_decl.ast.mut_token + 1; const bool is_pub = (var_decl.visib_token != AST_TOKEN_NONE); bool is_export = false; bool is_extern = false; if ((var_decl.extern_export_token != AST_TOKEN_NONE)) { const TokenTag t = astTokenTag(tree, var_decl.extern_export_token); is_export = t == TOKEN_KEYWORD_EXPORT; is_extern = t == TOKEN_KEYWORD_EXTERN; } if ((var_decl.threadlocal_token != AST_TOKEN_NONE) && !is_mutable) failTok(ag, var_decl.threadlocal_token, "threadlocal variable cannot be constant"); const bool is_threadlocal = (var_decl.threadlocal_token != AST_TOKEN_NONE); uint32_t lib_name = 0; // 0 = empty NullTerminatedString if ((var_decl.lib_name != AST_TOKEN_NONE)) { uint32_t lib_name_len; lib_name = strLitAsString(ag, var_decl.lib_name, &lib_name_len); if (lib_name_len == 0) failTok(ag, var_decl.lib_name, "library name cannot be empty"); } advanceSourceCursorToNode(ag, node); const uint32_t decl_column = ag->source_column; const uint32_t decl_inst = gzMakeDeclaration(gz, node); // wip_decls.nextDecl(decl_inst): record decl index into the decls slice. wipDeclsNext(ag, decl_inst); if (var_decl.ast.init_node != AST_NODE_NONE) { if (is_extern) failNode(ag, var_decl.ast.init_node, "extern variables have no initializers"); } else { if (!is_extern) failNode(ag, node, "variables must be initialized"); } if (is_extern && var_decl.ast.type_node == AST_NODE_NONE) failNode(ag, node, "unable to infer variable type"); if ((var_decl.comptime_token != AST_TOKEN_NONE)) ASTGEN_UNIMPLEMENTED( "globalVarDecl: comptime var (parser should reject)"); GenZir type_gz; memset(&type_gz, 0, sizeof(type_gz)); type_gz.base.tag = SCOPE_GEN_ZIR; type_gz.base.parent = scope; type_gz.src_baseline = node; type_gz.decl_line = ag->source_line; type_gz.astgen = ag; type_gz.is_comptime = true; type_gz.instructions_top = ag->gz_len; if (var_decl.ast.type_node != AST_NODE_NONE) { const ZirInstRef type_inst = expr( &type_gz, &type_gz.base, coerced_type_ri, var_decl.ast.type_node); gzAddBreakWithSrcNode( &type_gz, ZIR_INST_BREAK_INLINE, decl_inst, type_inst, node); } GenZir align_gz = gzMakeSubBlock(&type_gz, scope); if (var_decl.ast.align_node != AST_NODE_NONE) { const ZirInstRef align_inst = expr(&align_gz, &align_gz.base, coerced_align_ri, var_decl.ast.align_node); gzAddBreakWithSrcNode( &align_gz, ZIR_INST_BREAK_INLINE, decl_inst, align_inst, node); } GenZir addrspace_gz = gzMakeSubBlock(&type_gz, scope); if (var_decl.ast.addrspace_node != AST_NODE_NONE) { const ZirInstRef addrspace_ty = gzAddStdLangValue(&addrspace_gz, var_decl.ast.addrspace_node, STD_LANG_VALUE_ADDRESS_SPACE); ResultInfo adr_ri = { { RL_COERCED_TY, addrspace_ty, 0, 0, NULL, 0, 0 }, 0 }; const ZirInstRef addrspace_inst = expr(&addrspace_gz, &addrspace_gz.base, adr_ri, var_decl.ast.addrspace_node); gzAddBreakWithSrcNode(&addrspace_gz, ZIR_INST_BREAK_INLINE, decl_inst, addrspace_inst, node); } GenZir linksection_gz = gzMakeSubBlock(&type_gz, scope); if (var_decl.ast.section_node != AST_NODE_NONE) { const ResultInfo ls_ri = { { RL_COERCED_TY, ZIR_REF_SLICE_CONST_U8_TYPE, 0, 0, NULL, 0, 0 }, 0 }; const ZirInstRef ls_inst = expr(&linksection_gz, &linksection_gz.base, ls_ri, var_decl.ast.section_node); gzAddBreakWithSrcNode( &linksection_gz, ZIR_INST_BREAK_INLINE, decl_inst, ls_inst, node); } GenZir init_gz = gzMakeSubBlock(&type_gz, scope); if (var_decl.ast.init_node != AST_NODE_NONE) { ResultInfo init_ri; if (var_decl.ast.type_node != AST_NODE_NONE) { init_ri.rl.tag = RL_COERCED_TY; init_ri.rl.ty = decl_inst + ZIR_REF_START_INDEX; init_ri.ctx = 0; } else { init_ri.rl.tag = RL_NONE; init_ri.rl.ty = 0; init_ri.ctx = 0; } ZirInstRef init_inst; if (!nameStratExpr(&init_gz, &init_gz.base, init_ri, var_decl.ast.init_node, ZIR_NAME_STRATEGY_PARENT, &init_inst)) init_inst = expr( &init_gz, &init_gz.base, init_ri, var_decl.ast.init_node); gzAddBreakWithSrcNode( &init_gz, ZIR_INST_BREAK_INLINE, decl_inst, init_inst, node); } SetDeclArgs args; memset(&args, 0, sizeof(args)); args.src_line = type_gz.decl_line; args.src_column = decl_column; args.kind = is_mutable ? DECL_KIND_VAR : DECL_KIND_CONST; args.name = identAsString(ag, name_token); args.is_pub = is_pub; args.is_threadlocal = is_threadlocal; args.linkage = is_extern ? LINKAGE_EXTERN : (is_export ? LINKAGE_EXPORT : LINKAGE_NORMAL); args.lib_name = lib_name; args.type_gz = &type_gz; args.align_gz = &align_gz; args.addrspace_gz = &addrspace_gz; args.linksection_gz = &linksection_gz; args.value_gz = &init_gz; setDeclaration(decl_inst, &args); } // --- token-relative offset --- // AstGen.zig:GenZir.tokenIndexToRelative static int32_t gzTokenIndexToRelative(const GenZir* gz, AstTokenIndex tok) { const AstTokenIndex src_tok = astFirstToken(gz->astgen->tree, gz->src_baseline); return (int32_t)tok - (int32_t)src_tok; } // --- more GenZir emitters --- // AstGen.zig:GenZir.makeUnTok // Create an un_tok instruction without pushing it onto the gz // instruction stack. Returns the raw instruction index. Used by rvalue's ref // path; the instruction is later spliced into a body via the ref_table. static uint32_t gzMakeUnTok( GenZir* gz, ZirInstTag tag, ZirInstRef operand, AstTokenIndex abs_tok) { ZirInstData data; memset(&data, 0, sizeof(data)); data.un_tok.src_tok = gzTokenIndexToRelative(gz, abs_tok); data.un_tok.operand = operand; return agAppendInst(gz->astgen, tag, data); } // AstGen.zig:GenZir.addUnTok static ZirInstRef gzAddUnTok( GenZir* gz, ZirInstTag tag, ZirInstRef operand, AstTokenIndex abs_tok) { const uint32_t inst = gzMakeUnTok(gz, tag, operand, abs_tok); gzStackPush(gz->astgen, inst); return inst + ZIR_REF_START_INDEX; } // AstGen.zig:GenZir.addStrTok static ZirInstRef gzAddStrTok( GenZir* gz, ZirInstTag tag, uint32_t str_index, AstTokenIndex abs_tok) { ZirInstData data; memset(&data, 0, sizeof(data)); data.str_tok.start = str_index; data.str_tok.src_tok = gzTokenIndexToRelative(gz, abs_tok); return gzAdd(gz, tag, data); } // AstGen.zig:GenZir.addDbgVar // Emit a dbg_var_val/dbg_var_ptr (str_op). No-op in comptime. static void gzAddDbgVar( GenZir* gz, ZirInstTag tag, uint32_t str, ZirInstRef operand) { if (gz->is_comptime) return; ZirInstData data; memset(&data, 0, sizeof(data)); data.str_op.str = str; data.str_op.operand = operand; gzAdd(gz, tag, data); } // AstGen.zig:GenZir.addSaveErrRetIndex // `operand` is ZIR_REF_NONE for `.always`, else `.if_of_error_type`. static void gzAddSaveErrRetIndex(GenZir* gz, ZirInstRef operand) { ZirInstData data; memset(&data, 0, sizeof(data)); data.save_err_ret_index.operand = operand; gzAdd(gz, ZIR_INST_SAVE_ERR_RET_INDEX, data); } // AstGen.zig:GenZir.addNode static ZirInstRef gzAddNode( GenZir* gz, ZirInstTag tag, AstNodeIndex src_node) { ZirInstData data; memset(&data, 0, sizeof(data)); data.node = gzNodeIndexToRelative(gz, src_node); return gzAdd(gz, tag, data); } // AstGen.zig:GenZir.BranchTarget's `.ret`; any other value is `.block`. #define BRANCH_TARGET_RET UINT32_MAX // gz.addRestoreErrRetIndex's `cond`: `.always` or `.if_non_error`. typedef struct { bool always; ZirInstRef if_non_error; } RestoreErrRetIndexCond; #define RESTORE_ALWAYS ((RestoreErrRetIndexCond) { .always = true }) #define RESTORE_IF_NON_ERROR(operand) \ ((RestoreErrRetIndexCond) { .always = false, .if_non_error = (operand) }) // AstGen.zig:GenZir.addRestoreErrRetIndex static uint32_t gzAddRestoreErrRetIndex(GenZir* gz, uint32_t bt, RestoreErrRetIndexCond cond, AstNodeIndex src_node) { if (cond.always) return gzAddUnNode(gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, bt == BRANCH_TARGET_RET ? ZIR_REF_NONE : bt + ZIR_REF_START_INDEX, src_node) - ZIR_REF_START_INDEX; if (bt == BRANCH_TARGET_RET) return gzAddUnNode(gz, ZIR_INST_RESTORE_ERR_RET_INDEX_FN_ENTRY, cond.if_non_error, src_node) - ZIR_REF_START_INDEX; const uint32_t words[3] = { (uint32_t)gzNodeIndexToRelative(gz, src_node), bt + ZIR_REF_START_INDEX, cond.if_non_error }; return gzAddExtendedPayload(gz, ZIR_EXT_RESTORE_ERR_RET_INDEX, words, 3) - ZIR_REF_START_INDEX; } // AstGen.zig:restoreErrRetIndex static void restoreErrRetIndex(GenZir* gz, uint32_t bt, ResultInfo ri, AstNodeIndex node, ZirInstRef result) { switch (nodeMayEvalToError(gz->astgen->tree, node)) { case BUILTIN_FN_EVAL_TO_ERROR_ALWAYS: return; // never restore/pop case BUILTIN_FN_EVAL_TO_ERROR_NEVER: gzAddRestoreErrRetIndex( gz, bt, RESTORE_IF_NON_ERROR(ZIR_REF_NONE), node); break; case BUILTIN_FN_EVAL_TO_ERROR_MAYBE: { // Mirrors AstGen.restoreErrRetIndex: only use result as operand when // in an error-propagating context; otherwise restore unconditionally. ZirInstRef op; switch (ri.ctx) { case CTX_ERROR_HANDLING_EXPR: case CTX_RETURN: case CTX_FN_ARG: case CTX_CONST_INIT: // inferred_ptr: terrible workaround - always restore/pop if (ri.rl.tag == RL_INFERRED_PTR) { op = ZIR_REF_NONE; } else if (ri.rl.tag == RL_DESTRUCTURE) { return; // value must be tuple/array, never restore/pop } else if (ri.rl.tag == RL_PTR) { // Mirrors AstGen.restoreErrRetIndex: // .ptr => |ptr_res| try gz.addUnNode(.load, ptr_res.inst, // node) Load from the ptr to get the value for the error-trace // check. op = gzAddUnNode(gz, ZIR_INST_LOAD, ri.rl.inst, node); } else { op = result; } break; default: op = ZIR_REF_NONE; // always restore/pop break; } gzAddRestoreErrRetIndex(gz, bt, RESTORE_IF_NON_ERROR(op), node); break; } } } // --- debug info emitters --- // AstGen.zig:emitDbgStmt static void emitDbgStmt(GenZir* gz, uint32_t line, uint32_t column) { if (gz->is_comptime) return; AstGen* ag = gz->astgen; if (gz->instructions_top != GZ_UNSTACKED && ag->gz_len > gz->instructions_top) { const uint32_t last = ag->gz_stack[ag->gz_len - 1]; if (ag->inst_tags[last] == ZIR_INST_DBG_STMT) { ag->inst_datas[last].dbg_stmt.line = line; ag->inst_datas[last].dbg_stmt.column = column; return; } } ZirInstData data; memset(&data, 0, sizeof(data)); data.dbg_stmt.line = line; data.dbg_stmt.column = column; gzAdd(gz, ZIR_INST_DBG_STMT, data); } // AstGen.zig:emitDbgNode static void emitDbgNode(GenZir* gz, AstNodeIndex node) { if (gz->is_comptime) return; AstGen* ag = gz->astgen; advanceSourceCursorToNode(ag, node); const uint32_t line = ag->source_line - gz->decl_line; const uint32_t column = ag->source_column; emitDbgStmt(gz, line, column); } // AstGen.zig:emitDbgStmtForceCurrentIndex // Like emitDbgStmt but only coalesces when the // last gz instruction is also the last-allocated ZIR instruction. static void emitDbgStmtForceCurrentIndex( GenZir* gz, uint32_t line, uint32_t column) { AstGen* ag = gz->astgen; if (gz->instructions_top != GZ_UNSTACKED && ag->gz_len > gz->instructions_top) { const uint32_t last = ag->gz_stack[ag->gz_len - 1]; if (last == ag->inst_len - 1 && ag->inst_tags[last] == ZIR_INST_DBG_STMT) { ag->inst_datas[last].dbg_stmt.line = line; ag->inst_datas[last].dbg_stmt.column = column; return; } } ZirInstData data; memset(&data, 0, sizeof(data)); data.dbg_stmt.line = line; data.dbg_stmt.column = column; gzAdd(gz, ZIR_INST_DBG_STMT, data); } // AstGen.zig:maybeAdvanceSourceCursorToMainToken static LineColumn maybeAdvanceSourceCursorToMainToken( GenZir* gz, AstNodeIndex node) { AstGen* ag = gz->astgen; if (gz->is_comptime) { LineColumn lc = { ag->source_line - gz->decl_line, ag->source_column }; return lc; } const AstTokenIndex mt = astNodeMainToken(ag->tree, node); const uint32_t node_start = astTokenStart(ag->tree, mt); advanceSourceCursor(ag, node_start); LineColumn lc = { ag->source_line - gz->decl_line, ag->source_column }; return lc; } // AstGen.zig:reachableTypeExpr static ZirInstRef reachableTypeExpr(GenZir* gz, Scope* scope, AstNodeIndex type_node, AstNodeIndex reachable_node) { return reachableExprComptime( gz, scope, coerced_type_ri, type_node, reachable_node, REASON_TYPE); } // AstGen.zig:reachableExpr // Same as `expr` but fails with a compile error if the result type is // `noreturn`. static ZirInstRef reachableExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex reachable_node) { return reachableExprComptime( gz, scope, ri, node, reachable_node, NO_COMPTIME_REASON); } // AstGen.zig:reachableExprComptime // A `comptime_reason` of NO_COMPTIME_REASON is upstream's null: the // expression is not evaluated in a comptime context. static ZirInstRef reachableExprComptime(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex reachable_node, uint32_t comptime_reason) { const ZirInstRef result_inst = comptime_reason != NO_COMPTIME_REASON ? comptimeExpr(gz, scope, ri, node, comptime_reason) : expr(gz, scope, ri, node); if (refIsNoReturn(gz, result_inst)) failNode(gz->astgen, reachable_node, "unreachable code"); return result_inst; } // --- genDefers --- // AstGen.zig:GenZir.addDefer // Emit a `.defer` instruction replaying a pre-lowered defer body. static void gzAddDefer(GenZir* gz, uint32_t index, uint32_t len) { ZirInstData data; memset(&data, 0, sizeof(data)); data.defer_data.index = index; data.defer_data.len = len; gzAdd(gz, ZIR_INST_DEFER, data); } // anyErrdefers: mirrors AstGen.zig:anyErrdefers. Returns true if there is any // errdefer scope between inner_scope and outer_scope. static bool anyErrdefers(const Scope* outer_scope, Scope* inner_scope) { Scope* scope = inner_scope; while (scope != outer_scope) { switch (scope->tag) { case SCOPE_GEN_ZIR: scope = scope->parent; break; case SCOPE_LOCAL_VAL: scope = scope->parent; break; case SCOPE_LOCAL_PTR: scope = scope->parent; break; case SCOPE_DEFER_NORMAL: { ScopeDefer* d = (ScopeDefer*)scope; scope = d->base.parent; break; } case SCOPE_DEFER_ERROR: return true; default: ASTGEN_UNIMPLEMENTED("anyErrdefers: unhandled scope tag"); } } return false; } // AstGen.zig:genDefers static void genDefers(GenZir* gz, const Scope* outer_scope, Scope* inner_scope, bool normal_and_error) { Scope* scope = inner_scope; while (scope != outer_scope) { switch (scope->tag) { case SCOPE_GEN_ZIR: scope = scope->parent; break; case SCOPE_LOCAL_VAL: scope = scope->parent; break; case SCOPE_LOCAL_PTR: scope = scope->parent; break; case SCOPE_DEFER_NORMAL: { ScopeDefer* d = (ScopeDefer*)scope; scope = d->base.parent; gzAddDefer(gz, d->index, d->len); break; } case SCOPE_DEFER_ERROR: { ScopeDefer* d = (ScopeDefer*)scope; scope = d->base.parent; if (normal_and_error) gzAddDefer(gz, d->index, d->len); break; } default: ASTGEN_UNIMPLEMENTED("genDefers: unhandled scope tag"); } } } // --- addRet --- // AstGen.zig:GenZir.addRet static void gzAddRet( GenZir* gz, ResultInfo ri, ZirInstRef operand, AstNodeIndex node) { switch (ri.rl.tag) { case RL_PTR: gzAddUnNode(gz, ZIR_INST_RET_LOAD, ri.rl.inst, node); break; case RL_COERCED_TY: gzAddUnNode(gz, ZIR_INST_RET_NODE, operand, node); break; default: ASTGEN_UNIMPLEMENTED("AstGen.GenZir.addRet: result location"); } } // --- nodeMayEvalToError --- // AstGen.zig:nodeMayEvalToError static BuiltinFnEvalToError nodeMayEvalToError( const Ast* tree, AstNodeIndex start_node) { AstNodeIndex node = start_node; for (;;) { switch (astNodeTag(tree, node)) { case AST_NODE_ERROR_VALUE: return BUILTIN_FN_EVAL_TO_ERROR_ALWAYS; case AST_NODE_ASM: case AST_NODE_ASM_SIMPLE: case AST_NODE_IDENTIFIER: case AST_NODE_FIELD_ACCESS: case AST_NODE_DEREF: case AST_NODE_ARRAY_ACCESS: case AST_NODE_WHILE_SIMPLE: case AST_NODE_WHILE_CONT: case AST_NODE_FOR_SIMPLE: case AST_NODE_IF_SIMPLE: case AST_NODE_WHILE: case AST_NODE_IF: case AST_NODE_FOR: case AST_NODE_SWITCH: case AST_NODE_SWITCH_COMMA: case AST_NODE_CALL_ONE: case AST_NODE_CALL_ONE_COMMA: case AST_NODE_CALL: case AST_NODE_CALL_COMMA: return BUILTIN_FN_EVAL_TO_ERROR_MAYBE; case AST_NODE_NUMBER_LITERAL: case AST_NODE_STRING_LITERAL: case AST_NODE_ADD: case AST_NODE_SUB: case AST_NODE_MUL: case AST_NODE_ADD_WRAP: case AST_NODE_SUB_WRAP: case AST_NODE_MUL_WRAP: case AST_NODE_DIV: case AST_NODE_MOD: case AST_NODE_BIT_AND: case AST_NODE_BIT_OR: case AST_NODE_BIT_XOR: case AST_NODE_SHL: case AST_NODE_SHR: case AST_NODE_BANG_EQUAL: case AST_NODE_EQUAL_EQUAL: case AST_NODE_GREATER_THAN: case AST_NODE_GREATER_OR_EQUAL: case AST_NODE_LESS_THAN: case AST_NODE_LESS_OR_EQUAL: case AST_NODE_RETURN: case AST_NODE_BREAK: case AST_NODE_CONTINUE: case AST_NODE_BIT_NOT: case AST_NODE_BOOL_NOT: case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: case AST_NODE_DEFER: case AST_NODE_ERRDEFER: case AST_NODE_ADDRESS_OF: case AST_NODE_OPTIONAL_TYPE: case AST_NODE_NEGATION: case AST_NODE_NEGATION_WRAP: case AST_NODE_RESUME: case AST_NODE_ARRAY_TYPE: case AST_NODE_ARRAY_TYPE_SENTINEL: case AST_NODE_PTR_TYPE_ALIGNED: case AST_NODE_PTR_TYPE_SENTINEL: case AST_NODE_PTR_TYPE: case AST_NODE_PTR_TYPE_BIT_RANGE: case AST_NODE_SUSPEND: case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: case AST_NODE_FN_DECL: case AST_NODE_ANYFRAME_TYPE: case AST_NODE_ANYFRAME_LITERAL: case AST_NODE_ENUM_LITERAL: case AST_NODE_MULTILINE_STRING_LITERAL: case AST_NODE_CHAR_LITERAL: case AST_NODE_UNREACHABLE_LITERAL: case AST_NODE_ERROR_SET_DECL: case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: case AST_NODE_ADD_SAT: case AST_NODE_ARRAY_CAT: case AST_NODE_ASSIGN: case AST_NODE_ASSIGN_DESTRUCTURE: case AST_NODE_ASSIGN_BIT_AND: case AST_NODE_ASSIGN_BIT_OR: case AST_NODE_ASSIGN_SHL: case AST_NODE_ASSIGN_SHL_SAT: case AST_NODE_ASSIGN_SHR: case AST_NODE_ASSIGN_BIT_XOR: case AST_NODE_ASSIGN_DIV: case AST_NODE_ASSIGN_SUB: case AST_NODE_ASSIGN_SUB_WRAP: case AST_NODE_ASSIGN_SUB_SAT: case AST_NODE_ASSIGN_MOD: case AST_NODE_ASSIGN_ADD: case AST_NODE_ASSIGN_ADD_WRAP: case AST_NODE_ASSIGN_ADD_SAT: case AST_NODE_ASSIGN_MUL: case AST_NODE_ASSIGN_MUL_WRAP: case AST_NODE_ASSIGN_MUL_SAT: case AST_NODE_SHL_SAT: case AST_NODE_BOOL_AND: case AST_NODE_BOOL_OR: case AST_NODE_ERROR_UNION: case AST_NODE_MERGE_ERROR_SETS: case AST_NODE_MUL_SAT: case AST_NODE_SUB_SAT: case AST_NODE_SWITCH_RANGE: case AST_NODE_FOR_RANGE: case AST_NODE_SLICE: case AST_NODE_SLICE_OPEN: case AST_NODE_SLICE_SENTINEL: case AST_NODE_ARRAY_INIT_ONE: case AST_NODE_ARRAY_INIT_ONE_COMMA: case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: case AST_NODE_ARRAY_INIT_DOT: case AST_NODE_ARRAY_INIT_DOT_COMMA: case AST_NODE_ARRAY_INIT: case AST_NODE_ARRAY_INIT_COMMA: case AST_NODE_STRUCT_INIT_ONE: case AST_NODE_STRUCT_INIT_ONE_COMMA: case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: case AST_NODE_STRUCT_INIT_DOT: case AST_NODE_STRUCT_INIT_DOT_COMMA: case AST_NODE_STRUCT_INIT: case AST_NODE_STRUCT_INIT_COMMA: return BUILTIN_FN_EVAL_TO_ERROR_NEVER; // Forward the question to the LHS sub-expression. case AST_NODE_TRY: case AST_NODE_COMPTIME: case AST_NODE_NOSUSPEND: case AST_NODE_GROUPED_EXPRESSION: case AST_NODE_UNWRAP_OPTIONAL: node = astNodeData(tree, node).lhs; break; // LHS may still be an error under the outer optional/error union. case AST_NODE_CATCH: case AST_NODE_ORELSE: return BUILTIN_FN_EVAL_TO_ERROR_MAYBE; case AST_NODE_BLOCK_TWO: case AST_NODE_BLOCK_TWO_SEMICOLON: case AST_NODE_BLOCK: case AST_NODE_BLOCK_SEMICOLON: { const AstTokenIndex lbrace = astNodeMainToken(tree, node); if (astTokenTag(tree, lbrace - 1) == TOKEN_COLON) return BUILTIN_FN_EVAL_TO_ERROR_MAYBE; return BUILTIN_FN_EVAL_TO_ERROR_NEVER; } case AST_NODE_BUILTIN_CALL: case AST_NODE_BUILTIN_CALL_COMMA: case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: { const AstTokenIndex builtin_token = astNodeMainToken(tree, node); const BuiltinFn* info = builtinFnListGetToken(tree, builtin_token); if (info == NULL) return BUILTIN_FN_EVAL_TO_ERROR_MAYBE; return info->eval_to_error; } default: ASTGEN_UNIMPLEMENTED("nodeMayEvalToError: unhandled node tag"); } } } // AstGen.zig:nodeMayAppendToErrorTrace static bool nodeMayAppendToErrorTrace( const Ast* tree, AstNodeIndex start_node) { AstNodeIndex node = start_node; for (;;) { switch (astNodeTag(tree, node)) { case AST_NODE_ERROR_VALUE: case AST_NODE_IDENTIFIER: case AST_NODE_COMPTIME: return false; case AST_NODE_TRY: case AST_NODE_NOSUSPEND: node = astNodeData(tree, node).lhs; break; case AST_NODE_GROUPED_EXPRESSION: case AST_NODE_UNWRAP_OPTIONAL: node = astNodeData(tree, node).lhs; break; default: return nodeMayEvalToError(tree, start_node) != BUILTIN_FN_EVAL_TO_ERROR_NEVER; } } } // --- ret --- static ZirInstRef ret(GenZir* gz, Scope* scope, AstNodeIndex node) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; if (ag->fn_block == NULL) failNode(ag, node, "'return' outside function scope"); if (!gz->is_comptime) emitDbgNode(gz, node); const uint32_t ret_line = ag->source_line - gz->decl_line; const uint32_t ret_col = ag->source_column; const Scope* defer_outer = &ag->fn_block->base; const AstNodeIndex operand_node = astNodeData(tree, node).lhs; if (operand_node == AST_NODE_NONE) { genDefers(gz, defer_outer, scope, false); gzAddRestoreErrRetIndex(gz, BRANCH_TARGET_RET, RESTORE_ALWAYS, node); gzAddUnNode(gz, ZIR_INST_RET_NODE, ZIR_REF_VOID_VALUE, node); return ZIR_REF_UNREACHABLE_VALUE; } if (astNodeTag(tree, operand_node) == AST_NODE_ERROR_VALUE) { // Hot path for `return error.Foo`: bypass result location logic. const AstTokenIndex ident_token = astNodeMainToken(tree, operand_node) + 2; const uint32_t err_name_str_index = identAsString(ag, ident_token); genDefers(gz, defer_outer, scope, true); // normal_and_error emitDbgStmt(gz, ret_line, ret_col); gzAddStrTok( gz, ZIR_INST_RET_ERR_VALUE, err_name_str_index, ident_token); return ZIR_REF_UNREACHABLE_VALUE; } ResultInfo ri; memset(&ri, 0, sizeof(ri)); ri.ctx = CTX_RETURN; if (nodesNeedRl(ag, node)) { ri.rl.tag = RL_PTR; ri.rl.inst = gzAddNode(gz, ZIR_INST_RET_PTR, node); } else { ri.rl.tag = RL_COERCED_TY; ri.rl.ty = ag->fn_ret_ty; } // nameStratExpr handles container-decl operands (e.g. `return struct // {...}`) with the `.func` name strategy; otherwise fall back to // reachableExpr. ZirInstRef operand; if (!nameStratExpr( gz, scope, ri, operand_node, ZIR_NAME_STRATEGY_FUNC, &operand)) operand = reachableExpr(gz, scope, ri, operand_node, node); switch (nodeMayEvalToError(tree, operand_node)) { case BUILTIN_FN_EVAL_TO_ERROR_NEVER: genDefers(gz, defer_outer, scope, false); gzAddRestoreErrRetIndex(gz, BRANCH_TARGET_RET, RESTORE_ALWAYS, node); emitDbgStmt(gz, ret_line, ret_col); gzAddRet(gz, ri, operand, node); return ZIR_REF_UNREACHABLE_VALUE; case BUILTIN_FN_EVAL_TO_ERROR_ALWAYS: ASTGEN_UNIMPLEMENTED("ret: always-error return"); case BUILTIN_FN_EVAL_TO_ERROR_MAYBE: { if (!anyErrdefers(defer_outer, scope)) { // No errdefers: only regular defers; no branch needed. // AstGen.zig: .maybe, !anyErrdefers path. // load emitted after genDefers/emitDbgStmt, matching Zig // reference. genDefers(gz, defer_outer, scope, false); // normal_only emitDbgStmt(gz, ret_line, ret_col); const ZirInstRef result = (ri.rl.tag == RL_PTR) ? gzAddUnNode(gz, ZIR_INST_LOAD, ri.rl.inst, node) : operand; gzAddRestoreErrRetIndex( gz, BRANCH_TARGET_RET, RESTORE_IF_NON_ERROR(result), node); gzAddRet(gz, ri, operand, node); return ZIR_REF_UNREACHABLE_VALUE; } // Has errdefers: emit conditional branch. // AstGen.zig:ret .maybe, anyErrdefers=true path. const ZirInstRef result = (ri.rl.tag == RL_PTR) ? gzAddUnNode(gz, ZIR_INST_LOAD, ri.rl.inst, node) : operand; const ZirInstRef is_non_err = gzAddUnNode(gz, ZIR_INST_RET_IS_NON_ERR, result, node); const uint32_t condbr = gzAddCondBr(gz, ZIR_INST_CONDBR, node); // then_scope: non-error path — normal defers only + restore_always + // ret GenZir then_scope = gzMakeSubBlock(gz, scope); genDefers(&then_scope, defer_outer, scope, false); // normal_only gzAddRestoreErrRetIndex( &then_scope, BRANCH_TARGET_RET, RESTORE_ALWAYS, node); emitDbgStmt(&then_scope, ret_line, ret_col); gzAddRet(&then_scope, ri, operand, node); // else_scope: error path — all defers + ret GenZir else_scope = gzMakeSubBlock(gz, scope); genDefers(&else_scope, defer_outer, scope, true); // normal_and_error emitDbgStmt(&else_scope, ret_line, ret_col); gzAddRet(&else_scope, ri, operand, node); setCondBrPayload(condbr, is_non_err, &then_scope, &else_scope); return ZIR_REF_UNREACHABLE_VALUE; } } UNREACHABLE("ret: invalid nodeMayEvalToError result"); ZIG1_TCC_RETURN(ZIR_REF_NONE); } // --- simpleBinOp --- // AstGen.zig:simpleBinOp static ZirInstRef simpleBinOp(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, ZirInstTag op_inst_tag) { const Ast* tree = gz->astgen->tree; const AstNodeIndex lhs_node = astNodeData(tree, node).lhs; const AstNodeIndex rhs_node = astNodeData(tree, node).rhs; if (op_inst_tag == ZIR_INST_CMP_NEQ || op_inst_tag == ZIR_INST_CMP_EQ) { if (astNodeTag(tree, lhs_node) == AST_NODE_STRING_LITERAL || astNodeTag(tree, rhs_node) == AST_NODE_STRING_LITERAL) failNode(gz->astgen, node, "cannot compare strings with %s", op_inst_tag == ZIR_INST_CMP_EQ ? "==" : "!="); } const bool is_arith = op_inst_tag == ZIR_INST_ADD || op_inst_tag == ZIR_INST_SUB || op_inst_tag == ZIR_INST_MUL || op_inst_tag == ZIR_INST_DIV || op_inst_tag == ZIR_INST_MOD_REM; const ZirInstRef lhs = reachableExpr(gz, scope, none_ri, lhs_node, node); LineColumn cursor = { 0, 0 }; if (is_arith) cursor = maybeAdvanceSourceCursorToMainToken(gz, node); const ZirInstRef rhs = reachableExpr(gz, scope, none_ri, rhs_node, node); if (is_arith) emitDbgStmt(gz, cursor.line, cursor.column); const ZirInstRef result = gzAddPlNodeBin(gz, op_inst_tag, node, lhs, rhs); return rvalue(gz, ri, result, node); } // AstGen.zig:boolBinOp // Emits bool_br_and/bool_br_or with a BoolBr payload: // extra = { lhs: Ref, body_len: u32 } + body instructions. static ZirInstRef boolBinOp(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, ZirInstTag zir_tag) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; const AstNodeIndex lhs_node = astNodeData(tree, node).lhs; const AstNodeIndex rhs_node = astNodeData(tree, node).rhs; // Evaluate lhs with coerced_bool_ri. const ZirInstRef lhs = expr(gz, scope, coerced_bool_ri, lhs_node); // Reserve bool_br_and/bool_br_or instruction (payload_index set below). const uint32_t bool_br_idx = gzReserveInstructionIndex(gz); ZirInstData br_data; memset(&br_data, 0, sizeof(br_data)); br_data.pl_node.src_node = gzNodeIndexToRelative(gz, node); br_data.pl_node.payload_index = 0; // set after BoolBr payload is written ag->inst_tags[bool_br_idx] = (uint8_t)zir_tag; ag->inst_datas[bool_br_idx] = br_data; // Evaluate rhs in a sub-block. GenZir rhs_scope = gzMakeSubBlock(gz, scope); const ZirInstRef rhs = fullBodyExpr(&rhs_scope, &rhs_scope.base, coerced_bool_ri, rhs_node, BLOCK_KIND_ALLOW_BRANCH_HINT); if (!refIsNoReturn(gz, rhs)) gzAddBreakWithSrcNode( &rhs_scope, ZIR_INST_BREAK_INLINE, bool_br_idx, rhs, rhs_node); // BoolBr payload: { lhs, body_len } followed by body. uint32_t body_len_insts; const uint32_t* body = gzInstructionsSlice(&rhs_scope, &body_len_insts); const uint32_t body_len = countBodyLenAfterFixups(ag, body, body_len_insts); const uint32_t payload_index = ag->extra_len; extraAppend(ag, (uint32_t)lhs); extraAppend(ag, body_len); ag->inst_datas[bool_br_idx].pl_node.payload_index = payload_index; appendBodyWithFixups(ag, body, body_len_insts); gzUnstack(&rhs_scope); const ZirInstRef block_ref = bool_br_idx + ZIR_REF_START_INDEX; return rvalue(gz, ri, block_ref, node); } // --- addEnsureResult / unusedResultExpr --- // AstGen.zig:addEnsureResult // Returns the noreturn statement node (or 0 if none / not noreturn). static AstNodeIndex addEnsureResult( GenZir* gz, ZirInstRef maybe_unused_result, AstNodeIndex statement) { AstGen* ag = gz->astgen; AstNodeIndex noreturn_src_node = 0; bool elide_check; if (maybe_unused_result >= ZIR_REF_START_INDEX) { const uint32_t inst = maybe_unused_result - ZIR_REF_START_INDEX; const ZirInstTag tag = ag->inst_tags[inst]; const ZirInstData data = ag->inst_datas[inst]; if (tag == ZIR_INST_CALL || tag == ZIR_INST_FIELD_CALL) { // Modify the call's flags in-place to avoid a separate // ensure_result_used instruction. flags is the first payload word; // Call.Flags and FieldCall.Flags share the ensure_result_used bit // (bit 3). const uint32_t break_extra = data.pl_node.payload_index; ag->extra[break_extra] |= ZIR_CALL_FLAGS_ENSURE_RESULT_USED; elide_check = true; } else if (tag == ZIR_INST_BUILTIN_CALL) { // BuiltinCall payload: flags(u32) + modifier + callee + args. // flags: bit0=is_nosuspend, bit1=ensure_result_used. const uint32_t break_extra = data.pl_node.payload_index; ag->extra[break_extra] |= ZIR_BUILTIN_CALL_FLAGS_ENSURE_RESULT_USED; elide_check = true; } else if (tagIsNoReturn(tag)) { noreturn_src_node = statement; elide_check = true; } else if (tagIsAlwaysVoid(tag, data)) { elide_check = true; } else { // "Might be a type other than noreturn or void." elide_check = false; } } else { if (maybe_unused_result == ZIR_REF_UNREACHABLE_VALUE) { noreturn_src_node = statement; elide_check = true; } else if (maybe_unused_result == ZIR_REF_VOID_VALUE) { elide_check = true; } else { elide_check = false; } } if (!elide_check) gzAddUnNode( gz, ZIR_INST_ENSURE_RESULT_USED, maybe_unused_result, statement); return noreturn_src_node; } // AstGen.zig:unusedResultExpr static AstNodeIndex unusedResultExpr( GenZir* gz, Scope* scope, AstNodeIndex statement) { emitDbgNode(gz, statement); const ZirInstRef maybe_unused_result = expr(gz, scope, none_ri, statement); return addEnsureResult(gz, maybe_unused_result, statement); } // --- checkUsed --- // AstGen.zig:checkUsed static void checkUsed(const Scope* outer_scope, Scope* inner_scope) { Scope* scope = inner_scope; while (scope != outer_scope) { switch (scope->tag) { case SCOPE_GEN_ZIR: scope = scope->parent; break; case SCOPE_LOCAL_VAL: { LocalVal* s = (LocalVal*)scope; if (!s->used && !s->discarded) failTok(s->gen_zir->astgen, s->token_src, "unused %s", id_cat_names[s->id_cat]); scope = s->base.parent; break; } case SCOPE_LOCAL_PTR: { LocalPtr* s = (LocalPtr*)scope; if (!s->used && !s->discarded) failTok(s->gen_zir->astgen, s->token_src, "unused %s", id_cat_names[s->id_cat]); scope = s->base.parent; break; } case SCOPE_DEFER_NORMAL: case SCOPE_DEFER_ERROR: scope = scope->parent; break; default: ASTGEN_UNIMPLEMENTED("checkUsed: unhandled scope tag"); } } } // --- block statements --- // AstGen.zig:deferStmt // Lower a `defer`/`errdefer` statement. Pre-lowers the deferred // expression body into `extra` and returns a Scope.Defer that genDefers later // replays via a `.defer` instruction. static Scope* deferStmt( GenZir* gz, Scope* scope, AstNodeIndex node, ScopeTag scope_tag) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; GenZir defer_gen = gzMakeSubBlock(gz, scope); defer_gen.any_defer_node = node; // The C parser stores the deferred expression in `lhs` (rhs unused), as // Ast's `.@"defer"`/`.@"errdefer"` hold it in `.node`. const AstNodeIndex expr_node = astNodeData(tree, node).lhs; unusedResultExpr(&defer_gen, &defer_gen.base, expr_node); checkUsed(scope, &defer_gen.base); gzAddBreak(&defer_gen, ZIR_INST_BREAK_INLINE, 0, ZIR_REF_VOID_VALUE); uint32_t body_len_insts; const uint32_t* body = gzInstructionsSlice(&defer_gen, &body_len_insts); const uint32_t body_len = countBodyLenAfterFixups(ag, body, body_len_insts); const uint32_t index = ag->extra_len; appendBodyWithFixups(ag, body, body_len_insts); gzUnstack(&defer_gen); ScopeDefer* d = scopeAlloc(ag, sizeof(ScopeDefer)); memset(d, 0, sizeof(*d)); d->base.tag = scope_tag; d->base.parent = scope; d->index = index; d->len = body_len; return &d->base; } // AstGen.zig:lvalExpr // Lower an assignment left-hand side as a pointer (RL_REF). Only the // assignable node tags are supported; any other LHS is upstream's compile // error, and aborts here. static ZirInstRef lvalExpr(GenZir* gz, Scope* scope, AstNodeIndex node) { const Ast* tree = gz->astgen->tree; switch (astNodeTag(tree, node)) { case AST_NODE_UNWRAP_OPTIONAL: case AST_NODE_DEREF: case AST_NODE_FIELD_ACCESS: case AST_NODE_ARRAY_ACCESS: case AST_NODE_IDENTIFIER: case AST_NODE_GROUPED_EXPRESSION: case AST_NODE_ORELSE: break; case AST_NODE_BUILTIN_CALL: case AST_NODE_BUILTIN_CALL_COMMA: case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: // @field, @ptrCast, etc. may be valid lvalues; let expr() decide. break; default: ASTGEN_UNIMPLEMENTED("lvalExpr: unhandled or invalid assignment lhs"); } return expr(gz, scope, ref_ri, node); } // AstGen.zig:assign // `lhs = rhs`. static void assign(GenZir* gz, Scope* scope, AstNodeIndex infix_node) { const AstGen* ag = gz->astgen; const Ast* tree = ag->tree; emitDbgNode(gz, infix_node); const AstNodeIndex lhs = astNodeData(tree, infix_node).lhs; const AstNodeIndex rhs = astNodeData(tree, infix_node).rhs; if (astNodeTag(tree, lhs) == AST_NODE_IDENTIFIER) { // This intentionally does not support `@"_"` syntax. if (tokenIsUnderscore(tree, astNodeMainToken(tree, lhs))) { ResultInfo rhs_ri = discard_ri; rhs_ri.ctx = CTX_ASSIGNMENT; expr(gz, scope, rhs_ri, rhs); return; } } const ZirInstRef lvalue = lvalExpr(gz, scope, lhs); ResultInfo ri; memset(&ri, 0, sizeof(ri)); ri.rl.tag = RL_PTR; ri.rl.inst = lvalue; ri.rl.src_node = infix_node; expr(gz, scope, ri, rhs); } // AstGen.zig:assignOp // `lhs OP= rhs` for arithmetic/bitwise compound assignment. static void assignOp(GenZir* gz, Scope* scope, AstNodeIndex infix_node, ZirInstTag op_inst_tag) { const Ast* tree = gz->astgen->tree; emitDbgNode(gz, infix_node); const AstNodeIndex lhs_node = astNodeData(tree, infix_node).lhs; const AstNodeIndex rhs_node = astNodeData(tree, infix_node).rhs; const ZirInstRef lhs_ptr = lvalExpr(gz, scope, lhs_node); const bool is_arith = op_inst_tag == ZIR_INST_ADD || op_inst_tag == ZIR_INST_SUB || op_inst_tag == ZIR_INST_MUL || op_inst_tag == ZIR_INST_DIV || op_inst_tag == ZIR_INST_MOD_REM; LineColumn cursor = { 0, 0 }; if (is_arith) cursor = maybeAdvanceSourceCursorToMainToken(gz, infix_node); const ZirInstRef lhs = gzAddUnNode(gz, ZIR_INST_LOAD, lhs_ptr, infix_node); ZirInstRef rhs_res_ty; if (op_inst_tag == ZIR_INST_ADD || op_inst_tag == ZIR_INST_SUB) { // inplace_arith_result_ty (small = InplaceOp). ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = ZIR_EXT_INPLACE_ARITH_RESULT_TY; data.extended.small = op_inst_tag == ZIR_INST_ADD ? ZIR_INPLACE_OP_ADD_EQ : ZIR_INPLACE_OP_SUB_EQ; data.extended.operand = lhs; rhs_res_ty = gzAdd(gz, ZIR_INST_EXTENDED, data); } else { rhs_res_ty = gzAddUnNode(gz, ZIR_INST_TYPEOF, lhs, infix_node); } // Not `coerced_ty` since `add`/etc won't coerce to this type. ResultInfo rhs_ri; memset(&rhs_ri, 0, sizeof(rhs_ri)); rhs_ri.rl.tag = RL_TY; rhs_ri.rl.ty = rhs_res_ty; const ZirInstRef rhs = expr(gz, scope, rhs_ri, rhs_node); if (is_arith) emitDbgStmt(gz, cursor.line, cursor.column); const ZirInstRef result = gzAddPlNodeBin(gz, op_inst_tag, infix_node, lhs, rhs); gzAddPlNodeBin(gz, ZIR_INST_STORE_NODE, infix_node, lhs_ptr, result); } // AstGen.zig:assignShift // `lhs <<= rhs` / `lhs >>= rhs`. static void assignShift(GenZir* gz, Scope* scope, AstNodeIndex infix_node, ZirInstTag op_inst_tag) { const Ast* tree = gz->astgen->tree; emitDbgNode(gz, infix_node); const AstNodeIndex lhs_node = astNodeData(tree, infix_node).lhs; const AstNodeIndex rhs_node = astNodeData(tree, infix_node).rhs; const ZirInstRef lhs_ptr = lvalExpr(gz, scope, lhs_node); const ZirInstRef lhs = gzAddUnNode(gz, ZIR_INST_LOAD, lhs_ptr, infix_node); const ZirInstRef rhs_type = gzAddUnNode(gz, ZIR_INST_TYPEOF_LOG2_INT_TYPE, lhs, infix_node); ResultInfo rhs_ri; memset(&rhs_ri, 0, sizeof(rhs_ri)); rhs_ri.rl.tag = RL_TY; rhs_ri.rl.ty = rhs_type; const ZirInstRef rhs = expr(gz, scope, rhs_ri, rhs_node); const ZirInstRef result = gzAddPlNodeBin(gz, op_inst_tag, infix_node, lhs, rhs); gzAddPlNodeBin(gz, ZIR_INST_STORE_NODE, infix_node, lhs_ptr, result); } // AstGen.zig:assignShiftSat // `lhs <<|= rhs`. Saturating shift-left allows any integer // type for both LHS and RHS (unlike regular shift which coerces RHS type). static void assignShiftSat(GenZir* gz, Scope* scope, AstNodeIndex infix_node) { const Ast* tree = gz->astgen->tree; emitDbgNode(gz, infix_node); const AstNodeIndex lhs_node = astNodeData(tree, infix_node).lhs; const AstNodeIndex rhs_node = astNodeData(tree, infix_node).rhs; const ZirInstRef lhs_ptr = lvalExpr(gz, scope, lhs_node); const ZirInstRef lhs = gzAddUnNode(gz, ZIR_INST_LOAD, lhs_ptr, infix_node); const ZirInstRef rhs = expr(gz, scope, none_ri, rhs_node); const ZirInstRef result = gzAddPlNodeBin(gz, ZIR_INST_SHL_SAT, infix_node, lhs, rhs); gzAddPlNodeBin(gz, ZIR_INST_STORE_NODE, infix_node, lhs_ptr, result); } // AstGen.zig:breakExpr, AstGen.zig:continueExpr // The `.namespace` arm: the scope walk found no target. ZIG1_ATTRIBUTE((noreturn)) static void failNoTarget(const AstGen* ag, AstNodeIndex node, AstTokenIndex opt_label, const char* keyword) { if (opt_label != AST_TOKEN_NONE) { const char* name; uint32_t name_len; astTokenSlice(ag->tree, opt_label, &name, &name_len); failTok(ag, opt_label, "label not found: '%.*s'", (int)name_len, name); } failNode(ag, node, "%s expression outside loop", keyword); } // AstGen.zig:breakExpr // Lower a `break`, `break :label`, or `break :label value` node. // Walks the scope chain to find the matching break target and emits the break // instruction (plus defers / restore_err_ret_index as needed). static ZirInstRef breakExpr( GenZir* parent_gz, Scope* parent_scope, AstNodeIndex node) { const AstGen* ag = parent_gz->astgen; const Ast* tree = ag->tree; const AstTokenIndex opt_break_label = astNodeData(tree, node).lhs; const AstNodeIndex opt_rhs = astNodeData(tree, node).rhs; // Walk scope chain to find matching break target. Scope* scope = parent_scope; while (scope != NULL) { switch (scope->tag) { case SCOPE_GEN_ZIR: { GenZir* gen_zir = (GenZir*)scope; // Note: Zig checks `cur_defer_node` here for "break out of defer" // compile errors. We skip the check (same rationale as // continueExpr). if (opt_break_label != AST_TOKEN_NONE) { // Labeled break: find scope with matching label_token. if (gen_zir->label_token != 0) { const char *break_label, *scope_label; uint32_t break_label_len, scope_label_len; astTokenSlice( tree, opt_break_label, &break_label, &break_label_len); astTokenSlice(tree, gen_zir->label_token, &scope_label, &scope_label_len); if (break_label_len == scope_label_len && memcmp(break_label, scope_label, break_label_len) == 0) { gen_zir->label_used = true; goto found_target; } } scope = scope->parent; continue; } else { // Unlabeled break: find nearest scope that allows it. if (!gen_zir->allow_unlabeled_control_flow) { scope = scope->parent; continue; } goto found_target; } found_target:; { const ZirInstTag break_tag = gen_zir->is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK; if (opt_rhs != AST_NODE_NONE) { const ZirInstRef operand = reachableExpr(parent_gz, parent_scope, gen_zir->break_result_info, opt_rhs, node); genDefers(parent_gz, &gen_zir->base, parent_scope, false); if (!gen_zir->is_comptime) restoreErrRetIndex(parent_gz, gen_zir->break_target, gen_zir->break_result_info, opt_rhs, operand); switch (gen_zir->break_result_info.rl.tag) { case RL_PTR: case RL_DISCARD: gzAddBreak(parent_gz, break_tag, gen_zir->break_target, ZIR_REF_VOID_VALUE); break; default: gzAddBreakWithSrcNode(parent_gz, break_tag, gen_zir->break_target, operand, opt_rhs); break; } } else { rvalue(parent_gz, gen_zir->break_result_info, ZIR_REF_VOID_VALUE, node); genDefers(parent_gz, &gen_zir->base, parent_scope, false); if (!gen_zir->is_comptime) gzAddRestoreErrRetIndex(parent_gz, gen_zir->break_target, RESTORE_ALWAYS, node); gzAddBreak(parent_gz, break_tag, gen_zir->break_target, ZIR_REF_VOID_VALUE); } return ZIR_REF_UNREACHABLE_VALUE; } } case SCOPE_LOCAL_VAL: case SCOPE_LOCAL_PTR: case SCOPE_DEFER_NORMAL: case SCOPE_DEFER_ERROR: scope = scope->parent; continue; case SCOPE_NAMESPACE: case SCOPE_TOP: failNoTarget(ag, node, opt_break_label, "break"); } } ASTGEN_UNIMPLEMENTED( "breakExpr: scope chain exhausted without finding target"); ZIG1_TCC_RETURN(ZIR_REF_NONE); } // AstGen.zig:continueExpr // Lower a `continue` or `continue :label` statement. // Walks the scope chain to find the enclosing loop's continue_target and // emits a break to it (plus defers / restore_err_ret_index as needed). static ZirInstRef continueExpr( GenZir* parent_gz, Scope* parent_scope, AstNodeIndex node) { const Ast* tree = parent_gz->astgen->tree; const AstTokenIndex opt_break_label = astNodeData(tree, node).lhs; // opt_rhs: continue with value (for switch_continue); not supported here. Scope* scope = parent_scope; while (scope != NULL) { switch (scope->tag) { case SCOPE_GEN_ZIR: { GenZir* gen_zir = (GenZir*)scope; // Note: Zig checks `cur_defer_node` here (set only for the // immediate defer-body GenZir, not inherited) to detect "continue // out of defer" compile errors. Our C uses `any_defer_node` which // is inherited, so it would fire for loops inside defer bodies -- // but that's valid Zig. We skip the check: "continue out of defer" // is a compile error, and zig1 accepts programs upstream rejects. if (opt_break_label != AST_TOKEN_NONE) { // Labeled continue: find scope with matching label and a // continue_target. if (gen_zir->label_token != 0) { const char *break_label, *scope_label; uint32_t break_label_len, scope_label_len; astTokenSlice( tree, opt_break_label, &break_label, &break_label_len); astTokenSlice(tree, gen_zir->label_token, &scope_label, &scope_label_len); if (break_label_len == scope_label_len && memcmp(break_label, scope_label, break_label_len) == 0 && gen_zir->continue_target != 0) { gen_zir->label_used = true; goto continue_found; } } scope = scope->parent; continue; } else { // Unlabeled continue: find first scope that allows unlabeled // control flow and has a continue_target. if (!gen_zir->allow_unlabeled_control_flow || gen_zir->continue_target == 0) { scope = scope->parent; continue; } goto continue_found; } continue_found:; if (gen_zir->continue_is_switch) { // switch_continue: evaluate rhs, emit switch_continue. // Mirrors AstGen.zig:continueExpr .switch_continue branch. const AstNodeIndex rhs_node = (AstNodeIndex)astNodeData(tree, node).rhs; if (rhs_node == AST_NODE_NONE) ASTGEN_UNIMPLEMENTED( "continueExpr: switch_continue without rhs"); const ZirInstRef operand = reachableExpr(parent_gz, parent_scope, gen_zir->continue_result_info, rhs_node, node); genDefers(parent_gz, &gen_zir->base, parent_scope, false); if (!gen_zir->is_comptime) gzAddRestoreErrRetIndex(parent_gz, gen_zir->continue_target, RESTORE_ALWAYS, node); gzAddBreakWithSrcNode(parent_gz, ZIR_INST_SWITCH_CONTINUE, gen_zir->continue_target, operand, rhs_node); gen_zir->label_used_for_continue = true; return ZIR_REF_UNREACHABLE_VALUE; } else { const ZirInstTag break_tag = gen_zir->is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK; genDefers(parent_gz, &gen_zir->base, parent_scope, false); if (break_tag == ZIR_INST_BREAK_INLINE) gzAddUnNode(parent_gz, ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW, gen_zir->continue_target + ZIR_REF_START_INDEX, node); if (!gen_zir->is_comptime) gzAddRestoreErrRetIndex(parent_gz, gen_zir->continue_target, RESTORE_ALWAYS, node); gzAddBreak(parent_gz, break_tag, gen_zir->continue_target, ZIR_REF_VOID_VALUE); return ZIR_REF_UNREACHABLE_VALUE; } } case SCOPE_LOCAL_VAL: case SCOPE_LOCAL_PTR: case SCOPE_DEFER_NORMAL: case SCOPE_DEFER_ERROR: scope = scope->parent; continue; case SCOPE_NAMESPACE: case SCOPE_TOP: failNoTarget(parent_gz->astgen, node, opt_break_label, "continue"); } } ASTGEN_UNIMPLEMENTED( "continueExpr: scope chain exhausted without finding target"); ZIG1_TCC_RETURN(ZIR_REF_NONE); } // AstGen.zig:labeledBlockExpr // Lower a labeled block `label: { stmts }`. Called from // blockExpr when the block's lbrace is preceded by "identifier :". static ZirInstRef labeledBlockExpr(GenZir* gz, Scope* parent_scope, ResultInfo ri, AstNodeIndex block_node, const AstNodeIndex* statements, uint32_t statements_len, bool force_comptime, BlockKind block_kind) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; const AstTokenIndex lbrace = astNodeMainToken(tree, block_node); const AstTokenIndex label_token = lbrace - 2; // AstRlAnnotate: determine if this block needs a result pointer. const bool need_rl = nodesNeedRl(ag, block_node); ResultInfo block_ri; if (need_rl) { block_ri = ri; } else { block_ri = ri; switch (ri.rl.tag) { case RL_INFERRED_PTR: { block_ri.rl = none_ri.rl; break; } case RL_PTR: { // block_ri.rl = .{ .ty = ri.rl.resultType().? } // resultType for .ptr: emit typeof(ptr.inst) then // elem_type(ptr_ty). const ZirInstRef ptr_ty = gzAddUnNode(gz, ZIR_INST_TYPEOF, ri.rl.inst, block_node); const ZirInstRef elem_ty = gzAddUnNode(gz, ZIR_INST_ELEM_TYPE, ptr_ty, block_node); ResultInfo ty_ri = { { RL_TY, elem_ty, 0, 0, NULL, 0, 0 }, ri.ctx }; block_ri = ty_ri; break; } default: break; // block_ri = ri (already set above) } } // need_result_rvalue: true if block_ri.rl.tag differs from ri.rl.tag. const bool need_result_rvalue = (block_ri.rl.tag != ri.rl.tag); const ZirInstTag block_tag = force_comptime ? ZIR_INST_BLOCK_COMPTIME : ZIR_INST_BLOCK; const uint32_t block_inst = gzMakeBlockInst(gz, block_tag, block_node); gzStackPush(ag, block_inst); GenZir block_scope = gzMakeSubBlock(gz, parent_scope); block_scope.is_inline = force_comptime; // override (not inherited) block_scope.label_token = label_token; block_scope.label_used = false; block_scope.break_target = block_inst; block_scope.continue_target = 0; gzSetBreakResultInfo(&block_scope, block_ri); if (force_comptime) block_scope.is_comptime = true; blockExprStmts(&block_scope, &block_scope.base, statements, statements_len, block_kind); if (!gzEndsWithNoReturn(&block_scope)) { // As our last action before the return, "pop" the error trace if // needed. Unconditional in upstream labeledBlockExpr (no is_comptime // guard). gzAddRestoreErrRetIndex(gz, block_inst, RESTORE_ALWAYS, block_node); const ZirInstRef result = rvalue( gz, block_scope.break_result_info, ZIR_REF_VOID_VALUE, block_node); const ZirInstTag break_tag = force_comptime ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK; gzAddBreak(&block_scope, break_tag, block_inst, result); } // The unused-label error is not reported: zig1 accepts programs upstream // rejects. if (force_comptime) { gzSetBlockComptimeBody( &block_scope, block_inst, REASON_COMPTIME_KEYWORD); } else { gzSetBlockBody(&block_scope, block_inst); } if (need_result_rvalue) { return rvalue(gz, ri, block_inst + ZIR_REF_START_INDEX, block_node); } else { return block_inst + ZIR_REF_START_INDEX; } } // AstGen.zig:blockExpr // Lower a block expression `{ stmts }` (labeled or unlabeled). // Mirrors Zig's AstGen.blockExpr. static ZirInstRef blockExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex block_node, const AstNodeIndex* statements, uint32_t statements_len, BlockKind block_kind) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; const AstTokenIndex lbrace = astNodeMainToken(tree, block_node); if (astIsTokenPrecededByTags(tree, lbrace, (const TokenTag[]) { TOKEN_IDENTIFIER, TOKEN_COLON }, 2)) { return labeledBlockExpr(gz, scope, ri, block_node, statements, statements_len, false, block_kind); } if (!gz->is_comptime) { const uint32_t block_inst = gzMakeBlockInst(gz, ZIR_INST_BLOCK, block_node); gzStackPush(ag, block_inst); GenZir block_scope = gzMakeSubBlock(gz, scope); blockExprStmts(&block_scope, &block_scope.base, statements, statements_len, block_kind); if (!gzEndsWithNoReturn(&block_scope)) { gzAddRestoreErrRetIndex( gz, block_inst, RESTORE_ALWAYS, block_node); gzAddBreak( &block_scope, ZIR_INST_BREAK, block_inst, ZIR_REF_VOID_VALUE); } gzSetBlockBody(&block_scope, block_inst); } else { // Upstream never unstacks sub_gz: its instructions stay on the // shared stack, in gz's body. GenZir sub_gz = gzMakeSubBlock(gz, scope); blockExprStmts( &sub_gz, &sub_gz.base, statements, statements_len, block_kind); } return rvalue(gz, ri, ZIR_REF_VOID_VALUE, block_node); } // AstGen.zig:blockExprStmts static void blockExprStmts(GenZir* gz, Scope* parent_scope, const AstNodeIndex* statements, uint32_t statements_len, BlockKind block_kind) { const AstGen* ag = gz->astgen; const Ast* tree = ag->tree; if (statements_len == 0) return; AstNodeIndex noreturn_src_node = 0; Scope* scope = parent_scope; for (uint32_t stmt_idx = 0; stmt_idx < statements_len; stmt_idx++) { const AstNodeIndex statement = statements[stmt_idx]; if (noreturn_src_node != 0) failNode(gz->astgen, statement, "unreachable code"); const bool allow_branch_hint = block_kind == BLOCK_KIND_ALLOW_BRANCH_HINT && stmt_idx == 0; AstNodeIndex inner_node = statement; for (;;) { switch (astNodeTag(tree, inner_node)) { case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: scope = varDecl(gz, scope, statement); break; case AST_NODE_DEFER: scope = deferStmt(gz, scope, statement, SCOPE_DEFER_NORMAL); break; case AST_NODE_ERRDEFER: scope = deferStmt(gz, scope, statement, SCOPE_DEFER_ERROR); break; case AST_NODE_ASSIGN_DESTRUCTURE: scope = assignDestructureMaybeDecls(gz, scope, statement); break; case AST_NODE_ASSIGN: assign(gz, scope, statement); break; case AST_NODE_ASSIGN_SHL: assignShift(gz, scope, statement, ZIR_INST_SHL); break; case AST_NODE_ASSIGN_SHR: assignShift(gz, scope, statement, ZIR_INST_SHR); break; case AST_NODE_ASSIGN_BIT_AND: assignOp(gz, scope, statement, ZIR_INST_BIT_AND); break; case AST_NODE_ASSIGN_BIT_OR: assignOp(gz, scope, statement, ZIR_INST_BIT_OR); break; case AST_NODE_ASSIGN_BIT_XOR: assignOp(gz, scope, statement, ZIR_INST_XOR); break; case AST_NODE_ASSIGN_DIV: assignOp(gz, scope, statement, ZIR_INST_DIV); break; case AST_NODE_ASSIGN_SUB: assignOp(gz, scope, statement, ZIR_INST_SUB); break; case AST_NODE_ASSIGN_SUB_WRAP: assignOp(gz, scope, statement, ZIR_INST_SUBWRAP); break; case AST_NODE_ASSIGN_MOD: assignOp(gz, scope, statement, ZIR_INST_MOD_REM); break; case AST_NODE_ASSIGN_ADD: assignOp(gz, scope, statement, ZIR_INST_ADD); break; case AST_NODE_ASSIGN_ADD_WRAP: assignOp(gz, scope, statement, ZIR_INST_ADDWRAP); break; case AST_NODE_ASSIGN_MUL: assignOp(gz, scope, statement, ZIR_INST_MUL); break; case AST_NODE_ASSIGN_MUL_WRAP: assignOp(gz, scope, statement, ZIR_INST_MULWRAP); break; case AST_NODE_WHILE_SIMPLE: case AST_NODE_WHILE_CONT: case AST_NODE_WHILE: { whileExpr(gz, scope, none_ri, inner_node, true); break; } case AST_NODE_FOR_SIMPLE: case AST_NODE_FOR: { forExpr(gz, scope, none_ri, inner_node, true); break; } case AST_NODE_GROUPED_EXPRESSION: inner_node = astNodeData(tree, inner_node).lhs; continue; case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: case AST_NODE_BUILTIN_CALL: case AST_NODE_BUILTIN_CALL_COMMA: { AstNodeIndex buf[2]; AstNodeSlice params; astBuiltinCallParams(tree, buf, inner_node, ¶ms); emitDbgNode(gz, inner_node); const ZirInstRef result = builtinCall(gz, scope, none_ri, inner_node, params, allow_branch_hint, ZIR_NAME_STRATEGY_ANON); noreturn_src_node = addEnsureResult(gz, result, inner_node); break; } default: noreturn_src_node = unusedResultExpr(gz, scope, inner_node); break; } break; } } if (noreturn_src_node == 0) genDefers(gz, parent_scope, scope, false); checkUsed(parent_scope, scope); } // AstGen.zig:fullBodyExpr static ZirInstRef fullBodyExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, BlockKind block_kind) { const Ast* tree = gz->astgen->tree; AstNodeIndex stmt_buf[2]; AstNodeSlice statements; if (!astBlockStatements(tree, stmt_buf, node, &statements)) return expr(gz, scope, ri, node); const AstTokenIndex lbrace = astNodeMainToken(tree, node); if (astIsTokenPrecededByTags(tree, lbrace, (const TokenTag[]) { TOKEN_IDENTIFIER, TOKEN_COLON }, 2)) { // Labeled blocks are tricky to forward result location information // through, so just treat it as a normal expression with a nested // block. (Mirrors Zig's fullBodyExpr which calls blockExpr for this // case.) return blockExpr( gz, scope, ri, node, statements.ptr, statements.len, block_kind); } GenZir sub_gz = gzMakeSubBlock(gz, scope); blockExprStmts( &sub_gz, &sub_gz.base, statements.ptr, statements.len, block_kind); return rvalue(gz, ri, ZIR_REF_VOID_VALUE, node); } // --- callExpr / calleeExpr --- // Callee: direct callee ref, or a field (method-call syntax). typedef struct { bool is_field; ZirInstRef direct; // valid when !is_field ZirInstRef obj_ptr; // valid when is_field uint32_t field_name_start; // valid when is_field } Callee; // AstGen.zig:calleeExpr static Callee calleeExpr(GenZir* gz, Scope* scope, ResultInfo ri, ZirInstRef override_decl_literal_type, AstNodeIndex node) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; Callee out; memset(&out, 0, sizeof(out)); switch (astNodeTag(tree, node)) { case AST_NODE_FIELD_ACCESS: { const AstNodeIndex object_node = astNodeData(tree, node).lhs; const AstTokenIndex field_ident = astNodeData(tree, node).rhs; const uint32_t str_index = identAsString(ag, field_ident); const ZirInstRef lhs = expr(gz, scope, ref_ri, object_node); LineColumn cursor = maybeAdvanceSourceCursorToMainToken(gz, node); emitDbgStmt(gz, cursor.line, cursor.column); out.is_field = true; out.obj_ptr = lhs; out.field_name_start = str_index; return out; } case AST_NODE_ENUM_LITERAL: { // Use override_decl_literal_type if provided (e.g. from tryExpr), // otherwise fall back to resultLocType. const ZirInstRef res_ty = (override_decl_literal_type != ZIR_REF_NONE) ? override_decl_literal_type : resultLocType(gz, ri, node); if (res_ty == ZIR_REF_NONE) { // No result type; lower to a literal call of an enum literal. out.is_field = false; out.direct = expr(gz, scope, none_ri, node); return out; } // Decl literal call syntax, e.g. `const foo: T = .init();`. // Look up `init` in `T`, but don't try to coerce it. const uint32_t str_index = identAsString(ag, astNodeMainToken(tree, node)); const uint32_t words[2] = { res_ty, str_index }; const ZirInstRef callee = gzAddPlNodeExtra( gz, ZIR_INST_DECL_LITERAL_NO_COERCE, node, words, 2); out.is_field = false; out.direct = callee; return out; } default: { out.is_field = false; out.direct = expr(gz, scope, none_ri, node); return out; } } } // AstGen.zig:callExpr static ZirInstRef callExpr(GenZir* gz, Scope* scope, ResultInfo ri, ZirInstRef override_decl_literal_type, AstNodeIndex node) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeIndex call_buf[1]; AstFullCall call; astFullCall(tree, call_buf, node, &call); const Callee callee = calleeExpr( gz, scope, ri, override_decl_literal_type, call.ast.fn_expr); // modifier: .auto (0); zig1 does not track GenZir.nosuspend_node. const uint32_t modifier = 0; { advanceSourceCursor(ag, astTokenStart(tree, call.ast.lparen)); const uint32_t line = ag->source_line - gz->decl_line; const uint32_t column = ag->source_column; emitDbgStmtForceCurrentIndex(gz, line, column); } const uint32_t call_index = gzReserveInstructionIndex(gz); const ZirInstRef call_inst = call_index + ZIR_REF_START_INDEX; const uint32_t scratch_top = ag->scratch_len; // Reserve one length slot per arg; bodies are appended after. for (uint32_t i = 0; i < call.ast.params.len; i++) scratchAppend(ag, 0); for (uint32_t i = 0; i < call.ast.params.len; i++) { GenZir arg_block = gzMakeSubBlock(gz, scope); ResultInfo arg_ri = { { RL_COERCED_TY, call_inst, 0, 0, NULL, 0, 0 }, CTX_FN_ARG }; const ZirInstRef arg_ref = fullBodyExpr(&arg_block, &arg_block.base, arg_ri, call.ast.params.ptr[i], BLOCK_KIND_NORMAL); gzAddBreakWithSrcNode(&arg_block, ZIR_INST_BREAK_INLINE, call_index, arg_ref, call.ast.params.ptr[i]); uint32_t body_len_insts; const uint32_t* body = gzInstructionsSlice(&arg_block, &body_len_insts); scratchAppendBodyWithFixups(ag, body, body_len_insts); ag->scratch[scratch_top + i] = ag->scratch_len - scratch_top; gzUnstack(&arg_block); } const bool propagate_error_trace = ri.ctx == CTX_ERROR_HANDLING_EXPR || ri.ctx == CTX_RETURN || ri.ctx == CTX_FN_ARG || ri.ctx == CTX_CONST_INIT; uint32_t flags = (modifier & ZIR_CALL_FLAGS_PACKED_MODIFIER_MASK) << ZIR_CALL_FLAGS_PACKED_MODIFIER_SHIFT; if (!propagate_error_trace) flags |= ZIR_CALL_FLAGS_POP_ERROR_RETURN_TRACE; flags |= (call.ast.params.len & ZIR_CALL_FLAGS_ARGS_LEN_MASK) << ZIR_CALL_FLAGS_ARGS_LEN_SHIFT; const uint32_t payload_index = ag->extra_len; if (callee.is_field) { // Zir.Inst.FieldCall: { flags, obj_ptr, field_name_start } + args. extraAppend(ag, flags); extraAppend(ag, callee.obj_ptr); extraAppend(ag, callee.field_name_start); } else { // Zir.Inst.Call: { flags, callee } then trailing arg data. extraAppend(ag, flags); extraAppend(ag, callee.direct); } for (uint32_t i = scratch_top; i < ag->scratch_len; i++) extraAppend(ag, ag->scratch[i]); // scratch.reset() ag->scratch_len = scratch_top; ZirInstData data; memset(&data, 0, sizeof(data)); data.pl_node.src_node = gzNodeIndexToRelative(gz, node); data.pl_node.payload_index = payload_index; ag->inst_tags[call_index] = callee.is_field ? ZIR_INST_FIELD_CALL : ZIR_INST_CALL; ag->inst_datas[call_index] = data; return rvalue(gz, ri, call_inst, node); } // --- block / condbr helpers --- // AstGen.zig:GenZir.setBreakResultInfo static void gzSetBreakResultInfo(GenZir* gz, ResultInfo parent_ri) { switch (parent_ri.rl.tag) { case RL_COERCED_TY: { ResultInfo out = { { RL_TY, parent_ri.rl.ty, 0, 0, NULL, 0, 0 }, parent_ri.ctx }; gz->break_result_info = out; break; } case RL_DISCARD: { gz->break_result_info = discard_ri; break; } default: gz->break_result_info = parent_ri; } } // AstGen.zig:GenZir.makeBlockInst // Reserve a block-like instruction; payload_index set later. static uint32_t gzMakeBlockInst( GenZir* gz, ZirInstTag tag, AstNodeIndex node) { ZirInstData data; memset(&data, 0, sizeof(data)); data.pl_node.src_node = gzNodeIndexToRelative(gz, node); data.pl_node.payload_index = 0; // undefined; set by setBlockBody return agAppendInst(gz->astgen, tag, data); } // AstGen.zig:GenZir.setBlockBody // Finalize a block instruction with its body. Unstacks gz. static void gzSetBlockBody(GenZir* gz, uint32_t inst) { AstGen* ag = gz->astgen; uint32_t body_len_insts; const uint32_t* body = gzInstructionsSlice(gz, &body_len_insts); const uint32_t body_len = countBodyLenAfterFixups(ag, body, body_len_insts); // Zir.Inst.Block: { body_len }. const uint32_t payload_index = ag->extra_len; extraAppend(ag, body_len); ag->inst_datas[inst].pl_node.payload_index = payload_index; appendBodyWithFixups(ag, body, body_len_insts); gzUnstack(gz); } // AstGen.zig:GenZir.setBlockComptimeBody // Finalize a block_comptime instruction. Unstacks gz. static void gzSetBlockComptimeBody( GenZir* gz, uint32_t inst, uint32_t reason) { AstGen* ag = gz->astgen; uint32_t body_len_insts; const uint32_t* body = gzInstructionsSlice(gz, &body_len_insts); const uint32_t body_len = countBodyLenAfterFixups(ag, body, body_len_insts); assert(ag->inst_tags[inst] == ZIR_INST_BLOCK_COMPTIME); // Zir.Inst.BlockComptime: { reason, body_len }. const uint32_t payload_index = ag->extra_len; extraAppend(ag, reason); extraAppend(ag, body_len); ag->inst_datas[inst].pl_node.payload_index = payload_index; appendBodyWithFixups(ag, body, body_len_insts); gzUnstack(gz); } // AstGen.zig:GenZir.addCondBr // Reserve a condbr instruction; payload_index set by // setCondBrPayload. static uint32_t gzAddCondBr(GenZir* gz, ZirInstTag tag, AstNodeIndex node) { ZirInstData data; memset(&data, 0, sizeof(data)); data.pl_node.src_node = gzNodeIndexToRelative(gz, node); data.pl_node.payload_index = 0; // undefined const ZirInstRef ref = gzAdd(gz, tag, data); return ref - ZIR_REF_START_INDEX; } // AstGen.zig:setCondBrPayload static void setCondBrPayload( uint32_t condbr, ZirInstRef cond, GenZir* then_scope, GenZir* else_scope) { AstGen* ag = then_scope->astgen; uint32_t then_body_insts, else_body_insts; const uint32_t* then_body = gzInstructionsSliceUpto(then_scope, else_scope, &then_body_insts); const uint32_t* else_body = gzInstructionsSlice(else_scope, &else_body_insts); const uint32_t then_body_len = countBodyLenAfterFixups(ag, then_body, then_body_insts); const uint32_t else_body_len = countBodyLenAfterFixups(ag, else_body, else_body_insts); // Zir.Inst.CondBr: { condition, then_body_len, else_body_len }. const uint32_t payload_index = ag->extra_len; extraAppend(ag, cond); extraAppend(ag, then_body_len); extraAppend(ag, else_body_len); ag->inst_datas[condbr].pl_node.payload_index = payload_index; appendBodyWithFixups(ag, then_body, then_body_insts); appendBodyWithFixups(ag, else_body, else_body_insts); gzUnstack(else_scope); gzUnstack(then_scope); } // --- ifExpr: if/else, optional/error-union captures, labeled switch else --- // AstGen.zig:ifExpr static ZirInstRef ifExpr( GenZir* parent_gz, Scope* scope, ResultInfo ri, AstNodeIndex node) { AstGen* ag = parent_gz->astgen; const Ast* tree = ag->tree; AstFullIf if_full; astFullIf(tree, node, &if_full); // do_err_trace: mirrors Zig AstGen.zig const bool do_err_trace = (ag->fn_block != NULL) && (if_full.error_token != AST_TOKEN_NONE); const bool need_rl = nodesNeedRl(ag, node); ResultInfo block_ri = ri; if (!need_rl) { switch (ri.rl.tag) { case RL_PTR: { // Convert .ptr to .ty by emitting typeof+elem_type into parent_gz. const ZirInstRef ty = resultLocType(parent_gz, ri, node); block_ri.rl.tag = RL_TY; block_ri.rl.ty = ty; block_ri.rl.inst = 0; block_ri.rl.src_node = 0; break; } case RL_INFERRED_PTR: block_ri.rl.tag = RL_NONE; break; default: block_ri.rl = ri.rl; } } const bool need_result_rvalue = block_ri.rl.tag != ri.rl.tag; GenZir block_scope = gzMakeSubBlock(parent_gz, scope); gzSetBreakResultInfo(&block_scope, block_ri); const bool payload_is_ref = (if_full.payload_token != AST_TOKEN_NONE) && astTokenTag(tree, if_full.payload_token) == TOKEN_ASTERISK; emitDbgNode(parent_gz, if_full.ast.cond_expr); ZirInstRef cond_inst; ZirInstRef bool_bit; if ((if_full.error_token != AST_TOKEN_NONE)) { // Error-union capture: cond is the error union; branch on is_non_err. ResultInfo cond_ri; memset(&cond_ri, 0, sizeof(cond_ri)); cond_ri.rl.tag = payload_is_ref ? RL_REF : RL_NONE; cond_ri.ctx = CTX_ERROR_HANDLING_EXPR; const ZirInstRef err_union = expr( &block_scope, &block_scope.base, cond_ri, if_full.ast.cond_expr); cond_inst = err_union; bool_bit = gzAddUnNode(&block_scope, payload_is_ref ? ZIR_INST_IS_NON_ERR_PTR : ZIR_INST_IS_NON_ERR, err_union, if_full.ast.cond_expr); } else if ((if_full.payload_token != AST_TOKEN_NONE)) { // Optional capture: cond is the optional; branch on is_non_null. ResultInfo cond_ri; memset(&cond_ri, 0, sizeof(cond_ri)); cond_ri.rl.tag = payload_is_ref ? RL_REF : RL_NONE; const ZirInstRef optional = expr( &block_scope, &block_scope.base, cond_ri, if_full.ast.cond_expr); cond_inst = optional; bool_bit = gzAddUnNode(&block_scope, payload_is_ref ? ZIR_INST_IS_NON_NULL_PTR : ZIR_INST_IS_NON_NULL, optional, if_full.ast.cond_expr); } else { cond_inst = expr(&block_scope, &block_scope.base, coerced_bool_ri, if_full.ast.cond_expr); bool_bit = cond_inst; } const uint32_t condbr = gzAddCondBr(&block_scope, ZIR_INST_CONDBR, node); const uint32_t block = gzMakeBlockInst(parent_gz, ZIR_INST_BLOCK, node); gzSetBlockBody(&block_scope, block); gzStackPush(ag, block); GenZir then_scope = gzMakeSubBlock(parent_gz, scope); const AstNodeIndex then_node = if_full.ast.then_expr; Scope* then_sub_scope; LocalVal then_pv; memset(&then_pv, 0, sizeof(then_pv)); if ((if_full.error_token != AST_TOKEN_NONE)) { // Error-union then-branch: extract success payload or void-check. if ((if_full.payload_token != AST_TOKEN_NONE)) { const AstTokenIndex ident_token = if_full.payload_token + (payload_is_ref ? 1 : 0); // Always generate err_union_payload_unsafe (even for |_| // captures). Only skip scope binding when identifier is `_`. const ZirInstRef payload_inst = gzAddUnNode(&then_scope, payload_is_ref ? ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE_PTR : ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE, cond_inst, then_node); const uint32_t ident_name = identAsString(ag, ident_token); if (tokenIsUnderscore(tree, ident_token)) { then_sub_scope = &then_scope.base; } else { then_pv.base.tag = SCOPE_LOCAL_VAL; then_pv.base.parent = &then_scope.base; then_pv.gen_zir = &then_scope; then_pv.name = ident_name; then_pv.inst = payload_inst; then_pv.token_src = ident_token; then_pv.id_cat = ID_CAT_CAPTURE; gzAddDbgVar(&then_scope, ZIR_INST_DBG_VAR_VAL, ident_name, payload_inst); then_sub_scope = &then_pv.base; } } else { // No payload token: ensure the error-union payload is void. gzAddUnNode(&then_scope, ZIR_INST_ENSURE_ERR_UNION_PAYLOAD_VOID, cond_inst, node); then_sub_scope = &then_scope.base; } } else if ((if_full.payload_token != AST_TOKEN_NONE)) { const AstTokenIndex ident_token = if_full.payload_token + (payload_is_ref ? 1 : 0); if (tokenIsUnderscore(tree, ident_token)) { if (payload_is_ref) failTok(ag, if_full.payload_token, "pointer modifier invalid on discard"); then_sub_scope = &then_scope.base; } else { const ZirInstRef payload_inst = gzAddUnNode(&then_scope, payload_is_ref ? ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE_PTR : ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE, cond_inst, then_node); const uint32_t ident_name = identAsString(ag, ident_token); // detectLocalShadowing omitted: shadowing is a compile error // upstream, // and zig1 accepts programs upstream rejects. then_pv.base.tag = SCOPE_LOCAL_VAL; then_pv.base.parent = &then_scope.base; then_pv.gen_zir = &then_scope; then_pv.name = ident_name; then_pv.inst = payload_inst; then_pv.token_src = ident_token; then_pv.id_cat = ID_CAT_CAPTURE; gzAddDbgVar( &then_scope, ZIR_INST_DBG_VAR_VAL, ident_name, payload_inst); then_sub_scope = &then_pv.base; } } else { then_sub_scope = &then_scope.base; } const ZirInstRef then_result = fullBodyExpr(&then_scope, then_sub_scope, block_scope.break_result_info, then_node, BLOCK_KIND_ALLOW_BRANCH_HINT); checkUsed(&then_scope.base, then_sub_scope); if (!gzEndsWithNoReturn(&then_scope)) gzAddBreakWithSrcNode(&then_scope, ZIR_INST_BREAK, block, then_result, if_full.ast.then_expr); GenZir else_scope = gzMakeSubBlock(parent_gz, scope); // save_err_ret_index in else_scope for error-union if-else if condition // may modify the error trace. Mirrors AstGen.zig ifExpr. if (do_err_trace && nodeMayAppendToErrorTrace(tree, if_full.ast.cond_expr)) gzAddSaveErrRetIndex(&else_scope, ZIR_REF_NONE); if (if_full.ast.else_expr != AST_NODE_NONE) { Scope* sub_scope = &else_scope.base; LocalVal else_pv; memset(&else_pv, 0, sizeof(else_pv)); if ((if_full.error_token != AST_TOKEN_NONE)) { // Else branch: extract error code for |err| capture. // Always emit err_union_code (even for |_|), then only create // a scope binding if the capture is not '_'. Matches AstGen.zig // orelseCatchExpr which calls addUnNode then identAsString before // checking for '_'. const AstTokenIndex error_token = if_full.error_token; const ZirInstRef err_inst = gzAddUnNode(&else_scope, payload_is_ref ? ZIR_INST_ERR_UNION_CODE_PTR : ZIR_INST_ERR_UNION_CODE, cond_inst, if_full.ast.cond_expr); const uint32_t ident_name = identAsString(ag, error_token); if (!tokenIsUnderscore(tree, error_token)) { else_pv.base.tag = SCOPE_LOCAL_VAL; else_pv.base.parent = &else_scope.base; else_pv.gen_zir = &else_scope; else_pv.name = ident_name; else_pv.inst = err_inst; else_pv.token_src = error_token; else_pv.id_cat = ID_CAT_CAPTURE; gzAddDbgVar( &else_scope, ZIR_INST_DBG_VAR_VAL, ident_name, err_inst); sub_scope = &else_pv.base; } } AstFullSwitch switch_full = { 0 }; const ZirInstRef else_result = (if_full.error_token != AST_TOKEN_NONE) && switchOnErrCapture(tree, if_full.ast.else_expr, if_full.error_token, true, &switch_full) ? switchExpr(&else_scope, sub_scope, block_scope.break_result_info, if_full.ast.else_expr, switch_full, (SwitchNonErr) { .tag = SWITCH_NON_ERR_PEER_BREAK_TARGET, .block_inst = block, .block_ri = block_ri }) : fullBodyExpr(&else_scope, sub_scope, block_scope.break_result_info, if_full.ast.else_expr, BLOCK_KIND_ALLOW_BRANCH_HINT); if (!gzEndsWithNoReturn(&else_scope)) { if (do_err_trace) restoreErrRetIndex(&else_scope, block, block_scope.break_result_info, if_full.ast.else_expr, else_result); gzAddBreakWithSrcNode(&else_scope, ZIR_INST_BREAK, block, else_result, if_full.ast.else_expr); } checkUsed(&else_scope.base, sub_scope); } else { const ZirInstRef result = rvalue(&else_scope, ri, ZIR_REF_VOID_VALUE, node); gzAddBreak(&else_scope, ZIR_INST_BREAK, block, result); } setCondBrPayload(condbr, bool_bit, &then_scope, &else_scope); if (need_result_rvalue) return rvalue(parent_gz, ri, block + ZIR_REF_START_INDEX, node); return block + ZIR_REF_START_INDEX; } // --- whileExpr --- // AstGen.zig:whileExpr static ZirInstRef whileExpr(GenZir* parent_gz, Scope* scope, ResultInfo ri, AstNodeIndex node, bool is_statement) { AstGen* ag = parent_gz->astgen; const Ast* tree = ag->tree; AstFullWhile while_full; astFullWhile(tree, node, &while_full); const bool is_inline = (while_full.inline_token != AST_TOKEN_NONE); const bool has_error_token = (while_full.error_token != AST_TOKEN_NONE); const bool payload_is_ref = (while_full.payload_token != AST_TOKEN_NONE) && (astTokenTag(tree, while_full.payload_token) == TOKEN_ASTERISK); const bool need_rl = nodesNeedRl(ag, node); ResultInfo block_ri; memset(&block_ri, 0, sizeof(block_ri)); block_ri.ctx = ri.ctx; if (need_rl) { block_ri.rl = ri.rl; } else { switch (ri.rl.tag) { case RL_PTR: { // Convert .ptr to .ty by emitting typeof+elem_type into parent_gz. // Mirrors AstGen.zig whileExpr: // .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? }, const ZirInstRef ty = resultLocType(parent_gz, ri, node); block_ri.rl.tag = RL_TY; block_ri.rl.ty = ty; block_ri.rl.inst = 0; block_ri.rl.src_node = 0; break; } case RL_INFERRED_PTR: block_ri.rl.tag = RL_NONE; break; default: block_ri.rl = ri.rl; } } const bool need_result_rvalue = block_ri.rl.tag != ri.rl.tag; const ZirInstTag loop_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_LOOP; const ZirInstTag condbr_tag = is_inline ? ZIR_INST_CONDBR_INLINE : ZIR_INST_CONDBR; const ZirInstTag block_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_BLOCK; const ZirInstTag repeat_tag = is_inline ? ZIR_INST_REPEAT_INLINE : ZIR_INST_REPEAT; const ZirInstTag break_tag = is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK; const uint32_t loop_block = gzMakeBlockInst(parent_gz, loop_tag, node); gzStackPush(ag, loop_block); GenZir loop_scope = gzMakeSubBlock(parent_gz, scope); loop_scope.is_inline = is_inline; GenZir cond_scope = gzMakeSubBlock(parent_gz, &loop_scope.base); emitDbgNode(parent_gz, while_full.ast.cond_expr); // Evaluate the condition. // - error-union: fullBodyExpr + is_non_err/is_non_err_ptr // - optional payload: fullBodyExpr + is_non_null/is_non_null_ptr // - plain bool: fullBodyExpr with coerced_bool_ri ZirInstRef cond_inst; ZirInstRef bool_bit; if (has_error_token) { const ResultInfo cond_ri = { { payload_is_ref ? RL_REF : RL_NONE, 0, 0, 0, NULL, 0, 0 }, CTX_NONE }; cond_inst = fullBodyExpr(&cond_scope, &cond_scope.base, cond_ri, while_full.ast.cond_expr, BLOCK_KIND_NORMAL); const ZirInstTag is_non_err_tag = payload_is_ref ? ZIR_INST_IS_NON_ERR_PTR : ZIR_INST_IS_NON_ERR; bool_bit = gzAddUnNode( &cond_scope, is_non_err_tag, cond_inst, while_full.ast.cond_expr); } else if ((while_full.payload_token != AST_TOKEN_NONE)) { const ResultInfo cond_ri = { { payload_is_ref ? RL_REF : RL_NONE, 0, 0, 0, NULL, 0, 0 }, CTX_NONE }; cond_inst = fullBodyExpr(&cond_scope, &cond_scope.base, cond_ri, while_full.ast.cond_expr, BLOCK_KIND_NORMAL); const ZirInstTag is_non_null_tag = payload_is_ref ? ZIR_INST_IS_NON_NULL_PTR : ZIR_INST_IS_NON_NULL; bool_bit = gzAddUnNode( &cond_scope, is_non_null_tag, cond_inst, while_full.ast.cond_expr); } else { // No error/payload: cond is a bool. cond_inst = fullBodyExpr(&cond_scope, &cond_scope.base, coerced_bool_ri, while_full.ast.cond_expr, BLOCK_KIND_NORMAL); bool_bit = cond_inst; } const uint32_t condbr = gzAddCondBr(&cond_scope, condbr_tag, node); const uint32_t cond_block = gzMakeBlockInst(&loop_scope, block_tag, node); gzSetBlockBody(&cond_scope, cond_block); gzStackPush(ag, cond_block); GenZir then_scope = gzMakeSubBlock(parent_gz, &cond_scope.base); then_scope.instructions_top = GZ_UNSTACKED; // Set up optional/error payload capture (if any) before creating // continue_scope, so that continue_scope's parent chain includes the // payload LocalVal. bool has_payload_inst = false; uint32_t payload_inst_index = 0; uint32_t payload_dbg_var_name = 0; ZirInstRef payload_dbg_var_inst = ZIR_REF_NONE; LocalVal payload_val_scope; memset(&payload_val_scope, 0, sizeof(payload_val_scope)); Scope* then_sub_scope = &then_scope.base; if (has_error_token) { if ((while_full.payload_token != AST_TOKEN_NONE)) { // Error-union with payload capture: // err_union_payload_unsafe[_ptr]. const AstTokenIndex ident_token = while_full.payload_token + (payload_is_ref ? 1 : 0); const ZirInstTag ptag = payload_is_ref ? ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE_PTR : ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE; payload_inst_index = gzMakeUnNode( &then_scope, ptag, cond_inst, while_full.ast.cond_expr); has_payload_inst = true; if (!tokenIsUnderscore(tree, ident_token)) { const uint32_t ident_name = identAsString(ag, ident_token); payload_val_scope.base.tag = SCOPE_LOCAL_VAL; payload_val_scope.base.parent = &then_scope.base; payload_val_scope.gen_zir = &then_scope; payload_val_scope.name = ident_name; payload_val_scope.inst = payload_inst_index + ZIR_REF_START_INDEX; payload_val_scope.token_src = ident_token; payload_val_scope.id_cat = ID_CAT_CAPTURE; then_sub_scope = &payload_val_scope.base; payload_dbg_var_name = ident_name; payload_dbg_var_inst = payload_inst_index + ZIR_REF_START_INDEX; } } else { // Upstream's `then_scope.addUnNode` while then_scope is still // unstacked: the add lands on the shared instruction stack, at the // top of loop_scope, so the loop body holds it after cond_block. gzAddUnNode(&then_scope, ZIR_INST_ENSURE_ERR_UNION_PAYLOAD_VOID, cond_inst, node); } } else if ((while_full.payload_token != AST_TOKEN_NONE)) { const AstTokenIndex ident_token = while_full.payload_token + (payload_is_ref ? 1 : 0); const ZirInstTag ptag = payload_is_ref ? ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE_PTR : ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE; // Create instruction without pushing; it will be added to then_scope // after then_scope is stacked (matching Zig's makeUnNode pattern). payload_inst_index = gzMakeUnNode( &then_scope, ptag, cond_inst, while_full.ast.cond_expr); has_payload_inst = true; // identAsString is called before the _ check (mirrors AstGen.zig // whileExpr optional-payload path: identAsString is called // unconditionally, then `_` is detected and scope/dbg_var setup is // skipped). const uint32_t ident_name = identAsString(ag, ident_token); if (!tokenIsUnderscore(tree, ident_token)) { // detectLocalShadowing omitted: shadowing is a compile error // upstream, // and zig1 accepts programs upstream rejects. payload_val_scope.base.tag = SCOPE_LOCAL_VAL; payload_val_scope.base.parent = &then_scope.base; payload_val_scope.gen_zir = &then_scope; payload_val_scope.name = ident_name; payload_val_scope.inst = payload_inst_index + ZIR_REF_START_INDEX; payload_val_scope.token_src = ident_token; payload_val_scope.id_cat = ID_CAT_CAPTURE; then_sub_scope = &payload_val_scope.base; payload_dbg_var_name = ident_name; payload_dbg_var_inst = payload_inst_index + ZIR_REF_START_INDEX; } } // continue_scope is created here but only stacked below (its // instructions_top is set right before use); nothing reads it until then. GenZir continue_scope = gzMakeSubBlock(parent_gz, then_sub_scope); const uint32_t continue_block = gzMakeBlockInst(&then_scope, block_tag, node); gzAddNode(&loop_scope, repeat_tag, node); gzSetBlockBody(&loop_scope, loop_block); loop_scope.allow_unlabeled_control_flow = true; loop_scope.break_target = loop_block; loop_scope.continue_target = continue_block; if ((while_full.label_token != AST_TOKEN_NONE)) loop_scope.label_token = while_full.label_token; gzSetBreakResultInfo(&loop_scope, block_ri); then_scope.instructions_top = ag->gz_len; // Push payload instruction first (if any), then continue_block, matching // Zig's: then_scope.append(payload_inst); // then_scope.append(continue_block). if (has_payload_inst) gzStackPush(ag, payload_inst_index); if (payload_dbg_var_name != 0) gzAddDbgVar(&then_scope, ZIR_INST_DBG_VAR_VAL, payload_dbg_var_name, payload_dbg_var_inst); gzStackPush(ag, continue_block); if (while_full.ast.cont_expr != AST_NODE_NONE) unusedResultExpr( &then_scope, then_sub_scope, while_full.ast.cont_expr); continue_scope.instructions_top = ag->gz_len; { emitDbgNode(&continue_scope, while_full.ast.then_expr); const ZirInstRef unused_result = fullBodyExpr(&continue_scope, &continue_scope.base, none_ri, while_full.ast.then_expr, BLOCK_KIND_ALLOW_BRANCH_HINT); addEnsureResult( &continue_scope, unused_result, while_full.ast.then_expr); } checkUsed(&then_scope.base, then_sub_scope); if (!gzEndsWithNoReturn(&continue_scope)) { advanceSourceCursor(ag, astTokenStart(tree, astLastToken(tree, while_full.ast.then_expr))); emitDbgStmt(parent_gz, ag->source_line - parent_gz->decl_line, ag->source_column); // dbg_empty_stmt (small/operand are undefined in upstream; the compare // harness masks them for this opcode). ZirInstData ext; memset(&ext, 0, sizeof(ext)); ext.extended.opcode = ZIR_EXT_DBG_EMPTY_STMT; gzAdd(parent_gz, ZIR_INST_EXTENDED, ext); if (!continue_scope.is_comptime) gzAddRestoreErrRetIndex(&continue_scope, continue_block, RESTORE_ALWAYS, while_full.ast.then_expr); gzAddBreak( &continue_scope, break_tag, continue_block, ZIR_REF_VOID_VALUE); } gzSetBlockBody(&continue_scope, continue_block); if (!then_scope.is_comptime) { const AstNodeIndex cont_node = while_full.ast.cont_expr != AST_NODE_NONE ? while_full.ast.cont_expr : while_full.ast.then_expr; gzAddRestoreErrRetIndex( &then_scope, cond_block, RESTORE_ALWAYS, cont_node); } gzAddBreak(&then_scope, break_tag, cond_block, ZIR_REF_VOID_VALUE); GenZir else_scope = gzMakeSubBlock(parent_gz, &cond_scope.base); if (while_full.ast.else_expr != AST_NODE_NONE) { Scope* sub_scope = &else_scope.base; // If has_error_token: extract err_union_code into the else scope and // set up a LocalVal for the error capture (mirrors AstGen.zig). LocalVal err_capture_scope; memset(&err_capture_scope, 0, sizeof(err_capture_scope)); if (has_error_token) { const ZirInstTag err_tag = payload_is_ref ? ZIR_INST_ERR_UNION_CODE_PTR : ZIR_INST_ERR_UNION_CODE; const ZirInstRef else_payload_inst = gzAddUnNode( &else_scope, err_tag, cond_inst, while_full.ast.cond_expr); const AstTokenIndex error_token = while_full.error_token; // identAsString precedes the `_` check, as upstream's does. const uint32_t ident_name = identAsString(ag, error_token); if (!tokenIsUnderscore(tree, error_token)) { err_capture_scope.base.tag = SCOPE_LOCAL_VAL; err_capture_scope.base.parent = &else_scope.base; err_capture_scope.gen_zir = &else_scope; err_capture_scope.name = ident_name; err_capture_scope.inst = else_payload_inst; err_capture_scope.token_src = error_token; err_capture_scope.id_cat = ID_CAT_CAPTURE; gzAddDbgVar(&else_scope, ZIR_INST_DBG_VAR_VAL, ident_name, else_payload_inst); sub_scope = &err_capture_scope.base; } } loop_scope.allow_unlabeled_control_flow = false; loop_scope.continue_target = 0; const ZirInstRef else_result = fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, while_full.ast.else_expr, BLOCK_KIND_ALLOW_BRANCH_HINT); if (is_statement) addEnsureResult( &else_scope, else_result, while_full.ast.else_expr); checkUsed(&else_scope.base, sub_scope); if (!gzEndsWithNoReturn(&else_scope)) { if (!else_scope.is_comptime) gzAddRestoreErrRetIndex(&else_scope, loop_block, RESTORE_ALWAYS, while_full.ast.else_expr); gzAddBreakWithSrcNode(&else_scope, break_tag, loop_block, else_result, while_full.ast.else_expr); } } else { const ZirInstRef result = rvalue(&else_scope, ri, ZIR_REF_VOID_VALUE, node); gzAddBreak(&else_scope, break_tag, loop_block, result); } setCondBrPayload(condbr, bool_bit, &then_scope, &else_scope); const ZirInstRef result = need_result_rvalue ? rvalue(parent_gz, ri, loop_block + ZIR_REF_START_INDEX, node) : loop_block + ZIR_REF_START_INDEX; if (is_statement) gzAddUnNode(parent_gz, ZIR_INST_ENSURE_RESULT_USED, result, node); // loop_scope / cond_scope already unstacked above (idempotent). gzUnstack(&cond_scope); gzUnstack(&loop_scope); return result; } // AstGen's `no_switch_on_err` test: `node` is a switch on the error capture // itself, labeled iff `labeled`; it is decoded into `switch_full`. static bool switchOnErrCapture(const Ast* tree, AstNodeIndex node, AstTokenIndex capture_token, bool labeled, AstFullSwitch* switch_full) { if (!astFullSwitch(tree, node, switch_full) || (switch_full->label_token != AST_TOKEN_NONE) != labeled) return false; const AstNodeIndex cond = switch_full->ast.condition; if (astNodeTag(tree, cond) != AST_NODE_IDENTIFIER) return false; const char *cap_s, *cond_s; uint32_t cap_l, cond_l; astTokenSlice(tree, capture_token, &cap_s, &cap_l); astTokenSlice(tree, astNodeMainToken(tree, cond), &cond_s, &cond_l); return cap_l == cond_l && memcmp(cap_s, cond_s, cap_l) == 0; } // AstGen.zig:errorSetDecl // Upstream's duplicate-name error is not reported: zig1 accepts the programs // it rejects. static ZirInstRef errorSetDecl(GenZir* gz, ResultInfo ri, AstNodeIndex node) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; // Zir.Inst.ErrorSetDecl. const uint32_t payload_index = reserveExtra(ag, 1); uint32_t fields_len = 0; { const AstTokenIndex lbrace = astNodeData(tree, node).lhs; const AstTokenIndex rbrace = astNodeData(tree, node).rhs; for (AstTokenIndex tok_i = lbrace + 1; tok_i < rbrace; tok_i++) { switch (astTokenTag(tree, tok_i)) { case TOKEN_DOC_COMMENT: case TOKEN_COMMA: break; case TOKEN_IDENTIFIER: extraAppend(ag, identAsString(ag, tok_i)); fields_len += 1; break; default: UNREACHABLE("errorSetDecl: unexpected token"); } } } ag->extra[payload_index] = fields_len; const ZirInstRef result = gzAddPlNodePayloadIndex( gz, ZIR_INST_ERROR_SET_DECL, node, payload_index); return rvalue(gz, ri, result, node); } // AstGen.zig:orelseCatchExpr static ZirInstRef orelseCatchExpr(GenZir* parent_gz, Scope* scope, ResultInfo ri, AstNodeIndex node, ZirInstTag cond_op, ZirInstTag unwrap_op, ZirInstTag unwrap_code_op, AstTokenIndex payload_token) { AstGen* ag = parent_gz->astgen; const Ast* tree = ag->tree; const AstNodeIndex lhs = astNodeData(tree, node).lhs; const AstNodeIndex rhs = astNodeData(tree, node).rhs; const bool need_rl = nodesNeedRl(ag, node); ResultInfo block_ri = ri; if (!need_rl) { switch (ri.rl.tag) { case RL_PTR: { const ZirInstRef ty = resultLocType(parent_gz, ri, node); block_ri.rl = (ResultLoc) { RL_TY, ty, 0, 0, NULL, 0, 0 }; break; } case RL_INFERRED_PTR: block_ri.rl = none_ri.rl; break; default: break; } } // We need to call `rvalue` to write through to the pointer only if we // had a result pointer and aren't forwarding it. const bool need_result_rvalue = block_ri.rl.tag != ri.rl.tag; const bool do_err_trace = ag->fn_block != NULL && (cond_op == ZIR_INST_IS_NON_ERR || cond_op == ZIR_INST_IS_NON_ERR_PTR); GenZir block_scope = gzMakeSubBlock(parent_gz, scope); gzSetBreakResultInfo(&block_scope, block_ri); ResultInfo operand_ri = none_ri; switch (block_scope.break_result_info.rl.tag) { case RL_REF: case RL_REF_COERCED_TY: operand_ri.rl.tag = RL_REF; break; case RL_REF_CONST: operand_ri.rl.tag = RL_REF_CONST; break; default: break; } operand_ri.ctx = do_err_trace ? CTX_ERROR_HANDLING_EXPR : CTX_NONE; // This could be a pointer or value depending on the `operand_ri` // parameter. We cannot use `block_scope.break_result_info` because that // has the bare type, whereas this expression has the optional type. // Later we make up for this fact by calling rvalue on the else branch. const ZirInstRef operand = reachableExpr(&block_scope, &block_scope.base, operand_ri, lhs, rhs); const ZirInstRef cond = gzAddUnNode(&block_scope, cond_op, operand, node); const uint32_t condbr = gzAddCondBr(&block_scope, ZIR_INST_CONDBR, node); const uint32_t block = gzMakeBlockInst(parent_gz, ZIR_INST_BLOCK, node); gzSetBlockBody(&block_scope, block); // block_scope unstacked now, can add new instructions to parent_gz gzStackPush(ag, block); GenZir then_scope = gzMakeSubBlock(&block_scope, scope); // This could be a pointer or value depending on `unwrap_op`. const ZirInstRef unwrapped_payload = gzAddUnNode(&then_scope, unwrap_op, operand, node); ZirInstRef then_result; switch (ri.rl.tag) { case RL_REF: case RL_REF_CONST: case RL_REF_COERCED_TY: then_result = unwrapped_payload; break; default: then_result = rvalue(&then_scope, block_scope.break_result_info, unwrapped_payload, node); break; } gzAddBreakWithSrcNode( &then_scope, ZIR_INST_BREAK, block, then_result, node); GenZir else_scope = gzMakeSubBlock(&block_scope, scope); // We know that the operand (almost certainly) modified the error return // trace, so signal to Sema that it should save the new index for // restoring later. if (do_err_trace && nodeMayAppendToErrorTrace(tree, lhs)) gzAddSaveErrRetIndex(&else_scope, ZIR_REF_NONE); LocalVal err_val_scope; Scope* else_sub_scope = &else_scope.base; if (payload_token != AST_TOKEN_NONE) { if (tokenIsUnderscore(tree, payload_token)) failTok(ag, payload_token, "discard of error capture; omit it instead"); const uint32_t err_name = identAsString(ag, payload_token); // detectLocalShadowing is not ported: zig1 accepts the programs it // rejects. memset(&err_val_scope, 0, sizeof(err_val_scope)); err_val_scope.base.tag = SCOPE_LOCAL_VAL; err_val_scope.base.parent = &else_scope.base; err_val_scope.gen_zir = &else_scope; err_val_scope.name = err_name; err_val_scope.inst = gzAddUnNode(&else_scope, unwrap_code_op, operand, node); err_val_scope.token_src = payload_token; err_val_scope.id_cat = ID_CAT_CAPTURE; else_sub_scope = &err_val_scope.base; } ZirInstRef else_result; // A labeled `catch |e| l: switch (e)`; the unlabeled form is lowered in // expr. AstFullSwitch switch_full = { 0 }; if (astNodeTag(tree, node) == AST_NODE_CATCH && payload_token != AST_TOKEN_NONE && switchOnErrCapture(tree, rhs, payload_token, true, &switch_full)) { else_result = switchExpr(&else_scope, else_sub_scope, block_scope.break_result_info, rhs, switch_full, (SwitchNonErr) { .tag = SWITCH_NON_ERR_PEER_BREAK_TARGET, .block_inst = block, .block_ri = block_ri }); } else { else_result = fullBodyExpr(&else_scope, else_sub_scope, block_scope.break_result_info, rhs, BLOCK_KIND_ALLOW_BRANCH_HINT); } if (!gzEndsWithNoReturn(&else_scope)) { // As our last action before the break, "pop" the error trace if // needed if (do_err_trace) restoreErrRetIndex(&else_scope, block, block_scope.break_result_info, rhs, else_result); gzAddBreakWithSrcNode( &else_scope, ZIR_INST_BREAK, block, else_result, rhs); } checkUsed(&else_scope.base, else_sub_scope); setCondBrPayload(condbr, cond, &then_scope, &else_scope); if (need_result_rvalue) return rvalue(parent_gz, ri, block + ZIR_REF_START_INDEX, node); return block + ZIR_REF_START_INDEX; } // SwitchBlock.Bits #define SWBITS_HAS_MULTI ZIR_SWITCH_BITS_HAS_MULTI_CASES #define SWBITS_ANY_RANGES ZIR_SWITCH_BITS_ANY_RANGES #define SWBITS_HAS_ELSE ZIR_SWITCH_BITS_HAS_ELSE #define SWBITS_HAS_UNDER ZIR_SWITCH_BITS_HAS_UNDER #define SWBITS_HAS_CONTINUE ZIR_SWITCH_BITS_HAS_CONTINUE #define SWBITS_RUNTIME_CAPTURE ZIR_SWITCH_BITS_ANY_MAYBE_RUNTIME_CAPTURE #define SWBITS_PAYLOAD_PH ZIR_SWITCH_BITS_PAYLOAD_CAPTURE_INST_IS_PLACEHOLDER #define SWBITS_TAG_PH ZIR_SWITCH_BITS_TAG_CAPTURE_INST_IS_PLACEHOLDER #define SWBITS_SCALAR_LEN_SHIFT ZIR_SWITCH_BITS_SCALAR_CASES_LEN_SHIFT // One packed-struct field: `value` masked and moved to `PREFIX_FIELD_SHIFT`. #define PACK_FIELD(value, field) \ (((uint32_t)(value) & field##_MASK) << field##_SHIFT) // One bool field of a packed struct. #define PACK_BOOL(value, field) ((value) ? field : 0u) // SwitchBlock.ProngInfo.NonErr #define NON_ERR_INFO(body_len, capture, operand_is_ref) \ (PACK_FIELD(body_len, ZIR_PRONG_NON_ERR_BODY_LEN) \ | PACK_FIELD(capture, ZIR_PRONG_NON_ERR_CAPTURE) \ | PACK_BOOL(operand_is_ref, ZIR_PRONG_NON_ERR_OPERAND_IS_REF)) // SwitchBlock.ProngInfo #define PRONG_INFO(body_len, capture, is_inline, has_tag, is_cu) \ (PACK_FIELD(body_len, ZIR_PRONG_INFO_BODY_LEN) \ | PACK_FIELD(capture, ZIR_PRONG_INFO_CAPTURE) \ | PACK_BOOL(is_inline, ZIR_PRONG_INFO_IS_INLINE) \ | PACK_BOOL(has_tag, ZIR_PRONG_INFO_HAS_TAG_CAPTURE) \ | PACK_BOOL(is_cu, ZIR_PRONG_INFO_IS_COMPTIME_UNREACH)) // SwitchBlock.ProngInfo.Else #define ELSE_INFO(body_len, capture, is_inline, has_tag, is_snr) \ (PACK_FIELD(body_len, ZIR_PRONG_ELSE_BODY_LEN) \ | PACK_FIELD(capture, ZIR_PRONG_ELSE_CAPTURE) \ | PACK_BOOL(is_inline, ZIR_PRONG_ELSE_IS_INLINE) \ | PACK_BOOL(has_tag, ZIR_PRONG_ELSE_HAS_TAG_CAPTURE) \ | PACK_BOOL(is_snr, ZIR_PRONG_ELSE_IS_SIMPLE_NORETURN)) // SwitchBlock.ItemInfo #define ITEM_INFO(kind, data) \ (PACK_FIELD(kind, ZIR_ITEM_INFO_KIND) \ | PACK_FIELD(data, ZIR_ITEM_INFO_DATA)) #define ITEM_INFO_ENUM_LIT(str_idx) \ ITEM_INFO(ZIR_ITEM_INFO_KIND_ENUM_LITERAL, str_idx) #define ITEM_INFO_ERR_VAL(str_idx) \ ITEM_INFO(ZIR_ITEM_INFO_KIND_ERROR_VALUE, str_idx) #define ITEM_INFO_BODY_LEN(n) ITEM_INFO(ZIR_ITEM_INFO_KIND_BODY_LEN, n) #define ITEM_INFO_UNDER ITEM_INFO(ZIR_ITEM_INFO_KIND_UNDER, 0u) // Zir.Inst.SwitchBlock.ItemInfo.bodyLen (0 for a non-body item). static uint32_t itemInfoBodyLen(uint32_t item_info) { return (item_info & ZIR_ITEM_INFO_KIND_MASK) == ZIR_ITEM_INFO_KIND_BODY_LEN ? item_info >> ZIR_ITEM_INFO_DATA_SHIFT : 0; } // --- forExpr --- // AstGen.zig:forExpr static ZirInstRef forExpr(GenZir* parent_gz, Scope* scope, ResultInfo ri, AstNodeIndex node, bool is_statement) { AstGen* ag = parent_gz->astgen; const Ast* tree = ag->tree; AstFullFor for_full; astFullFor(tree, node, &for_full); const bool is_inline = (for_full.inline_token != AST_TOKEN_NONE); const bool need_rl = nodesNeedRl(ag, node); ResultInfo block_ri; memset(&block_ri, 0, sizeof(block_ri)); block_ri.ctx = ri.ctx; if (need_rl) { block_ri.rl = ri.rl; } else { switch (ri.rl.tag) { case RL_PTR: { // Convert .ptr to .ty by emitting typeof+elem_type into parent_gz. // Mirrors AstGen.zig forExpr: // .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? }, const ZirInstRef ty = resultLocType(parent_gz, ri, node); block_ri.rl.tag = RL_TY; block_ri.rl.ty = ty; block_ri.rl.inst = 0; block_ri.rl.src_node = 0; break; } case RL_INFERRED_PTR: block_ri.rl.tag = RL_NONE; break; default: block_ri.rl = ri.rl; } } const bool need_result_rvalue = block_ri.rl.tag != ri.rl.tag; // Allocate the index pointer and initialize to zero. const ZirInstTag alloc_tag = is_inline ? ZIR_INST_ALLOC_COMPTIME_MUT : ZIR_INST_ALLOC; const ZirInstRef index_ptr = gzAddUnNode(parent_gz, alloc_tag, ZIR_REF_USIZE_TYPE, node); { const uint32_t words[2] = { (uint32_t)index_ptr, (uint32_t)ZIR_REF_ZERO_USIZE }; gzAddPlNodeExtra(parent_gz, ZIR_INST_STORE_NODE, node, words, 2); } // Process inputs: build indexables[] and lens[] (pairs of refs per input). const uint32_t inputs_len = for_full.ast.inputs.len; // indexables[i], then lens[2*i] and lens[2*i+1] per input. ZirInstRef* indexables = zig1Calloc((size_t)inputs_len * 3 + 1, sizeof(ZirInstRef)); ZirInstRef* lens = indexables + inputs_len; bool any_len_checks = false; { AstTokenIndex capture_token = for_full.payload_token; for (uint32_t i = 0; i < inputs_len; i++) { const AstNodeIndex input = for_full.ast.inputs.ptr[i]; const bool capture_is_ref = astTokenTag(tree, capture_token) == TOKEN_ASTERISK; const AstTokenIndex ident_tok = capture_token + (capture_is_ref ? 1u : 0u); capture_token = ident_tok + 2; // skip ident + comma/pipe emitDbgNode(parent_gz, input); if (astNodeTag(tree, input) == AST_NODE_FOR_RANGE) { if (capture_is_ref) failTok( ag, ident_tok, "cannot capture reference to range"); const AstNodeIndex start_node = astNodeData(tree, input).lhs; const AstNodeIndex opt_end = astNodeData(tree, input).rhs; const ResultInfo usize_ri = { { RL_TY, ZIR_REF_USIZE_TYPE, 0, 0, NULL, 0, 0 }, 0 }; const ZirInstRef start_val = expr(parent_gz, scope, usize_ri, start_node); ZirInstRef end_val = ZIR_REF_NONE; if (opt_end != AST_NODE_NONE) end_val = expr(parent_gz, scope, usize_ri, opt_end); if (end_val == ZIR_REF_NONE) { lens[i * 2 + 0] = ZIR_REF_NONE; lens[i * 2 + 1] = ZIR_REF_NONE; } else { any_len_checks = true; lens[i * 2 + 0] = start_val; lens[i * 2 + 1] = end_val; } const bool start_is_zero = nodeIsTriviallyZero(tree, start_node); indexables[i] = start_is_zero ? ZIR_REF_NONE : start_val; } else { const ZirInstRef indexable = expr(parent_gz, scope, none_ri, input); any_len_checks = true; indexables[i] = indexable; lens[i * 2 + 0] = indexable; lens[i * 2 + 1] = ZIR_REF_NONE; } } } if (!any_len_checks) failNode(ag, node, "unbounded for loop"); // Emit for_len: MultiOp { operands_len } followed by lens refs. const uint32_t lens_len = inputs_len * 2; const uint32_t for_len_payload = ag->extra_len; extraAppend(ag, lens_len); for (uint32_t i = 0; i < lens_len; i++) extraAppend(ag, (uint32_t)lens[i]); const ZirInstRef len = gzAddPlNodePayloadIndex( parent_gz, ZIR_INST_FOR_LEN, node, for_len_payload); // Create the loop block; push it onto parent_gz's stack. const ZirInstTag loop_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_LOOP; const uint32_t loop_block = gzMakeBlockInst(parent_gz, loop_tag, node); gzStackPush(ag, loop_block); GenZir loop_scope = gzMakeSubBlock(parent_gz, scope); loop_scope.is_inline = is_inline; gzSetBreakResultInfo(&loop_scope, block_ri); // "Detached load": create the load instruction without pushing to // gz_stack. It will be inserted into loop_scope's area after cond_scope is // finalized. ZirInstData load_data; memset(&load_data, 0, sizeof(load_data)); load_data.un_node.operand = index_ptr; load_data.un_node.src_node = gzNodeIndexToRelative(&loop_scope, node); const uint32_t index_idx = agAppendInst(ag, ZIR_INST_LOAD, load_data); const ZirInstRef index = (ZirInstRef)(index_idx + ZIR_REF_START_INDEX); // cond_scope: cmp_lt + condbr. GenZir cond_scope = gzMakeSubBlock(parent_gz, &loop_scope.base); const ZirInstRef cond = gzAddPlNodeBin(&cond_scope, ZIR_INST_CMP_LT, node, index, len); const ZirInstTag condbr_tag = is_inline ? ZIR_INST_CONDBR_INLINE : ZIR_INST_CONDBR; const uint32_t condbr = gzAddCondBr(&cond_scope, condbr_tag, node); const ZirInstTag block_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_BLOCK; const uint32_t cond_block = gzMakeBlockInst(&loop_scope, block_tag, node); gzSetBlockBody(&cond_scope, cond_block); // Set loop control-flow targets. loop_scope.allow_unlabeled_control_flow = true; loop_scope.break_target = loop_block; loop_scope.continue_target = cond_block; if ((for_full.label_token != AST_TOKEN_NONE)) loop_scope.label_token = for_full.label_token; // Push index_idx and cond_block into loop_scope's gz_stack area. gzStackPush(ag, index_idx); gzStackPush(ag, cond_block); // then_scope: capture setup + body. GenZir then_scope = gzMakeSubBlock(parent_gz, &cond_scope.base); // The then-scope's chain points into it until the body is lowered. LocalVal* capture_scopes = zig1Calloc(inputs_len ? inputs_len : 1u, sizeof(LocalVal)); Scope* then_sub_scope = &then_scope.base; { AstTokenIndex capture_token = for_full.payload_token; for (uint32_t i = 0; i < inputs_len; i++) { const AstNodeIndex input = for_full.ast.inputs.ptr[i]; const bool capture_is_ref = astTokenTag(tree, capture_token) == TOKEN_ASTERISK; const AstTokenIndex ident_tok = capture_token + (capture_is_ref ? 1u : 0u); capture_token = ident_tok + 2; if (tokenIsUnderscore(tree, ident_tok)) { if (capture_is_ref) failTok(ag, ident_tok - 1, "pointer modifier invalid on discard"); continue; // discard: no LocalVal } const uint32_t name_str = identAsString(ag, ident_tok); ZirInstRef capture_inst; const bool is_counter = astNodeTag(tree, input) == AST_NODE_FOR_RANGE; if (indexables[i] == ZIR_REF_NONE) { // Zero-start counter: use the index variable directly. capture_inst = index; } else { ZirInstTag tag = ZIR_INST_ELEM_VAL; // overwritten below if (!capture_is_ref && !is_counter) tag = ZIR_INST_ELEM_VAL; else if (!capture_is_ref && is_counter) tag = ZIR_INST_ADD; else if (capture_is_ref && !is_counter) tag = ZIR_INST_ELEM_PTR; else ASTGEN_UNIMPLEMENTED( "forExpr: pointer capture of non-zero counter"); capture_inst = gzAddPlNodeBin( &then_scope, tag, input, indexables[i], index); } capture_scopes[i].base.tag = SCOPE_LOCAL_VAL; capture_scopes[i].base.parent = then_sub_scope; capture_scopes[i].gen_zir = &then_scope; capture_scopes[i].name = name_str; capture_scopes[i].inst = capture_inst; capture_scopes[i].token_src = ident_tok; capture_scopes[i].id_cat = ID_CAT_CAPTURE; gzAddDbgVar( &then_scope, ZIR_INST_DBG_VAR_VAL, name_str, capture_inst); then_sub_scope = &capture_scopes[i].base; } } // Emit the then body. { const ZirInstRef then_result = fullBodyExpr(&then_scope, then_sub_scope, none_ri, for_full.ast.then_expr, BLOCK_KIND_ALLOW_BRANCH_HINT); addEnsureResult(&then_scope, then_result, for_full.ast.then_expr); } checkUsed(&then_scope.base, then_sub_scope); // Advance source cursor + emit dbg_empty_stmt (as in whileExpr). advanceSourceCursor( ag, astTokenStart(tree, astLastToken(tree, for_full.ast.then_expr))); emitDbgStmt( parent_gz, ag->source_line - parent_gz->decl_line, ag->source_column); { ZirInstData ext; memset(&ext, 0, sizeof(ext)); ext.extended.opcode = ZIR_EXT_DBG_EMPTY_STMT; gzAdd(parent_gz, ZIR_INST_EXTENDED, ext); } if (!then_scope.is_comptime) gzAddRestoreErrRetIndex( &then_scope, cond_block, RESTORE_ALWAYS, for_full.ast.then_expr); { const ZirInstTag break_tag = is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK; gzAddBreak(&then_scope, break_tag, cond_block, ZIR_REF_VOID_VALUE); } // else_scope. GenZir else_scope = gzMakeSubBlock(parent_gz, &cond_scope.base); const ZirInstTag break_tag = is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK; if (for_full.ast.else_expr != AST_NODE_NONE) { // Mirror Zig AstGen.zig forExpr else branch. loop_scope.allow_unlabeled_control_flow = false; loop_scope.continue_target = 0; // .none const AstNodeIndex else_node = for_full.ast.else_expr; const ZirInstRef else_result = fullBodyExpr(&else_scope, &else_scope.base, loop_scope.break_result_info, else_node, BLOCK_KIND_ALLOW_BRANCH_HINT); if (is_statement) addEnsureResult(&else_scope, else_result, else_node); if (!gzEndsWithNoReturn(&else_scope)) { if (!else_scope.is_comptime) gzAddRestoreErrRetIndex( &else_scope, loop_block, RESTORE_ALWAYS, else_node); gzAddBreakWithSrcNode( &else_scope, break_tag, loop_block, else_result, else_node); } } else { const ZirInstRef result = rvalue(&else_scope, ri, ZIR_REF_VOID_VALUE, node); gzAddBreak(&else_scope, break_tag, loop_block, result); } setCondBrPayload(condbr, cond, &then_scope, &else_scope); // Finalize loop_scope: add add_unsafe + store_node + repeat. { const ZirInstRef index_plus_one = gzAddPlNodeBin( &loop_scope, ZIR_INST_ADD_UNSAFE, node, index, ZIR_REF_ONE_USIZE); const uint32_t store_words[2] = { (uint32_t)index_ptr, (uint32_t)index_plus_one }; gzAddPlNodeExtra( &loop_scope, ZIR_INST_STORE_NODE, node, store_words, 2); const ZirInstTag repeat_tag = is_inline ? ZIR_INST_REPEAT_INLINE : ZIR_INST_REPEAT; gzAddNode(&loop_scope, repeat_tag, node); gzSetBlockBody(&loop_scope, loop_block); } const ZirInstRef result = need_result_rvalue ? rvalue(parent_gz, ri, (ZirInstRef)(loop_block + ZIR_REF_START_INDEX), node) : (ZirInstRef)(loop_block + ZIR_REF_START_INDEX); if (is_statement) gzAddUnNode(parent_gz, ZIR_INST_ENSURE_RESULT_USED, result, node); gzUnstack(&cond_scope); gzUnstack(&loop_scope); free(indexables); free(capture_scopes); return result; } // --- tryExpr --- // AstGen.zig:GenZir.setTryBody // Finalize a try/try_ptr instruction with its else body. Unstacks. static void gzSetTryBody(GenZir* gz, uint32_t inst, ZirInstRef operand) { AstGen* ag = gz->astgen; uint32_t body_len_insts; const uint32_t* body = gzInstructionsSlice(gz, &body_len_insts); const uint32_t body_len = countBodyLenAfterFixups(ag, body, body_len_insts); // Zir.Inst.Try: { operand, body_len }. const uint32_t payload_index = ag->extra_len; extraAppend(ag, operand); extraAppend(ag, body_len); ag->inst_datas[inst].pl_node.payload_index = payload_index; appendBodyWithFixups(ag, body, body_len_insts); gzUnstack(gz); } static bool nodeIsCall(const Ast* tree, AstNodeIndex node) { switch (astNodeTag(tree, node)) { case AST_NODE_CALL_ONE: case AST_NODE_CALL_ONE_COMMA: case AST_NODE_CALL: case AST_NODE_CALL_COMMA: return true; default: return false; } } // AstGen.zig:switchExpr static ZirInstRef switchExpr(GenZir* parent_gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstFullSwitch switch_full, SwitchNonErr non_err) { AstGen* ag = parent_gz->astgen; const Ast* tree = ag->tree; AstNodeIndex switch_node = node; AstNodeIndex operand_node = switch_full.ast.condition; AstTokenIndex err_token = AST_TOKEN_NONE; switch (non_err.tag) { case SWITCH_NON_ERR_NONE: case SWITCH_NON_ERR_PEER_BREAK_TARGET: break; case SWITCH_NON_ERR_CATCH: switch_node = astNodeData(tree, node).rhs; operand_node = astNodeData(tree, node).lhs; err_token = astNodeMainToken(tree, node) + 2; break; case SWITCH_NON_ERR_IF: switch_node = non_err.if_full.ast.else_expr; operand_node = non_err.if_full.ast.cond_expr; err_token = non_err.if_full.error_token; break; } const AstNodeSlice case_nodes = switch_full.ast.cases; const bool needs_non_err_handling = non_err.tag == SWITCH_NON_ERR_CATCH || non_err.tag == SWITCH_NON_ERR_IF; const bool need_rl = nodesNeedRl(ag, node); ResultInfo block_ri = ri; if (!need_rl) { if (ri.rl.tag == RL_PTR) { block_ri.rl = (ResultLoc) { .tag = RL_TY, .ty = resultLocType(parent_gz, ri, node) }; } else if (ri.rl.tag == RL_INFERRED_PTR) { block_ri.rl = (ResultLoc) { .tag = RL_NONE }; } } // We need to call `rvalue` to write through to the pointer only if we had // a result pointer and aren't forwarding it. const bool need_result_rvalue = block_ri.rl.tag != ri.rl.tag; const AstNodeIndex catch_or_if_node = node; const bool do_err_trace = needs_non_err_handling && ag->fn_block != NULL; enum { NON_ERR_IS_REF_NO, NON_ERR_IS_REF_YES, NON_ERR_IS_REF_YES_CONST } non_err_is_ref = NON_ERR_IS_REF_NO; if (non_err.tag == SWITCH_NON_ERR_CATCH) { if (ri.rl.tag == RL_REF || ri.rl.tag == RL_REF_COERCED_TY) non_err_is_ref = NON_ERR_IS_REF_YES; else if (ri.rl.tag == RL_REF_CONST) non_err_is_ref = NON_ERR_IS_REF_YES_CONST; } else if (non_err.tag == SWITCH_NON_ERR_IF) { if (non_err.if_full.payload_token != AST_TOKEN_NONE && astTokenTag(tree, non_err.if_full.payload_token) == TOKEN_ASTERISK) non_err_is_ref = NON_ERR_IS_REF_YES; } const uint32_t err_capture_name = needs_non_err_handling ? identAsString(ag, err_token) : 0; // We perform two passes over the AST. This first pass is to collect // information for the following variables and make note of the special // prong AST node indices. bool any_payload_is_ref = false; bool any_has_payload_capture = false; bool any_has_tag_capture = false; bool any_maybe_runtime_capture = false; uint32_t scalar_cases_len = 0; uint32_t multi_cases_len = 0; uint32_t total_items_len = 0; uint32_t total_ranges_len = 0; AstNodeIndex else_case_node = AST_NODE_NONE; AstNodeIndex underscore_node = AST_NODE_NONE; for (uint32_t ci = 0; ci < case_nodes.len; ci++) { const AstNodeIndex case_node = case_nodes.ptr[ci]; AstFullSwitchCase case_full; astFullSwitchCase(tree, case_node, &case_full); if (case_full.payload_token != AST_TOKEN_NONE) { AstTokenIndex ident = case_full.payload_token; if (astTokenTag(tree, ident) == TOKEN_ASTERISK) { any_payload_is_ref = true; ident += 1; } if (!tokenIsUnderscore(tree, ident)) { any_has_payload_capture = true; // If we're capturing a union, its payload value cannot // always be comptime-known, even if its prong is inlined as // inlining only affects its enum tag. any_maybe_runtime_capture = true; } if (astTokenTag(tree, ident + 1) == TOKEN_COMMA) { any_has_tag_capture = true; if (case_full.inline_token == AST_TOKEN_NONE) any_maybe_runtime_capture = true; } } // Check for else prong. if (case_full.ast.values.len == 0) { else_case_node = case_node; continue; } // Check for '_' prong and ranges. bool case_has_ranges = false; for (uint32_t vi = 0; vi < case_full.ast.values.len; vi++) { const AstNodeIndex val = case_full.ast.values.ptr[vi]; if (astNodeTag(tree, val) == AST_NODE_SWITCH_RANGE) { total_ranges_len += 1; case_has_ranges = true; } else { total_items_len += 1; if (astNodeTag(tree, val) == AST_NODE_IDENTIFIER && tokenIsUnderscore(tree, astNodeMainToken(tree, val))) underscore_node = val; } } const uint32_t case_len = case_full.ast.values.len; if (case_len == 1 && !case_has_ranges) scalar_cases_len += 1; else multi_cases_len += 1; } const bool has_else = else_case_node != AST_NODE_NONE; const bool has_under = underscore_node != AST_NODE_NONE; const bool any_ranges = total_ranges_len > 0; // This contains all of the body lengths (already in the correct order) // and the bodies they belong to that go into the `extra` array later, // except the first body_table_end slots are a table that indexes the // item bodies (and also indirectly the prong bodies, as they are always // trailing after their item bodies). const uint32_t scratch_top = ag->scratch_len; uint32_t payloads_end = scratch_top; const uint32_t scalar_body_table = payloads_end; payloads_end += scalar_cases_len; const uint32_t multi_item_body_table = payloads_end; payloads_end += total_items_len + 2 * total_ranges_len - scalar_cases_len; const uint32_t multi_prong_body_table = payloads_end; payloads_end += multi_cases_len; const uint32_t body_table_end = payloads_end; const uint32_t scalar_prong_infos_start = payloads_end; payloads_end += scalar_cases_len; const uint32_t multi_prong_infos_start = payloads_end; payloads_end += multi_cases_len; const uint32_t multi_case_items_lens_start = payloads_end; payloads_end += multi_cases_len; const uint32_t multi_case_ranges_lens_start = payloads_end; if (any_ranges) payloads_end += multi_cases_len; const uint32_t scalar_item_infos_start = payloads_end; payloads_end += scalar_cases_len; const uint32_t multi_items_infos_start = payloads_end; payloads_end += total_items_len - scalar_cases_len + 2 * total_ranges_len; const uint32_t bodies_start = payloads_end; while (ag->scratch_len < bodies_start) scratchAppend(ag, 0); uint32_t non_err_prong_body_start = 0; uint32_t else_prong_body_start = 0; uint32_t non_err_body_len = 0; uint32_t non_err_info = 0; uint32_t else_body_len = 0; uint32_t else_info = 0; GenZir block_scope = gzMakeSubBlock(parent_gz, scope); // block_scope not used for collecting instructions block_scope.instructions_top = GZ_UNSTACKED; ResultInfo operand_ri = { .rl = { .tag = RL_NONE }, .ctx = do_err_trace ? CTX_ERROR_HANDLING_EXPR : CTX_NONE }; if (any_payload_is_ref || (needs_non_err_handling && non_err_is_ref == NON_ERR_IS_REF_YES)) operand_ri.rl.tag = RL_REF; else if (needs_non_err_handling && non_err_is_ref == NON_ERR_IS_REF_YES_CONST) operand_ri.rl.tag = RL_REF_CONST; advanceSourceCursorToNode(ag, operand_node); const uint32_t operand_line = ag->source_line - parent_gz->decl_line; const uint32_t operand_column = ag->source_column; const ZirInstRef raw_operand = needs_non_err_handling ? reachableExpr( parent_gz, scope, operand_ri, operand_node, switch_node) : expr(parent_gz, scope, operand_ri, operand_node); // Sema expects a dbg_stmt immediately before any kind of switch_block // inst. emitDbgStmtForceCurrentIndex(parent_gz, operand_line, operand_column); // This gets added to the parent block later, after the item expressions. const ZirInstTag switch_tag = needs_non_err_handling ? ZIR_INST_SWITCH_BLOCK_ERR_UNION : any_payload_is_ref ? ZIR_INST_SWITCH_BLOCK_REF : ZIR_INST_SWITCH_BLOCK; const uint32_t switch_block = gzMakeBlockInst(parent_gz, switch_tag, switch_node); const ZirInstRef switch_block_ref = switch_block + ZIR_REF_START_INDEX; // Set `break` target if applicable; `continue` target may differ! if (non_err.tag == SWITCH_NON_ERR_PEER_BREAK_TARGET) { block_scope.break_target = non_err.block_inst; gzSetBreakResultInfo(&block_scope, non_err.block_ri); } else { if (non_err.tag == SWITCH_NON_ERR_NONE && switch_full.label_token != AST_TOKEN_NONE) block_scope.break_target = switch_block; gzSetBreakResultInfo(&block_scope, block_ri); } // Prong items use the switch block instruction as their result type. const ResultInfo item_ri = { .rl = { .tag = RL_COERCED_TY, .ty = switch_block_ref } }; bool switch_block_inst_is_occupied = false; if (switch_full.label_token != AST_TOKEN_NONE) { block_scope.label_token = switch_full.label_token; block_scope.continue_target = switch_block; block_scope.continue_is_switch = true; block_scope.continue_result_info.rl = (ResultLoc) { .tag = any_payload_is_ref ? RL_REF_COERCED_TY : RL_COERCED_TY, .ty = switch_block_ref }; switch_block_inst_is_occupied = true; } // `switch_block_err_union` uses the switch block inst as its err capture/ // switch operand. This is always ok as its switch can never have a label. if (needs_non_err_handling) switch_block_inst_is_occupied = true; // `... => |payload| { ... }` uint32_t payload_capture_inst = switch_block; bool payload_capture_inst_is_placeholder = false; if (any_has_payload_capture) { if (!switch_block_inst_is_occupied) switch_block_inst_is_occupied = true; else { payload_capture_inst = appendPlaceholder(ag); payload_capture_inst_is_placeholder = true; } } // `... => |_, tag| { ... }` uint32_t tag_capture_inst = switch_block; bool tag_capture_inst_is_placeholder = false; if (any_has_tag_capture) { if (!switch_block_inst_is_occupied) switch_block_inst_is_occupied = true; else { tag_capture_inst = appendPlaceholder(ag); tag_capture_inst_is_placeholder = true; } } uint32_t prong_body_extra_insts[3]; uint32_t prong_body_extra_insts_len = 0; if (switch_block_inst_is_occupied) prong_body_extra_insts[prong_body_extra_insts_len++] = switch_block; if (payload_capture_inst_is_placeholder) prong_body_extra_insts[prong_body_extra_insts_len++] = payload_capture_inst; if (tag_capture_inst_is_placeholder) prong_body_extra_insts[prong_body_extra_insts_len++] = tag_capture_inst; const ZirInstRef switch_operand = needs_non_err_handling ? switch_block_ref : raw_operand; const ZirInstRef catch_or_if_operand = raw_operand; // We re-use this same scope for all case items and contents. GenZir scratch_scope = gzMakeSubBlock(parent_gz, &block_scope.base); scratch_scope.instructions_top = GZ_UNSTACKED; // We have to take care of the non-error body first if there is one. if (needs_non_err_handling) { scratch_scope.instructions_top = ag->gz_len; // It's always ok to use the switch block inst to refer to the error // union payload as the actual switch statement isn't even in scope // yet. const uint32_t non_err_payload_inst = switch_block; uint32_t non_err_capture = ZIR_PRONG_CAPTURE_NONE; if (non_err.tag == SWITCH_NON_ERR_CATCH) { // We always effectively capture the error union payload; we use // it to `break` from the entire `switch_block_err_union`. non_err_capture = non_err_is_ref != NON_ERR_IS_REF_NO ? ZIR_PRONG_CAPTURE_BY_REF : ZIR_PRONG_CAPTURE_BY_VAL; ZirInstRef then_result; switch (ri.rl.tag) { case RL_REF: case RL_REF_CONST: case RL_REF_COERCED_TY: then_result = switch_block_ref; break; default: then_result = rvalue(&scratch_scope, block_scope.break_result_info, switch_block_ref, catch_or_if_node); break; } gzAddBreakWithSrcNode(&scratch_scope, ZIR_INST_BREAK, switch_block, then_result, catch_or_if_node); } else { const AstFullIf if_full = non_err.if_full; LocalVal payload_val_scope; const AstNodeIndex then_node = if_full.ast.then_expr; Scope* then_sub_scope = &scratch_scope.base; if (if_full.payload_token != AST_TOKEN_NONE) { const AstTokenIndex ident_token = if_full.payload_token + (non_err_is_ref != NON_ERR_IS_REF_NO ? 1u : 0u); const uint32_t ident_name = identAsString(ag, ident_token); if (!tokenIsUnderscore(tree, ident_token)) { non_err_capture = non_err_is_ref != NON_ERR_IS_REF_NO ? ZIR_PRONG_CAPTURE_BY_REF : ZIR_PRONG_CAPTURE_BY_VAL; payload_val_scope = (LocalVal) { .base = { .tag = SCOPE_LOCAL_VAL, .parent = &scratch_scope.base }, .gen_zir = &scratch_scope, .name = ident_name, .inst = switch_block_ref, .token_src = ident_token, .id_cat = ID_CAT_CAPTURE, }; gzAddDbgVar(&scratch_scope, ZIR_INST_DBG_VAR_VAL, ident_name, switch_block_ref); then_sub_scope = &payload_val_scope.base; } } else { gzAddUnNode(&scratch_scope, ZIR_INST_ENSURE_ERR_UNION_PAYLOAD_VOID, catch_or_if_operand, catch_or_if_node); } const ZirInstRef then_result = fullBodyExpr(&scratch_scope, then_sub_scope, block_scope.break_result_info, then_node, BLOCK_KIND_ALLOW_BRANCH_HINT); checkUsed(&scratch_scope.base, then_sub_scope); if (!gzEndsWithNoReturn(&scratch_scope)) gzAddBreakWithSrcNode(&scratch_scope, ZIR_INST_BREAK, switch_block, then_result, then_node); } uint32_t body_slice_len; const uint32_t* body_slice = gzInstructionsSlice(&scratch_scope, &body_slice_len); non_err_prong_body_start = ag->scratch_len; non_err_body_len = scratchAppendBodyWithFixupsExtraRefs( ag, body_slice, body_slice_len, &non_err_payload_inst, 1); gzUnstack(&scratch_scope); non_err_info = NON_ERR_INFO(non_err_body_len, non_err_capture, non_err_is_ref != NON_ERR_IS_REF_NO); } // In this pass we generate all the item and prong expressions. uint32_t multi_case_index = 0; uint32_t scalar_case_index = 0; uint32_t multi_item_offset = 0; for (uint32_t ci = 0; ci < case_nodes.len; ci++) { const AstNodeIndex case_node = case_nodes.ptr[ci]; AstFullSwitchCase case_full; astFullSwitchCase(tree, case_node, &case_full); uint32_t ranges_len = 0; if (any_ranges) { for (uint32_t vi = 0; vi < case_full.ast.values.len; vi++) { if (astNodeTag(tree, case_full.ast.values.ptr[vi]) == AST_NODE_SWITCH_RANGE) ranges_len += 1; } } const uint32_t items_len = case_full.ast.values.len - ranges_len; const bool is_multi_case = items_len > 1 || ranges_len > 0; // item/range bodies in order of occurence uint32_t item_i = 0; uint32_t range_i = 0; for (uint32_t vi = 0; vi < case_full.ast.values.len; vi++) { const AstNodeIndex value = case_full.ast.values.ptr[vi]; const bool is_range = astNodeTag(tree, value) == AST_NODE_SWITCH_RANGE; const AstNodeIndex range[2] = { astNodeData(tree, value).lhs, astNodeData(tree, value).rhs }; const AstNodeIndex* nodes = is_range ? range : &value; for (uint32_t ni = 0; ni < (is_range ? 2u : 1u); ni++) { const AstNodeIndex item = nodes[ni]; // We lower enum literals, error values and number literals // manually to save space since they are very commonly used // as switch case items. const uint32_t body_start = ag->scratch_len; uint32_t item_info; if (astNodeTag(tree, item) == AST_NODE_ENUM_LITERAL) { item_info = ITEM_INFO_ENUM_LIT( identAsString(ag, astNodeMainToken(tree, item))); } else if (astNodeTag(tree, item) == AST_NODE_ERROR_VALUE) { // skip 'error', '.' item_info = ITEM_INFO_ERR_VAL( identAsString(ag, astNodeMainToken(tree, item) + 2)); } else if (value == underscore_node) { item_info = ITEM_INFO_UNDER; } else { scratch_scope.instructions_top = ag->gz_len; const ZirInstRef item_result = fullBodyExpr(&scratch_scope, scope, item_ri, item, BLOCK_KIND_NORMAL); if (!gzEndsWithNoReturn(&scratch_scope)) gzAddBreakWithSrcNode(&scratch_scope, ZIR_INST_BREAK_INLINE, switch_block, item_result, item); uint32_t item_slice_len; const uint32_t* item_slice = gzInstructionsSlice(&scratch_scope, &item_slice_len); const uint32_t body_len = scratchAppendBodyWithFixupsExtraRefs( ag, item_slice, item_slice_len, &switch_block, 1); gzUnstack(&scratch_scope); item_info = ITEM_INFO_BODY_LEN(body_len); } if (is_multi_case) { uint32_t offset; if (is_range) { offset = multi_item_offset + items_len + range_i; range_i += 1; } else { offset = multi_item_offset + item_i; item_i += 1; } ag->scratch[multi_item_body_table + offset] = body_start; ag->scratch[multi_items_infos_start + offset] = item_info; } else { ag->scratch[scalar_body_table + scalar_case_index] = body_start; ag->scratch[scalar_item_infos_start + scalar_case_index] = item_info; } } } if (is_multi_case) { ag->scratch[multi_case_items_lens_start + multi_case_index] = items_len; if (any_ranges) ag->scratch[multi_case_ranges_lens_start + multi_case_index] = ranges_len; multi_item_offset += items_len + 2 * ranges_len; } // Capture and prong body uint32_t dbg_var_payload_name = 0; ZirInstRef dbg_var_payload_inst = ZIR_REF_NONE; uint32_t dbg_var_tag_name = 0; ZirInstRef dbg_var_tag_inst = ZIR_REF_NONE; bool has_tag_capture = false; LocalVal err_capture_scope; LocalVal payload_capture_scope; LocalVal tag_capture_scope; uint32_t capture = ZIR_PRONG_CAPTURE_NONE; // Check all captures and make them available to the prong body. // Potential captures are: // - for regular switch: payload and tag // - for error switch: switch operand and payload Scope* switch_scope = &scratch_scope.base; if (needs_non_err_handling) { // We want to have the captured error we're switching on in scope! err_capture_scope = (LocalVal) { .base = { .tag = SCOPE_LOCAL_VAL, .parent = &scratch_scope.base }, .gen_zir = &scratch_scope, .name = err_capture_name, .inst = switch_operand, .token_src = err_token, .id_cat = ID_CAT_CAPTURE, }; switch_scope = &err_capture_scope.base; } Scope* prong_body_scope = switch_scope; if (case_full.payload_token != AST_TOKEN_NONE) { const AstTokenIndex payload_token = case_full.payload_token; const bool capture_is_ref = astTokenTag(tree, payload_token) == TOKEN_ASTERISK; const AstTokenIndex ident = payload_token + (capture_is_ref ? 1u : 0u); capture = capture_is_ref ? ZIR_PRONG_CAPTURE_BY_REF : ZIR_PRONG_CAPTURE_BY_VAL; Scope* payload_sub_scope = switch_scope; if (tokenIsUnderscore(tree, ident)) { capture = ZIR_PRONG_CAPTURE_NONE; } else { payload_capture_scope = (LocalVal) { .base = { .tag = SCOPE_LOCAL_VAL, .parent = switch_scope }, .gen_zir = &scratch_scope, .name = identAsString(ag, ident), .inst = payload_capture_inst + ZIR_REF_START_INDEX, .token_src = ident, .id_cat = ID_CAT_CAPTURE, }; dbg_var_payload_name = payload_capture_scope.name; dbg_var_payload_inst = payload_capture_scope.inst; payload_sub_scope = &payload_capture_scope.base; } prong_body_scope = payload_sub_scope; if (astTokenTag(tree, ident + 1) == TOKEN_COMMA) { const AstTokenIndex tag_token = ident + 2; has_tag_capture = true; tag_capture_scope = (LocalVal) { .base = { .tag = SCOPE_LOCAL_VAL, .parent = payload_sub_scope }, .gen_zir = &scratch_scope, .name = identAsString(ag, tag_token), .inst = tag_capture_inst + ZIR_REF_START_INDEX, .token_src = tag_token, .id_cat = ID_CAT_SWITCH_TAG_CAPTURE, }; dbg_var_tag_name = tag_capture_scope.name; dbg_var_tag_inst = tag_capture_scope.inst; prong_body_scope = &tag_capture_scope.base; } } scratch_scope.instructions_top = ag->gz_len; if (dbg_var_payload_name != 0) gzAddDbgVar(&scratch_scope, ZIR_INST_DBG_VAR_VAL, dbg_var_payload_name, dbg_var_payload_inst); if (dbg_var_tag_name != 0) gzAddDbgVar(&scratch_scope, ZIR_INST_DBG_VAR_VAL, dbg_var_tag_name, dbg_var_tag_inst); if (do_err_trace && nodeMayAppendToErrorTrace(tree, operand_node)) gzAddSaveErrRetIndex(&scratch_scope, ZIR_REF_NONE); const AstNodeIndex target_expr_node = case_full.ast.target_expr; const ZirInstRef case_result = fullBodyExpr(&scratch_scope, prong_body_scope, block_scope.break_result_info, target_expr_node, BLOCK_KIND_ALLOW_BRANCH_HINT); // If we would check `scratch_scope` here for an error switch, we // would get a false positive, that being the switch operand itself! checkUsed(switch_scope, prong_body_scope); if (!gzEndsWithNoReturn(&scratch_scope)) { // As our last action before the break, "pop" the error trace if // needed if (do_err_trace) restoreErrRetIndex(&scratch_scope, switch_block, block_scope.break_result_info, target_expr_node, case_result); gzAddBreakWithSrcNode(&scratch_scope, ZIR_INST_BREAK, switch_block, case_result, target_expr_node); } uint32_t body_slice_len; const uint32_t* body_slice = gzInstructionsSlice(&scratch_scope, &body_slice_len); const uint32_t body_start = ag->scratch_len; const uint32_t body_len = scratchAppendBodyWithFixupsExtraRefs(ag, body_slice, body_slice_len, prong_body_extra_insts, prong_body_extra_insts_len); gzUnstack(&scratch_scope); if (case_node == else_case_node) { // Specific `else` bodies can cause Sema to omit the "unreachable // else prong" error so that certain generic code patterns don't // trigger it. We do that for these bodies: // `else => unreachable,` // `else => return,` // `else => |e| return e,` (where `e` is any identifier) bool is_simple_noreturn = false; switch (astNodeTag(tree, target_expr_node)) { case AST_NODE_UNREACHABLE_LITERAL: is_simple_noreturn = true; break; case AST_NODE_RETURN: { const AstNodeIndex retval_node = astNodeData(tree, target_expr_node).lhs; if (retval_node == AST_NODE_NONE) is_simple_noreturn = true; else if (capture == ZIR_PRONG_CAPTURE_BY_VAL && astNodeTag(tree, retval_node) == AST_NODE_IDENTIFIER) is_simple_noreturn = identAsString(ag, case_full.payload_token) == identAsString( ag, astNodeMainToken(tree, retval_node)); break; } default: break; } else_info = ELSE_INFO(body_len, capture, case_full.inline_token != AST_TOKEN_NONE, has_tag_capture, is_simple_noreturn); else_body_len = body_len; else_prong_body_start = body_start; continue; } // We allow prongs with error items which are not inside the error // set being switched on if their body is `=> comptime unreachable,`. const bool is_comptime_unreach = astNodeTag(tree, target_expr_node) == AST_NODE_COMPTIME && astNodeTag(tree, astNodeData(tree, target_expr_node).lhs) == AST_NODE_UNREACHABLE_LITERAL; const uint32_t prong_info = PRONG_INFO(body_len, capture, case_full.inline_token != AST_TOKEN_NONE, has_tag_capture, is_comptime_unreach); if (is_multi_case) { ag->scratch[multi_prong_body_table + multi_case_index] = body_start; ag->scratch[multi_prong_infos_start + multi_case_index] = prong_info; multi_case_index += 1; } else { // prong body start is implicit, it's right behind our only item. ag->scratch[scalar_prong_infos_start + scalar_case_index] = prong_info; scalar_case_index += 1; } } // Now that the item expressions are generated we can add this. gzStackPush(ag, switch_block); // We've collected all of the data we need! Now we just have to finalize // it by copying our bodies from `payloads` to `extra`, this time in the // order expected by ZIR consumers. extraEnsureUnused(ag, 2u // raw_operand, bits + (multi_cases_len > 0 ? 1u : 0u) + (payload_capture_inst_is_placeholder ? 1u : 0u) + (tag_capture_inst_is_placeholder ? 1u : 0u) + (needs_non_err_handling ? 2u : 0u) + (has_else ? 1u : 0u) + ag->scratch_len - body_table_end); // singular pieces of data const uint32_t zir_payload_index = ag->extra_len; extraAppend(ag, raw_operand); extraAppend(ag, PACK_BOOL(multi_cases_len > 0, SWBITS_HAS_MULTI) | PACK_BOOL(any_ranges, SWBITS_ANY_RANGES) | PACK_BOOL(has_else, SWBITS_HAS_ELSE) | PACK_BOOL(has_under, SWBITS_HAS_UNDER) | PACK_BOOL(switch_full.label_token != AST_TOKEN_NONE && block_scope.label_used_for_continue, SWBITS_HAS_CONTINUE) | PACK_BOOL(any_maybe_runtime_capture, SWBITS_RUNTIME_CAPTURE) | PACK_BOOL(payload_capture_inst_is_placeholder, SWBITS_PAYLOAD_PH) | PACK_BOOL(tag_capture_inst_is_placeholder, SWBITS_TAG_PH) | (scalar_cases_len << SWBITS_SCALAR_LEN_SHIFT)); ag->inst_datas[switch_block].pl_node.payload_index = zir_payload_index; if (multi_cases_len > 0) extraAppend(ag, multi_cases_len); if (payload_capture_inst_is_placeholder) extraAppend(ag, payload_capture_inst); if (tag_capture_inst_is_placeholder) extraAppend(ag, tag_capture_inst); if (needs_non_err_handling) { extraAppend( ag, (uint32_t)gzNodeIndexToRelative(parent_gz, catch_or_if_node)); extraAppend(ag, non_err_info); } if (has_else) extraAppend(ag, else_info); // body lens for (uint32_t i = body_table_end; i < bodies_start; i++) extraAppend(ag, ag->scratch[i]); // bodies if (needs_non_err_handling) { for (uint32_t i = 0; i < non_err_body_len; i++) extraAppend(ag, ag->scratch[non_err_prong_body_start + i]); } if (has_else) { for (uint32_t i = 0; i < else_body_len; i++) extraAppend(ag, ag->scratch[else_prong_body_start + i]); } for (uint32_t scalar_i = 0; scalar_i < scalar_cases_len; scalar_i++) { const uint32_t item_info = ag->scratch[scalar_item_infos_start + scalar_i]; const uint32_t item_body_start = ag->scratch[scalar_body_table + scalar_i]; const uint32_t item_body_len = itemInfoBodyLen(item_info); const uint32_t prong_info = ag->scratch[scalar_prong_infos_start + scalar_i]; const uint32_t prong_body_start = item_body_start + item_body_len; const uint32_t prong_body_len = prong_info & ZIR_PRONG_INFO_BODY_LEN_MASK; for (uint32_t i = 0; i < prong_body_len; i++) extraAppend(ag, ag->scratch[prong_body_start + i]); for (uint32_t i = 0; i < item_body_len; i++) extraAppend(ag, ag->scratch[item_body_start + i]); } uint32_t multi_item_i = 0; for (uint32_t multi_i = 0; multi_i < multi_cases_len; multi_i++) { const uint32_t prong_body_start = ag->scratch[multi_prong_body_table + multi_i]; const uint32_t prong_info = ag->scratch[multi_prong_infos_start + multi_i]; const uint32_t prong_body_len = prong_info & ZIR_PRONG_INFO_BODY_LEN_MASK; for (uint32_t i = 0; i < prong_body_len; i++) extraAppend(ag, ag->scratch[prong_body_start + i]); const uint32_t items_len = ag->scratch[multi_case_items_lens_start + multi_i]; const uint32_t ranges_len = any_ranges ? ag->scratch[multi_case_ranges_lens_start + multi_i] : 0; // The table entries and body lens are already in the correct order // so we don't have to differentiate between items and ranges here. for (uint32_t k = 0; k < items_len + 2 * ranges_len; k++) { const uint32_t item_info = ag->scratch[multi_items_infos_start + multi_item_i]; const uint32_t body_start = ag->scratch[multi_item_body_table + multi_item_i]; for (uint32_t i = 0; i < itemInfoBodyLen(item_info); i++) extraAppend(ag, ag->scratch[body_start + i]); multi_item_i += 1; } } ag->scratch_len = scratch_top; if (need_result_rvalue) return rvalue(parent_gz, ri, switch_block_ref, switch_node); return switch_block_ref; } // AstGen.zig:tryExpr static ZirInstRef tryExpr( GenZir* parent_gz, Scope* scope, ResultInfo ri, AstNodeIndex node) { AstGen* ag = parent_gz->astgen; const Ast* tree = ag->tree; GenZir* fn_block = ag->fn_block; if (fn_block == NULL) failNode(ag, node, "'try' outside function scope"); if (parent_gz->any_defer_node != 0) failNode(ag, node, "'try' not allowed inside defer expression"); const AstNodeIndex operand_node = astNodeData(tree, node).lhs; // Emit debug info for the try, saving the line/column to re-emit // "backwards" after the operand is evaluated (for error return tracing). if (!parent_gz->is_comptime) emitDbgNode(parent_gz, node); const uint32_t try_line = ag->source_line - parent_gz->decl_line; const uint32_t try_column = ag->source_column; ResultLocTag operand_rl_tag; ZirInstTag block_tag; switch (ri.rl.tag) { case RL_REF: case RL_REF_COERCED_TY: operand_rl_tag = RL_REF; block_tag = ZIR_INST_TRY_PTR; break; case RL_REF_CONST: operand_rl_tag = RL_REF_CONST; block_tag = ZIR_INST_TRY_PTR; break; default: operand_rl_tag = RL_NONE; block_tag = ZIR_INST_TRY; break; } ResultInfo operand_ri = { { operand_rl_tag, 0, 0, 0, NULL, 0, 0 }, CTX_ERROR_HANDLING_EXPR }; ZirInstRef operand; if (nodeIsCall(tree, operand_node)) { // Mirrors Zig: `res_ty = ri.rl.resultType(parent_gz, operand_node) // orelse .none`. For RL_PTR, resultLocType emits typeof+elem_type as a // side effect and returns the element type ref, which is passed as // override_decl_literal_type so callExpr can emit // decl_literal_no_coerce for `.foo()` callee syntax. const ZirInstRef res_ty = resultLocType(parent_gz, ri, operand_node); operand = callExpr(parent_gz, scope, operand_ri, res_ty, operand_node); } else { operand = reachableExpr(parent_gz, scope, operand_ri, operand_node, node); } const uint32_t try_inst = gzMakeBlockInst(parent_gz, block_tag, node); gzStackPush(ag, try_inst); GenZir else_scope = gzMakeSubBlock(parent_gz, scope); ZirInstTag err_tag; switch (ri.rl.tag) { case RL_REF: case RL_REF_CONST: case RL_REF_COERCED_TY: err_tag = ZIR_INST_ERR_UNION_CODE_PTR; break; default: err_tag = ZIR_INST_ERR_UNION_CODE; break; } const ZirInstRef err_code = gzAddUnNode(&else_scope, err_tag, operand, node); genDefers(&else_scope, &fn_block->base, scope, true); emitDbgStmt(&else_scope, try_line, try_column); gzAddUnNode(&else_scope, ZIR_INST_RET_NODE, err_code, node); gzSetTryBody(&else_scope, try_inst, operand); const ZirInstRef result = try_inst + ZIR_REF_START_INDEX; switch (ri.rl.tag) { case RL_REF: case RL_REF_CONST: case RL_REF_COERCED_TY: return result; default: return rvalue(parent_gz, ri, result, node); } } // --- comptimeExpr / typeExpr --- // resultType: the coercion target type of a result location, or none. static ZirInstRef riResultType(ResultInfo ri) { switch (ri.rl.tag) { case RL_TY: case RL_COERCED_TY: return ri.rl.ty; default: return ZIR_REF_NONE; } } // ResultInfo.Loc.resultType: the coercion target type, emitting instructions // for pointer-based result locations. Returns none when no result type. static ZirInstRef resultLocType(GenZir* gz, ResultInfo ri, AstNodeIndex node) { switch (ri.rl.tag) { case RL_TY: case RL_COERCED_TY: return ri.rl.ty; case RL_REF_COERCED_TY: return gzAddUnNode(gz, ZIR_INST_ELEM_TYPE, ri.rl.ty, node); case RL_PTR: { const ZirInstRef ptr_ty = gzAddUnNode(gz, ZIR_INST_TYPEOF, ri.rl.inst, node); return gzAddUnNode(gz, ZIR_INST_ELEM_TYPE, ptr_ty, node); } default: return ZIR_REF_NONE; } } // ResultInfo.Loc.resultTypeForCast's error, for a location with no type. static void failNoResultType(const AstGen* ag, AstNodeIndex node) { const char* name; uint32_t name_len; astTokenSlice( ag->tree, astNodeMainToken(ag->tree, node), &name, &name_len); failNode( ag, node, "%.*s must have a known result type", (int)name_len, name); } // AstGen.zig:comptimeExpr static ZirInstRef comptimeExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, uint32_t reason) { return comptimeExpr2(gz, scope, ri, node, node, reason); } // AstGen.zig:comptimeExpr2 // Like comptimeExpr, but `src_node` is the node the `block_comptime` carries. static ZirInstRef comptimeExpr2(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstNodeIndex src_node, uint32_t reason) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; if (gz->is_comptime) return expr(gz, scope, ri, node); // Optimization: trivially-comptime expressions need no wrapper block. switch (astNodeTag(tree, node)) { case AST_NODE_IDENTIFIER: // Identifiers get special handling that may avoid the block_comptime. return identifierInner(gz, scope, ri, node, true, src_node, reason); case AST_NODE_NUMBER_LITERAL: case AST_NODE_CHAR_LITERAL: case AST_NODE_STRING_LITERAL: case AST_NODE_MULTILINE_STRING_LITERAL: case AST_NODE_ENUM_LITERAL: case AST_NODE_ERROR_VALUE: case AST_NODE_ANYFRAME_LITERAL: case AST_NODE_ERROR_SET_DECL: case AST_NODE_ERROR_UNION: case AST_NODE_MERGE_ERROR_SETS: case AST_NODE_OPTIONAL_TYPE: case AST_NODE_ANYFRAME_TYPE: case AST_NODE_PTR_TYPE_ALIGNED: case AST_NODE_PTR_TYPE_SENTINEL: case AST_NODE_PTR_TYPE: case AST_NODE_PTR_TYPE_BIT_RANGE: case AST_NODE_ARRAY_TYPE: case AST_NODE_ARRAY_TYPE_SENTINEL: case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: return expr(gz, scope, ri, node); case AST_NODE_BLOCK_TWO: case AST_NODE_BLOCK_TWO_SEMICOLON: case AST_NODE_BLOCK: case AST_NODE_BLOCK_SEMICOLON: { const AstTokenIndex lbrace = astNodeMainToken(tree, node); if (astIsTokenPrecededByTags(tree, lbrace, (const TokenTag[]) { TOKEN_IDENTIFIER, TOKEN_COLON }, 2)) { // Labeled comptime block: `comptime label: { ... }`. // Mirrors Zig's comptimeExpr2 labeled-block optimization. // Use ty_only_ri (no runtime result ptr), then rvalue for // copy-back. const ZirInstRef res_ty2 = riResultType(ri); ResultInfo ty_only_ri2; ty_only_ri2.ctx = ri.ctx; if (res_ty2 != ZIR_REF_NONE) { ty_only_ri2.rl.tag = RL_COERCED_TY; ty_only_ri2.rl.ty = res_ty2; } else { ty_only_ri2.rl.tag = RL_NONE; ty_only_ri2.rl.ty = 0; } AstNodeIndex buf[2]; AstNodeSlice stmts; astBlockStatements(tree, buf, node, &stmts); const ZirInstRef block_ref = labeledBlockExpr(gz, scope, ty_only_ri2, node, stmts.ptr, stmts.len, true, BLOCK_KIND_NORMAL); return rvalue(gz, ri, block_ref, node); } break; } default: break; } // Wrap in a `block_comptime`. GenZir block_scope = gzMakeSubBlock(gz, scope); block_scope.is_comptime = true; // Use src_node for the block_comptime instruction's source location. // For AST_NODE_COMPTIME nodes, src_node = the comptime keyword node; // for all other callsites, src_node == node (the expression itself). const uint32_t block_inst = gzMakeBlockInst(gz, ZIR_INST_BLOCK_COMPTIME, src_node); // Mirror Zig's comptimeExpr2: call resultType(gz, src_node) after // makeBlockInst. For RL_PTR, this emits typeof+elem_type into gz (the // outer scope), which appear between block_comptime and its body in the // instruction array. Matches AstGen.zig. const ZirInstRef res_ty = resultLocType(gz, ri, src_node); ResultInfo ty_only_ri; ty_only_ri.ctx = ri.ctx; if (res_ty != ZIR_REF_NONE) { ty_only_ri.rl.tag = RL_COERCED_TY; ty_only_ri.rl.ty = res_ty; } else { ty_only_ri.rl.tag = RL_NONE; ty_only_ri.rl.ty = 0; } const ZirInstRef block_result = fullBodyExpr( &block_scope, scope, ty_only_ri, node, BLOCK_KIND_NORMAL); if (!refIsNoReturn(&block_scope, block_result)) gzAddBreak( &block_scope, ZIR_INST_BREAK_INLINE, block_inst, block_result); gzSetBlockComptimeBody(&block_scope, block_inst, reason); gzStackPush(ag, block_inst); return rvalue(gz, ri, block_inst + ZIR_REF_START_INDEX, src_node); } // AstGen.zig:comptimeExprAst // The `comptime` syntax, which may not appear in an already comptime scope. static ZirInstRef comptimeExprAst( GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; if (gz->is_comptime) failTok(ag, astNodeMainToken(tree, node), "redundant comptime keyword in already comptime scope"); const AstNodeIndex body_node = astNodeData(tree, node).lhs; return comptimeExpr2( gz, scope, ri, body_node, node, REASON_COMPTIME_KEYWORD); } // AstGen.zig:typeExpr static ZirInstRef typeExpr(GenZir* gz, Scope* scope, AstNodeIndex type_node) { return comptimeExpr(gz, scope, coerced_type_ri, type_node, REASON_TYPE); } // --- varDecl (local) --- // AstGen.zig:varDecl static Scope* varDecl(GenZir* gz, Scope* scope, AstNodeIndex node) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; AstFullVarDecl var_decl; astFullVarDecl(tree, node, &var_decl); emitDbgNode(gz, node); const AstTokenIndex name_token = var_decl.ast.mut_token + 1; if (tokenIsUnderscore(tree, name_token)) failTok( ag, name_token, "'_' used as an identifier without @\"_\" syntax"); const uint32_t ident_name = identAsString(ag, name_token); // detectLocalShadowing omitted: shadowing is a compile error upstream, // and zig1 accepts programs upstream rejects. if (var_decl.ast.init_node == AST_NODE_NONE) failNode(ag, node, "variables must be initialized"); const ZirInstRef type_inst = var_decl.ast.type_node != AST_NODE_NONE ? typeExpr(gz, scope, var_decl.ast.type_node) : ZIR_REF_NONE; // Alignment expression, if any. Computed before the const/var split // because both paths use it. ZirInstRef align_inst_shared = ZIR_REF_NONE; if (var_decl.ast.align_node != AST_NODE_NONE) { align_inst_shared = comptimeExpr(gz, scope, coerced_align_ri, var_decl.ast.align_node, REASON_ALIGN); } const TokenTag mut = astTokenTag(tree, var_decl.ast.mut_token); if (mut == TOKEN_KEYWORD_CONST) { // const: the rvalue path (no result pointer) is valid only when the // init has no align and the node was not flagged by AstRlAnnotate. // Mirrors AstGen.zig: if (align_inst == .none and !nodes_need_rl) if (align_inst_shared == ZIR_REF_NONE && !nodesNeedRl(ag, node)) { ResultInfo ri; memset(&ri, 0, sizeof(ri)); if (type_inst != ZIR_REF_NONE) { ri.rl.tag = RL_TY; ri.rl.ty = type_inst; } else { ri.rl.tag = RL_NONE; ri.rl.ty = 0; } ri.ctx = CTX_CONST_INIT; ZirInstRef init_inst; if (!nameStratExpr(gz, scope, ri, var_decl.ast.init_node, ZIR_NAME_STRATEGY_DBG_VAR, &init_inst)) init_inst = reachableExprComptime(gz, scope, ri, var_decl.ast.init_node, node, var_decl.comptime_token != AST_TOKEN_NONE ? REASON_COMPTIME_KEYWORD : NO_COMPTIME_REASON); gzAddUnNode(gz, ZIR_INST_VALIDATE_CONST, init_inst, var_decl.ast.init_node); gzAddDbgVar(gz, ZIR_INST_DBG_VAR_VAL, ident_name, init_inst); // The const init expression may have modified the error return // trace, so signal to Sema to save the new index for restoring // later. if (nodeMayAppendToErrorTrace(tree, var_decl.ast.init_node)) gzAddSaveErrRetIndex(gz, init_inst); LocalVal* sub = scopeAlloc(ag, sizeof(LocalVal)); memset(sub, 0, sizeof(*sub)); sub->base.tag = SCOPE_LOCAL_VAL; sub->base.parent = scope; sub->gen_zir = gz; sub->name = ident_name; sub->inst = init_inst; sub->token_src = name_token; sub->id_cat = ID_CAT_LOCAL_CONSTANT; return &sub->base; } // const with result location: the init needs a result pointer (e.g., // `const x = [_]T{...}` or a struct-init requiring a ptr destination). // Mirrors AstGen.zig varDecl const path (nodesNeedRl == true). const bool force_comptime = (var_decl.comptime_token != AST_TOKEN_NONE); const bool is_comptime = gz->is_comptime || astNodeTag(tree, var_decl.ast.init_node) == AST_NODE_COMPTIME || force_comptime; ZirInstRef var_ptr; bool resolve_inferred; ResultInfo init_ri; memset(&init_ri, 0, sizeof(init_ri)); init_ri.ctx = CTX_CONST_INIT; if (type_inst != ZIR_REF_NONE) { if (align_inst_shared == ZIR_REF_NONE) { var_ptr = gzAddUnNode(gz, ZIR_INST_ALLOC, type_inst, node); } else { var_ptr = gzAddAllocExtended( gz, node, type_inst, align_inst_shared, true, is_comptime); } resolve_inferred = false; init_ri.rl.tag = RL_PTR; init_ri.rl.inst = var_ptr; } else { if (align_inst_shared == ZIR_REF_NONE) { const ZirInstTag alloc_tag = is_comptime ? ZIR_INST_ALLOC_INFERRED_COMPTIME : ZIR_INST_ALLOC_INFERRED; var_ptr = gzAddNode(gz, alloc_tag, node); } else { var_ptr = gzAddAllocExtended(gz, node, ZIR_REF_NONE, align_inst_shared, true, is_comptime); } resolve_inferred = true; init_ri.rl.tag = RL_INFERRED_PTR; init_ri.rl.inst = var_ptr; } ZirInstRef rl_init_inst; if (!nameStratExpr(gz, scope, init_ri, var_decl.ast.init_node, ZIR_NAME_STRATEGY_DBG_VAR, &rl_init_inst)) rl_init_inst = reachableExprComptime(gz, scope, init_ri, var_decl.ast.init_node, node, force_comptime ? REASON_COMPTIME_KEYWORD : NO_COMPTIME_REASON); if (nodeMayAppendToErrorTrace(tree, var_decl.ast.init_node)) gzAddSaveErrRetIndex(gz, rl_init_inst); const ZirInstRef const_ptr = resolve_inferred ? gzAddUnNode(gz, ZIR_INST_RESOLVE_INFERRED_ALLOC, var_ptr, node) : gzAddUnNode(gz, ZIR_INST_MAKE_PTR_CONST, var_ptr, node); gzAddDbgVar(gz, ZIR_INST_DBG_VAR_PTR, ident_name, const_ptr); LocalPtr* sub_ptr = scopeAlloc(ag, sizeof(LocalPtr)); memset(sub_ptr, 0, sizeof(*sub_ptr)); sub_ptr->base.tag = SCOPE_LOCAL_PTR; sub_ptr->base.parent = scope; sub_ptr->gen_zir = gz; sub_ptr->name = ident_name; sub_ptr->ptr = const_ptr; sub_ptr->token_src = name_token; sub_ptr->maybe_comptime = true; sub_ptr->id_cat = ID_CAT_LOCAL_CONSTANT; return &sub_ptr->base; } // keyword_var: mutable local. // `comptime var` sets is_comptime via has_comptime_token; also inherits // from gz->is_comptime if already in a comptime context. const bool is_comptime = gz->is_comptime || (var_decl.comptime_token != AST_TOKEN_NONE); // align_inst was already computed above (shared with const path). const ZirInstRef align_inst = align_inst_shared; ZirInstRef alloc; bool resolve_inferred; ResultInfo ri; memset(&ri, 0, sizeof(ri)); if (type_inst != ZIR_REF_NONE) { if (align_inst == ZIR_REF_NONE) { const ZirInstTag alloc_tag = is_comptime ? ZIR_INST_ALLOC_COMPTIME_MUT : ZIR_INST_ALLOC_MUT; alloc = gzAddUnNode(gz, alloc_tag, type_inst, node); } else { alloc = gzAddAllocExtended( gz, node, type_inst, align_inst, false, is_comptime); } resolve_inferred = false; ri.rl.tag = RL_PTR; ri.rl.inst = alloc; } else { if (align_inst == ZIR_REF_NONE) { const ZirInstTag alloc_tag = is_comptime ? ZIR_INST_ALLOC_INFERRED_COMPTIME_MUT : ZIR_INST_ALLOC_INFERRED_MUT; alloc = gzAddNode(gz, alloc_tag, node); } else { alloc = gzAddAllocExtended( gz, node, ZIR_REF_NONE, align_inst, false, is_comptime); } resolve_inferred = true; ri.rl.tag = RL_INFERRED_PTR; ri.rl.inst = alloc; } ZirInstRef init_inst_unused; if (!nameStratExpr(gz, scope, ri, var_decl.ast.init_node, ZIR_NAME_STRATEGY_DBG_VAR, &init_inst_unused)) { reachableExprComptime(gz, scope, ri, var_decl.ast.init_node, node, var_decl.comptime_token != AST_TOKEN_NONE ? REASON_COMPTIME_KEYWORD : NO_COMPTIME_REASON); } const ZirInstRef final_ptr = resolve_inferred ? gzAddUnNode(gz, ZIR_INST_RESOLVE_INFERRED_ALLOC, alloc, node) : alloc; gzAddDbgVar(gz, ZIR_INST_DBG_VAR_PTR, ident_name, final_ptr); LocalPtr* sub = scopeAlloc(ag, sizeof(LocalPtr)); memset(sub, 0, sizeof(*sub)); sub->base.tag = SCOPE_LOCAL_PTR; sub->base.parent = scope; sub->gen_zir = gz; sub->name = ident_name; sub->ptr = final_ptr; sub->token_src = name_token; sub->maybe_comptime = is_comptime; sub->id_cat = ID_CAT_LOCAL_VARIABLE; return &sub->base; } // --- assignDestructure --- // Implements AstGen.zig:assignDestructure. This variant does not declare any // new var/const (that is `assignDestructureMaybeDecls`). Every LHS component // is either a `_` discard or an lvalue expression. static void assignDestructure(GenZir* gz, Scope* scope, AstNodeIndex node) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; emitDbgNode(gz, node); const AstFullAssignDestructure full = astAssignDestructure(tree, node); const uint32_t var_count = full.ast.variables.len; const AstNodeIndex value_expr = full.ast.value_expr; const bool has_comptime_token = full.comptime_token != AST_TOKEN_NONE; // If this expression is marked comptime, wrap the whole thing in a // comptime block. GenZir gz_buf; GenZir* inner_gz = gz; if (has_comptime_token) { gz_buf = gzMakeSubBlock(gz, scope); gz_buf.is_comptime = true; inner_gz = &gz_buf; } DestructureComponent* components = zig1Calloc(var_count ? var_count : 1u, sizeof(DestructureComponent)); for (uint32_t i = 0; i < var_count; i++) { const AstNodeIndex variable_node = full.ast.variables.ptr[i]; // This intentionally does not support `@"_"` syntax. if (astNodeTag(tree, variable_node) == AST_NODE_IDENTIFIER && tokenIsUnderscore( tree, astNodeMainToken(tree, variable_node))) { components[i].tag = DC_DISCARD; continue; } components[i].tag = DC_TYPED_PTR; components[i].inst = lvalExpr(inner_gz, scope, variable_node); components[i].src_node = variable_node; } ResultInfo ds_ri; memset(&ds_ri, 0, sizeof(ds_ri)); ds_ri.rl.tag = RL_DESTRUCTURE; ds_ri.rl.ds_components = components; ds_ri.rl.ds_components_len = var_count; ds_ri.rl.ds_src_node = node; expr(inner_gz, scope, ds_ri, value_expr); if (has_comptime_token) { const uint32_t comptime_block_inst = gzMakeBlockInst(gz, ZIR_INST_BLOCK_COMPTIME, node); gzAddBreak(inner_gz, ZIR_INST_BREAK_INLINE, comptime_block_inst, ZIR_REF_VOID_VALUE); gzSetBlockComptimeBody( inner_gz, comptime_block_inst, REASON_COMPTIME_KEYWORD); gzStackPush(ag, comptime_block_inst); gzUnstack(inner_gz); } free(components); } // --- assignDestructureMaybeDecls --- // AstGen.zig:assignDestructureMaybeDecls static Scope* assignDestructureMaybeDecls( GenZir* gz, Scope* scope, AstNodeIndex node) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; emitDbgNode(gz, node); const AstFullAssignDestructure full = astAssignDestructure(tree, node); const uint32_t var_count = full.ast.variables.len; const AstNodeIndex value_expr = full.ast.value_expr; // Per-variable info. const size_t n = var_count ? var_count : 1u; DestructureComponent* components = zig1Calloc(n, sizeof(DestructureComponent)); AstNodeIndex* var_nodes = zig1Calloc(n, sizeof(AstNodeIndex)); AstTokenIndex* name_tokens = zig1Calloc(n, sizeof(AstTokenIndex)); bool* is_const_arr = zig1Calloc(n, sizeof(bool)); bool* is_inferred = zig1Calloc(n, sizeof(bool)); // First pass: allocate storage for each variable. const bool is_comptime = full.comptime_token != AST_TOKEN_NONE || gz->is_comptime; // Mirrors AstGen.zig: value_is_comptime = tree.nodeTag(value_expr) == // .comptime const bool value_is_comptime = (astNodeTag(tree, value_expr) == AST_NODE_COMPTIME); const SourceCursor saved_cursor = saveSourceCursor(ag); bool any_lvalue_expr = false; for (uint32_t i = 0; i < var_count; i++) { const AstNodeIndex var_node = full.ast.variables.ptr[i]; var_nodes[i] = var_node; const AstNodeTag vt = astNodeTag(tree, var_node); switch (vt) { case AST_NODE_IDENTIFIER: { // `_` identifier: discard. Other identifiers: lvalue (handled // below). if (tokenIsUnderscore(tree, astNodeMainToken(tree, var_node))) { components[i].tag = DC_DISCARD; break; } // Fall through: treat as lvalue expression. components[i].tag = DC_TYPED_PTR; components[i].inst = 0; // filled in by lvalExpr pass below components[i].src_node = var_node; // remember for lvalExpr pass any_lvalue_expr = true; break; } case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: case AST_NODE_GLOBAL_VAR_DECL: { AstFullVarDecl vd; astFullVarDecl(tree, var_node, &vd); const AstTokenIndex name_token = vd.ast.mut_token + 1; name_tokens[i] = name_token; const bool is_const = (astTokenTag(tree, vd.ast.mut_token) == TOKEN_KEYWORD_CONST); is_const_arr[i] = is_const; // Mirrors AstGen.zig: this_variable_comptime = is_comptime or // (is_const and value_is_comptime) const bool this_variable_comptime = is_comptime || (is_const && value_is_comptime); const ZirInstRef type_inst = vd.ast.type_node != AST_NODE_NONE ? typeExpr(gz, scope, vd.ast.type_node) : ZIR_REF_NONE; // `"const align_inst: Zir.Inst.Ref = if // (full_var_decl.ast.align_node.unwrap()) |align_node| try // comptimeExpr(gz, scope, coerced_align_ri, align_node, // .@\"align\") else .none;"` const ZirInstRef align_inst = vd.ast.align_node == AST_NODE_NONE ? ZIR_REF_NONE : comptimeExpr(gz, scope, coerced_align_ri, vd.ast.align_node, REASON_ALIGN); if (type_inst != ZIR_REF_NONE) { // Typed alloc const ZirInstTag alloc_tag = this_variable_comptime ? ZIR_INST_ALLOC_COMPTIME_MUT : (is_const ? ZIR_INST_ALLOC : ZIR_INST_ALLOC_MUT); const ZirInstRef ptr = align_inst == ZIR_REF_NONE ? gzAddUnNode(gz, alloc_tag, type_inst, node) : gzAddAllocExtended(gz, node, type_inst, align_inst, is_const, this_variable_comptime); components[i].tag = DC_TYPED_PTR; components[i].inst = ptr; components[i].src_node = 0; is_inferred[i] = false; } else { // Inferred alloc ZirInstTag alloc_tag; if (is_const) { alloc_tag = this_variable_comptime ? ZIR_INST_ALLOC_INFERRED_COMPTIME : ZIR_INST_ALLOC_INFERRED; } else { alloc_tag = this_variable_comptime ? ZIR_INST_ALLOC_INFERRED_COMPTIME_MUT : ZIR_INST_ALLOC_INFERRED_MUT; } const ZirInstRef ptr = align_inst == ZIR_REF_NONE ? gzAddNode(gz, alloc_tag, node) : gzAddAllocExtended(gz, node, ZIR_REF_NONE, align_inst, is_const, this_variable_comptime); components[i].tag = DC_INFERRED_PTR; components[i].inst = ptr; components[i].src_node = 0; is_inferred[i] = true; } break; } default: // Other non-declaration expressions: lvalue. components[i].tag = DC_TYPED_PTR; components[i].inst = 0; // filled in by lvalExpr pass below components[i].src_node = var_node; // remember for lvalExpr pass any_lvalue_expr = true; break; } } // `"// If this expression is marked comptime, we must wrap it in a // comptime block."`: the lvalues and the value are evaluated in it. GenZir gz_buf; GenZir* inner_gz = gz; if (full.comptime_token != AST_TOKEN_NONE) { gz_buf = gzMakeSubBlock(gz, scope); gz_buf.is_comptime = true; inner_gz = &gz_buf; } // Lvalue pass: evaluate lvalue expressions (existing variables) to get // their pointer. Mirrors Zig's any_lvalue_expr handling. if (any_lvalue_expr) { restoreSourceCursor(ag, saved_cursor); for (uint32_t i = 0; i < var_count; i++) { if (components[i].tag != DC_TYPED_PTR || components[i].inst != 0) continue; const AstNodeIndex lv_node = components[i].src_node; const AstNodeTag vt = astNodeTag(tree, lv_node); switch (vt) { case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: case AST_NODE_GLOBAL_VAR_DECL: continue; // already handled above default: components[i].inst = lvalExpr(inner_gz, scope, lv_node); // Keep src_node as var_node (identifier node) for use as // store_node src in the RL_DESTRUCTURE case — mirrors Zig's // typed_ptr.src_node which is set to variable_node and not // cleared during the lvalue pass. break; } } } // Evaluate RHS with RL_DESTRUCTURE result location. // Components are stored inline in ds_ri so nested destructures don't // clobber each other's state. Mirrors AstGen.zig passing rl_components // directly in the ResultInfo.destructure field. ResultInfo ds_ri; memset(&ds_ri, 0, sizeof(ds_ri)); ds_ri.rl.tag = RL_DESTRUCTURE; ds_ri.rl.ds_components = components; ds_ri.rl.ds_components_len = var_count; ds_ri.rl.ds_src_node = node; reachableExpr(inner_gz, scope, ds_ri, value_expr, node); if (full.comptime_token != AST_TOKEN_NONE) { // `"Finish the block_comptime. Inferred alloc resolution etc will // occur in the parent block."` const uint32_t comptime_block_inst = gzMakeBlockInst(gz, ZIR_INST_BLOCK_COMPTIME, node); gzAddBreak(inner_gz, ZIR_INST_BREAK_INLINE, comptime_block_inst, ZIR_REF_VOID_VALUE); gzSetBlockComptimeBody( inner_gz, comptime_block_inst, REASON_COMPTIME_KEYWORD); gzStackPush(ag, comptime_block_inst); gzUnstack(inner_gz); } // Second pass: resolve inferred allocs, emit dbg_var_ptr, build scope // chain. Skip discards and lvalue expressions (they mutate existing vars). Scope* cur_scope = scope; for (uint32_t i = 0; i < var_count; i++) { const AstNodeTag vt = astNodeTag(tree, var_nodes[i]); switch (vt) { case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: case AST_NODE_GLOBAL_VAR_DECL: break; // proceed with second pass default: continue; // discard or lvalue expr: no new scope needed } const AstNodeIndex var_node = var_nodes[i]; const bool is_const = is_const_arr[i]; const ZirInstRef raw_ptr = components[i].inst; ZirInstRef final_ptr; if (is_inferred[i]) { final_ptr = gzAddUnNode( gz, ZIR_INST_RESOLVE_INFERRED_ALLOC, raw_ptr, var_node); } else if (is_const) { final_ptr = gzAddUnNode(gz, ZIR_INST_MAKE_PTR_CONST, raw_ptr, node); } else { final_ptr = raw_ptr; } const uint32_t ident_name = identAsString(ag, name_tokens[i]); gzAddDbgVar(gz, ZIR_INST_DBG_VAR_PTR, ident_name, final_ptr); LocalPtr* sub = scopeAlloc(ag, sizeof(LocalPtr)); memset(sub, 0, sizeof(*sub)); sub->base.tag = SCOPE_LOCAL_PTR; sub->base.parent = cur_scope; sub->gen_zir = gz; sub->name = ident_name; sub->ptr = final_ptr; sub->token_src = name_tokens[i]; sub->maybe_comptime = is_const || is_comptime; sub->id_cat = is_const ? ID_CAT_LOCAL_CONSTANT : ID_CAT_LOCAL_VARIABLE; cur_scope = &sub->base; } free(components); free(var_nodes); free(name_tokens); free(is_const_arr); free(is_inferred); return cur_scope; } // AstGen.zig:ptrType static ZirInstRef ptrType(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstFullPtrType ptr_info) { AstGen* ag = gz->astgen; if (ptr_info.size == PTR_SIZE_C && ptr_info.allowzero_token != AST_TOKEN_NONE) failTok(ag, ptr_info.allowzero_token, "C pointers always allow address zero"); if (ptr_info.duplicate_token != AST_TOKEN_NONE) failTok(ag, ptr_info.duplicate_token, "Extra pointer qualifier"); const SourceCursor saved = saveSourceCursor(ag); const ZirInstRef elem_type = typeExpr(gz, scope, ptr_info.ast.child_type); ZirInstRef sentinel_ref = ZIR_REF_NONE; ZirInstRef align_ref = ZIR_REF_NONE; ZirInstRef addrspace_ref = ZIR_REF_NONE; ZirInstRef bit_start_ref = ZIR_REF_NONE; ZirInstRef bit_end_ref = ZIR_REF_NONE; if (ptr_info.ast.sentinel != AST_NODE_NONE) { // These attributes can appear in any order and they all come before // the element type so we need to reset the source cursor before // generating them. restoreSourceCursor(ag, saved); ResultInfo sri = { { RL_TY, elem_type, 0, 0, NULL, 0, 0 }, 0 }; sentinel_ref = comptimeExpr(gz, scope, sri, ptr_info.ast.sentinel, ptr_info.size == PTR_SIZE_SLICE ? REASON_SLICE_SENTINEL : REASON_POINTER_SENTINEL); } if (ptr_info.ast.addrspace_node != AST_NODE_NONE) { restoreSourceCursor(ag, saved); const ZirInstRef addrspace_ty = gzAddStdLangValue( gz, ptr_info.ast.addrspace_node, STD_LANG_VALUE_ADDRESS_SPACE); ResultInfo asr = { { RL_COERCED_TY, addrspace_ty, 0, 0, NULL, 0, 0 }, 0 }; addrspace_ref = comptimeExpr( gz, scope, asr, ptr_info.ast.addrspace_node, REASON_ADDRSPACE); } if (ptr_info.ast.align_node != AST_NODE_NONE) { restoreSourceCursor(ag, saved); align_ref = comptimeExpr(gz, scope, coerced_align_ri, ptr_info.ast.align_node, REASON_ALIGN); } if (ptr_info.ast.bit_range_start != AST_NODE_NONE) { assert(ptr_info.ast.bit_range_end != AST_NODE_NONE); ResultInfo bri = { { RL_COERCED_TY, ZIR_REF_U16_TYPE, 0, 0, NULL, 0, 0 }, 0 }; bit_start_ref = comptimeExpr( gz, scope, bri, ptr_info.ast.bit_range_start, REASON_TYPE); bit_end_ref = comptimeExpr( gz, scope, bri, ptr_info.ast.bit_range_end, REASON_TYPE); } // Zir.Inst.PtrType, then the trailing refs in its order. const uint32_t payload_index = ag->extra_len; extraAppend(ag, elem_type); extraAppend(ag, (uint32_t)gzNodeIndexToRelative(gz, node)); if (sentinel_ref != ZIR_REF_NONE) extraAppend(ag, sentinel_ref); if (align_ref != ZIR_REF_NONE) extraAppend(ag, align_ref); if (addrspace_ref != ZIR_REF_NONE) extraAppend(ag, addrspace_ref); if (bit_start_ref != ZIR_REF_NONE) { extraAppend(ag, bit_start_ref); extraAppend(ag, bit_end_ref); } uint8_t flags = 0; if (ptr_info.allowzero_token != AST_TOKEN_NONE) flags |= ZIR_PTR_TYPE_FLAGS_IS_ALLOWZERO; if (ptr_info.const_token == AST_TOKEN_NONE) flags |= ZIR_PTR_TYPE_FLAGS_IS_MUTABLE; if (ptr_info.volatile_token != AST_TOKEN_NONE) flags |= ZIR_PTR_TYPE_FLAGS_IS_VOLATILE; if (sentinel_ref != ZIR_REF_NONE) flags |= ZIR_PTR_TYPE_FLAGS_HAS_SENTINEL; if (align_ref != ZIR_REF_NONE) flags |= ZIR_PTR_TYPE_FLAGS_HAS_ALIGN; if (addrspace_ref != ZIR_REF_NONE) flags |= ZIR_PTR_TYPE_FLAGS_HAS_ADDRSPACE; if (bit_start_ref != ZIR_REF_NONE) flags |= ZIR_PTR_TYPE_FLAGS_HAS_BIT_RANGE; ZirInstData data; memset(&data, 0, sizeof(data)); data.ptr_type.flags = flags; data.ptr_type.size = (uint8_t)ptr_info.size; data.ptr_type._pad = 0; data.ptr_type.payload_index = payload_index; const ZirInstRef result = gzAdd(gz, ZIR_INST_PTR_TYPE, data); return rvalue(gz, ri, result, node); } // --- addFunc --- typedef struct { AstNodeIndex src_node; uint32_t lbrace_line; uint32_t lbrace_column; uint32_t param_block; // decl_inst GenZir* ret_gz; GenZir* body_gz; GenZir* cc_gz; ZirInstRef cc_ref; ZirInstRef ret_ref; uint32_t noalias_bits; bool is_var_args; bool is_inferred_error; bool is_noinline; bool ret_ty_is_generic; const uint32_t* param_insts; uint32_t param_insts_len; const uint32_t* ret_param_refs; uint32_t ret_param_refs_len; } AddFuncArgs; // AstGen.zig:GenZir.addFunc static ZirInstRef gzAddFunc(GenZir* gz, AddFuncArgs* args) { AstGen* ag = gz->astgen; ZirInstRef ret_ref = args->ret_ref == ZIR_REF_VOID_TYPE ? ZIR_REF_NONE : args->ret_ref; const uint32_t new_index = ag->inst_len; // Bodies: body_gz may be NULL for extern function prototypes. uint32_t body_len_insts = 0; const uint32_t* body = NULL; if (args->body_gz != NULL) body = gzInstructionsSlice(args->body_gz, &body_len_insts); // ret_gz / cc_gz may be null (e.g. test declarations), in which case the // corresponding body is empty. uint32_t ret_body_len_insts = 0; const uint32_t* ret_body = NULL; if (args->ret_gz != NULL) ret_body = gzInstructionsSliceUpto( args->ret_gz, args->body_gz, &ret_body_len_insts); uint32_t cc_body_len_insts = 0; const uint32_t* cc_body = NULL; if (args->cc_gz != NULL) { const GenZir* upto = args->ret_gz != NULL ? args->ret_gz : args->body_gz; cc_body = gzInstructionsSliceUpto(args->cc_gz, upto, &cc_body_len_insts); } // func_fancy path: used when cc_ref is set, is_var_args, noalias_bits, or // is_noinline. Mirrors AstGen.addFunc exactly — body_gz == NULL alone does // not trigger func_fancy; function type expressions (body == null, no cc // etc.) use the plain func path with body_len = 0. if (args->cc_ref != ZIR_REF_NONE || args->is_var_args || args->noalias_bits != 0 || args->is_noinline) { const uint32_t body_len = countBodyLenAfterFixupsExtraRefs(ag, body, body_len_insts, args->param_insts, args->param_insts_len); const bool has_cc_body = cc_body_len_insts != 0; // has_cc_ref and has_cc_body can both be true (inline fn: cc_ref is // the std_lang_value result, cc_body carries it). When has_cc_body, // only cc_body is written to extra (not cc_ref directly). const bool has_cc_ref = args->cc_ref != ZIR_REF_NONE; const bool has_ret_body = ret_body_len_insts != 0; const uint32_t bits = (args->is_var_args ? ZIR_FUNC_FANCY_BITS_IS_VAR_ARGS : 0u) | (args->is_inferred_error ? ZIR_FUNC_FANCY_BITS_IS_INFERRED_ERROR : 0u) | (args->is_noinline ? ZIR_FUNC_FANCY_BITS_IS_NOINLINE : 0u) | (has_cc_ref ? ZIR_FUNC_FANCY_BITS_HAS_CC_REF : 0u) | (has_cc_body ? ZIR_FUNC_FANCY_BITS_HAS_CC_BODY : 0u) | (ret_ref != ZIR_REF_NONE ? ZIR_FUNC_FANCY_BITS_HAS_RET_TY_REF : 0u) | (has_ret_body ? ZIR_FUNC_FANCY_BITS_HAS_RET_TY_BODY : 0u) | (args->noalias_bits != 0 ? ZIR_FUNC_FANCY_BITS_HAS_ANY_NOALIAS : 0u) | (args->ret_ty_is_generic ? ZIR_FUNC_FANCY_BITS_RET_TY_IS_GENERIC : 0u); const uint32_t payload_index = ag->extra_len; extraAppend(ag, args->param_block); // FuncFancy.param_block extraAppend(ag, body_len); // FuncFancy.body_len extraAppend(ag, bits); // FuncFancy.bits // Trailing: cc_body if has_cc_body; else cc_ref if has_cc_ref. // (When both are true, only cc_body is written to extra.) if (has_cc_body) { const uint32_t cc_body_len = countBodyLenAfterFixups(ag, cc_body, cc_body_len_insts); extraAppend(ag, cc_body_len); appendBodyWithFixups(ag, cc_body, cc_body_len_insts); // Fix up the break_inline's block_inst to point at this func. const uint32_t last_cc_inst = cc_body[cc_body_len_insts - 1]; const uint32_t break_extra = ag->inst_datas[last_cc_inst].break_data.payload_index; ag->extra[break_extra + 1] = new_index; // Break.block_inst } else if (has_cc_ref) { extraAppend(ag, args->cc_ref); } // Trailing: ret_body if has_ret_ty_body; else ret_ref if // has_ret_ty_ref. if (has_ret_body) { const uint32_t rp_len = countBodyLenAfterFixups( ag, args->ret_param_refs, args->ret_param_refs_len); const uint32_t rb_len = countBodyLenAfterFixups(ag, ret_body, ret_body_len_insts); extraAppend(ag, rp_len + rb_len); appendBodyWithFixups( ag, args->ret_param_refs, args->ret_param_refs_len); appendBodyWithFixups(ag, ret_body, ret_body_len_insts); // Patch break_inline's block_inst to point at this func. const uint32_t last_ret_inst = ret_body[ret_body_len_insts - 1]; const uint32_t break_extra = ag->inst_datas[last_ret_inst].break_data.payload_index; ag->extra[break_extra + 1] = new_index; // Break.block_inst } else if (ret_ref != ZIR_REF_NONE) { extraAppend(ag, ret_ref); } // Trailing: noalias_bits if has_any_noalias if (args->noalias_bits != 0) extraAppend(ag, args->noalias_bits); // Body instructions (0 for extern prototype) if (body_len_insts != 0) { appendBodyWithFixupsExtraRefs(ag, body, body_len_insts, args->param_insts, args->param_insts_len); } // src_locs_and_hash (7 words if body exists, 0 if no body) if (args->body_gz != NULL) { const AstNodeIndex block = astNodeData(ag->tree, args->src_node).rhs; const uint32_t rbrace_start = astTokenStart(ag->tree, astLastToken(ag->tree, block)); advanceSourceCursor(ag, rbrace_start); const uint32_t rbrace_line = ag->source_line - gz->decl_line; const uint32_t rbrace_column = ag->source_column; const uint32_t columns = args->lbrace_column | (rbrace_column << 16); extraAppend(ag, args->lbrace_line); extraAppend(ag, rbrace_line); extraAppend(ag, columns); extraAppend(ag, 0); // proto_hash[0] (masked) extraAppend(ag, 0); extraAppend(ag, 0); extraAppend(ag, 0); gzUnstack(args->body_gz); } // Unstack sub-blocks. if (args->ret_gz != NULL) gzUnstack(args->ret_gz); if (args->cc_gz != NULL) gzUnstack(args->cc_gz); ZirInstData data; memset(&data, 0, sizeof(data)); data.pl_node.src_node = gzNodeIndexToRelative(gz, args->src_node); data.pl_node.payload_index = payload_index; const uint32_t appended = agAppendInst(ag, ZIR_INST_FUNC_FANCY, data); assert(appended == new_index); gzStackPush(ag, new_index); return new_index + ZIR_REF_START_INDEX; } const uint32_t body_len = countBodyLenAfterFixupsExtraRefs( ag, body, body_len_insts, args->param_insts, args->param_insts_len); // src_locs_and_hash: only emitted when body_gz != NULL (has a body). // Mirrors AstGen: `if (args.body_gz != null) { ... } else &.{}` const bool has_body = (args->body_gz != NULL); uint32_t rbrace_line = 0; uint32_t columns = 0; if (has_body) { const AstNodeIndex block = astNodeData(ag->tree, args->src_node).rhs; // fn_decl body const uint32_t rbrace_start = astTokenStart(ag->tree, astLastToken(ag->tree, block)); advanceSourceCursor(ag, rbrace_start); rbrace_line = ag->source_line - gz->decl_line; const uint32_t rbrace_column = ag->source_column; columns = args->lbrace_column | (rbrace_column << 16); } // Zir.Inst.Func payload (field order: ret_ty, param_block, body_len). // RetTy body_len includes ret_param_refs (param refs used in the ret // type). const uint32_t ret_param_refs_len = countBodyLenAfterFixups( ag, args->ret_param_refs, args->ret_param_refs_len); const uint32_t ret_body_len = ret_body_len_insts != 0 ? ret_param_refs_len + countBodyLenAfterFixups(ag, ret_body, ret_body_len_insts) : (ret_ref != ZIR_REF_NONE ? 1u : 0u); const uint32_t ret_ty_word = (ret_body_len & ZIR_FUNC_RET_TY_BODY_LEN_MASK) | (args->ret_ty_is_generic ? ZIR_FUNC_RET_TY_IS_GENERIC : 0u); const uint32_t payload_index = ag->extra_len; extraAppend(ag, ret_ty_word); // RetTy: body_len:u31, is_generic:bit31 extraAppend(ag, args->param_block); extraAppend(ag, body_len); if (ret_body_len_insts != 0) { appendBodyWithFixups( ag, args->ret_param_refs, args->ret_param_refs_len); appendBodyWithFixups(ag, ret_body, ret_body_len_insts); // Patch the trailing break_inline's block_inst to point at this func. const uint32_t last_ret_inst = ret_body[ret_body_len_insts - 1]; const uint32_t break_extra = ag->inst_datas[last_ret_inst].break_data.payload_index; ag->extra[break_extra + 1] = new_index; // Break.block_inst } else if (ret_ref != ZIR_REF_NONE) { extraAppend(ag, ret_ref); } appendBodyWithFixupsExtraRefs( ag, body, body_len_insts, args->param_insts, args->param_insts_len); // src_locs (3) + proto_hash (4, masked → zero) — only when body present. // Order is important when unstacking: body first, then ret, then cc. if (has_body) { extraAppend(ag, args->lbrace_line); extraAppend(ag, rbrace_line); extraAppend(ag, columns); extraAppend(ag, 0); extraAppend(ag, 0); extraAppend(ag, 0); extraAppend(ag, 0); gzUnstack(args->body_gz); } if (args->ret_gz != NULL) gzUnstack(args->ret_gz); if (args->cc_gz != NULL) gzUnstack(args->cc_gz); ZirInstData data; memset(&data, 0, sizeof(data)); data.pl_node.src_node = gzNodeIndexToRelative(gz, args->src_node); data.pl_node.payload_index = payload_index; const uint32_t appended = agAppendInst(ag, args->is_inferred_error ? ZIR_INST_FUNC_INFERRED : ZIR_INST_FUNC, data); assert(appended == new_index); gzStackPush(ag, new_index); return new_index + ZIR_REF_START_INDEX; } // Set the active function body block. Wrapped in a helper so the address of // the caller's local `body_gz` is passed as an argument rather than assigned // directly to a field of a parameter (which static analysis rejects). static void agSetFnBlock(AstGen* ag, GenZir* body_gz) { ag->fn_block = body_gz; } // AstGen.zig:GenZir.addParam // Emit a param/param_comptime instruction with a Param payload // and its type body. Unstacks param_gz. Returns the param instruction index. static uint32_t gzAddParam(GenZir* gz, GenZir* param_gz, bool ty_is_generic, ZirInstTag tag, AstTokenIndex abs_tok, uint32_t name, const uint32_t* prev_param_insts, uint32_t prev_param_insts_len) { AstGen* ag = gz->astgen; uint32_t param_body_len; const uint32_t* param_body = gzInstructionsSlice(param_gz, ¶m_body_len); // prev_param_insts carry ref-table fixups: if a previous param was used // via RL_REF_CONST in this param's type body, its ref instruction must be // spliced into the body before the instruction that references it. const uint32_t body_len = countBodyLenAfterFixupsExtraRefs(ag, param_body, param_body_len, prev_param_insts, prev_param_insts_len); // Zir.Inst.Param: { name, type = body_len:u31 | is_generic:bit31 }. const uint32_t payload_index = ag->extra_len; extraAppend(ag, name); extraAppend(ag, (body_len & ZIR_PARAM_TYPE_BODY_LEN_MASK) | (ty_is_generic ? ZIR_PARAM_TYPE_IS_GENERIC : 0u)); appendBodyWithFixupsExtraRefs(ag, param_body, param_body_len, prev_param_insts, prev_param_insts_len); gzUnstack(param_gz); ZirInstData data; memset(&data, 0, sizeof(data)); data.pl_tok.src_tok = gzTokenIndexToRelative(gz, abs_tok); data.pl_tok.payload_index = payload_index; const uint32_t new_index = agAppendInst(ag, tag, data); gzStackPush(ag, new_index); return new_index; } // AstGen.zig:fnProtoExpr static ZirInstRef fnProtoExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstFullFnProto fn_proto) { const AstGen* ag = gz->astgen; const Ast* tree = ag->tree; if (fn_proto.name_token != AST_TOKEN_NONE) failTok(ag, fn_proto.name_token, "function type cannot have a name"); if (fn_proto.ast.align_expr != AST_NODE_NONE) failNode(ag, fn_proto.ast.align_expr, "function type cannot have an alignment"); if (fn_proto.ast.addrspace_expr != AST_NODE_NONE) failNode(ag, fn_proto.ast.addrspace_expr, "function type cannot have an addrspace"); if (fn_proto.ast.section_expr != AST_NODE_NONE) failNode(ag, fn_proto.ast.section_expr, "function type cannot have a linksection"); assert(fn_proto.ast.return_type != AST_NODE_NONE); const AstTokenIndex maybe_bang = astFirstToken(tree, fn_proto.ast.return_type) - 1; if (astTokenTag(tree, maybe_bang) == TOKEN_BANG) failTok( ag, maybe_bang, "function type cannot have an inferred error set"); assert(fn_proto.extern_export_inline_token == AST_TOKEN_NONE || astTokenTag(tree, fn_proto.extern_export_inline_token) != TOKEN_KEYWORD_EXTERN); return fnProtoExprInner(gz, scope, ri, node, fn_proto, false); } // AstGen.zig:fnProtoExprInner static ZirInstRef fnProtoExprInner(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstFullFnProto fn_proto, bool implicit_ccc) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; GenZir block_scope = gzMakeSubBlock(gz, scope); const uint32_t block_inst = gzMakeBlockInst(gz, ZIR_INST_BLOCK_INLINE, node); // Iterate parameters. prev_param_insts is empty (proto can't self-ref). uint32_t noalias_bits = 0; bool is_var_args = false; uint32_t param_type_i = 0; AstFullFnProtoIterator it = astFullFnProtoIterate(&fn_proto, tree); AstFullFnProtoParam param; for (; astFullFnProtoIteratorNext(&it, ¶m); param_type_i++) { bool is_comptime = false; if ((param.comptime_noalias != AST_TOKEN_NONE)) { const TokenTag tt = astTokenTag(tree, param.comptime_noalias); if (tt == TOKEN_KEYWORD_NOALIAS) { if (param_type_i >= 32) failTok(ag, param.comptime_noalias, "this compiler implementation only supports " "'noalias' on the first 32 parameters"); noalias_bits |= (uint32_t)1u << param_type_i; } else if (tt == TOKEN_KEYWORD_COMPTIME) { is_comptime = true; } } bool is_anytype = false; if ((param.anytype_ellipsis3 != AST_TOKEN_NONE)) { const TokenTag tt = astTokenTag(tree, param.anytype_ellipsis3); if (tt == TOKEN_KEYWORD_ANYTYPE) { is_anytype = true; } else { // ellipsis3 is_var_args = true; break; } } // param_name: "" for "_" or unnamed params. uint32_t param_name = 0; // .empty if ((param.name_token != AST_TOKEN_NONE)) { if (!tokenIsUnderscore(tree, param.name_token)) param_name = identAsString(ag, param.name_token); } if (is_anytype) { const AstTokenIndex name_token = (param.name_token != AST_TOKEN_NONE) ? param.name_token : param.anytype_ellipsis3; const ZirInstTag tag = is_comptime ? ZIR_INST_PARAM_ANYTYPE_COMPTIME : ZIR_INST_PARAM_ANYTYPE; gzAddStrTok(&block_scope, tag, param_name, name_token); } else { const AstNodeIndex param_type_node = param.type_expr; GenZir param_gz = gzMakeSubBlock(&block_scope, scope); param_gz.is_comptime = true; const ZirInstRef param_type = fullBodyExpr(¶m_gz, scope, coerced_type_ri, param_type_node, BLOCK_KIND_NORMAL); const uint32_t param_inst_expected = ag->inst_len + 1; gzAddBreakWithSrcNode(¶m_gz, ZIR_INST_BREAK_INLINE, param_inst_expected, param_type, param_type_node); // name_token: use param name if available, else main token of type const AstTokenIndex name_token = (param.name_token != AST_TOKEN_NONE) ? param.name_token : astNodeMainToken(tree, param_type_node); const ZirInstTag tag = is_comptime ? ZIR_INST_PARAM_COMPTIME : ZIR_INST_PARAM; // prev_param_insts is empty — proto params can't cross-reference const uint32_t pidx = gzAddParam(&block_scope, ¶m_gz, false, tag, name_token, param_name, NULL, 0); assert(pidx == param_inst_expected); } } // Calling convention. ZirInstRef cc_ref = ZIR_REF_NONE; if (fn_proto.ast.callconv_expr != AST_NODE_NONE) { const ZirInstRef cc_ty = gzAddStdLangValue(&block_scope, fn_proto.ast.callconv_expr, STD_LANG_VALUE_CALLING_CONVENTION); ResultInfo cc_ri = { { RL_COERCED_TY, cc_ty, 0, 0, NULL, 0, 0 }, 0 }; cc_ref = comptimeExpr(&block_scope, scope, cc_ri, fn_proto.ast.callconv_expr, REASON_CALLCONV); } else if (implicit_ccc) { // extern "c" fn with no explicit callconv: emit std_lang_value // (.calling_convention_c) as the implicit cc_ref. cc_ref = gzAddStdLangValue( &block_scope, node, STD_LANG_VALUE_CALLING_CONVENTION_C); } // Return type (comptime-known, required for function type). const AstNodeIndex return_type_node = fn_proto.ast.return_type; const ZirInstRef ret_ref = comptimeExpr(&block_scope, scope, coerced_type_ri, return_type_node, REASON_FN_RET_TY); // Build func_fancy with no body. AddFuncArgs func_args; memset(&func_args, 0, sizeof(func_args)); func_args.src_node = fn_proto.ast.proto_node; func_args.cc_ref = cc_ref; func_args.ret_ref = ret_ref; func_args.param_block = block_inst; func_args.body_gz = NULL; // no body (function type / extern prototype) func_args.is_var_args = is_var_args; func_args.noalias_bits = noalias_bits; const ZirInstRef result = gzAddFunc(&block_scope, &func_args); gzAddBreak(&block_scope, ZIR_INST_BREAK_INLINE, block_inst, result); gzSetBlockBody(&block_scope, block_inst); gzStackPush(ag, block_inst); return rvalue( gz, ri, block_inst + ZIR_REF_START_INDEX, fn_proto.ast.proto_node); } // --- fnDeclInner / fnDecl --- // AstGen.zig:fnDeclInner static void fnDeclInner(AstGen* ag, GenZir* decl_gz, Scope* scope, uint32_t decl_inst, AstNodeIndex decl_node, AstNodeIndex body_node, AstFullFnProto fn_proto) { const Ast* tree = ag->tree; bool is_noinline = false; bool has_inline_keyword = false; if ((fn_proto.extern_export_inline_token != AST_TOKEN_NONE)) { const TokenTag t = astTokenTag(tree, fn_proto.extern_export_inline_token); is_noinline = t == TOKEN_KEYWORD_NOINLINE; has_inline_keyword = t == TOKEN_KEYWORD_INLINE; } const AstNodeIndex return_type = fn_proto.ast.return_type; const AstTokenIndex maybe_bang = astFirstToken(tree, return_type) - 1; const bool is_inferred_error = astTokenTag(tree, maybe_bang) == TOKEN_BANG; // Count params (including anytype/...) to size param_insts, since the // `params` slice excludes anytype/... params. uint32_t total_params = 0; { AstFullFnProtoIterator cit = astFullFnProtoIterate(&fn_proto, tree); AstFullFnProtoParam pp; for (; astFullFnProtoIteratorNext(&cit, &pp);) total_params += 1; } uint32_t* param_insts = NULL; if (total_params != 0) { param_insts = zig1Malloc((size_t)total_params * sizeof(uint32_t)); } uint32_t param_insts_len = 0; // Heap-allocated so LocalVal.is_used_or_discarded can point at it without // capturing the address of a stack local. bool* any_param_used = scopeAlloc(ag, sizeof(bool)); *any_param_used = false; uint32_t noalias_bits = 0; bool is_var_args = false; Scope* params_scope = scope; uint32_t param_type_i = 0; AstFullFnProtoIterator it = astFullFnProtoIterate(&fn_proto, tree); AstFullFnProtoParam param; for (; astFullFnProtoIteratorNext(&it, ¶m); param_type_i++) { bool is_comptime = false; if ((param.comptime_noalias != AST_TOKEN_NONE)) { const TokenTag tt = astTokenTag(tree, param.comptime_noalias); if (tt == TOKEN_KEYWORD_NOALIAS) { if (param_type_i >= 32) failTok(ag, param.comptime_noalias, "this compiler implementation only supports " "'noalias' on the first 32 parameters"); noalias_bits |= (uint32_t)1u << param_type_i; } else if (tt == TOKEN_KEYWORD_COMPTIME) { is_comptime = true; } } bool is_anytype = false; if ((param.anytype_ellipsis3 != AST_TOKEN_NONE)) { const TokenTag tt = astTokenTag(tree, param.anytype_ellipsis3); if (tt == TOKEN_KEYWORD_ANYTYPE) { is_anytype = true; } else { // ellipsis3 is_var_args = true; break; } } uint32_t param_name; // NullTerminatedString; 0 = .empty if ((param.name_token != AST_TOKEN_NONE)) { if (tokenIsUnderscore(tree, param.name_token)) param_name = 0; else param_name = identAsString(ag, param.name_token); } else { ASTGEN_UNIMPLEMENTED("fnDeclInner: missing parameter name"); } // Unnamed params (upstream `orelse anytype_ellipsis3` / node main // token) only arise for varargs/unnamed-type params, which we reject // above via UNIMPLEMENTED, so name_token is always param.name_token. ZirInstRef param_inst; if (is_anytype) { const AstTokenIndex name_token = param.name_token; const ZirInstTag tag = is_comptime ? ZIR_INST_PARAM_ANYTYPE_COMPTIME : ZIR_INST_PARAM_ANYTYPE; param_inst = gzAddStrTok(decl_gz, tag, param_name, name_token); } else { const AstNodeIndex param_type_node = param.type_expr; *any_param_used = false; GenZir param_gz = gzMakeSubBlock(decl_gz, scope); const ZirInstRef param_type = fullBodyExpr(¶m_gz, params_scope, coerced_type_ri, param_type_node, BLOCK_KIND_NORMAL); const uint32_t param_inst_expected = ag->inst_len + 1; gzAddBreakWithSrcNode(¶m_gz, ZIR_INST_BREAK_INLINE, param_inst_expected, param_type, param_type_node); const bool param_type_is_generic = *any_param_used; const AstTokenIndex name_token = param.name_token; const ZirInstTag tag = is_comptime ? ZIR_INST_PARAM_COMPTIME : ZIR_INST_PARAM; const uint32_t pidx = gzAddParam(decl_gz, ¶m_gz, param_type_is_generic, tag, name_token, param_name, param_insts, param_insts_len); assert(param_inst_expected == pidx); param_inst = pidx + ZIR_REF_START_INDEX; } if (param_name == 0) continue; // .empty LocalVal* sub = scopeAlloc(ag, sizeof(LocalVal)); memset(sub, 0, sizeof(*sub)); sub->base.tag = SCOPE_LOCAL_VAL; sub->base.parent = params_scope; sub->gen_zir = decl_gz; sub->inst = param_inst; sub->token_src = param.name_token; sub->is_used_or_discarded = any_param_used; sub->name = param_name; sub->id_cat = ID_CAT_FUNCTION_PARAMETER; params_scope = &sub->base; // A named param here was counted in total_params, so param_insts is // allocated (asserted to satisfy static analysis). assert(param_insts != NULL); param_insts[param_insts_len++] = param_inst - ZIR_REF_START_INDEX; } // cc_gz GenZir cc_gz = gzMakeSubBlock(decl_gz, scope); ZirInstRef cc_ref = ZIR_REF_NONE; if (fn_proto.ast.callconv_expr != AST_NODE_NONE) { // Emit std_lang_value(.calling_convention) for coercion, then evaluate // the callconv expression into cc_gz and add a break_inline. const ZirInstRef cc_ty = gzAddStdLangValue(&cc_gz, fn_proto.ast.callconv_expr, STD_LANG_VALUE_CALLING_CONVENTION); ResultInfo cc_ri = { { RL_COERCED_TY, cc_ty, 0, 0, NULL, 0, 0 }, 0 }; cc_ref = expr(&cc_gz, scope, cc_ri, fn_proto.ast.callconv_expr); uint32_t cc_gz_len; gzInstructionsSlice(&cc_gz, &cc_gz_len); if (cc_gz_len != 0) gzAddBreak(&cc_gz, ZIR_INST_BREAK_INLINE, 0, cc_ref); } else if (has_inline_keyword) { // inline fn: emit std_lang_value(.calling_convention_inline) + // break_inline. cc_ref is also set to the std_lang_value result so // that has_cc_ref=true in FuncFancy.Bits (matches Zig reference). cc_ref = gzAddStdLangValue( &cc_gz, decl_node, STD_LANG_VALUE_CALLING_CONVENTION_INLINE); gzAddBreak(&cc_gz, ZIR_INST_BREAK_INLINE, 0, cc_ref); } // ret_gz GenZir ret_gz = gzMakeSubBlock(decl_gz, params_scope); *any_param_used = false; ZirInstRef ret_ref = expr(&ret_gz, params_scope, coerced_type_ri, return_type); uint32_t ret_gz_len; gzInstructionsSlice(&ret_gz, &ret_gz_len); if (ret_gz_len != 0) { gzAddBreak(&ret_gz, ZIR_INST_BREAK_INLINE, 0, ret_ref); } // fetchRemoveRefEntries(param_insts): pull the ref instructions for params // used in the return type out of the ref_table so the body re-refs // freshly. uint32_t* ret_param_refs = NULL; uint32_t ret_param_refs_len = 0; if (param_insts_len != 0) { ret_param_refs = zig1Malloc((size_t)param_insts_len * sizeof(uint32_t)); for (uint32_t i = 0; i < param_insts_len; i++) { uint32_t refv; if (refTableFetchRemove(ag, param_insts[i], &refv)) ret_param_refs[ret_param_refs_len++] = refv; } } const bool ret_ty_is_generic = *any_param_used; // body_gz GenZir body_gz; memset(&body_gz, 0, sizeof(body_gz)); body_gz.base.tag = SCOPE_GEN_ZIR; body_gz.is_comptime = false; body_gz.src_baseline = fn_proto.ast.proto_node; body_gz.decl_line = decl_gz->decl_line; body_gz.base.parent = params_scope; body_gz.astgen = ag; body_gz.instructions_top = ag->gz_len; GenZir* const prev_fn_block = ag->fn_block; const ZirInstRef prev_fn_ret_ty = ag->fn_ret_ty; agSetFnBlock(ag, &body_gz); if (is_inferred_error || ret_ref >= ZIR_REF_START_INDEX) ag->fn_ret_ty = gzAddNode(&body_gz, ZIR_INST_RET_TYPE, decl_node); else ag->fn_ret_ty = ret_ref; const bool prev_var_args = ag->fn_var_args; ag->fn_var_args = is_var_args; advanceSourceCursorToNode(ag, body_node); const uint32_t lbrace_line = ag->source_line - decl_gz->decl_line; const uint32_t lbrace_column = ag->source_column; fullBodyExpr(&body_gz, &body_gz.base, none_ri, body_node, BLOCK_KIND_ALLOW_BRANCH_HINT); checkUsed(scope, params_scope); if (!gzEndsWithNoReturn(&body_gz)) { gzAddRestoreErrRetIndex( &body_gz, BRANCH_TARGET_RET, RESTORE_ALWAYS, decl_node); gzAddUnTok(&body_gz, ZIR_INST_RET_IMPLICIT, ZIR_REF_VOID_VALUE, astLastToken(tree, body_node)); } ag->fn_var_args = prev_var_args; AddFuncArgs fargs; memset(&fargs, 0, sizeof(fargs)); fargs.src_node = decl_node; fargs.cc_ref = cc_ref; fargs.cc_gz = &cc_gz; fargs.ret_ref = ret_ref; fargs.ret_gz = &ret_gz; fargs.lbrace_line = lbrace_line; fargs.lbrace_column = lbrace_column; fargs.param_block = decl_inst; fargs.body_gz = &body_gz; fargs.is_var_args = is_var_args; fargs.is_inferred_error = is_inferred_error; fargs.is_noinline = is_noinline; fargs.noalias_bits = noalias_bits; fargs.ret_ty_is_generic = ret_ty_is_generic; fargs.param_insts = param_insts; fargs.param_insts_len = param_insts_len; fargs.ret_param_refs = ret_param_refs; fargs.ret_param_refs_len = ret_param_refs_len; const ZirInstRef func_inst = gzAddFunc(decl_gz, &fargs); gzAddBreakWithSrcNode( decl_gz, ZIR_INST_BREAK_INLINE, decl_inst, func_inst, decl_node); ag->fn_block = prev_fn_block; ag->fn_ret_ty = prev_fn_ret_ty; free(param_insts); free(ret_param_refs); } // AstGen.zig:fnDecl static void fnDecl(AstGen* ag, GenZir* gz, Scope* scope, AstNodeIndex decl_node, bool has_body, AstNodeIndex body_node, AstFullFnProto fn_proto) { const Ast* tree = ag->tree; if (!(fn_proto.name_token != AST_TOKEN_NONE)) ASTGEN_UNIMPLEMENTED("fnDecl: missing function name"); const AstTokenIndex fn_name_token = fn_proto.name_token; const uint32_t decl_inst = gzMakeDeclaration(gz, fn_proto.ast.proto_node); advanceSourceCursorToNode(ag, decl_node); const SourceCursor saved_cursor = saveSourceCursor(ag); const uint32_t decl_column = ag->source_column; const bool prev_within_fn = ag->within_fn; ag->within_fn = true; const bool is_pub = (fn_proto.visib_token != AST_TOKEN_NONE); bool is_export = false; bool is_extern = false; bool has_inline_keyword = false; if ((fn_proto.extern_export_inline_token != AST_TOKEN_NONE)) { const TokenTag t = astTokenTag(tree, fn_proto.extern_export_inline_token); is_export = t == TOKEN_KEYWORD_EXPORT; is_extern = t == TOKEN_KEYWORD_EXTERN; has_inline_keyword = t == TOKEN_KEYWORD_INLINE; } uint32_t lib_name = 0; // 0 = no lib name (.empty) if ((fn_proto.lib_name != AST_TOKEN_NONE)) { uint32_t lib_name_len; lib_name = strLitAsString(ag, fn_proto.lib_name, &lib_name_len); if (lib_name_len == 0) failTok(ag, fn_proto.lib_name, "library name cannot be empty"); // Check for null bytes (strLitAsString already appended to sb). const uint8_t* slice = &ag->string_bytes.items[lib_name]; for (uint32_t i = 0; i < lib_name_len; i++) { if (slice[i] == 0) failTok(ag, fn_proto.lib_name, "library name cannot contain null bytes"); } } if (fn_proto.ast.callconv_expr != AST_NODE_NONE && has_inline_keyword) failNode(ag, fn_proto.ast.callconv_expr, "explicit callconv incompatible with inline keyword"); const AstNodeIndex return_type = fn_proto.ast.return_type; const AstTokenIndex maybe_bang = astFirstToken(tree, return_type) - 1; const bool is_inferred_error = astTokenTag(tree, maybe_bang) == TOKEN_BANG; if (!has_body) { if (!is_extern) failTok( ag, fn_proto.ast.fn_token, "non-extern function has no body"); if (is_inferred_error) failTok(ag, maybe_bang, "function prototype may not have inferred error set"); } else { assert(!is_extern); } // wip_decls.nextDecl(decl_inst) wipDeclsNext(ag, decl_inst); GenZir type_gz; memset(&type_gz, 0, sizeof(type_gz)); type_gz.base.tag = SCOPE_GEN_ZIR; type_gz.is_comptime = true; type_gz.src_baseline = fn_proto.ast.proto_node; type_gz.decl_line = ag->source_line; type_gz.base.parent = scope; type_gz.astgen = ag; type_gz.instructions_top = ag->gz_len; if (is_extern) { const ZirInstRef type_inst = fnProtoExprInner( &type_gz, &type_gz.base, none_ri, decl_node, fn_proto, true); gzAddBreakWithSrcNode( &type_gz, ZIR_INST_BREAK_INLINE, decl_inst, type_inst, decl_node); restoreSourceCursor(ag, saved_cursor); } GenZir align_gz = gzMakeSubBlock(&type_gz, scope); if (fn_proto.ast.align_expr != AST_NODE_NONE) { const ZirInstRef inst = expr(&align_gz, &align_gz.base, coerced_align_ri, fn_proto.ast.align_expr); gzAddBreakWithSrcNode( &align_gz, ZIR_INST_BREAK_INLINE, decl_inst, inst, decl_node); } GenZir addrspace_gz = gzMakeSubBlock(&align_gz, scope); if (fn_proto.ast.addrspace_expr != AST_NODE_NONE) { const ZirInstRef addrspace_ty = gzAddStdLangValue(&addrspace_gz, fn_proto.ast.addrspace_expr, STD_LANG_VALUE_ADDRESS_SPACE); const ResultInfo addrspace_ri = { { RL_COERCED_TY, addrspace_ty, 0, 0, NULL, 0, 0 }, 0 }; const ZirInstRef inst = expr(&addrspace_gz, &addrspace_gz.base, addrspace_ri, fn_proto.ast.addrspace_expr); gzAddBreakWithSrcNode( &addrspace_gz, ZIR_INST_BREAK_INLINE, decl_inst, inst, decl_node); } GenZir linksection_gz = gzMakeSubBlock(&addrspace_gz, scope); if (fn_proto.ast.section_expr != AST_NODE_NONE) { const ResultInfo coerced_linksection_ri = { { RL_COERCED_TY, ZIR_REF_SLICE_CONST_U8_TYPE, 0, 0, NULL, 0, 0 }, 0 }; const ZirInstRef inst = expr(&linksection_gz, &linksection_gz.base, coerced_linksection_ri, fn_proto.ast.section_expr); gzAddBreakWithSrcNode(&linksection_gz, ZIR_INST_BREAK_INLINE, decl_inst, inst, decl_node); } GenZir value_gz = gzMakeSubBlock(&linksection_gz, scope); if (!is_extern) { restoreSourceCursor(ag, saved_cursor); fnDeclInner(ag, &value_gz, &value_gz.base, decl_inst, decl_node, body_node, fn_proto); } SetDeclArgs args; memset(&args, 0, sizeof(args)); args.src_line = type_gz.decl_line; args.src_column = decl_column; args.kind = DECL_KIND_CONST; args.name = identAsString(ag, fn_name_token); args.is_pub = is_pub; args.is_threadlocal = false; args.linkage = is_extern ? LINKAGE_EXTERN : (is_export ? LINKAGE_EXPORT : LINKAGE_NORMAL); args.lib_name = lib_name; args.type_gz = &type_gz; args.align_gz = &align_gz; args.addrspace_gz = &addrspace_gz; args.linksection_gz = &linksection_gz; args.value_gz = &value_gz; setDeclaration(decl_inst, &args); ag->within_fn = prev_within_fn; } // --- comptimeDecl --- // AstGen.zig:comptimeDecl static void comptimeDecl( AstGen* ag, GenZir* gz, Scope* scope, AstNodeIndex node) { const Ast* tree = ag->tree; const AstNodeIndex body_node = astNodeData(tree, node).lhs; // src_hasher is omitted; src_hash is masked to zero by the harness. // Up top so the ZIR instruction index marks the start range of this // top-level declaration. const uint32_t decl_inst = gzMakeDeclaration(gz, node); wipDeclsNext(ag, decl_inst); // wip_decls.nextDecl advanceSourceCursorToNode(ag, node); // This is just needed for the setDeclaration call. GenZir dummy_gz = gzMakeSubBlock(gz, scope); GenZir comptime_gz; memset(&comptime_gz, 0, sizeof(comptime_gz)); comptime_gz.base.tag = SCOPE_GEN_ZIR; comptime_gz.is_comptime = true; comptime_gz.src_baseline = node; comptime_gz.decl_line = ag->source_line; comptime_gz.base.parent = scope; comptime_gz.astgen = ag; comptime_gz.instructions_top = ag->gz_len; const uint32_t decl_column = ag->source_column; const ZirInstRef block_result = fullBodyExpr(&comptime_gz, &comptime_gz.base, none_ri, body_node, BLOCK_KIND_NORMAL); if (gzIsEmpty(&comptime_gz) || !refIsNoReturn(&comptime_gz, block_result)) gzAddBreak(&comptime_gz, ZIR_INST_BREAK_INLINE, decl_inst, ZIR_REF_VOID_VALUE); SetDeclArgs args; memset(&args, 0, sizeof(args)); args.src_line = comptime_gz.decl_line; args.src_column = decl_column; args.kind = DECL_KIND_COMPTIME; args.name = 0; // empty args.is_pub = false; args.is_threadlocal = false; args.linkage = LINKAGE_NORMAL; args.type_gz = &dummy_gz; args.align_gz = &dummy_gz; args.addrspace_gz = &dummy_gz; args.linksection_gz = &dummy_gz; args.value_gz = &comptime_gz; setDeclaration(decl_inst, &args); gzUnstack(&comptime_gz); gzUnstack(&dummy_gz); } // --- testDecl --- // AstGen.zig:testDecl static void testDecl(AstGen* ag, GenZir* gz, Scope* scope, AstNodeIndex node) { const Ast* tree = ag->tree; const AstNodeIndex body_node = astNodeData(tree, node).rhs; // src_hasher is omitted; src_hash is masked to zero by the harness. // Up top so the ZIR instruction index marks the start range of this // top-level declaration. const uint32_t decl_inst = gzMakeDeclaration(gz, node); wipDeclsNext(ag, decl_inst); // wip_decls.nextDecl advanceSourceCursorToNode(ag, node); // This is just needed for the setDeclaration call. GenZir dummy_gz = gzMakeSubBlock(gz, scope); GenZir decl_block; memset(&decl_block, 0, sizeof(decl_block)); decl_block.base.tag = SCOPE_GEN_ZIR; decl_block.is_comptime = true; decl_block.src_baseline = node; decl_block.decl_line = ag->source_line; decl_block.base.parent = scope; decl_block.astgen = ag; decl_block.instructions_top = ag->gz_len; const uint32_t decl_column = ag->source_column; const AstTokenIndex test_token = astNodeMainToken(tree, node); const AstTokenIndex test_name_token = test_token + 1; const TokenTag name_tag = astTokenTag(tree, test_name_token); uint32_t test_name = 0; // empty switch (name_tag) { case TOKEN_STRING_LITERAL: { uint32_t name_len; test_name = strLitAsString(ag, test_name_token, &name_len); break; } case TOKEN_IDENTIFIER: // decltest: name resolves via identAsString (namespace lookup is only // needed for error reporting, which zig1 omits). test_name = identAsString(ag, test_name_token); break; default: break; // unnamed test } GenZir fn_block; memset(&fn_block, 0, sizeof(fn_block)); fn_block.base.tag = SCOPE_GEN_ZIR; fn_block.is_comptime = false; fn_block.src_baseline = node; fn_block.decl_line = decl_block.decl_line; fn_block.base.parent = &decl_block.base; fn_block.astgen = ag; fn_block.instructions_top = ag->gz_len; const bool prev_within_fn = ag->within_fn; GenZir* const prev_fn_block = ag->fn_block; const ZirInstRef prev_fn_ret_ty = ag->fn_ret_ty; ag->within_fn = true; agSetFnBlock(ag, &fn_block); ag->fn_ret_ty = ZIR_REF_ANYERROR_VOID_ERROR_UNION_TYPE; advanceSourceCursorToNode(ag, body_node); const uint32_t lbrace_line = ag->source_line - decl_block.decl_line; const uint32_t lbrace_column = ag->source_column; fullBodyExpr( &fn_block, &fn_block.base, none_ri, body_node, BLOCK_KIND_NORMAL); // Zig's testDecl uses: if (fn_block.isEmpty() or // !fn_block.refIsNoReturn(block_result)) where block_result comes from // fullBodyExpr which always returns .void_value. // refIsNoReturn(.void_value) == false, so the condition is always true. // Therefore: always emit restore_err_ret_index + ret_implicit for test // functions, even when the body ends with an unreachable instruction. gzAddRestoreErrRetIndex( &fn_block, BRANCH_TARGET_RET, RESTORE_ALWAYS, node); gzAddUnTok(&fn_block, ZIR_INST_RET_IMPLICIT, ZIR_REF_VOID_VALUE, astLastToken(tree, body_node)); ag->within_fn = prev_within_fn; ag->fn_block = prev_fn_block; ag->fn_ret_ty = prev_fn_ret_ty; AddFuncArgs fargs; memset(&fargs, 0, sizeof(fargs)); fargs.src_node = node; fargs.cc_ref = ZIR_REF_NONE; fargs.cc_gz = NULL; fargs.ret_ref = ZIR_REF_ANYERROR_VOID_ERROR_UNION_TYPE; fargs.ret_gz = NULL; fargs.lbrace_line = lbrace_line; fargs.lbrace_column = lbrace_column; fargs.param_block = decl_inst; fargs.body_gz = &fn_block; fargs.is_var_args = false; fargs.is_inferred_error = false; fargs.is_noinline = false; fargs.noalias_bits = 0; fargs.ret_ty_is_generic = false; fargs.param_insts = NULL; fargs.param_insts_len = 0; const ZirInstRef func_inst = gzAddFunc(&decl_block, &fargs); gzAddBreak(&decl_block, ZIR_INST_BREAK_INLINE, decl_inst, func_inst); SetDeclArgs args; memset(&args, 0, sizeof(args)); args.src_line = decl_block.decl_line; args.src_column = decl_column; args.kind = name_tag == TOKEN_STRING_LITERAL ? DECL_KIND_TEST : (name_tag == TOKEN_IDENTIFIER ? DECL_KIND_DECLTEST : DECL_KIND_UNNAMED_TEST); args.name = test_name; args.is_pub = false; args.is_threadlocal = false; args.linkage = LINKAGE_NORMAL; args.type_gz = &dummy_gz; args.align_gz = &dummy_gz; args.addrspace_gz = &dummy_gz; args.linksection_gz = &dummy_gz; args.value_gz = &decl_block; setDeclaration(decl_inst, &args); gzUnstack(&fn_block); gzUnstack(&decl_block); gzUnstack(&dummy_gz); } // --- scanContainer --- typedef enum { CONTAINER_KIND_STRUCT, CONTAINER_KIND_ENUM, CONTAINER_KIND_UNION, } ContainerKind; typedef struct { uint32_t decls_len; uint32_t fields_len; bool any_field_aligns; bool any_field_values; bool any_comptime_fields; bool has_underscore_field; } ScanContainerResult; static void namespacePutDecl(Namespace* ns, uint32_t name, AstNodeIndex node) { // Overwrite semantics like a hashmap put (last wins); a duplicate is // upstream's compile error, which zig1 does not report. for (uint32_t i = 0; i < ns->decls_len; i++) { if (ns->decls[i].name == name) { ns->decls[i].node = node; return; } } const uint32_t need = ns->decls_len + 1; if (need > ns->decls_cap) { uint32_t new_cap = ns->decls_cap * 2; if (new_cap < need) new_cap = need; if (new_cap < 8) new_cap = 8; NamespaceDecl* n = zig1Realloc(ns->decls, (size_t)new_cap * sizeof(NamespaceDecl)); ns->decls = n; ns->decls_cap = new_cap; } ns->decls[ns->decls_len].name = name; ns->decls[ns->decls_len].node = node; ns->decls_len += 1; } // AstGen.zig:scanContainer static ScanContainerResult scanContainer(AstGen* ag, Namespace* namespace, const AstNodeIndex* members, uint32_t members_len, ContainerKind kind) { const Ast* tree = ag->tree; uint32_t decl_count = 0; bool any_field_aligns = false; bool any_field_values = false; bool any_comptime_fields = false; bool has_underscore_field = false; for (uint32_t mi = 0; mi < members_len; mi++) { const AstNodeIndex member_node = members[mi]; bool is_decl; AstTokenIndex name_token; switch (astNodeTag(tree, member_node)) { case AST_NODE_CONTAINER_FIELD_INIT: case AST_NODE_CONTAINER_FIELD_ALIGN: case AST_NODE_CONTAINER_FIELD: { AstFullContainerField full; astFullContainerField(tree, member_node, &full); if (kind == CONTAINER_KIND_ENUM || kind == CONTAINER_KIND_UNION) astFullContainerFieldConvertToNonTupleLike(&full, tree); if ((full.ast.align_expr != AST_NODE_NONE)) any_field_aligns = true; if ((full.ast.value_expr != AST_NODE_NONE)) any_field_values = true; if ((full.comptime_token != AST_TOKEN_NONE)) any_comptime_fields = true; if (tokenIsUnderscore(tree, full.ast.main_token)) has_underscore_field = true; if (full.ast.tuple_like) { // Structs: anonymous/positional fields are valid. // Union/enum: after convertToNonTupleLike, still tuple_like // means a type-only field (e.g. `u8,` in union) which is a // compile error; skip it here — unionDeclInner / enumDeclInner // will error properly. continue; } // A field name is interned here (matches upstream string order). identAsString(ag, full.ast.main_token); continue; } case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: decl_count += 1; is_decl = true; name_token = astNodeMainToken(tree, member_node) + 1; break; case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: case AST_NODE_FN_DECL: { decl_count += 1; const AstTokenIndex ident = astNodeMainToken(tree, member_node) + 1; if (astTokenTag(tree, ident) != TOKEN_IDENTIFIER) failNode(ag, member_node, "missing function name"); is_decl = true; name_token = ident; break; } case AST_NODE_COMPTIME: decl_count += 1; continue; case AST_NODE_TEST_DECL: { decl_count += 1; // Test names are interned here so string_bytes order matches // upstream. Redeclaration detection is only for errors, omitted. const AstTokenIndex test_name_token = astNodeMainToken(tree, member_node) + 1; switch (astTokenTag(tree, test_name_token)) { case TOKEN_STRING_LITERAL: { uint32_t nlen; strLitAsString(ag, test_name_token, &nlen); break; } case TOKEN_IDENTIFIER: identAsString(ag, test_name_token); break; default: break; // unnamed test } continue; } default: UNREACHABLE("scanContainer: unreachable member tag"); } const uint32_t name_str_index = identAsString(ag, name_token); if (is_decl) namespacePutDecl(namespace, name_str_index, member_node); // Duplicate and primitive-shadowing declarations are compile errors // upstream that zig1 does not report. } ScanContainerResult r; memset(&r, 0, sizeof(r)); r.decls_len = decl_count; r.fields_len = members_len - decl_count; r.any_field_aligns = any_field_aligns; r.any_field_values = any_field_values; r.any_comptime_fields = any_comptime_fields; r.has_underscore_field = has_underscore_field; return r; } // --- containerMember --- // AstGen.zig:containerMember // Returns true for a field (filling *out_field), false for a decl. static bool containerMember(GenZir* gz, Scope* scope, AstNodeIndex member_node, AstFullContainerField* out_field) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; switch (astNodeTag(tree, member_node)) { case AST_NODE_CONTAINER_FIELD_INIT: case AST_NODE_CONTAINER_FIELD_ALIGN: case AST_NODE_CONTAINER_FIELD: astFullContainerField(tree, member_node, out_field); return true; case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: case AST_NODE_FN_DECL: { AstNodeIndex full_buf[1]; AstFullFnProto full; astFullFnProto(tree, full_buf, member_node, &full); const bool has_body = astNodeTag(tree, member_node) == AST_NODE_FN_DECL; const AstNodeIndex body_node = has_body ? astNodeData(tree, member_node).rhs : 0; fnDecl(ag, gz, scope, member_node, has_body, body_node, full); return false; } case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: { AstFullVarDecl full; astFullVarDecl(tree, member_node, &full); globalVarDecl(ag, gz, scope, member_node, full); return false; } case AST_NODE_COMPTIME: comptimeDecl(ag, gz, scope, member_node); return false; case AST_NODE_TEST_DECL: testDecl(ag, gz, scope, member_node); return false; default: UNREACHABLE("containerMember: unreachable"); return false; } } // --- setStruct --- typedef struct { uint32_t src_line; uint32_t src_column; AstNodeIndex src_node; uint8_t name_strat; // Absolute nodes; the backing-int argument (read when it has a body) // and the first field (read when fields_len != 0). AstNodeIndex arg_baseline_src_node; AstNodeIndex fields_baseline_src_node; uint8_t layout; uint32_t decls_len; uint32_t fields_len; bool any_field_aligns; bool any_field_defaults; bool any_comptime_fields; // backing_int_type_body_len: 0 = no backing int; >0 = body length. // The backing int body precedes field bodies in `remaining`. uint32_t backing_int_type_body_len; uint32_t captures_len; const uint32_t* captures; // Zir.Inst.Capture[] const uint32_t* capture_names; // NullTerminatedString[] const uint32_t* remaining; uint32_t remaining_len; } SetStructArgs; // AstGen.zig:GenZir.setStruct static void setStruct(GenZir* gz, uint32_t inst, const SetStructArgs* args) { AstGen* ag = gz->astgen; // Zir.Inst.StructDecl payload: fields_hash_0..3, src_line, src_column, // src_node. const uint32_t payload_index = ag->extra_len; extraAppend(ag, 0); // fields_hash_0 (masked) extraAppend(ag, 0); // fields_hash_1 extraAppend(ag, 0); // fields_hash_2 extraAppend(ag, 0); // fields_hash_3 extraAppend(ag, args->src_line); extraAppend(ag, args->src_column); extraAppend(ag, (uint32_t)args->src_node); // src_node if (args->captures_len != 0) extraAppend(ag, args->captures_len); if (args->decls_len != 0) extraAppend(ag, args->decls_len); if (args->fields_len != 0) extraAppend(ag, args->fields_len); if (args->backing_int_type_body_len != 0) extraAppend(ag, args->arg_baseline_src_node); if (args->fields_len != 0) extraAppend(ag, args->fields_baseline_src_node); if (args->backing_int_type_body_len != 0) extraAppend(ag, args->backing_int_type_body_len); for (uint32_t i = 0; i < args->captures_len; i++) extraAppend(ag, args->captures[i]); for (uint32_t i = 0; i < args->captures_len; i++) extraAppend(ag, args->capture_names[i]); for (uint32_t i = 0; i < args->remaining_len; i++) extraAppend(ag, args->remaining[i]); unsigned small_u = 0; if (args->captures_len != 0) small_u |= ZIR_STRUCT_SMALL_HAS_CAPTURES_LEN; if (args->decls_len != 0) small_u |= ZIR_STRUCT_SMALL_HAS_DECLS_LEN; if (args->fields_len != 0) small_u |= ZIR_STRUCT_SMALL_HAS_FIELDS_LEN; small_u |= (unsigned)args->name_strat << ZIR_STRUCT_SMALL_NAME_STRATEGY_SHIFT; small_u |= (unsigned)args->layout << ZIR_STRUCT_SMALL_LAYOUT_SHIFT; if (args->backing_int_type_body_len != 0) small_u |= ZIR_STRUCT_SMALL_HAS_BACKING_INT_TYPE; if (args->any_field_aligns) small_u |= ZIR_STRUCT_SMALL_ANY_FIELD_ALIGNS; if (args->any_field_defaults) small_u |= ZIR_STRUCT_SMALL_ANY_FIELD_DEFAULTS; if (args->any_comptime_fields) small_u |= ZIR_STRUCT_SMALL_ANY_COMPTIME_FIELDS; ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = (uint16_t)ZIR_EXT_STRUCT_DECL; data.extended.small = (uint16_t)small_u; data.extended.operand = payload_index; ag->inst_tags[inst] = ZIR_INST_EXTENDED; ag->inst_datas[inst] = data; } // --- setEnum --- typedef struct { uint32_t src_line; uint32_t src_column; AstNodeIndex src_node; uint8_t name_strat; AstNodeIndex arg_baseline_src_node; AstNodeIndex fields_baseline_src_node; bool has_tag_type; uint32_t tag_type_body_len; bool nonexhaustive; uint32_t captures_len; const uint32_t* captures; // Zir.Inst.Capture[] const uint32_t* capture_names; // NullTerminatedString[] uint32_t decls_len; uint32_t fields_len; bool any_field_values; const uint32_t* remaining; uint32_t remaining_len; } SetEnumArgs; // AstGen.zig:GenZir.setEnum static void setEnum(GenZir* gz, uint32_t inst, const SetEnumArgs* args) { AstGen* ag = gz->astgen; // Zir.Inst.EnumDecl payload: fields_hash_0..3, src_line, src_column, // src_node. const uint32_t payload_index = ag->extra_len; extraAppend(ag, 0); // fields_hash_0 (masked) extraAppend(ag, 0); // fields_hash_1 extraAppend(ag, 0); // fields_hash_2 extraAppend(ag, 0); // fields_hash_3 extraAppend(ag, args->src_line); extraAppend(ag, args->src_column); extraAppend(ag, (uint32_t)args->src_node); // src_node // Trailing count words (captures_len first, then decls_len, etc.) if (args->captures_len != 0) extraAppend(ag, args->captures_len); if (args->decls_len != 0) extraAppend(ag, args->decls_len); if (args->fields_len != 0) extraAppend(ag, args->fields_len); if (args->has_tag_type) extraAppend(ag, args->arg_baseline_src_node); if (args->fields_len != 0) extraAppend(ag, args->fields_baseline_src_node); if (args->has_tag_type) extraAppend(ag, args->tag_type_body_len); // captures[], capture_names[], then remaining (decls, field data, bodies) for (uint32_t i = 0; i < args->captures_len; i++) extraAppend(ag, args->captures[i]); for (uint32_t i = 0; i < args->captures_len; i++) extraAppend(ag, args->capture_names[i]); for (uint32_t i = 0; i < args->remaining_len; i++) extraAppend(ag, args->remaining[i]); unsigned small_u = 0; if (args->captures_len != 0) small_u |= ZIR_ENUM_SMALL_HAS_CAPTURES_LEN; if (args->decls_len != 0) small_u |= ZIR_ENUM_SMALL_HAS_DECLS_LEN; if (args->fields_len != 0) small_u |= ZIR_ENUM_SMALL_HAS_FIELDS_LEN; small_u |= (unsigned)args->name_strat << ZIR_ENUM_SMALL_NAME_STRATEGY_SHIFT; if (args->has_tag_type) small_u |= ZIR_ENUM_SMALL_HAS_TAG_TYPE; if (args->nonexhaustive) small_u |= ZIR_ENUM_SMALL_NONEXHAUSTIVE; if (args->any_field_values) small_u |= ZIR_ENUM_SMALL_ANY_FIELD_VALUES; ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = (uint16_t)ZIR_EXT_ENUM_DECL; data.extended.small = (uint16_t)small_u; data.extended.operand = payload_index; ag->inst_tags[inst] = ZIR_INST_EXTENDED; ag->inst_datas[inst] = data; } // --- setUnion --- // UnionDecl.Kind values (Zir.Inst.UnionDecl.Kind) #define UNION_KIND_AUTO 0 #define UNION_KIND_TAGGED_EXPLICIT 1 #define UNION_KIND_TAGGED_ENUM 2 #define UNION_KIND_TAGGED_ENUM_EXPLICIT 3 #define UNION_KIND_EXTERN 4 #define UNION_KIND_PACKED 5 #define UNION_KIND_PACKED_EXPLICIT 6 typedef struct { uint32_t src_line; uint32_t src_column; AstNodeIndex src_node; uint8_t name_strat; AstNodeIndex arg_baseline_src_node; AstNodeIndex fields_baseline_src_node; uint8_t kind; // UNION_KIND_* uint32_t captures_len; const uint32_t* captures; // Zir.Inst.Capture[] const uint32_t* capture_names; // NullTerminatedString[] uint32_t decls_len; uint32_t fields_len; bool any_field_aligns; bool any_field_values; bool has_arg_type; // true iff arg_type_body_len should be emitted uint32_t arg_type_body_len; const uint32_t* remaining; uint32_t remaining_len; } SetUnionArgs; // AstGen.zig:GenZir.setUnion static void setUnion(GenZir* gz, uint32_t inst, const SetUnionArgs* args) { AstGen* ag = gz->astgen; // UnionDecl payload: fields_hash_0..3, src_line, src_column, src_node. const uint32_t payload_index = ag->extra_len; extraAppend(ag, 0); // fields_hash_0 (masked) extraAppend(ag, 0); // fields_hash_1 extraAppend(ag, 0); // fields_hash_2 extraAppend(ag, 0); // fields_hash_3 extraAppend(ag, args->src_line); extraAppend(ag, args->src_column); extraAppend(ag, (uint32_t)args->src_node); // src_node // Trailing count words (captures_len first, then decls_len, etc.) if (args->captures_len != 0) extraAppend(ag, args->captures_len); if (args->decls_len != 0) extraAppend(ag, args->decls_len); if (args->fields_len != 0) extraAppend(ag, args->fields_len); if (args->has_arg_type) extraAppend(ag, args->arg_baseline_src_node); if (args->fields_len != 0) extraAppend(ag, args->fields_baseline_src_node); if (args->has_arg_type) extraAppend(ag, args->arg_type_body_len); // captures[], capture_names[], then remaining (decls, field data, bodies) for (uint32_t i = 0; i < args->captures_len; i++) extraAppend(ag, args->captures[i]); for (uint32_t i = 0; i < args->captures_len; i++) extraAppend(ag, args->capture_names[i]); for (uint32_t i = 0; i < args->remaining_len; i++) extraAppend(ag, args->remaining[i]); // UnionDecl.Small (u16): // bit0 has_captures_len, bit1 has_decls_len, bit2 has_fields_len, // bits3-4 name_strategy (2 bits), bits5-7 kind (3 bits), // bit8 any_field_aligns, bit9 any_field_values unsigned small_u = 0; if (args->captures_len != 0) small_u |= ZIR_UNION_SMALL_HAS_CAPTURES_LEN; if (args->decls_len != 0) small_u |= ZIR_UNION_SMALL_HAS_DECLS_LEN; if (args->fields_len != 0) small_u |= ZIR_UNION_SMALL_HAS_FIELDS_LEN; small_u |= (unsigned)args->name_strat << ZIR_UNION_SMALL_NAME_STRATEGY_SHIFT; small_u |= (unsigned)args->kind << ZIR_UNION_SMALL_KIND_SHIFT; if (args->any_field_aligns) small_u |= ZIR_UNION_SMALL_ANY_FIELD_ALIGNS; if (args->any_field_values) small_u |= ZIR_UNION_SMALL_ANY_FIELD_VALUES; ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = (uint16_t)ZIR_EXT_UNION_DECL; data.extended.small = (uint16_t)small_u; data.extended.operand = payload_index; ag->inst_tags[inst] = ZIR_INST_EXTENDED; ag->inst_datas[inst] = data; } // --- unionDeclInner --- // AstGen.zig:unionDeclInner static ZirInstRef unionDeclInner(GenZir* gz, Scope* scope, AstNodeIndex node, const AstNodeIndex* members, uint32_t members_len, uint8_t layout, AstNodeIndex opt_arg_node, AstTokenIndex auto_enum_tok, uint8_t name_strat) { AstGen* ag = gz->astgen; // `"if (auto_enum_tok) |t| { if (layout != .auto) { return // astgen.failTok(t, "{s} union does not support enum tag type", ...`, // and the `.@"extern"` arm of `explicit_int_or_enum_tag` for `extern // union(T)`. const char* layout_name = layout == CONTAINER_LAYOUT_EXTERN ? "extern" : "packed"; if (opt_arg_node != AST_NODE_NONE && layout == CONTAINER_LAYOUT_EXTERN) failNode(ag, opt_arg_node, "%s union does not support enum tag type", layout_name); if (auto_enum_tok != AST_TOKEN_NONE && layout != CONTAINER_LAYOUT_AUTO) failTok(ag, auto_enum_tok, "%s union does not support enum tag type", layout_name); // Determine kind uint8_t kind; if (layout == CONTAINER_LAYOUT_AUTO) { if (auto_enum_tok == AST_TOKEN_NONE) { kind = opt_arg_node != AST_NODE_NONE ? (uint8_t)UNION_KIND_TAGGED_EXPLICIT : (uint8_t)UNION_KIND_AUTO; } else { kind = opt_arg_node != AST_NODE_NONE ? (uint8_t)UNION_KIND_TAGGED_ENUM_EXPLICIT : (uint8_t)UNION_KIND_TAGGED_ENUM; } } else if (layout == CONTAINER_LAYOUT_EXTERN) { kind = (uint8_t)UNION_KIND_EXTERN; } else { // packed kind = opt_arg_node != AST_NODE_NONE ? (uint8_t)UNION_KIND_PACKED_EXPLICIT : (uint8_t)UNION_KIND_PACKED; } advanceSourceCursorToNode(ag, node); const uint32_t decl_inst = gzReserveInstructionIndex(gz); const uint32_t src_line = ag->source_line; const uint32_t src_column = ag->source_column; Namespace namespace; memset(&namespace, 0, sizeof(namespace)); namespace.base.tag = SCOPE_NAMESPACE; namespace.base.parent = scope; namespace.node = node; namespace.inst = decl_inst; namespace.declaring_gz = gz; namespace.maybe_generic = ag->within_fn; GenZir block_scope; memset(&block_scope, 0, sizeof(block_scope)); block_scope.base.tag = SCOPE_GEN_ZIR; block_scope.base.parent = &namespace.base; block_scope.src_baseline = node; block_scope.decl_line = gz->decl_line; block_scope.astgen = ag; block_scope.is_comptime = true; block_scope.instructions_top = ag->gz_len; const ScanContainerResult scan_result = scanContainer( ag, &namespace, members, members_len, CONTAINER_KIND_UNION); const uint32_t fields_len = scan_result.fields_len; const uint32_t scratch_top = ag->scratch_len; // WipDecls.init: reserve decls slice const uint32_t prev_wip_start = ag->wip_decls_start; const uint32_t prev_wip_index = ag->wip_decls_index; ag->wip_decls_start = ag->scratch_len; ag->wip_decls_index = 0; for (uint32_t i = 0; i < scan_result.decls_len; i++) scratchAppend(ag, 0); // field_names[fields_len], field_type_body_lens[fields_len] const uint32_t field_names_start = ag->scratch_len; for (uint32_t i = 0; i < fields_len; i++) scratchAppend(ag, 0); const uint32_t field_type_lens_start = ag->scratch_len; for (uint32_t i = 0; i < fields_len; i++) scratchAppend(ag, 0); // field_align_body_lens[fields_len] (only if any_field_aligns) // Mirrors AstGen.zig: scratch.addOptionalSlice(any_field_aligns, // fields_len). It precedes the value lens in the trailing data. uint32_t field_align_lens_start = 0; if (scan_result.any_field_aligns) { field_align_lens_start = ag->scratch_len; for (uint32_t i = 0; i < fields_len; i++) scratchAppend(ag, 0); } // field_value_body_lens[fields_len] (only if any_field_values) // Mirrors AstGen.zig: scratch.addOptionalSlice(any_field_values, // fields_len) uint32_t field_value_lens_start = 0; if (scan_result.any_field_values) { field_value_lens_start = ag->scratch_len; for (uint32_t i = 0; i < fields_len; i++) scratchAppend(ag, 0); } // arg_type body (if explicit tag type) uint32_t arg_type_body_len = 0; if (opt_arg_node != AST_NODE_NONE) { block_scope.src_baseline = opt_arg_node; const ZirInstRef type_ref = typeExpr(&block_scope, &namespace.base, opt_arg_node); if (!gzEndsWithNoReturn(&block_scope)) gzAddBreak( &block_scope, ZIR_INST_BREAK_INLINE, decl_inst, type_ref); uint32_t b_insts; const uint32_t* b = gzInstructionsSlice(&block_scope, &b_insts); arg_type_body_len = scratchAppendBodyWithFixups(ag, b, b_insts); ag->gz_len = block_scope.instructions_top; block_scope.src_baseline = node; } AstNodeIndex fields_src_baseline = 0; // .none uint32_t next_field_idx = 0; for (uint32_t mi = 0; mi < members_len; mi++) { const AstNodeIndex member_node = members[mi]; AstFullContainerField field; const bool is_field = containerMember( &block_scope, &namespace.base, member_node, &field); if (!is_field) continue; astFullContainerFieldConvertToNonTupleLike(&field, ag->tree); if (field.ast.tuple_like) failTok(ag, field.ast.main_token, "union field missing name"); if ((field.comptime_token != AST_TOKEN_NONE)) failTok(ag, field.comptime_token, "union fields cannot be marked comptime"); const uint32_t field_idx = next_field_idx; next_field_idx += 1; // Field bodies are relative to the first field (src_baseline). if (field_idx == 0) fields_src_baseline = member_node; block_scope.src_baseline = fields_src_baseline; ag->scratch[field_names_start + field_idx] = identAsString(ag, field.ast.main_token); // field type body if ((field.ast.type_expr != AST_NODE_NONE)) { const ZirInstRef type_ref = typeExpr(&block_scope, &namespace.base, field.ast.type_expr); if (!gzEndsWithNoReturn(&block_scope)) gzAddBreak( &block_scope, ZIR_INST_BREAK_INLINE, decl_inst, type_ref); uint32_t tb_insts; const uint32_t* tb = gzInstructionsSlice(&block_scope, &tb_insts); const uint32_t tb_len = scratchAppendBodyWithFixups(ag, tb, tb_insts); ag->scratch[field_type_lens_start + field_idx] = tb_len; ag->gz_len = block_scope.instructions_top; } else { // tagged union field may have no type ag->scratch[field_type_lens_start + field_idx] = 0; } // field align body. // Mirrors AstGen.unionDeclInner: "if (member.ast.align_expr.unwrap()) // |align_node| { ... const align_ref = try expr(&block_scope, // &namespace.base, coerced_align_ri, align_node); ... }" — the same // shape structDeclInner already uses. if ((field.ast.align_expr != AST_NODE_NONE)) { if (layout == CONTAINER_LAYOUT_PACKED) failNode(ag, field.ast.align_expr, "unable to override alignment of packed union fields"); const ZirInstRef align_ref = expr(&block_scope, &namespace.base, coerced_align_ri, field.ast.align_expr); if (!gzEndsWithNoReturn(&block_scope)) gzAddBreak( &block_scope, ZIR_INST_BREAK_INLINE, decl_inst, align_ref); uint32_t ab_insts; const uint32_t* ab = gzInstructionsSlice(&block_scope, &ab_insts); const uint32_t ab_len = scratchAppendBodyWithFixups(ag, ab, ab_insts); ag->scratch[field_align_lens_start + field_idx] = ab_len; ag->gz_len = block_scope.instructions_top; } else if (scan_result.any_field_aligns) { ag->scratch[field_align_lens_start + field_idx] = 0; } // field value body (for explicitly-valued tagged unions) // Mirrors AstGen.zig. if ((field.ast.value_expr != AST_NODE_NONE)) { // ri: coerced_ty = decl_inst.toRef() const ResultInfo val_ri = { { RL_COERCED_TY, decl_inst + ZIR_REF_START_INDEX, 0, 0, NULL, 0, 0 }, 0 }; const ZirInstRef value_ref = expr( &block_scope, &namespace.base, val_ri, field.ast.value_expr); if (!gzEndsWithNoReturn(&block_scope)) gzAddBreak( &block_scope, ZIR_INST_BREAK_INLINE, decl_inst, value_ref); uint32_t vb_insts; const uint32_t* vb = gzInstructionsSlice(&block_scope, &vb_insts); const uint32_t vb_len = scratchAppendBodyWithFixups(ag, vb, vb_insts); ag->scratch[field_value_lens_start + field_idx] = vb_len; ag->gz_len = block_scope.instructions_top; } else if (scan_result.any_field_values) { ag->scratch[field_value_lens_start + field_idx] = 0; } block_scope.src_baseline = node; } assert(next_field_idx == fields_len); assert(ag->wip_decls_index == scan_result.decls_len); ag->wip_decls_start = prev_wip_start; ag->wip_decls_index = prev_wip_index; const uint32_t remaining_len = ag->scratch_len - scratch_top; SetUnionArgs args; memset(&args, 0, sizeof(args)); args.src_line = src_line; args.src_column = src_column; args.src_node = node; args.name_strat = name_strat; args.arg_baseline_src_node = opt_arg_node; args.fields_baseline_src_node = fields_src_baseline; args.kind = kind; args.captures_len = namespace.captures_len; args.captures = namespace.capture_keys; args.capture_names = namespace.capture_names; args.decls_len = scan_result.decls_len; args.fields_len = fields_len; args.any_field_aligns = scan_result.any_field_aligns; args.any_field_values = scan_result.any_field_values; args.has_arg_type = (opt_arg_node != AST_NODE_NONE && layout == CONTAINER_LAYOUT_AUTO) || (layout != CONTAINER_LAYOUT_AUTO && opt_arg_node != AST_NODE_NONE); args.arg_type_body_len = arg_type_body_len; args.remaining = &ag->scratch[scratch_top]; args.remaining_len = remaining_len; setUnion(gz, decl_inst, &args); ag->scratch_len = scratch_top; gzUnstack(&block_scope); free(namespace.decls); free(namespace.capture_keys); free(namespace.capture_names); return decl_inst + ZIR_REF_START_INDEX; } // --- tupleDecl --- // AstGen.zig:tupleDecl // Emits an `extended .tuple_decl` instruction. // Called when structDeclInner detects that at least one member is tuple-like. // Layout: extra[pi] = src_node (relative), then fields_len pairs: // (type_ref: u32, init_ref: u32) -- init_ref is 0 (.none) if no default. static ZirInstRef tupleDecl(GenZir* gz, Scope* scope, AstNodeIndex node, const AstNodeIndex* members, uint32_t members_len, uint8_t layout) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; if (layout != CONTAINER_LAYOUT_AUTO) failNode(ag, node, "%s tuples are not supported", layout == CONTAINER_LAYOUT_EXTERN ? "extern" : "packed"); // Collect type_ref + init_ref pairs into scratch. const uint32_t scratch_top = ag->scratch_len; uint32_t fields_len = 0; for (uint32_t mi = 0; mi < members_len; mi++) { const AstNodeIndex member_node = members[mi]; const AstNodeTag tag = astNodeTag(tree, member_node); if (tag != AST_NODE_CONTAINER_FIELD_INIT && tag != AST_NODE_CONTAINER_FIELD_ALIGN && tag != AST_NODE_CONTAINER_FIELD) failNode(ag, member_node, "tuple declarations cannot contain declarations"); AstFullContainerField field; astFullContainerField(tree, member_node, &field); if (!field.ast.tuple_like) failTok(ag, field.ast.main_token, "tuple field has a name"); if ((field.ast.align_expr != AST_NODE_NONE)) failTok(ag, field.ast.main_token, "tuple field has alignment"); if (!(field.ast.type_expr != AST_NODE_NONE)) ASTGEN_UNIMPLEMENTED("tupleDecl: tuple field missing type"); const ZirInstRef type_ref = typeExpr(gz, scope, field.ast.type_expr); scratchAppend(ag, (uint32_t)type_ref); if ((field.ast.value_expr != AST_NODE_NONE)) { if (!(field.comptime_token != AST_TOKEN_NONE)) failTok(ag, field.ast.main_token, "non-comptime tuple field has default initialization " "value"); const ResultInfo ri = { { RL_COERCED_TY, type_ref, 0, 0, NULL, 0, 0 }, CTX_NONE }; const ZirInstRef init_ref = comptimeExpr( gz, scope, ri, field.ast.value_expr, REASON_COMPTIME_KEYWORD); scratchAppend(ag, (uint32_t)init_ref); } else { if ((field.comptime_token != AST_TOKEN_NONE)) failTok(ag, field.comptime_token, "comptime field without default initialization value"); scratchAppend(ag, (uint32_t)ZIR_REF_NONE); // .none } fields_len += 1; } // Emit extra payload: src_node + fields_len*2 entries. const uint32_t payload_index = ag->extra_len; extraAppend(ag, (uint32_t)gzNodeIndexToRelative(gz, node)); // src_node for (uint32_t i = 0; i < fields_len * 2; i++) extraAppend(ag, ag->scratch[scratch_top + i]); ag->scratch_len = scratch_top; ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = (uint16_t)ZIR_EXT_TUPLE_DECL; data.extended.small = (uint16_t)fields_len; data.extended.operand = payload_index; return gzAdd(gz, ZIR_INST_EXTENDED, data); } // --- structDeclInner --- // AstGen.zig:structDeclInner static ZirInstRef structDeclInner(GenZir* gz, Scope* scope, AstNodeIndex node, AstFullContainerDecl container_decl, uint8_t layout, AstNodeIndex backing_int_node, uint8_t name_strat) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; const AstNodeIndex* members = container_decl.ast.members.ptr; const uint32_t members_len = container_decl.ast.members.len; // is_tuple detection: if any member is a tuple-like field, emit a // tuple_decl instruction instead of a struct_decl. for (uint32_t mi = 0; mi < members_len; mi++) { const AstNodeIndex mn = members[mi]; const AstNodeTag mt = astNodeTag(tree, mn); if (mt == AST_NODE_CONTAINER_FIELD_INIT || mt == AST_NODE_CONTAINER_FIELD_ALIGN || mt == AST_NODE_CONTAINER_FIELD) { AstFullContainerField f; astFullContainerField(tree, mn, &f); if (f.ast.tuple_like) { if (node == 0) failNode(ag, mn, "file cannot be a tuple"); return tupleDecl( gz, scope, node, members, members_len, layout); } } } advanceSourceCursorToNode(ag, node); const uint32_t decl_inst = gzReserveInstructionIndex(gz); const uint32_t src_line = ag->source_line; const uint32_t src_column = ag->source_column; if (members_len == 0 && backing_int_node == AST_NODE_NONE) { SetStructArgs args; memset(&args, 0, sizeof(args)); args.src_line = src_line; args.src_column = src_column; args.src_node = node; args.name_strat = name_strat; args.layout = layout; setStruct(gz, decl_inst, &args); return decl_inst + ZIR_REF_START_INDEX; } Namespace namespace; memset(&namespace, 0, sizeof(namespace)); namespace.base.tag = SCOPE_NAMESPACE; namespace.base.parent = scope; namespace.node = node; namespace.inst = decl_inst; namespace.declaring_gz = gz; namespace.maybe_generic = ag->within_fn; // block_scope: introduces the struct's decls into scope. Shares the gz // instruction stack. GenZir block_scope; memset(&block_scope, 0, sizeof(block_scope)); block_scope.base.tag = SCOPE_GEN_ZIR; block_scope.base.parent = &namespace.base; block_scope.src_baseline = node; block_scope.decl_line = gz->decl_line; block_scope.astgen = ag; block_scope.is_comptime = true; block_scope.instructions_top = ag->gz_len; const ScanContainerResult scan_result = scanContainer( ag, &namespace, members, members_len, CONTAINER_KIND_STRUCT); // Scratch: replicate the ZIR trailing data. With no fields, the WipDecls // slice holds the decl instruction indices, appended sequentially by // globalVarDecl/fnDecl (via scratchAppend). With fields present, we // pre-allocate the field slices in ZIR-trailing order; the field data is // written into those slots and the field bodies appended after. const uint32_t scratch_top = ag->scratch_len; // WipDecls.init: reserve the decls slice (precedes field slices in the ZIR // trailing data). Save/restore for nested container decls. const uint32_t prev_wip_start = ag->wip_decls_start; const uint32_t prev_wip_index = ag->wip_decls_index; ag->wip_decls_start = ag->scratch_len; ag->wip_decls_index = 0; for (uint32_t i = 0; i < scan_result.decls_len; i++) scratchAppend(ag, 0); const uint32_t fields_len = scan_result.fields_len; uint32_t field_names_start = 0; uint32_t field_type_lens_start = 0; uint32_t field_align_lens_start = 0; uint32_t field_default_lens_start = 0; uint32_t field_comptime_bits_start = 0; if (fields_len != 0) { field_names_start = ag->scratch_len; for (uint32_t i = 0; i < fields_len; i++) scratchAppend(ag, 0); field_type_lens_start = ag->scratch_len; for (uint32_t i = 0; i < fields_len; i++) scratchAppend(ag, 0); if (scan_result.any_field_aligns) { field_align_lens_start = ag->scratch_len; for (uint32_t i = 0; i < fields_len; i++) scratchAppend(ag, 0); } if (scan_result.any_field_values) { field_default_lens_start = ag->scratch_len; for (uint32_t i = 0; i < fields_len; i++) scratchAppend(ag, 0); } if (scan_result.any_comptime_fields) { field_comptime_bits_start = ag->scratch_len; const uint32_t bits_len = (fields_len + 31) / 32; for (uint32_t i = 0; i < bits_len; i++) scratchAppend(ag, 0); } } // Generate backing int type body (if any) before field bodies. uint32_t backing_int_type_body_len = 0; if (backing_int_node != AST_NODE_NONE) { block_scope.src_baseline = backing_int_node; const ZirInstRef bi_ref = fullBodyExpr(&block_scope, &namespace.base, coerced_type_ri, backing_int_node, BLOCK_KIND_NORMAL); if (!gzEndsWithNoReturn(&block_scope)) gzAddBreak(&block_scope, ZIR_INST_BREAK_INLINE, decl_inst, bi_ref); uint32_t bi_insts; const uint32_t* bi = gzInstructionsSlice(&block_scope, &bi_insts); backing_int_type_body_len = scratchAppendBodyWithFixups(ag, bi, bi_insts); ag->gz_len = block_scope.instructions_top; // reset block_scope instructions block_scope.src_baseline = node; } AstNodeIndex fields_src_baseline = 0; // .none uint32_t next_field_idx = 0; for (uint32_t mi = 0; mi < members_len; mi++) { const AstNodeIndex member_node = members[mi]; AstFullContainerField field; const bool is_field = containerMember( &block_scope, &namespace.base, member_node, &field); if (!is_field) continue; // A field member implies fields_len != 0, so the field slices were // pre-allocated above (ag->scratch is non-NULL here). assert(fields_len != 0); const uint32_t field_idx = next_field_idx; next_field_idx += 1; // "block_scope.src_baseline = fields_src_baseline.unwrap().?; defer // block_scope.src_baseline = node;": field bodies are relative to // the first field. if (field_idx == 0) fields_src_baseline = member_node; block_scope.src_baseline = fields_src_baseline; // Container kind is struct: no convertToNonTupleLike in scanContainer, // but structDeclInner always applies it. astFullContainerFieldConvertToNonTupleLike(&field, ag->tree); assert(!field.ast.tuple_like); ag->scratch[field_names_start + field_idx] = identAsString(ag, field.ast.main_token); // Field type body. if (!(field.ast.type_expr != AST_NODE_NONE)) failTok(ag, field.ast.main_token, "struct field missing type"); const ZirInstRef type_ref = typeExpr(&block_scope, &namespace.base, field.ast.type_expr); if (!gzEndsWithNoReturn(&block_scope)) gzAddBreak( &block_scope, ZIR_INST_BREAK_INLINE, decl_inst, type_ref); uint32_t body_len_insts; const uint32_t* body = gzInstructionsSlice(&block_scope, &body_len_insts); const uint32_t tb = scratchAppendBodyWithFixups(ag, body, body_len_insts); ag->scratch[field_type_lens_start + field_idx] = tb; ag->gz_len = block_scope.instructions_top; // reset block_scope insts if ((field.ast.align_expr != AST_NODE_NONE)) { // Mirrors AstGen structDeclInner: expr with coerced_align_ri // (RL_COERCED_TY to u29_type), then break_inline, then body. // AstGen.zig: "if (layout == .@\"packed\") return // astgen.failNode(align_node, \"unable to override alignment of // packed struct fields\", .{});" — the same rejection // unionDeclInner's arm carries. if (layout == CONTAINER_LAYOUT_PACKED) failNode(ag, field.ast.align_expr, "unable to override alignment of packed struct fields"); const ZirInstRef align_ref = expr(&block_scope, &namespace.base, coerced_align_ri, field.ast.align_expr); if (!gzEndsWithNoReturn(&block_scope)) gzAddBreak( &block_scope, ZIR_INST_BREAK_INLINE, decl_inst, align_ref); uint32_t ab_insts; const uint32_t* ab = gzInstructionsSlice(&block_scope, &ab_insts); const uint32_t ab_len = scratchAppendBodyWithFixups(ag, ab, ab_insts); ag->scratch[field_align_lens_start + field_idx] = ab_len; ag->gz_len = block_scope.instructions_top; } else if (scan_result.any_field_aligns) { ag->scratch[field_align_lens_start + field_idx] = 0; } if ((field.ast.value_expr != AST_NODE_NONE)) { // Field default value body (coerced to the struct decl instance). ResultInfo default_ri; memset(&default_ri, 0, sizeof(default_ri)); default_ri.rl.tag = RL_COERCED_TY; default_ri.rl.ty = decl_inst + ZIR_REF_START_INDEX; const ZirInstRef default_ref = expr(&block_scope, &namespace.base, default_ri, field.ast.value_expr); if (!gzEndsWithNoReturn(&block_scope)) gzAddBreak(&block_scope, ZIR_INST_BREAK_INLINE, decl_inst, default_ref); uint32_t db_insts; const uint32_t* db = gzInstructionsSlice(&block_scope, &db_insts); const uint32_t db_len = scratchAppendBodyWithFixups(ag, db, db_insts); ag->scratch[field_default_lens_start + field_idx] = db_len; ag->gz_len = block_scope.instructions_top; } else if (scan_result.any_field_values) { ag->scratch[field_default_lens_start + field_idx] = 0; } if ((field.comptime_token != AST_TOKEN_NONE)) { const uint32_t mask = 1u << (field_idx % 32); ag->scratch[field_comptime_bits_start + field_idx / 32] |= mask; } block_scope.src_baseline = node; } assert(next_field_idx == fields_len); // wip_decls.finish(): all reserved decl slots were filled. assert(ag->wip_decls_index == scan_result.decls_len); ag->wip_decls_start = prev_wip_start; ag->wip_decls_index = prev_wip_index; // remaining = all scratch data from scratch_top. const uint32_t remaining_len = ag->scratch_len - scratch_top; SetStructArgs args; memset(&args, 0, sizeof(args)); args.src_line = src_line; args.src_column = src_column; args.src_node = node; args.name_strat = name_strat; args.arg_baseline_src_node = backing_int_node; args.fields_baseline_src_node = fields_src_baseline; args.layout = layout; args.decls_len = scan_result.decls_len; args.fields_len = scan_result.fields_len; args.any_field_aligns = scan_result.any_field_aligns; args.any_field_defaults = scan_result.any_field_values; args.any_comptime_fields = scan_result.any_comptime_fields; args.backing_int_type_body_len = backing_int_type_body_len; args.captures_len = namespace.captures_len; args.captures = namespace.capture_keys; args.capture_names = namespace.capture_names; args.remaining = &ag->scratch[scratch_top]; args.remaining_len = remaining_len; setStruct(gz, decl_inst, &args); // scratch.reset() ag->scratch_len = scratch_top; gzUnstack(&block_scope); free(namespace.decls); free(namespace.capture_keys); free(namespace.capture_names); return decl_inst + ZIR_REF_START_INDEX; } // --- enumDeclInner --- static ZirInstRef enumDeclInner(GenZir* gz, Scope* scope, AstNodeIndex node, const AstNodeIndex* members, uint32_t members_len, AstNodeIndex arg_node, uint8_t name_strat) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; advanceSourceCursorToNode(ag, node); const uint32_t decl_inst = gzReserveInstructionIndex(gz); const uint32_t src_line = ag->source_line; const uint32_t src_column = ag->source_column; Namespace namespace; memset(&namespace, 0, sizeof(namespace)); namespace.base.tag = SCOPE_NAMESPACE; namespace.base.parent = scope; namespace.node = node; namespace.inst = decl_inst; namespace.declaring_gz = gz; namespace.maybe_generic = ag->within_fn; GenZir block_scope; memset(&block_scope, 0, sizeof(block_scope)); block_scope.base.tag = SCOPE_GEN_ZIR; block_scope.base.parent = &namespace.base; block_scope.src_baseline = node; block_scope.decl_line = gz->decl_line; block_scope.astgen = ag; block_scope.is_comptime = true; block_scope.instructions_top = ag->gz_len; const ScanContainerResult scan_result = scanContainer( ag, &namespace, members, members_len, CONTAINER_KIND_ENUM); // The name `_` is not a field; it marks a non-exhaustive enum. const uint32_t fields_len = scan_result.fields_len - (scan_result.has_underscore_field ? 1u : 0u); const uint32_t scratch_top = ag->scratch_len; // WipDecls.init: reserve decls slice (precedes field slices in remaining). const uint32_t prev_wip_start = ag->wip_decls_start; const uint32_t prev_wip_index = ag->wip_decls_index; ag->wip_decls_start = ag->scratch_len; ag->wip_decls_index = 0; for (uint32_t i = 0; i < scan_result.decls_len; i++) scratchAppend(ag, 0); // Header: field_names[fields_len], field_value_body_lens[fields_len]?. const uint32_t field_names_start = ag->scratch_len; for (uint32_t i = 0; i < fields_len; i++) scratchAppend(ag, 0); uint32_t field_value_lens_start = 0; if (scan_result.any_field_values) { field_value_lens_start = ag->scratch_len; for (uint32_t i = 0; i < fields_len; i++) scratchAppend(ag, 0); } // Before any field bodies comes the tag type, if specified. bool has_tag_type = false; uint32_t tag_type_body_len = 0; if (arg_node != AST_NODE_NONE) { has_tag_type = true; block_scope.src_baseline = arg_node; const ZirInstRef type_ref = typeExpr(&block_scope, &namespace.base, arg_node); if (!gzEndsWithNoReturn(&block_scope)) gzAddBreak( &block_scope, ZIR_INST_BREAK_INLINE, decl_inst, type_ref); uint32_t b_insts; const uint32_t* b = gzInstructionsSlice(&block_scope, &b_insts); tag_type_body_len = scratchAppendBodyWithFixups(ag, b, b_insts); ag->gz_len = block_scope.instructions_top; block_scope.src_baseline = node; } uint32_t next_field_idx = 0; AstNodeIndex fields_src_baseline = 0; // .none for (uint32_t mi = 0; mi < members_len; mi++) { const AstNodeIndex member_node = members[mi]; AstFullContainerField field; const bool is_field = containerMember( &block_scope, &namespace.base, member_node, &field); if (!is_field) continue; astFullContainerFieldConvertToNonTupleLike(&field, tree); if (field.ast.tuple_like) failTok(ag, field.ast.main_token, "enum field missing name"); if ((field.comptime_token != AST_TOKEN_NONE)) failTok(ag, field.comptime_token, "enum fields cannot be marked comptime"); if ((field.ast.type_expr != AST_NODE_NONE)) failNode(ag, field.ast.type_expr, "enum fields do not have types"); if ((field.ast.align_expr != AST_NODE_NONE)) failNode( ag, field.ast.align_expr, "enum fields cannot be aligned"); if (tokenIsUnderscore(tree, field.ast.main_token)) continue; // non-exhaustive marker; already counted in scan_result const uint32_t field_idx = next_field_idx; next_field_idx += 1; // Field bodies are relative to the first field (src_baseline). if (field_idx == 0) fields_src_baseline = member_node; block_scope.src_baseline = fields_src_baseline; ag->scratch[field_names_start + field_idx] = identAsString(ag, field.ast.main_token); if ((field.ast.value_expr != AST_NODE_NONE)) { ResultInfo val_ri; memset(&val_ri, 0, sizeof(val_ri)); val_ri.rl.tag = RL_COERCED_TY; val_ri.rl.ty = decl_inst + ZIR_REF_START_INDEX; const ZirInstRef value_ref = expr( &block_scope, &namespace.base, val_ri, field.ast.value_expr); if (!gzEndsWithNoReturn(&block_scope)) gzAddBreak( &block_scope, ZIR_INST_BREAK_INLINE, decl_inst, value_ref); uint32_t vb_insts; const uint32_t* vb = gzInstructionsSlice(&block_scope, &vb_insts); const uint32_t vb_len = scratchAppendBodyWithFixups(ag, vb, vb_insts); ag->scratch[field_value_lens_start + field_idx] = vb_len; ag->gz_len = block_scope.instructions_top; } else if (scan_result.any_field_values) { ag->scratch[field_value_lens_start + field_idx] = 0; } block_scope.src_baseline = node; } assert(next_field_idx == fields_len); // wip_decls.finish(): all reserved decl slots must be filled. assert(ag->wip_decls_index == scan_result.decls_len); ag->wip_decls_start = prev_wip_start; ag->wip_decls_index = prev_wip_index; const uint32_t remaining_len = ag->scratch_len - scratch_top; SetEnumArgs args; memset(&args, 0, sizeof(args)); args.src_line = src_line; args.src_column = src_column; args.src_node = node; args.name_strat = name_strat; args.arg_baseline_src_node = arg_node; args.fields_baseline_src_node = fields_src_baseline; args.has_tag_type = has_tag_type; args.tag_type_body_len = tag_type_body_len; args.nonexhaustive = scan_result.has_underscore_field; args.captures_len = namespace.captures_len; args.captures = namespace.capture_keys; args.capture_names = namespace.capture_names; args.decls_len = scan_result.decls_len; args.fields_len = fields_len; args.any_field_values = scan_result.any_field_values; args.remaining = &ag->scratch[scratch_top]; args.remaining_len = remaining_len; setEnum(gz, decl_inst, &args); ag->scratch_len = scratch_top; gzUnstack(&block_scope); free(namespace.decls); free(namespace.capture_keys); free(namespace.capture_names); return decl_inst + ZIR_REF_START_INDEX; } // --- opaqueDeclInner --- // opaqueDeclInner: lower `opaque { ... }` to a ZIR extended opaque_decl. // Ported from AstGen.zig containerDecl .keyword_opaque branch. // Handles both empty and non-empty opaques (non-empty opaques contain decls // only — no fields allowed). Mirrors AstGen.zig. static ZirInstRef opaqueDeclInner(GenZir* gz, Scope* scope, AstNodeIndex node, const AstNodeIndex* members, uint32_t members_len, uint8_t name_strat) { AstGen* ag = gz->astgen; advanceSourceCursorToNode(ag, node); const uint32_t decl_inst = gzReserveInstructionIndex(gz); const uint32_t src_line = ag->source_line; const uint32_t src_column = ag->source_column; if (members_len == 0) { // Fast path: empty opaque. // OpaqueDecl payload: src_line, src_node. const uint32_t payload_index = ag->extra_len; extraAppend(ag, src_line); extraAppend(ag, src_column); extraAppend(ag, (uint32_t)node); // src_node // captures_len = 0, decls_len = 0: no trailing words. const uint16_t small = (uint16_t)((unsigned)name_strat << ZIR_OPAQUE_SMALL_NAME_STRATEGY_SHIFT); ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = (uint16_t)ZIR_EXT_OPAQUE_DECL; data.extended.small = small; data.extended.operand = payload_index; ag->inst_tags[decl_inst] = ZIR_INST_EXTENDED; ag->inst_datas[decl_inst] = data; return decl_inst + ZIR_REF_START_INDEX; } // Non-empty opaque: process decl members (fields are compile errors). // Mirrors AstGen.zig .keyword_opaque branch. Namespace namespace; memset(&namespace, 0, sizeof(namespace)); namespace.base.tag = SCOPE_NAMESPACE; namespace.base.parent = scope; namespace.node = node; namespace.inst = decl_inst; namespace.declaring_gz = gz; namespace.maybe_generic = ag->within_fn; GenZir block_scope; memset(&block_scope, 0, sizeof(block_scope)); block_scope.base.tag = SCOPE_GEN_ZIR; block_scope.base.parent = &namespace.base; block_scope.src_baseline = node; block_scope.decl_line = gz->decl_line; block_scope.astgen = ag; block_scope.is_comptime = true; block_scope.instructions_top = ag->gz_len; const ScanContainerResult scan_result = scanContainer( ag, &namespace, members, members_len, CONTAINER_KIND_STRUCT); // WipDecls: reserve decl slots in scratch. const uint32_t scratch_top = ag->scratch_len; const uint32_t prev_wip_start = ag->wip_decls_start; const uint32_t prev_wip_index = ag->wip_decls_index; ag->wip_decls_start = ag->scratch_len; ag->wip_decls_index = 0; for (uint32_t i = 0; i < scan_result.decls_len; i++) scratchAppend(ag, 0); // Process members: only decls allowed in opaques. for (uint32_t mi = 0; mi < members_len; mi++) { const AstNodeIndex member_node = members[mi]; AstFullContainerField field; const bool is_field = containerMember( &block_scope, &namespace.base, member_node, &field); if (is_field) failNode(ag, member_node, "opaque types cannot have fields"); } // WipDecls.finish() assert(ag->wip_decls_index == scan_result.decls_len); ag->wip_decls_start = prev_wip_start; ag->wip_decls_index = prev_wip_index; // Build OpaqueDecl extra payload (mirrors setOpaque in AstGen.zig). // Layout: src_line, src_node, [captures_len], [decls_len], // captures[], capture_names[], decls[]. const uint32_t captures_len = namespace.captures_len; const uint32_t decls_len = scan_result.decls_len; const uint32_t payload_index = ag->extra_len; extraAppend(ag, src_line); extraAppend(ag, src_column); extraAppend(ag, (uint32_t)node); // src_node if (captures_len != 0) extraAppend(ag, captures_len); if (decls_len != 0) extraAppend(ag, decls_len); for (uint32_t i = 0; i < captures_len; i++) extraAppend(ag, namespace.capture_keys[i]); for (uint32_t i = 0; i < captures_len; i++) extraAppend(ag, namespace.capture_names[i]); // Decl instruction indices from scratch[scratch_top..]. for (uint32_t i = 0; i < decls_len; i++) extraAppend(ag, ag->scratch[scratch_top + i]); unsigned small_u = 0; if (captures_len != 0) small_u |= ZIR_OPAQUE_SMALL_HAS_CAPTURES_LEN; if (decls_len != 0) small_u |= ZIR_OPAQUE_SMALL_HAS_DECLS_LEN; small_u |= (unsigned)name_strat << ZIR_OPAQUE_SMALL_NAME_STRATEGY_SHIFT; ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = (uint16_t)ZIR_EXT_OPAQUE_DECL; data.extended.small = (uint16_t)small_u; data.extended.operand = payload_index; ag->inst_tags[decl_inst] = ZIR_INST_EXTENDED; ag->inst_datas[decl_inst] = data; // scratch.reset() ag->scratch_len = scratch_top; gzUnstack(&block_scope); free(namespace.decls); free(namespace.capture_keys); free(namespace.capture_names); return decl_inst + ZIR_REF_START_INDEX; } // --- containerDecl --- // AstGen.zig:containerDecl static ZirInstRef containerDecl(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, AstFullContainerDecl container_decl, uint8_t name_strat) { AstGen* ag = gz->astgen; const Ast* tree = ag->tree; GenZir* const prev_fn_block = ag->fn_block; ag->fn_block = NULL; const AstNodeIndex* members = container_decl.ast.members.ptr; const uint32_t members_len = container_decl.ast.members.len; const AstNodeIndex arg = container_decl.ast.arg; const AstTokenIndex main_token = container_decl.ast.main_token; uint8_t layout = CONTAINER_LAYOUT_AUTO; if (container_decl.layout_token != AST_TOKEN_NONE) { switch (astTokenTag(tree, container_decl.layout_token)) { case TOKEN_KEYWORD_PACKED: layout = CONTAINER_LAYOUT_PACKED; break; case TOKEN_KEYWORD_EXTERN: layout = CONTAINER_LAYOUT_EXTERN; break; default: UNREACHABLE("containerDecl: not a layout token"); } } ZirInstRef result; switch (astTokenTag(tree, main_token)) { case TOKEN_KEYWORD_STRUCT: result = structDeclInner( gz, scope, node, container_decl, layout, arg, name_strat); break; case TOKEN_KEYWORD_ENUM: result = enumDeclInner( gz, scope, node, members, members_len, arg, name_strat); break; case TOKEN_KEYWORD_UNION: result = unionDeclInner(gz, scope, node, members, members_len, layout, arg, container_decl.ast.enum_token, name_strat); break; case TOKEN_KEYWORD_OPAQUE: result = opaqueDeclInner( gz, scope, node, members, members_len, name_strat); break; default: ASTGEN_UNIMPLEMENTED( "containerDecl: only struct/enum/union/opaque supported"); } ag->fn_block = prev_fn_block; return rvalue(gz, ri, result, node); } // AstGen.zig:nameStratExpr static bool nameStratExpr(GenZir* gz, Scope* scope, ResultInfo ri, AstNodeIndex node, uint8_t name_strat, ZirInstRef* out) { const Ast* tree = gz->astgen->tree; const AstNodeTag nt = astNodeTag(tree, node); switch (nt) { case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: { AstNodeIndex buf[2]; AstFullContainerDecl container_decl; astFullContainerDecl(tree, buf, node, &container_decl); *out = containerDecl(gz, scope, ri, node, container_decl, name_strat); return true; } case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: case AST_NODE_BUILTIN_CALL: case AST_NODE_BUILTIN_CALL_COMMA: { const AstTokenIndex builtin_token = astNodeMainToken(tree, node); const BuiltinFn* info = builtinFnListGetToken(tree, builtin_token); if (info == NULL) return false; switch (info->tag) { case BFN_Enum: case BFN_Struct: case BFN_Union: { AstNodeIndex buf[2]; AstNodeSlice params; astBuiltinCallParams(tree, buf, node, ¶ms); *out = builtinCall(gz, scope, ri, node, params, false, name_strat); return true; } default: return false; } } default: return false; } } // astGen, ported from `std.zig.AstGen.generate`. Zir astGen(const Ast* tree) { AstGen ag; memset(&ag, 0, sizeof(ag)); ag.tree = tree; // String table index 0 is reserved for the empty string. u8ListAppend(&ag.string_bytes, 0); // The first ZIR_EXTRA_RESERVED_COUNT extra slots are reserved and filled // in at the end. extraEnsureUnused(&ag, ZIR_EXTRA_RESERVED_COUNT); ag.extra_len = ZIR_EXTRA_RESERVED_COUNT; Scope top_scope = { SCOPE_TOP, NULL }; GenZir gen_scope; memset(&gen_scope, 0, sizeof(gen_scope)); gen_scope.base.tag = SCOPE_GEN_ZIR; gen_scope.is_comptime = true; gen_scope.base.parent = &top_scope; gen_scope.src_baseline = 0; // .root gen_scope.decl_line = 0; gen_scope.astgen = &ag; gen_scope.instructions_top = 0; ag.nodes_need_rl = astRlAnnotate(tree); bool fatal; if (!tree->has_error) { const ZirInstRef struct_decl_ref = structDeclInner(&gen_scope, &gen_scope.base, 0, astContainerDeclRoot(tree), CONTAINER_LAYOUT_AUTO, AST_NODE_NONE, ZIR_NAME_STRATEGY_PARENT); if (struct_decl_ref - ZIR_REF_START_INDEX != ZIR_MAIN_STRUCT_INST) ASTGEN_UNIMPLEMENTED( "struct_decl: root struct_decl not at main_struct_inst"); fatal = false; } else { // zig1 reports no parse errors (lowerAstErrors). ag.has_compile_errors = true; fatal = true; } ag.extra[ZIR_EXTRA_COMPILE_ERRORS] = 0; if (ag.imports_len == 0) { ag.extra[ZIR_EXTRA_IMPORTS] = 0; } else { // Zir.Inst.Imports: {imports_len}, then Item{name, token} for each. const uint32_t imports_index = ag.extra_len; extraAppend(&ag, ag.imports_len); for (uint32_t i = 0; i < ag.imports_len; i++) { extraAppend(&ag, ag.import_names[i]); extraAppend(&ag, ag.import_tokens[i]); } ag.extra[ZIR_EXTRA_IMPORTS] = imports_index; } for (uint32_t i = 0; i < ag.scope_allocs_len; i++) free(ag.scope_allocs[i]); free(ag.scope_allocs); free(ag.gz_stack); free(ag.scratch); free(ag.string_table.slots); free(ag.ref_slots); free(ag.import_names); free(ag.import_tokens); free(ag.nodes_need_rl); Zir zir; memset(&zir, 0, sizeof(zir)); zir.has_compile_errors = ag.has_compile_errors; if (fatal) { free(ag.inst_tags); free(ag.inst_datas); zir.inst_tags = NULL; zir.inst_datas = NULL; zir.inst_len = 0; zir.inst_cap = 0; } else { zir.inst_tags = ag.inst_tags; zir.inst_datas = ag.inst_datas; zir.inst_len = ag.inst_len; zir.inst_cap = ag.inst_cap; } zir.extra = ag.extra; zir.extra_len = ag.extra_len; zir.extra_cap = ag.extra_cap; zir.string_bytes = ag.string_bytes.items; zir.string_bytes_len = ag.string_bytes.len; zir.string_bytes_cap = ag.string_bytes.cap; return zir; } Zir astGenFile(const Ast* tree, const char* dir, const char* path) { const Zig1ErrorSource saved = zig1_error_source; zig1_error_source = (Zig1ErrorSource) { dir, path, 0, 0 }; if (tree->has_error) { // Ast.errorOffset: the end of the token when token_is_prev. const char* tok_text; uint32_t tok_len = 0; if (tree->err_tok_is_prev) astTokenSlice(tree, tree->err_tok, &tok_text, &tok_len); zig1ErrorSourceAt( tree->source, astTokenStart(tree, tree->err_tok) + tok_len); zig1CompileError(tree->err_msg ? tree->err_msg : "invalid syntax"); } Zir zir = astGen(tree); zig1_error_source = saved; return zir; } // This file's macros end with it (zig1.c is one translation unit). #undef GZ_UNSTACKED #undef ASTGEN_UNIMPLEMENTED #undef failTok #undef failNode #undef SWBITS_HAS_MULTI #undef SWBITS_ANY_RANGES #undef SWBITS_HAS_ELSE #undef SWBITS_HAS_UNDER #undef SWBITS_HAS_CONTINUE #undef SWBITS_RUNTIME_CAPTURE #undef SWBITS_PAYLOAD_PH #undef SWBITS_TAG_PH #undef SWBITS_SCALAR_LEN_SHIFT #undef PACK_FIELD #undef PACK_BOOL #undef NON_ERR_INFO #undef PRONG_INFO #undef ELSE_INFO #undef ITEM_INFO #undef ITEM_INFO_ENUM_LIT #undef ITEM_INFO_ERR_VAL #undef ITEM_INFO_BODY_LEN #undef ITEM_INFO_UNDER #undef UNION_KIND_AUTO #undef UNION_KIND_TAGGED_EXPLICIT #undef UNION_KIND_TAGGED_ENUM #undef UNION_KIND_TAGGED_ENUM_EXPLICIT #undef UNION_KIND_EXTERN #undef UNION_KIND_PACKED #undef UNION_KIND_PACKED_EXPLICIT #undef NO_COMPTIME_REASON /* ---- end astgen.c ---- */ /* ---- begin ast_rl_annotate.c ---- */ // ast_rl_annotate.c — lib/std/zig/AstRlAnnotate.zig: the pass before AstGen // that finds the nodes whose expression consumes its result location. The // methods are astrl-prefixed (AstRlAnnotate.expr is astrlExpr), as AstGen // shares their names; the set is a bool per node. #ifndef ZIG1_H #include "zig1.h" #endif #include #include #include typedef struct { const Ast* tree; // RlNeededSet: true for each node in the set. bool* nodes_need_rl; } AstRlAnnotate; // AstRlAnnotate.ResultInfo. typedef struct { bool have_type; bool have_ptr; } AstRlResultInfo; static const AstRlResultInfo ASTRL_RI_NONE = { false, false }; static const AstRlResultInfo ASTRL_RI_TYPED_PTR = { true, true }; static const AstRlResultInfo ASTRL_RI_INFERRED_PTR = { false, true }; static const AstRlResultInfo ASTRL_RI_TYPE_ONLY = { true, false }; // AstRlAnnotate.Block: a labeled block or a loop. typedef struct AstRlBlock { struct AstRlBlock* parent; const char* label; // NULL = no label uint32_t label_len; bool is_loop; AstRlResultInfo ri; bool consumes_res_ptr; } AstRlBlock; static void astrlContainerDecl( AstRlAnnotate* astrl, AstRlBlock* block, AstFullContainerDecl full); static bool astrlExpr(AstRlAnnotate* astrl, AstNodeIndex node, AstRlBlock* block, AstRlResultInfo ri); static void astrlIdentString(const AstRlAnnotate* astrl, AstTokenIndex token, const char** out, uint32_t* out_len); // AstRlAnnotate.zig:blockExpr static bool astrlBlockExpr(AstRlAnnotate* astrl, AstRlBlock* parent_block, AstRlResultInfo ri, AstNodeIndex node, const AstNodeIndex* statements, uint32_t statements_len); static bool astrlBuiltinCall(AstRlAnnotate* astrl, AstRlBlock* block, AstNodeIndex node, const AstNodeIndex* args, uint32_t args_len); // AstRlAnnotate.zig:annotate bool* astRlAnnotate(const Ast* tree) { AstRlAnnotate astrl = { .tree = tree, .nodes_need_rl = zig1Calloc(tree->nodes.len, sizeof(bool)), }; // We can't perform analysis on a broken AST. AstGen will not run in this // case. if (tree->has_error) return astrl.nodes_need_rl; const AstNodeSlice members = astContainerDeclRoot(tree).ast.members; for (uint32_t i = 0; i < members.len; i++) astrlExpr(&astrl, members.ptr[i], NULL, ASTRL_RI_NONE); return astrl.nodes_need_rl; } // AstRlAnnotate.zig:containerDecl static void astrlContainerDecl( AstRlAnnotate* astrl, AstRlBlock* block, AstFullContainerDecl full) { const Ast* tree = astrl->tree; switch (astTokenTag(tree, full.ast.main_token)) { case TOKEN_KEYWORD_STRUCT: case TOKEN_KEYWORD_UNION: case TOKEN_KEYWORD_ENUM: if (full.ast.arg != AST_NODE_NONE) astrlExpr(astrl, full.ast.arg, block, ASTRL_RI_TYPE_ONLY); for (uint32_t i = 0; i < full.ast.members.len; i++) astrlExpr(astrl, full.ast.members.ptr[i], block, ASTRL_RI_NONE); break; case TOKEN_KEYWORD_OPAQUE: for (uint32_t i = 0; i < full.ast.members.len; i++) astrlExpr(astrl, full.ast.members.ptr[i], block, ASTRL_RI_NONE); break; default: UNREACHABLE("astrlContainerDecl: unexpected main token"); } } // AstRlAnnotate.zig:expr // True if `ri` provides a result pointer and the expression consumes it. static bool astrlExpr(AstRlAnnotate* astrl, AstNodeIndex node, AstRlBlock* block, AstRlResultInfo ri) { const Ast* tree = astrl->tree; switch (astNodeTag(tree, node)) { case AST_NODE_ROOT: case AST_NODE_SWITCH_CASE_ONE: case AST_NODE_SWITCH_CASE_INLINE_ONE: case AST_NODE_SWITCH_CASE: case AST_NODE_SWITCH_CASE_INLINE: case AST_NODE_SWITCH_RANGE: case AST_NODE_FOR_RANGE: case AST_NODE_ASM_OUTPUT: case AST_NODE_ASM_INPUT: UNREACHABLE("astrlExpr: unreachable node tag"); return false; case AST_NODE_DEFER: case AST_NODE_ERRDEFER: astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); return false; case AST_NODE_CONTAINER_FIELD_INIT: case AST_NODE_CONTAINER_FIELD_ALIGN: case AST_NODE_CONTAINER_FIELD: { AstFullContainerField full; astFullContainerField(tree, node, &full); astrlExpr(astrl, full.ast.type_expr, block, ASTRL_RI_TYPE_ONLY); if ((full.ast.align_expr != AST_NODE_NONE)) astrlExpr(astrl, full.ast.align_expr, block, ASTRL_RI_TYPE_ONLY); if ((full.ast.value_expr != AST_NODE_NONE)) astrlExpr(astrl, full.ast.value_expr, block, ASTRL_RI_TYPE_ONLY); return false; } case AST_NODE_TEST_DECL: astrlExpr(astrl, astNodeData(tree, node).rhs, block, ASTRL_RI_NONE); return false; case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: { AstFullVarDecl full; astFullVarDecl(tree, node, &full); AstRlResultInfo init_ri; if (full.ast.type_node != AST_NODE_NONE) { astrlExpr(astrl, full.ast.type_node, block, ASTRL_RI_TYPE_ONLY); init_ri = ASTRL_RI_TYPED_PTR; } else { init_ri = ASTRL_RI_INFERRED_PTR; } if (full.ast.init_node == AST_NODE_NONE) return false; switch (astTokenTag(tree, full.ast.mut_token)) { case TOKEN_KEYWORD_CONST: { const bool init_consumes_rl = astrlExpr(astrl, full.ast.init_node, block, init_ri); if (init_consumes_rl) astrl->nodes_need_rl[node] = true; return false; } case TOKEN_KEYWORD_VAR: astrlExpr(astrl, full.ast.init_node, block, init_ri); return false; default: UNIMPLEMENTED("expr: var decl mut token"); return false; } } case AST_NODE_ASSIGN_DESTRUCTURE: { const uint32_t ei = astNodeData(tree, node).lhs; const uint32_t count = tree->extra_data.arr[ei]; for (uint32_t i = 0; i < count; i++) astrlExpr( astrl, tree->extra_data.arr[ei + 1 + i], block, ASTRL_RI_NONE); astrlExpr(astrl, astNodeData(tree, node).rhs, block, ASTRL_RI_NONE); return false; } case AST_NODE_ASSIGN: { astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); astrlExpr( astrl, astNodeData(tree, node).rhs, block, ASTRL_RI_TYPED_PTR); return false; } case AST_NODE_ASSIGN_SHL: case AST_NODE_ASSIGN_SHL_SAT: case AST_NODE_ASSIGN_SHR: case AST_NODE_ASSIGN_BIT_AND: case AST_NODE_ASSIGN_BIT_OR: case AST_NODE_ASSIGN_BIT_XOR: case AST_NODE_ASSIGN_DIV: case AST_NODE_ASSIGN_SUB: case AST_NODE_ASSIGN_SUB_WRAP: case AST_NODE_ASSIGN_SUB_SAT: case AST_NODE_ASSIGN_MOD: case AST_NODE_ASSIGN_ADD: case AST_NODE_ASSIGN_ADD_WRAP: case AST_NODE_ASSIGN_ADD_SAT: case AST_NODE_ASSIGN_MUL: case AST_NODE_ASSIGN_MUL_WRAP: case AST_NODE_ASSIGN_MUL_SAT: astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); astrlExpr(astrl, astNodeData(tree, node).rhs, block, ASTRL_RI_NONE); return false; case AST_NODE_SHL: case AST_NODE_SHR: astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); astrlExpr( astrl, astNodeData(tree, node).rhs, block, ASTRL_RI_TYPE_ONLY); return false; case AST_NODE_ADD: case AST_NODE_ADD_WRAP: case AST_NODE_ADD_SAT: case AST_NODE_SUB: case AST_NODE_SUB_WRAP: case AST_NODE_SUB_SAT: case AST_NODE_MUL: case AST_NODE_MUL_WRAP: case AST_NODE_MUL_SAT: case AST_NODE_DIV: case AST_NODE_MOD: case AST_NODE_SHL_SAT: case AST_NODE_BIT_AND: case AST_NODE_BIT_OR: case AST_NODE_BIT_XOR: case AST_NODE_BANG_EQUAL: case AST_NODE_EQUAL_EQUAL: case AST_NODE_GREATER_THAN: case AST_NODE_GREATER_OR_EQUAL: case AST_NODE_LESS_THAN: case AST_NODE_LESS_OR_EQUAL: case AST_NODE_ARRAY_CAT: case AST_NODE_ERROR_UNION: case AST_NODE_MERGE_ERROR_SETS: astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); astrlExpr(astrl, astNodeData(tree, node).rhs, block, ASTRL_RI_NONE); return false; case AST_NODE_BOOL_AND: case AST_NODE_BOOL_OR: astrlExpr( astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_TYPE_ONLY); astrlExpr( astrl, astNodeData(tree, node).rhs, block, ASTRL_RI_TYPE_ONLY); return false; case AST_NODE_BOOL_NOT: astrlExpr( astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_TYPE_ONLY); return false; case AST_NODE_BIT_NOT: case AST_NODE_NEGATION: case AST_NODE_NEGATION_WRAP: astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); return false; case AST_NODE_IDENTIFIER: case AST_NODE_STRING_LITERAL: case AST_NODE_MULTILINE_STRING_LITERAL: case AST_NODE_NUMBER_LITERAL: case AST_NODE_UNREACHABLE_LITERAL: case AST_NODE_ASM_SIMPLE: case AST_NODE_ASM: case AST_NODE_ENUM_LITERAL: case AST_NODE_ERROR_VALUE: case AST_NODE_ANYFRAME_LITERAL: case AST_NODE_CONTINUE: case AST_NODE_CHAR_LITERAL: case AST_NODE_ERROR_SET_DECL: return false; case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: case AST_NODE_BUILTIN_CALL: case AST_NODE_BUILTIN_CALL_COMMA: { AstNodeIndex buf[2]; AstNodeSlice params; astBuiltinCallParams(tree, buf, node, ¶ms); return astrlBuiltinCall(astrl, block, node, params.ptr, params.len); } case AST_NODE_CALL_ONE: case AST_NODE_CALL_ONE_COMMA: case AST_NODE_CALL: case AST_NODE_CALL_COMMA: { AstNodeIndex full_buf[1]; AstFullCall full; astFullCall(tree, full_buf, node, &full); astrlExpr(astrl, full.ast.fn_expr, block, ASTRL_RI_NONE); for (uint32_t i = 0; i < full.ast.params.len; i++) astrlExpr( astrl, full.ast.params.ptr[i], block, ASTRL_RI_TYPE_ONLY); return false; } case AST_NODE_RETURN: { const AstNodeIndex lhs = astNodeData(tree, node).lhs; if (lhs != AST_NODE_NONE) { const bool ret_val_consumes_rl = astrlExpr(astrl, lhs, block, ASTRL_RI_TYPED_PTR); if (ret_val_consumes_rl) astrl->nodes_need_rl[node] = true; } return false; } case AST_NODE_FIELD_ACCESS: astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); return false; case AST_NODE_IF_SIMPLE: case AST_NODE_IF: { AstFullIf full; astFullIf(tree, node, &full); if ((full.error_token != AST_TOKEN_NONE) || (full.payload_token != AST_TOKEN_NONE)) astrlExpr(astrl, full.ast.cond_expr, block, ASTRL_RI_NONE); else astrlExpr(astrl, full.ast.cond_expr, block, ASTRL_RI_TYPE_ONLY); if (full.ast.else_expr != AST_NODE_NONE) { const bool then_uses_rl = astrlExpr(astrl, full.ast.then_expr, block, ri); const bool else_uses_rl = astrlExpr(astrl, full.ast.else_expr, block, ri); const bool uses_rl = then_uses_rl || else_uses_rl; if (uses_rl) astrl->nodes_need_rl[node] = true; return uses_rl; } astrlExpr(astrl, full.ast.then_expr, block, ASTRL_RI_NONE); return false; } case AST_NODE_WHILE_SIMPLE: case AST_NODE_WHILE_CONT: case AST_NODE_WHILE: { AstFullWhile full; astFullWhile(tree, node, &full); AstRlBlock new_block; memset(&new_block, 0, sizeof(new_block)); if ((full.label_token != AST_TOKEN_NONE)) astrlIdentString(astrl, full.label_token, &new_block.label, &new_block.label_len); if ((full.error_token != AST_TOKEN_NONE) || (full.payload_token != AST_TOKEN_NONE)) astrlExpr(astrl, full.ast.cond_expr, block, ASTRL_RI_NONE); else astrlExpr(astrl, full.ast.cond_expr, block, ASTRL_RI_TYPE_ONLY); new_block.parent = block; new_block.is_loop = true; new_block.ri = ri; new_block.consumes_res_ptr = false; if (full.ast.cont_expr != AST_NODE_NONE) astrlExpr(astrl, full.ast.cont_expr, &new_block, ASTRL_RI_NONE); astrlExpr(astrl, full.ast.then_expr, &new_block, ASTRL_RI_NONE); bool else_consumes_rl = false; if (full.ast.else_expr != AST_NODE_NONE) else_consumes_rl = astrlExpr(astrl, full.ast.else_expr, block, ri); if (new_block.consumes_res_ptr || else_consumes_rl) { astrl->nodes_need_rl[node] = true; return true; } return false; } case AST_NODE_FOR_SIMPLE: case AST_NODE_FOR: { AstFullFor full; astFullFor(tree, node, &full); AstRlBlock new_block; memset(&new_block, 0, sizeof(new_block)); if (full.label_token != AST_TOKEN_NONE) astrlIdentString(astrl, full.label_token, &new_block.label, &new_block.label_len); for (uint32_t i = 0; i < full.ast.inputs.len; i++) { const AstNodeIndex input = full.ast.inputs.ptr[i]; if (astNodeTag(tree, input) == AST_NODE_FOR_RANGE) { const AstNodeIndex start = astNodeData(tree, input).lhs; const AstNodeIndex opt_end = astNodeData(tree, input).rhs; astrlExpr(astrl, start, block, ASTRL_RI_TYPE_ONLY); if (opt_end != AST_NODE_NONE) astrlExpr(astrl, opt_end, block, ASTRL_RI_TYPE_ONLY); } else { astrlExpr(astrl, input, block, ASTRL_RI_NONE); } } new_block.parent = block; new_block.is_loop = true; new_block.ri = ri; new_block.consumes_res_ptr = false; astrlExpr(astrl, full.ast.then_expr, &new_block, ASTRL_RI_NONE); bool else_consumes_rl = false; if (full.ast.else_expr != AST_NODE_NONE) else_consumes_rl = astrlExpr(astrl, full.ast.else_expr, block, ri); if (new_block.consumes_res_ptr || else_consumes_rl) { astrl->nodes_need_rl[node] = true; return true; } return false; } case AST_NODE_SLICE_OPEN: { AstFullSlice s; astFullSlice(tree, node, &s); astrlExpr(astrl, s.ast.sliced, block, ASTRL_RI_NONE); astrlExpr(astrl, s.ast.start, block, ASTRL_RI_TYPE_ONLY); return false; } case AST_NODE_SLICE: { AstFullSlice s; astFullSlice(tree, node, &s); astrlExpr(astrl, s.ast.sliced, block, ASTRL_RI_NONE); astrlExpr(astrl, s.ast.start, block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, s.ast.end, block, ASTRL_RI_TYPE_ONLY); return false; } case AST_NODE_SLICE_SENTINEL: { AstFullSlice s; astFullSlice(tree, node, &s); astrlExpr(astrl, s.ast.sliced, block, ASTRL_RI_NONE); astrlExpr(astrl, s.ast.start, block, ASTRL_RI_TYPE_ONLY); if ((s.ast.end != AST_TOKEN_NONE)) astrlExpr(astrl, s.ast.end, block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, s.ast.sentinel, block, ASTRL_RI_NONE); return false; } case AST_NODE_DEREF: astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); return false; case AST_NODE_ADDRESS_OF: astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); return false; case AST_NODE_OPTIONAL_TYPE: astrlExpr( astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_TYPE_ONLY); return false; case AST_NODE_TRY: case AST_NODE_NOSUSPEND: return astrlExpr(astrl, astNodeData(tree, node).lhs, block, ri); case AST_NODE_GROUPED_EXPRESSION: case AST_NODE_UNWRAP_OPTIONAL: return astrlExpr(astrl, astNodeData(tree, node).lhs, block, ri); case AST_NODE_BLOCK_TWO: case AST_NODE_BLOCK_TWO_SEMICOLON: case AST_NODE_BLOCK: case AST_NODE_BLOCK_SEMICOLON: { AstNodeIndex buf[2]; AstNodeSlice statements; astBlockStatements(tree, buf, node, &statements); return astrlBlockExpr( astrl, block, ri, node, statements.ptr, statements.len); } case AST_NODE_ANYFRAME_TYPE: astrlExpr( astrl, astNodeData(tree, node).rhs, block, ASTRL_RI_TYPE_ONLY); return false; case AST_NODE_CATCH: case AST_NODE_ORELSE: { astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); const bool rhs_consumes_rl = astrlExpr(astrl, astNodeData(tree, node).rhs, block, ri); if (rhs_consumes_rl) astrl->nodes_need_rl[node] = true; return rhs_consumes_rl; } case AST_NODE_PTR_TYPE_ALIGNED: case AST_NODE_PTR_TYPE_SENTINEL: case AST_NODE_PTR_TYPE: case AST_NODE_PTR_TYPE_BIT_RANGE: { AstFullPtrType full; astFullPtrType(tree, node, &full); astrlExpr(astrl, full.ast.child_type, block, ASTRL_RI_TYPE_ONLY); if (full.ast.sentinel != AST_NODE_NONE) astrlExpr(astrl, full.ast.sentinel, block, ASTRL_RI_TYPE_ONLY); if (full.ast.addrspace_node != AST_NODE_NONE) astrlExpr( astrl, full.ast.addrspace_node, block, ASTRL_RI_TYPE_ONLY); if (full.ast.align_node != AST_NODE_NONE) astrlExpr(astrl, full.ast.align_node, block, ASTRL_RI_TYPE_ONLY); if (full.ast.bit_range_start != AST_NODE_NONE) { astrlExpr( astrl, full.ast.bit_range_start, block, ASTRL_RI_TYPE_ONLY); astrlExpr( astrl, full.ast.bit_range_end, block, ASTRL_RI_TYPE_ONLY); } return false; } case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: { AstNodeIndex buf[2]; AstFullContainerDecl full; astFullContainerDecl(tree, buf, node, &full); astrlContainerDecl(astrl, block, full); return false; } case AST_NODE_BREAK: { const AstTokenIndex opt_label = astNodeData(tree, node).lhs; const AstNodeIndex rhs = astNodeData(tree, node).rhs; if (rhs == AST_NODE_NONE) return false; AstRlBlock* cur = block; if (opt_label != AST_TOKEN_NONE) { const char* break_label; uint32_t break_label_len; astrlIdentString(astrl, opt_label, &break_label, &break_label_len); while (cur != NULL) { if (cur->label != NULL && cur->label_len == break_label_len && memcmp(cur->label, break_label, break_label_len) == 0) break; cur = cur->parent; } } else { while (cur != NULL) { if (cur->is_loop) break; cur = cur->parent; } } if (cur != NULL) { const bool consumes_break_rl = astrlExpr(astrl, rhs, block, cur->ri); if (consumes_break_rl) cur->consumes_res_ptr = true; } else { astrlExpr(astrl, rhs, block, ASTRL_RI_NONE); } return false; } case AST_NODE_ARRAY_TYPE: astrlExpr( astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_TYPE_ONLY); astrlExpr( astrl, astNodeData(tree, node).rhs, block, ASTRL_RI_TYPE_ONLY); return false; case AST_NODE_ARRAY_TYPE_SENTINEL: { const AstNodeIndex len_expr = astNodeData(tree, node).lhs; const uint32_t ei = astNodeData(tree, node).rhs; const AstNodeIndex sentinel = tree->extra_data.arr[ei]; const AstNodeIndex elem_type = tree->extra_data.arr[ei + 1]; astrlExpr(astrl, len_expr, block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, elem_type, block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, sentinel, block, ASTRL_RI_TYPE_ONLY); return false; } case AST_NODE_ARRAY_ACCESS: astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); astrlExpr( astrl, astNodeData(tree, node).rhs, block, ASTRL_RI_TYPE_ONLY); return false; case AST_NODE_COMPTIME: astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); return false; case AST_NODE_SWITCH: case AST_NODE_SWITCH_COMMA: { AstFullSwitch full; astFullSwitch(tree, node, &full); astrlExpr(astrl, full.ast.condition, block, ASTRL_RI_NONE); bool any_prong_consumed_rl = false; for (uint32_t i = 0; i < full.ast.cases.len; i++) { AstFullSwitchCase case_full; astFullSwitchCase(tree, full.ast.cases.ptr[i], &case_full); for (uint32_t j = 0; j < case_full.ast.values.len; j++) { const AstNodeIndex item_node = case_full.ast.values.ptr[j]; if (astNodeTag(tree, item_node) == AST_NODE_SWITCH_RANGE) { astrlExpr(astrl, astNodeData(tree, item_node).lhs, block, ASTRL_RI_NONE); astrlExpr(astrl, astNodeData(tree, item_node).rhs, block, ASTRL_RI_NONE); } else { astrlExpr(astrl, item_node, block, ASTRL_RI_NONE); } } if (astrlExpr(astrl, case_full.ast.target_expr, block, ri)) any_prong_consumed_rl = true; } if (any_prong_consumed_rl) astrl->nodes_need_rl[node] = true; return any_prong_consumed_rl; } case AST_NODE_SUSPEND: astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); return false; case AST_NODE_RESUME: astrlExpr(astrl, astNodeData(tree, node).lhs, block, ASTRL_RI_NONE); return false; case AST_NODE_ARRAY_INIT_ONE: case AST_NODE_ARRAY_INIT_ONE_COMMA: case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: case AST_NODE_ARRAY_INIT_DOT: case AST_NODE_ARRAY_INIT_DOT_COMMA: case AST_NODE_ARRAY_INIT: case AST_NODE_ARRAY_INIT_COMMA: { AstNodeIndex buf[2]; AstFullArrayInit full; memset(&full, 0, sizeof(full)); astFullArrayInit(tree, buf, node, &full); if (full.ast.type_expr != AST_NODE_NONE) { astrlExpr(astrl, full.ast.type_expr, block, ASTRL_RI_NONE); for (uint32_t i = 0; i < full.ast.elements.len; i++) astrlExpr(astrl, full.ast.elements.ptr[i], block, ASTRL_RI_TYPE_ONLY); return false; } if (ri.have_type) { for (uint32_t i = 0; i < full.ast.elements.len; i++) astrlExpr(astrl, full.ast.elements.ptr[i], block, ri); return ri.have_ptr; } for (uint32_t i = 0; i < full.ast.elements.len; i++) astrlExpr(astrl, full.ast.elements.ptr[i], block, ASTRL_RI_NONE); return false; } case AST_NODE_STRUCT_INIT_ONE: case AST_NODE_STRUCT_INIT_ONE_COMMA: case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: case AST_NODE_STRUCT_INIT_DOT: case AST_NODE_STRUCT_INIT_DOT_COMMA: case AST_NODE_STRUCT_INIT: case AST_NODE_STRUCT_INIT_COMMA: { AstNodeIndex buf[2]; AstFullStructInit full; memset(&full, 0, sizeof(full)); astFullStructInit(tree, buf, node, &full); if (full.ast.type_expr != AST_NODE_NONE) { astrlExpr(astrl, full.ast.type_expr, block, ASTRL_RI_NONE); for (uint32_t i = 0; i < full.ast.fields.len; i++) astrlExpr( astrl, full.ast.fields.ptr[i], block, ASTRL_RI_TYPE_ONLY); return false; } if (ri.have_type) { for (uint32_t i = 0; i < full.ast.fields.len; i++) astrlExpr(astrl, full.ast.fields.ptr[i], block, ri); return ri.have_ptr; } for (uint32_t i = 0; i < full.ast.fields.len; i++) astrlExpr(astrl, full.ast.fields.ptr[i], block, ASTRL_RI_NONE); return false; } case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: case AST_NODE_FN_DECL: { AstNodeIndex buf[1]; AstFullFnProto full; astFullFnProto(tree, buf, node, &full); const AstNodeIndex body_node = astNodeTag(tree, node) == AST_NODE_FN_DECL ? astNodeData(tree, node).rhs : AST_NODE_NONE; { AstFullFnProtoIterator it = astFullFnProtoIterate(&full, tree); AstFullFnProtoParam param; while (astFullFnProtoIteratorNext(&it, ¶m)) { if (param.anytype_ellipsis3 == AST_TOKEN_NONE) astrlExpr( astrl, param.type_expr, block, ASTRL_RI_TYPE_ONLY); } } if (full.ast.align_expr != AST_NODE_NONE) astrlExpr(astrl, full.ast.align_expr, block, ASTRL_RI_TYPE_ONLY); if (full.ast.addrspace_expr != AST_NODE_NONE) astrlExpr( astrl, full.ast.addrspace_expr, block, ASTRL_RI_TYPE_ONLY); if (full.ast.section_expr != AST_NODE_NONE) astrlExpr(astrl, full.ast.section_expr, block, ASTRL_RI_TYPE_ONLY); if (full.ast.callconv_expr != AST_NODE_NONE) astrlExpr( astrl, full.ast.callconv_expr, block, ASTRL_RI_TYPE_ONLY); assert(full.ast.return_type != AST_NODE_NONE); astrlExpr(astrl, full.ast.return_type, block, ASTRL_RI_TYPE_ONLY); if (body_node != AST_NODE_NONE) astrlExpr(astrl, body_node, block, ASTRL_RI_NONE); return false; } } UNREACHABLE("astrlExpr: invalid node tag"); ZIG1_TCC_RETURN(false); } // AstRlAnnotate.zig:identString // The name of an identifier token, without @"..." quoting; an escape // sequence in a quoted name is not modeled. static void astrlIdentString(const AstRlAnnotate* astrl, AstTokenIndex token, const char** out, uint32_t* out_len) { const Ast* tree = astrl->tree; const char* name; uint32_t name_len; astTokenSlice(tree, token, &name, &name_len); if (name_len > 0 && name[0] == '@') { // @"..." identifier: strip @" prefix and " suffix. // The inner content points directly into the source buffer. // Escape sequences in label names are not modeled and abort. assert(name_len >= 3 && name[1] == '"' && name[name_len - 1] == '"'); const char* inner = name + 2; const uint32_t inner_len = name_len - 3; for (uint32_t i = 0; i < inner_len; i++) { if (inner[i] == '\\') UNIMPLEMENTED("identString: @\"...\" with escape sequences"); } *out = inner; *out_len = inner_len; return; } *out = name; *out_len = name_len; } static bool astrlBlockExpr(AstRlAnnotate* astrl, AstRlBlock* parent_block, AstRlResultInfo ri, AstNodeIndex node, const AstNodeIndex* statements, uint32_t statements_len) { const Ast* tree = astrl->tree; const AstTokenIndex lbrace = astNodeMainToken(tree, node); if (astIsTokenPrecededByTags(tree, lbrace, (const TokenTag[]) { TOKEN_IDENTIFIER, TOKEN_COLON }, 2)) { AstRlBlock new_block; memset(&new_block, 0, sizeof(new_block)); new_block.parent = parent_block; astrlIdentString( astrl, lbrace - 2, &new_block.label, &new_block.label_len); new_block.is_loop = false; new_block.ri = ri; new_block.consumes_res_ptr = false; for (uint32_t i = 0; i < statements_len; i++) astrlExpr(astrl, statements[i], &new_block, ASTRL_RI_NONE); if (new_block.consumes_res_ptr) astrl->nodes_need_rl[node] = true; return new_block.consumes_res_ptr; } for (uint32_t i = 0; i < statements_len; i++) astrlExpr(astrl, statements[i], parent_block, ASTRL_RI_NONE); return false; } // AstRlAnnotate.zig:builtinCall static bool astrlBuiltinCall(AstRlAnnotate* astrl, AstRlBlock* block, AstNodeIndex node, const AstNodeIndex* args, uint32_t args_len) { const Ast* tree = astrl->tree; const AstTokenIndex builtin_token = astNodeMainToken(tree, node); const BuiltinFn* info = builtinFnListGetToken(tree, builtin_token); if (info == NULL) return false; if (info->param_count >= 0 && (uint32_t)info->param_count != args_len) return false; switch (info->tag) { case BFN_import: return false; case BFN_branch_hint: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_compile_log: case BFN_TypeOf: for (uint32_t i = 0; i < args_len; i++) astrlExpr(astrl, args[i], block, ASTRL_RI_NONE); return false; case BFN_as: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_bit_cast: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); return false; case BFN_union_init: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[2], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_min: case BFN_max: for (uint32_t i = 0; i < args_len; i++) astrlExpr(astrl, args[i], block, ASTRL_RI_NONE); return false; case BFN_export: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_extern: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_src: case BFN_This: case BFN_EnumLiteral: case BFN_return_address: case BFN_error_return_trace: case BFN_frame: case BFN_breakpoint: case BFN_disable_instrumentation: case BFN_disable_intrinsics: case BFN_in_comptime: case BFN_panic: case BFN_trap: case BFN_c_va_start: return false; case BFN_frame_address: return true; case BFN_sqrt: case BFN_sin: case BFN_cos: case BFN_tan: case BFN_exp: case BFN_exp2: case BFN_log: case BFN_log2: case BFN_log10: case BFN_floor: case BFN_ceil: case BFN_trunc: case BFN_round: case BFN_size_of: case BFN_bit_size_of: case BFN_align_of: case BFN_compile_error: case BFN_set_eval_branch_quota: case BFN_int_from_bool: case BFN_int_from_error: case BFN_error_from_int: case BFN_from_backing_int: case BFN_embed_file: case BFN_error_name: case BFN_set_runtime_safety: case BFN_Tuple: case BFN_wasm_memory_size: case BFN_splat: case BFN_set_float_mode: case BFN_type_info: case BFN_work_item_id: case BFN_work_group_size: case BFN_work_group_id: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_int_from_ptr: case BFN_int_from_enum: case BFN_abs: case BFN_tag_name: case BFN_type_name: case BFN_Frame: case BFN_int_from_float: case BFN_float_from_int: case BFN_ptr_from_int: case BFN_enum_from_int: case BFN_backing_int: case BFN_float_cast: case BFN_int_cast: case BFN_truncate: case BFN_error_cast: case BFN_ptr_cast: case BFN_align_cast: case BFN_addrspace_cast: case BFN_const_cast: case BFN_volatile_cast: case BFN_clz: case BFN_ctz: case BFN_pop_count: case BFN_byte_swap: case BFN_bit_reverse: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); return false; case BFN_div_exact: case BFN_div_floor: case BFN_div_ceil: case BFN_div_trunc: case BFN_mod: case BFN_rem: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); astrlExpr(astrl, args[1], block, ASTRL_RI_NONE); return false; case BFN_shl_exact: case BFN_shr_exact: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_bit_offset_of: case BFN_offset_of: case BFN_has_decl: case BFN_has_field: case BFN_field: case BFN_FieldType: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_field_parent_ptr: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_NONE); return false; case BFN_wasm_memory_grow: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_reduce: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_NONE); return false; case BFN_add_with_overflow: case BFN_sub_with_overflow: case BFN_mul_with_overflow: case BFN_shl_with_overflow: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); astrlExpr(astrl, args[1], block, ASTRL_RI_NONE); return false; case BFN_atomic_load: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_NONE); astrlExpr(astrl, args[2], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_atomic_rmw: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_NONE); astrlExpr(astrl, args[2], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[3], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[4], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_atomic_store: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_NONE); astrlExpr(astrl, args[2], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[3], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_mul_add: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[2], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_call: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_NONE); astrlExpr(astrl, args[2], block, ASTRL_RI_NONE); return false; case BFN_memcpy: case BFN_memmove: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); astrlExpr(astrl, args[1], block, ASTRL_RI_NONE); return false; case BFN_memset: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_shuffle: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_NONE); astrlExpr(astrl, args[2], block, ASTRL_RI_NONE); astrlExpr(astrl, args[3], block, ASTRL_RI_NONE); return false; case BFN_select: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_NONE); astrlExpr(astrl, args[2], block, ASTRL_RI_NONE); astrlExpr(astrl, args[3], block, ASTRL_RI_NONE); return false; case BFN_Int: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_Pointer: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[2], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[3], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_Fn: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[2], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[3], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_Struct: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[2], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[3], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[4], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_Union: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[2], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[3], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[4], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_Enum: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[2], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[3], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_SpirvType: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_Vector: astrlExpr(astrl, args[0], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_prefetch: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_c_va_arg: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); return false; case BFN_c_va_copy: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); return false; case BFN_c_va_end: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); return false; case BFN_cmpxchg_strong: case BFN_cmpxchg_weak: astrlExpr(astrl, args[0], block, ASTRL_RI_NONE); astrlExpr(astrl, args[1], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[2], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[3], block, ASTRL_RI_TYPE_ONLY); astrlExpr(astrl, args[4], block, ASTRL_RI_TYPE_ONLY); return false; } UNREACHABLE("astrlBuiltinCall: not a BuiltinFn.Tag"); ZIG1_TCC_RETURN(false); } /* ---- end ast_rl_annotate.c ---- */ /* ---- begin big_int.c ---- */ // big_int.c — lib/std/math/big/int.zig: Const (BigIntConst) and Mutable // (BigIntMutable) with the method names prefixed bigIntConst/bigIntMutable // (Mutable.addWrap is bigIntMutableAddWrap; Const methods without a Mutable // twin drop the Const), and the ll* limb-level helpers under their upstream // names (llmulacc, lldiv1, ...). A Limb is 64 bits (zig1.h). Declarations are // in zig1.h. #ifndef ZIG1_H #include "zig1.h" #endif #include // `@clz` / `@ctz` / `@popCount` over one `Limb`, hand-rolled rather than // `__builtin_*` for TCC. `@clz` and `@ctz` halve the searched // width each step, as every wide interpreter operation reaches limbClz. static uint32_t limbClz(uint64_t x) { if (x == 0) return 64u; uint32_t n = 0; for (uint32_t half = 32u; half != 0; half /= 2u) { if ((x >> (64u - half)) == 0) { n += half; x <<= half; } } return n; } static uint32_t limbCtz(uint64_t x) { if (x == 0) return 64u; uint32_t n = 0; for (uint32_t half = 32u; half != 0; half /= 2u) { if ((x << (64u - half)) == 0) { n += half; x >>= half; } } return n; } static uint32_t limbPopCount(uint64_t x) { uint32_t n = 0; while (x != 0) { n += (uint32_t)(x & 1u); x >>= 1; } return n; } // ── the low-level limb routines ───────────────────────────────────────────── // int.zig:llnormalize // `llnormalize`: `"var j = a.len; while (j > 0) : (j -= 1) { if (a[j - 1] != // 0) break; } return if (j != 0) j else 1;"`. static uint32_t llnormalize(const uint64_t* a, uint32_t len) { uint32_t j = len; while (j > 0) { if (a[j - 1u] != 0) break; j--; } return j != 0 ? j : 1u; } // int.zig:llnot // `llnot`: `"for (r) |*elem| elem.* = ~elem.*;"`. static void llnot(uint64_t* r, uint32_t len) { for (uint32_t i = 0; i < len; i++) r[i] = ~r[i]; } // int.zig:lladdcarry // `lladdcarry` (Knuth 4.3.1 Algorithm A): `r = a + b` over `a.len` limbs, // returning the carry out. Asserts `a.len >= b.len`. static uint64_t lladdcarry(uint64_t* r, const uint64_t* a, uint32_t a_len, const uint64_t* b, uint32_t b_len) { uint32_t i = 0; uint64_t carry = 0; for (; i < b_len; i++) { uint64_t s1 = a[i] + b[i]; uint64_t c1 = s1 < a[i] ? 1u : 0u; uint64_t s2 = s1 + carry; uint64_t c2 = s2 < s1 ? 1u : 0u; r[i] = s2; carry = c1 + c2; } for (; i < a_len; i++) { uint64_t s = a[i] + carry; carry = s < a[i] ? 1u : 0u; r[i] = s; } return carry; } // int.zig:llsubcarry // `llsubcarry`: `r = a - b` over `a.len` limbs, returning the borrow out. // Asserts `a.len >= b.len`. static uint64_t llsubcarry(uint64_t* r, const uint64_t* a, uint32_t a_len, const uint64_t* b, uint32_t b_len) { uint32_t i = 0; uint64_t borrow = 0; for (; i < b_len; i++) { uint64_t d1 = a[i] - b[i]; uint64_t b1 = a[i] < b[i] ? 1u : 0u; uint64_t d2 = d1 - borrow; uint64_t b2 = d1 < borrow ? 1u : 0u; r[i] = d2; borrow = b1 + b2; } for (; i < a_len; i++) { uint64_t d = a[i] - borrow; borrow = a[i] < borrow ? 1u : 0u; r[i] = d; } return borrow; } // ── Mutable ───────────────────────────────────────────────────────────────── // `Mutable.normalize`: `"r.len = llnormalize(r.limbs[0..length]);"` -- drop // leading zero limbs, keeping one for zero. static void bigIntMutableNormalize(BigIntMutable* r, uint32_t length) { r->len = llnormalize(r->limbs, length); } bool bigIntMutableCopy(BigIntMutable* r, BigIntConst a) { if (a.limbs_len > r->limbs_cap) return false; if (r->limbs != a.limbs) memmove(r->limbs, a.limbs, (size_t)a.limbs_len * sizeof(uint64_t)); // Normalize before setting `positive`, as upstream notes, so `eqlZero` // does not have to walk the extra zero limbs. bigIntMutableNormalize(r, a.limbs_len); r->positive = a.positive || bigIntEqlZero(a); return true; } // `Mutable.set` for the one-limb magnitudes this port reaches: `|value|` in // one limb with the given sign. Static: only this file's own `truncate`, // `setTwosCompIntLimit` and `saturate` reach it. static bool bigIntMutableSet(BigIntMutable* r, uint64_t mag, bool positive) { if (r->limbs_cap < 1u) return false; r->limbs[0] = mag; r->len = 1; r->positive = positive || mag == 0; return true; } // int.zig:Const.orderAbs static int bigIntOrderAbs(BigIntConst a, BigIntConst b) { if (a.limbs_len < b.limbs_len) return -1; if (a.limbs_len > b.limbs_len) return 1; uint32_t i = a.limbs_len - 1u; while (i != 0) { if (a.limbs[i] != b.limbs[i]) break; i--; } if (a.limbs[i] < b.limbs[i]) return -1; if (a.limbs[i] > b.limbs[i]) return 1; return 0; } // int.zig:Const.order int bigIntOrder(BigIntConst a, BigIntConst b) { if (a.positive != b.positive) { if (bigIntEqlZero(a) && bigIntEqlZero(b)) return 0; return a.positive ? 1 : -1; } int r = bigIntOrderAbs(a, b); return a.positive ? r : -r; } static bool bigIntMutableSubCarry( BigIntMutable* r, BigIntConst a, BigIntConst b, bool* out_carry); // `Mutable.addCarry`: the base implementation, which adds // `@max(a.limbs.len, b.limbs.len)` limbs and reports whether that overflowed. static bool bigIntMutableAddCarry( BigIntMutable* r, BigIntConst a, BigIntConst b, bool* out_carry) { *out_carry = false; if (bigIntEqlZero(a)) return bigIntMutableCopy(r, b); if (bigIntEqlZero(b)) return bigIntMutableCopy(r, a); if (a.positive != b.positive) { // `"(a) + (-b) => a - b"` / `"(-a) + (b) => b - a"`. if (a.positive) return bigIntMutableSubCarry(r, a, bigIntConstAbs(b), out_carry); return bigIntMutableSubCarry(r, b, bigIntConstAbs(a), out_carry); } r->positive = a.positive; uint32_t msl = a.limbs_len >= b.limbs_len ? a.limbs_len : b.limbs_len; if (msl > r->limbs_cap) return false; uint64_t c = a.limbs_len >= b.limbs_len ? lladdcarry(r->limbs, a.limbs, a.limbs_len, b.limbs, b.limbs_len) : lladdcarry(r->limbs, b.limbs, b.limbs_len, a.limbs, a.limbs_len); bigIntMutableNormalize(r, msl); *out_carry = c != 0; return true; } // `Mutable.subCarry`: the same for subtraction. The four sign cases are // upstream's, in upstream's order. static bool bigIntMutableSubCarry( BigIntMutable* r, BigIntConst a, BigIntConst b, bool* out_carry) { *out_carry = false; if (bigIntEqlZero(a)) { if (!bigIntMutableCopy(r, b)) return false; r->positive = !b.positive; return true; } if (bigIntEqlZero(b)) return bigIntMutableCopy(r, a); if (a.positive != b.positive) { // `"(a) - (-b) => a + b"` / `"(-a) - (b) => -a + -b"`. if (a.positive) return bigIntMutableAddCarry(r, a, bigIntConstAbs(b), out_carry); return bigIntMutableAddCarry(r, a, bigIntConstNegate(b), out_carry); } // Same sign: the larger magnitude leads, and the result's sign follows // which side that was. Upstream spells the two tests as `a.order(b) != // .lt` (both positive) and `a.order(b) == .lt` (both negative), and // `order` reverses `orderAbs` for a negative pair -- so both are "does // |a| lead", once inclusive of the tie and once not. The tie picks the // same answer either way (the difference is zero), which is why one // expression can serve both. bool a_leads = a.positive ? (bigIntOrderAbs(a, b) >= 0) : (bigIntOrderAbs(a, b) > 0); BigIntConst hi = a_leads ? a : b; BigIntConst lo = a_leads ? b : a; if (hi.limbs_len > r->limbs_cap) return false; uint64_t c = llsubcarry(r->limbs, hi.limbs, hi.limbs_len, lo.limbs, lo.limbs_len); bigIntMutableNormalize(r, hi.limbs_len); r->positive = a.positive == a_leads; *out_carry = c != 0; return true; } // `math.mulWide(Limb, a, b)`: the exact 128-bit product as two limbs. Built // from 32-bit halves rather than `__uint128_t`, which TCC does not have. // `mid` sums three values each below 2^32 plus a carry, so // it cannot wrap, and the true product is below 2^128, so neither can `hi`. static void mulWide(uint64_t x, uint64_t y, uint64_t* lo, uint64_t* hi) { uint64_t x0 = x & UINT64_C(0xFFFFFFFF), x1 = x >> 32; uint64_t y0 = y & UINT64_C(0xFFFFFFFF), y1 = y >> 32; uint64_t p00 = x0 * y0; uint64_t p01 = x0 * y1; uint64_t p10 = x1 * y0; uint64_t p11 = x1 * y1; uint64_t mid = (p00 >> 32) + (p01 & UINT64_C(0xFFFFFFFF)) + (p10 & UINT64_C(0xFFFFFFFF)); *lo = (mid << 32) | (p00 & UINT64_C(0xFFFFFFFF)); *hi = p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32); } // int.zig:addMulLimbWithCarry // `addMulLimbWithCarry`: `a + b * c + *carry`, with `*carry` left holding the // overflow. Upstream's comment on why the carry sum never overflows applies // unchanged: the two one-bit carries can both be set only when the product's // high limb is at most `maxInt(Limb) - 2`. static uint64_t addMulLimbWithCarry( uint64_t a, uint64_t b, uint64_t c, uint64_t* carry) { uint64_t o1 = a + *carry; uint64_t c1 = o1 < a ? 1u : 0u; uint64_t r2, c2; mulWide(b, c, &r2, &c2); uint64_t o2 = o1 + r2; uint64_t c3 = o2 < o1 ? 1u : 0u; *carry = c1 + c2 + c3; return o2; } // int.zig:subMulLimbWithBorrow // `subMulLimbWithBorrow`: `a - b * c - *carry`, with `*carry` left holding // the borrow. The mirror of `addMulLimbWithCarry`, and `llmulLimb`'s `.sub` // arm is its only caller. static uint64_t subMulLimbWithBorrow( uint64_t a, uint64_t b, uint64_t c, uint64_t* carry) { uint64_t o1 = a - *carry; uint64_t c1 = a < *carry ? 1u : 0u; uint64_t r2, c2; mulWide(b, c, &r2, &c2); uint64_t o2 = o1 - r2; uint64_t c3 = o1 < r2 ? 1u : 0u; *carry = c1 + c2 + c3; return o2; } // `llmulLimb`: `acc = acc (op) y * xi`, computed modulo `acc_len`, returning // whether the operation overflowed (carried, or borrowed) out of `acc`. // `divmod`'s step 3.3 is the `.sub` arm and reads that return. typedef enum { LL_ADD, LL_SUB } LlAccOp; // int.zig:llmulLimb static bool llmulLimb(LlAccOp op, uint64_t* acc, uint32_t acc_len, const uint64_t* y, uint32_t y_len, uint64_t xi) { if (xi == 0) return false; uint32_t split = y_len < acc_len ? y_len : acc_len; uint64_t carry = 0; uint32_t j = 0; if (op == LL_ADD) { for (; j < split; j++) acc[j] = addMulLimbWithCarry(acc[j], y[j], xi, &carry); for (; carry != 0 && j < acc_len; j++) { uint64_t s = acc[j] + carry; carry = s < acc[j] ? 1u : 0u; acc[j] = s; } } else { for (; j < split; j++) acc[j] = subMulLimbWithBorrow(acc[j], y[j], xi, &carry); for (; carry != 0 && j < acc_len; j++) { uint64_t d = acc[j] - carry; carry = acc[j] < carry ? 1u : 0u; acc[j] = d; } } return carry != 0; } // `llaccum(.add, ...)`: `r = r + a`, computed modulo `r.len`. `divmod`'s // step 3.4 is its only caller; the `.sub` arm upstream spells as // `llsubcarry` is already above. static void llaccumAdd( uint64_t* r, uint32_t r_len, const uint64_t* a, uint32_t a_len) { uint64_t carry = 0; uint32_t i = 0; for (; i < a_len; i++) { uint64_t s1 = r[i] + a[i]; uint64_t c1 = s1 < r[i] ? 1u : 0u; uint64_t s2 = s1 + carry; uint64_t c2 = s2 < s1 ? 1u : 0u; r[i] = s2; carry = c1 + c2; } for (; carry != 0 && i < r_len; i++) { uint64_t s = r[i] + carry; carry = s < r[i] ? 1u : 0u; r[i] = s; } } // int.zig:llmulacc // `llmulaccLong(.add, ...)` (Knuth 4.3.1, Algorithm M) reached through // `llmulacc`, whose only other arm is Karatsuba -- and that one is taken // `"if (y.len > 48)"` and an allocator was supplied. zig1 supplies none, // which is upstream's own null-allocator path, so `llmulacc` here is its // "order greatest first, then long multiplication" body and nothing else. static void llmulacc(uint64_t* r, uint32_t r_len, const uint64_t* a, uint32_t a_len, const uint64_t* b, uint32_t b_len) { const uint64_t* x = a; uint32_t x_len = a_len; const uint64_t* y = b; uint32_t y_len = b_len; if (a_len < b_len) { x = b; x_len = b_len; y = a; y_len = a_len; } for (uint32_t i = 0; i < y_len; i++) llmulLimb(LL_ADD, r + i, r_len - i, x, x_len, y[i]); } // int.zig:llshl // `llshl`: `r = a << shift`, returning the number of limbs affected. The // extra limb is written only when the top limb's shift overflows, which is // what `shiftLeftSat` relies on. static uint32_t llshl( uint64_t* r, const uint64_t* a, uint32_t a_len, uint64_t shift) { if (shift == 0) { if (a != r) memmove(r, a, (size_t)a_len * sizeof(uint64_t)); return a_len; } if (shift >= 64u) { uint32_t limb_shift = (uint32_t)(shift / 64u); uint32_t affected = llshl(r + limb_shift, a, a_len, shift % 64u); memset(r, 0, (size_t)limb_shift * sizeof(uint64_t)); return limb_shift + affected; } uint32_t bit_shift = (uint32_t)shift; uint32_t opposite_bit_shift = 64u - bit_shift; bool overflows = (a[a_len - 1u] >> opposite_bit_shift) != 0; uint32_t i = a_len; if (overflows) r[a_len] = a[a_len - 1u] >> opposite_bit_shift; while (i > 1u) { i--; r[i] = (a[i - 1u] >> opposite_bit_shift) | (a[i] << bit_shift); } r[0] = a[0] << bit_shift; return a_len + (overflows ? 1u : 0u); } // int.zig:llshr // `llshr`: `r = a >> shift`, returning the number of limbs affected. static uint32_t llshr( uint64_t* r, const uint64_t* a, uint32_t a_len, uint64_t shift) { if (a_len == 0) return 0; if (shift == 0) { if (a != r) memmove(r, a, (size_t)a_len * sizeof(uint64_t)); return a_len; } if (shift >= 64u) { uint64_t limb_shift = shift / 64u; if (limb_shift >= (uint64_t)a_len) { r[0] = 0; return 1; } return llshr( r, a + limb_shift, a_len - (uint32_t)limb_shift, shift % 64u); } uint32_t bit_shift = (uint32_t)shift; uint32_t opposite_bit_shift = 64u - bit_shift; if (a_len == 1u) { r[0] = a[0] >> bit_shift; return 1; } bool shrink = (a[a_len - 1u] >> bit_shift) == 0; uint32_t i = 0; for (; i + 1u < a_len; i++) r[i] = (a[i] >> bit_shift) | (a[i + 1u] << opposite_bit_shift); if (!shrink) r[i] = a[i] >> bit_shift; return a_len - (shrink ? 1u : 0u); } // int.zig:llsignedor // `llsignedor`: `r = a | b` in two's complement. `a_len >= b_len`, neither // operand is zero, and the return is whether the result is positive. The four // sign cases and their algebra are upstream's: // `(--a) | b = -(((-a - 1) & ~b) + 1)` // `a | (--b) = -((~a & (-b - 1)) + 1)` // `(--a) | (--b) = -((-a - 1) & (-b - 1) + 1)` static bool llsignedor(uint64_t* r, const uint64_t* a, uint32_t a_len, bool a_positive, const uint64_t* b, uint32_t b_len, bool b_positive) { uint32_t i = 0; if (a_positive && b_positive) { for (; i < b_len; i++) r[i] = a[i] | b[i]; for (; i < a_len; i++) r[i] = a[i]; return true; } if (!a_positive && b_positive) { uint64_t a_borrow = 1, r_carry = 1; for (; i < b_len; i++) { uint64_t s1 = a[i] - a_borrow; a_borrow = a[i] < a_borrow ? 1u : 0u; uint64_t v = (s1 & ~b[i]) + r_carry; r_carry = v < r_carry ? 1u : 0u; r[i] = v; } // "With b = 0, we get (-a - 1) & ~0 = -a - 1." "Copy the higher // limbs even after the borrow is zero in case r does not alias a." for (; i < a_len; i++) { uint64_t v = a[i] - a_borrow; a_borrow = a[i] < a_borrow ? 1u : 0u; r[i] = v; } return false; } if (a_positive && !b_positive) { uint64_t b_borrow = 1, r_carry = 1; for (; i < b_len; i++) { uint64_t s1 = b[i] - b_borrow; b_borrow = b[i] < b_borrow ? 1u : 0u; uint64_t v = (~a[i] & s1) + r_carry; r_carry = v < r_carry ? 1u : 0u; r[i] = v; } // `"With b = 0 and b_borrow = 0, we get ~a & (0 - 0) = 0."` The // caller normalizes by `b_len`, so the upper limbs are not read. return false; } uint64_t a_borrow = 1, b_borrow = 1, r_carry = 1; for (; i < b_len; i++) { uint64_t s1 = a[i] - a_borrow; a_borrow = a[i] < a_borrow ? 1u : 0u; uint64_t s2 = b[i] - b_borrow; b_borrow = b[i] < b_borrow ? 1u : 0u; uint64_t v = (s1 & s2) + r_carry; r_carry = v < r_carry ? 1u : 0u; r[i] = v; } return false; } // int.zig:llsignedand // `llsignedand`: `r = a & b` in two's complement, same contract. static bool llsignedand(uint64_t* r, const uint64_t* a, uint32_t a_len, bool a_positive, const uint64_t* b, uint32_t b_len, bool b_positive) { uint32_t i = 0; if (a_positive && b_positive) { for (; i < b_len; i++) r[i] = a[i] & b[i]; return true; } if (!a_positive && b_positive) { uint64_t a_borrow = 1; for (; i < b_len; i++) { uint64_t s = a[i] - a_borrow; a_borrow = a[i] < a_borrow ? 1u : 0u; r[i] = ~s & b[i]; } return true; } if (a_positive && !b_positive) { uint64_t b_borrow = 1; for (; i < b_len; i++) { uint64_t s = b[i] - b_borrow; b_borrow = b[i] < b_borrow ? 1u : 0u; r[i] = a[i] & ~s; } // `"a & ~(0 - 0) = a"`, so the upper limbs are a's. for (; i < a_len; i++) r[i] = a[i]; return true; } uint64_t a_borrow = 1, b_borrow = 1, r_carry = 1; for (; i < b_len; i++) { uint64_t s1 = a[i] - a_borrow; a_borrow = a[i] < a_borrow ? 1u : 0u; uint64_t s2 = b[i] - b_borrow; b_borrow = b[i] < b_borrow ? 1u : 0u; uint64_t v = (s1 | s2) + r_carry; r_carry = v < r_carry ? 1u : 0u; r[i] = v; } for (; i < a_len; i++) { uint64_t s1 = a[i] - a_borrow; a_borrow = a[i] < a_borrow ? 1u : 0u; uint64_t v = s1 + r_carry; r_carry = v < r_carry ? 1u : 0u; r[i] = v; } // `"The final addition can overflow here"`, so the extra limb is real. r[i] = r_carry; return false; } // int.zig:llsignedxor // `llsignedxor`: `r = a ^ b` in two's complement, same contract. Upstream // states the generic rule it implements: subtract one from a negative // operand's magnitude before the xor, and add one afterwards when the result // is to be negative. static bool llsignedxor(uint64_t* r, const uint64_t* a, uint32_t a_len, bool a_positive, const uint64_t* b, uint32_t b_len, bool b_positive) { uint32_t i = 0; uint64_t a_borrow = a_positive ? 0u : 1u; uint64_t b_borrow = b_positive ? 0u : 1u; uint64_t r_carry = (a_positive != b_positive) ? 1u : 0u; for (; i < b_len; i++) { uint64_t s1 = a[i] - a_borrow; a_borrow = a[i] < a_borrow ? 1u : 0u; uint64_t s2 = b[i] - b_borrow; b_borrow = b[i] < b_borrow ? 1u : 0u; uint64_t v = (s1 ^ s2) + r_carry; r_carry = v < r_carry ? 1u : 0u; r[i] = v; } for (; i < a_len; i++) { uint64_t s1 = a[i] - a_borrow; a_borrow = a[i] < a_borrow ? 1u : 0u; uint64_t v = s1 + r_carry; r_carry = v < r_carry ? 1u : 0u; r[i] = v; } if (a_positive != b_positive) r[i] = r_carry; return a_positive == b_positive; } // The one capacity this port gives every bitwise result: the largest of the // three functions' own upper bounds. static bool bitwiseFits(const BigIntMutable* r, BigIntConst a, BigIntConst b) { uint32_t msl = a.limbs_len >= b.limbs_len ? a.limbs_len : b.limbs_len; return msl + 1u <= r->limbs_cap; } bool bigIntMutableBitOr(BigIntMutable* r, BigIntConst a, BigIntConst b) { // `"Trivial cases, llsignedor does not support zero."` if (bigIntEqlZero(a)) return bigIntMutableCopy(r, b); if (bigIntEqlZero(b)) return bigIntMutableCopy(r, a); if (!bitwiseFits(r, a, b)) return false; if (a.limbs_len >= b.limbs_len) { r->positive = llsignedor(r->limbs, a.limbs, a.limbs_len, a.positive, b.limbs, b.limbs_len, b.positive); bigIntMutableNormalize(r, b.positive ? a.limbs_len : b.limbs_len); } else { r->positive = llsignedor(r->limbs, b.limbs, b.limbs_len, b.positive, a.limbs, a.limbs_len, a.positive); bigIntMutableNormalize(r, a.positive ? b.limbs_len : a.limbs_len); } return true; } bool bigIntMutableBitAnd(BigIntMutable* r, BigIntConst a, BigIntConst b) { // `"Trivial cases, llsignedand does not support zero."` -- and note this // one copies the zero operand, not the other one. if (bigIntEqlZero(a)) return bigIntMutableCopy(r, a); if (bigIntEqlZero(b)) return bigIntMutableCopy(r, b); if (!bitwiseFits(r, a, b)) return false; if (a.limbs_len >= b.limbs_len) { r->positive = llsignedand(r->limbs, a.limbs, a.limbs_len, a.positive, b.limbs, b.limbs_len, b.positive); bigIntMutableNormalize(r, b.positive ? b.limbs_len : (a.positive ? a.limbs_len : a.limbs_len + 1u)); } else { r->positive = llsignedand(r->limbs, b.limbs, b.limbs_len, b.positive, a.limbs, a.limbs_len, a.positive); bigIntMutableNormalize(r, a.positive ? a.limbs_len : (b.positive ? b.limbs_len : b.limbs_len + 1u)); } return true; } bool bigIntMutableBitXor(BigIntMutable* r, BigIntConst a, BigIntConst b) { // `"Trivial cases, because llsignedxor does not support negative zero."` if (bigIntEqlZero(a)) return bigIntMutableCopy(r, b); if (bigIntEqlZero(b)) return bigIntMutableCopy(r, a); if (!bitwiseFits(r, a, b)) return false; uint32_t extra = (a.positive != b.positive) ? 1u : 0u; // Upstream's split here is `>` rather than `>=`: with equal lengths the // second arm runs, and both arms are the same operation on equal-length // operands. if (a.limbs_len > b.limbs_len) { r->positive = llsignedxor(r->limbs, a.limbs, a.limbs_len, a.positive, b.limbs, b.limbs_len, b.positive); bigIntMutableNormalize(r, a.limbs_len + extra); } else { r->positive = llsignedxor(r->limbs, b.limbs, b.limbs_len, b.positive, a.limbs, a.limbs_len, a.positive); bigIntMutableNormalize(r, b.limbs_len + extra); } return true; } bool bigIntMutableShiftLeft(BigIntMutable* r, BigIntConst a, uint64_t shift) { uint64_t need = (uint64_t)a.limbs_len + shift / 64u + 1u; if (need > (uint64_t)r->limbs_cap) return false; uint32_t new_len = llshl(r->limbs, a.limbs, a.limbs_len, shift); bigIntMutableNormalize(r, new_len); r->positive = a.positive; return true; } bool bigIntMutableShiftRight(BigIntMutable* r, BigIntConst a, uint64_t shift) { uint64_t full_limbs_shifted_out = shift / 64u; uint32_t remaining_bits_shifted_out = (uint32_t)(shift % 64u); if ((uint64_t)a.limbs_len <= full_limbs_shifted_out) { // `"Shifting negative numbers converges to -1 instead of 0"`. if (r->limbs_cap < 1u) return false; r->len = 1; r->positive = a.positive; r->limbs[0] = a.positive ? 0u : 1u; return true; } uint32_t full = (uint32_t)full_limbs_shifted_out; bool nonzero_negative_shiftout = false; if (!a.positive) { for (uint32_t i = 0; i < full; i++) if (a.limbs[i] != 0) { nonzero_negative_shiftout = true; break; } if (!nonzero_negative_shiftout && remaining_bits_shifted_out != 0) { uint32_t not_covered = 64u - remaining_bits_shifted_out; if ((a.limbs[full] << not_covered) != 0) nonzero_negative_shiftout = true; } } // `llshr` writes at most `a.limbs_len` limbs, and the `addScalar` below // can want one more. if ((uint64_t)a.limbs_len + 1u > (uint64_t)r->limbs_cap) return false; r->len = llshr(r->limbs, a.limbs, a.limbs_len, shift); r->positive = a.positive; if (nonzero_negative_shiftout && !bigIntMutableAddScalar(r, bigIntMutableToConst(*r), -1)) return false; bigIntMutableNormalize(r, r->len); return true; } bool bigIntMutableMulNoAlias(BigIntMutable* r, BigIntConst a, BigIntConst b) { if (r->limbs == a.limbs || r->limbs == b.limbs) return false; // illegal aliasing if (a.limbs_len == 1u && b.limbs_len == 1u) { uint64_t lo, hi; mulWide(a.limbs[0], b.limbs[0], &lo, &hi); if (hi == 0) { if (r->limbs_cap < 1u) return false; r->limbs[0] = lo; r->len = 1; r->positive = (a.positive == b.positive) || lo == 0; return true; } } uint32_t need = a.limbs_len + b.limbs_len; if (need > r->limbs_cap) return false; memset(r->limbs, 0, (size_t)need * sizeof(uint64_t)); llmulacc(r->limbs, need, a.limbs, a.limbs_len, b.limbs, b.limbs_len); bigIntMutableNormalize(r, need); r->positive = (a.positive == b.positive); return true; } bool bigIntMutableAdd(BigIntMutable* r, BigIntConst a, BigIntConst b) { bool carry; if (!bigIntMutableAddCarry(r, a, b, &carry)) return false; if (carry) { // `"r.len = msl + 1; r.limbs[msl] = 1;"` -- addCarry normalized by // `msl`, so the intermediate limbs are zero here. uint32_t msl = a.limbs_len >= b.limbs_len ? a.limbs_len : b.limbs_len; if (msl + 1u > r->limbs_cap) return false; r->len = msl + 1u; r->limbs[msl] = 1; } return true; } bool bigIntMutableSub(BigIntMutable* r, BigIntConst a, BigIntConst b) { return bigIntMutableAdd(r, a, bigIntConstNegate(b)); } bool bigIntMutableSetFloat128Trunc( BigIntMutable* r, const uint32_t words[4], bool* out_exact) { bool negative = (words[3] >> 31) != 0; uint32_t biased = (words[3] >> 16) & 0x7FFFu; uint64_t frac_lo = (uint64_t)words[0] | ((uint64_t)words[1] << 32); uint64_t frac_hi = (uint64_t)words[2] | ((uint64_t)(words[3] & 0xFFFFu) << 32); // `"if (abs_value < 1.0) { if (abs_value == 0.0) { self.set(0); return // .exact; } ... .trunc => 0, ... return .inexact; }"` if (biased < 16383u) { *out_exact = biased == 0 && frac_lo == 0 && frac_hi == 0; return bigIntMutableSet(r, 0, true); } uint32_t exponent = biased - 16383u; // `"const mantissa = int_bit | repr.mantissa;"`: 112 fraction bits. uint64_t mantissa[2] = { frac_lo, frac_hi | (UINT64_C(1) << 48) }; BigIntConst m = { mantissa, 2, true }; if (exponent >= 112u) { // `"self.set(mantissa); self.shiftLeft(self.toConst(), exponent - // @bitSizeOf(Repr.Normalized.Fraction)); ... return .exact;"` if (!bigIntMutableShiftLeft(r, m, exponent - 112u)) return false; *out_exact = true; } else { // `"self.set(mantissa >> (112 - exponent)); const round_bits = ... // mantissa << exponent; if (round_bits == 0) ... return .exact;"` uint32_t shift = 112u - exponent; if (!bigIntMutableShiftRight(r, m, shift)) return false; *out_exact = shift < 64u ? (frac_lo & ((UINT64_C(1) << shift) - 1u)) == 0 : frac_lo == 0 && (mantissa[1] & ((UINT64_C(1) << (shift - 64u)) - 1u)) == 0; } r->positive = !negative; return true; } bool bigIntMutableAddScalar(BigIntMutable* r, BigIntConst a, int64_t scalar) { uint64_t limb = scalar < 0 ? (uint64_t)0 - (uint64_t)scalar : (uint64_t)scalar; BigIntConst operand; operand.limbs = &limb; operand.limbs_len = 1; operand.positive = scalar >= 0; return bigIntMutableAdd(r, a, operand); } // ── division ──────────────────────────────────────────────────────────────── // One `DoubleLimb / Limb` step: `(rem_hi, lo) / d` with `rem_hi < d`, so the // quotient fits one limb. This is what `lldiv1`'s // `@divTrunc(pdiv, b)` spells over a `DoubleLimb`; tcc has no `__uint128_t`, // so the bits are walked by hand. static uint64_t llDivLimbStep( uint64_t rem_hi, uint64_t lo, uint64_t d, uint64_t* rem) { uint64_t q = 0; uint64_t r = 0; for (uint32_t i = 128; i > 0; i--) { uint32_t bit_index = i - 1u; uint64_t bit = bit_index >= 64u ? ((rem_hi >> (bit_index - 64u)) & 1u) : ((lo >> bit_index) & 1u); bool r_hi = (r >> 63) != 0; r = (r << 1) | bit; if (r_hi || r >= d) { r -= d; if (bit_index < 64u) q |= UINT64_C(1) << bit_index; } } *rem = r; return q; } // int.zig:lldiv1 // `lldiv1` (Knuth 4.3.1, Exercise 16): `quo = a / b`, `*rem = a % b`, for a // ONE-limb divisor. Asserts `a_len > 1 or a[0] >= b` and `quo.len >= a.len`. static void lldiv1(uint64_t* quo, uint64_t* rem, const uint64_t* a, uint32_t a_len, uint64_t b) { *rem = 0; for (uint32_t ri = 0; ri < a_len; ri++) { uint32_t i = a_len - ri - 1u; // `pdiv = (rem << limb_bits) | a[i]`, as its own two limbs. if (*rem == 0 && a[i] == 0) { quo[i] = 0; } else if (*rem == 0 && a[i] < b) { quo[i] = 0; *rem = a[i]; } else if (*rem == 0 && a[i] == b) { quo[i] = 1; *rem = 0; } else { quo[i] = llDivLimbStep(*rem, a[i], b, rem); } } } // int.zig:lldiv0p5 // `lldiv0p5`: the same for a divisor below `maxInt(HalfLimb)`, which splits // each limb into halves so no `DoubleLimb` division is needed at all. static void lldiv0p5(uint64_t* quo, uint64_t* rem, const uint64_t* a, uint32_t a_len, uint32_t b) { *rem = 0; for (uint32_t ri = 0; ri < a_len; ri++) { uint32_t i = a_len - ri - 1u; uint64_t ai_high = a[i] >> 32; uint64_t ai_low = a[i] & UINT64_C(0xFFFFFFFF); uint64_t ai_high_with_carry = (*rem << 32) | ai_high; uint64_t ai_high_quo = ai_high_with_carry / b; *rem = ai_high_with_carry % b; uint64_t ai_low_with_carry = (*rem << 32) | ai_low; uint64_t ai_low_quo = ai_low_with_carry / b; *rem = ai_low_with_carry % b; quo[i] = (ai_high_quo << 32) | ai_low_quo; } } static bool bigIntDivmod( BigIntMutable* q, BigIntMutable* r, BigIntMutable* x, BigIntMutable* y); // `div`: `q = x / y (rem r)`, truncating. `q` may alias `x`; `q` and `r` may // not alias each other, and `y` must be nonzero (upstream asserts both). static bool bigIntDiv( // cppcheck-suppress constParameterPointer ; keeps upstream's `x: *Mutable` BigIntMutable* q, BigIntMutable* r, BigIntMutable* x, BigIntMutable* y) { bool q_positive = (x->positive == y->positive); bool r_positive = x->positive; if (bigIntOrderAbs(bigIntMutableToConst(*x), bigIntMutableToConst(*y)) < 0) { // `"q may alias x so handle r first."` if (!bigIntMutableCopy(r, bigIntMutableToConst(*x))) return false; r->positive = r_positive; return bigIntMutableSet(q, 0, true); } // `"Handle trailing zero-words of divisor/dividend. These are not handled // in the following algorithms."` uint32_t x_trailing = 0; while (x_trailing < x->len && x->limbs[x_trailing] == 0) x_trailing++; uint32_t y_trailing = 0; while (y_trailing < y->len && y->limbs[y_trailing] == 0) y_trailing++; uint32_t xy_trailing = x_trailing < y_trailing ? x_trailing : y_trailing; if (y->len - xy_trailing == 1u) { uint64_t divisor = y->limbs[y->len - 1u]; if (x->len - xy_trailing > q->limbs_cap || r->limbs_cap < 1u) return false; // `"Optimization for small divisor. By using a half limb we can avoid // requiring DoubleLimb divisions in the hot code path."` if (divisor < UINT64_C(0xFFFFFFFF)) { lldiv0p5(q->limbs, &r->limbs[0], x->limbs + xy_trailing, x->len - xy_trailing, (uint32_t)divisor); } else { lldiv1(q->limbs, &r->limbs[0], x->limbs + xy_trailing, x->len - xy_trailing, divisor); } bigIntMutableNormalize(q, x->len - xy_trailing); q->positive = q_positive; r->len = 1; r->positive = r_positive; } else { // `"Shrink x, y such that the trailing zero limbs shared between are // removed."` BigIntMutable x0; x0.limbs = x->limbs + xy_trailing; x0.limbs_cap = x->limbs_cap - xy_trailing; x0.len = x->len - xy_trailing; x0.positive = true; BigIntMutable y0; y0.limbs = y->limbs + xy_trailing; y0.limbs_cap = y->limbs_cap - xy_trailing; y0.len = y->len - xy_trailing; y0.positive = true; if (!bigIntDivmod(q, r, &x0, &y0)) return false; q->positive = q_positive; r->positive = r_positive; } if (xy_trailing != 0 && r->limbs[r->len - 1u] != 0) { // `"Manually shift here since we know its limb aligned."` if ((uint64_t)r->len + xy_trailing > (uint64_t)r->limbs_cap) return false; memmove(r->limbs + xy_trailing, r->limbs, (size_t)r->len * sizeof(uint64_t)); memset(r->limbs, 0, (size_t)xy_trailing * sizeof(uint64_t)); r->len += xy_trailing; } return true; } // int.zig:Mutable.divmod // `divmod` (Handbook of Applied Cryptography, 14.20): `x = qy + r` with // `0 <= r < y`. `y` is modified but returned intact. static bool bigIntDivmod( BigIntMutable* q, BigIntMutable* r, BigIntMutable* x, BigIntMutable* y) { // 0. Normalize so that `y[t] > b/2`. uint32_t lz = limbClz(y->limbs[y->len - 1u]); // `"Force an extra limb so that y is even."` -- `Const.isOdd` is // `limbs[0] & 1`. uint64_t norm_shift = (lz == 0 && (y->limbs[0] & 1u) != 0) ? 64u : lz; if (!bigIntMutableShiftLeft(x, bigIntMutableToConst(*x), norm_shift) || !bigIntMutableShiftLeft(y, bigIntMutableToConst(*y), norm_shift)) return false; uint32_t n = x->len - 1u; uint32_t t = y->len - 1u; if (n < t) return false; uint32_t shift = n - t; // 1. `"for 0 <= j <= n - t, set q[j] to 0"`. if (shift + 1u > q->limbs_cap) return false; q->len = shift + 1u; q->positive = true; memset(q->limbs, 0, (size_t)q->len * sizeof(uint64_t)); // 2. `"while x >= y * b^(n - t): x -= y * b^(n - t); q[n - t] += 1"`, // which step 0 made an `if`. The multiplication by `b^(n - t)` is a limb // shift, so it is `x / b^(n - t) >= y` over `x.limbs[shift..]`. { BigIntMutable tmp; tmp.limbs = x->limbs + shift; tmp.limbs_cap = x->limbs_cap - shift; tmp.len = x->len - shift; tmp.positive = true; if (bigIntOrder(bigIntMutableToConst(tmp), bigIntMutableToConst(*y)) >= 0) { // `"Due to the check above, this cannot underflow."` if (!bigIntMutableSub( &tmp, bigIntMutableToConst(tmp), bigIntMutableToConst(*y))) return false; // Upstream's `"x.limbs.len = tmp.limbs.len + shift;"` restates // `x`'s own capacity (`tmp.limbs` is `x.limbs[shift..]`), so // there is nothing for it to change here. q->limbs[shift] += 1u; } } // 3. `"for i from n down to t + 1, do"`. for (uint32_t i = n; i >= t + 1u; i--) { uint32_t k = i - t - 1u; // 3.1. if (x->limbs[i] == y->limbs[t]) { q->limbs[k] = UINT64_MAX; } else { uint64_t step_rem; q->limbs[k] = llDivLimbStep( x->limbs[i], x->limbs[i - 1u], y->limbs[t], &step_rem); } // 3.2. `"while q[i - t - 1] * (y[t] * b + y[t - 1]) > x[i] * b * b + // x[i - 1] + x[i - 2]: q[i - t - 1] -= 1"`. Both triples are BIG // endian, so a lexicographic compare is the numeric one. uint64_t y0 = t > 0u ? y->limbs[t - 1u] : 0u; uint64_t y1 = y->limbs[t]; uint64_t tmp0[3]; tmp0[0] = x->limbs[i]; tmp0[1] = i >= 1u ? x->limbs[i - 1u] : 0u; tmp0[2] = i >= 2u ? x->limbs[i - 2u] : 0u; for (;;) { // `"Ad-hoc 2x1 multiplication with q[i - t - 1]"`: `tmp1[0]` is // the running carry, exactly as upstream spells it. uint64_t tmp1[3]; tmp1[0] = 0; tmp1[2] = addMulLimbWithCarry(0, y0, q->limbs[k], &tmp1[0]); tmp1[1] = addMulLimbWithCarry(0, y1, q->limbs[k], &tmp1[0]); bool greater = false; for (uint32_t z = 0; z < 3u; z++) { if (tmp1[z] != tmp0[z]) { greater = tmp1[z] > tmp0[z]; break; } } if (!greater) break; q->limbs[k] -= 1u; } // 3.3. `"x -= q[i - t - 1] * y * b^(i - t - 1)"`. bool underflow = llmulLimb( LL_SUB, x->limbs + k, x->len - k, y->limbs, y->len, q->limbs[k]); // 3.4. `"if x < 0: x += y * b^(i - t - 1); q[i - t - 1] -= 1"`. if (underflow) { llaccumAdd(x->limbs + k, x->len - k, y->limbs, y->len); q->limbs[k] -= 1u; } } bigIntMutableNormalize(x, x->len); bigIntMutableNormalize(q, q->len); // `"De-normalize r and y."` return bigIntMutableShiftRight(r, bigIntMutableToConst(*x), norm_shift) && bigIntMutableShiftRight(y, bigIntMutableToConst(*y), norm_shift); } // int.zig:calcDivLimbsBufferLen uint32_t bigIntCalcDivLimbsBufferLen(uint32_t a_len, uint32_t b_len) { return a_len + b_len + 4u; } // `Const.toMutable`: `"@memcpy(limbs[0..self.limbs.len], ...)"` plus the same // sign and length. static BigIntMutable bigIntConstToMutable( BigIntConst a, uint64_t* limbs, uint32_t limbs_cap) { BigIntMutable m; memcpy(limbs, a.limbs, (size_t)a.limbs_len * sizeof(uint64_t)); m.limbs = limbs; m.limbs_cap = limbs_cap; m.len = a.limbs_len; m.positive = a.positive; return m; } // `divTrunc` / `divFloor`'s shared prologue: `"const sep = a.limbs.len + 2; // var x = a.toMutable(limbs_buffer[0..sep]); var y = b.toMutable( // limbs_buffer[sep..]);"`. static bool bigIntDivSplitBuffer(BigIntConst a, BigIntConst b, uint64_t* limbs_buffer, uint32_t limbs_buffer_len, BigIntMutable* x, BigIntMutable* y) { uint32_t sep = a.limbs_len + 2u; if (limbs_buffer_len < bigIntCalcDivLimbsBufferLen(a.limbs_len, b.limbs_len) || bigIntEqlZero(b)) return false; *x = bigIntConstToMutable(a, limbs_buffer, sep); *y = bigIntConstToMutable(b, limbs_buffer + sep, limbs_buffer_len - sep); return true; } bool bigIntMutableDivTrunc(BigIntMutable* q, BigIntMutable* r, BigIntConst a, BigIntConst b, uint64_t* limbs_buffer, uint32_t limbs_buffer_len) { BigIntMutable x, y; if (q->limbs == r->limbs || !bigIntDivSplitBuffer(a, b, limbs_buffer, limbs_buffer_len, &x, &y)) return false; return bigIntDiv(q, r, &x, &y); } bool bigIntMutableDivFloor(BigIntMutable* q, BigIntMutable* r, BigIntConst a, BigIntConst b, uint64_t* limbs_buffer, uint32_t limbs_buffer_len) { BigIntMutable x, y; if (q->limbs == r->limbs || !bigIntDivSplitBuffer(a, b, limbs_buffer, limbs_buffer_len, &x, &y)) return false; if (!bigIntDiv(q, r, &x, &y)) return false; // `div` performed truncating division; these four arms are upstream's // correction to a flooring quotient and its `@mod` remainder. The algebra // for each is in `lib/std/math/big/int.zig`'s `divFloor`. if (a.positive && b.positive) { // `"Positive-positive case, don't need to do anything."` } else if (a.positive && !b.positive) { if (!bigIntEqlZero(bigIntMutableToConst(*r))) { if (!bigIntMutableAddScalar(q, bigIntMutableToConst(*q), -1)) return false; r->positive = true; if (!bigIntMutableSub(r, bigIntMutableToConst(*r), bigIntConstAbs(bigIntMutableToConst(y)))) return false; } } else if (!a.positive && b.positive) { if (!bigIntEqlZero(bigIntMutableToConst(*r))) { if (!bigIntMutableAddScalar(q, bigIntMutableToConst(*q), -1)) return false; r->positive = false; if (!bigIntMutableAdd(r, bigIntMutableToConst(*r), bigIntConstAbs(bigIntMutableToConst(y)))) return false; } } else { // `"a/b -> q is positive, don't need to do anything to fix // flooring."` -- only `@mod`'s sign changes. r->positive = false; } return true; } // int.zig:Mutable.divCeil bool bigIntMutableDivCeil(BigIntMutable* q, BigIntMutable* r, BigIntConst a, BigIntConst b, uint64_t* limbs_buffer, uint32_t limbs_buffer_len) { BigIntMutable x, y; if (q->limbs == r->limbs || !bigIntDivSplitBuffer(a, b, limbs_buffer, limbs_buffer_len, &x, &y)) return false; if (!bigIntDiv(q, r, &x, &y)) return false; // `"if (a.positive != b.positive or r.eqlZero())"` the truncating // quotient already rounds up; otherwise `@divCeil(a, b) = @divTrunc(a, // b) + 1` and the remainder is `@rem(a, b) - b`. if (a.positive != b.positive || bigIntEqlZero(bigIntMutableToConst(*r))) return true; return bigIntMutableAddScalar(q, bigIntMutableToConst(*q), 1) && bigIntMutableSub( r, bigIntMutableToConst(*r), bigIntMutableToConst(y)); } // int.zig:Const.eqlZero bool bigIntEqlZero(BigIntConst a) { uint64_t d = 0; for (uint32_t i = 0; i < a.limbs_len; i++) d |= a.limbs[i]; return d == 0; } uint64_t bigIntConstClz(BigIntConst a, uint64_t bits) { if (!a.positive && !bigIntEqlZero(a)) return 0; uint64_t total_limb_lz = 0; uint32_t i = a.limbs_len; while (i != 0) { i -= 1; uint32_t this_limb_lz = limbClz(a.limbs[i]); total_limb_lz += this_limb_lz; if (this_limb_lz != 64u) break; } uint64_t total_limb_bits = (uint64_t)a.limbs_len * 64u; return total_limb_lz + bits - total_limb_bits; } uint64_t bigIntConstPopCount(BigIntConst a, uint64_t bit_count) { uint64_t sum = 0; if (a.positive) { for (uint32_t i = 0; i < a.limbs_len; i++) sum += limbPopCount(a.limbs[i]); return sum; } uint64_t remaining_bits = bit_count; uint64_t carry = 1; for (uint32_t i = 0; i + 1u < a.limbs_len; i++) { uint64_t add_res = ~a.limbs[i] + carry; carry = (carry != 0 && add_res == 0) ? 1u : 0u; sum += limbPopCount(add_res); remaining_bits -= 64u; } uint64_t limb = a.limbs[a.limbs_len - 1u]; uint32_t leading_zeroes = limbClz(limb); sum += limbPopCount(~limb + carry); sum -= leading_zeroes; sum += remaining_bits - (64u - leading_zeroes); return sum; } // int.zig:Const.bitCountAbs uint64_t bigIntBitCountAbs(BigIntConst a) { uint64_t top = a.limbs[a.limbs_len - 1u]; return (uint64_t)(a.limbs_len - 1u) * 64u + (64u - limbClz(top)); } // int.zig:Const.bitCountTwosComp uint64_t bigIntBitCountTwosComp(BigIntConst a) { uint64_t bits = bigIntBitCountAbs(a); if (!a.positive) { bits += 1; if (limbPopCount(a.limbs[a.limbs_len - 1u]) == 1) { bool only_one_bit = true; for (uint32_t i = 0; i + 1u < a.limbs_len; i++) if (limbPopCount(a.limbs[i]) != 0) { only_one_bit = false; break; } if (only_one_bit) bits -= 1; } } return bits; } // int.zig:Const.bitCountTwosCompForSignedness static uint64_t bigIntBitCountTwosCompForSignedness( BigIntConst a, bool is_signed) { return bigIntBitCountTwosComp(a) + ((a.positive && is_signed) ? 1u : 0u); } // int.zig:Const.fitsInTwosComp bool bigIntFitsInTwosComp(BigIntConst a, bool is_signed, uint64_t bit_count) { if (bigIntEqlZero(a)) return true; if (!is_signed && !a.positive) return false; return bit_count >= bigIntBitCountTwosCompForSignedness(a, is_signed); } // `Const.toInt`, at the two widths this frontend asks for. `fitsInTwosComp` // Is the range test -- `"if (!self.fitsInTwosComp(info.signedness, // info.bits)) return error.TargetTooSmall;"` -- and the negative arm is // upstream's `"if (math.cast(Int, r)) |ok| return -ok else return // minInt(Int);"`, which is exactly the wrapping negation of the magnitude at // this width. bool bigIntToU64(BigIntConst a, uint64_t* out) { if (bigIntEqlZero(a)) { *out = 0; return true; } if (!bigIntFitsInTwosComp(a, false, 64)) return false; *out = a.limbs[0]; return true; } bool bigIntToI64(BigIntConst a, int64_t* out) { if (bigIntEqlZero(a)) { *out = 0; return true; } if (!bigIntFitsInTwosComp(a, true, 64)) return false; uint64_t r = a.limbs[0]; *out = a.positive ? (int64_t)r : (int64_t)((uint64_t)0 - r); return true; } // ── the 2s-complement width rules ─────────────────────────────────────────── // int.zig:calcTwosCompLimbCount uint32_t bigIntCalcTwosCompLimbCount(uint64_t bit_count) { uint64_t n = (bit_count + 63u) / 64u; return n < 1u ? 1u : (uint32_t)n; } // `@truncate(bit_count - 1)` to `Log2Limb`, which is the index of the sign // bit inside the TOP limb. static uint32_t signBitIndex(uint64_t bit_count) { return (uint32_t)((bit_count - 1u) & 63u); } // `@as(Limb, maxInt(Limb)) >> @truncate(-%bit_count)`: the mask of the bits // the top limb keeps. A width that is a whole number of limbs keeps all of // them (`-%bit_count` truncates to 0). static uint64_t topLimbMask(uint64_t bit_count) { return UINT64_MAX >> (uint32_t)((UINT64_C(0) - bit_count) & 63u); } bool bigIntMutableSetTwosCompIntLimit(BigIntMutable* r, BigIntTwosCompLimit limit, bool is_signed, uint64_t bit_count) { if (bit_count == 0) return bigIntMutableSet(r, 0, true); uint32_t req_limbs = bigIntCalcTwosCompLimbCount(bit_count); if (req_limbs > r->limbs_cap) return false; uint64_t signmask = UINT64_C(1) << signBitIndex(bit_count); uint64_t mask = (signmask << 1) - 1u; // wraps to all-ones on a full limb r->positive = true; if (is_signed) { if (limit == BIG_INT_LIMIT_MIN) { // `"Negative bound, signed = -0x80"`. r->len = req_limbs; memset(r->limbs, 0, (size_t)(r->len - 1u) * sizeof(uint64_t)); r->limbs[r->len - 1u] = signmask; r->positive = false; return true; } // `"Positive bound, signed = 0x7F"`, normalized: the sign bit is 0, // so the value is the max of a bit_count-1 wide unsigned. if (bit_count == 1) return bigIntMutableSet(r, 0, true); uint32_t new_req_limbs = bigIntCalcTwosCompLimbCount(bit_count - 1u); uint64_t new_signmask = UINT64_C(1) << signBitIndex(bit_count - 1u); uint64_t new_mask = (new_signmask << 1) - 1u; r->len = new_req_limbs; memset(r->limbs, 0xFF, (size_t)(r->len - 1u) * sizeof(uint64_t)); r->limbs[r->len - 1u] = new_mask; return true; } if (limit == BIG_INT_LIMIT_MIN) return bigIntMutableSet(r, 0, true); // `"Min bound, unsigned = 0x00"` // `"Max bound, unsigned = 0xFF"`. r->len = req_limbs; memset(r->limbs, 0xFF, (size_t)(r->len - 1u) * sizeof(uint64_t)); r->limbs[r->len - 1u] = mask; return true; } bool bigIntMutableTruncate( BigIntMutable* r, BigIntConst a, bool is_signed, uint64_t bit_count) { if (bit_count == 0) return bigIntMutableSet(r, 0, true); // `"Handle 0-bit integers."` uint32_t max_limbs = bigIntCalcTwosCompLimbCount(bit_count); if (max_limbs > r->limbs_cap) return false; uint64_t sign_bit = UINT64_C(1) << signBitIndex(bit_count); uint64_t mask = topLimbMask(bit_count); // `"Guess whether the result will have the same sign as `a`."` Upstream // names the three cases the guess is deliberately wrong in; both arms // below settle the sign again at the end, which is what makes that legal. bool same_sign_guess = is_signed ? (max_limbs > a.limbs_len || (a.limbs[max_limbs - 1u] & sign_bit) == 0) : a.positive; BigIntConst abs_trunc_a; abs_trunc_a.positive = true; abs_trunc_a.limbs = a.limbs; abs_trunc_a.limbs_len = llnormalize( a.limbs, a.limbs_len < max_limbs ? a.limbs_len : max_limbs); if (same_sign_guess || bigIntEqlZero(abs_trunc_a)) { // The result is zero, or keeps `a`'s sign: its magnitude is just the // low bits of `|a|`. if (!bigIntMutableCopy(r, abs_trunc_a)) return false; if (max_limbs <= r->len) r->limbs[max_limbs - 1u] &= mask; bigIntMutableNormalize(r, r->len); r->positive = a.positive || bigIntEqlZero(bigIntMutableToConst(*r)); return true; } // The result flips sign (or is the minimum signed integer): the answer is // the two's complement of the low bits, spelled `~(|a| - 1)` masked to // the width. if (!bigIntMutableAddScalar(r, abs_trunc_a, -1)) return false; llnot(r->limbs, r->len); for (uint32_t i = r->len; i < max_limbs; i++) r->limbs[i] = UINT64_MAX; r->limbs[max_limbs - 1u] &= mask; bigIntMutableNormalize(r, max_limbs); r->positive = is_signed // `"The only value with the sign bit still set is the minimum signed // integer."` ? (!a.positive && (r->limbs[max_limbs - 1u] & sign_bit) == 0) : (!a.positive || bigIntEqlZero(bigIntMutableToConst(*r))); return true; } bool bigIntTruncateToU128( BigIntConst a, uint64_t bit_count, uint64_t* lo, uint64_t* hi) { if (bit_count > 128u) return false; uint64_t pat[2]; BigIntMutable r; r.limbs = pat; r.limbs_cap = 2; r.len = 0; r.positive = true; if (!bigIntMutableTruncate(&r, a, false, bit_count)) return false; *lo = r.len >= 1u ? r.limbs[0] : 0u; *hi = r.len >= 2u ? r.limbs[1] : 0u; return true; } // `Mutable.readPackedTwosComplement` at the one endianness this tree's guest // has (wasm32), so its `.big` arms are not transcribed and `readPackedInt` / // `readVarPackedInt` are common.h's packedMemRead. Needs // `calcTwosCompLimbCount(bit_count)` limbs. bool bigIntMutableReadPackedTwosComplement(BigIntMutable* r, const uint8_t* buffer, uint64_t bit_offset, uint32_t bit_count, bool is_signed) { if (r->limbs_cap < bigIntCalcTwosCompLimbCount(bit_count)) return false; if (bit_count == 0) { r->limbs[0] = 0; r->len = 1; r->positive = true; return true; } // `"Check whether the input is negative"` -- the sign bit is the top bit // of the field. bool positive = true; if (is_signed) { uint64_t total_bits = bit_offset + bit_count; uint64_t last_byte = (total_bits + 7u) / 8u - 1u; uint8_t sign_bit = (uint8_t)(1u << ((total_bits - 1u) % 8u)); positive = (buffer[last_byte] & sign_bit) == 0; } // `"Copy all complete limbs"`, negating as it goes (`"2's complement // (bitwise not, then add carry bit)"`). uint64_t carry = 1; uint32_t limb_index = 0; uint32_t bit_index = 0; while (limb_index < bit_count / 64u) { uint64_t limb, hi; packedMemRead(buffer, bit_index + bit_offset + 64u, bit_index + bit_offset, 64u, &limb, &hi); bit_index += 64u; if (!positive) { uint64_t notl = ~limb; uint64_t sum = notl + carry; carry = sum < notl ? 1u : 0u; limb = sum; } r->limbs[limb_index] = limb; limb_index++; } // `"Copy the remaining bits"`: a partial limb, read as `.unsigned` or // sign-extended as `.signed` exactly as upstream's `readVarPackedInt` // does, then negated with the same carry. if (bit_count != bit_index) { uint32_t rem = bit_count - bit_index; // 1..63 uint64_t limb, hi; if (!packedMemRead(buffer, bit_index + bit_offset + rem, bit_index + bit_offset, rem, &limb, &hi)) return false; uint64_t mask = (UINT64_C(1) << rem) - 1u; if (is_signed && (limb & (UINT64_C(1) << (rem - 1u))) != 0) limb |= ~mask; if (!positive) limb = ~limb + carry; r->limbs[limb_index] = limb; limb_index++; } r->positive = positive; r->len = limb_index; bigIntMutableNormalize(r, r->len); return true; } bool bigIntMutableReadTwosComplement(BigIntMutable* r, const uint8_t* buffer, uint32_t bit_count, bool is_signed) { return bigIntMutableReadPackedTwosComplement( r, buffer, 0, bit_count, is_signed); } // `Const.writePackedTwosComplement`. Upstream asserts `"x.fitsInTwosComp(if // (x.positive) .unsigned else .signed, bit_count)"`; zig1 cannot abort inside // this module, so that assertion is a returned false. bool bigIntConstWritePackedTwosComplement( BigIntConst a, uint8_t* buffer, uint64_t bit_offset, uint64_t bit_count) { if (!bigIntFitsInTwosComp(a, !a.positive, bit_count)) return false; uint64_t carry = 1; uint32_t limb_index = 0; uint64_t bit_index = 0; while (limb_index < bit_count / 64u) { uint64_t limb = limb_index < a.limbs_len ? a.limbs[limb_index] : 0u; if (!a.positive) { uint64_t notl = ~limb; uint64_t sum = notl + carry; carry = sum < notl ? 1u : 0u; limb = sum; } packedMemWrite(buffer, bit_index + bit_offset + 64u, bit_index + bit_offset, 64u, limb, 0); bit_index += 64u; limb_index++; } if (bit_count != bit_index) { uint64_t limb = limb_index < a.limbs_len ? a.limbs[limb_index] : 0u; if (!a.positive) limb = ~limb + carry; uint32_t rem = (uint32_t)(bit_count - bit_index); packedMemWrite(buffer, bit_index + bit_offset + rem, bit_index + bit_offset, rem, limb, 0); } return true; } // `Const.writeTwosComplement` -- `"return writePackedTwosComplement(x, // buffer, 0, 8 * buffer.len, endian);"` -- so the caller names the buffer in // bytes and every bit of it is written. Writing the whole buffer is what // sign- or zero-extends a value into the padding of an over-wide ABI slot, // which is the canonical form every reader of those bytes then expects. bool bigIntConstWriteTwosComplement( BigIntConst a, uint8_t* buffer, uint32_t byte_count) { return bigIntConstWritePackedTwosComplement( a, buffer, 0, (uint64_t)byte_count * 8u); } // `Mutable.byteSwap`/`bitReverse`'s start: `a`'s little-endian two's // complement image at `byte_count` bytes, written over `r.limbs`' own bytes, // which the caller permutes in place and reads back. NULL when it does not // fit. static uint8_t* bigIntTwosImageInPlace( BigIntMutable* r, BigIntConst a, uint32_t byte_count) { uint8_t* bytes = (uint8_t*)r->limbs; if ((uint64_t)r->limbs_cap * 8u < byte_count || !bigIntConstWriteTwosComplement(a, bytes, byte_count)) return NULL; return bytes; } bool bigIntMutableByteSwap( BigIntMutable* r, BigIntConst a, bool is_signed, uint32_t byte_count) { uint8_t* bytes = bigIntTwosImageInPlace(r, a, byte_count); if (bytes == NULL) return false; for (uint32_t k = 0; k < byte_count / 2u; k++) { uint8_t t = bytes[k]; bytes[k] = bytes[byte_count - k - 1u]; bytes[byte_count - k - 1u] = t; } return bigIntMutableReadTwosComplement( r, bytes, byte_count * 8u, is_signed); } bool bigIntMutableBitReverse( BigIntMutable* r, BigIntConst a, bool is_signed, uint32_t bit_count) { uint8_t* bytes = bigIntTwosImageInPlace(r, a, (bit_count + 7u) / 8u); if (bytes == NULL) return false; for (uint32_t k = 0; k < bit_count / 2u; k++) { uint32_t i = k, j = bit_count - k - 1u; bool bi = ((bytes[i / 8u] >> (i % 8u)) & 1) != 0; bool bj = ((bytes[j / 8u] >> (j % 8u)) & 1) != 0; if (bi != bj) { bytes[i / 8u] ^= (uint8_t)(1u << (i % 8u)); bytes[j / 8u] ^= (uint8_t)(1u << (j % 8u)); } } return bigIntMutableReadTwosComplement(r, bytes, bit_count, is_signed); } // The low `bits` of a {lo, hi} pair, masked to the width. void bigIntMaskU128(uint64_t* lo, uint64_t* hi, uint32_t bits) { if (bits == 0) { *lo = 0; *hi = 0; } else if (bits < 64u) { *lo &= (UINT64_C(1) << bits) - 1u; *hi = 0; } else if (bits == 64u) { *hi = 0; } else if (bits < 128u) { *hi &= (UINT64_C(1) << (bits - 64u)) - 1u; } } void bigIntFromTwosCompU128(uint64_t lo, uint64_t hi, uint32_t bits, bool is_signed, uint64_t* space, BigIntConst* out) { if (bits <= 128u) bigIntMaskU128(&lo, &hi, bits); bool neg = false; if (is_signed && bits >= 1u && bits <= 128u) { uint64_t sign = bits > 64u ? ((hi >> (bits - 65u)) & 1u) : ((lo >> (bits - 1u)) & 1u); if (sign != 0) { // The magnitude of a negative two's-complement pattern is its // negation at the same width, which is what `big.int` stores // beside the sign flag. // The +1 carries into `hi` exactly when `lo` was zero. const bool carry = lo == 0; lo = ~lo + 1u; hi = ~hi + (carry ? 1u : 0u); bigIntMaskU128(&lo, &hi, bits); neg = true; } } space[0] = lo; space[1] = hi; out->limbs = space; out->limbs_len = hi != 0 ? 2u : 1u; out->positive = !neg; } bool bigIntMutableSaturate( BigIntMutable* r, BigIntConst a, bool is_signed, uint64_t bit_count) { if (bigIntFitsInTwosComp(a, is_signed, bit_count)) return true; return bigIntMutableSetTwosCompIntLimit(r, r->positive ? BIG_INT_LIMIT_MAX : BIG_INT_LIMIT_MIN, is_signed, bit_count); } // `addWrap` and `addSat` share upstream's prologue: both ignore the limbs // above the type's width (`truncate` would drop them anyway) and both have to // put back the carry `addCarry` reported when the operands were shorter than // the type. static BigIntConst clampToReqLimbs(BigIntConst a, uint32_t req_limbs) { if (a.limbs_len > req_limbs) a.limbs_len = req_limbs; return a; } bool bigIntMutableAddWrap(BigIntMutable* r, BigIntConst a, BigIntConst b, bool is_signed, uint64_t bit_count, bool* out_overflow) { uint32_t req_limbs = bigIntCalcTwosCompLimbCount(bit_count); bool carry; if (!bigIntMutableAddCarry(r, clampToReqLimbs(a, req_limbs), clampToReqLimbs(b, req_limbs), &carry)) return false; bool carry_truncated = false; if (carry) { uint32_t msl = a.limbs_len >= b.limbs_len ? a.limbs_len : b.limbs_len; if (msl < req_limbs) { if (msl + 1u > r->limbs_cap) return false; r->len = msl + 1u; r->limbs[msl] = 1; } else { carry_truncated = true; } } if (!bigIntFitsInTwosComp( bigIntMutableToConst(*r), is_signed, bit_count)) { if (!bigIntMutableTruncate( r, bigIntMutableToConst(*r), is_signed, bit_count)) return false; *out_overflow = true; return true; } *out_overflow = carry_truncated; return true; } bool bigIntMutableSubWrap(BigIntMutable* r, BigIntConst a, BigIntConst b, bool is_signed, uint64_t bit_count, bool* out_overflow) { return bigIntMutableAddWrap( r, a, bigIntConstNegate(b), is_signed, bit_count, out_overflow); } bool bigIntMutableAddSat(BigIntMutable* r, BigIntConst a, BigIntConst b, bool is_signed, uint64_t bit_count) { uint32_t req_limbs = bigIntCalcTwosCompLimbCount(bit_count); bool carry; if (!bigIntMutableAddCarry(r, clampToReqLimbs(a, req_limbs), clampToReqLimbs(b, req_limbs), &carry)) return false; if (carry) { uint32_t msl = a.limbs_len >= b.limbs_len ? a.limbs_len : b.limbs_len; if (msl < req_limbs) { if (msl + 1u > r->limbs_cap) return false; r->len = msl + 1u; r->limbs[msl] = 1; // `"Note: Saturation may still be required if msl == // req_limbs - 1"` -- so this falls through to `saturate`. } else { // `"Overflowed req_limbs, definitely saturate."` return bigIntMutableSetTwosCompIntLimit(r, r->positive ? BIG_INT_LIMIT_MAX : BIG_INT_LIMIT_MIN, is_signed, bit_count); } } return bigIntMutableSaturate( r, bigIntMutableToConst(*r), is_signed, bit_count); } bool bigIntMutableSubSat(BigIntMutable* r, BigIntConst a, BigIntConst b, bool is_signed, uint64_t bit_count) { return bigIntMutableAddSat( r, a, bigIntConstNegate(b), is_signed, bit_count); } // `Const.ctz`: `"Limbs are stored in little-endian order. Converting a // negative number to twos-complement flips all bits above the lowest set bit, // which does not affect the trailing zero count."` -- so the MAGNITUDE's // trailing zeros are the answer for either sign, clamped to `bits`. A zero // has no set bit and answers `bits`. uint64_t bigIntConstCtz(BigIntConst a, uint64_t bits) { if (bigIntEqlZero(a)) return bits; uint64_t result = 0; for (uint32_t i = 0; i < a.limbs_len; i++) { uint32_t limb_tz = limbCtz(a.limbs[i]); result += limb_tz; if (limb_tz != 64u) break; } return result < bits ? result : bits; } // `Mutable.shiftLeftSat`: `a << shift` clamped to the type's own limits // instead of wrapped. Needs `calcTwosCompLimbCount(bit_count)` limbs. bool bigIntMutableShiftLeftSat(BigIntMutable* r, BigIntConst a, uint64_t shift, bool is_signed, uint64_t bit_count) { // `"Special case: When the argument is negative, but the result is // supposed to be unsigned, return 0 in all cases."` if (!a.positive && !is_signed) return bigIntMutableSet(r, 0, true); // `"Handle shifts larger than the target type. This also deals with 0-bit // integers."` if (bit_count <= shift) { if (bigIntEqlZero(a)) return bigIntMutableSet(r, 0, true); return bigIntMutableSetTwosCompIntLimit(r, a.positive ? BIG_INT_LIMIT_MAX : BIG_INT_LIMIT_MIN, is_signed, bit_count); } // `"Check whether the shift is going to overflow ... when (in 2s // complement) any bit above `bit_count - shift` is set in the unshifted // value. Note, the sign bit is not counted here."` uint64_t checkbit = bit_count - shift - (is_signed ? 1u : 0u); if (checkbit >= (uint64_t)a.limbs_len * 64u) { // `"`checkbit` is outside the range of a, so definitely no overflow // will take place. We can defer to a normal shift."` return bigIntMutableShiftLeft(r, a, shift); } if (checkbit < (uint64_t)(a.limbs_len - 1u) * 64u) { // `"`checkbit` is not in the most significant limb."` return bigIntMutableSetTwosCompIntLimit(r, a.positive ? BIG_INT_LIMIT_MAX : BIG_INT_LIMIT_MIN, is_signed, bit_count); } if ((a.limbs[a.limbs_len - 1u] >> (uint32_t)(checkbit & 63u)) != 0) { return bigIntMutableSetTwosCompIntLimit(r, a.positive ? BIG_INT_LIMIT_MAX : BIG_INT_LIMIT_MIN, is_signed, bit_count); } // `"This shift should not be able to overflow, so invoke llshl and // normalize manually to avoid the extra required limb."` uint64_t need = (uint64_t)a.limbs_len + shift / 64u; if (need > (uint64_t)r->limbs_cap) return false; uint32_t new_len = llshl(r->limbs, a.limbs, a.limbs_len, shift); bigIntMutableNormalize(r, new_len); r->positive = a.positive; return true; } // Bit `i` of the magnitude, zero past its limbs. static bool bigIntMagBit(BigIntConst a, uint64_t i) { uint64_t limb = i / 64u; return limb < a.limbs_len && ((a.limbs[limb] >> (i % 64u)) & 1u) != 0; } bool bigIntToFloatBits(BigIntConst a, uint32_t float_bits, uint64_t* out_lo, uint64_t* out_hi, bool* out_exact) { // `std.math.FloatRepr(Float)`: significand bits including the integer // bit, and the largest unbiased exponent. uint32_t prec, emax, frac_bits; switch (float_bits) { case 16: prec = 11; emax = 15; frac_bits = 10; break; case 32: prec = 24; emax = 127; frac_bits = 23; break; case 64: prec = 53; emax = 1023; frac_bits = 52; break; case 80: prec = 64; emax = 16383; frac_bits = 64; break; case 128: prec = 113; emax = 16383; frac_bits = 112; break; default: return false; } uint32_t len = a.limbs_len; while (len > 0 && a.limbs[len - 1u] == 0) len--; BigIntConst n = { a.limbs, len, a.positive }; uint64_t sign = a.positive ? 0u : 1u; *out_lo = 0; *out_hi = 0; bool exact = true; if (len == 0) { // "if (normalized_abs.eqlZero()) return .{ if (self.positive) 0.0 // else -0.0, .exact };" if (float_bits == 128 || float_bits == 80) *out_hi = sign << (float_bits == 128 ? 63u : 15u); else *out_lo = sign << (float_bits - 1u); *out_exact = true; return true; } uint64_t width = bigIntBitCountAbs(n); // The significand: the top `prec` bits, rounded to nearest, ties to // even, on the round bit and the sticky or of everything below it. uint64_t m_lo = 0, m_hi = 0; uint64_t shift = width > prec ? width - prec : 0; // The significand normalized to `prec` bits, its integer bit at // `prec - 1`: the magnitude's top bits, zero-filled below a narrower one. for (uint64_t k = 0; k < prec; k++) { uint64_t bit = k < width && bigIntMagBit(n, width - 1u - k); m_hi = (m_hi << 1) | (m_lo >> 63); m_lo = (m_lo << 1) | bit; } uint64_t exp = width - 1u; if (shift > 0) { bool round = bigIntMagBit(n, shift - 1u); bool sticky = false; for (uint64_t k = 0; k + 1u < shift && !sticky; k++) sticky = bigIntMagBit(n, k); exact = !round && !sticky; if (round && (sticky || (m_lo & 1u))) { m_lo++; if (m_lo == 0) m_hi++; // A carry out of the top: 2^prec, i.e. 1.0 at the next exponent. bool carry = prec >= 64 ? (m_hi >> (prec - 64u)) != 0 : (m_lo >> prec) != 0; if (carry) { m_lo = (m_lo >> 1) | (m_hi << 63); m_hi >>= 1; exp++; } } } uint64_t bias = emax; uint64_t biased = exp + bias; bool inf = exp > emax; if (inf) { exact = false; biased = 2u * emax + 1u; m_lo = 0; m_hi = 0; } if (float_bits == 80) { // The explicit integer bit stays in the 64-bit significand. *out_lo = inf ? (UINT64_C(1) << 63) : m_lo; *out_hi = (sign << 15) | biased; *out_exact = exact; return true; } if (float_bits == 128) { *out_lo = m_lo; *out_hi = (m_hi & ((UINT64_C(1) << 48) - 1u)) | (biased << 48) | (sign << 63); *out_exact = exact; return true; } uint64_t frac_mask = (UINT64_C(1) << frac_bits) - 1u; *out_lo = (m_lo & frac_mask) | (biased << frac_bits) | (sign << (float_bits - 1u)); *out_exact = exact; return true; } // ── Conversions to fixed-width words ──────────────────────────────────────── // `big_int.toInt(u32)`: the rung test upstream's `.int` arm asks at five of // its six type rungs and once more in its generic tail. A negative never // passes it (`error.NegativeIntoUnsigned`), which is why no rung below needs // to name the sign. bool bigIntToU32(BigIntConst a, uint32_t* out) { uint64_t u; if (!bigIntToU64(a, &u) || u > UINT32_MAX) return false; *out = (uint32_t)u; return true; } // `big_int.toInt(i32)`, bit-cast to the `u32` item `data` word the two // signed rungs store. bool bigIntToBitCastI32(BigIntConst a, uint32_t* out) { int64_t i; if (!bigIntToI64(a, &i) || i < INT32_MIN || i > INT32_MAX) return false; *out = (uint32_t)(int32_t)i; return true; } // (part of) Value.zig:toFloat // `big.int.Const.toFloat(f128, .nearest_even)` as `Value.toFloat` asks it, // at any width, as the four low-word-first words; returns its Exactness. bool bigIntToF128Words(BigIntConst v, uint32_t out[4]) { uint64_t lo, hi; bool exact; bigIntToFloatBits(v, 128, &lo, &hi, &exact); out[0] = (uint32_t)lo; out[1] = (uint32_t)(lo >> 32); out[2] = (uint32_t)hi; out[3] = (uint32_t)(hi >> 32); return exact; } /* ---- end big_int.c ---- */ /* ---- begin builtin.c ---- */ // builtin.c — ported from src/Builtin.zig `append`/`generate`, specialized to // zig1's fixed wasm32-wasi compilation profiles. Every value that // `Builtin.append` reads from the resolved `std.Target` / compilation config // is a constant selected by the profile; all supported profiles pin the same // target. The formatting *structure* (the sequence and shape of each printed // block) is a faithful transcription of `append`. #ifndef ZIG1_H #include "zig1.h" #endif #include // upstream_gen.h carries both from stage1/upstream.zig: the version string // and its `std.SemanticVersion` fields as `std.Build.Step.Options` renders // them. #if !defined(ZIG1_UPSTREAM_VERSION) || !defined(ZIG1_UPSTREAM_SEMVER) #error \ "ZIG1_UPSTREAM_VERSION and ZIG1_UPSTREAM_SEMVER come from upstream_gen.h" #endif // --- fixed target config (the `Builtin` field values shared by profiles) ---- // Mirrors the target portion of the `@This()` struct passed to // `Builtin.append`. A general port would read these from `Compilation.Config` // and the resolved `std.Target`. #define B_ZIG_BACKEND "stage2_wasm" // use_llvm=false, wasm backend #define B_LINK_MODE "static" #define B_UNWIND_TABLES "async" #define B_SINGLE_THREADED "true" #define B_ARCH "wasm32" #define B_ARCH_FAMILY "wasm" #define B_CPU_MODEL "lime1" #define B_OS_TAG "wasi" #define B_OBJECT_FORMAT "wasm" #define B_LINK_LIBC "false" #define B_LINK_LIBCPP "false" #define B_ERROR_TRACING "false" // have_error_return_tracing #define B_VALGRIND "false" #define B_SANITIZE_THREAD "false" #define B_FUZZ "false" #define B_PIC "false" #define B_PIE "false" #define B_CODE_MODEL "default" #define B_WASI_EXEC_MODEL "command" // `std.Build.Step.Options` source corresponding to build.zig's // addWasiUpdateStep. Keep the option order aligned with build.zig so the // profile is mechanically auditable. This storage is borrowed; the named // module loader copies it before giving ownership to Ast. static const char builtinBootstrapBuildOptions[] = "pub const mem_leak_frames: u32 = 0;\n" "pub const have_llvm: bool = false;\n" "pub const debug_gpa: bool = false;\n" "pub const version: [:0]const u8 = " "\"" ZIG1_UPSTREAM_VERSION "\";\n" "pub const semver: @import(\"std\").SemanticVersion = " ".{\n" ZIG1_UPSTREAM_SEMVER "};\n" "pub const enable_debug_extensions: bool = false;\n" "pub const enable_logging: bool = false;\n" "pub const enable_tracy: bool = false;\n" "pub const enable_tracy_callstack: bool = false;\n" "pub const enable_tracy_allocation: bool = false;\n" "pub const tracy_callstack_depth: u32 = 0;\n" "pub const value_tracing: bool = false;\n" "pub const @\"src.dev.Env\" = enum (u5) {\n" " bootstrap = 0,\n" " core = 1,\n" " full = 2,\n" " c_source = 3,\n" " ast_gen = 4,\n" " sema = 5,\n" " @\"aarch64-linux\" = 6,\n" " cbe = 7,\n" " llvm = 8,\n" " @\"loongarch-linux\" = 9,\n" " @\"powerpc-linux\" = 10,\n" " @\"riscv64-linux\" = 11,\n" " spirv = 12,\n" " spork8 = 13,\n" " wasm = 14,\n" " @\"x86_64-linux\" = 15,\n" " @\"x86_64-windows\" = 16,\n" "};\n" "pub const dev: @\"src.dev.Env\" = .bootstrap;\n" "pub const @\"build.IoMode\" = enum (u1) {\n" " threaded = 0,\n" " evented = 1,\n" "};\n" "pub const io_mode: @\"build.IoMode\" = .threaded;\n" "pub const @\"build.ValueInterpretMode\" = enum (u1) {\n" " direct = 0,\n" " by_name = 1,\n" "};\n" "pub const value_interpret_mode: @\"build.ValueInterpretMode\" = " ".by_name;\n"; const char* builtinBootstrapBuildOptionsSource(void) { return builtinBootstrapBuildOptions; } // Enabled CPU features, in `arch.allFeaturesList()` order (the order in which // `append`'s feature loop emits them). For the fixed `wasm32` + `lime1` model // this set is constant. static const char* const builtinAirgenEnabledFeatures[] = { "bulk_memory_opt", "call_indirect_overlong", "extended_const", "multivalue", "mutable_globals", "nontrapping_fptoint", "sign_ext", }; // build.zig:addWasiUpdateStep resolves // `baseline+nontrapping_bulk_memory_len0`. wasm32's baseline CPU model is // lime1; `Builtin.append` prints enabled features in enum order. static const char* const builtinBootstrapEnabledFeatures[] = { "bulk_memory_opt", "call_indirect_overlong", "extended_const", "multivalue", "mutable_globals", "nontrapping_bulk_memory_len0", "nontrapping_fptoint", "sign_ext", }; typedef struct { const char* version; const char* output_mode; const char* is_test; const char* abi; const char* optimize; // std.lang.Optimize tag const char* strip_debug_info; const char* omit_frame_pointer; bool emit_test_functions; const char* const* enabled_features; uint32_t enabled_features_len; } BuiltinProfileConfig; static const BuiltinProfileConfig builtinAirgenProfile = { .version = "0.0.0-zig1-airgen", .output_mode = "Obj", .is_test = "false", .abi = "musl", .optimize = "small", .strip_debug_info = "true", .omit_frame_pointer = "true", .emit_test_functions = false, .enabled_features = builtinAirgenEnabledFeatures, .enabled_features_len = (uint32_t)(sizeof(builtinAirgenEnabledFeatures) / sizeof(builtinAirgenEnabledFeatures[0])), }; static const BuiltinProfileConfig builtinBootstrapProfile = { .version = ZIG1_UPSTREAM_VERSION, .output_mode = "Exe", .is_test = "false", .abi = "musl", .optimize = "small", .strip_debug_info = "true", .omit_frame_pointer = "true", .emit_test_functions = false, .enabled_features = builtinBootstrapEnabledFeatures, .enabled_features_len = (uint32_t)(sizeof(builtinBootstrapEnabledFeatures) / sizeof(builtinBootstrapEnabledFeatures[0])), }; // What upstream's wasm32-wasi behavior-test build renders, not inferred from // the bootstrap profile. This ReleaseSmall profile eventually drives upstream // test discovery and the stock terminal runner in the interpreter. static const BuiltinProfileConfig builtinTestProfile = { .version = ZIG1_UPSTREAM_VERSION, .output_mode = "Exe", .is_test = "true", .abi = "none", .optimize = "small", .strip_debug_info = "true", .omit_frame_pointer = "true", .emit_test_functions = true, .enabled_features = builtinAirgenEnabledFeatures, .enabled_features_len = (uint32_t)(sizeof(builtinAirgenEnabledFeatures) / sizeof(builtinAirgenEnabledFeatures[0])), }; // WASI OS version range (semver). `target.os.versionRange()` for wasi. #define B_OS_SEMVER_MIN_MAJOR 0u #define B_OS_SEMVER_MIN_MINOR 1u #define B_OS_SEMVER_MIN_PATCH 0u #define B_OS_SEMVER_MAX_MAJOR 0u #define B_OS_SEMVER_MAX_MINOR 3u #define B_OS_SEMVER_MAX_PATCH 0u // --- growable output buffer ------------------------------------------------ typedef struct { char* data; size_t len; size_t cap; } Buf; static void builtinBufReserve(Buf* b, size_t extra) { if (b->len + extra + 1 <= b->cap) return; size_t cap = b->cap ? b->cap : 2048; while (b->len + extra + 1 > cap) cap *= 2; b->data = zig1Realloc(b->data, cap); b->cap = cap; } static void builtinBufAdd(Buf* b, const char* s) { size_t n = strlen(s); builtinBufReserve(b, n); memcpy(b->data + b->len, s, n); b->len += n; b->data[b->len] = '\0'; } static void builtinBufAddUint(Buf* b, uint32_t v) { char tmp[16]; snprintf(tmp, sizeof(tmp), "%u", v); builtinBufAdd(b, tmp); } // `std.zig.fmtIdPU(name)` renders an identifier bare unless it needs `@""` // escaping (invalid identifier bytes or a keyword). Every tag name emitted for // the fixed bootstrap target (arch/os/abi/feature/enum-literal names) is a // valid, non-keyword identifier, so escaping never triggers here; emit bare. static void builtinBufAddId(Buf* b, const char* name) { builtinBufAdd(b, name); } static const BuiltinProfileConfig* builtinProfileConfig( CompilationProfile profile_kind) { switch (profile_kind) { case COMPILATION_PROFILE_AIRGEN: return &builtinAirgenProfile; case COMPILATION_PROFILE_BOOTSTRAP_WASI: return &builtinBootstrapProfile; case COMPILATION_PROFILE_TEST_WASI: return &builtinTestProfile; } UNREACHABLE("builtin: invalid compilation profile"); ZIG1_TCC_RETURN(NULL); } char* builtinGenerateForProfile(CompilationProfile profile_kind) { const BuiltinProfileConfig* profile = builtinProfileConfig(profile_kind); Buf b = { 0 }; // Header: version, backend, and the scalar config decls, up to the open of // the CPU feature set. Mirrors `append`'s first `buffer.print`. builtinBufAdd(&b, "const std = @import(\"std\");\n"); builtinBufAdd(&b, "pub const zig_version = " "std.SemanticVersion.parse(zig_version_string) catch unreachable;\n"); builtinBufAdd(&b, "pub const zig_version_string = \""); builtinBufAdd(&b, profile->version); builtinBufAdd(&b, "\";\n"); builtinBufAdd(&b, "pub const zig_backend = std.lang.CompilerBackend."); builtinBufAddId(&b, B_ZIG_BACKEND); builtinBufAdd(&b, ";\n\n"); builtinBufAdd(&b, "pub const output_mode: std.lang.OutputMode = ."); builtinBufAddId(&b, profile->output_mode); builtinBufAdd(&b, ";\n"); builtinBufAdd(&b, "pub const link_mode: std.lang.LinkMode = ."); builtinBufAddId(&b, B_LINK_MODE); builtinBufAdd(&b, ";\n"); builtinBufAdd(&b, "pub const unwind_tables: std.lang.UnwindTables = ."); builtinBufAddId(&b, B_UNWIND_TABLES); builtinBufAdd(&b, ";\n"); builtinBufAdd(&b, "pub const is_test = "); builtinBufAdd(&b, profile->is_test); builtinBufAdd(&b, ";\n"); builtinBufAdd(&b, "pub const single_threaded = " B_SINGLE_THREADED ";\n"); builtinBufAdd(&b, "/// Deprecated; to be removed in 0.18.0. Use `target.abi` " "instead.\n"); builtinBufAdd(&b, "pub const abi: std.Target.Abi = ."); builtinBufAddId(&b, profile->abi); builtinBufAdd(&b, ";\n"); builtinBufAdd(&b, "/// Deprecated; to be removed in 0.18.0. Use `target.cpu` " "instead.\n"); builtinBufAdd(&b, "pub const cpu: std.Target.Cpu = .{\n"); builtinBufAdd(&b, " .arch = ."); builtinBufAddId(&b, B_ARCH); builtinBufAdd(&b, ",\n"); builtinBufAdd(&b, " .model = &std.Target."); builtinBufAddId(&b, B_ARCH_FAMILY); builtinBufAdd(&b, ".cpu."); builtinBufAddId(&b, B_CPU_MODEL); builtinBufAdd(&b, ",\n"); builtinBufAdd(&b, " .features = std.Target."); builtinBufAddId(&b, B_ARCH_FAMILY); builtinBufAdd(&b, ".featureSet(&.{\n"); // Feature loop: `append` emits one ` .,\n` per enabled // feature in `allFeaturesList()` order. for (uint32_t i = 0; i < profile->enabled_features_len; i++) { builtinBufAdd(&b, " ."); builtinBufAddId(&b, profile->enabled_features[i]); builtinBufAdd(&b, ",\n"); } // Close the feature set + cpu, then the OS header up to `.version_range`. builtinBufAdd(&b, " }),\n"); builtinBufAdd(&b, "};\n"); builtinBufAdd(&b, "/// Deprecated; to be removed in 0.18.0. Use `target.os` " "instead.\n"); builtinBufAdd(&b, "pub const os: std.Target.Os = .{\n"); builtinBufAdd(&b, " .tag = ."); builtinBufAddId(&b, B_OS_TAG); builtinBufAdd(&b, ",\n"); builtinBufAdd(&b, " .version_range = .{"); // OS version range: the wasi `.semver` branch of `append`'s switch. builtinBufAdd(&b, " .semver = .{\n"); builtinBufAdd(&b, " .min = .{\n"); builtinBufAdd(&b, " .major = "); builtinBufAddUint(&b, B_OS_SEMVER_MIN_MAJOR); builtinBufAdd(&b, ",\n"); builtinBufAdd(&b, " .minor = "); builtinBufAddUint(&b, B_OS_SEMVER_MIN_MINOR); builtinBufAdd(&b, ",\n"); builtinBufAdd(&b, " .patch = "); builtinBufAddUint(&b, B_OS_SEMVER_MIN_PATCH); builtinBufAdd(&b, ",\n"); builtinBufAdd(&b, " },\n"); builtinBufAdd(&b, " .max = .{\n"); builtinBufAdd(&b, " .major = "); builtinBufAddUint(&b, B_OS_SEMVER_MAX_MAJOR); builtinBufAdd(&b, ",\n"); builtinBufAdd(&b, " .minor = "); builtinBufAddUint(&b, B_OS_SEMVER_MAX_MINOR); builtinBufAdd(&b, ",\n"); builtinBufAdd(&b, " .patch = "); builtinBufAddUint(&b, B_OS_SEMVER_MAX_PATCH); builtinBufAdd(&b, ",\n"); builtinBufAdd(&b, " },\n"); builtinBufAdd(&b, " }},\n"); // `};` (close os), then the `target` struct block (dynamic_linker = // .none). builtinBufAdd(&b, "};\n"); builtinBufAdd(&b, "pub const target: std.Target = .{\n"); builtinBufAdd(&b, " .cpu = cpu,\n"); builtinBufAdd(&b, " .os = os,\n"); builtinBufAdd(&b, " .abi = abi,\n"); builtinBufAdd(&b, " .ofmt = object_format,\n"); builtinBufAdd(&b, " .dynamic_linker = .none,\n"); builtinBufAdd(&b, "};\n"); // Trailing scalar config decls (object_format .. omit_frame_pointer). builtinBufAdd(&b, "/// Deprecated; to be removed in 0.18.0. Use `target.ofmt` " "instead.\n"); builtinBufAdd(&b, "pub const object_format: std.Target.ObjectFormat = ."); builtinBufAddId(&b, B_OBJECT_FORMAT); builtinBufAdd(&b, ";\n"); builtinBufAdd(&b, "/// Deprecated, to be removed after 0.18.0\n"); builtinBufAdd(&b, "pub const mode = optimize;\n"); builtinBufAdd(&b, "pub const optimize: std.lang.Optimize = ."); builtinBufAddId(&b, profile->optimize); builtinBufAdd(&b, ";\n"); builtinBufAdd(&b, "pub const link_libc = " B_LINK_LIBC ";\n"); builtinBufAdd(&b, "pub const link_libcpp = " B_LINK_LIBCPP ";\n"); builtinBufAdd( &b, "pub const have_error_return_tracing = " B_ERROR_TRACING ";\n"); builtinBufAdd(&b, "pub const valgrind_support = " B_VALGRIND ";\n"); builtinBufAdd(&b, "pub const sanitize_thread = " B_SANITIZE_THREAD ";\n"); builtinBufAdd(&b, "pub const fuzz = " B_FUZZ ";\n"); builtinBufAdd(&b, "pub const position_independent_code = " B_PIC ";\n"); builtinBufAdd( &b, "pub const position_independent_executable = " B_PIE ";\n"); builtinBufAdd(&b, "pub const strip_debug_info = "); builtinBufAdd(&b, profile->strip_debug_info); builtinBufAdd(&b, ";\n"); builtinBufAdd(&b, "pub const code_model: std.lang.CodeModel = ."); builtinBufAddId(&b, B_CODE_MODEL); builtinBufAdd(&b, ";\n"); builtinBufAdd(&b, "pub const omit_frame_pointer = "); builtinBufAdd(&b, profile->omit_frame_pointer); builtinBufAdd(&b, ";\n"); // `if (target.os.tag == .wasi)`: emit the wasi exec model decl. builtinBufAdd(&b, "pub const wasi_exec_model: std.lang.WasiExecModel = ."); builtinBufAddId(&b, B_WASI_EXEC_MODEL); builtinBufAdd(&b, ";\n"); // `if (opts.is_test)`: Builtin.append exposes a placeholder which // `populateTestFunctions` replaces after test declaration discovery. if (profile->emit_test_functions) builtinBufAdd(&b, "pub var test_functions: []const std.lang.TestFn = &.{}; " "// overwritten later\n"); return b.data; } // This file's macros end with it (zig1.c is one translation unit). #undef B_ZIG_BACKEND #undef B_LINK_MODE #undef B_UNWIND_TABLES #undef B_SINGLE_THREADED #undef B_ARCH #undef B_ARCH_FAMILY #undef B_CPU_MODEL #undef B_OS_TAG #undef B_OBJECT_FORMAT #undef B_LINK_LIBC #undef B_LINK_LIBCPP #undef B_ERROR_TRACING #undef B_VALGRIND #undef B_SANITIZE_THREAD #undef B_FUZZ #undef B_PIC #undef B_PIE #undef B_CODE_MODEL #undef B_WASI_EXEC_MODEL #undef B_OS_SEMVER_MIN_MAJOR #undef B_OS_SEMVER_MIN_MINOR #undef B_OS_SEMVER_MIN_PATCH #undef B_OS_SEMVER_MAX_MAJOR #undef B_OS_SEMVER_MAX_MINOR #undef B_OS_SEMVER_MAX_PATCH /* ---- end builtin.c ---- */ /* ---- begin builtin_fn.c ---- */ // builtin_fn.c — lib/std/zig/BuiltinFn.zig: every builtin's BuiltinFn record, // transcribed from `list` in upstream's order. #ifndef ZIG1_H #include "zig1.h" #endif #include // BuiltinFn.list, keyed by the builtin's name. static const struct { const char* name; BuiltinFn info; } builtin_fn_list[] = { { "@addWithOverflow", { BFN_add_with_overflow, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@addrSpaceCast", { BFN_addrspace_cast, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@alignCast", { BFN_align_cast, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@alignOf", { BFN_align_of, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@as", { BFN_as, BUILTIN_FN_EVAL_TO_ERROR_MAYBE, false, false, 2 } }, { "@atomicLoad", { BFN_atomic_load, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 3 } }, { "@atomicRmw", { BFN_atomic_rmw, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 5 } }, { "@atomicStore", { BFN_atomic_store, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 4 } }, { "@bitCast", { BFN_bit_cast, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@bitOffsetOf", { BFN_bit_offset_of, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@intFromBool", { BFN_int_from_bool, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@bitSizeOf", { BFN_bit_size_of, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@branchHint", { BFN_branch_hint, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 1 } }, { "@breakpoint", { BFN_breakpoint, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 0 } }, { "@disableInstrumentation", { BFN_disable_instrumentation, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 0 } }, { "@disableIntrinsics", { BFN_disable_intrinsics, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 0 } }, { "@mulAdd", { BFN_mul_add, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 4 } }, { "@byteSwap", { BFN_byte_swap, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@bitReverse", { BFN_bit_reverse, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@offsetOf", { BFN_offset_of, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@call", { BFN_call, BUILTIN_FN_EVAL_TO_ERROR_MAYBE, false, false, 3 } }, { "@clz", { BFN_clz, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@cmpxchgStrong", { BFN_cmpxchg_strong, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 6 } }, { "@cmpxchgWeak", { BFN_cmpxchg_weak, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 6 } }, { "@compileError", { BFN_compile_error, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@compileLog", { BFN_compile_log, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, -1 } }, { "@constCast", { BFN_const_cast, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@ctz", { BFN_ctz, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@cVaArg", { BFN_c_va_arg, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 2 } }, { "@cVaCopy", { BFN_c_va_copy, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 1 } }, { "@cVaEnd", { BFN_c_va_end, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 1 } }, { "@cVaStart", { BFN_c_va_start, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 0 } }, { "@divExact", { BFN_div_exact, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@divFloor", { BFN_div_floor, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@divCeil", { BFN_div_ceil, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@divTrunc", { BFN_div_trunc, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@embedFile", { BFN_embed_file, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@intFromEnum", { BFN_int_from_enum, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@errorName", { BFN_error_name, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@errorReturnTrace", { BFN_error_return_trace, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 0 } }, { "@intFromError", { BFN_int_from_error, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@errorCast", { BFN_error_cast, BUILTIN_FN_EVAL_TO_ERROR_MAYBE, false, false, 1 } }, { "@export", { BFN_export, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@extern", { BFN_extern, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@field", { BFN_field, BUILTIN_FN_EVAL_TO_ERROR_MAYBE, true, false, 2 } }, { "@fieldParentPtr", { BFN_field_parent_ptr, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@FieldType", { BFN_FieldType, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@floatCast", { BFN_float_cast, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@intFromFloat", { BFN_int_from_float, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@frame", { BFN_frame, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 0 } }, { "@Frame", { BFN_Frame, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@frameAddress", { BFN_frame_address, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 0 } }, { "@hasDecl", { BFN_has_decl, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@hasField", { BFN_has_field, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@import", { BFN_import, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@inComptime", { BFN_in_comptime, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 0 } }, { "@intCast", { BFN_int_cast, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@backingInt", { BFN_backing_int, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@fromBackingInt", { BFN_from_backing_int, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@enumFromInt", { BFN_enum_from_int, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@errorFromInt", { BFN_error_from_int, BUILTIN_FN_EVAL_TO_ERROR_ALWAYS, false, false, 1 } }, { "@floatFromInt", { BFN_float_from_int, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@ptrFromInt", { BFN_ptr_from_int, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@max", { BFN_max, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, -1 } }, { "@memcpy", { BFN_memcpy, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@memset", { BFN_memset, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@memmove", { BFN_memmove, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@min", { BFN_min, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, -1 } }, { "@wasmMemorySize", { BFN_wasm_memory_size, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@wasmMemoryGrow", { BFN_wasm_memory_grow, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@mod", { BFN_mod, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@mulWithOverflow", { BFN_mul_with_overflow, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@panic", { BFN_panic, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@popCount", { BFN_pop_count, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@prefetch", { BFN_prefetch, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@ptrCast", { BFN_ptr_cast, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@intFromPtr", { BFN_int_from_ptr, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@rem", { BFN_rem, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@returnAddress", { BFN_return_address, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 0 } }, { "@select", { BFN_select, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 4 } }, { "@setEvalBranchQuota", { BFN_set_eval_branch_quota, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@setFloatMode", { BFN_set_float_mode, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@setRuntimeSafety", { BFN_set_runtime_safety, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@shlExact", { BFN_shl_exact, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@shlWithOverflow", { BFN_shl_with_overflow, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@shrExact", { BFN_shr_exact, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@shuffle", { BFN_shuffle, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 4 } }, { "@sizeOf", { BFN_size_of, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@splat", { BFN_splat, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@reduce", { BFN_reduce, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@src", { BFN_src, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 0 } }, { "@sqrt", { BFN_sqrt, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@sin", { BFN_sin, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@cos", { BFN_cos, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@tan", { BFN_tan, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@exp", { BFN_exp, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@exp2", { BFN_exp2, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@log", { BFN_log, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@log2", { BFN_log2, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@log10", { BFN_log10, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@abs", { BFN_abs, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@floor", { BFN_floor, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@ceil", { BFN_ceil, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@trunc", { BFN_trunc, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@round", { BFN_round, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@subWithOverflow", { BFN_sub_with_overflow, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@tagName", { BFN_tag_name, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@This", { BFN_This, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 0 } }, { "@trap", { BFN_trap, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 0 } }, { "@truncate", { BFN_truncate, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@EnumLiteral", { BFN_EnumLiteral, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 0 } }, { "@Int", { BFN_Int, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@Tuple", { BFN_Tuple, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@Pointer", { BFN_Pointer, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 4 } }, { "@Fn", { BFN_Fn, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 4 } }, { "@Struct", { BFN_Struct, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 5 } }, { "@Union", { BFN_Union, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 5 } }, { "@Enum", { BFN_Enum, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 4 } }, { "@SpirvType", { BFN_SpirvType, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@typeInfo", { BFN_type_info, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@typeName", { BFN_type_name, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@TypeOf", { BFN_TypeOf, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, -1 } }, { "@unionInit", { BFN_union_init, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 3 } }, { "@Vector", { BFN_Vector, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 2 } }, { "@volatileCast", { BFN_volatile_cast, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, false, 1 } }, { "@workItemId", { BFN_work_item_id, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 1 } }, { "@workGroupSize", { BFN_work_group_size, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 1 } }, { "@workGroupId", { BFN_work_group_id, BUILTIN_FN_EVAL_TO_ERROR_NEVER, false, true, 1 } }, }; // BuiltinFn.list.get: a linear scan where upstream uses a StaticStringMap. const BuiltinFn* builtinFnListGet(const char* name, uint32_t name_len) { for (size_t i = 0; i < sizeof(builtin_fn_list) / sizeof(builtin_fn_list[0]); i++) { if (strlen(builtin_fn_list[i].name) == name_len && memcmp(builtin_fn_list[i].name, name, name_len) == 0) return &builtin_fn_list[i].info; } return NULL; } const BuiltinFn* builtinFnListGetToken(const Ast* tree, AstTokenIndex token) { const char* name; uint32_t name_len; astTokenSlice(tree, token, &name, &name_len); return builtinFnListGet(name, name_len); } /* ---- end builtin_fn.c ---- */ /* ---- begin common.c ---- */ #ifndef ZIG1_COMMON_H #include "common.h" #endif #ifndef ZIG1_WYHASH_H #include "wyhash.h" #endif #include #include #include #include Zig1ErrorSource zig1_error_source = { NULL, NULL, 0, 0 }; // `dir/path:line:column` of zig1_error_source, as zig1CompileError prints it. static void zig1PrintErrorSource(const Zig1ErrorSource* src) { if (src->dir != NULL && src->dir[0] != '\0' && strcmp(src->dir, ".") != 0) fprintf(stderr, "%s/", src->dir); fputs(src->path, stderr); if (src->line != 0) fprintf(stderr, ":%" PRIu32 ":%" PRIu32, src->line, src->column); } void zig1AbortLoudly( const char* kind, const char* msg, const char* file, int line) { fprintf(stderr, "%s: %s (%s:%d)\n", kind, msg, file, line); if (zig1_error_source.path != NULL) { fputs("zig1: while analyzing the declaration at ", stderr); zig1PrintErrorSource(&zig1_error_source); fputc('\n', stderr); } fputs(strcmp(kind, "UNREACHABLE") == 0 ? "zig1: this is a bug in zig1, not an error in the program\n" : "zig1: zig1 does not implement this upstream function or " "feature; not an error in the program\n", stderr); abort(); } void zig1ErrorSourceClear(void) { zig1_error_source = (Zig1ErrorSource) { NULL, NULL, 0, 0 }; } void zig1ErrorSourceAt(const char* source, uint32_t offset) { uint32_t line = 1; uint32_t column = 1; for (uint32_t i = 0; i < offset && source[i] != '\0'; i++) { if (source[i] == '\n') { line++; column = 1; } else { column++; } } zig1_error_source.line = line; zig1_error_source.column = column; } void zig1CompileError(const char* msg) { if (zig1_error_source.path != NULL) { zig1PrintErrorSource(&zig1_error_source); fputs(": ", stderr); } fprintf(stderr, "error: %s\n", msg); exit(ZIG1_EXIT_COMPILE_ERROR); } const char* zig1CompileErrorMessageV(const char* fmt, va_list args) { static char msg[512]; vsnprintf(msg, sizeof(msg), fmt, args); return msg; } void zig1CompileErrorFmt(const char* fmt, ...) { va_list args; va_start(args, fmt); const char* msg = zig1CompileErrorMessageV(fmt, args); va_end(args); zig1CompileError(msg); } void zig1Fatal(const char* fmt, ...) { va_list args; va_start(args, fmt); fputs("zig1: ", stderr); vfprintf(stderr, fmt, args); fputc('\n', stderr); va_end(args); exit(2); } void zig1Oom(void) { zig1Fatal("out of memory"); } char* zig1Strndup(const char* s, size_t len) { char* out = zig1Malloc(len + 1); memcpy(out, s, len); out[len] = '\0'; return out; } char* zig1PathJoin(const char* dir, const char* sub, size_t sub_len) { if (dir == NULL || dir[0] == '\0') return zig1Strndup(sub, sub_len); size_t dir_len = strlen(dir); char* out = zig1Malloc(dir_len + 1 + sub_len + 1); memcpy(out, dir, dir_len); out[dir_len] = '/'; memcpy(out + dir_len + 1, sub, sub_len); out[dir_len + 1 + sub_len] = '\0'; return out; } char* zig1ReadFile(const char* path, size_t* out_len) { FILE* f = fopen(path, "rb"); if (!f) return NULL; // fseek, not rewind: rewind cannot report a failure. long size = fseek(f, 0, SEEK_END) == 0 ? ftell(f) : -1; if (size < 0) { // a pipe (zig1's own -M files, `-Mfoo=<(cmd)`): to EOF size_t len = 0, cap = 4096; char* buf = zig1Malloc(cap + 1); while (true) { ssize_t got = read(fileno(f), buf + len, (unsigned)(cap - len)); if (got < 0 && errno == EINTR) continue; if (got < 0) zig1Fatal("cannot read '%s'", path); if (got == 0) break; len += (size_t)got; // Past u32 the file is too big; checked before `cap` doubles, // which a 32-bit size_t would wrap. if (len >= UINT32_MAX) zig1Fatal("cannot read '%s'", path); if (len == cap) buf = zig1Realloc(buf, (cap *= 2) + 1); } fclose(f); buf[len] = '\0'; *out_len = len; return buf; } if ((uint64_t)size >= UINT32_MAX || fseek(f, 0, SEEK_SET) != 0) { zig1Fatal("cannot read '%s'", path); } size_t len = (size_t)size; char* buf = zig1Malloc(len + 1); size_t got = fread(buf, 1, len, f); fclose(f); if (got != len) { zig1Fatal("short read on '%s'", path); } buf[len] = '\0'; *out_len = len; return buf; } static void zig1StringTableGrow(Zig1StringTable* table) { const uint32_t old_cap = table->cap; Zig1StringTableSlot* old_slots = table->slots; if (old_cap > UINT32_MAX / 2) UNIMPLEMENTED("wasm32: string table capacity overflow"); const uint32_t new_cap = zig1GrowCap(old_cap, 256u); Zig1StringTableSlot* new_slots = zig1Malloc((size_t)new_cap * sizeof(Zig1StringTableSlot)); // Every byte 0xFF: each slot's str_index is UINT32_MAX. memset(new_slots, 0xFF, (size_t)new_cap * sizeof(Zig1StringTableSlot)); const uint32_t mask = new_cap - 1; for (uint32_t i = 0; i < old_cap; i++) { if (old_slots[i].str_index == UINT32_MAX) continue; uint32_t slot = old_slots[i].hash & mask; while (new_slots[slot].str_index != UINT32_MAX) slot = (slot + 1) & mask; new_slots[slot] = old_slots[i]; } free(old_slots); table->slots = new_slots; table->cap = new_cap; } uint32_t zig1StringTableFind(const Zig1StringTable* table, const uint8_t* bytes, const char* key, uint32_t len) { if (table->cap == 0) return UINT32_MAX; const uint32_t h = (uint32_t)wyhashHash(0, key, len); const uint32_t mask = table->cap - 1; for (uint32_t slot = h & mask;; slot = (slot + 1) & mask) { const uint32_t idx = table->slots[slot].str_index; if (idx == UINT32_MAX) return UINT32_MAX; if (table->slots[slot].hash == h) { const char* cand = (const char*)&bytes[idx]; if ((uint32_t)strlen(cand) == len && memcmp(cand, key, len) == 0) return idx; } } } void zig1StringTableInsert(Zig1StringTable* table, uint32_t str_index, const char* key, uint32_t len) { // Append-only, so the load needs a count, not live plus tombstones. if (table->cap == 0 || ((uint64_t)table->count + 1) * 10 > (uint64_t)table->cap * 7) zig1StringTableGrow(table); const uint32_t h = (uint32_t)wyhashHash(0, key, len); const uint32_t mask = table->cap - 1; uint32_t slot = h & mask; while (table->slots[slot].str_index != UINT32_MAX) slot = (slot + 1) & mask; table->slots[slot] = (Zig1StringTableSlot) { str_index, h }; table->count++; } /* ---- end common.c ---- */ /* ---- begin compilation.c ---- */ // compilation.c — one root module from its source to its interpreted `main` // (declarations in zig1.h). Sema reaches the root module's `pub fn main` the // way stock Zig does, through start.zig's entry point and `root.main` (an // `export fn main` is an export root). The entry follows lib/std/start.zig // for wasm32-wasi: `_start` is startWasi -> callMain, whose Init.Minimal // argument compilationEntryMainArgs builds, and wrapMain maps the result to // the exit code (interpRunMain). The analysis between is Compilation.update's: // the work queue (performAllTheWork, after // Zcu.findOutdatedToAnalyze) that a test compilation also drives, and the AIR // of every analyzed function. #ifndef ZIG1_H #include "zig1.h" #endif #include #include #include #include #include // An open-addressing set of u32s (the queue's indexes). typedef struct { uint32_t len; uint32_t cap; uint32_t* items; uint8_t* used; } U32Set; static uint32_t compilationEntryMainArgs( CompilationCtx* cctx, uint32_t main_func_decl, uint32_t* out_arg_const); // ── The root module, from source to its interpreted main ──────────────────── // The root source file `path` (caller frees); one that cannot be read exits // ZIG1_EXIT_COMPILE_ERROR, as upstream refuses it. static char* compilationReadRoot(const char* path, uint32_t* out_len) { size_t len = 0; char* buf = zig1ReadFile(path, &len); if (!buf) { fprintf(stderr, "error: unable to open '%s'\n", path); exit(ZIG1_EXIT_COMPILE_ERROR); } *out_len = (uint32_t)len; return buf; } // The module's root type name: the file's basename without ".zig". static char* compilationRootStem(const char* path) { const char* base = zig1PathBasename(path); return zig1Strndup(base, zig1ZigStemLen(base, strlen(base))); } // start.zig's `root.main`: the root namespace's `main` Nav, not a test // compilation's main module's. static const SemaFuncAir* compilationFindRootMain( Zcu* zcu, const SemaFuncAirList* func_airs) { InternPool* ip = &zcu->intern_pool; if (zcu->root_file_idx >= zcu->num_files) return NULL; uint32_t root_ns = zcu->files[zcu->root_file_idx].ns; if (root_ns == IP_NAMESPACE_NONE) return NULL; uint32_t main_name = ipGetOrPutString(ip, "main", 4); uint32_t main_nav = ipNamespaceLookup(ip, root_ns, main_name); if (main_nav == IP_INDEX_NONE) return NULL; const IpNav* nav = ipGetNav(ip, main_nav); if (!nav->has_value_resolved || nav->resolved_value >= ip->items_len) return NULL; uint8_t tag = ip->item_tags[nav->resolved_value]; if (tag != IP_TAG_FUNC_DECL && tag != IP_TAG_FUNC_INSTANCE) return NULL; return semaFuncAirListFind(func_airs, nav->resolved_value); } CompilationConfig compilationBootstrapConfig(const char* module_root, const char* std_root, const char* wasi_lib_dir, const char* const* wasi_argv, uint32_t wasi_argv_len) { // The `build_options` module of b7_ref.sh's reference zig1.wasm build, // `-Mbuild_options=build_options.zig`, which names its root struct. static CompilationModule bootstrap_module = { .name = "build_options", .sub_path = "build_options.zig", .source = NULL, }; static const CompilationDependency bootstrap_dependency = { .name = "build_options", .module_index = 0, }; bootstrap_module.source = builtinBootstrapBuildOptionsSource(); return (CompilationConfig) { .module_root = module_root, .std_root = std_root, .target_cpu_arch = "wasm32", .target_cpu_model = "generic", .object_format = "wasm", .link_mode = "static", .is_test = false, .profile = COMPILATION_PROFILE_BOOTSTRAP_WASI, .modules = &bootstrap_module, .modules_len = 1, .root_dependencies = &bootstrap_dependency, .root_dependencies_len = 1, .wasi_argv = wasi_argv, .wasi_argv_len = wasi_argv_len, .wasi_lib_dir = wasi_lib_dir, }; } // Interpret `main_fn` as lib/std/start.zig's wasm32-wasi entry calls it, with // the compilation's guest argv; the process exit code (0..255). static int compilationRunEntry(CompilationCtx* cctx, Zcu* zcu, const SemaFuncAir* main_fn, const SemaFuncAirList* func_airs) { const CompilationConfig* config = &zcu->comp->config; // start.zig callMain's comptime dispatch on main's signature: zero // parameters -> a bare call; the Init.Minimal form gets the synthesized // wasm32-wasi Minimal constant as its argument. uint32_t entry_arg = 0; uint32_t entry_nargs = compilationEntryMainArgs(cctx, main_fn->func_decl, &entry_arg); // Analysis is over: an interpreter abort names no declaration. zig1ErrorSourceClear(); const InterpHost host = { .argv = config->wasi_argv, .argc = config->wasi_argv_len, .lib_dir = config->wasi_lib_dir, .global_cache = config->wasi_global_cache, .exports = zcu->exports, .exports_len = zcu->exports_len, }; return interpRunMain(&zcu->intern_pool, func_airs, main_fn, entry_nargs == 0 ? NULL : &entry_arg, entry_nargs, &host); } int compilationRun( const char* fname, const char* main_fname, CompilationConfig config) { uint32_t src_len = 0; char* source = compilationReadRoot(fname, &src_len); Ast ast = astParse(source, src_len, AST_MODE_ZIG); Zir zir = astGenFile(&ast, NULL, fname); uint32_t main_src_len = 0; char* main_source = NULL; Ast main_ast = { 0 }; Zir main_zir = { 0 }; if (main_fname != NULL) { main_source = compilationReadRoot(main_fname, &main_src_len); main_ast = astParse(main_source, main_src_len, AST_MODE_ZIG); main_zir = astGenFile(&main_ast, NULL, main_fname); } char* stem = compilationRootStem(fname); if (config.module_name == NULL) config.module_name = stem; if (config.root_name == NULL) config.root_name = stem; Compilation comp = { .config = config }; Zcu* zcu = zcuInit(&comp); CompilationCtx cctx; compilationInit(&cctx, zcu, &zir, fname); if (main_fname != NULL) { cctx.main_zir = &main_zir; cctx.main_sub_path = main_fname; } SemaFuncAirList list = compilationAnalyze(&cctx); // start.zig's `root.main`: the root file's own `main` declaration. int rc = ZIG1_EXIT_COMPILE_ERROR; const SemaFuncAir* main_fn = compilationFindRootMain(zcu, &list); if (main_fn == NULL) { fprintf(stderr, "%s: error: root source file struct '%s' has no member named " "'main'\n", fname, stem); } else { rc = compilationRunEntry(&cctx, zcu, main_fn, &list); } compilationFuncAirListDeinit(&list); compilationDeinit(&cctx); zcuDeinit(zcu); free(stem); if (main_fname != NULL) { zirDeinit(&main_zir); astDeinit(&main_ast); free(main_source); } zirDeinit(&zir); astDeinit(&ast); free(source); return rc; } // The main module's root directory: the test file's own, as stock `zig test // ` makes it, which relative imports and `@import("root")` see. static char* compilationFileDir(const char* path) { size_t len = zig1PathDirLen(path); if (len == 0) return zig1Strdup(path[0] == '/' ? "/" : "."); return zig1Strndup(path, len); } // The root name of `path` as a file of the module rooted at `module_root`: its // path below the root, dotted, as test/behavior.zig's imports name // test/behavior/.zig. static char* compilationModuleRootName( const char* path, const char* module_root) { size_t root_len = strlen(module_root); if (strncmp(path, module_root, root_len) != 0 || path[root_len] != '/') zig1Fatal("test file is not below --module-root"); const char* below = path + root_len + 1; char* name = zig1Strndup(below, zig1ZigStemLen(below, strlen(below))); for (char* c = name; *c; c++) if (*c == '/') *c = '.'; return name; } int compilationRunTest(CompilationRunOptions* opts) { const char* lib_dir = opts->lib_dir != NULL ? opts->lib_dir : "lib"; char* runner_root = zig1PathJoin(lib_dir, "compiler", 8); char* runner = zig1PathJoin(runner_root, "test_runner.zig", 15); char* owned_main_root = opts->module_root ? NULL : compilationFileDir(opts->fname); char* main_root_name = opts->module_root ? compilationModuleRootName(opts->fname, opts->module_root) : compilationRootStem(opts->fname); CompilationConfig config = compilationRunOptionsConfig(opts); config.module_root = runner_root; config.module_name = "root"; config.root_name = "root"; config.main_module_root = opts->module_root ? opts->module_root : owned_main_root; config.main_module_name = "test"; config.main_root_name = main_root_name; config.target_cpu_model = "lime1"; config.is_test = true; config.profile = COMPILATION_PROFILE_TEST_WASI; int rc = compilationRun(runner, opts->fname, config); free(main_root_name); free(owned_main_root); free(runner); free(runner_root); return rc; } // ── `zig1 --run` and `zig1 --test`'s options ──────────────────────────────── int compilationRunOptionsParse( CompilationRunOptions* opts, int argc, char** argv, int i) { // main.zig buildOutputType's module arguments: `--dep [=]` // edges go to the next `-M=`, and the first -M is the root. opts->modules = zig1Malloc((size_t)argc * sizeof(CompilationModule)); opts->root_dependencies = zig1Malloc((size_t)argc * sizeof(CompilationDependency)); opts->modules_len = 0; opts->root_dependencies_len = 0; // Each --dep's , the module name it resolves to after the parse. const char** dep_values = zig1Malloc((size_t)argc * sizeof(char*)); char* root_file = NULL; uint32_t root_deps_len = 0; // the --dep edges before the root's -M bool end_of_options = false; for (; i < argc; i++) { char* arg = argv[i]; if (strcmp(arg, "--zig-lib-dir") == 0) { if (i + 1 >= argc) zig1Fatal("--zig-lib-dir needs an argument"); opts->lib_dir = argv[++i]; } else if (strcmp(arg, "--module-root") == 0) { if (i + 1 >= argc) zig1Fatal("--module-root needs an argument"); opts->module_root = argv[++i]; } else if (!opts->is_test && strcmp(arg, "--bootstrap-profile") == 0) { opts->bootstrap_profile = true; } else if (!opts->is_test && strcmp(arg, "--dep") == 0) { if (i + 1 >= argc) zig1Fatal("--dep needs an argument"); char* key = argv[++i]; char* eq = strchr(key, '='); if (eq != NULL) *eq = '\0'; dep_values[opts->root_dependencies_len] = eq ? eq + 1 : key; opts->root_dependencies[opts->root_dependencies_len++] = (CompilationDependency) { .name = key }; } else if (!opts->is_test && strncmp(arg, "-M", 2) == 0) { char* eq = strchr(arg, '='); if (eq == NULL) zig1Fatal("zig1 does not implement a module without a root " "source file (handleModArg)"); *eq = '\0'; for (uint32_t m = 0; m < opts->modules_len; m++) if (strcmp(opts->modules[m].name, arg + 2) == 0) zig1Fatal( "unable to add module '%s': already exists", arg + 2); if (root_file == NULL) { root_file = eq + 1; root_deps_len = opts->root_dependencies_len; } else if (opts->root_dependencies_len != root_deps_len) { zig1Fatal("zig1 does not implement dependencies of a module " "other than the root (Module.deps)"); } // handleModArg: rooted at the file's directory. opts->modules[opts->modules_len++] = (CompilationModule) { .name = arg + 2, .root_dir = compilationFileDir(eq + 1), .sub_path = zig1PathBasename(eq + 1), }; } else if (opts->modules_len != 0 && strcmp(arg, "--") == 0) { end_of_options = true; i++; break; } else { // The root file, ending the options, or the argument not taken. if (arg[0] != '-' && opts->modules_len == 0) { opts->fname = arg; i++; } break; } } if (root_file != NULL && (end_of_options || i == argc)) { // Edges after the last -M go to no module. opts->root_dependencies_len = root_deps_len; // createModule: each dependency names a -M module, the root included. for (uint32_t d = 0; d < opts->root_dependencies_len; d++) { uint32_t m = 0; while (m < opts->modules_len && strcmp(opts->modules[m].name, dep_values[d]) != 0) m++; if (m == opts->modules_len) zig1Fatal("module '%s' depends on non-existent module '%s'", opts->modules[0].name, opts->root_dependencies[d].name); opts->root_dependencies[d].module_index = m == 0 ? COMPILATION_MODULE_ROOT : m - 1; } // The guest's argv[0], the root file, takes the place of the `--` // or of the last option. opts->fname = root_file; argv[i - 1] = root_file; } free(dep_values); return i; } CompilationConfig compilationRunOptionsConfig(CompilationRunOptions* opts) { opts->std_root = opts->lib_dir == NULL ? NULL : zig1PathJoin(opts->lib_dir, "..", 2); CompilationConfig config = compilationBootstrapConfig(opts->module_root, opts->std_root, opts->lib_dir, opts->wasi_argv, opts->wasi_argv_len); config.wasi_global_cache = opts->global_cache; if (!opts->bootstrap_profile) config.profile = COMPILATION_PROFILE_AIRGEN; if (opts->modules_len != 0) { // -M modules replace the bootstrap profile's build_options. if (opts->module_root == NULL) config.module_root = opts->modules[0].root_dir; config.module_name = opts->modules[0].name; config.modules = opts->modules + 1; config.modules_len = opts->modules_len - 1; config.root_dependencies = opts->root_dependencies; config.root_dependencies_len = opts->root_dependencies_len; } else if (!opts->bootstrap_profile) { config.modules = NULL; config.modules_len = 0; config.root_dependencies = NULL; config.root_dependencies_len = 0; } return config; } void compilationRunOptionsDeinit(CompilationRunOptions* opts) { for (uint32_t m = 0; m < opts->modules_len; m++) free(opts->modules[m].root_dir); free(opts->modules); free(opts->root_dependencies); free(opts->std_root); } // ── Analysis: the work queue (Compilation.update) ─────────────────────────── static void compilationUpdateQueueEnsureFunc( UpdateQueue* ctx, uint32_t func, const char* loop_msg); void compilationInit(CompilationCtx* cctx, Zcu* zcu, const Zir* zir, const char* root_sub_path) { cctx->zcu = zcu; cctx->ip = &zcu->intern_pool; cctx->zir = zir; cctx->root_sub_path = root_sub_path; cctx->main_zir = NULL; cctx->main_sub_path = NULL; } void compilationDeinit(CompilationCtx* cctx) { cctx->zcu = NULL; cctx->ip = NULL; cctx->zir = NULL; } // Decode a top-level `comptime { ... }` declaration instruction, yielding its // value body (the block statements). Unlike `zirDecodeDeclaration` this // accepts the unnamed `comptime` kind (DECL_ID_COMPTIME): a comptime // declaration has no name, no lib name, and no // type/align/linksection/addrspace bodies, so its value-body length word sits // immediately after the flags header. Returns false for any other declaration // kind. static bool compilationDecodeComptimeDecl( const Zir* zir, uint32_t inst, uint32_t* out_off, uint32_t* out_len) { if (zir->inst_tags[inst] != ZIR_INST_DECLARATION) return false; uint32_t p = zir->inst_datas[inst].declaration.payload_index; uint32_t id = zirDeclarationId(zir, inst); if (id != DECL_ID_COMPTIME) return false; // p+6 is the value-body length (comptime has value body, no other bodies). uint32_t idx = p + 6; uint32_t value_len = zir->extra[idx++]; *out_off = idx; *out_len = value_len; return true; } // Derive the `func` instruction and parameter insts/types from a // declaration's value body. Mirrors the param walk in navFuncDecl / the export // loop. Returns false if the decl does not define a function. `ip` is used to // resolve non-preinterned parameter types. static bool compilationDeclFuncInfo(InternPool* ip, Zcu* zcu, const Zir* zir, const DeclInfo* decl, uint32_t* out_func_inst, uint32_t* param_insts, uint32_t* param_types, uint32_t* out_nparams, uint32_t container_type) { // Scope container-typed parameter names to the method's owning container // namespace: `self: *Self` (`const Self = @This()` in the container) or // any param naming a sibling container-scoped type decl resolves its // decl_val there, not just the file root. Mirrors navFuncDecl's signature // scope. A file-root helper fn has container_type == IP_INDEX_NONE and // this stays unset (file-root lookup unchanged). uint32_t container_ns = ipContainerNamespaceOrNone(ip, container_type); uint32_t saved_type_ns = ctSwapNs(&zcu->ct.type_resolve_ns, container_ns); uint32_t func_inst = UINT32_MAX; uint32_t nparams = 0; for (uint32_t b = 0; b < decl->value_body_len; b++) { uint32_t inst = zir->extra[decl->value_body_off + b]; ZirInstTag t = zir->inst_tags[inst]; if (t == ZIR_INST_PARAM || t == ZIR_INST_PARAM_COMPTIME) { uint32_t pp = zir->inst_datas[inst].pl_tok.payload_index; uint32_t body_word = zir->extra[pp + 1]; uint32_t param_ty; if (body_word & ZIR_PARAM_TYPE_IS_GENERIC) { // `[generic]` parameter type: `generic_poison_type` in the // declared signature (see navFuncDecl). For a func_instance, // the caller substitutes the resolved runtime type from the // instance's `func_type` after this walk. param_ty = IP_INDEX_GENERIC_POISON_TYPE; } else { uint32_t plen = body_word & ZIR_PARAM_TYPE_BODY_LEN_MASK; if (plen == 0) UNIMPLEMENTED("funcCommon: empty parameter type body"); uint32_t last = zir->extra[pp + 2 + plen - 1]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) UNREACHABLE("compilationDeclFuncInfo: param body not " "break_inline"); uint32_t ref = zir->inst_datas[last].break_data.operand; if (zirRefIsThis(zir, ref)) { // `self: @This()`: enclosing container type (see // navFuncDecl). container_type is the method's owning // struct/enum/union type. param_ty = container_type; } else if (ctRetTypeHasComputedAlign(zir, ref)) { // A parameter whose pointer type has a comptime-computed // alignment depending on an earlier comptime param // (`ptr: []align(alignment.toByteUnits()) u8`): the align // FIELD_CALL isn't evaluated in this signature-decode // frame. Stock resolves such a dependent type lazily at // instantiation; use generic_poison here (the arg binds by // value / coerces poison-leniently) — same as the // `[generic]` param arm above. param_ty = IP_INDEX_GENERIC_POISON_TYPE; } else if (ctTypeBodyNeedsComptimeFold(zir, ref)) { // Keep compilationDeclFuncInfo in lockstep with // navFuncDecl for concrete generic type calls in parameter // position. uint32_t pns = zcu->ct.type_resolve_ns != IP_NAMESPACE_NONE ? zcu->ct.type_resolve_ns : zcuNamespaceOfZir(zcu, zir); param_ty = ctFoldComptimeTypeBody(zcu, zir, &zir->extra[pp + 2], plen, pns, TYPE_NAME_CTX_NONE); } else { param_ty = ctResolveParamTypeRef(ip, zcu, zir, ref); } } param_insts[nparams] = inst; param_types[nparams] = param_ty; nparams++; } else if (t == ZIR_INST_PARAM_ANYTYPE || t == ZIR_INST_PARAM_ANYTYPE_COMPTIME) { // `anytype` parameter (e.g. std.mem.sliceAsBytes's `slice`): no // type body — its declared signature type is `generic_poison`. // For a func_instance the caller substitutes the resolved runtime // type from the instance's `func_type` after this walk (a // `comptime anytype` binds its comptime arg instead). param_insts[nparams] = inst; param_types[nparams] = IP_INDEX_GENERIC_POISON_TYPE; nparams++; } else if (zirIsFuncInst(t)) { func_inst = inst; break; } } zcu->ct.type_resolve_ns = saved_type_ns; *out_func_inst = func_inst; *out_nparams = nparams; return func_inst != UINT32_MAX; } static void compilationFuncAirIndexPlace( SemaFuncAirList* list, uint32_t func_decl, uint32_t item_index) { uint32_t mask = list->index_cap - 1u; uint32_t slot = semaU32Hash(func_decl) & mask; while (list->index_used[slot]) { if (list->index_keys[slot] == func_decl) return; // keep the first match slot = (slot + 1u) & mask; } list->index_used[slot] = 1; list->index_keys[slot] = func_decl; list->index_values[slot] = item_index; list->index_len++; } static void compilationFuncAirIndexGrow(SemaFuncAirList* list) { uint32_t old_cap = list->index_cap; uint32_t* old_keys = list->index_keys; uint32_t* old_values = list->index_values; uint8_t* old_used = list->index_used; uint32_t new_cap = zig1GrowCap(old_cap, 1024u); list->index_keys = zig1Malloc((size_t)new_cap * sizeof(uint32_t)); list->index_values = zig1Malloc((size_t)new_cap * sizeof(uint32_t)); list->index_used = zig1Calloc(new_cap, sizeof(uint8_t)); list->index_cap = new_cap; list->index_len = 0; for (uint32_t i = 0; i < old_cap; i++) if (old_used[i]) compilationFuncAirIndexPlace(list, old_keys[i], old_values[i]); free(old_keys); free(old_values); free(old_used); } static void compilationFuncAirIndexInsert( SemaFuncAirList* list, uint32_t func_decl, uint32_t item_index) { if (list->index_cap == 0 || ((uint64_t)list->index_len + 1u) * 10u > (uint64_t)list->index_cap * 7u) compilationFuncAirIndexGrow(list); compilationFuncAirIndexPlace(list, func_decl, item_index); } // analyzeFuncBody's setWantRuntimeFnAnalysis and `funcSetHasErrorTrace(.., // fn_ty_info.cc == .auto)`, in the func_decl / func_instance's FuncAnalysis // word. static void compilationFuncAnalysisBits(InternPool* ip, uint32_t func) { bool trace = ipFuncTypeCcTag(ip, ipTypeOf(ip, func)) == CC_TAG_AUTO; ip->extra[ip->item_datas[func]] |= IP_FUNC_ANALYSIS_WANT_RUNTIME_ANALYSIS | (trace ? IP_FUNC_ANALYSIS_HAS_ERROR_TRACE : 0u); } static void compilationAppendFuncAirOwned( SemaFuncAirList* list, SemaFuncAir item) { if (semaFuncAirListFind(list, item.func_decl) != NULL) { free(item.name); airDeinit(&item.air); return; } if (list->len == list->cap) { list->cap = zig1GrowCap(list->cap, 4u); list->items = zig1Realloc(list->items, list->cap * sizeof(SemaFuncAir)); } list->items[list->len] = item; compilationFuncAirIndexInsert(list, item.func_decl, list->len++); } // Append a lowered function's AIR under an already-fully-qualified name (e.g. // a method's Nav fqn "root.f.S.get", which is not simply "."). static void compilationAppendFuncAirNamed(SemaFuncAirList* list, const char* full_name, uint32_t func_decl, uint32_t func_ip, Air air) { if (semaFuncAirListFind(list, func_decl) != NULL) { airDeinit(&air); return; } compilationAppendFuncAirOwned(list, (SemaFuncAir) { .name = zig1Strdup(full_name), .func_ip = func_ip, .func_decl = func_decl, .air = air }); } static void compilationU32SetGrow(U32Set* set) { U32Set old = *set; uint32_t new_cap = zig1GrowCap(old.cap, 1024u); set->items = zig1Calloc(new_cap, sizeof(uint32_t)); set->used = zig1Calloc(new_cap, sizeof(uint8_t)); set->len = 0; set->cap = new_cap; for (uint32_t i = 0; i < old.cap; i++) { if (!old.used[i]) continue; uint32_t item = old.items[i]; uint32_t mask = set->cap - 1u; uint32_t h = semaU32Hash(item) & mask; while (set->used[h]) h = (h + 1u) & mask; set->used[h] = 1; set->items[h] = item; set->len++; } free(old.items); free(old.used); } static bool compilationU32SetContains(const U32Set* set, uint32_t item) { if (set->cap == 0) return false; uint32_t mask = set->cap - 1u; uint32_t h = semaU32Hash(item) & mask; while (set->used[h]) { if (set->items[h] == item) return true; h = (h + 1u) & mask; } return false; } static void compilationU32SetInsert(U32Set* set, uint32_t item) { if (set->cap == 0 || set->len * 10u >= set->cap * 7u) compilationU32SetGrow(set); uint32_t mask = set->cap - 1u; uint32_t h = semaU32Hash(item) & mask; while (set->used[h]) { if (set->items[h] == item) return; h = (h + 1u) & mask; } set->used[h] = 1; set->items[h] = item; set->len++; } typedef struct { CompilationCtx* cctx; SemaFuncAirList* list; U32List* pending; U32List* analyzed; U32Set* analyzed_set; U32List* resolving; // Zcu.outdated_ready.funcs: `pending` entries join in queue order (from // pending_cursor); analysis takes `keys()[0]` and clearOutdatedState // swapRemoves a function wherever it sits. U32List ready; uint32_t pending_cursor; } SemaPendingCtx; static bool compilationU32ListContains(const U32List* list, uint32_t item) { for (uint32_t i = 0; i < list->len; i++) if (list->items[i] == item) return true; return false; } static void compilationU32ListRemoveItem(U32List* list, uint32_t item) { for (uint32_t i = 0; i < list->len; i++) { if (list->items[i] != item) continue; for (uint32_t j = i + 1; j < list->len; j++) list->items[j - 1] = list->items[j]; list->len--; return; } } static uint32_t compilationIesPendingEntry( const InternPool* ip, uint32_t func_index) { if (func_index < ip->items_len && ip->item_tags[func_index] == IP_TAG_FUNC_DECL) return ipFuncDeclOwnerNav(ip, func_index); if (func_index < ip->items_len && ip->item_tags[func_index] == IP_TAG_FUNC_INSTANCE) return func_index | PENDING_INSTANCE_FLAG; UNIMPLEMENTED("resolveInferredErrorSetTy: owner not a function"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } static void compilationFinalizeFuncIes(InternPool* ip, uint32_t func_index) { uint32_t slot = ipFuncIesResolvedSlot(ip, func_index); if (slot == UINT32_MAX) return; if (slot >= ip->extra_len) UNREACHABLE("resolveInferredErrorSetTy: missing resolved slot"); if (ip->extra[slot] == IP_INDEX_NONE) ip->extra[slot] = ipGetErrorSetType(ip, NULL, 0); } static void compilationReadySync(SemaPendingCtx* ctx) { for (; ctx->pending_cursor < ctx->pending->len; ctx->pending_cursor++) { uint32_t entry = ctx->pending->items[ctx->pending_cursor]; if (!compilationU32SetContains(ctx->analyzed_set, entry)) u32ListAppend(&ctx->ready, entry); } } static void compilationAnalyzePendingEntry( SemaPendingCtx* ctx, uint32_t entry, const char* loop_msg) { CompilationCtx* cctx = ctx->cctx; Zcu* zcu = cctx->zcu; InternPool* ip = &zcu->intern_pool; // ensureFuncBodyUpToDate's clearOutdatedState, before anything else: an // entry already analyzed must not stay at the front of the drain. compilationReadySync(ctx); for (uint32_t i = 0; i < ctx->ready.len; i++) if (ctx->ready.items[i] == entry) { ctx->ready.items[i] = ctx->ready.items[--ctx->ready.len]; break; } if (compilationU32SetContains(ctx->analyzed_set, entry)) return; if (compilationU32ListContains(ctx->resolving, entry)) UNIMPLEMENTED(loop_msg); u32ListAppend(ctx->resolving, entry); if (entry & PENDING_INSTANCE_FLAG) { uint32_t inst_idx = entry & ~PENDING_INSTANCE_FLAG; uint32_t owner_fdecl = ipFuncInstanceGenericOwner(ip, inst_idx); uint32_t owner_nav_idx = ipFuncDeclOwnerNav(ip, owner_fdecl); const IpNav* owner_nav = ipGetNav(ip, owner_nav_idx); uint32_t owner_ns = owner_nav->analysis_namespace; uint32_t owner_analysis_inst; const Zir* nzir; if (!semaNavZir(cctx->zcu, owner_nav_idx, &nzir, &owner_analysis_inst)) UNREACHABLE("resolveInferredErrorSetTy: invalid owner Nav"); DeclInfo decl; if (!zirDecodeFunctionDeclaration(nzir, owner_analysis_inst, &decl) || !decl.has_value_body) UNREACHABLE("resolveInferredErrorSetTy: instance owner has no " "value body"); uint32_t func_inst = UINT32_MAX; uint32_t pcap = decl.value_body_len ? decl.value_body_len : 1; uint32_t* param_insts = ARR_INIT(uint32_t, pcap); uint32_t* param_types = ARR_INIT(uint32_t, pcap); uint32_t nparams = 0; if (!compilationDeclFuncInfo(ip, cctx->zcu, nzir, &decl, &func_inst, param_insts, param_types, &nparams, ipGetNamespace(ip, owner_ns)->owner_type)) UNIMPLEMENTED( "resolveInferredErrorSetTy: instance owner is not a function"); uint32_t* comptime_args = ARR_INIT(uint32_t, nparams ? nparams : 1); for (uint32_t i = 0; i < nparams; i++) comptime_args[i] = ipFuncInstanceComptimeArg(ip, inst_idx, i); uint32_t inst_ty = ipFuncInstanceType(ip, inst_idx); uint32_t override_ret = ipFuncTypeReturnType(ip, inst_ty); uint32_t rt = 0; for (uint32_t i = 0; i < nparams; i++) { if (comptime_args[i] != IP_INDEX_NONE) { // Instance analysis must bind a comptime parameter's value // and monomorphic type from the same source. Anonymous // concrete parameter types (e.g. `comptime sign: enum { ... // }`) can be freshly interned by compilationDeclFuncInfo // during this drain, while the stored func_instance comptime // arg was coerced against the owner func_type at the call // site. Upstream has one logical parameter type here; // normalize the body-side type to the bound value's type so // `@TypeOf(param)` and comptime switch validation see a // consistent pair. uint32_t arg_ty = ipTypeOf(ip, ipFuncInstanceComptimeArg(ip, inst_idx, i)); if (arg_ty == IP_INDEX_NONE) UNIMPLEMENTED("InternPool.getFuncInstance: comptime arg " "has no type"); param_types[i] = arg_ty; } else { param_types[i] = ipFuncTypeParamType(ip, inst_ty, rt++); } } uint32_t inst_nav_idx = ipFuncInstanceOwnerNav(ip, inst_idx); const IpNav* inst_nav = ipGetNav(ip, inst_nav_idx); uint32_t inst_name = inst_nav->name; uint32_t inst_fqn = inst_nav->fqn; PerThread pt = { .zcu = cctx->zcu, .zir = nzir }; uint32_t saved_ns = ctSwapNs(&zcu->ct.type_resolve_ns, owner_ns); Air air = zptAnalyzeFuncBody(&pt, inst_idx, func_inst, param_insts, param_types, nparams, comptime_args, override_ret, // `.type_name_ctx = func_nav.name, .type_fqn_ctx = func_nav.fqn`. inst_name, (TypeNameCtx) { inst_name, inst_fqn }, owner_nav_idx, ipFuncInstanceBranchQuota(ip, inst_idx), ctx->pending); zcu->ct.type_resolve_ns = saved_ns; compilationFinalizeFuncIes(ip, inst_idx); compilationFuncAnalysisBits(ip, inst_idx); compilationAppendFuncAirNamed(ctx->list, ipStringBytes(ip, inst_fqn), inst_idx, ip->items_len, air); free(param_insts); free(param_types); free(comptime_args); compilationU32ListRemoveItem(ctx->resolving, entry); u32ListAppend(ctx->analyzed, entry); compilationU32SetInsert(ctx->analyzed_set, entry); return; } uint32_t nav_idx = entry; const IpNav* nav = ipGetNav(ip, nav_idx); if (!nav->has_analysis) UNREACHABLE("compilationAnalyze: pending callee nav has no analysis"); uint32_t nav_ns = nav->analysis_namespace; uint32_t analysis_inst; uint32_t name = nav->name; uint32_t fqn = nav->fqn; const Zir* nzir; if (!semaNavZir(cctx->zcu, nav_idx, &nzir, &analysis_inst)) UNREACHABLE("compilationAnalyze: pending callee has invalid Nav"); DeclInfo decl; if (!zirDecodeFunctionDeclaration(nzir, analysis_inst, &decl) || !decl.has_value_body) UNREACHABLE( "compilationAnalyze: pending callee decl has no value body"); uint32_t func_inst = UINT32_MAX; uint32_t pcap = decl.value_body_len ? decl.value_body_len : 1; uint32_t* param_insts = ARR_INIT(uint32_t, pcap); uint32_t* param_types = ARR_INIT(uint32_t, pcap); uint32_t nparams = 0; uint32_t saved_ns = ctSwapNs(&zcu->ct.type_resolve_ns, nav_ns); bool is_fn = compilationDeclFuncInfo(ip, cctx->zcu, nzir, &decl, &func_inst, param_insts, param_types, &nparams, ipGetNamespace(ip, nav_ns)->owner_type); zcu->ct.type_resolve_ns = saved_ns; if (!is_fn) { // ScanDeclIter queues every exported Nav, including exported data. // Resolve such a declaration for its diagnostics and nested // namespace side effects; only function declarations contribute AIR. free(param_insts); free(param_types); zptEnsureNavValUpToDate(cctx->zcu, nav_idx, NULL); compilationU32ListRemoveItem(ctx->resolving, entry); u32ListAppend(ctx->analyzed, entry); compilationU32SetInsert(ctx->analyzed_set, entry); return; } uint32_t func_decl = zptEnsureNavValUpToDate(cctx->zcu, nav_idx, NULL); uint32_t ovr_ret = IP_INDEX_NONE; if (zirFuncInstIsInferred(nzir, func_inst)) ovr_ret = ipFuncTypeReturnType(ip, ipFuncDeclType(ip, func_decl)); PerThread pt = { .zcu = cctx->zcu, .zir = nzir }; saved_ns = ctSwapNs(&zcu->ct.type_resolve_ns, nav_ns); Air air = zptAnalyzeFuncBody(&pt, func_decl, func_inst, param_insts, param_types, nparams, NULL, ovr_ret, name, (TypeNameCtx) { name, fqn }, nav_idx, 0, ctx->pending); zcu->ct.type_resolve_ns = saved_ns; compilationFinalizeFuncIes(ip, func_decl); compilationFuncAnalysisBits(ip, func_decl); compilationAppendFuncAirNamed( ctx->list, ipStringBytes(ip, fqn), func_decl, ip->items_len, air); free(param_insts); free(param_types); compilationU32ListRemoveItem(ctx->resolving, entry); u32ListAppend(ctx->analyzed, entry); compilationU32SetInsert(ctx->analyzed_set, entry); } static void compilationIesResolver(void* opaque, uint32_t func_index) { SemaPendingCtx* ctx = (SemaPendingCtx*)opaque; if (ctx->cctx->zcu->update_queue != NULL) { compilationUpdateQueueEnsureFunc(ctx->cctx->zcu->update_queue, func_index, "resolveInferredErrorSetTy: dependency loop"); return; } const InternPool* ip = &ctx->cctx->zcu->intern_pool; uint32_t entry = compilationIesPendingEntry(ip, func_index); compilationAnalyzePendingEntry( ctx, entry, "resolveInferredErrorSetTy: dependency loop"); } // Register the executable root exactly once. The work queue runs before full // runner analysis, so registration must be shared and idempotent. static uint32_t compilationRegisterRootFile(CompilationCtx* cctx) { Zcu* zcu = cctx->zcu; if (zcu->root_file_idx != UINT32_MAX) return zcu->root_file_idx; const CompilationConfig* config = &zcu->comp->config; if (cctx->root_sub_path == NULL) UNIMPLEMENTED( "Compilation.create: root file source identity has no path"); const char* stored_path = cctx->root_sub_path; if (config->module_root != NULL) stored_path = zptPathBelowDir( stored_path, config->module_root, strlen(config->module_root)); uint32_t file_idx = zcuAddFileBorrowed( zcu, ZCU_MODULE_ROOT, cctx->zir, cctx->root_sub_path); zcu->root_file_idx = file_idx; zcu->files[file_idx].sub_path = zig1Strdup(stored_path); const char* module_name = config->module_name != NULL ? config->module_name : config->root_name; if (module_name == NULL) UNIMPLEMENTED( "Compilation.create: root module has no fully-qualified name"); const char* sub_file_path = config->module_root != NULL ? stored_path : zig1PathBasename(cctx->root_sub_path); zcuSetFileSourceIdentity(zcu, file_idx, module_name, sub_file_path); return file_idx; } // Register the user test source as main, distinct from the test-runner root. // In non-test compilations main aliases root to preserve existing semantics. static uint32_t compilationRegisterMainFile(CompilationCtx* cctx) { Zcu* zcu = cctx->zcu; uint32_t root_file_idx = compilationRegisterRootFile(cctx); if (cctx->main_zir == NULL) { zcu->main_file_idx = root_file_idx; return root_file_idx; } if (zcu->main_file_idx != UINT32_MAX && zcu->main_file_idx != root_file_idx) return zcu->main_file_idx; const CompilationConfig* config = &zcu->comp->config; if (config->main_module_name == NULL || config->main_root_name == NULL || cctx->main_sub_path == NULL) UNIMPLEMENTED( "Compilation.create: test main module identity is incomplete"); const char* stored_path = cctx->main_sub_path; if (config->main_module_root != NULL) stored_path = zptPathBelowDir(stored_path, config->main_module_root, strlen(config->main_module_root)); uint32_t file_idx = zcuAddFileBorrowed( zcu, ZCU_MODULE_MAIN, cctx->main_zir, cctx->main_sub_path); zcu->main_file_idx = file_idx; zcu->files[file_idx].sub_path = zig1Strdup(stored_path); const char* sub_file_path = config->main_module_root != NULL ? stored_path : zig1PathBasename(cctx->main_sub_path); zcuSetFileSourceIdentity( zcu, file_idx, config->main_module_name, sub_file_path); return file_idx; } typedef enum { UPDATE_WORK_NAV_VALUE, UPDATE_WORK_NAV_TYPE, UPDATE_WORK_COMPTIME, UPDATE_WORK_STRUCT_DEFAULTS, } UpdateWorkKind; typedef struct { UpdateWorkKind kind; uint32_t nav; const Zir* zir; uint32_t namespace_idx; uint32_t body_off; uint32_t body_len; } UpdateWork; typedef struct { uint32_t func; uint32_t pending_entry; } UpdateFuncWork; struct UpdateQueue { CompilationCtx* cctx; uint32_t target_module_id; SemaFuncAirList func_airs; U32List pending; U32List analyzed; U32Set analyzed_set; U32List resolving; U32Set seen_namespaces; // The index of Zcu.test_functions: a selected test's body is queued with // its signature. U32Set test_functions; U32Set wanted_funcs; U32Set wanted_struct_layouts; SemaPendingCtx pending_ctx; UpdateWork* ready_other; uint32_t ready_other_len; uint32_t ready_other_cap; UpdateFuncWork* ready_funcs; uint32_t ready_funcs_len; uint32_t ready_funcs_cap; uint32_t* nav_value_pos; uint32_t* nav_type_pos; uint32_t nav_pos_cap; uint32_t* comptime_pos; uint32_t comptime_pos_cap; uint32_t* struct_defaults_pos; uint32_t struct_defaults_pos_cap; uint32_t* func_pos; uint32_t func_pos_cap; uint32_t pending_import_cursor; }; static void compilationUpdateQueueGrowPositions( uint32_t** positions, uint32_t* cap, uint32_t needed) { if (needed < *cap) return; uint32_t old_cap = *cap; uint32_t next = old_cap ? old_cap : 16; while (next <= needed) { if (next > UINT32_MAX / 2u) UNIMPLEMENTED("update queue: position capacity overflow"); next *= 2u; } #if SIZE_MAX < UINT64_MAX if ((uint64_t)next > (uint64_t)SIZE_MAX / sizeof(**positions)) UNIMPLEMENTED("update queue: position allocation overflow"); #endif uint32_t* grown = zig1Realloc(*positions, (size_t)next * sizeof(*grown)); for (uint32_t i = old_cap; i < next; i++) grown[i] = UINT32_MAX; *positions = grown; *cap = next; } static void compilationUpdateQueueGrowNavPositions( UpdateQueue* ctx, uint32_t needed) { if (needed < ctx->nav_pos_cap) return; uint32_t old_cap = ctx->nav_pos_cap; uint32_t next = old_cap ? old_cap : 16; while (next <= needed) { if (next > UINT32_MAX / 2u) UNIMPLEMENTED("update queue: Nav position capacity overflow"); next *= 2u; } #if SIZE_MAX < UINT64_MAX if ((uint64_t)next > (uint64_t)SIZE_MAX / sizeof(*ctx->nav_value_pos)) UNIMPLEMENTED("update queue: Nav position allocation overflow"); #endif uint32_t* values = zig1Realloc(ctx->nav_value_pos, (size_t)next * sizeof(*values)); uint32_t* types = zig1Realloc(ctx->nav_type_pos, (size_t)next * sizeof(*types)); for (uint32_t i = old_cap; i < next; i++) { values[i] = UINT32_MAX; types[i] = UINT32_MAX; } ctx->nav_value_pos = values; ctx->nav_type_pos = types; ctx->nav_pos_cap = next; } static uint32_t* compilationUpdateQueueOtherPosition( UpdateQueue* ctx, UpdateWorkKind kind, uint32_t id) { switch (kind) { case UPDATE_WORK_NAV_VALUE: compilationUpdateQueueGrowNavPositions(ctx, id); return &ctx->nav_value_pos[id]; case UPDATE_WORK_NAV_TYPE: compilationUpdateQueueGrowNavPositions(ctx, id); return &ctx->nav_type_pos[id]; case UPDATE_WORK_COMPTIME: compilationUpdateQueueGrowPositions( &ctx->comptime_pos, &ctx->comptime_pos_cap, id); return &ctx->comptime_pos[id]; case UPDATE_WORK_STRUCT_DEFAULTS: compilationUpdateQueueGrowPositions( &ctx->struct_defaults_pos, &ctx->struct_defaults_pos_cap, id); return &ctx->struct_defaults_pos[id]; } UNREACHABLE("update queue: invalid position kind"); ZIG1_TCC_RETURN(NULL); } static void compilationUpdateQueueEnsureOtherCapacity(UpdateQueue* ctx) { if (ctx->ready_other_len != ctx->ready_other_cap) return; uint32_t next = zig1GrowCap(ctx->ready_other_cap, 16u); if (next < ctx->ready_other_cap) UNIMPLEMENTED("update queue: ready-other capacity overflow"); UpdateWork* grown = zig1Realloc(ctx->ready_other, (size_t)next * sizeof(*grown)); ctx->ready_other = grown; ctx->ready_other_cap = next; } static void compilationUpdateQueueQueueOther(UpdateQueue* ctx, UpdateWorkKind kind, uint32_t id, const Zir* zir, uint32_t namespace_idx, uint32_t off, uint32_t len) { uint32_t* position = compilationUpdateQueueOtherPosition(ctx, kind, id); if (*position != UINT32_MAX) return; compilationUpdateQueueEnsureOtherCapacity(ctx); uint32_t pos = ctx->ready_other_len++; ctx->ready_other[pos] = (UpdateWork) { .kind = kind, .nav = id, .zir = zir, .namespace_idx = namespace_idx, .body_off = off, .body_len = len, }; *position = pos; } static bool compilationUpdateQueueRemoveOther( UpdateQueue* ctx, UpdateWorkKind kind, uint32_t id) { uint32_t* position = compilationUpdateQueueOtherPosition(ctx, kind, id); if (*position == UINT32_MAX) return false; uint32_t pos = *position; if (pos >= ctx->ready_other_len) UNREACHABLE("update queue: invalid ready-other position"); uint32_t last = --ctx->ready_other_len; UpdateWork moved = ctx->ready_other[last]; if (pos != last) { ctx->ready_other[pos] = moved; uint32_t* moved_position = compilationUpdateQueueOtherPosition(ctx, moved.kind, moved.nav); if (*moved_position != last) UNIMPLEMENTED("update queue: stale ready-other position"); *moved_position = pos; } *position = UINT32_MAX; return true; } // (part of) PerThread.zig:ensureTypeLayoutUpToDate // `"if (ip.setWantTypeLayout(comp.io, ty.toIntern()))"`: a struct's first // layout demand makes its `.struct_defaults` unit outdated and ready. void compilationUpdateQueueStructLayoutWanted( UpdateQueue* ctx, uint32_t struct_ty) { if (compilationU32SetContains(&ctx->wanted_struct_layouts, struct_ty)) return; compilationU32SetInsert(&ctx->wanted_struct_layouts, struct_ty); compilationUpdateQueueQueueOther(ctx, UPDATE_WORK_STRUCT_DEFAULTS, struct_ty, NULL, IP_NAMESPACE_NONE, 0, 0); } // (part of) PerThread.zig:ensureStructDefaultsUpToDate // `"if (zcu.clearOutdatedState(anal_unit)) break :outdated true;"`. void compilationUpdateQueueStructDefaultsEnter( UpdateQueue* ctx, uint32_t struct_ty) { compilationUpdateQueueRemoveOther( ctx, UPDATE_WORK_STRUCT_DEFAULTS, struct_ty); } static void compilationUpdateQueueEnsureFuncCapacity(UpdateQueue* ctx) { if (ctx->ready_funcs_len != ctx->ready_funcs_cap) return; uint32_t next = zig1GrowCap(ctx->ready_funcs_cap, 16u); if (next < ctx->ready_funcs_cap) UNIMPLEMENTED("update queue: ready-function capacity overflow"); UpdateFuncWork* grown = zig1Realloc(ctx->ready_funcs, (size_t)next * sizeof(*grown)); ctx->ready_funcs = grown; ctx->ready_funcs_cap = next; } static uint32_t compilationUpdateQueueCanonicalFunc( const InternPool* ip, uint32_t value) { if (value >= ip->items_len) return IP_INDEX_NONE; value = ipUnwrapCoercedFunc(ip, value); if (value == IP_INDEX_NONE || value >= ip->items_len || (ip->item_tags[value] != IP_TAG_FUNC_DECL && ip->item_tags[value] != IP_TAG_FUNC_INSTANCE)) return IP_INDEX_NONE; return value; } static uint32_t compilationUpdateQueuePendingFunc( const InternPool* ip, uint32_t entry) { if (entry & PENDING_INSTANCE_FLAG) return compilationUpdateQueueCanonicalFunc( ip, entry & ~PENDING_INSTANCE_FLAG); if (entry >= ip->navs_len) return IP_INDEX_NONE; const IpNav* nav = ipGetNav(ip, entry); if (!nav->has_value_resolved) return IP_INDEX_NONE; return compilationUpdateQueueCanonicalFunc(ip, nav->resolved_value); } static uint32_t compilationUpdateQueueFuncPendingEntry( const InternPool* ip, uint32_t func) { if (func >= ip->items_len) return IP_INDEX_NONE; if (ip->item_tags[func] == IP_TAG_FUNC_DECL) return ipFuncDeclOwnerNav(ip, func); if (ip->item_tags[func] == IP_TAG_FUNC_INSTANCE) return func | PENDING_INSTANCE_FLAG; return IP_INDEX_NONE; } static void compilationUpdateQueueQueueFunc( UpdateQueue* ctx, uint32_t func, uint32_t pending_entry) { const InternPool* ip = ctx->cctx->ip; func = compilationUpdateQueueCanonicalFunc(ip, func); if (func == IP_INDEX_NONE) UNREACHABLE("update queue: invalid function queue identity"); if (compilationU32SetContains(&ctx->analyzed_set, pending_entry) || compilationU32SetContains(&ctx->wanted_funcs, func)) return; compilationU32SetInsert(&ctx->wanted_funcs, func); compilationUpdateQueueGrowPositions( &ctx->func_pos, &ctx->func_pos_cap, func); // Only a function first inserted into wanted_funcs above gets a position. if (ctx->func_pos[func] != UINT32_MAX) UNREACHABLE("update queue: duplicate ready function"); compilationUpdateQueueEnsureFuncCapacity(ctx); uint32_t pos = ctx->ready_funcs_len++; ctx->ready_funcs[pos] = (UpdateFuncWork) { .func = func, .pending_entry = pending_entry, }; ctx->func_pos[func] = pos; } static bool compilationUpdateQueueRemoveFunc(UpdateQueue* ctx, uint32_t func) { compilationUpdateQueueGrowPositions( &ctx->func_pos, &ctx->func_pos_cap, func); uint32_t pos = ctx->func_pos[func]; if (pos == UINT32_MAX) return false; if (pos >= ctx->ready_funcs_len) UNREACHABLE("update queue: invalid ready-function position"); uint32_t last = --ctx->ready_funcs_len; if (pos != last) { UpdateFuncWork work = ctx->ready_funcs[last]; ctx->ready_funcs[pos] = work; if (work.func >= ctx->func_pos_cap || ctx->func_pos[work.func] != last) UNREACHABLE("update queue: stale ready-function position"); ctx->func_pos[work.func] = pos; } ctx->func_pos[func] = UINT32_MAX; return true; } static void compilationUpdateQueueImportPending(UpdateQueue* ctx) { const InternPool* ip = ctx->cctx->ip; while (ctx->pending_import_cursor < ctx->pending.len) { uint32_t entry = ctx->pending.items[ctx->pending_import_cursor++]; uint32_t func = compilationUpdateQueuePendingFunc(ip, entry); if (func == IP_INDEX_NONE) UNIMPLEMENTED("update queue: pending entry has no function"); compilationUpdateQueueQueueFunc(ctx, func, entry); } } static uint32_t compilationNamespaceDeclNav( const InternPool* ip, uint32_t ns_idx, uint32_t decl_inst) { const IpNamespace* ns = &ip->namespaces[ns_idx]; for (uint32_t i = 0; i < ns->pub_navs_len; i++) if (ip->navs[ns->pub_navs[i]].analysis_inst == decl_inst) return ns->pub_navs[i]; for (uint32_t i = 0; i < ns->priv_navs_len; i++) if (ip->navs[ns->priv_navs[i]].analysis_inst == decl_inst) return ns->priv_navs[i]; return IP_INDEX_NONE; } // (part of) Zcu.zig:ensureNavValAnalysisQueued // ScanDeclIter.scanDecl: "if (want_analysis or decl.linkage == .@\"export\") // { try zcu.ensureNavValAnalysisQueued(nav); }", in declaration order. static void compilationQueueScannedExports(Zcu* zcu, uint32_t ns_idx, const Zir* zir, const uint32_t* decls, uint32_t decls_len) { const InternPool* ip = &zcu->intern_pool; for (uint32_t i = 0; i < decls_len; i++) { DeclInfo decl; if (!zirDecodeDeclaration(zir, decls[i], &decl) || !decl.is_export) continue; uint32_t nav = compilationNamespaceDeclNav(ip, ns_idx, decls[i]); if (nav == IP_INDEX_NONE) UNIMPLEMENTED( "ensureNavValAnalysisQueued: scanned export has no Nav"); navQueueValueAnalysis(zcu, nav); } } void compilationUpdateQueueNavFirstWant(UpdateQueue* ctx, uint32_t nav) { compilationUpdateQueueQueueOther( ctx, UPDATE_WORK_NAV_VALUE, nav, NULL, IP_NAMESPACE_NONE, 0, 0); compilationUpdateQueueQueueOther( ctx, UPDATE_WORK_NAV_TYPE, nav, NULL, IP_NAMESPACE_NONE, 0, 0); } void compilationUpdateQueueNavValueEnter(UpdateQueue* ctx, uint32_t nav) { if (nav >= ctx->cctx->ip->navs_len) UNREACHABLE("update queue: entering invalid Nav value"); const IpNav* n = ipGetNav(ctx->cctx->ip, nav); if (!n->has_value_resolved && !n->analysis_wanted) navQueueValueAnalysis(ctx->cctx->zcu, nav); compilationUpdateQueueRemoveOther(ctx, UPDATE_WORK_NAV_VALUE, nav); } void compilationUpdateQueueNavTypeEnter(UpdateQueue* ctx, uint32_t nav) { if (nav >= ctx->cctx->ip->navs_len) UNREACHABLE("update queue: entering invalid Nav type"); if (!ipGetNav(ctx->cctx->ip, nav)->analysis_wanted) navQueueValueAnalysis(ctx->cctx->zcu, nav); compilationUpdateQueueRemoveOther(ctx, UPDATE_WORK_NAV_TYPE, nav); } void compilationUpdateQueueNavValuePrePublish(UpdateQueue* ctx, uint32_t nav) { const Zir* zir; uint32_t inst; if (!semaNavZir(ctx->cctx->zcu, nav, &zir, &inst)) UNIMPLEMENTED("update queue: published Nav has no declaration"); DeclInfo decl; if (!zirDecodeFunctionDeclaration(zir, inst, &decl)) UNREACHABLE("update queue: published Nav is undecodable"); if (!decl.has_type_body) compilationUpdateQueueRemoveOther(ctx, UPDATE_WORK_NAV_TYPE, nav); } void compilationUpdateQueueNavValuePublished( UpdateQueue* ctx, uint32_t nav, uint32_t value) { const Zir* zir; uint32_t inst; if (!semaNavZir(ctx->cctx->zcu, nav, &zir, &inst)) UNIMPLEMENTED("update queue: published Nav has no source"); DeclInfo decl; if (!zirDecodeFunctionDeclaration(zir, inst, &decl)) UNREACHABLE("update queue: published Nav is undecodable"); if (!decl.is_export && !compilationU32SetContains(&ctx->test_functions, nav)) return; uint32_t func = compilationUpdateQueueCanonicalFunc(ctx->cctx->ip, value); if (func == IP_INDEX_NONE) return; uint32_t entry = compilationUpdateQueueFuncPendingEntry(ctx->cctx->ip, func); if (entry == IP_INDEX_NONE) UNREACHABLE("update queue: function has no pending adapter"); compilationUpdateQueueQueueFunc(ctx, func, entry); } static void compilationUpdateQueueEnsureFunc( UpdateQueue* ctx, uint32_t func, const char* loop_msg) { const InternPool* ip = ctx->cctx->ip; func = compilationUpdateQueueCanonicalFunc(ip, func); if (func == IP_INDEX_NONE) UNREACHABLE("update queue: ensuring invalid function"); uint32_t entry = compilationUpdateQueueFuncPendingEntry(ip, func); if (entry == IP_INDEX_NONE) UNREACHABLE("update queue: function has no pending adapter"); compilationUpdateQueueQueueFunc(ctx, func, entry); compilationUpdateQueueRemoveFunc(ctx, func); compilationAnalyzePendingEntry(&ctx->pending_ctx, entry, loop_msg); compilationUpdateQueueImportPending(ctx); } // PerThread.zig:ScanDeclIter.scanDecl's test arm: a test declaration of the // main module joins Zcu.test_functions and is queued for analysis. `test_i` // walks the namespace's test Navs, which scanDecl appended in source order. static void compilationUpdateQueueScanDeclTest(UpdateQueue* ctx, uint32_t ns_idx, uint32_t file_idx, const Zir* zir, uint32_t decl_inst, uint32_t* test_i) { DeclInfo decl; if (!zirDecodeFunctionDeclaration(zir, decl_inst, &decl) || (decl.id != DECL_ID_UNNAMED_TEST && decl.id != DECL_ID_TEST && decl.id != DECL_ID_DECLTEST)) return; Zcu* zcu = ctx->cctx->zcu; const IpNamespace* ns = ipGetNamespace(&zcu->intern_pool, ns_idx); if (*test_i >= ns->test_navs_len) UNREACHABLE("scanDecl: missing namespace test Nav"); uint32_t nav = ns->test_navs[(*test_i)++]; // "if (file.mod != zcu.main_mod) break :a false;". if (zcu->files[file_idx].module_id != ctx->target_module_id) return; u32ListAppend(&zcu->test_functions, nav); compilationU32SetInsert(&ctx->test_functions, nav); navQueueValueAnalysis(zcu, nav); } // Observe ScanDeclIter's completed namespace: its exports and comptime units, // and a test compilation's selected test Navs. static void compilationUpdateQueueNamespaceScanned(void* opaque, uint32_t ns_idx, const Zir* zir, const uint32_t* decls, uint32_t decls_len) { UpdateQueue* ctx = (UpdateQueue*)opaque; CompilationCtx* cctx = ctx->cctx; InternPool* ip = cctx->ip; if (ns_idx >= ip->namespaces_len) UNREACHABLE("update queue: scanned namespace is invalid"); uint32_t file_idx = ipGetNamespace(ip, ns_idx)->file_scope; if (file_idx >= cctx->zcu->num_files) UNREACHABLE("update queue: scanned namespace has invalid file"); if (compilationU32SetContains(&ctx->seen_namespaces, ns_idx)) return; compilationU32SetInsert(&ctx->seen_namespaces, ns_idx); // Exports and comptime declarations are eager in every populated analysis // root and imported namespace. compilationQueueScannedExports(cctx->zcu, ns_idx, zir, decls, decls_len); // ScanDeclIter visits comptime/test declarations in source order during // its unnamed pass. Reconstruct that same work order from `decls`; test // Navs were appended to the namespace in the corresponding subsequence. // Nothing in this loop grows an InternPool arena, so `ns` remains valid. const IpNamespace* ns = ipGetNamespace(ip, ns_idx); uint32_t test_i = 0; uint32_t comptime_i = 0; for (uint32_t i = 0; i < decls_len; i++) { uint32_t off = 0, len = 0; if (compilationDecodeComptimeDecl(zir, decls[i], &off, &len)) { if (comptime_i >= ns->comptime_units_len) UNREACHABLE("update queue: missing comptime unit"); uint32_t cu = ns->comptime_units[comptime_i++]; compilationUpdateQueueQueueOther( ctx, UPDATE_WORK_COMPTIME, cu, zir, ns_idx, off, len); continue; } // scanDecl: "if (!comp.config.is_test) break :a false;". if (cctx->zcu->comp->config.is_test) compilationUpdateQueueScanDeclTest( ctx, ns_idx, file_idx, zir, decls[i], &test_i); } if (comptime_i != ns->comptime_units_len) UNIMPLEMENTED("update queue: excess comptime unit"); } // Compilation.zig:update, Zcu.zig:findOutdatedToAnalyze, // (part of) PerThread.zig:analyzeComptimeUnit // Compilation.update's analysis: populate the analysis roots, then run // Zcu.findOutdatedToAnalyze's work queue (PerThread.analyzeComptimeUnit, // ensureNavValUpToDate, ensureFuncBodyUpToDate) to a fixpoint. A test // compilation also selects the main module's tests (ScanDeclIter.scanDecl). static SemaFuncAirList performAllTheWork(CompilationCtx* cctx) { const CompilationConfig* config = &cctx->zcu->comp->config; compilationRegisterRootFile(cctx); uint32_t target_file = compilationRegisterMainFile(cctx); uint32_t target_module_id = cctx->main_zir == NULL ? ZCU_MODULE_ROOT : ZCU_MODULE_MAIN; UpdateQueue ctx = { .cctx = cctx, .target_module_id = target_module_id, }; ctx.pending_ctx = (SemaPendingCtx) { .cctx = cctx, .list = &ctx.func_airs, .pending = &ctx.pending, .analyzed = &ctx.analyzed, .analyzed_set = &ctx.analyzed_set, .resolving = &ctx.resolving, }; Zcu* zcu = cctx->zcu; zcu->namespace_scan_observer = compilationUpdateQueueNamespaceScanned; zcu->namespace_scan_observer_ctx = &ctx; cctx->ip->resolve_ies = compilationIesResolver; cctx->ip->resolve_ies_ctx = &ctx.pending_ctx; cctx->ip->fnptr_pending = &ctx.pending; if (zcu->update_queue != NULL) UNIMPLEMENTED("update queue: nested update"); zcu->update_queue = &ctx; // Populate the real analysis roots before selecting any work: std, main // in a test compilation, then the three optional root dependencies in // upstream order (Compilation.update's analysis_roots_buffer). Root is // never an analysis root; std/start reaches it through an actual // @import("root"). uint32_t std_file = zptDemandLoadFile(cctx->zcu, ZCU_MODULE_STD, STD_ROOT_SUB_PATH); cctx->zcu->std_file_idx = std_file; PerThread pt = { .zcu = cctx->zcu, .zir = cctx->zcu->files[std_file].zir }; zptEnsureFilePopulated(&pt, std_file); if (config->is_test) { pt.zir = cctx->zcu->files[target_file].zir; zptEnsureFilePopulated(&pt, target_file); } static const char* const dependency_roots[] = { "compiler_rt", "ubsan_rt", "zigc" }; for (uint32_t i = 0; i < sizeof(dependency_roots) / sizeof(dependency_roots[0]); i++) { uint32_t file = zptDemandLoadNamedModule( cctx->zcu, ZCU_MODULE_ROOT, dependency_roots[i]); if (file == UINT32_MAX) continue; pt.zir = cctx->zcu->files[file].zir; zptEnsureFilePopulated(&pt, file); } // Match findOutdatedToAnalyze: first ready function, otherwise the first // ready other key. Selection itself does not mutate membership; the exact // ensure removes its own key at entry, including recursive ensures. while (ctx.ready_funcs_len != 0 || ctx.ready_other_len != 0) { if (ctx.ready_funcs_len != 0) { UpdateFuncWork work = ctx.ready_funcs[0]; compilationUpdateQueueEnsureFunc(&ctx, work.func, "update queue: inferred error set dependency loop"); } else { UpdateWork work = ctx.ready_other[0]; switch (work.kind) { case UPDATE_WORK_NAV_VALUE: zptEnsureNavValUpToDate(cctx->zcu, work.nav, NULL); break; case UPDATE_WORK_NAV_TYPE: if (zptEnsureNavTypeUpToDate(cctx->zcu, work.nav) == IP_INDEX_NONE) zptEnsureNavValUpToDate(cctx->zcu, work.nav, NULL); break; case UPDATE_WORK_COMPTIME: compilationUpdateQueueRemoveOther( &ctx, UPDATE_WORK_COMPTIME, work.nav); zptAnalyzeComptimeUnit(cctx->zcu, work.zir, work.namespace_idx, work.body_off, work.body_len); break; case UPDATE_WORK_STRUCT_DEFAULTS: ensureStructDefaultsResolved(cctx->zcu, work.nav); break; } compilationUpdateQueueImportPending(&ctx); } compilationUpdateQueueImportPending(&ctx); } zcu->update_queue = NULL; zcu->namespace_scan_observer = NULL; zcu->namespace_scan_observer_ctx = NULL; cctx->ip->fnptr_pending = NULL; cctx->ip->resolve_ies = NULL; cctx->ip->resolve_ies_ctx = NULL; free(ctx.func_pos); free(ctx.comptime_pos); free(ctx.struct_defaults_pos); free(ctx.nav_type_pos); free(ctx.nav_value_pos); free(ctx.ready_funcs); free(ctx.ready_other); free(ctx.resolving.items); free(ctx.analyzed.items); free(ctx.analyzed_set.items); free(ctx.analyzed_set.used); free(ctx.seen_namespaces.items); free(ctx.seen_namespaces.used); free(ctx.test_functions.items); free(ctx.test_functions.used); free(ctx.wanted_funcs.items); free(ctx.wanted_funcs.used); free(ctx.wanted_struct_layouts.items); free(ctx.wanted_struct_layouts.used); free(ctx.pending.items); free(ctx.pending_ctx.ready.items); return ctx.func_airs; } // Compilation.zig:update's analysis: performAllTheWork, then a test // compilation's populateTestFunctions. SemaFuncAirList compilationAnalyze(CompilationCtx* cctx) { SemaFuncAirList list = performAllTheWork(cctx); if (cctx->zcu->comp->config.is_test) zptPopulateTestFunctions(cctx->zcu, &list); return list; } // ── The entry point's arguments (start.zig callMain) ──────────────────────── // Resolve a named pub decl of `container_ty` (a struct type) to its Nav, // aborting with `what` context when the container has no namespace or no such // decl. Mirrors the namespace lookups start.zig's comptime references perform. static uint32_t compilationEntryContainerDeclNav(InternPool* ip, uint32_t container_ty, const char* name, const char* what) { if (!ipTagIsAnyStruct(ip->item_tags[container_ty])) UNIMPLEMENTED( "start.callMain: expected a struct type on the Init.Minimal path"); uint32_t ns = ipStructNamespaceOrNone(ip, container_ty); if (ns == IP_NAMESPACE_NONE) UNIMPLEMENTED("start.callMain: container on the Init.Minimal path has " "no namespace"); uint32_t name_str = ipGetOrPutString(ip, name, (uint32_t)strlen(name)); uint32_t nav = ipNamespaceLookup(ip, ns, name_str); if (nav == IP_INDEX_NONE) { fprintf(stderr, "zig1: entry: %s has no decl '%s'\n", what, name); UNIMPLEMENTED("start.callMain: Init.Minimal path decl missing"); } return nav; } // `.{ .block = Environ.Block.global }` — build the `environ` field value for // the wasm32-wasi entry (startWasi passes `std.process.Environ.Block.global`; // callMain coerces it into `.environ = .{ .block = environ }`). The `.global` // initializer is read from the block type's own decl (`pub const global: // GlobalBlock = .{ .use_global = true };`), not re-invented here. static uint32_t compilationEntryEnvironValue(Zcu* zcu, uint32_t environ_ty) { InternPool* ip = &zcu->intern_pool; uint32_t n = ipStructFieldsLen(ip, environ_ty); if (n != 1) UNIMPLEMENTED("start.callMain: Environ shape not { block }"); if (strcmp( ipStringBytes(ip, ipStructFieldName(ip, environ_ty, 0)), "block") != 0) UNIMPLEMENTED("start.callMain: Environ field is not 'block'"); uint32_t block_ty = ipStructFieldType(ip, environ_ty, 0); uint32_t global_nav = compilationEntryContainerDeclNav( ip, block_ty, "global", "Environ.Block"); uint32_t global_ty = IP_INDEX_NONE; uint32_t global_val = zptEnsureNavValUpToDate(zcu, global_nav, &global_ty); if (global_ty != block_ty) UNIMPLEMENTED("start.callMain: Environ.Block.global type mismatch"); const uint32_t elems[1] = { global_val }; return ipGetAggregate(ip, environ_ty, elems, 1); } // `.{ .vector = {} }` — build the `args` field value. On wasm32-wasi // `Args.Vector == void` (the argv is queried at runtime through the WASI // syscalls; see lib/std/process/Args.zig `Vector`), so startWasi's `args = // {}` is the void value. Any other vector shape is a non-wasi profile the // driver does not synthesize. static uint32_t compilationEntryArgsValue(Zcu* zcu, uint32_t args_ty) { InternPool* ip = &zcu->intern_pool; uint32_t n = ipStructFieldsLen(ip, args_ty); if (n != 1) UNIMPLEMENTED("start.callMain: Args shape not { vector }"); if (strcmp(ipStringBytes(ip, ipStructFieldName(ip, args_ty, 0)), "vector") != 0) UNIMPLEMENTED("start.callMain: Args field is not 'vector'"); if (ipStructFieldType(ip, args_ty, 0) != ZIR_REF_VOID_TYPE) UNIMPLEMENTED("start.callMain: non-wasi Args.Vector (expected void)"); const uint32_t elems[1] = { ZIR_REF_VOID_VALUE }; return ipGetAggregate(ip, args_ty, elems, 1); } // Entry-signature dispatch, mirroring lib/std/start.zig `callMain` // (start.zig): with zero parameters `main` is called bare; when the first // parameter type is `std.process.Init.Minimal` (type identity against the // compilation's `std` module, exactly upstream's `fn_info.param_types[0].? == // std.process.Init.Minimal`), the entry receives the wasm32-wasi Minimal // value `.{ .args = .{ .vector = {} }, .environ = .{ .block = .global } }` // (startWasi passes `args = {}` — WASI argv is fetched at runtime via the // WASI syscalls, not the entry argument — and `Environ.Block.global`). The // value is built mechanically from the resolved types: each field by name, // `.global` read from the block type's own decl. Returns the number of entry // arguments (0 or 1) and writes the interned Minimal constant through // `out_arg_const` when it returns 1. Aborts loudly on any other entry // signature (upstream's full-`Init` form needs allocator/Io machinery not yet // ported). Call only on the exe driver path, after compilationAnalyze. static uint32_t compilationEntryMainArgs( CompilationCtx* cctx, uint32_t main_func_decl, uint32_t* out_arg_const) { Zcu* zcu = cctx->zcu; InternPool* ip = &zcu->intern_pool; uint32_t func_ty = ipFuncDeclType(ip, main_func_decl); uint32_t params_len = ipFuncTypeParamsLen(ip, func_ty); // callMain: `if (fn_info.param_types.len == 0) return // wrapMain(root.main())` — the Init.Minimal identity check below happens // only when a first parameter exists, so a parameterless main never // touches std.process. if (params_len == 0) return 0; if (params_len != 1) UNIMPLEMENTED("start.callMain: main takes more than one parameter"); uint32_t param0_ty = ipFuncTypeParamType(ip, func_ty, 0); // Resolve `std.process.Init.Minimal` through the compilation's std module // — the same decl chain the comptime reference in callMain names. uint32_t std_ns = zcu->files[zcu->std_file_idx].ns; uint32_t process_str = ipGetOrPutString(ip, "process", 7); uint32_t process_nav = ipNamespaceLookup(ip, std_ns, process_str); if (process_nav == IP_INDEX_NONE) UNIMPLEMENTED("start.callMain: 'std' has no 'process'"); uint32_t process_ty = zptEnsureNavValUpToDate(zcu, process_nav, NULL); uint32_t init_nav = compilationEntryContainerDeclNav( ip, process_ty, "Init", "std.process"); uint32_t init_ty = zptEnsureNavValUpToDate(zcu, init_nav, NULL); uint32_t minimal_nav = compilationEntryContainerDeclNav( ip, init_ty, "Minimal", "std.process.Init"); uint32_t minimal_ty = zptEnsureNavValUpToDate(zcu, minimal_nav, NULL); // `if (fn_info.param_types[0].? == std.process.Init.Minimal)` — type // identity, not shape. The full-`Init` main form (the else branch of // callMain) needs allocator/Io/preopens machinery not yet ported. if (param0_ty != minimal_ty) UNIMPLEMENTED("start.callMain: main's first parameter is not " "std.process.Init.Minimal"); // `.{ .args = .{ .vector = {} }, .environ = .{ .block = .global } }` — // fill Minimal's fields by name from the resolved type (aggregate element // order is the declared field order), so the constant is derived from the // std under compilation, never a baked-in layout. Param-type resolution // (navFuncDecl on main) already forced these layouts. if (!ensureLayoutResolved(zcu, minimal_ty)) UNIMPLEMENTED("start.callMain: Init.Minimal layout unresolved"); uint32_t fields_len = ipStructFieldsLen(ip, minimal_ty); if (fields_len != 2) UNIMPLEMENTED( "start.callMain: Init.Minimal shape not { environ, args }"); uint32_t elems[2]; for (uint32_t i = 0; i < fields_len; i++) { const char* fname = ipStringBytes(ip, ipStructFieldName(ip, minimal_ty, i)); uint32_t field_ty = ipStructFieldType(ip, minimal_ty, i); if (strcmp(fname, "environ") == 0) elems[i] = compilationEntryEnvironValue(zcu, field_ty); else if (strcmp(fname, "args") == 0) elems[i] = compilationEntryArgsValue(zcu, field_ty); else UNIMPLEMENTED("start.callMain: unexpected Init.Minimal field"); } *out_arg_const = ipGetAggregate(ip, minimal_ty, elems, fields_len); return 1; } void compilationFuncAirListDeinit(SemaFuncAirList* list) { for (uint32_t i = 0; i < list->len; i++) { free(list->items[i].name); airDeinit(&list->items[i].air); } free(list->items); free(list->index_keys); free(list->index_values); free(list->index_used); list->items = NULL; list->len = 0; list->cap = 0; list->index_keys = NULL; list->index_values = NULL; list->index_used = NULL; list->index_len = 0; list->index_cap = 0; } /* ---- end compilation.c ---- */ /* ---- begin comptime_ptr_access.c ---- */ // comptime_ptr_access.c — src/Sema/comptime_ptr_access.zig: loads and stores // through a comptime-known pointer value (loadComptimePtr, storeComptimePtr // and prepareComptimePtrStore over the comptime allocations' MutableValues, // with flattenArray/unflattenArray and recursiveIndex) and the packed arms // that read and write a packed field through its host value. A helper carries // the sema prefix. Declarations are in zig1.h. #ifndef ZIG1_H #include "zig1.h" #endif #include // comptime_ptr_access.zig's `flattenArray`: append the non-array leaves of // `val` (sentinels included) to `out`, skipping the first `*skip`. static bool flattenArray(Zcu* zcu, const MutableValue* val, uint64_t* skip, uint64_t* next_idx, uint32_t* out, uint64_t out_len) { const InternPool* ip = &zcu->intern_pool; if (*next_idx == out_len) return true; uint32_t ty = mutableValueTypeOf(ip, val); uint32_t base_ty; uint64_t base_count; if (!ztArrayBase(ip, ty, &base_ty, &base_count)) return false; if (*skip >= base_count) { *skip -= base_count; return true; } IpArrayInfo info; if (ipArrayInfo(ip, ty, &info) != IP_ARR_OK || info.is_vector) { out[(*next_idx)++] = mutableValueIntern(zcu, val); return true; } uint64_t child_count; if (!ztArrayBase(ip, info.child, &base_ty, &child_count)) return false; for (uint32_t i = 0; i < info.len; i++) { // "Optimization: the `getElem` here may be expensive since we might // intern an element of the `bytes` representation, so avoid doing // it unnecessarily." if (*next_idx == out_len) return true; if (*skip >= child_count) { *skip -= child_count; continue; } MutableValue elem = mutableValueGetElem(zcu, val, i); if (!flattenArray(zcu, &elem, skip, next_idx, out, out_len)) return false; } if (info.sentinel != IP_INDEX_NONE) { MutableValue sent = mutableValueInterned(info.sentinel); return flattenArray(zcu, &sent, skip, next_idx, out, out_len); } return true; } // comptime_ptr_access.zig's `unflattenArray`. `elems_len` stands in for // upstream's slice bounds check; the caller sizes `elems` with ztArrayBase, // which counts exactly the leaves read here, so it never fires. static uint32_t unflattenArray(Zcu* zcu, uint32_t ty, const uint32_t* elems, uint64_t elems_len, uint64_t* next_idx) { InternPool* ip = &zcu->intern_pool; IpArrayInfo info; if (ipArrayInfo(ip, ty, &info) != IP_ARR_OK || info.is_vector) { if (*next_idx >= elems_len) return IP_INDEX_NONE; return zptGetCoerced(zcu, elems[(*next_idx)++], ty); } uint32_t* buf = info.len ? zig1Malloc(info.len * sizeof(uint32_t)) : NULL; uint32_t result = IP_INDEX_NONE; for (uint32_t i = 0; i < info.len; i++) { buf[i] = unflattenArray(zcu, info.child, elems, elems_len, next_idx); if (buf[i] == IP_INDEX_NONE) goto done; } // "TODO: validate sentinel" if (info.sentinel != IP_INDEX_NONE && unflattenArray(zcu, info.child, elems, elems_len, next_idx) == IP_INDEX_NONE) goto done; result = zptAggregateValue(ip, ty, buf, info.len); done: free(buf); return result; } static ComptimeLoadResult semaComptimeLoadResult( ComptimeLoadTag tag, uint32_t ty) { ComptimeLoadResult r = { tag, mutableValueInterned(IP_INDEX_NONE), ty }; return r; } static ComptimeLoadResult semaComptimeLoadSuccess(MutableValue mv) { ComptimeLoadResult r = { ZPT_LOAD_SUCCESS, mv, IP_INDEX_NONE }; return r; } // loadComptimePtrInner's struct arm: the field of `val` (of struct type `ty`) // holding all `size` bytes at `*offset`, with `*offset` rebased onto it; // false when the load spans fields. static bool semaStructFieldHolding(Zcu* zcu, MutableValue* val, uint32_t ty, uint64_t* offset, uint64_t size) { const InternPool* ip = &zcu->intern_pool; for (uint32_t f = 0; f < ipStructFieldsLen(ip, ty); f++) { uint64_t start = ipStructFieldOffset(ip, ty, f); uint64_t len = ztAbiSize(zcu, ipStructFieldType(ip, ty, f)); if (len == UINT64_MAX) return false; if (*offset >= start && *offset + size <= start + len) { *val = mutableValueGetElem(zcu, val, f); *offset -= start; return true; } } return false; } // comptime_ptr_access.zig's `loadComptimePtrInner`: load `load_ty` from the // pointer value `ptr`, whose type is ignored. `array_offset` counts `load_ty` // elements to skip when `load_ty` is an array. static ComptimeLoadResult loadComptimePtrInner( Zcu* zcu, uint32_t ptr, uint32_t load_ty, uint64_t array_offset) { InternPool* ip = &zcu->intern_pool; if (ptr >= ip->items_len) return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); if (ipIsUndef(ip, ptr)) return semaComptimeLoadResult(ZPT_LOAD_UNDEF, IP_INDEX_NONE); // `switch (ptr.base_addr)`. zig1 spells `.field` / `.arr_elem` of a Nav, // uav or comptime field as that base plus a byte offset. MutableValue base_val; uint64_t byte_offset = 0; if (!ipPtrValueByteOffset(ip, ptr, &byte_offset)) return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); uint8_t tag = ip->item_tags[ptr]; if (tag == IP_TAG_PTR_NAV) { IpPtrNav pn; if (ipDecodePtrNavAny(ip, ptr, &pn) != IP_PN_OK) return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); // `"try sema.ensureNavResolved(block, src, nav_id, .fully);"`, a // `var` included. uint32_t nav_ty = IP_INDEX_NONE; uint32_t nav_val = zptEnsureNavValUpToDate(zcu, pn.nav_index, &nav_ty); if (!ipGetNav(ip, pn.nav_index)->is_const) return semaComptimeLoadResult( ZPT_LOAD_RUNTIME_LOAD, IP_INDEX_NONE); base_val = mutableValueInterned(nav_val); // "We let `.@"extern"` through here if it's a fn." if (ipGetNav(ip, pn.nav_index)->is_extern && ip->item_tags[nav_ty] != IP_TAG_TYPE_FUNCTION) return semaComptimeLoadResult( ZPT_LOAD_RUNTIME_LOAD, IP_INDEX_NONE); } else if (ipIsPtrUav(ip, ptr)) { base_val = mutableValueInterned(ipPtrUavValue(ip, ptr)); } else if (ipIsPtrComptimeField(ip, ptr)) { base_val = mutableValueInterned(ipPtrComptimeFieldValue(ip, ptr)); } else if (ipIsPtrInt(ip, ptr)) { return semaComptimeLoadResult(ZPT_LOAD_RUNTIME_LOAD, IP_INDEX_NONE); } else if (ipIsPtrEuPayload(ip, ptr) || ipIsPtrOptPayload(ip, ptr)) { bool eu = ipIsPtrEuPayload(ip, ptr); uint32_t base_ptr = eu ? ipPtrEuPayloadBase(ip, ptr) : ipPtrOptPayloadBase(ip, ptr); IpPtrInfo base_info; if (ipPtrInfo(ip, ipTypeOf(ip, base_ptr), &base_info) != IP_PTRI_OK) return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); ComptimeLoadResult r = loadComptimePtrInner(zcu, base_ptr, base_info.child, 0); if (r.tag != ZPT_LOAD_SUCCESS) return r; MutableValueUnpack kind; base_val = mutableValueUnpack(ip, &r.success, &kind, NULL); if (kind == MV_UNPACK_UNDEF) return semaComptimeLoadResult(ZPT_LOAD_UNDEF, IP_INDEX_NONE); if (kind == MV_UNPACK_NONE) return semaComptimeLoadResult( eu ? ZPT_LOAD_ERR_PAYLOAD : ZPT_LOAD_NULL_PAYLOAD, IP_INDEX_NONE); } else if (ipIsPtrElem(ip, ptr)) { // "We have a comptime-only array. This case is a little nasty." uint32_t base_ptr = ipPtrElemBase(ip, ptr); IpPtrInfo base_info; uint32_t load_one_ty; uint64_t load_count; if (ipPtrInfo(ip, ipTypeOf(ip, base_ptr), &base_info) != IP_PTRI_OK || !ztArrayBase(ip, load_ty, &load_one_ty, &load_count)) return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); uint64_t count = load_one_ty == base_info.child ? load_count : 1; if (count > UINT32_MAX) return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); uint32_t want_ty = ipGetArrayType( ip, (uint32_t)count, base_info.child, IP_INDEX_NONE); ComptimeLoadResult r = loadComptimePtrInner( zcu, base_ptr, want_ty, ipPtrElemIndex(ip, ptr)); if (r.tag != ZPT_LOAD_SUCCESS) return r; base_val = r.success; } else if (ipIsPtrField(ip, ptr)) { // "Field of a slice, or of an auto-layout struct or union." uint32_t base_ptr = ipPtrFieldBase(ip, ptr); IpPtrInfo base_info; if (ipPtrInfo(ip, ipTypeOf(ip, base_ptr), &base_info) != IP_PTRI_OK) return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); ComptimeLoadResult r = loadComptimePtrInner(zcu, base_ptr, base_info.child, 0); if (r.tag != ZPT_LOAD_SUCCESS) return r; uint32_t index = ipPtrFieldIndex(ip, ptr); uint32_t agg_ty = mutableValueTypeOf(ip, &r.success); if (ip->item_tags[agg_ty] == IP_TAG_TYPE_UNION) { // `.@"union"`: undef, then the active tag must name the field. uint32_t un_tag; if (r.success.tag == MV_UN) { un_tag = r.success.u.un.tag; base_val = *r.success.u.un.payload; } else if (ipIsUndef(ip, r.success.u.interned)) { return semaComptimeLoadResult(ZPT_LOAD_UNDEF, IP_INDEX_NONE); } else if (ip->item_tags[r.success.u.interned] == IP_TAG_UNION_VALUE) { const uint32_t* un = &ip->extra[ip->item_datas[r.success.u.interned]]; un_tag = un[1]; base_val = mutableValueInterned(un[2]); } else { return semaComptimeLoadResult( ZPT_LOAD_UNMODELED, IP_INDEX_NONE); } if (semaUnionFieldIndexFromTag(ip, agg_ty, un_tag) != index) return semaComptimeLoadResult( ZPT_LOAD_INACTIVE_UNION_FIELD, IP_INDEX_NONE); } else if (ipTagIsAnyStruct(ip->item_tags[agg_ty]) || ztIsTuple(ip, agg_ty) || ztIsSlice(ip, agg_ty)) { base_val = mutableValueGetElem(zcu, &r.success, index); } else { return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); } } else if (ipIsPtrComptimeAlloc(ip, ptr)) { base_val = getComptimeAlloc(zcu, ipPtrComptimeAllocIndex(ip, ptr))->val; } else { return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); } uint32_t base_ty = mutableValueTypeOf(ip, &base_val); if (base_ty == IP_INDEX_NONE) return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); IpArrayInfo load_info; bool load_is_array = ipArrayInfo(ip, load_ty, &load_info) == IP_ARR_OK && !load_info.is_vector; if (byte_offset == 0 && (!load_is_array || array_offset == 0) && coerceInMemoryAllowed(zcu, ip, load_ty, base_ty, false)) // "We already have a value which is IMC to the desired type." return semaComptimeLoadSuccess(base_val); // restructure_array: "We might also be changing the length of an array, // or restructuring it. e.g. [1][2][3]T -> [3][2]T." uint32_t load_one_ty, val_one_ty; uint64_t load_count, val_count; if (ztArrayBase(ip, load_ty, &load_one_ty, &load_count) && ztArrayBase(ip, base_ty, &val_one_ty, &val_count)) { bool restructure = true; uint64_t extra_base_index = 0; if (byte_offset != 0) { uint64_t elem_len, elem_align, elem_bits; restructure = false; if (ztComptimeOnlyStatus(zcu, load_one_ty) == ZT_COMPTIME_ONLY_NO && ztLayoutInfo( zcu, ip, load_one_ty, &elem_len, &elem_align, &elem_bits) && elem_len != 0 && byte_offset % elem_len == 0) { restructure = true; extra_base_index = byte_offset / elem_len; } } if (restructure && coerceInMemoryAllowed( zcu, ip, load_one_ty, val_one_ty, false)) { // "Changing the length of an array." uint64_t skip_base = extra_base_index; if (load_is_array) { uint32_t child_one_ty; uint64_t child_count; if (!ztArrayBase( ip, load_info.child, &child_one_ty, &child_count)) return semaComptimeLoadResult( ZPT_LOAD_UNMODELED, IP_INDEX_NONE); skip_base += child_count * array_offset; } if (skip_base + load_count > val_count) return semaComptimeLoadResult(ZPT_LOAD_OUT_OF_BOUNDS, base_ty); uint32_t* elems = load_count ? zig1Malloc(load_count * sizeof(uint32_t)) : NULL; uint64_t skip = skip_base, next_idx = 0; uint32_t val = IP_INDEX_NONE; if (flattenArray( zcu, &base_val, &skip, &next_idx, elems, load_count) && next_idx == load_count) { next_idx = 0; val = unflattenArray( zcu, load_ty, elems, load_count, &next_idx); } free(elems); return val == IP_INDEX_NONE ? semaComptimeLoadResult(ZPT_LOAD_UNMODELED, val) : semaComptimeLoadSuccess(mutableValueInterned(val)); } } // zig1 divergence: the byte offset that spells a `.field` / `.arr_elem` // base address of a Nav, uav or comptime field is walked back down the // aggregate, whatever its layout. uint32_t projected = IP_INDEX_NONE; if (array_offset == 0 && base_val.tag == MV_INTERNED && ctProjectValueAtOffset(zcu, base_val.u.interned, base_ty, byte_offset, load_ty, 0, &projected)) return semaComptimeLoadSuccess(mutableValueInterned(projected)); // The same divergence: upstream's `.field` base is the auto-layout // struct field holding the whole load, so descend into it before the // layout check. uint64_t field_load_size = ztAbiSize(zcu, load_ty); while (array_offset == 0 && base_val.tag == MV_INTERNED && field_load_size != UINT64_MAX && ipTagIsAnyStruct(ip->item_tags[base_ty]) && !ipStructIsPacked(ip, base_ty) && !ztHasWellDefinedLayout(ip, base_ty) && !ipIsUndef(ip, base_val.u.interned)) { if (!semaStructFieldHolding( zcu, &base_val, base_ty, &byte_offset, field_load_size)) break; base_ty = mutableValueTypeOf(ip, &base_val); } // "We need to reinterpret memory, which is only possible if neither the // load type nor the type of the base value are comptime-only." if (!ztHasWellDefinedLayout(ip, load_ty)) return semaComptimeLoadResult(ZPT_LOAD_NEEDED_WELL_DEFINED, load_ty); if (!ztHasWellDefinedLayout(ip, base_ty)) return semaComptimeLoadResult(ZPT_LOAD_NEEDED_WELL_DEFINED, base_ty); uint64_t load_size = ztAbiSize(zcu, load_ty); uint64_t base_size = ztAbiSize(zcu, base_ty); uint64_t child_size = load_is_array ? ztAbiSize(zcu, load_info.child) : 0; if (load_size == UINT64_MAX || base_size == UINT64_MAX || child_size == UINT64_MAX) return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); MutableValue cur_val = base_val; uint64_t cur_offset = byte_offset + child_size * array_offset; if (cur_offset + load_size > base_size) return semaComptimeLoadResult(ZPT_LOAD_OUT_OF_BOUNDS, base_ty); // "If the memory region we're interested in refers to one field or // array element, let's just look at that." for (;;) { uint32_t cur_ty = mutableValueTypeOf(ip, &cur_val); IpArrayInfo cur_array; if (ipArrayInfo(ip, cur_ty, &cur_array) == IP_ARR_OK && !cur_array.is_vector) { uint64_t elem_size = ztAbiSize(zcu, cur_array.child); if (elem_size == UINT64_MAX || elem_size == 0) break; uint64_t elem_idx = cur_offset / elem_size; if (cur_offset + load_size > elem_size * (elem_idx + 1)) break; cur_val = mutableValueGetElem(zcu, &cur_val, (uint32_t)elem_idx); cur_offset -= elem_idx * elem_size; } else if (ipTagIsAnyStruct(ip->item_tags[cur_ty]) && ipStructIsExtern(ip, cur_ty)) { if (!semaStructFieldHolding( zcu, &cur_val, cur_ty, &cur_offset, load_size)) break; } else { break; } } // "Fast path: check again if we're now at the type we want to load." if (cur_offset == 0 && mutableValueTypeOf(ip, &cur_val) == load_ty) return semaComptimeLoadSuccess(cur_val); uint32_t result; switch (castMemory(zcu, mutableValueIntern(zcu, &cur_val), load_ty, cur_offset, &result)) { case ZPT_CAST_OK: return semaComptimeLoadSuccess(mutableValueInterned(result)); case ZPT_CAST_RUNTIME: return semaComptimeLoadResult(ZPT_LOAD_RUNTIME_LOAD, IP_INDEX_NONE); default: return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); } } // comptime_ptr_access.zig:loadComptimePtr ComptimeLoadResult loadComptimePtr(Zcu* zcu, uint32_t ptr) { InternPool* ip = &zcu->intern_pool; IpPtrInfo info; if (ptr >= ip->items_len || ipPtrInfo(ip, ipTypeOf(ip, ptr), &info) != IP_PTRI_OK) return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); // zirRef's `ptr_uav` tail mints `*const ` for a type value's uav // (sema.c); its pointee is that value. if (info.child == IP_INDEX_NONE) return (ip->item_tags[ptr] == IP_TAG_PTR_UAV && ipPtrUavByteOffset(ip, ptr) == 0) ? semaComptimeLoadSuccess( mutableValueInterned(ipPtrUavValue(ip, ptr))) : semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); // zig1 still types some generic-body pointers `*generic_poison`. if (info.child == IP_INDEX_GENERIC_POISON_TYPE) return semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); uint32_t host_size = IP_PTR_PACKED_HOST_SIZE(info.packed_offset); if (host_size == 0) return loadComptimePtrInner(zcu, ptr, info.child, 0); uint32_t vector_index = IP_PTR_FLAGS_VECTOR_INDEX(info.flags); ComptimeLoadResult r; if (vector_index == IP_PTR_VECTOR_INDEX_NONE) { // The host loaded as `u(host_size * 8)`, the element read out of its // packed memory at `bit_offset` (semaPackedPtrLoad answers none for // `.exceeds_host_size`). uint32_t load_ty = ipGetIntType(ip, false, host_size * 8u); r = loadComptimePtrInner(zcu, ptr, load_ty, 0); if (r.tag != ZPT_LOAD_SUCCESS) return r; uint32_t val = semaPackedPtrLoad(zcu, ipTypeOf(ip, ptr), load_ty, mutableValueIntern(zcu, &r.success)); return val == IP_INDEX_NONE ? semaComptimeLoadResult(ZPT_LOAD_EXCEEDS_HOST_SIZE, val) : semaComptimeLoadSuccess(mutableValueInterned(val)); } if (vector_index >= host_size) return semaComptimeLoadResult( ZPT_LOAD_EXCEEDS_HOST_SIZE, IP_INDEX_NONE); r = loadComptimePtrInner( zcu, ptr, ipGetVectorType(ip, host_size, info.child), 0); if (r.tag != ZPT_LOAD_SUCCESS) return r; uint32_t vec = mutableValueIntern(zcu, &r.success); uint32_t elem; if (ipIsUndef(ip, vec)) return semaComptimeLoadSuccess( mutableValueInterned(ipGetUndef(ip, info.child))); return ipAggregateElemValue(ip, vec, vector_index, &elem) == IP_AGG_ELEM_STORED ? semaComptimeLoadSuccess(mutableValueInterned(elem)) : semaComptimeLoadResult(ZPT_LOAD_UNMODELED, IP_INDEX_NONE); } // comptime_ptr_access.zig's `ComptimeStoreStrategy`: `.direct` stores to // `val`, `.index` at `elem_index` of the array `val`, `.flat_index` at // `flat_elem_index` of `val`'s flattened base elements, `.reinterpret` at // `byte_offset` into `val`'s memory; the rest are the error cases, shared // with ComptimeStoreTag. typedef enum { SEMA_STORE_STRAT_DIRECT, SEMA_STORE_STRAT_INDEX, SEMA_STORE_STRAT_FLAT_INDEX, SEMA_STORE_STRAT_REINTERPRET, SEMA_STORE_STRAT_COMPTIME_FIELD, SEMA_STORE_STRAT_ERROR, } ComptimeStoreStrategyTag; typedef struct { ComptimeStoreStrategyTag tag; MutableValue* val; uint64_t index; // elem_index, flat_elem_index or byte_offset ComptimeStoreTag err; uint32_t err_ty; // needed_well_defined / out_of_bounds } ComptimeStoreStrategy; static ComptimeStoreStrategy semaStoreStrat( ComptimeStoreStrategyTag tag, MutableValue* val, uint64_t index) { ComptimeStoreStrategy s = { tag, val, index, ZPT_STORE_SUCCESS, 0 }; return s; } static ComptimeStoreStrategy semaStoreStratErr( ComptimeStoreTag err, uint32_t ty) { ComptimeStoreStrategy s = { SEMA_STORE_STRAT_ERROR, NULL, 0, err, ty }; return s; } // comptime_ptr_access.zig:recursiveIndex // Treat `*index` as an index into the base elements of the nested array // `mv`: the innermost array holding it and the index within it, the last // level not dereferenced so setElem can keep a compact representation. static bool recursiveIndex( Zcu* zcu, MutableValue* mv, uint64_t* index, MutableValue** out_arr) { const InternPool* ip = &zcu->intern_pool; uint32_t ty = mutableValueTypeOf(ip, mv); uint32_t base_ty; uint64_t ty_base_elems; IpArrayInfo info; if (!ztArrayBase(ip, ty, &base_ty, &ty_base_elems) || ipArrayInfo(ip, ty, &info) != IP_ARR_OK) UNREACHABLE("recursiveIndex: not an array"); if (*index >= ty_base_elems) { *index -= ty_base_elems; return false; } IpArrayInfo elem_info; if (ipArrayInfo(ip, info.child, &elem_info) != IP_ARR_OK || elem_info.is_vector) { *out_arr = mv; return true; } for (uint32_t i = 0; i < info.storage_len; i++) if (recursiveIndex(zcu, mutableValueElem(zcu, mv, i), index, out_arr)) return true; UNREACHABLE("recursiveIndex: index past the array"); ZIG1_TCC_RETURN(false); } // The value a base strategy addresses: `.direct`'s value, or `.index`'s // element (prepareComptimePtrStore's payload and field arms). static MutableValue* semaStoreStratValue(Zcu* zcu, ComptimeStoreStrategy s) { if (s.tag == SEMA_STORE_STRAT_INDEX) return mutableValueElem(zcu, s.val, (uint32_t)s.index); if (s.tag != SEMA_STORE_STRAT_DIRECT) UNREACHABLE("prepareComptimePtrStore: base is not a single value"); return s.val; } // comptime_ptr_access.zig:prepareComptimePtrStore // Decide how a comptime store of `store_ty` through `ptr` is performed, // ignoring the pointer's type. `array_offset` counts `store_ty` elements to // skip when `store_ty` is an array. static ComptimeStoreStrategy prepareComptimePtrStore( Zcu* zcu, uint32_t ptr, uint32_t store_ty, uint64_t array_offset) { InternPool* ip = &zcu->intern_pool; if (ipIsUndef(ip, ptr)) return semaStoreStratErr(ZPT_STORE_UNDEF, IP_INDEX_NONE); uint64_t byte_offset = 0; if (!ipPtrValueByteOffset(ip, ptr, &byte_offset)) UNIMPLEMENTED("prepareComptimePtrStore: pointer byte offset"); // `base_strat` is never an error case. ComptimeStoreStrategy base_strat; uint8_t tag = ip->item_tags[ptr]; if (tag == IP_TAG_PTR_NAV || ipIsPtrUav(ip, ptr) || ipIsPtrInt(ip, ptr)) { return semaStoreStratErr(ZPT_STORE_RUNTIME_STORE, IP_INDEX_NONE); } else if (ipIsPtrComptimeField(ip, ptr)) { return semaStoreStrat(SEMA_STORE_STRAT_COMPTIME_FIELD, NULL, 0); } else if (ipIsPtrComptimeAlloc(ip, ptr)) { base_strat = semaStoreStrat(SEMA_STORE_STRAT_DIRECT, &getComptimeAlloc(zcu, ipPtrComptimeAllocIndex(ip, ptr))->val, 0); } else if (ipIsPtrEuPayload(ip, ptr) || ipIsPtrOptPayload(ip, ptr)) { bool eu = ipIsPtrEuPayload(ip, ptr); uint32_t base_ptr = eu ? ipPtrEuPayloadBase(ip, ptr) : ipPtrOptPayloadBase(ip, ptr); IpPtrInfo base_info; if (ipPtrInfo(ip, ipTypeOf(ip, base_ptr), &base_info) != IP_PTRI_OK) UNREACHABLE("prepareComptimePtrStore: payload base type"); ComptimeStoreStrategy r = prepareComptimePtrStore(zcu, base_ptr, base_info.child, 0); if (r.tag == SEMA_STORE_STRAT_ERROR || r.tag == SEMA_STORE_STRAT_COMPTIME_FIELD) return r; MutableValue* val = semaStoreStratValue(zcu, r); mutableValueUnintern(zcu, val, false, false); if (val->tag == (eu ? MV_EU_PAYLOAD : MV_OPT_PAYLOAD)) { base_strat = semaStoreStrat( SEMA_STORE_STRAT_DIRECT, val->u.opt_payload.child, 0); } else if (val->tag == MV_INTERNED && ipIsUndef(ip, val->u.interned)) { return semaStoreStratErr(ZPT_STORE_UNDEF, IP_INDEX_NONE); } else { return semaStoreStratErr( eu ? ZPT_STORE_ERR_PAYLOAD : ZPT_STORE_NULL_PAYLOAD, IP_INDEX_NONE); } } else if (ipIsPtrElem(ip, ptr)) { // "We have a comptime-only array. This case is a little nasty." uint32_t base_ptr = ipPtrElemBase(ip, ptr); IpPtrInfo base_info; uint32_t store_one_ty; uint64_t store_count; if (ipPtrInfo(ip, ipTypeOf(ip, base_ptr), &base_info) != IP_PTRI_OK || !ztArrayBase(ip, store_ty, &store_one_ty, &store_count)) UNREACHABLE("prepareComptimePtrStore: element base type"); uint64_t count = store_one_ty == base_info.child ? store_count : 1; if (count > UINT32_MAX) UNIMPLEMENTED("prepareComptimePtrStore: wide element store"); uint32_t want_ty = ipGetArrayType( ip, (uint32_t)count, base_info.child, IP_INDEX_NONE); base_strat = prepareComptimePtrStore( zcu, base_ptr, want_ty, ipPtrElemIndex(ip, ptr)); if (base_strat.tag == SEMA_STORE_STRAT_ERROR || base_strat.tag == SEMA_STORE_STRAT_COMPTIME_FIELD) return base_strat; if (base_strat.tag == SEMA_STORE_STRAT_REINTERPRET) UNREACHABLE("prepareComptimePtrStore: comptime-only array " "reinterpreted"); } else if (ipIsPtrField(ip, ptr)) { // "Field of a slice, or of an auto-layout struct or union." uint32_t base_ptr = ipPtrFieldBase(ip, ptr); IpPtrInfo base_info; if (ipPtrInfo(ip, ipTypeOf(ip, base_ptr), &base_info) != IP_PTRI_OK) UNREACHABLE("prepareComptimePtrStore: field base type"); ComptimeStoreStrategy r = prepareComptimePtrStore(zcu, base_ptr, base_info.child, 0); if (r.tag == SEMA_STORE_STRAT_ERROR || r.tag == SEMA_STORE_STRAT_COMPTIME_FIELD) return r; MutableValue* agg_val = semaStoreStratValue(zcu, r); uint32_t agg_ty = mutableValueTypeOf(ip, agg_val); uint32_t index = ipPtrFieldIndex(ip, ptr); if (ip->item_tags[agg_ty] == IP_TAG_TYPE_UNION) { if (agg_val->tag == MV_INTERNED && ipIsUndef(ip, agg_val->u.interned)) return semaStoreStratErr(ZPT_STORE_UNDEF, IP_INDEX_NONE); mutableValueUnintern(zcu, agg_val, false, false); if (agg_val->tag != MV_UN) UNREACHABLE("prepareComptimePtrStore: union value"); if (semaUnionFieldIndexFromTag(ip, agg_ty, agg_val->u.un.tag) != index) return semaStoreStratErr( ZPT_STORE_INACTIVE_UNION_FIELD, IP_INDEX_NONE); base_strat = semaStoreStrat( SEMA_STORE_STRAT_DIRECT, agg_val->u.un.payload, 0); } else { base_strat = semaStoreStrat(SEMA_STORE_STRAT_DIRECT, mutableValueElem(zcu, agg_val, index), 0); } } else { UNIMPLEMENTED("prepareComptimePtrStore: pointer base address"); } IpArrayInfo store_arr; bool store_is_array = ipArrayInfo(ip, store_ty, &store_arr) == IP_ARR_OK && !store_arr.is_vector; if (byte_offset == 0 && (!store_is_array || array_offset == 0) && (base_strat.tag == SEMA_STORE_STRAT_DIRECT || base_strat.tag == SEMA_STORE_STRAT_INDEX)) { uint32_t base_val_ty = mutableValueTypeOf(ip, base_strat.val); IpArrayInfo idx_arr; if (base_strat.tag == SEMA_STORE_STRAT_INDEX) { if (ipArrayInfo(ip, base_val_ty, &idx_arr) != IP_ARR_OK) UNREACHABLE("prepareComptimePtrStore: index base not array"); base_val_ty = idx_arr.child; } // "The base strategy already gets us a value which the desired // type is IMC to." if (coerceInMemoryAllowed(zcu, ip, base_val_ty, store_ty, true)) return base_strat; } // restructure_array: "We might also be changing the length of an array, // or restructuring it. e.g. [1][2][3]T -> [3][2]T." uint32_t store_one_ty; uint64_t store_count; if (base_strat.tag != SEMA_STORE_STRAT_REINTERPRET && ztArrayBase(ip, store_ty, &store_one_ty, &store_count)) { bool restructure = true; uint64_t extra_base_index = 0; if (byte_offset != 0) { uint64_t elem_len, elem_align, elem_bits; restructure = false; if (ztComptimeOnlyStatus(zcu, store_one_ty) == ZT_COMPTIME_ONLY_NO && ztLayoutInfo( zcu, ip, store_one_ty, &elem_len, &elem_align, &elem_bits) && elem_len != 0 && byte_offset % elem_len == 0) { restructure = true; extra_base_index = byte_offset / elem_len; } } MutableValue* base_val = base_strat.val; uint64_t base_elem_offset = 0; uint32_t oob_ty = mutableValueTypeOf(ip, base_val); if (base_strat.tag == SEMA_STORE_STRAT_INDEX) { IpArrayInfo idx_arr; uint32_t one_ty; uint64_t elem_count; if (ipArrayInfo(ip, oob_ty, &idx_arr) != IP_ARR_OK || !ztArrayBase(ip, idx_arr.child, &one_ty, &elem_count)) UNREACHABLE("prepareComptimePtrStore: index base not array"); base_elem_offset = elem_count * base_strat.index; oob_ty = idx_arr.child; } else if (base_strat.tag == SEMA_STORE_STRAT_FLAT_INDEX) { base_elem_offset = base_strat.index; } uint32_t val_one_ty; uint64_t val_count; if (restructure && ztArrayBase( ip, mutableValueTypeOf(ip, base_val), &val_one_ty, &val_count) && coerceInMemoryAllowed( zcu, ip, val_one_ty, store_one_ty, true)) { if (base_elem_offset + extra_base_index + store_count > val_count) return semaStoreStratErr(ZPT_STORE_OUT_OF_BOUNDS, oob_ty); if (store_is_array) { uint32_t child_one_ty; uint64_t child_count; if (!ztArrayBase( ip, store_arr.child, &child_one_ty, &child_count)) UNREACHABLE("prepareComptimePtrStore: array base"); return semaStoreStrat(SEMA_STORE_STRAT_FLAT_INDEX, base_val, child_count * array_offset + base_elem_offset + extra_base_index); } // "`base_val` must be an array, since otherwise the "direct // reinterpret" logic above noticed it." uint64_t index = base_elem_offset + extra_base_index; MutableValue* arr_val; if (!recursiveIndex(zcu, base_val, &index, &arr_val)) UNREACHABLE("prepareComptimePtrStore: index past the array"); return semaStoreStrat(SEMA_STORE_STRAT_INDEX, arr_val, index); } } // "We need to reinterpret memory, which is only possible if neither the // store type nor the type of the base value have an ill-defined layout." if (!ztHasWellDefinedLayout(ip, store_ty)) return semaStoreStratErr(ZPT_STORE_NEEDED_WELL_DEFINED, store_ty); MutableValue* cur_val = base_strat.val; uint64_t cur_offset = 0; uint32_t base_val_ty = mutableValueTypeOf(ip, cur_val); if (base_strat.tag == SEMA_STORE_STRAT_INDEX) { IpArrayInfo idx_arr; if (ipArrayInfo(ip, base_val_ty, &idx_arr) != IP_ARR_OK) UNREACHABLE("prepareComptimePtrStore: index base not array"); cur_offset = base_strat.index * ztAbiSize(zcu, idx_arr.child); } else if (base_strat.tag == SEMA_STORE_STRAT_FLAT_INDEX) { uint32_t one_ty; uint64_t count; if (!ztArrayBase(ip, base_val_ty, &one_ty, &count)) UNREACHABLE("prepareComptimePtrStore: flat base not array"); cur_offset = base_strat.index * ztAbiSize(zcu, one_ty); } else if (base_strat.tag == SEMA_STORE_STRAT_REINTERPRET) { cur_offset = base_strat.index; } cur_offset += byte_offset; if (!ztHasWellDefinedLayout(ip, base_val_ty)) return semaStoreStratErr(ZPT_STORE_NEEDED_WELL_DEFINED, base_val_ty); if (store_is_array && array_offset > 0) cur_offset += ztAbiSize(zcu, store_arr.child) * array_offset; uint64_t need_bytes = ztAbiSize(zcu, store_ty); if (cur_offset + need_bytes > ztAbiSize(zcu, base_val_ty)) return semaStoreStratErr(ZPT_STORE_OUT_OF_BOUNDS, base_val_ty); // "If the memory region we're interested in refers to one field or // array element, let's just look at that." for (;;) { uint32_t cur_ty = mutableValueTypeOf(ip, cur_val); IpArrayInfo cur_array; if (ipArrayInfo(ip, cur_ty, &cur_array) == IP_ARR_OK && !cur_array.is_vector) { uint64_t elem_size = ztAbiSize(zcu, cur_array.child); if (elem_size == 0) break; uint64_t elem_idx = cur_offset / elem_size; if (cur_offset + need_bytes > elem_size * (elem_idx + 1)) break; cur_val = mutableValueElem(zcu, cur_val, (uint32_t)elem_idx); cur_offset -= elem_idx * elem_size; } else if (ipTagIsAnyStruct(ip->item_tags[cur_ty]) && ipStructIsExtern(ip, cur_ty)) { uint32_t f = 0; uint32_t fields = ipStructFieldsLen(ip, cur_ty); for (; f < fields; f++) { uint64_t start_off = ipStructFieldOffset(ip, cur_ty, f); uint64_t end_off = start_off + ztAbiSize(zcu, ipStructFieldType(ip, cur_ty, f)); if (cur_offset >= start_off && cur_offset + need_bytes <= end_off) { cur_val = mutableValueElem(zcu, cur_val, f); cur_offset -= start_off; break; } } if (f == fields) break; // "pointer spans multiple fields" } else { break; } } // "Fast path: check again if we're now at the type we want to store." if (cur_offset == 0 && mutableValueTypeOf(ip, cur_val) == store_ty) return semaStoreStrat(SEMA_STORE_STRAT_DIRECT, cur_val, 0); return semaStoreStrat(SEMA_STORE_STRAT_REINTERPRET, cur_val, cur_offset); } static ComptimeStoreResult semaStoreResult( ComptimeStoreTag tag, uint32_t val) { ComptimeStoreResult r = { tag, val }; return r; } // `inline else => |payload, tag| return @unionInit(ComptimeStoreResult, // @tagName(tag), payload)`: a failed load as the store's result. static ComptimeStoreResult semaStoreFromLoad(ComptimeLoadResult r) { switch (r.tag) { case ZPT_LOAD_RUNTIME_LOAD: return semaStoreResult(ZPT_STORE_RUNTIME_STORE, IP_INDEX_NONE); case ZPT_LOAD_UNDEF: return semaStoreResult(ZPT_STORE_UNDEF, IP_INDEX_NONE); case ZPT_LOAD_ERR_PAYLOAD: return semaStoreResult(ZPT_STORE_ERR_PAYLOAD, IP_INDEX_NONE); case ZPT_LOAD_NULL_PAYLOAD: return semaStoreResult(ZPT_STORE_NULL_PAYLOAD, IP_INDEX_NONE); case ZPT_LOAD_INACTIVE_UNION_FIELD: return semaStoreResult(ZPT_STORE_INACTIVE_UNION_FIELD, IP_INDEX_NONE); case ZPT_LOAD_NEEDED_WELL_DEFINED: return semaStoreResult(ZPT_STORE_NEEDED_WELL_DEFINED, r.ty); case ZPT_LOAD_OUT_OF_BOUNDS: return semaStoreResult(ZPT_STORE_OUT_OF_BOUNDS, r.ty); case ZPT_LOAD_EXCEEDS_HOST_SIZE: return semaStoreResult(ZPT_STORE_EXCEEDS_HOST_SIZE, IP_INDEX_NONE); case ZPT_LOAD_SUCCESS: case ZPT_LOAD_UNMODELED: break; } return semaStoreResult(ZPT_STORE_UNMODELED, IP_INDEX_NONE); } // comptime_ptr_access.zig:storeComptimePtrInner // storeComptimePtr, ignoring the pointer's type: a store of `store_val` // through a single-item pointer to its own type. static ComptimeStoreResult storeComptimePtrInner( Zcu* zcu, uint32_t ptr, uint32_t store_val) { InternPool* ip = &zcu->intern_pool; uint32_t store_ty = ipTypeOf(ip, store_val); if (ztOnePossibleValue(ip, store_ty) != IP_INDEX_NONE) return semaStoreResult(ZPT_STORE_SUCCESS, IP_INDEX_NONE); ComptimeStoreStrategy strat = prepareComptimePtrStore(zcu, ptr, store_ty, 0); switch (strat.tag) { case SEMA_STORE_STRAT_COMPTIME_FIELD: { // "To "store" to a comptime field, just perform a load of the field // and see if the store value matches." ComptimeLoadResult r = loadComptimePtr(zcu, ptr); if (r.tag != ZPT_LOAD_SUCCESS) return semaStoreFromLoad(r); uint32_t expected = mutableValueIntern(zcu, &r.success); return store_val == expected ? semaStoreResult(ZPT_STORE_SUCCESS, IP_INDEX_NONE) : semaStoreResult(ZPT_STORE_COMPTIME_FIELD_MISMATCH, expected); } case SEMA_STORE_STRAT_ERROR: return semaStoreResult(strat.err, strat.err_ty); case SEMA_STORE_STRAT_DIRECT: { uint32_t want_ty = mutableValueTypeOf(ip, strat.val); *strat.val = mutableValueInterned(zptGetCoerced(zcu, store_val, want_ty)); return semaStoreResult(ZPT_STORE_SUCCESS, IP_INDEX_NONE); } case SEMA_STORE_STRAT_INDEX: { IpArrayInfo arr; if (ipArrayInfo(ip, mutableValueTypeOf(ip, strat.val), &arr) != IP_ARR_OK) UNREACHABLE("storeComptimePtr: index into a non-array"); mutableValueSetElem(zcu, strat.val, (uint32_t)strat.index, mutableValueInterned(zptGetCoerced(zcu, store_val, arr.child))); return semaStoreResult(ZPT_STORE_SUCCESS, IP_INDEX_NONE); } case SEMA_STORE_STRAT_FLAT_INDEX: { uint32_t one_ty; uint64_t store_elems; if (!ztArrayBase(ip, store_ty, &one_ty, &store_elems)) UNREACHABLE("storeComptimePtr: flat store of a non-array"); uint32_t* flat_elems = zig1Malloc((store_elems ? store_elems : 1u) * sizeof(uint32_t)); uint64_t next_idx = 0, skip = 0; MutableValue src = mutableValueInterned(store_val); if (!flattenArray(zcu, &src, &skip, &next_idx, flat_elems, store_elems) || next_idx != store_elems) UNIMPLEMENTED("storeComptimePtr: flatten the stored array"); for (uint64_t idx = 0; idx < store_elems; idx++) { // "TODO: recursiveIndex in a loop does a lot of redundant work!" uint64_t index = strat.index + idx; MutableValue* val_ptr; if (!recursiveIndex(zcu, strat.val, &index, &val_ptr)) UNREACHABLE("storeComptimePtr: flat index past the array"); mutableValueSetElem(zcu, val_ptr, (uint32_t)index, mutableValueInterned(flat_elems[idx])); } free(flat_elems); return semaStoreResult(ZPT_STORE_SUCCESS, IP_INDEX_NONE); } case SEMA_STORE_STRAT_REINTERPRET: { uint32_t val_ty = mutableValueTypeOf(ip, strat.val); if (!ztHasWellDefinedLayout(ip, val_ty)) return semaStoreResult(ZPT_STORE_NEEDED_WELL_DEFINED, val_ty); if (!ztHasWellDefinedLayout(ip, store_ty)) return semaStoreResult(ZPT_STORE_NEEDED_WELL_DEFINED, store_ty); uint32_t old_val = mutableValueIntern(zcu, strat.val); *strat.val = mutableValueInterned(semaReinterpretStore( zcu, old_val, val_ty, store_ty, strat.index, store_val)); return semaStoreResult(ZPT_STORE_SUCCESS, IP_INDEX_NONE); } } UNREACHABLE("storeComptimePtr: bad strategy"); ZIG1_TCC_RETURN(semaStoreResult(ZPT_STORE_SUCCESS, IP_INDEX_NONE)); } // comptime_ptr_access.zig:storeComptimePtr // Store `store_val`, whose type is the pointer's child, through the // comptime pointer `ptr`; a bit pointer splices it into its host. ComptimeStoreResult storeComptimePtr( Zcu* zcu, uint32_t ptr, uint32_t store_val) { InternPool* ip = &zcu->intern_pool; IpPtrInfo info; if (ipPtrInfo(ip, ipTypeOf(ip, ptr), &info) != IP_PTRI_OK) UNREACHABLE("storeComptimePtr: not a pointer"); uint32_t host_size = IP_PTR_PACKED_HOST_SIZE(info.packed_offset); if (host_size == 0) return storeComptimePtrInner(zcu, ptr, store_val); uint32_t vector_index = IP_PTR_FLAGS_VECTOR_INDEX(info.flags); if (vector_index == IP_PTR_VECTOR_INDEX_NONE) { uint32_t backing_ty = ipGetIntType(ip, false, host_size * 8u); ComptimeLoadResult r = loadComptimePtrInner(zcu, ptr, backing_ty, 0); if (r.tag != ZPT_LOAD_SUCCESS) return semaStoreFromLoad(r); // "Write the new element... then read the resulting backing integer // value... and store that back into memory." uint32_t new_backing = semaPackedPtrStore(zcu, ipTypeOf(ip, ptr), backing_ty, mutableValueIntern(zcu, &r.success), store_val); if (new_backing == IP_INDEX_NONE) return semaStoreResult(ZPT_STORE_EXCEEDS_HOST_SIZE, IP_INDEX_NONE); return storeComptimePtrInner(zcu, ptr, new_backing); } if (vector_index >= host_size) return semaStoreResult(ZPT_STORE_EXCEEDS_HOST_SIZE, IP_INDEX_NONE); uint32_t vec_ty = ipGetVectorType(ip, host_size, info.child); ComptimeLoadResult r = loadComptimePtrInner(zcu, ptr, vec_ty, 0); if (r.tag != ZPT_LOAD_SUCCESS) return semaStoreFromLoad(r); uint32_t old_vector = mutableValueIntern(zcu, &r.success); uint32_t* elems = zig1Malloc(host_size * sizeof(uint32_t)); for (uint32_t i = 0; i < host_size; i++) elems[i] = ipIsUndef(ip, old_vector) ? ipGetUndef(ip, info.child) : valElemValue(ip, old_vector, i, "storeComptimePtr: vector"); elems[vector_index] = store_val; uint32_t new_vector = zptAggregateValue(ip, vec_ty, elems, host_size); free(elems); return storeComptimePtrInner(zcu, ptr, new_vector); } // comptime_ptr_access.zig's packed arms, over the host value the pointer's // address names (`host_ty`). A `host_size != 0` pointer loads the host as // `uN` and reads the element at `bit_offset`: `"backing_int_val // .writeToPackedMemory(zcu, buf, 0); const result_val: Value = try // .readFromPackedMemory(elem_ty, pt, buf, ptr_info.packed_offset // .bit_offset);"`; a `host_size == 0` one (a packed union's field) is the // type-punned read at offset 0 `loadComptimePtrInner` performs. An undefined // host writes as zeroes, as `writeToPackedMemory`'s undef arm does. Returns // IP_INDEX_NONE for a leaf the packed-memory pair does not model and for // upstream's `.exceeds_host_size`. static uint8_t* semaPackedPtrHostBuf(Zcu* zcu, uint32_t ptr_ty, uint32_t host_ty, uint32_t host_val, uint64_t* host_bits, uint64_t* bit_off) { InternPool* ip = &zcu->intern_pool; IpPtrInfo info; uint64_t hsz, hal, hbits, esz, eal, ebits; uint32_t host_size, bit_offset; if (ipPtrInfo(ip, ptr_ty, &info) != IP_PTRI_OK || !ipPtrPackedHost( info.flags, info.packed_offset, &host_size, &bit_offset) || !ztLayoutInfo(zcu, ip, host_ty, &hsz, &hal, &hbits) || !ztLayoutInfo(zcu, ip, info.child, &esz, &eal, &ebits)) return NULL; *bit_off = bit_offset; // `"if (ptr_info.packed_offset.bit_offset + elem_ty.bitSize(zcu) > // host_size * 8) return .exceeds_host_size;"` *host_bits = host_size != 0 ? (uint64_t)host_size * 8u : hbits; if (*host_bits < hbits || *bit_off + ebits > *host_bits) return NULL; // An undefined host writes as zeroes (`writeToPackedMemory`'s undef arm). return semaPackedMemBuf(zcu, host_val == IP_INDEX_NONE ? ipGetUndef(ip, host_ty) : host_val, *host_bits); } uint32_t semaPackedPtrLoad( Zcu* zcu, uint32_t ptr_ty, uint32_t host_ty, uint32_t host_val) { uint64_t host_bits, bit_off; uint8_t* buf = semaPackedPtrHostBuf( zcu, ptr_ty, host_ty, host_val, &host_bits, &bit_off); if (!buf) return IP_INDEX_NONE; const InternPool* ip = &zcu->intern_pool; uint32_t res = valReadFromPackedMemory( zcu, ip->extra[ip->item_datas[ptr_ty]], buf, host_bits, bit_off); free(buf); return res; } // storeComptimePtr's packed arm: `"old_backing_int_val.writeToPackedMemory( // zcu, buf, 0); store_val.writeToPackedMemory(zcu, buf, ptr_info // .packed_offset.bit_offset); const new_backing_int_val: Value = try // .readFromPackedMemory(backing_ty, pt, buf, 0);"` -- the new host value, // read back at the host's own type for the caller to store at the host. uint32_t semaPackedPtrStore(Zcu* zcu, uint32_t ptr_ty, uint32_t host_ty, uint32_t host_val, uint32_t store_val) { uint64_t host_bits, bit_off; uint8_t* buf = semaPackedPtrHostBuf( zcu, ptr_ty, host_ty, host_val, &host_bits, &bit_off); if (!buf) return IP_INDEX_NONE; uint32_t res = valWriteToPackedMemory(zcu, store_val, buf, host_bits, bit_off) ? valReadFromPackedMemory(zcu, host_ty, buf, host_bits, 0) : IP_INDEX_NONE; free(buf); return res; } /* ---- end comptime_ptr_access.c ---- */ /* ---- begin intern_pool.c ---- */ // intern_pool.c — src/InternPool.zig, every function prefixed ip // (InternPool.getPtrType is ipGetPtrType, indexToKey's arms are the ipXxx // accessors of each key): the items/extra/string storage, the get-or-put // interning of every key with its dedup hash index, Nav and // namespace tables, and the layout resolution of src/Sema/type_resolution.zig // that writes into the stored items. Declarations are in zig1.h: // items, extra and string storage plus the 128 static preinterns // (`static_keys` and `InternPool.init`) at fixed indices 0..127. #ifndef ZIG1_H #include "zig1.h" #endif #include #include #include #include #include static uint32_t ipGetPtrComptimeFieldOffset( InternPool* ip, uint32_t ty, uint32_t field_val, uint64_t byte_offset); // --- growable arenas --- static void ipItemsEnsure(InternPool* ip, uint32_t need) { if (ip->items_len + need <= ip->items_cap) return; uint32_t cap = ip->items_cap ? ip->items_cap : 256; while (ip->items_len + need > cap) cap *= 2; ip->item_tags = zig1Realloc(ip->item_tags, cap * sizeof(uint8_t)); ip->item_datas = zig1Realloc(ip->item_datas, cap * sizeof(uint32_t)); ip->items_cap = cap; } static void ipExtraEnsure(InternPool* ip, uint32_t need) { if (ip->extra_len + need <= ip->extra_cap) return; uint32_t cap = ip->extra_cap ? ip->extra_cap : 256; while (ip->extra_len + need > cap) cap *= 2; ip->extra = zig1Realloc(ip->extra, cap * sizeof(uint32_t)); ip->extra_cap = cap; } // `Int.limbs_items_len` = `@divExact(@sizeOf(Int), @sizeOf(Limb))`, with // `Int = packed struct { ty: Index, limbs_len: u32 }` (8 bytes) and a 64-bit // limb: the header occupies exactly one limb, whose low half is `ty` and // whose high half is `limbs_len` on a little-endian host. #define IP_INT_LIMBS_ITEMS_LEN 1u static void ipLimbsEnsure(InternPool* ip, uint32_t need) { if (ip->limbs_len + need <= ip->limbs_cap) return; uint32_t cap = ip->limbs_cap ? ip->limbs_cap : 64; while (ip->limbs_len + need > cap) cap *= 2; ip->limbs = zig1Realloc(ip->limbs, (size_t)cap * sizeof(uint64_t)); ip->limbs_cap = cap; } // Append one item { tag, data }, returning its index. static uint32_t ipAddItem(InternPool* ip, IpTag tag, uint32_t data) { ipItemsEnsure(ip, 1); uint32_t idx = ip->items_len; ip->item_tags[idx] = (uint8_t)tag; ip->item_datas[idx] = data; ip->items_len++; return idx; } // InternPool.zig:addExtra // Push `n` words into the `extra` arena, returning the offset of the first. static uint32_t ipAddExtra(InternPool* ip, const uint32_t* words, uint32_t n) { ipExtraEnsure(ip, n); uint32_t off = ip->extra_len; for (uint32_t i = 0; i < n; i++) ip->extra[off + i] = words[i]; ip->extra_len += n; return off; } // --- Dedup hash index --- // // Every `ipGet*` dedup site looks up the first item matching (tag, key words) // before appending. This index answers that in O(1) amortized and returns // exactly the item a first-match scan of `ip->items` would, so the pool's // observable contents (item and extra arrays, append order) do not depend on // it. // // First match wins, insert if absent: an indexed tag's entry is the first // item ever created with its key. That holds because every creator of an // indexed tag inserts right after its `ipAddItem`: the 128 static preinterns // (`ipDedupSeedStatic`, in order), the tag's own find-then-append getter, // and, for `type_function` and `type_error_union`, the four-item blocks of // `ipGetFuncDeclIes` and `ipGetFuncInstance`. Those two insert without a // find, which is safe because their key's `return_type` or error-set word is // an item appended moments earlier in the same call, so the key cannot be // present yet (the audit is at `ipGetFuncType`). No indexed tag ever holds // two items with one key, so unlike the string table (ipStrDedupFind, whose // key is truncated) the slot-order match is the lowest-index match. // `type_error_set` has its own table (`errset_dedup_*` in zig1.h) because // its key is not contiguous in `extra`. // // Growth: open addressing, linear probing, power-of-two capacity, doubled // and rehashed when an insert would push load above 70%. A slot stores // {item_idx, hash}, not item_idx alone, so growth never re-derives a key from // live IP content: a `bytes`/`aggregate`/`tuple_type`/`type_function` key has // no stored word count (an `Aggregate` is `{ty}` and `len` element words, // `len` implied by `ty`). The errset table shares the same grow and place // primitives. At 12 bytes a slot and tens of thousands of items, capacity // stays at a few MiB. // Every dedup-index hasher below routes through `wyhash.c`. Hashing only // decides bucket placement: `ipDedupFind` (and its errset/memo/decl siblings) // always follow a hash hit with a full key comparison before returning a // match (see the `for (uint32_t j = 0; j < n; j++) if (actual[j] != words[j])` // loop below), and nothing in this file iterates a dedup table in hash order, // so the hash algorithm never changes a find/insert answer. // // The seed carries tag identity, mirroring upstream's `Key.hash64` // (`src/InternPool.zig`, `"const seed = @intFromEnum(@as(KeyTag, // key));"`): `ipDedupHashWords`/`ipDedupHashErrorSetKey`/ // `ipDedupHashMemoizedCallKey`/`ipDedupHashDeclKey` seed with `(uint64_t)tag` // and stream no tag byte into the body. The seed value need not match // upstream's (zig1's `IpTag` ordinals are an independently renumbered set), // and the partition is finer than upstream's: zig1 seeds with its physical-tag // enum `IpTag`, which is finer than upstream's `KeyTag` -- e.g. // `IP_TAG_TYPE_INT_SIGNED`/`IP_TAG_TYPE_INT_UNSIGNED` are two physical tags // for upstream's single `Key.int_type` variant. That is safe because // `ipDedupFind` requires `ip->item_tags[idx] == tag` to match exactly before // any key-word compare runs, so cross-tag dedup never occurs. // `ipDedupHashErrorSetKey` hashes the names bytes only, like upstream's // `error_set_type` arm `Hash.hash(seed, // std.mem.sliceAsBytes(x.names.get(ip)))` // (`src/InternPool.zig`). `ipDedupHashMemoizedCallKey` hashes func then // args, like `.memoized_call`'s `autoHash(func); for (arg_values) |arg| // autoHash(arg);` (`src/InternPool.zig`). `ipDedupHashDeclKey` keeps // a separately-justified simplification (`key_word` = owner_nav alone, not // upstream's `.func` arm's fuller {owner_nav, ty, uncoerced_ty==ty} -- see // the table's header comment above `ipDeclDedupKeyWord`). `ipDedupHashWords` // is a generic {tag, contiguous words} hash serving ~43 different upstream // `Key` variants, each with its own bespoke material recipe (e.g. `.ptr`'s // `addr_tag`-dependent extra word, `.aggregate`'s byte-array element-tag // interleaving, `.int`'s BigInt canonicalization); replicating those recipes // is not required, since answer-invariance holds for any material by the // full-compare argument. `ipDedupHashBytes` (the string table) matches // upstream's plain seed of 0 over the raw content bytes // (`ip.getOrPutTrailingString`, `src/InternPool.zig`, `"const // full_hash = Hash.hash(0, key);"`): `DEDUP_HASH_SEED` is that seed. #define DEDUP_HASH_SEED ((uint64_t)0) // Feed one little-endian word into an in-progress Wyhash stream. Factored // out so `ipDedupHashWords` (contiguous key) and `ipDedupHashErrorSetKey`/ // `ipDedupHashMemoizedCallKey`/`ipDedupHashDeclKey` (non-contiguous keys built // from a scalar plus a separately-walked pointer -- see those functions) // agree byte-for-byte on what a {words...} hash means, without any of them // materializing a temp buffer just to call one shared one-shot helper. static void ipDedupHashMixWord(Wyhash* h, uint32_t w) { wyhashUpdateU32(h, w); } // Wyhash over `n` little-endian words, seeded by `tag` (tag identity lives // in the seed, not the streamed data -- see the comment above // `DEDUP_HASH_SEED`). static uint32_t ipDedupHashWords( uint8_t tag, const uint32_t* words, uint32_t n) { Wyhash h; wyhashInit(&h, (uint64_t)tag); for (uint32_t i = 0; i < n; i++) { ipDedupHashMixWord(&h, words[i]); } return (uint32_t)wyhashFinal(&h); } // Wyhash over raw string bytes (the string dedup table's key domain). static uint32_t ipDedupHashBytes(const char* bytes, uint32_t len) { return (uint32_t)wyhashHash(DEDUP_HASH_SEED, bytes, len); } // Shared insert-into-a-fresh-or-growing-table primitive: place (hash, // item_idx, n) into the first empty slot found by linear probing from // `hash & (cap-1)`. `n` is the key word count (item table) or 0 (string // table; see IpDedupSlot's comment in zig1.h). Callers guarantee // `slots` has at least one empty slot (load kept <= 70% by the // grow-before-insert check in the public helpers). static void ipDedupPlace(IpDedupSlot* slots, uint32_t cap, uint32_t hash, uint32_t item_idx, uint32_t n) { uint32_t mask = cap - 1; uint32_t slot = hash & mask; while (slots[slot].item_idx != IP_INDEX_NONE) slot = (slot + 1) & mask; slots[slot].item_idx = item_idx; slots[slot].hash = hash; slots[slot].n = n; } // Grow+rehash one of the two tables (selected by which pointer/cap/count // triple is passed) to double its capacity. Re-buckets every live entry // using its stored hash (see the "Growth" comment above for why that avoids // needing to re-derive variable-length keys). static void ipDedupGrow(IpDedupSlot** slots_p, uint32_t* cap_p) { uint32_t old_cap = *cap_p; IpDedupSlot* old_slots = *slots_p; // Loud abort before doubling could wrap a uint32_t capacity (would // otherwise silently truncate `new_cap`, corrupting every subsequent // `hash & (new_cap-1)` bucket computation). At 12 bytes/slot this table // would already be a multi-GiB allocation long before old_cap gets // anywhere near this bound; this is a hard invariant check, not an // expected path. if (old_cap > UINT32_MAX / 2) { UNIMPLEMENTED("getOrPutKey: dedup table capacity overflow"); } uint32_t new_cap = zig1GrowCap(old_cap, 1024u); // Guard the allocation-size multiply explicitly (size_t cast before the // multiply, matching the codebase's loud-overflow convention) rather // than relying on `new_cap * sizeof(IpDedupSlot)` not overflowing a // 32-bit `unsigned int` promotion on platforms where that matters. IpDedupSlot* new_slots = zig1Malloc((size_t)new_cap * sizeof(IpDedupSlot)); memset(new_slots, 0xFF, (size_t)new_cap * sizeof(IpDedupSlot)); for (uint32_t i = 0; i < old_cap; i++) if (old_slots[i].item_idx != IP_INDEX_NONE) ipDedupPlace(new_slots, new_cap, old_slots[i].hash, old_slots[i].item_idx, old_slots[i].n); free(old_slots); *slots_p = new_slots; *cap_p = new_cap; } // Ensure the table has room for one more entry without exceeding 70% load, // growing (or performing the initial allocation) first if needed. static void ipDedupEnsureCapacity( IpDedupSlot** slots_p, uint32_t* cap_p, uint32_t* count_p) { if (*cap_p == 0) { uint32_t cap = 1024; // power of two; ~8x the 128 static preinterns *slots_p = zig1Malloc((size_t)cap * sizeof(IpDedupSlot)); memset(*slots_p, 0xFF, (size_t)cap * sizeof(IpDedupSlot)); *cap_p = cap; *count_p = 0; return; } // 70% load factor, checked as an integer comparison (no float): grow // when (count+1) * 10 > cap * 7. Cast `*count_p` to uint64_t before the // +1 (not after): `*count_p` can legitimately reach UINT32_MAX-ish scale // over a large compile, and `*count_p + 1` in uint32_t arithmetic would // silently wrap to 0 right when the check matters most. if (((uint64_t)*count_p + 1) * 10 > (uint64_t)(*cap_p) * 7) ipDedupGrow(slots_p, cap_p); } // --- Item dedup table ------------------------------------------------- // Find the first item with (tag, words[0..n)). `data_is_key`: true when the // tag's key is exactly the item's 1-word `data` (no `extra` payload; n must // be 1); false when the key is the `n` words at // `ip->extra[item_datas[idx] .. +n)`. Returns IP_INDEX_NONE on a miss // (including when the table hasn't been built yet, i.e. before the first // insert -- see ipDedupSeedStatic, which builds it before any dynamic // intern can run). static uint32_t ipDedupFind(const InternPool* ip, IpTag tag, const uint32_t* words, uint32_t n, bool data_is_key) { if (ip->dedup_cap == 0) return IP_INDEX_NONE; uint32_t h = ipDedupHashWords((uint8_t)tag, words, n); uint32_t mask = ip->dedup_cap - 1; uint32_t slot = h & mask; for (;;) { uint32_t idx = ip->dedup_slots[slot].item_idx; if (idx == IP_INDEX_NONE) return IP_INDEX_NONE; if (ip->dedup_slots[slot].hash == h && ip->dedup_slots[slot].n == n && ip->item_tags[idx] == (uint8_t)tag) { // The `n` check above is load-bearing, not an optimization: for // a variable-length-key tag (aggregate/tuple/error-set) a // same-tag, same-hash-bucket candidate with a shorter stored key // would otherwise have `actual[0..n)` read past that candidate's // own `extra` payload before this loop even gets to comparing // words (see IpDedupSlot's `n` comment in zig1.h). const uint32_t* actual = data_is_key ? &ip->item_datas[idx] : &ip->extra[ip->item_datas[idx]]; bool eq = true; for (uint32_t j = 0; j < n; j++) if (actual[j] != words[j]) { eq = false; break; } if (eq) return idx; } slot = (slot + 1) & mask; } } // Record a just-appended item (`item_idx`, already tagged `tag` in // `ip->item_tags`) under key `words[0..n)`. Must be called with the exact // same `words`/`n` the preceding `ipDedupFind` miss used, immediately after // the `ipAddItem`/`ipAddExtra` pair that created it -- this is what keeps the // index holding only first occurrences (a caller only reaches this after a // find miss, by construction of the find-or-append pattern each indexed // getter follows). static void ipDedupInsert(InternPool* ip, IpTag tag, uint32_t item_idx, const uint32_t* words, uint32_t n) { ipDedupEnsureCapacity(&ip->dedup_slots, &ip->dedup_cap, &ip->dedup_count); uint32_t h = ipDedupHashWords((uint8_t)tag, words, n); ipDedupPlace(ip->dedup_slots, ip->dedup_cap, h, item_idx, n); ip->dedup_count++; } // Seed the item dedup index with the 128 static preinterns (ipInitStatic), // for every tag whose dynamic getter is index-accelerated below. Called once // from ipInit, right after ipInitStatic returns and before any dynamic // intern can run, so the index's first entries are the lowest-index items. // Order within this loop doesn't matter (insert-if-absent; no two static // preinterns of an indexed tag ever share a key -- ipInitStatic hand-writes // each one once). static void ipDedupSeedStatic(InternPool* ip) { for (uint32_t i = 0; i < ip->items_len; i++) { IpTag tag = (IpTag)ip->item_tags[i]; uint32_t d = ip->item_datas[i]; switch (tag) { // 1-word key == item.data directly. case IP_TAG_INT_USIZE: case IP_TAG_INT_U8: case IP_TAG_INT_COMPTIME_INT_U32: case IP_TAG_INT_COMPTIME_INT_I32: case IP_TAG_ONLY_POSSIBLE_VALUE: case IP_TAG_TYPE_OPTIONAL: case IP_TAG_TYPE_SLICE: case IP_TAG_TYPE_ANYERROR_UNION: case IP_TAG_UNDEF: case IP_TAG_TYPE_INT_SIGNED: case IP_TAG_TYPE_INT_UNSIGNED: // `ipInitStatic` preinterns no enum_literal today (the only creator is // `ipGetEnumLiteral`), so this arm currently seeds nothing; it is // listed so the "static preinterns + the getter are the only two // creators" invariant above holds by construction if one is ever // added, rather than by an unwritten assumption. case IP_TAG_ENUM_LITERAL: ipDedupInsert(ip, tag, i, &d, 1); break; // 2-word key in extra[d..+2). case IP_TAG_TYPE_VECTOR: case IP_TAG_INT_SMALL: case IP_TAG_TYPE_ERROR_UNION: ipDedupInsert(ip, tag, i, &ip->extra[d], 2); break; // 4-word key in extra[d..+4) (type_pointer's Key.PtrType). case IP_TAG_TYPE_POINTER: ipDedupInsert(ip, tag, i, &ip->extra[d], IP_TYPE_POINTER_LEN); break; // type_tuple: extra = {fields_len, types[fields_len], // values[fields_len]}; only the fields_len==0 empty_tuple_type is // preinterned, so n collapses to 1 (just the fields_len word). case IP_TAG_TYPE_TUPLE: { uint32_t fields_len = ip->extra[d]; ipDedupInsert(ip, tag, i, &ip->extra[d], 1 + 2 * fields_len); break; } default: break; // not an indexed tag } } } // --- String dedup table ------------------------------------------------- // Find an interned string's strings-list index by content, or IP_INDEX_NONE // on a miss. Same first-match/insert-if-absent reasoning as the item table, // specialized to strings: `ipGetOrPutString` is the only dynamic creator of // string entries (see zig1.h). The one exception is the empty string // at index 0, written directly by `ipInit` (not through `ipGetOrPutString`) // -- `ipInit` seeds it by hand right after writing it (a single // `ipStrDedupInsert(&ip, 0, "", 0)` call), so the index holds every string. // // A query holding a NUL never matches: such a string is appended anew, as // upstream's string map never holds one. Several entries can share a hash // (equal contents never do), so the whole probe chain is scanned and the // lowest verifying index returned -- `ipDedupGrow`'s rehash re-places entries // in slot order, which can invert a chain's insertion order. static uint32_t ipStrDedupFind( const InternPool* ip, const char* bytes, uint32_t len) { if (ip->str_dedup_cap == 0) return IP_INDEX_NONE; if (memchr(bytes, 0, len) != NULL) return IP_INDEX_NONE; // never matches; the caller appends uint32_t h = ipDedupHashBytes(bytes, len); uint32_t mask = ip->str_dedup_cap - 1; uint32_t slot = h & mask; uint32_t best = IP_INDEX_NONE; // lowest verifying index so far for (;;) { uint32_t idx = ip->str_dedup_slots[slot].item_idx; if (idx == IP_INDEX_NONE) return best; if (ip->str_dedup_slots[slot].hash == h) { const char* s = &ip->string_bytes[ip->string_offsets[idx]]; // Stored strings are NUL-terminated (see ipGetOrPutString), so // strlen gives the stored length. if ((uint32_t)strlen(s) == len && memcmp(s, bytes, len) == 0 && (best == IP_INDEX_NONE || idx < best)) best = idx; } slot = (slot + 1) & mask; } } // `n` is unused for the string table (see IpDedupSlot's comment in // zig1.h) -- passed as 0. static void ipStrDedupInsert( InternPool* ip, uint32_t str_idx, const char* bytes, uint32_t len) { ipDedupEnsureCapacity( &ip->str_dedup_slots, &ip->str_dedup_cap, &ip->str_dedup_count); uint32_t h = ipDedupHashBytes(bytes, len); ipDedupPlace(ip->str_dedup_slots, ip->str_dedup_cap, h, str_idx, 0); ip->str_dedup_count++; } // --- type_error_set dedup table --- // // `ipGetErrorSetType`'s key is {names_len, names[0..names_len)}, but the // `extra` layout is Tag.ErrorSet { names_len, names_map } (2 words) then the // trailing names -- `names_map` sits between names_len and the name list, so // the key is not a contiguous `extra` span starting at item_datas[idx] and // can't use the generic `ipDedupFind`/`ipDedupInsert` (which assume that). // This dedicated table reuses the shared slot/grow/place primitives above // (they only need a precomputed hash + item_idx + a stored guard length, // never re-deriving the key from live content, so the non-contiguous // layout doesn't matter to them) with its own hash + content-verify pair // that knows to skip the `names_map` word. Single creator (the getter // below is the only `ipAddItem(ip, IP_TAG_TYPE_ERROR_SET, ...)` call site in // this file -- verified by grep), so first-match/insert-if-absent is // trivially preserved by find-before-append, exactly like the generic // item table's single-creator tags. // Hash the logical key {names[0..names_len)}, skipping the names_map word, // by streaming each name through the same `ipDedupHashMixWord` step // `ipDedupHashWords` uses, seeded by the tag -- matching upstream's // `.error_set_type` arm exactly: `Hash.hash(seed, sliceAsBytes(x.names))` // (`src/InternPool.zig`), names bytes only, no length word. static uint32_t ipDedupHashErrorSetKey( uint32_t names_len, const uint32_t* names) { Wyhash h; wyhashInit(&h, (uint64_t)IP_TAG_TYPE_ERROR_SET); for (uint32_t j = 0; j < names_len; j++) ipDedupHashMixWord(&h, names[j]); return (uint32_t)wyhashFinal(&h); } // Find by content. Slot.n (== the candidate's names_len) lets a // non-matching-length candidate be rejected without dereferencing `extra` // at all; the live item's own w[0] is still the authority actually // compared against (defense in depth -- slot.n and the item are written // together by `ipErrorSetDedupInsert` and should never disagree). Returns // IP_INDEX_NONE on a miss, including "table not built yet". static uint32_t ipErrorSetDedupFind( const InternPool* ip, uint32_t names_len, const uint32_t* names) { if (ip->errset_dedup_cap == 0) return IP_INDEX_NONE; uint32_t h = ipDedupHashErrorSetKey(names_len, names); uint32_t mask = ip->errset_dedup_cap - 1; uint32_t slot = h & mask; for (;;) { uint32_t idx = ip->errset_dedup_slots[slot].item_idx; if (idx == IP_INDEX_NONE) return IP_INDEX_NONE; if (ip->errset_dedup_slots[slot].hash == h && ip->errset_dedup_slots[slot].n == names_len) { const uint32_t* w = &ip->extra[ip->item_datas[idx]]; bool eq = (w[0] == names_len); for (uint32_t j = 0; eq && j < names_len; j++) if (w[2 + j] != names[j]) eq = false; if (eq) return idx; } slot = (slot + 1) & mask; } } // Record a just-appended type_error_set item. Must be called with the exact // same `names`/`names_len` the preceding `ipErrorSetDedupFind` miss used, // immediately after the item's `ipAddExtra`/`ipAddItem` pair -- same // discipline as `ipDedupInsert`. static void ipErrorSetDedupInsert(InternPool* ip, uint32_t item_idx, uint32_t names_len, const uint32_t* names) { ipDedupEnsureCapacity(&ip->errset_dedup_slots, &ip->errset_dedup_cap, &ip->errset_dedup_count); uint32_t h = ipDedupHashErrorSetKey(names_len, names); ipDedupPlace( ip->errset_dedup_slots, ip->errset_dedup_cap, h, item_idx, names_len); ip->errset_dedup_count++; } // --- memoized_call dedup table --------------------------------------------- // // `ipGetMemoizedCall`'s key is {func, arg_values[0..args_len)}, mirroring // upstream `Key.MemoizedCall.hash64`/`eql` (InternPool.zig, // 3143-3146) -- `result`/`branch_count` are explicitly excluded ("ignored by // hash+eql", Sema.zig), because at lookup time (before the call // body has been evaluated) `result`/`branch_count` aren't known yet. The // `extra` layout is `{func, args_len, result, branch_count}` (4 words) then // the trailing `args_len` arg_values -- `args_len`/`result`/`branch_count` // sit between `func` and the arg list, so (exactly like `errset_dedup_*` // above) the key is not a contiguous span starting at `item_datas[idx]` and // can't use the generic `ipDedupFind`/`ipDedupInsert`. Single creator: // `ipGetMemoizedCall` is the only `ipAddItem(ip, IP_TAG_MEMOIZED_CALL, ...)` // call site in this file, so find-before-append keeps first match wins. // // Unlike the other dedup tables, the find half is exposed as its own public // entry point (`ipLookupMemoizedCall`) instead of being folded into the // getter: callers must be able to try the lookup, and on a hit, skip the // (potentially expensive) call-body evaluation entirely -- mirroring // upstream's `Sema.analyzeCall`, which checks the memo before decoding or // running the callee body (Sema.zig), not just before appending the // InternPool item. `ipGetMemoizedCall` still performs the insert half itself // (right after `ipAddItem`), so the lookup-aware caller's discipline is: call // `ipLookupMemoizedCall` first: on a hit, use the stored result and never // call `ipGetMemoizedCall` at all; on a miss, evaluate, and (if the call // still wants memoization once the result is known) call `ipGetMemoizedCall` // with the same {func, arg_values, args_len} the lookup miss used -- // `analyzeComptimeCall1` and `ctAnalyzeComptimeCallTyped` (sema_comptime.c, // the latter only when no argument can mutate comptime state) both follow it. // `ipGetMemoizedCall` still appends without consulting the item table and // does find-before-insert on the index only, so a duplicate-keyed item (the // callee's body interning the same key between the miss and the append) is // never indexed and `ipLookupMemoizedCall` resolves to the first item with a // given key -- see `ipGetMemoizedCall`'s definition. // Hash the key {func, arg_values[0..args_len)}, skipping the `args_len`/ // `result`/`branch_count` header words, by streaming `func` then each arg // value through the same `ipDedupHashMixWord` step `ipDedupHashWords` uses, // seeded by the tag -- matching upstream's `.memoized_call` arm exactly: // `autoHash(func); for (arg_values) |arg| autoHash(arg);` // (`src/InternPool.zig`), func then args, no length word. static uint32_t ipDedupHashMemoizedCallKey( uint32_t func, uint32_t args_len, const uint32_t* arg_values) { Wyhash h; wyhashInit(&h, (uint64_t)IP_TAG_MEMOIZED_CALL); ipDedupHashMixWord(&h, func); for (uint32_t j = 0; j < args_len; j++) ipDedupHashMixWord(&h, arg_values[j]); return (uint32_t)wyhashFinal(&h); } // Find by content. `slot.n` (== the candidate's `args_len`) lets a // non-matching-arity candidate be rejected without reading past its own // `extra` payload (same defense-in-depth `n` guard `ipDedupFind` and // `ipErrorSetDedupFind` use). Returns IP_INDEX_NONE on a miss, including // "table not built yet" (mirrors upstream `getIfExists` finding no matching // key when no memoized_call with this key was ever interned). uint32_t ipLookupMemoizedCall(const InternPool* ip, uint32_t func, const uint32_t* arg_values, uint32_t args_len) { if (ip->memo_dedup_cap == 0) return IP_INDEX_NONE; uint32_t h = ipDedupHashMemoizedCallKey(func, args_len, arg_values); uint32_t mask = ip->memo_dedup_cap - 1; uint32_t slot = h & mask; for (;;) { uint32_t idx = ip->memo_dedup_slots[slot].item_idx; if (idx == IP_INDEX_NONE) return IP_INDEX_NONE; if (ip->memo_dedup_slots[slot].hash == h && ip->memo_dedup_slots[slot].n == args_len) { const uint32_t* w = &ip->extra[ip->item_datas[idx]]; bool eq = (w[IP_MEMOIZED_CALL_FUNC] == func) && (w[IP_MEMOIZED_CALL_ARGS_LEN] == args_len); for (uint32_t j = 0; eq && j < args_len; j++) if (w[IP_MEMOIZED_CALL_LEN + j] != arg_values[j]) { eq = false; break; } if (eq) return idx; } slot = (slot + 1) & mask; } } // Record a just-appended memoized_call item. Must be called with the exact // same `func`/`arg_values`/`args_len` the preceding `ipLookupMemoizedCall` // miss used, immediately after the item's `ipAddExtra`/`ipAddItem` pair -- // same discipline as `ipDedupInsert`/`ipErrorSetDedupInsert`. static void ipMemoizedCallDedupInsert(InternPool* ip, uint32_t item_idx, uint32_t func, uint32_t args_len, const uint32_t* arg_values) { ipDedupEnsureCapacity( &ip->memo_dedup_slots, &ip->memo_dedup_cap, &ip->memo_dedup_count); uint32_t h = ipDedupHashMemoizedCallKey(func, args_len, arg_values); ipDedupPlace( ip->memo_dedup_slots, ip->memo_dedup_cap, h, item_idx, args_len); ip->memo_dedup_count++; } uint32_t ipMemoizedCallResult(const InternPool* ip, uint32_t idx) { return ip->extra[ip->item_datas[idx] + IP_MEMOIZED_CALL_RESULT]; } uint32_t ipMemoizedCallBranchCount(const InternPool* ip, uint32_t idx) { return ip->extra[ip->item_datas[idx] + IP_MEMOIZED_CALL_BRANCH_COUNT]; } uint32_t ipMemoizedCallBranchQuota(const InternPool* ip, uint32_t idx) { return ip->extra[ip->item_datas[idx] + IP_MEMOIZED_CALL_BRANCH_QUOTA]; } // --- declared-key dedup table ---------------------------------------------- // // Table for the declared-key family. Currently holds only `func_decl` // (`ipGetFuncDecl` + `ipGetFuncDeclIes` both key on IP_TAG_FUNC_DECL -- // provided every creator does find-then-insert with the same key derivation, // which holds here for a different reason than `type_function`'s "provably // absent" argument atop this file: a given Nav has at most one declared // value, so no `owner_nav` is ever legitimately offered to both getters). // Key = one word, at a tag-specific `extra` offset (see `ipDeclDedupKeyWord`, // written generically over IpTag so a future tag can be added without a new // table). func_decl's key is a simplification of upstream's `.func` hash and // eql (InternPool.zig): upstream requires // `owner_nav` equality unconditionally (generic_owner == .none in both // getters here) plus, for the common non-inferred-error-set case, `ty` // equality too -- but `ipGetFuncDeclIes`'s `ty` is a forward reference to a // type_function created moments earlier in the same call (never seen by any // earlier entry), so a `ty`-inclusive key could never dedup an IES // redeclaration at all; upstream itself special-cases this ("the types may // be inequal but we still want to find the same function body instance", // InternPool.zig). This port keys on `owner_nav` alone for both // `ipGetFuncDecl`/`ipGetFuncDeclIes`: a Nav has exactly one declared value, // so `owner_nav` equality is already upstream's necessary (and, given this // port never re-declares the same Nav with a different signature -- no // incremental compilation; see Sema.zig's "TODO...not supported under // incremental compilation" note) sufficient // condition in practice. // // Struct and enum types stay out of this table. Upstream keys them on // {zir_index, captures}; zig1 does not model captures, and // `semaDeclaredNamespaceType` (sema.c) reuses one zir_index for every // capture-distinct instantiation of a struct-returning function (`List(T)` // per T), so a zir_index-only key would merge them. // Their dedup is one layer up, in `ctDedupLookup`/`ctDedupInsert` // (sema_comptime.c) and the memoized_call layer. // Read the declared-key's identity word for `tag`'s item at `item_idx`. // Unreachable for any tag not in the declared-key family (every caller of // `ipDeclDedupFind`/`Insert` only ever passes one of these). A new tag joins // the table here. static uint32_t ipDeclDedupKeyWord( const InternPool* ip, IpTag tag, uint32_t item_idx) { uint32_t off = ip->item_datas[item_idx]; if (tag != IP_TAG_FUNC_DECL) { UNIMPLEMENTED("getOrPutKey: tag not in the declared-key family"); } return ip->extra[off + 1]; // owner_nav (word 1 of the 8/9-word header) } // Seeded by the tag, same convention as the other hashers. Material // (`key_word` = owner_nav alone) is unchanged and stays the documented // simplification of upstream's fuller `.func` arm -- see the table's header // comment above `ipDeclDedupKeyWord`. static uint32_t ipDedupHashDeclKey(IpTag tag, uint32_t key_word) { Wyhash h; wyhashInit(&h, (uint64_t)tag); ipDedupHashMixWord(&h, key_word); return (uint32_t)wyhashFinal(&h); } // Find by (tag, key_word). Returns IP_INDEX_NONE on a miss, including "table // not built yet". static uint32_t ipDeclDedupFind( const InternPool* ip, IpTag tag, uint32_t key_word) { if (ip->decl_dedup_cap == 0) return IP_INDEX_NONE; uint32_t h = ipDedupHashDeclKey(tag, key_word); uint32_t mask = ip->decl_dedup_cap - 1; uint32_t slot = h & mask; for (;;) { uint32_t idx = ip->decl_dedup_slots[slot].item_idx; if (idx == IP_INDEX_NONE) return IP_INDEX_NONE; if (ip->decl_dedup_slots[slot].hash == h && ip->item_tags[idx] == (uint8_t)tag && ipDeclDedupKeyWord(ip, tag, idx) == key_word) return idx; slot = (slot + 1) & mask; } } // Record a just-appended item. Must be called with the exact same // `tag`/`key_word` the preceding `ipDeclDedupFind` miss used, immediately // after the item's `ipAddItem` -- same discipline as `ipDedupInsert`. static void ipDeclDedupInsert( InternPool* ip, IpTag tag, uint32_t item_idx, uint32_t key_word) { ipDedupEnsureCapacity( &ip->decl_dedup_slots, &ip->decl_dedup_cap, &ip->decl_dedup_count); uint32_t h = ipDedupHashDeclKey(tag, key_word); ipDedupPlace(ip->decl_dedup_slots, ip->decl_dedup_cap, h, item_idx, 1); ip->decl_dedup_count++; } // --- static preinterns (static_keys + InternPool.init) -------------------- // These reproduce, in the exact order upstream `InternPool.init` interns // `static_keys`, the 128 fixed-index entries. The item `data` for composite // tags is the `extra` offset produced by pushing that item's payload, so the // offsets and payload contents are byte-identical to upstream's. static void ipAddIntType(InternPool* ip, bool is_signed, uint32_t bits) { ipAddItem(ip, is_signed ? IP_TAG_TYPE_INT_SIGNED : IP_TAG_TYPE_INT_UNSIGNED, bits); } // type_pointer: Tag.TypePointer (Key.PtrType) = { child, sentinel, flags, // packed_offset }. Each preintern's sentinel/flags are upstream's // `static_keys` entry for that index; `packed_offset` is `.{0,0}` for all of // them. These exact words are what `ipDedupSeedStatic` indexes, so a dynamic // rebuild of the same shape finds the preintern here instead of appending a // duplicate item. flags (PtrType.Flags, packed u32): size bits 0-1, alignment // bits 2-7 // (`.none` = 63 = 0x3F), is_const bit 8, vector_index bits 16-31 // (`.none` = 0xFFFF) -- so 0xFFFF00FC = `.one`, 0xFFFF01FC = const `.one`, // 0xFFFF00FD = `.many`, 0xFFFF01FD = const `.many`. static void ipAddPointer( InternPool* ip, uint32_t child, uint32_t sentinel, uint32_t flags) { const uint32_t words[4] = { child, sentinel, flags, 0 }; uint32_t off = ipAddExtra(ip, words, IP_TYPE_POINTER_LEN); ipAddItem(ip, IP_TAG_TYPE_POINTER, off); } // type_vector: Tag payload `Vector` = { len, child } (2 words). static void ipAddVector(InternPool* ip, uint32_t len, uint32_t child) { const uint32_t words[2] = { len, child }; uint32_t off = ipAddExtra(ip, words, 2); ipAddItem(ip, IP_TAG_TYPE_VECTOR, off); } // int_small: Tag payload `IntSmall` = { ty, value } (2 words). static void ipAddIntSmall(InternPool* ip, uint32_t ty, uint32_t value) { const uint32_t words[2] = { ty, value }; uint32_t off = ipAddExtra(ip, words, 2); ipAddItem(ip, IP_TAG_INT_SMALL, off); } static void ipInitStatic(InternPool* ip) { // 0..14: integer types. ipAddIntType(ip, false, 0); // u0 ipAddIntType(ip, false, 1); // u1 ipAddIntType(ip, false, 8); // u8 ipAddIntType(ip, true, 8); // i8 ipAddIntType(ip, false, 16); // u16 ipAddIntType(ip, true, 16); // i16 ipAddIntType(ip, false, 29); // u29 ipAddIntType(ip, false, 32); // u32 ipAddIntType(ip, true, 32); // i32 ipAddIntType(ip, false, 64); // u64 ipAddIntType(ip, true, 64); // i64 ipAddIntType(ip, false, 80); // u80 ipAddIntType(ip, false, 128); // u128 ipAddIntType(ip, true, 128); // i128 ipAddIntType(ip, false, 256); // u256 // 15..43: simple types, with anyframe (`anyframe_type = .none`) at 40. for (uint32_t i = 15; i <= 39; i++) ipAddItem(ip, IP_TAG_SIMPLE_TYPE, 0); // usize..noreturn ipAddItem( ip, IP_TAG_TYPE_ANYFRAME, 0xFFFFFFFFu); // 40: anyframe (child none) ipAddItem(ip, IP_TAG_SIMPLE_TYPE, 0); // 41: null ipAddItem(ip, IP_TAG_SIMPLE_TYPE, 0); // 42: undefined ipAddItem(ip, IP_TAG_SIMPLE_TYPE, 0); // 43: enum_literal // 44..55: pointer / slice / optional preinterns. ipAddPointer(ip, 15, IP_INDEX_NONE, IP_PTR_FLAGS_MUT_ONE); // 44 *usize ipAddPointer(ip, 37, IP_INDEX_NONE, IP_PTR_FLAGS_CONST_ONE); // 45 *const comptime_int ipAddPointer(ip, 2, IP_INDEX_NONE, IP_PTR_FLAGS_MUT_MANY); // 46 [*]u8 ipAddPointer( ip, 2, IP_INDEX_NONE, IP_PTR_FLAGS_CONST_MANY); // 47 [*]const u8 ipAddPointer( ip, 2, ZIR_REF_ZERO_U8, IP_PTR_FLAGS_CONST_MANY); // 48 [*:0]const u8 ipAddItem(ip, IP_TAG_TYPE_SLICE, 47); // 49 []const u8 (ptr = 47) ipAddItem( ip, IP_TAG_TYPE_SLICE, 48); // 50 []const u8 sentinel 0 (ptr = 48) ipAddPointer(ip, 49, IP_INDEX_NONE, IP_PTR_FLAGS_CONST_MANY); // 51 [*]const []const u8 ipAddItem(ip, IP_TAG_TYPE_SLICE, 51); // 52 [][]const u8 (ptr = 51) ipAddItem(ip, IP_TAG_TYPE_OPTIONAL, 35); // 53 ?type ipAddPointer( ip, 35, IP_INDEX_NONE, IP_PTR_FLAGS_CONST_MANY); // 54 [*]const type ipAddItem(ip, IP_TAG_TYPE_SLICE, 54); // 55 []type (ptr = 54) // 56..102: vector types { len, child }. ipAddVector(ip, 8, 3); // vector_8_i8 ipAddVector(ip, 16, 3); ipAddVector(ip, 32, 3); ipAddVector(ip, 64, 3); ipAddVector(ip, 1, 2); // vector_1_u8 ipAddVector(ip, 2, 2); ipAddVector(ip, 4, 2); ipAddVector(ip, 8, 2); ipAddVector(ip, 16, 2); ipAddVector(ip, 32, 2); ipAddVector(ip, 64, 2); ipAddVector(ip, 2, 5); // vector_2_i16 ipAddVector(ip, 4, 5); ipAddVector(ip, 8, 5); ipAddVector(ip, 16, 5); ipAddVector(ip, 32, 5); ipAddVector(ip, 4, 4); // vector_4_u16 ipAddVector(ip, 8, 4); ipAddVector(ip, 16, 4); ipAddVector(ip, 32, 4); ipAddVector(ip, 2, 8); // vector_2_i32 ipAddVector(ip, 4, 8); ipAddVector(ip, 8, 8); ipAddVector(ip, 16, 8); ipAddVector(ip, 4, 7); // vector_4_u32 ipAddVector(ip, 8, 7); ipAddVector(ip, 16, 7); ipAddVector(ip, 2, 10); // vector_2_i64 ipAddVector(ip, 4, 10); ipAddVector(ip, 8, 10); ipAddVector(ip, 2, 9); // vector_2_u64 ipAddVector(ip, 4, 9); ipAddVector(ip, 8, 9); ipAddVector(ip, 1, 12); // vector_1_u128 ipAddVector(ip, 2, 12); ipAddVector(ip, 1, 14); // vector_1_u256 ipAddVector(ip, 4, 27); // vector_4_f16 ipAddVector(ip, 8, 27); ipAddVector(ip, 16, 27); ipAddVector(ip, 32, 27); ipAddVector(ip, 2, 28); // vector_2_f32 ipAddVector(ip, 4, 28); ipAddVector(ip, 8, 28); ipAddVector(ip, 16, 28); ipAddVector(ip, 2, 29); // vector_2_f64 ipAddVector(ip, 4, 29); ipAddVector(ip, 8, 29); // 103..107: optional/error-union/error-set/poison/empty-tuple types. ipAddItem(ip, IP_TAG_TYPE_OPTIONAL, 39); // 103 ?noreturn ipAddItem(ip, IP_TAG_TYPE_ANYERROR_UNION, 34); // 104 anyerror!void ipAddItem(ip, IP_TAG_SIMPLE_TYPE, 0); // 105 adhoc_inferred_error_set ipAddItem(ip, IP_TAG_SIMPLE_TYPE, 0); // 106 generic_poison { uint32_t w = 0; // TypeTuple { fields_len = 0 } uint32_t off = ipAddExtra(ip, &w, 1); ipAddItem(ip, IP_TAG_TYPE_TUPLE, off); // 107 empty_tuple_type } // 108..127: value preinterns. ipAddItem(ip, IP_TAG_UNDEF, 42); // 108 undefined (untyped; ty = undefined) ipAddItem(ip, IP_TAG_UNDEF, 33); // 109 @as(bool, undefined) ipAddItem(ip, IP_TAG_UNDEF, 15); // 110 @as(usize, undefined) ipAddItem(ip, IP_TAG_UNDEF, 1); // 111 @as(u1, undefined) ipAddItem(ip, IP_TAG_INT_COMPTIME_INT_U32, 0); // 112 0 (comptime_int) ipAddItem(ip, IP_TAG_INT_USIZE, 0); // 113 @as(usize, 0) ipAddIntSmall(ip, 1, 0); // 114 @as(u1, 0) ipAddItem(ip, IP_TAG_INT_U8, 0); // 115 @as(u8, 0) ipAddItem(ip, IP_TAG_INT_COMPTIME_INT_U32, 1); // 116 1 (comptime_int) ipAddItem(ip, IP_TAG_INT_USIZE, 1); // 117 @as(usize, 1) ipAddIntSmall(ip, 1, 1); // 118 @as(u1, 1) ipAddItem(ip, IP_TAG_INT_U8, 1); // 119 @as(u8, 1) ipAddItem(ip, IP_TAG_INT_U8, 4); // 120 @as(u8, 4) ipAddItem(ip, IP_TAG_INT_COMPTIME_INT_I32, 0xFFFFFFFFu); // 121 -1 ipAddItem(ip, IP_TAG_SIMPLE_VALUE, 0); // 122 {} (void) ipAddItem(ip, IP_TAG_SIMPLE_VALUE, 0); // 123 unreachable ipAddItem(ip, IP_TAG_SIMPLE_VALUE, 0); // 124 null ipAddItem(ip, IP_TAG_SIMPLE_VALUE, 0); // 125 true ipAddItem(ip, IP_TAG_SIMPLE_VALUE, 0); // 126 false ipAddItem(ip, IP_TAG_ONLY_POSSIBLE_VALUE, 107); // 127 .{} (empty_tuple) } // InternPool.zig:addMap static uint32_t ipAddMap(InternPool* ip) { // Maps live in InternPool.maps (a separate arena from items/extra); we // only model their count so `field_name_map` values are handed out in // order. return ip->maps_len++; } // InternPool.zig:trackZir uint32_t ipTrackZir(InternPool* ip) { // Tracked insts live in InternPool.tracked_insts (separate arena). We // model only the count; the first tracked instruction is TrackedInst.Index // 0. return ip->tracked_insts_len++; } uint32_t ipTrackZirInst(InternPool* ip, uint32_t file, uint32_t inst) { const uint32_t key[2] = { file, inst }; uint32_t h = ipDedupHashWords(0, key, 2); if (ip->tracked_dedup_cap != 0) { uint32_t mask = ip->tracked_dedup_cap - 1; for (uint32_t slot = h & mask;; slot = (slot + 1) & mask) { uint32_t idx = ip->tracked_dedup_slots[slot].item_idx; if (idx == IP_INDEX_NONE) break; if (ip->tracked_dedup_slots[slot].hash == h && ip->tracked_keys[2 * idx] == file && ip->tracked_keys[2 * idx + 1] == inst) return idx; } } uint32_t idx = ipTrackZir(ip); if (idx >= ip->tracked_keys_cap) { uint32_t cap = zig1GrowCap(ip->tracked_keys_cap, 64u); while (cap <= idx) cap *= 2; ip->tracked_keys = zig1Realloc( ip->tracked_keys, (size_t)cap * 2 * sizeof(uint32_t)); memset(&ip->tracked_keys[2 * ip->tracked_keys_cap], 0xFF, (size_t)(cap - ip->tracked_keys_cap) * 2 * sizeof(uint32_t)); ip->tracked_keys_cap = cap; } ip->tracked_keys[2 * idx] = file; ip->tracked_keys[2 * idx + 1] = inst; ipDedupEnsureCapacity(&ip->tracked_dedup_slots, &ip->tracked_dedup_cap, &ip->tracked_dedup_count); ipDedupPlace(ip->tracked_dedup_slots, ip->tracked_dedup_cap, h, idx, 2); ip->tracked_dedup_count++; return idx; } // --- string interning (InternPool.getOrPutString) ------------------------ // Strings live in the `string_bytes` arena, deduplicated through the string // dedup index. Strings never create IP items, so they do not perturb the // dump. static void ipStringEnsure(InternPool* ip, uint32_t need) { if (ip->string_len + need <= ip->string_cap) return; uint32_t cap = ip->string_cap ? ip->string_cap : 1024; while (ip->string_len + need > cap) cap *= 2; ip->string_bytes = zig1Realloc(ip->string_bytes, cap); ip->string_cap = cap; } static void ipStringOffsetsEnsure(InternPool* ip, uint32_t need) { if (ip->strings_count + need <= ip->strings_cap) return; uint32_t cap = ip->strings_cap ? ip->strings_cap : 128; while (ip->strings_count + need > cap) cap *= 2; ip->string_offsets = zig1Realloc(ip->string_offsets, cap * sizeof(uint32_t)); ip->strings_cap = cap; } // InternPool.zig:getOrPutString // InternPool.getOrPutString (`slice` then a NUL, through // getOrPutTrailingString): a key holding a NUL is appended as a new string // and never entered into the string map (`.maybe_embedded_nulls => if // (has_embedded_null) { ...; return value; }`), so no later query finds it: // ipStrDedupFind rejects a query that holds a NUL, and an entry is indexed // under its whole key, which no NUL-free query matches. uint32_t ipGetOrPutString(InternPool* ip, const char* bytes, uint32_t len) { uint32_t found = ipStrDedupFind(ip, bytes, len); if (found != IP_INDEX_NONE) return found; ipStringEnsure(ip, len + 1); uint32_t off = ip->string_len; memcpy(&ip->string_bytes[off], bytes, len); ip->string_bytes[off + len] = '\0'; ip->string_len += len + 1; ipStringOffsetsEnsure(ip, 1); uint32_t idx = ip->strings_count; ip->string_offsets[idx] = off; ip->strings_count++; ipStrDedupInsert(ip, idx, bytes, len); return idx; } // InternPool.zig:getOrPutStringFmt uint32_t ipGetOrPutStringFmt(InternPool* ip, const char* fmt, ...) { va_list ap; va_start(ap, fmt); int n = vsnprintf(NULL, 0, fmt, ap); va_end(ap); if (n < 0) UNIMPLEMENTED("getOrPutStringFmt: vsnprintf failed"); char* bytes = zig1Malloc((size_t)n + 1u); va_start(ap, fmt); vsnprintf(bytes, (size_t)n + 1u, fmt, ap); va_end(ap); uint32_t s = ipGetOrPutString(ip, bytes, (uint32_t)n); free(bytes); return s; } const char* ipStringBytes(const InternPool* ip, uint32_t str_idx) { return &ip->string_bytes[ip->string_offsets[str_idx]]; } // Port of `InternPool.NullTerminatedString.length`: // `return strings[unwrapped.index + 1] - 1 - strings[unwrapped.index];` // Upstream's `strings` list always has a following entry to subtract from // (its `toSlice` reads `strings[unwrapped.index + 1] - 1` as the end offset); // zig1's `string_offsets` holds only the starts, so the last string's end is // `string_len` instead. Same arithmetic either way: `ipGetOrPutString` is the // only dynamic writer and appends exactly `len + 1` bytes contiguously // (`ip->string_len += len + 1`), so adjacent starts differ by len+1, and // `ipInit` seeds index 0 as the empty string at offset 0 with string_len = 1 // (0 either way). O(1), no scan. // // DIVERGENCE from the `strlen(ipStringBytes(...))` idiom: for a string // containing an embedded NUL the two disagree — strlen yields the truncated // prefix, this yields the true stored length. zig1's string identity is // strlen-based (`ipStrDedupFind` rejects a query containing a NUL outright and // its comparator is `strlen(s) == len`), so feeding a true length back into // `ipGetOrPutString` for such a string appends a second entry instead of // deduping. Unreachable for the identifier-derived names the current callers // hold; a future embedded-NUL string would have to revisit the dedup side, not // this accessor (which matches upstream). uint32_t ipStringLen(const InternPool* ip, uint32_t str_idx) { uint32_t start = ip->string_offsets[str_idx]; uint32_t end = str_idx + 1u < ip->strings_count ? ip->string_offsets[str_idx + 1u] : ip->string_len; return end - 1u - start; } // `if (slice.len > 1 and slice[0] == '0') return null;` ... `return // std.fmt.parseUnsigned(u32, slice, 10) catch null;` bool ipBytesToUnsigned(const char* bytes, uint32_t len, uint32_t* out) { if (len == 0 || (len > 1 && bytes[0] == '0')) return false; uint32_t v = 0; for (uint32_t k = 0; k < len; k++) { unsigned char c = (unsigned char)bytes[k]; if (c < '0' || c > '9') return false; uint32_t digit = (uint32_t)(c - '0'); if (v > (UINT32_MAX - digit) / 10u) return false; v = v * 10u + digit; } *out = v; return true; } // --- Nav / Namespace / ComptimeUnit storage (separate arenas) ------------ // InternPool.zig:createNamespace uint32_t ipCreateNamespace(InternPool* ip, uint32_t parent, InternPoolIndex owner_type, uint32_t file_scope) { if (ip->namespaces_len == ip->namespaces_cap) { ip->namespaces_cap = zig1GrowCap(ip->namespaces_cap, 8u); ip->namespaces = zig1Realloc( ip->namespaces, ip->namespaces_cap * sizeof(IpNamespace)); } uint32_t idx = ip->namespaces_len++; IpNamespace* ns = &ip->namespaces[idx]; memset(ns, 0, sizeof(*ns)); ns->parent = parent; ns->owner_type = owner_type; ns->file_scope = file_scope; return idx; } IpNamespace* ipGetNamespace(InternPool* ip, uint32_t ns_idx) { return &ip->namespaces[ns_idx]; } static uint32_t ipNamespaceOrNone( const InternPool* ip, uint32_t ns, uint32_t owner_type) { if (ns != 0) return ns; if (ip->namespaces_len > 0 && ip->namespaces[0].owner_type == owner_type) return 0; return IP_NAMESPACE_NONE; } // InternPool.zig:createDeclNav uint32_t ipCreateDeclNav(InternPool* ip, uint32_t name, uint32_t fqn, uint32_t zir_index, uint32_t zir_inst, uint32_t ns, bool is_const, bool is_threadlocal, bool is_extern) { if (ip->navs_len == ip->navs_cap) { ip->navs_cap = zig1GrowCap(ip->navs_cap, 16u); ip->navs = zig1Realloc(ip->navs, ip->navs_cap * sizeof(IpNav)); } uint32_t idx = ip->navs_len++; IpNav* nav = &ip->navs[idx]; memset(nav, 0, sizeof(*nav)); nav->name = name; nav->fqn = fqn; nav->is_const = is_const; nav->is_threadlocal = is_threadlocal; nav->is_extern = is_extern; nav->has_analysis = true; nav->analysis_namespace = ns; nav->analysis_zir_index = zir_index; nav->analysis_inst = zir_inst; nav->analysis_wanted = false; nav->has_resolved = false; nav->has_value_resolved = false; nav->value_resolving = false; nav->modifiers_resolved = false; nav->resolved_alignment = IP_ALIGNMENT_NONE; // InternPool.Alignment.none nav->resolved_type = IP_INDEX_NONE; nav->resolved_value = IP_INDEX_NONE; return idx; } // InternPool.zig:getNav const IpNav* ipGetNav(const InternPool* ip, uint32_t nav_idx) { return &ip->navs[nav_idx]; } // InternPool.zig:createComptimeUnit uint32_t ipCreateComptimeUnit( InternPool* ip, uint32_t zir_index, uint32_t ns) { if (ip->comptime_units_len == ip->comptime_units_cap) { ip->comptime_units_cap = zig1GrowCap(ip->comptime_units_cap, 8u); ip->comptime_units = zig1Realloc(ip->comptime_units, ip->comptime_units_cap * sizeof(IpComptimeUnit)); } uint32_t idx = ip->comptime_units_len++; ip->comptime_units[idx].zir_index = zir_index; ip->comptime_units[idx].ns = ns; return idx; } uint32_t ipNamespaceLookupPub( const InternPool* ip, uint32_t ns_idx, uint32_t name) { const IpNamespace* ns = &ip->namespaces[ns_idx]; for (uint32_t i = 0; i < ns->pub_navs_len; i++) if (ip->navs[ns->pub_navs[i]].name == name) return ns->pub_navs[i]; return IP_INDEX_NONE; } uint32_t ipNamespaceLookup(InternPool* ip, uint32_t ns_idx, uint32_t name) { uint32_t pub = ipNamespaceLookupPub(ip, ns_idx, name); if (pub != IP_INDEX_NONE) return pub; IpNamespace* ns = &ip->namespaces[ns_idx]; for (uint32_t i = 0; i < ns->priv_navs_len; i++) if (ip->navs[ns->priv_navs[i]].name == name) return ns->priv_navs[i]; return IP_INDEX_NONE; } // Tag.TypeStruct.Flags for an *auto*-layout struct with no captures: // any_captures = .false, layout = .auto, no comptime fields / defaults / // aligns, class = .no_possible_value (0), alignment = .none, want_layout = // false. Mirrors the `.flags` literal in getDeclaredStructType's `.auto` // branch. static uint32_t ipTypeStructFlagsAuto(void) { return ((uint32_t)IP_STRUCT_ANY_CAPTURES_FALSE << IP_STRUCT_FLAGS_ANY_CAPTURES_SHIFT) | ((uint32_t)IP_ALIGNMENT_NONE << IP_STRUCT_FLAGS_ALIGNMENT_SHIFT); } // The shared `Tag.TypeStruct` append of `getDeclaredStructType` and // `getReifiedStructType`: `type_hash` is NULL for a declared struct, else the // reified key's hash, stored as the leading `type_hash: PackedU64` group with // `flags.any_captures = .reified`. static uint32_t ipStructTypeAppend(InternPool* ip, uint32_t zir_index, const uint64_t* type_hash, uint32_t captures_len, uint32_t fields_len, IpContainerLayout layout, bool any_comptime_fields, bool any_field_defaults, bool any_field_aligns); // InternPool.zig:getDeclaredStructType uint32_t ipGetDeclaredStructType(InternPool* ip, uint32_t zir_index, uint32_t captures_len, uint32_t fields_len, IpContainerLayout layout, bool any_comptime_fields, bool any_field_defaults, bool any_field_aligns) { // getOrPutKey dedup not applied here (see the `decl_dedup_*` table // comment: verification found that struct-type instantiation is not // actually zir_index-unique in this port -- `semaDeclaredNamespaceType` // (sema.c) reuses the same `struct_inst`/zir_index across every // capture-distinct generic instantiation of a struct-returning function, // relying on this getter to always append a fresh item; the real // capture-aware dedup for that case already happens one layer up, in // `ctDedupLookup`/`ctDedupInsert`, which ipGetDeclaredStructType has no // visibility into). Each call appends a fresh item, which is correct // both for the single file-root struct and for every capture-distinct // generic instantiation. return ipStructTypeAppend(ip, zir_index, NULL, captures_len, fields_len, layout, any_comptime_fields, any_field_defaults, any_field_aligns); } // `bits.captures_len = .reified`: the u31 field's maximum. static bool ipStructIsReified(const InternPool* ip, uint32_t struct_idx); static uint32_t ipStructPackedAppend(InternPool* ip, uint32_t zir_index, const uint64_t* type_hash, uint32_t fields_len, uint32_t backing_int_ty, bool explicit_backing, bool any_field_defaults); // `Key.{Struct,Union}Type.reified`'s identity words: `zir_index`, then the // two `type_hash` words that follow the header. static bool ipUnionIsReified(const InternPool* ip, uint32_t union_idx); static bool ipReifiedKeyEq(const InternPool* ip, IpTag tag, uint32_t idx, uint32_t zir_index, uint64_t type_hash) { uint8_t item_tag = ip->item_tags[idx]; uint32_t hash_word; if (tag == IP_TAG_TYPE_STRUCT) { if (!ipTagIsAnyStruct(item_tag) || !ipStructIsReified(ip, idx)) return false; hash_word = ipTagIsStructPacked(item_tag) ? IP_TYPE_STRUCT_PACKED_LEN : IP_TYPE_STRUCT_LEN; } else { if (!ipTagIsAnyUnion(item_tag) || !ipUnionIsReified(ip, idx)) return false; hash_word = ipTagIsUnionPacked(item_tag) ? IP_TYPE_UNION_PACKED_LEN : IP_TYPE_UNION_LEN; } const uint32_t* w = &ip->extra[ip->item_datas[idx]]; return w[0] == zir_index && w[hash_word] == (uint32_t)type_hash && w[hash_word + 1] == (uint32_t)(type_hash >> 32); } static uint32_t ipReifiedKeyHash( IpTag tag, uint32_t zir_index, uint64_t type_hash) { Wyhash h; wyhashInit(&h, (uint64_t)tag); ipDedupHashMixWord(&h, zir_index); wyhashUpdateU64(&h, type_hash); return (uint32_t)wyhashFinal(&h); } // (part of) InternPool.zig:getOrPutKey // `ip.getOrPutKey(.{ .{struct,union}_type = .{ .reified = .{ .zir_index, // .type_hash } } })`, in the decl-dedup table (its slots name items). static uint32_t ipReifiedFind(const InternPool* ip, IpTag tag, uint32_t zir_index, uint64_t type_hash, uint32_t h) { if (ip->decl_dedup_cap == 0) return IP_INDEX_NONE; uint32_t mask = ip->decl_dedup_cap - 1; for (uint32_t slot = h & mask;; slot = (slot + 1) & mask) { uint32_t idx = ip->decl_dedup_slots[slot].item_idx; if (idx == IP_INDEX_NONE) return IP_INDEX_NONE; if (ip->decl_dedup_slots[slot].hash == h && ipReifiedKeyEq(ip, tag, idx, zir_index, type_hash)) return idx; } } static void ipReifiedInsert(InternPool* ip, uint32_t h, uint32_t idx) { ipDedupEnsureCapacity( &ip->decl_dedup_slots, &ip->decl_dedup_cap, &ip->decl_dedup_count); ipDedupPlace(ip->decl_dedup_slots, ip->decl_dedup_cap, h, idx, 3); ip->decl_dedup_count++; } // InternPool.zig:getReifiedStructType uint32_t ipGetReifiedStructType(InternPool* ip, uint32_t zir_index, uint64_t type_hash, uint32_t fields_len, IpContainerLayout layout, bool any_comptime_fields, bool any_field_defaults, bool any_field_aligns, uint32_t packed_backing_int_ty, bool* out_existing) { uint32_t h = ipReifiedKeyHash(IP_TAG_TYPE_STRUCT, zir_index, type_hash); uint32_t found = ipReifiedFind(ip, IP_TAG_TYPE_STRUCT, zir_index, type_hash, h); if (found != IP_INDEX_NONE) { *out_existing = true; return found; } uint32_t idx = layout == IP_STRUCT_LAYOUT_PACKED ? ipStructPackedAppend(ip, zir_index, &type_hash, fields_len, packed_backing_int_ty, packed_backing_int_ty != IP_INDEX_NONE, any_field_defaults) : ipStructTypeAppend(ip, zir_index, &type_hash, 0, fields_len, layout, any_comptime_fields, any_field_defaults, any_field_aligns); ipReifiedInsert(ip, h, idx); *out_existing = false; return idx; } static uint32_t ipStructTypeAppend(InternPool* ip, uint32_t zir_index, const uint64_t* type_hash, uint32_t captures_len, uint32_t fields_len, IpContainerLayout layout, bool any_comptime_fields, bool any_field_defaults, bool any_field_aligns) { if (layout != IP_STRUCT_LAYOUT_AUTO && layout != IP_STRUCT_LAYOUT_EXTERN) { // packed is handled by ipGetDeclaredStructTypePacked; anything else is // unexpected here. UNREACHABLE("ipGetDeclaredStructType: unexpected layout"); } uint32_t field_name_map = ipAddMap(ip); // Push the Tag.TypeStruct payload (auto branch), then any trailing // per-field/per-capture words. name/fqn/name_nav/namespace are set by // `setName`/`finish`: written here as placeholders. uint32_t off = ip->extra_len; // Flags: the auto-layout base, plus the trailing-array presence bits // (mirrors upstream getDeclaredStructType's `.flags = .{ ... // .any_comptime_fields, .any_field_defaults, .any_field_aligns }` — // loadStructType gates the trailing decode on these, so they must match // the arrays serialized below). uint32_t st_flags = ipTypeStructFlagsAuto(); if (any_comptime_fields) st_flags |= IP_STRUCT_FLAGS_ANY_COMPTIME_FIELDS; if (any_field_defaults) st_flags |= IP_STRUCT_FLAGS_ANY_FIELD_DEFAULTS; if (any_field_aligns) st_flags |= IP_STRUCT_FLAGS_ANY_FIELD_ALIGNS; if (layout == IP_STRUCT_LAYOUT_EXTERN) st_flags |= IP_STRUCT_FLAGS_LAYOUT; // .@"extern" if (type_hash != NULL) st_flags = (st_flags & ~(IP_STRUCT_FLAGS_ANY_CAPTURES_MASK << IP_STRUCT_FLAGS_ANY_CAPTURES_SHIFT)) | ((uint32_t)IP_STRUCT_ANY_CAPTURES_REIFIED << IP_STRUCT_FLAGS_ANY_CAPTURES_SHIFT); const uint32_t hdr[IP_TYPE_STRUCT_LEN] = { [IP_TYPE_STRUCT_ZIR_INDEX] = zir_index, [IP_TYPE_STRUCT_NAME] = 0, // set by setName [IP_TYPE_STRUCT_FQN] = 0, // set by setName [IP_TYPE_STRUCT_NAME_NAV] = IP_INDEX_NONE, // .none [IP_TYPE_STRUCT_NAMESPACE] = 0, // set by finish [IP_TYPE_STRUCT_FIELDS_LEN] = fields_len, [IP_TYPE_STRUCT_FIELD_NAME_MAP] = field_name_map, [IP_TYPE_STRUCT_SIZE] = 0, // 0 until layout resolved [IP_TYPE_STRUCT_FLAGS] = st_flags, }; ipAddExtra(ip, hdr, IP_TYPE_STRUCT_LEN); // Trailing arrays (all empty for a fields_len==0 file root struct); // ordered exactly as getDeclaredStructType appends them. if (type_hash != NULL) { // `PackedU64.init(ini.type_hash)`: `a` the low word, `b` the high. const uint32_t th[2] = { (uint32_t)*type_hash, (uint32_t)(*type_hash >> 32) }; ipAddExtra(ip, th, 2); } if (captures_len != 0) { ipAddExtra(ip, &captures_len, 1); // captures_len // The capture words themselves are not modeled. UNIMPLEMENTED("InternPool.getDeclaredStructType: captures"); } for (uint32_t i = 0; i < fields_len; i++) { // field_name uint32_t empty = 0; ipAddExtra(ip, &empty, 1); } for (uint32_t i = 0; i < fields_len; i++) { // field_type = .none uint32_t none = IP_INDEX_NONE; ipAddExtra(ip, &none, 1); } if (any_field_defaults) { for (uint32_t i = 0; i < fields_len; i++) { // field_default = .none uint32_t none = IP_INDEX_NONE; ipAddExtra(ip, &none, 1); } } if (any_field_aligns) { // Zero, not `.none`: upstream mints this group as // `"appendNTimesAssumeCapacity(.{0}, (ini.fields_len + 3) / 4); // // field_align"` and every field byte is overwritten later, by // `ipSetStructFieldAlignsNone` here as by `resolveStructLayout` // upstream — so the trailing padding bytes of the last word keep the // 0 written here, and a 0 in a field byte means "not resolved yet". uint32_t n = (fields_len + 3) / 4; for (uint32_t i = 0; i < n; i++) { // field_align (placeholder) uint32_t zero = 0; ipAddExtra(ip, &zero, 1); } } if (any_comptime_fields) { uint32_t n = (fields_len + 31) / 32; for (uint32_t i = 0; i < n; i++) { // field_is_comptime_bits uint32_t z = 0; ipAddExtra(ip, &z, 1); } } if (layout != IP_STRUCT_LAYOUT_EXTERN) { // field_runtime_order (RuntimeOrder.unresolved), auto layout only. for (uint32_t i = 0; i < fields_len; i++) { uint32_t unresolved = 0xFFFFFFFFu; ipAddExtra(ip, &unresolved, 1); } } for (uint32_t i = 0; i < fields_len; i++) { // field_offset = 0 uint32_t z = 0; ipAddExtra(ip, &z, 1); } return ipAddItem(ip, IP_TAG_TYPE_STRUCT, off); } // Every container payload carries `name, fqn, name_nav, namespace` in a row; // only where `name` sits differs per tag // (`std.meta.fieldIndex(Tag.TypeStructPacked, "name").?`). #define IP_CONTAINER_FQN (IP_TYPE_STRUCT_FQN - IP_TYPE_STRUCT_NAME) #define IP_CONTAINER_NAME_NAV (IP_TYPE_STRUCT_NAME_NAV - IP_TYPE_STRUCT_NAME) #define IP_CONTAINER_NAMESPACE (IP_TYPE_STRUCT_NAMESPACE - IP_TYPE_STRUCT_NAME) #define IP_CONTAINER_NAME_RUN(P) \ (IP_TYPE_##P##_FQN - IP_TYPE_##P##_NAME == IP_CONTAINER_FQN \ && IP_TYPE_##P##_NAME_NAV - IP_TYPE_##P##_NAME \ == IP_CONTAINER_NAME_NAV \ && IP_TYPE_##P##_NAMESPACE - IP_TYPE_##P##_NAME \ == IP_CONTAINER_NAMESPACE) // A negative array size, C99's static assertion: the generated container // layouts keep fqn, name_nav and namespace at the struct's offsets from name. typedef char IpContainerNameWordsMatch [(IP_CONTAINER_NAME_RUN(STRUCT_PACKED) && IP_CONTAINER_NAME_RUN(UNION) && IP_CONTAINER_NAME_RUN(UNION_PACKED) && IP_CONTAINER_NAME_RUN(ENUM) && IP_CONTAINER_NAME_RUN(OPAQUE)) ? 1 : -1]; static uint32_t ipContainerNameWord(const InternPool* ip, uint32_t ty) { uint32_t d = ip->item_datas[ty]; switch ((IpTag)ip->item_tags[ty]) { case IP_TAG_TYPE_STRUCT: return d + IP_TYPE_STRUCT_NAME; case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: return d + IP_TYPE_STRUCT_PACKED_NAME; case IP_TAG_TYPE_UNION: return d + IP_TYPE_UNION_NAME; case IP_TAG_TYPE_UNION_PACKED_AUTO: case IP_TAG_TYPE_UNION_PACKED_EXPLICIT: return d + IP_TYPE_UNION_PACKED_NAME; case IP_TAG_TYPE_ENUM_AUTO: case IP_TAG_TYPE_ENUM_EXPLICIT: case IP_TAG_TYPE_ENUM_NONEXHAUSTIVE: return d + IP_TYPE_ENUM_NAME; case IP_TAG_TYPE_OPAQUE: return d + IP_TYPE_OPAQUE_NAME; default: UNREACHABLE("container name: not a container type"); } ZIG1_TCC_RETURN(0); } static uint32_t ipStructNamespaceWord( const InternPool* ip, uint32_t struct_idx) { return ipContainerNameWord(ip, struct_idx) + IP_CONTAINER_NAMESPACE; } // Where the trailing groups start: `type_hash: PackedU64` comes first for a // `.reified` struct (`any_captures == .reified` / `captures_len == .reified`); // zig1 never mints captures, so nothing else precedes `field_name`. static bool ipStructIsReified(const InternPool* ip, uint32_t struct_idx) { uint32_t data = ip->item_datas[struct_idx]; if (ipStructIsPacked(ip, struct_idx)) return (ip->extra[data + IP_TYPE_STRUCT_PACKED_BITS] & IP_PACKED_CAPTURES_LEN_REIFIED) == IP_PACKED_CAPTURES_LEN_REIFIED; return ((ip->extra[data + IP_TYPE_STRUCT_FLAGS] >> IP_STRUCT_FLAGS_ANY_CAPTURES_SHIFT) & IP_STRUCT_FLAGS_ANY_CAPTURES_MASK) == IP_STRUCT_ANY_CAPTURES_REIFIED; } static uint32_t ipStructFieldNamesBase( const InternPool* ip, uint32_t struct_idx) { return ip->item_datas[struct_idx] + (ipStructIsPacked(ip, struct_idx) ? IP_TYPE_STRUCT_PACKED_LEN : IP_TYPE_STRUCT_LEN) + (ipStructIsReified(ip, struct_idx) ? IP_PACKED_U64_LEN : 0u); } // (part of) InternPool.zig:WipContainerType.setName // `wip.setName(ip, type_name, type_fqn, name_nav)`: `name_nav` is the Nav a // `.parent`-strategy type is named after, else `.none`. void ipSetTypeName(InternPool* ip, uint32_t ty, uint32_t type_name, uint32_t type_fqn, uint32_t name_nav) { uint32_t w = ipContainerNameWord(ip, ty); ip->extra[w] = type_name; ip->extra[w + IP_CONTAINER_FQN] = type_fqn; ip->extra[w + IP_CONTAINER_NAME_NAV] = name_nav; } // InternPool.zig:setWantTypeLayout // InternPool.setWantTypeLayout: the `want_layout` flag of a struct, union or // enum, set by the first reference to its layout. void ipSetWantTypeLayout(InternPool* ip, uint32_t ty) { uint32_t d = ip->item_datas[ty]; switch ((IpTag)ip->item_tags[ty]) { case IP_TAG_TYPE_STRUCT: ip->extra[d + IP_TYPE_STRUCT_FLAGS] |= IP_STRUCT_FLAGS_WANT_LAYOUT; break; case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: ip->extra[d + IP_TYPE_STRUCT_PACKED_BITS] |= IP_STRUCT_PACKED_BITS_WANT_LAYOUT; break; case IP_TAG_TYPE_UNION_PACKED_AUTO: case IP_TAG_TYPE_UNION_PACKED_EXPLICIT: ip->extra[d + IP_TYPE_UNION_PACKED_BITS] |= IP_UNION_PACKED_BITS_WANT_LAYOUT; break; case IP_TAG_TYPE_UNION: ip->extra[d + IP_TYPE_UNION_FLAGS] |= IP_UNION_FLAGS_WANT_LAYOUT; break; case IP_TAG_TYPE_ENUM_AUTO: case IP_TAG_TYPE_ENUM_EXPLICIT: case IP_TAG_TYPE_ENUM_NONEXHAUSTIVE: ip->extra[d + IP_TYPE_ENUM_BITS] |= IP_ENUM_BITS_WANT_LAYOUT; break; default: break; } } // Type.zig:containerTypeName uint32_t ipContainerTypeName(const InternPool* ip, uint32_t type_idx) { return ip->extra[ipContainerNameWord(ip, type_idx)]; } uint32_t ipContainerTypeFqn(const InternPool* ip, uint32_t type_idx) { return ip->extra[ipContainerNameWord(ip, type_idx) + IP_CONTAINER_FQN]; } // `wip.finish` backpatches the `namespace` word with the container's namespace // (mirrors upstream `WipContainerType.finish`); read it back for method / decl // lookup. void ipSetStructNamespace(InternPool* ip, uint32_t struct_idx, uint32_t ns) { ip->extra[ipStructNamespaceWord(ip, struct_idx)] = ns; } // The struct's namespace word, with its ambiguity resolved: the namespace word // is initialized to 0 by ipGetDeclaredStructType and only backpatched by // `finish`, yet 0 is also a valid namespace index — the first namespace ever // created (std's file-root namespace: std is the first analysis root, so // zptEnsureFilePopulated(std) runs before any other ipCreateNamespace). // Disambiguate by ownership: namespace 0 is this struct's namespace iff its // owner_type is this struct (mirrors upstream, where Namespace.owner_type // back-references the container and `.none` is a distinct // OptionalNamespaceIndex sentinel rather than 0). Returns IP_NAMESPACE_NONE // when the struct has no namespace. uint32_t ipStructNamespaceOrNone(const InternPool* ip, uint32_t struct_idx) { return ipNamespaceOrNone( ip, ip->extra[ipStructNamespaceWord(ip, struct_idx)], struct_idx); } static uint32_t ipEnumFieldNamesOff(const InternPool* ip, uint32_t enum_ty); void ipDeferFieldNames(InternPool* ip, uint32_t ty, const uint8_t* strings, const uint32_t* names, uint32_t len) { if (ip->pending_field_names_len == ip->pending_field_names_cap) { uint32_t cap = zig1GrowCap(ip->pending_field_names_cap, 16u); IpPendingFieldNames* items = zig1Realloc( ip->pending_field_names, cap * sizeof(IpPendingFieldNames)); ip->pending_field_names = items; ip->pending_field_names_cap = cap; } ip->pending_field_names[ip->pending_field_names_len++] = (IpPendingFieldNames) { ty, strings, names, len }; } // (part of) InternPool.zig:getOrPutString // resolveStructLayout's / resolveEnumLayout's `"const name = try // ip.getOrPutString(gpa, io, pt.tid, name_slice, .no_embedded_nulls);"` per // ZIR field name of `key`, in order, into `dest`'s field names (a generated // union tag enum takes its union's names). static void ipResolvePendingFieldNames( InternPool* ip, uint32_t key, uint32_t dest) { for (uint32_t p = 0; p < ip->pending_field_names_len; p++) { if (ip->pending_field_names[p].ty != key) continue; IpPendingFieldNames pending = ip->pending_field_names[p]; ip->pending_field_names[p] = ip->pending_field_names[--ip->pending_field_names_len]; bool is_enum = ipIsEnumType(ip, dest); for (uint32_t i = 0; i < pending.len; i++) { const char* s = (const char*)&pending.strings[pending.names[i]]; uint32_t name = ipGetOrPutString(ip, s, (uint32_t)strlen(s)); if (is_enum) ip->extra[ipEnumFieldNamesOff(ip, dest) + i] = name; else ip->extra[ipStructFieldNamesBase(ip, dest) + i] = name; } return; } } void ipResolveFieldNames(InternPool* ip, uint32_t ty) { ipResolvePendingFieldNames(ip, ty, ty); } // PerThread.zig:smallestUnsignedInt, Type.zig:smallestUnsignedBits // `pt.smallestUnsignedInt` / `Type.smallestUnsignedBits`. uint32_t ipSmallestUnsignedInt(InternPool* ip, uint64_t max) { uint32_t bits = 0; while (bits < 64u && (max >> bits) != 0) bits++; return ipGetIntType(ip, false, bits); } // (part of) Sema/type_resolution.zig:resolveEnumLayout // Sets the inferred int tag type: `"if (empty_exhaustive) break :ty // .noreturn;"`, else `"Type.smallestUnsignedBits(enum_obj.field_names.len -| // 1)"` bits. Every inferred tag type is exhaustive (a non-exhaustive enum // names its own). void ipSetInferredEnumIntTagType( InternPool* ip, uint32_t enum_ty, uint32_t fields_len) { ipSetEnumIntTagType(ip, enum_ty, fields_len == 0 ? ZIR_REF_NORETURN_TYPE : ipSmallestUnsignedInt(ip, fields_len - 1u)); } void ipResolveEnumAutoTagType(InternPool* ip, uint32_t enum_ty) { uint32_t off = ip->item_datas[enum_ty]; if (ip->extra[off + IP_TYPE_ENUM_INT_TAG_TYPE] != IP_INDEX_NONE) return; if (ip->item_tags[enum_ty] != IP_TAG_TYPE_ENUM_AUTO) { if (ip->resolve_enum_layout != NULL) ip->resolve_enum_layout(ip->resolve_enum_layout_ctx, enum_ty); return; } ipSetWantTypeLayout(ip, enum_ty); ipResolveFieldNames(ip, enum_ty); ipSetInferredEnumIntTagType( ip, enum_ty, ip->extra[off + IP_TYPE_ENUM_FIELDS_LEN]); } // A field name still `.none` is a read before layout resolution; resolve it // there. static bool ipHasPendingFieldNames(const InternPool* ip, uint32_t ty) { for (uint32_t p = 0; p < ip->pending_field_names_len; p++) if (ip->pending_field_names[p].ty == ty) return true; return false; } static uint32_t ipFieldNameAt(InternPool* ip, uint32_t ty, uint32_t word) { if ((ip->extra[word] == 0 || ip->extra[word] == IP_INDEX_NONE) && ipHasPendingFieldNames(ip, ty)) { // Upstream resolves the layout before any name is read. ipSetWantTypeLayout(ip, ty); ipResolveFieldNames(ip, ty); } return ip->extra[word]; } // The per-field `field_name` array immediately follows the 8-word header (see // ipGetDeclaredStructType's trailing layout). Backpatch/read field i's name // (NullTerminatedString index). Mirrors the field-name half of // `WipContainerType.finish` / `loadStructType().field_names`. void ipSetStructFieldName( InternPool* ip, uint32_t struct_idx, uint32_t i, uint32_t name) { ip->extra[ipStructFieldNamesBase(ip, struct_idx) + i] = name; } uint32_t ipStructFieldName(InternPool* ip, uint32_t struct_idx, uint32_t i) { if (ipStructFieldsPlaceholder(ip, struct_idx)) UNREACHABLE("struct field name read before its layout resolution"); return ipFieldNameAt( ip, struct_idx, ipStructFieldNamesBase(ip, struct_idx) + i); } // A declared struct's fields before resolveStructLayout populates them: its // names are minted 0 (the empty string, no field's name). A packed struct's // names wait in pending_field_names instead. bool ipStructFieldsPlaceholder(const InternPool* ip, uint32_t struct_idx) { return ipStructFieldsLen(ip, struct_idx) != 0 && ip->extra[ipStructFieldNamesBase(ip, struct_idx)] == 0 && !ipHasPendingFieldNames(ip, struct_idx); } uint32_t ipStructFieldIndex( InternPool* ip, uint32_t struct_idx, uint32_t name) { uint32_t len = ipStructFieldsLen(ip, struct_idx); for (uint32_t i = 0; i < len; i++) { if (ipStructFieldName(ip, struct_idx, i) == name) return i; } return UINT32_MAX; } // The per-field `field_type` array follows the `field_name` array (see the // trailing layout in ipGetDeclaredStructType). Backpatch/read field i's type // (InternPool type index). Mirrors resolveStructFieldTypes / // `loadStructType().field_types`. void ipSetStructFieldType( InternPool* ip, uint32_t struct_idx, uint32_t i, uint32_t ty) { uint32_t fields_len = ipStructFieldsLen(ip, struct_idx); ip->extra[ipStructFieldNamesBase(ip, struct_idx) + fields_len + i] = ty; } uint32_t ipStructFieldType( const InternPool* ip, uint32_t struct_idx, uint32_t i) { uint32_t fields_len = ipStructFieldsLen(ip, struct_idx); uint32_t word = ipStructFieldNamesBase(ip, struct_idx) + fields_len + i; if (ip->extra[word] == IP_INDEX_NONE && ipStructFieldsPlaceholder(ip, struct_idx)) UNREACHABLE("struct field type read before its layout resolution"); return ip->extra[word]; } // Locate the optional field_default array shared by ordinary/extern // Tag.TypeStruct and the two packed `_DEFAULTS` tags. Returns false only when // the selected struct genuinely omits the array; malformed indices/payloads // stop loudly instead of turning a bad offset into a plausible `.none`. static bool ipStructFieldDefaultsBase(const InternPool* ip, uint32_t struct_idx, uint32_t* out_base, uint32_t* out_fields_len) { if (struct_idx >= ip->items_len) UNREACHABLE("struct field default item index out of bounds"); uint8_t tag = ip->item_tags[struct_idx]; uint32_t data = ip->item_datas[struct_idx]; bool packed = ipTagIsStructPacked(tag); if (tag != IP_TAG_TYPE_STRUCT && !packed) UNREACHABLE("struct field default access on non-struct item"); if (data > ip->extra_len || ip->extra_len - data < (packed ? IP_TYPE_STRUCT_PACKED_LEN : IP_TYPE_STRUCT_LEN)) UNREACHABLE("malformed struct field default header"); bool present = tag == IP_TAG_TYPE_STRUCT ? (ip->extra[data + IP_TYPE_STRUCT_FLAGS] & IP_STRUCT_FLAGS_ANY_FIELD_DEFAULTS) != 0 : tag == IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS || tag == IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS; uint32_t fields_len = ipStructFieldsLen(ip, struct_idx); uint64_t base64 = (uint64_t)ipStructFieldNamesBase(ip, struct_idx) + 2u * fields_len; if (base64 > ip->extra_len || (present && fields_len > ip->extra_len - (uint32_t)base64)) UNREACHABLE("malformed struct field default array"); *out_base = (uint32_t)base64; *out_fields_len = fields_len; return present; } uint32_t ipStructFieldDefault( const InternPool* ip, uint32_t struct_idx, uint32_t i) { uint32_t base; uint32_t fields_len; bool present = ipStructFieldDefaultsBase(ip, struct_idx, &base, &fields_len); if (i >= fields_len) UNREACHABLE("ipStructFieldDefault: index out of bounds"); if (!present) return IP_INDEX_NONE; uint32_t value = ip->extra[base + i]; if (value != IP_INDEX_NONE && value >= ip->items_len) UNREACHABLE("struct field default value index out of bounds"); return value; } void ipSetStructFieldDefault( InternPool* ip, uint32_t struct_idx, uint32_t i, uint32_t value) { uint32_t base; uint32_t fields_len; bool present = ipStructFieldDefaultsBase(ip, struct_idx, &base, &fields_len); if (i >= fields_len) UNREACHABLE("ipSetStructFieldDefault: index out of bounds"); if (!present) UNIMPLEMENTED( "InternPool.loadStructType: struct has no field default array"); if (value != IP_INDEX_NONE && value >= ip->items_len) UNREACHABLE("struct field default write value out of bounds"); ip->extra[base + i] = value; } static uint32_t ipStructFieldComptimeBitsBase( const InternPool* ip, uint32_t struct_idx) { // A packed struct has no field_is_comptime_bits group at all (upstream's // `LoadedStructType.field_is_comptime_bits` is `.empty` on the packed // path), so every field reads back non-comptime. if (ipStructIsPacked(ip, struct_idx)) return IP_INDEX_NONE; uint32_t data = ip->item_datas[struct_idx]; uint32_t fields_len = ip->extra[data + IP_TYPE_STRUCT_FIELDS_LEN]; uint32_t flags = ip->extra[data + IP_TYPE_STRUCT_FLAGS]; if ((flags & IP_STRUCT_FLAGS_ANY_COMPTIME_FIELDS) == 0) return IP_INDEX_NONE; uint32_t base = ipStructFieldNamesBase(ip, struct_idx) + 2 * fields_len; // field_name + field_type if (flags & IP_STRUCT_FLAGS_ANY_FIELD_DEFAULTS) base += fields_len; if (flags & IP_STRUCT_FLAGS_ANY_FIELD_ALIGNS) base += (fields_len + 3) / 4; return base; } void ipSetStructFieldIsComptime( InternPool* ip, uint32_t struct_idx, uint32_t i, bool is_comptime) { uint32_t fields_len = ipStructFieldsLen(ip, struct_idx); if (i >= fields_len) { UNREACHABLE("ipSetStructFieldIsComptime: index out of bounds"); } uint32_t base = ipStructFieldComptimeBitsBase(ip, struct_idx); if (base == IP_INDEX_NONE) { if (!is_comptime) return; UNIMPLEMENTED( "InternPool.loadStructType: struct has no comptime-field bit bag"); } uint32_t mask = 1u << (i & 31u); if (is_comptime) ip->extra[base + i / 32u] |= mask; else ip->extra[base + i / 32u] &= ~mask; } bool ipStructFieldIsComptime( const InternPool* ip, uint32_t struct_idx, uint32_t i) { uint32_t fields_len = ipStructFieldsLen(ip, struct_idx); if (i >= fields_len) { UNREACHABLE("ipStructFieldIsComptime: index out of bounds"); } uint32_t base = ipStructFieldComptimeBitsBase(ip, struct_idx); if (base == IP_INDEX_NONE) return false; if (ipStructFieldsPlaceholder(ip, struct_idx)) UNREACHABLE("struct field comptime bit read before its layout " "resolution"); return (ip->extra[base + i / 32u] & (1u << (i & 31u))) != 0; } // True iff the serialized struct has a per-field alignment array (created // when any sibling carries an explicit annotation). static bool ipStructHasFieldAligns(const InternPool* ip, uint32_t struct_idx) { // No field_align group on the packed path either (upstream's // `field_aligns` is `.empty` there, so every field align is `.none`). if (ipStructIsPacked(ip, struct_idx)) return false; return (ip->extra[ip->item_datas[struct_idx] + IP_TYPE_STRUCT_FLAGS] & IP_STRUCT_FLAGS_ANY_FIELD_ALIGNS) != 0; } static uint32_t ipStructFieldAlignsBase( const InternPool* ip, uint32_t struct_idx) { if (ipStructIsPacked(ip, struct_idx)) return IP_INDEX_NONE; uint32_t data = ip->item_datas[struct_idx]; uint32_t fields_len = ip->extra[data + IP_TYPE_STRUCT_FIELDS_LEN]; uint32_t flags = ip->extra[data + IP_TYPE_STRUCT_FLAGS]; if ((flags & IP_STRUCT_FLAGS_ANY_FIELD_ALIGNS) == 0) return IP_INDEX_NONE; uint32_t base = ipStructFieldNamesBase(ip, struct_idx) + 2 * fields_len; if (flags & IP_STRUCT_FLAGS_ANY_FIELD_DEFAULTS) base += fields_len; // field_default return base; } void ipSetStructFieldAlignment( InternPool* ip, uint32_t struct_idx, uint32_t i, uint32_t alignment) { uint32_t fields_len = ipStructFieldsLen(ip, struct_idx); uint32_t base = ipStructFieldAlignsBase(ip, struct_idx); if (i >= fields_len || base == IP_INDEX_NONE || alignment > IP_ALIGNMENT_NONE) { UNREACHABLE("invalid struct field alignment write"); } uint32_t shift = (i & 3u) * 8u; uint32_t mask = 0xFFu << shift; ip->extra[base + i / 4u] = (ip->extra[base + i / 4u] & ~mask) | (alignment << shift); } // Set every FIELD's alignment byte to `.none`, leaving the last word's // padding bytes at the 0 the mint wrote. This is upstream's // `resolveStructLayout` writing // `"struct_obj.field_aligns.get(ip)[zir_field.idx] = field_align;"` once a // struct's fields are resolved, whose value is `.none` for a field with no // align body (`"zir_field.align_body orelse break :a .none;"`). A field // that has a body overwrites this in the same pass, via // `semaResolveStructFieldAlignment`. void ipSetStructFieldAlignsNone(InternPool* ip, uint32_t struct_idx) { if (!ipStructHasFieldAligns(ip, struct_idx)) return; uint32_t fields_len = ipStructFieldsLen(ip, struct_idx); for (uint32_t i = 0; i < fields_len; i++) ipSetStructFieldAlignment(ip, struct_idx, i, IP_ALIGNMENT_NONE); } uint32_t ipStructFieldAlignment( const InternPool* ip, uint32_t struct_idx, uint32_t i) { uint32_t fields_len = ipStructFieldsLen(ip, struct_idx); if (i >= fields_len) { UNREACHABLE("struct field alignment index out of bounds"); } uint32_t base = ipStructFieldAlignsBase(ip, struct_idx); if (base == IP_INDEX_NONE) return IP_ALIGNMENT_NONE; if (ipStructFieldsPlaceholder(ip, struct_idx)) UNREACHABLE("struct field align read before its layout resolution"); uint32_t shift = (i & 3u) * 8u; return (ip->extra[base + i / 4u] >> shift) & IP_ALIGNMENT_NONE; } // (part of) InternPool.zig:resolveStructLayout // `InternPool.resolveStructLayout` backpatches the `size` word. void ipSetStructSize(InternPool* ip, uint32_t struct_idx, uint32_t size) { ipLayoutRejectPacked(ip, struct_idx, "ipSetStructSize"); ip->extra[ip->item_datas[struct_idx] + IP_TYPE_STRUCT_SIZE] = size; } // (part of) InternPool.zig:resolveStructLayout // The packed `flags` word's `alignment` field (Alignment as log2 units, // .none == 63). Backpatch just those bits, leaving // the rest of the flags word intact. Mirrors `flags.alignment = alignment` in // InternPool.resolveStructLayout. void ipSetStructAlignment( InternPool* ip, uint32_t struct_idx, uint32_t log2_align) { ipLayoutRejectPacked(ip, struct_idx, "ipSetStructAlignment"); uint32_t* flags = &ip->extra[ip->item_datas[struct_idx] + IP_TYPE_STRUCT_FLAGS]; *flags = (*flags & ~(IP_STRUCT_FLAGS_ALIGNMENT_MASK << IP_STRUCT_FLAGS_ALIGNMENT_SHIFT)) | ((log2_align & IP_STRUCT_FLAGS_ALIGNMENT_MASK) << IP_STRUCT_FLAGS_ALIGNMENT_SHIFT); } // InternPool.Alignment.fromByteUnits (InternPool.zig): a zero byte count // is `.none` (63, maxInt(u6)); anything else must be a power of two and // encodes as its @ctz. Aborts through `ctx` where upstream asserts. uint32_t ipAlignmentFromByteUnits(uint64_t bytes, const char* ctx) { if (bytes == 0) return IP_ALIGNMENT_NONE; if ((bytes & (bytes - 1u)) != 0) UNIMPLEMENTED(ctx); uint32_t log2 = 0; while ((bytes >> log2) != 1u) log2++; if (log2 > 62u) UNIMPLEMENTED(ctx); return log2; } void ipSetStructClass( InternPool* ip, uint32_t struct_idx, uint32_t type_class) { ipLayoutRejectPacked(ip, struct_idx, "ipSetStructClass"); if (type_class > TYPE_CLASS_FULLY_COMPTIME) { UNREACHABLE("invalid struct Type.Class"); } uint32_t* flags = &ip->extra[ip->item_datas[struct_idx] + IP_TYPE_STRUCT_FLAGS]; *flags = (*flags & ~(IP_STRUCT_FLAGS_CLASS_MASK << IP_STRUCT_FLAGS_CLASS_SHIFT)) | (type_class << IP_STRUCT_FLAGS_CLASS_SHIFT); } // field_offset is the last trailing array of a struct: after field_name and // field_type (fields_len words each), the optional field_default / // field_align / field_is_comptime_bits arrays (present iff their flag bit is // set), and — auto layout only — field_runtime_order (fields_len words); an // extern struct has no runtime-order group, so this is the field_offset base. static uint32_t ipStructFieldRuntimeOrderBase( const InternPool* ip, uint32_t struct_idx) { uint32_t data = ip->item_datas[struct_idx]; uint32_t fields_len = ip->extra[data + IP_TYPE_STRUCT_FIELDS_LEN]; uint32_t flags = ip->extra[data + IP_TYPE_STRUCT_FLAGS]; uint32_t base = ipStructFieldNamesBase(ip, struct_idx) + 2 * fields_len; // field_name + field_type if (flags & IP_STRUCT_FLAGS_ANY_FIELD_DEFAULTS) base += fields_len; if (flags & IP_STRUCT_FLAGS_ANY_FIELD_ALIGNS) base += (fields_len + 3) / 4; if (flags & IP_STRUCT_FLAGS_ANY_COMPTIME_FIELDS) base += (fields_len + 31) / 32; return base; } void ipSetStructFieldRuntimeOrder(InternPool* ip, uint32_t struct_idx, uint32_t runtime_position, uint32_t field_index) { ipLayoutRejectPacked(ip, struct_idx, "ipSetStructFieldRuntimeOrder"); if (runtime_position >= ipStructFieldsLen(ip, struct_idx)) { UNREACHABLE("ipSetStructFieldRuntimeOrder: index out of bounds"); } ip->extra[ipStructFieldRuntimeOrderBase(ip, struct_idx) + runtime_position] = field_index; } static uint32_t ipStructFieldOffsetBase( const InternPool* ip, uint32_t struct_idx) { uint32_t base = ipStructFieldRuntimeOrderBase(ip, struct_idx); if (!ipStructIsExtern(ip, struct_idx)) // field_runtime_order: auto only base += ipStructFieldsLen(ip, struct_idx); return base; } void ipSetStructFieldOffset( InternPool* ip, uint32_t struct_idx, uint32_t i, uint32_t off) { ipLayoutRejectPacked(ip, struct_idx, "ipSetStructFieldOffset"); ip->extra[ipStructFieldOffsetBase(ip, struct_idx) + i] = off; } uint32_t ipStructFieldOffset( const InternPool* ip, uint32_t struct_idx, uint32_t i) { // `"assert(struct_type.layout != .@\"packed\");"` — a packed field's // position is a bit offset, recomputed by the `bitOffsetOf` packed arm. ipLayoutRejectPacked(ip, struct_idx, "ipStructFieldOffset"); return ip->extra[ipStructFieldOffsetBase(ip, struct_idx) + i]; } void ipStructFieldOffsets( const InternPool* ip, uint32_t struct_idx, uint32_t* out_offsets) { ipLayoutRejectPacked(ip, struct_idx, "ipStructFieldOffsets"); uint32_t fields_len = ipStructFieldsLen(ip, struct_idx); uint32_t base = ipStructFieldOffsetBase(ip, struct_idx); memcpy( out_offsets, &ip->extra[base], (size_t)fields_len * sizeof(uint32_t)); } // ── Packed structs ────────────────────────────────────────────────────────── // `Tag.TypeStructPacked.Bits`: `captures_len: enum(u31)` + `want_layout: // bool`, packed LSB-first. static uint32_t ipTypeStructPackedBits( uint32_t captures_len, bool want_layout) { return (captures_len & IP_STRUCT_PACKED_BITS_CAPTURES_LEN_MASK) | (want_layout ? IP_STRUCT_PACKED_BITS_WANT_LAYOUT : 0u); } // Upstream's `Tag.TypeStructPacked` header, word for word (see the `hdr` // literal below), then `"const struct_packed_encoding = .{"`'s trailing groups // in order: `field_names`, `field_types`, and — for the two `_defaults` tags, // per `"const struct_packed_defaults_encoding = .{"` — `field_defaults`. The // `type_hash?` / `captures?` groups precede those but occupy no words here: // both are keyed on `bits.captures_len`, which zig1 always mints 0 and never // `.reified`. Tag chosen as `getDeclaredStructType` does, from // `(explicit_backing, any_field_defaults)`. // `bits` mints `want_layout = false` as upstream at mint — upstream's own // `"pub fn setWantTypeLayout("` flips it true on the first layout reference // and never resets it (residual D3d) — and `captures_len = 0`, since // zig1 decodes and discards a packed struct's captures // (`"ci += 2 * captures_len; // captures[] + capture_names[]"`, // sema_comptime.c) into a namespace-side table instead (residual D3b). The // Tag.TypeStructPacked item both getters append; `type_hash` non-NULL is // getReifiedStructType's `.reified` shape (`PackedU64.init(ini.type_hash)` // right after the header). static uint32_t ipStructPackedAppend(InternPool* ip, uint32_t zir_index, const uint64_t* type_hash, uint32_t fields_len, uint32_t backing_int_ty, bool explicit_backing, bool any_field_defaults) { uint32_t field_name_map = ipAddMap(ip); uint32_t off = ip->extra_len; const uint32_t hdr[IP_TYPE_STRUCT_PACKED_LEN] = { [IP_TYPE_STRUCT_PACKED_ZIR_INDEX] = zir_index, [IP_TYPE_STRUCT_PACKED_BITS] = ipTypeStructPackedBits( type_hash != NULL ? IP_PACKED_CAPTURES_LEN_REIFIED : 0, false), [IP_TYPE_STRUCT_PACKED_NAME] = 0, // set by setName [IP_TYPE_STRUCT_PACKED_FQN] = 0, // set by setName [IP_TYPE_STRUCT_PACKED_NAME_NAV] = IP_INDEX_NONE, // .none [IP_TYPE_STRUCT_PACKED_NAMESPACE] = 0, // set by finish // `.none` at mint; ipSetStructPackedBacking patches it [IP_TYPE_STRUCT_PACKED_BACKING_INT_TYPE] = backing_int_ty, [IP_TYPE_STRUCT_PACKED_FIELDS_LEN] = fields_len, [IP_TYPE_STRUCT_PACKED_FIELD_NAME_MAP] = field_name_map, }; ipAddExtra(ip, hdr, IP_TYPE_STRUCT_PACKED_LEN); if (type_hash != NULL) { const uint32_t th[2] = { (uint32_t)*type_hash, (uint32_t)(*type_hash >> 32) }; ipAddExtra(ip, th, 2); } for (uint32_t i = 0; i < fields_len; i++) { // field_name uint32_t empty = 0; ipAddExtra(ip, &empty, 1); } for (uint32_t i = 0; i < fields_len; i++) { // field_type = .none uint32_t none = IP_INDEX_NONE; ipAddExtra(ip, &none, 1); } if (any_field_defaults) { // The group is real payload upstream. It starts unresolved and the // struct-default lifecycle fills each exact slot after layout. for (uint32_t i = 0; i < fields_len; i++) { // field_default = .none uint32_t none = IP_INDEX_NONE; ipAddExtra(ip, &none, 1); } } IpTag tag = explicit_backing ? (any_field_defaults ? IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS : IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT) : (any_field_defaults ? IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS : IP_TAG_TYPE_STRUCT_PACKED_AUTO); return ipAddItem(ip, tag, off); } uint32_t ipGetDeclaredStructTypePacked(InternPool* ip, uint32_t zir_index, uint32_t fields_len, uint32_t backing_int_ty, bool explicit_backing, bool any_field_defaults) { // getOrPutKey dedup not applied here -- see the reasoning at // ipGetDeclaredStructType (same declared-struct shape, same // generic-instantiation hazard). return ipStructPackedAppend(ip, zir_index, NULL, fields_len, backing_int_ty, explicit_backing, any_field_defaults); } // True for an `extern struct` (`Tag.TypeStruct.Flags.layout`, bit 2). Extern // structs keep declaration order + C ABI layout (semaComputeStructLayout does // not reorder their fields by alignment). bool ipStructIsExtern(const InternPool* ip, uint32_t struct_idx) { if (ip->item_tags[struct_idx] != IP_TAG_TYPE_STRUCT) return false; return (ip->extra[ip->item_datas[struct_idx] + IP_TYPE_STRUCT_FLAGS] & IP_STRUCT_FLAGS_LAYOUT) != 0; } // Patches a packed struct's backing-int word after interning, mirroring // `"pub fn resolvePackedStructLayout("` (size/alignment are the backing int's, // so upstream stores neither). Needed for a deferred field type resolvable // only after the struct's own namespace is scanned — the type mints with // `.none` backing so the namespace exists first. void ipSetStructPackedBacking( InternPool* ip, uint32_t struct_idx, uint32_t backing_int_ty) { ip->extra[ip->item_datas[struct_idx] + IP_TYPE_STRUCT_PACKED_BACKING_INT_TYPE] = backing_int_ty; } // InternPool.get's `"assert(ip.typeOf(elem) == child);"` and its siblings. static void ipAssertTypeOf( const InternPool* ip, uint32_t val, uint32_t ty, const char* msg) { if (ipTypeOf(ip, val) != ty) UNREACHABLE(msg); } uint32_t ipGetBitpack( InternPool* ip, uint32_t struct_ty, uint32_t backing_val) { // Dedup by { ty, backing_int_val } like every other `ipGet*`: stock // `InternPool.get` hashes `.bitpack` on exactly those two words // ("Hash.hash(seed, asBytes(&bitpack.ty) ++ // asBytes(&bitpack.backing_int_val))") and returns the existing index on // a hit, which is what makes `Value.eql`'s interned-index identity a // value comparison for packed structs. // `"assert(ip.typeOf(bitpack.backing_int_val) == // ip.loadStructType(bitpack.ty).packed_backing_int_type)"` ipAssertTypeOf(ip, backing_val, ipBitpackBackingInt(ip, struct_ty), "InternPool.get: bitpack value not of its backing int type"); const uint32_t words[2] = { struct_ty, backing_val }; uint32_t found = ipDedupFind(ip, IP_TAG_BITPACK, words, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_BITPACK, off); ipDedupInsert(ip, IP_TAG_BITPACK, idx, words, 2); return idx; } uint32_t ipBitpackType(const InternPool* ip, uint32_t idx) { return ip->extra[ip->item_datas[idx]]; } uint32_t ipBitpackBackingVal(const InternPool* ip, uint32_t idx) { return ip->extra[ip->item_datas[idx] + 1]; } // --- the typed accessor layer (see the big comment in zig1.h) ------ IpArrayStatus ipArrayInfo( const InternPool* ip, uint32_t ty, IpArrayInfo* out) { return ipArrayInfoInline(ip, ty, out); } static IpAggElemKind ipAggregateElemKind(const InternPool* ip, uint32_t agg, uint32_t index, uint32_t* out_sentinel) { // Value.elemValue's `.undef` arm comes before the `.aggregate` one and // answers `"try pt.intern(.{ .undef = ... .childType(zcu) ... })"` for // every index -- an undefined sequence has no sentinel to reach, so the // caller's own undef handling answers and this classification declines. if (agg < ip->items_len && ip->item_tags[agg] == IP_TAG_UNDEF) return IP_AGG_ELEM_STORED; IpArrayInfo info; if (ipArrayInfo(ip, ipTypeOf(ip, agg), &info) != IP_ARR_OK || index < info.len) return IP_AGG_ELEM_STORED; if (index == info.len && info.sentinel != IP_INDEX_NONE) { *out_sentinel = info.sentinel; return IP_AGG_ELEM_SENTINEL; } return IP_AGG_ELEM_OUT_OF_RANGE; } IpAggElemKind ipAggregateElemValue( InternPool* ip, uint32_t agg, uint32_t index, uint32_t* out) { IpAggElemKind kind = ipAggregateElemKind(ip, agg, index, out); if (kind != IP_AGG_ELEM_STORED) return kind; // `"switch (aggregate.storage)"` -- the three storage forms // InternPool.get may pick, read only after the `index < len` rule above // has said this index is a stored element. switch ((IpTag)ip->item_tags[agg]) { case IP_TAG_REPEATED: *out = ip->extra[ip->item_datas[agg] + 1]; // Repeated.elem_val return kind; case IP_TAG_AGGREGATE: *out = ip->extra[ip->item_datas[agg] + 1 + index]; // ty then elems return kind; case IP_TAG_BYTES: { const char* bytes = ipStringBytes(ip, ip->extra[ip->item_datas[agg] + 1]); *out = ipGetIntU8(ip, (uint8_t)bytes[index]); return kind; } case IP_TAG_ONLY_POSSIBLE_VALUE: // indexToKey: a tuple's only possible value is `"tuple.values"`. if (!ipItemTagIs(ip, ip->item_datas[agg], IP_TAG_TYPE_TUPLE)) return IP_AGG_ELEM_UNSUPPORTED_STORAGE; *out = ipTupleFieldValue(ip, ip->item_datas[agg], index); return kind; default: return IP_AGG_ELEM_UNSUPPORTED_STORAGE; } } IpPtrInfoStatus ipPtrInfo(const InternPool* ip, uint32_t ty, IpPtrInfo* out) { return ipPtrInfoInline(ip, ty, out); } // Type.zig:optionalChild uint32_t ipOptionalChild(const InternPool* ip, uint32_t ty) { if (ipItemTagIs(ip, ty, IP_TAG_TYPE_POINTER) && IP_PTR_FLAGS_SIZE( ip->extra[ip->item_datas[ty] + IP_TYPE_POINTER_FLAGS]) == IP_PTR_SIZE_C) return ty; if (ty >= ip->items_len || ip->item_tags[ty] != IP_TAG_TYPE_OPTIONAL) UNREACHABLE("ipOptionalChild: not an optional type or C pointer"); // `type_optional` stores the child Index directly in item.data. return ip->item_datas[ty]; } uint32_t ipOptEuBaseType(const InternPool* ip, uint32_t ty) { while (ty < ip->items_len) { if (ip->item_tags[ty] == IP_TAG_TYPE_OPTIONAL) { ty = ipOptionalChild(ip, ty); continue; } if (ip->item_tags[ty] == IP_TAG_TYPE_ERROR_UNION || ip->item_tags[ty] == IP_TAG_TYPE_ANYERROR_UNION) { ty = ipErrorUnionPayload(ip, ty); continue; } break; } return ty; } // Shared, fully bounds-checked locator for TypeTuple's two trailing arrays // (`values` selects values[] over types[]). One body so the `+1` / // `+1 + fields_len` arithmetic exists in exactly one place. static uint32_t ipTupleTrailing( const InternPool* ip, uint32_t ty, uint32_t i, bool values) { uint32_t n = ipTupleFieldCount(ip, ty); if (i >= n) UNREACHABLE("ipTupleField*: field index out of range"); uint32_t base = ip->item_datas[ty]; // ipTupleFieldCount already proved base < extra_len. if ((uint64_t)(ip->extra_len - base) < 1u + 2u * (uint64_t)n) UNREACHABLE("ipTupleField*: malformed tuple type payload"); return ip->extra[base + 1u + (values ? n : 0u) + i]; } uint32_t ipTupleFieldCount(const InternPool* ip, uint32_t ty) { if (ty >= ip->items_len || ip->item_tags[ty] != IP_TAG_TYPE_TUPLE) UNREACHABLE("ipTupleFieldCount: not a tuple type"); uint32_t base = ip->item_datas[ty]; if (base >= ip->extra_len) UNREACHABLE("ipTupleFieldCount: malformed tuple type payload"); return ip->extra[base]; } uint32_t ipTupleFieldType(const InternPool* ip, uint32_t ty, uint32_t i) { return ipTupleTrailing(ip, ty, i, false); } uint32_t ipTupleFieldValue(const InternPool* ip, uint32_t ty, uint32_t i) { return ipTupleTrailing(ip, ty, i, true); } bool ipIsPtrComptimeAlloc(const InternPool* ip, uint32_t v) { return ipItemTagIs(ip, v, IP_TAG_PTR_COMPTIME_ALLOC); } uint32_t ipPtrComptimeAllocIndex(const InternPool* ip, uint32_t v) { if (!ipIsPtrComptimeAlloc(ip, v)) UNREACHABLE("ipPtrComptimeAllocIndex: not a ptr_comptime_alloc"); uint32_t base = ip->item_datas[v]; if (base > ip->extra_len || ip->extra_len - base < 4u) UNREACHABLE( "ipPtrComptimeAllocIndex: malformed ptr_comptime_alloc payload"); return ip->extra[base + 1]; // PtrComptimeAlloc.index } uint64_t ipPtrComptimeAllocByteOffset(const InternPool* ip, uint32_t v) { if (!ipIsPtrComptimeAlloc(ip, v)) UNREACHABLE("ipPtrComptimeAllocByteOffset: not a ptr_comptime_alloc"); uint32_t base = ip->item_datas[v]; if (base > ip->extra_len || ip->extra_len - base < 4u) UNREACHABLE( "ipPtrComptimeAllocByteOffset: malformed ptr_comptime_alloc " "payload"); uint64_t off_hi = ip->extra[base + 2]; uint64_t off_lo = ip->extra[base + 3]; return (off_hi << 32) | off_lo; } // Type.zig:bitSize, Type.zig:unpackable // `Type.bitSize` of a type a bit pointer can point at (`Type.unpackable` is // null): bool, ints, floats, enums by their tag int, and packed // structs / unions by their backing int, which must already be resolved // (upstream ptrType asserts the same). static uint32_t ipPackableBitSize(InternPool* ip, uint32_t ty) { uint32_t bits; bool is_signed; if (ty == ZIR_REF_BOOL_TYPE) return 1; if (ty == ZIR_REF_VOID_TYPE) return 0; if (ztIntInfo(ip, ty, &bits, &is_signed) || ztSimpleFloatBits(ty, &bits)) return bits; if (ty < ip->items_len && (ip->item_tags[ty] == IP_TAG_TYPE_ENUM_EXPLICIT || ip->item_tags[ty] == IP_TAG_TYPE_ENUM_NONEXHAUSTIVE || ip->item_tags[ty] == IP_TAG_TYPE_ENUM_AUTO)) return ipPackableBitSize(ip, ipEnumTypeIntTagType(ip, ty)); if (ty < ip->items_len && (ipTagIsStructPacked(ip->item_tags[ty]) || ipTagIsUnionPacked(ip->item_tags[ty]))) { uint32_t backing = ipBitpackBackingInt(ip, ty); if (backing == IP_INDEX_NONE) UNIMPLEMENTED("ptrType: bit pointer to an unresolved packed type"); return ipPackableBitSize(ip, backing); } UNIMPLEMENTED("ptrType: bit pointer to an unpackable type"); ZIG1_TCC_RETURN(0); } uint32_t ipGetPtrTypeFull(InternPool* ip, uint32_t child, uint32_t flags, uint32_t sentinel, uint32_t packed_offset) { // Tag.TypePointer == Key.PtrType (extern struct): child, sentinel, flags, // packed_offset — each one word. `.slice`-size pointers take a different // path upstream (emit a type_slice), not reached here. `sentinel` is an // interned value of the child type (`0` for `[:0]const u8`'s many-ptr) or // IP_INDEX_NONE (the common no-sentinel case, ipGetPtrType below). // Upstream Zcu.PerThread.ptrType canonicalizes every `.c` pointer to // allowzero before InternPool deduplication, even when ZIR omitted it, // and a bit pointer whose host is exactly as wide as the pointee to // `host_size = 0`. if (IP_PTR_FLAGS_SIZE(flags) == IP_PTR_SIZE_C) flags |= IP_PTR_FLAGS_IS_ALLOWZERO; uint32_t host_size = IP_PTR_PACKED_HOST_SIZE(packed_offset); if (host_size != 0 && IP_PTR_FLAGS_VECTOR_INDEX(flags) == IP_PTR_VECTOR_INDEX_NONE && host_size * 8u == ipPackableBitSize(ip, child)) packed_offset &= ~0xFFFFu; const uint32_t words[IP_TYPE_POINTER_LEN] = { [IP_TYPE_POINTER_CHILD] = child, [IP_TYPE_POINTER_SENTINEL] = sentinel, // .none unless terminated [IP_TYPE_POINTER_FLAGS] = flags, // PtrType.Flags (packed u32) [IP_TYPE_POINTER_PACKED_OFFSET] = packed_offset, }; // getOrPutKey dedup: upstream `InternPool.get` looks the `Key.PtrType` up // in the shard map and returns the existing index on a hit, appending only // on a miss. Reproduce that by scanning existing `type_pointer` items for // a match on all four `Key.PtrType` words. This is required for the // program-entry chain: e.g. the `*const type` formed for `std.debug`'s nav // pointer must reuse the identical `*const type` already interned for // `std.start` (entry 130), rather than appending a spurious new // type_pointer. uint32_t found = ipDedupFind( ip, IP_TAG_TYPE_POINTER, words, IP_TYPE_POINTER_LEN, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, IP_TYPE_POINTER_LEN); uint32_t idx = ipAddItem(ip, IP_TAG_TYPE_POINTER, off); ipDedupInsert(ip, IP_TAG_TYPE_POINTER, idx, words, IP_TYPE_POINTER_LEN); return idx; } uint32_t ipGetPtrTypeSentinel( InternPool* ip, uint32_t child, uint32_t flags, uint32_t sentinel) { return ipGetPtrTypeFull(ip, child, flags, sentinel, 0); } uint32_t ipGetPtrType(InternPool* ip, uint32_t child, uint32_t flags) { return ipGetPtrTypeSentinel(ip, child, flags, IP_INDEX_NONE); } uint32_t ipGetPtrNav(InternPool* ip, uint32_t ty, uint32_t nav) { return ipGetPtrNavOffset(ip, ty, nav, 0); } uint32_t ipGetPtrNavOffset( InternPool* ip, uint32_t ty, uint32_t nav, uint64_t byte_offset) { // PtrNav: ty, nav, byte_offset_a (high), byte_offset_b (low). // Dedup (mirrors upstream `pt.intern(.{ .ptr = ... })`, which interns via // the shared dedup map): a `*const fn` nav pointer referenced twice — e.g. // `helper(x) + helper(x)` — yields the same ptr entry, no fresh IP item. uint32_t off_lo = (uint32_t)(byte_offset & UINT32_MAX); uint32_t off_hi = (uint32_t)(byte_offset >> 32); // Index-accelerated: `ipGetPtrNavOffset` (this getter, called directly // for `ipGetPtrNav`'s byte_offset==0 case too) is the tag's only // creator -- no static preintern, no other ipAddItem site. const uint32_t words[4] = { ty, nav, off_hi, off_lo }; uint32_t found = ipDedupFind(ip, IP_TAG_PTR_NAV, words, 4, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, IP_TYPE_POINTER_LEN); uint32_t idx = ipAddItem(ip, IP_TAG_PTR_NAV, off); ipDedupInsert(ip, IP_TAG_PTR_NAV, idx, words, 4); return idx; } // Tag.TypeFunction.Flags (packed u32) for a function's calling convention with // no var-args / comptime / noalias bits. Mirrors the `.flags` literal built in // getFuncType (InternPool.zig) from `PackedCallingConvention.pack(cc)`. // `.auto` for a non-exported fn, `.wasm_mvp` for an exported one on wasm32 // (Sema.zig -> cCallingConvention), each with a `.none` incoming stack // alignment and `extra` 0. Both, and navFuncDecl's `.@"inline"`, have // extraLen() == 0. uint32_t ipTypeFunctionFlags(bool is_export) { const uint32_t align_none = IP_PTR_ALIGN_NONE << IP_PACKED_CC_ALIGN_SHIFT; return is_export ? (((uint32_t)CC_TAG_WASM_MVP << IP_PACKED_CC_TAG_SHIFT) | align_none) : (((uint32_t)CC_TAG_AUTO << IP_PACKED_CC_TAG_SHIFT) | align_none); } // InternPool.zig:getFuncType uint32_t ipGetFuncType(InternPool* ip, uint32_t return_type, const uint32_t* param_types, uint32_t params_len, uint32_t flags, uint32_t comptime_bits, uint32_t noalias_bits) { // Tag.TypeFunction (InternPool.zig): { params_len, return_type, // flags }, then `comptime_bits: u32` (if flags.has_comptime_bits), // `noalias_bits: u32` (if flags.has_noalias_bits), the SPIR-V options // words // (`flags.cc.extraLen()` words, always 0 here -- see the header), then // trailing `param_type: Index` for each `params_len`. // // A generic function whose signature includes a `comptime` parameter // (e.g. `fn g(comptime n: u32, x: u32)`) carries a non-zero // `comptime_bits` (bit i set for a comptime parameter i); getFuncType then // sets flags.has_comptime_bits and stores the extra word (InternPool.zig: // 9083). Likewise `noalias` parameters set bits in `noalias_bits` // (flags.has_noalias_bits + one extra word). Each grows the entry, which // the byte-identical IP offset gate depends on. bool has_comptime = comptime_bits != 0; if (has_comptime) flags |= IP_FUNC_FLAGS_HAS_COMPTIME_BITS; bool has_noalias = noalias_bits != 0; if (has_noalias) flags |= IP_FUNC_FLAGS_HAS_NOALIAS_BITS; // // Dedup by {params_len, return_type, flags, [comptime_bits], // [noalias_bits], param_types[0..params_len)} (mirrors // InternPool.getOrPutKey). Two functions with the same signature and // same cc share one type_function (e.g. exported `add` and `sub`); a // `.wasm_mvp` export and an `.auto` helper of the same signature do not // dedup because their `flags` differ. // // Indexed. The creators of a type_function // item are this getter's miss path, `ipGetFuncDeclIes`'s `off_ft` item // and `ipGetFuncInstance`'s inferred-error-set `off_ft` item. The other // two insert with a key built exactly as this getter builds it (header // order params_len, return_type, flags, then the optional comptime_bits // and noalias_bits, then the param types), without a find: their // `return_type` is a `type_error_union` item they appended moments // earlier in the same call, so the key cannot be present yet. None of the // key words of a type_function item is written after its creation; the // later writes near one (`FuncInstance.owner_nav`, the resolved // inferred-error-set slot) patch other items. uint32_t n = 3 + (has_comptime ? 1u : 0u) + (has_noalias ? 1u : 0u) + params_len; // A stack buffer for the common case, as this getter also runs on every // dedup hit; malloc only for very wide signatures. uint32_t key_stack[40]; uint32_t* key = n <= 40 ? key_stack : (uint32_t*)zig1Malloc((size_t)n * sizeof(uint32_t)); uint32_t p = 0; key[p++] = params_len; key[p++] = return_type; key[p++] = flags; if (has_comptime) key[p++] = comptime_bits; if (has_noalias) key[p++] = noalias_bits; for (uint32_t i = 0; i < params_len; i++) key[p++] = param_types[i]; uint32_t found = ipDedupFind(ip, IP_TAG_TYPE_FUNCTION, key, n, false); if (key != key_stack) free(key); if (found != IP_INDEX_NONE) return found; // dedup: return existing entry // No match: append new type_function entry. uint32_t off = ip->extra_len; const uint32_t hdr[3] = { params_len, // params_len return_type, // return_type flags, // flags }; ipAddExtra(ip, hdr, 3); if (has_comptime) ipAddExtra(ip, &comptime_bits, 1); if (has_noalias) ipAddExtra(ip, &noalias_bits, 1); for (uint32_t i = 0; i < params_len; i++) ipAddExtra(ip, ¶m_types[i], 1); uint32_t idx = ipAddItem(ip, IP_TAG_TYPE_FUNCTION, off); ipDedupInsert(ip, IP_TAG_TYPE_FUNCTION, idx, &ip->extra[off], n); return idx; } // InternPool.zig:getFuncDecl uint32_t ipGetFuncDecl(InternPool* ip, uint32_t owner_nav, uint32_t ty, bool is_noinline, uint32_t zir_body_inst, uint32_t lbrace_line, uint32_t rbrace_line, uint32_t lbrace_column, uint32_t rbrace_column) { // Tag.FuncDecl (InternPool.zig): { analysis, owner_nav, ty, // zir_body_inst, lbrace_line, rbrace_line, lbrace_column, rbrace_column }. // `analysis` is the default FuncAnalysis (packed u32, all zero except // is_noinline (bit 4) for a `noinline fn`: // want_runtime_analysis=false, branch_hint=.none(0), // has_error_trace=false, inferred_error_set=false, // disable_instrumentation=false, disable_intrinsics=false). Because // inferred_error_set is false, there is no trailing error-set word. // // getOrPutKey dedup on `owner_nav` (see the `decl_dedup_*` table comment // for why this is a sound simplification of upstream's `owner_nav`+`ty` // `.func` key here). `ty` is already known (caller-supplied, not a // forward reference), so -- unlike ipGetFuncDeclIes -- the check can run // before any item/extra is appended. uint32_t decl_found = ipDeclDedupFind(ip, IP_TAG_FUNC_DECL, owner_nav); if (decl_found != IP_INDEX_NONE) return decl_found; uint32_t off = ip->extra_len; const uint32_t words[8] = { is_noinline ? IP_FUNC_ANALYSIS_IS_NOINLINE : 0u, // analysis (FuncAnalysis) owner_nav, // owner_nav (Nav.Index) ty, // ty (Index) = the type_function zir_body_inst, // zir_body_inst (TrackedInst.Index) lbrace_line, // lbrace_line rbrace_line, // rbrace_line lbrace_column, // lbrace_column rbrace_column, // rbrace_column }; ipAddExtra(ip, words, 8); uint32_t idx = ipAddItem(ip, IP_TAG_FUNC_DECL, off); ipDeclDedupInsert(ip, IP_TAG_FUNC_DECL, idx, owner_nav); return idx; } // InternPool.zig:getFuncDeclIes // Intern a function declaration with an inferred error set (`fn f() !T`), // mirroring InternPool.getFuncDeclIes (InternPool.zig). Unlike the plain // `getFuncType` + `getFuncDecl` pair, an inferred-error-set function interns // four consecutive IP items in this exact order (the func decl and its type // mutually reference one another and the freshly-formed error-union / inferred // error set types): // N+0 func_decl analysis.inferred_error_set=true, ty=N+3 // N+1 type_error_union { error_set_type=N+2, payload_type=bare_ret } // N+2 type_inferred_error_set data = N+0 (the owning func_decl index) // N+3 type_function { params_len, return_type=N+1, flags } // The func_decl carries a trailing `inferred_error_set` word (the resolved // error set, initially `.none`) that a plain func_decl does not. Because each // function's inferred error set is unique to that function, no two such // func_types ever dedup, so the four items are always appended fresh (matching // the non-`existing` branch of getFuncDeclIes). This path is reached only when // analyzing a `!T` declaration. uint32_t ipGetFuncDeclIes(InternPool* ip, uint32_t owner_nav, uint32_t bare_return_type, const uint32_t* param_types, uint32_t params_len, uint32_t base_flags, uint32_t comptime_bits, bool is_noinline, uint32_t zir_body_inst, uint32_t lbrace_line, uint32_t rbrace_line, uint32_t lbrace_column, uint32_t rbrace_column) { uint32_t func_index = ip->items_len; uint32_t error_union_type = func_index + 1; uint32_t error_set_type = func_index + 2; uint32_t func_ty = func_index + 3; // Saved so a dedup hit below can roll back all 4 speculatively-appended // items' extra words, mirroring upstream's `errdefer { items.mutate.len // -= 4; extra.mutate.len = prev_extra_len; }` (InternPool.zig, // taken on the `getOrPutKey` hit path at). Unlike // `ipGetFuncDecl`, `ty` here (`func_ty`) is a forward reference to the // type_function this very call is about to create, so the dedup key // can't be checked before appending -- see the `decl_dedup_*` table // comment. uint32_t prev_extra_len = ip->extra_len; // func_decl analysis word: branch_hint .none (bits 1-3 = 0), // inferred_error_set = bit 6, is_noinline = bit 4. uint32_t analysis = IP_FUNC_ANALYSIS_INFERRED_ERROR_SET | (is_noinline ? IP_FUNC_ANALYSIS_IS_NOINLINE : 0u); const uint32_t fd_words[9] = { analysis, // analysis (FuncAnalysis) owner_nav, // owner_nav (Nav.Index) func_ty, // ty (Index) = instance type_function (N+3) zir_body_inst, // zir_body_inst (TrackedInst.Index) lbrace_line, rbrace_line, lbrace_column, rbrace_column, IP_INDEX_NONE, // trailing resolved inferred_error_set (.none) }; uint32_t off_fd = ipAddExtra(ip, fd_words, 9); // type_function flags: cc/export bits from `base_flags`, plus // has_comptime_bits (bit 19) when the signature has comptime params, plus // is_noinline (bit 21). (is_var_args / has_noalias_bits are not produced // by this path.) uint32_t ft_flags = base_flags; if (comptime_bits != 0) ft_flags |= IP_FUNC_FLAGS_HAS_COMPTIME_BITS; if (is_noinline) ft_flags |= IP_FUNC_FLAGS_IS_NOINLINE; const uint32_t ft_hdr[3] = { params_len, error_union_type, ft_flags }; uint32_t off_ft = ipAddExtra(ip, ft_hdr, 3); if (comptime_bits != 0) ipAddExtra(ip, &comptime_bits, 1); for (uint32_t i = 0; i < params_len; i++) ipAddExtra(ip, ¶m_types[i], 1); // The ErrorUnionType payload is serialized after the TypeFunction payload, // matching the stock extra ordering (getFuncDeclIes writes FuncDecl, then // TypeFunction, then ErrorUnionType inline in the items append) — keeps // `data=` offsets byte-identical should this path ever be byte-compared. const uint32_t eu_words[2] = { error_set_type, bare_return_type }; uint32_t off_eu = ipAddExtra(ip, eu_words, 2); ipAddItem(ip, IP_TAG_FUNC_DECL, off_fd); // N+0 ipAddItem(ip, IP_TAG_TYPE_ERROR_UNION, off_eu); // N+1 ipAddItem(ip, IP_TAG_TYPE_INFERRED_ERROR_SET, func_index); // N+2 ipAddItem(ip, IP_TAG_TYPE_FUNCTION, off_ft); // N+3 // getOrPutKey dedup on `owner_nav` (see the `decl_dedup_*` table comment) // -- append-then-check-then-maybe-roll-back, mirroring upstream exactly // (InternPool.zig): a hit undoes all four items and their extra // words (upstream's `items.mutate.len -= 4; extra.mutate.len = // prev_extra_len`) and returns the pre-existing func_index. A hit here // also means the type_function dedup-insert below never runs for this // call's type_function item, which the rollback removed. // // Known residue, not rolled back here: the caller's `ipTrackZir(ip)` id // (see the call site in zcu_per_thread.c, navFuncDecl's is_inferred_error // branch, `uint32_t zir_body_inst = ipTrackZir(ip);` immediately before // this getter is called). `ipTrackZir` is a bare monotonic counter // (`ip->tracked_insts_len++`, this file) with no rollback path of its // own, so a dedup hit below permanently burns that id even though this // call's `func_decl` (and the other 3 speculatively-appended items) are // discarded. Effect: for any program that hits this dedup path, every // `ipTrackZir` id (and hence every `zir_body_inst`/`zir_index` word baked // into a later item's `extra` payload) sits +1 vs upstream from that // point on -- invisible to the tag+offset-shape dump, since tracked-inst // ids aren't themselves dumped, only the words derived from them. // Residue accepted. uint32_t decl_found = ipDeclDedupFind(ip, IP_TAG_FUNC_DECL, owner_nav); if (decl_found != IP_INDEX_NONE) { ip->items_len = func_index; ip->extra_len = prev_extra_len; return decl_found; } ipDeclDedupInsert(ip, IP_TAG_FUNC_DECL, func_index, owner_nav); // Insert this out-of-getter type_function item into ipGetFuncType's // index -- see the creator-audit comment at ipGetFuncType for why an // unconditional insert (no find-before-insert here) is safe: this key's // `return_type` word is `error_union_type` (func_index + 1), an index // this very call just created via a bare, non-deduped ipAddItem two lines // above, so the key is provably absent from the index until now. The // key words (params_len, error_union_type, ft_flags, [comptime_bits], // param_types) are exactly the `n` words written contiguously starting // at off_ft above (no has_noalias branch on this path, matching the // getter's own optional-word order). uint32_t ft_n = 3 + (comptime_bits != 0 ? 1u : 0u) + params_len; // The absence proof above is load-bearing: should it ever fail, abort // instead of silently forking // dedup decisions (ipDedupGrow does not preserve probe order for // duplicate keys). if (ipDedupFind(ip, IP_TAG_TYPE_FUNCTION, &ip->extra[off_ft], ft_n, false) != IP_INDEX_NONE) { UNREACHABLE("ipGetFuncDeclIes: type_function key unexpectedly " "already interned"); } ipDedupInsert(ip, IP_TAG_TYPE_FUNCTION, func_ty, &ip->extra[off_ft], ft_n); if (ipDedupFind(ip, IP_TAG_TYPE_ERROR_UNION, &ip->extra[off_eu], 2, false) != IP_INDEX_NONE) { UNREACHABLE("ipGetFuncDeclIes: type_error_union key unexpectedly " "already interned"); } ipDedupInsert( ip, IP_TAG_TYPE_ERROR_UNION, error_union_type, &ip->extra[off_eu], 2); return func_index; } // InternPool.zig:funcTypeReturnType uint32_t ipFuncTypeReturnType(const InternPool* ip, uint32_t func_ty) { return ip->extra[ip->item_datas[func_ty] + IP_TYPE_FUNCTION_RETURN_TYPE]; } // Return the packed Flags word of a `type_function` item. static uint32_t ipFuncTypeFlags(const InternPool* ip, uint32_t func_ty) { return ip->extra[ip->item_datas[func_ty] + IP_TYPE_FUNCTION_FLAGS]; } bool ipFuncTypeIsInline(const InternPool* ip, uint32_t func_ty) { // The low 8 bits of Flags encode PackedCallingConvention.tag // (CallingConvention.Tag). `.@"inline"` is Tag ordinal 3 // (auto=0, async=1, naked=2, @"inline"=3 — see // std.lang.CallingConvention). return ((ipFuncTypeFlags(ip, func_ty) >> IP_PACKED_CC_TAG_SHIFT) & IP_PACKED_CC_TAG_MASK) == CC_TAG_INLINE; } bool ipFuncTypeIsNoinline(const InternPool* ip, uint32_t func_ty) { // Flags.is_noinline is bit 21 (cc occupies bits 0..17, then is_var_args, // has_comptime_bits, has_noalias_bits, is_noinline), the bit // sema_comptime.c sets for a `noinline fn`. return (ipFuncTypeFlags(ip, func_ty) & IP_FUNC_FLAGS_IS_NOINLINE) != 0; } uint32_t ipFuncTypeCcTag(const InternPool* ip, uint32_t func_ty) { // PackedCallingConvention.tag occupies the low 8 bits of Flags.cc // (InternPool.zig: `tag: std.lang.CallingConvention.Tag` is the // first field of the u18 packed struct, and Flags.cc is its low field). return ipFuncTypeFlags(ip, func_ty) & IP_PACKED_CC_TAG_MASK; } uint32_t ipFuncTypeCcPacked(const InternPool* ip, uint32_t func_ty) { // Flags.cc, the u18 PackedCallingConvention, is the low 18 bits. return ipFuncTypeFlags(ip, func_ty) & IP_PACKED_CC_MASK; } bool ipFuncTypeIsVarArgs(const InternPool* ip, uint32_t func_ty) { // Flags.is_var_args is bit 18 (immediately above the u18 `cc` field). return (ipFuncTypeFlags(ip, func_ty) & IP_FUNC_FLAGS_IS_VAR_ARGS) != 0; } uint32_t ipFuncTypeComptimeBits(const InternPool* ip, uint32_t func_ty) { // Flags.has_comptime_bits is bit 19; the comptime_bits word (if present) // immediately follows the header. if (!(ipFuncTypeFlags(ip, func_ty) & IP_FUNC_FLAGS_HAS_COMPTIME_BITS)) return 0; return ip->extra[ip->item_datas[func_ty] + IP_TYPE_FUNCTION_LEN]; } uint32_t ipFuncTypeNoaliasBits(const InternPool* ip, uint32_t func_ty) { // Flags.has_noalias_bits is bit 20; the noalias_bits word (if present) // follows the header and the optional comptime_bits word. uint32_t flags = ipFuncTypeFlags(ip, func_ty); if (!(flags & IP_FUNC_FLAGS_HAS_NOALIAS_BITS)) return 0; uint32_t off = ip->item_datas[func_ty] + IP_TYPE_FUNCTION_LEN; if (flags & IP_FUNC_FLAGS_HAS_COMPTIME_BITS) off += 1; // skip comptime_bits return ip->extra[off]; } // InternPool.zig:funcTypeParamsLen uint32_t ipFuncTypeParamsLen(const InternPool* ip, uint32_t func_ty) { return ip->extra[ip->item_datas[func_ty] + IP_TYPE_FUNCTION_PARAMS_LEN]; } uint32_t ipFuncTypeParamType( const InternPool* ip, uint32_t func_ty, uint32_t i) { // Tag.TypeFunction header: { params_len, return_type, flags }, then an // optional `comptime_bits` word (if has_comptime_bits), then an optional // `noalias_bits` word (if has_noalias_bits), then the trailing // `param_type: Index` for each param. uint32_t flags = ipFuncTypeFlags(ip, func_ty); uint32_t ptbase = ip->item_datas[func_ty] + IP_TYPE_FUNCTION_LEN; if (flags & IP_FUNC_FLAGS_HAS_COMPTIME_BITS) ptbase += 1; if (flags & IP_FUNC_FLAGS_HAS_NOALIAS_BITS) ptbase += 1; // Asserting accessor (the convention for callers that have // already established the shape): upstream reads `fn_info.param_types` as // a slice, so an out-of-range parameter index panics rather than returning // a neighbouring `extra[]` word. Without this, `i >= params_len` reads // past the trailing `param_type: Index` array into whatever follows, and // the several callers that guard the result with `!= IP_INDEX_NONE` would // accept that garbage word as a type. if (i >= ipFuncTypeParamsLen(ip, func_ty)) UNREACHABLE("func type: param index out of range"); return ip->extra[ptbase + i]; } uint32_t ipFuncDeclOwnerNav(const InternPool* ip, uint32_t func_decl) { // Tag.FuncDecl payload: { analysis, owner_nav, ... }. if (!ipItemTagIs(ip, func_decl, IP_TAG_FUNC_DECL)) UNREACHABLE("ipFuncDeclOwnerNav: not a func_decl"); return ip->extra[ip->item_datas[func_decl] + 1]; } uint32_t ipFuncDeclType(const InternPool* ip, uint32_t func_decl) { // Tag.FuncDecl payload: { analysis, owner_nav, ty, ... }. return ip->extra[ip->item_datas[func_decl] + 2]; } // InternPool.zig:getExtern // InternPool.getExtern: the existing item when one has the same key (name, // ty, lib_name, flags, zir_index, and the owner Nav's const / alignment), else // the 7-word Tag.Extern payload with a new owner Nav named `name` (its fqn // too), resolved to the new extern. DIVERGENCE: a bodyless `extern` // declaration (source .syntax) passes its own Nav as `owner_nav`, where // upstream creates a second one. Only function-typed externs are interned. uint32_t ipGetExtern(InternPool* ip, uint32_t name, uint32_t ty, uint32_t lib_name, uint32_t flags, uint32_t zir_index, bool is_const, uint32_t alignment, uint32_t owner_nav) { for (uint32_t i = 0; i < ip->items_len; i++) { if (ip->item_tags[i] != IP_TAG_EXTERN) continue; const uint32_t* w = &ip->extra[ip->item_datas[i]]; const IpNav* nav = ipGetNav(ip, w[3]); if (w[0] == ty && w[1] == lib_name && w[2] == flags && w[4] == zir_index && nav->name == name && nav->is_const == is_const && nav->resolved_alignment == alignment) return i; } uint32_t extern_index = ip->items_len; if (owner_nav == IP_INDEX_NONE) { owner_nav = ipCreateDeclNav(ip, name, name, zir_index, 0, IP_NAMESPACE_NONE, is_const, false, true); IpNav* nav = &ip->navs[owner_nav]; nav->has_analysis = false; nav->has_resolved = true; nav->has_value_resolved = true; nav->modifiers_resolved = true; nav->resolved_alignment = (uint8_t)alignment; nav->resolved_type = ty; nav->resolved_value = extern_index; } // location_or_descriptor_set / descriptor_binding: upstream leaves both // undefined without a decoration; zero is written, as its `simple_type` // arm does. const uint32_t words[7] = { ty, lib_name, flags, owner_nav, zir_index, 0, 0 }; return ipAddItem(ip, IP_TAG_EXTERN, ipAddExtra(ip, words, 7)); } uint32_t ipExternFunctionOwnerNav(const InternPool* ip, uint32_t extern_fn) { if (extern_fn >= ip->items_len || ip->item_tags[extern_fn] != IP_TAG_EXTERN) { UNREACHABLE("extern function owner requested for non-extern"); } return ip->extra[ip->item_datas[extern_fn] + 3]; // Tag.Extern.owner_nav } uint32_t ipExternFunctionLibraryName( const InternPool* ip, uint32_t extern_fn) { if (extern_fn >= ip->items_len || ip->item_tags[extern_fn] != IP_TAG_EXTERN) { UNREACHABLE("extern function library requested for non-extern"); } return ip->extra[ip->item_datas[extern_fn] + 1]; // Tag.Extern.lib_name } static bool ipFuncInstanceMatches(InternPool* ip, uint32_t idx, uint32_t generic_owner_fdecl, bool inferred_error_set, uint32_t return_key, uint32_t expected_inst_flags, uint32_t noalias_bits, const uint32_t* runtime_param_types, uint32_t runtime_params_len, const uint32_t* comptime_args, uint32_t comptime_args_len) { if (idx >= ip->items_len || ip->item_tags[idx] != IP_TAG_FUNC_INSTANCE) return false; if (ipFuncInstanceGenericOwner(ip, idx) != generic_owner_fdecl) return false; for (uint32_t i = 0; i < comptime_args_len; i++) if (ipFuncInstanceComptimeArg(ip, idx, i) != comptime_args[i]) return false; uint32_t prev_ty = ipFuncInstanceType(ip, idx); if (prev_ty >= ip->items_len || ip->item_tags[prev_ty] != IP_TAG_TYPE_FUNCTION) return false; if (ipFuncTypeParamsLen(ip, prev_ty) != runtime_params_len) return false; if (ipFuncTypeFlags(ip, prev_ty) != expected_inst_flags) return false; if (ipFuncTypeNoaliasBits(ip, prev_ty) != noalias_bits) return false; for (uint32_t i = 0; i < runtime_params_len; i++) if (ipFuncTypeParamType(ip, prev_ty, i) != runtime_param_types[i]) return false; uint32_t prev_ret = ipFuncTypeReturnType(ip, prev_ty); if (inferred_error_set) { if (prev_ret >= ip->items_len || ip->item_tags[prev_ret] != IP_TAG_TYPE_ERROR_UNION) return false; uint32_t prev_eu_off = ip->item_datas[prev_ret]; uint32_t prev_err = ip->extra[prev_eu_off]; return ip->extra[prev_eu_off + 1] == return_key && prev_err < ip->items_len && ip->item_tags[prev_err] == IP_TAG_TYPE_INFERRED_ERROR_SET && ip->item_datas[prev_err] == idx; } return prev_ret == return_key; } // Hash the complete lookup key used by ipGetFuncInstance. The verifier below // remains the authority on equality; this index only selects candidates in // average constant time. static uint32_t ipDedupHashFuncInstanceKey(uint32_t generic_owner_fdecl, bool inferred_error_set, uint32_t return_key, uint32_t expected_inst_flags, uint32_t noalias_bits, const uint32_t* runtime_param_types, uint32_t runtime_params_len, const uint32_t* comptime_args, uint32_t comptime_args_len) { Wyhash h; wyhashInit(&h, (uint64_t)IP_TAG_FUNC_INSTANCE); ipDedupHashMixWord(&h, generic_owner_fdecl); ipDedupHashMixWord(&h, inferred_error_set ? 1u : 0u); ipDedupHashMixWord(&h, return_key); ipDedupHashMixWord(&h, expected_inst_flags); ipDedupHashMixWord(&h, noalias_bits); ipDedupHashMixWord(&h, runtime_params_len); for (uint32_t i = 0; i < runtime_params_len; i++) ipDedupHashMixWord(&h, runtime_param_types[i]); ipDedupHashMixWord(&h, comptime_args_len); for (uint32_t i = 0; i < comptime_args_len; i++) ipDedupHashMixWord(&h, comptime_args[i]); return (uint32_t)wyhashFinal(&h); } static uint32_t ipFuncInstanceDedupFind(InternPool* ip, uint32_t hash, uint32_t generic_owner_fdecl, bool inferred_error_set, uint32_t return_key, uint32_t expected_inst_flags, uint32_t noalias_bits, const uint32_t* runtime_param_types, uint32_t runtime_params_len, const uint32_t* comptime_args, uint32_t comptime_args_len) { if (ip->func_instance_dedup_cap == 0) return IP_INDEX_NONE; uint32_t mask = ip->func_instance_dedup_cap - 1; uint32_t slot = hash & mask; for (;;) { uint32_t idx = ip->func_instance_dedup_slots[slot].item_idx; if (idx == IP_INDEX_NONE) return IP_INDEX_NONE; if (ip->func_instance_dedup_slots[slot].hash == hash && ipFuncInstanceMatches(ip, idx, generic_owner_fdecl, inferred_error_set, return_key, expected_inst_flags, noalias_bits, runtime_param_types, runtime_params_len, comptime_args, comptime_args_len)) return idx; slot = (slot + 1) & mask; } } static void ipFuncInstanceDedupInsert( InternPool* ip, uint32_t item_idx, uint32_t hash) { ipDedupEnsureCapacity(&ip->func_instance_dedup_slots, &ip->func_instance_dedup_cap, &ip->func_instance_dedup_count); ipDedupPlace(ip->func_instance_dedup_slots, ip->func_instance_dedup_cap, hash, item_idx, 0); ip->func_instance_dedup_count++; } // Port of Namespace.internFullyQualifiedName (Zcu.zig): fqn = // "." (just the owner name when `name` is empty, // just `name` when the owner has no interned name -- an anonymous inline // container, or a generic instance's owner whose namespace does not exist // yet). Copies the source strings // into local buffers first so the string-arena realloc inside the final // intern cannot dangle them. Shared by scanNamespace's decl/test Navs // (zcu_per_thread.c) and finishFuncInstance (ipGetFuncInstance below). uint32_t ipInternNamespacedName( InternPool* ip, uint32_t ns_idx, uint32_t name) { uint32_t ns_name = ns_idx >= ip->namespaces_len ? IP_INDEX_NONE : ipContainerTypeFqn(ip, ipGetNamespace(ip, ns_idx)->owner_type); if (ns_name == IP_INDEX_NONE) return name; if (name == 0) return ns_name; // name == .empty const char* ns_bytes = ipStringBytes(ip, ns_name); const char* name_bytes = ipStringBytes(ip, name); size_t ns_len = strlen(ns_bytes); size_t name_len = strlen(name_bytes); if (ns_len > UINT32_MAX || name_len > UINT32_MAX || ns_len + 1 > UINT32_MAX - name_len) UNIMPLEMENTED("InternPool.getOrPutString: fully-qualified declaration " "name exceeds u32"); size_t fqn_len = ns_len + 1 + name_len; char* fqn = ARR_INIT(char, fqn_len + 1); memcpy(fqn, ns_bytes, ns_len); fqn[ns_len] = '.'; memcpy(fqn + ns_len + 1, name_bytes, name_len + 1); uint32_t result = ipGetOrPutString(ip, fqn, (uint32_t)fqn_len); free(fqn); return result; } // InternPool.zig:getFuncInstance, Sema.zig:analyzeCall // Intern a monomorphized function instance (mirrors // InternPool.getFuncInstance, InternPool.zig). Interns the instance's // `type_function` (runtime params only, generic owner's cc, no comptime_bits) // then a `func_instance` item { analysis, owner_nav, ty, branch_quota, // generic_owner } trailed by `comptime_args` (one Index per generic-owner // parameter: the interned comptime value, or IP_INDEX_NONE for a runtime // parameter). For generic inferred-error functions, the instance gets its own // error-union / inferred-error-set types and resolved-set slot. Also creates // the instance's Nav named // "__func_" (finishFuncInstance) so its AIR carries that name. // `comptime_args_len` must equal the generic owner's parameter count. // `noalias_bits` are the instance-signature bits, i.e. Remapped to runtime // argument positions by the caller (Sema.analyzeCall sets bit `runtime_idx` // for each noalias runtime arg, Sema.zig). uint32_t ipGetFuncInstance(InternPool* ip, uint32_t generic_owner_fdecl, uint32_t bare_return_type, const uint32_t* runtime_param_types, uint32_t runtime_params_len, const uint32_t* comptime_args, uint32_t comptime_args_len, uint32_t noalias_bits, uint32_t* anon_name_counter) { uint32_t owner_ty = ipFuncDeclType(ip, generic_owner_fdecl); // Instance func_type: generic owner's cc/is_noinline, but strip the // has_comptime_bits / has_noalias_bits flags (the instance signature has // no comptime params; its own noalias word is re-added from the remapped // `noalias_bits` by ipGetFuncType). uint32_t inst_flags = ipFuncTypeFlags(ip, owner_ty) & ~(IP_FUNC_FLAGS_HAS_COMPTIME_BITS | IP_FUNC_FLAGS_HAS_NOALIAS_BITS); bool inferred_error_set = false; uint32_t bare_payload_type = IP_INDEX_NONE; if (bare_return_type < ip->items_len && ip->item_tags[bare_return_type] == IP_TAG_TYPE_ERROR_UNION) { uint32_t eu_off = ip->item_datas[bare_return_type]; uint32_t err_set = ip->extra[eu_off]; if (err_set < ip->items_len && ip->item_tags[err_set] == IP_TAG_TYPE_INFERRED_ERROR_SET && ip->item_datas[err_set] == generic_owner_fdecl) { inferred_error_set = true; bare_payload_type = ip->extra[eu_off + 1]; } } uint32_t owner_params_len = ipFuncTypeParamsLen(ip, owner_ty); if (comptime_args_len != owner_params_len) { UNREACHABLE("func_instance comptime args length mismatch"); } uint32_t expected_inst_flags = inst_flags; if (noalias_bits != 0) expected_inst_flags |= IP_FUNC_FLAGS_HAS_NOALIAS_BITS; uint32_t return_key = inferred_error_set ? bare_payload_type : bare_return_type; uint32_t instance_hash = ipDedupHashFuncInstanceKey(generic_owner_fdecl, inferred_error_set, return_key, expected_inst_flags, noalias_bits, runtime_param_types, runtime_params_len, comptime_args, comptime_args_len); uint32_t found = ipFuncInstanceDedupFind(ip, instance_hash, generic_owner_fdecl, inferred_error_set, return_key, expected_inst_flags, noalias_bits, runtime_param_types, runtime_params_len, comptime_args, comptime_args_len); if (found != IP_INDEX_NONE) return found; if (inferred_error_set) { uint32_t inst_idx = ip->items_len; uint32_t error_union_type = inst_idx + 1; uint32_t error_set_type = inst_idx + 2; uint32_t func_ty = inst_idx + 3; uint32_t analysis = IP_FUNC_ANALYSIS_INFERRED_ERROR_SET | ((ipFuncTypeFlags(ip, owner_ty) & IP_FUNC_FLAGS_IS_NOINLINE) ? IP_FUNC_ANALYSIS_IS_NOINLINE : 0u); inst_flags = expected_inst_flags; uint32_t off_fi = ip->extra_len; const uint32_t fi_words[5] = { analysis, 0, // owner_nav — patched after the Nav is created func_ty, 0, // branch_quota generic_owner_fdecl, }; ipAddExtra(ip, fi_words, 5); uint32_t resolved_none = IP_INDEX_NONE; ipAddExtra(ip, &resolved_none, 1); for (uint32_t i = 0; i < comptime_args_len; i++) ipAddExtra(ip, &comptime_args[i], 1); uint32_t off_ft = ip->extra_len; const uint32_t ft_hdr[3] = { runtime_params_len, error_union_type, inst_flags }; ipAddExtra(ip, ft_hdr, 3); if (noalias_bits != 0) ipAddExtra(ip, &noalias_bits, 1); for (uint32_t i = 0; i < runtime_params_len; i++) ipAddExtra(ip, &runtime_param_types[i], 1); const uint32_t eu_words[2] = { error_set_type, bare_payload_type }; uint32_t off_eu = ipAddExtra(ip, eu_words, 2); ipAddItem(ip, IP_TAG_FUNC_INSTANCE, off_fi); ipAddItem(ip, IP_TAG_TYPE_ERROR_UNION, off_eu); ipAddItem(ip, IP_TAG_TYPE_INFERRED_ERROR_SET, inst_idx); ipAddItem(ip, IP_TAG_TYPE_FUNCTION, off_ft); // Same out-of-getter index insert as ipGetFuncDeclIes, and safe for // the identical reason: this key's `return_type` word is // `error_union_type` (inst_idx + 1), just created above via a bare, // non-deduped ipAddItem, so the key is provably new. Word layout here // has the optional-noalias branch instead of optional-comptime (an // instance signature never carries comptime params -- inst_flags // above has has_comptime_bits stripped), still matching the // getter's header/optional-word/params order. uint32_t ft_n = 3 + (noalias_bits != 0 ? 1u : 0u) + runtime_params_len; // Assert the absence proof (see ipGetFuncDeclIes). if (ipDedupFind( ip, IP_TAG_TYPE_FUNCTION, &ip->extra[off_ft], ft_n, false) != IP_INDEX_NONE) { UNREACHABLE("ipGetFuncInstance: type_function key unexpectedly " "already interned"); } ipDedupInsert( ip, IP_TAG_TYPE_FUNCTION, func_ty, &ip->extra[off_ft], ft_n); if (ipDedupFind( ip, IP_TAG_TYPE_ERROR_UNION, &ip->extra[off_eu], 2, false) != IP_INDEX_NONE) { UNREACHABLE("ipGetFuncInstance: type_error_union key " "unexpectedly already interned"); } ipDedupInsert(ip, IP_TAG_TYPE_ERROR_UNION, error_union_type, &ip->extra[off_eu], 2); uint32_t owner_nav_idx = ipFuncDeclOwnerNav(ip, generic_owner_fdecl); const IpNav* owner_nav = ipGetNav(ip, owner_nav_idx); const char* owner_name = ipStringBytes(ip, owner_nav->name); uint32_t name_str = ipGetOrPutStringFmt( ip, "%s__func_%u", owner_name, (*anon_name_counter)++); uint32_t nav_idx = ipCreateDeclNav(ip, name_str, ipInternNamespacedName( ip, owner_nav->analysis_namespace, name_str), owner_nav->analysis_zir_index, owner_nav->analysis_inst, owner_nav->analysis_namespace, true, false, false); ip->extra[off_fi + 1] = nav_idx; IpNav* instance_nav = &ip->navs[nav_idx]; instance_nav->has_resolved = true; instance_nav->has_value_resolved = true; instance_nav->modifiers_resolved = true; instance_nav->resolved_alignment = IP_ALIGNMENT_NONE; instance_nav->resolved_type = func_ty; instance_nav->resolved_value = inst_idx; ipFuncInstanceDedupInsert(ip, inst_idx, instance_hash); return inst_idx; } uint32_t func_ty = ipGetFuncType(ip, bare_return_type, runtime_param_types, runtime_params_len, inst_flags, 0, noalias_bits); // The instance's FuncAnalysis carries is_noinline (bit 4) when the // generic owner is `noinline` (stock getFuncInstance: analysis.is_noinline // = arg.is_noinline = the owner func type's flag, here bit 21). uint32_t analysis = (ipFuncTypeFlags(ip, owner_ty) & IP_FUNC_FLAGS_IS_NOINLINE) ? IP_FUNC_ANALYSIS_IS_NOINLINE : 0u; // Predict the instance's own IP index (used to name its Nav) — it is the // next item appended after the func_instance extra payload. uint32_t off = ip->extra_len; const uint32_t words[5] = { analysis, // analysis (FuncAnalysis) 0, // owner_nav — patched after the Nav is created func_ty, // ty (Index) = instance type_function 0, // branch_quota generic_owner_fdecl, // generic_owner (Index) -> FuncDecl }; ipAddExtra(ip, words, 5); for (uint32_t i = 0; i < comptime_args_len; i++) ipAddExtra(ip, &comptime_args[i], 1); uint32_t inst_idx = ipAddItem(ip, IP_TAG_FUNC_INSTANCE, off); // finishFuncInstance: create the instance's Nav named // "__func_" (`zcu.anon_name_counter`, then incremented), // reusing the generic owner's namespace + ZIR declaration so the body can // be re-analyzed. uint32_t owner_nav_idx = ipFuncDeclOwnerNav(ip, generic_owner_fdecl); const IpNav* owner_nav = ipGetNav(ip, owner_nav_idx); const char* owner_name = ipStringBytes(ip, owner_nav->name); uint32_t name_str = ipGetOrPutStringFmt( ip, "%s__func_%u", owner_name, (*anon_name_counter)++); uint32_t nav_idx = ipCreateDeclNav(ip, name_str, ipInternNamespacedName(ip, owner_nav->analysis_namespace, name_str), owner_nav->analysis_zir_index, owner_nav->analysis_inst, owner_nav->analysis_namespace, true, false, false); ip->extra[off + 1] = nav_idx; // patch owner_nav // Synthetic instance Navs do not run declaration analysis themselves; // their exact value/type are the func_instance just interned. Publish the // complete cache now so a function pointer to the instance cannot look // like an unresolved data Nav. IpNav* instance_nav = &ip->navs[nav_idx]; instance_nav->has_resolved = true; instance_nav->has_value_resolved = true; instance_nav->modifiers_resolved = true; instance_nav->resolved_alignment = IP_ALIGNMENT_NONE; instance_nav->resolved_type = func_ty; instance_nav->resolved_value = inst_idx; ipFuncInstanceDedupInsert(ip, inst_idx, instance_hash); return inst_idx; } uint32_t ipFuncInstanceGenericOwner(const InternPool* ip, uint32_t inst) { // Tag.FuncInstance payload: { analysis, owner_nav, ty, branch_quota, // generic_owner }. return ip->extra[ip->item_datas[inst] + 4]; } uint32_t ipFuncInstanceOwnerNav(const InternPool* ip, uint32_t inst) { return ip->extra[ip->item_datas[inst] + 1]; } uint32_t ipFuncValueOwnerNav(const InternPool* ip, uint32_t val) { if (val >= ip->items_len) return IP_INDEX_NONE; // indexToKey's `.func_coerced` arm is extraFuncCoerced: the original // function's `Key.Func` with only `ty` replaced, so a coerced function's // owner_nav is the original's, not none. val = ipUnwrapCoercedFunc(ip, val); if (ip->item_tags[val] == IP_TAG_FUNC_DECL) return ipFuncDeclOwnerNav(ip, val); if (ip->item_tags[val] == IP_TAG_FUNC_INSTANCE) return ipFuncInstanceOwnerNav(ip, val); // analyzeNavRefInner redirects on two value kinds, not one: // `".func => |f| break :nav f.owner_nav,"` and // `".@\"extern\" => |e| break :nav e.owner_nav,"`. if (ip->item_tags[val] == IP_TAG_EXTERN) return ipExternFunctionOwnerNav(ip, val); return IP_INDEX_NONE; } uint32_t ipFuncInstanceComptimeArg( const InternPool* ip, uint32_t inst, uint32_t i) { // Trailing comptime_args follow the 5-word FuncInstance header, except an // inferred-error-set instance has one resolved-error-set word first. uint32_t off = ip->item_datas[inst]; uint32_t args_off = off + 5; if (ip->extra[off] & IP_FUNC_ANALYSIS_INFERRED_ERROR_SET) args_off += 1; return ip->extra[args_off + i]; } uint32_t ipFuncInstanceType(const InternPool* ip, uint32_t inst) { // Tag.FuncInstance payload: { analysis, owner_nav, ty, branch_quota, // generic_owner }. return ip->extra[ip->item_datas[inst] + 2]; } uint32_t ipFuncInstanceBranchQuota(const InternPool* ip, uint32_t inst) { return ip->extra[ip->item_datas[inst] + 3]; } void ipFuncInstanceMaxBranchQuota(InternPool* ip, uint32_t inst, uint32_t q) { uint32_t* quota = &ip->extra[ip->item_datas[inst] + 3]; if (q > *quota) *quota = q; } uint32_t ipGetFuncCoerced(InternPool* ip, uint32_t func, uint32_t ty) { if (func >= ip->items_len || ty >= ip->items_len || ip->item_tags[ty] != IP_TAG_TYPE_FUNCTION) return IP_INDEX_NONE; if (ip->item_tags[func] == IP_TAG_FUNC_DECL && ipFuncDeclType(ip, func) == ty) return func; if (ip->item_tags[func] == IP_TAG_FUNC_INSTANCE && ipFuncInstanceType(ip, func) == ty) return func; if (ip->item_tags[func] != IP_TAG_FUNC_DECL && ip->item_tags[func] != IP_TAG_FUNC_INSTANCE) return IP_INDEX_NONE; const uint32_t words[2] = { ty, func }; uint32_t found = ipDedupFind(ip, IP_TAG_FUNC_COERCED, words, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_FUNC_COERCED, off); ipDedupInsert(ip, IP_TAG_FUNC_COERCED, idx, &ip->extra[off], 2); return idx; } // InternPool.zig:unwrapCoercedFunc // InternPool.unwrapCoercedFunc: a `func_coerced` item stands for the original // function value carried at its `"func"` field (`".func_coerced => ...items( // .\"0\")[item.data + fieldIndex(Tag.FuncCoerced, \"func\")]"`); // `func_instance`/`func_decl` are already unwrapped and pass through. uint32_t ipUnwrapCoercedFunc(const InternPool* ip, uint32_t func) { if (func < ip->items_len && ip->item_tags[func] == IP_TAG_FUNC_COERCED) { uint32_t d = ip->item_datas[func]; if (d <= ip->extra_len && ip->extra_len - d >= 2) return ip->extra[d + 1]; return IP_INDEX_NONE; } return func; } uint32_t ipFuncIesResolvedSlot(const InternPool* ip, uint32_t func_index) { if (func_index >= ip->items_len) return UINT32_MAX; if (ip->item_tags[func_index] == IP_TAG_FUNC_DECL) { uint32_t off = ip->item_datas[func_index]; if ((ip->extra[off] & IP_FUNC_ANALYSIS_INFERRED_ERROR_SET) == 0) return UINT32_MAX; return off + 8; } if (ip->item_tags[func_index] == IP_TAG_FUNC_INSTANCE) { uint32_t off = ip->item_datas[func_index]; if ((ip->extra[off] & IP_FUNC_ANALYSIS_INFERRED_ERROR_SET) == 0) return UINT32_MAX; return off + 5; } return UINT32_MAX; } // An inferred error set's resolved error set (InternPool.funcIesResolved), // IP_INDEX_NONE while unresolved; any other type is itself. uint32_t ipResolvedInferredErrorSet(const InternPool* ip, uint32_t error_set) { if (error_set >= ip->items_len || ip->item_tags[error_set] != IP_TAG_TYPE_INFERRED_ERROR_SET) return error_set; uint32_t func_index = ip->item_datas[error_set]; uint32_t slot = ipFuncIesResolvedSlot(ip, func_index); if (slot == UINT32_MAX || slot >= ip->extra_len) return IP_INDEX_NONE; return ip->extra[slot]; } uint32_t ipGetMemoizedCall(InternPool* ip, uint32_t func, const uint32_t* arg_values, uint32_t args_len, uint32_t result, uint32_t branch_count, uint32_t branch_quota) { // MemoizedCall: { func, args_len, result, branch_count, branch_quota }, // trailing `arg_values: Index` for each `args_len`. See the // `.memoized_call` case of InternPool.get. // // Always appends a fresh item; both callers call it only after an // `ipLookupMemoizedCall` miss (see the comment above that function). The // `memo_dedup_*` index insert below, in contrast, is find-before-insert // (it re-checks `ipLookupMemoizedCall` itself immediately before // recording), so the index stays first-match-wins regardless of which // caller path -- or how many duplicate-keyed appends -- reach here: a // duplicate-keyed item is appended to the pool but never indexed, so // `ipLookupMemoizedCall` always resolves to the earliest item with a // given key. uint32_t off = ip->extra_len; const uint32_t hdr[IP_MEMOIZED_CALL_LEN] = { [IP_MEMOIZED_CALL_FUNC] = func, [IP_MEMOIZED_CALL_ARGS_LEN] = args_len, [IP_MEMOIZED_CALL_RESULT] = result, [IP_MEMOIZED_CALL_BRANCH_COUNT] = branch_count, [IP_MEMOIZED_CALL_BRANCH_QUOTA] = branch_quota, }; ipAddExtra(ip, hdr, IP_MEMOIZED_CALL_LEN); for (uint32_t i = 0; i < args_len; i++) ipAddExtra(ip, &arg_values[i], 1); uint32_t idx = ipAddItem(ip, IP_TAG_MEMOIZED_CALL, off); if (ipLookupMemoizedCall(ip, func, arg_values, args_len) == IP_INDEX_NONE) ipMemoizedCallDedupInsert(ip, idx, func, args_len, arg_values); return idx; } uint32_t ipGetIntType(InternPool* ip, bool is_signed, uint32_t bits) { // Int types store the bit width directly in the item `data` (no `extra` // payload). `InternPool.get` for `.int_type` deduplicates: scan existing // int-type items of the matching signedness for the same bit width. IpTag tag = is_signed ? IP_TAG_TYPE_INT_SIGNED : IP_TAG_TYPE_INT_UNSIGNED; uint32_t found = ipDedupFind(ip, tag, &bits, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, tag, bits); ipDedupInsert(ip, tag, idx, &bits, 1); return idx; } uint32_t ipGetEnumTypeAuto(InternPool* ip, uint32_t zir_index, uint32_t name, uint32_t fqn, uint32_t name_nav, uint32_t namespace_idx, uint32_t fields_len, const uint32_t* field_names) { // getOrPutKey dedup not applied here -- see the `decl_dedup_*` table // comment: the same generic-instantiation hazard found for // ipGetDeclaredStructType applies to the enum family (sema_comptime.c's // @Type(.enum=...) reification path explicitly documents relying on the // memoized_call layer above it, not a zir_index-keyed dedup at this // getter, for repeat-instantiation correctness). Each call appends a // fresh item. addMap for the field_name_map (separate arena, not // dumped). uint32_t field_name_map = ipAddMap(ip); // Tag.TypeEnum header. `bits` packs captures_len(u31)=0 | // want_layout(bool)=false = 0. `int_tag_type` starts `.none` and is // backpatched by ipSetEnumIntTagType once the tag type is interned. uint32_t off = ip->extra_len; const uint32_t hdr[IP_TYPE_ENUM_LEN] = { [IP_TYPE_ENUM_BITS] = 0, // captures_len=0, want_layout=false [IP_TYPE_ENUM_NAME] = name, [IP_TYPE_ENUM_FQN] = fqn, [IP_TYPE_ENUM_NAME_NAV] = name_nav, [IP_TYPE_ENUM_NAMESPACE] = namespace_idx, [IP_TYPE_ENUM_INT_TAG_TYPE] = IP_INDEX_NONE, // backpatched [IP_TYPE_ENUM_FIELDS_LEN] = fields_len, [IP_TYPE_ENUM_FIELD_NAME_MAP] = field_name_map, }; ipAddExtra(ip, hdr, IP_TYPE_ENUM_LEN); ipAddExtra(ip, &zir_index, 1); // zir_index (TrackedInst.Index) // No captures (captures_len==0); no field_value_map (auto tag mode). for (uint32_t i = 0; i < fields_len; i++) ipAddExtra(ip, &field_names[i], 1); // field_name per field // No field_value trailing array for `.type_enum_auto`. return ipAddItem(ip, IP_TAG_TYPE_ENUM_AUTO, off); } uint32_t ipGetEnumTypeExplicit(InternPool* ip, uint32_t zir_index, uint32_t name, uint32_t fqn, uint32_t name_nav, uint32_t namespace_idx, uint32_t int_tag_ty, uint32_t fields_len, const uint32_t* field_names, const uint32_t* field_values, bool nonexhaustive) { // Mirrors InternPool.getDeclaredEnumType for the explicit/nonexhaustive // cases (no captures/decls). Two maps: the field_name_map (as auto) plus a // field_value_map (explicit only). Both are separate arenas, never dumped. // // getOrPutKey dedup not applied here -- see ipGetEnumTypeAuto / // ipGetDeclaredStructType for why (generic-instantiation / // reification hazard this getter can't see). IpTag tag = nonexhaustive ? IP_TAG_TYPE_ENUM_NONEXHAUSTIVE : IP_TAG_TYPE_ENUM_EXPLICIT; uint32_t field_name_map = ipAddMap(ip); uint32_t field_value_map = ipAddMap(ip); // Tag.TypeEnum header. Unlike the auto case, `int_tag_type` is the // user-specified tag type (known up front), not a `.none` forward ref. uint32_t off = ip->extra_len; const uint32_t hdr[IP_TYPE_ENUM_LEN] = { [IP_TYPE_ENUM_BITS] = 0, // captures_len=0, want_layout=false [IP_TYPE_ENUM_NAME] = name, [IP_TYPE_ENUM_FQN] = fqn, [IP_TYPE_ENUM_NAME_NAV] = name_nav, [IP_TYPE_ENUM_NAMESPACE] = namespace_idx, [IP_TYPE_ENUM_INT_TAG_TYPE] = int_tag_ty, [IP_TYPE_ENUM_FIELDS_LEN] = fields_len, [IP_TYPE_ENUM_FIELD_NAME_MAP] = field_name_map, }; ipAddExtra(ip, hdr, IP_TYPE_ENUM_LEN); ipAddExtra(ip, &zir_index, 1); // zir_index (TrackedInst.Index) ipAddExtra(ip, &field_value_map, 1); // field_value_map (MapIndex) for (uint32_t i = 0; i < fields_len; i++) ipAddExtra(ip, &field_names[i], 1); // field_name per field for (uint32_t i = 0; i < fields_len; i++) ipAddExtra( ip, &field_values[i], 1); // field_value (interned int) per field return ipAddItem(ip, tag, off); } // InternPool.zig:isEnumType bool ipIsEnumType(const InternPool* ip, uint32_t ty) { uint8_t t = ip->item_tags[ty]; return t == IP_TAG_TYPE_ENUM_AUTO || t == IP_TAG_TYPE_ENUM_EXPLICIT || t == IP_TAG_TYPE_ENUM_NONEXHAUSTIVE; } // Where an enum type's `field_name[]` array begins: after the header and its // `zir_index` (or a generated tag's `owner_union`) word, and for // explicit/nonexhaustive one `field_value_map` word. static uint32_t ipEnumFieldNamesOff(const InternPool* ip, uint32_t enum_ty) { return ip->item_datas[enum_ty] + IP_TYPE_ENUM_LEN + 1u + (ip->item_tags[enum_ty] == IP_TAG_TYPE_ENUM_AUTO ? 0u : 1u); } void ipSetEnumIntTagType( InternPool* ip, uint32_t enum_idx, uint32_t int_tag_type) { uint32_t off = ip->item_datas[enum_idx]; ip->extra[off + IP_TYPE_ENUM_INT_TAG_TYPE] = int_tag_type; } // An explicit/nonexhaustive enum's `field_value[i]`, written when its layout // resolves (the type is interned with `.none` values). void ipSetEnumFieldValue( InternPool* ip, uint32_t enum_ty, uint32_t i, uint32_t value) { ip->extra[ipEnumFieldNamesOff(ip, enum_ty) + ipEnumFieldsLen(ip, enum_ty) + i] = value; } void ipSetEnumNamespace( InternPool* ip, uint32_t enum_idx, uint32_t namespace_idx) { // Backpatched for an enum with member decls: the namespace is created // after the type is interned (ipCreateNamespace needs owner_type), same // pattern as ipSetStructNamespace. uint32_t off = ip->item_datas[enum_idx]; ip->extra[off + IP_TYPE_ENUM_NAMESPACE] = namespace_idx; } uint32_t ipEnumNamespaceOrNone(const InternPool* ip, uint32_t enum_ty) { // Like ipStructNamespaceOrNone/ipUnionNamespaceOrNone: the namespace // header word initializes to 0 before a real namespace is backpatched, // but namespace 0 is also valid. It belongs to this enum only when the // namespace owner back-references this enum; otherwise the enum has no // namespace. uint32_t off = ip->item_datas[enum_ty]; return ipNamespaceOrNone( ip, ip->extra[off + IP_TYPE_ENUM_NAMESPACE], enum_ty); } uint32_t ipGetOpaqueType(InternPool* ip, uint32_t zir_index, uint32_t name, uint32_t fqn, uint32_t name_nav, uint32_t namespace_idx) { // Mirrors InternPool.getDeclaredOpaqueType's ipAddExtra(Tag.TypeOpaque). // captures_len is 0 — a capturing opaque decl aborts loudly at the decl // site — so no trailing CaptureValue words follow. const uint32_t words[IP_TYPE_OPAQUE_LEN] = { [IP_TYPE_OPAQUE_ZIR_INDEX] = zir_index, [IP_TYPE_OPAQUE_CAPTURES_LEN] = 0, [IP_TYPE_OPAQUE_NAME] = name, [IP_TYPE_OPAQUE_FQN] = fqn, [IP_TYPE_OPAQUE_NAME_NAV] = name_nav, [IP_TYPE_OPAQUE_NAMESPACE] = namespace_idx, }; uint32_t off = ipAddExtra(ip, words, IP_TYPE_OPAQUE_LEN); return ipAddItem(ip, IP_TAG_TYPE_OPAQUE, off); } // Tag.TypeOpaque's `namespace` word, backpatched once the namespace exists // (it is created after the type, its owner). void ipSetOpaqueNamespace( InternPool* ip, uint32_t opaque_idx, uint32_t namespace_idx) { ip->extra[ip->item_datas[opaque_idx] + IP_TYPE_OPAQUE_NAMESPACE] = namespace_idx; } uint32_t ipOpaqueNamespaceOrNone(const InternPool* ip, uint32_t opaque_idx) { return ipNamespaceOrNone(ip, ip->extra[ip->item_datas[opaque_idx] + IP_TYPE_OPAQUE_NAMESPACE], opaque_idx); } static uint32_t ipGetIntI32(InternPool* ip, uint32_t value); uint32_t ipGetIntSmall(InternPool* ip, uint32_t ty, uint32_t value) { // InternPool.get's `.int` case (InternPool.zig) picks the storage // tag from `int.ty` alone: `.u8_type` -> `.int_u8`, `.u16_type` -> // `.int_u16`, `.u32_type` -> `.int_u32`, `.i32_type` -> `.int_i32`, // `.usize_type` -> `.int_usize` (when it fits a u32, which a u32 `value` // always does), `.comptime_int_type` -> `.int_comptime_int_u32`, and only // `else` falls through to `.int_small`. One (ty, value) therefore has // exactly one representation upstream, which is what makes `.int` keys // dedup: upstream's hash and eql canonicalize to the BigInt value, while // zig1 deduplicates per item tag. Without this dispatch the same tag // value reached through two callers (`@enumFromInt` interning at the // enum's int tag type vs. `enumValueFieldIndex` interning the field's // index) produced two distinct int items, hence two distinct `enum_tag` // items, and comptime `==` on them answered false. Upstream's `get` // consults its key map first, so `.int` keys that are static preinterns // (`@as(u1, 0/1)`, `@as(usize, 0/1)`, `@as(u8, 0/1/4)`, `comptime_int` // 0/1/-1) answer with that preinterned Index; here `ipInitStatic` writes // those as real items and `ipDedupSeedStatic` seeds them, so the dedup // below (and each per-tag getter's) returns them for free -- including // `int_small{u1, 0/1}` -> `.zero_u1`/`.one_u1`. switch (ty) { case ZIR_REF_U8_TYPE: return ipGetIntU8(ip, value); case ZIR_REF_U16_TYPE: return ipGetIntU16(ip, value); case ZIR_REF_U32_TYPE: return ipGetIntU32(ip, value); case ZIR_REF_I32_TYPE: return ipGetIntI32(ip, value); case ZIR_REF_USIZE_TYPE: return ipGetIntUsize(ip, value); case ZIR_REF_COMPTIME_INT_TYPE: return ipGetIntComptimeIntU32(ip, value); default: break; } // Dedup by scanning existing int_small items (mirrors InternPool.get's // hash-map lookup for the `.int_small` path, InternPool.zig). const uint32_t words[2] = { ty, value }; uint32_t found = ipDedupFind(ip, IP_TAG_INT_SMALL, words, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_INT_SMALL, off); ipDedupInsert(ip, IP_TAG_INT_SMALL, idx, words, 2); return idx; } uint32_t ipGetEnumTag(InternPool* ip, uint32_t ty, uint32_t int_val) { // Dedup by scanning existing enum_tag items (mirrors InternPool.get's // interning of `.enum_tag`): the same {ty, int} reuses one entry, so an // enum tag interned for a call argument and for a later comparison of the // same field share one IP index. // Tag.EnumTag { ty, int } (2 words). // `"assert(ip.typeOf(enum_tag.int) == enum_obj.int_tag_type);"` ipAssertTypeOf(ip, int_val, ipEnumTypeIntTagType(ip, ty), "InternPool.get: enum_tag int not of its int tag type"); const uint32_t words[2] = { ty, int_val }; uint32_t found = ipDedupFind(ip, IP_TAG_ENUM_TAG, words, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_ENUM_TAG, off); ipDedupInsert(ip, IP_TAG_ENUM_TAG, idx, words, 2); return idx; } uint32_t ipEnumTagFromFieldName( InternPool* ip, uint32_t enum_ty, uint32_t field_name_str) { // Resolve a `.` of an auto enum to its `enum_tag` value (mirrors // Zcu.PerThread.enumValueFieldIndex for the auto-numbered case): the tag // int is `@as(int_tag_type, field_index)`, then `enum_tag { enum_ty, int // }`. The `type_enum_auto` header is followed by zir_index, then // field_names[fields_len] (see ipGetEnumTypeAuto). // For an explicit/nonexhaustive enum the trailing data (see // ipGetEnumTypeExplicit) adds a field_value_map word and a // field_value[fields_len] array; the tag int is the field's explicit // Value, not its index. For auto the tag int is `@as(int_tag_type, // field_index)`. uint32_t int_tag_ty = ipEnumTypeIntTagType(ip, enum_ty); uint32_t fields_len = ipEnumFieldsLen(ip, enum_ty); uint32_t names_off = ipEnumFieldNamesOff(ip, enum_ty); uint32_t field_idx = ipEnumFieldIndexFromName(ip, enum_ty, field_name_str); if (field_idx == UINT32_MAX) return IP_INDEX_NONE; if (ip->item_tags[enum_ty] == IP_TAG_TYPE_ENUM_AUTO) { uint32_t tag_int = ipGetIntSmall(ip, int_tag_ty, field_idx); return ipGetEnumTag(ip, enum_ty, tag_int); } // Explicit value: field_value[field_idx] follows field_name[fields_len]. uint32_t tag_int = ip->extra[names_off + fields_len + field_idx]; return ipGetEnumTag(ip, enum_ty, tag_int); } // Read the exact low 64-bit representation of a fixed-width integer value. // The same typed value may use a specialized carrier (`int_u8`, etc.) or the // generic `int_small`/`int_large` carrier, so enum reverse lookup cannot use // the InternPool item index as value identity. static bool ipFixedIntBits( const InternPool* ip, uint32_t value, uint64_t* out_bits) { if (value >= ip->items_len) return false; uint32_t d = ip->item_datas[value]; switch ((IpTag)ip->item_tags[value]) { case IP_TAG_INT_U8: case IP_TAG_INT_U16: case IP_TAG_INT_U32: case IP_TAG_INT_I32: case IP_TAG_INT_USIZE: *out_bits = d; return true; case IP_TAG_INT_SMALL: if (d > ip->extra_len || ip->extra_len - d < 2u) return false; *out_bits = ip->extra[d + 1]; return true; case IP_TAG_INT_POSITIVE: case IP_TAG_INT_NEGATIVE: // The limb carrier's own low-64 reading, which is the same // two's-complement word the narrower carriers store. return ipTryReadIntLow64Bits(ip, value, out_bits); default: return false; } } // Value.zig:eql // `Value.eql` for two integers of one type. A value the narrow carriers // cannot hold lives only in the canonical limb carrier (`ipAddInt`'s caller // "has already ruled out every narrower carrier"), so the two are equal iff // both are limb-backed with the same sign and magnitude, or both narrow with // the same word. static bool ipIntValueEqlSameType( const InternPool* ip, uint32_t a, uint32_t b) { const uint64_t *al, *bl; uint32_t an, bn; bool ap, bp; bool aw = ipIntBigLimbs(ip, a, NULL, &al, &an, &ap); bool bw = ipIntBigLimbs(ip, b, NULL, &bl, &bn, &bp); if (aw || bw) return aw && bw && ap == bp && an == bn && memcmp(al, bl, (size_t)an * sizeof(uint64_t)) == 0; uint64_t x, y; return ipFixedIntBits(ip, a, &x) && ipFixedIntBits(ip, b, &y) && x == y; } uint32_t ipEnumFieldIndexFromTagBits(InternPool* ip, uint32_t enum_ty, uint64_t tag_bits, uint32_t width, bool* out_ambiguous) { uint32_t fields_len = ipEnumFieldsLen(ip, enum_ty); // Carriers differ in how far a negative value's word is sign-extended; // compare the low `width` bits, which every carrier agrees on. uint64_t mask = width >= 64 ? UINT64_MAX : ((uint64_t)1 << width) - 1u; tag_bits &= mask; if (ip->item_tags[enum_ty] == IP_TAG_TYPE_ENUM_AUTO) return tag_bits < fields_len ? (uint32_t)tag_bits : UINT32_MAX; uint32_t found = UINT32_MAX; for (uint32_t i = 0; i < fields_len; i++) { uint32_t field_int = ipEnumFieldValue(ip, enum_ty, i); uint64_t field_bits; if (field_int == IP_INDEX_NONE || !ipFixedIntBits(ip, field_int, &field_bits) || (field_bits & mask) != tag_bits) continue; if (found != UINT32_MAX) { if (out_ambiguous) *out_ambiguous = true; return found; } found = i; } return found; } uint32_t ipEnumTagFieldIndex(InternPool* ip, uint32_t enum_tag_idx) { if (enum_tag_idx >= ip->items_len || ip->item_tags[enum_tag_idx] != IP_TAG_ENUM_TAG) return UINT32_MAX; uint32_t enum_ty = ipEnumTagType(ip, enum_tag_idx); uint32_t tag_int = ipEnumTagValue(ip, enum_tag_idx); // Every ipGetEnumTag producer coerces the tag int to the enum's int tag // type, and every stored explicit field value is interned at that same // type (semaEnumDeclType's coerceExtra to int_tag_ty, reify_enum's // `[len]tag_ty`-coerced elements), so a tag whose int type differs can // never name a declared field: one type check covers every field. if (ipTypeOf(ip, tag_int) != ipEnumTypeIntTagType(ip, enum_ty)) return UINT32_MAX; // `LoadedEnumType.tagValueIndex`: Value identity of the tag int, which // the 64-bit word cannot give past 64 bits (`enum(u128)`'s `(1 << 64) + // 1` read as 1). if (ip->item_tags[enum_ty] == IP_TAG_TYPE_ENUM_AUTO) { uint64_t tag_bits; if (ipIntBigLimbs(ip, tag_int, NULL, NULL, NULL, NULL) || !ipFixedIntBits(ip, tag_int, &tag_bits)) return UINT32_MAX; // limb-backed: >= 2^32, past any field return ipEnumFieldIndexFromTagBits(ip, enum_ty, tag_bits, 64, NULL); } uint32_t fields_len = ipEnumFieldsLen(ip, enum_ty); for (uint32_t i = 0; i < fields_len; i++) if (ipIntValueEqlSameType( ip, ipEnumFieldValue(ip, enum_ty, i), tag_int)) return i; return UINT32_MAX; } uint32_t ipEnumFieldsLen(const InternPool* ip, uint32_t enum_ty) { return ip->extra[ip->item_datas[enum_ty] + IP_TYPE_ENUM_FIELDS_LEN]; } uint32_t ipEnumFieldName( InternPool* ip, uint32_t enum_ty, uint32_t field_index) { return ipFieldNameAt( ip, enum_ty, ipEnumFieldNamesOff(ip, enum_ty) + field_index); } uint32_t ipEnumFieldIndexFromName( InternPool* ip, uint32_t enum_ty, uint32_t field_name_str) { if (enum_ty >= ip->items_len || !ipIsEnumType(ip, enum_ty)) return UINT32_MAX; uint32_t fields_len = ipEnumFieldsLen(ip, enum_ty); for (uint32_t i = 0; i < fields_len; i++) if (ipEnumFieldName(ip, enum_ty, i) == field_name_str) return i; return UINT32_MAX; } uint32_t ipEnumFieldValue( InternPool* ip, uint32_t enum_ty, uint32_t field_index) { if (enum_ty >= ip->items_len || field_index >= ipEnumFieldsLen(ip, enum_ty) || ip->item_tags[enum_ty] == IP_TAG_TYPE_ENUM_AUTO) return IP_INDEX_NONE; if (ip->item_tags[enum_ty] != IP_TAG_TYPE_ENUM_EXPLICIT && ip->item_tags[enum_ty] != IP_TAG_TYPE_ENUM_NONEXHAUSTIVE) return IP_INDEX_NONE; ipEnumTypeIntTagType(ip, enum_ty); // the layout holds the values uint32_t names_off = ipEnumFieldNamesOff(ip, enum_ty); return ip->extra[names_off + ipEnumFieldsLen(ip, enum_ty) + field_index]; } uint32_t ipEnumTagValue(const InternPool* ip, uint32_t enum_tag_idx) { // Tag.EnumTag { ty, int } (2 words); `int` is the already-interned/typed // backing int value (see ipGetEnumTag). uint32_t off = ip->item_datas[enum_tag_idx]; return ip->extra[off + 1]; } uint32_t ipEnumTagType(const InternPool* ip, uint32_t enum_tag_idx) { // Tag.EnumTag { ty, int } (2 words); `ty` is the enum type. uint32_t off = ip->item_datas[enum_tag_idx]; return ip->extra[off]; } uint32_t ipGetEnumLiteral(InternPool* ip, uint32_t str_idx) { // Tag.enum_literal stores the NullTerminatedString index directly in // `data` (no extra), mirroring InternPool.zig's `.enum_literal => { .data // = // @intFromEnum(enum_literal) }`. // // Dedup, like every other key upstream interns: `InternPool.get` reaches // the append above only after `getOrPutKey` misses // (`"if (ip.indexToKey(index).eql(key, ip)) return .{ .existing = index // };"`), and `Key.eql`'s enum-literal arm is plain string-index equality // (`".enum_literal => |a_info| {"` … `"return a_info == b_info;"`). So a // repeated `.name` yields the same Index upstream. This getter // therefore carries one item per name; the key is exactly the 1-word // `data`, so the shared item index serves it directly. uint32_t found = ipDedupFind( ip, IP_TAG_ENUM_LITERAL, &str_idx, 1, /*data_is_key=*/true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_ENUM_LITERAL, str_idx); ipDedupInsert(ip, IP_TAG_ENUM_LITERAL, idx, &str_idx, 1); return idx; } // Tag.TypeUnion.Flags (packed u32) for a non-packed union with auto backing // tag mode, no captures, and no field aligns. `tag_usage` is the exact // LoadedUnionType.TagUsage enum(u2) value; `is_extern_layout` sets the one-bit // layout field. The auto-layout common bits remain byte-identical: // ReleaseSmall `union {}` (`.none`) is 0x1f805 and `union(enum)` (`.tagged`) // is 0x1f845. Mirrors InternPool.getDeclaredUnionType's flags initializer. // TypeUnion.Flags' one-bit `layout` field. #define IP_UNION_FLAGS_LAYOUT_EXTERN IP_UNION_FLAGS_LAYOUT static uint32_t ipTypeUnionFlags(uint32_t tag_usage, bool is_extern_layout) { if (tag_usage > IP_UNION_TAG_USAGE_TAGGED) { UNIMPLEMENTED("InternPool.loadUnionType: invalid tag usage"); } uint32_t flags = 0; flags |= (uint32_t)IP_UNION_ANY_CAPTURES_FALSE << IP_UNION_FLAGS_ANY_CAPTURES_SHIFT; flags |= (uint32_t)IP_BACKING_TYPE_MODE_AUTO << IP_UNION_FLAGS_ENUM_TAG_MODE_SHIFT; if (is_extern_layout) flags |= IP_UNION_FLAGS_LAYOUT_EXTERN; flags |= tag_usage << IP_UNION_FLAGS_TAG_USAGE_SHIFT; flags |= (uint32_t)IP_ALIGNMENT_NONE << IP_UNION_FLAGS_ALIGNMENT_SHIFT; return flags; } // The `Tag.TypeUnionPacked` header both packed-union getters append: `bits` // carries `captures_len` (0 declared, `.reified` reified); backing and tag // are `.none` until layout. static void ipUnionPackedHeader(InternPool* ip, uint32_t zir_index, uint32_t captures_len, uint32_t fields_len) { const uint32_t hdr[IP_TYPE_UNION_PACKED_LEN] = { [IP_TYPE_UNION_PACKED_ZIR_INDEX] = zir_index, [IP_TYPE_UNION_PACKED_BITS] = ipTypeStructPackedBits(captures_len, false), [IP_TYPE_UNION_PACKED_NAME] = 0, // set by setName [IP_TYPE_UNION_PACKED_FQN] = 0, // set by setName [IP_TYPE_UNION_PACKED_NAME_NAV] = IP_INDEX_NONE, // .none [IP_TYPE_UNION_PACKED_NAMESPACE] = 0, // set by finish [IP_TYPE_UNION_PACKED_BACKING_INT_TYPE] = IP_INDEX_NONE, [IP_TYPE_UNION_PACKED_ENUM_TAG_TYPE] = IP_INDEX_NONE, [IP_TYPE_UNION_PACKED_FIELDS_LEN] = fields_len, }; ipAddExtra(ip, hdr, IP_TYPE_UNION_PACKED_LEN); } // (part of) InternPool.zig:getDeclaredUnionType // Intern a declared non-packed union type (no captures, no field aligns). // Mirrors InternPool.getDeclaredUnionType: pushes the // Tag.TypeUnion header (name/fqn/namespace left as placeholders, enum_tag_type // `.none` until backpatched by ipSetUnionEnumTagType) then a `field_type` // array of `.none` placeholders (backpatched by ipSetUnionFieldType). No // dedup: each call appends a fresh item where upstream would find the // existing key. No field_name_map: union field names live in the // generated tag enum, not the union itself. // getReifiedUnionType: a packed union is the `Tag.TypeUnionPacked` header // with `captures_len = .reified`; any other the `Tag.TypeUnion` header with // `any_captures = .reified` and `enum_tag_mode = .explicit` iff the tag is // given. Then `type_hash`, `reified_field_name[]` and `field_type[]`. uint32_t ipGetReifiedUnionType(InternPool* ip, uint32_t zir_index, uint64_t type_hash, uint32_t fields_len, IpContainerLayout layout, uint32_t tag_usage, uint32_t enum_tag_type, bool* out_existing) { uint32_t h = ipReifiedKeyHash(IP_TAG_TYPE_UNION, zir_index, type_hash); uint32_t found = ipReifiedFind(ip, IP_TAG_TYPE_UNION, zir_index, type_hash, h); if (found != IP_INDEX_NONE) { *out_existing = true; return found; } uint32_t off = ip->extra_len; bool is_packed = layout == IP_STRUCT_LAYOUT_PACKED; if (is_packed) { ipUnionPackedHeader( ip, zir_index, IP_PACKED_CAPTURES_LEN_REIFIED, fields_len); } else { uint32_t flags = ipTypeUnionFlags(tag_usage, layout == IP_STRUCT_LAYOUT_EXTERN); flags = (flags & ~(IP_UNION_FLAGS_ANY_CAPTURES_MASK << IP_UNION_FLAGS_ANY_CAPTURES_SHIFT)) | ((uint32_t)IP_UNION_ANY_CAPTURES_REIFIED << IP_UNION_FLAGS_ANY_CAPTURES_SHIFT); if (enum_tag_type != IP_INDEX_NONE) flags = (flags & ~IP_UNION_FLAGS_ENUM_TAG_MODE) | ((uint32_t)IP_BACKING_TYPE_MODE_EXPLICIT << IP_UNION_FLAGS_ENUM_TAG_MODE_SHIFT); const uint32_t hdr[IP_TYPE_UNION_LEN] = { [IP_TYPE_UNION_ZIR_INDEX] = zir_index, [IP_TYPE_UNION_NAME] = 0, // set by setName [IP_TYPE_UNION_FQN] = 0, // set by setName [IP_TYPE_UNION_NAME_NAV] = IP_INDEX_NONE, [IP_TYPE_UNION_NAMESPACE] = 0, // set by finish [IP_TYPE_UNION_ENUM_TAG_TYPE] = enum_tag_type, [IP_TYPE_UNION_FIELDS_LEN] = fields_len, [IP_TYPE_UNION_SIZE] = 0, [IP_TYPE_UNION_PADDING] = 0, [IP_TYPE_UNION_FLAGS] = flags, }; ipAddExtra(ip, hdr, IP_TYPE_UNION_LEN); } const uint32_t th[2] = { (uint32_t)type_hash, (uint32_t)(type_hash >> 32) }; ipAddExtra(ip, th, 2); for (uint32_t i = 0; i < fields_len; i++) { // reified_field_name = "" uint32_t empty = 0; ipAddExtra(ip, &empty, 1); } for (uint32_t i = 0; i < fields_len; i++) { // field_type = .none uint32_t none = IP_INDEX_NONE; ipAddExtra(ip, &none, 1); } uint32_t idx = ipAddItem(ip, is_packed ? IP_TAG_TYPE_UNION_PACKED_AUTO : IP_TAG_TYPE_UNION, off); ipReifiedInsert(ip, h, idx); *out_existing = false; return idx; } // InternPool.zig:getDeclaredUnionType uint32_t ipGetDeclaredUnionType(InternPool* ip, uint32_t zir_index, uint32_t fields_len, uint32_t tag_usage, bool is_extern_layout, bool any_field_aligns) { uint32_t off = ip->extra_len; uint32_t flags = ipTypeUnionFlags(tag_usage, is_extern_layout); if (any_field_aligns) flags |= IP_UNION_FLAGS_ANY_FIELD_ALIGNS; const uint32_t hdr[IP_TYPE_UNION_LEN] = { [IP_TYPE_UNION_ZIR_INDEX] = zir_index, [IP_TYPE_UNION_NAME] = 0, // set by setName [IP_TYPE_UNION_FQN] = 0, // set by setName [IP_TYPE_UNION_NAME_NAV] = IP_INDEX_NONE, // .none [IP_TYPE_UNION_NAMESPACE] = 0, // set by finish [IP_TYPE_UNION_ENUM_TAG_TYPE] = IP_INDEX_NONE, // backpatched [IP_TYPE_UNION_FIELDS_LEN] = fields_len, [IP_TYPE_UNION_SIZE] = 0, // 0 until layout resolved [IP_TYPE_UNION_PADDING] = 0, // 0 until layout resolved [IP_TYPE_UNION_FLAGS] = flags, }; ipAddExtra(ip, hdr, IP_TYPE_UNION_LEN); for (uint32_t i = 0; i < fields_len; i++) { // field_type = .none uint32_t none = IP_INDEX_NONE; ipAddExtra(ip, &none, 1); } // `"extra.appendNTimesAssumeCapacity(.{0}, (ini.fields_len + 3) / 4); // // field_align"` for (uint32_t i = 0; any_field_aligns && i < (fields_len + 3u) / 4u; i++) { uint32_t zero = 0; ipAddExtra(ip, &zero, 1); } return ipAddItem(ip, IP_TAG_TYPE_UNION, off); } // `getDeclaredUnionType`'s `.@"packed"` arm: `"const extra_index = // addExtraAssumeCapacity(extra, Tag.TypeUnionPacked{ ... .backing_int_type = // .none, .enum_tag_type = .none, .fields_len = ini.fields_len });"` then // `"extra.appendNTimesAssumeCapacity(.{@intFromEnum(Index.none)}, // ini.fields_len); // field_type"`, tagged by `packed_backing_mode`. // `bits` is `captures_len = 0, want_layout = false`, the same mint-time word // as ipGetDeclaredStructTypePacked's (captures live in the namespace table). uint32_t ipGetDeclaredUnionTypePacked(InternPool* ip, uint32_t zir_index, uint32_t fields_len, bool explicit_backing) { uint32_t off = ip->extra_len; ipUnionPackedHeader(ip, zir_index, 0, fields_len); for (uint32_t i = 0; i < fields_len; i++) { // field_type = .none uint32_t none = IP_INDEX_NONE; ipAddExtra(ip, &none, 1); } return ipAddItem(ip, explicit_backing ? IP_TAG_TYPE_UNION_PACKED_EXPLICIT : IP_TAG_TYPE_UNION_PACKED_AUTO, off); } // The header words `Tag.TypeUnion` and `Tag.TypeUnionPacked` share by name // but not by position (`std.meta.fieldIndex(Tag.TypeUnion[Packed], ...)`). static uint32_t ipUnionWord(const InternPool* ip, uint32_t union_idx, uint32_t packed_word, uint32_t plain_word) { return ip->item_datas[union_idx] + (ipUnionIsPacked(ip, union_idx) ? packed_word : plain_word); } // InternPool.zig:resolvePackedUnionLayout void ipResolvePackedUnionLayout(InternPool* ip, uint32_t union_idx, uint32_t enum_tag_ty, uint32_t backing_int_ty) { if (!ipUnionIsPacked(ip, union_idx)) UNREACHABLE("ipResolvePackedUnionLayout: malformed InternPool item"); uint32_t off = ip->item_datas[union_idx]; ip->extra[off + IP_TYPE_UNION_PACKED_ENUM_TAG_TYPE] = enum_tag_ty; ip->extra[off + IP_TYPE_UNION_PACKED_BACKING_INT_TYPE] = backing_int_ty; } // Backpatch the union's `namespace`; mirrors WipContainerType.finish (same // pattern as ipSetStructNamespace). void ipSetUnionNamespace(InternPool* ip, uint32_t union_idx, uint32_t ns) { ip->extra[ipUnionWord(ip, union_idx, IP_TYPE_UNION_PACKED_NAMESPACE, IP_TYPE_UNION_NAMESPACE)] = ns; } // Like ipStructNamespaceOrNone: resolves the raw namespace word's 0-vs-unset // ambiguity by ownership (namespace 0 is this union's iff its owner_type is // this union). Returns IP_NAMESPACE_NONE when the union has no namespace. uint32_t ipUnionNamespaceOrNone(const InternPool* ip, uint32_t union_idx) { return ipNamespaceOrNone(ip, ip->extra[ipUnionWord(ip, union_idx, IP_TYPE_UNION_PACKED_NAMESPACE, IP_TYPE_UNION_NAMESPACE)], union_idx); } // Backpatch the union's `enum_tag_type`; mirrors // WipContainerType field population during layout resolution. void ipSetUnionEnumTagType( InternPool* ip, uint32_t union_idx, uint32_t tag_ty) { ip->extra[ipUnionWord(ip, union_idx, IP_TYPE_UNION_PACKED_ENUM_TAG_TYPE, IP_TYPE_UNION_ENUM_TAG_TYPE)] = tag_ty; } uint32_t ipUnionEnumTagType(const InternPool* ip, uint32_t union_idx) { return ip->extra[ipUnionWord(ip, union_idx, IP_TYPE_UNION_PACKED_ENUM_TAG_TYPE, IP_TYPE_UNION_ENUM_TAG_TYPE)]; } uint32_t ipUnionTagUsage(const InternPool* ip, uint32_t union_idx) { // `loadUnionType`'s packed arm: `".tag_usage = .none"`. if (ipUnionIsPacked(ip, union_idx)) return IP_UNION_TAG_USAGE_NONE; uint32_t flags = ip->extra[ip->item_datas[union_idx] + IP_TYPE_UNION_FLAGS]; return (flags >> IP_UNION_FLAGS_TAG_USAGE_SHIFT) & IP_UNION_FLAGS_TAG_USAGE_MASK; } bool ipUnionIsExtern(const InternPool* ip, uint32_t union_idx) { if (ipUnionIsPacked(ip, union_idx)) return false; return (ip->extra[ip->item_datas[union_idx] + IP_TYPE_UNION_FLAGS] & IP_UNION_FLAGS_LAYOUT_EXTERN) != 0; } uint32_t ipUnionPadding(const InternPool* ip, uint32_t union_idx) { ipLayoutRejectPacked(ip, union_idx, "ipUnionPadding"); return ip->extra[ip->item_datas[union_idx] + IP_TYPE_UNION_PADDING]; } bool ipUnionHasRuntimeTag(const InternPool* ip, uint32_t union_idx) { ipLayoutRejectPacked(ip, union_idx, "ipUnionHasRuntimeTag"); return (ip->extra[ip->item_datas[union_idx] + IP_TYPE_UNION_FLAGS] & IP_UNION_FLAGS_HAS_RUNTIME_TAG) != 0; } // InternPool.zig:resolveUnionLayout void ipResolveUnionLayout(InternPool* ip, uint32_t union_idx, uint32_t enum_tag_ty, uint32_t type_class, bool has_runtime_tag, uint32_t size, uint32_t padding, uint32_t alignment_log2) { if (union_idx >= ip->items_len || ip->item_tags[union_idx] != IP_TAG_TYPE_UNION) UNREACHABLE("ipResolveUnionLayout: malformed InternPool item"); if (enum_tag_ty == IP_INDEX_NONE || type_class > TYPE_CLASS_FULLY_COMPTIME || alignment_log2 >= IP_ALIGNMENT_NONE || padding > size) UNREACHABLE("ipResolveUnionLayout: malformed InternPool item"); if (ipUnionLayoutResolved(ip, union_idx)) { if (ipUnionEnumTagType(ip, union_idx) != enum_tag_ty || ipUnionClass(ip, union_idx) != type_class || ipUnionHasRuntimeTag(ip, union_idx) != has_runtime_tag || ipUnionSize(ip, union_idx) != size || ipUnionPadding(ip, union_idx) != padding || ipUnionAlignmentLog2(ip, union_idx) != alignment_log2) UNREACHABLE("ipResolveUnionLayout: malformed InternPool item"); return; } uint32_t off = ip->item_datas[union_idx]; ip->extra[off + IP_TYPE_UNION_ENUM_TAG_TYPE] = enum_tag_ty; ip->extra[off + IP_TYPE_UNION_SIZE] = size; ip->extra[off + IP_TYPE_UNION_PADDING] = padding; uint32_t flags = ip->extra[off + IP_TYPE_UNION_FLAGS]; flags &= ~((IP_UNION_FLAGS_CLASS_MASK << IP_UNION_FLAGS_CLASS_SHIFT) | IP_UNION_FLAGS_HAS_RUNTIME_TAG | (IP_UNION_FLAGS_ALIGNMENT_MASK << IP_UNION_FLAGS_ALIGNMENT_SHIFT)); flags |= (type_class & IP_UNION_FLAGS_CLASS_MASK) << IP_UNION_FLAGS_CLASS_SHIFT; if (has_runtime_tag) flags |= IP_UNION_FLAGS_HAS_RUNTIME_TAG; flags |= (alignment_log2 & IP_UNION_FLAGS_ALIGNMENT_MASK) << IP_UNION_FLAGS_ALIGNMENT_SHIFT; ip->extra[off + IP_TYPE_UNION_FLAGS] = flags; } uint32_t ipUnionFieldsLen(const InternPool* ip, uint32_t union_idx) { return ip->extra[ipUnionWord(ip, union_idx, IP_TYPE_UNION_PACKED_FIELDS_LEN, IP_TYPE_UNION_FIELDS_LEN)]; } static bool ipUnionIsReified(const InternPool* ip, uint32_t union_idx) { uint32_t data = ip->item_datas[union_idx]; if (ipUnionIsPacked(ip, union_idx)) return (ip->extra[data + IP_TYPE_UNION_PACKED_BITS] & IP_PACKED_CAPTURES_LEN_REIFIED) == IP_PACKED_CAPTURES_LEN_REIFIED; return ((ip->extra[data + IP_TYPE_UNION_FLAGS] >> IP_UNION_FLAGS_ANY_CAPTURES_SHIFT) & IP_UNION_FLAGS_ANY_CAPTURES_MASK) == IP_UNION_ANY_CAPTURES_REIFIED; } // `reified_field_name[]` follows a reified union's header and `type_hash`. static uint32_t ipUnionFieldNamesStart( const InternPool* ip, uint32_t union_idx) { return ipUnionWord( ip, union_idx, IP_TYPE_UNION_PACKED_LEN, IP_TYPE_UNION_LEN) + IP_PACKED_U64_LEN; } // The per-field `field_type` array follows the header (and, reified, the // `type_hash` and `reified_field_name[]`). static uint32_t ipUnionFieldTypesStart( const InternPool* ip, uint32_t union_idx) { if (!ipUnionIsReified(ip, union_idx)) return ipUnionWord( ip, union_idx, IP_TYPE_UNION_PACKED_LEN, IP_TYPE_UNION_LEN); return ipUnionFieldNamesStart(ip, union_idx) + ipUnionFieldsLen(ip, union_idx); } void ipSetUnionReifiedFieldName( InternPool* ip, uint32_t union_idx, uint32_t i, uint32_t name) { ip->extra[ipUnionFieldNamesStart(ip, union_idx) + i] = name; } void ipSetUnionFieldType( InternPool* ip, uint32_t union_idx, uint32_t i, uint32_t ty) { ip->extra[ipUnionFieldTypesStart(ip, union_idx) + i] = ty; } uint32_t ipUnionFieldType( const InternPool* ip, uint32_t union_idx, uint32_t i) { return ip->extra[ipUnionFieldTypesStart(ip, union_idx) + i]; } // `LoadedUnionType.field_aligns`: one Alignment byte per field, four to a // word, after `field_type[]` when `Flags.any_field_aligns`; empty otherwise. bool ipUnionHasFieldAligns(const InternPool* ip, uint32_t union_idx) { return !ipUnionIsPacked(ip, union_idx) && (ip->extra[ip->item_datas[union_idx] + IP_TYPE_UNION_FLAGS] & IP_UNION_FLAGS_ANY_FIELD_ALIGNS) != 0; } void ipSetUnionFieldAlignment( InternPool* ip, uint32_t union_idx, uint32_t i, uint32_t alignment) { if (!ipUnionHasFieldAligns(ip, union_idx) || i >= ipUnionFieldsLen(ip, union_idx) || alignment > IP_PTR_ALIGN_NONE) { UNREACHABLE("invalid union field alignment write"); } uint32_t base = ipUnionFieldTypesStart(ip, union_idx) + ipUnionFieldsLen(ip, union_idx) + i / 4u; uint32_t shift = (i & 3u) * 8u; ip->extra[base] = (ip->extra[base] & ~(0xFFu << shift)) | (alignment << shift); } uint32_t ipUnionFieldAlignment( const InternPool* ip, uint32_t union_idx, uint32_t i) { if (!ipUnionHasFieldAligns(ip, union_idx)) return IP_PTR_ALIGN_NONE; uint32_t base = ipUnionFieldTypesStart(ip, union_idx) + ipUnionFieldsLen(ip, union_idx) + i / 4u; return (ip->extra[base] >> ((i & 3u) * 8u)) & 0xFFu; } // (part of) InternPool.zig:getGeneratedEnumTagType // Intern the auto-generated hypothetical tag enum for an auto-tag-mode union. // Mirrors InternPool.getGeneratedEnumTagType: a `type_enum_auto` whose // `captures_len` field is the sentinel `.generated_union_tag` // (maxInt(u31)-1 = 0x7FFFFFFE), with a trailing `owner_union` (the union type) // in place of the `zir_index` a declared enum would store, followed by the // `field_name` array. `int_tag_type` starts `.none` and is populated during // layout resolution. The header matches ipGetEnumTypeAuto's, so // ipEnumTagFromFieldName / ipSetEnumIntTagType read it identically. // getGeneratedEnumTagType's `.wip` arm in resolveUnionLayout names the new // enum, then ensureLayoutResolved(.backing_enum) runs resolveEnumLayout, which // interns the union's ZIR field names into it ("Generated tag enums for // declared unions do not yet have field names populated"). // The name and fqn getGeneratedEnumTagType's `.wip` arm gives a union's // generated tag: `"@typeInfo({f}).@\"union\".tag_type.?"` of union_obj.name, // then of union_obj.fqn. void ipGeneratedUnionTagName( InternPool* ip, uint32_t union_ty, uint32_t* out_name, uint32_t* out_fqn) { *out_name = ipGetOrPutStringFmt(ip, "@typeInfo(%s).@\"union\".tag_type.?", ipStringBytes(ip, ipContainerTypeName(ip, union_ty))); *out_fqn = ipGetOrPutStringFmt(ip, "@typeInfo(%s).@\"union\".tag_type.?", ipStringBytes(ip, ipContainerTypeFqn(ip, union_ty))); } static void ipGeneratedTagNameAndFields( InternPool* ip, uint32_t union_ty, uint32_t tag_enum) { uint32_t tag_name, tag_fqn; ipGeneratedUnionTagName(ip, union_ty, &tag_name, &tag_fqn); ipSetTypeName(ip, tag_enum, tag_name, tag_fqn, IP_INDEX_NONE); // `pt.createNamespace(.{ .parent = union_obj.namespace.toOptional(), // .owner_type = wip.index, ... })`, then ensureLayoutResolved of the tag. uint32_t union_ns = ipUnionNamespaceOrNone(ip, union_ty); if (union_ns != IP_NAMESPACE_NONE) ipSetEnumNamespace(ip, tag_enum, ipCreateNamespace(ip, union_ns, tag_enum, ipGetNamespace(ip, union_ns)->file_scope)); ipSetWantTypeLayout(ip, tag_enum); for (uint32_t p = 0; p < ip->pending_field_names_len; p++) if (ip->pending_field_names[p].ty == union_ty) ip->pending_field_names[p].ty = tag_enum; ipResolveFieldNames(ip, tag_enum); // A reified union's names are its `reified_field_name[]`. if (ipUnionIsReified(ip, union_ty)) for (uint32_t i = 0; i < ipUnionFieldsLen(ip, union_ty); i++) ip->extra[ipEnumFieldNamesOff(ip, tag_enum) + i] = ip->extra[ipUnionFieldNamesStart(ip, union_ty) + i]; } static uint32_t ipGetGeneratedUnionTagEnum( InternPool* ip, uint32_t union_ty, uint32_t fields_len) { uint32_t field_name_map = ipAddMap(ip); uint32_t off = ip->extra_len; const uint32_t hdr[IP_TYPE_ENUM_LEN] = { [IP_TYPE_ENUM_BITS] = (uint32_t)IP_ENUM_CAPTURES_LEN_GENERATED_UNION_TAG << IP_ENUM_BITS_CAPTURES_LEN_SHIFT, // want_layout=0 [IP_TYPE_ENUM_NAME] = 0, // set by setName [IP_TYPE_ENUM_FQN] = 0, // set by setName [IP_TYPE_ENUM_NAME_NAV] = IP_INDEX_NONE, // .none [IP_TYPE_ENUM_NAMESPACE] = 0, // set by finish // resolved after the enum item exists [IP_TYPE_ENUM_INT_TAG_TYPE] = IP_INDEX_NONE, [IP_TYPE_ENUM_FIELDS_LEN] = fields_len, [IP_TYPE_ENUM_FIELD_NAME_MAP] = field_name_map, }; ipAddExtra(ip, hdr, IP_TYPE_ENUM_LEN); ipAddExtra(ip, &union_ty, 1); // owner_union (Index) for (uint32_t i = 0; i < fields_len; i++) { uint32_t none = IP_INDEX_NONE; ipAddExtra(ip, &none, 1); // field_name per field } uint32_t tag_enum = ipAddItem(ip, IP_TAG_TYPE_ENUM_AUTO, off); ipGeneratedTagNameAndFields(ip, union_ty, tag_enum); return tag_enum; } static uint32_t ipGetGeneratedUnionTagEnumExplicit(InternPool* ip, uint32_t union_ty, uint32_t fields_len, uint32_t explicit_int_tag_ty, uint32_t explicit_int_tag_bits, bool explicit_int_tag_signed, const uint32_t* field_values) { uint32_t field_name_map = ipAddMap(ip); uint32_t field_value_map = ipAddMap(ip); uint32_t off = ip->extra_len; const uint32_t hdr[IP_TYPE_ENUM_LEN] = { [IP_TYPE_ENUM_BITS] = (uint32_t)IP_ENUM_CAPTURES_LEN_GENERATED_UNION_TAG << IP_ENUM_BITS_CAPTURES_LEN_SHIFT, // want_layout=0 [IP_TYPE_ENUM_NAME] = 0, // set by setName [IP_TYPE_ENUM_FQN] = 0, // set by setName [IP_TYPE_ENUM_NAME_NAV] = IP_INDEX_NONE, // .none [IP_TYPE_ENUM_NAMESPACE] = 0, // set by finish // resolved after the enum item exists [IP_TYPE_ENUM_INT_TAG_TYPE] = IP_INDEX_NONE, [IP_TYPE_ENUM_FIELDS_LEN] = fields_len, [IP_TYPE_ENUM_FIELD_NAME_MAP] = field_name_map, }; ipAddExtra(ip, hdr, IP_TYPE_ENUM_LEN); ipAddExtra(ip, &union_ty, 1); // owner_union (Index) ipAddExtra(ip, &field_value_map, 1); // field_value_map (MapIndex) for (uint32_t i = 0; i < fields_len; i++) { uint32_t none = IP_INDEX_NONE; ipAddExtra(ip, &none, 1); // field_name per field } uint32_t values_off = ip->extra_len; for (uint32_t i = 0; i < fields_len; i++) { uint32_t none = IP_INDEX_NONE; ipAddExtra(ip, &none, 1); // field_value placeholder } uint32_t tag_enum = ipAddItem(ip, IP_TAG_TYPE_ENUM_EXPLICIT, off); ipGeneratedTagNameAndFields(ip, union_ty, tag_enum); uint32_t int_tag_ty = explicit_int_tag_ty; if (int_tag_ty == IP_INDEX_NONE) int_tag_ty = ipGetIntType(ip, explicit_int_tag_signed, explicit_int_tag_bits); ipSetEnumIntTagType(ip, tag_enum, int_tag_ty); for (uint32_t i = 0; i < fields_len; i++) ip->extra[values_off + i] = field_values != NULL ? field_values[i] : ipGetIntSmall(ip, int_tag_ty, i); return tag_enum; } void ipRegisterGeneratedUnionTag(InternPool* ip, uint32_t union_ty, uint32_t fields_len, uint32_t explicit_int_tag_ty, uint32_t explicit_int_tag_bits, bool explicit_int_tag_signed) { for (uint32_t i = 0; i < ip->pending_union_tags_len; i++) if (ip->pending_union_tags[i].union_type == union_ty) return; if (ip->pending_union_tags_len == ip->pending_union_tags_cap) { uint32_t cap = zig1GrowCap(ip->pending_union_tags_cap, 16u); IpPendingUnionTag* items = zig1Realloc( ip->pending_union_tags, cap * sizeof(IpPendingUnionTag)); ip->pending_union_tags = items; ip->pending_union_tags_cap = cap; } ip->pending_union_tags[ip->pending_union_tags_len++] = (IpPendingUnionTag) { union_ty, fields_len, explicit_int_tag_ty, explicit_int_tag_bits, explicit_int_tag_signed, IP_INDEX_NONE, NULL, false }; } // The index of `union_ty`'s pending tag, or `pending_union_tags_len` if none. static uint32_t ipPendingUnionTagIndex( const InternPool* ip, uint32_t union_ty) { for (uint32_t i = ip->pending_union_tags_len; i-- > 0;) if (ip->pending_union_tags[i].union_type == union_ty) return i; return ip->pending_union_tags_len; } void ipSetPendingPackedUnionBacking( InternPool* ip, uint32_t union_ty, uint32_t backing_ty) { uint32_t i = ipPendingUnionTagIndex(ip, union_ty); if (i == ip->pending_union_tags_len) UNREACHABLE( "ipSetPendingPackedUnionBacking: union type has no pending tag"); ip->pending_union_tags[i].packed_backing_ty = backing_ty; } void ipSetPendingUnionTagValues( InternPool* ip, uint32_t union_ty, uint32_t* values) { uint32_t i = ipPendingUnionTagIndex(ip, union_ty); if (i == ip->pending_union_tags_len) UNREACHABLE( "ipSetPendingUnionTagValues: union type has no pending tag"); free(ip->pending_union_tags[i].field_values); ip->pending_union_tags[i].field_values = values; ip->pending_union_tags[i].values_pending = false; } void ipDeferPendingUnionTagValues(InternPool* ip, uint32_t union_ty) { uint32_t i = ipPendingUnionTagIndex(ip, union_ty); if (i == ip->pending_union_tags_len) UNREACHABLE( "ipDeferPendingUnionTagValues: union type has no pending tag"); ip->pending_union_tags[i].values_pending = true; } uint32_t ipPendingPackedUnionBacking(const InternPool* ip, uint32_t union_ty) { uint32_t i = ipPendingUnionTagIndex(ip, union_ty); return i == ip->pending_union_tags_len ? IP_INDEX_NONE : ip->pending_union_tags[i].packed_backing_ty; } uint32_t ipEnsureGeneratedUnionTag(InternPool* ip, uint32_t union_ty) { uint32_t existing = ipUnionEnumTagType(ip, union_ty); if (existing != IP_INDEX_NONE) return existing; for (uint32_t i = ip->pending_union_tags_len; i-- > 0;) { const IpPendingUnionTag* pending = &ip->pending_union_tags[i]; if (pending->union_type != union_ty) continue; if (pending->values_pending) { // resolveUnionLayout evaluates the value bodies first // (semaResolveAllUnionDeferred); this demand skipped it. UNIMPLEMENTED("InternPool.loadUnionType: union tag made before " "its field values"); } // Stock getGeneratedEnumTagType creates the enum with a `.none` // int_tag_type. Resolving that enum's layout subsequently interns the // inferred uN, so a new backing integer must follow—not precede—the // type_enum_auto item in IP order. bool explicit_int_tag = pending->explicit_int_tag_ty != IP_INDEX_NONE || pending->explicit_int_tag_bits != UINT32_MAX; uint32_t tag_enum = explicit_int_tag ? ipGetGeneratedUnionTagEnumExplicit(ip, union_ty, pending->fields_len, pending->explicit_int_tag_ty, pending->explicit_int_tag_bits, pending->explicit_int_tag_signed, pending->field_values) : ipGetGeneratedUnionTagEnum(ip, union_ty, pending->fields_len); ipSetUnionEnumTagType(ip, union_ty, tag_enum); if (explicit_int_tag) return tag_enum; ipSetInferredEnumIntTagType(ip, tag_enum, pending->fields_len); return tag_enum; } UNREACHABLE("union layout: generated tag metadata missing"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // Resolve a union field name to its declaration index (via the tag enum's // field_name array). Returns UINT32_MAX if not found. uint32_t ipUnionFieldIndex( InternPool* ip, uint32_t union_ty, uint32_t field_name_str) { ipEnsureGeneratedUnionTag(ip, union_ty); uint32_t fields_len = ipUnionFieldsLen(ip, union_ty); for (uint32_t i = 0; i < fields_len; i++) if (ipUnionFieldName(ip, union_ty, i) == field_name_str) return i; return UINT32_MAX; } // loadUnionType's field names are its tag enum's, populated by layout // resolution. uint32_t ipUnionFieldName( InternPool* ip, uint32_t union_ty, uint32_t field_index) { if (field_index >= ipUnionFieldsLen(ip, union_ty)) return IP_INDEX_NONE; uint32_t tag_enum = ipEnsureGeneratedUnionTag(ip, union_ty); return ipEnumFieldName(ip, tag_enum, field_index); } // InternPool.zig:aggregateTypeLenIncludingSentinel // An array's or vector's stored length, a struct's or tuple's field count. uint32_t ipAggregateTypeLenIncludingSentinel( const InternPool* ip, uint32_t ty) { IpArrayInfo info; if (ipArrayInfo(ip, ty, &info) == IP_ARR_OK) return info.storage_len; if (ip->item_tags[ty] == IP_TAG_TYPE_TUPLE) return ipTupleFieldCount(ip, ty); return ipStructFieldsLen(ip, ty); } // A struct or tuple type: Type.zigTypeTag == .@"struct". bool ipIsStructOrTupleType(const InternPool* ip, uint32_t ty) { return ipTagIsAnyStruct(ip->item_tags[ty]) || ip->item_tags[ty] == IP_TAG_TYPE_TUPLE; } // InternPool.zig:getUnion // Intern a `union_value` (Key.Union = {ty, tag, val}, 3 words). Mirrors // InternPool.getUnion: dedup by scanning existing union_value items. uint32_t ipGetUnionValue( InternPool* ip, uint32_t ty, uint32_t tag, uint32_t val) { const uint32_t words[3] = { ty, tag, val }; uint32_t found = ipDedupFind(ip, IP_TAG_UNION_VALUE, words, 3, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 3); uint32_t idx = ipAddItem(ip, IP_TAG_UNION_VALUE, off); ipDedupInsert(ip, IP_TAG_UNION_VALUE, idx, words, 3); return idx; } uint32_t ipGetArrayType( InternPool* ip, uint32_t len, uint32_t child, uint32_t sentinel) { // Mirror InternPool.get for `.array_type` (InternPool.zig): when the // length fits u32 and there is no sentinel, emit a compact // `type_array_small` (Tag payload `Vector` = {len, child}, 2 words). // Otherwise emit `type_array_big` (Tag payload `Array` = {len0, len1, // child, sentinel}, 4 words; len is a PackedU64 split into two words). // `len` here is a u32, so len0=len, len1=0. // getOrPutKey dedup: scan matching items so identical array types reuse. if (sentinel == IP_INDEX_NONE) { const uint32_t words[2] = { len, child }; uint32_t found = ipDedupFind(ip, IP_TAG_TYPE_ARRAY_SMALL, words, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_TYPE_ARRAY_SMALL, off); ipDedupInsert(ip, IP_TAG_TYPE_ARRAY_SMALL, idx, words, 2); return idx; } const uint32_t words[4] = { len, 0, child, sentinel }; uint32_t found = ipDedupFind(ip, IP_TAG_TYPE_ARRAY_BIG, words, 4, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, IP_TYPE_POINTER_LEN); uint32_t idx = ipAddItem(ip, IP_TAG_TYPE_ARRAY_BIG, off); ipDedupInsert(ip, IP_TAG_TYPE_ARRAY_BIG, idx, words, 4); return idx; } uint32_t ipGetUndef(InternPool* ip, uint32_t ty) { // Mirror InternPool.get for `.undef`: an `undefined` value of type `ty`. // The type index is stored directly in item_datas (see the preinterned // undef entries). Dedup by scanning existing undef items with the same ty. uint32_t found = ipDedupFind(ip, IP_TAG_UNDEF, &ty, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_UNDEF, ty); ipDedupInsert(ip, IP_TAG_UNDEF, idx, &ty, 1); return idx; } uint32_t ipGetPtrComptimeAlloc( InternPool* ip, uint32_t ptr_ty, uint32_t alloc_index) { return ipGetPtrComptimeAllocOffset(ip, ptr_ty, alloc_index, 0); } uint32_t ipGetPtrComptimeAllocOffset(InternPool* ip, uint32_t ptr_ty, uint32_t alloc_index, uint64_t byte_offset) { // Mirror InternPool.get for `.ptr` with base_addr `.comptime_alloc` // (Tag.ptr_comptime_alloc): PtrComptimeAlloc = { ty, index, byte_offset_a, // byte_offset_b }. Dedups by {ty, index, offset}, as `Key.Ptr.eql` // does: `alloc_index` is a ComptimeAllocIndex of the analyzing Sema, so // two Semas' allocations share their pointers. uint32_t off_lo = (uint32_t)(byte_offset & 0xFFFFFFFFu); uint32_t off_hi = (uint32_t)(byte_offset >> 32); const uint32_t words[4] = { ptr_ty, alloc_index, off_hi, off_lo }; uint32_t found = ipDedupFind(ip, IP_TAG_PTR_COMPTIME_ALLOC, words, 4, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, IP_TYPE_POINTER_LEN); uint32_t idx = ipAddItem(ip, IP_TAG_PTR_COMPTIME_ALLOC, off); ipDedupInsert(ip, IP_TAG_PTR_COMPTIME_ALLOC, idx, words, 4); return idx; } // InternPool.zig:getTupleType uint32_t ipGetTupleType(InternPool* ip, uint32_t fields_len, const uint32_t* types, const uint32_t* values) { // Mirror InternPool.get for `.tuple_type`: TypeTuple { fields_len } then // `fields_len` type Indices and `fields_len` value Indices. Dedup: // find-or-append against existing type_tuple items with the same // fields_len, types, and values. Index-accelerated: `ipGetTupleType` is // the tag's only dynamic creator; the sole static preintern // (empty_tuple_type, fields_len==0) is seeded by ipDedupSeedStatic. // Variable-length key (fields_len varies), so it must be assembled into // a temp buffer before the find (there is no live `extra` slot to point // at yet); reused as-is for the insert once appended. uint32_t n = 1 + 2 * fields_len; uint32_t* words = (uint32_t*)zig1Malloc((size_t)n * sizeof(uint32_t)); words[0] = fields_len; for (uint32_t j = 0; j < fields_len; j++) words[1 + j] = types[j]; for (uint32_t j = 0; j < fields_len; j++) words[1 + fields_len + j] = values[j]; uint32_t found = ipDedupFind(ip, IP_TAG_TYPE_TUPLE, words, n, false); if (found != IP_INDEX_NONE) { free(words); return found; } uint32_t off = ipAddExtra(ip, words, n); free(words); uint32_t idx = ipAddItem(ip, IP_TAG_TYPE_TUPLE, off); ipDedupInsert(ip, IP_TAG_TYPE_TUPLE, idx, &ip->extra[off], n); return idx; } // The ten storage tags whose `indexToKey` arm is `.int` (`InternPool.Tag`'s // `.int_u8 .. .int_negative` run), listed here and nowhere else; `ipTypeOf` // answers what type such an item has. bool ipTagIsIntValue(IpTag tag) { switch (tag) { case IP_TAG_INT_U8: case IP_TAG_INT_U16: case IP_TAG_INT_U32: case IP_TAG_INT_I32: case IP_TAG_INT_USIZE: case IP_TAG_INT_COMPTIME_INT_U32: case IP_TAG_INT_COMPTIME_INT_I32: case IP_TAG_INT_SMALL: case IP_TAG_INT_POSITIVE: case IP_TAG_INT_NEGATIVE: return true; default: return false; } } bool ipIndexIsType(const InternPool* ip, uint32_t idx) { if (idx >= ip->items_len) return false; switch ((IpTag)ip->item_tags[idx]) { case IP_TAG_SIMPLE_TYPE: case IP_TAG_TYPE_INT_SIGNED: case IP_TAG_TYPE_INT_UNSIGNED: case IP_TAG_TYPE_VECTOR: case IP_TAG_TYPE_POINTER: case IP_TAG_TYPE_SLICE: case IP_TAG_TYPE_OPTIONAL: case IP_TAG_TYPE_ANYFRAME: case IP_TAG_TYPE_ANYERROR_UNION: case IP_TAG_TYPE_TUPLE: case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: case IP_TAG_TYPE_FUNCTION: case IP_TAG_TYPE_ENUM_AUTO: case IP_TAG_TYPE_ARRAY_BIG: case IP_TAG_TYPE_ARRAY_SMALL: case IP_TAG_TYPE_ERROR_SET: case IP_TAG_TYPE_ERROR_UNION: case IP_TAG_TYPE_UNION: case IP_TAG_TYPE_UNION_PACKED_AUTO: case IP_TAG_TYPE_UNION_PACKED_EXPLICIT: case IP_TAG_TYPE_INFERRED_ERROR_SET: case IP_TAG_TYPE_ENUM_EXPLICIT: case IP_TAG_TYPE_ENUM_NONEXHAUSTIVE: case IP_TAG_TYPE_OPAQUE: return true; default: return false; } } // InternPool.zig:typeOf uint32_t ipTypeOf(const InternPool* ip, uint32_t idx) { if (idx >= ip->items_len) return IP_INDEX_NONE; if (ipIndexIsType(ip, idx)) return ZIR_REF_TYPE_TYPE; uint32_t d = ip->item_datas[idx]; switch ((IpTag)ip->item_tags[idx]) { case IP_TAG_UNDEF: case IP_TAG_ONLY_POSSIBLE_VALUE: case IP_TAG_OPT_NULL: return d < ip->items_len && ipIndexIsType(ip, d) ? d : IP_INDEX_NONE; case IP_TAG_SIMPLE_VALUE: if (idx == ZIR_REF_VOID_VALUE) return ZIR_REF_VOID_TYPE; if (idx == ZIR_REF_UNREACHABLE_VALUE) return ZIR_REF_NORETURN_TYPE; if (idx == ZIR_REF_NULL_VALUE) return ZIR_REF_NULL_TYPE; if (idx == ZIR_REF_BOOL_TRUE || idx == ZIR_REF_BOOL_FALSE) return ZIR_REF_BOOL_TYPE; return IP_INDEX_NONE; case IP_TAG_INT_U8: return ZIR_REF_U8_TYPE; case IP_TAG_INT_U16: return ZIR_REF_U16_TYPE; case IP_TAG_FLOAT_F16: return ZIR_REF_F16_TYPE; case IP_TAG_FLOAT_F32: return ZIR_REF_F32_TYPE; case IP_TAG_FLOAT_F64: return ZIR_REF_F64_TYPE; case IP_TAG_FLOAT_F80: return ZIR_REF_F80_TYPE; case IP_TAG_FLOAT_F128: return ZIR_REF_F128_TYPE; case IP_TAG_FLOAT_C_LONGDOUBLE_F128: return ZIR_REF_C_LONGDOUBLE_TYPE; case IP_TAG_FLOAT_COMPTIME_FLOAT: return ZIR_REF_COMPTIME_FLOAT_TYPE; case IP_TAG_INT_U32: return ZIR_REF_U32_TYPE; case IP_TAG_INT_I32: return ZIR_REF_I32_TYPE; case IP_TAG_INT_USIZE: return ZIR_REF_USIZE_TYPE; case IP_TAG_INT_COMPTIME_INT_U32: case IP_TAG_INT_COMPTIME_INT_I32: return ZIR_REF_COMPTIME_INT_TYPE; case IP_TAG_INT_POSITIVE: case IP_TAG_INT_NEGATIVE: { // `Int.ty` is the low half of the header limb -- this carrier is the // one that answers for `comptime_int` and sized alike. uint32_t bty; if (!ipIntBigLimbs(ip, idx, &bty, NULL, NULL, NULL)) return IP_INDEX_NONE; return bty < ip->items_len && ipIndexIsType(ip, bty) ? bty : IP_INDEX_NONE; } case IP_TAG_INT_SMALL: case IP_TAG_BYTES: case IP_TAG_AGGREGATE: case IP_TAG_REPEATED: case IP_TAG_PTR_NAV: case IP_TAG_PTR_UAV: case IP_TAG_PTR_UAV_ALIGNED: case IP_TAG_PTR_COMPTIME_FIELD: case IP_TAG_PTR_INT: case IP_TAG_PTR_SLICE: case IP_TAG_PTR_COMPTIME_ALLOC: case IP_TAG_ERROR_SET_ERROR: case IP_TAG_ERROR_UNION_ERROR: case IP_TAG_UNION_VALUE: case IP_TAG_OPT_PAYLOAD: case IP_TAG_ERROR_UNION_PAYLOAD: case IP_TAG_BITPACK: // The chained pointer family PtrBaseIndex {ty, base, index, off_a, off_b} // and PtrBase {ty, base, off_a, off_b} both carry `ty` at extra word 0, // same as the arm above. case IP_TAG_PTR_FIELD: case IP_TAG_PTR_ELEM: case IP_TAG_PTR_OPT_PAYLOAD: case IP_TAG_PTR_EU_PAYLOAD: if (d >= ip->extra_len) return IP_INDEX_NONE; return ip->extra[d] < ip->items_len && ipIndexIsType(ip, ip->extra[d]) ? ip->extra[d] : IP_INDEX_NONE; case IP_TAG_FUNC_DECL: case IP_TAG_FUNC_INSTANCE: if (d > ip->extra_len || ip->extra_len - d < 3) return IP_INDEX_NONE; return ip->extra[d + 2] < ip->items_len && ipIndexIsType(ip, ip->extra[d + 2]) ? ip->extra[d + 2] : IP_INDEX_NONE; case IP_TAG_FUNC_COERCED: if (d > ip->extra_len || ip->extra_len - d < 2) return IP_INDEX_NONE; return ip->extra[d] < ip->items_len && ipIndexIsType(ip, ip->extra[d]) ? ip->extra[d] : IP_INDEX_NONE; case IP_TAG_EXTERN: // Tag.Extern.ty is word 0. if (d > ip->extra_len || ip->extra_len - d < 7) return IP_INDEX_NONE; return ip->extra[d] < ip->items_len && ipIndexIsType(ip, ip->extra[d]) ? ip->extra[d] : IP_INDEX_NONE; case IP_TAG_ENUM_TAG: return ipEnumTagType(ip, idx); case IP_TAG_ENUM_LITERAL: return ZIR_REF_ENUM_LITERAL_TYPE; default: return IP_INDEX_NONE; } } IpPtrNavStatus ipDecodePtrNavAny( const InternPool* ip, uint32_t value, IpPtrNav* out) { if (value >= ip->items_len || ip->item_tags[value] != IP_TAG_PTR_NAV) return IP_PN_NOT_PTR_NAV; uint32_t pd = ip->item_datas[value]; if (pd > ip->extra_len || ip->extra_len - pd < 4u) return IP_PN_PAYLOAD_OOB; uint32_t ptr_ty = ip->extra[pd]; uint32_t nav_idx = ip->extra[pd + 1]; uint64_t byte_offset = ((uint64_t)ip->extra[pd + 2] << 32) | ip->extra[pd + 3]; if (ptr_ty >= ip->items_len || ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) return IP_PN_BAD_PTR_TYPE; uint32_t td = ip->item_datas[ptr_ty]; if (td > ip->extra_len || ip->extra_len - td < IP_TYPE_POINTER_LEN) return IP_PN_PTR_PAYLOAD_OOB; uint32_t flags = ip->extra[td + IP_TYPE_POINTER_FLAGS]; uint32_t sentinel = ip->extra[td + IP_TYPE_POINTER_SENTINEL]; uint32_t packed_offset = ip->extra[td + IP_TYPE_POINTER_PACKED_OFFSET]; // PtrNav's base type is any pointer except `.slice`; slices have their // own PtrSlice value representation. if (IP_PTR_FLAGS_SIZE(flags) == IP_PTR_SIZE_SLICE) return IP_PN_BAD_PTR_TYPE; if (nav_idx >= ip->navs_len) return IP_PN_BAD_NAV; const IpNav* nav = ipGetNav(ip, nav_idx); if (!nav->has_resolved || !nav->modifiers_resolved || !ipIndexIsType(ip, nav->resolved_type)) return IP_PN_UNRESOLVED_TYPE; uint32_t child_ty = ip->extra[td + IP_TYPE_POINTER_CHILD]; if (!ipIndexIsType(ip, child_ty)) return IP_PN_BAD_CHILD; if (out) { out->ptr_type = ptr_ty; out->nav_index = nav_idx; out->nav_type = nav->resolved_type; out->child_type = child_ty; out->ptr_sentinel = sentinel; out->ptr_flags = flags; out->packed_offset = packed_offset; out->byte_offset = byte_offset; } return IP_PN_OK; } IpPtrNavStatus ipDecodePtrNav( const InternPool* ip, uint32_t value, IpPtrNav* out) { IpPtrNav decoded; IpPtrNavStatus status = ipDecodePtrNavAny(ip, value, &decoded); if (status != IP_PN_OK) return status; if (decoded.ptr_sentinel != IP_INDEX_NONE) return IP_PN_HAS_SENTINEL; if (IP_PTR_FLAGS_SIZE(decoded.ptr_flags) != IP_PTR_SIZE_ONE) return IP_PN_NOT_ONE; if (decoded.packed_offset != 0) return IP_PN_PACKED; if (out) *out = decoded; return IP_PN_OK; } uint32_t ipGetOnlyPossibleValue(InternPool* ip, uint32_t ty) { // Mirror InternPool.get for the only-possible-value of a fully-comptime // type: item.data = the type Index (see the preinterned empty-tuple opv at // index 127, data=107). Dedup by type. uint32_t found = ipDedupFind(ip, IP_TAG_ONLY_POSSIBLE_VALUE, &ty, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_ONLY_POSSIBLE_VALUE, ty); ipDedupInsert(ip, IP_TAG_ONLY_POSSIBLE_VALUE, idx, &ty, 1); return idx; } uint32_t ipGetOptionalType(InternPool* ip, uint32_t child) { // Mirror InternPool.get for `.opt_type` (Key.optional): the child type // Index is stored directly in item.data (see the preinterned `?type` at // index 53 and `?noreturn` at 103). getOrPutKey dedup: scan existing // type_optional items for the same child. uint32_t found = ipDedupFind(ip, IP_TAG_TYPE_OPTIONAL, &child, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_TYPE_OPTIONAL, child); ipDedupInsert(ip, IP_TAG_TYPE_OPTIONAL, idx, &child, 1); return idx; } uint32_t ipGetOptNull(InternPool* ip, uint32_t opt_ty) { // `.opt_null` value: item.data = the optional type Index. Dedup on the // optional type (there is exactly one none value per optional type). uint32_t found = ipDedupFind(ip, IP_TAG_OPT_NULL, &opt_ty, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_OPT_NULL, opt_ty); ipDedupInsert(ip, IP_TAG_OPT_NULL, idx, &opt_ty, 1); return idx; } // InternPool.get(.aggregate)'s asserts: an array's or vector's `len` or // `len_including_sentinel` elements have its child type and end in its // sentinel, a tuple's or struct's have their field types. `repeated` is // `.repeated_elem` storage, elems[0] standing for all `len`. static void ipAssertAggregateElems(const InternPool* ip, uint32_t ty, const uint32_t* elems, uint32_t len, bool repeated) { IpArrayInfo ai; if (ipArrayInfo(ip, ty, &ai) == IP_ARR_OK) { if (len == 0) return; if (repeated ? ai.sentinel != IP_INDEX_NONE && elems[0] != ai.sentinel : len != ai.len && (len != ai.storage_len || elems[ai.len] != ai.sentinel)) UNREACHABLE("InternPool.get: aggregate length or sentinel is not " "its array type's"); for (uint32_t j = 0; j < (repeated ? 1u : ai.len); j++) ipAssertTypeOf(ip, elems[j], ai.child, "InternPool.get: array element not of its child type"); return; } bool is_tuple = ipItemTagIs(ip, ty, IP_TAG_TYPE_TUPLE); if (!is_tuple && (ty >= ip->items_len || !ipTagIsAnyStruct(ip->item_tags[ty]) || ipStructIsPacked(ip, ty))) UNREACHABLE("InternPool.get: aggregate of a type that is not an " "array, vector, tuple or non-packed struct"); for (uint32_t j = 0; j < len; j++) ipAssertTypeOf(ip, elems[repeated ? 0u : j], is_tuple ? ipTupleFieldType(ip, ty, j) : ipStructFieldType(ip, ty, j), "InternPool.get: field value not of its field type"); } uint32_t ipGetOptPayload(InternPool* ip, uint32_t opt_ty, uint32_t payload) { // `.opt_payload` value: extra { opt_ty, payload_val }. Dedup on both. // `"assert(opt.val == .none or ip.indexToKey(opt.ty).opt_type == // ip.typeOf(opt.val));"` if (!ipItemTagIs(ip, opt_ty, IP_TAG_TYPE_OPTIONAL)) UNREACHABLE("InternPool.get: opt of a non-optional type"); ipAssertTypeOf(ip, payload, ipOptionalChild(ip, opt_ty), "InternPool.get: optional payload not of its child type"); const uint32_t words[2] = { opt_ty, payload }; uint32_t found = ipDedupFind(ip, IP_TAG_OPT_PAYLOAD, words, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_OPT_PAYLOAD, off); ipDedupInsert(ip, IP_TAG_OPT_PAYLOAD, idx, words, 2); return idx; } uint32_t ipGetVectorType(InternPool* ip, uint32_t len, uint32_t child) { // Mirror InternPool.get for `.vector_type` (Tag payload `Vector` = {len, // child}, 2 words). getOrPutKey dedup: identical vector types reuse one // entry. Reached by comptime `@Vector(len, T)` (std.simd.iota). const uint32_t words[2] = { len, child }; uint32_t found = ipDedupFind(ip, IP_TAG_TYPE_VECTOR, words, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_TYPE_VECTOR, off); ipDedupInsert(ip, IP_TAG_TYPE_VECTOR, idx, words, 2); return idx; } // The find half of `ipGetSliceType`'s getOrPutKey, with no PUT: // IP_INDEX_NONE when the slice type is not already interned. static uint32_t ipFindSliceType(const InternPool* ip, uint32_t many_ptr) { return ipDedupFind(ip, IP_TAG_TYPE_SLICE, &many_ptr, 1, true); } uint32_t ipGetSliceType(InternPool* ip, uint32_t many_ptr) { // Tag.type_slice stores the many-item pointer type Index directly in // item.data (see the preinterned `[]const u8` at index 49, whose data is // the `[*]const u8` pointer 47). getOrPutKey dedup: scan existing // type_slice items for the same many-pointer. uint32_t found = ipFindSliceType(ip, many_ptr); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_TYPE_SLICE, many_ptr); ipDedupInsert(ip, IP_TAG_TYPE_SLICE, idx, &many_ptr, 1); return idx; } uint32_t ipGetAnyerrorUnionType(InternPool* ip, uint32_t payload) { // Mirror InternPool.get for `.error_union_type` when the error set is // `anyerror`: emits `type_anyerror_union` with item.data = payload type // Index (InternPool.zig). See the preinterned `anyerror!void` at // index 104. getOrPutKey dedup: scan existing items for the same payload. uint32_t found = ipDedupFind(ip, IP_TAG_TYPE_ANYERROR_UNION, &payload, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_TYPE_ANYERROR_UNION, payload); ipDedupInsert(ip, IP_TAG_TYPE_ANYERROR_UNION, idx, &payload, 1); return idx; } // InternPool.zig:getErrorSetType // `ip.getIfExists` of an error set type, `names` in any order. uint32_t ipErrorSetTypeIfExists( const InternPool* ip, uint32_t* names, uint32_t names_len) { for (uint32_t a = 1; a < names_len; a++) { uint32_t key = names[a], b = a; for (; b > 0 && names[b - 1] > key; b--) names[b] = names[b - 1]; names[b] = key; } return ipErrorSetDedupFind(ip, names_len, names); } uint32_t ipGetErrorSetType( InternPool* ip, const uint32_t* names, uint32_t names_len) { // Mirror InternPool.getErrorSetType: extra layout is Tag.ErrorSet // { names_len, names_map } (2 words) followed by `names_len` trailing // NullTerminatedString words. `names` must be pre-sorted by string index. // Dedup by {names_len, the trailing name list}. (The names_map value is // not part of the dumped item.data, so it does not affect the // byte-identical gate.) // // Indexed through a dedicated table // (`errset_dedup_*`), not the generic `ipDedupFind`/`ipDedupInsert`: // the key {names_len, names...} is not contiguous in `extra` (names_map // sits between names_len and the name array), so it can't use the // generic helpers, which assume the key is exactly the `n` words // starting at item_datas[idx]. `ipErrorSetDedupFind`/`Insert` (above) // know to skip the names_map word. Single creator: this getter is the // only `ipAddItem(ip, IP_TAG_TYPE_ERROR_SET, ...)` in this file, so // find-before-append keeps first match wins. // // zirErrorSetDecl: `"_ = try pt.getErrorValue(name);"` per name. for (uint32_t i = 0; i < names_len; i++) ipGetErrorValue(ip, names[i]); // `"const names_map = try ip.addMap(...)"` precedes the lookup and // `.existing` reverts only `extra`: a hit consumes a map slot too. uint32_t names_map = ipAddMap(ip); uint32_t found = ipErrorSetDedupFind(ip, names_len, names); if (found != IP_INDEX_NONE) return found; uint32_t off = ip->extra_len; const uint32_t hdr[2] = { names_len, names_map }; ipAddExtra(ip, hdr, 2); if (names_len) ipAddExtra(ip, names, names_len); uint32_t idx = ipAddItem(ip, IP_TAG_TYPE_ERROR_SET, off); ipErrorSetDedupInsert(ip, idx, names_len, names); return idx; } uint32_t ipGetErrorUnionType( InternPool* ip, uint32_t error_set, uint32_t payload) { // Mirror InternPool.get for `.error_union_type`: Key.ErrorUnionType = // { error_set_type, payload_type } (2 words). Dedup by {error_set, // payload}. The two inferred-error-set creators insert their directly // appended error-union items into this same index, so the lookup keeps the // old first-match semantics without scanning the full item array. const uint32_t words[2] = { error_set, payload }; uint32_t found = ipDedupFind(ip, IP_TAG_TYPE_ERROR_UNION, words, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_TYPE_ERROR_UNION, off); ipDedupInsert(ip, IP_TAG_TYPE_ERROR_UNION, idx, words, 2); return idx; } // InternPool.zig:getErrorValueIfExists uint32_t ipGetErrorValueIfExists(const InternPool* ip, uint32_t name) { return name < ip->string_error_value_cap ? ip->string_error_value[name] : 0u; } // InternPool.zig:getErrorValue uint32_t ipGetErrorValue(InternPool* ip, uint32_t name) { uint32_t v = ipGetErrorValueIfExists(ip, name); if (v != 0 || name == 0) // `"if (name == .empty) return .none;"` return v; if (ip->global_error_names_len >= UINT16_MAX) UNIMPLEMENTED("getErrorValue: global error set exceeds ErrorInt"); if (name >= ip->string_error_value_cap) { uint32_t cap = ip->strings_cap > name ? ip->strings_cap : name + 1u; uint32_t* map = zig1Realloc(ip->string_error_value, cap * sizeof(*map)); memset(map + ip->string_error_value_cap, 0, (cap - ip->string_error_value_cap) * sizeof(*map)); ip->string_error_value = map; ip->string_error_value_cap = cap; } if (ip->global_error_names_len == ip->global_error_names_cap) { ip->global_error_names_cap = ip->global_error_names_cap * 2u + 16u; ip->global_error_names = zig1Realloc(ip->global_error_names, ip->global_error_names_cap * sizeof(uint32_t)); } ip->global_error_names[ip->global_error_names_len++] = name; ip->string_error_value[name] = ip->global_error_names_len; return ip->global_error_names_len; } uint32_t ipGetErrorSetError(InternPool* ip, uint32_t ty, uint32_t name) { // Mirror InternPool.get for `.err`: Key.Error = { ty, name } (2 words). // Dedup by {ty, name}. Every error value's name is in the global error // set (Sema registers it with `pt.getErrorValue` before interning). ipGetErrorValue(ip, name); const uint32_t words[2] = { ty, name }; uint32_t found = ipDedupFind(ip, IP_TAG_ERROR_SET_ERROR, words, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_ERROR_SET_ERROR, off); ipDedupInsert(ip, IP_TAG_ERROR_SET_ERROR, idx, words, 2); return idx; } uint32_t ipGetErrorUnionError(InternPool* ip, uint32_t ty, uint32_t name) { // Mirror InternPool.get for `.error_union` with `.err_name`: emits // `error_union_error` with Key.Error = { ty, name } (2 words). Dedup. ipGetErrorValue(ip, name); const uint32_t words[2] = { ty, name }; uint32_t found = ipDedupFind(ip, IP_TAG_ERROR_UNION_ERROR, words, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_ERROR_UNION_ERROR, off); ipDedupInsert(ip, IP_TAG_ERROR_UNION_ERROR, idx, words, 2); return idx; } uint32_t ipGetErrorUnionPayload( InternPool* ip, uint32_t eu_ty, uint32_t payload) { // `.error_union` payload value: extra { eu_ty, payload_val }. Dedup on // both. // `"assert(ip.isErrorUnionType(error_union.ty));"`. The payload-type check // is zig1's own, stricter than upstream, whose getCoerced `.error_union` // arm keeps the old payload under a new error-union type. if (!ipItemTagIs(ip, eu_ty, IP_TAG_TYPE_ERROR_UNION) && !ipItemTagIs(ip, eu_ty, IP_TAG_TYPE_ANYERROR_UNION)) UNREACHABLE("InternPool.get: error_union of a non-error-union type"); ipAssertTypeOf(ip, payload, ipErrorUnionPayload(ip, eu_ty), "InternPool.get: error-union payload not of its payload type"); const uint32_t words[2] = { eu_ty, payload }; uint32_t found = ipDedupFind(ip, IP_TAG_ERROR_UNION_PAYLOAD, words, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_ERROR_UNION_PAYLOAD, off); ipDedupInsert(ip, IP_TAG_ERROR_UNION_PAYLOAD, idx, words, 2); return idx; } // InternPool.zig:errorUnionPayload uint32_t ipErrorUnionPayload(const InternPool* ip, uint32_t eu_ty) { if (ip->item_tags[eu_ty] == IP_TAG_TYPE_ANYERROR_UNION) return ip->item_datas[eu_ty]; // payload stored directly in item.data // Named `type_error_union`: extra = { error_set_type, payload_type }. return ip->extra[ip->item_datas[eu_ty] + 1]; } uint32_t ipErrorUnionErrorSet(const InternPool* ip, uint32_t eu_ty) { if (ip->item_tags[eu_ty] == IP_TAG_TYPE_ANYERROR_UNION) return 36; // `anyerror` (InternPool.Index.anyerror_type) // Named `type_error_union`: extra = { error_set_type, payload_type }. return ip->extra[ip->item_datas[eu_ty]]; } // Intern a `bytes` aggregate value: `Bytes` = { ty, bytes } (2 words). `ty` // is the aggregate (array) type; `bytes` is a String index. Dedups. static uint32_t ipGetBytes(InternPool* ip, uint32_t ty, uint32_t bytes_str) { // `.bytes => |bytes| { assert(child == .u8_type); ... }` IpArrayInfo child_info; if (ipArrayInfo(ip, ty, &child_info) != IP_ARR_OK || child_info.child != ZIR_REF_U8_TYPE) UNREACHABLE("InternPool.get: bytes of a type whose child is not u8"); // Tag.bytes == `Bytes` = { ty, bytes } (2 words). `bytes` is a String // index into the string arena. Dedup by {ty, bytes}: a NUL-free string // has one index per content. uint32_t blen = ipStringLen(ip, bytes_str); const char* b = ipStringBytes(ip, bytes_str); bool embedded = memchr(b, 0, blen) != NULL; uint32_t words[2] = { ty, bytes_str }; if (!embedded) { uint32_t found = ipDedupFind(ip, IP_TAG_BYTES, words, 2, false); if (found != IP_INDEX_NONE) return found; } else { // getOrPutTrailingString drops one trailing NUL, the sentinel's // when the type has a zero sentinel, and the item keeps that `klen`. IpArrayInfo bai; bool zero_sent = ipArrayInfo(ip, ty, &bai) == IP_ARR_OK && bai.sentinel == ZIR_REF_ZERO_U8; uint32_t klen = zero_sent || b[blen - 1] != 0 ? blen : blen - 1u; // An embedded-NUL string is never deduped, so the aggregate key is // compared by its stored bytes (`Key.eql`'s `.bytes` arm). for (uint32_t i = 0; i < ip->items_len; i++) { if (ip->item_tags[i] != IP_TAG_BYTES || ip->extra[ip->item_datas[i]] != ty) continue; uint32_t os = ip->extra[ip->item_datas[i] + 1]; if (ipStringLen(ip, os) == klen && memcmp(ipStringBytes(ip, os), b, klen) == 0) return i; } // A new `bytes` item re-interns its bytes (`"const string = try // ip.getOrPutTrailingString(gpa, io, tid, len_including_sentinel, // .maybe_embedded_nulls);"`), which appends again. char* copy = zig1Malloc((size_t)blen + 1u); memcpy(copy, b, blen + 1u); words[1] = ipGetOrPutString(ip, copy, klen); free(copy); } uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_BYTES, off); ipDedupInsert(ip, IP_TAG_BYTES, idx, words, 2); return idx; } uint32_t ipInternBytesAggregate( InternPool* ip, uint32_t ty, uint32_t bytes_str, uint32_t len) { // InternPool.get(.aggregate), `.bytes` storage branch: check whether // every byte equals the first ("bytes.toSlice(len_including_sentinel, // ip)[1..]" all-equal scan), the type's sentinel byte included. const char* bytes = ipStringBytes(ip, bytes_str); bool all_equal = true; IpArrayInfo ai; uint64_t sv; // The `"if (len == 0)"` only_possible_value early-out; `len` excludes // the sentinel. if (ipArrayInfo(ip, ty, &ai) == IP_ARR_OK && ai.len == 0) return ipGetOnlyPossibleValue(ip, ty); if (len > 0 && ipArrayInfo(ip, ty, &ai) == IP_ARR_OK && ai.sentinel != IP_INDEX_NONE) all_equal = ipTryReadIntLow64Bits(ip, ai.sentinel, &sv) && sv == (uint8_t)bytes[0]; for (uint32_t j = 1; all_equal && j < len; j++) { if (bytes[j] != bytes[0]) { all_equal = false; break; } } if (len > 0 && all_equal) { // Intern the single repeated byte, then `repeated{ty, elem_val}` // (dedup scan mirrors ipGetAggregate's identical repeated block). uint32_t elem = ipGetIntU8(ip, (uint8_t)bytes[0]); return ipGetRepeated(ip, ty, elem); } return ipGetBytes(ip, ty, bytes_str); } uint32_t ipGetAggregate( InternPool* ip, uint32_t ty, const uint32_t* elems, uint32_t len) { // Tag.aggregate == `Aggregate` = { ty } (1 word) followed by the caller's // element `Index` words and, when the type carries a sentinel, one more // word holding it (InternPool.zig, extraDataTrail; the writer appends // `"extra.appendAssumeCapacity(.{@intFromEnum(sentinel)});"`). Dedup by // the whole payload. // // Every decision below reads its length off the type for an array/vector // sequence; a struct/tuple, where `ipArrayInfo` declines, falls back to // the caller's `len`. Upstream's own sequence length is // `"ip.aggregateTypeLen(aggregate.ty);"`, which excludes the sentinel, // while `elems` may or may not carry the sentinel as a trailing element. // The other legal shape carries `"len_including_sentinel"` elements, what // `zirArrayInit` builds; both shapes occur in zig1 -- with-sentinel // dominant in the compiler. ipAssertAggregateElems(ip, ty, elems, len, false); IpArrayInfo ai; uint32_t nelems = len; // aggregateTypeLen uint32_t child = IP_INDEX_NONE; // `.none` for a struct/tuple uint32_t sentinel = IP_INDEX_NONE; if (ipArrayInfo(ip, ty, &ai) == IP_ARR_OK) { nelems = ai.len; child = ai.child; sentinel = ai.sentinel; } // InternPool.get(.aggregate) `len == 0` early-out (InternPool.zig ~7854): // a zero-length aggregate is interned as `only_possible_value(ty)`, // unconditionally — never as an empty `aggregate` item (see the // preinterned empty-tuple opv at index 127). E.g. the `[0]T` array value // behind `ArrayList(T).empty`'s `&.{}`. `nelems == 0` is upstream's test; // `len == 0` is zig1's own caller convention (an empty aggregate is // passed as `NULL, 0`) and is kept so those callers never index `elems`. if (len == 0 || nelems == 0) return ipGetOnlyPossibleValue(ip, ty); // `.tuple_type => |tuple_type| opv:`: elements equal to the tuple's // comptime field values are the type's only possible value. if (ipItemTagIs(ip, ty, IP_TAG_TYPE_TUPLE)) { uint32_t j = 0; while (j < len && elems[j] == ipTupleFieldValue(ip, ty, j)) j++; if (j == len) return ipGetOnlyPossibleValue(ip, ty); } // // InternPool.get(.aggregate) `repeated` optimization (checked before the // u8-bytes path, matching upstream order): when every element is the same // value, store a single `repeated` value = Repeated{ ty, elem_val } rather // than an N-element aggregate. A 1-element aggregate is trivially // repeated. Upstream scans // `"elems[1..@intCast(len_including_sentinel)]"`: the type's sentinel is // part of the uniformity test. { bool all_equal = sentinel == IP_INDEX_NONE || sentinel == elems[0]; for (uint32_t j = 1; all_equal && j < nelems; j++) if (elems[j] != elems[0]) { all_equal = false; break; } if (all_equal) { uint32_t elem = elems[0]; return ipGetRepeated(ip, ty, elem); } } // InternPool.get(.aggregate) byte optimization: when the element type is // `u8` (index 2), a non-repeated element run is stored as a `bytes` value // (the element ints packed into a string) rather than an `aggregate`. // Upstream's gate is on the child alone, // `"if (child == .u8_type) bytes: {"`, so it fires for a sentineled // `[N:0]u8` and a `@Vector(N, u8)` just as it does for a plain `[N]u8`; // `child` above comes from the same array/vector decode upstream's // `switch (ty_key)` performs. Upstream additionally appends the sentinel // byte and interns a string of `"@intCast(len_including_sentinel),"` // bytes. if (child == 2 /* u8_type */) { // The byte-packing optimization applies only when every element is a // defined integer. Sema/InternPool.get breaks out of the `bytes:` // block on an `.undef` element and stores a regular `aggregate` // instead (a partially-initialized comptime `var [N]u8 = undefined`, // e.g. pdqsort's `offsets_l` scratch array with only some slots // written) — scan first and fall through to the aggregate path if any // element is not a defined int. A defined byte value may be carried as // `int_u8` (data holds the value) or `int_small { ty, value }` (a // u8-typed comptime `@intCast` result); stock reads either via // `int.storage.u64` (`else => unreachable` for anything but // int/undef). int_small(u8,V) packs to the same byte V as // int_u8(V). bool packable = true; for (uint32_t j = 0; j < nelems; j++) { IpTag t = ip->item_tags[elems[j]]; if (t != IP_TAG_INT_U8 && t != IP_TAG_INT_SMALL) { packable = false; break; } } if (packable) { // `nelems` bytes, i.e. Without the sentinel. Upstream appends the // sentinel byte too and interns a `len_including_sentinel` string; // zig1's string arena NUL-terminates every entry and its dedup // ignores everything from the first NUL on, so a with-sentinel // query can never dedup against a without-sentinel one. The // sentinel-EXCLUDING form is the convention every other zig1 // `bytes` mint already uses (`ipInternBytesAggregate` off a string // literal, whose `[N:0]u8` value must stay the same item as the // one built element-by-element here), so the choice is value // identity, not style. The stored bytes still agree with // upstream's for a zero sentinel: the arena's own terminator // occupies the slot upstream fills with the sentinel byte. char* buf = (char*)zig1Malloc(nelems); for (uint32_t j = 0; j < nelems; j++) { uint32_t v = (ip->item_tags[elems[j]] == IP_TAG_INT_U8) ? ip->item_datas[elems[j]] : ip->extra[ip->item_datas[elems[j]] + 1]; // int_small buf[j] = (char)(uint8_t)v; } uint32_t str = ipGetOrPutString(ip, buf, nelems); free(buf); return ipGetBytes(ip, ty, str); } // else: an undef (or non-int) element -> store a regular aggregate. } // Index-accelerated: key is {ty, elems[0..len), sentinel?}, contiguous // with the `extra` layout below, but `elems` isn't contiguous with `ty` in // the CALLER's memory, so the find needs a temp buffer assembled up front // (freed again once used; the eventual insert reuses the live `extra` // slice instead of keeping it). The sentinel word is appended // unconditionally when the type has one, exactly as upstream does — so a // caller that already passed the sentinel as its last element produces a // payload carrying it twice, of which `indexToKey` reads only the first // (`"ip.aggregateTypeLenIncludingSentinel(extra.data.ty));"`). uint32_t n = 1 + len + (sentinel != IP_INDEX_NONE ? 1u : 0u); uint32_t* key = (uint32_t*)zig1Malloc((size_t)n * sizeof(uint32_t)); key[0] = ty; for (uint32_t j = 0; j < len; j++) key[1 + j] = elems[j]; if (sentinel != IP_INDEX_NONE) key[1 + len] = sentinel; uint32_t found = ipDedupFind(ip, IP_TAG_AGGREGATE, key, n, false); free(key); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, &ty, 1); if (len) ipAddExtra(ip, elems, len); if (sentinel != IP_INDEX_NONE) ipAddExtra(ip, &sentinel, 1); uint32_t idx = ipAddItem(ip, IP_TAG_AGGREGATE, off); ipDedupInsert(ip, IP_TAG_AGGREGATE, idx, &ip->extra[off], n); return idx; } uint32_t ipGetRepeated(InternPool* ip, uint32_t ty, uint32_t elem) { // InternPool.get(.aggregate)'s `"if (len == 0)"` only_possible_value // holds for `repeated_elem` storage too; `len` excludes the sentinel. IpArrayInfo ai; ipAssertAggregateElems( ip, ty, &elem, ipAggregateTypeLenIncludingSentinel(ip, ty), true); if (ipArrayInfo(ip, ty, &ai) == IP_ARR_OK && ai.len == 0) return ipGetOnlyPossibleValue(ip, ty); const uint32_t rep[2] = { ty, elem }; uint32_t found = ipDedupFind(ip, IP_TAG_REPEATED, rep, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, rep, 2); uint32_t idx = ipAddItem(ip, IP_TAG_REPEATED, off); ipDedupInsert(ip, IP_TAG_REPEATED, idx, rep, 2); return idx; } uint32_t ipGetPtrUav(InternPool* ip, uint32_t ty, uint32_t val) { // Tag.ptr_uav == `PtrUav` = { ty, val, byte_offset_a, byte_offset_b } // (byte_offset == 0). Dedup by {ty, val, 0, 0}. const uint32_t words[4] = { ty, val, 0, 0 }; uint32_t found = ipDedupFind(ip, IP_TAG_PTR_UAV, words, 4, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, IP_TYPE_POINTER_LEN); uint32_t idx = ipAddItem(ip, IP_TAG_PTR_UAV, off); ipDedupInsert(ip, IP_TAG_PTR_UAV, idx, words, 4); return idx; } uint32_t ipGetPtrUavAligned( InternPool* ip, uint32_t ty, uint32_t val, uint32_t orig_ty) { return ipGetPtrUavAlignedOffset(ip, ty, val, orig_ty, 0); } // Offset-carrying variants: a comptime pointer into an unnamed aggregate at // `byte_offset` (base_addr `.uav`, nonzero byte offset — e.g. `a[start..end]` // keeps the same pointee `val` but shifts the pointer). `byte_offset == 0` // yields exactly the words the non-offset variants above produce. static uint32_t ipGetPtrUavOffset( InternPool* ip, uint32_t ty, uint32_t val, uint64_t byte_offset) { // Tag.ptr_uav == `PtrUav` = { ty, val, byte_offset_a, byte_offset_b }. // A comptime `*const [N]T` pointer into an aggregate at `byte_offset` // (e.g. the result of slicing `a[start..end]`: base_addr stays the same // unnamed-aggregate `val`, only the pointee type and byte offset change). // Dedup by {ty, val, off_hi, off_lo}. uint32_t off_lo = (uint32_t)(byte_offset & 0xFFFFFFFFu); uint32_t off_hi = (uint32_t)(byte_offset >> 32); const uint32_t words[4] = { ty, val, off_hi, off_lo }; uint32_t found = ipDedupFind(ip, IP_TAG_PTR_UAV, words, 4, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, IP_TYPE_POINTER_LEN); uint32_t idx = ipAddItem(ip, IP_TAG_PTR_UAV, off); ipDedupInsert(ip, IP_TAG_PTR_UAV, idx, words, 4); return idx; } uint32_t ipGetPtrUavAlignedOffset(InternPool* ip, uint32_t ty, uint32_t val, uint32_t orig_ty, uint64_t byte_offset) { // Tag.ptr_uav_aligned == `PtrUavAligned` = // { ty, val, orig_ty, byte_offset_a, byte_offset_b }. // Dedup by {ty, val, orig_ty, off_hi, off_lo}. `byte_offset == 0` yields // exactly the same words the non-offset `ipGetPtrUavAligned` produced, so // existing callers are byte-identical. uint32_t off_lo = (uint32_t)(byte_offset & 0xFFFFFFFFu); uint32_t off_hi = (uint32_t)(byte_offset >> 32); const uint32_t words[5] = { ty, val, orig_ty, off_hi, off_lo }; uint32_t found = ipDedupFind(ip, IP_TAG_PTR_UAV_ALIGNED, words, 5, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 5); uint32_t idx = ipAddItem(ip, IP_TAG_PTR_UAV_ALIGNED, off); ipDedupInsert(ip, IP_TAG_PTR_UAV_ALIGNED, idx, words, 5); return idx; } uint32_t ipGetPtrUavRetypedOffset(InternPool* ip, uint32_t dest_ty, uint32_t backing, uint32_t orig_ty, uint64_t byte_offset) { if (dest_ty >= ip->items_len || orig_ty >= ip->items_len || ip->item_tags[dest_ty] != IP_TAG_TYPE_POINTER || ip->item_tags[orig_ty] != IP_TAG_TYPE_POINTER) UNREACHABLE("ipGetPtrUavRetypedOffset: non-pointer type"); uint32_t dd = ip->item_datas[dest_ty]; uint32_t od = ip->item_datas[orig_ty]; uint32_t dest_align = IP_PTR_FLAGS_ALIGN(ip->extra[dd + IP_TYPE_POINTER_FLAGS]); uint32_t orig_align = IP_PTR_FLAGS_ALIGN(ip->extra[od + IP_TYPE_POINTER_FLAGS]); bool same_alignment = dest_ty == orig_ty || (dest_align == orig_align && (ip->extra[dd + IP_TYPE_POINTER_CHILD] == ip->extra[od + IP_TYPE_POINTER_CHILD] || dest_align != IP_PTR_ALIGN_NONE)); return same_alignment ? ipGetPtrUavOffset(ip, dest_ty, backing, byte_offset) : ipGetPtrUavAlignedOffset(ip, dest_ty, backing, orig_ty, byte_offset); } uint64_t ipPtrUavByteOffset(const InternPool* ip, uint32_t ptr_uav) { if (ptr_uav >= ip->items_len) UNREACHABLE("ipPtrUavByteOffset: malformed InternPool item"); uint8_t tag = ip->item_tags[ptr_uav]; uint32_t high_word; uint32_t words; if (tag == IP_TAG_PTR_UAV) { high_word = 2; words = 4; } else if (tag == IP_TAG_PTR_UAV_ALIGNED) { high_word = 3; words = 5; } else { UNREACHABLE("ipPtrUavByteOffset: malformed InternPool item"); } uint32_t d = ip->item_datas[ptr_uav]; if (d > ip->extra_len || ip->extra_len - d < words) UNREACHABLE("ipPtrUavByteOffset: malformed InternPool item"); return ((uint64_t)ip->extra[d + high_word] << 32) | ip->extra[d + high_word + 1]; } uint32_t ipPtrUavValue(const InternPool* ip, uint32_t ptr_uav) { if (ptr_uav >= ip->items_len || (ip->item_tags[ptr_uav] != IP_TAG_PTR_UAV && ip->item_tags[ptr_uav] != IP_TAG_PTR_UAV_ALIGNED)) UNREACHABLE("ipPtrUavValue: malformed InternPool item"); uint32_t d = ip->item_datas[ptr_uav]; if (d > ip->extra_len || ip->extra_len - d < 2) UNREACHABLE("ipPtrUavValue: malformed InternPool item"); return ip->extra[d + 1]; } uint32_t ipGetPtrComptimeField( InternPool* ip, uint32_t ty, uint32_t field_val) { return ipGetPtrComptimeFieldOffset(ip, ty, field_val, 0); } static uint32_t ipGetPtrComptimeFieldOffset( InternPool* ip, uint32_t ty, uint32_t field_val, uint64_t byte_offset) { uint32_t off_lo = (uint32_t)byte_offset; uint32_t off_hi = (uint32_t)(byte_offset >> 32); const uint32_t words[4] = { ty, field_val, off_hi, off_lo }; uint32_t found = ipDedupFind(ip, IP_TAG_PTR_COMPTIME_FIELD, words, 4, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, IP_TYPE_POINTER_LEN); uint32_t idx = ipAddItem(ip, IP_TAG_PTR_COMPTIME_FIELD, off); ipDedupInsert(ip, IP_TAG_PTR_COMPTIME_FIELD, idx, words, 4); return idx; } bool ipIsPtrComptimeField(const InternPool* ip, uint32_t v) { return ipItemTagIs(ip, v, IP_TAG_PTR_COMPTIME_FIELD); } uint32_t ipPtrComptimeFieldValue(const InternPool* ip, uint32_t v) { if (!ipIsPtrComptimeField(ip, v)) UNREACHABLE("ipPtrComptimeFieldValue: not a ptr_comptime_field"); uint32_t off = ip->item_datas[v]; if (off > ip->extra_len || ip->extra_len - off < 4u) UNREACHABLE("ipPtrComptimeFieldValue: malformed payload"); return ip->extra[off + 1]; } static uint64_t ipPtrComptimeFieldByteOffset( const InternPool* ip, uint32_t v) { if (!ipIsPtrComptimeField(ip, v)) UNREACHABLE("ipPtrComptimeFieldByteOffset: not a ptr_comptime_field"); uint32_t off = ip->item_datas[v]; if (off > ip->extra_len || ip->extra_len - off < 4u) UNREACHABLE("ipPtrComptimeFieldByteOffset: malformed payload"); return ((uint64_t)ip->extra[off + 2] << 32) | ip->extra[off + 3]; } uint32_t ipGetPtrInt(InternPool* ip, uint32_t ty, uint64_t address) { uint32_t off_lo = (uint32_t)address; uint32_t off_hi = (uint32_t)(address >> 32); const uint32_t words[3] = { ty, off_hi, off_lo }; uint32_t found = ipDedupFind(ip, IP_TAG_PTR_INT, words, 3, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 3); uint32_t idx = ipAddItem(ip, IP_TAG_PTR_INT, off); ipDedupInsert(ip, IP_TAG_PTR_INT, idx, words, 3); return idx; } bool ipIsPtrInt(const InternPool* ip, uint32_t v) { return ipItemTagIs(ip, v, IP_TAG_PTR_INT); } uint64_t ipPtrIntAddress(const InternPool* ip, uint32_t v) { if (!ipIsPtrInt(ip, v)) UNREACHABLE("ipPtrIntAddress: not a ptr_int"); uint32_t off = ip->item_datas[v]; if (off > ip->extra_len || ip->extra_len - off < 3u) UNREACHABLE("ipPtrIntAddress: malformed payload"); return ((uint64_t)ip->extra[off + 1] << 32) | ip->extra[off + 2]; } uint32_t ipGetPtrSlice( InternPool* ip, uint32_t ty, uint32_t ptr, uint32_t len) { // Tag.ptr_slice == `PtrSlice` = { ty, ptr, len } (3 words). `ptr` is a // many-pointer value; `len` is a usize value. Dedup by {ty, ptr, len}. // `.slice`'s asserts: a slice type and a usize len. The ptr's exact // many-pointer type is zig1's own check; upstream asserts only `.many`. if (!ipItemTagIs(ip, ty, IP_TAG_TYPE_SLICE)) UNREACHABLE("InternPool.get: slice of a non-slice type"); ipAssertTypeOf(ip, ptr, ip->item_datas[ty], "InternPool.get: slice ptr not of its many-pointer type"); ipAssertTypeOf( ip, len, ZIR_REF_USIZE_TYPE, "InternPool.get: slice len not a usize"); const uint32_t words[3] = { ty, ptr, len }; uint32_t found = ipDedupFind(ip, IP_TAG_PTR_SLICE, words, 3, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 3); uint32_t idx = ipAddItem(ip, IP_TAG_PTR_SLICE, off); ipDedupInsert(ip, IP_TAG_PTR_SLICE, idx, words, 3); return idx; } // Decode accessors, the read-side counterpart // to `ipGetPtrSlice` above -- mirrors `ipIsPtrElem`'s own family (same // bounds / tag-check shape; `PtrSlice` carries no byte_offset word, so there // is no `ipPtrSliceByteOffset` counterpart). `ctCellSliceMake` produces // cell-pointer slices (its `.ptr` is the reinterpreted base or a `ptr_elem`, // upstream's two canonical shapes), and `ctCellPtrResolveDescriptorAt`'s // ptr_slice arm resolves them. bool ipIsPtrSlice(const InternPool* ip, uint32_t v) { return ipItemTagIs(ip, v, IP_TAG_PTR_SLICE); } uint32_t ipPtrSlicePtr(const InternPool* ip, uint32_t v) { if (!ipIsPtrSlice(ip, v)) UNIMPLEMENTED("Value.slicePtr: not a ptr_slice"); uint32_t xoff = ip->item_datas[v]; if (xoff > ip->extra_len || ip->extra_len - xoff < 3u) UNREACHABLE("ipPtrSlicePtr: malformed ptr_slice payload"); return ip->extra[xoff + 1]; // PtrSlice.ptr } uint32_t ipPtrSliceLen(const InternPool* ip, uint32_t v) { if (!ipIsPtrSlice(ip, v)) UNIMPLEMENTED("Value.sliceLen: not a ptr_slice"); uint32_t xoff = ip->item_datas[v]; if (xoff > ip->extra_len || ip->extra_len - xoff < 3u) UNREACHABLE("ipPtrSliceLen: malformed ptr_slice payload"); return ip->extra[xoff + 2]; // PtrSlice.len, an interned usize value } // The four upstream pointer-chain mint helpers (ptr_eu_payload, // ptr_opt_payload, ptr_elem, ptr_field), each with an `...Offset` variant. static uint32_t ipGetPtrEuPayloadOffset( InternPool* ip, uint32_t ty, uint32_t base, uint64_t byte_offset); uint32_t ipGetPtrEuPayload(InternPool* ip, uint32_t ty, uint32_t base) { return ipGetPtrEuPayloadOffset(ip, ty, base, 0); } static uint32_t ipGetPtrEuPayloadOffset( InternPool* ip, uint32_t ty, uint32_t base, uint64_t byte_offset) { // Tag.ptr_eu_payload == `PtrBase` = { ty, base, byte_offset_a, // byte_offset_b }. `base` is the interned error-union pointer this one // derives from. Dedup by {ty, base, off_hi, off_lo}. uint32_t off_lo = (uint32_t)(byte_offset & 0xFFFFFFFFu); uint32_t off_hi = (uint32_t)(byte_offset >> 32); const uint32_t words[4] = { ty, base, off_hi, off_lo }; uint32_t found = ipDedupFind(ip, IP_TAG_PTR_EU_PAYLOAD, words, 4, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, IP_TYPE_POINTER_LEN); uint32_t idx = ipAddItem(ip, IP_TAG_PTR_EU_PAYLOAD, off); ipDedupInsert(ip, IP_TAG_PTR_EU_PAYLOAD, idx, words, 4); return idx; } static uint32_t ipGetPtrOptPayloadOffset( InternPool* ip, uint32_t ty, uint32_t base, uint64_t byte_offset); uint32_t ipGetPtrOptPayload(InternPool* ip, uint32_t ty, uint32_t base) { return ipGetPtrOptPayloadOffset(ip, ty, base, 0); } static uint32_t ipGetPtrOptPayloadOffset( InternPool* ip, uint32_t ty, uint32_t base, uint64_t byte_offset) { // Tag.ptr_opt_payload == `PtrBase` = { ty, base, byte_offset_a, // byte_offset_b }. Same shape and dedup key as ipGetPtrEuPayloadOffset. uint32_t off_lo = (uint32_t)(byte_offset & 0xFFFFFFFFu); uint32_t off_hi = (uint32_t)(byte_offset >> 32); const uint32_t words[4] = { ty, base, off_hi, off_lo }; uint32_t found = ipDedupFind(ip, IP_TAG_PTR_OPT_PAYLOAD, words, 4, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, IP_TYPE_POINTER_LEN); uint32_t idx = ipAddItem(ip, IP_TAG_PTR_OPT_PAYLOAD, off); ipDedupInsert(ip, IP_TAG_PTR_OPT_PAYLOAD, idx, words, 4); return idx; } // Decode accessors, the read-side counterpart to // ipGetPtrEuPayload(Offset)/ipGetPtrOptPayload(Offset) above -- mirrors // ipIsPtrField/ipIsPtrElem's own families, minus the index accessor // neither payload tag carries: `PtrBase` is 4 words {ty, base, off_hi, // off_lo}, one word narrower than `PtrBaseIndex`'s 5 -- a payload hop has // no position to index, only a base to peel one wrapper off of. bool ipIsPtrEuPayload(const InternPool* ip, uint32_t v) { return ipItemTagIs(ip, v, IP_TAG_PTR_EU_PAYLOAD); } uint32_t ipPtrEuPayloadBase(const InternPool* ip, uint32_t v) { if (!ipIsPtrEuPayload(ip, v)) UNREACHABLE("ipPtrEuPayloadBase: not a ptr_eu_payload"); uint32_t xoff = ip->item_datas[v]; if (xoff > ip->extra_len || ip->extra_len - xoff < 4u) UNREACHABLE("ipPtrEuPayloadBase: malformed ptr_eu_payload payload"); return ip->extra[xoff + 1]; // PtrBase.base } uint64_t ipPtrEuPayloadByteOffset(const InternPool* ip, uint32_t v) { if (!ipIsPtrEuPayload(ip, v)) UNREACHABLE("ipPtrEuPayloadByteOffset: not a ptr_eu_payload"); uint32_t xoff = ip->item_datas[v]; if (xoff > ip->extra_len || ip->extra_len - xoff < 4u) UNREACHABLE( "ipPtrEuPayloadByteOffset: malformed ptr_eu_payload payload"); uint64_t off_hi = ip->extra[xoff + 2]; uint64_t off_lo = ip->extra[xoff + 3]; return (off_hi << 32) | off_lo; } bool ipIsPtrOptPayload(const InternPool* ip, uint32_t v) { return ipItemTagIs(ip, v, IP_TAG_PTR_OPT_PAYLOAD); } uint32_t ipPtrOptPayloadBase(const InternPool* ip, uint32_t v) { if (!ipIsPtrOptPayload(ip, v)) UNREACHABLE("ipPtrOptPayloadBase: not a ptr_opt_payload"); uint32_t xoff = ip->item_datas[v]; if (xoff > ip->extra_len || ip->extra_len - xoff < 4u) UNREACHABLE("ipPtrOptPayloadBase: malformed ptr_opt_payload payload"); return ip->extra[xoff + 1]; // PtrBase.base } uint64_t ipPtrOptPayloadByteOffset(const InternPool* ip, uint32_t v) { if (!ipIsPtrOptPayload(ip, v)) UNREACHABLE("ipPtrOptPayloadByteOffset: not a ptr_opt_payload"); uint32_t xoff = ip->item_datas[v]; if (xoff > ip->extra_len || ip->extra_len - xoff < 4u) UNREACHABLE( "ipPtrOptPayloadByteOffset: malformed ptr_opt_payload payload"); uint64_t off_hi = ip->extra[xoff + 2]; uint64_t off_lo = ip->extra[xoff + 3]; return (off_hi << 32) | off_lo; } uint32_t ipGetPtrElem( InternPool* ip, uint32_t ty, uint32_t base, uint32_t index) { return ipGetPtrElemOffset(ip, ty, base, index, 0); } uint32_t ipGetPtrElemOffset(InternPool* ip, uint32_t ty, uint32_t base, uint32_t index, uint64_t byte_offset) { // Tag.ptr_elem == `PtrBaseIndex` = { ty, base, index, byte_offset_a, // byte_offset_b } (5 words). `index` (an array position) is minted as an // interned usize value first -- mirrors upstream's `ip.get(.{ .int = // .{ .ty = .usize_type, .storage = .{ .u64 = base_index.index } } })` // funnel, which runs before `PtrBaseIndex.init` is built. Dedup by // {ty, base, index_val, off_hi, off_lo}. uint32_t index_val = ipGetIntUsize(ip, index); uint32_t off_lo = (uint32_t)(byte_offset & 0xFFFFFFFFu); uint32_t off_hi = (uint32_t)(byte_offset >> 32); const uint32_t words[5] = { ty, base, index_val, off_hi, off_lo }; uint32_t found = ipDedupFind(ip, IP_TAG_PTR_ELEM, words, 5, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 5); uint32_t idx = ipAddItem(ip, IP_TAG_PTR_ELEM, off); ipDedupInsert(ip, IP_TAG_PTR_ELEM, idx, words, 5); return idx; } // Decode accessors, the read-side counterpart to // ipGetPtrElem/ipGetPtrElemOffset above -- mirrors ipIsPtrField's own // family exactly (same bounds / tag-check shape, // PtrBaseIndex is the same 5-word layout for both tags). bool ipIsPtrElem(const InternPool* ip, uint32_t v) { return ipItemTagIs(ip, v, IP_TAG_PTR_ELEM); } uint32_t ipPtrElemBase(const InternPool* ip, uint32_t v) { if (!ipIsPtrElem(ip, v)) UNREACHABLE("ipPtrElemBase: not a ptr_elem"); uint32_t xoff = ip->item_datas[v]; if (xoff > ip->extra_len || ip->extra_len - xoff < 5u) UNREACHABLE("ipPtrElemBase: malformed ptr_elem payload"); return ip->extra[xoff + 1]; // PtrBaseIndex.base } uint32_t ipPtrElemIndex(const InternPool* ip, uint32_t v) { if (!ipIsPtrElem(ip, v)) UNREACHABLE("ipPtrElemIndex: not a ptr_elem"); uint32_t xoff = ip->item_datas[v]; if (xoff > ip->extra_len || ip->extra_len - xoff < 5u) UNREACHABLE("ipPtrElemIndex: malformed ptr_elem payload"); uint32_t index_val = ip->extra[xoff + 2]; // PtrBaseIndex.index, interned if (index_val >= ip->items_len || ip->item_tags[index_val] != IP_TAG_INT_USIZE) UNREACHABLE("ipPtrElemIndex: malformed interned index"); return ip->item_datas[index_val]; } uint64_t ipPtrElemByteOffset(const InternPool* ip, uint32_t v) { if (!ipIsPtrElem(ip, v)) UNREACHABLE("ipPtrElemByteOffset: not a ptr_elem"); uint32_t xoff = ip->item_datas[v]; if (xoff > ip->extra_len || ip->extra_len - xoff < 5u) UNREACHABLE("ipPtrElemByteOffset: malformed ptr_elem payload"); uint64_t off_hi = ip->extra[xoff + 3]; uint64_t off_lo = ip->extra[xoff + 4]; return (off_hi << 32) | off_lo; } uint32_t ipPtrDerivedRoot(const InternPool* ip, uint32_t v) { while (v < ip->items_len && (ip->item_tags[v] == IP_TAG_PTR_FIELD || ip->item_tags[v] == IP_TAG_PTR_ELEM || ip->item_tags[v] == IP_TAG_PTR_OPT_PAYLOAD || ip->item_tags[v] == IP_TAG_PTR_EU_PAYLOAD)) v = ip->extra[ip->item_datas[v] + 1]; return v; } static uint32_t ipGetPtrFieldOffset(InternPool* ip, uint32_t ty, uint32_t base, uint32_t index, uint64_t byte_offset); uint32_t ipGetPtrField( InternPool* ip, uint32_t ty, uint32_t base, uint32_t index) { return ipGetPtrFieldOffset(ip, ty, base, index, 0); } static uint32_t ipGetPtrFieldOffset(InternPool* ip, uint32_t ty, uint32_t base, uint32_t index, uint64_t byte_offset) { // Tag.ptr_field == `PtrBaseIndex` = { ty, base, index, byte_offset_a, // byte_offset_b } (5 words). Same index-minting convention and dedup key // shape as ipGetPtrElemOffset (`index` is a field position, not an array // position). uint32_t index_val = ipGetIntUsize(ip, index); uint32_t off_lo = (uint32_t)(byte_offset & 0xFFFFFFFFu); uint32_t off_hi = (uint32_t)(byte_offset >> 32); const uint32_t words[5] = { ty, base, index_val, off_hi, off_lo }; uint32_t found = ipDedupFind(ip, IP_TAG_PTR_FIELD, words, 5, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 5); uint32_t idx = ipAddItem(ip, IP_TAG_PTR_FIELD, off); ipDedupInsert(ip, IP_TAG_PTR_FIELD, idx, words, 5); return idx; } // Decode accessors, the read-side counterpart to // ipGetPtrField/ipGetPtrFieldOffset above. Same bounds / tag-check shape as // ipIsPtrComptimeAlloc/ipPtrComptimeAllocIndex/ipPtrComptimeAllocByteOffset // (this file, above). bool ipIsPtrField(const InternPool* ip, uint32_t v) { return ipItemTagIs(ip, v, IP_TAG_PTR_FIELD); } uint32_t ipPtrFieldBase(const InternPool* ip, uint32_t v) { if (!ipIsPtrField(ip, v)) UNREACHABLE("ipPtrFieldBase: not a ptr_field"); uint32_t xoff = ip->item_datas[v]; if (xoff > ip->extra_len || ip->extra_len - xoff < 5u) UNREACHABLE("ipPtrFieldBase: malformed ptr_field payload"); return ip->extra[xoff + 1]; // PtrBaseIndex.base } uint32_t ipPtrFieldIndex(const InternPool* ip, uint32_t v) { if (!ipIsPtrField(ip, v)) UNREACHABLE("ipPtrFieldIndex: not a ptr_field"); uint32_t xoff = ip->item_datas[v]; if (xoff > ip->extra_len || ip->extra_len - xoff < 5u) UNREACHABLE("ipPtrFieldIndex: malformed ptr_field payload"); uint32_t index_val = ip->extra[xoff + 2]; // PtrBaseIndex.index, interned // ipGetPtrField/ipGetPtrFieldOffset mint `index` exclusively via // ipGetIntUsize (IP_TAG_INT_USIZE, raw value in item_datas) -- re-checked // here, not assumed, matching this file's own defensive-decode style. if (index_val >= ip->items_len || ip->item_tags[index_val] != IP_TAG_INT_USIZE) UNREACHABLE("ipPtrFieldIndex: malformed interned index"); return ip->item_datas[index_val]; } uint64_t ipPtrFieldByteOffset(const InternPool* ip, uint32_t v) { if (!ipIsPtrField(ip, v)) UNREACHABLE("ipPtrFieldByteOffset: not a ptr_field"); uint32_t xoff = ip->item_datas[v]; if (xoff > ip->extra_len || ip->extra_len - xoff < 5u) UNREACHABLE("ipPtrFieldByteOffset: malformed ptr_field payload"); uint64_t off_hi = ip->extra[xoff + 3]; uint64_t off_lo = ip->extra[xoff + 4]; return (off_hi << 32) | off_lo; } bool ipPtrValueByteOffset( const InternPool* ip, uint32_t value, uint64_t* out_byte_offset) { if (value >= ip->items_len) return false; if (ipIsPtrComptimeAlloc(ip, value)) *out_byte_offset = ipPtrComptimeAllocByteOffset(ip, value); else if (ipIsPtrComptimeField(ip, value)) *out_byte_offset = ipPtrComptimeFieldByteOffset(ip, value); else if (ipIsPtrInt(ip, value)) *out_byte_offset = ipPtrIntAddress(ip, value); else if (ipIsPtrField(ip, value)) *out_byte_offset = ipPtrFieldByteOffset(ip, value); else if (ipIsPtrElem(ip, value)) *out_byte_offset = ipPtrElemByteOffset(ip, value); else if (ipIsPtrEuPayload(ip, value)) *out_byte_offset = ipPtrEuPayloadByteOffset(ip, value); else if (ipIsPtrOptPayload(ip, value)) *out_byte_offset = ipPtrOptPayloadByteOffset(ip, value); else if (ip->item_tags[value] == IP_TAG_PTR_UAV || ip->item_tags[value] == IP_TAG_PTR_UAV_ALIGNED) *out_byte_offset = ipPtrUavByteOffset(ip, value); else if (ip->item_tags[value] == IP_TAG_PTR_NAV) { IpPtrNav nav; if (ipDecodePtrNavAny(ip, value, &nav) != IP_PN_OK) return false; *out_byte_offset = nav.byte_offset; } else { return false; } return true; } // InternPool.Key.Ptr.BaseAddr.eql over two pointer values, decoded in place // (nothing interned): the same carrier and base, ignoring type and offset. static bool ipPtrIsUav(const InternPool* ip, uint32_t v) { return ip->item_tags[v] == IP_TAG_PTR_UAV || ip->item_tags[v] == IP_TAG_PTR_UAV_ALIGNED; } uint32_t ipPtrUavOrigTy(const InternPool* ip, uint32_t v) { return ip->item_tags[v] == IP_TAG_PTR_UAV_ALIGNED ? ip->extra[ip->item_datas[v] + 2] : ipTypeOf(ip, v); } bool ipPtrBaseAddrEql(const InternPool* ip, uint32_t a, uint32_t b) { if (a >= ip->items_len || b >= ip->items_len) return false; // `.uav => ad.val == b.uav.val and ad.orig_ty == b.uav.orig_ty`: the one // base stored under two tags. if (ipPtrIsUav(ip, a) || ipPtrIsUav(ip, b)) return ipPtrIsUav(ip, a) && ipPtrIsUav(ip, b) && ip->extra[ip->item_datas[a] + 1] == ip->extra[ip->item_datas[b] + 1] && ipPtrUavOrigTy(ip, a) == ipPtrUavOrigTy(ip, b); if (ip->item_tags[a] != ip->item_tags[b]) return false; switch ((IpTag)ip->item_tags[a]) { case IP_TAG_PTR_NAV: { IpPtrNav na, nb; return ipDecodePtrNavAny(ip, a, &na) == IP_PN_OK && ipDecodePtrNavAny(ip, b, &nb) == IP_PN_OK && na.nav_index == nb.nav_index; } default: break; } if (ipIsPtrComptimeAlloc(ip, a)) return ipPtrComptimeAllocIndex(ip, a) == ipPtrComptimeAllocIndex(ip, b); if (ipIsPtrComptimeField(ip, a)) return ipPtrComptimeFieldValue(ip, a) == ipPtrComptimeFieldValue(ip, b); if (ipIsPtrInt(ip, a)) return true; if (ipIsPtrField(ip, a)) return ipPtrFieldBase(ip, a) == ipPtrFieldBase(ip, b) && ipPtrFieldIndex(ip, a) == ipPtrFieldIndex(ip, b); if (ipIsPtrElem(ip, a)) return ipPtrElemBase(ip, a) == ipPtrElemBase(ip, b) && ipPtrElemIndex(ip, a) == ipPtrElemIndex(ip, b); if (ipIsPtrEuPayload(ip, a)) return ipPtrEuPayloadBase(ip, a) == ipPtrEuPayloadBase(ip, b); if (ipIsPtrOptPayload(ip, a)) return ipPtrOptPayloadBase(ip, a) == ipPtrOptPayloadBase(ip, b); return false; } uint32_t ipRemintPtrValue( InternPool* ip, uint32_t value, uint32_t dest_ty, uint64_t byte_offset) { if (value >= ip->items_len || dest_ty >= ip->items_len || ip->item_tags[dest_ty] != IP_TAG_TYPE_POINTER) return IP_INDEX_NONE; uint32_t src_ty = ipTypeOf(ip, value); if (src_ty >= ip->items_len || ip->item_tags[src_ty] != IP_TAG_TYPE_POINTER) return IP_INDEX_NONE; uint64_t old_byte_offset; if (src_ty == dest_ty && ipPtrValueByteOffset(ip, value, &old_byte_offset) && old_byte_offset == byte_offset) return value; if (ip->item_tags[value] == IP_TAG_PTR_UAV || ip->item_tags[value] == IP_TAG_PTR_UAV_ALIGNED) { uint32_t d = ip->item_datas[value]; uint32_t backing = ip->extra[d + 1]; uint32_t orig_ty = ip->item_tags[value] == IP_TAG_PTR_UAV_ALIGNED ? ip->extra[d + 2] : src_ty; return ipGetPtrUavRetypedOffset( ip, dest_ty, backing, orig_ty, byte_offset); } if (ip->item_tags[value] == IP_TAG_PTR_NAV) { IpPtrNav nav; if (ipDecodePtrNavAny(ip, value, &nav) != IP_PN_OK) UNREACHABLE("ipRetypePtrValue: malformed ptr_nav"); return ipGetPtrNavOffset(ip, dest_ty, nav.nav_index, byte_offset); } if (ipIsPtrComptimeAlloc(ip, value)) return ipGetPtrComptimeAllocOffset( ip, dest_ty, ipPtrComptimeAllocIndex(ip, value), byte_offset); if (ipIsPtrComptimeField(ip, value)) return ipGetPtrComptimeFieldOffset( ip, dest_ty, ipPtrComptimeFieldValue(ip, value), byte_offset); if (ipIsPtrInt(ip, value)) return ipGetPtrInt(ip, dest_ty, byte_offset); if (ipIsPtrField(ip, value)) return ipGetPtrFieldOffset(ip, dest_ty, ipPtrFieldBase(ip, value), ipPtrFieldIndex(ip, value), byte_offset); if (ipIsPtrElem(ip, value)) return ipGetPtrElemOffset(ip, dest_ty, ipPtrElemBase(ip, value), ipPtrElemIndex(ip, value), byte_offset); if (ipIsPtrEuPayload(ip, value)) return ipGetPtrEuPayloadOffset( ip, dest_ty, ipPtrEuPayloadBase(ip, value), byte_offset); if (ipIsPtrOptPayload(ip, value)) return ipGetPtrOptPayloadOffset( ip, dest_ty, ipPtrOptPayloadBase(ip, value), byte_offset); return IP_INDEX_NONE; } uint32_t ipRetypePtrValue(InternPool* ip, uint32_t value, uint32_t dest_ty) { // InternPool.getCoerced: "`.undef => return ip.get(gpa, io, tid, .{ // .undef = new_ty })`". if (value < ip->items_len && ip->item_tags[value] == IP_TAG_UNDEF) return ipGetUndef(ip, dest_ty); // Its `.opt` arm at a `.ptr_type`: null is the address 0, a payload is // coerced on. Only a C pointer takes an optional in memory, so the arm's // `.slice` leg (a type_slice destination here) is never reached. if ((ipItemTagIs(ip, value, IP_TAG_OPT_NULL) || ipItemTagIs(ip, value, IP_TAG_OPT_PAYLOAD)) && ipItemTagIs(ip, dest_ty, IP_TAG_TYPE_SLICE)) UNREACHABLE("getCoerced: an optional retyped as a slice"); if (ipItemTagIs(ip, value, IP_TAG_OPT_NULL) && ipItemTagIs(ip, dest_ty, IP_TAG_TYPE_POINTER)) return ipGetPtrInt(ip, dest_ty, 0); if (ipItemTagIs(ip, value, IP_TAG_OPT_PAYLOAD) && ipItemTagIs(ip, dest_ty, IP_TAG_TYPE_POINTER)) return ipRetypePtrValue(ip, ipPayloadVal(ip, value), dest_ty); // Its `.slice` arm: the pointer at the new slice's many type. if (value < ip->items_len && ip->item_tags[value] == IP_TAG_PTR_SLICE) { uint32_t d = ip->item_datas[value]; uint32_t many = ipRetypePtrValue(ip, ip->extra[d + 1], ip->item_datas[dest_ty]); return many == IP_INDEX_NONE ? many : ipGetPtrSlice(ip, dest_ty, many, ip->extra[d + 2]); } uint64_t byte_offset; if (!ipPtrValueByteOffset(ip, value, &byte_offset)) return IP_INDEX_NONE; return ipRemintPtrValue(ip, value, dest_ty, byte_offset); } uint32_t ipGetNullTerminatedStringSlice( InternPool* ip, uint32_t string_index) { uint32_t len = ipStringLen(ip, string_index); uint32_t array_ty = ipGetArrayType(ip, len, ZIR_REF_U8_TYPE, ZIR_REF_ZERO_U8); uint32_t bytes = ipInternBytesAggregate(ip, array_ty, string_index, len); uint32_t many_ptr = ipGetPtrUav(ip, ZIR_REF_MANYPTR_CONST_U8_SENTINEL_0_TYPE, bytes); uint32_t len_val = ipGetIntUsize(ip, len); return ipGetPtrSlice( ip, ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE, many_ptr, len_val); } uint32_t ipGetNullTerminatedBytesSlice( InternPool* ip, const char* raw_bytes, uint32_t len) { // Sema.zirBuiltinSrc's module/file arms form the array type before their // getOrPutString call. Keep that item/string insertion order exact. uint32_t array_ty = ipGetArrayType(ip, len, ZIR_REF_U8_TYPE, ZIR_REF_ZERO_U8); uint32_t string_index = ipGetOrPutString(ip, raw_bytes, len); uint32_t bytes = ipInternBytesAggregate(ip, array_ty, string_index, len); uint32_t many_ptr = ipGetPtrUav(ip, ZIR_REF_MANYPTR_CONST_U8_SENTINEL_0_TYPE, bytes); uint32_t len_val = ipGetIntUsize(ip, len); return ipGetPtrSlice( ip, ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE, many_ptr, len_val); } uint32_t ipGetIntUsize(InternPool* ip, uint32_t value) { // `.int` with ty=usize whose value fits u32 stores the value directly in // the item `data` as `int_usize` (no extra; InternPool.zig). Dedup by // tag+value. uint32_t found = ipDedupFind(ip, IP_TAG_INT_USIZE, &value, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_INT_USIZE, value); ipDedupInsert(ip, IP_TAG_INT_USIZE, idx, &value, 1); return idx; } uint32_t ipGetIntComptimeIntU32(InternPool* ip, uint32_t value) { // `.int` with ty=comptime_int whose magnitude fits u32 stores the value // directly in the item `data` as `int_comptime_int_u32` (no extra; // InternPool.zig). Dedup by tag+value; for 0/1 this reuses the static // preinterns (indices 112/116). uint32_t found = ipDedupFind(ip, IP_TAG_INT_COMPTIME_INT_U32, &value, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_INT_COMPTIME_INT_U32, value); ipDedupInsert(ip, IP_TAG_INT_COMPTIME_INT_U32, idx, &value, 1); return idx; } // Intern a negative comptime_int value whose magnitude fits i32 // (`int_comptime_int_i32`, the i32-bit-cast value stored directly in item // `data`). Dedups by value (-1 reuses the static preintern at index 121). // Mirrors InternPool.zig's `.int_comptime_int_i32` storage arm. static uint32_t ipGetIntComptimeIntI32(InternPool* ip, uint32_t value) { // Negative `comptime_int` whose magnitude fits i32 stores the // (u32-bit-cast) i32 value directly in the item `data` as // `int_comptime_int_i32` (no extra; InternPool.zig). Dedup by // tag+value; for -1 this reuses the static preintern (index 121). uint32_t found = ipDedupFind(ip, IP_TAG_INT_COMPTIME_INT_I32, &value, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_INT_COMPTIME_INT_I32, value); ipDedupInsert(ip, IP_TAG_INT_COMPTIME_INT_I32, idx, &value, 1); return idx; } uint32_t ipGetIntComptimeIntU64(InternPool* ip, uint64_t value) { // A `u64` is a one-limb `big.int` here (`Limb` is 64 bits), so this is // `get()`'s `"inline .u64, .i64 => |x| { ... BigIntMutable.init(&buf, x) // .toConst() ... }"` tail: build the limb and run the same ladder the // big-int spelling runs. There is no `int_comptime_int_u64` rung upstream. uint64_t limbs[1] = { value }; return ipGetInt( ip, ZIR_REF_COMPTIME_INT_TYPE, (BigIntConst) { limbs, 1, true }); } // (part of) InternPool.zig:indexToKeyBigInt // `InternPool.indexToKeyBigInt`: `"const int: Int = @bitCast(limbs_items // [limb_index..][0..Int.limbs_items_len].*); const big_int: BigIntConst = .{ // .limbs = limbs_items[limb_index + Int.limbs_items_len ..][0..int.limbs_len], // .positive = positive };"`. bool ipIntBigLimbs(const InternPool* ip, uint32_t idx, uint32_t* out_ty, const uint64_t** out_limbs, uint32_t* out_len, bool* out_positive) { if (idx >= ip->items_len) return false; IpTag tag = (IpTag)ip->item_tags[idx]; if (tag != IP_TAG_INT_POSITIVE && tag != IP_TAG_INT_NEGATIVE) return false; uint32_t d = ip->item_datas[idx]; uint64_t header = ip->limbs[d]; if (out_ty) *out_ty = (uint32_t)(header & 0xFFFFFFFFu); if (out_len) *out_len = (uint32_t)(header >> 32); if (out_limbs) *out_limbs = &ip->limbs[d + IP_INT_LIMBS_ITEMS_LEN]; if (out_positive) *out_positive = tag == IP_TAG_INT_POSITIVE; return true; } // The limb-backed tags' dedup key, as the shared hasher wants it: the type // then the magnitude, each limb split low word first. `words` must hold // `2 + 2 * limbs_len` entries. static void ipLimbsDedupKey( uint32_t ty, const uint64_t* limbs, uint32_t limbs_len, uint32_t* words) { words[0] = ty; words[1] = limbs_len; for (uint32_t i = 0; i < limbs_len; i++) { words[2u + i * 2u] = (uint32_t)(limbs[i] & 0xFFFFFFFFu); words[2u + i * 2u + 1u] = (uint32_t)(limbs[i] >> 32); } } // The item with (tag, ty, limbs), or IP_INDEX_NONE. Its own verifier, because // a candidate's key lives in the `limbs` arena and the shared one reads // `extra` (see the table's comment in zig1.h). static uint32_t ipLimbsDedupFind( const InternPool* ip, IpTag tag, const uint32_t* words, uint32_t n) { if (ip->limbs_dedup_cap == 0) return IP_INDEX_NONE; uint32_t h = ipDedupHashWords((uint8_t)tag, words, n); uint32_t mask = ip->limbs_dedup_cap - 1; uint32_t slot = h & mask; for (;;) { uint32_t idx = ip->limbs_dedup_slots[slot].item_idx; if (idx == IP_INDEX_NONE) return IP_INDEX_NONE; if (ip->limbs_dedup_slots[slot].hash == h && ip->limbs_dedup_slots[slot].n == n && ip->item_tags[idx] == (uint8_t)tag // The stored header limb `ty | (limbs_len << 32)` and the // magnitude limbs after it are `words[0..n)` byte for byte on // this little-endian host, so the key compares raw -- the same // comparison `ipDedupFind` makes against its `extra` words. && memcmp(&ip->limbs[ip->item_datas[idx]], words, (size_t)n * sizeof(uint32_t)) == 0) return idx; slot = (slot + 1) & mask; } } static void ipLimbsDedupInsert(InternPool* ip, IpTag tag, uint32_t item_idx, const uint32_t* words, uint32_t n) { ipDedupEnsureCapacity( &ip->limbs_dedup_slots, &ip->limbs_dedup_cap, &ip->limbs_dedup_count); uint32_t h = ipDedupHashWords((uint8_t)tag, words, n); ipDedupPlace(ip->limbs_dedup_slots, ip->limbs_dedup_cap, h, item_idx, n); ip->limbs_dedup_count++; } // InternPool.zig:addInt // `InternPool.addInt`: `"items_list.appendAssumeCapacity(.{ .tag = tag, // .data = limbs_list.mutate.len }); limbs_list.addManyAsArrayAssumeCapacity( // Int.limbs_items_len)[0].* = @bitCast(Int{ .ty = ty, .limbs_len = limbs_len // }); limbs_list.appendSliceAssumeCapacity(.{limbs});"` -- the item's `data` // is the limbs index the header lands at, read before the append. static uint32_t ipAddInt(InternPool* ip, uint32_t ty, const uint64_t* limbs, uint32_t limbs_len, bool positive) { IpTag tag = positive ? IP_TAG_INT_POSITIVE : IP_TAG_INT_NEGATIVE; uint32_t n = 2u + limbs_len * 2u; uint32_t* words = zig1Malloc((size_t)n * sizeof(uint32_t)); ipLimbsDedupKey(ty, limbs, limbs_len, words); uint32_t found = ipLimbsDedupFind(ip, tag, words, n); if (found != IP_INDEX_NONE) { free(words); return found; } ipLimbsEnsure(ip, IP_INT_LIMBS_ITEMS_LEN + limbs_len); uint32_t data = ip->limbs_len; uint32_t idx = ipAddItem(ip, tag, data); ip->limbs[ip->limbs_len++] = (uint64_t)ty | ((uint64_t)limbs_len << 32); // From the key copy, not from `limbs`: a caller may hand over limbs // borrowed from this very arena (`ipIntBigLimbs`), which `ipLimbsEnsure` // above has just invalidated. for (uint32_t i = 0; i < limbs_len; i++) ip->limbs[ip->limbs_len++] = (uint64_t)words[2u + i * 2u] | ((uint64_t)words[2u + i * 2u + 1u] << 32); ipLimbsDedupInsert(ip, tag, idx, words, n); free(words); return idx; } // InternPool.zig:get, InternPool.zig:isIntegerType // `InternPool.get()`'s `.int` arm, whole (InternPool.zig). One // minter for every integer value of every integer type: the per-type rungs // (`u8`, `u16`, `u32`, `i32`, `usize`, `comptime_int`) first, each reached // only by an exact type index; then the generic tail, which is `int_small` // when `big_int.toInt(u32)` succeeds and `addInt` -- the limb-backed // `int_positive` / `int_negative`, carrying `ty` beside the magnitude -- // otherwise. Nothing between, and no rung restated per sign: the operand is // one `big.int.Const` and carries its own. // // The caller owes `assert(ip.isIntegerType(int.ty))` and, at the four rungs // upstream spells `catch unreachable`, that the value fits the rung's width. uint32_t ipGetInt(InternPool* ip, uint32_t ty, BigIntConst big_int) { uint32_t limbs_len = big_int.limbs_len; while (limbs_len > 1u && big_int.limbs[limbs_len - 1] == 0) limbs_len--; big_int.limbs_len = limbs_len; if (limbs_len == 1u && big_int.limbs[0] == 0) big_int.positive = true; // `-0` is `0` uint32_t casted; switch (ty) { case ZIR_REF_U8_TYPE: if (!bigIntToU32(big_int, &casted) || casted > 0xFFu) UNREACHABLE("ipGetInt: u8 value out of range"); return ipGetIntU8(ip, casted); case ZIR_REF_U16_TYPE: if (!bigIntToU32(big_int, &casted) || casted > 0xFFFFu) UNREACHABLE("ipGetInt: u16 value out of range"); return ipGetIntU16(ip, casted); case ZIR_REF_U32_TYPE: if (!bigIntToU32(big_int, &casted)) UNREACHABLE("ipGetInt: u32 value out of range"); return ipGetIntU32(ip, casted); case ZIR_REF_I32_TYPE: if (!bigIntToBitCastI32(big_int, &casted)) UNREACHABLE("ipGetInt: i32 value out of range"); return ipGetIntI32(ip, casted); case ZIR_REF_USIZE_TYPE: // `usize`'s rung falls through to the generic tail when the value // does not fit a `u32`; it does not assert. if (bigIntToU32(big_int, &casted)) return ipGetIntUsize(ip, casted); break; case ZIR_REF_COMPTIME_INT_TYPE: if (bigIntToU32(big_int, &casted)) return ipGetIntComptimeIntU32(ip, casted); if (bigIntToBitCastI32(big_int, &casted)) return ipGetIntComptimeIntI32(ip, casted); break; default: break; } if (bigIntToU32(big_int, &casted)) return ipGetIntSmall(ip, ty, casted); return ipAddInt(ip, ty, big_int.limbs, limbs_len, big_int.positive); } bool ipTryReadIntLow64Bits(const InternPool* ip, uint32_t idx, uint64_t* out) { if (idx >= ip->items_len) return false; switch ((IpTag)ip->item_tags[idx]) { case IP_TAG_INT_U8: case IP_TAG_INT_U16: case IP_TAG_INT_U32: case IP_TAG_INT_USIZE: case IP_TAG_INT_COMPTIME_INT_U32: *out = (uint64_t)ip->item_datas[idx]; return true; case IP_TAG_INT_I32: case IP_TAG_INT_COMPTIME_INT_I32: *out = (uint64_t)(int64_t)(int32_t)ip->item_datas[idx]; return true; case IP_TAG_INT_SMALL: { uint32_t d = ip->item_datas[idx]; *out = (uint64_t)ip->extra[d + 1]; return true; } case IP_TAG_INT_POSITIVE: case IP_TAG_INT_NEGATIVE: { // The low limb of the magnitude, negated in two's complement when the // value is negative -- the same low-64 reading every narrower carrier // hands back. A caller that needs the whole value asks // `ipIntBigLimbs`. const uint64_t* bl; uint32_t bn; bool bpos; if (!ipIntBigLimbs(ip, idx, NULL, &bl, &bn, &bpos)) return false; *out = bpos ? bl[0] : (uint64_t)0 - bl[0]; return true; } default: return false; } } bool ipTryReadIntLow128Bits( const InternPool* ip, uint32_t idx, uint64_t* lo, uint64_t* hi) { if (!ipTryReadIntLow64Bits(ip, idx, lo)) return false; *hi = 0; if (idx >= ip->items_len) return false; switch ((IpTag)ip->item_tags[idx]) { case IP_TAG_INT_POSITIVE: case IP_TAG_INT_NEGATIVE: { // The raw low 128 bits of a magnitude-and-sign value are that value // narrowed to 128 bits read as unsigned -- `Mutable.truncate`, the // one rule that turns a `big.int.Const` into a two's-complement // pattern. BigIntConst v; if (!ipIntBigLimbs(ip, idx, NULL, &v.limbs, &v.limbs_len, &v.positive)) return false; return bigIntTruncateToU128(v, 128, lo, hi); } default: return true; } } uint32_t ipGetIntComptimeIntI64(InternPool* ip, int64_t value) { // `BigIntMutable.init(&buf, x)` for a signed `x`: sign in the flag, the // magnitude in the limb. `~w + 1` rather than `-value`, which is UB at // INT64_MIN -- the one value this arm exists for. uint64_t limbs[1] = { value >= 0 ? (uint64_t)value : ~(uint64_t)value + 1u }; return ipGetInt( ip, ZIR_REF_COMPTIME_INT_TYPE, (BigIntConst) { limbs, 1, value >= 0 }); } uint32_t ipGetIntU16(InternPool* ip, uint32_t value) { // `.int` with ty=u16 stores the value directly in the item `data` as // `int_u16` (no extra; InternPool.zig). Dedup by tag+value. uint32_t found = ipDedupFind(ip, IP_TAG_INT_U16, &value, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_INT_U16, value); ipDedupInsert(ip, IP_TAG_INT_U16, idx, &value, 1); return idx; } uint32_t ipGetFloatF16(InternPool* ip, uint32_t bits) { // `.float_f16` stores the raw half bits directly in item `data` // Dedup by the canonical 16-bit representation. bits &= 0xFFFFu; uint32_t found = ipDedupFind(ip, IP_TAG_FLOAT_F16, &bits, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_FLOAT_F16, bits); ipDedupInsert(ip, IP_TAG_FLOAT_F16, idx, &bits, 1); return idx; } bool ipReadFloatF16Bits(const InternPool* ip, uint32_t idx, uint32_t* out) { if (idx >= ip->items_len || ip->item_tags[idx] != IP_TAG_FLOAT_F16) return false; *out = ip->item_datas[idx] & 0xFFFFu; return true; } uint32_t ipGetFloatF32(InternPool* ip, uint32_t bits) { uint32_t found = ipDedupFind(ip, IP_TAG_FLOAT_F32, &bits, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_FLOAT_F32, bits); ipDedupInsert(ip, IP_TAG_FLOAT_F32, idx, &bits, 1); return idx; } bool ipReadFloatF32Bits(const InternPool* ip, uint32_t idx, uint32_t* out) { if (idx >= ip->items_len || ip->item_tags[idx] != IP_TAG_FLOAT_F32) return false; *out = ip->item_datas[idx]; return true; } uint32_t ipGetFloatF64(InternPool* ip, uint64_t bits) { const uint32_t words[2] = { (uint32_t)bits, (uint32_t)(bits >> 32) }; uint32_t found = ipDedupFind(ip, IP_TAG_FLOAT_F64, words, 2, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 2); uint32_t idx = ipAddItem(ip, IP_TAG_FLOAT_F64, off); ipDedupInsert(ip, IP_TAG_FLOAT_F64, idx, words, 2); return idx; } bool ipReadFloatF64Bits(const InternPool* ip, uint32_t idx, uint64_t* out) { if (idx >= ip->items_len || ip->item_tags[idx] != IP_TAG_FLOAT_F64) return false; uint32_t off = ip->item_datas[idx]; if (off > ip->extra_len || ip->extra_len - off < 2u) return false; *out = (uint64_t)ip->extra[off] | ((uint64_t)ip->extra[off + 1] << 32); return true; } uint32_t ipGetFloatF80(InternPool* ip, const uint32_t input[3]) { // Upstream Float80 stores three little-endian words, with only the low // u16 of piece2 meaningful. Keep the padding canonical for dedup. const uint32_t words[3] = { input[0], input[1], input[2] & 0xFFFFu }; uint32_t found = ipDedupFind(ip, IP_TAG_FLOAT_F80, words, 3, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, 3); uint32_t idx = ipAddItem(ip, IP_TAG_FLOAT_F80, off); ipDedupInsert(ip, IP_TAG_FLOAT_F80, idx, words, 3); return idx; } bool ipReadFloatF80Bits( const InternPool* ip, uint32_t idx, uint32_t words[3]) { if (idx >= ip->items_len || ip->item_tags[idx] != IP_TAG_FLOAT_F80) return false; uint32_t off = ip->item_datas[idx]; if (off > ip->extra_len || ip->extra_len - off < 3u) return false; words[0] = ip->extra[off]; words[1] = ip->extra[off + 1]; words[2] = ip->extra[off + 2] & 0xFFFFu; return true; } // `.float_f128` and `.float_c_longdouble_f128` share the Float128 payload. static uint32_t ipGetFloat128Tagged( InternPool* ip, IpTag tag, const uint32_t words[4]) { uint32_t found = ipDedupFind(ip, tag, words, 4, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, IP_TYPE_POINTER_LEN); uint32_t idx = ipAddItem(ip, tag, off); ipDedupInsert(ip, tag, idx, words, 4); return idx; } static uint32_t ipGetFloatF128(InternPool* ip, const uint32_t words[4]) { return ipGetFloat128Tagged(ip, IP_TAG_FLOAT_F128, words); } // The f128 bits of an `f128` or (wasm32's f128-backed) `c_longdouble` value. bool ipReadFloatF128Bits( const InternPool* ip, uint32_t idx, uint32_t words[4]) { if (idx >= ip->items_len || (ip->item_tags[idx] != IP_TAG_FLOAT_F128 && ip->item_tags[idx] != IP_TAG_FLOAT_C_LONGDOUBLE_F128)) return false; uint32_t off = ip->item_datas[idx]; if (off > ip->extra_len || ip->extra_len - off < 4u) return false; words[0] = ip->extra[off]; words[1] = ip->extra[off + 1]; words[2] = ip->extra[off + 2]; words[3] = ip->extra[off + 3]; return true; } void ipFloatF80WordsFromLow80Bits( uint64_t lo, uint64_t hi, uint32_t words[3]) { words[0] = (uint32_t)lo; words[1] = (uint32_t)(lo >> 32); words[2] = (uint32_t)hi & 0xFFFFu; } void ipFloatF80WordsToLow80Bits( const uint32_t words[3], uint64_t* lo, uint64_t* hi) { *lo = (uint64_t)words[0] | ((uint64_t)words[1] << 32); *hi = (uint64_t)(words[2] & 0xFFFFu); } CrtU128 ipFloat128FromWords(const uint32_t w[4]) { return (CrtU128) { (uint64_t)w[0] | ((uint64_t)w[1] << 32), (uint64_t)w[2] | ((uint64_t)w[3] << 32) }; } void ipFloat128ToWords(CrtU128 v, uint32_t w[4]) { w[0] = (uint32_t)v.lo; w[1] = (uint32_t)(v.lo >> 32); w[2] = (uint32_t)v.hi; w[3] = (uint32_t)(v.hi >> 32); } // f128 -> f16/f32/f64 on compiler_rt's truncf, except that a NaN keeps the // source's quiet bit: whether a cast quiets is `valFloatCast`'s rule, // which sets the f128 quiet bit first where the host conversion would. static uint64_t ipFloat128Narrow(uint32_t bits, CrtU128 v) { uint64_t r = crtTruncf(bits, 128, v).lo; bool nan = ((v.hi >> 48) & 0x7FFFu) == 0x7FFFu && ((v.hi & UINT64_C(0xFFFFFFFFFFFF)) | v.lo) != 0; if (nan && ((v.hi >> 47) & 1u) == 0) r &= ~(UINT64_C(1) << (bits == 16 ? 9 : bits == 32 ? 22 : 51)); return r; } uint32_t ipGetFloatComptimeFloatFromDouble(InternPool* ip, double value) { uint64_t bits; uint32_t words[4]; memcpy(&bits, &value, sizeof(bits)); ipFloat128ToWords(crtExtendf(128, 64, (CrtU128) { bits, 0 }), words); return ipGetFloatComptimeFloatFromWords(ip, words); } uint32_t ipGetFloatComptimeFloatFromWords( InternPool* ip, const uint32_t words[4]) { uint32_t found = ipDedupFind(ip, IP_TAG_FLOAT_COMPTIME_FLOAT, words, 4, false); if (found != IP_INDEX_NONE) return found; uint32_t off = ipAddExtra(ip, words, IP_TYPE_POINTER_LEN); uint32_t idx = ipAddItem(ip, IP_TAG_FLOAT_COMPTIME_FLOAT, off); ipDedupInsert(ip, IP_TAG_FLOAT_COMPTIME_FLOAT, idx, words, 4); return idx; } // The four f128 words of an interned `comptime_float`, or NULL. static const uint32_t* ipComptimeFloatWords( const InternPool* ip, uint32_t idx) { if (idx >= ip->items_len || ip->item_tags[idx] != IP_TAG_FLOAT_COMPTIME_FLOAT) return NULL; uint32_t off = ip->item_datas[idx]; if (off > ip->extra_len || ip->extra_len - off < 4u) return NULL; return &ip->extra[off]; } bool ipReadFloatComptimeFloatAsF64Bits( const InternPool* ip, uint32_t idx, uint64_t* out) { const uint32_t* words = ipComptimeFloatWords(ip, idx); if (words == NULL) return false; *out = ipFloat128Narrow(64, ipFloat128FromWords(words)); return true; } bool ipReadFloatComptimeFloatAsF128Bits( const InternPool* ip, uint32_t idx, uint32_t out[4]) { const uint32_t* words = ipComptimeFloatWords(ip, idx); if (words == NULL) return false; memcpy(out, words, 4 * sizeof(uint32_t)); return true; } uint32_t ipGetFloatFromF128Bits( InternPool* ip, uint32_t ty, const uint32_t words[4]) { const CrtU128 v = ipFloat128FromWords(words); switch (ty) { case ZIR_REF_F16_TYPE: return ipGetFloatF16(ip, (uint32_t)ipFloat128Narrow(16, v)); case ZIR_REF_F32_TYPE: return ipGetFloatF32(ip, (uint32_t)ipFloat128Narrow(32, v)); case ZIR_REF_F64_TYPE: return ipGetFloatF64(ip, ipFloat128Narrow(64, v)); case ZIR_REF_F80_TYPE: { uint32_t f80[3]; const CrtU128 r = crtTrunctfxf2(v); ipFloatF80WordsFromLow80Bits(r.lo, r.hi, f80); return ipGetFloatF80(ip, f80); } case ZIR_REF_F128_TYPE: return ipGetFloatF128(ip, words); case ZIR_REF_C_LONGDOUBLE_TYPE: return ipGetFloat128Tagged(ip, IP_TAG_FLOAT_C_LONGDOUBLE_F128, words); case ZIR_REF_COMPTIME_FLOAT_TYPE: return ipGetFloatComptimeFloatFromWords(ip, words); default: return IP_INDEX_NONE; } } bool ipReadFloatAsF128Bits( const InternPool* ip, uint32_t idx, uint32_t words[4]) { uint32_t bits32; uint64_t bits64; uint32_t f80[3]; CrtU128 v = { 0, 0 }; if (idx >= ip->items_len) return false; switch (ip->item_tags[idx]) { case IP_TAG_FLOAT_F16: if (!ipReadFloatF16Bits(ip, idx, &bits32)) return false; v = crtExtendf(128, 16, (CrtU128) { bits32, 0 }); break; case IP_TAG_FLOAT_F32: if (!ipReadFloatF32Bits(ip, idx, &bits32)) return false; v = crtExtendf(128, 32, (CrtU128) { bits32, 0 }); break; case IP_TAG_FLOAT_F64: if (!ipReadFloatF64Bits(ip, idx, &bits64)) return false; v = crtExtendf(128, 64, (CrtU128) { bits64, 0 }); break; case IP_TAG_FLOAT_F80: if (!ipReadFloatF80Bits(ip, idx, f80)) return false; ipFloatF80WordsToLow80Bits(f80, &v.lo, &v.hi); v = crtExtendxftf2(v); break; case IP_TAG_FLOAT_F128: case IP_TAG_FLOAT_C_LONGDOUBLE_F128: return ipReadFloatF128Bits(ip, idx, words); case IP_TAG_FLOAT_COMPTIME_FLOAT: return ipReadFloatComptimeFloatAsF128Bits(ip, idx, words); default: return false; } ipFloat128ToWords(v, words); return true; } uint32_t ipGetIntU32(InternPool* ip, uint32_t value) { // `.int` with ty=u32 stores the value directly in the item `data` as // `int_u32` (no extra; InternPool.zig). Dedup by tag+value. uint32_t found = ipDedupFind(ip, IP_TAG_INT_U32, &value, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_INT_U32, value); ipDedupInsert(ip, IP_TAG_INT_U32, idx, &value, 1); return idx; } // Intern a `@as(i32, value)` int value (`int_i32`, value bit-cast to u32 and // stored directly in item `data`). Dedups by value. static uint32_t ipGetIntI32(InternPool* ip, uint32_t value) { // `.int` with ty=i32 stores the (u32-bit-cast) value directly in the item // `data` as `int_i32` (no extra; InternPool.zig). Dedup by tag+value. uint32_t found = ipDedupFind(ip, IP_TAG_INT_I32, &value, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_INT_I32, value); ipDedupInsert(ip, IP_TAG_INT_I32, idx, &value, 1); return idx; } uint32_t ipGetIntU8(InternPool* ip, uint32_t value) { // `.int` with ty=u8 stores the value directly in the item `data` as // `int_u8` (no extra; InternPool.zig). Dedup by tag+value. uint32_t found = ipDedupFind(ip, IP_TAG_INT_U8, &value, 1, true); if (found != IP_INDEX_NONE) return found; uint32_t idx = ipAddItem(ip, IP_TAG_INT_U8, value); ipDedupInsert(ip, IP_TAG_INT_U8, idx, &value, 1); return idx; } // InternPool.zig:init InternPool ipInit(void) { InternPool ip; memset(&ip, 0, sizeof(ip)); // Reserve string index 0 for the empty string (offset 0), mirroring // upstream InternPool.init interning "" first as NullTerminatedString 0. ip.string_cap = 1024; ip.string_bytes = zig1Malloc(ip.string_cap); ip.string_bytes[0] = '\0'; ip.string_len = 1; ip.strings_cap = 128; ip.string_offsets = zig1Malloc(ip.strings_cap * sizeof(uint32_t)); ip.string_offsets[0] = 0; // empty string starts at byte 0 ip.strings_count = 1; // The empty string at index 0 above is written directly (not through // ipGetOrPutString), so it must be seeded into the string dedup index by // hand -- otherwise the first real ipGetOrPutString("", 0) call would // miss the index and append a second empty string. ipStrDedupInsert(&ip, 0, "", 0); ipInitStatic(&ip); // Seed the item dedup index with the 128 static preinterns just written // by ipInitStatic, before any dynamic ipGet* call can run. See // ipDedupSeedStatic's comment for why this must run exactly here. ipDedupSeedStatic(&ip); return ip; } // InternPool.zig:deinit void ipDeinit(InternPool* ip) { free(ip->item_tags); free(ip->item_datas); free(ip->extra); free(ip->limbs); free(ip->dedup_slots); free(ip->limbs_dedup_slots); free(ip->string_bytes); free(ip->string_offsets); free(ip->str_dedup_slots); free(ip->errset_dedup_slots); free(ip->memo_dedup_slots); free(ip->decl_dedup_slots); free(ip->tracked_keys); free(ip->tracked_dedup_slots); free(ip->func_instance_dedup_slots); free(ip->global_error_names); free(ip->string_error_value); for (uint32_t i = 0; i < ip->namespaces_len; i++) { IpNamespace* ns = &ip->namespaces[i]; free(ns->pub_navs); free(ns->priv_navs); free(ns->comptime_units); free(ns->test_navs); } free(ip->namespaces); free(ip->navs); free(ip->comptime_units); for (uint32_t i = 0; i < ip->pending_union_tags_len; i++) free(ip->pending_union_tags[i].field_values); free(ip->pending_union_tags); free(ip->pending_field_names); memset(ip, 0, sizeof(*ip)); } const char* ipTagName(IpTag tag) { #define IP_TAG_NAME_CASE(tag_value, name) \ case tag_value: \ return name; switch (tag) { IP_TAG_NAMES(IP_TAG_NAME_CASE) } #undef IP_TAG_NAME_CASE return "?"; } // This file's macros end with it (zig1.c is one translation unit). bool ipPtrKey(const InternPool* ip, uint32_t v, IpPtrKey* k) { if (v < ZIR_REF_START_INDEX || v >= ip->items_len) return false; k->tag = ip->item_tags[v]; k->a = k->b = 0; k->off = 0; IpPtrNav pn; switch (k->tag) { case IP_TAG_PTR_NAV: if (ipDecodePtrNavAny(ip, v, &pn) != IP_PN_OK) return false; k->a = pn.nav_index; k->off = pn.byte_offset; return true; case IP_TAG_PTR_UAV: case IP_TAG_PTR_UAV_ALIGNED: // `.uav = .{ .val, .orig_ty }`: getCoerced keeps orig_ty, so two // uavs of one value under different original types differ. k->tag = IP_TAG_PTR_UAV; k->a = ipPtrUavValue(ip, v); k->b = ipPtrUavOrigTy(ip, v); k->off = ipPtrUavByteOffset(ip, v); return true; case IP_TAG_PTR_COMPTIME_ALLOC: k->a = ipPtrComptimeAllocIndex(ip, v); k->off = ipPtrComptimeAllocByteOffset(ip, v); return true; case IP_TAG_PTR_COMPTIME_FIELD: k->a = ipPtrComptimeFieldValue(ip, v); k->off = ipPtrComptimeFieldByteOffset(ip, v); return true; case IP_TAG_PTR_INT: k->off = ipPtrIntAddress(ip, v); return true; case IP_TAG_PTR_ELEM: k->a = ipPtrElemBase(ip, v); k->b = ipPtrElemIndex(ip, v); k->off = ipPtrElemByteOffset(ip, v); return true; case IP_TAG_PTR_FIELD: k->a = ipPtrFieldBase(ip, v); k->b = ipPtrFieldIndex(ip, v); k->off = ipPtrFieldByteOffset(ip, v); return true; case IP_TAG_PTR_EU_PAYLOAD: k->a = ipPtrEuPayloadBase(ip, v); k->off = ipPtrEuPayloadByteOffset(ip, v); return true; case IP_TAG_PTR_OPT_PAYLOAD: k->a = ipPtrOptPayloadBase(ip, v); k->off = ipPtrOptPayloadByteOffset(ip, v); return true; default: return false; } } // ── Integer values and preinterned types ──────────────────────────────────── // InternPool.zig:typeOf // typeOf for a preinterned InternPool index. Mirrors InternPool.typeOf's // static-key switch (the only indices reachable before the dynamic // InternPool exists). Aborts on anything outside that range. uint32_t ipTypeOfPreinterned(uint32_t val) { // InternPool.typeOf's static types: `".u0_type, .u1_type, ... , // .generic_poison_type, .empty_tuple_type, => .type_type,"` -- the // dense type range that ends at `.empty_tuple_type`. if (val <= ZIR_REF_EMPTY_TUPLE_TYPE) return ZIR_REF_TYPE_TYPE; switch (val) { case ZIR_REF_UNDEF: // InternPool.typeOf: `.undef => .undefined_type`. return ZIR_REF_UNDEFINED_TYPE; case ZIR_REF_ZERO: case ZIR_REF_ONE: case ZIR_REF_NEGATIVE_ONE: return ZIR_REF_COMPTIME_INT_TYPE; case ZIR_REF_UNDEF_USIZE: case ZIR_REF_ZERO_USIZE: case ZIR_REF_ONE_USIZE: return ZIR_REF_USIZE_TYPE; case ZIR_REF_UNDEF_U1: case ZIR_REF_ZERO_U1: case ZIR_REF_ONE_U1: return ZIR_REF_U1_TYPE; case ZIR_REF_ZERO_U8: case ZIR_REF_ONE_U8: case ZIR_REF_FOUR_U8: return ZIR_REF_U8_TYPE; case ZIR_REF_VOID_VALUE: return ZIR_REF_VOID_TYPE; case ZIR_REF_UNREACHABLE_VALUE: return ZIR_REF_NORETURN_TYPE; case ZIR_REF_UNDEF_BOOL: case ZIR_REF_BOOL_TRUE: case ZIR_REF_BOOL_FALSE: return ZIR_REF_BOOL_TYPE; case ZIR_REF_EMPTY_TUPLE: // InternPool.typeOf: `.empty_tuple => .empty_tuple_type` // (src/InternPool.zig). Reached when the untyped `.{}` call // argument (struct_init_empty_result at generic poison) is typed for // the generic instantiation. return ZIR_REF_EMPTY_TUPLE_TYPE; case ZIR_REF_NULL_VALUE: // `@TypeOf(null)` is the comptime-only `null` type (InternPool.typeOf // `.null_value` -> `.null_type`). Reached when a comptime switch prong // breaks with the bare `null` literal (`.none => null` in // Writer.print's `switch (placeholder.arg)`); the merge/peer type is // computed from the break operand. return ZIR_REF_NULL_TYPE; default: UNREACHABLE("ipTypeOfPreinterned: non-preinterned value"); } ZIG1_TCC_RETURN(IP_INDEX_NONE); } // (part of) InternPool.zig:typeOf // The InternPool type of a comptime-known value index (not a runtime AIR // ref). Mirrors InternPool.typeOf // for the value shapes zig1 materializes: dynamically-interned sized ints, // enum tags, comptime pointers (uav/nav), optionals, and struct/array // aggregates all carry their type on the item; everything else falls back to // the static preintern table (bool/void/comptime_int literals). Aborts (via // ipTypeOfPreinterned) on a value whose type is not recoverable. uint32_t ipComptimeValueType(const InternPool* ip, uint32_t operand) { if (operand == IP_INDEX_NONE || operand >= ip->items_len) UNREACHABLE("ipComptimeValueType: invalid InternPool value"); uint32_t exact_ty = ipTypeOf(ip, operand); if (exact_ty == IP_INDEX_NONE) UNIMPLEMENTED("typeOf: unsupported InternPool value"); return exact_ty; } // True if a comptime integer value (already read into a sign-extended u64 via // ipTryReadIntValue) is negative: its bit pattern is negative and its entry's // type is signed (comptime_int negatives are int_comptime_int_i32). An // unsigned all-ones value (u64 max) has bit 63 set but is not negative. bool ipIntValueIsNegative(const InternPool* ip, uint32_t idx, uint64_t n) { if ((int64_t)n >= 0) return false; uint8_t t = ip->item_tags[idx]; if (t == IP_TAG_INT_COMPTIME_INT_I32 || t == IP_TAG_INT_I32) return true; // The tag is the sign, at any type and any width: `int_positive` / // `int_negative` is where `big.int.Const.positive` lives in this storage. if (t == IP_TAG_INT_NEGATIVE) return true; if (t == IP_TAG_INT_POSITIVE) return false; uint32_t ty = ipIntValueType(ip, idx); if (ty == IP_INDEX_NONE || ty == ZIR_REF_COMPTIME_INT_TYPE) return false; uint32_t w; bool s; if (!ztIntInfo(ip, ty, &w, &s) || !s) return false; // No width rule is left to state: every carrier that reaches here is as // wide as its type (`int_i32`, `int_small` at 32 bits or fewer), and // anything wider is limb-backed and answered from the tag above. return true; } // (part of) InternPool.zig:indexToKey // Read the raw integer value from an interned IP integer entry into `*out` // (signed i32/isize entries are sign-extended to their two's-complement 64-bit // value; unsigned entries are zero-extended). Returns `true` iff `idx` names a // readable integer entry; `false` for a non-integer tag. Mirrors // InternPool.indexToKey().int.getValue() for the integer entry shapes zig1 // materializes. // // Prefer this over ipReadIntValue when the operand's value may legitimately be // UINT64_MAX (e.g. the comptime_int -1, whose sign-extension is also // ipReadIntValue's "unreadable" answer). The success flag tells "unreadable // tag" from "value happens to be UINT64_MAX". bool ipTryReadIntValue(const InternPool* ip, uint32_t idx, uint64_t* out) { switch ((IpTag)ip->item_tags[idx]) { case IP_TAG_INT_U8: case IP_TAG_INT_U16: case IP_TAG_INT_U32: case IP_TAG_INT_USIZE: case IP_TAG_INT_COMPTIME_INT_U32: *out = (uint64_t)ip->item_datas[idx]; return true; case IP_TAG_INT_I32: case IP_TAG_INT_COMPTIME_INT_I32: // Stored as bit-cast (u32) i32; sign-extend to i64 for arithmetic. // (int_comptime_int_i32 is a negative comptime_int fitting i32, e.g. // `std.math.minInt(isize)` = -(1<<31), or a bit-cast -1.) *out = (uint64_t)(int64_t)(int32_t)ip->item_datas[idx]; return true; case IP_TAG_INT_SMALL: { // {ty, value} in extra: extra[data+1] = value word. A signed isize // or C/sized integer <=32 bits is stored bit-cast; sign-extend so a // negative value reads as its two's-complement 64-bit value (mirrors // the INT_I32 arm). uint32_t sm_ty = ip->extra[ip->item_datas[idx]]; uint32_t sm_raw = ip->extra[ip->item_datas[idx] + 1]; uint32_t sm_bits; bool sm_signed; if (ztIntInfo(ip, sm_ty, &sm_bits, &sm_signed) && sm_signed && sm_bits > 0u && sm_bits <= 32u) { uint64_t mask = sm_bits == 32u ? UINT64_C(0xFFFFFFFF) : ((UINT64_C(1) << sm_bits) - 1u); uint64_t raw = (uint64_t)sm_raw & mask; uint64_t sign_bit = UINT64_C(1) << (sm_bits - 1u); *out = (raw & sign_bit) ? (raw | ~mask) : raw; } else { *out = (uint64_t)sm_raw; } return true; } case IP_TAG_INT_POSITIVE: case IP_TAG_INT_NEGATIVE: { // `Value.toBigInt(...).toInt(u64)`, falling to `toInt(i64)` for a // negative -- this function hands back the sign-extended word and // asks the sign separately, so both readings spell the same word. A // magnitude with no 64-bit word declines, exactly as the 128-bit // carrier's `hi != 0` arm above declines. BigIntConst big; int64_t big_i; if (!ipIntBigLimbs( ip, idx, NULL, &big.limbs, &big.limbs_len, &big.positive)) return false; if (bigIntToU64(big, out)) return true; if (!bigIntToI64(big, &big_i)) return false; *out = (uint64_t)big_i; return true; } default: return false; } } // Legacy convenience wrapper: returns the integer value, or UINT64_MAX on an // unrecognized tag. Callers that cannot have a legitimate UINT64_MAX operand // keep using this; those that can (comptime_int -1) use ipTryReadIntValue. uint64_t ipReadIntValue(const InternPool* ip, uint32_t idx) { uint64_t v; if (ipTryReadIntValue(ip, idx, &v)) return v; if (idx < ip->items_len && ipIntBigLimbs(ip, idx, NULL, NULL, NULL, NULL)) UNIMPLEMENTED("Value.toUnsignedInt: integer exceeds 64 bits"); return UINT64_MAX; } // (part of) InternPool.zig:typeOf // Get the type IP index of an interned IP integer entry, IP_INDEX_NONE for a // non-integer tag: `InternPool.typeOf` already answers this for every `.int` // storage tag, so this is the int-tag question plus that one answer. uint32_t ipIntValueType(const InternPool* ip, uint32_t idx) { if (idx >= ip->items_len || !ipTagIsIntValue((IpTag)ip->item_tags[idx])) return IP_INDEX_NONE; return ipTypeOf(ip, idx); } // Intern a comptime-computed integer result at `dest_ty`, where `n` is the // result's two's-complement word: the one-limb spelling of the {lo, hi} // adapter below, which is the whole difference between them. uint32_t ipInternIntResult(InternPool* ip, uint64_t n, uint32_t dest_ty) { return ipInternIntResultU128(ip, n, 0, dest_ty); } uint32_t ipInternIntResultU128( InternPool* ip, uint64_t lo, uint64_t hi, uint32_t dest_ty) { uint64_t space[2]; BigIntConst v; if (!semaPatternToBigInt(ip, lo, hi, dest_ty, space, &v)) return IP_INDEX_NONE; return ipGetInt(ip, dest_ty, v); } #undef IP_INT_LIMBS_ITEMS_LEN #undef DEDUP_HASH_SEED #undef IP_CONTAINER_FQN #undef IP_CONTAINER_NAME_NAV #undef IP_CONTAINER_NAMESPACE #undef IP_CONTAINER_NAME_RUN #undef IP_UNION_FLAGS_LAYOUT_EXTERN /* ---- end intern_pool.c ---- */ /* ---- begin interp.c ---- */ // interp.c — the AIR interpreter, zig1's own. It executes Sema's AIR as a // wasm32-wasi machine: values live in a flat 32-bit address space laid out // with the wasm32 ABI, calls and nested bodies are heap stacks one loop // drives (interpExecute), and the WASI imports are served from the host. AIR // it does not model aborts through UNIMPLEMENTED. With no upstream file to // mirror, its anchors name what each piece reproduces: Air.zig for an // instruction's operands, Type.zig/Value.zig for layout and value encodings, // lib/std/os/wasi.zig and upstream's stage1/wasi.c for the WASI host, // lib/std/start.zig for the entry. #ifndef ZIG1_H #include "zig1.h" #endif #include #include #include #include #include #include #include static void interpWriteLE(uint32_t addr, uint64_t bits, uint32_t size); // The program executed `unreachable` or `@trap`, both wasm's `unreachable` // instruction: a trap that ends it abnormally. The fault is the program's, so // the message carries no zig1 hint; the exit is SIGABRT, as for an abort. ZIG1_ATTRIBUTE((noreturn, cold)) static void interpTrap(const char* what) { fprintf(stderr, "zig1: the program reached %s\n", what); abort(); } // An enum's runtime representation is its integer tag, so layout/scalar code // treats an enum type as its backing int tag type (Sema: an enum's abi // size/align/repr = its int_tag_type). Non-enum types pass through unchanged. ZIG1_INLINE uint32_t interpBackingType(InternPool* ip, uint32_t ty) { if (ty < ZIR_REF_START_INDEX) return ty; if (ty < ip->items_len && ipIsEnumType(ip, ty)) return ipEnumTypeIntTagType(ip, ty); return ty; } typedef struct { uint16_t width; uint16_t storage_bits; bool is_signed; bool valid; } InterpIntInfo; static const InterpIntInfo g_static_int_info[ZIR_REF_C_ULONGLONG_TYPE + 1u] = { [ZIR_REF_U0_TYPE] = { 0, 0, false, true }, [ZIR_REF_U1_TYPE] = { 1, 8, false, true }, [ZIR_REF_U8_TYPE] = { 8, 8, false, true }, [ZIR_REF_I8_TYPE] = { 8, 8, true, true }, [ZIR_REF_U16_TYPE] = { 16, 16, false, true }, [ZIR_REF_I16_TYPE] = { 16, 16, true, true }, [ZIR_REF_U29_TYPE] = { 29, 32, false, true }, [ZIR_REF_U32_TYPE] = { 32, 32, false, true }, [ZIR_REF_I32_TYPE] = { 32, 32, true, true }, [ZIR_REF_U64_TYPE] = { 64, 64, false, true }, [ZIR_REF_I64_TYPE] = { 64, 64, true, true }, [ZIR_REF_U80_TYPE] = { 80, 128, false, true }, [ZIR_REF_U128_TYPE] = { 128, 128, false, true }, [ZIR_REF_I128_TYPE] = { 128, 128, true, true }, [ZIR_REF_U256_TYPE] = { 256, 256, false, true }, [ZIR_REF_USIZE_TYPE] = { 32, 32, false, true }, [ZIR_REF_ISIZE_TYPE] = { 32, 32, true, true }, [ZIR_REF_C_CHAR_TYPE] = { 8, 8, true, true }, [ZIR_REF_C_SHORT_TYPE] = { 16, 16, true, true }, [ZIR_REF_C_USHORT_TYPE] = { 16, 16, false, true }, [ZIR_REF_C_INT_TYPE] = { 32, 32, true, true }, [ZIR_REF_C_UINT_TYPE] = { 32, 32, false, true }, [ZIR_REF_C_LONG_TYPE] = { 32, 32, true, true }, [ZIR_REF_C_ULONG_TYPE] = { 32, 32, false, true }, [ZIR_REF_C_LONGLONG_TYPE] = { 64, 64, true, true }, [ZIR_REF_C_ULONGLONG_TYPE] = { 64, 64, false, true }, }; ZIG1_INLINE const InterpIntInfo* interpStaticIntInfo(uint32_t ty) { if (ty >= sizeof(g_static_int_info) / sizeof(g_static_int_info[0]) || !g_static_int_info[ty].valid) return NULL; return &g_static_int_info[ty]; } // A `type_int_signed` / `type_int_unsigned` item: its width and signedness. ZIG1_INLINE bool interpDynIntInfo( const InternPool* ip, uint32_t ty, uint32_t* out_width, bool* out_signed) { if (ty >= ip->items_len) return false; uint8_t tag = ip->item_tags[ty]; if (tag != IP_TAG_TYPE_INT_SIGNED && tag != IP_TAG_TYPE_INT_UNSIGNED) return false; *out_width = ip->item_datas[ty]; *out_signed = tag == IP_TAG_TYPE_INT_SIGNED; return true; } ZIG1_INLINE bool interpIntTypeInfo( uint32_t ty, uint32_t* out_width, bool* out_signed) { const InterpIntInfo* info = interpStaticIntInfo(ty); if (!info) return false; *out_width = info->width; *out_signed = info->is_signed; return true; } // Bit width + signedness of a scalar AIR type. Handles bool, `usize` (wasm32: // 32-bit unsigned pointer width), and dynamic signed/unsigned integer types. ZIG1_INLINE void interpTypeInfo( InternPool* ip, uint32_t ty, uint32_t* out_width, bool* out_signed); static ZIG1_NOINLINE void interpTypeInfoUncached( InternPool* ip, uint32_t ty, uint32_t* out_width, bool* out_signed) { if (ty == ZIR_REF_BOOL_TYPE) { *out_width = 1; *out_signed = false; return; } if (ty == ZIR_REF_VOID_TYPE) { // `Type.bitSize` of `void` is 0: a zero-bit packed field, and an // operand whose every value is the same. *out_width = 0; *out_signed = false; return; } if (ztSimpleFloatBits(ty, out_width)) { *out_signed = false; return; } uint8_t tag = ip->item_tags[ty]; if (ipTypeIsBitpack(ip, ty)) { // A packed struct/union scalar has the width/signedness of its // (unsigned) backing integer. interpTypeInfo(ip, ipBitpackBackingInt(ip, ty), out_width, out_signed); return; } if (tag == IP_TAG_TYPE_POINTER) { // A pointer's runtime representation is its 32-bit wasm32 address // (`a.ptr == b.ptr` in std.mem.eqlBytes compares many-pointers). *out_width = 32; *out_signed = false; return; } if (ty == ZIR_REF_ANYERROR_TYPE || tag == IP_TAG_TYPE_ERROR_SET || tag == IP_TAG_TYPE_INFERRED_ERROR_SET) { // An error value's runtime representation is an unsigned integer error // code, ZT_ERROR_ABI_SIZE bytes wide (u16, matching // Zcu.errorSetBits) — see the error-union layout in // interpErrUnionOffsets. An inferred error set (`fn f() !T`) shares // the same error-code representation as a named set. *out_width = ZT_ERROR_ABI_SIZE * 8u; *out_signed = false; return; } UNIMPLEMENTED("Type.intInfo: non-scalar type width/signedness"); } // Inline: every integer operation asks it. An integer (an enum through its // tag integer) answers here; interpTypeInfoUncached (given the backing type) // has the rest. ZIG1_INLINE void interpTypeInfo( InternPool* ip, uint32_t ty, uint32_t* out_width, bool* out_signed) { ty = interpBackingType(ip, ty); if (interpIntTypeInfo(ty, out_width, out_signed) || interpDynIntInfo(ip, ty, out_width, out_signed)) return; interpTypeInfoUncached(ip, ty, out_width, out_signed); } // Reduce a raw 64-bit computation to the canonical scalar form for a value of // `width` bits: keep the low `width` bits, then sign-extend into the high bits // when signed (zero-fill when unsigned). A width >= 64 is already canonical. // `CodeGen.emitUndefined` (src/codegen/wasm/CodeGen.zig) and // `Value.writeToMemory` (src/Value.zig) agree on one byte: an undefined // value reads as 0xAA repeated. This is the widest form of it; narrower // types take it through `interpNormalize`. #define INTERP_UNDEF_PATTERN UINT64_C(0xAAAAAAAAAAAAAAAA) static uint64_t interpNormalize(uint64_t raw, uint32_t width, bool is_signed) { if (width == 0) return 0; if (width >= 64) return raw; uint64_t mask = ((uint64_t)1 << width) - 1u; uint64_t v = raw & mask; if (is_signed && (v & ((uint64_t)1 << (width - 1))) != 0) v |= ~mask; return v; } // ── Interpreter state ─────────────────────────────────────────────────────── // Everything the interpreter keeps between instructions lives in its one // instance, g_interp. The interpreter is single-threaded and not reentrant: // one program runs at a time (interpRunMain), its calls and nested bodies are // heap stacks that one loop drives (interpExecute), so the C stack does not // grow with the program's, and a program that ends in proc_exit unwinds to // interpRunMain with longjmp. // Host buffer capacities are kept from one program to the next. // Memo entry for runtime @tagName results (AIR tag_name). typedef struct { uint32_t enum_ty; uint32_t field_idx; uint32_t slice_addr; } TagNameMemo; // One chunk of the AIR value stack (Interp.value_chunks). typedef struct { InterpValue* base; uint32_t len; uint32_t cap; } InterpValueChunk; #define INTERP_VALUE_CHUNK_MIN 65536u typedef SLICE(InterpValueChunk) InterpValueChunks; // What ends an InterpBody, besides running off its end: the control transfer // it catches (interpFlowOut). typedef enum { INTERP_BODY_FN, // a function's main body: its activation returns INTERP_BODY_BLOCK, // a block's body: catches a br to the block INTERP_BODY_LOOP, // a loop's body: catches a repeat of the loop INTERP_BODY_LOOP_SWITCH, // a loop_switch_br prong: catches its dispatch INTERP_BODY_BRANCH, // a cond_br or switch_br prong, or a try's else body } InterpBodyKind; // One body the dispatch loop (interpExecute) is in: its instructions, the // index of the next one, and the instruction that owns it. typedef struct { const uint32_t* body; uint32_t len; uint32_t k; uint32_t inst; InterpBodyKind kind; } InterpBody; typedef SLICE(InterpBody) InterpBodies; // One function activation (defined after InterpCtx). typedef struct InterpFrame InterpFrame; typedef SLICE(InterpFrame) InterpFrames; typedef struct { // The program's host, from interpRunMain: the module's recorded // `@export`s (Zcu.exports), which resolve extern data Navs by name, and // the WASI host its imports call. const ZcuExport* exports; uint32_t exports_len; Wasi wasi; // The frame arena: a flat, growable byte buffer behind the frame region. // Addresses are direct offsets into it, biased by INTERP_FRAME_BASE; // `arena_len` is the high-water bump pointer. uint8_t* arena; uint32_t arena_len; uint32_t arena_cap; // The persistent const arena (interpDecodeConst's materialized consts). uint8_t* const_arena; uint32_t const_len; uint32_t const_cap; // Persistent interpreted wasm linear memory. Guest-visible addresses are // raw wasm addresses; pointer values bias them by INTERP_HEAP_BASE only // inside the interpreter so they remain disjoint from runtime // frames/globals/consts. uint8_t* heap_arena; uint32_t heap_len; uint32_t heap_cap; // Persistent mutable-global storage. Lifetime is one top-level // interpreted program (including every nested call); the buffer capacity // is retained between programs, while its bump pointer is reset. // Addresses are keyed by Nav index through `nav_*` below, not by a // particular ptr_nav IP value, so repeated pointers to one Nav have // stable identity. uint8_t* global_arena; uint32_t global_len; uint32_t global_cap; // Per-program data Nav address/initialization cache. state: 0=unseen, // 1=address allocated and initializer currently decoding, 2=initialized. // Recording state/address before decoding permits address cycles such as // a const whose initializer contains its own address. // IpNav.resolved_value is never changed: it remains // initializer/provenance. uint32_t* nav_addr; uint8_t* nav_state; uint32_t nav_cap; // Dense per-program error-code assignment. InternPool string handles are // arena offsets and may exceed the u16 error ABI; storing them directly // would truncate and could alias unrelated errors. Code 0 is success, // while each distinct error name receives one stable code in 1..65535 // for the duration of the top-level program (including all nested // calls). uint32_t* error_name_slices; uint32_t error_name_slices_len; uint32_t error_name_slices_cap; // interpLayout's answers by type index: (size << 6 | log2 align) + 1, // 0 for a type not yet asked. A type's layout cannot change while the // program runs, since the interpreter resolves nothing. uint64_t* layout_memo; uint32_t layout_memo_cap; // The const cache (see "The const cache" below). uint32_t* cc_key; // IP index, or CC_EMPTY uint32_t* cc_addr; // materialized const address uint32_t* cc_ty; // InterpValue.ty uint32_t cc_cap; // slot count (power of two, or 0) uint32_t cc_count; // live entries // Memo for runtime @tagName results (AIR tag_name). Each distinct // (enum_ty, field_idx) maps to one persistent const-arena slice, so a // loop rendering the same enum tag repeatedly — real c.zig renders many // types via `@tagName(Int)` — allocates each name once (bounded by the // enum's field count, exactly the static string-literal pool stock would // emit) rather than per call. Lifetime: the const arena (the field names // are static const data, like stock's string literals). Reset per // top-level program alongside the const arena, since the biased // addresses are per-run. TagNameMemo* tagname_memo; uint32_t tagname_memo_len; uint32_t tagname_memo_cap; // Per pool value, the strictest explicit alignment (log2 + 1; 0 for // none) any uav pointer to it carries. Codegen places a uav after // lowering every reference (Wasm.zig `overaligned_uavs`), so the first // placement already takes the strictest; the table grows with the pool // and resets per program. uint8_t* uav_align; uint32_t uav_align_len; // pool items scanned uint32_t uav_align_cap; // Each activation's AIR value array lives on one LIFO stack of zeroed // chunks: activations nest, so a frame's array is released before its // caller's. A chunk never moves (a caller's `ctx.values` stays valid), // and a released chunk is kept for the next push instead of returning to // malloc. `len` counts the chunks in use; the last of them is open. InterpValueChunks value_chunks; // The program's call stack, innermost last: its activations, and the // bodies they are in (each activation's own from its bodies_base on). // Their capacity is kept from one program to the next. InterpFrames frames; InterpBodies bodies; // proc_exit's unwind target in interpRunMain, and the exit status it // carries there. jmp_buf exit_jmp; int exit_status; } Interp; static Interp g_interp; // interpCheckedBump's result. typedef enum { INTERP_BUMP_OK = 0, INTERP_BUMP_BAD_ALIGN, INTERP_BUMP_ALIGN_OVERFLOW, INTERP_BUMP_EXHAUSTED, } InterpBumpStatus; // ── Runtime memory arena ──────────────────────────────────────────────────── // ── Persistent const arena ────────────────────────────────────────────────── // interpDecodeConst materializes interned comptime aggregates (strings, // arrays, structs, slices, optionals, error unions) into arena bytes so a // InterpValue can carry their address. Those materialized consts are static in // Zig semantics — they live in .rodata and outlive every stack frame (a // function may legitimately `return "hi"` or return a slice into a const // array), so they must not be reclaimed by the frame-scoped watermark restore. // They live in a separate, persistent buffer whose offsets are biased by // INTERP_CONST_BASE. Ordinary mutable Navs live in a third persistent global // arena, and interpreted wasm linear memory is biased by INTERP_HEAP_BASE. // Thus one 32-bit address unambiguously names a runtime frame // ([FRAME_BASE, GLOBAL_BASE)), mutable global ([GLOBAL_BASE, CONST_BASE)), // const ([CONST_BASE, HEAP_BASE)), // or heap (>= HEAP_BASE). Bootstrapping the compiler needs deep runtime-frame // stacks while globals/consts remain tiny, so keep the wasm heap base stable // and spend most sub-2GiB address space on frame memory. // // That address is the guest's wasm32 address: `@intFromPtr` and `@ptrFromInt` // are the identity, and the heap's first page is wasm page HEAP_BASE / 64 KiB. // Frames start at FRAME_BASE: below it is upstream's wasm32 stack (stack // first, 16 MiB, growing down), whose contents zig1 cannot reproduce. A // pointer there (null included) is nameable, but reading or writing through // it aborts; frame addresses are biased by FRAME_BASE into their host buffer. // Known divergence: upstream's wasm heap starts near 16 MiB, this one at 2 // GiB, so a heap address does not fit i32/isize (`std.math.cast(i32, addr)` is // null here) and isize arithmetic on addresses or page counts overflows. #define INTERP_FRAME_BASE 0x01000000u #define INTERP_GLOBAL_BASE 0x70000000u #define INTERP_CONST_BASE 0x78000000u #define INTERP_HEAP_BASE 0x80000000u #define INTERP_WASM_PAGE_SIZE 65536u // The last wasm page has no storage behind it: its guest addresses name // themselves (`std.mem.Allocator`'s zero-length `alignBackward(usize, // maxInt(usize), alignment)` pointers live there), and the heap window ends // where it starts. #define INTERP_WASM_TOP_PAGE (UINT32_MAX - INTERP_WASM_PAGE_SIZE + 1u) // Checked-u32 primitives of arena allocation, ABI layout and pointer // arithmetic: false on overflow, else the result in `*out` (when not NULL). static bool interpCheckedU32Add(uint32_t a, uint32_t b, uint32_t* out) { if (b > UINT32_MAX - a) return false; if (out) *out = a + b; return true; } static bool interpCheckedU32Mul(uint32_t a, uint32_t b, uint32_t* out) { if (a != 0 && b > UINT32_MAX / a) return false; if (out) *out = a * b; return true; } static bool interpCheckedAlignForward( uint32_t value, uint32_t align, uint32_t* out) { if (align == 0 || (align & (align - 1u)) != 0) return false; uint32_t mask = align - 1u; if (mask > UINT32_MAX - value) return false; if (out) *out = (value + mask) & ~mask; return true; } static bool interpCheckedGrowCapacity( uint32_t current, uint32_t need, uint32_t limit, uint32_t* out) { if (limit == 0 || need >= limit || current >= limit) return false; uint32_t cap = current; if (cap == 0) cap = need < 256u ? 256u : need; if (cap >= limit) cap = need; while (cap < need) { if (cap > (limit - 1u) / 2u) { cap = need; break; } cap *= 2u; } if (cap < need || cap >= limit) return false; if (out) *out = cap; return true; } // Next power-of-two cache capacity after a load-factor-triggered grow. Unlike // a general reserve request, this always requests strictly more than current. static bool interpCheckedNextCacheCapacity( uint32_t current, uint32_t limit, uint32_t* out) { uint32_t need; if (!interpCheckedU32Add(current, 1u, &need)) return false; return interpCheckedGrowCapacity(current, need, limit, out); } // Compute an aligned bump allocation in [0, limit), where `need` is the // exclusive end and must remain strictly below `limit` (matching the three // disjoint interpreter address regions). static InterpBumpStatus interpCheckedBump(uint32_t len, uint32_t size, uint32_t align, uint32_t limit, uint32_t* off, uint32_t* need) { if (align == 0 || (align & (align - 1u)) != 0) return INTERP_BUMP_BAD_ALIGN; uint32_t aligned; if (!interpCheckedAlignForward(len, align, &aligned)) return INTERP_BUMP_ALIGN_OVERFLOW; if (aligned >= limit || size >= limit - aligned) return INTERP_BUMP_EXHAUSTED; if (off) *off = aligned; if (need) *need = aligned + size; // proven strictly below limit return INTERP_BUMP_OK; } static void interpConstReset(void); // Reserve `size` bytes aligned to `align` (a power of two) in the arena and // return the address (a nonzero offset). Bytes are zero-initialised. static uint32_t interpArenaAlloc(uint32_t size, uint32_t align) { if (g_interp.arena_len < INTERP_FRAME_BASE) g_interp.arena_len = INTERP_FRAME_BASE; uint32_t off, need; InterpBumpStatus bump = interpCheckedBump( g_interp.arena_len, size, align, INTERP_GLOBAL_BASE, &off, &need); if (bump == INTERP_BUMP_BAD_ALIGN) UNIMPLEMENTED("wasm32: runtime arena alignment is not a power of two"); if (bump == INTERP_BUMP_ALIGN_OVERFLOW) UNIMPLEMENTED("wasm32: runtime arena alignment overflow"); if (bump != INTERP_BUMP_OK) { // The runtime arena has grown into the address range reserved for the // global arena. Abort loudly rather than alias regions. UNIMPLEMENTED("wasm32: runtime arena exhausted (>= global base)"); } uint32_t host_need = need - INTERP_FRAME_BASE; if (host_need > g_interp.arena_cap) { uint32_t nc; if (!interpCheckedGrowCapacity(g_interp.arena_cap, host_need, INTERP_GLOBAL_BASE - INTERP_FRAME_BASE, &nc)) UNIMPLEMENTED("wasm32: runtime arena capacity overflow"); uint8_t* p = zig1Realloc(g_interp.arena, nc); memset(p + g_interp.arena_cap, 0, nc - g_interp.arena_cap); g_interp.arena = p; g_interp.arena_cap = nc; } // Zero the freshly handed-out span (it may lie below the previous high // water after a reset, so is not guaranteed clean). A zero-size span may // precede the arena's first allocation. if (size != 0) memset(g_interp.arena + (off - INTERP_FRAME_BASE), 0, size); g_interp.arena_len = need; return off; } // Resolve and bounds-check a complete byte range in one of the three disjoint // regions. Checking `size <= len - off` avoids overflow and prevents a range // which begins in mutable-global memory from crossing into const addresses // (or a frame range from crossing into globals). A non-empty range below // FRAME_BASE never resolves. NULL when the range has no bytes; // `region`/`off`/`len` name where it was looked up. ZIG1_INLINE uint8_t* interpArenaResolve(uint32_t addr, uint32_t size, const char** region, uint32_t* off, uint32_t* len) { uint8_t* arena; if (addr < INTERP_FRAME_BASE) { // Only an empty range resolves (to the frame buffer's start). *off = 0; *len = 0; arena = size == 0 ? g_interp.arena : NULL; *region = "stack"; } else if (addr < INTERP_GLOBAL_BASE) { *off = addr - INTERP_FRAME_BASE; *len = g_interp.arena_len > INTERP_FRAME_BASE ? g_interp.arena_len - INTERP_FRAME_BASE : 0; arena = g_interp.arena; *region = "frame"; } else if (addr < INTERP_CONST_BASE) { *off = addr - INTERP_GLOBAL_BASE; *len = g_interp.global_len; arena = g_interp.global_arena; *region = "global"; } else if (addr < INTERP_HEAP_BASE) { *off = addr - INTERP_CONST_BASE; *len = g_interp.const_len; arena = g_interp.const_arena; *region = "const"; } else { *off = addr - INTERP_HEAP_BASE; *len = g_interp.heap_len; arena = g_interp.heap_arena; *region = "heap"; } if (*off > *len || size > *len - *off) return NULL; if (arena == NULL) { // A region with no bytes yet still has its empty ranges. static uint8_t empty_range; return size == 0 ? &empty_range : NULL; } return arena + *off; } // The out-of-range report, out of line so interpArenaRangePtr's hit path // inlines. ZIG1_ATTRIBUTE((noreturn, cold, noinline)) static void interpArenaRangeOob(uint32_t addr, uint32_t size) { const char* region; uint32_t off, len; interpArenaResolve(addr, size, ®ion, &off, &len); fprintf(stderr, "interp: arena range oob region=%s addr=%u size=%u off=%u len=%u\n", region, addr, size, off, len); // The wasm stack below FRAME_BASE and the rest of upstream's linear // memory are not modelled (the layout comment above), so a valid program // can reach the first and last aborts; an in-range region always has its // buffer. if (addr < INTERP_FRAME_BASE && size != 0) UNIMPLEMENTED("wasm32: memory access in the wasm stack below frames"); if (off <= len && size <= len - off) UNREACHABLE("wasm32: arena memory range has no backing buffer"); UNIMPLEMENTED("wasm32: memory outside zig1's modelled regions"); } // All memory reads/writes route through this helper; callers never obtain an // unchecked global host pointer. ZIG1_INLINE uint8_t* interpArenaRangePtr(uint32_t addr, uint32_t size) { const char* region; uint32_t off, len; uint8_t* p = interpArenaResolve(addr, size, ®ion, &off, &len); if (!p) interpArenaRangeOob(addr, size); return p; } // Copy `size` guest bytes from `src` to `dst` (the ranges may overlap). static void interpArenaMove(uint32_t dst, uint32_t src, uint32_t size) { if (size != 0) memmove(interpArenaRangePtr(dst, size), interpArenaRangePtr(src, size), size); } // Whether [addr, addr+size) is backed by live interpreter memory; a non-empty // range in the wasm stack below the frames (the first 16 MiB) never is. static bool interpArenaRangeValid(uint32_t addr, uint32_t size) { const char* region; uint32_t off, len; return interpArenaResolve(addr, size, ®ion, &off, &len) != NULL; } static uint32_t interpWasmMemorySizePages(void) { return (INTERP_HEAP_BASE + g_interp.heap_len) / INTERP_WASM_PAGE_SIZE; } static uint32_t interpWasmHeapWindowBytes(void) { return INTERP_WASM_TOP_PAGE - INTERP_HEAP_BASE; } static bool interpCheckedWasmHeapGrow( uint32_t heap_len, uint32_t delta_pages, uint32_t* need) { const uint32_t heap_window = interpWasmHeapWindowBytes(); if (heap_len > heap_window) return false; uint32_t delta_bytes; if (!interpCheckedU32Mul(delta_pages, INTERP_WASM_PAGE_SIZE, &delta_bytes)) return false; uint32_t new_len; if (!interpCheckedU32Add(heap_len, delta_bytes, &new_len)) return false; if (new_len > heap_window) return false; if (need) *need = new_len; return true; } static uint32_t interpWasmMemoryGrow(uint32_t delta_pages) { uint32_t old_pages = interpWasmMemorySizePages(); uint32_t need; const uint32_t heap_window = interpWasmHeapWindowBytes(); if (!interpCheckedWasmHeapGrow(g_interp.heap_len, delta_pages, &need)) return UINT32_MAX; if (need > g_interp.heap_cap) { uint32_t nc; if (need == heap_window) { nc = heap_window; } else if (!interpCheckedGrowCapacity( g_interp.heap_cap, need, heap_window, &nc)) { return UINT32_MAX; } uint8_t* p = zig1Realloc(g_interp.heap_arena, nc); memset(p + g_interp.heap_cap, 0, nc - g_interp.heap_cap); g_interp.heap_arena = p; g_interp.heap_cap = nc; } if (need > g_interp.heap_len) memset(g_interp.heap_arena + g_interp.heap_len, 0, need - g_interp.heap_len); g_interp.heap_len = need; return old_pages; } static bool interpAddressIsConstArena(uint32_t addr) { return addr >= INTERP_CONST_BASE && addr < INTERP_HEAP_BASE; } // An internal address's arena; pointer arithmetic may not leave it. static uint32_t interpAddressRegion(uint32_t addr) { // The last wasm page is its own region: arithmetic reaching it from the // heap window (or leaving it) would alias another guest address. So is // the stack below the frames: leaving it would alias a live frame. if (addr >= INTERP_WASM_TOP_PAGE) return 4u; if (addr < INTERP_FRAME_BASE) return 5u; if (addr >= INTERP_HEAP_BASE) return 3u; if (addr >= INTERP_CONST_BASE) return 2u; if (addr >= INTERP_GLOBAL_BASE) return 1u; return 0u; } // Checked byte-address arithmetic. In addition to integer overflow, reject a // result that silently crosses into another arena's biased address range; // interpArenaRangePtr would otherwise classify the wrapped/crossed result as a // different allocation domain. static uint32_t interpAddressAdd(uint32_t base, uint32_t byte_offset) { uint32_t out; if (!interpCheckedU32Add(base, byte_offset, &out) || interpAddressRegion(out) != interpAddressRegion(base)) UNIMPLEMENTED("wasm32: pointer address addition overflow"); return out; } static uint32_t interpAddressIndex( uint32_t base, uint32_t index, uint32_t element_size) { uint32_t byte_offset; if (!interpCheckedU32Mul(index, element_size, &byte_offset)) UNIMPLEMENTED("wasm32: pointer element offset overflow"); return interpAddressAdd(base, byte_offset); } static uint32_t interpAddressSub(uint32_t base, uint32_t byte_offset) { if (byte_offset > base) UNIMPLEMENTED("wasm32: pointer address subtraction underflow"); uint32_t out = base - byte_offset; if (interpAddressRegion(out) != interpAddressRegion(base)) UNIMPLEMENTED("wasm32: pointer subtraction crossed arena region"); return out; } // Convert the bits carried by a pointer/aggregate InterpValue into the wasm32 // address model without silently truncating a malformed wider value. ZIG1_INLINE uint32_t interpAddressFromBits(uint64_t bits) { if (bits > UINT32_MAX) UNIMPLEMENTED("wasm32: pointer address exceeds wasm32"); return (uint32_t)bits; } // Reserve storage in the persistent mutable-global arena. Its contents live // for the whole top-level program, across nested frames, and are reset only at // the next top-level program. static uint32_t interpGlobalArenaAlloc(uint32_t size, uint32_t align) { const uint32_t limit = INTERP_CONST_BASE - INTERP_GLOBAL_BASE; if (g_interp.global_len < 8) g_interp.global_len = 8; uint32_t off, need; InterpBumpStatus bump = interpCheckedBump( g_interp.global_len, size, align, limit, &off, &need); if (bump == INTERP_BUMP_BAD_ALIGN) UNIMPLEMENTED("wasm32: global arena alignment is not a power of two"); if (bump == INTERP_BUMP_ALIGN_OVERFLOW) UNIMPLEMENTED("wasm32: global arena alignment overflow"); if (bump != INTERP_BUMP_OK) UNIMPLEMENTED("wasm32: global arena exhausted"); if (need > g_interp.global_cap) { uint32_t nc; if (!interpCheckedGrowCapacity(g_interp.global_cap, need, limit, &nc)) UNIMPLEMENTED("wasm32: global arena capacity overflow"); uint8_t* p = zig1Realloc(g_interp.global_arena, nc); memset(p + g_interp.global_cap, 0, nc - g_interp.global_cap); g_interp.global_arena = p; g_interp.global_cap = nc; } g_interp.global_len = need; uint32_t addr; if (!interpCheckedU32Add(INTERP_GLOBAL_BASE, off, &addr)) UNIMPLEMENTED("wasm32: global arena address overflow"); if (size != 0) memset(interpArenaRangePtr(addr, size), 0, size); return addr; } // Reserve `size` bytes aligned to `align` in the persistent const arena and // return the biased address (>= INTERP_CONST_BASE). Never frame-reclaimed; // only interpConstReset (per top-level program) discards it. static uint32_t interpConstArenaAlloc(uint32_t size, uint32_t align) { const uint32_t limit = INTERP_HEAP_BASE - INTERP_CONST_BASE; if (g_interp.const_len < 8) g_interp.const_len = 8; // reserve offset 0 (reads as "null") uint32_t off, need; InterpBumpStatus bump = interpCheckedBump( g_interp.const_len, size, align, limit, &off, &need); if (bump == INTERP_BUMP_BAD_ALIGN) UNIMPLEMENTED("wasm32: const arena alignment is not a power of two"); if (bump == INTERP_BUMP_ALIGN_OVERFLOW) UNIMPLEMENTED("wasm32: const arena alignment overflow"); if (bump != INTERP_BUMP_OK) UNIMPLEMENTED("wasm32: const arena exhausted"); if (need > g_interp.const_cap) { uint32_t nc; if (!interpCheckedGrowCapacity(g_interp.const_cap, need, limit, &nc)) UNIMPLEMENTED("wasm32: const arena capacity overflow"); uint8_t* p = zig1Realloc(g_interp.const_arena, nc); memset(p + g_interp.const_cap, 0, nc - g_interp.const_cap); g_interp.const_arena = p; g_interp.const_cap = nc; } memset(g_interp.const_arena + off, 0, size); g_interp.const_len = need; uint32_t addr; if (!interpCheckedU32Add(INTERP_CONST_BASE, off, &addr)) UNIMPLEMENTED("wasm32: const arena address overflow"); return addr; } // ── Const-materialization cache // ── The const cache ───────────────────────────────────────────────────────── // interpDecodeConst rematerializes an interned aggregate on every reference — // a const array indexed in a loop is a fresh materialization per iteration. // This open-addressing map memoizes idx -> {addr, ty} so a given interned // const is materialized once per top-level program. Correctness rests on two // facts: (1) the const arena is never frame-reclaimed, so a cached address is // live for the whole run; (2) comptime consts are immutable — a program can // only read a materialized const or copy it (an AIR `store` of an aggregate // copies its bytes into a distinct alloc via interpStoreTyped; no code path // writes through a materialized-const address, and writing through a // `*const` is illegal Zig), so one shared materialization is indistinguishable // from many. Keyed by IP index; the cache + const arena are reset together per // top-level program (indices are per-InternPool; the test binary runs many). #define CC_EMPTY 0xffffffffu // A uav value's own storage is keyed by the value's IP index with this bit // set, apart from the value's decode (IP indices never reach it). #define CC_UAV_STORAGE 0x80000000u static void interpConstReset(void) { g_interp.const_len = 0; g_interp.cc_count = 0; g_interp.uav_align_len = 0; for (uint32_t i = 0; i < g_interp.cc_cap; i++) g_interp.cc_key[i] = CC_EMPTY; g_interp.tagname_memo_len = 0; // its addresses were in the const arena g_interp.error_name_slices_len = 0; } // Start a fresh top-level interpreted program. Persistent regions survive // nested activations, but no address/cache entry may leak across separate // programs because each has a different InternPool/Nav index space. Retain // host capacities to avoid allocation churn. static void interpProgramReset(void) { interpConstReset(); g_interp.global_len = 0; g_interp.heap_len = 0; if (g_interp.nav_cap != 0) memset(g_interp.nav_state, 0, g_interp.nav_cap * sizeof(*g_interp.nav_state)); if (g_interp.layout_memo_cap != 0) memset(g_interp.layout_memo, 0, g_interp.layout_memo_cap * sizeof(*g_interp.layout_memo)); } // (part of) InternPool.zig:getErrorValue // An error value's runtime representation is its global error set value // (InternPool.getErrorValue); Sema registered every name it interned. static uint32_t interpErrorCode(const InternPool* ip, uint32_t name) { uint32_t code = ipGetErrorValueIfExists(ip, name); if (code == 0) UNIMPLEMENTED("getErrorValue: error name not in the global error set"); return code; } ZIG1_INLINE uint32_t interpAbiSize(InternPool* ip, uint32_t ty); ZIG1_INLINE uint32_t interpAbiAlign(InternPool* ip, uint32_t ty); static void interpWriteSliceHeader(uint32_t addr, uint64_t ptr, uint32_t len); static uint32_t interpErrorNameSlice(InternPool* ip, uint32_t code) { if (code == 0 || code - 1u >= ip->global_error_names_len) UNIMPLEMENTED("error_name: error code out of range"); uint32_t idx = code - 1u; if (idx < g_interp.error_name_slices_len && g_interp.error_name_slices[idx] != 0) return g_interp.error_name_slices[idx]; uint32_t need = idx + 1u; if (need > g_interp.error_name_slices_cap) { uint32_t nc; if (!interpCheckedGrowCapacity(g_interp.error_name_slices_cap, need, (uint32_t)UINT16_MAX + 1u, &nc)) UNIMPLEMENTED("error_name: slice cache capacity overflow"); uint32_t* slices = zig1Realloc(g_interp.error_name_slices, nc * sizeof(*slices)); for (uint32_t i = g_interp.error_name_slices_cap; i < nc; i++) slices[i] = 0; g_interp.error_name_slices = slices; g_interp.error_name_slices_cap = nc; } if (need > g_interp.error_name_slices_len) { for (uint32_t i = g_interp.error_name_slices_len; i < need; i++) g_interp.error_name_slices[i] = 0; g_interp.error_name_slices_len = need; } uint32_t name = ip->global_error_names[idx]; const char* sb = ipStringBytes(ip, name); size_t host_len = strlen(sb); if (host_len > UINT32_MAX) UNIMPLEMENTED("error_name: name exceeds wasm32"); uint32_t nlen = (uint32_t)host_len; uint32_t alloc_len; if (!interpCheckedU32Add(nlen, 1u, &alloc_len)) UNIMPLEMENTED("error_name: sentinel length overflow"); uint32_t arr_addr = interpConstArenaAlloc(alloc_len, 1u); for (uint32_t i = 0; i < nlen; i++) interpWriteLE(interpAddressAdd(arr_addr, i), (uint8_t)sb[i], 1u); interpWriteLE(interpAddressAdd(arr_addr, nlen), 0u, 1u); uint32_t slice_ty = ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE; uint32_t slice_addr = interpConstArenaAlloc( interpAbiSize(ip, slice_ty), interpAbiAlign(ip, slice_ty)); interpWriteSliceHeader(slice_addr, arr_addr, nlen); g_interp.error_name_slices[idx] = slice_addr; return slice_addr; } // Look up a cached materialization; on hit write *out_addr/*out_ty and return // true. static bool interpConstCacheLookup( uint32_t idx, uint32_t* out_addr, uint32_t* out_ty) { if (g_interp.cc_cap == 0) return false; uint32_t mask = g_interp.cc_cap - 1u; uint32_t h = (idx * 2654435761u) & mask; // Knuth multiplicative hash for (uint32_t probe = 0; probe < g_interp.cc_cap; probe++) { uint32_t slot = (h + probe) & mask; uint32_t k = g_interp.cc_key[slot]; if (k == CC_EMPTY) return false; if (k == idx) { *out_addr = g_interp.cc_addr[slot]; *out_ty = g_interp.cc_ty[slot]; return true; } } return false; } static void interpConstCacheInsert(uint32_t idx, uint32_t addr, uint32_t ty) { // Grow (and rehash) when over ~70% full, or on first use. uint32_t next_count; if (!interpCheckedU32Add(g_interp.cc_count, 1u, &next_count)) UNIMPLEMENTED("wasm32: const cache count overflow"); // `count / cap > 0.7`, written without overflowing either product. bool grow = g_interp.cc_cap == 0 || next_count > g_interp.cc_cap - (g_interp.cc_cap / 10u) * 3u; if (grow) { uint32_t ncap; if (!interpCheckedNextCacheCapacity(g_interp.cc_cap, UINT32_MAX / (uint32_t)sizeof(uint32_t), &ncap)) UNIMPLEMENTED("wasm32: const cache capacity overflow"); // Open-addressing masks require a power-of-two capacity. The growth // helper doubles from the initial 256, so this invariant is stable. if ((ncap & (ncap - 1u)) != 0) UNIMPLEMENTED("wasm32: const cache capacity is not power of two"); size_t bytes = (size_t)ncap * sizeof(uint32_t); uint32_t* nk = zig1Malloc(bytes); uint32_t* na = zig1Malloc(bytes); uint32_t* nt = zig1Malloc(bytes); for (uint32_t i = 0; i < ncap; i++) nk[i] = CC_EMPTY; uint32_t nmask = ncap - 1u; for (uint32_t i = 0; i < g_interp.cc_cap; i++) { if (g_interp.cc_key[i] == CC_EMPTY) continue; uint32_t h = (g_interp.cc_key[i] * 2654435761u) & nmask; for (uint32_t probe = 0; probe < ncap; probe++) { uint32_t slot = (h + probe) & nmask; if (nk[slot] == CC_EMPTY) { nk[slot] = g_interp.cc_key[i]; na[slot] = g_interp.cc_addr[i]; nt[slot] = g_interp.cc_ty[i]; break; } } } free(g_interp.cc_key); free(g_interp.cc_addr); free(g_interp.cc_ty); g_interp.cc_key = nk; g_interp.cc_addr = na; g_interp.cc_ty = nt; g_interp.cc_cap = ncap; } uint32_t mask = g_interp.cc_cap - 1u; uint32_t h = (idx * 2654435761u) & mask; for (uint32_t probe = 0; probe < g_interp.cc_cap; probe++) { uint32_t slot = (h + probe) & mask; if (g_interp.cc_key[slot] == CC_EMPTY) { g_interp.cc_key[slot] = idx; g_interp.cc_addr[slot] = addr; g_interp.cc_ty[slot] = ty; g_interp.cc_count = next_count; return; } if (g_interp.cc_key[slot] == idx) return; // already present (idempotent) } } // Write/read `size` low bytes of `bits` in little-endian order (wasm32 byte // order; independent of host endianness). Dispatches on the address region. static void interpWriteLE(uint32_t addr, uint64_t bits, uint32_t size) { // A scalar store is at most 8 bytes: `bits` holds the whole value. A wider // (>64-bit) integer would need the `Value.hi` limb, which this LE writer // does not carry, and `bits >> (i*8)` with i>=8 is UB — abort loudly // rather than silently drop the high limb. Wyhash's u128 lives only in SSA // (it is consumed by `@truncate` to u64 before any store), so this is // unreached today; the guard fires only if a wide value ever escapes to // memory. if (size > 8) UNIMPLEMENTED("store: >64-bit scalar store not modeled"); uint8_t* p = interpArenaRangePtr(addr, size); switch (size) { case 0: return; case 1: p[0] = (uint8_t)bits; return; case 2: p[0] = (uint8_t)bits; p[1] = (uint8_t)(bits >> 8u); return; case 4: p[0] = (uint8_t)bits; p[1] = (uint8_t)(bits >> 8u); p[2] = (uint8_t)(bits >> 16u); p[3] = (uint8_t)(bits >> 24u); return; case 8: p[0] = (uint8_t)bits; p[1] = (uint8_t)(bits >> 8u); p[2] = (uint8_t)(bits >> 16u); p[3] = (uint8_t)(bits >> 24u); p[4] = (uint8_t)(bits >> 32u); p[5] = (uint8_t)(bits >> 40u); p[6] = (uint8_t)(bits >> 48u); p[7] = (uint8_t)(bits >> 56u); return; } for (uint32_t i = 0; i < size; i++) p[i] = (uint8_t)((bits >> (i * 8u)) & 0xffu); } static uint64_t interpReadLE(uint32_t addr, uint32_t size) { if (size > 8) UNIMPLEMENTED("load: >64-bit scalar load not modeled"); const uint8_t* p = interpArenaRangePtr(addr, size); switch (size) { case 0: return 0; case 1: return p[0]; case 2: return (uint64_t)p[0] | ((uint64_t)p[1] << 8u); case 4: return (uint64_t)p[0] | ((uint64_t)p[1] << 8u) | ((uint64_t)p[2] << 16u) | ((uint64_t)p[3] << 24u); case 8: return (uint64_t)p[0] | ((uint64_t)p[1] << 8u) | ((uint64_t)p[2] << 16u) | ((uint64_t)p[3] << 24u) | ((uint64_t)p[4] << 32u) | ((uint64_t)p[5] << 40u) | ((uint64_t)p[6] << 48u) | ((uint64_t)p[7] << 56u); } uint64_t v = 0; for (uint32_t i = 0; i < size; i++) v |= (uint64_t)p[i] << (i * 8u); return v; } // ── Type layout (wasm32 ABI) ──────────────────────────────────────────────── // The interpreter's flat memory is laid out by type.c's Type.abiSize / // abiAlignment / structFieldOffset / getUnionLayout, the one wasm32 layout // Sema also uses. static bool interpTypeIsOptional(const InternPool* ip, uint32_t ty) { return ip->item_tags[ty] == IP_TAG_TYPE_OPTIONAL; } static bool interpTypeIsErrUnion(const InternPool* ip, uint32_t ty) { uint8_t tag = ip->item_tags[ty]; return tag == IP_TAG_TYPE_ANYERROR_UNION || tag == IP_TAG_TYPE_ERROR_UNION; } // The type tags that are always by-address aggregates. A packed struct or // union has its own tags and is a scalar (its backing integer); an anonymous // tuple ({usize, u1} from @addWithOverflow) is by-address like any struct. ZIG1_INLINE bool interpTagIsAggregate(uint8_t tag) { switch (tag) { case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_TUPLE: case IP_TAG_TYPE_UNION: case IP_TAG_TYPE_ARRAY_SMALL: case IP_TAG_TYPE_ARRAY_BIG: case IP_TAG_TYPE_VECTOR: case IP_TAG_TYPE_SLICE: case IP_TAG_TYPE_ERROR_UNION: case IP_TAG_TYPE_ANYERROR_UNION: return true; default: return false; } } // Most aggregates live in the arena: an InterpValue carries the address of // their bytes, and store/load copy `abiSize` bytes rather than encoding a // scalar. Payload-niche optionals whose payload fits in one scalar word (not // slices) are the exception: their ABI is exactly the payload word, so Values // carry that word directly. static bool interpTypeIsAggregate(const InternPool* ip, uint32_t ty) { // Vectors, preinterned or not, are by-address like arrays; the tag // decides below. if (ty < ZIR_REF_SLICE_CONST_U8_TYPE || ty == ZIR_REF_MANYPTR_CONST_SLICE_CONST_U8_TYPE || ty == ZIR_REF_MANYPTR_CONST_TYPE_TYPE) return false; uint8_t tag = ip->item_tags[ty]; if (tag == IP_TAG_TYPE_OPTIONAL) { uint32_t child = ipOptionalChild(ip, ty); return !ztOptionalReprIsPayload(ip, ty) || (ztIsSlice(ip, child)); } return interpTagIsAggregate(tag); } // Type.zig:hasRuntimeBits, Type.zig:classify // `Type.hasRuntimeBits` (src/Type.zig), a trivial wrapper over `Type.classify` // — already ported as `ztTypeClass`: // return switch (ty.classify(zcu)) { // .no_possible_value, .one_possible_value, .fully_comptime => false, // .runtime, .partially_comptime => true, // }; // DIVERGENCE (deliberate, and the conservative direction): `ZT_CLASS_UNKNOWN` // — malformed/unresolved aggregate metadata or an illegal by-value cycle, a // state upstream's `classify` cannot be in — has no upstream answer. It is // reported as "has runtime bits", since every caller uses `false` to skip // work, leaving the loud abort with whoever actually resolves the layout. This // is not the header's forbidden "silently treat unknown as // runtime-representable": nothing here inspects a layout. static bool interpTypeHasRuntimeBits(InternPool* ip, uint32_t ty) { switch (ztTypeClass(ip, ty)) { case ZT_CLASS_NPV: case ZT_CLASS_OPV: case ZT_CLASS_FULL: return false; case ZT_CLASS_RUNTIME: case ZT_CLASS_PARTIAL: case ZT_CLASS_UNKNOWN: return true; } UNIMPLEMENTED("hasRuntimeBits: unknown type class"); ZIG1_TCC_RETURN(false); } typedef enum { // ?noreturn: the sole value is null and no payload/flag bytes exist. INTERP_OPT_REPR_NPV_NULL, // Pointer-, slice-, and error-like optional: payload zero is null. INTERP_OPT_REPR_PAYLOAD_NICHE, // Ordinary runtime optional: the interpreter's {payload, flag} model. INTERP_OPT_REPR_PAYLOAD_FLAG, // ?type and other fully-comptime children: state exists only at comptime. INTERP_OPT_REPR_FULL, } InterpOptionalRepr; static InterpOptionalRepr interpOptionalRepr(InternPool* ip, uint32_t opt_ty) { ZtTypeClass child_class = ztTypeClass(ip, ipOptionalChild(ip, opt_ty)); switch (child_class) { case ZT_CLASS_NPV: return INTERP_OPT_REPR_NPV_NULL; case ZT_CLASS_FULL: return INTERP_OPT_REPR_FULL; case ZT_CLASS_OPV: case ZT_CLASS_RUNTIME: case ZT_CLASS_PARTIAL: return ztOptionalReprIsPayload(ip, opt_ty) ? INTERP_OPT_REPR_PAYLOAD_NICHE : INTERP_OPT_REPR_PAYLOAD_FLAG; case ZT_CLASS_UNKNOWN: UNIMPLEMENTED( "Type.optionalReprIsPayload: optional child class unresolved"); } UNIMPLEMENTED("Type.optionalReprIsPayload: invalid optional child class"); ZIG1_TCC_RETURN(INTERP_OPT_REPR_NPV_NULL); } static bool interpErrUnionFullyComptime(InternPool* ip, uint32_t eu_ty) { if (!interpTypeIsErrUnion(ip, eu_ty)) UNREACHABLE("interp: error-union class of non-error-union"); ZtTypeClass payload_class = ztTypeClass(ip, ipErrorUnionPayload(ip, eu_ty)); if (payload_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED( "Type.errorUnionPayload: error-union payload class unresolved"); return payload_class == ZT_CLASS_FULL; } static uint32_t interpLayoutMul(uint32_t a, uint32_t b) { uint32_t out; if (!interpCheckedU32Mul(a, b, &out)) UNIMPLEMENTED("wasm32: ABI layout multiplication overflow"); return out; } static uint32_t interpLayoutAlign(uint32_t value, uint32_t align) { uint32_t out; if (!interpCheckedAlignForward(value, align, &out)) UNIMPLEMENTED("wasm32: ABI layout alignment overflow"); return out; } typedef struct { uint32_t len; // language-visible length, excluding the sentinel uint32_t storage_len; // Type.Array.lenIncludingSentinel() uint32_t child; uint32_t sentinel; // interned value, or IP_INDEX_NONE } InterpArrayInfo; // (part of) Type.zig:arrayLen, InternPool.zig:aggregateTypeLen, // Type.zig:childType // Decode small/big array metadata without conflating // language length with storage length. A TypeArrayBig sentinel occupies one // extra ABI element but remains excluded from `.len`, slicing, bounds, and AIR // aggregate payloads. // // Vectors decode here too, exactly as upstream's Type.arrayLen (src/Type.zig, // via InternPool.aggregateTypeLen) and Type.childType (via ip.childType's // unified `inline .array_type, .vector_type` arm) cover `.array_type` and // `.vector_type` uniformly: a vector shares // the small array's // {len, child} payload words and has no sentinel. This is what lets the // AIR `array_elem_val` and `aggregate_init` handlers stay single-bodied, // mirroring Sema's own elemValArray sharing between `.array` and `.vector` // (src/Sema.zig). Element stride is the element ABI size for both // (src/Type.zig); only the total size's tail rounding differs. // The asserting view of the shared `ipArrayInfo` accessor (zig1.h): the // interpreter has already established that `ty` is a sequence type, so every // non-OK status is an internal-invariant violation and aborts. An // out-of-range `ty` reports IP_ARR_NOT_SEQ, the "non-array type" abort. static InterpArrayInfo interpArrayInfo(const InternPool* ip, uint32_t ty) { IpArrayInfo shared; switch (ipArrayInfoInline(ip, ty, &shared)) { case IP_ARR_OK: break; case IP_ARR_NOT_SEQ: UNREACHABLE("interp: arrayInfo of non-array type"); case IP_ARR_MALFORMED: UNREACHABLE("interp: malformed array type payload"); case IP_ARR_LEN_TOO_BIG: UNIMPLEMENTED("arrayLen: array length exceeds u32 address model"); } if (shared.storage_len_overflowed) UNIMPLEMENTED("arrayLen: sentinel array storage length overflow"); InterpArrayInfo info; info.len = shared.len; info.storage_len = shared.storage_len; info.child = shared.child; info.sentinel = shared.sentinel; return info; } // type.c's layout without a Zcu, since the interpreter resolves nothing; a // type that reaches it unresolved (zig1's Sema lacks some of upstream's // ensureLayoutResolved sites) is laid out on the spot. Every load, store and // alloc asks, so a type's size and alignment are memoized per program // (layout_memo). static ZIG1_NOINLINE uint64_t interpLayoutMemoFill( InternPool* ip, uint32_t ty) { uint64_t size, align, bits; if (!ztLayoutInfo(NULL, ip, ty, &size, &align, &bits) || size > UINT32_MAX || align > (UINT64_C(1) << 31)) UNIMPLEMENTED("Type.abiSize: type has no wasm32 layout"); if (ty >= g_interp.layout_memo_cap) { uint32_t cap = ip->items_len; g_interp.layout_memo = zig1Realloc(g_interp.layout_memo, cap * sizeof(uint64_t)); memset(g_interp.layout_memo + g_interp.layout_memo_cap, 0, (cap - g_interp.layout_memo_cap) * sizeof(uint64_t)); g_interp.layout_memo_cap = cap; } uint64_t m = (size << 6) | ztLog2PowerOfTwoU64(align); g_interp.layout_memo[ty] = m + 1u; return m; } ZIG1_INLINE uint64_t interpLayout(InternPool* ip, uint32_t ty) { if (ty < g_interp.layout_memo_cap && g_interp.layout_memo[ty] != 0) return g_interp.layout_memo[ty] - 1u; return interpLayoutMemoFill(ip, ty); } ZIG1_INLINE uint32_t interpAbiSize(InternPool* ip, uint32_t ty) { return (uint32_t)(interpLayout(ip, ty) >> 6); } ZIG1_INLINE uint32_t interpAbiAlign(InternPool* ip, uint32_t ty) { return UINT32_C(1) << (interpLayout(ip, ty) & 63u); } static uint32_t interpStructFieldOffset( InternPool* ip, uint32_t ty, uint32_t field_index) { return (uint32_t)ztStructFieldOffset(NULL, ip, ty, field_index); } static ZtUnionLayout interpUnionLayout(InternPool* ip, uint32_t uty) { ZtUnionLayout layout; if (!ztGetUnionLayout(NULL, ip, uty, &layout)) UNIMPLEMENTED("Type.getUnionLayout: union has no wasm32 layout"); return layout; } // The tag int of the union at `addr`: tag_size bytes at UnionLayout.tagOffset. static uint64_t interpUnionTagLoad(const ZtUnionLayout* ul, uint32_t addr) { return interpReadLE( interpAddressAdd(addr, (uint32_t)ztUnionLayoutTagOffset(ul)), (uint32_t)ul->tag_size); } static void interpUnionTagStore( const ZtUnionLayout* ul, uint32_t addr, uint64_t bits) { interpWriteLE(interpAddressAdd(addr, (uint32_t)ztUnionLayoutTagOffset(ul)), bits, (uint32_t)ul->tag_size); } ZIG1_INLINE uint32_t interpPtrPayloadBase( const InternPool* ip, uint32_t ptr_ty) { if (ptr_ty >= ip->items_len || ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) UNREACHABLE("interp: pointer operation on non-pointer type"); uint32_t base = ip->item_datas[ptr_ty]; if (base > ip->extra_len || ip->extra_len - base < IP_TYPE_POINTER_LEN) UNREACHABLE("interp: malformed pointer type payload"); return base; } // The pointee (child) type of a pointer type. ZIG1_INLINE uint32_t interpPtrChild(const InternPool* ip, uint32_t ptr_ty) { uint32_t base = interpPtrPayloadBase(ip, ptr_ty); return ip->extra[base + IP_TYPE_POINTER_CHILD]; } // `@ptrFromInt`: the guest address is the interpreter's (see // INTERP_HEAP_BASE). static InterpValue interpPtrFromIntScalar(uint32_t res_ty, InterpValue op) { return (InterpValue) { interpAddressFromBits(op.bits), res_ty }; } // (part of) Type.zig:ptrAlignment, Type.zig:fromInterned // `InternPool.Alignment.toByteUnits` under `Type.ptrAlignment`'s fallback // (src/Type.zig): "if (ptr_key.flags.alignment != .none) return // ptr_key.flags.alignment; return // Type.fromInterned(ptr_key.child).abiAlignment(zcu);". `.none` is the u6 // sentinel IP_PTR_ALIGN_NONE, which is also what a Nav's resolved alignment // carries, so the pointer and the two Nav slots below name one rule. static inline uint32_t interpAlignmentToBytes( InternPool* ip, uint32_t align_log2, uint32_t natural_ty) { if (align_log2 == IP_PTR_ALIGN_NONE) return interpAbiAlign(ip, natural_ty); if (align_log2 >= 32u) UNIMPLEMENTED( "ptrAlignment: alignment exceeds the 32-bit address space"); return UINT32_C(1) << align_log2; } // An AIR `alloc` carries the whole result pointer type, so `var x: u32 // align(64)` reaches the interpreter as `*align(64) u32` and the explicit // alignment lives nowhere else -- reading only the child's ABI alignment // silently placed every over-aligned local at its natural alignment. static inline uint32_t interpPtrAlignment(InternPool* ip, uint32_t ptr_ty) { uint32_t base = interpPtrPayloadBase(ip, ptr_ty); return interpAlignmentToBytes( ip, IP_PTR_FLAGS_ALIGN(ip->extra[base + 2u]), ip->extra[base]); } // (part of) Sema.zig:elemPtrVector // Apply a vector element pointer's `vector_index` to a base address. // // Sema.elemPtrVector lowers `&v[i]` to `"block.addTyOp(.ptr_cast, // elem_ptr_ty, vector_ptr)"` -- the pointer value still addresses the whole // vector, and the element is carried by the pointer type // (`"vector_index = @enumFromInt(index)"`, `"host_size = // @intCast(vector_len)"`). So every access through such a pointer must apply // the element offset itself, which for this chain's C/wasm backend family is // the element ABI stride -- literally how src/codegen/c.zig spells the access // (`"try w.print("[{d}]", .{@intFromEnum(index)});"`), and the same stride // ztLayoutInfo's `.vector_type` arm lays a vector out at. // Returns `addr` unchanged for an ordinary pointer. static uint32_t interpVectorElemAddrIndexed( InternPool* ip, uint32_t base, uint32_t vector_index, uint32_t addr); // Inline: every load and store through a pointer asks it, and almost no // pointer carries a vector_index. ZIG1_INLINE uint32_t interpVectorElemAddr( InternPool* ip, uint32_t ptr_ty, uint32_t addr) { if (ptr_ty >= ip->items_len || ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) return addr; uint32_t base = interpPtrPayloadBase(ip, ptr_ty); uint32_t vector_index = IP_PTR_FLAGS_VECTOR_INDEX(ip->extra[base + IP_TYPE_POINTER_FLAGS]); if (vector_index == IP_PTR_VECTOR_INDEX_NONE) return addr; return interpVectorElemAddrIndexed(ip, base, vector_index, addr); } static uint32_t interpVectorElemAddrIndexed( InternPool* ip, uint32_t base, uint32_t vector_index, uint32_t addr) { // Zcu.PerThread.ptrType's own invariant for this pointer shape: // `"_ => assert(@intFromEnum(info.flags.vector_index) < // info.packed_offset.host_size),"`, host_size counting elements. if (vector_index >= IP_PTR_PACKED_HOST_SIZE(ip->extra[base + 3u])) UNIMPLEMENTED( "ptr_elem_ptr: vector_index beyond the pointer's host_size"); return interpAddressIndex( addr, vector_index, interpAbiSize(ip, ip->extra[base])); } // The pointer's size class (`Ptr.Size`) from the packed flags word (extra // offset +2). static inline uint32_t interpPtrSizeFromBase( const InternPool* ip, uint32_t base) { return IP_PTR_FLAGS_SIZE(ip->extra[base + 2u]); } static void interpWriteSliceHeader(uint32_t addr, uint64_t ptr, uint32_t len) { interpWriteLE(addr, ptr, 4u); interpWriteLE(interpAddressAdd(addr, 4u), len, 4u); } static InterpValue interpMakeSliceValue( InternPool* ip, uint32_t slice_ty, uint64_t ptr, uint32_t len) { uint32_t saddr = interpArenaAlloc( interpAbiSize(ip, slice_ty), interpAbiAlign(ip, slice_ty)); interpWriteSliceHeader(saddr, ptr, len); return (InterpValue) { saddr, slice_ty }; } static uint32_t interpSliceCastLen(InternPool* ip, uint32_t dst_slice_ty, uint32_t src_slice_ty, uint32_t src_len) { uint32_t src_many = ip->item_datas[src_slice_ty]; uint32_t dst_many = ip->item_datas[dst_slice_ty]; uint32_t src_elem = interpPtrChild(ip, src_many); uint32_t dst_elem = interpPtrChild(ip, dst_many); uint32_t src_size = interpAbiSize(ip, src_elem); uint32_t dst_size = interpAbiSize(ip, dst_elem); // Equal element sizes keep the length -- a zero-size child included, // which is how an in-memory `[]void` -> `[]const void` coercion reaches // here. if (src_size == dst_size) return src_len; if (src_size == 0 || dst_size == 0) UNIMPLEMENTED("ptr_cast: slice with zero-size child"); uint32_t byte_len; if (!interpCheckedU32Mul(src_len, src_size, &byte_len)) UNIMPLEMENTED("ptr_cast: slice byte length overflow"); if (byte_len % dst_size != 0) UNIMPLEMENTED("ptr_cast: slice length not element-aligned"); return byte_len / dst_size; } // Whether `ty` is stored as an integer (an int, an enum through its tag // int, a packed container through its backing int), and that integer's // bit width. ZIG1_INLINE bool interpIntegerStorageWidth( InternPool* ip, uint32_t ty, uint32_t* width) { for (;;) { ty = interpBackingType(ip, ty); const InterpIntInfo* info = interpStaticIntInfo(ty); if (info != NULL) { *width = info->width; return true; } bool is_signed; if (interpDynIntInfo(ip, ty, width, &is_signed)) return true; if (ty >= ip->items_len || !ipTypeIsBitpack(ip, ty)) return false; ty = ipBitpackBackingInt(ip, ty); } } static bool interpTypeIsIntegerStorage(InternPool* ip, uint32_t ty) { uint32_t width; return interpIntegerStorageWidth(ip, ty, &width); } // AIR stores normally have an operand whose type is exactly the pointer // child. The one intentional representation exception is a comptime // whole-array pointer standing in for a slice value; interpStoreTyped // materializes its {ptr,len} pair. Keep that exception narrow and structural. static bool interpErrorSetCanStore( const InternPool* ip, uint32_t dest_ty, uint32_t value_ty) { if (!ipIsErrorSetType(ip, dest_ty) || !ipIsErrorSetType(ip, value_ty)) return false; if (dest_ty == value_ty || dest_ty == ZIR_REF_ANYERROR_TYPE) return true; if (value_ty == ZIR_REF_ANYERROR_TYPE) return false; if (dest_ty >= ip->items_len || value_ty >= ip->items_len) return false; if (ip->item_tags[dest_ty] != IP_TAG_TYPE_ERROR_SET || ip->item_tags[value_ty] != IP_TAG_TYPE_ERROR_SET) return false; uint32_t len = ipErrorSetNamesLen(ip, value_ty); for (uint32_t i = 0; i < len; i++) { uint32_t name = ipErrorSetName(ip, value_ty, i); if (!ipErrorSetHasName(ip, dest_ty, name, IP_IES_NAME_NONE)) return false; } return true; } static bool interpStoreTypeMatches( InternPool* ip, uint32_t dest_ty, uint32_t value_ty); static bool interpStructStoreTypeMatches( InternPool* ip, uint32_t dest_ty, uint32_t value_ty) { if (!ipTagIsAnyStruct(ip->item_tags[dest_ty]) || !ipTagIsAnyStruct(ip->item_tags[value_ty])) return false; if (ipStructIsPacked(ip, dest_ty) || ipStructIsPacked(ip, value_ty)) return false; uint32_t n = ipStructFieldsLen(ip, dest_ty); if (ipStructFieldsLen(ip, value_ty) != n) return false; if (interpAbiSize(ip, dest_ty) != interpAbiSize(ip, value_ty) || interpAbiAlign(ip, dest_ty) != interpAbiAlign(ip, value_ty)) return false; for (uint32_t i = 0; i < n; i++) { if (ipStructFieldName(ip, dest_ty, i) != ipStructFieldName(ip, value_ty, i) || ipStructFieldIsComptime(ip, dest_ty, i) != ipStructFieldIsComptime(ip, value_ty, i) || interpStructFieldOffset(ip, dest_ty, i) != interpStructFieldOffset(ip, value_ty, i) || !interpStoreTypeMatches(ip, ipStructFieldType(ip, dest_ty, i), ipStructFieldType(ip, value_ty, i))) return false; } return true; } static bool interpStoreTypeMatches( InternPool* ip, uint32_t dest_ty, uint32_t value_ty) { if (dest_ty == value_ty) return true; if (dest_ty >= ip->items_len || value_ty >= ip->items_len) return false; if (ip->item_tags[dest_ty] == IP_TAG_TYPE_POINTER && ip->item_tags[value_ty] == IP_TAG_TYPE_POINTER) { // AIR retains the source pointer type for representation-preserving // coercions. The reached forms are alignment/qualifier weakening // (same child) and type erasure to *anyopaque; both store one wasm32 // address and no reinterpretation of pointee storage occurs here. uint32_t dest_child = interpPtrChild(ip, dest_ty); uint32_t value_child = interpPtrChild(ip, value_ty); return dest_child == value_child || dest_child == ZIR_REF_ANYOPAQUE_TYPE; } if (ztIsSlice(ip, dest_ty) && ztIsSlice(ip, value_ty)) { // Mutable-to-const and sentinel-forgetting slice coercions retain the // source slice type in AIR. Their runtime {ptr,len} representation is // identical. This can recurse for nested slices, e.g. // `[]const [:0]const u8` stored into `[]const []const u8`. uint32_t dest_many = ip->item_datas[dest_ty]; uint32_t value_many = ip->item_datas[value_ty]; uint32_t dest_child = interpPtrChild(ip, dest_many); uint32_t value_child = interpPtrChild(ip, value_many); return dest_child == value_child || interpStoreTypeMatches(ip, dest_child, value_child); } if ((ip->item_tags[dest_ty] == IP_TAG_TYPE_ARRAY_SMALL || ip->item_tags[dest_ty] == IP_TAG_TYPE_ARRAY_BIG || ip->item_tags[dest_ty] == IP_TAG_TYPE_VECTOR) && (ip->item_tags[value_ty] == IP_TAG_TYPE_ARRAY_SMALL || ip->item_tags[value_ty] == IP_TAG_TYPE_ARRAY_BIG || ip->item_tags[value_ty] == IP_TAG_TYPE_VECTOR)) { InterpArrayInfo dest_array = interpArrayInfo(ip, dest_ty); InterpArrayInfo value_array = interpArrayInfo(ip, value_ty); return dest_array.len == value_array.len && dest_array.storage_len == value_array.storage_len && dest_array.child == value_array.child && dest_array.sentinel == value_array.sentinel; } // AIR keeps the RHS integer type for ordinary implicit integer coercions // (for example u32 -> usize on wasm32). Encode using the destination type // below, which performs the required extension/truncation of the already // Sema-validated value instead of sizing the store from the RHS type. if (interpTypeIsIntegerStorage(ip, dest_ty) && interpTypeIsIntegerStorage(ip, value_ty)) return true; if (interpErrorSetCanStore(ip, dest_ty, value_ty)) return true; if (interpStructStoreTypeMatches(ip, dest_ty, value_ty)) return true; if (!ztIsSlice(ip, dest_ty) || ip->item_tags[value_ty] != IP_TAG_TYPE_POINTER) return false; uint32_t source_child = interpPtrChild(ip, value_ty); if (source_child >= ip->items_len || (ip->item_tags[source_child] != IP_TAG_TYPE_ARRAY_SMALL && ip->item_tags[source_child] != IP_TAG_TYPE_ARRAY_BIG)) return false; InterpArrayInfo source_array = interpArrayInfo(ip, source_child); uint32_t source_elem = source_array.child; uint32_t dest_many = ip->item_datas[dest_ty]; return interpPtrChild(ip, dest_many) == source_elem; } // Decode the general PtrNav representation used by the interpreter. A data // pointer may be `.one`, `.many`, or C-sized and may project within the Nav's // full declared storage; bit-addressed pointers remain unsupported and are // rejected by the materialization site. Function pointers remain exact // zero-offset `.one` pointers and are checked at their call sites. Returns the // resolved Nav and optionally the decoded payload. static const IpNav* interpPtrNav( const InternPool* ip, uint32_t ptr_nav, IpPtrNav* out) { IpPtrNav decoded; if (ipDecodePtrNavAny(ip, ptr_nav, &decoded) != IP_PN_OK) UNREACHABLE("interp: malformed ptr_nav"); const IpNav* nav = ipGetNav(ip, decoded.nav_index); if (!nav->has_resolved || !nav->has_value_resolved || !nav->modifiers_resolved || nav->resolved_type == IP_INDEX_NONE || nav->resolved_value == IP_INDEX_NONE) UNIMPLEMENTED("InternPool.getNav: ptr_nav names unresolved Nav"); if (ipTypeOf(ip, nav->resolved_value) != nav->resolved_type) UNREACHABLE("interp: ptr_nav Nav initializer type mismatch"); if (out) *out = decoded; return nav; } // Byte offset of the has_value flag inside `?T`: the payload occupies bytes // [0, abiSize(child)) and the 1-byte bool flag follows immediately. static uint32_t interpOptFlagOffset(InternPool* ip, uint32_t opt_ty) { if (interpOptionalRepr(ip, opt_ty) != INTERP_OPT_REPR_PAYLOAD_FLAG) UNREACHABLE("interp: optional has no runtime flag"); return interpAbiSize(ip, ip->item_datas[opt_ty]); } // Read the null niche from a payload-represented optional. Slices retain all // eight payload bytes and use only their pointer field for nullness; pointer // and error-set payloads use zero across their scalar ABI bytes. static bool interpOptionalNicheNonNull( InternPool* ip, uint32_t opt_ty, uint32_t addr) { if (interpOptionalRepr(ip, opt_ty) != INTERP_OPT_REPR_PAYLOAD_NICHE) UNREACHABLE("interp: niche test of non-niche optional"); uint32_t child = ipOptionalChild(ip, opt_ty); uint32_t size = ztIsSlice(ip, child) ? 4u : interpAbiSize(ip, child); return interpReadLE(addr, size) != 0; } // Byte offsets of the payload and error code inside `E!T`. The field with the // larger alignment comes first (mirrors codegen.zig errUnion{Payload,Error} // Offset): payload_align >= error_align → payload at 0, else error at 0. static void interpErrUnionOffsets(InternPool* ip, uint32_t eu_ty, uint32_t* out_payload_off, uint32_t* out_err_off) { if (interpErrUnionFullyComptime(ip, eu_ty)) UNIMPLEMENTED("Type.errorUnionPayload: fully-comptime error union has " "no runtime ABI"); uint32_t payload = ipErrorUnionPayload(ip, eu_ty); uint32_t palign = interpAbiAlign(ip, payload); uint32_t psize = interpAbiSize(ip, payload); if (palign >= ZT_ERROR_ABI_ALIGN) { *out_payload_off = 0; *out_err_off = interpLayoutAlign(psize, ZT_ERROR_ABI_ALIGN); } else { *out_err_off = 0; *out_payload_off = interpLayoutAlign(ZT_ERROR_ABI_SIZE, palign); } } // ── The by-ref scalar carrier ───────────────────────────────────────────── // // `src/codegen/wasm/CodeGen.zig` `isByRef`: `".int => return // ty.intInfo(zcu).bits > 64"`, the same clause for `.@"enum"`, `".float => // return ty.floatBits(target) > 64"`, and a packed container asks its backing // integer (`"return isByRef(ty.bitpackBackingInt(zcu), zcu, target);"`). // Upstream's own wasm backend therefore keeps every scalar wider than 64 bits // in memory, so this interpreter does too: such an InterpValue carries the // address of its `abiSize` little-endian bytes, exactly as an aggregate's // does. static ZIG1_NOINLINE bool interpTypeIsByRefScalarSlow( InternPool* ip, uint32_t ty) { uint32_t bits; if (ztSimpleFloatBits(interpBackingType(ip, ty), &bits)) return bits > 64u; return interpIntegerStorageWidth(ip, ty, &bits) && bits > 64u; } // Inline: every load, store and constant decode asks it. An enum asks its // tag integer (interpBackingType). A preinterned type is then a float, a // static integer (the table covers every one: the type_int items 0..14 and // usize, isize and the C integers) or neither; an interned one is an // integer, or by-ref only through a packed container's backing integer, // which the slow path follows. ZIG1_INLINE bool interpTypeIsByRefScalar(InternPool* ip, uint32_t ty) { ty = interpBackingType(ip, ty); if (ty < ZIR_REF_START_INDEX) { uint32_t bits; if (ztSimpleFloatBits(ty, &bits)) return bits > 64u; const InterpIntInfo* info = interpStaticIntInfo(ty); return info != NULL && info->width > 64u; } if (ty < ip->items_len) { uint8_t tag = ip->item_tags[ty]; if (tag == IP_TAG_TYPE_INT_SIGNED || tag == IP_TAG_TYPE_INT_UNSIGNED) return ip->item_datas[ty] > 64u; if (!ipTypeIsBitpack(ip, ty)) return false; } return interpTypeIsByRefScalarSlow(ip, ty); } // The widest integer this frame carries, in `big.int` limbs: Zig's own // maximum integer width, 65535 bits (`calcTwosCompLimbCount(65535)`), so no // valid integer type declines. The buffers live on the stack of leaf helpers. #define INTERP_MAX_INT_BITS 65535u #define INTERP_MAX_INT_LIMBS ((INTERP_MAX_INT_BITS + 63u) / 64u) // A product needs both operands' limbs plus a carry limb. #define INTERP_MAX_INT_RES_LIMBS (2u * INTERP_MAX_INT_LIMBS + 2u) // One by-ref scalar slot: `abiSize` bytes at `abiAlign`, in the activation's // frame arena, or in the const arena for an interned constant that must // outlive every frame. static uint32_t interpByRefAlloc(InternPool* ip, uint32_t ty, bool in_const) { uint32_t size = interpAbiSize(ip, ty); uint32_t align = interpAbiAlign(ip, ty); return in_const ? interpConstArenaAlloc(size, align) : interpArenaAlloc(size, align); } // Value.zig:toBigInt // `Value.toBigInt` for this carrier: a by-ref scalar's bytes read as a // `width`-bit two's-complement value (`Mutable.readTwosComplement`), a narrow // one's register read as the same pattern. `limbs` needs // `calcTwosCompLimbCount(width)` entries, and at least two. static BigIntConst interpValueToBigInt(InternPool* ip, InterpValue v, uint32_t width, bool is_signed, uint64_t* limbs, uint32_t limbs_cap) { if (interpTypeIsByRefScalar(ip, v.ty)) { uint32_t size = interpAbiSize(ip, v.ty); BigIntMutable m = { limbs, limbs_cap, 0, true }; if (!bigIntMutableReadTwosComplement(&m, interpArenaRangePtr(interpAddressFromBits(v.bits), size), width, is_signed)) UNIMPLEMENTED("toBigInt: integer wider than the limb buffer"); return bigIntMutableToConst(m); } BigIntConst c; bigIntFromTwosCompU128(v.bits, 0, width, is_signed, limbs, &c); return c; } // Value.zig:order // `Value.order` of two integers read at one `width`/signedness // (`big.int.Const.order`), by-ref slots included. static int interpIntOrder(InternPool* ip, InterpValue a, InterpValue b, uint32_t width, bool is_signed) { uint64_t a_space[INTERP_MAX_INT_LIMBS], b_space[INTERP_MAX_INT_LIMBS]; return bigIntOrder(interpValueToBigInt(ip, a, width, is_signed, a_space, INTERP_MAX_INT_LIMBS), interpValueToBigInt( ip, b, width, is_signed, b_space, INTERP_MAX_INT_LIMBS)); } // (part of) PerThread.zig:intValue_big // `pt.intValue_big` for it. A by-ref type gets a fresh slot whose whole // `abiSize` is written, which is both the truncation to the type // (`Mutable.truncate`) and the canonicalization of the slot's padding. static InterpValue interpValueFromBigInt(InternPool* ip, BigIntConst c, uint32_t ty, uint32_t width, bool is_signed, bool in_const) { InterpValue out = { 0, ty }; uint64_t tbuf[INTERP_MAX_INT_LIMBS]; BigIntMutable t = { tbuf, INTERP_MAX_INT_LIMBS, 0, true }; if (!bigIntMutableTruncate(&t, c, is_signed, width)) UNIMPLEMENTED("intValue_big: integer wider than the limb buffer"); BigIntConst tc = bigIntMutableToConst(t); if (interpTypeIsByRefScalar(ip, ty)) { uint32_t size = interpAbiSize(ip, ty); uint32_t addr = interpByRefAlloc(ip, ty, in_const); if (!bigIntConstWriteTwosComplement( tc, interpArenaRangePtr(addr, size), size)) UNIMPLEMENTED("intValue_big: integer does not fit its ABI slot"); out.bits = addr; return out; } uint64_t lo, hi; if (!bigIntTruncateToU128(tc, 64u, &lo, &hi)) UNIMPLEMENTED("intValue_big: integer wider than 64 bits"); out.bits = interpNormalize(lo, width, is_signed); return out; } // The <= 128-bit two's-complement pattern of one scalar, for the arms that // reinterpret bits rather than compute with them: `@bitCast`, the packed // containers, and the extended float formats (whose bits are not a number at // all). It is `Value.writeToMemory`/`readFromMemory` at this one width, not a // carrier -- the carrier is still the two shapes above. typedef struct { uint64_t lo; uint64_t hi; uint32_t ty; } InterpPattern; static InterpPattern interpScalarPattern(InternPool* ip, InterpValue v) { InterpPattern p = { 0, 0, v.ty }; if (!interpTypeIsByRefScalar(ip, v.ty)) { p.lo = v.bits; return p; } uint32_t size = interpAbiSize(ip, v.ty); if (size > 16u) UNIMPLEMENTED("bitcast: bit pattern wider than 128 bits"); uint32_t addr = interpAddressFromBits(v.bits); p.lo = interpReadLE(addr, size < 8u ? size : 8u); if (size > 8u) p.hi = interpReadLE(interpAddressAdd(addr, 8u), size - 8u); return p; } // Bit `i` of a scalar's two's-complement pattern, at any width: the bit // counting builtins ask nothing else of a value. static bool interpValueBit(InternPool* ip, InterpValue v, uint32_t i) { if (!interpTypeIsByRefScalar(ip, v.ty)) return i < 64u && ((v.bits >> i) & 1u) != 0; uint32_t size = interpAbiSize(ip, v.ty); if (i / 8u >= size) return false; const uint8_t* p = interpArenaRangePtr(interpAddressFromBits(v.bits), size); return ((p[i / 8u] >> (i % 8u)) & 1) != 0; } static InterpValue interpScalarFromPattern( InternPool* ip, InterpPattern p, bool in_const) { InterpValue out = { 0, p.ty }; if (!interpTypeIsByRefScalar(ip, p.ty)) { out.bits = p.lo; return out; } uint32_t size = interpAbiSize(ip, p.ty); if (size > 16u) UNIMPLEMENTED("bitcast: bit pattern wider than 128 bits"); uint32_t addr = interpByRefAlloc(ip, p.ty, in_const); interpWriteLE(addr, p.lo, size < 8u ? size : 8u); if (size > 8u) interpWriteLE(interpAddressAdd(addr, 8u), p.hi, size - 8u); out.bits = addr; return out; } typedef struct { uint32_t size; uint32_t width; bool is_signed; bool address_like; } InterpStaticScalarInfo; ZIG1_INLINE bool interpStaticScalarInfo( uint32_t ty, InterpStaticScalarInfo* out) { if (ty == ZIR_REF_BOOL_TYPE) { *out = (InterpStaticScalarInfo) { 1u, 1u, false, false }; return true; } const InterpIntInfo* int_info = interpStaticIntInfo(ty); if (int_info) { *out = (InterpStaticScalarInfo) { int_info->storage_bits / 8u, int_info->width, int_info->is_signed, false }; return true; } uint32_t float_bits; if (ztSimpleFloatBits(ty, &float_bits)) { uint32_t size = float_bits > 64u ? 16u : float_bits / 8u; *out = (InterpStaticScalarInfo) { size, float_bits, false, false }; return true; } switch (ty) { case ZIR_REF_ANYOPAQUE_TYPE: case ZIR_REF_NORETURN_TYPE: case ZIR_REF_TYPE_TYPE: case ZIR_REF_COMPTIME_INT_TYPE: case ZIR_REF_COMPTIME_FLOAT_TYPE: case ZIR_REF_NULL_TYPE: case ZIR_REF_UNDEFINED_TYPE: case ZIR_REF_ENUM_LITERAL_TYPE: *out = (InterpStaticScalarInfo) { 0u, 0u, false, false }; return true; case ZIR_REF_ANYERROR_TYPE: *out = (InterpStaticScalarInfo) { ZT_ERROR_ABI_SIZE, 16u, false, false }; return true; case ZIR_REF_PTR_USIZE_TYPE: case ZIR_REF_PTR_CONST_COMPTIME_INT_TYPE: case ZIR_REF_MANYPTR_U8_TYPE: case ZIR_REF_MANYPTR_CONST_U8_TYPE: case ZIR_REF_MANYPTR_CONST_U8_SENTINEL_0_TYPE: case ZIR_REF_MANYPTR_CONST_SLICE_CONST_U8_TYPE: case ZIR_REF_MANYPTR_CONST_TYPE_TYPE: *out = (InterpStaticScalarInfo) { 4u, 32u, false, true }; return true; default: return false; } } // A by-ref scalar stores exactly as an aggregate does -- its bytes are // already the canonical little-endian image the slot wants, so the store is // the copy. A by-value one is its register, little-endian in `size` bytes. // The two shapes are not interchangeable: a register handed to the by-ref // arm would be read as an address, so a mismatch declines loudly. static inline void interpStoreScalarBits(InternPool* ip, uint32_t addr, uint32_t ty, InterpValue val, uint32_t size) { bool by_ref = interpTypeIsByRefScalar(ip, ty); if (val.ty != ty && by_ref != interpTypeIsByRefScalar(ip, val.ty)) UNIMPLEMENTED("store: scalar stored in the other carrier shape"); if (by_ref) { interpArenaMove(addr, interpAddressFromBits(val.bits), size); return; } if (size > 8u) UNIMPLEMENTED("store: by-value scalar store wider than 64 bits"); interpWriteLE(addr, val.bits, size); } // `interpStoreTypeMatches` lets an ordinary implicit integer coercion keep the // RHS's own type into the store, on the promise that the store "performs the // required extension/truncation of the already Sema-validated value". With // two carrier shapes that extension is a re-materialization, not a wider // `interpWriteLE`: it is `Sema.intCast`'s value-preserving cast at the slot's // type, read at the VALUE's width and signedness and written at the slot's. static InterpValue interpIntCastScalar( InternPool* ip, uint32_t dst_ty, InterpValue op); static InterpValue interpStoreCoerceScalar( InternPool* ip, uint32_t ty, InterpValue val) { // A value of the slot's own type needs no coercion (both carrier shapes // and ABI sizes match). if (val.ty == ty) return val; // Two by-ref slots of one ABI size share the canonical image; a wider // slot needs the value extended, not the narrower slot over-read. bool by_ref = interpTypeIsByRefScalar(ip, ty); if (by_ref == interpTypeIsByRefScalar(ip, val.ty) && (!by_ref || interpAbiSize(ip, ty) == interpAbiSize(ip, val.ty))) return val; if (!interpTypeIsIntegerStorage(ip, ty)) return val; if (interpTypeIsIntegerStorage(ip, val.ty)) return interpIntCastScalar(ip, ty, val); // A `comptime_int`, `bool` or error value carries no integer type of its // own here; its decode is the sign-extended 64-bit word. uint32_t width; bool is_signed; interpTypeInfo(ip, ty, &width, &is_signed); uint64_t space[2]; return interpValueFromBigInt(ip, interpValueToBigInt(ip, val, 64u, true, space, 2u), ty, width, is_signed, false); } // (part of) Type.zig:fieldPtrType // ── Bit pointers ──────────────────────────────────────────────────────────── // A pointer whose type has `packed_offset.host_size != 0` (a packed struct // field's, `Type.fieldPtrType`) holds its HOST's address; the field's place // in the host is in the type. Upstream's wasm backend never loads or stores // through one -- `airLoad`/`airStore` assert `"host_size == 0); // legalize // .expand_packed_load"` -- because Legalize rewrites the load to the host // loaded as `uN`, `shr` by `bit_offset`, `trunc` and `bitcast`, and the store // to the host's read-modify-write. The interpreter performs those two blocks // directly over the host's bytes. False for a byte-addressed pointer. ZIG1_INLINE bool interpPtrBitHost(const InternPool* ip, uint32_t ptr_ty, uint32_t* host_size, uint32_t* bit_off) { uint32_t base = interpPtrPayloadBase(ip, ptr_ty); return ipPtrPackedHost( ip->extra[base + 2u], ip->extra[base + 3u], host_size, bit_off) && *host_size != 0u; } // (part of) Legalize.zig:packedLoadBlockPayload // Legalize.packedLoadBlockPayload. static InterpValue interpPackedLoad(InternPool* ip, uint32_t addr, uint32_t host_size, uint32_t bit_off, uint32_t elem_ty) { uint32_t width; bool is_signed; interpTypeInfo(ip, elem_ty, &width, &is_signed); if (width > PACKED_MEM_CARRIER_BITS) { // Past the {lo, hi} pair, the same `trunc` + `bitcast` over limbs. if ((uint64_t)bit_off + width > (uint64_t)host_size * 8u) UNIMPLEMENTED( "packedLoadBlockPayload: bit-pointer field outside its host"); // INTERP_MAX_INT_LIMBS holds any integer width, and the field is // inside its host, so the read cannot decline. uint64_t limbs[INTERP_MAX_INT_LIMBS]; BigIntMutable r = { limbs, INTERP_MAX_INT_LIMBS, 0, true }; if (!bigIntMutableReadPackedTwosComplement(&r, interpArenaRangePtr(addr, host_size), bit_off, width, false)) UNIMPLEMENTED( "packedLoadBlockPayload: bit-pointer field read declined"); return interpValueFromBigInt( ip, bigIntMutableToConst(r), elem_ty, width, is_signed, false); } uint64_t lo, hi; if (!packedMemRead(interpArenaRangePtr(addr, host_size), (uint64_t)host_size * 8u, bit_off, width, &lo, &hi)) UNIMPLEMENTED("packedLoadBlockPayload: bit-pointer field outside its " "host or wider than 128 bits"); // `trunc` to the unsigned `res_int_ty`, then `bitcast` to the element // type, whose sign the re-truncation applies. uint64_t space[2]; BigIntConst c; bigIntFromTwosCompU128(lo, hi, width, false, space, &c); return interpValueFromBigInt(ip, c, elem_ty, width, is_signed, false); } // (part of) Legalize.zig:packedStoreBlockPayload // Legalize.packedStoreBlockPayload: `"(load & keep_mask) | (int_cast( // bitcast(operand)) << bit_offset)"` stored back to the host. static void interpPackedStore(InternPool* ip, uint32_t addr, uint32_t host_size, uint32_t bit_off, uint32_t elem_ty, InterpValue val) { if (interpAddressIsConstArena(addr)) UNIMPLEMENTED( "packedStoreBlockPayload: packed store into const arena"); val = interpStoreCoerceScalar(ip, elem_ty, val); uint32_t width; bool is_signed; interpTypeInfo(ip, elem_ty, &width, &is_signed); if (width > PACKED_MEM_CARRIER_BITS) { uint64_t limbs[INTERP_MAX_INT_LIMBS]; if ((uint64_t)bit_off + width > (uint64_t)host_size * 8u) UNIMPLEMENTED( "packedStoreBlockPayload: bit-pointer field outside its host"); if (!bigIntConstWritePackedTwosComplement( interpValueToBigInt( ip, val, width, is_signed, limbs, INTERP_MAX_INT_LIMBS), interpArenaRangePtr(addr, host_size), bit_off, width)) UNIMPLEMENTED("packedStoreBlockPayload: packed field value does " "not fit its width"); return; } InterpPattern p = interpScalarPattern(ip, val); if (!packedMemWrite(interpArenaRangePtr(addr, host_size), (uint64_t)host_size * 8u, bit_off, width, p.lo, p.hi)) UNIMPLEMENTED("packedStoreBlockPayload: bit-pointer field outside its " "host or wider than 128 bits"); } // Store an InterpValue of static type `ty` at arena address `addr`. An // aggregate value is the address of its bytes → copy `abiSize(ty)` bytes; a // scalar is encoded little-endian in its `abiSize` bytes. static void interpStoreTyped( InternPool* ip, uint32_t addr, uint32_t ty, InterpValue val) { if (ty == ZIR_REF_VOID_TYPE) return; // zero-bit store is a no-op val = interpStoreCoerceScalar(ip, ty, val); InterpStaticScalarInfo static_info; if (interpStaticScalarInfo(ty, &static_info)) { if (static_info.size == 0u) return; interpStoreScalarBits(ip, addr, ty, val, static_info.size); return; } // A value of the slot's own pointer or non-packed, non-error-union // aggregate type: its tag alone settles every special case below. if (ty >= ZIR_REF_START_INDEX && val.ty == ty) { uint8_t tag = ip->item_tags[ty]; if (tag == IP_TAG_TYPE_POINTER) { interpWriteLE(addr, val.bits, 4u); // wasm32 address return; } if (interpTagIsAggregate(tag) && tag != IP_TAG_TYPE_ERROR_UNION && tag != IP_TAG_TYPE_ANYERROR_UNION) { interpArenaMove( addr, interpAddressFromBits(val.bits), interpAbiSize(ip, ty)); return; } } uint32_t size = interpAbiSize(ip, ty); // A fully-comptime error union contributes alignment but no runtime // bytes. Its error/payload state is retained only in the InternPool value; // embedding it in a partially-comptime aggregate writes nothing. if (interpTypeIsErrUnion(ip, ty) && interpErrUnionFullyComptime(ip, ty)) return; // Sema gives every stored value its slot's type (Air's store and // aggregate_init operands, InternPool's aggregate elements): an // optional, slice or error union is stored only as itself. if (val.ty != ty && (interpTypeIsOptional(ip, ty) || interpTypeIsErrUnion(ip, ty) || ztIsSlice(ip, ty))) UNREACHABLE("store: value not of its slot's optional, slice or " "error-union type"); if (interpTypeIsAggregate(ip, ty)) { // dest is a runtime slot or a const being built; src may be a // materialized const aggregate (e.g. `var x = const_arr` copies it) — // dispatch both addresses to their region. interpArenaMove(addr, interpAddressFromBits(val.bits), size); } else { interpStoreScalarBits(ip, addr, ty, val, size); } } // Load a by-value InterpValue of type `ty` from arena address `addr`. Every // caller is a value-producing AIR operation (argument binding, load/ret_load, // aggregate field/element value, optional/error-union payload value, or try // payload); // pointer-producing variants compute an address directly and never call this // helper. Therefore an aggregate load must snapshot its bytes into the current // frame. Returning the source address would alias mutable storage, letting a // later store change an already-produced SSA value. Scalars are decoded and // normalized in place. // One snapshot slot per value-producing AIR instruction and activation. A // loop may execute the instruction repeatedly; it overwrites this slot // instead of growing the frame arena on each iteration. `interpFramePop`'s // return hoist asks the same question of the call instruction's prior slot, // with the watermark already restored, so it is this same call. static uint32_t interpSnapshotSlot( InternPool* ip, uint32_t ty, uint32_t size, const InterpValue* reuse) { if (reuse && reuse->ty == ty && reuse->bits <= UINT32_MAX && (uint32_t)reuse->bits < INTERP_GLOBAL_BASE && (uint32_t)reuse->bits <= g_interp.arena_len && size <= g_interp.arena_len - (uint32_t)reuse->bits) return (uint32_t)reuse->bits; return interpArenaAlloc(size, interpAbiAlign(ip, ty)); } // An aggregate loads as a snapshot of its `abiSize` bytes. static InterpValue interpLoadAggregate( InternPool* ip, uint32_t addr, uint32_t ty, const InterpValue* reuse) { uint32_t size = interpAbiSize(ip, ty); uint32_t snapshot = interpSnapshotSlot(ip, ty, size, reuse); interpArenaMove(snapshot, addr, size); return (InterpValue) { snapshot, ty }; } static InterpValue interpLoadTyped( InternPool* ip, uint32_t addr, uint32_t ty, const InterpValue* reuse) { InterpValue out = { 0, ty }; if (ty == ZIR_REF_VOID_TYPE) return out; // zero-bit load: the void value // A static scalar of at most 64 bits is by value; a wider one (u128, // f80, ...) takes the by-ref arm below. InterpStaticScalarInfo static_info; if (interpStaticScalarInfo(ty, &static_info) && static_info.width <= 64u) { if (static_info.size == 0u) return out; uint64_t raw = interpReadLE(addr, static_info.size); out.bits = static_info.address_like ? raw : interpNormalize(raw, static_info.width, static_info.is_signed); return out; } // A pointer or a non-packed aggregate type item: its tag alone settles // the by-ref and aggregate tests below (neither is integer storage). if (ty >= ZIR_REF_START_INDEX) { uint8_t tag = ip->item_tags[ty]; if (tag == IP_TAG_TYPE_POINTER) { out.bits = interpReadLE(addr, 4u); // wasm32 address, raw return out; } if (interpTagIsAggregate(tag)) return interpLoadAggregate(ip, addr, ty, reuse); } if (interpTypeIsByRefScalar(ip, ty)) { // A by-ref scalar loads exactly as an aggregate does: a snapshot into // this activation's frame, because returning the source address would // alias mutable storage. An integer is re-canonicalized on the way in // -- the slot it came from may carry anything in the padding above // its own width. uint32_t size = interpAbiSize(ip, ty); uint32_t snapshot = interpSnapshotSlot(ip, ty, size, reuse); uint32_t float_bits; if (ztSimpleFloatBits(interpBackingType(ip, ty), &float_bits)) { interpArenaMove(snapshot, addr, size); out.bits = snapshot; return out; } uint32_t width; bool is_signed; interpTypeInfo(ip, ty, &width, &is_signed); uint64_t rbuf[INTERP_MAX_INT_LIMBS]; BigIntMutable r = { rbuf, INTERP_MAX_INT_LIMBS, 0, true }; if (!bigIntMutableReadTwosComplement( &r, interpArenaRangePtr(addr, size), width, is_signed)) UNIMPLEMENTED("load: integer wider than the limb buffer"); if (!bigIntConstWriteTwosComplement(bigIntMutableToConst(r), interpArenaRangePtr(snapshot, size), size)) UNIMPLEMENTED("load: integer does not fit its ABI slot"); out.bits = snapshot; return out; } if (interpTypeIsAggregate(ip, ty)) return interpLoadAggregate(ip, addr, ty, reuse); uint32_t size = interpAbiSize(ip, ty); if (size > 8u) UNIMPLEMENTED("load: by-value scalar load wider than 64 bits"); uint64_t raw = interpReadLE(addr, size); if (ip->item_tags[ty] == IP_TAG_TYPE_POINTER || ip->item_tags[ty] == IP_TAG_TYPE_OPTIONAL) { out.bits = raw; // an address; no numeric normalization return out; } uint32_t width; bool is_signed; interpTypeInfo(ip, ty, &width, &is_signed); out.bits = interpNormalize(raw, width, is_signed); return out; } static bool interpValuesEqual( InternPool* ip, InterpValue lhs, InterpValue rhs); static bool interpOptionalValueNonNull(InternPool* ip, InterpValue opt) { InterpOptionalRepr repr = interpOptionalRepr(ip, opt.ty); switch (repr) { case INTERP_OPT_REPR_NPV_NULL: return false; case INTERP_OPT_REPR_FULL: UNIMPLEMENTED("cmp_eq: runtime compare of a fully-comptime optional"); case INTERP_OPT_REPR_PAYLOAD_NICHE: if (!interpTypeIsAggregate(ip, opt.ty)) return opt.bits != 0; return interpOptionalNicheNonNull( ip, opt.ty, interpAddressFromBits(opt.bits)); case INTERP_OPT_REPR_PAYLOAD_FLAG: return interpReadLE(interpAddressAdd(interpAddressFromBits(opt.bits), interpOptFlagOffset(ip, opt.ty)), 1u) != 0; } UNIMPLEMENTED( "Type.optionalReprIsPayload: unknown optional representation"); ZIG1_TCC_RETURN(false); } static bool interpOptionalValuesEqual( InternPool* ip, InterpValue lhs, InterpValue rhs) { if (lhs.ty != rhs.ty || !interpTypeIsOptional(ip, lhs.ty)) UNREACHABLE("interp: optional compare type mismatch"); uint32_t child = ip->item_datas[lhs.ty]; if (interpTypeIsAggregate(ip, child) && !interpTypeIsOptional(ip, child)) UNIMPLEMENTED("cmp_eq: aggregate optional payload"); bool lhs_some = interpOptionalValueNonNull(ip, lhs); bool rhs_some = interpOptionalValueNonNull(ip, rhs); if (lhs_some != rhs_some) return false; if (!lhs_some) return true; if (interpAbiSize(ip, child) == 0) return true; if (interpTypeIsOptional(ip, child)) { // Nested optionals are stored as a child optional at payload offset 0. // Recurse directly over those bytes instead of snapshot-loading the // child aggregate; otherwise a loop comparing `??T` would allocate two // throwaway optional snapshots per iteration. if (!interpTypeIsAggregate(ip, lhs.ty)) { InterpValue lhs_payload = { lhs.bits, child }; InterpValue rhs_payload = { rhs.bits, child }; return interpOptionalValuesEqual(ip, lhs_payload, rhs_payload); } InterpValue lhs_payload = { lhs.bits, child }; InterpValue rhs_payload = { rhs.bits, child }; return interpOptionalValuesEqual(ip, lhs_payload, rhs_payload); } if (!interpTypeIsAggregate(ip, lhs.ty)) { InterpValue lhs_payload = { lhs.bits, child }; InterpValue rhs_payload = { rhs.bits, child }; return interpValuesEqual(ip, lhs_payload, rhs_payload); } // Payload loads snapshot aggregate children into the current activation's // frame arena, the same lifetime as any other value-producing AIR load. // Unsupported aggregate payloads abort in interpValuesEqual below. InterpValue lhs_payload = interpLoadTyped(ip, interpAddressFromBits(lhs.bits), child, NULL); InterpValue rhs_payload = interpLoadTyped(ip, interpAddressFromBits(rhs.bits), child, NULL); return interpValuesEqual(ip, lhs_payload, rhs_payload); } static bool interpValuesEqual( InternPool* ip, InterpValue lhs, InterpValue rhs) { if (lhs.ty != rhs.ty) UNREACHABLE("interp: equality type mismatch"); if (interpTypeIsOptional(ip, lhs.ty)) return interpOptionalValuesEqual(ip, lhs, rhs); if (interpAbiSize(ip, lhs.ty) == 0) return true; if (interpTypeIsAggregate(ip, lhs.ty)) UNIMPLEMENTED("cmp_eq: aggregate equality"); if (interpTypeIsByRefScalar(ip, lhs.ty)) { // Both images are canonical (every producer writes the whole slot), // so the bytes decide. uint32_t size = interpAbiSize(ip, lhs.ty); return memcmp( interpArenaRangePtr(interpAddressFromBits(lhs.bits), size), interpArenaRangePtr(interpAddressFromBits(rhs.bits), size), size) == 0; } return lhs.bits == rhs.bits; } // Decode a static InternPool constant (int / bool / void) into an InterpValue. // This is the uncached worker; interpDecodeConst wraps it with the // materialization cache. Its recursive references to nested elements call the // cached interpDecodeConst so nested aggregates are memoized too. static InterpValue interpDecodeConstUncached(InternPool* ip, uint32_t idx); // True for interned tags whose decode yields a by-value scalar // (InterpValue.bits is the value itself, never a const-arena address). // Critical for the cache below: a wide integer's value can be >= // INTERP_CONST_BASE (e.g. Wyhash's `secret` u64 `0xe7037ed1a0b428db`, whose // low 32 bits `0xa0b428db` exceed the base), so the "bits look like a // const-arena address" heuristic must not fire for it — caching + retruncating // it to 32 bits would corrupt the value. Only genuine by-reference decodes // (aggregates/pointers/optionals/error-unions, which call // interpConstArenaAlloc) may be memoized as const-arena addresses. static bool interpConstIsByValueScalar(InternPool* ip, uint32_t idx) { uint8_t tag = ip->item_tags[idx]; switch (tag) { case IP_TAG_SIMPLE_VALUE: case IP_TAG_ENUM_LITERAL: case IP_TAG_ERROR_SET_ERROR: // These fix a type of at most 64 bits (or `comptime_int`); // interpDecodeConstScalar decodes a subset of this group. case IP_TAG_INT_U8: case IP_TAG_INT_U16: case IP_TAG_INT_U32: case IP_TAG_INT_I32: case IP_TAG_INT_USIZE: case IP_TAG_INT_COMPTIME_INT_U32: case IP_TAG_INT_COMPTIME_INT_I32: case IP_TAG_FLOAT_F16: case IP_TAG_FLOAT_F32: case IP_TAG_FLOAT_F64: return true; case IP_TAG_FLOAT_F80: case IP_TAG_FLOAT_F128: case IP_TAG_FLOAT_C_LONGDOUBLE_F128: case IP_TAG_ENUM_TAG: case IP_TAG_BITPACK: break; case IP_TAG_AGGREGATE: // A packed struct aggregate decodes to its backing scalar, like // a `bitpack` (interpDecodeConstUncached's packed arm). if (!ipStructIsPacked(ip, ip->extra[ip->item_datas[idx]])) return false; break; default: if (!ipTagIsIntValue((IpTag)tag)) return false; } // A scalar constant is by value exactly when `isByRef` says its own type // fits a register; a `comptime_int` has no width and stays a word. uint32_t vty = ipTypeOf(ip, idx); return vty == ZIR_REF_COMPTIME_INT_TYPE || !interpTypeIsByRefScalar(ip, vty); } // Whether `interpDecodeConst(idx)` answers by value (`Value.bits` is the // constant itself) rather than by address (`Value.bits` is a const-arena // address holding the bytes). The const-materialization cache and the // `.uav` pointer arm both need this exact question, so it is one predicate. static bool interpConstIsByValue(InternPool* ip, uint32_t idx) { // `undefined` is by-ADDRESS for an aggregate or a by-ref scalar type; for // a register-sized scalar it is the plain 0xAA... word the IP_TAG_UNDEF // arm mints, which for a 64-bit type legitimately exceeds UINT32_MAX and // is no address. return interpConstIsByValueScalar(ip, idx) || (ipIsUndef(ip, idx) && !interpTypeIsAggregate(ip, ip->item_datas[idx]) && !interpTypeIsByRefScalar(ip, ip->item_datas[idx])); } // The constants whose item alone is their register value: bool, void, the // fixed-width int carriers up to 32 bits, small comptime_ints, f16 and f32. // interpDecodeConst tries this before its by-value test (each answers by // value), and interpDecodeConstUncached before its full switch. ZIG1_INLINE bool interpDecodeConstScalar( const InternPool* ip, uint32_t idx, InterpValue* out) { uint32_t data = ip->item_datas[idx]; switch (ip->item_tags[idx]) { case IP_TAG_SIMPLE_VALUE: if (idx == ZIR_REF_BOOL_TRUE) *out = (InterpValue) { 1, ZIR_REF_BOOL_TYPE }; else if (idx == ZIR_REF_BOOL_FALSE) *out = (InterpValue) { 0, ZIR_REF_BOOL_TYPE }; else if (idx == ZIR_REF_VOID_VALUE) *out = (InterpValue) { 0, ZIR_REF_VOID_TYPE }; else UNIMPLEMENTED( "InternPool.indexToKey: unhandled simple-value constant"); return true; case IP_TAG_INT_U8: *out = (InterpValue) { data, ZIR_REF_U8_TYPE }; return true; case IP_TAG_INT_U16: *out = (InterpValue) { data, ZIR_REF_U16_TYPE }; return true; case IP_TAG_INT_U32: *out = (InterpValue) { data, ZIR_REF_U32_TYPE }; return true; case IP_TAG_INT_I32: *out = (InterpValue) { interpNormalize(data, 32, true), ZIR_REF_I32_TYPE }; return true; case IP_TAG_INT_USIZE: *out = (InterpValue) { data, ZIR_REF_USIZE_TYPE }; return true; case IP_TAG_INT_COMPTIME_INT_U32: // A small `comptime_int` stored in item_datas (ipReadIntValue). *out = (InterpValue) { data, ZIR_REF_COMPTIME_INT_TYPE }; return true; case IP_TAG_INT_COMPTIME_INT_I32: // A small negative one, sign-extended to the 64-bit word. *out = (InterpValue) { (uint64_t)(int64_t)(int32_t)data, ZIR_REF_COMPTIME_INT_TYPE }; return true; case IP_TAG_FLOAT_F16: *out = (InterpValue) { data & 0xFFFFu, ZIR_REF_F16_TYPE }; return true; case IP_TAG_FLOAT_F32: *out = (InterpValue) { data, ZIR_REF_F32_TYPE }; return true; default: return false; } } static InterpValue interpDecodeConst(InternPool* ip, uint32_t idx); // The constants whose item points at their value: an `int_small` whose type // `isByRef` keeps in a register (a wider one is by address and decodes // through interpDecodeConstUncached), and an `enum_tag`, which is its tag // integer's decode, a register or a const-arena slot alike. // interpDecodeConst tries this after interpDecodeConstScalar, and // interpDecodeConstUncached before its full switch. ZIG1_INLINE bool interpDecodeConstWord( InternPool* ip, uint32_t idx, InterpValue* out) { uint32_t data = ip->item_datas[idx]; switch (ip->item_tags[idx]) { case IP_TAG_INT_SMALL: { // IntSmall payload in `extra`: {ty, value}. uint32_t ty = ip->extra[data]; if (interpTypeIsByRefScalar(ip, ty)) return false; uint32_t width; bool is_signed; interpTypeInfo(ip, ty, &width, &is_signed); *out = (InterpValue) { interpNormalize(ip->extra[data + 1], width, is_signed), ty }; return true; } case IP_TAG_ENUM_TAG: { // Its runtime representation is its integer tag (Value.intFromEnum), // with the enum type kept so layout reads the tag integer through // interpBackingType. *out = (InterpValue) { interpDecodeConst(ip, ipEnumTagValue(ip, idx)).bits, ipEnumTagType(ip, idx) }; return true; } default: return false; } } // Decode an InternPool constant. By-address constants are served from the // const-materialization cache (see the cache block above): a constant whose // decode lands bytes in the const arena (InterpValue.bits >= // INTERP_CONST_BASE) is memoized by IP index, so a const referenced N times is // materialized once. By-value scalar constants are decoded directly: a wide // integer's value can itself land >= INTERP_CONST_BASE and must not be // confused for a cached address. static InterpValue interpDecodeConst(InternPool* ip, uint32_t idx) { InterpValue v; bool by_value = interpDecodeConstScalar(ip, idx, &v) || interpDecodeConstWord(ip, idx, &v); if (!by_value && interpConstIsByValue(ip, idx)) { v = interpDecodeConstUncached(ip, idx); by_value = true; } if (by_value) return v; uint32_t caddr, cty; if (interpConstCacheLookup(idx, &caddr, &cty)) { InterpValue hit = { caddr, cty }; return hit; } v = interpDecodeConstUncached(ip, idx); if (v.bits > UINT32_MAX) UNIMPLEMENTED("InternPool.indexToKey: materialized const address " "exceeds wasm32"); if ((uint32_t)v.bits >= INTERP_CONST_BASE) interpConstCacheInsert(idx, (uint32_t)v.bits, v.ty); return v; } // Backing integer bits for one enum field without interning during runtime. // Auto enums use the declaration index; explicit enums retain the exact // interned field value in their trailing payload. static uint64_t interpEnumFieldTagBits( InternPool* ip, uint32_t enum_ty, uint32_t enum_field) { if (enum_field >= ipEnumFieldsLen(ip, enum_ty)) UNREACHABLE("interp: enum field index out of bounds"); // The answer is a word, so an enum whose backing integer `isByRef` has no // tag bits to hand back and says so rather than returning its address. if (interpTypeIsByRefScalar(ip, enum_ty)) UNIMPLEMENTED("Type.enumTagFieldIndex: enum tag wider than 64 bits"); if (ip->item_tags[enum_ty] == IP_TAG_TYPE_ENUM_AUTO) return enum_field; uint32_t int_value = ipEnumFieldValue(ip, enum_ty, enum_field); if (int_value == IP_INDEX_NONE) UNIMPLEMENTED( "Type.enumTagFieldIndex: explicit enum field has no value"); return interpDecodeConst(ip, int_value).bits; } // Reverse-map a runtime enum value (canonical interp bits) to its declaration // field index via the shared InternPool scan (ipEnumFieldIndexFromTagBits, // which compares the low `width` bits). A non-canonical input keeps the loud // miss instead of being masked into range. Reject missing or ambiguous tag // values loudly. static uint32_t interpEnumFieldFromTagBits( InternPool* ip, uint32_t enum_ty, uint64_t tag_bits) { uint32_t width; bool is_signed; uint32_t int_tag_ty = ipEnumTypeIntTagType(ip, enum_ty); interpTypeInfo(ip, int_tag_ty, &width, &is_signed); uint32_t found = UINT32_MAX; bool ambiguous = false; if (interpTypeIsByRefScalar(ip, enum_ty)) { // `LoadedEnumType.tagValueIndex` by value: `tag_bits` is the slot's // address, and each field value is read at the same width. InterpValue tag = { tag_bits, int_tag_ty }; uint32_t fields_len = ipEnumFieldsLen(ip, enum_ty); for (uint32_t i = 0; i < fields_len && !ambiguous; i++) { uint32_t field_int = ipEnumFieldValue(ip, enum_ty, i); if (field_int == IP_INDEX_NONE) UNIMPLEMENTED("Type.enumTagFieldIndex: explicit enum field " "has no value"); InterpValue field = interpDecodeConst(ip, field_int); field.ty = int_tag_ty; if (interpIntOrder(ip, tag, field, width, is_signed) != 0) continue; ambiguous = found != UINT32_MAX; found = i; } } else if (interpNormalize(tag_bits, width, is_signed) == tag_bits) found = ipEnumFieldIndexFromTagBits( ip, enum_ty, tag_bits, width, &ambiguous); if (ambiguous) UNIMPLEMENTED( "Type.enumTagFieldIndex: ambiguous explicit enum tag value"); if (found == UINT32_MAX) UNIMPLEMENTED("Type.enumTagFieldIndex: enum tag has no exact field"); return found; } // Map a union declaration-order field to its explicit/auto tag value by // field name. Union field order is not assumed to match the tag enum order. static uint64_t interpUnionFieldTagBits( InternPool* ip, uint32_t union_ty, uint32_t union_field) { uint32_t name = ipUnionFieldName(ip, union_ty, union_field); if (name == IP_INDEX_NONE) UNREACHABLE("interp: union field name metadata missing"); uint32_t enum_ty = ipUnionEnumTagType(ip, union_ty); if (!ipIsEnumType(ip, enum_ty)) UNREACHABLE("interp: union tag type is not an enum"); uint32_t match = UINT32_MAX; for (uint32_t i = 0; i < ipEnumFieldsLen(ip, enum_ty); i++) { if (ipEnumFieldName(ip, enum_ty, i) != name) continue; if (match != UINT32_MAX) UNIMPLEMENTED("Zcu.unionTagFieldIndex: ambiguous enum field name"); match = i; } if (match == UINT32_MAX) UNIMPLEMENTED( "Zcu.unionTagFieldIndex: union field missing from tag enum"); return interpEnumFieldTagBits(ip, enum_ty, match); } // Reverse the mapping for a comptime union value: decoded enum-tag bits -> // enum field name -> union declaration-order field. Reject missing or // ambiguous tag values loudly. static uint32_t interpUnionFieldFromTagBits( InternPool* ip, uint32_t union_ty, uint64_t tag_bits) { uint32_t enum_ty = ipUnionEnumTagType(ip, union_ty); uint32_t enum_field = interpEnumFieldFromTagBits(ip, enum_ty, tag_bits); uint32_t name = ipEnumFieldName(ip, enum_ty, enum_field); uint32_t found = UINT32_MAX; uint32_t fields_len = ipUnionFieldsLen(ip, union_ty); for (uint32_t i = 0; i < fields_len; i++) { if (ipUnionFieldName(ip, union_ty, i) != name) continue; if (found != UINT32_MAX) UNIMPLEMENTED( "Zcu.unionTagFieldIndex: ambiguous union field name"); found = i; } if (found == UINT32_MAX) UNIMPLEMENTED( "Zcu.unionTagFieldIndex: union tag has no payload field"); return found; } static void interpNavCacheEnsure(uint32_t nav_idx) { if (nav_idx < g_interp.nav_cap) return; uint32_t need; if (!interpCheckedU32Add(nav_idx, 1u, &need)) UNIMPLEMENTED("wasm32: Nav address cache index overflow"); uint32_t nc; if (!interpCheckedGrowCapacity(g_interp.nav_cap, need, UINT32_MAX / (uint32_t)sizeof(uint32_t), &nc)) UNIMPLEMENTED("wasm32: Nav address cache exhausted"); size_t addr_bytes = (size_t)nc * sizeof(*g_interp.nav_addr); size_t state_bytes = (size_t)nc * sizeof(*g_interp.nav_state); uint32_t* na = zig1Realloc(g_interp.nav_addr, addr_bytes); uint8_t* ns = zig1Realloc(g_interp.nav_state, state_bytes); g_interp.nav_addr = na; g_interp.nav_state = ns; memset(g_interp.nav_addr + g_interp.nav_cap, 0, (nc - g_interp.nav_cap) * sizeof(*g_interp.nav_addr)); memset(g_interp.nav_state + g_interp.nav_cap, 0, (nc - g_interp.nav_cap) * sizeof(*g_interp.nav_state)); g_interp.nav_cap = nc; } // Materialize the storage object denoted by a data Nav. Address identity is // keyed by Nav index, not ptr_nav IP index. Allocate and publish the address // before decoding the initializer so recursive address references see the // same object. Const objects live in the read-only const region; ordinary // mutable objects live in the writable global region. The initializer stays // in IpNav.resolved_value and is copied exactly once per top-level program. static uint32_t interpExternNavAddress(InternPool* ip, uint32_t nav_idx); static uint32_t interpNavAddress(InternPool* ip, uint32_t nav_idx) { if (nav_idx >= ip->navs_len) UNREACHABLE("interp: data ptr_nav has invalid Nav"); const IpNav* nav = ipGetNav(ip, nav_idx); if (nav->is_extern) return interpExternNavAddress(ip, nav_idx); if (nav->is_threadlocal) UNIMPLEMENTED("InternPool.getNav: threadlocal data Nav address"); if (!nav->has_resolved || !nav->has_value_resolved || !nav->modifiers_resolved || nav->resolved_type == IP_INDEX_NONE || nav->resolved_value == IP_INDEX_NONE) UNIMPLEMENTED( "InternPool.getNav: data ptr_nav has no resolved initializer"); if (nav->resolved_value >= ip->items_len) UNREACHABLE("interp: data Nav initializer is invalid"); if (nav->resolved_type >= ip->items_len || ip->item_tags[nav->resolved_type] == IP_TAG_TYPE_FUNCTION) UNREACHABLE("interp: data Nav has function/invalid type"); if (ipTypeOf(ip, nav->resolved_value) != nav->resolved_type) UNREACHABLE("interp: data Nav initializer type mismatch"); // Validate the Nav on every materialization lookup, not merely on the // first cache miss. This keeps the address cache an optimization rather // than an alternate path around the PtrNav invariants above. interpNavCacheEnsure(nav_idx); if (g_interp.nav_state[nav_idx] != 0) return g_interp.nav_addr[nav_idx]; uint32_t size = interpAbiSize(ip, nav->resolved_type); uint32_t alloc_size = size == 0 ? 1u : size; // `Nav.status.resolved.alignment` is an `InternPool.Alignment` too, so // the slot uses the same rule the pointer path does. uint32_t align = interpAlignmentToBytes( ip, nav->resolved_alignment, nav->resolved_type); uint32_t addr = nav->is_const ? interpConstArenaAlloc(alloc_size, align) : interpGlobalArenaAlloc(alloc_size, align); g_interp.nav_addr[nav_idx] = addr; g_interp.nav_state[nav_idx] = 1; // publish before recursive initializer decode InterpValue initializer = interpDecodeConst(ip, nav->resolved_value); interpStoreTyped(ip, addr, nav->resolved_type, initializer); g_interp.nav_state[nav_idx] = 2; return addr; } // Runtime Nav pointers name storage that stock could not encode as a // comptime ptr_nav (currently non-extern threadlocal vars). The zig1 // interpreter executes a single thread, so TLS storage is modeled as the same // per-program persistent Nav object as an ordinary mutable global. Keep this // separate from interpNavAddress so a stray comptime ptr_nav to TLS still // aborts instead of silently bypassing the missing runtime_nav_ptr lowering. static uint32_t interpRuntimeNavAddress( InternPool* ip, uint32_t nav_idx, uint32_t ptr_ty) { if (nav_idx >= ip->navs_len) UNREACHABLE("interp: runtime_nav_ptr has invalid Nav"); const IpNav* nav = ipGetNav(ip, nav_idx); if (nav->is_extern) UNIMPLEMENTED("runtime_nav_ptr: extern runtime_nav_ptr"); if (!nav->is_threadlocal) UNREACHABLE("interp: non-threadlocal runtime_nav_ptr"); if (!nav->has_resolved || nav->resolved_type == IP_INDEX_NONE) UNIMPLEMENTED("runtime_nav_ptr: unresolved runtime_nav_ptr Nav"); if (ptr_ty >= ip->items_len || ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER || ip->extra[ip->item_datas[ptr_ty]] != nav->resolved_type) UNREACHABLE("interp: runtime_nav_ptr type mismatch"); if (!nav->has_value_resolved || nav->resolved_value == IP_INDEX_NONE) UNIMPLEMENTED("runtime_nav_ptr: has no initializer"); interpNavCacheEnsure(nav_idx); if (g_interp.nav_state[nav_idx] != 0) return g_interp.nav_addr[nav_idx]; uint32_t size = interpAbiSize(ip, nav->resolved_type); uint32_t alloc_size = size == 0 ? 1u : size; // `Nav.status.resolved.alignment` is an `InternPool.Alignment` too, so // the slot uses the same rule the pointer path does. uint32_t align = interpAlignmentToBytes( ip, nav->resolved_alignment, nav->resolved_type); uint32_t addr = nav->is_const ? interpConstArenaAlloc(alloc_size, align) : interpGlobalArenaAlloc(alloc_size, align); g_interp.nav_addr[nav_idx] = addr; g_interp.nav_state[nav_idx] = 1; InterpValue initializer = interpDecodeConst(ip, nav->resolved_value); if (initializer.ty != nav->resolved_type) UNREACHABLE("interp: runtime_nav_ptr initializer type mismatch"); interpStoreTyped(ip, addr, nav->resolved_type, initializer); g_interp.nav_state[nav_idx] = 2; return addr; } // The linker's symbol resolution for an extern data Nav: the address of the // module's `@export` of the same name (Zcu.Exported `.nav` storage, or the // `.uav` value's read-only placement, which every pointer to that value // shares), a strong definition taking precedence over weak ones and the // first weak one standing otherwise. zig1 links one module and no // libraries, so an extern no export names is unresolved. static uint32_t interpExternNavAddress(InternPool* ip, uint32_t nav_idx) { interpNavCacheEnsure(nav_idx); if (g_interp.nav_state[nav_idx] != 0) return g_interp.nav_addr[nav_idx]; uint32_t name = ipGetNav(ip, nav_idx)->name; uint32_t chosen = g_interp.exports_len; for (uint32_t e = 0; e < g_interp.exports_len; e++) { if (g_interp.exports[e].name != name) continue; if (chosen == g_interp.exports_len || (g_interp.exports[chosen].linkage != GLOBAL_LINKAGE_STRONG && g_interp.exports[e].linkage == GLOBAL_LINKAGE_STRONG)) chosen = e; } if (chosen == g_interp.exports_len) UNIMPLEMENTED("@export: extern data Nav names no export"); const ZcuExport* ex = &g_interp.exports[chosen]; uint32_t addr; if (!ex->exported_is_uav) { addr = interpNavAddress(ip, ex->exported); } else { // `&x` interned the uav pointer the export was formed from. One // pool scan per extern Nav per program; the address is cached. uint32_t ptr = IP_INDEX_NONE; for (uint32_t i = 0; i < ip->items_len && ptr == IP_INDEX_NONE; i++) if ((ipIsPtrUav(ip, i)) && ipPtrUavValue(ip, i) == ex->exported && ipPtrUavByteOffset(ip, i) == 0) ptr = i; if (ptr == IP_INDEX_NONE) UNIMPLEMENTED("@export: exported uav has no pointer"); addr = interpAddressFromBits(interpDecodeConst(ip, ptr).bits); } g_interp.nav_addr[nav_idx] = addr; g_interp.nav_state[nav_idx] = 2; return addr; } // A function pointer's runtime value: the function's func_decl, or for an // extern the first extern naming the same symbol (name and library), since // the linker resolves every such declaration to one address. static uint32_t interpFunctionHandle(const InternPool* ip, uint32_t fn_val) { if (fn_val >= ip->items_len) UNIMPLEMENTED( "InternPool.getNav: fn-pointer nav has no resolved function"); if (ip->item_tags[fn_val] != IP_TAG_EXTERN) return fn_val; uint32_t name = ipGetNav(ip, ipExternFunctionOwnerNav(ip, fn_val))->name; uint32_t lib = ipExternFunctionLibraryName(ip, fn_val); for (uint32_t i = 0;; i++) if (ip->item_tags[i] == IP_TAG_EXTERN && ipExternFunctionLibraryName(ip, i) == lib && ipGetNav(ip, ipExternFunctionOwnerNav(ip, i))->name == name) return i; } // A uav's placement alignment (CodeGen.zig `.uav_ref`, Wasm.zig // `overaligned_uavs`): the original pointer type's explicit alignment when it // exceeds that pointer's child ABI alignment, else the value type's ABI one. static uint32_t interpUavAlign( InternPool* ip, uint32_t uav, uint32_t value_ty) { uint32_t orig_ty = ipPtrUavOrigTy(ip, uav); uint32_t this_align = interpPtrAlignment(ip, orig_ty); return this_align > interpAbiAlign(ip, ip->extra[ip->item_datas[orig_ty]]) ? this_align : interpAbiAlign(ip, value_ty); } // The strictest explicit alignment in bytes any uav pointer asks of `val`, // or 0 when none does. static uint32_t interpUavStrictestAlign(const InternPool* ip, uint32_t val) { if (g_interp.uav_align_cap < ip->items_len) { g_interp.uav_align = zig1Realloc(g_interp.uav_align, ip->items_len); g_interp.uav_align_cap = ip->items_len; } memset(g_interp.uav_align + g_interp.uav_align_len, 0, ip->items_len - g_interp.uav_align_len); for (uint32_t i = g_interp.uav_align_len; i < ip->items_len; i++) { if (!ipIsPtrUav(ip, i)) continue; IpPtrInfo orig; if (ipPtrInfo(ip, ipPtrUavOrigTy(ip, i), &orig) != IP_PTRI_OK || orig.alignment == IP_PTR_ALIGN_NONE) continue; uint8_t* slot = &g_interp.uav_align[ipPtrUavValue(ip, i)]; if (orig.alignment + 1u > *slot) *slot = (uint8_t)(orig.alignment + 1u); } g_interp.uav_align_len = ip->items_len; return g_interp.uav_align[val] == 0 ? 0u : 1u << (g_interp.uav_align[val] - 1u); } static InterpValue interpDecodeConstUncached(InternPool* ip, uint32_t idx) { InterpValue v = { 0, 0 }; if (interpDecodeConstScalar(ip, idx, &v) || interpDecodeConstWord(ip, idx, &v)) return v; uint8_t tag = ip->item_tags[idx]; uint32_t data = ip->item_datas[idx]; switch (tag) { case IP_TAG_FLOAT_F64: if (!ipReadFloatF64Bits(ip, idx, &v.bits)) UNREACHABLE("interp: malformed f64 constant"); v.ty = ZIR_REF_F64_TYPE; return v; case IP_TAG_FLOAT_F80: { uint32_t words[3]; if (!ipReadFloatF80Bits(ip, idx, words)) UNREACHABLE("interp: malformed f80 constant"); InterpPattern p80 = { 0, 0, ZIR_REF_F80_TYPE }; ipFloatF80WordsToLow80Bits(words, &p80.lo, &p80.hi); return interpScalarFromPattern(ip, p80, true); } case IP_TAG_FLOAT_F128: case IP_TAG_FLOAT_C_LONGDOUBLE_F128: { uint32_t words[4]; if (!ipReadFloatF128Bits(ip, idx, words)) UNREACHABLE("interp: malformed f128 constant"); InterpPattern p128 = { (uint64_t)words[0] | ((uint64_t)words[1] << 32), (uint64_t)words[2] | ((uint64_t)words[3] << 32), ipTypeOf(ip, idx) }; return interpScalarFromPattern(ip, p128, true); } case IP_TAG_INT_SMALL: { // IntSmall payload in `extra`: {ty, value}. `ipGetInt` mints this // carrier whenever the value fits a `u32` data word, at any type -- // so `@as(u128, 5)` arrives here and its type is by REF (a by-value // one took interpDecodeConstWord). uint32_t ty = ip->extra[data]; uint32_t raw = ip->extra[data + 1]; uint32_t width; bool is_signed; interpTypeInfo(ip, ty, &width, &is_signed); uint64_t space[2]; BigIntConst c; bigIntFromTwosCompU128( interpNormalize(raw, width, is_signed), 0, 64u, true, space, &c); return interpValueFromBigInt(ip, c, ty, width, is_signed, true); } case IP_TAG_INT_POSITIVE: case IP_TAG_INT_NEGATIVE: { // `InternPool.Tag.int_positive` / `int_negative`: a magnitude in the // separate limbs arena, the sign in the tag, and `Int.ty` beside it // -- which is `big.int.Const` exactly, so `pt.intValue_big` at that // type is the whole decode. uint32_t big_ty, big_len; const uint64_t* big_limbs; bool big_positive; if (!ipIntBigLimbs( ip, idx, &big_ty, &big_limbs, &big_len, &big_positive)) UNREACHABLE("interp: malformed limb-backed integer constant"); if (big_ty == ZIR_REF_COMPTIME_INT_TYPE) { // A `comptime_int` has no width to mask to, so the scalar keeps // the sign-extended 64-bit word the narrower `comptime_int` // carriers hand over. A magnitude the word cannot hold stays // loud, which is the same bound those carriers had. BigIntConst big = { big_limbs, big_len, big_positive }; int64_t big_i; if (bigIntToU64(big, &v.bits)) { } else if (bigIntToI64(big, &big_i)) { v.bits = (uint64_t)big_i; } else { UNIMPLEMENTED("InternPool.indexToKey: comptime_int constant " "wider than 64 bits"); } v.ty = ZIR_REF_COMPTIME_INT_TYPE; return v; } // `lowerConstant`'s wide-int arm: the magnitude beside its sign is // `big.int.Const`, and `pt.intValue_big` at the type is what turns it // into this frame's carrier -- a register below 65 bits, the // little-endian two's-complement bytes of a const-arena slot above. uint32_t width; bool is_signed; interpTypeInfo(ip, big_ty, &width, &is_signed); BigIntConst bigc = { big_limbs, big_len, big_positive }; return interpValueFromBigInt(ip, bigc, big_ty, width, is_signed, true); } case IP_TAG_BITPACK: { // A packed struct instance: represented as a scalar of its backing // integer (the interp stores packed structs bit-packed, LSB-first). // The InterpValue carries the backing magnitude with the packed struct // type, so // @bitCast to the backing int is the identity and field reads // shift+mask. uint32_t bp_ty = ipBitpackType(ip, idx); uint32_t backing_val = ipBitpackBackingVal(ip, idx); // The packed struct and its backing integer are the same width, so // they are the same carrier shape: a register, or the same bytes. InterpValue bv = interpDecodeConst(ip, backing_val); v.bits = bv.bits; v.ty = bp_ty; return v; } case IP_TAG_BYTES: { // A comptime byte array (`[N]u8`) interned as a string. Materialize // its bytes into the arena and return the aggregate's address (mirrors // how runtime aggregates live in the arena). extra: {array_ty, // string}. uint32_t arr_ty = ip->extra[data]; uint32_t str = ip->extra[data + 1]; const char* sb = ipStringBytes(ip, str); InterpArrayInfo array = interpArrayInfo(ip, arr_ty); uint32_t addr = interpConstArenaAlloc( interpAbiSize(ip, arr_ty), interpAbiAlign(ip, arr_ty)); uint32_t esize = interpAbiSize(ip, array.child); for (uint32_t i = 0; i < array.len; i++) interpWriteLE( interpAddressIndex(addr, i, esize), (uint8_t)sb[i], esize); if (array.sentinel != IP_INDEX_NONE) { InterpValue sentinel = interpDecodeConst(ip, array.sentinel); interpStoreTyped(ip, interpAddressIndex(addr, array.len, esize), array.child, sentinel); } v.bits = addr; v.ty = arr_ty; return v; } case IP_TAG_AGGREGATE: { // A comptime aggregate (array or struct) value. extra: {agg_ty, // elem/field values...}. Materialize into the arena element by // element, recursively decoding each interned element value. uint32_t agg_ty = ip->extra[data]; uint32_t base = data + 1; uint8_t agg_tag = ip->item_tags[agg_ty]; if (ipStructIsPacked(ip, agg_ty)) { uint32_t n = ipStructFieldsLen(ip, agg_ty); uint64_t lo = 0; uint64_t hi = 0; for (uint32_t i = 0; i < n; i++) { uint32_t fty = ipStructFieldType(ip, agg_ty, i); uint32_t fwidth; bool fsigned; interpTypeInfo(ip, fty, &fwidth, &fsigned); if (fwidth == 0) continue; if (fwidth > 64) UNIMPLEMENTED("InternPool.indexToKey: packed const field " "exceeds 64-bit model"); uint32_t off = zcuStructPackedFieldBitOffset(NULL, ip, agg_ty, i); if (off >= 128u || fwidth > 128u - off) UNIMPLEMENTED("InternPool.indexToKey: packed const " "backing exceeds 128-bit model"); InterpValue ev = interpDecodeConst(ip, ip->extra[base + i]); packedBitsInsert(&lo, &hi, off, fwidth, ev.bits); } uint32_t bwidth; bool bsigned; interpTypeInfo(ip, agg_ty, &bwidth, &bsigned); if (bwidth > 128u) UNIMPLEMENTED("InternPool.indexToKey: packed const backing " "exceeds 128 bits"); InterpPattern pp = { lo, hi, agg_ty }; bigIntMaskU128(&pp.lo, &pp.hi, bwidth); return interpScalarFromPattern(ip, pp, true); } uint32_t addr = interpConstArenaAlloc( interpAbiSize(ip, agg_ty), interpAbiAlign(ip, agg_ty)); // A vector shares this arm: interpAbiSize's `.vector_type` arm lays // its elements out contiguously at the element's own ABI stride for // this chain's C/wasm backend family, exactly as an array does, and // the IP_TAG_REPEATED sibling below already reaches it through the // same interpArrayInfo. if (agg_tag == IP_TAG_TYPE_ARRAY_SMALL || agg_tag == IP_TAG_TYPE_ARRAY_BIG || agg_tag == IP_TAG_TYPE_VECTOR) { InterpArrayInfo array = interpArrayInfo(ip, agg_ty); uint32_t esize = interpAbiSize(ip, array.child); for (uint32_t i = 0; i < array.len; i++) { InterpValue ev = interpDecodeConst(ip, ip->extra[base + i]); interpStoreTyped( ip, interpAddressIndex(addr, i, esize), array.child, ev); } if (array.sentinel != IP_INDEX_NONE) { InterpValue sentinel = interpDecodeConst(ip, array.sentinel); interpStoreTyped(ip, interpAddressIndex(addr, array.len, esize), array.child, sentinel); } } else if (ipTagIsAnyStruct(agg_tag)) { uint32_t n = ipStructFieldsLen(ip, agg_ty); for (uint32_t i = 0; i < n; i++) { if (ipStructFieldIsComptime(ip, agg_ty, i)) continue; // fixed field has no runtime bytes uint32_t fty = ipStructFieldType(ip, agg_ty, i); ZtTypeClass field_class = ztTypeClass(ip, fty); if (field_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED("InternPool.indexToKey: unresolved " "aggregate field class"); if (field_class != ZT_CLASS_RUNTIME && field_class != ZT_CLASS_PARTIAL) continue; // FULL/OPV fields carry no runtime bytes InterpValue ev = interpDecodeConst(ip, ip->extra[base + i]); interpStoreTyped(ip, interpAddressAdd( addr, interpStructFieldOffset(ip, agg_ty, i)), fty, ev); } } else if (agg_tag == IP_TAG_TYPE_TUPLE) { // A tuple aggregate (`.{30, 12}` bound to an anytype param). // TypeTuple extra = { fields_len, types[], values[] }: a field // with a stored value is comptime (zero-bit at runtime, skipped, // as in ztStructFieldOffset); only runtime fields // (values[i] == .none) store their element bytes. uint32_t td = ip->item_datas[agg_ty]; uint32_t n = ip->extra[td]; for (uint32_t i = 0; i < n; i++) { if (ip->extra[td + 1 + n + i] != IP_INDEX_NONE) continue; // comptime field: no runtime bytes uint32_t fty = ip->extra[td + 1 + i]; InterpValue ev = interpDecodeConst(ip, ip->extra[base + i]); interpStoreTyped(ip, interpAddressAdd( addr, interpStructFieldOffset(ip, agg_ty, i)), fty, ev); } } else { UNIMPLEMENTED("InternPool.indexToKey: aggregate const of " "non-array/struct type"); } v.bits = addr; v.ty = agg_ty; return v; } case IP_TAG_REPEATED: { // A comptime aggregate whose elements are all one repeated value. // extra: {agg_ty, elem_value}. NOTE ipGetAggregate canonicalizes any // all-equal-element aggregate here — including every 1-element one — // so a tuple aggregate (`.{57}` bound to an anytype param) can carry // a TypeTuple agg_ty: store the repeated element only into runtime // fields (values[i] == .none) at their layout offsets, exactly like // the IP_TAG_AGGREGATE tuple arm below (comptime fields are zero-bit; // consumers fold them from the type). uint32_t agg_ty = ip->extra[data]; uint32_t elem_ref = ip->extra[data + 1]; if (ipStructIsPacked(ip, agg_ty)) { uint32_t n = ipStructFieldsLen(ip, agg_ty); InterpValue elem = interpDecodeConst(ip, elem_ref); uint64_t lo = 0; uint64_t hi = 0; for (uint32_t i = 0; i < n; i++) { uint32_t fty = ipStructFieldType(ip, agg_ty, i); uint32_t fwidth; bool fsigned; interpTypeInfo(ip, fty, &fwidth, &fsigned); if (fwidth == 0) continue; if (fwidth > 64) UNIMPLEMENTED("InternPool.indexToKey: repeated packed " "const field exceeds 64-bit model"); uint32_t off = zcuStructPackedFieldBitOffset(NULL, ip, agg_ty, i); if (off >= 128u || fwidth > 128u - off) UNIMPLEMENTED("InternPool.indexToKey: repeated packed " "const backing exceeds 128-bit model"); packedBitsInsert(&lo, &hi, off, fwidth, elem.bits); } uint32_t bwidth; bool bsigned; interpTypeInfo(ip, agg_ty, &bwidth, &bsigned); if (bwidth > 128u) UNIMPLEMENTED("InternPool.indexToKey: repeated packed const " "backing exceeds 128 bits"); InterpPattern pp = { lo, hi, agg_ty }; bigIntMaskU128(&pp.lo, &pp.hi, bwidth); return interpScalarFromPattern(ip, pp, true); } uint32_t addr = interpConstArenaAlloc( interpAbiSize(ip, agg_ty), interpAbiAlign(ip, agg_ty)); if (ztIsTuple(ip, agg_ty)) { uint32_t td = ip->item_datas[agg_ty]; uint32_t n = ip->extra[td]; for (uint32_t i = 0; i < n; i++) { if (ip->extra[td + 1 + n + i] != IP_INDEX_NONE) continue; // comptime field: no runtime bytes uint32_t fty = ip->extra[td + 1 + i]; InterpValue ev = interpDecodeConst(ip, elem_ref); interpStoreTyped(ip, interpAddressAdd( addr, interpStructFieldOffset(ip, agg_ty, i)), fty, ev); } v.bits = addr; v.ty = agg_ty; return v; } if (ipTagIsAnyStruct(ip->item_tags[agg_ty])) { // A struct value whose fields are all the same interned value // (ipGetAggregate's repeated optimization — a 1-field struct is // trivially repeated, e.g. a `packed struct`-free // single-enum-field struct default). Store the one decoded element // into each field at its auto-layout offset, mirroring the // IP_TAG_AGGREGATE struct arm (each field's declared type equals // the repeated value's type, so interpStoreTyped copies the right // ABI bytes per field). uint32_t n = ipStructFieldsLen(ip, agg_ty); for (uint32_t i = 0; i < n; i++) { if (ipStructFieldIsComptime(ip, agg_ty, i)) continue; // fixed field has no runtime bytes uint32_t fty = ipStructFieldType(ip, agg_ty, i); ZtTypeClass field_class = ztTypeClass(ip, fty); if (field_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED("InternPool.indexToKey: unresolved repeated " "field class"); if (field_class != ZT_CLASS_RUNTIME && field_class != ZT_CLASS_PARTIAL) continue; // FULL/OPV fields carry no runtime bytes InterpValue sev = interpDecodeConst(ip, elem_ref); interpStoreTyped(ip, interpAddressAdd( addr, interpStructFieldOffset(ip, agg_ty, i)), fty, sev); } v.bits = addr; v.ty = agg_ty; return v; } InterpArrayInfo array = interpArrayInfo(ip, agg_ty); uint32_t esize = interpAbiSize(ip, array.child); InterpValue ev = interpDecodeConst(ip, elem_ref); for (uint32_t i = 0; i < array.len; i++) interpStoreTyped( ip, interpAddressIndex(addr, i, esize), array.child, ev); if (array.sentinel != IP_INDEX_NONE) { InterpValue sentinel = interpDecodeConst(ip, array.sentinel); interpStoreTyped(ip, interpAddressIndex(addr, array.len, esize), array.child, sentinel); } v.bits = addr; v.ty = agg_ty; return v; } case IP_TAG_PTR_UAV: case IP_TAG_PTR_UAV_ALIGNED: { // A comptime pointer into an unnamed aggregate (e.g. the `[*]const T` // many-ptr base of a runtime slice `a[i..j]`). extra layout: // ptr_uav = { ptr_ty, pointee_val, off_hi, off_lo }; ptr_uav_aligned // = { ptr_ty, pointee_val, orig_ty, off_hi, off_lo }. Materialize the // pointee aggregate into the arena and return base + byte_offset; the // value's type is the pointer type so downstream (ptr_add, loads) // reads the element size from the pointee. uint32_t base = data; uint32_t ptr_ty = ip->extra[base]; uint32_t pointee = ip->extra[base + 1]; uint64_t byte_off_wide = ipPtrUavByteOffset(ip, idx); if (byte_off_wide > UINT32_MAX) UNIMPLEMENTED( "InternPool.indexToKey: uav pointer offset exceeds wasm32"); uint32_t byte_off = (uint32_t)byte_off_wide; InterpValue agg = interpDecodeConst(ip, pointee); // A pointer pointee (`&p` of a comptime-known `p: *T`), or an // optional that interpTypeIsAggregate keeps in one word (a payload- // niche optional: `?*T`, `?[*]T`, `?E`), decodes to its value (the // address or error code it holds), not to its storage: it is by // value here exactly like a scalar. uint32_t pointee_val_ty = ipTypeOf(ip, pointee); bool pointee_is_ptr = ipItemTagIs(ip, pointee_val_ty, IP_TAG_TYPE_POINTER) || (interpTypeIsOptional(ip, pointee_val_ty) && !interpTypeIsAggregate(ip, pointee_val_ty)); bool runtime_ty = ztTypeClass(ip, pointee_val_ty) == ZT_CLASS_RUNTIME; uint32_t strictest = runtime_ty ? interpUavStrictestAlign(ip, pointee) : 0u; // By-address storage moves to its own uav placement only when some // reference over-aligns it (Wasm.zig's `overaligned_uavs`). if (interpConstIsByValue(ip, pointee) || pointee_is_ptr || strictest > interpAbiAlign(ip, pointee_val_ty)) { // `Sema.analyzeRef` interns `.ptr = .{ .base_addr = .uav = .{ // .val = val } }` for every comptime operand, scalar included // (src/Sema.zig), and codegen lowers a uav as read-only // data keyed by its value (CodeGen.zig's `mir_uavs`, Wasm.zig's // `uavs_exe`, both keyed by `uav.val`) at the value's own type: // every pointer to one value, whatever its pointer type, shares // one address. A comptime-only value has no runtime type, so it // gets storage per pointer at the pointer's child type. uint32_t store_ty = runtime_ty ? pointee_val_ty : ip->extra[ip->item_datas[ptr_ty]]; uint32_t align = interpUavAlign(ip, idx, store_ty); if (strictest > align) align = strictest; uint32_t caddr, cty; if (runtime_ty && interpConstCacheLookup( pointee | CC_UAV_STORAGE, &caddr, &cty)) { // Codegen places a uav once, already at the strictest // alignment any reference asks (interpUavStrictestAlign). if (caddr % align != 0) UNIMPLEMENTED("InternPool.indexToKey: uav needs a " "stricter alignment than its placement"); agg.bits = caddr; } else { uint32_t addr = interpConstArenaAlloc( interpAbiSize(ip, store_ty), align); interpStoreTyped(ip, addr, store_ty, agg); if (runtime_ty) interpConstCacheInsert( pointee | CC_UAV_STORAGE, addr, store_ty); agg.bits = addr; } } if (agg.bits > UINT32_MAX) UNIMPLEMENTED("InternPool.indexToKey: const aggregate address is " "not wasm32"); v.bits = interpAddressAdd(interpAddressFromBits(agg.bits), byte_off); v.ty = ptr_ty; return v; } case IP_TAG_PTR_NAV: { // A comptime pointer to a Nav. For a `*const fn` this is a function // Pointer: its runtime value is a handle naming the function itself // (interpFunctionHandle), so every pointer to one function compares // equal whatever its pointer type. The value's type is the pointer // type (extra[data+0]). Data Navs resolve // through a per-program Nav-index cache: const and mutable globals // have stable, distinct addresses and their initializer is copied // once. Never reinterpret or mutate IpNav.resolved_value. IpPtrNav ptr; const IpNav* nav_data = interpPtrNav(ip, idx, &ptr); // The Nav, not the pointer's child type, decides what the address // is: a function Nav's is its handle whatever the pointer was cast // to (`@ptrCast(&f)`), a data Nav's its storage even when read as a // function pointer (`@ptrCast(&global_array)`). if (ip->item_tags[ptr.nav_type] != IP_TAG_TYPE_FUNCTION) { if (ptr.byte_offset > UINT32_MAX) UNIMPLEMENTED( "InternPool.indexToKey: ptr_nav offset exceeds wasm32"); uint32_t nav_size = interpAbiSize(ip, nav_data->resolved_type); // A function or `anyopaque` child has no size to bound. uint32_t child_size = ip->item_tags[ptr.child_type] == IP_TAG_TYPE_FUNCTION || ptr.child_type == ZIR_REF_ANYOPAQUE_TYPE ? 0u : interpAbiSize(ip, ptr.child_type); if (ptr.byte_offset > nav_size || child_size > nav_size - (uint32_t)ptr.byte_offset) UNREACHABLE("interp: projected ptr_nav is out of bounds"); uint32_t base = interpNavAddress(ip, ptr.nav_index); v.bits = interpAddressAdd(base, (uint32_t)ptr.byte_offset); v.ty = ptr.ptr_type; return v; } if (ptr.byte_offset != 0) UNIMPLEMENTED("InternPool.indexToKey: projected ptr_nav cannot " "name a function"); v.bits = interpFunctionHandle(ip, nav_data->resolved_value); v.ty = ptr.ptr_type; return v; } case IP_TAG_PTR_ELEM: { // A comptime `.arr_elem` pointer: its base's address plus the // element's offset and the pointer's own byte offset. uint32_t e_base = ipPtrElemBase(ip, idx); IpArrayInfo arr; if (ipArrayInfo( ip, ip->extra[ip->item_datas[ipTypeOf(ip, e_base)]], &arr) != IP_ARR_OK) UNIMPLEMENTED( "InternPool.indexToKey: element pointer base is not an array"); uint64_t e_byte_off; if (!ipPtrValueByteOffset(ip, idx, &e_byte_off) || e_byte_off > UINT32_MAX) UNIMPLEMENTED("InternPool.indexToKey: element pointer offset " "exceeds wasm32"); InterpValue e_bp = interpDecodeConst(ip, e_base); v.bits = interpAddressAdd( interpAddressAdd(interpAddressFromBits(e_bp.bits), interpAbiSize(ip, arr.child) * ipPtrElemIndex(ip, idx)), (uint32_t)e_byte_off); v.ty = ipTypeOf(ip, idx); return v; } case IP_TAG_PTR_FIELD: { // A comptime `.field` pointer: its base's address plus the field's // offset, the address AIR_INST_STRUCT_FIELD_PTR computes at runtime // (a packed struct's bit pointer keeps the host's address). uint32_t f_base = ipPtrFieldBase(ip, idx); uint32_t f_pty = ip->extra[ip->item_datas[ipTypeOf(ip, f_base)]]; uint32_t f_off = 0; uint8_t f_tag = ip->item_tags[f_pty]; if (f_tag == IP_TAG_TYPE_STRUCT || f_tag == IP_TAG_TYPE_TUPLE || f_tag == IP_TAG_TYPE_UNION) f_off = interpStructFieldOffset(ip, f_pty, ipPtrFieldIndex(ip, idx)); else if (f_tag == IP_TAG_TYPE_SLICE) f_off = ipPtrFieldIndex(ip, idx) * 4u; // `ptr`, then `len` else if (!ipTypeIsBitpack(ip, f_pty)) UNIMPLEMENTED( "InternPool.indexToKey: field pointer into a non-container"); uint64_t f_byte_off = ipPtrFieldByteOffset(ip, idx); if (f_byte_off > UINT32_MAX) UNIMPLEMENTED( "InternPool.indexToKey: field pointer offset exceeds wasm32"); InterpValue f_bp = interpDecodeConst(ip, f_base); v.bits = interpAddressAdd( interpAddressAdd(interpAddressFromBits(f_bp.bits), f_off), (uint32_t)f_byte_off); v.ty = ipTypeOf(ip, idx); return v; } case IP_TAG_PTR_EU_PAYLOAD: { // A comptime `.eu_payload` pointer: its base's address plus the // payload's offset, as AIR_INST_UNWRAP_ERRUNION_PAYLOAD_PTR computes. uint32_t e_base = ipPtrEuPayloadBase(ip, idx); uint32_t e_poff, e_eoff; interpErrUnionOffsets(ip, ip->extra[ip->item_datas[ipTypeOf(ip, e_base)]], &e_poff, &e_eoff); uint64_t e_byte_off = ipPtrEuPayloadByteOffset(ip, idx); if (e_byte_off > UINT32_MAX) UNIMPLEMENTED("InternPool.indexToKey: payload pointer offset " "exceeds wasm32"); InterpValue e_bp = interpDecodeConst(ip, e_base); v.bits = interpAddressAdd( interpAddressAdd(interpAddressFromBits(e_bp.bits), e_poff), (uint32_t)e_byte_off); v.ty = ipTypeOf(ip, idx); return v; } case IP_TAG_PTR_INT: { // Mapped as AIR_INST_PTR_FROM_INT's is, so a comptime and a runtime // `@ptrFromInt(k)` are one pointer. uint32_t pi_ty = ipTypeOf(ip, idx); return interpPtrFromIntScalar( pi_ty, (InterpValue) { ipPtrIntAddress(ip, idx), pi_ty }); } case IP_TAG_PTR_SLICE: { // A comptime slice value `{ ptr: [*]T, len }`. extra: { slice_ty, // many_ptr, len_val }. The `ptr` field (offset 0) is the many // pointer's own decode -- a uav's storage (a by-value scalar uav // included) or a Nav's stable address, which the slice aliases -- // and the `len` field (offset 4) the element count (wasm32 layout, // matching codegen c.zig's slice representation). The slice struct // lives in the const arena, cached per IP index. uint32_t slice_ty = ip->extra[data]; InterpValue ptr = interpDecodeConst(ip, ip->extra[data + 1]); InterpValue len = interpDecodeConst(ip, ip->extra[data + 2]); if (ptr.bits > UINT32_MAX) UNIMPLEMENTED( "InternPool.indexToKey: slice pointer address is not wasm32"); uint32_t saddr = interpConstArenaAlloc( interpAbiSize(ip, slice_ty), interpAbiAlign(ip, slice_ty)); interpWriteLE(saddr, (uint32_t)ptr.bits, 4); // ptr field interpWriteLE(interpAddressAdd(saddr, 4u), len.bits, 4); // len v.bits = saddr; v.ty = slice_ty; return v; } case IP_TAG_ERROR_SET_ERROR: { // A comptime error value `error.Name`. Its runtime representation is // the error's global error set value. extra: { set_ty, name }. uint32_t set_ty = ip->extra[data]; uint32_t name = ip->extra[data + 1]; v.bits = interpErrorCode(ip, name); v.ty = set_ty; return v; } case IP_TAG_ERROR_UNION_ERROR: { // A comptime `E!T` value holding an error (e.g. the value produced by // `return error.Name`). Materialize it into the arena as an error- // union aggregate: the payload is left undefined and the dense error // code (shared with IP_TAG_ERROR_SET_ERROR) is written at the error // offset. extra: { eu_ty, name }. uint32_t eu_ty = ip->extra[data]; uint32_t name = ip->extra[data + 1]; uint32_t error_code = interpErrorCode(ip, name); if (interpErrUnionFullyComptime(ip, eu_ty)) { uint32_t addr = interpConstArenaAlloc(0u, interpAbiAlign(ip, eu_ty)); v.bits = addr; v.ty = eu_ty; return v; } uint32_t poff, eoff; interpErrUnionOffsets(ip, eu_ty, &poff, &eoff); uint32_t addr = interpConstArenaAlloc( interpAbiSize(ip, eu_ty), interpAbiAlign(ip, eu_ty)); interpWriteLE( interpAddressAdd(addr, eoff), error_code, ZT_ERROR_ABI_SIZE); v.bits = addr; v.ty = eu_ty; return v; } case IP_TAG_OPT_NULL: { // A comptime optional-none value `(?T) null` (e.g. from `return // null`). Materialize zeroed optional bytes in the arena. A niche // representation is already null; a flagged representation also has // its discriminator cleared. item.data = the optional type. uint32_t opt_ty = data; InterpOptionalRepr repr = interpOptionalRepr(ip, opt_ty); if (repr == INTERP_OPT_REPR_PAYLOAD_NICHE && !interpTypeIsAggregate(ip, opt_ty)) { v.bits = 0; v.ty = opt_ty; return v; } uint32_t addr = interpConstArenaAlloc( interpAbiSize(ip, opt_ty), interpAbiAlign(ip, opt_ty)); if (repr == INTERP_OPT_REPR_PAYLOAD_FLAG) interpWriteLE( interpAddressAdd(addr, interpOptFlagOffset(ip, opt_ty)), 0u, 1u); v.bits = addr; v.ty = opt_ty; return v; } case IP_TAG_OPT_PAYLOAD: { // A comptime optional-payload value `(?T) x`. Materialize the optional // aggregate: write the decoded payload at offset 0 and, for a // non-niche representation, set the non-null flag. extra: { // opt_ty, val }. uint32_t opt_ty = ip->extra[data]; uint32_t payload_ref = ip->extra[data + 1]; uint32_t child = ip->item_datas[opt_ty]; InterpOptionalRepr repr = interpOptionalRepr(ip, opt_ty); if (repr == INTERP_OPT_REPR_NPV_NULL) UNIMPLEMENTED("InternPool.indexToKey: optional NPV child cannot " "have payload"); InterpValue pv = interpDecodeConst(ip, payload_ref); if (repr == INTERP_OPT_REPR_PAYLOAD_NICHE && !interpTypeIsAggregate(ip, opt_ty)) { v.bits = pv.bits; v.ty = opt_ty; return v; } uint32_t addr = interpConstArenaAlloc( interpAbiSize(ip, opt_ty), interpAbiAlign(ip, opt_ty)); if (repr == INTERP_OPT_REPR_FULL) { v.bits = addr; v.ty = opt_ty; return v; } interpStoreTyped(ip, addr, child, pv); if (repr == INTERP_OPT_REPR_PAYLOAD_FLAG) interpWriteLE( interpAddressAdd(addr, interpOptFlagOffset(ip, opt_ty)), 1u, 1u); v.bits = addr; v.ty = opt_ty; return v; } case IP_TAG_ERROR_UNION_PAYLOAD: { // A comptime `E!T` value holding a payload (e.g. `return 1` in an // `anyerror!u8` fn). Materialize the error-union aggregate: write the // decoded payload at the payload offset and set the error code to 0 // (no error), so is_err / unwrap_errunion_payload read it back. // Mirrors the WRAP_ERRUNION_PAYLOAD layout. extra: { eu_ty, val }. uint32_t eu_ty = ip->extra[data]; uint32_t payload_ref = ip->extra[data + 1]; uint32_t payload_ty = ipErrorUnionPayload(ip, eu_ty); if (interpErrUnionFullyComptime(ip, eu_ty)) { uint32_t addr = interpConstArenaAlloc(0u, interpAbiAlign(ip, eu_ty)); v.bits = addr; v.ty = eu_ty; return v; } uint32_t poff, eoff; interpErrUnionOffsets(ip, eu_ty, &poff, &eoff); uint32_t addr = interpConstArenaAlloc( interpAbiSize(ip, eu_ty), interpAbiAlign(ip, eu_ty)); InterpValue pv = interpDecodeConst(ip, payload_ref); interpStoreTyped(ip, interpAddressAdd(addr, poff), payload_ty, pv); interpWriteLE(interpAddressAdd(addr, eoff), 0u, ZT_ERROR_ABI_SIZE); v.bits = addr; v.ty = eu_ty; return v; } case IP_TAG_ONLY_POSSIBLE_VALUE: { // InternPool.indexToKey reconstructs an OPV array's storage from its // type: "elems = sentinel[0..@intFromBool(sentinel[0] != .none)]". // Its language length is zero, but a sentinel still occupies storage. // Keep this storage in the persistent constant arena like other // decoded aggregates. A scalar OPV (e.g. u0) has zero significant // bits. uint32_t oty = data; if (interpTypeIsAggregate(ip, oty)) { uint32_t addr = interpConstArenaAlloc( interpAbiSize(ip, oty), interpAbiAlign(ip, oty)); if (ip->item_tags[oty] == IP_TAG_TYPE_ARRAY_BIG) { InterpArrayInfo array = interpArrayInfo(ip, oty); uint32_t esize = interpAbiSize(ip, array.child); // Zero-bit sentinels (including type values) need no decode. if (array.sentinel != IP_INDEX_NONE && esize != 0u) { InterpValue sentinel = interpDecodeConst(ip, array.sentinel); interpStoreTyped(ip, interpAddressIndex(addr, array.len, esize), array.child, sentinel); } } v.bits = addr; v.ty = oty; return v; } v.bits = 0; v.ty = oty; return v; } case IP_TAG_UNDEF: { // An `undefined` value of type `ty` (item.data). Its bytes are 0xAA, // not zero: `Value.writeToMemory` (src/Value.zig) is // if (val.isUndef(zcu)) { @memset(buffer[0..size], 0xAA); return; // } // and the wasm backend's register-level counterpart // `CodeGen.emitUndefined` (src/codegen/wasm/CodeGen.zig) gives // the same pattern at the value's own width (0xaaaaaaaa for anything // through 32 bits, 0xaaaaaaaaaaaaaaaa through 64). That is what // behavior/undefined.zig's "returned undef is 0xaa bytes when // runtime safety is enabled" observes. Writing the pattern once and // letting `interpLoadTyped` decode it keeps the two cases -- an // aggregate that stays at its address, a scalar that becomes a // normalized word -- on the interpreter's own reader instead of a // second width table. The const arena is the right home: this decode // is memoized per InternPool index by interpDecodeConst. uint32_t uty = data; uint32_t usize = interpAbiSize(ip, uty); if (interpTypeIsAggregate(ip, uty)) { uint32_t uaddr = interpConstArenaAlloc(usize, interpAbiAlign(ip, uty)); if (usize != 0) memset(interpArenaRangePtr(uaddr, usize), 0xAA, usize); v.bits = uaddr; v.ty = uty; return v; } if (usize == 0) { v.bits = 0; v.ty = uty; return v; } // `emitUndefined`'s optional arm: a payload-repr optional takes the // PAYLOAD's pattern (a non-payload-repr one is an aggregate above). uint32_t uscalar = uty; if (interpTypeIsOptional(ip, uscalar)) uscalar = ipOptionalChild(ip, uscalar); uint32_t uwidth; bool usigned; interpTypeInfo(ip, uscalar, &uwidth, &usigned); // Not delivered: `emitUndefined`'s `.bool` arm. Upstream gives a // bool 0xaaaaaaaa, which reads back true; here a bool is one bit // wide, so the mask below keeps bit 0 of 0xAA and the value reads // false. The root cause is wider -- zig1 reads a 0xAA byte as a bool // `false` where upstream reads `true` -- not this pattern. if (interpTypeIsByRefScalar(ip, uty)) { // `emitUndefined` writes the same byte at every width, so a // by-ref undefined is its slot filled with it. uint32_t usz = interpAbiSize(ip, uty); uint32_t uaddr = interpConstArenaAlloc(usz, interpAbiAlign(ip, uty)); memset(interpArenaRangePtr(uaddr, usz), 0xAA, usz); v.bits = uaddr; } else { v.bits = interpNormalize(INTERP_UNDEF_PATTERN, uwidth, usigned); } v.ty = uty; return v; } case IP_TAG_UNION_VALUE: { // A comptime tagged-`union(enum)` value (`.{ .ret = 7 }` folded to a // `union_value`). extra: { uty, tag, val } where `tag` is the interned // enum-tag value and `val` the payload value. Materialize into the // (persistent) const arena mirroring AIR_INST_UNION_INIT exactly: the // active field's payload at payload_off (typed store), the tag int at // tag_off. The tag's backing integer is not necessarily the union's // declaration-order field index: explicit tag enums may use arbitrary // values and ordering, so map value -> enum name -> union field. uint32_t uty = ip->extra[data]; uint32_t tag_ref = ip->extra[data + 1]; uint32_t val_ref = ip->extra[data + 2]; ZtUnionLayout ul = interpUnionLayout(ip, uty); if (tag_ref == IP_INDEX_NONE) { // An extern union's tag-less value (`castMemory`'s backing // bytes): the payload at offset 0, at its own type. uint32_t addr = interpConstArenaAlloc( (uint32_t)ul.abi_size, (uint32_t)ul.abi_align); interpStoreTyped(ip, addr, ipTypeOf(ip, val_ref), interpDecodeConst(ip, val_ref)); v.bits = addr; v.ty = uty; return v; } if (ip->item_tags[tag_ref] != IP_TAG_ENUM_TAG || ipEnumTagType(ip, tag_ref) != ipUnionEnumTagType(ip, uty)) UNREACHABLE("interp: union constant tag type mismatch"); InterpValue tagv = interpDecodeConst(ip, tag_ref); // enum tag -> tag int uint32_t field_index = interpUnionFieldFromTagBits(ip, uty, tagv.bits); uint32_t field_ty = ipUnionFieldType(ip, uty, field_index); uint32_t addr = interpConstArenaAlloc( (uint32_t)ul.abi_size, (uint32_t)ul.abi_align); InterpValue pv = interpDecodeConst(ip, val_ref); interpStoreTyped(ip, interpAddressAdd(addr, (uint32_t)ztUnionLayoutPayloadOffset(&ul)), field_ty, pv); interpUnionTagStore(&ul, addr, tagv.bits); v.bits = addr; v.ty = uty; return v; } default: UNIMPLEMENTED("InternPool.indexToKey: unhandled constant value shape"); } ZIG1_TCC_RETURN((InterpValue) { 0 }); } // Execution context for one function activation. typedef struct { InternPool* ip; const Air* air; const SemaFuncAirList* all; // for resolving `call` callees by func_decl const InterpValue* args; // concrete arguments, in signature order uint32_t nargs; // number of concrete arguments passed uint32_t args_used; // next positional argument an `arg` inst binds InterpValue* values; // [inst_len] per-instruction computed values uint32_t frame_mark; // first arena offset owned by this activation } InterpCtx; struct InterpFrame { InterpCtx ctx; const SemaFuncAir* fn; uint32_t bodies_base; // its INTERP_BODY_FN body in Interp.bodies uint32_t call_inst; // the caller's call instruction, or UINT32_MAX uint32_t result_ty; // its return type as called uint32_t call_ty; // the type the caller sees the result at, or NONE }; // Resolve an AIR value ref: an instruction result, or a decoded constant. ZIG1_INLINE InterpValue interpResolveRef(const InterpCtx* ctx, uint32_t ref) { if (ref >= AIR_REF_INST_BASE) return ctx->values[ref - AIR_REF_INST_BASE]; return interpDecodeConst(ctx->ip, ref); } // The width > 64 path of interpBinOp: integer ops on by-ref values, whose // slot bytes are the operands. Every rule here is arith.c's -- the // same `src/Sema/arith.zig` workers the comptime fold and this frame's own // <= 64-bit arm already call -- over `std.math.big.int` operands, read out of // those bytes by `Mutable.readTwosComplement` and written back by // `Const.writeTwosComplement`. The hand-rolled 128-bit carry chains, the // bit-at-a-time unsigned divider, the open-coded saturating shift and their // "signed wide div/rem/mod" / "signed wide shr" / "signed wide shl_sat" // declines are gone with them, and so is 128 bits as a boundary: `big.int` // is signed and unbounded, and a memory slot is as wide as its type. static bool interpArithZirTag(uint8_t tag, ZirInstTag* out); static bool interpCmpZirTag(uint8_t tag, ZirInstTag* out); // The one place an `ArithStatus` becomes this frame's loud abort. Both // integer arms reach the same statuses, so they read the same list. ZIG1_ATTRIBUTE((noreturn)) static void interpArithAbort(ArithStatus st) { switch (st) { case ARITH_DIV_ZERO: UNIMPLEMENTED("div_trunc: integer division by zero"); case ARITH_OVERFLOW: UNIMPLEMENTED("add_safe: overflow of integer type"); case ARITH_REMAINDER: UNIMPLEMENTED("div_exact: exact division produced remainder"); case ARITH_SHIFT_AMOUNT: UNIMPLEMENTED("shl: invalid shift amount"); case ARITH_SHIFT_TOO_LARGE: UNIMPLEMENTED("shl: shift amount too large for fixed-width integer"); default: // ARITH_UNSUPPORTED_OP UNIMPLEMENTED("Air.Inst.Data.bin_op: unsupported integer arithmetic"); } } static InterpValue interpBinOpWide(InternPool* ip, uint8_t tag, InterpValue lhs, InterpValue rhs, uint32_t width, bool is_signed) { uint64_t a_space[INTERP_MAX_INT_LIMBS], b_space[INTERP_MAX_INT_LIMBS]; BigIntConst a = interpValueToBigInt( ip, lhs, width, is_signed, a_space, INTERP_MAX_INT_LIMBS); // A shift amount keeps its own type (`arith.shl`/`shr` read it apart // from the shifted operand), so it is read at its own width and sign. uint32_t b_width = width; bool b_signed = is_signed; ZirInstTag zop; bool has_zop = interpArithZirTag(tag, &zop); if (has_zop && arithTagIsShift(zop) && interpTypeIsIntegerStorage(ip, rhs.ty)) interpTypeInfo(ip, rhs.ty, &b_width, &b_signed); BigIntConst b = interpValueToBigInt( ip, rhs, b_width, b_signed, b_space, INTERP_MAX_INT_LIMBS); InterpValue out = { 0, lhs.ty }; switch (tag) { case AIR_INST_CMP_EQ: case AIR_INST_CMP_EQ_OPTIMIZED: case AIR_INST_CMP_NEQ: case AIR_INST_CMP_NEQ_OPTIMIZED: case AIR_INST_CMP_LT: case AIR_INST_CMP_LT_OPTIMIZED: case AIR_INST_CMP_LTE: case AIR_INST_CMP_LTE_OPTIMIZED: case AIR_INST_CMP_GT: case AIR_INST_CMP_GT_OPTIMIZED: case AIR_INST_CMP_GTE: case AIR_INST_CMP_GTE_OPTIMIZED: case AIR_INST_MIN: case AIR_INST_MAX: { // `Value.order` -> `big.int.Const.order`, which is also the one // comparison the comptime frame reaches (`semaCmpComptime`). The // sign-and-magnitude ordering needs no signedness case of its own: // the read above already applied it. int ord = bigIntOrder(a, b); if (tag == AIR_INST_MIN) return ord < 0 ? lhs : rhs; if (tag == AIR_INST_MAX) return ord > 0 ? lhs : rhs; ZirInstTag cmp_op; if (!interpCmpZirTag(tag, &cmp_op)) UNREACHABLE("interp: wide comparison tag"); out.ty = ZIR_REF_BOOL_TYPE; out.bits = valCmpApplyOrder(cmp_op, ord) ? 1u : 0u; return out; } default: break; } if (!has_zop) { fprintf(stderr, "interp: unhandled wide integer op tag=%u lhs_ty=%u rhs_ty=%u " "width=%u signed=%u lhs=%016llx rhs=%016llx\n", (unsigned)tag, lhs.ty, rhs.ty, width, is_signed ? 1u : 0u, (unsigned long long)lhs.bits, (unsigned long long)rhs.bits); UNIMPLEMENTED( "Air.Inst.Data.bin_op: unhandled wide (>64-bit) integer op"); } ArithIntType aty = arithIntTypeSized(width, is_signed); // arith.c's own bound for this pair. At <= 128-bit operands every // operation stays within a handful of limbs, so the buffer is on the // stack; an operation that wants more is a shape this carrier cannot // hold and says so rather than writing past it. uint32_t cap = arithIntBinOpLimbs(zop, a, b, aty); if (cap > INTERP_MAX_INT_RES_LIMBS) UNIMPLEMENTED( "Air.Inst.Data.bin_op: integer arithmetic needs a wider result"); uint64_t buf[INTERP_MAX_INT_RES_LIMBS]; BigIntMutable r = { buf, INTERP_MAX_INT_RES_LIMBS, 0, true }; ArithStatus wst = arithIntBinOp(zop, a, b, aty, &r); if (wst != ARITH_OK) interpArithAbort(wst); return interpValueFromBigInt( ip, bigIntMutableToConst(r), lhs.ty, width, is_signed, false); } // f32/f64 comparisons and `@min`/`@max`, on the operands widened exactly to // double (`ty` is the result type of `@min`/`@max`). Their arithmetic is // arith.c's float half, the rule the comptime fold asks too. static InterpValue interpFloatCmpMinMax( uint8_t tag, double lhs, double rhs, uint32_t ty, uint32_t float_bits) { InterpValue out = { 0, ZIR_REF_BOOL_TYPE }; bool res; switch (tag) { case AIR_INST_MIN: case AIR_INST_MAX: out.ty = ty; out.bits = arithFloatFromDouble( tag == AIR_INST_MIN ? fmin(lhs, rhs) : fmax(lhs, rhs), float_bits); return out; case AIR_INST_CMP_LT: res = lhs < rhs; break; case AIR_INST_CMP_LTE: res = lhs <= rhs; break; case AIR_INST_CMP_EQ: res = f64EqExact(lhs, rhs); break; case AIR_INST_CMP_GTE: res = lhs >= rhs; break; case AIR_INST_CMP_GT: res = lhs > rhs; break; case AIR_INST_CMP_NEQ: res = !f64EqExact(lhs, rhs); break; default: UNIMPLEMENTED("cmp_neq: float binary op"); } out.bits = res ? 1u : 0u; return out; } // Compute a binary/comparison AIR op. Result type is the operand (lhs) type, // except comparisons which yield `bool`. // One vector operation is its scalar one applied lane by lane, with `res_ty` // the result vector type (defined below, next to its only other caller). static InterpValue interpVectorLanes(InternPool* ip, uint8_t tag, InterpValue lhs, InterpValue rhs, uint32_t res_ty); // Each AIR arithmetic tag is one arith.c (src/Sema/arith.zig) // operation, keyed by the ZIR tag the comptime fold uses. AIR's plain // add/sub/mul is UB on overflow and the interpreter wraps it, so those and // their safe and optimized forms map to `.addwrap`/`.subwrap`/`.mulwrap` // (`+ - *` on a float); shl_exact/shr_exact are the plain shifts, upstream's // release lowering; the *_with_overflow tags are their op. static bool interpArithZirTag(uint8_t tag, ZirInstTag* out) { switch (tag) { case AIR_INST_ADD: case AIR_INST_ADD_SAFE: case AIR_INST_ADD_OPTIMIZED: case AIR_INST_ADD_WRAP: *out = ZIR_INST_ADDWRAP; return true; case AIR_INST_SUB: case AIR_INST_SUB_SAFE: case AIR_INST_SUB_OPTIMIZED: case AIR_INST_SUB_WRAP: *out = ZIR_INST_SUBWRAP; return true; case AIR_INST_MUL: case AIR_INST_MUL_SAFE: case AIR_INST_MUL_OPTIMIZED: case AIR_INST_MUL_WRAP: *out = ZIR_INST_MULWRAP; return true; case AIR_INST_ADD_SAT: *out = ZIR_INST_ADD_SAT; return true; case AIR_INST_SUB_SAT: *out = ZIR_INST_SUB_SAT; return true; case AIR_INST_MUL_SAT: *out = ZIR_INST_MUL_SAT; return true; case AIR_INST_DIV_FLOAT: case AIR_INST_DIV_FLOAT_OPTIMIZED: *out = ZIR_INST_DIV; return true; case AIR_INST_DIV_TRUNC: case AIR_INST_DIV_TRUNC_OPTIMIZED: *out = ZIR_INST_DIV_TRUNC; return true; case AIR_INST_DIV_EXACT: case AIR_INST_DIV_EXACT_OPTIMIZED: *out = ZIR_INST_DIV_EXACT; return true; case AIR_INST_DIV_FLOOR: case AIR_INST_DIV_FLOOR_OPTIMIZED: *out = ZIR_INST_DIV_FLOOR; return true; case AIR_INST_DIV_CEIL: case AIR_INST_DIV_CEIL_OPTIMIZED: *out = ZIR_INST_DIV_CEIL; return true; case AIR_INST_REM: case AIR_INST_REM_OPTIMIZED: *out = ZIR_INST_REM; return true; case AIR_INST_MOD: case AIR_INST_MOD_OPTIMIZED: *out = ZIR_INST_MOD; return true; case AIR_INST_BIT_AND: *out = ZIR_INST_BIT_AND; return true; case AIR_INST_BIT_OR: *out = ZIR_INST_BIT_OR; return true; case AIR_INST_XOR: *out = ZIR_INST_XOR; return true; case AIR_INST_SHL: case AIR_INST_SHL_EXACT: *out = ZIR_INST_SHL; return true; case AIR_INST_SHL_SAT: *out = ZIR_INST_SHL_SAT; return true; case AIR_INST_SHR: case AIR_INST_SHR_EXACT: *out = ZIR_INST_SHR; return true; case AIR_INST_ADD_WITH_OVERFLOW: *out = ZIR_INST_ADD; return true; case AIR_INST_SUB_WITH_OVERFLOW: *out = ZIR_INST_SUB; return true; case AIR_INST_MUL_WITH_OVERFLOW: *out = ZIR_INST_MUL; return true; case AIR_INST_SHL_WITH_OVERFLOW: *out = ZIR_INST_SHL; return true; case AIR_INST_SQRT: *out = ZIR_INST_SQRT; return true; case AIR_INST_SIN: *out = ZIR_INST_SIN; return true; case AIR_INST_COS: *out = ZIR_INST_COS; return true; case AIR_INST_TAN: *out = ZIR_INST_TAN; return true; case AIR_INST_EXP: *out = ZIR_INST_EXP; return true; case AIR_INST_EXP2: *out = ZIR_INST_EXP2; return true; case AIR_INST_LOG: *out = ZIR_INST_LOG; return true; case AIR_INST_LOG2: *out = ZIR_INST_LOG2; return true; case AIR_INST_LOG10: *out = ZIR_INST_LOG10; return true; case AIR_INST_FLOOR: *out = ZIR_INST_FLOOR; return true; case AIR_INST_CEIL: *out = ZIR_INST_CEIL; return true; case AIR_INST_ROUND: *out = ZIR_INST_ROUND; return true; case AIR_INST_TRUNC_FLOAT: *out = ZIR_INST_TRUNC; return true; default: return false; } } // The ZIR comparison of an AIR cmp tag (valCmpApplyOrder's key). static bool interpCmpZirTag(uint8_t tag, ZirInstTag* out) { switch (tag) { case AIR_INST_CMP_LT: case AIR_INST_CMP_LT_OPTIMIZED: *out = ZIR_INST_CMP_LT; return true; case AIR_INST_CMP_LTE: case AIR_INST_CMP_LTE_OPTIMIZED: *out = ZIR_INST_CMP_LTE; return true; case AIR_INST_CMP_EQ: case AIR_INST_CMP_EQ_OPTIMIZED: *out = ZIR_INST_CMP_EQ; return true; case AIR_INST_CMP_NEQ: case AIR_INST_CMP_NEQ_OPTIMIZED: *out = ZIR_INST_CMP_NEQ; return true; case AIR_INST_CMP_GTE: case AIR_INST_CMP_GTE_OPTIMIZED: *out = ZIR_INST_CMP_GTE; return true; case AIR_INST_CMP_GT: case AIR_INST_CMP_GT_OPTIMIZED: *out = ZIR_INST_CMP_GT; return true; default: return false; } } // The binary op on scalar integers of at most 64 bits. static InterpValue interpScalarIntBinOp(uint8_t tag, InterpValue lhs, InterpValue rhs, uint32_t width, bool is_signed) { uint64_t a = lhs.bits; uint64_t b = rhs.bits; int64_t sa = (int64_t)a; int64_t sb = (int64_t)b; InterpValue out = { 0, lhs.ty }; // Comparisons first: they produce bool, not a wrapped operand-typed value. switch (tag) { case AIR_INST_CMP_LT: out.ty = ZIR_REF_BOOL_TYPE; out.bits = (is_signed ? (sa < sb) : (a < b)) ? 1u : 0u; return out; case AIR_INST_CMP_LTE: out.ty = ZIR_REF_BOOL_TYPE; out.bits = (is_signed ? (sa <= sb) : (a <= b)) ? 1u : 0u; return out; case AIR_INST_CMP_EQ: out.ty = ZIR_REF_BOOL_TYPE; out.bits = (a == b) ? 1u : 0u; return out; case AIR_INST_CMP_GTE: out.ty = ZIR_REF_BOOL_TYPE; out.bits = (is_signed ? (sa >= sb) : (a >= b)) ? 1u : 0u; return out; case AIR_INST_CMP_GT: out.ty = ZIR_REF_BOOL_TYPE; out.bits = (is_signed ? (sa > sb) : (a > b)) ? 1u : 0u; return out; case AIR_INST_CMP_NEQ: out.ty = ZIR_REF_BOOL_TYPE; out.bits = (a != b) ? 1u : 0u; return out; default: break; } uint64_t r; switch (tag) { case AIR_INST_ADD: case AIR_INST_ADD_WRAP: r = a + b; break; case AIR_INST_SUB: case AIR_INST_SUB_WRAP: r = a - b; break; case AIR_INST_MUL: case AIR_INST_MUL_WRAP: r = a * b; break; case AIR_INST_BIT_AND: r = a & b; break; case AIR_INST_BIT_OR: r = a | b; break; case AIR_INST_XOR: r = a ^ b; break; case AIR_INST_SHL: case AIR_INST_SHL_EXACT: r = a << (b & 63u); break; case AIR_INST_SHR: r = is_signed ? (uint64_t)(sa >> (sb & 63)) : (a >> (b & 63u)); break; case AIR_INST_MIN: r = is_signed ? (uint64_t)(sa < sb ? sa : sb) : (a < b ? a : b); break; case AIR_INST_MAX: r = is_signed ? (uint64_t)(sa > sb ? sa : sb) : (a > b ? a : b); break; default: { // The whole division and remainder family lives in arith.c, over // exact magnitudes, because upstream keeps one `intDivTrunc` / // `intDivFloor` / `intDivExact` / `intMod` / `intRem` and calls it // from every frame. An exact quotient that does not fit, such as // `@divTrunc(minInt(i32), -1)`, is ARITH_OVERFLOW, which this frame // refuses rather than inventing a wrapped answer. One tag map serves // every width. ZirInstTag aop; if (!interpArithZirTag(tag, &aop)) UNIMPLEMENTED("Air.Inst.Data.bin_op: unhandled binary op"); ArithIntType aty = arithIntTypeSized(width, is_signed); uint64_t a_space[1], b_space[1]; BigIntConst av = arithIntFromWord(a_space, a, is_signed && sa < 0); BigIntConst bv = arithIntFromWord(b_space, b, is_signed && sb < 0); // `arithIntBinOpLimbs` is arith.c's own bound for this pair; at this // frame's <= 64-bit operands it is a handful of limbs, so the buffer // is on the stack rather than malloc'd. uint32_t a_cap = arithIntBinOpLimbs(aop, av, bv, aty); if (a_cap > 8u) UNIMPLEMENTED("Air.Inst.Data.bin_op: integer arithmetic needs a " "wider result"); uint64_t a_buf[8]; BigIntMutable ares = { a_buf, 8u, 0, true }; ArithStatus ast = arithIntBinOp(aop, av, bv, aty, &ares); if (ast != ARITH_OK) interpArithAbort(ast); uint64_t aw; if (!arithIntToWord(bigIntMutableToConst(ares), aty, &aw)) UNIMPLEMENTED( "Air.Inst.Data.bin_op: integer result wider than 64 bits"); r = aw; break; } } out.bits = interpNormalize(r, width, is_signed); return out; } static InterpValue interpBinOp( InternPool* ip, uint8_t tag, InterpValue lhs, InterpValue rhs) { uint32_t width; bool is_signed; if (lhs.ty == rhs.ty) { if (lhs.ty == ZIR_REF_BOOL_TYPE) { width = 1; is_signed = false; return interpScalarIntBinOp(tag, lhs, rhs, width, is_signed); } if (interpIntTypeInfo(lhs.ty, &width, &is_signed)) { if (width > 64) return interpBinOpWide(ip, tag, lhs, rhs, width, is_signed); return interpScalarIntBinOp(tag, lhs, rhs, width, is_signed); } // A dynamic integer type's width and signedness are its item, as // interpTypeInfo reads them below; none of the arms between applies. if (interpDynIntInfo(ip, lhs.ty, &width, &is_signed)) { if (width > 64) return interpBinOpWide(ip, tag, lhs, rhs, width, is_signed); return interpScalarIntBinOp(tag, lhs, rhs, width, is_signed); } } if (interpTypeIsOptional(ip, lhs.ty) || interpTypeIsOptional(ip, rhs.ty)) { if (tag != AIR_INST_CMP_EQ && tag != AIR_INST_CMP_NEQ) UNIMPLEMENTED("cmp_lt: optional ordering"); bool eq = interpOptionalValuesEqual(ip, lhs, rhs); InterpValue out = { 0, ZIR_REF_BOOL_TYPE }; out.bits = (tag == AIR_INST_CMP_EQ ? eq : !eq) ? 1u : 0u; return out; } if ((lhs.ty == ZIR_REF_F16_TYPE || lhs.ty == ZIR_REF_F32_TYPE || lhs.ty == ZIR_REF_F64_TYPE) && rhs.ty == lhs.ty) { // `+ - * /` and the division family are arith.c's float half, the // rule the comptime fold uses (the float policy: f16 computed in f32 // and rounded once, which `__divhf3` does not always give; f32/f64 // at the host's float/double). uint32_t fb = lhs.ty == ZIR_REF_F16_TYPE ? 16u : lhs.ty == ZIR_REF_F32_TYPE ? 32u : 64u; ZirInstTag f_tag; if (interpArithZirTag(tag, &f_tag)) { CrtU128 r; if (arithFloatBinOp(f_tag, (CrtU128) { lhs.bits, 0 }, (CrtU128) { rhs.bits, 0 }, fb, &r) != ARITH_OK) UNIMPLEMENTED( "Air.Inst.Data.bin_op: f16/f32/f64 arithmetic declined"); return (InterpValue) { r.lo, lhs.ty }; } // The comparisons and `@min`/`@max` read the exact widening into a // double, which answers `-0.0 == 0.0` and every NaN case as the // comptime side's `valCmpFloatOperand` does. double lf, rf; arithFloatToDouble(lhs.bits, fb, &lf); arithFloatToDouble(rhs.bits, fb, &rf); return interpFloatCmpMinMax(tag, lf, rf, lhs.ty, fb); } uint32_t lhs_float_bits; uint32_t rhs_float_bits; // Both sides are classified up front: the float arm below reads both // widths. const bool lhs_is_float = ztSimpleFloatBits(lhs.ty, &lhs_float_bits); const bool rhs_is_float = ztSimpleFloatBits(rhs.ty, &rhs_float_bits); if (lhs_is_float || rhs_is_float) { // Sema coerces both operands to one peer type: a float against any // other type is a zig1 frame defect, not a shape to convert. if (lhs.ty != rhs.ty) UNIMPLEMENTED("Air.Inst.Data.bin_op: mixed float binary op"); if (!ztSimpleFloatBits(lhs.ty, &lhs_float_bits)) UNREACHABLE("interp: non-float in float binary op"); InterpValue out = { 0, lhs.ty }; if (lhs_float_bits > 64u) { // An extended float is by REF (`isByRef`'s `floatBits > 64`), so // the bit-level questions below read its slot's pattern once. InterpPattern lp = interpScalarPattern(ip, lhs); InterpPattern rp = interpScalarPattern(ip, rhs); // CodeGen.floatAdd / floatSub / floatMul / floatDiv: an f80/f128 // add, sub, mul or div is its compiler_rt libcall (`".f128 => // return cg.callIntrinsic(.__addtf3, ...)"`), the same routine // Sema's comptime fold reaches through arith.c. ZirInstTag crt_tag; if (interpArithZirTag(tag, &crt_tag)) { const CrtU128 a = { lp.lo, lp.hi }, b = { rp.lo, rp.hi }; CrtU128 r; if (arithFloatBinOp(crt_tag, a, b, lhs_float_bits, &r) == ARITH_OK) return interpScalarFromPattern( ip, (InterpPattern) { r.lo, r.hi, lhs.ty }, false); } // CodeGen.floatCmp: `".f128 => { const intrinsic = switch (op) { // .lt => .__lttf2, ... .gte => .__getf2, .gt => .__gttf2 }; ... // return cg.intCmp(.i32, op, result, .{ .imm32 = 0 });"` -- the // `GE` flavour for `>=`/`>`, the `LE` one otherwise. ZirInstTag cmp_op; if (interpCmpZirTag(tag, &cmp_op)) { const CrtU128 a = { lp.lo, lp.hi }, b = { rp.lo, rp.hi }; bool ge = cmp_op == ZIR_INST_CMP_GTE || cmp_op == ZIR_INST_CMP_GT; out.ty = ZIR_REF_BOOL_TYPE; out.bits = valCmpApplyOrder( cmp_op, crtCmp(lhs_float_bits, ge, a, b)) ? 1u : 0u; return out; } // CodeGen.floatMin / floatMax: `__fminx` / `fminq` / `__fmaxx` / // `fmaxq`. if (tag == AIR_INST_MIN || tag == AIR_INST_MAX) { const CrtU128 r = crtFminFmax(lhs_float_bits, tag == AIR_INST_MAX, (CrtU128) { lp.lo, lp.hi }, (CrtU128) { rp.lo, rp.hi }); return interpScalarFromPattern( ip, (InterpPattern) { r.lo, r.hi, lhs.ty }, false); } UNIMPLEMENTED("Air.Inst.Data.bin_op: float binary op on a " "by-reference float"); } UNIMPLEMENTED("Air.Inst.Data.bin_op: unhandled float binary op"); } if (lhs.ty < ip->items_len && rhs.ty < ip->items_len && ip->item_tags[lhs.ty] == IP_TAG_TYPE_VECTOR && ip->item_tags[rhs.ty] == IP_TAG_TYPE_VECTOR) { // A vector binary operation is its scalar operation applied lane by // lane: upstream analyses `@Vector(N, T)` arithmetic by resolving the // per-element operation on `T` (Sema.analyzeArithmetic's vector arm // folds `"const lhs_elem = try lhs_val.elemValue(pt, i);"` through // the same scalar `arith` it uses for a scalar operand, and the AIR // tag is the same one), and every backend lowers it elementwise. // Recursing into this very function is therefore the operation, not // an approximation of it. // The two operand types need not be equal: a shift's amount is a // vector of the element's Log2Int (Sema.log2IntType's `.vector` arm), // so each side is read at its own element type and stride, exactly as // the scalar pair would be. Only the lane count has to agree. // A COMPARISON's scalar result is `bool`, not the element type; // upstream gives vector comparison its own AIR tag (`cmp_vector`, // src/Air.zig) whose result is `@Vector(N, bool)`, so it arrives at // the same lane loop with that result type instead of this one. return interpVectorLanes(ip, tag, lhs, rhs, lhs.ty); } ZirInstTag bin_zop; if (lhs.ty != rhs.ty && interpTypeIsIntegerStorage(ip, lhs.ty) && interpTypeIsIntegerStorage(ip, rhs.ty) && interpArithZirTag(tag, &bin_zop) && arithTagIsShift(bin_zop)) { // A shift's result is the LHS's type and its amount is never peer // widened: `u8 <<| @as(usize, 8)` is 255 at u8, not 256 at usize. interpTypeInfo(ip, lhs.ty, &width, &is_signed); return interpBinOpWide(ip, tag, lhs, rhs, width, is_signed); } if (lhs.ty != rhs.ty && interpTypeIsIntegerStorage(ip, lhs.ty) && interpTypeIsIntegerStorage(ip, rhs.ty)) { uint32_t lhs_width, rhs_width; bool lhs_signed, rhs_signed; interpTypeInfo(ip, lhs.ty, &lhs_width, &lhs_signed); interpTypeInfo(ip, rhs.ty, &rhs_width, &rhs_signed); if (lhs_signed != rhs_signed) UNIMPLEMENTED( "Air.Inst.Data.bin_op: mixed signedness integer binary op"); // Widening to the peer type is `Sema.intCast`'s value-preserving // cast, which is also where the carrier may change shape (a u64 // register becoming a u128 slot). if (rhs_width > lhs_width) lhs = interpIntCastScalar(ip, rhs.ty, lhs); else if (lhs_width > rhs_width) rhs = interpIntCastScalar(ip, lhs.ty, rhs); } interpTypeInfo(ip, lhs.ty, &width, &is_signed); if (width > 64) return interpBinOpWide(ip, tag, lhs, rhs, width, is_signed); return interpScalarIntBinOp(tag, lhs, rhs, width, is_signed); } // Resolve a `call` callee ref to the analyzed function it targets. The callee // is a static InternPool `func_decl`; each analyzed function carries its own // `func_decl` InternPool index, so resolution is by exact identity. Matching // by unqualified name would mis-resolve when two decls in different containers // share a short name (e.g. `A.v` and `B.v`, or the ubiquitous // `init`/`deinit`): it returns whichever appears first. static const SemaFuncAir* interpResolveCallee( const InternPool* ip, const SemaFuncAirList* all, uint32_t callee_ref) { if (callee_ref >= AIR_REF_INST_BASE) UNIMPLEMENTED("call: indirect (runtime) call"); if (callee_ref >= ip->items_len) UNREACHABLE("call: callee InternPool index out of bounds"); // A comptime-known function pointer (`*const fn`) is a `ptr_nav` naming // the function's Nav; resolve it to that Nav's `func_decl` (the Nav's // cached resolved value). This is the comptime-known-vtable indirect-call // case. if (ip->item_tags[callee_ref] == IP_TAG_PTR_NAV) { IpPtrNav ptr; const IpNav* nav_data = interpPtrNav(ip, callee_ref, &ptr); // A function pointer cast to another function type still names // its function (`@as(*const fn (*const anyopaque) T, // @ptrCast(&f))`). if (ip->item_tags[ptr.nav_type] != IP_TAG_TYPE_FUNCTION) UNIMPLEMENTED("call: ptr_nav callee does not point to fn"); if (ptr.packed_offset != 0 || ptr.byte_offset != 0) UNIMPLEMENTED("call: projected ptr_nav used as callee"); uint32_t fdecl = nav_data->resolved_value; if (fdecl == IP_INDEX_NONE || fdecl >= ip->items_len) UNIMPLEMENTED("call: fn-pointer nav has no resolved func_decl"); callee_ref = fdecl; } // A comptime pointer to a comptime field or an anonymous value (`&tp[1]` // of a tuple of functions) names that function value. if (ipIsPtrComptimeField(ip, callee_ref)) callee_ref = ipPtrComptimeFieldValue(ip, callee_ref); else if (ipIsPtrUav(ip, callee_ref)) callee_ref = ipPtrUavValue(ip, callee_ref); if (callee_ref >= ip->items_len || (ip->item_tags[callee_ref] != IP_TAG_FUNC_DECL && ip->item_tags[callee_ref] != IP_TAG_FUNC_INSTANCE)) UNIMPLEMENTED("InternPool.getNav: call to non-func_decl value"); const SemaFuncAir* found = semaFuncAirListFind(all, callee_ref); if (found) return found; UNIMPLEMENTED("InternPool.getNav: call callee not found in analyzed set"); ZIG1_TCC_RETURN(NULL); } // Call a wasi_snapshot_preview1 import on the WASI host (wasi.c), its typed // arguments lowered to the wasm-level ones. A false result means the extern // is not one zig1 models yet. static bool interpCallExtern(const InternPool* ip, uint32_t extern_fn, const InterpValue* args, InterpValue* out) { if (extern_fn >= ip->items_len || ip->item_tags[extern_fn] != IP_TAG_EXTERN) return false; uint32_t library = ipExternFunctionLibraryName(ip, extern_fn); if (library == IP_INDEX_NONE || strcmp(ipStringBytes(ip, library), "wasi_snapshot_preview1") != 0) return false; const IpNav* nav = ipGetNav(ip, ipExternFunctionOwnerNav(ip, extern_fn)); const WasiImport* import = wasiImportFind(ipStringBytes(ip, nav->name)); if (import == NULL) return false; uint64_t wasm_args[WASI_MAX_PARAMS]; for (uint32_t i = 0; i < import->params; i++) wasm_args[i] = args[i].bits; const WasiCall call = { &g_interp.wasi, wasm_args, interpArenaRangePtr }; uint32_t result = import->call != NULL ? import->call(&call) : 0u; // 0: SUCCESS if (!import->returns_errno) { // proc_exit: the program ends here, back in interpRunMain. g_interp.exit_status = (int)result; longjmp(g_interp.exit_jmp, 1); } *out = (InterpValue) { result, ipFuncTypeReturnType(ip, ipTypeOf(ip, extern_fn)) }; return true; } // How control leaves a body: off its end, or by a ret, br, repeat or // switch_dispatch (interpExecute's flow_target and flow_val). typedef enum { INTERP_FALLTHROUGH, INTERP_RETURN, INTERP_BREAK, INTERP_REPEAT, INTERP_SWITCH_DISPATCH } InterpFlow; // Sema.zig:intCast // `Sema.intCast`'s value-preserving cast at one scalar: the same integer // value, re-normalized to the destination's width and signedness (the value // is guaranteed to fit -- src/codegen/c.zig `airIntCast` is a plain C cast to // the dest int). static InterpValue interpIntCastScalar( InternPool* ip, uint32_t dst_ty, InterpValue op) { uint32_t width; bool is_signed; interpTypeInfo(ip, dst_ty, &width, &is_signed); if (!interpTypeIsByRefScalar(ip, dst_ty) && !interpTypeIsByRefScalar(ip, op.ty)) { InterpValue out = { interpNormalize(op.bits, width, is_signed), dst_ty }; return out; } // The value is read at the SOURCE's width and signedness (that is what // makes the cast value-preserving) and re-materialized at the // destination's, which is also where the carrier may change shape. uint32_t sw; bool ss; interpTypeInfo(ip, op.ty, &sw, &ss); uint64_t space[INTERP_MAX_INT_LIMBS]; BigIntConst c = interpValueToBigInt(ip, op, sw, ss, space, INTERP_MAX_INT_LIMBS); return interpValueFromBigInt(ip, c, dst_ty, width, is_signed, false); } // (part of) Value.zig:absScalar, arith.zig:floatNeg, Type.zig:floatBits // `@abs` and negation of a float are the same operation on the stored // pattern: the sign bit cleared (`Value.absScalar`'s `@abs(val.toFloat(fN))`) // or flipped (`arith.floatNeg`), with every other bit -- NaN payloads // included -- kept. One table of the sign positions, driven by the width // `ztSimpleFloatBits` (== `Type.floatBits`) already reported at each call // site, so the two instructions cannot disagree about a width or leave one // out. static InterpValue interpFloatSignBits( InternPool* ip, InterpValue v, uint32_t float_bits, bool clear) { InterpPattern p = interpScalarPattern(ip, v); switch (float_bits) { case 16: p.lo = clear ? ((uint16_t)p.lo & 0x7FFFu) : ((uint16_t)p.lo ^ 0x8000u); break; case 32: p.lo = clear ? ((uint32_t)p.lo & 0x7FFFFFFFu) : ((uint32_t)p.lo ^ 0x80000000u); break; case 64: p.lo = clear ? (p.lo & UINT64_C(0x7FFFFFFFFFFFFFFF)) : (p.lo ^ UINT64_C(0x8000000000000000)); break; case 80: // The pattern's high word holds bits 64..79, whose top bit (79) is // the sign. p.hi = clear ? (p.hi & UINT64_C(0x7FFF)) : (p.hi ^ UINT64_C(0x8000)); break; case 128: p.hi = clear ? (p.hi & UINT64_C(0x7FFFFFFFFFFFFFFF)) : (p.hi ^ UINT64_C(0x8000000000000000)); break; default: UNIMPLEMENTED("absScalar: float sign op at an unhandled width"); } return interpScalarFromPattern(ip, p, false); } // (part of) arith.zig:bitwiseNot // `arith.bitwiseNot`'s worker `intBitwiseNot` at one scalar, in the runtime // frame: complement, then keep the type's low bits with its own sign // extension. `bool` needs no arm of its own -- `interpTypeInfo` answers it one // unsigned bit and `~x` normalized to that is the flip, which is the same // collapse ctEvalBody's `bool_not` arm already made onto `bitwiseNot`. static InterpValue interpNotScalar( InternPool* ip, uint32_t ty, InterpValue op) { uint32_t width; bool is_signed; interpTypeInfo(ip, ty, &width, &is_signed); if (!interpTypeIsByRefScalar(ip, ty)) { InterpValue out = { interpNormalize(~op.bits, width, is_signed), ty }; return out; } // `bitNotWrap` complements the whole value and trims it to the type: // `~x == -x - 1` at any width, which is `addScalar(negate(x), -1)`. uint64_t space[INTERP_MAX_INT_LIMBS]; BigIntConst c = interpValueToBigInt( ip, op, width, is_signed, space, INTERP_MAX_INT_LIMBS); uint64_t nbuf[INTERP_MAX_INT_LIMBS + 1u]; BigIntMutable n = { nbuf, INTERP_MAX_INT_LIMBS + 1u, 0, true }; if (!bigIntMutableAddScalar(&n, bigIntConstNegate(c), -1)) UNIMPLEMENTED("bitwiseNot: integer wider than the limb buffer"); return interpValueFromBigInt( ip, bigIntMutableToConst(n), ty, width, is_signed, false); } // `intByteSwap` / `intBitReverse` at one runtime scalar: `Mutable.byteSwap` // / `bitReverse` at the type's width, through the one carrier pair. static InterpValue interpBitPermuteScalar( InternPool* ip, uint8_t tag, uint32_t ty, InterpValue op) { uint32_t width; bool is_signed; interpTypeInfo(ip, ty, &width, &is_signed); uint64_t space[INTERP_MAX_INT_LIMBS]; BigIntConst c = interpValueToBigInt( ip, op, width, is_signed, space, INTERP_MAX_INT_LIMBS); uint64_t rbuf[INTERP_MAX_INT_LIMBS]; BigIntMutable r = { rbuf, INTERP_MAX_INT_LIMBS, 0, true }; if (!(tag == AIR_INST_BYTE_SWAP ? bigIntMutableByteSwap(&r, c, is_signed, width / 8u) : bigIntMutableBitReverse(&r, c, is_signed, width))) UNIMPLEMENTED("bit_reverse: integer wider than the limb buffer"); return interpValueFromBigInt( ip, bigIntMutableToConst(r), ty, width, is_signed, false); } // One lane of the runtime unary float ops: `arithFloatUnary`, the rule the // comptime fold (`maybeConstantUnaryMath`) asks too, at the wasm program's // evaluation (`runtime`). static InterpValue interpFloatUnaryScalar( InternPool* ip, uint8_t tag, InterpValue op) { uint32_t float_bits; if (!ztSimpleFloatBits(op.ty, &float_bits)) UNREACHABLE("interp: unary float op on non-float"); if (op.ty == ZIR_REF_F128_TYPE && tag == AIR_INST_LOG2) { InterpPattern lp = interpScalarPattern(ip, op); if (lp.lo == 0 && lp.hi == 0) { InterpPattern rp = { 0, UINT64_C(0xffff000000000000), // -inf binary128 op.ty }; return interpScalarFromPattern(ip, rp, false); } } ZirInstTag zt; if (!interpArithZirTag(tag, &zt)) UNIMPLEMENTED("Air.Inst.Data.un_op: unary float op"); // f80/f128 are by-reference scalars: read and write their patterns. const InterpPattern p = interpScalarPattern(ip, op); CrtU128 r; if (arithFloatUnary(zt, (CrtU128) { p.lo, p.hi }, float_bits, true, &r) != ARITH_OK) UNIMPLEMENTED( "Air.Inst.Data.un_op: unary float op on a by-reference float"); return interpScalarFromPattern( ip, (InterpPattern) { r.lo, r.hi, op.ty }, false); } // `arith.negate`'s scalar arm (AIR `neg` on one integer or float lane). static InterpValue interpNegScalar(InternPool* ip, InterpValue op) { uint32_t float_bits; if (ztSimpleFloatBits(op.ty, &float_bits)) { // `arith.floatNeg`: the sign bit flipped at the type's own // width, which is why upstream routes float negation here and // not through `0 - x` (a negative zero, an infinity and a NaN // payload all survive it). return interpFloatSignBits(ip, op, float_bits, false); } uint32_t width; bool is_signed; interpTypeInfo(ip, op.ty, &width, &is_signed); if (!interpTypeIsByRefScalar(ip, op.ty)) { InterpValue out = { interpNormalize((uint64_t)0 - op.bits, width, is_signed), op.ty }; return out; } // `arith.sub`'s `0 - x`: the same magnitude with the sign // flipped, truncated to the type. uint64_t nspace[INTERP_MAX_INT_LIMBS]; BigIntConst nc = interpValueToBigInt( ip, op, width, is_signed, nspace, INTERP_MAX_INT_LIMBS); return interpValueFromBigInt( ip, bigIntConstNegate(nc), op.ty, width, is_signed, false); } // Value.zig:absScalar // `Value.absScalar` (AIR `abs` on one lane; `rty` is the lane result type). static InterpValue interpAbsScalar( InternPool* ip, uint32_t rty, InterpValue op) { uint32_t float_bits; if (ztSimpleFloatBits(op.ty, &float_bits)) { // `Value.absScalar`'s float arm `@abs(val.toFloat(fN))` clears the // sign bit at the operand's own type (`Sema.zirAbs` rewrites only // integers). return interpFloatSignBits(ip, op, float_bits, true); } uint32_t owidth; bool osigned; interpTypeInfo(ip, op.ty, &owidth, &osigned); uint32_t rwidth; bool rsigned; interpTypeInfo(ip, rty, &rwidth, &rsigned); // `Const.abs`: the same limbs wearing a positive sign. The // magnitude of `minInt` needs the result type's extra bit, which // `@abs`'s unsigned peer supplies. uint64_t aspace[INTERP_MAX_INT_LIMBS]; BigIntConst ac = interpValueToBigInt( ip, op, owidth, osigned, aspace, INTERP_MAX_INT_LIMBS); return interpValueFromBigInt( ip, bigIntConstAbs(ac), rty, rwidth, rsigned, false); } // `@clz` / `@ctz` / `@popCount` of one integer at `res_ty`: the operand's // own `owidth`-bit two's-complement pattern, which a by-ref operand keeps in // its slot's bytes. A zero operand runs off the end of the width and // answers `owidth`. static InterpValue interpBitCountScalar( InternPool* ip, uint8_t tag, uint32_t res_ty, InterpValue op) { uint32_t owidth; bool osigned; interpTypeInfo(ip, op.ty, &owidth, &osigned); uint32_t cnt = 0; if (tag == AIR_INST_POPCOUNT) { for (uint32_t i = 0; i < owidth; i++) cnt += interpValueBit(ip, op, i) ? 1u : 0u; } else if (tag == AIR_INST_CTZ) { while (cnt < owidth && !interpValueBit(ip, op, cnt)) cnt++; } else { // AIR_INST_CLZ while (cnt < owidth && !interpValueBit(ip, op, owidth - 1u - cnt)) cnt++; } InterpValue out = { cnt, res_ty }; return out; } // One `float_from_int` lane: the integer converted by compiler_rt's // `floatFromInt` (`__floattitf` and kin), rounded once. static InterpValue interpFloatFromIntScalar( InternPool* ip, uint32_t dst_ty, InterpValue op) { uint32_t dst_bits; if (!ztSimpleFloatBits(dst_ty, &dst_bits)) UNREACHABLE("interp: float_from_int non-float dest"); if (!interpTypeIsIntegerStorage(ip, op.ty)) UNREACHABLE("interp: float_from_int non-int operand"); uint32_t op_width; bool op_signed; interpTypeInfo(ip, op.ty, &op_width, &op_signed); // compiler_rt's `floatFromInt` (`__floattitf`, `__floateisf` and kin): // the big integer rounded once to nearest, ties to even, at any width. uint64_t ospace[INTERP_MAX_INT_LIMBS]; BigIntConst oc = interpValueToBigInt( ip, op, op_width, op_signed, ospace, INTERP_MAX_INT_LIMBS); uint64_t lo, hi; bool exact; if (!bigIntToFloatBits(oc, dst_bits, &lo, &hi, &exact)) UNIMPLEMENTED("float_from_int: destination width"); return interpScalarFromPattern( ip, (InterpPattern) { lo, hi, dst_ty }, false); } // The extended float widths, and f128's all-ones biased exponent (an // infinity or a NaN). #define INTERP_F80_BITS 80u #define INTERP_F128_BITS 128u #define INTERP_F128_EXP_NONFINITE 0x7FFFu // One `int_from_float` lane: compiler_rt's `intFromFloat` (`__fixtfti`, // `__fixxfti`, `__fixdfei` and kin), the float truncated toward zero. Every // float format widens exactly to f128 first, so `Mutable.setFloat(.trunc)` // does the conversion at any destination width. A NaN, an infinity or a value // outside the destination is illegal behavior with no safety check in // ReleaseSmall; it answers the truncated pattern (0 for a non-finite). static InterpValue interpIntFromFloatScalar( InternPool* ip, uint32_t dst_ty, InterpValue op) { uint32_t dst_width; bool dst_signed; interpTypeInfo(ip, dst_ty, &dst_width, &dst_signed); if (!interpTypeIsIntegerStorage(ip, dst_ty)) UNREACHABLE("interp: int_from_float non-int dest"); uint32_t src_bits; if (!ztSimpleFloatBits(op.ty, &src_bits)) UNREACHABLE("interp: int_from_float non-float operand"); InterpPattern fop = interpScalarPattern(ip, op); const CrtU128 v = { fop.lo, fop.hi }; uint32_t words[4]; ipFloat128ToWords(src_bits == INTERP_F128_BITS ? v : src_bits == INTERP_F80_BITS ? crtExtendxftf2(v) : crtExtendf(INTERP_F128_BITS, src_bits, v), words); uint64_t space[INTERP_MAX_INT_LIMBS] = { 0 }; BigIntMutable r = { space, INTERP_MAX_INT_LIMBS, 1, true }; bool exact; if (((words[3] >> 16) & INTERP_F128_EXP_NONFINITE) != INTERP_F128_EXP_NONFINITE && !bigIntMutableSetFloat128Trunc(&r, words, &exact)) UNIMPLEMENTED("int_from_float: limb buffer too short"); return interpValueFromBigInt( ip, bigIntMutableToConst(r), dst_ty, dst_width, dst_signed, false); } // One `fptrunc`/`fpext` lane. Exact zero is representation-all-zero for // every modeled float width. static InterpValue interpFloatCastScalar( InternPool* ip, uint32_t dst_ty, InterpValue op) { uint32_t dst_bits; if (!ztSimpleFloatBits(dst_ty, &dst_bits)) UNREACHABLE("interp: float cast non-float dest"); uint32_t src_bits; if (!ztSimpleFloatBits(op.ty, &src_bits)) UNREACHABLE("interp: float cast non-float operand"); InterpPattern sp = interpScalarPattern(ip, op); InterpPattern dp = { 0, 0, dst_ty }; if (sp.lo == 0 && sp.hi == 0) return interpScalarFromPattern(ip, dp, false); if (src_bits == 32 && dst_bits == 64) { // `f64.promote_f32`, a native wasm instruction. uint32_t raw32 = (uint32_t)sp.lo; float f; memcpy(&f, &raw32, sizeof f); double d = (double)f; memcpy(&dp.lo, &d, sizeof d); } else if (src_bits == 64 && dst_bits == 32) { // `f32.demote_f64`, likewise. double d; memcpy(&d, &sp.lo, sizeof d); float f = (float)d; uint32_t raw32; memcpy(&raw32, &f, sizeof raw32); dp.lo = raw32; } else if (dst_bits == 80 && src_bits <= 64) { // `".f80 => switch (src_ty) { .f16 => ... __extendhfxf2, // .f32 => ... __extendsfxf2, .f64 => ... __extenddfxf2"`. const CrtU128 r = crtExtendF80(src_bits, (CrtU128) { sp.lo, sp.hi }); dp.lo = r.lo; dp.hi = r.hi; } else if (src_bits == 80 && dst_bits <= 64) { // `".f16 => switch (src_ty) { ... .f80 => __truncxfhf2"`, and // `__truncxfsf2` / `__truncxfdf2` for f32 / f64. const CrtU128 r = crtTruncF80(dst_bits, (CrtU128) { sp.lo, sp.hi }); dp.lo = r.lo; dp.hi = r.hi; } else if ((src_bits == 80 && dst_bits == 128) || (src_bits == 128 && dst_bits == 80)) { // `".f128 => switch (src_ty) { ... .f80 => // __extendxftf2"` and `".f80 => switch (src_ty) { ... // .f128 => __trunctfxf2"`. const CrtU128 v = { sp.lo, sp.hi }; const CrtU128 r = dst_bits == 128 ? crtExtendxftf2(v) : crtTrunctfxf2(v); dp.lo = r.lo; dp.hi = r.hi; } else if (src_bits != 80 && dst_bits != 80) { // CodeGen.airFpext / airFptrunc's libcalls (`__extendhftf2`, // `__extenddftf2`, `__trunctfdf2`, `__truncsfhf2`, ...): // compiler_rt's extendf / truncf. const CrtU128 v = { sp.lo, sp.hi }; CrtU128 r = dst_bits > src_bits ? crtExtendf(dst_bits, src_bits, v) : dst_bits < src_bits ? crtTruncf(dst_bits, src_bits, v) : v; dp.lo = r.lo; dp.hi = r.hi; } else { UNIMPLEMENTED("fpext: nonzero extended float cast"); } return interpScalarFromPattern(ip, dp, false); } // One lane (or the scalar form) of a unary `ty_op`, at the result type. static InterpValue interpUnOpLane( InternPool* ip, uint8_t tag, uint32_t res_ty, InterpValue op) { switch (tag) { case AIR_INST_NOT: return interpNotScalar(ip, res_ty, op); case AIR_INST_NEG: case AIR_INST_NEG_OPTIMIZED: return interpNegScalar(ip, op); case AIR_INST_ABS: return interpAbsScalar(ip, res_ty, op); case AIR_INST_INT_CAST: case AIR_INST_TRUNC: return interpIntCastScalar(ip, res_ty, op); case AIR_INST_FLOAT_FROM_INT: return interpFloatFromIntScalar(ip, res_ty, op); case AIR_INST_PTR_FROM_INT: return interpPtrFromIntScalar(res_ty, op); case AIR_INST_INT_FROM_PTR: return (InterpValue) { interpAddressFromBits(op.bits), res_ty }; case AIR_INST_INT_FROM_FLOAT: case AIR_INST_INT_FROM_FLOAT_OPTIMIZED: case AIR_INST_INT_FROM_FLOAT_SAFE: case AIR_INST_INT_FROM_FLOAT_OPTIMIZED_SAFE: return interpIntFromFloatScalar(ip, res_ty, op); case AIR_INST_FPTRUNC: case AIR_INST_FPEXT: return interpFloatCastScalar(ip, res_ty, op); case AIR_INST_BYTE_SWAP: case AIR_INST_BIT_REVERSE: return interpBitPermuteScalar(ip, tag, res_ty, op); case AIR_INST_CLZ: case AIR_INST_CTZ: case AIR_INST_POPCOUNT: return interpBitCountScalar(ip, tag, res_ty, op); case AIR_INST_SQRT: case AIR_INST_SIN: case AIR_INST_COS: case AIR_INST_TAN: case AIR_INST_EXP: case AIR_INST_EXP2: case AIR_INST_LOG: case AIR_INST_LOG2: case AIR_INST_LOG10: case AIR_INST_FLOOR: case AIR_INST_CEIL: case AIR_INST_TRUNC_FLOAT: case AIR_INST_ROUND: return interpFloatUnaryScalar(ip, tag, op); default: UNIMPLEMENTED("int_cast: unary ty_op lane"); } ZIG1_TCC_RETURN((InterpValue) { 0 }); } // (part of) Legalize.zig:scalarizeBlockPayload // `Air.Legalize.scalarizeBlockPayload`'s unary form, for a `ty_op` whose // destination is a vector: the same instruction per lane, at the result // vector's element type -- `"const operand = loop.block.addBinOp(l, // .legalize_vec_elem_val, orig_operand, index_val).toRef(); break :elem // loop.block.addTyOp(l, orig.tag, res_elem_ty, operand).toRef();"` -- with // each lane stored back into a result vector. `orig.tag` is why one loop // serves `.scalarize_int_cast` and `.scalarize_not` alike. The operand's lanes // are read at the operand vector's own element type and stride, since a cast // is exactly where the two differ. static InterpValue interpScalarizeUnOp( InternPool* ip, uint8_t tag, uint32_t res_ty, InterpValue op) { InterpArrayInfo res = interpArrayInfo(ip, res_ty); InterpArrayInfo src = interpArrayInfo(ip, op.ty); if (res.len != src.len) UNREACHABLE("interp: unary vector operand lengths differ"); uint32_t res_stride = interpAbiSize(ip, res.child); uint32_t src_stride = interpAbiSize(ip, src.child); uint32_t out_addr = interpArenaAlloc( interpAbiSize(ip, res_ty), interpAbiAlign(ip, res_ty)); uint32_t src_addr = interpAddressFromBits(op.bits); for (uint32_t i = 0; i < res.len; i++) { InterpValue lane = interpLoadTyped(ip, interpAddressAdd(src_addr, interpLayoutMul(i, src_stride)), src.child, NULL); InterpValue lane_out = interpUnOpLane(ip, tag, res.child, lane); interpStoreTyped(ip, interpAddressAdd(out_addr, interpLayoutMul(i, res_stride)), res.child, lane_out); } InterpValue out = { out_addr, res_ty }; return out; } // (part of) Legalize.zig:scalarizeBlockPayload, Sema.zig:log2IntType // `Air.Legalize.scalarizeBlockPayload`'s binary form, the lane loop every // vector `bin_op` takes. `res_ty` is the result vector type: the operand type // for arithmetic, and `@Vector(N, bool)` for a comparison -- which is // upstream's own reason for giving `cmp_vector` its own AIR tag. Each side is // read at its own element type and stride (a shift's amount is a vector of the // element's Log2Int, Sema.log2IntType's `.vector` arm), the scalar operation // runs, and the lane lands at the RESULT's stride. Only the lane count has to // agree. static InterpValue interpVectorLanes(InternPool* ip, uint8_t tag, InterpValue lhs, InterpValue rhs, uint32_t res_ty) { InterpArrayInfo vec = interpArrayInfo(ip, lhs.ty); InterpArrayInfo rvec = interpArrayInfo(ip, rhs.ty); InterpArrayInfo res = interpArrayInfo(ip, res_ty); uint32_t child_size = interpAbiSize(ip, vec.child); uint32_t rchild_size = interpAbiSize(ip, rvec.child); uint32_t res_size = interpAbiSize(ip, res.child); if (child_size == 0 || rchild_size == 0 || res_size == 0) UNIMPLEMENTED("scalarizeBlockPayload: zero-size vector lane"); if (vec.len != rvec.len || vec.len != res.len) UNREACHABLE("interp: vector binary op operand lengths differ"); uint32_t out_addr = interpArenaAlloc( interpAbiSize(ip, res_ty), interpAbiAlign(ip, res_ty)); uint32_t lhs_addr = interpAddressFromBits(lhs.bits); uint32_t rhs_addr = interpAddressFromBits(rhs.bits); for (uint32_t i = 0; i < vec.len; i++) { InterpValue a = interpLoadTyped(ip, interpAddressAdd(lhs_addr, interpLayoutMul(i, child_size)), vec.child, NULL); InterpValue b = interpLoadTyped(ip, interpAddressAdd(rhs_addr, interpLayoutMul(i, rchild_size)), rvec.child, NULL); InterpValue r = interpBinOp(ip, tag, a, b); if (r.ty != res.child) UNIMPLEMENTED("scalarizeBlockPayload: vector lane result type is " "not the element type"); interpStoreTyped(ip, interpAddressAdd(out_addr, interpLayoutMul(i, res_size)), res.child, r); } InterpValue out = { out_addr, res_ty }; return out; } // (part of) arith.zig:addWithOverflow // `arith.addWithOverflow` / `subWithOverflow` / `mulWithOverflow`'s scalar arm // (`WithOverflowScalar` -> `intWithOverflow`) in the runtime frame: // the wrapped result at the operand type plus the u1 bit saying the exact // result did not fit. This is the same `arithIntWithOverflow` the comptime // folder asks (arith.c `arithWithOverflowScalar`), so one rule answers // both frames, signed and unsigned. static void interpWithOverflowScalar(InternPool* ip, uint8_t tag, InterpValue lhs, InterpValue rhs, InterpValue* out_wrapped, uint32_t* out_bit) { ZirInstTag ztag; if (!interpArithZirTag(tag, &ztag)) UNIMPLEMENTED("addWithOverflow: overflow arithmetic op"); uint32_t ovf_w; bool ovf_signed; interpTypeInfo(ip, lhs.ty, &ovf_w, &ovf_signed); if (ovf_w <= 64) { ArithIntType aty = arithIntTypeSized(ovf_w, ovf_signed); uint64_t l_space[1], r_space[1]; BigIntConst la = arithIntFromWord( l_space, lhs.bits, ovf_signed && (int64_t)lhs.bits < 0); BigIntConst ra = arithIntFromWord( r_space, rhs.bits, ovf_signed && (int64_t)rhs.bits < 0); uint64_t ovf_buf[8]; BigIntMutable ovf_res = { ovf_buf, 8u, 0, true }; if (arithIntBinOpLimbs(ztag, la, ra, aty) > 8u) UNIMPLEMENTED( "addWithOverflow: overflow arithmetic needs a wider result"); bool overflow = false; if (arithIntWithOverflow(ztag, la, ra, aty, &ovf_res, &overflow) != ARITH_OK) UNIMPLEMENTED("addWithOverflow: overflow arithmetic op"); uint64_t word = 0; if (!arithIntToWord(bigIntMutableToConst(ovf_res), aty, &word)) UNIMPLEMENTED("addWithOverflow: overflow arithmetic result wider " "than 64 bits"); out_wrapped->bits = word; out_wrapped->ty = lhs.ty; *out_bit = overflow ? 1u : 0u; return; } // Wider than the one-limb carrier: the same `arithIntWithOverflow` the // arm above asks, over the by-ref operands read as `big.int` // (`Mutable.readTwosComplement`): `intAddWithOverflowInner` / // `intSubWithOverflowInner` / `intMulWithOverflowInner` compute the exact // result and ask `fitsInTwosComp` at the type, signed or not, at any // width. uint64_t wl_space[INTERP_MAX_INT_LIMBS], wr_space[INTERP_MAX_INT_LIMBS]; BigIntConst wla = interpValueToBigInt( ip, lhs, ovf_w, ovf_signed, wl_space, INTERP_MAX_INT_LIMBS); BigIntConst wra = interpValueToBigInt( ip, rhs, ovf_w, ovf_signed, wr_space, INTERP_MAX_INT_LIMBS); ArithIntType waty = arithIntTypeSized(ovf_w, ovf_signed); if (arithIntBinOpLimbs(ztag, wla, wra, waty) > INTERP_MAX_INT_RES_LIMBS) UNIMPLEMENTED( "addWithOverflow: overflow arithmetic needs a wider result"); uint64_t wbuf[INTERP_MAX_INT_RES_LIMBS]; BigIntMutable wres = { wbuf, INTERP_MAX_INT_RES_LIMBS, 0, true }; bool wide_ovf = false; if (arithIntWithOverflow(ztag, wla, wra, waty, &wres, &wide_ovf) != ARITH_OK) UNIMPLEMENTED("addWithOverflow: overflow arithmetic op"); *out_wrapped = interpValueFromBigInt( ip, bigIntMutableToConst(wres), lhs.ty, ovf_w, ovf_signed, false); *out_bit = wide_ovf ? 1u : 0u; } // ── `@bitCast` over a sequence whose elements are not whole bytes ────────── // // `@bitCast` reinterprets a value's bit representation, and for an array or a // vector that representation is packed: Value.writeToPackedMemory's `.array, // .vector` arm writes each element at `elem_bit_off` and steps by `"const // elem_bits: usize = @intCast(ty.childType(zcu).bitSize(zcu));"`, with // readFromPackedMemory reading it back the same way. The ABI layout is a // different thing entirely -- `vec.len * elem.abiSize`, a whole byte per lane // for every sub-byte element, 16 bytes for an f80 -- so a byte copy would // reinterpret `@Vector(4, u1)` as a `u4` by taking lane 0's whole byte. The // BIT_CAST arm takes the packed route only for the types where the two // representations differ, through a buffer of the operand's bitSize. typedef struct { uint32_t len; uint32_t elem_bits; // the lane's bit width (its packed stride) uint32_t elem_size; // the lane's ABI byte stride uint32_t child; } InterpBitSeq; // Type.zig:bitSize // Type.bitSize for a bool / integer / float leaf and for a sequence of them, // nested sequences included (`".array, .vector => // ty.arrayLenIncludingSentinel(zcu) // * ty.childType(zcu).bitSize(zcu)"`). False for any other leaf. static bool interpSeqLeafBits(InternPool* ip, uint32_t ty, uint32_t* out_bits); // The packed layout of a sequence type whose leaves have one: its lanes' // count, bit stride, byte stride and type. False for a leaf or for a // sequence with no packed layout. static bool interpSeqLayout(InternPool* ip, uint32_t ty, InterpBitSeq* out) { if (ty >= ip->items_len) return false; uint8_t tag = ip->item_tags[ty]; if (tag != IP_TAG_TYPE_VECTOR && tag != IP_TAG_TYPE_ARRAY_SMALL && tag != IP_TAG_TYPE_ARRAY_BIG) return false; InterpArrayInfo seq = interpArrayInfo(ip, ty); if (!interpSeqLeafBits(ip, seq.child, &out->elem_bits)) return false; out->len = seq.len; out->elem_size = interpAbiSize(ip, seq.child); out->child = seq.child; return true; } static bool interpSeqLeafBits( InternPool* ip, uint32_t ty, uint32_t* out_bits) { if (ty >= ip->items_len) return false; bool is_signed; if (ty == ZIR_REF_BOOL_TYPE || interpTypeIsIntegerStorage(ip, ty)) { interpTypeInfo(ip, ty, out_bits, &is_signed); return true; } if (ztSimpleFloatBits(ty, out_bits)) return true; InterpBitSeq seq; if (!interpSeqLayout(ip, ty, &seq)) return false; *out_bits = interpLayoutMul(seq.len, seq.elem_bits); return true; } // A sequence whose lanes' bits do not fill their ABI bytes. static bool interpSeqIsBitPacked( InternPool* ip, uint32_t ty, InterpBitSeq* out) { return interpSeqLayout(ip, ty, out) && out->elem_bits != interpLayoutMul(out->elem_size, 8u); } // Copy `n` bits from bit `src_off` of `src` (`src_bits` long) to bit // `dst_off` of `dst` (`dst_bits` long), in packedMemRead/packedMemWrite's // 128-bit carrier steps, so a leaf of any width is copied. static void interpBitCopy(uint8_t* dst, uint64_t dst_bits, uint32_t dst_off, const uint8_t* src, uint64_t src_bits, uint32_t src_off, uint32_t n) { for (uint32_t k = 0; k < n; k += PACKED_MEM_CARRIER_BITS) { uint32_t chunk = n - k < PACKED_MEM_CARRIER_BITS ? n - k : PACKED_MEM_CARRIER_BITS; uint64_t lo, hi; if (!packedMemRead(src, src_bits, src_off + k, chunk, &lo, &hi) || !packedMemWrite(dst, dst_bits, dst_off + k, chunk, lo, hi)) UNIMPLEMENTED("bitcast: packed bit copy out of bounds"); } } // Value.zig:writeToPackedMemory // The value at `addr` of type `ty` written to `buf` (`buf_bits` long) at bit // `bit_off`: a leaf contributes its `bitSize` low bits, a sequence its // elements in order, element i at `i * elem_bits`. static void interpWriteToPackedMemory(InternPool* ip, uint32_t addr, uint32_t ty, uint8_t* buf, uint64_t buf_bits, uint32_t bit_off) { InterpBitSeq seq; if (interpSeqLayout(ip, ty, &seq)) { for (uint32_t i = 0; i < seq.len; i++) interpWriteToPackedMemory(ip, interpAddressAdd(addr, interpLayoutMul(i, seq.elem_size)), seq.child, buf, buf_bits, bit_off + interpLayoutMul(i, seq.elem_bits)); return; } uint32_t bits; if (!interpSeqLeafBits(ip, ty, &bits)) UNIMPLEMENTED("bitcast: of a type with no packed layout"); uint32_t size = interpAbiSize(ip, ty); interpBitCopy(buf, buf_bits, bit_off, interpArenaRangePtr(addr, size), interpLayoutMul(size, 8u), 0, bits); } // Value.zig:readFromPackedMemory // The inverse into the ABI slot at `addr`. readFromPackedMemory hands each // leaf back as a value canonical for its type, so an integer or bool slot is // reloaded (which re-canonicalizes the bits above its width) and stored back: // one value, one byte image, whichever route wrote it. static void interpReadFromPackedMemory(InternPool* ip, const uint8_t* buf, uint64_t buf_bits, uint32_t bit_off, uint32_t ty, uint32_t addr) { InterpBitSeq seq; if (interpSeqLayout(ip, ty, &seq)) { for (uint32_t i = 0; i < seq.len; i++) interpReadFromPackedMemory(ip, buf, buf_bits, bit_off + interpLayoutMul(i, seq.elem_bits), seq.child, interpAddressAdd(addr, interpLayoutMul(i, seq.elem_size))); return; } uint32_t bits; if (!interpSeqLeafBits(ip, ty, &bits)) UNIMPLEMENTED("bitcast: to a type with no packed layout"); uint32_t size = interpAbiSize(ip, ty); uint8_t* slot = interpArenaRangePtr(addr, size); memset(slot, 0, size); interpBitCopy( slot, interpLayoutMul(size, 8u), 0, buf, buf_bits, bit_off, bits); uint32_t float_bits; if (!ztSimpleFloatBits(ty, &float_bits)) interpStoreTyped(ip, addr, ty, interpLoadTyped(ip, addr, ty, NULL)); } // An activation's value-stack slots: its arguments, then one value per AIR // instruction (at least one). ZIG1_INLINE uint32_t interpFrameSlotsLen(const Air* air, uint32_t nargs) { return nargs + (air->inst_len ? air->inst_len : 1u); } // Push an activation's value-stack slots: `nargs` arguments, for the caller // to fill, then its instruction values, zeroed. static InterpValue* interpValuesPush(const Air* air, uint32_t nargs) { uint32_t n = interpFrameSlotsLen(air, nargs); uint32_t len = g_interp.value_chunks.len; bool fits = len != 0 && g_interp.value_chunks.arr[len - 1u].cap - g_interp.value_chunks.arr[len - 1u].len >= n; InterpValue* p; if (fits) { InterpValueChunk* top = &g_interp.value_chunks.arr[len - 1u]; p = top->base + top->len; top->len += n; } else { if (len == g_interp.value_chunks.cap) { uint32_t old = g_interp.value_chunks.cap; SLICE_RESIZE(InterpValueChunk, &g_interp.value_chunks, zig1GrowCap(old, 8u)); memset(g_interp.value_chunks.arr + old, 0, (size_t)(g_interp.value_chunks.cap - old) * sizeof(InterpValueChunk)); } InterpValueChunk* c = &g_interp.value_chunks.arr[g_interp.value_chunks.len++]; if (c->cap < n) { uint32_t cap = n > INTERP_VALUE_CHUNK_MIN ? n : INTERP_VALUE_CHUNK_MIN; free(c->base); c->base = zig1Malloc((size_t)cap * sizeof *c->base); c->cap = cap; } c->len = n; p = c->base; } memset(p + nargs, 0, (size_t)(n - nargs) * sizeof *p); return p; } static void interpValuesPop(uint32_t n) { InterpValueChunk* c = &g_interp.value_chunks.arr[g_interp.value_chunks.len - 1u]; c->len -= n; if (c->len == 0) g_interp.value_chunks.len--; } // Push a body onto the dispatch loop's body stack, at its first instruction. ZIG1_INLINE void interpBodyPush( InterpBodyKind kind, uint32_t inst, const uint32_t* body, uint32_t len) { SLICE_APPEND(InterpBody, &g_interp.bodies, ((InterpBody) { body, len, 0, inst, kind })); } // Start an activation of `fn` on `slots` (interpValuesPush, its `nargs` // arguments filled): its frame watermark and its main body. `call_inst` is the // caller's call instruction, which receives the result (UINT32_MAX for a // run's root); `result_ty` overrides the return type the function declares, // and `call_ty` is the type the caller sees the result at (IP_INDEX_NONE for // the return type). static void interpFramePush(const SemaFuncAir* fn, const SemaFuncAirList* all, InternPool* ip, InterpValue* slots, uint32_t nargs, uint32_t call_inst, uint32_t result_ty, uint32_t call_ty) { const Air* air = &fn->air; SLICE_ENSURE_CAPACITY(InterpFrame, &g_interp.frames, 1); InterpFrame* fr = &g_interp.frames.arr[g_interp.frames.len++]; fr->fn = fn; fr->bodies_base = g_interp.bodies.len; fr->call_inst = call_inst; fr->result_ty = result_ty != IP_INDEX_NONE ? result_ty : ipFuncTypeReturnType(ip, ipTypeOf(ip, fn->func_decl)); fr->call_ty = call_ty; // Frame-scoped reclamation watermark. Every `alloc`/`ret_ptr`/aggregate // temporary this activation makes lands at an arena offset >= frame_mark; // on return they are reclaimed (bump pointer restored to frame_mark), // faithful to Zig stack slots being invalid after return. Writes through // pointer parameters target the caller's memory (offset < frame_mark) and // so survive; mutable globals and materialized comptime consts live in // persistent disjoint arenas (address >= INTERP_GLOBAL_BASE) and are // never touched here. InterpCtx* ctx = &fr->ctx; ctx->ip = ip; ctx->air = air; ctx->all = all; ctx->args = slots; ctx->nargs = nargs; ctx->args_used = 0; ctx->values = slots + nargs; ctx->frame_mark = g_interp.arena_len; // Main body: extra[0] = the main Block payload index; extra[body_index] = // body_len; the following words are the body's inst indices. uint32_t body_index = air->extra[0]; interpBodyPush(INTERP_BODY_FN, UINT32_MAX, &air->extra[body_index + 1], air->extra[body_index]); } // End the innermost activation, whose main body left with `fl` (`result` the // operand of its ret or br, which Sema coerced to the return type): its frame // memory and values released, and, for a guest call, the result stored in the // caller's call instruction. static ZIG1_NOINLINE InterpValue interpFramePop( InterpFlow fl, InterpValue result) { InterpFrame* fr = &g_interp.frames.arr[g_interp.frames.len - 1u]; InterpCtx* ctx = &fr->ctx; InternPool* ip = ctx->ip; uint32_t result_ty = fr->result_ty; if (fl == INTERP_FALLTHROUGH && result_ty == ZIR_REF_VOID_TYPE) { result = (InterpValue) { 0, ZIR_REF_VOID_TYPE }; } else if (fl != INTERP_RETURN && fl != INTERP_BREAK) { fprintf(stderr, "interp: fallthrough fn=%s result_ty=%u flow=%d body_len=%u\n", fr->fn->name ? fr->fn->name : "", result_ty, (int)fl, g_interp.bodies.arr[fr->bodies_base].len); UNREACHABLE("interp: function body completed without returning"); } // Caller-owned storage of the result: the caller's call instruction. InterpValue* return_storage = fr->call_inst == UINT32_MAX ? NULL : &g_interp.frames.arr[g_interp.frames.len - 2u] .ctx.values[fr->call_inst]; interpValuesPop(interpFrameSlotsLen(ctx->air, ctx->nargs)); // Reclaim this frame's runtime allocations. Every return that lives in // memory -- an aggregate, or a scalar `isByRef` puts there -- is a // by-value snapshot regardless of whether its source currently points // into this frame, a caller frame, mutable global storage, or const // storage. Copy through interpArenaRangePtr before restoring the // watermark, then place the bytes in caller-owned storage. A guest call // reuses the destination call instruction's prior slot so a loop does not // bump- allocate one return object per iteration. A shallow abiSize copy // is faithful: pointer fields preserve their pointer values. if (interpTypeIsAggregate(ip, result.ty) || interpTypeIsByRefScalar(ip, result.ty)) { uint32_t sz = interpAbiSize(ip, result.ty); uint8_t* tmp = NULL; if (sz != 0) { tmp = zig1Malloc(sz); memcpy(tmp, interpArenaRangePtr(interpAddressFromBits(result.bits), sz), sz); } g_interp.arena_len = ctx->frame_mark; uint32_t na = interpSnapshotSlot(ip, result.ty, sz, return_storage); if (sz != 0) { memcpy(interpArenaRangePtr(na, sz), tmp, sz); free(tmp); } result.bits = na; } else { g_interp.arena_len = ctx->frame_mark; } if (return_storage) { *return_storage = result; if (fr->call_ty != IP_INDEX_NONE) return_storage->ty = fr->call_ty; } g_interp.bodies.len = fr->bodies_base; g_interp.frames.len--; return result; } // The prong of the switch_br or loop_switch_br payload at `payload` that // `cond` selects. Payload (mirrors sema.c's finishSwitchBr flush): // [0] cases_len, [1] else_body_len, // then num_bags = (cases_len + 1 + 9)/10 branch-hint words, // then per case: { items_len, ranges_len, body_len, // items[items_len], ranges[2*ranges_len], body[body_len] }, // then the else body. // A prong matches when the condition equals one of its item refs or lies // within one of its [first,last] ranges; if no prong matches, the else body // runs. static void interpSwitchProng(const InterpCtx* ctx, uint32_t payload, InterpValue cond, const uint32_t** out_body, uint32_t* out_len) { const Air* air = ctx->air; uint32_t cases_len = air->extra[payload]; uint32_t else_body_len = air->extra[payload + 1]; uint32_t num_bags = (cases_len + 1u + 9u) / 10u; uint32_t cursor = payload + 2u + num_bags; uint32_t cond_width; bool cond_signed; interpTypeInfo(ctx->ip, cond.ty, &cond_width, &cond_signed); // A by-ref condition's bits are an address: items compare values and // ranges order them (`Value.order`), never bits. bool cond_by_ref = interpTypeIsByRefScalar(ctx->ip, cond.ty); for (uint32_t ci = 0; ci < cases_len; ci++) { uint32_t items_len = air->extra[cursor++]; uint32_t ranges_len = air->extra[cursor++]; uint32_t case_body_len = air->extra[cursor++]; const uint32_t* items = &air->extra[cursor]; cursor += items_len; const uint32_t* ranges = &air->extra[cursor]; cursor += 2u * ranges_len; const uint32_t* case_body = &air->extra[cursor]; cursor += case_body_len; bool matched = false; for (uint32_t it = 0; it < items_len && !matched; it++) { InterpValue iv = interpResolveRef(ctx, items[it]); if (cond_by_ref ? interpValuesEqual(ctx->ip, iv, cond) : iv.bits == cond.bits) matched = true; } for (uint32_t r = 0; r < ranges_len && !matched; r++) { InterpValue lo = interpResolveRef(ctx, ranges[2u * r]); InterpValue hi = interpResolveRef(ctx, ranges[2u * r + 1u]); if (interpIntOrder(ctx->ip, cond, lo, cond_width, cond_signed) >= 0 && interpIntOrder(ctx->ip, cond, hi, cond_width, cond_signed) <= 0) matched = true; } if (matched) { *out_body = case_body; *out_len = case_body_len; return; } } *out_body = &air->extra[cursor]; // else body follows *out_len = else_body_len; } // Take control out of the innermost body with `fl` (to the instruction // `target`, with `val`), as far as the body that catches it: a br to a block // ends the block, a repeat restarts its loop, a switch_dispatch reselects its // loop_switch_br prong, and anything that reaches an activation's main body // ends the activation. Running off a body's end continues after the // instruction that owns it. True when the run's root activation ends, with its // result in `*result`. static bool interpFlowOut( InterpFlow fl, uint32_t target, InterpValue val, InterpValue* result) { for (;;) { InterpBody* top = &g_interp.bodies.arr[g_interp.bodies.len - 1u]; InterpCtx* ctx = &g_interp.frames.arr[g_interp.frames.len - 1u].ctx; switch (top->kind) { case INTERP_BODY_FN: *result = interpFramePop(fl, val); return g_interp.frames.len == 0; case INTERP_BODY_BLOCK: if (fl == INTERP_BREAK && target == top->inst) { // A `br` targeting this block delivers its result value. ctx->values[top->inst] = val; g_interp.bodies.len--; return false; } break; case INTERP_BODY_LOOP: // A `loop` re-runs its body until a non-`repeat` control transfer // (a `br` out, or a `ret`) escapes it; its noreturn body does not // fall through. if (fl == INTERP_REPEAT && target == top->inst) { top->k = 0; return false; } break; case INTERP_BODY_LOOP_SWITCH: if (fl == INTERP_SWITCH_DISPATCH && target == top->inst) { interpSwitchProng(ctx, ctx->air->inst_datas[top->inst].pair.b, val, &top->body, &top->len); top->k = 0; return false; } break; case INTERP_BODY_BRANCH: break; } g_interp.bodies.len--; if (fl == INTERP_FALLTHROUGH) return false; } } // The arguments of the call whose Call payload is at `payload`, resolved into // `out`, each already of its parameter type (analyzeCall coerced it). Through // a function pointer cast to another function type (`@ptrCast(&f)`, // `recast_call`) the callee reads its own parameter types: a pointer argument // is the same address in either, and only that reinterpretation is modeled. static void interpCallArgs(const InterpCtx* ctx, uint32_t payload, uint32_t callee_func_ty, bool recast_call, InterpValue* out) { uint32_t args_len = ctx->air->extra[payload]; for (uint32_t i = 0; i < args_len; i++) { InterpValue arg = interpResolveRef(ctx, ctx->air->extra[payload + 1u + i]); if (recast_call && i < ipFuncTypeParamsLen(ctx->ip, callee_func_ty)) { uint32_t param_ty = ipFuncTypeParamType(ctx->ip, callee_func_ty, i); if (arg.ty != param_ty) { if (!ipItemTagIs(ctx->ip, arg.ty, IP_TAG_TYPE_POINTER) || !ipItemTagIs(ctx->ip, param_ty, IP_TAG_TYPE_POINTER)) UNIMPLEMENTED("call: call through a recast function type " "changing a non-pointer parameter"); arg.ty = param_ty; } } out[i] = arg; } } // Run `fn` with `args` as a run's root activation, and every activation it // calls, to its return value: one loop over the innermost body's // instructions, with calls and nested bodies pushed on the heap stacks // (Interp.frames and Interp.bodies), never on the C stack. ctx, air, body, // body_len and k cache the innermost activation and body; fl, flow_target and // flow_val are the control transfer in flight. static InterpValue interpExecute(const SemaFuncAir* fn, const SemaFuncAirList* all, InternPool* ip, const InterpValue* args, uint32_t nargs) { InterpCtx* ctx; const Air* air; const uint32_t* body; uint32_t body_len; uint32_t k; uint32_t inst; uint8_t tag; uint32_t a; uint32_t b; InterpFlow fl; uint32_t flow_target = 0; InterpValue flow_val = { 0, 0 }; InterpValue root_result; #define INTERP_LOAD_FRAME() \ do { \ ctx = &g_interp.frames.arr[g_interp.frames.len - 1u].ctx; \ air = ctx->air; \ const InterpBody* top_ \ = &g_interp.bodies.arr[g_interp.bodies.len - 1u]; \ body = top_->body; \ body_len = top_->len; \ k = top_->k; \ } while (0) // Enter a nested body of the current instruction. #define INTERP_ENTER(kind, owner, sub, sub_len) \ do { \ g_interp.bodies.arr[g_interp.bodies.len - 1u].k = k; \ interpBodyPush((kind), (owner), (sub), (sub_len)); \ body = (sub); \ body_len = (sub_len); \ k = 0; \ } while (0) InterpValue* slots = interpValuesPush(&fn->air, nargs); if (nargs != 0) memcpy(slots, args, (size_t)nargs * sizeof *slots); interpFramePush( fn, all, ip, slots, nargs, UINT32_MAX, IP_INDEX_NONE, IP_INDEX_NONE); INTERP_LOAD_FRAME(); for (;;) { if (k == body_len) { fl = INTERP_FALLTHROUGH; goto flow_out; } inst = body[k++]; tag = air->inst_tags[inst]; a = air->inst_datas[inst].pair.a; b = air->inst_datas[inst].pair.b; switch (tag) { case AIR_INST_DBG_EMPTY_STMT: case AIR_INST_PREFETCH: // CodeGen.airPrefetch: a hint wasm drops break; case AIR_INST_ARG: // Bind the next positional argument to this instruction's value. // `pair.b` (Air.Arg.zir_param_index) is the parameter's position // in the original declared signature — debug metadata, not the ABI // slot: a generic instance drops its comptime parameters from the // runtime signature, so a runtime param's zir_param_index (e.g. 1 // for `x` in `fn g(comptime T: type, x: T)`) exceeds its call-site // argument position (0). `arg` instructions are emitted in // signature order, so sequential binding matches the call's // runtime-argument order exactly (upstream binds args by their // emission order too; src_index is for names/debug only). // // Zero-runtime-bit arguments are not part of the argument // protocol, mirroring the C backend. `codegen/c.zig`'s `airCall` // lowers no such argument — "if (!arg_ty.hasRuntimeBits(zcu)) { // resolved_arg.* = .none; continue; }", and the emission loop then // "if (resolved_arg == .none) continue;" — so its `airArg` ("const // i = f.next_arg_index; f.next_arg_index += 1;") walks only the // lowered parameters, in signature order. DIVERGENCE of placement, // not of rule: interp has no emission step — the argument array is // the ABI — so the identical, type-keyed filter is applied at the // consuming end of that same array. `pair.a` is this parameter's // declared type (`data.pair.a = param_types[i]` in // zcu_per_thread.c's parameter loop, a static IP index), the same // key upstream uses. // // zcu_per_thread.c's parameter loop mirrors // `Zcu.PerThread.analyzeFnBody`'s "if (try // param_ty.onePossibleValue(pt)) |opv| {", binding a // one-possible-value parameter directly and emitting no `arg`, // while `Sema.analyzeCall` still leaves the argument in the AIR // call. That is exactly the case this loop exists for: without // it, an OPV parameter followed by an arg-emitting parameter // would shift every later binding by one. if (interpTypeHasRuntimeBits(ctx->ip, a)) while (ctx->args_used < ctx->nargs && !interpTypeHasRuntimeBits( ctx->ip, ctx->args[ctx->args_used].ty)) ctx->args_used++; if (ctx->args_used >= ctx->nargs) UNIMPLEMENTED("arg: more arg insts than call arguments"); { InterpValue arg = ctx->args[ctx->args_used++]; if (interpTypeIsAggregate(ctx->ip, arg.ty)) // A by-value aggregate argument is callee-owned SSA // state, regardless of whether the caller's source lives // in its frame, mutable global memory, or const memory. // Snapshot into this ARG instruction's reusable slot so // a callee mutation cannot change the argument and loops // cannot accumulate copies. ctx->values[inst] = interpLoadTyped(ctx->ip, interpAddressFromBits(arg.bits), arg.ty, &ctx->values[inst]); else ctx->values[inst] = arg; } break; case AIR_INST_ADD: case AIR_INST_ADD_WRAP: case AIR_INST_ADD_SAT: case AIR_INST_SUB: case AIR_INST_SUB_WRAP: case AIR_INST_SUB_SAT: case AIR_INST_MUL: case AIR_INST_MUL_WRAP: case AIR_INST_MUL_SAT: // `Sema.zirDiv` emits `.div_float` for a float `/`; the float binop // below has always answered it, but this dispatch list did not name // it, so it reached the "unhandled AIR instruction" abort. case AIR_INST_DIV_FLOAT: case AIR_INST_DIV_FLOAT_OPTIMIZED: case AIR_INST_DIV_TRUNC: case AIR_INST_DIV_EXACT: case AIR_INST_DIV_EXACT_OPTIMIZED: case AIR_INST_DIV_FLOOR: case AIR_INST_DIV_CEIL: case AIR_INST_REM: case AIR_INST_MOD: case AIR_INST_BIT_AND: case AIR_INST_BIT_OR: case AIR_INST_XOR: case AIR_INST_SHL: case AIR_INST_SHL_EXACT: case AIR_INST_SHL_SAT: case AIR_INST_SHR: case AIR_INST_SHR_EXACT: case AIR_INST_MIN: case AIR_INST_MAX: case AIR_INST_CMP_LT: case AIR_INST_CMP_LTE: case AIR_INST_CMP_EQ: case AIR_INST_CMP_GTE: case AIR_INST_CMP_GT: case AIR_INST_CMP_NEQ: { // bin_op: pair.a = lhs, pair.b = rhs. InterpValue l = interpResolveRef(ctx, a); InterpValue r = interpResolveRef(ctx, b); ctx->values[inst] = interpBinOp(ctx->ip, tag, l, r); break; } case AIR_INST_NOT: { // ty_op: pair.a = result type (== operand type), pair.b = operand. // A vector result is the elementwise form of this very // instruction, so it is the same scalar rule per lane -- the same // shape `int_cast` takes below. uint32_t nty = a; InterpValue op = interpResolveRef(ctx, b); ctx->values[inst] = ipItemTagIs(ctx->ip, nty, IP_TAG_TYPE_VECTOR) ? interpScalarizeUnOp(ctx->ip, tag, nty, op) : interpNotScalar(ctx->ip, nty, op); break; } case AIR_INST_BYTE_SWAP: case AIR_INST_BIT_REVERSE: { // ty_op: pair.a = result type (== operand type), pair.b = operand; // a vector is the same scalar rule per lane. uint32_t bty = a; InterpValue op = interpResolveRef(ctx, b); ctx->values[inst] = ipItemTagIs(ctx->ip, bty, IP_TAG_TYPE_VECTOR) ? interpScalarizeUnOp(ctx->ip, tag, bty, op) : interpBitPermuteScalar(ctx->ip, tag, bty, op); break; } case AIR_INST_SQRT: case AIR_INST_SIN: case AIR_INST_COS: case AIR_INST_TAN: case AIR_INST_EXP: case AIR_INST_EXP2: case AIR_INST_LOG: case AIR_INST_LOG2: case AIR_INST_LOG10: case AIR_INST_FLOOR: case AIR_INST_CEIL: case AIR_INST_TRUNC_FLOAT: case AIR_INST_ROUND: { // un_op: pair.a = float operand, a scalar or a vector // (`Air.Legalize`'s scalarization, one lane at a time). InterpValue op = interpResolveRef(ctx, a); ctx->values[inst] = ipItemTagIs(ctx->ip, op.ty, IP_TAG_TYPE_VECTOR) ? interpScalarizeUnOp(ctx->ip, tag, op.ty, op) : interpFloatUnaryScalar(ctx->ip, tag, op); break; } case AIR_INST_NEG: case AIR_INST_NEG_OPTIMIZED: { // un_op: pair.a = operand. Numeric negation; a vector one lane // at a time (`Air.Legalize`'s scalarization). InterpValue op = interpResolveRef(ctx, a); ctx->values[inst] = ipItemTagIs(ctx->ip, op.ty, IP_TAG_TYPE_VECTOR) ? interpScalarizeUnOp(ctx->ip, tag, op.ty, op) : interpNegScalar(ctx->ip, op); break; } case AIR_INST_ABS: { // ty_op: pair.a = result type (the operand's unsigned same-width // peer — Sema.zirAbs maps e.g. isize -> usize), pair.b = operand; // a vector one lane at a time. uint32_t rty = a; InterpValue op = interpResolveRef(ctx, b); ctx->values[inst] = ipItemTagIs(ctx->ip, rty, IP_TAG_TYPE_VECTOR) ? interpScalarizeUnOp(ctx->ip, tag, rty, op) : interpAbsScalar(ctx->ip, rty, op); break; } case AIR_INST_CALL: case AIR_INST_CALL_NEVER_TAIL: case AIR_INST_CALL_NEVER_INLINE: { // pl_op: pair.a = callee ref, pair.b = Call payload extra index. // extra[payload] = args_len; extra[payload+1 ..] = arg refs. uint32_t payload = b; uint32_t args_len = air->extra[payload]; // The callee operand `a` is either a comptime function value/ // pointer (an IP index: func_decl / func_instance / ptr_nav) or a // runtime function pointer (an AIR ref whose InterpValue.bits is // the ptr_nav handle produced by interpDecodeConst). Resolve a // runtime operand to its handle first (the indirect-call case). A // function coerced to another function type runs its original // body at its original type (InternPool.unwrapCoercedFunc). uint32_t callee_handle = a >= AIR_REF_INST_BASE ? a : ipUnwrapCoercedFunc(ctx->ip, a); uint32_t callee_func_ty = IP_INDEX_NONE; uint32_t through_ty = IP_INDEX_NONE; if (a >= AIR_REF_INST_BASE) { InterpValue cv = interpResolveRef(ctx, a); uint32_t child; if (cv.ty < ctx->ip->items_len && ctx->ip->item_tags[cv.ty] == IP_TAG_TYPE_POINTER) { child = interpPtrChild(ctx->ip, cv.ty); } else { child = cv.ty; } if (child >= ctx->ip->items_len || ctx->ip->item_tags[child] != IP_TAG_TYPE_FUNCTION) UNREACHABLE( "interp: indirect callee has non-function type"); callee_handle = interpAddressFromBits(cv.bits); if (callee_handle >= ctx->ip->items_len) UNREACHABLE( "interp: indirect callee handle out of bounds"); callee_func_ty = ipTypeOf(ctx->ip, callee_handle); through_ty = child; } if (callee_func_ty == IP_INDEX_NONE) callee_func_ty = ipTypeOf(ctx->ip, callee_handle); // The function type the call is made through, and, for a // comptime-known function pointer, the function it names. if (through_ty == IP_INDEX_NONE) through_ty = callee_func_ty; if (ctx->ip->item_tags[callee_handle] == IP_TAG_PTR_NAV) { IpPtrNav callee_pn; interpPtrNav(ctx->ip, callee_handle, &callee_pn); through_ty = callee_pn.child_type; callee_func_ty = callee_pn.nav_type; } // A call through a function pointer cast to another function // type (interpCallArgs). bool recast_call = through_ty != callee_func_ty; uint32_t through_ret = ipFuncTypeReturnType(ctx->ip, through_ty); uint32_t callee_ret = ipFuncTypeReturnType(ctx->ip, callee_func_ty); // Error unions over one payload share a layout whatever their // error sets (an `error{}!void` function called as // `fn () anyerror!void`): the result is retyped below. bool recast_eu_ret = through_ret != callee_ret && interpTypeIsErrUnion(ctx->ip, through_ret) && interpTypeIsErrUnion(ctx->ip, callee_ret) && ipErrorUnionPayload(ctx->ip, through_ret) == ipErrorUnionPayload(ctx->ip, callee_ret); if (through_ret != callee_ret && !recast_eu_ret && !(ipItemTagIs(ctx->ip, through_ret, IP_TAG_TYPE_POINTER) && ipItemTagIs(ctx->ip, callee_ret, IP_TAG_TYPE_POINTER))) UNIMPLEMENTED("call: call through a recast function type " "changing a non-pointer return type"); if (ipItemTagIs(ctx->ip, callee_handle, IP_TAG_EXTERN)) { InterpValue extern_args[WASI_MAX_PARAMS]; if (args_len <= WASI_MAX_PARAMS) { interpCallArgs(ctx, payload, callee_func_ty, recast_call, extern_args); if (interpCallExtern(ctx->ip, callee_handle, extern_args, &ctx->values[inst])) break; } uint32_t owner_nav = ipExternFunctionOwnerNav(ctx->ip, callee_handle); uint32_t library = ipExternFunctionLibraryName(ctx->ip, callee_handle); const IpNav* nav = ipGetNav(ctx->ip, owner_nav); fprintf(stderr, "interp: unsupported extern %s.%s\n", library == IP_INDEX_NONE ? "(none)" : ipStringBytes(ctx->ip, library), ipStringBytes(ctx->ip, nav->name)); UNIMPLEMENTED("call: unsupported extern call"); } const SemaFuncAir* callee = interpResolveCallee(ctx->ip, ctx->all, callee_handle); // The arguments go straight into the callee's value slots. InterpValue* callee_slots = interpValuesPush(&callee->air, args_len); interpCallArgs( ctx, payload, callee_func_ty, recast_call, callee_slots); g_interp.bodies.arr[g_interp.bodies.len - 1u].k = k; interpFramePush(callee, ctx->all, ctx->ip, callee_slots, args_len, inst, callee_ret, recast_eu_ret ? through_ret : IP_INDEX_NONE); INTERP_LOAD_FRAME(); break; } case AIR_INST_BLOCK: { // ty_pl: pair.a = result type, pair.b = payload extra index; // extra[payload] = body_len, following words = body inst indices. uint32_t payload = b; uint32_t blk_len = air->extra[payload]; const uint32_t* blk_body = &air->extra[payload + 1]; INTERP_ENTER(INTERP_BODY_BLOCK, inst, blk_body, blk_len); break; } case AIR_INST_LOOP: { // ty_pl: pair.a = result type (noreturn), pair.b = payload extra // index; extra[payload] = body_len, following words = body inst // indices. A `repeat` naming this loop restarts the body; one // naming an outer loop, or a break/return, propagates outward. uint32_t payload = b; uint32_t loop_len = air->extra[payload]; const uint32_t* loop_body = &air->extra[payload + 1]; INTERP_ENTER(INTERP_BODY_LOOP, inst, loop_body, loop_len); break; } case AIR_INST_REPEAT: { // repeat: pair.a = loop AIR inst index to branch back to. flow_target = a; fl = INTERP_REPEAT; goto flow_out; } case AIR_INST_COND_BR: { // pl_op: pair.a = condition ref, pair.b = CondBr payload extra // index; extra[payload] = then_len, [payload+1] = else_len, // [payload+2] = branch hints, then the then-body then the // else-body. uint32_t payload = b; uint32_t then_len = air->extra[payload]; uint32_t else_len = air->extra[payload + 1]; const uint32_t* then_body = &air->extra[payload + 3]; const uint32_t* else_body = &air->extra[payload + 3 + then_len]; InterpValue cond = interpResolveRef(ctx, a); if (cond.bits & 1u) INTERP_ENTER(INTERP_BODY_BRANCH, inst, then_body, then_len); else INTERP_ENTER(INTERP_BODY_BRANCH, inst, else_body, else_len); break; } case AIR_INST_SWITCH_BR: case AIR_INST_LOOP_SWITCH_BR: { // pl_op: pair.a = condition ref, pair.b = SwitchBr payload extra // index (interpSwitchProng). switch_br is a noreturn terminator: // each prong body itself transfers control (br to a merge block, // or ret), which propagates outward; a loop_switch_br prong's // switch_dispatch reselects the prong (interpFlowOut). const uint32_t* prong; uint32_t prong_len; interpSwitchProng( ctx, b, interpResolveRef(ctx, a), &prong, &prong_len); INTERP_ENTER(tag == AIR_INST_LOOP_SWITCH_BR ? INTERP_BODY_LOOP_SWITCH : INTERP_BODY_BRANCH, inst, prong, prong_len); break; } case AIR_INST_SWITCH_DISPATCH: { // br layout: pair.a = loop_switch_br AIR inst index, pair.b = // replacement condition ref. flow_target = a; flow_val = interpResolveRef(ctx, b); fl = INTERP_SWITCH_DISPATCH; goto flow_out; } case AIR_INST_BR: { // br: pair.a = target block AIR inst index, pair.b = operand ref. flow_target = a; flow_val = interpResolveRef(ctx, b); fl = INTERP_BREAK; goto flow_out; } case AIR_INST_RET: case AIR_INST_RET_SAFE: // un_op: pair.a = returned value ref. flow_val = interpResolveRef(ctx, a); fl = INTERP_RETURN; goto flow_out; case AIR_INST_ALLOC: case AIR_INST_RET_PTR: { // ty_op-ish: pair.a = result pointer type; its pointee is the // element type. Reserve aligned arena bytes and yield the address // as a pointer InterpValue. `ret_ptr` is the result-location // pointer of a by-ref-returning function (src/Air.zig: "If the // function will pass the result by-ref, this instruction returns // the result pointer. Otherwise it is equivalent to `alloc`.") — // the interpreter's InterpValue model always materializes the // result from the slot at `ret_load`, so a plain arena slot is // exactly right. An AIR alloc is a stack slot for this activation; // if a loop re-executes the same alloc instruction, reuse the slot // instead of bumping frame memory until function return. uint32_t ptr_ty = a; uint32_t elem_ty = interpPtrChild(ctx->ip, ptr_ty); uint32_t size = interpAbiSize(ctx->ip, elem_ty); uint32_t align = interpPtrAlignment(ctx->ip, ptr_ty); uint32_t addr; InterpValue prev = ctx->values[inst]; if (prev.ty == ptr_ty && prev.bits <= UINT32_MAX && (uint32_t)prev.bits >= ctx->frame_mark && (uint32_t)prev.bits < INTERP_GLOBAL_BASE && interpArenaRangeValid((uint32_t)prev.bits, size)) { addr = (uint32_t)prev.bits; if (size != 0) memset(interpArenaRangePtr(addr, size), 0, size); } else { addr = interpArenaAlloc(size, align); } InterpValue out = { addr, ptr_ty }; ctx->values[inst] = out; break; } case AIR_INST_RET_LOAD: { // un_op: pair.a = result pointer ref (usually the `ret_ptr` // above). Equivalent to a `load` of the pointee followed by `ret` // on the loaded value (src/Air.zig ret_load). InterpValue rp = interpResolveRef(ctx, a); uint32_t pointee = interpPtrChild(ctx->ip, rp.ty); flow_val = interpLoadTyped( ctx->ip, interpAddressFromBits(rp.bits), pointee, NULL); fl = INTERP_RETURN; goto flow_out; } case AIR_INST_STORE: case AIR_INST_ATOMIC_STORE_UNORDERED: case AIR_INST_ATOMIC_STORE_MONOTONIC: case AIR_INST_ATOMIC_STORE_RELEASE: case AIR_INST_ATOMIC_STORE_SEQ_CST: case AIR_INST_STORE_SAFE: { // bin_op: pair.a = ptr ref, pair.b = value ref. Write the value's // bytes at the pointer's address (scalar encode or aggregate // copy), sized by the value's static type. InterpValue ptr = interpResolveRef(ctx, a); InterpValue val = interpResolveRef(ctx, b); uint32_t pointee = interpPtrChild(ctx->ip, ptr.ty); if (!interpStoreTypeMatches(ctx->ip, pointee, val.ty)) { if (val.ty < ctx->ip->items_len && ctx->ip->item_tags[val.ty] == IP_TAG_TYPE_UNION && pointee == ipUnionEnumTagType(ctx->ip, val.ty)) { ZtUnionLayout ul = interpUnionLayout(ctx->ip, val.ty); uint64_t raw = interpUnionTagLoad( &ul, interpAddressFromBits(val.bits)); uint32_t width; bool is_signed; interpTypeInfo(ctx->ip, pointee, &width, &is_signed); val.bits = interpNormalize(raw, width, is_signed); val.ty = pointee; } else { UNIMPLEMENTED( "store_safe: store pointer/value type mismatch"); } } uint32_t ptr_addr = interpAddressFromBits(ptr.bits); uint32_t host_size, bit_off; if (interpPtrBitHost(ctx->ip, ptr.ty, &host_size, &bit_off)) { interpPackedStore( ctx->ip, ptr_addr, host_size, bit_off, pointee, val); break; } if (interpAddressIsConstArena(ptr_addr)) // A runtime store must never target the persistent const // arena: that would be a write through a `*const` (illegal // Zig) and would corrupt a shared cached materialization. // Abort loudly rather than silently poison the const cache. UNIMPLEMENTED("store_safe: runtime store into const arena"); ptr_addr = interpVectorElemAddr(ctx->ip, ptr.ty, ptr_addr); interpStoreTyped(ctx->ip, ptr_addr, pointee, val); break; } case AIR_INST_LOAD: { // ty_op: pair.a = result (element) type, pair.b = ptr ref. Decode // the bytes at the pointer's address per the element type. uint32_t elem_ty = a; InterpValue ptr = interpResolveRef(ctx, b); uint32_t pointee = interpPtrChild(ctx->ip, ptr.ty); if (pointee != elem_ty) UNREACHABLE("interp: load pointer/result type mismatch"); uint32_t host_size, bit_off; if (interpPtrBitHost(ctx->ip, ptr.ty, &host_size, &bit_off)) { ctx->values[inst] = interpPackedLoad(ctx->ip, interpAddressFromBits(ptr.bits), host_size, bit_off, elem_ty); break; } uint32_t load_addr = interpVectorElemAddr( ctx->ip, ptr.ty, interpAddressFromBits(ptr.bits)); ctx->values[inst] = interpLoadTyped( ctx->ip, load_addr, elem_ty, &ctx->values[inst]); break; } case AIR_INST_RUNTIME_NAV_PTR: { InterpValue out = { interpRuntimeNavAddress(ctx->ip, b, a), a }; ctx->values[inst] = out; break; } case AIR_INST_ATOMIC_LOAD: { // atomic_load: pair.a = ptr ref, pair.b = std.lang.AtomicOrder. // The zig1 interpreter is single-threaded, so ordering does not // affect the loaded value; execute it as a regular typed load. InterpValue ptr = interpResolveRef(ctx, a); uint32_t elem_ty = interpPtrChild(ctx->ip, ptr.ty); uint32_t host_size, bit_off; if (interpPtrBitHost(ctx->ip, ptr.ty, &host_size, &bit_off)) { ctx->values[inst] = interpPackedLoad(ctx->ip, interpAddressFromBits(ptr.bits), host_size, bit_off, elem_ty); break; } ctx->values[inst] = interpLoadTyped(ctx->ip, interpAddressFromBits(ptr.bits), elem_ty, &ctx->values[inst]); break; } case AIR_INST_CMPXCHG_WEAK: case AIR_INST_CMPXCHG_STRONG: { // cmpxchg ty_pl: a = result `?T`, b = Air.Cmpxchg payload // { ptr, expected_value, new_value, flags }. Weak is allowed to // succeed whenever strong would in this single-threaded // interpreter. uint32_t opt_ty = a; uint32_t payload = b; uint32_t ptr_ref = air->extra[payload]; uint32_t expected_ref = air->extra[payload + 1]; uint32_t new_ref = air->extra[payload + 2]; // The orderings (extra + 3) have no single-threaded effect. InterpValue ptr = interpResolveRef(ctx, ptr_ref); InterpValue expected = interpResolveRef(ctx, expected_ref); InterpValue new_val = interpResolveRef(ctx, new_ref); uint32_t pointee = interpPtrChild(ctx->ip, ptr.ty); if (ctx->ip->item_tags[opt_ty] != IP_TAG_TYPE_OPTIONAL || ctx->ip->item_datas[opt_ty] != pointee) UNREACHABLE("interp: cmpxchg result type mismatch"); if (!interpStoreTypeMatches(ctx->ip, pointee, expected.ty) || !interpStoreTypeMatches(ctx->ip, pointee, new_val.ty)) UNREACHABLE("interp: cmpxchg pointer/value type mismatch"); uint32_t cx_host_size, cx_bit_off; if (interpPtrBitHost(ctx->ip, ptr.ty, &cx_host_size, &cx_bit_off)) UNIMPLEMENTED("cmpxchg_strong: packed cmpxchg"); uint32_t ptr_addr = interpAddressFromBits(ptr.bits); if (interpAddressIsConstArena(ptr_addr)) UNIMPLEMENTED( "cmpxchg_strong: runtime cmpxchg into const arena"); // The result slot has type `?T`, so it cannot also reuse a // by-value aggregate snapshot of old `T` across loop iterations. // Borrow aggregate old values from the pointee bytes instead: // cmpxchg is single-threaded here, and on a miss those same bytes // are copied directly into the optional result payload. InterpValue old = interpTypeIsAggregate(ctx->ip, pointee) ? (InterpValue) { ptr_addr, pointee } : interpLoadTyped( ctx->ip, ptr_addr, pointee, &ctx->values[inst]); bool matched; if (old.ty == expected.ty) { matched = interpValuesEqual(ctx->ip, old, expected); } else { if (interpTypeIsAggregate(ctx->ip, old.ty) || interpTypeIsAggregate(ctx->ip, expected.ty)) UNREACHABLE( "interp: cmpxchg aggregate expected type mismatch"); uint32_t cx_width; bool cx_signed; interpTypeInfo(ctx->ip, pointee, &cx_width, &cx_signed); matched = interpTypeIsByRefScalar(ctx->ip, pointee) ? interpIntOrder( ctx->ip, old, expected, cx_width, cx_signed) == 0 : old.bits == expected.bits; } if (matched) interpStoreTyped(ctx->ip, ptr_addr, pointee, new_val); InterpOptionalRepr repr = interpOptionalRepr(ctx->ip, opt_ty); if (repr != INTERP_OPT_REPR_PAYLOAD_NICHE && repr != INTERP_OPT_REPR_PAYLOAD_FLAG) UNIMPLEMENTED( "cmpxchg: optional result without representation"); if (repr == INTERP_OPT_REPR_PAYLOAD_NICHE && !interpTypeIsAggregate(ctx->ip, opt_ty)) { ctx->values[inst] = matched ? (InterpValue) { 0, opt_ty } : (InterpValue) { old.bits, opt_ty }; break; } uint32_t opt_size = interpAbiSize(ctx->ip, opt_ty); uint32_t out_addr; const InterpValue* reuse = &ctx->values[inst]; if (reuse->ty == opt_ty && reuse->bits <= UINT32_MAX && (uint32_t)reuse->bits < INTERP_GLOBAL_BASE && (uint32_t)reuse->bits <= g_interp.arena_len && opt_size <= g_interp.arena_len - (uint32_t)reuse->bits) { out_addr = (uint32_t)reuse->bits; } else { out_addr = interpArenaAlloc( opt_size, interpAbiAlign(ctx->ip, opt_ty)); } if (opt_size != 0) memset(interpArenaRangePtr(out_addr, opt_size), 0, opt_size); if (!matched) { interpStoreTyped(ctx->ip, out_addr, pointee, old); if (repr == INTERP_OPT_REPR_PAYLOAD_FLAG) interpWriteLE(interpAddressAdd(out_addr, interpOptFlagOffset(ctx->ip, opt_ty)), 1u, 1u); } ctx->values[inst] = (InterpValue) { out_addr, opt_ty }; break; } case AIR_INST_ATOMIC_RMW: { // atomic_rmw: pl_op.operand = ptr ref, payload = // Air.AtomicRmw { operand, flags }. Single-threaded interpreter: // load old value, compute the new value, store it, and yield old. uint32_t payload = b; uint32_t operand_ref = air->extra[payload]; uint32_t flags = air->extra[payload + 1]; uint32_t op = (flags >> AIR_ATOMIC_FLAGS_HIGH_SHIFT) & AIR_ATOMIC_RMW_OP_MASK; // The ordering has no single-threaded effect. InterpValue ptr = interpResolveRef(ctx, a); InterpValue operand = interpResolveRef(ctx, operand_ref); uint32_t pointee = interpPtrChild(ctx->ip, ptr.ty); if (!interpStoreTypeMatches(ctx->ip, pointee, operand.ty)) UNREACHABLE("interp: atomic_rmw pointer/value type mismatch"); uint32_t rmw_host_size, rmw_bit_off; if (interpPtrBitHost( ctx->ip, ptr.ty, &rmw_host_size, &rmw_bit_off)) UNIMPLEMENTED("atomic_rmw: packed atomic_rmw"); uint32_t ptr_addr = interpAddressFromBits(ptr.bits); if (interpAddressIsConstArena(ptr_addr)) UNIMPLEMENTED( "atomic_rmw: runtime atomic_rmw into const arena"); InterpValue old = interpLoadTyped( ctx->ip, ptr_addr, pointee, &ctx->values[inst]); InterpValue new_val = operand; if (op != ATOMIC_RMW_OP_XCHG) { if (!interpTypeIsIntegerStorage(ctx->ip, pointee)) UNIMPLEMENTED("atomic_rmw: non-integer op"); switch (op) { case ATOMIC_RMW_OP_ADD: new_val = interpBinOp( ctx->ip, AIR_INST_ADD_WRAP, old, operand); break; case ATOMIC_RMW_OP_SUB: new_val = interpBinOp( ctx->ip, AIR_INST_SUB_WRAP, old, operand); break; case ATOMIC_RMW_OP_AND: new_val = interpBinOp(ctx->ip, AIR_INST_BIT_AND, old, operand); break; case ATOMIC_RMW_OP_NAND: { InterpValue and_val = interpBinOp(ctx->ip, AIR_INST_BIT_AND, old, operand); uint32_t width; bool is_signed; interpTypeInfo(ctx->ip, pointee, &width, &is_signed); new_val = interpNotScalar(ctx->ip, pointee, and_val); break; } case ATOMIC_RMW_OP_OR: new_val = interpBinOp(ctx->ip, AIR_INST_BIT_OR, old, operand); break; case ATOMIC_RMW_OP_XOR: new_val = interpBinOp(ctx->ip, AIR_INST_XOR, old, operand); break; case ATOMIC_RMW_OP_MAX: new_val = interpBinOp(ctx->ip, AIR_INST_MAX, old, operand); break; case ATOMIC_RMW_OP_MIN: new_val = interpBinOp(ctx->ip, AIR_INST_MIN, old, operand); break; default: UNIMPLEMENTED("atomic_rmw: unsupported op"); } } interpStoreTyped(ctx->ip, ptr_addr, pointee, new_val); ctx->values[inst] = old; break; } case AIR_INST_CMP_VECTOR: { // cmp_vector ty_pl: a = the result type (`@Vector(N, bool)`), // b = an Air.VectorCmp payload { lhs, rhs, op }. Upstream gives // vector comparison its own tag precisely because its result is // not the element type, so the lane loop writes bools into a // freshly allocated bool vector rather than reusing the operand's // layout. Each lane is this interpreter's own scalar comparison // (Sema.compareVector: `"const res_bool = try // sema.compareScalar(lhs_elem, op, rhs_elem, // ty.scalarType(zcu));"`). uint32_t vc_res_ty = a; uint32_t vc_payload = b; InterpValue vc_lhs = interpResolveRef(ctx, air->extra[vc_payload]); InterpValue vc_rhs = interpResolveRef(ctx, air->extra[vc_payload + 1]); AirInstTag vc_scalar; if (!airVectorCmpDecodeOp(air->extra[vc_payload + 2], &vc_scalar)) UNIMPLEMENTED("cmp_vector: unhandled cmp_vector operation"); if (ctx->ip->item_tags[vc_lhs.ty] != IP_TAG_TYPE_VECTOR || ctx->ip->item_tags[vc_rhs.ty] != IP_TAG_TYPE_VECTOR || ctx->ip->item_tags[vc_res_ty] != IP_TAG_TYPE_VECTOR) UNREACHABLE("interp: cmp_vector operand is not a vector"); if (interpArrayInfo(ctx->ip, vc_res_ty).child != ZIR_REF_BOOL_TYPE) UNREACHABLE("interp: cmp_vector result shape mismatch"); ctx->values[inst] = interpVectorLanes( ctx->ip, (uint8_t)vc_scalar, vc_lhs, vc_rhs, vc_res_ty); break; } case AIR_INST_REDUCE: { // reduce: pair.a = operand ref, pair.b = the std.lang.ReduceOp // declaration index. Sema.zirReduce's own fold is the definition: // `"var accum: Value = try operand_val.elemValue(pt, 0);"` then // each later element combined with the scalar operation // (`arith.bitwiseBin`, `Value.numberMin`/`numberMax`, // `arith.addMaybeWrap`, `arith.mulMaybeWrap`), so the accumulator // runs lane 0 upward through this interpreter's own scalar paths. InterpValue vop = interpResolveRef(ctx, a); InterpArrayInfo rvec = interpArrayInfo(ctx->ip, vop.ty); if (ctx->ip->item_tags[vop.ty] != IP_TAG_TYPE_VECTOR || rvec.len == 0) UNREACHABLE("interp: reduce operand is not a vector"); uint32_t relem_size = interpAbiSize(ctx->ip, rvec.child); uint32_t rbase = interpAddressFromBits(vop.bits); // The operation is loop-invariant: resolve it once, through the // one table that names std.lang.ReduceOp's order (zig1.h). AirReduceKind rkind; AirInstTag rop_tag; if (!airReduceOp(b, &rkind, &rop_tag)) UNIMPLEMENTED("reduce: unhandled reduce operation"); uint32_t rfloat_bits; bool rfloat = ztSimpleFloatBits(rvec.child, &rfloat_bits); InterpValue accum = interpLoadTyped(ctx->ip, rbase, rvec.child, NULL); for (uint32_t i = 1; i < rvec.len; i++) { InterpValue elem = interpLoadTyped(ctx->ip, interpAddressAdd(rbase, interpLayoutMul(i, relem_size)), rvec.child, NULL); if (rkind != AIR_REDUCE_COMBINE && rfloat) { // A float lane's `numberMin`/`numberMax` drops a NaN // operand, which is the `min`/`max` binop's rule, not a // comparison's. accum = interpBinOp(ctx->ip, rkind == AIR_REDUCE_MIN ? AIR_INST_MIN : AIR_INST_MAX, accum, elem); continue; } if (rkind != AIR_REDUCE_COMBINE) { // `"Value.numberMin(accum, elem_val, zcu)"` / // `numberMax`: pick the smaller (larger) of the two, so // one comparison selects the accumulator. InterpValue lt = interpBinOp(ctx->ip, (uint8_t)rop_tag, elem, accum); if (lt.ty != ZIR_REF_BOOL_TYPE) UNIMPLEMENTED("reduce: comparison result"); bool elem_smaller = lt.bits != 0; if (elem_smaller == (rkind == AIR_REDUCE_MIN)) accum = elem; continue; } accum = interpBinOp(ctx->ip, (uint8_t)rop_tag, accum, elem); } if (accum.ty != rvec.child) UNIMPLEMENTED("reduce: result type is not the element type"); ctx->values[inst] = accum; break; } case AIR_INST_PTR_CAST: { // ty_op: pair.a = dest pointer type, pair.b = operand. InterpValue op = interpResolveRef(ctx, b); if (ctx->ip->item_tags[a] == IP_TAG_TYPE_POINTER && ztIsSlice(ctx->ip, op.ty)) { InterpValue out = { interpReadLE(interpAddressFromBits(op.bits), 4u), a }; ctx->values[inst] = out; break; } if (ztIsSlice(ctx->ip, a) && ztIsSlice(ctx->ip, op.ty)) { // `@ptrCast([]T)` -> `[]U` keeps the address and preserves // the byte length, then re-expresses it in destination // element units. This is reached by ZIR cache load/save, // which casts `[]u32` extra storage to `[]u8` for raw I/O; // keeping the source element count would read/write only the // first quarter of the cache body. uint32_t src_addr = interpAddressFromBits(op.bits); uint64_t src_ptr = interpReadLE(src_addr, 4u); uint32_t src_len = (uint32_t)interpReadLE( interpAddressAdd(src_addr, 4u), 4u); uint32_t dst_len = interpSliceCastLen(ctx->ip, a, op.ty, src_len); ctx->values[inst] = interpMakeSliceValue(ctx->ip, a, src_ptr, dst_len); break; } if (ztIsSlice(ctx->ip, a) && ctx->ip->item_tags[op.ty] == IP_TAG_TYPE_POINTER) { // `@ptrCast(*T)` -> `[]U` (e.g. std.mem.swap's `@ptrCast(a): // []u8`). Stock ptrCastFull reconstructs a slice {ptr, len} // with len = abiSize(pointee)/abiSize(U) (the element count // that fits). The front-end lowers this to a bare ptr_cast, // so synthesize the slice here — otherwise the reinterpret // below would read the pointee's own bytes as {ptr,len} and // `.len` would be garbage. uint32_t pointee = interpPtrChild(ctx->ip, op.ty); uint32_t many = ctx->ip->item_datas[a]; // slice's [*]U type uint32_t elem = interpPtrChild(ctx->ip, many); uint32_t esz = interpAbiSize(ctx->ip, elem); uint32_t slen = esz ? (interpAbiSize(ctx->ip, pointee) / esz) : 0u; ctx->values[inst] = interpMakeSliceValue(ctx->ip, a, op.bits, slen); break; } if (ctx->ip->item_tags[a] == IP_TAG_TYPE_OPTIONAL && ztIsSlice(ctx->ip, op.ty)) { // `@ptrCast([]T)` -> `?[]U`: `Type.ptrInfo` looks through the // optional, so this is the slice-to-slice byte-length rule // above followed by ptrCastFull's optional wrap // (src/Sema.zig). A non-null optional slice is // the slice header itself, its pointer field acting as the // null niche. uint32_t ochild = ipOptionalChild(ctx->ip, a); if (!ztIsSlice(ctx->ip, ochild)) UNIMPLEMENTED( "ptr_cast: slice cast to non-slice optional"); InterpOptionalRepr orepr = interpOptionalRepr(ctx->ip, a); if (orepr != INTERP_OPT_REPR_PAYLOAD_NICHE && orepr != INTERP_OPT_REPR_PAYLOAD_FLAG) UNIMPLEMENTED( "ptr_cast: slice cast to zero-size optional"); uint32_t sl_addr = interpAddressFromBits(op.bits); uint64_t sl_ptr = interpReadLE(sl_addr, 4u); uint32_t sl_len = (uint32_t)interpReadLE( interpAddressAdd(sl_addr, 4u), 4u); uint32_t dst_len = interpSliceCastLen(ctx->ip, ochild, op.ty, sl_len); uint32_t oaddr = interpArenaAlloc( interpAbiSize(ctx->ip, a), interpAbiAlign(ctx->ip, a)); interpWriteSliceHeader(oaddr, sl_ptr, dst_len); if (orepr == INTERP_OPT_REPR_PAYLOAD_FLAG) interpWriteLE(interpAddressAdd( oaddr, interpOptFlagOffset(ctx->ip, a)), (sl_ptr != 0) ? 1u : 0u, 1u); InterpValue oout = { oaddr, a }; ctx->values[inst] = oout; break; } if (ctx->ip->item_tags[a] == IP_TAG_TYPE_OPTIONAL && ctx->ip->item_tags[op.ty] == IP_TAG_TYPE_POINTER) { uint32_t ochild = ctx->ip->item_datas[a]; // `@ptrCast(ptr)` -> `?ptr` (e.g. HashMap's `self.metadata = // @ptrCast(@alignCast(metadata))`). Materialize the optional // aggregate. A niche optional needs only the pointer payload; // a flagged optional additionally records its nullness. InterpOptionalRepr orepr = interpOptionalRepr(ctx->ip, a); if (orepr != INTERP_OPT_REPR_PAYLOAD_NICHE && orepr != INTERP_OPT_REPR_PAYLOAD_FLAG) UNIMPLEMENTED( "ptr_cast: pointer cast to zero-size optional"); if (orepr == INTERP_OPT_REPR_PAYLOAD_NICHE && !interpTypeIsAggregate(ctx->ip, a)) { InterpValue oout = { op.bits, a }; ctx->values[inst] = oout; break; } uint32_t oaddr = interpArenaAlloc( interpAbiSize(ctx->ip, a), interpAbiAlign(ctx->ip, a)); InterpValue pv = { op.bits, ochild }; interpStoreTyped(ctx->ip, oaddr, ochild, pv); if (orepr == INTERP_OPT_REPR_PAYLOAD_FLAG) interpWriteLE(interpAddressAdd( oaddr, interpOptFlagOffset(ctx->ip, a)), (op.bits != 0) ? 1u : 0u, 1u); InterpValue oout = { oaddr, a }; ctx->values[inst] = oout; break; } // Reinterpret: same address, new pointer type. InterpValue out = { op.bits, a }; ctx->values[inst] = out; break; } case AIR_INST_PTR_FROM_INT: { // ty_op: pair.a = result pointer type, pair.b = integer address. InterpValue op = interpResolveRef(ctx, b); if (ipItemTagIs(ctx->ip, a, IP_TAG_TYPE_VECTOR)) { // `Air.Legalize`'s `.scalarize_ptr_from_int`. ctx->values[inst] = interpScalarizeUnOp(ctx->ip, tag, a, op); break; } InterpValue out = interpPtrFromIntScalar(a, op); if (a < ctx->ip->items_len && ctx->ip->item_tags[a] == IP_TAG_TYPE_OPTIONAL && interpTypeIsAggregate(ctx->ip, a)) { InterpOptionalRepr orepr = interpOptionalRepr(ctx->ip, a); if (orepr != INTERP_OPT_REPR_PAYLOAD_NICHE && orepr != INTERP_OPT_REPR_PAYLOAD_FLAG) UNIMPLEMENTED("ptr_from_int: zero-size optional"); uint32_t child = ipOptionalChild(ctx->ip, a); uint32_t oaddr = interpArenaAlloc( interpAbiSize(ctx->ip, a), interpAbiAlign(ctx->ip, a)); interpStoreTyped( ctx->ip, oaddr, child, (InterpValue) { out.bits, child }); if (orepr == INTERP_OPT_REPR_PAYLOAD_FLAG) interpWriteLE(interpAddressAdd( oaddr, interpOptFlagOffset(ctx->ip, a)), op.bits != 0 ? 1u : 0u, 1u); out.bits = oaddr; } ctx->values[inst] = out; break; } case AIR_INST_PTR_SLICE_PTR_PTR: { // ty_op: pair.a = result `*[*]T` type, pair.b = `*slice` operand. // Address of the slice struct's `ptr` field (offset 0). // (src/Air.zig ptr_slice_ptr_ptr.) InterpValue sp = interpResolveRef(ctx, b); InterpValue out = { interpAddressFromBits(sp.bits), a }; ctx->values[inst] = out; break; } case AIR_INST_PTR_SLICE_LEN_PTR: { // ty_op: pair.a = result `*usize` type, pair.b = `*slice` operand. // Address of the slice struct's `len` field (offset 4). // (src/Air.zig ptr_slice_len_ptr.) InterpValue sp = interpResolveRef(ctx, b); InterpValue out = { interpAddressAdd(interpAddressFromBits(sp.bits), 4u), a }; ctx->values[inst] = out; break; } case AIR_INST_STRUCT_FIELD_PTR_INDEX_0: case AIR_INST_STRUCT_FIELD_PTR_INDEX_1: case AIR_INST_STRUCT_FIELD_PTR_INDEX_2: case AIR_INST_STRUCT_FIELD_PTR_INDEX_3: case AIR_INST_STRUCT_FIELD_PTR: { // pair.a = result field-pointer type. The index_N forms are ty_op // (pair.b = container pointer); the generic form addStructFieldPtr // emits for field_index >= 4 is ty_pl (pair.b = extra index of // Air.StructField { struct_operand, field_index }). The result // address is base + field offset. uint32_t fptr_ty = a; bool generic = tag == AIR_INST_STRUCT_FIELD_PTR; uint32_t field_index = generic ? air->extra[b + 1] : (uint32_t)(tag - AIR_INST_STRUCT_FIELD_PTR_INDEX_0); InterpValue cptr = interpResolveRef(ctx, generic ? air->extra[b] : b); uint32_t struct_ty = interpPtrChild(ctx->ip, cptr.ty); // CodeGen.structFieldPtr's `.@"packed"` arms: a bit pointer // keeps the host's address, and so does a field pointer whose host // ptrType dropped (the field fills it, so its byte offset, // `(field bit offset + container bit_offset) / 8`, is 0); and // `".@\"union\" => 0"`. if (ipStructIsPacked(ctx->ip, struct_ty)) { ctx->values[inst] = (InterpValue) { cptr.bits, fptr_ty }; break; } if (ipUnionIsPacked(ctx->ip, struct_ty)) { ctx->values[inst] = (InterpValue) { cptr.bits, fptr_ty }; break; } uint32_t off = interpStructFieldOffset(ctx->ip, struct_ty, field_index); InterpValue out = { interpAddressAdd(interpAddressFromBits(cptr.bits), off), fptr_ty }; ctx->values[inst] = out; break; } case AIR_INST_FIELD_PARENT_PTR: { // ty_pl: pair.a = parent pointer type, pair.b = payload extra // index (Air.FieldParentPtr { field_ptr: Ref, field_index }). // The inverse of struct_field_ptr: the parent's address is the // field's address minus the field's layout offset within the // parent struct (codegen/c.zig airFieldParentPtr lowers to the // same subtraction via offsetof). uint32_t fpp_res_ty = a; uint32_t fpp_field_ptr_ref = air->extra[b]; uint32_t fpp_field_index = air->extra[b + 1]; InterpValue fpp_fptr = interpResolveRef(ctx, fpp_field_ptr_ref); uint32_t fpp_parent_ptr_ty = fpp_res_ty; if (fpp_res_ty < ctx->ip->items_len && ctx->ip->item_tags[fpp_res_ty] == IP_TAG_TYPE_OPTIONAL) { uint32_t fpp_child = ipOptionalChild(ctx->ip, fpp_res_ty); if (fpp_child < ctx->ip->items_len && ctx->ip->item_tags[fpp_child] == IP_TAG_TYPE_POINTER && !interpTypeIsAggregate(ctx->ip, fpp_res_ty)) { // `@alignCast(@fieldParentPtr(...))` can lower with the // result type `?*T`: the payload-niche optional is the // same wasm32 address word as its pointer child. Use the // child pointer for layout, but keep the optional result // type on the returned InterpValue. fpp_parent_ptr_ty = fpp_child; } } uint32_t fpp_parent_ty = interpPtrChild(ctx->ip, fpp_parent_ptr_ty); uint32_t fpp_off; bool fpp_packed_union = ipUnionIsPacked(ctx->ip, fpp_parent_ty); if (ipStructIsPacked(ctx->ip, fpp_parent_ty) || fpp_packed_union) { // CodeGen.airFieldParentPtr's `.@"packed"` arm: // `"@divExact(parent_ptr_offset + field_offset - // field_ptr_offset, 8)"`, each `packed_offset.bit_offset`. // Neither pointer is a vector element's, so a false // interpPtrBitHost leaves a byte-addressed bit offset of 0. uint32_t p_host, f_host, p_bit = 0, f_bit = 0; interpPtrBitHost(ctx->ip, fpp_parent_ptr_ty, &p_host, &p_bit); interpPtrBitHost(ctx->ip, fpp_fptr.ty, &f_host, &f_bit); // `"if (zcu.typeToStruct(parent_ty)) |loaded_struct| zcu. // structPackedFieldBitOffset(loaded_struct, field_index) // else 0"`. uint32_t bits = p_bit + (fpp_packed_union ? 0u : zcuStructPackedFieldBitOffset(NULL, ctx->ip, fpp_parent_ty, fpp_field_index)) - f_bit; if (bits % 8u != 0u) UNIMPLEMENTED("field_parent_ptr: inexact packed offset"); fpp_off = bits / 8u; } else { fpp_off = interpStructFieldOffset( ctx->ip, fpp_parent_ty, fpp_field_index); } InterpValue fpp_out = { interpAddressSub( interpAddressFromBits(fpp_fptr.bits), fpp_off), fpp_res_ty }; ctx->values[inst] = fpp_out; break; } case AIR_INST_PTR_ELEM_PTR: { // ty_pl: pair.a = result pointer type, pair.b = payload extra // index (Air.Bin {ptr, index}). For a `.one` pointer to `[n]T` the // array bytes start at the pointer address; otherwise the pointer // already addresses the elements. Result = base + index * // elem_size. uint32_t res_ty = a; uint32_t payload = b; uint32_t ptr_ref = air->extra[payload]; uint32_t index_ref = air->extra[payload + 1]; InterpValue ptr = interpResolveRef(ctx, ptr_ref); InterpValue index = interpResolveRef(ctx, index_ref); uint32_t ptr_base = interpPtrPayloadBase(ctx->ip, ptr.ty); uint32_t pointee = ctx->ip->extra[ptr_base]; uint32_t elem_ty; if (interpPtrSizeFromBase(ctx->ip, ptr_base) == IP_PTR_SIZE_ONE && (ctx->ip->item_tags[pointee] == IP_TAG_TYPE_ARRAY_SMALL || ctx->ip->item_tags[pointee] == IP_TAG_TYPE_ARRAY_BIG)) { InterpArrayInfo array = interpArrayInfo(ctx->ip, pointee); elem_ty = array.child; } else { elem_ty = pointee; } uint32_t esize = interpAbiSize(ctx->ip, elem_ty); InterpValue out = { interpAddressIndex(interpAddressFromBits(ptr.bits), interpAddressFromBits(index.bits), esize), res_ty }; ctx->values[inst] = out; break; } case AIR_INST_AGGREGATE_INIT: { // ty_pl: pair.a = aggregate type, pair.b = payload extra index of // `len` element refs. Allocate arena bytes for the aggregate, // write each element at its offset, and yield the aggregate's // address. uint32_t agg_ty = a; uint32_t payload = b; uint8_t agg_tag = ctx->ip->item_tags[agg_ty]; if (ipStructIsPacked(ctx->ip, agg_ty)) { // Packed struct init: fold the backing integer scalar from the // element values placed at their LSB-first bit offsets (no // arena bytes — a packed struct is a scalar). uint32_t n = ipStructFieldsLen(ctx->ip, agg_ty); uint64_t backing = 0; for (uint32_t i = 0; i < n; i++) { uint32_t fty = ipStructFieldType(ctx->ip, agg_ty, i); uint32_t fwidth; bool fsigned; interpTypeInfo(ctx->ip, fty, &fwidth, &fsigned); uint32_t off = zcuStructPackedFieldBitOffset( NULL, ctx->ip, agg_ty, i); if (off >= 64 || fwidth > 64 || off + fwidth > 64) UNIMPLEMENTED( "aggregate_init: packed backing exceeds 64 bits"); InterpValue ev = interpResolveRef(ctx, air->extra[payload + i]); // The one bit-field rule (common.h), as the comptime // packed init already writes it; the bound above keeps // `hi` unwritten. uint64_t hi_unused = 0; packedBitsInsert( &backing, &hi_unused, off, fwidth, ev.bits); } uint32_t bwidth; bool bsigned; interpTypeInfo(ctx->ip, agg_ty, &bwidth, &bsigned); InterpValue out = { interpNormalize(backing, bwidth, false), agg_ty }; ctx->values[inst] = out; break; } // A vector destination (Sema.coerceArrayLike's // addAggregateInit, src/Sema.zig) takes the array path: // both lay their elements out contiguously at the element ABI // stride (src/Type.zig) and a vector has no // sentinel, so interpArrayInfo's shared decode is exact. Only the // total size's tail rounding differs, and interpAbiSize already // applies it to the allocation below. bool is_array = (agg_tag == IP_TAG_TYPE_ARRAY_SMALL || agg_tag == IP_TAG_TYPE_ARRAY_BIG || agg_tag == IP_TAG_TYPE_VECTOR); uint32_t addr = interpArenaAlloc(interpAbiSize(ctx->ip, agg_ty), interpAbiAlign(ctx->ip, agg_ty)); if (is_array) { InterpArrayInfo array = interpArrayInfo(ctx->ip, agg_ty); uint32_t esize = interpAbiSize(ctx->ip, array.child); for (uint32_t i = 0; i < array.len; i++) { InterpValue ev = interpResolveRef(ctx, air->extra[payload + i]); interpStoreTyped(ctx->ip, interpAddressIndex(addr, i, esize), array.child, ev); } if (array.sentinel != IP_INDEX_NONE) { InterpValue sentinel = interpDecodeConst(ctx->ip, array.sentinel); interpStoreTyped(ctx->ip, interpAddressIndex(addr, array.len, esize), array.child, sentinel); } } else if (agg_tag == IP_TAG_TYPE_TUPLE) { // A runtime anon tuple (`.{x}` bound to `args: anytype`, the // codegen `w.print(fmt, .{val})` idiom). TypeTuple extra = // { fields_len, types[], values[] }: a field with a stored // value is a comptime (zero-bit) field skipped here (the // front-end still emitted a payload slot for it to keep field // indices aligned with the layout); only runtime fields // (values[i] == .none) store their element bytes. Mirrors the // interpDecodeConst tuple arm. uint32_t td = ctx->ip->item_datas[agg_ty]; uint32_t n = ctx->ip->extra[td]; for (uint32_t i = 0; i < n; i++) { if (ctx->ip->extra[td + 1 + n + i] != IP_INDEX_NONE) continue; // comptime field: no runtime bytes uint32_t fty = ctx->ip->extra[td + 1 + i]; InterpValue ev = interpResolveRef(ctx, air->extra[payload + i]); interpStoreTyped(ctx->ip, interpAddressAdd( addr, interpStructFieldOffset(ctx->ip, agg_ty, i)), fty, ev); } } else { uint32_t n = ipStructFieldsLen(ctx->ip, agg_ty); for (uint32_t i = 0; i < n; i++) { if (ipStructFieldIsComptime(ctx->ip, agg_ty, i)) continue; // fixed field has no runtime bytes uint32_t fty = ipStructFieldType(ctx->ip, agg_ty, i); ZtTypeClass field_class = ztTypeClass(ctx->ip, fty); if (field_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED("aggregate_init: unresolved " "aggregate_init field class"); if (field_class != ZT_CLASS_RUNTIME && field_class != ZT_CLASS_PARTIAL) continue; // FULL/OPV fields carry no runtime bytes InterpValue ev = interpResolveRef(ctx, air->extra[payload + i]); interpStoreTyped(ctx->ip, interpAddressAdd( addr, interpStructFieldOffset(ctx->ip, agg_ty, i)), fty, ev); } } InterpValue out = { addr, agg_ty }; ctx->values[inst] = out; break; } case AIR_INST_SHUFFLE_ONE: case AIR_INST_SHUFFLE_TWO: { // ty_pl: pair.a = result vector type, pair.b = payload: the // mask (one word per result element), then the runtime // operand(s). shuffle_two's ShuffleTwoMask is `.undef` // (maxInt), `aElem(i)` (`i << 1`) or `bElem(i)` (`i << 1 | 1`); // shuffle_one's ShuffleOneMask is an index into its operand, or // bit 31 and a comptime value. uint32_t res_ty = a; uint32_t payload = b; InterpArrayInfo res = interpArrayInfo(ctx->ip, res_ty); uint32_t esize = interpAbiSize(ctx->ip, res.child); bool two = tag == AIR_INST_SHUFFLE_TWO; InterpValue ops[2]; ops[0] = interpResolveRef(ctx, air->extra[payload + res.len]); if (two) ops[1] = interpResolveRef(ctx, air->extra[payload + res.len + 1]); uint32_t addr = interpArenaAlloc(interpAbiSize(ctx->ip, res_ty), interpAbiAlign(ctx->ip, res_ty)); for (uint32_t i = 0; i < res.len; i++) { uint32_t m = air->extra[payload + i]; InterpValue ev; if (two && m == UINT32_MAX) continue; // `.undef`: the lane keeps whatever it holds if (!two && (m & AIR_SHUFFLE_ONE_MASK_KIND) != 0) { ev = interpDecodeConst( ctx->ip, m & AIR_SHUFFLE_ONE_MASK_INDEX_MASK); } else { const InterpValue* src = two ? &ops[m & AIR_SHUFFLE_TWO_MASK_B_ELEM] : &ops[0]; uint32_t lane = two ? m >> AIR_SHUFFLE_TWO_MASK_INDEX_SHIFT : m; ev = interpLoadTyped(ctx->ip, interpAddressIndex( interpAddressFromBits(src->bits), lane, esize), res.child, NULL); } interpStoreTyped(ctx->ip, interpAddressIndex(addr, i, esize), res.child, ev); } InterpValue out = { addr, res_ty }; ctx->values[inst] = out; break; } case AIR_INST_UNION_INIT: { // ty_pl: pair.a = union type, pair.b = payload extra index of // UnionInit{ field_index, init_ref }. Allocate the union in the // arena, write the active field's payload at the payload offset // and the tag's exact enum integer at the tag offset. For // plain/extern unions, interpUnionLayout reports tag_size=0, so // the tag write is a no-op and the ABI difference stays in // layout. Mirrors codegen's union lowering + Sema.addUnionInit. uint32_t uty = a; uint32_t payload = b; uint32_t field_index = air->extra[payload]; uint32_t init_ref = air->extra[payload + 1]; ZtUnionLayout ul = interpUnionLayout(ctx->ip, uty); uint32_t addr = interpArenaAlloc( (uint32_t)ul.abi_size, (uint32_t)ul.abi_align); uint32_t field_ty = ipUnionFieldType(ctx->ip, uty, field_index); InterpValue iv = interpResolveRef(ctx, init_ref); interpStoreTyped(ctx->ip, interpAddressAdd( addr, (uint32_t)ztUnionLayoutPayloadOffset(&ul)), field_ty, iv); uint64_t tag_bits = interpUnionFieldTagBits(ctx->ip, uty, field_index); interpUnionTagStore(&ul, addr, tag_bits); InterpValue out = { addr, uty }; ctx->values[inst] = out; break; } case AIR_INST_GET_UNION_TAG: { // ty_op: pair.a = result enum-tag type, pair.b = tagged-union // operand (byval; its bits are its arena address). Read the tag // int stored at the union's tag offset (mirrors Sema.unionToTag / // codegen's get_union_tag). The result keeps the enum type so its // width resolves via the enum's int tag type downstream. uint32_t tag_ty = a; InterpValue un = interpResolveRef(ctx, b); ZtUnionLayout ul = interpUnionLayout(ctx->ip, un.ty); uint64_t raw = interpUnionTagLoad(&ul, interpAddressFromBits(un.bits)); uint32_t width; bool is_signed; interpTypeInfo(ctx->ip, tag_ty, &width, &is_signed); InterpValue out = { interpNormalize(raw, width, is_signed), tag_ty }; ctx->values[inst] = out; break; } case AIR_INST_SET_UNION_TAG: { // bin_op: pair.a = union pointer, pair.b = enum tag value. Write // the tag's int at the union's tag offset (Sema.unionFieldPtr's // initializing `set_union_tag`; codegen airSetUnionTag). The // pointer's pointee is the union type; the rhs resolves to the // enum tag whose bits are its int value. Void statement (no // result). InterpValue sut_ptr = interpResolveRef(ctx, a); InterpValue sut_tag = interpResolveRef(ctx, b); uint32_t sut_uty = interpPtrChild(ctx->ip, sut_ptr.ty); ZtUnionLayout ul = interpUnionLayout(ctx->ip, sut_uty); uint32_t sut_addr = interpAddressFromBits(sut_ptr.bits); if (interpAddressIsConstArena(sut_addr)) UNIMPLEMENTED("set_union_tag: into const arena"); interpUnionTagStore(&ul, sut_addr, sut_tag.bits); break; } case AIR_INST_UNION_FROM_ENUM: { // ty_op: pair.a = union type, pair.b = enum tag. Every field is // zero-bit, so the union is its tag: write the tag's int at the // tag offset of a fresh union (codegen airUnionFromEnum). uint32_t ufe_uty = a; InterpValue ufe_tag = interpResolveRef(ctx, b); ZtUnionLayout ul = interpUnionLayout(ctx->ip, ufe_uty); uint32_t ufe_addr = interpArenaAlloc( (uint32_t)ul.abi_size, (uint32_t)ul.abi_align); interpUnionTagStore(&ul, ufe_addr, ufe_tag.bits); InterpValue ufe_out = { ufe_addr, ufe_uty }; ctx->values[inst] = ufe_out; break; } case AIR_INST_AGG_FIELD_VAL: { // ty_pl: pair.a = field type, pair.b = extra index of // Air.StructField { struct_operand, field_index }. Return the // field by value from a byval struct/union operand (its bits are // the aggregate's arena address). For a union every active-field // payload shares the payload offset (Sema.unionFieldVal); for a // struct use the field's offset. Mirrors the struct_field_ptr // union/struct split but loads the value rather than yielding a // pointer. uint32_t field_ty = a; uint32_t payload = b; uint32_t agg_ref = air->extra[payload]; uint32_t field_index = air->extra[payload + 1]; InterpValue agg = interpResolveRef(ctx, agg_ref); if (ipStructIsPacked(ctx->ip, agg.ty)) { // Packed field read: shift the backing scalar down by the // field's LSB bit offset and mask to the field width, then // normalize to the field type (Sema packed field access). uint32_t off = zcuStructPackedFieldBitOffset( NULL, ctx->ip, agg.ty, field_index); uint32_t fwidth; bool fsigned; interpTypeInfo(ctx->ip, field_ty, &fwidth, &fsigned); if (fwidth == 0) { InterpValue out = { 0, field_ty }; ctx->values[inst] = out; break; } if (interpTypeIsByRefScalar(ctx->ip, agg.ty)) { // A backing integer wider than 64 bits is in memory: // Legalize's packed load over its bytes. ctx->values[inst] = interpPackedLoad(ctx->ip, interpAddressFromBits(agg.bits), interpAbiSize(ctx->ip, agg.ty), off, field_ty); break; } InterpPattern ap = interpScalarPattern(ctx->ip, agg); uint64_t raw = packedBitsExtract(ap.lo, ap.hi, off, fwidth); InterpValue out = { interpNormalize(raw, fwidth, fsigned), field_ty }; ctx->values[inst] = out; break; } uint32_t off = interpStructFieldOffset(ctx->ip, agg.ty, field_index); ctx->values[inst] = interpLoadTyped(ctx->ip, interpAddressAdd(interpAddressFromBits(agg.bits), off), field_ty, &ctx->values[inst]); break; } case AIR_INST_WRAP_OPTIONAL: { // ty_op: pair.a = result `?T` type, pair.b = payload operand. // Allocate the optional and write its payload at offset 0. Only a // non-niche representation needs a separate has-value flag. uint32_t opt_ty = a; InterpValue payload = interpResolveRef(ctx, b); uint32_t child = ctx->ip->item_datas[opt_ty]; InterpOptionalRepr repr = interpOptionalRepr(ctx->ip, opt_ty); if (repr != INTERP_OPT_REPR_PAYLOAD_NICHE && repr != INTERP_OPT_REPR_PAYLOAD_FLAG) UNIMPLEMENTED("wrap_optional: runtime wrap of optional " "without representation"); if (repr == INTERP_OPT_REPR_PAYLOAD_NICHE && !interpTypeIsAggregate(ctx->ip, opt_ty)) { ctx->values[inst] = (InterpValue) { payload.bits, opt_ty }; break; } uint32_t addr = interpArenaAlloc(interpAbiSize(ctx->ip, opt_ty), interpAbiAlign(ctx->ip, opt_ty)); interpStoreTyped(ctx->ip, addr, child, payload); if (repr == INTERP_OPT_REPR_PAYLOAD_FLAG) interpWriteLE(interpAddressAdd( addr, interpOptFlagOffset(ctx->ip, opt_ty)), 1u, 1u); InterpValue out = { addr, opt_ty }; ctx->values[inst] = out; break; } case AIR_INST_OPTIONAL_PAYLOAD: { // ty_op: pair.a = payload (child) type, pair.b = `?T` operand. The // payload lives at offset 0. (No safety check: zig1 sema lowers // `.?` without a null check, ReleaseSmall-style.) uint32_t child_ty = a; InterpValue opt = interpResolveRef(ctx, b); InterpOptionalRepr repr = interpOptionalRepr(ctx->ip, opt.ty); if (repr != INTERP_OPT_REPR_PAYLOAD_NICHE && repr != INTERP_OPT_REPR_PAYLOAD_FLAG) UNIMPLEMENTED("optional_payload: payload of optional without " "runtime payload"); if (repr == INTERP_OPT_REPR_PAYLOAD_NICHE && !interpTypeIsAggregate(ctx->ip, opt.ty)) { ctx->values[inst] = (InterpValue) { opt.bits, child_ty }; break; } ctx->values[inst] = interpLoadTyped(ctx->ip, interpAddressFromBits(opt.bits), child_ty, &ctx->values[inst]); break; } case AIR_INST_OPTIONAL_PAYLOAD_PTR: case AIR_INST_OPTIONAL_PAYLOAD_PTR_SET: { // ty_op: pair.a = result `*T` type, pair.b = `*?T` operand. The // payload is at offset 0, so the payload pointer equals the // optional pointer. `_ptr_set` additionally marks it non-null. uint32_t res_ty = a; InterpValue optptr = interpResolveRef(ctx, b); uint32_t opt_ty = interpPtrChild(ctx->ip, optptr.ty); InterpOptionalRepr repr = interpOptionalRepr(ctx->ip, opt_ty); if (repr != INTERP_OPT_REPR_PAYLOAD_NICHE && repr != INTERP_OPT_REPR_PAYLOAD_FLAG) UNIMPLEMENTED("optional_payload_ptr_set: payload pointer of " "zero-size optional"); if (tag == AIR_INST_OPTIONAL_PAYLOAD_PTR_SET && repr == INTERP_OPT_REPR_PAYLOAD_FLAG) { uint32_t opt_addr = interpAddressFromBits(optptr.bits); if (interpAddressIsConstArena(opt_addr)) // Setting the non-null flag mutates the pointee; a // const-arena target would corrupt a shared cached const // (only reachable via write-through-const = UB). Abort // loudly, matching the runtime-store guard. UNIMPLEMENTED("optional_payload_ptr_set: into const"); interpWriteLE(interpAddressAdd(opt_addr, interpOptFlagOffset(ctx->ip, opt_ty)), 1u, 1u); } InterpValue out = { optptr.bits, res_ty }; ctx->values[inst] = out; break; } case AIR_INST_IS_NULL: case AIR_INST_IS_NON_NULL: { // un_op: pair.a = `?T` operand. Read the has_value flag. InterpValue opt = interpResolveRef(ctx, a); InterpOptionalRepr repr = interpOptionalRepr(ctx->ip, opt.ty); bool non_null; if (repr == INTERP_OPT_REPR_NPV_NULL) { non_null = false; } else if (repr == INTERP_OPT_REPR_FULL) { UNIMPLEMENTED("is_non_null: runtime null test of " "fully-comptime optional"); } else if (repr == INTERP_OPT_REPR_PAYLOAD_NICHE) { non_null = interpTypeIsAggregate(ctx->ip, opt.ty) ? interpOptionalNicheNonNull( ctx->ip, opt.ty, interpAddressFromBits(opt.bits)) : opt.bits != 0; } else { uint64_t has = interpReadLE( interpAddressAdd(interpAddressFromBits(opt.bits), interpOptFlagOffset(ctx->ip, opt.ty)), 1u); non_null = has != 0; } InterpValue out = { 0, ZIR_REF_BOOL_TYPE }; out.bits = (tag == AIR_INST_IS_NON_NULL) ? (non_null ? 1u : 0u) : (non_null ? 0u : 1u); ctx->values[inst] = out; break; } case AIR_INST_IS_NULL_PTR: case AIR_INST_IS_NON_NULL_PTR: { // un_op: pair.a = `*?T` operand. Runtime optionals read the flag; // ?noreturn has the sole null value and no bytes to inspect. InterpValue optr = interpResolveRef(ctx, a); uint32_t opt_ty = interpPtrChild(ctx->ip, optr.ty); InterpOptionalRepr repr = interpOptionalRepr(ctx->ip, opt_ty); bool non_null; if (repr == INTERP_OPT_REPR_NPV_NULL) { non_null = false; } else if (repr == INTERP_OPT_REPR_FULL) { UNIMPLEMENTED("is_non_null_ptr: runtime ptr null test of " "fully-comptime optional"); } else if (repr == INTERP_OPT_REPR_PAYLOAD_NICHE) { non_null = interpOptionalNicheNonNull( ctx->ip, opt_ty, interpAddressFromBits(optr.bits)); } else { uint64_t has = interpReadLE( interpAddressAdd(interpAddressFromBits(optr.bits), interpOptFlagOffset(ctx->ip, opt_ty)), 1u); non_null = has != 0; } bool result = tag == AIR_INST_IS_NON_NULL_PTR ? non_null : !non_null; InterpValue out = { result ? 1u : 0u, ZIR_REF_BOOL_TYPE }; ctx->values[inst] = out; break; } case AIR_INST_ERRUNION_PAYLOAD_PTR_SET: { // ty_op: pair.a = result `*T` type, pair.b = `*E!T` operand // (Sema.analyzeErrUnionPayloadPtr initializing=true, // Sema.zig: "set the error union error code" to 0/no-error // and yield the payload pointer). Mutates the pointee -> // const-arena guarded. uint32_t eps_res_ty = a; InterpValue eps_ptr = interpResolveRef(ctx, b); uint32_t eps_addr = interpAddressFromBits(eps_ptr.bits); if (interpAddressIsConstArena(eps_addr)) UNIMPLEMENTED("errunion_payload_ptr_set: into const arena"); uint32_t eps_eu_ty = interpPtrChild(ctx->ip, eps_ptr.ty); uint32_t eps_poff, eps_eoff; interpErrUnionOffsets(ctx->ip, eps_eu_ty, &eps_poff, &eps_eoff); interpWriteLE( interpAddressAdd(eps_addr, eps_eoff), 0u, ZT_ERROR_ABI_SIZE); InterpValue eps_out = { interpAddressAdd(eps_addr, eps_poff), eps_res_ty }; ctx->values[inst] = eps_out; break; } case AIR_INST_WRAP_ERRUNION_PAYLOAD: { // ty_op: pair.a = result `E!T` type, pair.b = payload operand. // Write the payload and set the error code to 0 (no error). uint32_t eu_ty = a; InterpValue payload = interpResolveRef(ctx, b); uint32_t payload_ty = ipErrorUnionPayload(ctx->ip, eu_ty); uint32_t poff, eoff; interpErrUnionOffsets(ctx->ip, eu_ty, &poff, &eoff); uint32_t addr = interpArenaAlloc( interpAbiSize(ctx->ip, eu_ty), interpAbiAlign(ctx->ip, eu_ty)); interpStoreTyped( ctx->ip, interpAddressAdd(addr, poff), payload_ty, payload); interpWriteLE(interpAddressAdd(addr, eoff), 0u, ZT_ERROR_ABI_SIZE); InterpValue out = { addr, eu_ty }; ctx->values[inst] = out; break; } case AIR_INST_WRAP_ERRUNION_ERR: { // ty_op: pair.a = result `E!T` type, pair.b = error operand (an // error code). Write the error code; the payload is left // undefined. uint32_t eu_ty = a; InterpValue err = interpResolveRef(ctx, b); if (err.bits == 0 || err.bits > UINT16_MAX) UNIMPLEMENTED("wrap_errunion_err: error operand is not a " "dense u16 code"); uint32_t poff, eoff; interpErrUnionOffsets(ctx->ip, eu_ty, &poff, &eoff); uint32_t addr = interpArenaAlloc( interpAbiSize(ctx->ip, eu_ty), interpAbiAlign(ctx->ip, eu_ty)); interpWriteLE( interpAddressAdd(addr, eoff), err.bits, ZT_ERROR_ABI_SIZE); InterpValue out = { addr, eu_ty }; ctx->values[inst] = out; break; } case AIR_INST_UNWRAP_ERRUNION_PAYLOAD: { // ty_op: pair.a = payload type, pair.b = `E!T` operand. uint32_t payload_ty = a; InterpValue eu = interpResolveRef(ctx, b); uint32_t poff, eoff; interpErrUnionOffsets(ctx->ip, eu.ty, &poff, &eoff); ctx->values[inst] = interpLoadTyped(ctx->ip, interpAddressAdd(interpAddressFromBits(eu.bits), poff), payload_ty, &ctx->values[inst]); break; } case AIR_INST_UNWRAP_ERRUNION_PAYLOAD_PTR: { // ty_op: pair.a = result `*T` type, pair.b = `*(E!T)` operand. uint32_t res_ty = a; InterpValue euptr = interpResolveRef(ctx, b); uint32_t eu_ty = interpPtrChild(ctx->ip, euptr.ty); uint32_t poff, eoff; interpErrUnionOffsets(ctx->ip, eu_ty, &poff, &eoff); InterpValue out = { interpAddressAdd(interpAddressFromBits(euptr.bits), poff), res_ty }; ctx->values[inst] = out; break; } case AIR_INST_UNWRAP_ERRUNION_ERR: case AIR_INST_UNWRAP_ERRUNION_ERR_PTR: { // ty_op: pair.a = error-set type, pair.b = `E!T` operand, or // `*(E!T)` for the pointer form. uint32_t err_ty = a; InterpValue eu = interpResolveRef(ctx, b); uint32_t eu_addr; uint32_t eu_ty; if (tag == AIR_INST_UNWRAP_ERRUNION_ERR_PTR) { eu_addr = interpAddressFromBits(eu.bits); eu_ty = interpPtrChild(ctx->ip, eu.ty); } else { eu_addr = interpAddressFromBits(eu.bits); eu_ty = eu.ty; } uint32_t poff, eoff; interpErrUnionOffsets(ctx->ip, eu_ty, &poff, &eoff); InterpValue out = { interpReadLE(interpAddressAdd(eu_addr, eoff), ZT_ERROR_ABI_SIZE), err_ty }; ctx->values[inst] = out; break; } case AIR_INST_IS_ERR: case AIR_INST_IS_NON_ERR: case AIR_INST_IS_ERR_PTR: case AIR_INST_IS_NON_ERR_PTR: { // un_op: pair.a = `E!T` operand, or `*(E!T)` for the pointer // forms. Read the error code (0 == ok). InterpValue eu = interpResolveRef(ctx, a); bool ptr_form = tag == AIR_INST_IS_ERR_PTR || tag == AIR_INST_IS_NON_ERR_PTR; uint32_t eu_addr; uint32_t eu_ty; if (ptr_form) { eu_addr = interpAddressFromBits(eu.bits); eu_ty = interpPtrChild(ctx->ip, eu.ty); } else { eu_addr = interpAddressFromBits(eu.bits); eu_ty = eu.ty; } uint32_t poff, eoff; interpErrUnionOffsets(ctx->ip, eu_ty, &poff, &eoff); uint64_t code = interpReadLE( interpAddressAdd(eu_addr, eoff), ZT_ERROR_ABI_SIZE); bool is_error = (code != 0); InterpValue out = { 0, ZIR_REF_BOOL_TYPE }; out.bits = (tag == AIR_INST_IS_ERR || tag == AIR_INST_IS_ERR_PTR) ? (is_error ? 1u : 0u) : (is_error ? 0u : 1u); ctx->values[inst] = out; break; } case AIR_INST_TRY: case AIR_INST_TRY_COLD: { // pl_op: pair.a = error-union operand, pair.b = Air.Try payload // extra index; extra[payload] = else_body_len, following words = // the else-body inst indices. Mirrors src/Air.zig `@"try"`: read // the operand's error code — if it is an error, run the else body // (guaranteed noreturn: it ends in a `ret` that propagates the // error union up the stack); otherwise the result is the unwrapped // payload, exactly as AIR_INST_UNWRAP_ERRUNION_PAYLOAD would // yield. uint32_t payload = b; uint32_t else_len = air->extra[payload]; const uint32_t* else_body = &air->extra[payload + 1]; InterpValue eu = interpResolveRef(ctx, a); uint32_t poff, eoff; interpErrUnionOffsets(ctx->ip, eu.ty, &poff, &eoff); uint64_t code = interpReadLE( interpAddressAdd(interpAddressFromBits(eu.bits), eoff), ZT_ERROR_ABI_SIZE); if (code != 0) { INTERP_ENTER(INTERP_BODY_BRANCH, inst, else_body, else_len); break; } uint32_t payload_ty = ipErrorUnionPayload(ctx->ip, eu.ty); ctx->values[inst] = interpLoadTyped(ctx->ip, interpAddressAdd(interpAddressFromBits(eu.bits), poff), payload_ty, &ctx->values[inst]); break; } case AIR_INST_TRY_PTR: case AIR_INST_TRY_PTR_COLD: { // ty_pl: pair.a = result `*payload` type, pair.b = Air.TryPtr // payload. extra[payload] = error-union pointer ref, // extra[payload+1] = else_body_len, then the else-body insts. // On success, return the payload address without loading it. uint32_t res_ty = a; uint32_t payload = b; uint32_t eu_ptr_ref = air->extra[payload]; uint32_t else_len = air->extra[payload + 1]; const uint32_t* else_body = &air->extra[payload + 2]; InterpValue eu_ptr = interpResolveRef(ctx, eu_ptr_ref); uint32_t eu_ty = interpPtrChild(ctx->ip, eu_ptr.ty); uint32_t poff, eoff; interpErrUnionOffsets(ctx->ip, eu_ty, &poff, &eoff); uint32_t eu_addr = interpAddressFromBits(eu_ptr.bits); uint64_t code = interpReadLE( interpAddressAdd(eu_addr, eoff), ZT_ERROR_ABI_SIZE); if (code != 0) { INTERP_ENTER(INTERP_BODY_BRANCH, inst, else_body, else_len); break; } InterpValue out = { interpAddressAdd(eu_addr, poff), res_ty }; ctx->values[inst] = out; break; } case AIR_INST_TAG_NAME: { // `@tagName(e)` on a runtime enum value (Sema.zirTagName -> // block.addUnOp(.tag_name, operand)): produce the tag's field-name // string as a `[:0]const u8` slice. un_op: pair.a = operand. The // enum's field_names are selected by the tag; the C backend // renders this as a runtime switch returning each name string // literal. We materialize the name bytes as a `[len:0]u8` array // (len bytes + a 0 sentinel) then the {ptr,len} slice struct in // the const arena, memoized per (enum_ty, field_idx). Explicit // enum values are reverse-mapped exactly; declaration index and // tag integer are intentionally independent. InterpValue ev = interpResolveRef(ctx, a); uint32_t enum_ty = ev.ty; if (!ipIsEnumType(ctx->ip, enum_ty)) UNREACHABLE("interp tag_name: operand not an enum type"); uint32_t field_idx = interpEnumFieldFromTagBits(ctx->ip, enum_ty, ev.bits); uint32_t slice_ty = ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE; uint32_t slice_addr = 0; for (uint32_t m = 0; m < g_interp.tagname_memo_len; m++) { if (g_interp.tagname_memo[m].enum_ty == enum_ty && g_interp.tagname_memo[m].field_idx == field_idx) { slice_addr = g_interp.tagname_memo[m].slice_addr; break; } } if (slice_addr == 0) { uint32_t name_str = ipEnumFieldName(ctx->ip, enum_ty, field_idx); const char* sb = ipStringBytes(ctx->ip, name_str); size_t host_len = strlen(sb); if (host_len > UINT32_MAX) UNIMPLEMENTED("tag_name: name exceeds wasm32"); uint32_t nlen = (uint32_t)host_len; uint32_t alloc_len; if (!interpCheckedU32Add(nlen, 1u, &alloc_len)) UNIMPLEMENTED("tag_name: sentinel length overflow"); uint32_t arr_addr = interpConstArenaAlloc(alloc_len, 1u); for (uint32_t i = 0; i < nlen; i++) interpWriteLE( interpAddressAdd(arr_addr, i), (uint8_t)sb[i], 1u); interpWriteLE( interpAddressAdd(arr_addr, nlen), 0u, 1u); // sentinel slice_addr = interpConstArenaAlloc(interpAbiSize(ctx->ip, slice_ty), interpAbiAlign(ctx->ip, slice_ty)); interpWriteLE(slice_addr, arr_addr, 4u); // ptr field interpWriteLE( interpAddressAdd(slice_addr, 4u), nlen, 4u); // len if (g_interp.tagname_memo_len == g_interp.tagname_memo_cap) { uint32_t need; if (!interpCheckedU32Add( g_interp.tagname_memo_len, 1u, &need)) UNIMPLEMENTED("tag_name: memo count overflow"); uint32_t nc; if (!interpCheckedGrowCapacity(g_interp.tagname_memo_cap, need, UINT32_MAX / (uint32_t)sizeof(TagNameMemo), &nc)) UNIMPLEMENTED("tag_name: memo capacity overflow"); size_t bytes = (size_t)nc * sizeof(TagNameMemo); g_interp.tagname_memo = zig1Realloc(g_interp.tagname_memo, bytes); g_interp.tagname_memo_cap = nc; } g_interp.tagname_memo[g_interp.tagname_memo_len].enum_ty = enum_ty; g_interp.tagname_memo[g_interp.tagname_memo_len].field_idx = field_idx; g_interp.tagname_memo[g_interp.tagname_memo_len].slice_addr = slice_addr; if (!interpCheckedU32Add(g_interp.tagname_memo_len, 1u, &g_interp.tagname_memo_len)) UNIMPLEMENTED("tag_name: memo length overflow"); } ctx->values[inst].bits = slice_addr; ctx->values[inst].ty = slice_ty; break; } case AIR_INST_INT_FROM_ERROR: case AIR_INST_ERROR_FROM_INT: // CodeGen.airIntFromError / airErrorFromInt: the error value is // its integer. ctx->values[inst] = (InterpValue) { interpResolveRef(ctx, b).bits, a }; break; case AIR_INST_ERROR_NAME: { // `@errorName(e)` on a runtime error value. Error values are // global error set values (interpErrorCode); materialize the // corresponding static `[:0]const u8` slice once per code. InterpValue ev = interpResolveRef(ctx, a); if (ev.bits == 0 || ev.bits > UINT16_MAX) UNREACHABLE("interp error_name: operand is not an error"); uint32_t slice_ty = ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE; uint32_t slice_addr = interpErrorNameSlice(ctx->ip, (uint32_t)ev.bits); ctx->values[inst].bits = slice_addr; ctx->values[inst].ty = slice_ty; break; } case AIR_INST_BIT_CAST: { // ty_op: pair.a = dest type, pair.b = operand. Reinterpret the // operand's bytes as the dest type (src/codegen/c.zig airBitCast: // memcpy min(srcAbi,dstAbi) bytes). For an aggregate dest the // value is just its arena address; for a scalar, keep the low // min(src,dst) bytes and re-normalize to the dest width. uint32_t dst_ty = a; InterpValue op = interpResolveRef(ctx, b); InterpValue out = { 0, dst_ty }; uint32_t bc_float_bits; InterpBitSeq sseq, dseq; bool src_packed = interpSeqIsBitPacked(ctx->ip, op.ty, &sseq); bool dst_packed = interpSeqIsBitPacked(ctx->ip, dst_ty, &dseq); if (src_packed || dst_packed) { // A byte copy cannot reinterpret these: see // interpSeqIsBitPacked. Two sequences that pack identically // are the exception -- an array and a vector of the same // element share both representations, so the byte copy below // is already exact and nothing here should touch it. bool same_shape = src_packed && dst_packed && sseq.len == dseq.len && sseq.elem_bits == dseq.elem_bits && sseq.elem_size == dseq.elem_size; if (!same_shape) { // Sema.bitCastVal's route: writeToPackedMemory of the // operand, readFromPackedMemory as the destination. A // sequence operand is read where it lives; a scalar is // stored to a slot first. uint32_t bc_bits; if (!interpSeqLeafBits(ctx->ip, op.ty, &bc_bits)) UNIMPLEMENTED( "bitcast: of a type with no packed layout"); uint32_t src_addr; if (src_packed || interpTypeIsAggregate(ctx->ip, op.ty)) { src_addr = interpAddressFromBits(op.bits); } else { src_addr = interpArenaAlloc(interpAbiSize(ctx->ip, op.ty), interpAbiAlign(ctx->ip, op.ty)); interpStoreTyped(ctx->ip, src_addr, op.ty, op); } uint32_t buf_len = (bc_bits + 7u) / 8u; uint8_t* buf = ARR_INIT(uint8_t, buf_len ? buf_len : 1u); memset(buf, 0, buf_len ? buf_len : 1u); interpWriteToPackedMemory( ctx->ip, src_addr, op.ty, buf, bc_bits, 0); uint32_t dst_addr = interpArenaAlloc(interpAbiSize(ctx->ip, dst_ty), interpAbiAlign(ctx->ip, dst_ty)); interpReadFromPackedMemory( ctx->ip, buf, bc_bits, 0, dst_ty, dst_addr); free(buf); ctx->values[inst] = interpLoadTyped(ctx->ip, dst_addr, dst_ty, NULL); break; } } if (ctx->ip->item_tags[dst_ty] == IP_TAG_TYPE_OPTIONAL && (ctx->ip->item_tags[op.ty] == IP_TAG_TYPE_POINTER || ztIsSlice(ctx->ip, op.ty))) { // Pointer/slice reinterpreted as an optional pointer/slice // (Sema.coerce's in-memory arm lowers `[*]align(A) u8` -> // `?[*]u8` returns to bit_cast — exact under the payload's // zero niche. Materialize bytes because a pointer InterpValue // holds the address directly while an optional InterpValue is // by-address in this interpreter. A fallback flagged // representation also receives its discriminator. InterpOptionalRepr orepr = interpOptionalRepr(ctx->ip, dst_ty); if (orepr != INTERP_OPT_REPR_PAYLOAD_NICHE && orepr != INTERP_OPT_REPR_PAYLOAD_FLAG) UNIMPLEMENTED("bitcast: to zero-size optional"); uint32_t ochild = ctx->ip->item_datas[dst_ty]; uint64_t nn = (ztIsSlice(ctx->ip, op.ty)) ? interpReadLE(interpAddressFromBits(op.bits), 4u) // slice's ptr field : op.bits; if (orepr == INTERP_OPT_REPR_PAYLOAD_NICHE && !interpTypeIsAggregate(ctx->ip, dst_ty)) { out.bits = nn; ctx->values[inst] = out; break; } uint32_t oaddr = interpArenaAlloc(interpAbiSize(ctx->ip, dst_ty), interpAbiAlign(ctx->ip, dst_ty)); interpStoreTyped(ctx->ip, oaddr, ochild, op); if (orepr == INTERP_OPT_REPR_PAYLOAD_FLAG) interpWriteLE(interpAddressAdd(oaddr, interpOptFlagOffset(ctx->ip, dst_ty)), (nn != 0) ? 1u : 0u, 1u); out.bits = oaddr; } else if (ctx->ip->item_tags[dst_ty] == IP_TAG_TYPE_POINTER && ctx->ip->item_tags[op.ty] == IP_TAG_TYPE_OPTIONAL) { // The reverse: an optional pointer reinterpreted as a plain // pointer (stock: niche-exact). The payload sits at offset 0 // of the optional's aggregate; read it rather than passing the // optional's own address through. InterpOptionalRepr orepr = interpOptionalRepr(ctx->ip, op.ty); if (orepr != INTERP_OPT_REPR_PAYLOAD_NICHE && orepr != INTERP_OPT_REPR_PAYLOAD_FLAG) UNIMPLEMENTED("bitcast: from zero-size optional"); out.bits = interpTypeIsAggregate(ctx->ip, op.ty) ? interpReadLE(interpAddressFromBits(op.bits), 4u) : op.bits; } else if (interpTypeIsErrUnion(ctx->ip, dst_ty) && !interpTypeIsErrUnion(ctx->ip, op.ty)) { // A bare payload value reinterpreted as an error union // (Sema.coerce's in-memory arm lowers a payload whose type // differs only by e.g. pointer alignment flags from E!T's // payload to bit_cast — exact in stock's EU ABI where the // payload occupies its slot directly; here the EU is a // {payload, code} arena aggregate, so materialize it with // code=0 like WRAP_ERRUNION_PAYLOAD. // allocBytesAligned's `return @alignCast(byte_ptr)` as // `Error![*]align(A) u8` is the driving case — the scalar // pointer value would otherwise be misread as the error // union's arena address by the caller's `try`.) uint32_t bpayload_ty = ipErrorUnionPayload(ctx->ip, dst_ty); uint32_t bpoff, beoff; interpErrUnionOffsets(ctx->ip, dst_ty, &bpoff, &beoff); uint32_t baddr = interpArenaAlloc(interpAbiSize(ctx->ip, dst_ty), interpAbiAlign(ctx->ip, dst_ty)); interpStoreTyped( ctx->ip, interpAddressAdd(baddr, bpoff), bpayload_ty, op); interpWriteLE( interpAddressAdd(baddr, beoff), 0u, ZT_ERROR_ABI_SIZE); out.bits = baddr; } else if (ipTypeIsBitpack(ctx->ip, op.ty) // A float's storage is its IEEE bits, so it bitcasts to and // from a packed type like an integer of the same width. && !interpTypeIsIntegerStorage(ctx->ip, dst_ty) && !interpTypeIsAggregate(ctx->ip, dst_ty) && !ztSimpleFloatBits(dst_ty, &bc_float_bits)) { UNIMPLEMENTED("bitcast: packed source to non-int storage"); } else if (ipTypeIsBitpack(ctx->ip, dst_ty) && !interpTypeIsIntegerStorage(ctx->ip, op.ty) && !interpTypeIsAggregate(ctx->ip, op.ty) && !ztSimpleFloatBits(op.ty, &bc_float_bits)) { UNIMPLEMENTED("bitcast: non-int storage to packed"); } else if (ctx->ip->item_tags[dst_ty] == IP_TAG_TYPE_POINTER && ztIsSlice(ctx->ip, op.ty)) { out.bits = interpReadLE(interpAddressFromBits(op.bits), 4u); } else if (interpTypeIsAggregate(ctx->ip, dst_ty) || interpTypeIsByRefScalar(ctx->ip, dst_ty)) { // `@bitCast` into anything that lives in memory is the byte // copy, and a by-ref scalar's bytes are already its image. uint32_t dbytes = interpAbiSize(ctx->ip, dst_ty); uint32_t daddr = interpByRefAlloc(ctx->ip, dst_ty, false); uint32_t sbytes = interpAbiSize(ctx->ip, op.ty); uint32_t nbytes = sbytes < dbytes ? sbytes : dbytes; if (nbytes != 0) { if (interpTypeIsAggregate(ctx->ip, op.ty) || interpTypeIsByRefScalar(ctx->ip, op.ty)) { interpArenaMove( daddr, interpAddressFromBits(op.bits), nbytes); } else if (nbytes <= 8u) { interpWriteLE(daddr, op.bits, nbytes); } else { UNIMPLEMENTED( "bitcast: by-value scalar wider than 64 bits"); } } out.bits = daddr; } else if (ctx->ip->item_tags[dst_ty] == IP_TAG_TYPE_POINTER) { out.bits = op.bits; } else { uint32_t sbytes = interpAbiSize(ctx->ip, op.ty); uint32_t dbytes = interpAbiSize(ctx->ip, dst_ty); // A memory-resident source (`@bitCast([2]u32)` to a // `packed struct(u64)`, Zir.getDeclaration) has no scalar // width; Sema already checked the bit sizes, and its bytes // are read below. if ((ipTypeIsBitpack(ctx->ip, op.ty) || ipTypeIsBitpack(ctx->ip, dst_ty)) && !interpTypeIsAggregate(ctx->ip, op.ty)) { uint32_t swidth, dwidth; bool ssigned, dsigned; interpTypeInfo(ctx->ip, op.ty, &swidth, &ssigned); interpTypeInfo(ctx->ip, dst_ty, &dwidth, &dsigned); if (swidth != dwidth) COMPILE_ERROR_UPSTREAM( "interp: bitcast packed size mismatch"); } // The destination is by value here, so `dbytes <= 8` and // the copy is one word. uint32_t nbytes = sbytes < dbytes ? sbytes : dbytes; uint64_t raw; if (interpTypeIsAggregate(ctx->ip, op.ty) || interpTypeIsByRefScalar(ctx->ip, op.ty)) { // A source that lives in memory is reinterpreted by // reading its bytes (`@bitCast(a[n..][0..4].*)` in // std.mem.eqlBytes reads the [4]u8 into a u32; codegen // memcpy semantics). raw = interpReadLE(interpAddressFromBits(op.bits), nbytes); } else { uint64_t mask = (nbytes >= 8) ? ~UINT64_C(0) : ((UINT64_C(1) << (nbytes * 8u)) - 1u); raw = op.bits & mask; } uint32_t width; bool is_signed; interpTypeInfo(ctx->ip, dst_ty, &width, &is_signed); out.bits = interpNormalize(raw, width, is_signed); } ctx->values[inst] = out; break; } case AIR_INST_MUL_ADD: { // pl_op: pair.a = addend, pair.b = extra index of Bin{mulend1, // mulend2}; a vector one lane at a time (`Air.Legalize`'s // `.scalarize_mul_add`), each lane `arithFloatMulAdd`. InterpValue addend = interpResolveRef(ctx, a); InterpValue mulend1 = interpResolveRef(ctx, air->extra[b]); InterpValue mulend2 = interpResolveRef(ctx, air->extra[b + 1]); bool is_vec = addend.ty < ctx->ip->items_len && ctx->ip->item_tags[addend.ty] == IP_TAG_TYPE_VECTOR; uint32_t lanes = 1, stride = 0, out_addr = 0; uint32_t lane_ty = addend.ty; if (is_vec) { InterpArrayInfo vi = interpArrayInfo(ctx->ip, addend.ty); lanes = vi.len; lane_ty = vi.child; stride = interpAbiSize(ctx->ip, lane_ty); out_addr = interpArenaAlloc(interpAbiSize(ctx->ip, addend.ty), interpAbiAlign(ctx->ip, addend.ty)); } uint32_t float_bits; if (!ztSimpleFloatBits(lane_ty, &float_bits)) UNREACHABLE("interp: mul_add on non-float"); for (uint32_t i = 0; i < lanes; i++) { InterpValue x = mulend1, y = mulend2, z = addend; if (is_vec) { uint32_t off = interpLayoutMul(i, stride); x = interpLoadTyped(ctx->ip, interpAddressAdd( interpAddressFromBits(mulend1.bits), off), lane_ty, NULL); y = interpLoadTyped(ctx->ip, interpAddressAdd( interpAddressFromBits(mulend2.bits), off), lane_ty, NULL); z = interpLoadTyped(ctx->ip, interpAddressAdd( interpAddressFromBits(addend.bits), off), lane_ty, NULL); } // `@mulAdd` rounds once, as comptime `Value.mulAdd` does; the // wasm backend's unfused `floatMulAdd` deviates from that // (see "Upstream divergences"). InterpValue r = { 0, lane_ty }; if (arithFloatMulAdd( float_bits, x.bits, y.bits, z.bits, &r.bits) != ARITH_OK) UNIMPLEMENTED("mul_add: has no exact carrier"); if (!is_vec) { ctx->values[inst] = r; break; } interpStoreTyped(ctx->ip, interpAddressAdd(out_addr, interpLayoutMul(i, stride)), lane_ty, r); } if (is_vec) ctx->values[inst] = (InterpValue) { out_addr, addend.ty }; break; } case AIR_INST_SELECT: { // pl_op: pair.a = predicate `@Vector(N, bool)`, pair.b = extra // index of Bin{lhs = a, rhs = b}; lane i is a[i] where pred[i], // else b[i] (`Air.Legalize`'s `.scalarize_select`). InterpValue pred = interpResolveRef(ctx, a); InterpValue sa = interpResolveRef(ctx, air->extra[b]); InterpValue sb = interpResolveRef(ctx, air->extra[b + 1]); InterpArrayInfo pv = interpArrayInfo(ctx->ip, pred.ty); InterpArrayInfo av = interpArrayInfo(ctx->ip, sa.ty); uint32_t pstride = interpAbiSize(ctx->ip, pv.child); uint32_t astride = interpAbiSize(ctx->ip, av.child); if (pv.len != av.len || sa.ty != sb.ty || astride == 0) UNIMPLEMENTED("select: select operand shapes"); uint32_t out_addr = interpArenaAlloc( interpAbiSize(ctx->ip, sa.ty), interpAbiAlign(ctx->ip, sa.ty)); for (uint32_t i = 0; i < av.len; i++) { InterpValue p = interpLoadTyped(ctx->ip, interpAddressAdd(interpAddressFromBits(pred.bits), interpLayoutMul(i, pstride)), pv.child, NULL); InterpValue src = p.bits != 0 ? sa : sb; InterpValue lane = interpLoadTyped(ctx->ip, interpAddressAdd(interpAddressFromBits(src.bits), interpLayoutMul(i, astride)), av.child, NULL); interpStoreTyped(ctx->ip, interpAddressAdd(out_addr, interpLayoutMul(i, astride)), av.child, lane); } ctx->values[inst] = (InterpValue) { out_addr, sa.ty }; break; } case AIR_INST_ERROR_CAST: { // ty_op: pair.a = destination error set, pair.b = error operand. // Error-set values are dense u16 codes in this interpreter; the // cast only narrows the static type after Sema has proven the // value belongs to the destination set. InterpValue op = interpResolveRef(ctx, b); InterpValue out = { op.bits, a }; ctx->values[inst] = out; break; } case AIR_INST_SPLAT: { // ty_op: pair.a = the vector or array type, pair.b = the // scalar, stored into every element; an array's sentinel is its // type's (`Air.Inst.Tag.splat`). InterpArrayInfo spl = interpArrayInfo(ctx->ip, a); InterpValue lane = interpResolveRef(ctx, b); uint32_t stride = interpAbiSize(ctx->ip, spl.child); uint32_t out_addr = interpArenaAlloc( interpAbiSize(ctx->ip, a), interpAbiAlign(ctx->ip, a)); for (uint32_t i = 0; i < spl.len; i++) interpStoreTyped(ctx->ip, interpAddressAdd(out_addr, interpLayoutMul(i, stride)), spl.child, lane); if (spl.sentinel != IP_INDEX_NONE) interpStoreTyped(ctx->ip, interpAddressAdd( out_addr, interpLayoutMul(spl.len, stride)), spl.child, interpDecodeConst(ctx->ip, spl.sentinel)); ctx->values[inst].bits = out_addr; ctx->values[inst].ty = a; break; } case AIR_INST_INT_CAST: case AIR_INST_INT_FROM_PTR: case AIR_INST_TRUNC: case AIR_INST_FLOAT_FROM_INT: case AIR_INST_INT_FROM_FLOAT: case AIR_INST_INT_FROM_FLOAT_OPTIMIZED: case AIR_INST_INT_FROM_FLOAT_SAFE: case AIR_INST_INT_FROM_FLOAT_OPTIMIZED_SAFE: case AIR_INST_FPTRUNC: case AIR_INST_FPEXT: { // ty_op: pair.a = dest type, pair.b = operand. `@intCast` (and // the ReleaseSmall runtime `@intCast`/coercion lowerings that // emit int_cast rather than int_cast_safe) produces the same // integer value in the destination type. `@truncate`'s // `Mutable.truncate` to a width no wider than the operand's is // that same cast: reading the operand signed or unsigned differs // by 2^width(op), which the truncation drops. A vector destination // is the elementwise form of the very same instruction // (`Air.Legalize`'s `.scalarize_int_cast`, `.scalarize_fpext`, // ...). InterpValue op = interpResolveRef(ctx, b); ctx->values[inst] = ipItemTagIs(ctx->ip, a, IP_TAG_TYPE_VECTOR) ? interpScalarizeUnOp(ctx->ip, tag, a, op) : interpUnOpLane(ctx->ip, tag, a, op); break; } case AIR_INST_UNREACH: // no_op terminator emitted for a ReleaseSmall `unreachable` // (Sema.analyzeUnreachable's non-safety-check arm — e.g. // std.debug.assert's `if (!ok) unreachable;`). interpTrap("unreachable code"); case AIR_INST_TRAP: interpTrap("@trap"); case AIR_INST_ADD_WITH_OVERFLOW: case AIR_INST_SUB_WITH_OVERFLOW: case AIR_INST_MUL_WITH_OVERFLOW: case AIR_INST_SHL_WITH_OVERFLOW: { // ty_pl: pair.a = the result tuple type // `Zcu.PerThread.overflowArithmeticTupleType(operand_ty)`, pair.b // = Bin payload { lhs, rhs }. Materialize the tuple into an arena // slot (fields at their layout offsets) and yield its address — // matching every other by-address aggregate InterpValue. // // A vector operand is the same operation per lane, into the two // vector halves of that tuple: `Air.Legalize`'s // `scalarizeOverflowBlockPayload` lowers `@Vector(N, T)` overflow // arithmetic to a loop whose body is // `"???_with_overflow(struct { Int, u1 }, %9, %10)"` on the two // `legalize_vec_elem_val` lanes, storing field 0 into // `struct_field_ptr_index_0` and field 1 into // `struct_field_ptr_index_1`. The tuple's own field types say // which shape this is, since upstream builds them together. uint32_t ovf_tuple = a; uint32_t ovf_payload = b; InterpValue ovf_l = interpResolveRef(ctx, air->extra[ovf_payload]); InterpValue ovf_r = interpResolveRef(ctx, air->extra[ovf_payload + 1]); uint32_t ovf_int_ty = ipTupleFieldType(ctx->ip, ovf_tuple, 0); uint32_t ovf_bit_ty = ipTupleFieldType(ctx->ip, ovf_tuple, 1); if (ovf_int_ty == IP_INDEX_NONE || ovf_bit_ty == IP_INDEX_NONE) UNIMPLEMENTED( "shl_with_overflow: overflow arithmetic result type"); uint32_t ovf_addr = interpArenaAlloc(interpAbiSize(ctx->ip, ovf_tuple), interpAbiAlign(ctx->ip, ovf_tuple)); uint32_t ovf_int_at = interpAddressAdd( ovf_addr, interpStructFieldOffset(ctx->ip, ovf_tuple, 0)); uint32_t ovf_bit_at = interpAddressAdd( ovf_addr, interpStructFieldOffset(ctx->ip, ovf_tuple, 1)); if (ctx->ip->item_tags[ovf_int_ty] == IP_TAG_TYPE_VECTOR) { InterpArrayInfo ovf_vi = interpArrayInfo(ctx->ip, ovf_int_ty); InterpArrayInfo ovf_vb = interpArrayInfo(ctx->ip, ovf_bit_ty); uint32_t ovf_is = interpAbiSize(ctx->ip, ovf_vi.child); uint32_t ovf_bs = interpAbiSize(ctx->ip, ovf_vb.child); uint32_t ovf_la = interpAddressFromBits(ovf_l.bits); uint32_t ovf_ra = interpAddressFromBits(ovf_r.bits); for (uint32_t i = 0; i < ovf_vi.len; i++) { uint32_t off = interpLayoutMul(i, ovf_is); InterpValue le = interpLoadTyped(ctx->ip, interpAddressAdd(ovf_la, off), ovf_vi.child, NULL); InterpValue re = interpLoadTyped(ctx->ip, interpAddressAdd(ovf_ra, off), ovf_vi.child, NULL); InterpValue lane_wrapped; uint32_t lane_bit; interpWithOverflowScalar( ctx->ip, tag, le, re, &lane_wrapped, &lane_bit); interpStoreTyped(ctx->ip, interpAddressAdd(ovf_int_at, off), ovf_vi.child, lane_wrapped); InterpValue lane_bit_val = { lane_bit, ovf_vb.child }; interpStoreTyped(ctx->ip, interpAddressAdd( ovf_bit_at, interpLayoutMul(i, ovf_bs)), ovf_vb.child, lane_bit_val); } } else { InterpValue wrapped; uint32_t ovf_bit; interpWithOverflowScalar( ctx->ip, tag, ovf_l, ovf_r, &wrapped, &ovf_bit); interpStoreTyped(ctx->ip, ovf_int_at, ovf_int_ty, wrapped); InterpValue ovf_bit_val = { ovf_bit, ovf_bit_ty }; interpStoreTyped(ctx->ip, ovf_bit_at, ovf_bit_ty, ovf_bit_val); } InterpValue ovf_out = { ovf_addr, ovf_tuple }; ctx->values[inst] = ovf_out; break; } case AIR_INST_MEMSET: { // bin_op: pair.a = dest slice/array-ptr, pair.b = element value. // An undefined element value leaves the region undefined — // "ignoring this instruction is legal lowering" (src/Air.zig // memset doc) — so it is a no-op here. A defined element value // fill is not yet modeled (no reached shape); abort loudly. uint32_t ms_val_ref = air->inst_datas[inst].pair.b; if (ms_val_ref < AIR_REF_INST_BASE && ipIsUndef(ctx->ip, ms_val_ref)) break; // legal no-op // Defined-value fill: `@memset(dest, elem)` writes `elem` into // every element of `dest`. dest is either a slice value // ({ptr@0,len@4} aggregate) or a single-pointer to an array // `*[N]T`; resolve its base address, element count, and element // size, then splat the scalar element bits across the region. // (src/Air.zig memset; codegen/c.zig lowers to a per-element store // loop / memset.) InterpValue ms_dest = interpResolveRef(ctx, air->inst_datas[inst].pair.a); InterpValue ms_val = interpResolveRef(ctx, ms_val_ref); uint32_t ms_base, ms_count, ms_elem; if (ztIsSlice(ctx->ip, ms_dest.ty)) { uint32_t ms_daddr = interpAddressFromBits(ms_dest.bits); ms_base = (uint32_t)interpReadLE(ms_daddr, 4); ms_count = (uint32_t)interpReadLE( interpAddressAdd(ms_daddr, 4u), 4); uint32_t ms_many = ctx->ip->item_datas[ms_dest.ty]; ms_elem = interpPtrChild(ctx->ip, ms_many); } else if (ctx->ip->item_tags[ms_dest.ty] == IP_TAG_TYPE_POINTER) { // `*[N]T`: dest.bits is the array address; count/elem from the // pointee array type. ms_base = interpAddressFromBits(ms_dest.bits); uint32_t ms_arr = interpPtrChild(ctx->ip, ms_dest.ty); if (ctx->ip->item_tags[ms_arr] != IP_TAG_TYPE_ARRAY_BIG && ctx->ip->item_tags[ms_arr] != IP_TAG_TYPE_ARRAY_SMALL) UNIMPLEMENTED("memset: ptr dest not to an array"); InterpArrayInfo array = interpArrayInfo(ctx->ip, ms_arr); ms_count = array.len; ms_elem = array.child; } else { UNIMPLEMENTED("memset: dest neither slice nor array ptr"); } if (ms_count != 0 && interpAddressIsConstArena(ms_base)) UNIMPLEMENTED("memset: dest into const arena"); uint32_t ms_esz = interpAbiSize(ctx->ip, ms_elem); if (ms_esz == 0) break; // zero-bit element: nothing to write if (interpTypeIsAggregate(ctx->ip, ms_elem)) { for (uint32_t ms_i = 0; ms_i < ms_count; ms_i++) interpStoreTyped(ctx->ip, interpAddressIndex(ms_base, ms_i, ms_esz), ms_elem, ms_val); break; } if (ms_esz > 8) UNIMPLEMENTED("memset: element wider than 8 bytes"); for (uint32_t ms_i = 0; ms_i < ms_count; ms_i++) interpWriteLE(interpAddressIndex(ms_base, ms_i, ms_esz), ms_val.bits, ms_esz); break; } case AIR_INST_MEMCPY: case AIR_INST_MEMMOVE: { // bin_op: pair.a is either a destination slice value ({ptr,len} // aggregate) or a runtime one-pointer to a matching array. pair.b // is a raw source pointer; the front-end emitted slice_ptr when // the source operand was a slice. Copy `len * abiSize(elem)` // bytes. AIR memmove uses the same shape; C memmove preserves // overlapping-range behavior for both tags. InterpValue mc_dest = interpResolveRef(ctx, air->inst_datas[inst].pair.a); InterpValue mc_src = interpResolveRef(ctx, air->inst_datas[inst].pair.b); uint32_t mc_dptr; uint32_t mc_dlen; uint32_t mc_elem; if (ztIsSlice(ctx->ip, mc_dest.ty)) { uint32_t mc_daddr = interpAddressFromBits(mc_dest.bits); mc_dptr = (uint32_t)interpReadLE(mc_daddr, 4); mc_dlen = (uint32_t)interpReadLE( interpAddressAdd(mc_daddr, 4u), 4); uint32_t mc_many = ctx->ip->item_datas[mc_dest.ty]; mc_elem = interpPtrChild(ctx->ip, mc_many); } else if (ctx->ip->item_tags[mc_dest.ty] == IP_TAG_TYPE_POINTER) { uint32_t mc_arr = interpPtrChild(ctx->ip, mc_dest.ty); if (ctx->ip->item_tags[mc_arr] != IP_TAG_TYPE_ARRAY_BIG && ctx->ip->item_tags[mc_arr] != IP_TAG_TYPE_ARRAY_SMALL) UNIMPLEMENTED( "memcpy: memcpy/memmove ptr dest not to an array"); InterpArrayInfo mc_array = interpArrayInfo(ctx->ip, mc_arr); // The source may be a slice or many-pointer whose length Sema // checked; zirMemcpy's coerceInMemoryAllowed admits a // differing element type of the same layout. if (ctx->ip->item_tags[mc_src.ty] == IP_TAG_TYPE_POINTER) { uint32_t mc_src_arr = interpPtrChild(ctx->ip, mc_src.ty); if (ctx->ip->item_tags[mc_src_arr] == IP_TAG_TYPE_ARRAY_BIG || ctx->ip->item_tags[mc_src_arr] == IP_TAG_TYPE_ARRAY_SMALL) { InterpArrayInfo mc_src_array = interpArrayInfo(ctx->ip, mc_src_arr); if (mc_array.len != mc_src_array.len || interpAbiSize(ctx->ip, mc_array.child) != interpAbiSize(ctx->ip, mc_src_array.child)) UNREACHABLE( "interp: memcpy operands differ in layout"); } } mc_dptr = interpAddressFromBits(mc_dest.bits); mc_dlen = mc_array.len; mc_elem = mc_array.child; } else { UNIMPLEMENTED( "memcpy: memcpy/memmove dest neither slice nor array ptr"); } uint32_t mc_esz = interpAbiSize(ctx->ip, mc_elem); uint32_t mc_saddr = interpAddressFromBits(mc_src.bits); // A slice source (zirArrayCat's memcpy hotpath passes the operand // itself): its pointer half. if (ztIsSlice(ctx->ip, mc_src.ty)) mc_saddr = (uint32_t)interpReadLE(mc_saddr, 4); if (mc_dlen != 0 && interpAddressIsConstArena(mc_dptr)) // @memcpy into a const-arena buffer would corrupt a shared // cached const (write-through-const = UB); abort loudly like // the runtime-store guard. The SRC may legitimately be a // const. UNIMPLEMENTED("memcpy: memcpy/memmove dest into const arena"); if (mc_dlen != 0) { // src may be a many-pointer into a materialized const (e.g. // @memcpy/@memmove from a const array); dest is a runtime // slice buffer. uint32_t mc_bytes; if (!interpCheckedU32Mul(mc_dlen, mc_esz, &mc_bytes)) UNIMPLEMENTED( "memcpy: memcpy/memmove byte length overflow"); interpArenaMove(mc_dptr, mc_saddr, mc_bytes); } break; } case AIR_INST_RET_ADDR: { // `@returnAddress()`: no interpreter analog of a machine return // address -- yield 0 (documented as a legal "unsupported target" // value), typed usize. Only threaded through as the allocator // ret_addr bookkeeping parameter. InterpValue out = { 0, ZIR_REF_USIZE_TYPE }; ctx->values[inst] = out; break; } case AIR_INST_CLZ: case AIR_INST_CTZ: case AIR_INST_POPCOUNT: { // ty_op: pair.a = result int type (smallestUnsignedInt of the // operand's bit width), pair.b = operand. Bit-count builtins over // the operand's value within its own bit width: @clz counts // leading zero bits, @ctz trailing zero bits (both = the width for // a zero operand), @popCount the set bits. (src/Sema.zig // zirBitCount -> Air clz/ctz/popcount; src/codegen/c.zig -> // zig_clz/zig_ctz/zig_popcount.) `Alignment.fromByteUnits`'s // `@ctz(byte_units)` reaches this for a runtime usize operand. InterpValue op = interpResolveRef(ctx, b); ctx->values[inst] = ipItemTagIs(ctx->ip, a, IP_TAG_TYPE_VECTOR) ? interpScalarizeUnOp(ctx->ip, tag, a, op) : interpBitCountScalar(ctx->ip, tag, a, op); break; } case AIR_INST_ARRAY_ELEM_VAL: { // bin_op: pair.a = array value, pair.b = element index (usize). // The array operand is a comptime aggregate resolved into the // arena (InterpValue.bits = its address); read the element at // base + index*elem_size. (src/Sema.zig elemValArray -> // Air `array_elem_val`; result type = the array's element type.) InterpValue arr = interpResolveRef(ctx, a); InterpValue idx = interpResolveRef(ctx, b); if (ztIsTuple(ctx->ip, arr.ty)) { if (idx.bits > UINT32_MAX) UNIMPLEMENTED( "array_elem_val: tuple elem index exceeds wasm32"); uint32_t elem_idx = (uint32_t)idx.bits; if (elem_idx >= ipTupleFieldCount(ctx->ip, arr.ty)) UNIMPLEMENTED( "array_elem_val: tuple elem index out of range"); uint32_t field_val = ipTupleFieldValue(ctx->ip, arr.ty, elem_idx); if (field_val != IP_INDEX_NONE) { ctx->values[inst] = interpDecodeConst(ctx->ip, field_val); break; } uint32_t field_ty = ipTupleFieldType(ctx->ip, arr.ty, elem_idx); ctx->values[inst] = interpLoadTyped(ctx->ip, interpAddressAdd(interpAddressFromBits(arr.bits), interpStructFieldOffset(ctx->ip, arr.ty, elem_idx)), field_ty, &ctx->values[inst]); break; } InterpArrayInfo array = interpArrayInfo(ctx->ip, arr.ty); uint32_t esize = interpAbiSize(ctx->ip, array.child); uint32_t elem_addr = interpAddressIndex(interpAddressFromBits(arr.bits), interpAddressFromBits(idx.bits), esize); if (!interpArenaRangeValid(elem_addr, esize)) { fprintf(stderr, "interp: array_elem_val oob arr_ty=%u arr_bits=%llu " "idx_ty=%u idx_bits=%llu child=%u esize=%u elem_addr=%u\n", arr.ty, (unsigned long long)arr.bits, idx.ty, (unsigned long long)idx.bits, array.child, esize, elem_addr); UNIMPLEMENTED("array_elem_val: range out of bounds"); } ctx->values[inst] = interpLoadTyped( ctx->ip, elem_addr, array.child, &ctx->values[inst]); break; } case AIR_INST_PTR_ADD: { // ty_pl: pair.a = result pointer type (many-ptr `[*]T`), pair.b = // Bin payload index { ptr, offset }. The offset is in element-type // units. Result = base + offset * @sizeOf(T). (src/Sema.zig // analyzePtrArithmetic -> Air `ptr_add`.) uint32_t res_ty = a; uint32_t payload = b; InterpValue ptr = interpResolveRef(ctx, air->extra[payload]); InterpValue off = interpResolveRef(ctx, air->extra[payload + 1]); uint32_t esize = interpAbiSize(ctx->ip, interpPtrChild(ctx->ip, res_ty)); InterpValue out = { interpAddressIndex(interpAddressFromBits(ptr.bits), interpAddressFromBits(off.bits), esize), res_ty }; ctx->values[inst] = out; break; } case AIR_INST_PTR_SUB: { // ty_pl: pair.a = result pointer type, pair.b = Bin payload index // { ptr, offset }. Result = base - offset * @sizeOf(T) (element // units), mirroring PTR_ADD (HashMap's `self.header()` computes // `@as([*]Header, ...) - 1`). (src/Sema.zig analyzePtrArithmetic.) uint32_t res_ty = a; uint32_t payload = b; InterpValue ptr = interpResolveRef(ctx, air->extra[payload]); InterpValue off = interpResolveRef(ctx, air->extra[payload + 1]); uint32_t esize = interpAbiSize(ctx->ip, interpPtrChild(ctx->ip, res_ty)); uint32_t byte_off; if (!interpCheckedU32Mul( interpAddressFromBits(off.bits), esize, &byte_off)) UNIMPLEMENTED("ptr_sub: pointer element offset overflow"); InterpValue out = { interpAddressSub( interpAddressFromBits(ptr.bits), byte_off), res_ty }; ctx->values[inst] = out; break; } case AIR_INST_SLICE: { // ty_pl: pair.a = slice type, pair.b = Bin payload index // { ptr, len }. Assemble a runtime slice value `{ ptr@0, len@4 }` // in the arena (wasm32 layout, matching IP_TAG_PTR_SLICE + the // slice_len/slice_elem_val readers). (src/Sema.zig analyzeSlice -> // Air `slice`.) uint32_t slice_ty = a; uint32_t payload = b; InterpValue ptr = interpResolveRef(ctx, air->extra[payload]); InterpValue len = interpResolveRef(ctx, air->extra[payload + 1]); uint32_t saddr = interpArenaAlloc(interpAbiSize(ctx->ip, slice_ty), interpAbiAlign(ctx->ip, slice_ty)); interpWriteLE(saddr, ptr.bits, 4); // ptr field interpWriteLE(interpAddressAdd(saddr, 4u), len.bits, 4); InterpValue out = { saddr, slice_ty }; ctx->values[inst] = out; break; } case AIR_INST_SLICE_LEN: { // ty_op: pair.a = result type (usize), pair.b = slice operand. // Read the slice struct's `len` field (offset 4) from the arena. // (src/Sema.zig analyzeSliceLen -> Air `slice_len`.) InterpValue sl = interpResolveRef(ctx, b); InterpValue r = { interpReadLE( interpAddressAdd(interpAddressFromBits(sl.bits), 4u), 4), a }; ctx->values[inst] = r; break; } case AIR_INST_ARRAY_TO_VECTOR: { // ty_op: pair.a = vector type, pair.b = the array operand // (Sema.coerceArrayLike with matching element types). Both lay // their elements out at the element ABI stride, so the vector's // bytes are the array's first ones; a sentinel array's trailing // sentinel is not copied. uint32_t vec_ty = a; InterpValue arr = interpResolveRef(ctx, b); uint32_t vec_size = interpAbiSize(ctx->ip, vec_ty); uint32_t addr = interpArenaAlloc(vec_size, interpAbiAlign(ctx->ip, vec_ty)); interpArenaMove(addr, interpAddressFromBits(arr.bits), vec_size); InterpValue out = { addr, vec_ty }; ctx->values[inst] = out; break; } case AIR_INST_ARRAY_TO_SLICE: { // ty_op: pair.a = slice type, pair.b = `*[N]T` array pointer // operand. Build the 2-field slice struct in the arena: the `ptr` // field (offset 0) = the array's address (the operand pointer), // the `len` field (offset 4) = the array length N (from the // pointer's pointee array type). (src/Sema.zig // coerceArrayPtrToSlice -> Air `array_to_slice`.) uint32_t slice_ty = a; InterpValue ptr = interpResolveRef(ctx, b); uint32_t arr_ty = interpPtrChild(ctx->ip, ptr.ty); InterpArrayInfo array = interpArrayInfo(ctx->ip, arr_ty); uint32_t saddr = interpArenaAlloc(interpAbiSize(ctx->ip, slice_ty), interpAbiAlign(ctx->ip, slice_ty)); interpWriteLE(saddr, ptr.bits, 4); // ptr field = array addr interpWriteLE(interpAddressAdd(saddr, 4u), array.len, 4); InterpValue out = { saddr, slice_ty }; ctx->values[inst] = out; break; } case AIR_INST_SLICE_PTR: { // ty_op: pair.a = result type (many-ptr `[*]T`), pair.b = slice // operand. Read the slice struct's `ptr` field (offset 0) from the // arena. (src/Sema.zig analyzeSlicePtr -> Air `slice_ptr`.) InterpValue sl = interpResolveRef(ctx, b); InterpValue r = { interpReadLE(interpAddressFromBits(sl.bits), 4), a }; ctx->values[inst] = r; break; } case AIR_INST_SLICE_ELEM_PTR: { // ty_pl: pair.a = result pointer type (`*const T`), pair.b = // payload extra index (Air.Bin {slice_val, index}). Read the // slice's `ptr` field (offset 0) then add index*elem_size — // same address arithmetic as SLICE_ELEM_VAL below, but yielding // the pointer instead of loading through it. (src/Sema.zig // elemPtrSlice -> Air `slice_elem_ptr`.) uint32_t res_ty = a; uint32_t payload = b; InterpValue sl = interpResolveRef(ctx, air->extra[payload]); InterpValue idx = interpResolveRef(ctx, air->extra[payload + 1]); uint32_t child = interpPtrChild(ctx->ip, res_ty); uint32_t esize = interpAbiSize(ctx->ip, child); uint32_t base = (uint32_t)interpReadLE(interpAddressFromBits(sl.bits), 4); InterpValue out = { interpAddressIndex(base, interpAddressFromBits(idx.bits), esize), res_ty }; ctx->values[inst] = out; break; } case AIR_INST_SLICE_ELEM_VAL: { // bin_op: pair.a = slice value, pair.b = element index (usize). // Read the slice's `ptr` field (offset 0), then load the element // at base + index*elem_size. Element type = the slice's // many-pointer child. (src/Sema.zig elemVal(slice) -> Air // `slice_elem_val`.) InterpValue sl = interpResolveRef(ctx, a); InterpValue idx = interpResolveRef(ctx, b); uint32_t many = ctx->ip->item_datas[sl.ty]; // [*]T type uint32_t child = ctx->ip->extra[ctx->ip->item_datas[many]]; // T uint32_t esize = interpAbiSize(ctx->ip, child); uint32_t base = (uint32_t)interpReadLE(interpAddressFromBits(sl.bits), 4); uint32_t elem_addr = interpAddressIndex( base, interpAddressFromBits(idx.bits), esize); ctx->values[inst] = interpLoadTyped( ctx->ip, elem_addr, child, &ctx->values[inst]); break; } case AIR_INST_PTR_ELEM_VAL: { // bin_op: pair.a = many or C pointer, pair.b = element index // (usize): the element at base + index*elem_size. (src/Sema.zig // elemVal's `.many, .c` arm -> Air `ptr_elem_val`.) InterpValue ptr = interpResolveRef(ctx, a); InterpValue idx = interpResolveRef(ctx, b); uint32_t child = interpPtrChild(ctx->ip, ptr.ty); uint32_t elem_addr = interpAddressIndex(interpAddressFromBits(ptr.bits), interpAddressFromBits(idx.bits), interpAbiSize(ctx->ip, child)); ctx->values[inst] = interpLoadTyped( ctx->ip, elem_addr, child, &ctx->values[inst]); break; } case AIR_INST_WASM_MEMORY_SIZE: { // pl_op: pair.a = .none, pair.b = memory index. zig1 models the // bootstrap target's single wasm memory only. if (b != 0) UNIMPLEMENTED("wasm_memory_size: nonzero memory index"); InterpValue out = { interpWasmMemorySizePages(), ZIR_REF_USIZE_TYPE }; ctx->values[inst] = out; break; } case AIR_INST_WASM_MEMORY_GROW: { // pl_op: pair.a = delta pages, pair.b = memory index. Return old // page count, or -1 on growth failure, matching wasm memory.grow. if (b != 0) UNIMPLEMENTED("wasm_memory_grow: nonzero memory index"); InterpValue delta = interpResolveRef(ctx, a); if (delta.bits > UINT32_MAX) UNIMPLEMENTED("wasm_memory_grow: delta too wide"); uint32_t old_pages = interpWasmMemoryGrow((uint32_t)delta.bits); InterpValue out = { old_pages, ZIR_REF_ISIZE_TYPE }; ctx->values[inst] = out; break; } default: // Name the missing tag in the abort so the next work item is // identifiable straight from the failure output. fprintf(stderr, "interp: unhandled AIR tag %u\n", (unsigned)tag); UNIMPLEMENTED("Air.Inst.Tag: unhandled AIR instruction tag"); } continue; flow_out: g_interp.bodies.arr[g_interp.bodies.len - 1u].k = k; if (interpFlowOut(fl, flow_target, flow_val, &root_result)) return root_result; INTERP_LOAD_FRAME(); } #undef INTERP_LOAD_FRAME #undef INTERP_ENTER } // Run the program's entry function at top level with interned-constant // arguments. Mirrors the provenance of lib/std/start.zig `callMain`: on // wasm32-wasi the `std.process.Init.Minimal` argument is fully // comptime-known, so the driver hands the interned constant and the // interpreter materializes it inside the fresh program (a caller cannot // pre-materialize: the top-level program reset would discard the bytes). static InterpValue interpRunEntry(const SemaFuncAir* fn, const SemaFuncAirList* all, InternPool* ip, const uint32_t* arg_consts, uint32_t nargs) { // Top-level only: this is the program boundary (start.zig's callMain). if (g_interp.frames.len != 0) UNREACHABLE("interpRunEntry: nested entry run"); // Reset first: the entry constants materialize into the fresh program. interpProgramReset(); InterpValue args[4]; if (nargs > sizeof(args) / sizeof(args[0])) UNIMPLEMENTED("wasm32: too many entry arguments"); for (uint32_t i = 0; i < nargs; i++) args[i] = interpDecodeConst(ip, arg_consts[i]); return interpExecute(fn, all, ip, args, nargs); } // Map the entry function's result to a process exit code, mirroring // lib/std/start.zig `wrapMain` (start.zig): `void` -> 0, `u8` -> the // value, an error union -> `std.log.err("{t}", .{err})` ("error: \n" // on stderr; the error-return-trace dump is not modeled) then 1, otherwise // the unwrapped `void`/`u8` payload. Any other result type is upstream's // `bad_main_ret` compile error — aborts loudly. static int interpWrapMainResult(InternPool* ip, InterpValue result) { // start.zig wrapMain (start.zig): switch (ReturnType) { void => 0, // u8 => result, ... } then the error-union unwrap with the same payload // mapping. `noreturn` cannot reach here (the body would not return). if (result.ty == ZIR_REF_VOID_TYPE) return 0; if (result.ty == ZIR_REF_U8_TYPE) return (int)(result.bits & 0xFFu); if (interpTypeIsErrUnion(ip, result.ty)) { if (interpErrUnionFullyComptime(ip, result.ty)) // A fully-comptime `E!T` carries no runtime error code; nothing // upstream-shaped returns one from main. UNIMPLEMENTED("start.callMain: fully-comptime error-union result"); uint32_t poff, eoff; interpErrUnionOffsets(ip, result.ty, &poff, &eoff); uint64_t code = interpReadLE( interpAddressAdd(interpAddressFromBits(result.bits), eoff), ZT_ERROR_ABI_SIZE); if (code != 0) { // `result catch |err| { std.log.err("{t}", .{err}); ...; return // 1; }` — the default log renders "error: \n" on stderr // (std.log.defaultLog: level text + ": " + message). The // error-return-trace dump needs trace machinery not modeled by // the interpreter. if (code - 1u >= ip->global_error_names_len) UNIMPLEMENTED("start.callMain: error code out of range"); fprintf(stderr, "error: %s\n", ipStringBytes(ip, ip->global_error_names[code - 1u])); return 1; } uint32_t payload_ty = ipErrorUnionPayload(ip, result.ty); if (payload_ty == ZIR_REF_VOID_TYPE) return 0; if (payload_ty == ZIR_REF_U8_TYPE) return (int)interpReadLE( interpAddressAdd(interpAddressFromBits(result.bits), poff), 1u); // wrapMain's final switch: only void/u8 payloads are valid. UNIMPLEMENTED("start.callMain: unsupported error-union payload type"); } // Upstream this is the `bad_main_ret` compile error. UNIMPLEMENTED("start.callMain: unsupported main return type"); ZIG1_TCC_RETURN(0); } // The entry's run up to its exit code, or to the program's proc_exit. static int interpRunGuarded(const SemaFuncAir* entry, const SemaFuncAirList* funcs, InternPool* ip, const uint32_t* entry_args, uint32_t entry_nargs) { const uint32_t frame_mark = g_interp.arena_len; if (setjmp(g_interp.exit_jmp) != 0) { // proc_exit unwound every activation without its own release: the // frame arena's mark and the value stack are put back as // the activations would have left them. Descriptors the program left // open stay open, as after a return. g_interp.arena_len = frame_mark; g_interp.value_chunks.len = 0; g_interp.frames.len = 0; g_interp.bodies.len = 0; return g_interp.exit_status; } InterpValue result = interpRunEntry(entry, funcs, ip, entry_args, entry_nargs); return interpWrapMainResult(ip, result); } int interpRunMain(InternPool* ip, const SemaFuncAirList* funcs, const SemaFuncAir* entry, const uint32_t* entry_args, uint32_t entry_nargs, const InterpHost* host) { if (host->argc != 0 && host->argv == NULL) UNIMPLEMENTED("wasm32: nonzero argc without argv"); g_interp.exports = host->exports; g_interp.exports_len = host->exports_len; g_interp.wasi.argv = host->argv; g_interp.wasi.argc = host->argc; g_interp.wasi.lib_dir = host->lib_dir; g_interp.wasi.global_cache = host->global_cache; int rc = interpRunGuarded(entry, funcs, ip, entry_args, entry_nargs); g_interp.exports = NULL; g_interp.exports_len = 0; g_interp.wasi.argv = NULL; g_interp.wasi.argc = 0; g_interp.wasi.lib_dir = NULL; g_interp.wasi.global_cache = NULL; wasiPreopenEntriesFree(&g_interp.wasi); return rc; } // This file's macros end with it (zig1.c is one translation unit). #undef INTERP_UNDEF_PATTERN #undef INTERP_FRAME_BASE #undef INTERP_GLOBAL_BASE #undef INTERP_CONST_BASE #undef INTERP_HEAP_BASE #undef INTERP_WASM_PAGE_SIZE #undef INTERP_WASM_TOP_PAGE #undef CC_EMPTY #undef CC_UAV_STORAGE #undef INTERP_MAX_INT_BITS #undef INTERP_MAX_INT_LIMBS #undef INTERP_MAX_INT_RES_LIMBS #undef INTERP_F80_BITS #undef INTERP_F128_BITS #undef INTERP_F128_EXP_NONFINITE #undef INTERP_VALUE_CHUNK_MIN /* ---- end interp.c ---- */ /* ---- begin lower_zon.c ---- */ // src/Sema/LowerZon.zig: a ZON file's Zoir lowered to an interned value of // the import's result type (Sema.zirImport's `.zon` arm). A lowering that // does not match the result type is a compile error. #ifndef ZIG1_H #include "zig1.h" #endif typedef struct { Zcu* zcu; InternPool* ip; const Zoir* zoir; uint32_t file_index; } LowerZon; static uint32_t lowerZonLowerExprAnonResTy(LowerZon* self, uint32_t node); static uint32_t lowerZonLowerExprKnownResTy( LowerZon* self, uint32_t node, uint32_t res_ty); static void lowerZonCheckTypeInner( LowerZon* self, uint32_t ty, uint32_t parent_opt_ty, U32List* visited); // `pt.intern(.{ .int = .{ .ty, .storage = .{ .i64 = val } } })`. static uint32_t lowerZonIntI64(InternPool* ip, uint32_t ty, int64_t val) { const uint64_t limb = val < 0 ? (uint64_t)0 - (uint64_t)val : (uint64_t)val; return ipGetInt(ip, ty, (BigIntConst) { &limb, 1, val >= 0 }); } // `pt.floatValue(ty, x)` of an f128 `x`. static uint32_t lowerZonFloatValue( InternPool* ip, uint32_t ty, const uint32_t words[4]) { const uint32_t v = ipGetFloatFromF128Bits(ip, ty, words); if (v == IP_INDEX_NONE) UNIMPLEMENTED("LowerZon.lowerFloat: a c_longdouble result type"); return v; } // Sema.addStrLit of the string `bytes[0..len]` (`.maybe_embedded_nulls`). static uint32_t lowerZonAddStrLit( InternPool* ip, const uint8_t* bytes, uint32_t len) { const uint32_t ip_str = ipGetOrPutString(ip, (const char*)bytes, len); return addNullTerminatedStrLit(ip, ip_str, NULL); } static uint32_t lowerZonEnumLiteralString(LowerZon* self, uint32_t nts) { const char* name = zoirNullTerminatedString(self->zoir, nts); return ipGetOrPutString(self->ip, name, (uint32_t)strlen(name)); } // LowerZon.zig:run // `res_ty` IP_INDEX_NONE is an import without a result type. uint32_t lowerZonRun(Zcu* zcu, uint32_t file_index, uint32_t res_ty) { LowerZon lower_zon = { .zcu = zcu, .ip = &zcu->intern_pool, .zoir = zcu->files[file_index].zoir, .file_index = file_index, }; if (res_ty == IP_INDEX_NONE) return lowerZonLowerExprAnonResTy(&lower_zon, 0); // checkType: the struct and union types visited, which are the only // self-referential ones. U32List visited = { 0 }; lowerZonCheckTypeInner(&lower_zon, res_ty, IP_INDEX_NONE, &visited); free(visited.items); return lowerZonLowerExprKnownResTy(&lower_zon, 0, res_ty); } // LowerZon.zig:lowerExprAnonResTy static uint32_t lowerZonLowerExprAnonResTy(LowerZon* self, uint32_t node) { InternPool* ip = self->ip; const ZoirNode n = zoirNodeGet(self->zoir, node); switch (n.tag) { case ZOIR_NODE_TRUE: return ZIR_REF_BOOL_TRUE; case ZOIR_NODE_FALSE: return ZIR_REF_BOOL_FALSE; case ZOIR_NODE_NULL: return ZIR_REF_NULL_VALUE; case ZOIR_NODE_POS_INF: case ZOIR_NODE_NEG_INF: case ZOIR_NODE_NAN: COMPILE_ERROR_UPSTREAM("LowerZon.lowerExprAnonResTy: infinity or NaN " "requires a known result type"); case ZOIR_NODE_INT_LITERAL: if (!n.data.int_literal.is_big) return lowerZonIntI64( ip, ZIR_REF_COMPTIME_INT_TYPE, n.data.int_literal.small); return ipGetInt(ip, ZIR_REF_COMPTIME_INT_TYPE, n.data.int_literal.big); case ZOIR_NODE_FLOAT_LITERAL: return lowerZonFloatValue( ip, ZIR_REF_COMPTIME_FLOAT_TYPE, n.data.float_literal); case ZOIR_NODE_CHAR_LITERAL: return lowerZonIntI64( ip, ZIR_REF_COMPTIME_INT_TYPE, n.data.char_literal); case ZOIR_NODE_ENUM_LITERAL: return ipGetEnumLiteral( ip, lowerZonEnumLiteralString(self, n.data.enum_literal)); case ZOIR_NODE_STRING_LITERAL: return lowerZonAddStrLit( ip, n.data.string_literal.ptr, n.data.string_literal.len); case ZOIR_NODE_EMPTY_LITERAL: return ZIR_REF_EMPTY_TUPLE; case ZOIR_NODE_ARRAY_LITERAL: { const ZoirNodeRange nodes = n.data.array_literal; uint32_t* types = zig1Malloc((size_t)nodes.len * sizeof(uint32_t)); uint32_t* values = zig1Malloc((size_t)nodes.len * sizeof(uint32_t)); for (uint32_t i = 0; i < nodes.len; i++) { values[i] = lowerZonLowerExprAnonResTy(self, zoirNodeRangeAt(nodes, i)); types[i] = ipTypeOf(ip, values[i]); } const uint32_t ty = ipGetTupleType(ip, nodes.len, types, values); const uint32_t v = zptAggregateValue(ip, ty, values, nodes.len); free(types); free(values); return v; } case ZOIR_NODE_STRUCT_LITERAL: UNIMPLEMENTED("LowerZon.lowerExprAnonResTy: a struct literal without " "a result type (getReifiedStructType)"); } UNREACHABLE("LowerZon.lowerExprAnonResTy: Zoir node tag"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } static bool lowerZonVisited(U32List* visited, uint32_t ty) { for (uint32_t i = 0; i < visited->len; i++) if (visited->items[i] == ty) return true; u32ListAppend(visited, ty); return false; } // LowerZon.zig:checkTypeInner // `parent_opt_ty` is IP_INDEX_NONE outside an optional. static void lowerZonCheckTypeInner( LowerZon* self, uint32_t ty, uint32_t parent_opt_ty, U32List* visited) { const InternPool* ip = self->ip; switch (ztZigTypeTag(ip, ty)) { case ZIG_TYPE_TAG_BOOL: case ZIG_TYPE_TAG_INT: case ZIG_TYPE_TAG_FLOAT: case ZIG_TYPE_TAG_NULL: case ZIG_TYPE_TAG_ENUM: case ZIG_TYPE_TAG_COMPTIME_FLOAT: case ZIG_TYPE_TAG_COMPTIME_INT: case ZIG_TYPE_TAG_ENUM_LITERAL: return; case ZIG_TYPE_TAG_NORETURN: case ZIG_TYPE_TAG_VOID: case ZIG_TYPE_TAG_TYPE: case ZIG_TYPE_TAG_UNDEFINED: case ZIG_TYPE_TAG_ERROR_UNION: case ZIG_TYPE_TAG_ERROR_SET: case ZIG_TYPE_TAG_FN: case ZIG_TYPE_TAG_FRAME: case ZIG_TYPE_TAG_ANYFRAME: case ZIG_TYPE_TAG_OPAQUE: case ZIG_TYPE_TAG_SPIRV: COMPILE_ERROR_UPSTREAM( "LowerZon.checkTypeInner: type is not available in ZON"); case ZIG_TYPE_TAG_POINTER: { const bool is_slice = ipItemTagIs(ip, ty, IP_TAG_TYPE_SLICE); const IpPtrInfo ptr_info = ipPtrInfoKnown(ip, is_slice ? ip->item_datas[ty] : ty); if (!ptr_info.is_const) COMPILE_ERROR_UPSTREAM("LowerZon.checkTypeInner: ZON does not " "allow mutable pointers"); if (is_slice) { lowerZonCheckTypeInner( self, ptr_info.child, IP_INDEX_NONE, visited); return; } if (ptr_info.size != IP_PTR_SIZE_ONE) COMPILE_ERROR_UPSTREAM("LowerZon.checkTypeInner: ZON does not " "allow many-pointers or C pointers"); lowerZonCheckTypeInner(self, ptr_info.child, parent_opt_ty, visited); return; } case ZIG_TYPE_TAG_OPTIONAL: if (parent_opt_ty != IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM("LowerZon.checkTypeInner: ZON does not " "allow nested optionals"); lowerZonCheckTypeInner(self, ipOptionalChild(ip, ty), ty, visited); return; case ZIG_TYPE_TAG_ARRAY: case ZIG_TYPE_TAG_VECTOR: { IpArrayInfo info; if (ipArrayInfo(ip, ty, &info) != IP_ARR_OK) UNREACHABLE("LowerZon.checkTypeInner: array type"); lowerZonCheckTypeInner(self, info.child, IP_INDEX_NONE, visited); return; } case ZIG_TYPE_TAG_STRUCT: if (ztIsTuple(ip, ty)) { const uint32_t n = ipTupleFieldCount(ip, ty); for (uint32_t i = 0; i < n; i++) lowerZonCheckTypeInner( self, ipTupleFieldType(ip, ty, i), IP_INDEX_NONE, visited); return; } if (lowerZonVisited(visited, ty)) return; ensureLayoutResolved(self->zcu, ty); for (uint32_t i = 0; i < ipStructFieldsLen(ip, ty); i++) lowerZonCheckTypeInner( self, ipStructFieldType(ip, ty, i), IP_INDEX_NONE, visited); return; case ZIG_TYPE_TAG_UNION: if (lowerZonVisited(visited, ty)) return; ensureLayoutResolved(self->zcu, ty); for (uint32_t i = 0; i < ipUnionFieldsLen(ip, ty); i++) { const uint32_t field_type = ipUnionFieldType(ip, ty, i); if (field_type != ZIR_REF_VOID_TYPE) lowerZonCheckTypeInner( self, field_type, IP_INDEX_NONE, visited); } return; } UNREACHABLE("LowerZon.checkTypeInner: zigTypeTag"); } static uint32_t lowerZonLowerExprKnownResTyInner( LowerZon* self, uint32_t node, uint32_t res_ty); // LowerZon.zig:lowerExprKnownResTy // The inner lowering's error.WrongType is the "expected type" error. static uint32_t lowerZonLowerExprKnownResTy( LowerZon* self, uint32_t node, uint32_t res_ty) { const uint32_t v = lowerZonLowerExprKnownResTyInner(self, node, res_ty); if (v == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM( "LowerZon.lowerExprKnownResTy: expected the result type"); return v; } // LowerZon.zig:lowerBool static uint32_t lowerZonLowerBool(const LowerZon* self, uint32_t node) { switch (zoirNodeGet(self->zoir, node).tag) { case ZOIR_NODE_TRUE: return ZIR_REF_BOOL_TRUE; case ZOIR_NODE_FALSE: return ZIR_REF_BOOL_FALSE; default: return IP_INDEX_NONE; } } // LowerZon.zig:lowerInt static uint32_t lowerZonLowerInt( LowerZon* self, uint32_t node, uint32_t res_ty) { InternPool* ip = self->ip; const ZoirNode n = zoirNodeGet(self->zoir, node); // A fixed-size integer result checks that the value is in bounds. uint32_t bits = 0; bool is_signed = false; const bool fixed = ztZigTypeTag(ip, res_ty) == ZIG_TYPE_TAG_INT; if (fixed && !ztIntInfo(ip, res_ty, &bits, &is_signed)) UNREACHABLE("LowerZon.lowerInt: int type"); switch (n.tag) { case ZOIR_NODE_INT_LITERAL: { if (n.data.int_literal.is_big) { if (fixed && !bigIntFitsInTwosComp( n.data.int_literal.big, is_signed, bits)) COMPILE_ERROR_UPSTREAM("LowerZon.lowerInt: type cannot " "represent integer value"); return ipGetInt(ip, res_ty, n.data.int_literal.big); } const int32_t rhs = n.data.int_literal.small; if (fixed) { if (!is_signed && rhs < 0) COMPILE_ERROR_UPSTREAM("LowerZon.lowerInt: type cannot " "represent integer value"); // Fewer bits than an i32 holds: check the min and max values. if (bits < 32) { const uint32_t unsigned_bits = bits - (is_signed ? 1u : 0u); const int64_t min_int = is_signed ? -((int64_t)1 << unsigned_bits) : 0; const int64_t max_int = ((int64_t)1 << unsigned_bits) - 1; if (rhs < min_int || rhs > max_int) COMPILE_ERROR_UPSTREAM("LowerZon.lowerInt: type cannot " "represent integer value"); } } return lowerZonIntI64(ip, res_ty, rhs); } case ZOIR_NODE_FLOAT_LITERAL: UNIMPLEMENTED("LowerZon.lowerInt: a float literal of an integer " "type (Mutable.setFloat)"); case ZOIR_NODE_CHAR_LITERAL: { const uint32_t val = n.data.char_literal; if (fixed) { const uint32_t unsigned_bits = bits - (is_signed ? 1u : 0u); if (unsigned_bits < 21 && val >= (1u << unsigned_bits)) COMPILE_ERROR_UPSTREAM("LowerZon.lowerInt: type cannot " "represent integer value"); } return lowerZonIntI64(ip, res_ty, val); } default: return IP_INDEX_NONE; } } // LowerZon.zig:lowerFloat static uint32_t lowerZonLowerFloat( LowerZon* self, uint32_t node, uint32_t res_ty) { InternPool* ip = self->ip; const ZoirNode n = zoirNodeGet(self->zoir, node); uint32_t words[4]; switch (n.tag) { case ZOIR_NODE_INT_LITERAL: { if (n.data.int_literal.is_big) UNIMPLEMENTED("LowerZon.lowerFloat: a big integer literal of a " "float type (Const.toFloat)"); const int32_t v = n.data.int_literal.small; const uint64_t magnitude = v < 0 ? (uint64_t)0 - (uint64_t)v : (uint64_t)v; ipFloat128ToWords( crtFloatFromInt(128, u128Make(0, magnitude), v < 0), words); return lowerZonFloatValue(ip, res_ty, words); } case ZOIR_NODE_FLOAT_LITERAL: return lowerZonFloatValue(ip, res_ty, n.data.float_literal); case ZOIR_NODE_CHAR_LITERAL: ipFloat128ToWords( crtFloatFromInt(128, u128Make(0, n.data.char_literal), false), words); return lowerZonFloatValue(ip, res_ty, words); case ZOIR_NODE_POS_INF: case ZOIR_NODE_NEG_INF: case ZOIR_NODE_NAN: { if (res_ty == ZIR_REF_COMPTIME_FLOAT_TYPE) COMPILE_ERROR_UPSTREAM( "LowerZon.lowerFloat: expected type 'comptime_float'"); // std.math.inf(f128), -inf, std.math.nan(f128). CrtU128 v = u128Make(0x7FFF000000000000ull, 0); if (n.tag == ZOIR_NODE_NEG_INF) v = u128Make(0xFFFF000000000000ull, 0); if (n.tag == ZOIR_NODE_NAN) v = u128Make(0x7FFF800000000000ull, 0); ipFloat128ToWords(v, words); return lowerZonFloatValue(ip, res_ty, words); } default: return IP_INDEX_NONE; } } // LowerZon.zig:lowerNull static uint32_t lowerZonLowerNull(const LowerZon* self, uint32_t node) { return zoirNodeGet(self->zoir, node).tag == ZOIR_NODE_NULL ? ZIR_REF_NULL_VALUE : IP_INDEX_NONE; } // The elements of an array, tuple, slice or vector literal: an // `array_literal`'s range, an `empty_literal` as none; false otherwise. static bool lowerZonElemNodes( const LowerZon* self, uint32_t node, ZoirNodeRange* out) { const ZoirNode n = zoirNodeGet(self->zoir, node); switch (n.tag) { case ZOIR_NODE_ARRAY_LITERAL: *out = n.data.array_literal; return true; case ZOIR_NODE_EMPTY_LITERAL: *out = (ZoirNodeRange) { .start = node, .len = 0 }; return true; default: return false; } } // LowerZon.zig:lowerArray static uint32_t lowerZonLowerArray( LowerZon* self, uint32_t node, uint32_t res_ty) { IpArrayInfo array_info; if (ipArrayInfo(self->ip, res_ty, &array_info) != IP_ARR_OK) UNREACHABLE("LowerZon.lowerArray: array type"); ZoirNodeRange nodes; if (!lowerZonElemNodes(self, node, &nodes) || nodes.len != array_info.len) return IP_INDEX_NONE; const bool has_sentinel = array_info.sentinel != IP_INDEX_NONE; const uint32_t elems_len = nodes.len + (has_sentinel ? 1u : 0u); uint32_t* elems = zig1Malloc((size_t)(elems_len ? elems_len : 1) * sizeof(uint32_t)); for (uint32_t i = 0; i < nodes.len; i++) elems[i] = lowerZonLowerExprKnownResTy( self, zoirNodeRangeAt(nodes, i), array_info.child); if (has_sentinel) elems[elems_len - 1] = array_info.sentinel; const uint32_t v = zptAggregateValue(self->ip, res_ty, elems, elems_len); free(elems); return v; } // LowerZon.zig:lowerEnum static uint32_t lowerZonLowerEnum( LowerZon* self, uint32_t node, uint32_t res_ty) { InternPool* ip = self->ip; ensureLayoutResolved(self->zcu, res_ty); const ZoirNode n = zoirNodeGet(self->zoir, node); if (n.tag != ZOIR_NODE_ENUM_LITERAL) return IP_INDEX_NONE; const uint32_t field_name_interned = lowerZonEnumLiteralString(self, n.data.enum_literal); if (ipEnumFieldIndexFromName(ip, res_ty, field_name_interned) == UINT32_MAX) COMPILE_ERROR_UPSTREAM("LowerZon.lowerEnum: enum has no member"); return ipEnumTagFromFieldName(ip, res_ty, field_name_interned); } // LowerZon.zig:lowerEnumLiteral static uint32_t lowerZonLowerEnumLiteral(LowerZon* self, uint32_t node) { const ZoirNode n = zoirNodeGet(self->zoir, node); if (n.tag != ZOIR_NODE_ENUM_LITERAL) return IP_INDEX_NONE; return ipGetEnumLiteral( self->ip, lowerZonEnumLiteralString(self, n.data.enum_literal)); } // LowerZon.zig:lowerTuple static uint32_t lowerZonLowerTuple( LowerZon* self, uint32_t node, uint32_t res_ty) { InternPool* ip = self->ip; ZoirNodeRange elem_nodes; if (!lowerZonElemNodes(self, node, &elem_nodes)) return IP_INDEX_NONE; const uint32_t fields_len = ipTupleFieldCount(ip, res_ty); uint32_t* elems = zig1Malloc((size_t)(fields_len ? fields_len : 1) * sizeof(uint32_t)); // A comptime field's value, IP_INDEX_NONE for a runtime one. for (uint32_t i = 0; i < fields_len; i++) elems[i] = ipTupleFieldValue(ip, res_ty, i); for (uint32_t i = 0; i < elem_nodes.len; i++) { if (i >= fields_len) COMPILE_ERROR_UPSTREAM("LowerZon.lowerTuple: index outside tuple"); const uint32_t val = lowerZonLowerExprKnownResTy(self, zoirNodeRangeAt(elem_nodes, i), ipTupleFieldType(ip, res_ty, i)); if (elems[i] != IP_INDEX_NONE && val != elems[i]) COMPILE_ERROR_UPSTREAM("LowerZon.lowerTuple: value stored in " "comptime field does not match the " "default value of the field"); elems[i] = val; } for (uint32_t i = 0; i < fields_len; i++) if (elems[i] == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM("LowerZon.lowerTuple: missing tuple field"); const uint32_t v = zptAggregateValue(ip, res_ty, elems, fields_len); free(elems); return v; } // LowerZon.zig:lowerStruct static uint32_t lowerZonLowerStruct( LowerZon* self, uint32_t node, uint32_t res_ty) { InternPool* ip = self->ip; ensureLayoutResolved(self->zcu, res_ty); ensureStructDefaultsResolved(self->zcu, res_ty); const ZoirNode n = zoirNodeGet(self->zoir, node); const uint32_t* names = NULL; ZoirNodeRange vals = { .start = node, .len = 0 }; if (n.tag == ZOIR_NODE_STRUCT_LITERAL) { names = n.data.struct_literal.names; vals = n.data.struct_literal.vals; } else if (n.tag != ZOIR_NODE_EMPTY_LITERAL) { return IP_INDEX_NONE; } const uint32_t fields_len = ipStructFieldsLen(ip, res_ty); uint32_t* field_values = zig1Malloc((size_t)(fields_len ? fields_len : 1) * sizeof(uint32_t)); for (uint32_t i = 0; i < fields_len; i++) field_values[i] = ipStructFieldDefault(ip, res_ty, i); for (uint32_t i = 0; i < vals.len; i++) { const uint32_t field_name = lowerZonEnumLiteralString(self, names[i]); const uint32_t name_index = ipStructFieldIndex(ip, res_ty, field_name); if (name_index == UINT32_MAX) COMPILE_ERROR_UPSTREAM("LowerZon.lowerStruct: unexpected field"); const uint32_t field_type = ipStructFieldType(ip, res_ty, name_index); const uint32_t default_val = field_values[name_index]; field_values[name_index] = lowerZonLowerExprKnownResTy( self, zoirNodeRangeAt(vals, i), field_type); if (ipStructFieldIsComptime(ip, res_ty, name_index) && field_values[name_index] != default_val) COMPILE_ERROR_UPSTREAM("LowerZon.lowerStruct: value stored in " "comptime field does not match the " "default value of the field"); } for (uint32_t i = 0; i < fields_len; i++) if (field_values[i] == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM("LowerZon.lowerStruct: missing field"); if (ipStructIsPacked(ip, res_ty)) UNIMPLEMENTED("LowerZon.lowerStruct: a packed struct " "(Value.writeToPackedMemory)"); const uint32_t v = zptAggregateValue(ip, res_ty, field_values, fields_len); free(field_values); return v; } // LowerZon.zig:lowerSlice static uint32_t lowerZonLowerSlice( LowerZon* self, uint32_t node, uint32_t res_ty) { InternPool* ip = self->ip; const uint32_t many_ptr_ty = ip->item_datas[res_ty]; const IpPtrInfo ptr_info = ipPtrInfoKnown(ip, many_ptr_ty); // String literals. const bool string_alignment = ptr_info.alignment == IP_PTR_ALIGN_NONE || ptr_info.alignment == 0; const bool string_sentinel = ptr_info.sentinel == IP_INDEX_NONE || ptr_info.sentinel == ZIR_REF_ZERO_U8; if (string_alignment && ptr_info.child == ZIR_REF_U8_TYPE && string_sentinel) { const ZoirNode n = zoirNodeGet(self->zoir, node); if (n.tag == ZOIR_NODE_STRING_LITERAL) { const uint32_t str_ref = lowerZonAddStrLit( ip, n.data.string_literal.ptr, n.data.string_literal.len); return semaCoerceComptime(self->zcu, ip, str_ref, res_ty); } } // Slice literals. ZoirNodeRange elem_nodes; if (!lowerZonElemNodes(self, node, &elem_nodes)) return IP_INDEX_NONE; // Upstream's element loop runs over the sentinel's slot too // (`elem_nodes.at(i)` past the range), so only a slice without a // sentinel lowers. if (ptr_info.sentinel != IP_INDEX_NONE) UNIMPLEMENTED("LowerZon.lowerSlice: a sentinel-terminated slice of " "an array literal"); const uint32_t elems_len = elem_nodes.len; uint32_t* elems = zig1Malloc((size_t)(elems_len ? elems_len : 1) * sizeof(uint32_t)); for (uint32_t i = 0; i < elems_len; i++) elems[i] = lowerZonLowerExprKnownResTy( self, zoirNodeRangeAt(elem_nodes, i), ptr_info.child); const uint32_t array_ty = ipGetArrayType(ip, elems_len, ptr_info.child, IP_INDEX_NONE); const uint32_t array_val = zptAggregateValue(ip, array_ty, elems, elems_len); free(elems); const uint32_t orig_ty = ipGetPtrType(ip, array_ty, IP_PTR_FLAGS_CONST_ONE); const uint32_t many_item_ptr = ipGetPtrUavRetypedOffset(ip, many_ptr_ty, array_val, orig_ty, 0); const uint32_t len = ipGetIntUsize(ip, elems_len); return ipGetPtrSlice(ip, res_ty, many_item_ptr, len); } // LowerZon.zig:lowerUnion static uint32_t lowerZonLowerUnion( LowerZon* self, uint32_t node, uint32_t res_ty) { InternPool* ip = self->ip; ensureLayoutResolved(self->zcu, res_ty); const uint32_t enum_tag_type = ipUnionEnumTagType(ip, res_ty); const ZoirNode n = zoirNodeGet(self->zoir, node); uint32_t field_name; uint32_t field_node = UINT32_MAX; switch (n.tag) { case ZOIR_NODE_ENUM_LITERAL: field_name = lowerZonEnumLiteralString(self, n.data.enum_literal); break; case ZOIR_NODE_STRUCT_LITERAL: if (n.data.struct_literal.vals.len != 1) return IP_INDEX_NONE; field_name = lowerZonEnumLiteralString(self, n.data.struct_literal.names[0]); field_node = zoirNodeRangeAt(n.data.struct_literal.vals, 0); break; default: return IP_INDEX_NONE; } const uint32_t name_index = ipEnumFieldIndexFromName(ip, enum_tag_type, field_name); if (name_index == UINT32_MAX) return IP_INDEX_NONE; const uint32_t tag = ipEnumTagFromFieldName(ip, enum_tag_type, field_name); const uint32_t field_type = ipUnionFieldType(ip, res_ty, name_index); uint32_t val; if (field_node != UINT32_MAX) { if (field_type == ZIR_REF_VOID_TYPE) COMPILE_ERROR_UPSTREAM( "LowerZon.lowerUnion: expected type 'void'"); val = lowerZonLowerExprKnownResTy(self, field_node, field_type); } else { if (field_type != ZIR_REF_VOID_TYPE) return IP_INDEX_NONE; val = ZIR_REF_VOID_VALUE; } if (ipUnionIsPacked(ip, res_ty)) UNIMPLEMENTED("LowerZon.lowerUnion: a packed union (Sema.bitCastVal)"); return ipGetUnionValue(ip, res_ty, tag, val); } // LowerZon.zig:lowerVector static uint32_t lowerZonLowerVector( LowerZon* self, uint32_t node, uint32_t res_ty) { IpArrayInfo vector_info; if (ipArrayInfo(self->ip, res_ty, &vector_info) != IP_ARR_OK) UNREACHABLE("LowerZon.lowerVector: vector type"); ZoirNodeRange elem_nodes; if (!lowerZonElemNodes(self, node, &elem_nodes)) return IP_INDEX_NONE; if (elem_nodes.len != vector_info.len) COMPILE_ERROR_UPSTREAM( "LowerZon.lowerVector: expected a vector element count"); uint32_t* elems = zig1Malloc( (size_t)(vector_info.len ? vector_info.len : 1) * sizeof(uint32_t)); for (uint32_t i = 0; i < vector_info.len; i++) elems[i] = lowerZonLowerExprKnownResTy( self, zoirNodeRangeAt(elem_nodes, i), vector_info.child); const uint32_t v = zptAggregateValue(self->ip, res_ty, elems, vector_info.len); free(elems); return v; } // LowerZon.zig:lowerExprKnownResTyInner // IP_INDEX_NONE is upstream's error.WrongType. static uint32_t lowerZonLowerExprKnownResTyInner( LowerZon* self, uint32_t node, uint32_t res_ty) { InternPool* ip = self->ip; switch (ztZigTypeTag(ip, res_ty)) { case ZIG_TYPE_TAG_OPTIONAL: { if (zoirNodeGet(self->zoir, node).tag == ZOIR_NODE_NULL) return ipGetOptNull(ip, res_ty); const uint32_t child = lowerZonLowerExprKnownResTyInner( self, node, ipOptionalChild(ip, res_ty)); if (child == IP_INDEX_NONE) return IP_INDEX_NONE; return ipGetOptPayload(ip, res_ty, child); } case ZIG_TYPE_TAG_POINTER: { if (ipItemTagIs(ip, res_ty, IP_TAG_TYPE_SLICE)) return lowerZonLowerSlice(self, node, res_ty); const IpPtrInfo ptr_info = ipPtrInfoKnown(ip, res_ty); if (ptr_info.size != IP_PTR_SIZE_ONE) UNREACHABLE("LowerZon.lowerExprKnownResTyInner: unsupported " "pointer type, checked in checkType"); const uint32_t val = lowerZonLowerExprKnownResTyInner(self, node, ptr_info.child); if (val == IP_INDEX_NONE) return IP_INDEX_NONE; return ipGetPtrUav(ip, res_ty, val); } case ZIG_TYPE_TAG_BOOL: return lowerZonLowerBool(self, node); case ZIG_TYPE_TAG_INT: case ZIG_TYPE_TAG_COMPTIME_INT: return lowerZonLowerInt(self, node, res_ty); case ZIG_TYPE_TAG_FLOAT: case ZIG_TYPE_TAG_COMPTIME_FLOAT: return lowerZonLowerFloat(self, node, res_ty); case ZIG_TYPE_TAG_NULL: return lowerZonLowerNull(self, node); case ZIG_TYPE_TAG_ENUM: return lowerZonLowerEnum(self, node, res_ty); case ZIG_TYPE_TAG_ENUM_LITERAL: return lowerZonLowerEnumLiteral(self, node); case ZIG_TYPE_TAG_ARRAY: return lowerZonLowerArray(self, node, res_ty); case ZIG_TYPE_TAG_STRUCT: // lowerStructOrTuple. if (ztIsTuple(ip, res_ty)) return lowerZonLowerTuple(self, node, res_ty); return lowerZonLowerStruct(self, node, res_ty); case ZIG_TYPE_TAG_UNION: return lowerZonLowerUnion(self, node, res_ty); case ZIG_TYPE_TAG_VECTOR: return lowerZonLowerVector(self, node, res_ty); case ZIG_TYPE_TAG_TYPE: case ZIG_TYPE_TAG_NORETURN: case ZIG_TYPE_TAG_UNDEFINED: case ZIG_TYPE_TAG_ERROR_UNION: case ZIG_TYPE_TAG_ERROR_SET: case ZIG_TYPE_TAG_FN: case ZIG_TYPE_TAG_OPAQUE: case ZIG_TYPE_TAG_SPIRV: case ZIG_TYPE_TAG_FRAME: case ZIG_TYPE_TAG_ANYFRAME: case ZIG_TYPE_TAG_VOID: COMPILE_ERROR_UPSTREAM("LowerZon.lowerExprKnownResTyInner: type not " "available in ZON"); } UNREACHABLE("LowerZon.lowerExprKnownResTyInner: zigTypeTag"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } /* ---- end lower_zon.c ---- */ /* ---- begin main.c ---- */ // main.c — zig1's command line, upstream's stage1/wasi.c main: `zig1 // ` runs the compiler, /../src/main.zig, // with as the guest's /lib and with argv[0] and as // its argv (compilationRun). A first argument of exactly `--run`, `--test` or // `--bootstrap-build-options` is one of zig1's own commands instead // (AGENTS.txt §4b), as the usage text below gives them. #ifndef ZIG1_H #include "zig1.h" #endif #include #include #include static void usage(const char* argv0) { fprintf(stderr, "usage: %s \n" " %s --run [--zig-lib-dir DIR] [--module-root DIR] " "[--bootstrap-profile] []\n" " %s --run [--zig-lib-dir DIR] [--module-root DIR] " "[--bootstrap-profile] [--dep NAME[=MOD]]... -MNAME=FILE... " "[-- ]\n" " %s --test [--zig-lib-dir DIR] [--module-root DIR] " "[]\n" " %s --bootstrap-build-options\n", argv0, argv0, argv0, argv0, argv0); } // Directories.init's global cache, the guest's /cache (caller frees), or // NULL with its message. static char* hostStart(const char* lib_dir) { char* global_cache = resolveGlobalCacheDir(); if (global_cache == NULL) { fputs("error: unable to resolve zig cache directory: " "AppDataDirUnavailable\n", stderr); return NULL; } if (global_cache[0] == '\0') { // openUnresolved: "" is the cwd free(global_cache); global_cache = zig1Strdup("."); } if (!wasiHostStart(global_cache)) { fprintf(stderr, "error: unable to open global cache directory '%s': %s\n", global_cache, strerror(errno)); } else if (strcmp(lib_dir, global_cache) == 0) { fprintf(stderr, "error: zig lib directory '%s' cannot be equal to global cache " "directory '%s'\n", lib_dir, global_cache); } else { return global_cache; } free(global_cache); return NULL; } // `zig1 --run` runs the root file's `main`, `zig1 --test` upstream's test // runner over the file's tests; either exits with the program's status, or // as below with 2 for a usage error. static int runCommand(int argc, char** argv) { CompilationRunOptions opts = { .is_test = strcmp(argv[1], "--test") == 0 }; int i = compilationRunOptionsParse(&opts, argc, argv, 2); if (opts.fname == NULL) { usage(argv[0]); if (i < argc) zig1Fatal("unknown option: %s", argv[i]); return 2; } char* global_cache = NULL; if (!opts.is_test) { global_cache = hostStart(opts.lib_dir != NULL ? opts.lib_dir : "lib"); if (global_cache == NULL) return 1; } opts.global_cache = global_cache; // The guest's argv is the root file as given and the arguments after it. opts.wasi_argv = (const char* const*)&argv[i - 1]; opts.wasi_argv_len = (uint32_t)(argc - i + 1); int rc = opts.is_test ? compilationRunTest(&opts) : compilationRun(opts.fname, NULL, compilationRunOptionsConfig(&opts)); compilationRunOptionsDeinit(&opts); free(global_cache); return rc; } // Exit status: the compiler's own; 1 for a usage error or a lib dir that // cannot be used, as upstream's; ZIG1_EXIT_COMPILE_ERROR (126) when zig1 // refuses to compile the compiler, for a parse, AstGen or Sema compile // error; 2 for a driver failure, out of memory included; and SIGABRT (134) // when the compiler needs something zig1 does not implement. Parse and // AstGen errors carry upstream's line and column; a Sema error carries where // the declaration being analyzed starts. int main(int argc, char** argv) { if (argc < 2) { usage(argv[0]); return 1; } if (strcmp(argv[1], "--run") == 0 || strcmp(argv[1], "--test") == 0) return runCommand(argc, argv); if (strcmp(argv[1], "--bootstrap-build-options") == 0) { // The bootstrap profile's build_options module, which b7_ref.sh // builds the reference zig1.wasm with. if (argc != 2) { usage(argv[0]); return 2; } fputs(builtinBootstrapBuildOptionsSource(), stdout); return 0; } const char* lib_dir = argv[1]; int err = wasiDirError(lib_dir); if (err != 0) { fprintf(stderr, "error: unable to open zig lib directory '%s': %s\n", lib_dir, strerror(err)); return 1; } char* root = zig1PathJoin(lib_dir, "..", 2); char* main_zig = zig1PathJoin(root, "src/main.zig", 12); FILE* main_file = fopen(main_zig, "rb"); if (main_file == NULL) { fprintf(stderr, "error: unable to open '%s': %s\n", main_zig, strerror(errno)); return 1; } fclose(main_file); char* global_cache = hostStart(lib_dir); if (global_cache == NULL) return 1; // The guest's argv is argv without the lib dir, as wasi.c's args_get. argv[1] = argv[0]; CompilationRunOptions opts = { .fname = main_zig, .lib_dir = lib_dir, .module_root = root, .bootstrap_profile = true, .wasi_argv = (const char* const*)&argv[1], .wasi_argv_len = (uint32_t)(argc - 1), .global_cache = global_cache, }; int code = compilationRun(main_zig, NULL, compilationRunOptionsConfig(&opts)); compilationRunOptionsDeinit(&opts); free(global_cache); free(main_zig); free(root); return code; } /* ---- end main.c ---- */ /* ---- begin mutable_value.c ---- */ // mutable_value.c — src/mutable_value.zig: MutableValue, the contents of a // comptime allocation, mutated in place and interned only on demand. A // MutableValue method or helper is mutableValue and its name // (mutableValueIntern is MutableValue.intern). Its sub-values live in the // analyzing Sema's arena (semaArenaAlloc). Declarations are in zig1.h. #ifndef ZIG1_H #include "zig1.h" #endif #include static MutableValue* mutableValueCreate(Zcu* zcu, MutableValue val) { MutableValue* mv = semaArenaAlloc(zcu, sizeof(MutableValue)); *mv = val; return mv; } // `.{ .interned = ip_index }`. MutableValue mutableValueInterned(uint32_t ip_index) { MutableValue mv = { .tag = MV_INTERNED }; mv.u.interned = ip_index; return mv; } static MutableValue* mutableValueAllocElems(Zcu* zcu, uint32_t len) { return semaArenaAlloc(zcu, (len ? len : 1u) * sizeof(MutableValue)); } // The `aggregate` of a `bytes` value, each byte an interned `u8`. static void mutableValueBytesToAggregate(Zcu* zcu, MutableValue* mv) { InternPool* ip = &zcu->intern_pool; MutableValueBytes bytes = mv->u.bytes; MutableValue* elems = mutableValueAllocElems(zcu, bytes.len); for (uint32_t i = 0; i < bytes.len; i++) elems[i] = mutableValueInterned(ipGetIntU8(ip, bytes.data[i])); mv->tag = MV_AGGREGATE; mv->u.aggregate = (MutableValueAggregate) { bytes.ty, elems, bytes.len }; } // mutable_value.zig:MutableValue.intern uint32_t mutableValueIntern(Zcu* zcu, const MutableValue* mv) { InternPool* ip = &zcu->intern_pool; switch (mv->tag) { case MV_INTERNED: return mv->u.interned; case MV_EU_PAYLOAD: return ipGetErrorUnionPayload(ip, mv->u.eu_payload.ty, mutableValueIntern(zcu, mv->u.eu_payload.child)); case MV_OPT_PAYLOAD: return ipGetOptPayload(ip, mv->u.opt_payload.ty, mutableValueIntern(zcu, mv->u.opt_payload.child)); case MV_REPEATED: { // `pt.aggregateSplatValue`: an undefined element splats to undef. uint32_t elem = mutableValueIntern(zcu, mv->u.repeated.child); return ipItemTagIs(ip, elem, IP_TAG_UNDEF) ? ipGetUndef(ip, mv->u.repeated.ty) : ipGetRepeated(ip, mv->u.repeated.ty, elem); } case MV_BYTES: { // `.storage = .{ .bytes = getOrPutString(b.data, // .maybe_embedded_nulls) }`, stored without its sentinel byte // (ipGetAggregate's bytes convention). IpArrayInfo info; uint32_t len = mv->u.bytes.len; if (ipArrayInfo(ip, mv->u.bytes.ty, &info) == IP_ARR_OK) len = info.len; uint32_t str = ipGetOrPutString(ip, (const char*)mv->u.bytes.data, len); return ipInternBytesAggregate(ip, mv->u.bytes.ty, str, len); } case MV_AGGREGATE: { const MutableValueAggregate* a = &mv->u.aggregate; uint32_t* elems = zig1Malloc((a->len ? a->len : 1u) * sizeof(uint32_t)); for (uint32_t i = 0; i < a->len; i++) elems[i] = mutableValueIntern(zcu, &a->elems[i]); uint32_t res = zptAggregateValue(ip, a->ty, elems, a->len); free(elems); return res; } case MV_SLICE: return ipGetPtrSlice(ip, mv->u.slice.ty, mutableValueIntern(zcu, mv->u.slice.ptr), mutableValueIntern(zcu, mv->u.slice.len)); case MV_UN: return ipGetUnionValue(ip, mv->u.un.ty, mv->u.un.tag, mutableValueIntern(zcu, mv->u.un.payload)); } UNREACHABLE("mutableValueIntern: bad tag"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // mutable_value.zig:MutableValue.unintern // Un-interns the top level of `mv`: non-error error unions, non-null // optionals, slices, non-packed unions and non-packed aggregates. The // `bytes` representation needs `allow_bytes`, `repeated` `allow_repeated`. void mutableValueUnintern( Zcu* zcu, MutableValue* mv, bool allow_bytes, bool allow_repeated) { InternPool* ip = &zcu->intern_pool; if (mv->tag == MV_BYTES) { if (!allow_bytes) mutableValueBytesToAggregate(zcu, mv); return; } if (mv->tag != MV_INTERNED) return; uint32_t v = mv->u.interned; uint32_t ty = ipTypeOf(ip, v); uint32_t d = ip->item_datas[v]; switch ((IpTag)ip->item_tags[v]) { case IP_TAG_OPT_PAYLOAD: mv->tag = MV_OPT_PAYLOAD; mv->u.opt_payload = (MutableValueSubValue) { ty, mutableValueCreate(zcu, mutableValueInterned(ip->extra[d + 1])) }; return; case IP_TAG_ERROR_UNION_PAYLOAD: mv->tag = MV_EU_PAYLOAD; mv->u.eu_payload = (MutableValueSubValue) { ty, mutableValueCreate(zcu, mutableValueInterned(ip->extra[d + 1])) }; return; case IP_TAG_PTR_SLICE: mv->tag = MV_SLICE; mv->u.slice = (MutableValueSlice) { ty, mutableValueCreate( zcu, mutableValueInterned(ipPtrSlicePtr(ip, v))), mutableValueCreate( zcu, mutableValueInterned(ipPtrSliceLen(ip, v))) }; return; case IP_TAG_UNION_VALUE: mv->tag = MV_UN; mv->u.un = (MutableValueUnion) { ty, ip->extra[d + 1], mutableValueCreate(zcu, mutableValueInterned(ip->extra[d + 2])) }; return; case IP_TAG_BYTES: case IP_TAG_AGGREGATE: case IP_TAG_REPEATED: { uint32_t len = ipAggregateTypeLenIncludingSentinel(ip, ty); if (ip->item_tags[v] == IP_TAG_REPEATED && allow_repeated) { mv->tag = MV_REPEATED; mv->u.repeated = (MutableValueSubValue) { ty, mutableValueCreate( zcu, mutableValueInterned(ip->extra[d + 1])) }; return; } if (ip->item_tags[v] == IP_TAG_BYTES && allow_bytes) { uint8_t* data = semaArenaAlloc(zcu, len ? len : 1u); for (uint32_t i = 0; i < len; i++) data[i] = (uint8_t)ipReadIntValue( ip, valElemValue(ip, v, i, "MutableValue.unintern")); mv->tag = MV_BYTES; mv->u.bytes = (MutableValueBytes) { ty, data, len }; return; } MutableValue* elems = mutableValueAllocElems(zcu, len); for (uint32_t i = 0; i < len; i++) elems[i] = mutableValueInterned( valElemValue(ip, v, i, "MutableValue.unintern")); mv->tag = MV_AGGREGATE; mv->u.aggregate = (MutableValueAggregate) { ty, elems, len }; return; } case IP_TAG_ONLY_POSSIBLE_VALUE: { // indexToKey's `.only_possible_value`: an aggregate of an array's // sentinel, a tuple's values or a struct's field values; a packed // struct's has no elements and stays interned. IpArrayInfo arr; uint32_t len; if (ipArrayInfo(ip, ty, &arr) == IP_ARR_OK) len = arr.storage_len; else if (ipIsStructOrTupleType(ip, ty) && (ip->item_tags[ty] == IP_TAG_TYPE_TUPLE || !ipStructIsPacked(ip, ty))) len = ipAggregateTypeLenIncludingSentinel(ip, ty); else return; MutableValue* elems = mutableValueAllocElems(zcu, len); for (uint32_t i = 0; i < len; i++) { uint32_t elem = ipArrayInfo(ip, ty, &arr) == IP_ARR_OK ? (i < arr.len ? ztOnePossibleValue(ip, arr.child) : arr.sentinel) : ip->item_tags[ty] == IP_TAG_TYPE_TUPLE ? ipTupleFieldValue(ip, ty, i) : semaStructFieldComptimeValue(zcu, ty, i); if (elem == IP_INDEX_NONE && ipIsStructOrTupleType(ip, ty)) elem = ztOnePossibleValue(ip, ztFieldType(ip, ty, i)); elems[i] = mutableValueInterned(elem); } mv->tag = MV_AGGREGATE; mv->u.aggregate = (MutableValueAggregate) { ty, elems, len }; return; } case IP_TAG_UNDEF: break; default: return; } // `.undef => |ty_ip| switch (Type.fromInterned(ty_ip).zigTypeTag(zcu))`. IpArrayInfo arr; bool is_seq = ipArrayInfo(ip, ty, &arr) == IP_ARR_OK; if (is_seq || ipIsStructOrTupleType(ip, ty)) { bool is_struct = !is_seq; if (is_struct && ip->item_tags[ty] != IP_TAG_TYPE_TUPLE && ipStructIsPacked(ip, ty)) return; bool has_sent = is_seq && arr.sentinel != IP_INDEX_NONE; if (is_struct || has_sent || !allow_repeated) { uint32_t len_no_sent = is_seq ? arr.len : ipAggregateTypeLenIncludingSentinel(ip, ty); uint32_t len = len_no_sent + (has_sent ? 1u : 0u); MutableValue* elems = mutableValueAllocElems(zcu, len); if (is_seq) { uint32_t undef_elem = ipGetUndef(ip, arr.child); for (uint32_t i = 0; i < len_no_sent; i++) elems[i] = mutableValueInterned(undef_elem); } else { for (uint32_t i = 0; i < len_no_sent; i++) elems[i] = mutableValueInterned( ipGetUndef(ip, ztFieldType(ip, ty, i))); } if (has_sent) elems[len_no_sent] = mutableValueInterned(arr.sentinel); mv->tag = MV_AGGREGATE; mv->u.aggregate = (MutableValueAggregate) { ty, elems, len }; } else { mv->tag = MV_REPEATED; mv->u.repeated = (MutableValueSubValue) { ty, mutableValueCreate( zcu, mutableValueInterned(ipGetUndef(ip, arr.child))) }; } return; } if (ip->item_tags[ty] == IP_TAG_TYPE_UNION) { if (!ipUnionIsExtern(ip, ty)) return; // Type.externUnionBackingType: `[abiSize]u8`. uint64_t size = ztAbiSize(zcu, ty); if (size > UINT32_MAX) UNIMPLEMENTED("MutableValue.unintern: extern union too large"); uint32_t backing_ty = ipGetArrayType( ip, (uint32_t)size, ZIR_REF_U8_TYPE, IP_INDEX_NONE); mv->tag = MV_UN; mv->u.un = (MutableValueUnion) { ty, IP_INDEX_NONE, mutableValueCreate( zcu, mutableValueInterned(ipGetUndef(ip, backing_ty))) }; return; } if (ip->item_tags[ty] == IP_TAG_TYPE_SLICE) { mv->tag = MV_SLICE; mv->u.slice = (MutableValueSlice) { ty, mutableValueCreate( zcu, mutableValueInterned(ipGetUndef(ip, ip->item_datas[ty]))), mutableValueCreate( zcu, mutableValueInterned(ZIR_REF_UNDEF_USIZE)) }; } } // mutable_value.zig:MutableValue.elem // The MutableValue of field or element `field_idx`, mutable in place until // `mv`'s representation changes. MutableValue* mutableValueElem( Zcu* zcu, MutableValue* mv, uint32_t field_idx) { const InternPool* ip = &zcu->intern_pool; // "Convert to the `aggregate` representation." switch (mv->tag) { case MV_EU_PAYLOAD: case MV_OPT_PAYLOAD: case MV_UN: UNREACHABLE("MutableValue.elem: not an aggregate or slice"); case MV_INTERNED: mutableValueUnintern(zcu, mv, false, false); break; case MV_BYTES: mutableValueBytesToAggregate(zcu, mv); break; case MV_REPEATED: { uint32_t ty = mv->u.repeated.ty; uint32_t len = ipAggregateTypeLenIncludingSentinel(ip, ty); MutableValue child = *mv->u.repeated.child; MutableValue* elems = mutableValueAllocElems(zcu, len); for (uint32_t i = 0; i < len; i++) elems[i] = child; mv->tag = MV_AGGREGATE; mv->u.aggregate = (MutableValueAggregate) { ty, elems, len }; break; } case MV_SLICE: case MV_AGGREGATE: break; } if (mv->tag == MV_AGGREGATE) { if (field_idx >= mv->u.aggregate.len) UNREACHABLE("MutableValue.elem: index out of bounds"); return &mv->u.aggregate.elems[field_idx]; } if (mv->tag == MV_SLICE && field_idx < 2) return field_idx == 0 ? mv->u.slice.ptr : mv->u.slice.len; UNREACHABLE("MutableValue.elem: not an aggregate or slice"); ZIG1_TCC_RETURN(NULL); } // mutable_value.zig:MutableValue.isTrivialInt static bool mutableValueIsTrivialInt( const InternPool* ip, const MutableValue* mv) { return mv->tag == MV_INTERNED && ipTagIsIntValue((IpTag)ip->item_tags[mv->u.interned]); } // mutable_value.zig:MutableValue.eqlTrivial // Fast equality which may return false negatives. static bool mutableValueEqlTrivial( const MutableValue* a, const MutableValue* b) { if (a->tag != b->tag) return false; switch (a->tag) { case MV_INTERNED: return a->u.interned == b->u.interned; case MV_EU_PAYLOAD: case MV_OPT_PAYLOAD: case MV_REPEATED: // The three SubValue arms share one layout. return a->u.repeated.ty == b->u.repeated.ty && mutableValueEqlTrivial( a->u.repeated.child, b->u.repeated.child); case MV_BYTES: return a->u.bytes.ty == b->u.bytes.ty && a->u.bytes.len == b->u.bytes.len && memcmp(a->u.bytes.data, b->u.bytes.data, a->u.bytes.len) == 0; case MV_AGGREGATE: if (a->u.aggregate.ty != b->u.aggregate.ty || a->u.aggregate.len != b->u.aggregate.len) return false; for (uint32_t i = 0; i < a->u.aggregate.len; i++) if (!mutableValueEqlTrivial( &a->u.aggregate.elems[i], &b->u.aggregate.elems[i])) return false; return true; case MV_SLICE: return a->u.slice.ty == b->u.slice.ty && a->u.slice.ptr->u.interned == b->u.slice.ptr->u.interned && a->u.slice.len->u.interned == b->u.slice.len->u.interned; case MV_UN: return a->u.un.ty == b->u.un.ty && a->u.un.tag == b->u.un.tag && mutableValueEqlTrivial(a->u.un.payload, b->u.un.payload); } return false; } // The trivial int's byte, `Value.toUnsignedInt` of a u8. static uint8_t mutableValueTrivialByte( const InternPool* ip, const MutableValue* mv) { return (uint8_t)ipReadIntValue(ip, mv->u.interned); } // mutable_value.zig:MutableValue.setElem // Replace one whole field or element of an aggregate or slice, keeping the // most compact representation. void mutableValueSetElem( Zcu* zcu, MutableValue* mv, uint32_t field_idx, MutableValue field_val) { const InternPool* ip = &zcu->intern_pool; bool is_trivial_int = mutableValueIsTrivialInt(ip, &field_val); mutableValueUnintern(zcu, mv, is_trivial_int, true); switch (mv->tag) { case MV_SLICE: if (field_idx == 0) *mv->u.slice.ptr = field_val; else if (field_idx == 1) *mv->u.slice.len = field_val; else UNREACHABLE("MutableValue.setElem: slice field"); return; case MV_BYTES: if (!is_trivial_int || field_idx >= mv->u.bytes.len) UNREACHABLE("MutableValue.setElem: bytes element"); mv->u.bytes.data[field_idx] = mutableValueTrivialByte(ip, &field_val); return; case MV_REPEATED: { MutableValueSubValue r = mv->u.repeated; if (mutableValueEqlTrivial(&field_val, r.child)) return; // "We must switch to either the `aggregate` or the `bytes` // representation." uint32_t len = ipAggregateTypeLenIncludingSentinel(ip, r.ty); IpArrayInfo arr; if (ipArrayInfo(ip, r.ty, &arr) == IP_ARR_OK && is_trivial_int && arr.child == ZIR_REF_U8_TYPE && mutableValueIsTrivialInt(ip, r.child)) { uint8_t* bytes = semaArenaAlloc(zcu, len ? len : 1u); memset(bytes, mutableValueTrivialByte(ip, r.child), len); bytes[field_idx] = mutableValueTrivialByte(ip, &field_val); mv->tag = MV_BYTES; mv->u.bytes = (MutableValueBytes) { r.ty, bytes, len }; } else { MutableValue* elems = mutableValueAllocElems(zcu, len); for (uint32_t i = 0; i < len; i++) elems[i] = *r.child; elems[field_idx] = field_val; mv->tag = MV_AGGREGATE; mv->u.aggregate = (MutableValueAggregate) { r.ty, elems, len }; } return; } case MV_AGGREGATE: { MutableValueAggregate a = mv->u.aggregate; if (field_idx >= a.len) UNREACHABLE("MutableValue.setElem: index out of bounds"); a.elems[field_idx] = field_val; if (ipIsStructOrTupleType(ip, a.ty)) return; // "Attempt to switch to a more efficient representation." bool is_repeated = true; for (uint32_t i = 0; i < a.len && is_repeated; i++) is_repeated = mutableValueEqlTrivial(&a.elems[i], &field_val); IpArrayInfo arr; if (is_repeated) { mv->tag = MV_REPEATED; mv->u.repeated = (MutableValueSubValue) { a.ty, mutableValueCreate(zcu, field_val) }; } else if (is_trivial_int && ipArrayInfo(ip, a.ty, &arr) == IP_ARR_OK && arr.child == ZIR_REF_U8_TYPE) { for (uint32_t i = 0; i < a.len; i++) if (!mutableValueIsTrivialInt(ip, &a.elems[i])) return; uint8_t* bytes = semaArenaAlloc(zcu, a.len ? a.len : 1u); for (uint32_t i = 0; i < a.len; i++) bytes[i] = mutableValueTrivialByte(ip, &a.elems[i]); mv->tag = MV_BYTES; mv->u.bytes = (MutableValueBytes) { a.ty, bytes, a.len }; } return; } case MV_INTERNED: case MV_EU_PAYLOAD: case MV_OPT_PAYLOAD: case MV_UN: break; } UNREACHABLE("MutableValue.setElem: not an aggregate or slice"); } // mutable_value.zig:MutableValue.getElem // The value of one field or element of an aggregate or slice. MutableValue mutableValueGetElem( Zcu* zcu, const MutableValue* mv, uint32_t field_idx) { InternPool* ip = &zcu->intern_pool; switch (mv->tag) { case MV_INTERNED: { uint32_t v = mv->u.interned; uint32_t ty = ipTypeOf(ip, v); if (ip->item_tags[ty] == IP_TAG_TYPE_SLICE) { if (ipItemTagIs(ip, v, IP_TAG_UNDEF)) return mutableValueInterned(field_idx == 0 ? ipGetUndef(ip, ip->item_datas[ty]) : ZIR_REF_UNDEF_USIZE); return mutableValueInterned(valSliceField(ip, v, field_idx)); } // Value.elemValue / Value.fieldValue: an undefined aggregate's // element is undefined. if (ipItemTagIs(ip, v, IP_TAG_UNDEF)) { IpArrayInfo arr; return mutableValueInterned(ipGetUndef(ip, ipArrayInfo(ip, ty, &arr) == IP_ARR_OK ? arr.child : ztFieldType(ip, ty, field_idx))); } if (ip->item_tags[ty] == IP_TAG_TYPE_UNION) return mutableValueInterned(ip->extra[ip->item_datas[v] + 2]); return mutableValueInterned( valElemValue(ip, v, field_idx, "MutableValue.getElem")); } case MV_UN: return *mv->u.un.payload; case MV_SLICE: return field_idx == 0 ? *mv->u.slice.ptr : *mv->u.slice.len; case MV_BYTES: return mutableValueInterned( ipGetIntU8(ip, mv->u.bytes.data[field_idx])); case MV_REPEATED: return *mv->u.repeated.child; case MV_AGGREGATE: return mv->u.aggregate.elems[field_idx]; case MV_EU_PAYLOAD: case MV_OPT_PAYLOAD: break; } UNREACHABLE("MutableValue.getElem: not an aggregate or slice"); ZIG1_TCC_RETURN(*mv); } // mutable_value.zig:MutableValue.typeOf uint32_t mutableValueTypeOf(const InternPool* ip, const MutableValue* mv) { switch (mv->tag) { case MV_INTERNED: return ipTypeOf(ip, mv->u.interned); case MV_EU_PAYLOAD: case MV_OPT_PAYLOAD: case MV_REPEATED: return mv->u.repeated.ty; case MV_BYTES: return mv->u.bytes.ty; case MV_AGGREGATE: return mv->u.aggregate.ty; case MV_SLICE: return mv->u.slice.ty; case MV_UN: return mv->u.un.ty; } return IP_INDEX_NONE; } // mutable_value.zig:MutableValue.unpackOptional, // mutable_value.zig:MutableValue.unpackErrorUnion The payload of a non-null // optional or a non-error error union; `*kind` says which arm: // MV_UNPACK_UNDEF, MV_UNPACK_NONE (`.null` / `.err`, whose name is // `*err_name`) or MV_UNPACK_PAYLOAD. MutableValue mutableValueUnpack(const InternPool* ip, const MutableValue* mv, MutableValueUnpack* kind, uint32_t* err_name) { if (mv->tag == MV_OPT_PAYLOAD || mv->tag == MV_EU_PAYLOAD) { *kind = MV_UNPACK_PAYLOAD; return *mv->u.opt_payload.child; } if (mv->tag != MV_INTERNED) UNREACHABLE("MutableValue.unpack: not an optional or error union"); uint32_t v = mv->u.interned; uint32_t d = ip->item_datas[v]; switch ((IpTag)ip->item_tags[v]) { case IP_TAG_UNDEF: *kind = MV_UNPACK_UNDEF; return *mv; case IP_TAG_OPT_NULL: *kind = MV_UNPACK_NONE; return *mv; case IP_TAG_ERROR_UNION_ERROR: *kind = MV_UNPACK_NONE; if (err_name) *err_name = ip->extra[d + 1]; return *mv; case IP_TAG_OPT_PAYLOAD: case IP_TAG_ERROR_UNION_PAYLOAD: *kind = MV_UNPACK_PAYLOAD; return mutableValueInterned(ip->extra[d + 1]); default: break; } UNREACHABLE("MutableValue.unpack: not an optional or error union"); ZIG1_TCC_RETURN(*mv); } /* ---- end mutable_value.c ---- */ /* ---- begin number_literal.c ---- */ // number_literal.c — lib/std/zig/number_literal.zig. #ifndef ZIG1_H #include "zig1.h" #endif #include #include #include static NumberLiteralResult numberLiteralFailure( NumberLiteralError failure, uint32_t index) { NumberLiteralResult r = { NUMBER_LITERAL_FAILURE, 0, 10, failure, index }; return r; } // number_literal.zig:parseNumberLiteral NumberLiteralResult parseNumberLiteral(const char* bytes, uint32_t len) { uint32_t i = 0; uint8_t base = 10; if (len >= 2 && bytes[0] == '0') { switch (bytes[1]) { case 'b': base = 2; i = 2; break; case 'o': base = 8; i = 2; break; case 'x': base = 16; i = 2; break; case 'B': case 'O': case 'X': return numberLiteralFailure( NUMBER_LITERAL_ERROR_UPPER_CASE_BASE, 1); case '.': case 'e': case 'E': break; default: return numberLiteralFailure(NUMBER_LITERAL_ERROR_LEADING_ZERO, 0); } } if (len == 2 && base != 10) return numberLiteralFailure(NUMBER_LITERAL_ERROR_DIGIT_AFTER_BASE, 0); uint64_t x = 0; bool overflow = false; bool underscore = false; bool period = false; char special = 0; bool exponent = false; bool is_float = false; for (; i < len; i++) { const char c = bytes[i]; switch (c) { case '_': if (i == 2 && base != 10) return numberLiteralFailure( NUMBER_LITERAL_ERROR_INVALID_UNDERSCORE_AFTER_SPECIAL, i); if (special != 0) return numberLiteralFailure( NUMBER_LITERAL_ERROR_INVALID_UNDERSCORE_AFTER_SPECIAL, i); if (underscore) return numberLiteralFailure( NUMBER_LITERAL_ERROR_REPEATED_UNDERSCORE, i); underscore = true; continue; case 'e': case 'E': if (base != 10) break; is_float = true; if (exponent) return numberLiteralFailure( NUMBER_LITERAL_ERROR_DUPLICATE_EXPONENT, i); if (underscore) return numberLiteralFailure( NUMBER_LITERAL_ERROR_EXPONENT_AFTER_UNDERSCORE, i); special = c; exponent = true; continue; case 'p': case 'P': if (base != 16) break; if (i == 2) return numberLiteralFailure( NUMBER_LITERAL_ERROR_DIGIT_AFTER_BASE, 0); is_float = true; if (exponent) return numberLiteralFailure( NUMBER_LITERAL_ERROR_DUPLICATE_EXPONENT, i); if (underscore) return numberLiteralFailure( NUMBER_LITERAL_ERROR_EXPONENT_AFTER_UNDERSCORE, i); special = c; exponent = true; continue; case '.': // `const digit_index = i - ".e".len;` wraps for i < 2, so // `digit_index < bytes.len` fails exactly then. if (exponent && i >= 2 && bytes[i - 2] >= '0' && bytes[i - 2] <= '9') return numberLiteralFailure( NUMBER_LITERAL_ERROR_PERIOD_AFTER_EXPONENT, i); is_float = true; if (base != 10 && base != 16) return numberLiteralFailure( NUMBER_LITERAL_ERROR_INVALID_FLOAT_BASE, 1); if (period) return numberLiteralFailure( NUMBER_LITERAL_ERROR_DUPLICATE_PERIOD, 0); period = true; if (underscore) return numberLiteralFailure( NUMBER_LITERAL_ERROR_SPECIAL_AFTER_UNDERSCORE, i); special = c; continue; case '+': case '-': switch (special) { case 'p': case 'P': break; case 'e': case 'E': if (base != 10) return numberLiteralFailure( NUMBER_LITERAL_ERROR_INVALID_EXPONENT_SIGN, i); break; default: return numberLiteralFailure( NUMBER_LITERAL_ERROR_INVALID_EXPONENT_SIGN, i); } special = c; continue; default: break; } uint8_t digit; if (c >= '0' && c <= '9') digit = (uint8_t)(c - '0'); else if (c >= 'A' && c <= 'Z') digit = (uint8_t)(c - 'A' + 10); else if (c >= 'a' && c <= 'z') digit = (uint8_t)(c - 'a' + 10); else return numberLiteralFailure( NUMBER_LITERAL_ERROR_INVALID_CHARACTER, i); if (digit >= base) return numberLiteralFailure(NUMBER_LITERAL_ERROR_INVALID_DIGIT, i); if (exponent && digit >= 10) return numberLiteralFailure( NUMBER_LITERAL_ERROR_INVALID_DIGIT_EXPONENT, i); underscore = false; special = 0; if (is_float) continue; if (x != 0) { if (x > UINT64_MAX / base) overflow = true; x = x * base; } if (x > UINT64_MAX - digit) overflow = true; x = x + digit; } if (underscore) return numberLiteralFailure( NUMBER_LITERAL_ERROR_TRAILING_UNDERSCORE, len - 1); if (special != 0) return numberLiteralFailure( NUMBER_LITERAL_ERROR_TRAILING_SPECIAL, len - 1); NumberLiteralResult r; memset(&r, 0, sizeof(r)); r.base = base; if (is_float) { r.tag = NUMBER_LITERAL_FLOAT; r.int_val = 0; } else if (overflow) { r.tag = NUMBER_LITERAL_BIG_INT; r.int_val = 0; } else { r.tag = NUMBER_LITERAL_INT; r.int_val = x; } return r; } /* ---- end number_literal.c ---- */ /* ---- begin parse_float.c ---- */ // parse_float.c — lib/std/fmt/parse_float/ at T = f128, the one float type // AstGen parses a literal as: parse.zig's number scan, then convert_hex.zig, // convert_fast.zig or convert_slow.zig with decimal.zig. A value is its // binary128 bit pattern, as C99 has no portable binary128. #ifndef ZIG1_H #include "zig1.h" #endif #include #include #include // --- 128-bit unsigned integers --- // upstream's u128 MantissaT, as softfloat.h's {lo, hi} pair (tcc lacks // __uint128_t), plus the few operations below. typedef CrtU128 u128; static inline u128 u128_add_u64(u128 a, uint64_t b) { return u128Add(a, u128Make(0, b)); } // a * m, wrapping as upstream's u128 `*%` (callers keep it in range). static inline u128 u128_mul_small(u128 a, uint32_t m) { const u128 lo = u128Mul64(a.lo, m); return u128Make(lo.hi + a.hi * m, lo.lo); } // a / d and a % d for a small divisor d (base-2^32 long division). static inline u128 u128_divmod_small(u128 a, uint32_t d, uint32_t* rem) { const uint32_t limb[4] = { (uint32_t)(a.hi >> 32), (uint32_t)(a.hi & 0xffffffffULL), (uint32_t)(a.lo >> 32), (uint32_t)(a.lo & 0xffffffffULL), }; uint64_t r = 0; uint32_t q[4]; for (int i = 0; i < 4; i++) { const uint64_t cur = (r << 32) | limb[i]; q[i] = (uint32_t)(cur / d); r = cur % d; } if (rem != NULL) *rem = (uint32_t)r; return u128Make( ((uint64_t)q[0] << 32) | q[1], ((uint64_t)q[2] << 32) | q[3]); } // --- convert_slow.zig and decimal.zig's Decimal(f128) --- // pow2_to_pow5_table[i] = { delta, cutoff } where cutoff = str(5^i) and // delta = floor(i * log10(2)) + 1. // Mirrors Zig's decimal.zig pow2_to_pow5_table (128 entries, indices 0..127). typedef struct { uint8_t delta; const char* cutoff; } Pow2ToPow5Entry; static const Pow2ToPow5Entry pow2_to_pow5_table[128] = { /* 0 */ { 0, "" }, /* 1 */ { 1, "5" }, /* 2 */ { 1, "25" }, /* 3 */ { 1, "125" }, /* 4 */ { 2, "625" }, /* 5 */ { 2, "3125" }, /* 6 */ { 2, "15625" }, /* 7 */ { 3, "78125" }, /* 8 */ { 3, "390625" }, /* 9 */ { 3, "1953125" }, /* 10 */ { 4, "9765625" }, /* 11 */ { 4, "48828125" }, /* 12 */ { 4, "244140625" }, /* 13 */ { 4, "1220703125" }, /* 14 */ { 5, "6103515625" }, /* 15 */ { 5, "30517578125" }, /* 16 */ { 5, "152587890625" }, /* 17 */ { 6, "762939453125" }, /* 18 */ { 6, "3814697265625" }, /* 19 */ { 6, "19073486328125" }, /* 20 */ { 7, "95367431640625" }, /* 21 */ { 7, "476837158203125" }, /* 22 */ { 7, "2384185791015625" }, /* 23 */ { 7, "11920928955078125" }, /* 24 */ { 8, "59604644775390625" }, /* 25 */ { 8, "298023223876953125" }, /* 26 */ { 8, "1490116119384765625" }, /* 27 */ { 9, "7450580596923828125" }, /* 28 */ { 9, "37252902984619140625" }, /* 29 */ { 9, "186264514923095703125" }, /* 30 */ { 10, "931322574615478515625" }, /* 31 */ { 10, "4656612873077392578125" }, /* 32 */ { 10, "23283064365386962890625" }, /* 33 */ { 10, "116415321826934814453125" }, /* 34 */ { 11, "582076609134674072265625" }, /* 35 */ { 11, "2910383045673370361328125" }, /* 36 */ { 11, "14551915228366851806640625" }, /* 37 */ { 12, "72759576141834259033203125" }, /* 38 */ { 12, "363797880709171295166015625" }, /* 39 */ { 12, "1818989403545856475830078125" }, /* 40 */ { 13, "9094947017729282379150390625" }, /* 41 */ { 13, "45474735088646411895751953125" }, /* 42 */ { 13, "227373675443232059478759765625" }, /* 43 */ { 13, "1136868377216160297393798828125" }, /* 44 */ { 14, "5684341886080801486968994140625" }, /* 45 */ { 14, "28421709430404007434844970703125" }, /* 46 */ { 14, "142108547152020037174224853515625" }, /* 47 */ { 15, "710542735760100185871124267578125" }, /* 48 */ { 15, "3552713678800500929355621337890625" }, /* 49 */ { 15, "17763568394002504646778106689453125" }, /* 50 */ { 16, "88817841970012523233890533447265625" }, /* 51 */ { 16, "444089209850062616169452667236328125" }, /* 52 */ { 16, "2220446049250313080847263336181640625" }, /* 53 */ { 16, "11102230246251565404236316680908203125" }, /* 54 */ { 17, "55511151231257827021181583404541015625" }, /* 55 */ { 17, "277555756156289135105907917022705078125" }, /* 56 */ { 17, "1387778780781445675529539585113525390625" }, /* 57 */ { 18, "6938893903907228377647697925567626953125" }, /* 58 */ { 18, "34694469519536141888238489627838134765625" }, /* 59 */ { 18, "173472347597680709441192448139190673828125" }, /* 60 */ { 19, "867361737988403547205962240695953369140625" }, /* 61 */ { 19, "4336808689942017736029811203479766845703125" }, /* 62 */ { 19, "21684043449710088680149056017398834228515625" }, /* 63 */ { 19, "108420217248550443400745280086994171142578125" }, /* 64 */ { 20, "542101086242752217003726400434970855712890625" }, /* 65 */ { 20, "2710505431213761085018632002174854278564453125" }, /* 66 */ { 20, "13552527156068805425093160010874271392822265625" }, /* 67 */ { 21, "67762635780344027125465800054371356964111328125" }, /* 68 */ { 21, "338813178901720135627329000271856784820556640625" }, /* 69 */ { 21, "1694065894508600678136645001359283924102783203125" }, /* 70 */ { 22, "8470329472543003390683225006796419620513916015625" }, /* 71 */ { 22, "42351647362715016953416125033982098102569580078125" }, /* 72 */ { 22, "211758236813575084767080625169910490512847900390625" }, /* 73 */ { 22, "1058791184067875423835403125849552452564239501953125" }, /* 74 */ { 23, "5293955920339377119177015629247762262821197509765625" }, /* 75 */ { 23, "26469779601696885595885078146238811314105987548828125" }, /* 76 */ { 23, "132348898008484427979425390731194056570529937744140625" }, /* 77 */ { 24, "661744490042422139897126953655970282852649688720703125" }, /* 78 */ { 24, "3308722450212110699485634768279851414263248443603515625" }, /* 79 */ { 24, "16543612251060553497428173841399257071316242218017578125" }, /* 80 */ { 25, "82718061255302767487140869206996285356581211090087890625" }, /* 81 */ { 25, "413590306276513837435704346034981426782906055450439453125" }, /* 82 */ { 25, "2067951531382569187178521730174907133914530277252197265625" }, /* 83 */ { 25, "10339757656912845935892608650874535669572651386260986328125" }, /* 84 */ { 26, "51698788284564229679463043254372678347863256931304931640625" }, /* 85 */ { 26, "258493941422821148397315216271863391739316284656524658203125" }, /* 86 */ { 26, "1292469707114105741986576081359316958696581423282623291015625" }, /* 87 */ { 27, "6462348535570528709932880406796584793482907116413116455078125" }, /* 88 */ { 27, "32311742677852643549664402033982923967414535582065582275390625" }, /* 89 */ { 27, "161558713389263217748322010169914619837072677910327911376953125" }, /* 90 */ { 28, "807793566946316088741610050849573099185363389551639556884765625" }, /* 91 */ { 28, "4038967834731580443708050254247865495926816947758197784423828125" }, /* 92 */ { 28, "20194839173657902218540251271239327479634084738790988922119140625" }, /* 93 */ { 28, "100974195868289511092701256356196637398170423693954944610595703125" }, /* 94 */ { 29, "504870979341447555463506281780983186990852118469774723052978515625" }, /* 95 */ { 29, "252435489670723777731753140890491593495426059234887361526489257812" "5" }, /* 96 */ { 29, "1262177448353618888658765704452457967477130296174436807632446289062" "5" }, /* 97 */ { 30, "6310887241768094443293828522262289837385651480872184038162231445312" "5" }, /* 98 */ { 30, "31554436208840472216469142611311449186928257404360920190811157226562" "5" }, /* 99 */ { 30, "157772181044202361082345713056557245934641287021804600954055786132812" "5" }, /* 100 */ { 31, "788860905221011805411728565282786229673206435109023004770278930664062" "5" }, /* 101 */ { 31, "394430452610505902705864282641393114836603217554511502385139465332031" "25" }, /* 102 */ { 31, "197215226305252951352932141320696557418301608777255751192569732666015" "625" }, /* 103 */ { 32, "986076131526264756764660706603482787091508043886278755962848663330078" "125" }, /* 104 */ { 32, "493038065763132378382330353301741393545754021943139377981424331665039" "0625" }, /* 105 */ { 32, "246519032881566189191165176650870696772877010971569688990712165832519" "53125" }, /* 106 */ { 32, "123259516440783094595582588325435348386438505485784844495356082916259" "765625" }, /* 107 */ { 33, "616297582203915472977912941627176741932192527428924222476780414581298" "828125" }, /* 108 */ { 33, "308148791101957736488956470813588370966096263714462111238390207290649" "4140625" }, /* 109 */ { 33, "154074395550978868244478235406794185483048131857231055619195103645324" "70703125" }, /* 110 */ { 34, "770371977754894341222391177033970927415240659286155278095975518226623" "53515625" }, /* 111 */ { 34, "385185988877447170611195588516985463707620329643077639047987759113311" "767578125" }, /* 112 */ { 34, "192592994438723585305597794258492731853810164821538819523993879556655" "8837890625" }, /* 113 */ { 35, "962964972193617926527988971292463659269050824107694097619969397783279" "4189453125" }, /* 114 */ { 35, "481482486096808963263994485646231829634525412053847048809984698891639" "70947265625" }, /* 115 */ { 35, "240741243048404481631997242823115914817262706026923524404992349445819" "854736328125" }, /* 116 */ { 35, "120370621524202240815998621411557957408631353013461762202496174722909" "9273681640625" }, /* 117 */ { 36, "601853107621011204079993107057789787043156765067308811012480873614549" "6368408203125" }, /* 118 */ { 36, "300926553810505602039996553528894893521578382533654405506240436807274" "81842041015625" }, /* 119 */ { 36, "150463276905252801019998276764447446760789191266827202753120218403637" "409210205078125" }, /* 120 */ { 37, "752316384526264005099991383822237233803945956334136013765601092018187" "046051025390625" }, /* 121 */ { 37, "376158192263132002549995691911118616901972978167068006882800546009093" "5230255126953125" }, /* 122 */ { 37, "188079096131566001274997845955559308450986489083534003441400273004546" "76151275634765625" }, /* 123 */ { 38, "940395480657830006374989229777796542254932445417670017207001365022733" "80756378173828125" }, /* 124 */ { 38, "470197740328915003187494614888898271127466222708835008603500682511366" "903781890869140625" }, /* 125 */ { 38, "235098870164457501593747307444449135563733111354417504301750341255683" "4518909454345703125" }, /* 126 */ { 38, "117549435082228750796873653722224567781866555677208752150875170627841" "72594547271728515625" }, /* 127 */ { 39, "587747175411143753984368268611122838909332778386043760754375853139208" "62972736358642578125" }, }; // Decimal128: big-decimal for f128, mirroring Zig's Decimal(f128). // MantissaT = u128, max_digits = 11564, max_decimal_digits = 37. #define DEC128_MAX_DIGITS 11564 #define DEC128_MAX_DWO 38 // max_digits_without_overflow #define DEC128_DP_RANGE 32767 #define DEC128_MANT_BITS \ 112 // floatMantissaBits(f128) == floatFractionalBits(f128) #define DEC128_MAX_DEC_DIGITS 37 // max_decimal_digits #define DEC128_MAX_SHIFT 60 typedef struct { uint32_t num_digits; int32_t decimal_point; bool truncated; uint8_t digits[DEC128_MAX_DIGITS]; } Decimal128; // Compute number of new decimal digits from decimal * 2^shift. // Mirrors Zig's Decimal(f128).numberOfDigitsLeftShift. static uint32_t decimalNumberOfDigitsLeftShift( const Decimal128* d, uint32_t shift) { const Pow2ToPow5Entry* x = &pow2_to_pow5_table[shift]; const char* co = x->cutoff; uint32_t co_len = (uint32_t)strlen(co); for (uint32_t i = 0; i < co_len; i++) { if (i >= d->num_digits) return (uint32_t)(x->delta - 1); uint8_t p5 = (uint8_t)(co[i] - '0'); if (d->digits[i] < p5) return (uint32_t)(x->delta - 1); if (d->digits[i] > p5) return (uint32_t)x->delta; /* equal: continue */ } return (uint32_t)x->delta; } // Decimal128 * 2^shift. Mirrors Zig's Decimal(f128).leftShift. static void decimalLeftShift(Decimal128* d, uint32_t shift) { if (d->num_digits == 0) return; uint32_t num_new = decimalNumberOfDigitsLeftShift(d, shift); uint32_t read_index = d->num_digits; uint32_t write_index = d->num_digits + num_new; u128 n = u128Make(0, 0); while (read_index != 0) { read_index -= 1; write_index -= 1; n = u128Add(n, u128Shl(u128Make(0, d->digits[read_index]), shift)); uint32_t r; u128 q = u128_divmod_small(n, 10, &r); if (write_index < DEC128_MAX_DIGITS) d->digits[write_index] = (uint8_t)r; else if (r > 0) d->truncated = true; n = q; } while (!u128IsZero(n)) { write_index -= 1; uint32_t r; u128 q = u128_divmod_small(n, 10, &r); if (write_index < DEC128_MAX_DIGITS) d->digits[write_index] = (uint8_t)r; else if (r > 0) d->truncated = true; n = q; } d->num_digits += num_new; if (d->num_digits > DEC128_MAX_DIGITS) d->num_digits = DEC128_MAX_DIGITS; d->decimal_point += (int32_t)num_new; /* trim trailing zeros */ while (d->num_digits != 0 && d->digits[d->num_digits - 1] == 0) d->num_digits--; } // Decimal128 * 2^(-shift). Mirrors Zig's Decimal(f128).rightShift. static void decimalRightShift(Decimal128* d, uint32_t shift) { uint32_t read_index = 0; uint32_t write_index = 0; u128 n = u128Make(0, 0); u128 mask = u128LowMask(shift); /* accumulate until n >> shift != 0 */ while (u128IsZero(u128Shr(n, shift))) { if (read_index < d->num_digits) { n = u128_add_u64(u128_mul_small(n, 10), d->digits[read_index]); read_index++; } else if (u128IsZero(n)) { return; } else { while (u128IsZero(u128Shr(n, shift))) { n = u128_mul_small(n, 10); read_index++; } break; } } d->decimal_point -= (int32_t)read_index - 1; if (d->decimal_point < -DEC128_DP_RANGE) { d->num_digits = 0; d->decimal_point = 0; d->truncated = false; return; } while (read_index < d->num_digits) { uint8_t new_digit = (uint8_t)(u128Shr(n, shift).lo); n = u128_add_u64( u128_mul_small(u128And(n, mask), 10), d->digits[read_index]); read_index++; d->digits[write_index] = new_digit; write_index++; } while (!u128IsZero(n)) { uint8_t new_digit = (uint8_t)(u128Shr(n, shift).lo); n = u128_mul_small(u128And(n, mask), 10); if (write_index < DEC128_MAX_DIGITS) { d->digits[write_index] = new_digit; write_index++; } else if (new_digit > 0) { d->truncated = true; } } d->num_digits = write_index; /* trim trailing zeros */ while (d->num_digits != 0 && d->digits[d->num_digits - 1] == 0) d->num_digits--; } // Round Decimal128 to integer. Mirrors Zig's Decimal(f128).round. static u128 decimalRound(const Decimal128* d) { if (d->num_digits == 0 || d->decimal_point < 0) return u128Make(0, 0); if (d->decimal_point > DEC128_MAX_DEC_DIGITS) return u128Make(~(uint64_t)0, ~(uint64_t)0); /* maxInt(u128) */ uint32_t dp = (uint32_t)d->decimal_point; u128 n = u128Make(0, 0); for (uint32_t i = 0; i < dp; i++) { n = u128_mul_small(n, 10); if (i < d->num_digits) n = u128_add_u64(n, d->digits[i]); } bool round_up = false; if (dp < d->num_digits) { round_up = d->digits[dp] >= 5; if (d->digits[dp] == 5 && dp + 1 == d->num_digits) round_up = d->truncated || (dp != 0 && (d->digits[dp - 1] & 1) != 0); } if (round_up) n = u128_add_u64(n, 1); return n; } // convert_slow.zig:getShift static uint32_t getShift(uint32_t n) { static const uint8_t powers[19] = { 0, 3, 6, 9, 13, 16, 19, 23, 26, 29, 33, 36, 39, 43, 46, 49, 53, 56, 59 }; return n < 19 ? (uint32_t)powers[n] : (uint32_t)DEC128_MAX_SHIFT; } // Build a +inf f128 bit pattern. static u128 biasedFpInf(void) { return u128Shl(u128Make(0, 0x7fff), 112); } // convert_slow.zig `convertSlow(f128, s)` past its `Decimal(T).parse(s)`: // Simple Decimal Conversion of `d`; returns the positive bit pattern. static u128 convertSlowDecimal(Decimal128* d) { /* Bounds check (mirrors convertSlow). */ if (d->decimal_point < -4966) { return u128Make(0, 0); } if (d->decimal_point >= 4934) { return biasedFpInf(); } int32_t exp2 = 0; /* Shift right toward [1/2, 1). */ while (d->decimal_point > 0) { uint32_t n = (uint32_t)d->decimal_point; uint32_t sh = getShift(n); decimalRightShift(d, sh); if (d->decimal_point < -DEC128_DP_RANGE) return u128Make(0, 0); exp2 += (int32_t)sh; } /* Shift left toward [1/2, 1). */ for (;;) { if (d->decimal_point > 0) break; uint32_t sh; if (d->decimal_point == 0) { if (d->num_digits == 0) return u128Make(0, 0); if (d->digits[0] >= 5) break; sh = (d->digits[0] <= 1) ? 2u : 1u; } else { uint32_t nabs = (uint32_t)(-(d->decimal_point)); sh = getShift(nabs); } decimalLeftShift(d, sh); if (d->decimal_point > DEC128_DP_RANGE) return biasedFpInf(); exp2 -= (int32_t)sh; } /* Binary format uses [1,2); we normalised to [1/2,1). */ exp2 -= 1; /* Subnormal handling. */ const int32_t MIN_EXP = -16383; /* f128 minimum_exponent */ while (MIN_EXP + 1 > exp2) { uint32_t nsub = (uint32_t)((MIN_EXP + 1) - exp2); if (nsub > DEC128_MAX_SHIFT) nsub = DEC128_MAX_SHIFT; decimalRightShift(d, nsub); exp2 += (int32_t)nsub; } if (exp2 - MIN_EXP >= 0x7fff) return biasedFpInf(); /* Extract fractional_bits+1 = 113 bits of mantissa. */ decimalLeftShift(d, DEC128_MANT_BITS + 1); /* leftShift(113) */ u128 mantissa = decimalRound(d); /* Rounding overflow: mantissa grew to 2^113. */ if (u128Cmp(mantissa, u128Bit(DEC128_MANT_BITS + 1)) >= 0) { decimalRightShift(d, 1); exp2 += 1; mantissa = decimalRound(d); if (exp2 - MIN_EXP >= 0x7fff) return biasedFpInf(); } int32_t power2 = exp2 - MIN_EXP; if (u128Cmp(mantissa, u128Bit(DEC128_MANT_BITS)) < 0) power2 -= 1; /* mask to 112 explicit mantissa bits (removes implicit leading 1) */ mantissa = u128And(mantissa, u128LowMask(DEC128_MANT_BITS)); return u128Or( u128Shl(u128Make(0, (uint64_t)(uint32_t)power2), 112), mantissa); } // The exact value N * 10^exp10 as a Decimal, rounded by the same // convertSlowDecimal: what convert_fast.zig's single correctly-rounded f128 // multiply/divide yields whenever its path is taken. static u128 convertExactF128(u128 N, int32_t exp10) { if (u128IsZero(N)) return u128Make(0, 0); Decimal128 d; d.truncated = false; d.num_digits = 0; /* extract decimal digits of N (MSB first) */ { uint8_t tmp[40]; uint32_t tlen = 0; u128 nc = N; while (!u128IsZero(nc)) { uint32_t r; nc = u128_divmod_small(nc, 10, &r); tmp[tlen++] = (uint8_t)r; } for (uint32_t i = 0; i < tlen; i++) d.digits[i] = tmp[tlen - 1 - i]; d.num_digits = tlen; /* fill slots up to DEC128_MAX_DWO with 0 (mirrors parse()) */ for (uint32_t i = tlen; i < (uint32_t)DEC128_MAX_DWO; i++) d.digits[i] = 0; } /* decimal_point = exp10 + num_digits (mirrors Decimal.parse end) */ d.decimal_point = exp10 + (int32_t)d.num_digits; return convertSlowDecimal(&d); } // std.fmt.parse_float's FloatStream: `offsetTrue` excludes the underscores // `scanDigit` stepped over (and only those: `skipChars` does not count). typedef struct { const char* s; uint32_t len; uint32_t offset; uint32_t underscore_count; } PfStream; static uint32_t pfOffsetTrue(const PfStream* st) { return st->offset - st->underscore_count; } static bool pfFirstIs(const PfStream* st, char c) { return st->offset < st->len && st->s[st->offset] == c; } static bool pfFirstIsLower(const PfStream* st, char c) { return st->offset < st->len && (st->s[st->offset] | 0x20) == c; } static int pfScanDigit(PfStream* st, uint32_t base) { while (st->offset < st->len) { char c = st->s[st->offset]; if (c >= '0' && c <= '9') { st->offset++; return c - '0'; } if (base == 16 && c >= 'a' && c <= 'f') { st->offset++; return c - 'a' + 10; } if (base == 16 && c >= 'A' && c <= 'F') { st->offset++; return c - 'A' + 10; } if (c != '_') break; st->offset++; st->underscore_count++; } return -1; } // parse.zig `tryParseDigits` (wrapping; its 8-digit fast loop computes the // same value) and `tryParseNDigits`, whose bound is `min_n_digit_int` -- // 10^(n-1) in both bases, as upstream spells it. static void pfTryParseDigits(PfStream* st, u128* x, uint32_t base) { int d; while ((d = pfScanDigit(st, base)) >= 0) *x = u128_add_u64(u128_mul_small(*x, base), (uint64_t)d); } static void pfTryParseNDigits( PfStream* st, u128* x, uint32_t base, uint32_t n) { u128 min = u128Make(0, 1); for (uint32_t i = 1; i < n; i++) min = u128_mul_small(min, 10); while (u128Cmp(*x, min) < 0) { int d = pfScanDigit(st, base); if (d < 0) break; *x = u128_add_u64(u128_mul_small(*x, base), (uint64_t)d); } } // parse.zig `parseScientific` (its exponent saturates at 0x1000_0000). static bool pfParseScientific(PfStream* st, int64_t* out) { bool negative = false; if (st->offset < st->len) { char c = st->s[st->offset]; negative = c == '-'; if (c == '-' || c == '+') st->offset++; } if (!(st->offset < st->len && st->s[st->offset] >= '0' && st->s[st->offset] <= '9')) return false; int64_t exponent = 0; int d; while ((d = pfScanDigit(st, 10)) >= 0) { if (exponent < 0x10000000) exponent = 10 * exponent + d; } *out = negative ? -exponent : exponent; return true; } // common.zig `Number(f128)`. typedef struct { int64_t exponent; u128 mantissa; bool many_digits; } PfNumber; // parse.zig `parsePartialNumberBase` for the MantissaT = u128 of f128. The // tokenizer has already validated the literal, so upstream's // `validUnderscores` and whole-token checks always pass here. static bool pfParsePartialNumberBase(PfStream* st, uint32_t base, uint32_t max_mantissa_digits, char exp_char_lower, PfNumber* out) { u128 mantissa = u128Make(0, 0); pfTryParseDigits(st, &mantissa, base); const uint32_t int_end = pfOffsetTrue(st); int64_t n_digits = (int64_t)pfOffsetTrue(st); int64_t exponent = 0; if (pfFirstIs(st, '.')) { st->offset++; const uint32_t marker = pfOffsetTrue(st); pfTryParseDigits(st, &mantissa, base); const uint32_t n_after_dot = pfOffsetTrue(st) - marker; exponent = -(int64_t)n_after_dot; n_digits += n_after_dot; } if (base == 16) exponent *= 4; if (n_digits == 0) return false; int64_t exp_number = 0; if (pfFirstIsLower(st, exp_char_lower)) { st->offset++; if (!pfParseScientific(st, &exp_number)) return false; exponent += exp_number; } out->many_digits = false; if (n_digits <= (int64_t)max_mantissa_digits) { out->exponent = exponent; out->mantissa = mantissa; return true; } n_digits -= max_mantissa_digits; st->offset = 0; // re-parse from beginning st->underscore_count = 0; while (pfFirstIs(st, '0') || pfFirstIs(st, '.') || pfFirstIs(st, '_')) { const char next = st->s[st->offset]; if (next != '_') n_digits -= next >= '0' ? next - ('0' - 1) : 0; // `-|` else st->underscore_count++; st->offset++; } if (n_digits > 0) { // "at this point we have more than max_mantissa_digits significant // digits, let's try again" out->many_digits = true; mantissa = u128Make(0, 0); st->offset = 0; st->underscore_count = 0; pfTryParseNDigits(st, &mantissa, base, max_mantissa_digits); u128 min = u128Make(0, 1); for (uint32_t i = 1; i < max_mantissa_digits; i++) min = u128_mul_small(min, 10); if (u128Cmp(mantissa, min) >= 0) { // big int exponent = (int64_t)int_end - (int64_t)pfOffsetTrue(st); } else { // "the next byte must be present and be '.'" st->offset++; const uint32_t marker = pfOffsetTrue(st); pfTryParseNDigits(st, &mantissa, base, max_mantissa_digits); exponent = (int64_t)marker - (int64_t)pfOffsetTrue(st); } if (base == 16) exponent *= 4; exponent += exp_number; } out->exponent = exponent; out->mantissa = mantissa; return true; } // convert_hex.zig `convertHex(f128, n)`: round-to-even with a sticky // `many_digits` bit; returns the positive bit pattern. static u128 pfConvertHex(PfNumber n) { if (u128IsZero(n.mantissa)) return u128Make(0, 0); const int64_t max_exp = 16383, min_exp = -16382, exp_bias = min_exp - 1; n.exponent += 112; // fractional_bits while ( !u128IsZero(n.mantissa) && u128IsZero(u128Shr(n.mantissa, 112 + 2))) { n.mantissa = u128Shl(n.mantissa, 1); n.exponent -= 1; } if (n.many_digits) n.mantissa = u128Or(n.mantissa, u128Make(0, 1)); while (!u128IsZero(u128Shr(n.mantissa, 1 + 112 + 2))) { n.mantissa = u128Or( u128Shr(n.mantissa, 1), u128And(n.mantissa, u128Make(0, 1))); n.exponent += 1; } while ( u128Cmp(n.mantissa, u128Make(0, 1)) > 0 && n.exponent < min_exp - 2) { n.mantissa = u128Or( u128Shr(n.mantissa, 1), u128And(n.mantissa, u128Make(0, 1))); n.exponent += 1; } uint64_t round = n.mantissa.lo & 3u; n.mantissa = u128Shr(n.mantissa, 2); round |= n.mantissa.lo & 1u; // round to even n.exponent += 2; if (round == 3) { n.mantissa = u128_add_u64(n.mantissa, 1); if (u128Cmp(n.mantissa, u128Bit(1 + 112)) == 0) { n.mantissa = u128Shr(n.mantissa, 1); n.exponent += 1; } } if (u128IsZero(u128Shr(n.mantissa, 112))) n.exponent = exp_bias; if (n.exponent > max_exp) return biasedFpInf(); u128 bits = u128And(n.mantissa, u128LowMask(112)); return u128Or(bits, u128Shl( u128Make(0, (uint64_t)((n.exponent - exp_bias) & 0x7fff)), 112)); } // convert_fast.zig `isFastPath(f128, n)` and `convertFast`'s own decline // (the "disguised" exponent's `math.mul` overflow or oversize mantissa): // true exactly when `convertFast` returns a value, which is then the one // correctly-rounded N * 10^e (`convertExactF128`). static bool pfConvertFastApplies(PfNumber n) { const u128 max_mantissa = u128Shl(u128Make(0, 2), 112); // 2 << 112 if (n.many_digits || n.exponent < -48 || n.exponent > 82 || (u128Cmp(n.mantissa, max_mantissa) > 0)) return false; if (n.exponent <= 48) return true; uint32_t rem; const u128 mul_limit = u128_divmod_small(u128Make(~(uint64_t)0, ~(uint64_t)0), 10, &rem); u128 m = n.mantissa; for (int64_t i = 48; i < n.exponent; i++) { if (u128Cmp(m, mul_limit) > 0) return false; // `math.mul` overflow m = u128_mul_small(m, 10); } return !(u128Cmp(m, max_mantissa) > 0); } // decimal.zig `Decimal(f128).parse`, transcribed with its FloatStream // arithmetic -- including the trailing-zero scan, which indexes the raw // bytes at `offsetTrue() - 1`. static void decimalParse(Decimal128* d, const char* s, uint32_t len) { d->num_digits = 0; d->decimal_point = 0; d->truncated = false; PfStream st = { s, len, 0, 0 }; while (pfFirstIs(&st, '0') || pfFirstIs(&st, '_')) st.offset++; int dg; while ((dg = pfScanDigit(&st, 10)) >= 0) { if (d->num_digits < DEC128_MAX_DIGITS) d->digits[d->num_digits] = (uint8_t)dg; d->num_digits += 1; } if (pfFirstIs(&st, '.')) { st.offset++; const uint32_t marker = pfOffsetTrue(&st); if (d->num_digits == 0) { while (pfFirstIs(&st, '0')) // "Skip leading zeroes" st.offset++; } while ((dg = pfScanDigit(&st, 10)) >= 0) { if (d->num_digits < DEC128_MAX_DIGITS) d->digits[d->num_digits] = (uint8_t)dg; d->num_digits += 1; } d->decimal_point = (int32_t)marker - (int32_t)pfOffsetTrue(&st); } if (d->num_digits != 0) { // "Ignore trailing zeros if any" uint32_t n_trailing_zeros = 0; uint32_t i = pfOffsetTrue(&st) - 1; for (;;) { if (s[i] == '0') n_trailing_zeros += 1; else if (s[i] != '.') break; i -= 1; if (i == 0) break; } d->decimal_point += (int32_t)n_trailing_zeros; d->num_digits -= n_trailing_zeros; d->decimal_point += (int32_t)d->num_digits; if (d->num_digits > DEC128_MAX_DIGITS) { d->truncated = true; d->num_digits = DEC128_MAX_DIGITS; } } if (pfFirstIsLower(&st, 'e')) { st.offset++; bool neg_exp = false; if (pfFirstIs(&st, '-')) { neg_exp = true; st.offset++; } else if (pfFirstIs(&st, '+')) { st.offset++; } int32_t exp_num = 0; while ((dg = pfScanDigit(&st, 10)) >= 0) { if (exp_num < 0x10000) exp_num = 10 * exp_num + dg; } d->decimal_point += neg_exp ? -exp_num : exp_num; } for (uint32_t i = d->num_digits; i < DEC128_MAX_DWO; i++) d->digits[i] = 0; } // parse_float.zig:parseFloat, at T = f128, of a number literal the // tokenizer accepted: the bit pattern of its positive value. CrtU128 parseFloatF128(const char* bytes, uint32_t len) { PfNumber n; PfStream st = { bytes, len, 0, 0 }; if (len >= 2 && bytes[0] == '0' && (bytes[1] == 'x' || bytes[1] == 'X')) { st.s = bytes + 2; st.len = len - 2; if (!pfParsePartialNumberBase(&st, 16, 32, 'p', &n)) UNREACHABLE("parseFloat: a hex float the tokenizer accepted"); return pfConvertHex(n); } if (!pfParsePartialNumberBase(&st, 10, 38, 'e', &n)) UNREACHABLE("parseFloat: a float the tokenizer accepted"); if (pfConvertFastApplies(n)) return convertExactF128(n.mantissa, (int32_t)n.exponent); Decimal128 d; decimalParse(&d, bytes, len); return convertSlowDecimal(&d); } // This file's macros end with it (zig1.c is one translation unit). #undef DEC128_MAX_DIGITS #undef DEC128_MAX_DWO #undef DEC128_DP_RANGE #undef DEC128_MANT_BITS #undef DEC128_MAX_DEC_DIGITS #undef DEC128_MAX_SHIFT /* ---- end parse_float.c ---- */ /* ---- begin parser.c ---- */ // parser.c — lib/std/zig/Parse.zig, function for function under upstream's // names (parseXxx returns null_node, Node.OptionalIndex.none, where upstream // returns null; expectXxx fails where upstream fails), in Parse.zig's order. // Parser state and the Ast it builds are declared in zig1.h. #ifndef ZIG1_H #include "zig1.h" #endif #include #include #include #include static const AstNodeIndex null_node = AST_NODE_NONE; static const AstTokenIndex null_token = AST_TOKEN_NONE; typedef struct { uint32_t len; AstNodeIndex lhs; AstNodeIndex rhs; bool trailing; } Members; typedef struct { enum { FIELD_STATE_NONE, FIELD_STATE_SEEN, FIELD_STATE_END } tag; union { uint32_t end; } payload; } FieldState; typedef struct { enum { SMALL_SPAN_ZERO_OR_ONE, SMALL_SPAN_MULTI } tag; union { AstNodeIndex zero_or_one; AstSubRange multi; } payload; } SmallSpan; typedef struct { AstNodeIndex align_node; AstNodeIndex addrspace_node; AstNodeIndex bit_range_start; AstNodeIndex bit_range_end; } PtrModifiers; typedef struct { int8_t prec; AstNodeTag tag; enum { ASSOC_LEFT, ASSOC_NONE, } assoc; } OperInfo; static AstNodeTag assignOpNode(TokenTag); static AstTokenIndex assertToken(Parser*, TokenTag); static void astNodeListEnsureCapacity(AstNodeList*, uint32_t); static AstTokenIndex eatDocComments(Parser*); static AstTokenIndex eatToken(Parser*, TokenTag); static AstNodeIndex expectBlockExprStatement(Parser*); static AstNodeIndex expectContainerField(Parser*); static AstNodeIndex expectExpr(Parser*); static AstNodeIndex expectIfStatement(Parser*); static AstNodeIndex expectSemicolon(Parser*); static AstNodeIndex expectTestDecl(Parser*); static AstTokenIndex expectToken(Parser*, TokenTag); static AstNodeIndex expectTopLevelDecl(Parser*); static AstNodeIndex expectVarAssignStatement(Parser*, AstTokenIndex); static AstNodeIndex finishAssignDestructureExpr(Parser*, AstNodeIndex); static AstNodeIndex finishAssignExpr(Parser*, AstNodeIndex); static uint32_t forPrefix(Parser*); static AstSubRange listToSpan(Parser*, const AstNodeIndex*, uint32_t); static AstTokenIndex nextToken(Parser*); static AstNodeIndex parseAddrSpace(Parser*); static AstNodeIndex expectAsmExpr(Parser*); static AstNodeIndex parseAsmInputItem(Parser*); static AstNodeIndex parseAsmOutputItem(Parser*); static AstNodeIndex parseAssignExpr(Parser*); static AstNodeIndex parseBlock(Parser*); static AstNodeIndex parseBlockExpr(Parser*); static AstTokenIndex parseBlockLabel(Parser*); static AstTokenIndex parseBreakLabel(Parser*); static AstNodeIndex expectBuiltinCall(Parser*); static AstNodeIndex parseByteAlign(Parser*); static AstNodeIndex parseCallconv(Parser*); static AstNodeIndex expectContainerDeclAuto(Parser*); static Members parseContainerMembers(Parser*); static AstNodeIndex parseCurlySuffixExpr(Parser*); static AstNodeIndex parseErrorUnionExpr(Parser*); static AstNodeIndex parseExpr(Parser*); static AstNodeIndex parseExprPrecedence(Parser*, int32_t); static AstNodeIndex parseFieldInit(Parser*); static AstNodeIndex parseFnProto(Parser*); static AstNodeIndex parseForStatement(Parser*); static AstNodeIndex parseGlobalVarDecl(Parser*); static AstNodeIndex parseLabeledStatement(Parser*); static AstNodeIndex parseLinkSection(Parser*); static AstNodeIndex parseLoopStatement(Parser*); static SmallSpan parseParamDeclList(Parser*); static void parsePayload(Parser*); static AstNodeIndex parsePrefixExpr(Parser*); static AstNodeIndex parsePrimaryExpr(Parser*); static AstNodeIndex parsePrimaryTypeExpr(Parser*); static PtrModifiers parsePtrModifiers(Parser*, bool byte_align); static AstNodeIndex parseSuffixExpr(Parser*); static AstNodeIndex parseSuffixOp(Parser*, AstNodeIndex); static AstNodeIndex parseSwitchExpr(Parser*, bool is_labeled); static AstNodeIndex parseSwitchItem(Parser*); static AstSubRange parseSwitchProngList(Parser*); static AstNodeIndex parseTypeExpr(Parser*); static AstNodeIndex parseWhileContinueExpr(Parser*); static AstNodeIndex expectWhileExpr(Parser*); static AstNodeIndex parseWhileStatement(Parser*); static bool tokensOnSameLine(Parser*, AstTokenIndex, AstTokenIndex); static AstNodeIndex expectIf(Parser*, AstNodeIndex (*)(Parser*)); static AstNodeIndex expectFor(Parser*, AstNodeIndex (*)(Parser*)); static AstTokenIndex parsePtrPayload(Parser*); static AstNodeIndex expectTypeExpr(Parser*); static AstNodeIndex expectPrefixExpr(Parser*); static AstNodeIndex expectAssignExpr(Parser*); static AstNodeIndex expectWhileTypeExpr(Parser*); static AstNodeIndex expectSwitchExpr(Parser*, bool is_labeled); static AstNodeIndex expectSwitchSuffix(Parser*, AstTokenIndex); static AstNodeIndex expectFieldInit(Parser*); static AstTokenIndex eatTokens(Parser*, const TokenTag*, uint32_t); static AstNodeIndex ptrTypeNode( Parser*, AstTokenIndex, AstNodeIndex, PtrModifiers, AstNodeIndex); static void warnMsgAt(Parser*, AstTokenIndex, bool found, const char*); // Parse.zig:warn // A non-fatal parse error: parsing continues, and the tree is not lowered. static void warn(Parser* p, const char* msg) { p->has_warn = true; warnMsgAt(p, p->tok_i, false, msg); } // Parse.zig:warnMsg // Records the first parse error or warning, at `tok`. An "expected ..." // error on the first token of a line is reported at the end of the token // before it. With `found`, the message gains Ast.renderError's // `, found '{s}'` for `tok`. static void warnMsgAt( Parser* p, AstTokenIndex tok, bool found, const char* msg) { if (p->err_buf[0] != '\0') return; if (found) snprintf(p->err_buf, ERR_BUF_SIZE, "%s, found '%s'", msg, tokenTagSymbol(p->token_tags[tok])); else snprintf(p->err_buf, ERR_BUF_SIZE, "%s", msg); // Parse.zig:warnMsg's list is every expected_* tag but expected_statement // and expected_expr_or_var_decl (and expected_container_members, which // zig1 does not report). bool prev = strncmp(msg, "expected ", 9) == 0 && strcmp(msg, "expected statement") != 0 && strcmp(msg, "expected expression or var decl") != 0 && tok != 0 && !tokensOnSameLine(p, tok - 1, tok); p->err_tok = prev ? tok - 1 : tok; p->err_tok_is_prev = prev; } // Parse.zig:failExpected static void failExpected(Parser* p, TokenTag expected) { char msg[64]; snprintf(msg, sizeof(msg), "expected '%s'", tokenTagSymbol(expected)); p->has_compile_errors = true; warnMsgAt(p, p->tok_i, true, msg); } // Parse.zig:fail, a parse error that stops parsing. SET_ERROR_FOUND adds // the found token, as Ast.renderError does for that tag. #define SET_ERROR(p, msg) \ do { \ (p)->has_compile_errors = true; \ warnMsgAt((p), (p)->tok_i, false, (msg)); \ } while (0) #define SET_ERROR_FOUND(p, msg) \ do { \ (p)->has_compile_errors = true; \ warnMsgAt((p), (p)->tok_i, true, (msg)); \ } while (0) static AstSubRange membersToSpan(const Members self, Parser* p) { if (self.len <= 2) { const AstNodeIndex nodes[] = { self.lhs, self.rhs }; return listToSpan(p, nodes, self.len); } else { return (AstSubRange) { .start = self.lhs, .end = self.rhs }; } } // Parse.zig:listToSpan static AstSubRange listToSpan( Parser* p, const AstNodeIndex* list, uint32_t count) { SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, count); memcpy(p->extra_data.arr + p->extra_data.len, list, count * sizeof(AstNodeIndex)); p->extra_data.len += count; return (AstSubRange) { .start = p->extra_data.len - count, .end = p->extra_data.len, }; } // Parse.zig:addNode static AstNodeIndex addNode(AstNodeList* nodes, AstNodeItem item) { astNodeListEnsureCapacity(nodes, 1); nodes->tags[nodes->len] = (uint8_t)item.tag; nodes->main_tokens[nodes->len] = item.main_token; nodes->datas[nodes->len] = item.data; return nodes->len++; } // Parse.zig:setNode static AstNodeIndex setNode(Parser* p, uint32_t i, AstNodeItem item) { p->nodes.tags[i] = (uint8_t)item.tag; p->nodes.main_tokens[i] = item.main_token; p->nodes.datas[i] = item.data; return i; } // Parse.zig:reserveNode static uint32_t reserveNode(Parser* p, AstNodeTag tag) { astNodeListEnsureCapacity(&p->nodes, 1); p->nodes.len++; p->nodes.tags[p->nodes.len - 1] = (uint8_t)tag; return p->nodes.len - 1; } // Parse.zig:addExtra static AstNodeIndex parserAddExtra( Parser* p, const AstNodeIndex* extra, uint32_t count) { const AstNodeIndex result = p->extra_data.len; SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, count); memcpy(p->extra_data.arr + p->extra_data.len, extra, count * sizeof(AstNodeIndex)); p->extra_data.len += count; return result; } static void astNodeListEnsureCapacity(AstNodeList* list, uint32_t additional) { const uint32_t new_len = list->len + additional; if (new_len <= list->cap) { return; } const uint32_t new_cap = new_len > list->cap * 2 ? new_len : list->cap * 2; list->tags = zig1Realloc(list->tags, new_cap * sizeof(uint8_t)); list->main_tokens = zig1Realloc(list->main_tokens, new_cap * sizeof(AstTokenIndex)); list->datas = zig1Realloc(list->datas, new_cap * sizeof(AstData)); list->cap = new_cap; } // Parse.zig:parseRoot void parseRoot(Parser* p) { addNode( &p->nodes, (AstNodeItem) { .tag = AST_NODE_ROOT, .main_token = 0 }); Members root_members = parseContainerMembers(p); if (p->has_compile_errors) return; AstSubRange root_decls = membersToSpan(root_members, p); if (p->token_tags[p->tok_i] != TOKEN_EOF) { failExpected(p, TOKEN_EOF); return; } p->nodes.datas[0].lhs = root_decls.start; p->nodes.datas[0].rhs = root_decls.end; } // Parse.zig:parseZon // The root's data is the one expression a ZON file holds. void parseZon(Parser* p) { addNode( &p->nodes, (AstNodeItem) { .tag = AST_NODE_ROOT, .main_token = 0 }); const AstNodeIndex node_index = expectExpr(p); if (p->has_compile_errors) return; if (p->token_tags[p->tok_i] != TOKEN_EOF) { failExpected(p, TOKEN_EOF); return; } p->nodes.datas[0].lhs = node_index; } // Parse.zig:parseContainerMembers static Members parseContainerMembers(Parser* p) { const uint32_t scratch_top = p->scratch.len; while (eatToken(p, TOKEN_CONTAINER_DOC_COMMENT) != null_token) ; FieldState field_state = { .tag = FIELD_STATE_NONE }; bool trailing = false; while (1) { const AstTokenIndex doc_comment = eatDocComments(p); if (p->has_compile_errors) goto break_loop; switch (p->token_tags[p->tok_i]) { case TOKEN_KEYWORD_TEST: { if (doc_comment != null_token) { SET_ERROR(p, "test_doc_comment"); goto break_loop; } const AstNodeIndex test_decl = expectTestDecl(p); if (field_state.tag == FIELD_STATE_SEEN) { field_state.tag = FIELD_STATE_END; field_state.payload.end = test_decl; } SLICE_APPEND(AstNodeIndex, &p->scratch, test_decl); trailing = false; break; } case TOKEN_KEYWORD_COMPTIME: // comptime can be a container field modifier or a comptime // block/decl. Check if it's followed by a block (comptime { ... // }). if (p->token_tags[p->tok_i + 1] == TOKEN_L_BRACE) { if (doc_comment != null_token) { SET_ERROR(p, "comptime_doc_comment"); goto break_loop; } const AstTokenIndex comptime_token = nextToken(p); const AstNodeIndex block_node = parseBlock(p); SLICE_APPEND(AstNodeIndex, &p->scratch, addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_COMPTIME, .main_token = comptime_token, .data = { .lhs = block_node, .rhs = 0 }, })); trailing = false; break; } // Otherwise it's a container field with comptime modifier goto container_field; case TOKEN_KEYWORD_PUB: { p->tok_i++; AstNodeIndex top_level_decl = expectTopLevelDecl(p); if (top_level_decl != null_node) { if (field_state.tag == FIELD_STATE_SEEN) { field_state.tag = FIELD_STATE_END; field_state.payload.end = top_level_decl; } SLICE_APPEND(AstNodeIndex, &p->scratch, top_level_decl); } trailing = p->token_tags[p->tok_i - 1] == TOKEN_SEMICOLON; break; } case TOKEN_KEYWORD_CONST: case TOKEN_KEYWORD_VAR: case TOKEN_KEYWORD_THREADLOCAL: case TOKEN_KEYWORD_EXPORT: case TOKEN_KEYWORD_EXTERN: case TOKEN_KEYWORD_INLINE: case TOKEN_KEYWORD_NOINLINE: case TOKEN_KEYWORD_FN: { const AstNodeIndex top_level_decl = expectTopLevelDecl(p); if (top_level_decl != null_node) { if (field_state.tag == FIELD_STATE_SEEN) { field_state.tag = FIELD_STATE_END; field_state.payload.end = top_level_decl; } SLICE_APPEND(AstNodeIndex, &p->scratch, top_level_decl); } trailing = (p->token_tags[p->tok_i - 1] == TOKEN_SEMICOLON); break; } case TOKEN_EOF: case TOKEN_R_BRACE: goto break_loop; container_field: default:; // skip parseCStyleContainer const AstNodeIndex field_node = expectContainerField(p); switch (field_state.tag) { case FIELD_STATE_NONE: field_state.tag = FIELD_STATE_SEEN; break; case FIELD_STATE_SEEN: break; case FIELD_STATE_END: SET_ERROR(p, "parseContainerMembers error condition"); goto break_loop; } SLICE_APPEND(AstNodeIndex, &p->scratch, field_node); switch (p->token_tags[p->tok_i]) { case TOKEN_COMMA: p->tok_i++; trailing = true; continue; case TOKEN_R_BRACE: case TOKEN_EOF: trailing = false; goto break_loop; default: SET_ERROR(p, "expected comma after field"); goto break_loop; } } } break_loop:; const uint32_t items_len = p->scratch.len - scratch_top; p->scratch.len = scratch_top; switch (items_len) { case 0: return (Members) { .len = 0, .lhs = null_node, .rhs = null_node, .trailing = trailing, }; case 1: return (Members) { .len = 1, .lhs = p->scratch.arr[scratch_top], .rhs = null_node, .trailing = trailing, }; case 2: return (Members) { .len = 2, .lhs = p->scratch.arr[scratch_top], .rhs = p->scratch.arr[scratch_top + 1], .trailing = trailing, }; default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], items_len); return (Members) { .len = items_len, .lhs = span.start, .rhs = span.end, .trailing = trailing, }; } } // Parse.zig:expectTestDecl static AstNodeIndex expectTestDecl(Parser* p) { const AstTokenIndex test_token = assertToken(p, TOKEN_KEYWORD_TEST); const AstTokenIndex test_name = (p->token_tags[p->tok_i] == TOKEN_STRING_LITERAL || p->token_tags[p->tok_i] == TOKEN_IDENTIFIER) ? nextToken(p) : null_token; const AstNodeIndex body = parseBlock(p); if (body == null_node) { SET_ERROR(p, "expected block after test"); return null_node; } return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_TEST_DECL, .main_token = test_token, .data = { .lhs = test_name, .rhs = body }, }); } // Parse.zig:expectTopLevelDecl static AstNodeIndex expectTopLevelDecl(Parser* p) { AstTokenIndex extern_export_inline_token = nextToken(p); bool is_extern = false; switch (p->token_tags[extern_export_inline_token]) { case TOKEN_KEYWORD_EXTERN: eatToken(p, TOKEN_STRING_LITERAL); is_extern = true; break; case TOKEN_KEYWORD_EXPORT: case TOKEN_KEYWORD_INLINE: case TOKEN_KEYWORD_NOINLINE: break; default: p->tok_i--; } AstNodeIndex fn_proto = parseFnProto(p); if (fn_proto != null_node) { switch (p->token_tags[p->tok_i]) { case TOKEN_SEMICOLON: p->tok_i++; return fn_proto; case TOKEN_L_BRACE: { if (is_extern) { SET_ERROR(p, "extern_fn_body"); return null_node; } AstNodeIndex fn_decl_index = reserveNode(p, AST_NODE_FN_DECL); AstNodeIndex body_block = parseBlock(p); return setNode(p, fn_decl_index, (AstNodeItem) { .tag = AST_NODE_FN_DECL, .main_token = p->nodes.main_tokens[fn_proto], .data = { .lhs = fn_proto, .rhs = body_block }, }); } default: SET_ERROR(p, "expected ';' or block after function prototype"); return null_node; } } eatToken(p, TOKEN_KEYWORD_THREADLOCAL); AstNodeIndex var_decl = parseGlobalVarDecl(p); if (var_decl != null_node) { return var_decl; } // Parse.expectContainerDocComment/parseContainerDeclarations fall // through here; the language has no `usingnamespace` (no Ast.Node.Tag), // so name the real condition. SET_ERROR(p, "expected a container-level declaration"); return null_node; } // Parse.zig:parseFnProto static AstNodeIndex parseFnProto(Parser* p) { AstTokenIndex fn_token = eatToken(p, TOKEN_KEYWORD_FN); if (fn_token == null_token) return null_node; AstNodeIndex fn_proto_index = reserveNode(p, AST_NODE_FN_PROTO); eatToken(p, TOKEN_IDENTIFIER); SmallSpan params = parseParamDeclList(p); const AstNodeIndex align_expr = parseByteAlign(p); const AstNodeIndex addrspace_expr = parseAddrSpace(p); const AstNodeIndex section_expr = parseLinkSection(p); const AstNodeIndex callconv_expr = parseCallconv(p); eatToken(p, TOKEN_BANG); const AstNodeIndex return_type_expr = parseTypeExpr(p); if (return_type_expr == null_node) { SET_ERROR_FOUND(p, "expected return type expression"); return null_node; } if (align_expr == null_node && section_expr == null_node && callconv_expr == null_node && addrspace_expr == null_node) { switch (params.tag) { case SMALL_SPAN_ZERO_OR_ONE: return setNode(p, fn_proto_index, (AstNodeItem) { .tag = AST_NODE_FN_PROTO_SIMPLE, .main_token = fn_token, .data = { .lhs = params.payload.zero_or_one, .rhs = return_type_expr, }, }); case SMALL_SPAN_MULTI: return setNode(p, fn_proto_index, (AstNodeItem) { .tag = AST_NODE_FN_PROTO_MULTI, .main_token = fn_token, .data = { .lhs = parserAddExtra(p, (AstNodeIndex[]) { params.payload.multi.start, params.payload.multi.end }, 2), .rhs = return_type_expr, }, }); } } // Complex fn proto with align/section/callconv/addrspace switch (params.tag) { case SMALL_SPAN_ZERO_OR_ONE: return setNode(p, fn_proto_index, (AstNodeItem) { .tag = AST_NODE_FN_PROTO_ONE, .main_token = fn_token, .data = { .lhs = parserAddExtra(p, (AstNodeIndex[]) { params.payload.zero_or_one, align_expr, addrspace_expr, section_expr, callconv_expr }, 5), .rhs = return_type_expr, }, }); case SMALL_SPAN_MULTI: return setNode(p, fn_proto_index, (AstNodeItem) { .tag = AST_NODE_FN_PROTO, .main_token = fn_token, .data = { .lhs = parserAddExtra(p, (AstNodeIndex[]) { params.payload.multi.start, params.payload.multi.end, align_expr, addrspace_expr, section_expr, callconv_expr }, 6), .rhs = return_type_expr, }, }); } return null_node; } // Parse.zig:setVarDeclInitExpr static void setVarDeclInitExpr( Parser* p, AstNodeIndex var_decl, AstNodeIndex init_expr) { switch (p->nodes.tags[var_decl]) { case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_GLOBAL_VAR_DECL: p->nodes.datas[var_decl].rhs = init_expr; break; default: UNREACHABLE("setVarDeclInitExpr: not a var decl"); } } // Parse.zig:parseVarDeclProto static AstNodeIndex parseVarDeclProto(Parser* p) { AstTokenIndex mut_token; if ((mut_token = eatToken(p, TOKEN_KEYWORD_CONST)) == null_token) if ((mut_token = eatToken(p, TOKEN_KEYWORD_VAR)) == null_token) return null_node; expectToken(p, TOKEN_IDENTIFIER); const AstNodeIndex type_node = eatToken(p, TOKEN_COLON) == null_token ? null_node : expectTypeExpr(p); const AstNodeIndex align_node = parseByteAlign(p); const AstNodeIndex addrspace_node = parseAddrSpace(p); const AstNodeIndex section_node = parseLinkSection(p); if (section_node == null_node && addrspace_node == null_node) { if (align_node == null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_SIMPLE_VAR_DECL, .main_token = mut_token, .data = { .lhs = type_node, .rhs = null_node }, }); } if (type_node == null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ALIGNED_VAR_DECL, .main_token = mut_token, .data = { .lhs = align_node, .rhs = null_node }, }); } return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_LOCAL_VAR_DECL, .main_token = mut_token, .data = { .lhs = parserAddExtra(p, (AstNodeIndex[]) { type_node, align_node }, 2), .rhs = null_node, }, }); } return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_GLOBAL_VAR_DECL, .main_token = mut_token, .data = { .lhs = parserAddExtra(p, (AstNodeIndex[]) { type_node, align_node, addrspace_node, section_node }, 4), .rhs = null_node, }, }); } // Parse.zig:parseGlobalVarDecl static AstNodeIndex parseGlobalVarDecl(Parser* p) { const AstNodeIndex var_decl = parseVarDeclProto(p); if (var_decl == null_node) { return null_node; } AstNodeIndex init_node = null_node; switch (p->token_tags[p->tok_i]) { case TOKEN_EQUAL_EQUAL: warn(p, "variable initialized with '==' instead of '='"); p->tok_i += 1; init_node = expectExpr(p); break; case TOKEN_EQUAL: p->tok_i += 1; init_node = expectExpr(p); break; default: break; } setVarDeclInitExpr(p, var_decl, init_node); expectSemicolon(p); return var_decl; } // Parse.zig:expectContainerField static AstNodeIndex expectContainerField(Parser* p) { eatToken(p, TOKEN_KEYWORD_COMPTIME); const AstTokenIndex main_token = p->tok_i; static const TokenTag name_tags[] = { TOKEN_IDENTIFIER, TOKEN_COLON }; eatTokens(p, name_tags, 2); const AstNodeIndex type_expr = expectTypeExpr(p); if (type_expr == null_node) return null_node; const AstNodeIndex align_expr = parseByteAlign(p); const AstNodeIndex value_expr = eatToken(p, TOKEN_EQUAL) != null_token ? expectExpr(p) : null_node; if (align_expr == null_node) { return addNode( &p->nodes, (AstNodeItem) { .tag = AST_NODE_CONTAINER_FIELD_INIT, .main_token = main_token, .data = { .lhs = type_expr, .rhs = value_expr, }, }); } else if (value_expr == null_node) { return addNode( &p->nodes, (AstNodeItem) { .tag = AST_NODE_CONTAINER_FIELD_ALIGN, .main_token = main_token, .data = { .lhs = type_expr, .rhs = align_expr, }, }); } else { return addNode( &p->nodes, (AstNodeItem) { .tag = AST_NODE_CONTAINER_FIELD, .main_token = main_token, .data = { .lhs = type_expr, .rhs = parserAddExtra(p, (AstNodeIndex[]) { align_expr, value_expr }, 2), }, }); } } // Parse.zig:expectStatement static AstNodeIndex expectStatement(Parser* p, bool is_block_level) { const AstTokenIndex comptime_token = eatToken(p, TOKEN_KEYWORD_COMPTIME); if (comptime_token != null_token) { // comptime followed by block => comptime block statement const AstNodeIndex block = parseBlockExpr(p); if (block != null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_COMPTIME, .main_token = comptime_token, .data = { .lhs = block, .rhs = 0 }, }); } // comptime var decl or expression if (is_block_level) return expectVarAssignStatement(p, comptime_token); { const AstNodeIndex assign = expectAssignExpr(p); if (assign == null_node) return null_node; expectSemicolon(p); if (p->nodes.tags[assign] == AST_NODE_ASSIGN_DESTRUCTURE) return assign; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_COMPTIME, .main_token = comptime_token, .data = { .lhs = assign, .rhs = 0 }, }); } } const AstNodeIndex tok = p->token_tags[p->tok_i]; switch (tok) { case TOKEN_KEYWORD_DEFER: if (is_block_level) return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_DEFER, .main_token = nextToken(p), .data = { .lhs = expectBlockExprStatement(p), .rhs = 0, }, }); break; case TOKEN_KEYWORD_ERRDEFER: if (is_block_level) { const AstTokenIndex errdefer_token = nextToken(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ERRDEFER, .main_token = errdefer_token, .data = { .lhs = expectBlockExprStatement(p), .rhs = 0, }, }); } break; case TOKEN_KEYWORD_NOSUSPEND: return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_NOSUSPEND, .main_token = nextToken(p), .data = { .lhs = expectBlockExprStatement(p), .rhs = 0, }, }); case TOKEN_KEYWORD_SUSPEND: return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_SUSPEND, .main_token = nextToken(p), .data = { .lhs = expectBlockExprStatement(p), .rhs = 0, }, }); case TOKEN_KEYWORD_IF: return expectIfStatement(p); case TOKEN_KEYWORD_ENUM: case TOKEN_KEYWORD_STRUCT: case TOKEN_KEYWORD_UNION: // Parse.zig: fall through to expression parsing. default:; } const AstNodeIndex labeled_statement = parseLabeledStatement(p); if (labeled_statement != null_node) return labeled_statement; if (is_block_level) { return expectVarAssignStatement(p, null_token); } else { const AstNodeIndex assign = expectAssignExpr(p); expectSemicolon(p); return assign; } } // Parse.zig:expectVarAssignStatement static AstNodeIndex expectVarAssignStatement( Parser* p, AstTokenIndex comptime_token) { const uint32_t scratch_top = p->scratch.len; while (true) { const AstNodeIndex var_decl_proto = parseVarDeclProto(p); if (var_decl_proto != null_node) { SLICE_APPEND(AstNodeIndex, &p->scratch, var_decl_proto); } else { const AstNodeIndex expr = parseExpr(p); if (expr == null_node) { if (p->scratch.len == scratch_top) SET_ERROR_FOUND(p, "expected statement"); else SET_ERROR_FOUND(p, "expected expression or var decl"); return null_node; } SLICE_APPEND(AstNodeIndex, &p->scratch, expr); } if (eatToken(p, TOKEN_COMMA) == null_token) break; } const uint32_t lhs_count = p->scratch.len - scratch_top; assert(lhs_count > 0); // Try to eat '=' for assignment/initialization // (matches upstream: `const equal_token = p.eatToken(.equal) orelse eql:`) AstTokenIndex equal_token = eatToken(p, TOKEN_EQUAL); if (equal_token == null_token) { if (lhs_count > 1) { // Destructure requires '=' SET_ERROR(p, "expected '='"); return null_node; } const AstNodeIndex lhs = p->scratch.arr[scratch_top]; p->scratch.len = scratch_top; const AstNodeTag lhs_tag = p->nodes.tags[lhs]; if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL || lhs_tag == AST_NODE_ALIGNED_VAR_DECL || lhs_tag == AST_NODE_LOCAL_VAR_DECL || lhs_tag == AST_NODE_GLOBAL_VAR_DECL) { // var decl without init requires '=' SET_ERROR(p, "expected '='"); return null_node; } // Expression statement: finish with assignment operators or semicolon const AstNodeIndex expr = finishAssignExpr(p, lhs); // Liberal acceptance: upstream's // expectSemicolon(.expected_semi_after_stmt) requires the `;`, zig1 // accepts a statement without it. eatToken(p, TOKEN_SEMICOLON); if (comptime_token != null_token) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_COMPTIME, .main_token = comptime_token, .data = { .lhs = expr, .rhs = 0 }, }); } return expr; } // Have '=', parse RHS and semicolon const AstNodeIndex rhs = expectExpr(p); expectSemicolon(p); if (lhs_count == 1) { const AstNodeIndex lhs = p->scratch.arr[scratch_top]; p->scratch.len = scratch_top; const AstNodeTag lhs_tag = p->nodes.tags[lhs]; if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL || lhs_tag == AST_NODE_ALIGNED_VAR_DECL || lhs_tag == AST_NODE_LOCAL_VAR_DECL || lhs_tag == AST_NODE_GLOBAL_VAR_DECL) { // var decl initialization: const x = val; p->nodes.datas[lhs].rhs = rhs; return lhs; } // Simple assignment: x = val; const AstNodeIndex assign = addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ASSIGN, .main_token = equal_token, .data = { .lhs = lhs, .rhs = rhs }, }); if (comptime_token != null_token) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_COMPTIME, .main_token = comptime_token, .data = { .lhs = assign, .rhs = 0 }, }); } return assign; } // Destructure: a, b, c = rhs // rhs and semicolon already parsed above // Store count + lhs nodes in extra_data const AstNodeIndex extra_start = p->extra_data.len; SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, lhs_count + 1); p->extra_data.arr[p->extra_data.len++] = lhs_count; memcpy(p->extra_data.arr + p->extra_data.len, &p->scratch.arr[scratch_top], lhs_count * sizeof(AstNodeIndex)); p->extra_data.len += lhs_count; p->scratch.len = scratch_top; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ASSIGN_DESTRUCTURE, .main_token = equal_token, .data = { .lhs = extra_start, .rhs = rhs }, }); } // Parse.zig:expectIfStatement static AstNodeIndex expectIfStatement(Parser* p) { const AstTokenIndex if_token = assertToken(p, TOKEN_KEYWORD_IF); expectToken(p, TOKEN_L_PAREN); const AstNodeIndex condition = expectExpr(p); expectToken(p, TOKEN_R_PAREN); parsePtrPayload(p); bool else_required = false; AstNodeIndex then_body; const AstNodeIndex block2 = parseBlockExpr(p); if (block2 != null_node) { then_body = block2; } else { then_body = parseAssignExpr(p); if (then_body == null_node) { SET_ERROR_FOUND(p, "expected block or assignment"); return null_node; } if (eatToken(p, TOKEN_SEMICOLON) != null_token) return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_IF_SIMPLE, .main_token = if_token, .data = { .lhs = condition, .rhs = then_body }, }); else_required = true; } if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { if (else_required) warn(p, "expected ';' or 'else' after statement"); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_IF_SIMPLE, .main_token = if_token, .data = { .lhs = condition, .rhs = then_body }, }); } parsePayload(p); const AstNodeIndex else_body = expectStatement(p, false); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_IF, .main_token = if_token, .data = { .lhs = condition, .rhs = parserAddExtra(p, (AstNodeIndex[]) { then_body, else_body }, 2), }, }); } // Parse.zig:parseLabeledStatement static AstNodeIndex parseLabeledStatement(Parser* p) { const AstTokenIndex label_token = parseBlockLabel(p); const AstNodeIndex block = parseBlock(p); if (block != null_node) return block; const AstNodeIndex loop_stmt = parseLoopStatement(p); if (loop_stmt != null_node) return loop_stmt; const AstNodeIndex switch_expr = parseSwitchExpr(p, label_token != null_token); if (switch_expr != null_node) return switch_expr; if (label_token != null_token) { SET_ERROR_FOUND(p, "expected 'while', 'for', 'inline', or '{'"); return null_node; } return null_node; } // Parse.zig:parseLoopStatement static AstNodeIndex parseLoopStatement(Parser* p) { const AstTokenIndex inline_token = eatToken(p, TOKEN_KEYWORD_INLINE); const AstNodeIndex for_statement = parseForStatement(p); if (for_statement != null_node) return for_statement; const AstNodeIndex while_statement = parseWhileStatement(p); if (while_statement != null_node) return while_statement; if (inline_token == null_token) return null_node; SET_ERROR_FOUND(p, "expected 'while' or 'for'"); return null_node; } // Parse.zig:parseForStatement static AstNodeIndex parseForStatement(Parser* p) { const AstTokenIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR); if (for_token == null_token) return null_node; const uint32_t scratch_top = p->scratch.len; const uint32_t inputs = forPrefix(p); // Statement body: block or assign expr bool else_required = false; bool seen_semicolon = false; AstNodeIndex then_body; const AstNodeIndex block = parseBlockExpr(p); if (block != null_node) { then_body = block; } else { then_body = parseAssignExpr(p); if (then_body == null_node) { SET_ERROR_FOUND(p, "expected block or assignment"); return null_node; } if (eatToken(p, TOKEN_SEMICOLON) != null_token) { seen_semicolon = true; } else { else_required = true; } } bool has_else = false; if (!seen_semicolon && eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) { parsePayload(p); SLICE_APPEND(AstNodeIndex, &p->scratch, then_body); const AstNodeIndex else_body = expectStatement(p, false); SLICE_APPEND(AstNodeIndex, &p->scratch, else_body); has_else = true; } else if (inputs == 1) { if (else_required) warn(p, "expected ';' or 'else' after statement"); p->scratch.len = scratch_top; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_FOR_SIMPLE, .main_token = for_token, .data = { .lhs = p->scratch.arr[scratch_top], .rhs = then_body, }, }); } else { if (else_required) warn(p, "expected ';' or 'else' after statement"); SLICE_APPEND(AstNodeIndex, &p->scratch, then_body); } const uint32_t total = p->scratch.len - scratch_top; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], total); p->scratch.len = scratch_top; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_FOR, .main_token = for_token, .data = { .lhs = span.start, .rhs = ((uint32_t)inputs & AST_FOR_INPUTS_MASK) | (has_else ? AST_FOR_HAS_ELSE : 0), }, }); } // Parse.zig:parseWhileStatement static AstNodeIndex parseWhileStatement(Parser* p) { const AstTokenIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE); if (while_token == null_token) return null_node; expectToken(p, TOKEN_L_PAREN); const AstNodeIndex condition = expectExpr(p); expectToken(p, TOKEN_R_PAREN); parsePtrPayload(p); const AstNodeIndex cont_expr = parseWhileContinueExpr(p); // Statement body: block, or assign expr bool else_required = false; AstNodeIndex body; const AstNodeIndex block = parseBlockExpr(p); if (block != null_node) { body = block; } else { body = parseAssignExpr(p); if (body == null_node) { SET_ERROR_FOUND(p, "expected block or assignment"); return null_node; } if (eatToken(p, TOKEN_SEMICOLON) != null_token) { if (cont_expr != null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_WHILE_CONT, .main_token = while_token, .data = { .lhs = condition, .rhs = parserAddExtra(p, (AstNodeIndex[]) { cont_expr, body }, 2), }, }); } return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_WHILE_SIMPLE, .main_token = while_token, .data = { .lhs = condition, .rhs = body }, }); } else_required = true; } if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { if (else_required) warn(p, "expected ';' or 'else' after statement"); if (cont_expr != null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_WHILE_CONT, .main_token = while_token, .data = { .lhs = condition, .rhs = parserAddExtra(p, (AstNodeIndex[]) { cont_expr, body }, 2), }, }); } return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_WHILE_SIMPLE, .main_token = while_token, .data = { .lhs = condition, .rhs = body }, }); } parsePayload(p); const AstNodeIndex else_body = expectStatement(p, false); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_WHILE, .main_token = while_token, .data = { .lhs = condition, .rhs = parserAddExtra(p, (AstNodeIndex[]) { cont_expr, body, else_body }, 3), }, }); } // Parse.zig:parseBlockExprStatement static AstNodeIndex parseBlockExprStatement(Parser* p) { const AstNodeIndex block_expr = parseBlockExpr(p); if (block_expr != null_node) return block_expr; const AstNodeIndex assign_expr = parseAssignExpr(p); if (assign_expr != null_node) { expectSemicolon(p); return assign_expr; } return null_node; } // Parse.zig:expectBlockExprStatement static AstNodeIndex expectBlockExprStatement(Parser* p) { const AstNodeIndex node = parseBlockExprStatement(p); if (node == null_node) SET_ERROR_FOUND(p, "expected block or expression"); return node; } // Parse.zig:parseBlockExpr static AstNodeIndex parseBlockExpr(Parser* p) { if (p->token_tags[p->tok_i] == TOKEN_L_BRACE) return parseBlock(p); if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER && p->token_tags[p->tok_i + 1] == TOKEN_COLON && p->token_tags[p->tok_i + 2] == TOKEN_L_BRACE) { p->tok_i += 2; return parseBlock(p); } return null_node; } // Parse.zig:parseAssignExpr static AstNodeIndex parseAssignExpr(Parser* p) { const AstNodeIndex expr = parseExpr(p); if (expr == null_node) return null_node; return finishAssignExpr(p, expr); } // Parse.zig:parseSingleAssignExpr static AstNodeIndex parseSingleAssignExpr(Parser* p) { const AstNodeIndex expr = parseExpr(p); if (expr == null_node) return null_node; const AstNodeTag tag = assignOpNode(p->token_tags[p->tok_i]); if (tag == AST_NODE_ROOT) return expr; const AstTokenIndex op_token = nextToken(p); const AstNodeIndex rhs = expectExpr(p); return addNode(&p->nodes, (AstNodeItem) { .tag = tag, .main_token = op_token, .data = { .lhs = expr, .rhs = rhs }, }); } // Parse.zig:finishAssignExpr static AstNodeIndex finishAssignExpr(Parser* p, AstNodeIndex lhs) { if (p->token_tags[p->tok_i] == TOKEN_COMMA) return finishAssignDestructureExpr(p, lhs); const AstNodeTag assign_tag = assignOpNode(p->token_tags[p->tok_i]); if (assign_tag == AST_NODE_ROOT) return lhs; const AstTokenIndex op_token = nextToken(p); const AstNodeIndex rhs = expectExpr(p); return addNode(&p->nodes, (AstNodeItem) { .tag = assign_tag, .main_token = op_token, .data = { .lhs = lhs, .rhs = rhs }, }); } // Parse.zig:assignOpNode static AstNodeTag assignOpNode(TokenTag tok) { switch (tok) { case TOKEN_EQUAL: return AST_NODE_ASSIGN; case TOKEN_PLUS_EQUAL: return AST_NODE_ASSIGN_ADD; case TOKEN_MINUS_EQUAL: return AST_NODE_ASSIGN_SUB; case TOKEN_ASTERISK_EQUAL: return AST_NODE_ASSIGN_MUL; case TOKEN_SLASH_EQUAL: return AST_NODE_ASSIGN_DIV; case TOKEN_PERCENT_EQUAL: return AST_NODE_ASSIGN_MOD; case TOKEN_AMPERSAND_EQUAL: return AST_NODE_ASSIGN_BIT_AND; case TOKEN_PIPE_EQUAL: return AST_NODE_ASSIGN_BIT_OR; case TOKEN_CARET_EQUAL: return AST_NODE_ASSIGN_BIT_XOR; case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_EQUAL: return AST_NODE_ASSIGN_SHL; case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT_EQUAL: return AST_NODE_ASSIGN_SHR; case TOKEN_PLUS_PERCENT_EQUAL: return AST_NODE_ASSIGN_ADD_WRAP; case TOKEN_MINUS_PERCENT_EQUAL: return AST_NODE_ASSIGN_SUB_WRAP; case TOKEN_ASTERISK_PERCENT_EQUAL: return AST_NODE_ASSIGN_MUL_WRAP; case TOKEN_PLUS_PIPE_EQUAL: return AST_NODE_ASSIGN_ADD_SAT; case TOKEN_MINUS_PIPE_EQUAL: return AST_NODE_ASSIGN_SUB_SAT; case TOKEN_ASTERISK_PIPE_EQUAL: return AST_NODE_ASSIGN_MUL_SAT; case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE_EQUAL: return AST_NODE_ASSIGN_SHL_SAT; default: return AST_NODE_ROOT; // not an assignment op } } // Parse.zig:finishAssignDestructureExpr static AstNodeIndex finishAssignDestructureExpr( Parser* p, AstNodeIndex first_lhs) { const uint32_t scratch_top = p->scratch.len; SLICE_APPEND(AstNodeIndex, &p->scratch, first_lhs); while (eatToken(p, TOKEN_COMMA) != null_token) { const AstNodeIndex expr = expectExpr(p); SLICE_APPEND(AstNodeIndex, &p->scratch, expr); } const AstTokenIndex equal_token = expectToken(p, TOKEN_EQUAL); const AstNodeIndex rhs = expectExpr(p); const uint32_t lhs_count = p->scratch.len - scratch_top; assert(lhs_count > 1); const AstNodeIndex extra_start = p->extra_data.len; SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, lhs_count + 1); p->extra_data.arr[p->extra_data.len++] = lhs_count; memcpy(p->extra_data.arr + p->extra_data.len, &p->scratch.arr[scratch_top], lhs_count * sizeof(AstNodeIndex)); p->extra_data.len += lhs_count; p->scratch.len = scratch_top; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ASSIGN_DESTRUCTURE, .main_token = equal_token, .data = { .lhs = extra_start, .rhs = rhs }, }); } // Parse.zig:expectSingleAssignExpr static AstNodeIndex expectSingleAssignExpr(Parser* p) { const AstNodeIndex expr = parseSingleAssignExpr(p); if (expr == null_node) SET_ERROR_FOUND(p, "expected expression or assignment"); return expr; } // Parse.zig:expectAssignExpr static AstNodeIndex expectAssignExpr(Parser* p) { const AstNodeIndex expr = parseAssignExpr(p); if (expr == null_node) SET_ERROR_FOUND(p, "expected expression or assignment"); return expr; } // Parse.zig:parseExpr static AstNodeIndex parseExpr(Parser* p) { return parseExprPrecedence(p, 0); } // Parse.zig:expectExpr static AstNodeIndex expectExpr(Parser* p) { const AstNodeIndex node = parseExpr(p); if (node == null_node) { SET_ERROR_FOUND(p, "expected expression"); return null_node; } return node; } static OperInfo operTable(TokenTag tok_tag) { switch (tok_tag) { case TOKEN_KEYWORD_OR: return (OperInfo) { .prec = 10, .tag = AST_NODE_BOOL_OR }; case TOKEN_KEYWORD_AND: return (OperInfo) { .prec = 20, .tag = AST_NODE_BOOL_AND }; case TOKEN_EQUAL_EQUAL: return (OperInfo) { .prec = 30, .tag = AST_NODE_EQUAL_EQUAL, .assoc = ASSOC_NONE }; case TOKEN_BANG_EQUAL: return (OperInfo) { .prec = 30, .tag = AST_NODE_BANG_EQUAL, .assoc = ASSOC_NONE }; case TOKEN_ANGLE_BRACKET_LEFT: return (OperInfo) { .prec = 30, .tag = AST_NODE_LESS_THAN, .assoc = ASSOC_NONE }; case TOKEN_ANGLE_BRACKET_RIGHT: return (OperInfo) { .prec = 30, .tag = AST_NODE_GREATER_THAN, .assoc = ASSOC_NONE }; case TOKEN_ANGLE_BRACKET_LEFT_EQUAL: return (OperInfo) { .prec = 30, .tag = AST_NODE_LESS_OR_EQUAL, .assoc = ASSOC_NONE }; case TOKEN_ANGLE_BRACKET_RIGHT_EQUAL: return (OperInfo) { .prec = 30, .tag = AST_NODE_GREATER_OR_EQUAL, .assoc = ASSOC_NONE }; case TOKEN_AMPERSAND: return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_AND }; case TOKEN_CARET: return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_XOR }; case TOKEN_PIPE: return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_OR }; case TOKEN_KEYWORD_ORELSE: return (OperInfo) { .prec = 40, .tag = AST_NODE_ORELSE }; case TOKEN_KEYWORD_CATCH: return (OperInfo) { .prec = 40, .tag = AST_NODE_CATCH }; case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT: return (OperInfo) { .prec = 50, .tag = AST_NODE_SHL }; case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE: return (OperInfo) { .prec = 50, .tag = AST_NODE_SHL_SAT }; case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT: return (OperInfo) { .prec = 50, .tag = AST_NODE_SHR }; case TOKEN_PLUS: return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD }; case TOKEN_MINUS: return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB }; case TOKEN_PLUS_PLUS: return (OperInfo) { .prec = 60, .tag = AST_NODE_ARRAY_CAT }; case TOKEN_PLUS_PERCENT: return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD_WRAP }; case TOKEN_MINUS_PERCENT: return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB_WRAP }; case TOKEN_PLUS_PIPE: return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD_SAT }; case TOKEN_MINUS_PIPE: return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB_SAT }; case TOKEN_PIPE_PIPE: return (OperInfo) { .prec = 70, .tag = AST_NODE_MERGE_ERROR_SETS }; case TOKEN_ASTERISK: return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL }; case TOKEN_SLASH: return (OperInfo) { .prec = 70, .tag = AST_NODE_DIV }; case TOKEN_PERCENT: return (OperInfo) { .prec = 70, .tag = AST_NODE_MOD }; case TOKEN_ASTERISK_PERCENT: return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL_WRAP }; case TOKEN_ASTERISK_PIPE: return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL_SAT }; default: return (OperInfo) { .prec = -1, .tag = AST_NODE_ROOT }; } } // Parse.zig:parseExprPrecedence static AstNodeIndex parseExprPrecedence(Parser* p, int32_t min_prec) { assert(min_prec >= 0); AstNodeIndex node = parsePrefixExpr(p); if (node == null_node) return null_node; int8_t banned_prec = -1; while (true) { const TokenTag tok_tag = p->token_tags[p->tok_i]; const OperInfo info = operTable(tok_tag); if (info.prec < min_prec) break; if (info.prec == banned_prec) { SET_ERROR(p, "chained comparison operators"); return null_node; } const AstTokenIndex oper_token = nextToken(p); if (tok_tag == TOKEN_KEYWORD_CATCH) parsePayload(p); { const uint32_t tok_len = (uint32_t)strlen(tokenTagLexeme(tok_tag)); const uint32_t tok_start = p->token_starts[oper_token]; if (tok_start + tok_len >= p->source_len) { SET_ERROR_FOUND(p, "expected expression"); return null_node; } const char char_before = p->source[tok_start - 1]; const char char_after = p->source[tok_start + tok_len]; if (tok_tag == TOKEN_AMPERSAND && char_after == '&') { SET_ERROR(p, "invalid ampersand ampersand"); return null_node; } else if (isspace((unsigned char)char_before) != isspace((unsigned char)char_after)) { SET_ERROR(p, "mismatched binary op whitespace"); return null_node; } } const AstNodeIndex rhs = parseExprPrecedence(p, info.prec + 1); if (rhs == null_node) { SET_ERROR_FOUND(p, "expected expression"); return null_node; } node = addNode( &p->nodes, (AstNodeItem) { .tag = info.tag, .main_token = oper_token, .data = { .lhs = node, .rhs = rhs, }, }); if (info.assoc == ASSOC_NONE) banned_prec = info.prec; } return node; } // Parse.zig:parsePrefixExpr static AstNodeIndex parsePrefixExpr(Parser* p) { AstNodeTag tag; switch (p->token_tags[p->tok_i]) { case TOKEN_BANG: tag = AST_NODE_BOOL_NOT; break; case TOKEN_MINUS: tag = AST_NODE_NEGATION; break; case TOKEN_TILDE: tag = AST_NODE_BIT_NOT; break; case TOKEN_MINUS_PERCENT: tag = AST_NODE_NEGATION_WRAP; break; case TOKEN_AMPERSAND: tag = AST_NODE_ADDRESS_OF; break; case TOKEN_KEYWORD_TRY: tag = AST_NODE_TRY; break; default: return parsePrimaryExpr(p); } const AstTokenIndex main_token = nextToken(p); return addNode(&p->nodes, (AstNodeItem) { .tag = tag, .main_token = main_token, .data = { .lhs = expectPrefixExpr(p), .rhs = 0 }, }); } // Parse.zig:expectPrefixExpr static AstNodeIndex expectPrefixExpr(Parser* p) { const AstNodeIndex expr = parsePrefixExpr(p); if (expr == null_node) SET_ERROR_FOUND(p, "expected prefix expression"); return expr; } // Parse.zig:parseTypeExpr static AstNodeIndex parseTypeExpr(Parser* p) { switch (p->token_tags[p->tok_i]) { case TOKEN_QUESTION_MARK: { const AstTokenIndex main_token = nextToken(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_OPTIONAL_TYPE, .main_token = main_token, .data = { .lhs = expectTypeExpr(p), .rhs = 0 }, }); } case TOKEN_KEYWORD_ANYFRAME: switch (p->token_tags[p->tok_i + 1]) { case TOKEN_ARROW: { const AstTokenIndex main_token = nextToken(p); const AstTokenIndex arrow = nextToken(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ANYFRAME_TYPE, .main_token = main_token, .data = { .lhs = arrow, .rhs = expectTypeExpr(p) }, }); } default: return parseErrorUnionExpr(p); } case TOKEN_ASTERISK: { const AstTokenIndex asterisk = nextToken(p); const PtrModifiers mods = parsePtrModifiers(p, false); const AstNodeIndex elem_type = expectTypeExpr(p); if (mods.bit_range_start != null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_PTR_TYPE_BIT_RANGE, .main_token = asterisk, .data = { .lhs = parserAddExtra(p, (AstNodeIndex[]) { null_node, mods.align_node, mods.addrspace_node, mods.bit_range_start, mods.bit_range_end }, 5), .rhs = elem_type, }, }); } else if (mods.addrspace_node != null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_PTR_TYPE, .main_token = asterisk, .data = { .lhs = parserAddExtra(p, (AstNodeIndex[]) { null_node, mods.align_node, mods.addrspace_node }, 3), .rhs = elem_type, }, }); } else { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_PTR_TYPE_ALIGNED, .main_token = asterisk, .data = { .lhs = mods.align_node, .rhs = elem_type }, }); } } case TOKEN_L_BRACKET: switch (p->token_tags[p->tok_i + 1]) { case TOKEN_ASTERISK: { const AstTokenIndex l_bracket = nextToken(p); nextToken(p); AstNodeIndex sentinel = null_node; const AstTokenIndex ident = eatToken(p, TOKEN_IDENTIFIER); if (ident != null_token) { const uint32_t ident_start = p->token_starts[ident]; const uint32_t next = ident_start + 1; const bool continues_identifier = next < p->source_len && (isalnum((unsigned char)p->source[next]) || p->source[next] == '_'); const bool is_c = p->source[ident_start] == 'c' && !continues_identifier; if (!is_c) p->tok_i -= 1; } else if (eatToken(p, TOKEN_COLON) != null_token) { sentinel = expectExpr(p); } expectToken(p, TOKEN_R_BRACKET); const PtrModifiers mods = parsePtrModifiers(p, true); const AstNodeIndex elem_type = expectTypeExpr(p); return ptrTypeNode(p, l_bracket, sentinel, mods, elem_type); } default: { const AstTokenIndex lbracket = nextToken(p); const AstNodeIndex len_expr = parseExpr(p); const AstNodeIndex sentinel = eatToken(p, TOKEN_COLON) != null_token ? expectExpr(p) : null_node; expectToken(p, TOKEN_R_BRACKET); if (len_expr == null_node) { const PtrModifiers mods = parsePtrModifiers(p, true); const AstNodeIndex elem_type = expectTypeExpr(p); return ptrTypeNode(p, lbracket, sentinel, mods, elem_type); } switch (p->token_tags[p->tok_i]) { case TOKEN_KEYWORD_ALIGN: case TOKEN_KEYWORD_CONST: case TOKEN_KEYWORD_VOLATILE: case TOKEN_KEYWORD_ALLOWZERO: case TOKEN_KEYWORD_ADDRSPACE: { char msg[96]; snprintf(msg, sizeof(msg), "pointer modifier '%s' not allowed on array child type", tokenTagSymbol(p->token_tags[p->tok_i])); SET_ERROR(p, msg); return null_node; } default: break; } const AstNodeIndex elem_type = expectTypeExpr(p); if (sentinel == null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ARRAY_TYPE, .main_token = lbracket, .data = { .lhs = len_expr, .rhs = elem_type }, }); } return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ARRAY_TYPE_SENTINEL, .main_token = lbracket, .data = { .lhs = len_expr, .rhs = parserAddExtra( p, (AstNodeIndex[]) { sentinel, elem_type }, 2), }, }); } } default: return parseErrorUnionExpr(p); } } // The three node shapes parseTypeExpr gives a many-item or slice pointer, // which upstream spells out at both of its sites. static AstNodeIndex ptrTypeNode(Parser* p, AstTokenIndex main_token, AstNodeIndex sentinel, PtrModifiers mods, AstNodeIndex elem_type) { if (sentinel == null_node && mods.addrspace_node == null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_PTR_TYPE_ALIGNED, .main_token = main_token, .data = { .lhs = mods.align_node, .rhs = elem_type }, }); } else if (mods.align_node == null_node && mods.addrspace_node == null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_PTR_TYPE_SENTINEL, .main_token = main_token, .data = { .lhs = sentinel, .rhs = elem_type }, }); } else { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_PTR_TYPE, .main_token = main_token, .data = { .lhs = parserAddExtra(p, (AstNodeIndex[]) { sentinel, mods.align_node, mods.addrspace_node }, 3), .rhs = elem_type, }, }); } } // Parse.zig:expectTypeExpr static AstNodeIndex expectTypeExpr(Parser* p) { const AstNodeIndex expr = parseTypeExpr(p); if (expr == null_node) SET_ERROR_FOUND(p, "expected type expression"); return expr; } // Parse.zig:parsePrimaryExpr static AstNodeIndex parsePrimaryExpr(Parser* p) { switch (p->token_tags[p->tok_i]) { case TOKEN_KEYWORD_ASM: return expectAsmExpr(p); case TOKEN_KEYWORD_IF: return expectIf(p, expectExpr); case TOKEN_KEYWORD_BREAK: { const AstTokenIndex main_token = nextToken(p); const AstTokenIndex label = parseBreakLabel(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_BREAK, .main_token = main_token, .data = { .lhs = label, .rhs = parseExpr(p) }, }); } case TOKEN_KEYWORD_CONTINUE: { const AstTokenIndex main_token = nextToken(p); const AstTokenIndex label = parseBreakLabel(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_CONTINUE, .main_token = main_token, .data = { .lhs = label, .rhs = parseExpr(p) }, }); } case TOKEN_KEYWORD_COMPTIME: { const AstTokenIndex main_token = nextToken(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_COMPTIME, .main_token = main_token, .data = { .lhs = expectExpr(p), .rhs = 0 }, }); } case TOKEN_KEYWORD_NOSUSPEND: { const AstTokenIndex main_token = nextToken(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_NOSUSPEND, .main_token = main_token, .data = { .lhs = expectExpr(p), .rhs = 0 }, }); } case TOKEN_KEYWORD_RESUME: { const AstTokenIndex main_token = nextToken(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_RESUME, .main_token = main_token, .data = { .lhs = expectExpr(p), .rhs = 0 }, }); } case TOKEN_KEYWORD_RETURN: { const AstTokenIndex main_token = nextToken(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_RETURN, .main_token = main_token, .data = { .lhs = parseExpr(p), .rhs = 0 }, }); } case TOKEN_IDENTIFIER: if (p->token_tags[p->tok_i + 1] == TOKEN_COLON) { switch (p->token_tags[p->tok_i + 2]) { case TOKEN_KEYWORD_INLINE: p->tok_i += 3; switch (p->token_tags[p->tok_i]) { case TOKEN_KEYWORD_FOR: return expectFor(p, expectExpr); case TOKEN_KEYWORD_WHILE: return expectWhileExpr(p); default: SET_ERROR_FOUND(p, "expected 'while' or 'for'"); return null_node; } case TOKEN_KEYWORD_FOR: p->tok_i += 2; return expectFor(p, expectExpr); case TOKEN_KEYWORD_WHILE: p->tok_i += 2; return expectWhileExpr(p); default: return parseCurlySuffixExpr(p); } } else { return parseCurlySuffixExpr(p); } case TOKEN_KEYWORD_INLINE: p->tok_i += 1; switch (p->token_tags[p->tok_i]) { case TOKEN_KEYWORD_FOR: return expectFor(p, expectExpr); case TOKEN_KEYWORD_WHILE: return expectWhileExpr(p); default: SET_ERROR_FOUND(p, "expected 'while' or 'for'"); return null_node; } case TOKEN_KEYWORD_FOR: return expectFor(p, expectExpr); case TOKEN_KEYWORD_WHILE: return expectWhileExpr(p); case TOKEN_L_BRACE: return parseBlock(p); default: return parseCurlySuffixExpr(p); } } // Parse.zig:parseBlock static AstNodeIndex parseBlock(Parser* p) { const AstNodeIndex lbrace = eatToken(p, TOKEN_L_BRACE); if (lbrace == null_token) return null_node; const uint32_t scratch_top = p->scratch.len; while (1) { if (p->token_tags[p->tok_i] == TOKEN_R_BRACE) break; // "const AstNodeIndex statement" once tinycc supports typeof_unqual // (C23) AstNodeIndex statement = expectStatement(p, true); if (statement == null_node) break; SLICE_APPEND(AstNodeIndex, &p->scratch, statement); } expectToken(p, TOKEN_R_BRACE); const uint32_t statements_len = p->scratch.len - scratch_top; p->scratch.len = scratch_top; const bool semicolon = statements_len != 0 && (p->token_tags[p->tok_i - 2] == TOKEN_SEMICOLON); switch (statements_len) { case 0: return addNode( &p->nodes, (AstNodeItem) { .tag = AST_NODE_BLOCK_TWO, .main_token = lbrace, .data = { .lhs = null_node, .rhs = null_node, }, }); case 1: return addNode( &p->nodes, (AstNodeItem) { .tag = semicolon ? AST_NODE_BLOCK_TWO_SEMICOLON : AST_NODE_BLOCK_TWO, .main_token = lbrace, .data = { .lhs = p->scratch.arr[scratch_top], .rhs = null_node, }, }); case 2: return addNode( &p->nodes, (AstNodeItem) { .tag = semicolon ? AST_NODE_BLOCK_TWO_SEMICOLON : AST_NODE_BLOCK_TWO, .main_token = lbrace, .data = { .lhs = p->scratch.arr[scratch_top], .rhs = p->scratch.arr[scratch_top + 1], }, }); default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], statements_len); return addNode( &p->nodes, (AstNodeItem) { .tag = semicolon ? AST_NODE_BLOCK_SEMICOLON : AST_NODE_BLOCK, .main_token = lbrace, .data = { .lhs = span.start, .rhs = span.end, }, }); } } // Parse.zig:forPrefix // forPrefix parses the for prefix: (expr, expr, ...) |captures|. // Returns the number of input expressions. The inputs are appended // to the scratch buffer. static uint32_t forPrefix(Parser* p) { const uint32_t start = p->scratch.len; expectToken(p, TOKEN_L_PAREN); while (true) { AstNodeIndex input = expectExpr(p); if (eatToken(p, TOKEN_ELLIPSIS2) != null_token) { const AstTokenIndex ellipsis = p->tok_i - 1; const AstNodeIndex end = parseExpr(p); input = addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_FOR_RANGE, .main_token = ellipsis, .data = { .lhs = input, .rhs = end }, }); } SLICE_APPEND(AstNodeIndex, &p->scratch, input); if (p->token_tags[p->tok_i] == TOKEN_COMMA) { p->tok_i++; if (eatToken(p, TOKEN_R_PAREN) != null_token) break; continue; } expectToken(p, TOKEN_R_PAREN); break; } const uint32_t inputs = p->scratch.len - start; // Parse payload |a, *b, c| if (eatToken(p, TOKEN_PIPE) == null_token) { SET_ERROR_FOUND(p, "expected loop payload"); return 0; } { while (true) { eatToken(p, TOKEN_ASTERISK); expectToken(p, TOKEN_IDENTIFIER); if (p->token_tags[p->tok_i] == TOKEN_COMMA) { p->tok_i++; if (eatToken(p, TOKEN_PIPE) != null_token) break; continue; } expectToken(p, TOKEN_PIPE); break; } } return inputs; } // Parse.zig:expectWhileExpr static AstNodeIndex expectWhileExpr(Parser* p) { const AstTokenIndex while_token = assertToken(p, TOKEN_KEYWORD_WHILE); expectToken(p, TOKEN_L_PAREN); const AstNodeIndex condition = expectExpr(p); expectToken(p, TOKEN_R_PAREN); parsePtrPayload(p); const AstNodeIndex cont_expr = parseWhileContinueExpr(p); const AstNodeIndex then_expr = expectExpr(p); if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { if (cont_expr == null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_WHILE_SIMPLE, .main_token = while_token, .data = { .lhs = condition, .rhs = then_expr }, }); } return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_WHILE_CONT, .main_token = while_token, .data = { .lhs = condition, .rhs = parserAddExtra( p, (AstNodeIndex[]) { cont_expr, then_expr }, 2), }, }); } parsePayload(p); const AstNodeIndex else_expr = expectExpr(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_WHILE, .main_token = while_token, .data = { .lhs = condition, .rhs = parserAddExtra(p, (AstNodeIndex[]) { cont_expr, then_expr, else_expr }, 3), }, }); } // Parse.zig:parseCurlySuffixExpr static AstNodeIndex parseCurlySuffixExpr(Parser* p) { const AstNodeIndex lhs = parseTypeExpr(p); if (lhs == null_node) return null_node; const AstTokenIndex lbrace = eatToken(p, TOKEN_L_BRACE); if (lbrace == null_token) return lhs; // If there are 0 or 1 items, we can use ArrayInitOne/StructInitOne; // otherwise we use the full ArrayInit/StructInit. const uint32_t scratch_top = p->scratch.len; const AstNodeIndex field_init = parseFieldInit(p); if (field_init != null_node) { SLICE_APPEND(AstNodeIndex, &p->scratch, field_init); while (true) { switch (p->token_tags[p->tok_i]) { case TOKEN_COMMA: p->tok_i += 1; break; case TOKEN_R_BRACE: p->tok_i += 1; goto struct_init_done; case TOKEN_COLON: case TOKEN_R_PAREN: case TOKEN_R_BRACKET: failExpected(p, TOKEN_R_BRACE); p->scratch.len = scratch_top; return null_node; default: // Likely just a missing comma; give error but continue // parsing. warn(p, "expected ',' after initializer"); break; } if (eatToken(p, TOKEN_R_BRACE) != null_token) break; const AstNodeIndex next = expectFieldInit(p); if (next == null_node) { p->scratch.len = scratch_top; return null_node; } SLICE_APPEND(AstNodeIndex, &p->scratch, next); } struct_init_done:; const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; const uint32_t inits_len = p->scratch.len - scratch_top; assert(inits_len != 0); AstNodeIndex result; if (inits_len <= 1) { result = addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_STRUCT_INIT_ONE_COMMA : AST_NODE_STRUCT_INIT_ONE, .main_token = lbrace, .data = { .lhs = lhs, .rhs = p->scratch.arr[scratch_top] }, }); } else { const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], inits_len); result = addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_STRUCT_INIT_COMMA : AST_NODE_STRUCT_INIT, .main_token = lbrace, .data = { .lhs = lhs, .rhs = parserAddExtra(p, (AstNodeIndex[]) { span.start, span.end }, 2), }, }); } p->scratch.len = scratch_top; return result; } while (true) { if (eatToken(p, TOKEN_R_BRACE) != null_token) break; const AstNodeIndex elem_init = expectExpr(p); if (elem_init == null_node) { p->scratch.len = scratch_top; return null_node; } SLICE_APPEND(AstNodeIndex, &p->scratch, elem_init); switch (p->token_tags[p->tok_i]) { case TOKEN_COMMA: p->tok_i += 1; break; case TOKEN_R_BRACE: p->tok_i += 1; goto array_init_done; case TOKEN_COLON: case TOKEN_R_PAREN: case TOKEN_R_BRACKET: failExpected(p, TOKEN_R_BRACE); p->scratch.len = scratch_top; return null_node; default: // Likely just a missing comma; give error but continue parsing. warn(p, "expected ',' after initializer"); break; } } array_init_done:; const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; const uint32_t inits_len = p->scratch.len - scratch_top; AstNodeIndex result; switch (inits_len) { case 0: result = addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_STRUCT_INIT_ONE, .main_token = lbrace, .data = { .lhs = lhs, .rhs = null_node }, }); break; case 1: result = addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_ARRAY_INIT_ONE_COMMA : AST_NODE_ARRAY_INIT_ONE, .main_token = lbrace, .data = { .lhs = lhs, .rhs = p->scratch.arr[scratch_top] }, }); break; default: { const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], inits_len); result = addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_ARRAY_INIT_COMMA : AST_NODE_ARRAY_INIT, .main_token = lbrace, .data = { .lhs = lhs, .rhs = parserAddExtra( p, (AstNodeIndex[]) { span.start, span.end }, 2), }, }); break; } } p->scratch.len = scratch_top; return result; } // Parse.zig:parseErrorUnionExpr static AstNodeIndex parseErrorUnionExpr(Parser* p) { const AstNodeIndex suffix_expr = parseSuffixExpr(p); if (suffix_expr == null_node) return null_node; const AstNodeIndex bang = eatToken(p, TOKEN_BANG); if (bang == null_token) return suffix_expr; return addNode( &p->nodes, (AstNodeItem) { .tag = AST_NODE_ERROR_UNION, .main_token = bang, .data = { .lhs = suffix_expr, .rhs = expectTypeExpr(p), }, }); } // Parse.zig:parseSuffixExpr static AstNodeIndex parseSuffixExpr(Parser* p) { AstNodeIndex res = parsePrimaryTypeExpr(p); if (res == null_node) return res; while (true) { const AstNodeIndex suffix_op = parseSuffixOp(p, res); if (suffix_op != null_node) { res = suffix_op; continue; } const AstTokenIndex lparen = eatToken(p, TOKEN_L_PAREN); if (lparen == null_token) return res; const uint32_t scratch_top = p->scratch.len; while (true) { if (eatToken(p, TOKEN_R_PAREN) != null_token) break; const AstNodeIndex arg = expectExpr(p); SLICE_APPEND(AstNodeIndex, &p->scratch, arg); if (p->token_tags[p->tok_i] == TOKEN_COMMA) { p->tok_i++; continue; } expectToken(p, TOKEN_R_PAREN); break; } const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; const uint32_t params_len = p->scratch.len - scratch_top; p->scratch.len = scratch_top; switch (params_len) { case 0: res = addNode( &p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_CALL_ONE_COMMA : AST_NODE_CALL_ONE, .main_token = lparen, .data = { .lhs = res, .rhs = null_node, }, }); break; case 1: res = addNode( &p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_CALL_ONE_COMMA : AST_NODE_CALL_ONE, .main_token = lparen, .data = { .lhs = res, .rhs = p->scratch.arr[scratch_top], }, }); break; default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], params_len); res = addNode( &p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_CALL_COMMA : AST_NODE_CALL, .main_token = lparen, .data = { .lhs = res, .rhs = parserAddExtra(p, (AstNodeIndex[]) { span.start, span.end, }, 2), }, }); break; } } } // Parse.zig:parsePrimaryTypeExpr static AstNodeIndex parsePrimaryTypeExpr(Parser* p) { switch (p->token_tags[p->tok_i]) { case TOKEN_CHAR_LITERAL: return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_CHAR_LITERAL, .main_token = nextToken(p), .data = { 0 } }); case TOKEN_NUMBER_LITERAL: return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_NUMBER_LITERAL, .main_token = nextToken(p), .data = { 0 } }); case TOKEN_KEYWORD_UNREACHABLE: return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_UNREACHABLE_LITERAL, .main_token = nextToken(p), .data = { 0 } }); case TOKEN_KEYWORD_ANYFRAME: return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ANYFRAME_LITERAL, .main_token = nextToken(p), .data = { 0 } }); case TOKEN_STRING_LITERAL: return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_STRING_LITERAL, .main_token = nextToken(p), .data = { 0 } }); case TOKEN_BUILTIN: return expectBuiltinCall(p); case TOKEN_KEYWORD_FN: return parseFnProto(p); case TOKEN_KEYWORD_IF: return expectIf(p, expectTypeExpr); case TOKEN_KEYWORD_SWITCH: return expectSwitchExpr(p, false); case TOKEN_KEYWORD_EXTERN: case TOKEN_KEYWORD_PACKED: p->tok_i += 1; return expectContainerDeclAuto(p); case TOKEN_KEYWORD_STRUCT: case TOKEN_KEYWORD_OPAQUE: case TOKEN_KEYWORD_ENUM: case TOKEN_KEYWORD_UNION: return expectContainerDeclAuto(p); case TOKEN_KEYWORD_COMPTIME: { const AstTokenIndex main_token = nextToken(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_COMPTIME, .main_token = main_token, .data = { .lhs = expectTypeExpr(p), .rhs = 0 }, }); } case TOKEN_MULTILINE_STRING_LITERAL_LINE: { const AstTokenIndex first_line = nextToken(p); while (p->token_tags[p->tok_i] == TOKEN_MULTILINE_STRING_LITERAL_LINE) p->tok_i += 1; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_MULTILINE_STRING_LITERAL, .main_token = first_line, .data = { .lhs = first_line, .rhs = p->tok_i - 1 }, }); } case TOKEN_IDENTIFIER: if (p->token_tags[p->tok_i + 1] == TOKEN_COLON) { switch (p->token_tags[p->tok_i + 2]) { case TOKEN_KEYWORD_INLINE: p->tok_i += 3; switch (p->token_tags[p->tok_i]) { case TOKEN_KEYWORD_FOR: return expectFor(p, expectTypeExpr); case TOKEN_KEYWORD_WHILE: return expectWhileTypeExpr(p); default: SET_ERROR_FOUND(p, "expected 'while' or 'for'"); return null_node; } case TOKEN_KEYWORD_FOR: p->tok_i += 2; return expectFor(p, expectTypeExpr); case TOKEN_KEYWORD_WHILE: p->tok_i += 2; return expectWhileTypeExpr(p); case TOKEN_KEYWORD_SWITCH: p->tok_i += 2; return expectSwitchExpr(p, true); case TOKEN_L_BRACE: p->tok_i += 2; return parseBlock(p); default: break; } } return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_IDENTIFIER, .main_token = nextToken(p), .data = { 0 } }); case TOKEN_KEYWORD_INLINE: p->tok_i += 1; switch (p->token_tags[p->tok_i]) { case TOKEN_KEYWORD_FOR: return expectFor(p, expectTypeExpr); case TOKEN_KEYWORD_WHILE: return expectWhileTypeExpr(p); default: SET_ERROR_FOUND(p, "expected 'while' or 'for'"); return null_node; } case TOKEN_KEYWORD_FOR: return expectFor(p, expectTypeExpr); case TOKEN_KEYWORD_WHILE: return expectWhileTypeExpr(p); case TOKEN_PERIOD: switch (p->token_tags[p->tok_i + 1]) { case TOKEN_IDENTIFIER: p->tok_i += 1; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ENUM_LITERAL, .main_token = nextToken(p), // identifier .data = { 0 } }); case TOKEN_L_BRACE: { const AstTokenIndex lbrace = p->tok_i + 1; p->tok_i = lbrace + 1; // If there are 0, 1, or 2 items, we can use // ArrayInitDotTwo/StructInitDotTwo; otherwise we use the full // ArrayInitDot/StructInitDot. const uint32_t scratch_top = p->scratch.len; const AstNodeIndex field_init = parseFieldInit(p); if (field_init != null_node) { SLICE_APPEND(AstNodeIndex, &p->scratch, field_init); while (true) { switch (p->token_tags[p->tok_i]) { case TOKEN_COMMA: p->tok_i += 1; break; case TOKEN_R_BRACE: p->tok_i += 1; goto struct_init_done; case TOKEN_COLON: case TOKEN_R_PAREN: case TOKEN_R_BRACKET: failExpected(p, TOKEN_R_BRACE); p->scratch.len = scratch_top; return null_node; default: // Likely just a missing comma; give error but // continue parsing. warn(p, "expected ',' after initializer"); break; } if (eatToken(p, TOKEN_R_BRACE) != null_token) break; const AstNodeIndex next = expectFieldInit(p); if (next == null_node) { p->scratch.len = scratch_top; return null_node; } SLICE_APPEND(AstNodeIndex, &p->scratch, next); } struct_init_done:; const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; const uint32_t inits_len = p->scratch.len - scratch_top; assert(inits_len != 0); AstNodeIndex result; if (inits_len <= 2) { result = addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_STRUCT_INIT_DOT_TWO_COMMA : AST_NODE_STRUCT_INIT_DOT_TWO, .main_token = lbrace, .data = { .lhs = p->scratch.arr[scratch_top], .rhs = inits_len >= 2 ? p->scratch.arr[scratch_top + 1] : null_node, }, }); } else { const AstSubRange span = listToSpan( p, &p->scratch.arr[scratch_top], inits_len); result = addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_STRUCT_INIT_DOT_COMMA : AST_NODE_STRUCT_INIT_DOT, .main_token = lbrace, .data = { .lhs = span.start, .rhs = span.end }, }); } p->scratch.len = scratch_top; return result; } while (true) { if (eatToken(p, TOKEN_R_BRACE) != null_token) break; const AstNodeIndex elem_init = expectExpr(p); if (elem_init == null_node) { p->scratch.len = scratch_top; return null_node; } SLICE_APPEND(AstNodeIndex, &p->scratch, elem_init); switch (p->token_tags[p->tok_i]) { case TOKEN_COMMA: p->tok_i += 1; break; case TOKEN_R_BRACE: p->tok_i += 1; goto array_init_done; case TOKEN_COLON: case TOKEN_R_PAREN: case TOKEN_R_BRACKET: failExpected(p, TOKEN_R_BRACE); p->scratch.len = scratch_top; return null_node; default: // Likely just a missing comma; give error but continue // parsing. warn(p, "expected ',' after initializer"); break; } } array_init_done:; const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA; const uint32_t inits_len = p->scratch.len - scratch_top; AstNodeIndex result; if (inits_len <= 2) { result = addNode(&p->nodes, (AstNodeItem) { .tag = inits_len == 0 ? AST_NODE_STRUCT_INIT_DOT_TWO : comma ? AST_NODE_ARRAY_INIT_DOT_TWO_COMMA : AST_NODE_ARRAY_INIT_DOT_TWO, .main_token = lbrace, .data = { .lhs = inits_len >= 1 ? p->scratch.arr[scratch_top] : null_node, .rhs = inits_len >= 2 ? p->scratch.arr[scratch_top + 1] : null_node, }, }); } else { const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], inits_len); result = addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_ARRAY_INIT_DOT_COMMA : AST_NODE_ARRAY_INIT_DOT, .main_token = lbrace, .data = { .lhs = span.start, .rhs = span.end }, }); } p->scratch.len = scratch_top; return result; } default: return null_node; } case TOKEN_KEYWORD_ERROR: switch (p->token_tags[p->tok_i + 1]) { case TOKEN_L_BRACE: { const AstTokenIndex error_token = p->tok_i; p->tok_i += 2; while (true) { if (eatToken(p, TOKEN_R_BRACE) != null_token) break; eatDocComments(p); if (expectToken(p, TOKEN_IDENTIFIER) == null_token) return null_node; switch (p->token_tags[p->tok_i]) { case TOKEN_COMMA: p->tok_i += 1; break; case TOKEN_R_BRACE: p->tok_i += 1; goto error_set_done; case TOKEN_COLON: case TOKEN_R_PAREN: case TOKEN_R_BRACKET: failExpected(p, TOKEN_R_BRACE); return null_node; default: // Likely just a missing comma; give error but continue // parsing. warn(p, "expected ',' after field"); break; } } error_set_done: return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ERROR_SET_DECL, .main_token = error_token, .data = { .lhs = error_token + 1, // lbrace .rhs = p->tok_i - 1, // rbrace }, }); } default: { const AstTokenIndex main_token = nextToken(p); expectToken(p, TOKEN_PERIOD); expectToken(p, TOKEN_IDENTIFIER); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ERROR_VALUE, .main_token = main_token, .data = { 0 } }); } } case TOKEN_L_PAREN: { const AstTokenIndex main_token = nextToken(p); const AstNodeIndex inner = expectExpr(p); const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_GROUPED_EXPRESSION, .main_token = main_token, .data = { .lhs = inner, .rhs = rparen }, }); } default: return null_node; } } // Parse.zig:expectWhileTypeExpr static AstNodeIndex expectWhileTypeExpr(Parser* p) { const AstTokenIndex while_token = assertToken(p, TOKEN_KEYWORD_WHILE); expectToken(p, TOKEN_L_PAREN); const AstNodeIndex condition = expectExpr(p); expectToken(p, TOKEN_R_PAREN); parsePtrPayload(p); const AstNodeIndex cont_expr = parseWhileContinueExpr(p); const AstNodeIndex then_expr = expectTypeExpr(p); if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { if (cont_expr == null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_WHILE_SIMPLE, .main_token = while_token, .data = { .lhs = condition, .rhs = then_expr }, }); } return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_WHILE_CONT, .main_token = while_token, .data = { .lhs = condition, .rhs = parserAddExtra( p, (AstNodeIndex[]) { cont_expr, then_expr }, 2), }, }); } parsePayload(p); const AstNodeIndex else_expr = expectTypeExpr(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_WHILE, .main_token = while_token, .data = { .lhs = condition, .rhs = parserAddExtra(p, (AstNodeIndex[]) { cont_expr, then_expr, else_expr }, 3), }, }); } // Parse.zig:parseSwitchExpr static AstNodeIndex parseSwitchExpr(Parser* p, bool is_labeled) { const AstTokenIndex switch_token = eatToken(p, TOKEN_KEYWORD_SWITCH); if (switch_token == null_token) return null_node; return expectSwitchSuffix(p, is_labeled ? switch_token - 2 : switch_token); } // Parse.zig:expectSwitchExpr static AstNodeIndex expectSwitchExpr(Parser* p, bool is_labeled) { const AstTokenIndex switch_token = assertToken(p, TOKEN_KEYWORD_SWITCH); return expectSwitchSuffix(p, is_labeled ? switch_token - 2 : switch_token); } // Parse.zig:expectSwitchSuffix static AstNodeIndex expectSwitchSuffix(Parser* p, AstTokenIndex main_token) { expectToken(p, TOKEN_L_PAREN); const AstNodeIndex expr_node = expectExpr(p); expectToken(p, TOKEN_R_PAREN); expectToken(p, TOKEN_L_BRACE); const AstSubRange cases = parseSwitchProngList(p); const bool trailing_comma = p->token_tags[p->tok_i - 1] == TOKEN_COMMA; expectToken(p, TOKEN_R_BRACE); return addNode(&p->nodes, (AstNodeItem) { .tag = trailing_comma ? AST_NODE_SWITCH_COMMA : AST_NODE_SWITCH, .main_token = main_token, .data = { .lhs = expr_node, .rhs = parserAddExtra( p, (AstNodeIndex[]) { cases.start, cases.end }, 2), }, }); } // Parse.zig:expectAsmExpr static AstNodeIndex expectAsmExpr(Parser* p) { const AstTokenIndex asm_token = nextToken(p); assert(p->token_tags[asm_token] == TOKEN_KEYWORD_ASM); eatToken(p, TOKEN_KEYWORD_VOLATILE); expectToken(p, TOKEN_L_PAREN); const AstNodeIndex template = expectExpr(p); // Simple asm: asm("...") if (eatToken(p, TOKEN_R_PAREN) != null_token) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ASM_SIMPLE, .main_token = asm_token, .data = { .lhs = template, .rhs = p->tok_i - 1 }, }); } // Complex asm with outputs, inputs, clobbers expectToken(p, TOKEN_COLON); const uint32_t scratch_top = p->scratch.len; // Parse outputs while (true) { const AstNodeIndex output = parseAsmOutputItem(p); if (output == null_node) break; SLICE_APPEND(AstNodeIndex, &p->scratch, output); if (eatToken(p, TOKEN_COMMA) == null_token) break; } // Parse inputs (after second colon) if (eatToken(p, TOKEN_COLON) != null_token) { while (true) { const AstNodeIndex input = parseAsmInputItem(p); if (input == null_node) break; SLICE_APPEND(AstNodeIndex, &p->scratch, input); if (eatToken(p, TOKEN_COMMA) == null_token) break; } } // Parse clobbers (after third colon): an expression such as // `.{ .clobber1 = true, .clobber2 = true }`. AstNodeIndex clobbers = null_node; if (eatToken(p, TOKEN_COLON) != null_token) clobbers = expectExpr(p); const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN); const uint32_t items_len = p->scratch.len - scratch_top; const AstSubRange items_span = listToSpan(p, &p->scratch.arr[scratch_top], items_len); p->scratch.len = scratch_top; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ASM, .main_token = asm_token, .data = { .lhs = template, .rhs = parserAddExtra(p, (AstNodeIndex[]) { items_span.start, items_span.end, clobbers, rparen }, 4), }, }); } // Parse.zig:parseAsmOutputItem static AstNodeIndex parseAsmOutputItem(Parser* p) { if (p->token_tags[p->tok_i] == TOKEN_L_BRACKET) { p->tok_i++; // [ const AstTokenIndex ident = expectToken(p, TOKEN_IDENTIFIER); expectToken(p, TOKEN_R_BRACKET); expectToken(p, TOKEN_STRING_LITERAL); expectToken(p, TOKEN_L_PAREN); AstNodeIndex type_expr = null_node; if (eatToken(p, TOKEN_ARROW) != null_token) { type_expr = expectTypeExpr(p); } else { expectToken(p, TOKEN_IDENTIFIER); } const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ASM_OUTPUT, .main_token = ident, .data = { .lhs = type_expr, .rhs = rparen }, }); } return null_node; } // Parse.zig:parseAsmInputItem static AstNodeIndex parseAsmInputItem(Parser* p) { if (p->token_tags[p->tok_i] == TOKEN_L_BRACKET) { p->tok_i++; // [ const AstTokenIndex ident = expectToken(p, TOKEN_IDENTIFIER); expectToken(p, TOKEN_R_BRACKET); expectToken(p, TOKEN_STRING_LITERAL); expectToken(p, TOKEN_L_PAREN); const AstNodeIndex operand = expectExpr(p); const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ASM_INPUT, .main_token = ident, .data = { .lhs = operand, .rhs = rparen }, }); } return null_node; } // Parse.zig:parseBreakLabel static AstTokenIndex parseBreakLabel(Parser* p) { if (eatToken(p, TOKEN_COLON) == null_token) return null_token; return expectToken(p, TOKEN_IDENTIFIER); } // Parse.zig:parseBlockLabel static AstTokenIndex parseBlockLabel(Parser* p) { if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER && p->token_tags[p->tok_i + 1] == TOKEN_COLON) { const AstTokenIndex identifier = p->tok_i; p->tok_i += 2; return identifier; } return null_token; } // Parse.zig:parseFieldInit static AstNodeIndex parseFieldInit(Parser* p) { static const TokenTag field_init_tags[] = { TOKEN_PERIOD, TOKEN_IDENTIFIER, TOKEN_EQUAL }; if (eatTokens(p, field_init_tags, 3) != null_token) return expectExpr(p); return null_node; } // Parse.zig:expectFieldInit static AstNodeIndex expectFieldInit(Parser* p) { static const TokenTag field_init_tags[] = { TOKEN_PERIOD, TOKEN_IDENTIFIER, TOKEN_EQUAL }; if (eatTokens(p, field_init_tags, 3) != null_token) return expectExpr(p); SET_ERROR(p, "expected field initializer"); return null_node; } // Parse.zig:parseWhileContinueExpr static AstNodeIndex parseWhileContinueExpr(Parser* p) { if (eatToken(p, TOKEN_COLON) == null_token) return null_node; expectToken(p, TOKEN_L_PAREN); const AstNodeIndex expr = parseAssignExpr(p); expectToken(p, TOKEN_R_PAREN); return expr; } // Parse.zig:parseLinkSection static AstNodeIndex parseLinkSection(Parser* p) { if (eatToken(p, TOKEN_KEYWORD_LINKSECTION) == null_token) return null_node; expectToken(p, TOKEN_L_PAREN); const AstNodeIndex expr = expectExpr(p); expectToken(p, TOKEN_R_PAREN); return expr; } // Parse.zig:parseCallconv static AstNodeIndex parseCallconv(Parser* p) { if (eatToken(p, TOKEN_KEYWORD_CALLCONV) == null_token) return null_node; expectToken(p, TOKEN_L_PAREN); const AstNodeIndex expr = expectExpr(p); expectToken(p, TOKEN_R_PAREN); return expr; } // Parse.zig:parseAddrSpace static AstNodeIndex parseAddrSpace(Parser* p) { if (eatToken(p, TOKEN_KEYWORD_ADDRSPACE) == null_token) return null_node; expectToken(p, TOKEN_L_PAREN); const AstNodeIndex expr = expectExpr(p); expectToken(p, TOKEN_R_PAREN); return expr; } // Parse.zig:expectParamDecl // null_node for `...` and `anytype`. static AstNodeIndex expectParamDecl(Parser* p) { // Unlike ordinary parameters, the variadic marker cannot have doc // comments. if (eatToken(p, TOKEN_ELLIPSIS3) != null_token) return null_node; eatDocComments(p); switch (p->token_tags[p->tok_i]) { case TOKEN_KEYWORD_NOALIAS: case TOKEN_KEYWORD_COMPTIME: p->tok_i += 1; break; default: break; } static const TokenTag name_tags[] = { TOKEN_IDENTIFIER, TOKEN_COLON }; eatTokens(p, name_tags, 2); if (eatToken(p, TOKEN_KEYWORD_ANYTYPE) != null_token) return null_node; return expectTypeExpr(p); } // Parse.zig:parsePayload static void parsePayload(Parser* p) { if (eatToken(p, TOKEN_PIPE) == null_token) return; expectToken(p, TOKEN_IDENTIFIER); expectToken(p, TOKEN_PIPE); } // Parse.zig:parsePtrPayload static AstTokenIndex parsePtrPayload(Parser* p) { if (eatToken(p, TOKEN_PIPE) == null_token) return null_token; eatToken(p, TOKEN_ASTERISK); const AstTokenIndex identifier = expectToken(p, TOKEN_IDENTIFIER); expectToken(p, TOKEN_PIPE); return identifier; } // Parse.zig:parsePtrIndexPayload // Returns the first identifier token, if any. static AstTokenIndex parsePtrIndexPayload(Parser* p) { if (eatToken(p, TOKEN_PIPE) == null_token) return null_token; eatToken(p, TOKEN_ASTERISK); const AstTokenIndex identifier = expectToken(p, TOKEN_IDENTIFIER); if (eatToken(p, TOKEN_COMMA) != null_token) expectToken(p, TOKEN_IDENTIFIER); expectToken(p, TOKEN_PIPE); return identifier; } // Parse.zig:parseSwitchProng static AstNodeIndex parseSwitchProng(Parser* p) { const uint32_t items_old_len = p->scratch.len; const bool is_inline = eatToken(p, TOKEN_KEYWORD_INLINE) != null_token; if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { while (true) { const AstNodeIndex item = parseSwitchItem(p); if (item == null_node) break; SLICE_APPEND(AstNodeIndex, &p->scratch, item); if (eatToken(p, TOKEN_COMMA) == null_token) break; } if (p->scratch.len == items_old_len) { if (is_inline) p->tok_i -= 1; return null_node; } } const AstTokenIndex arrow = expectToken(p, TOKEN_EQUAL_ANGLE_BRACKET_RIGHT); parsePtrIndexPayload(p); const uint32_t items_len = p->scratch.len - items_old_len; AstNodeIndex case_node; switch (items_len) { case 0: case 1: { // In Zig, .data fields are evaluated in order, so for // switch_case_one, the items are in data[0] and the body // (expectSingleAssignExpr) is in data[1]. Since items don't go // through extra_data here, ordering doesn't matter. const AstNodeIndex case_body = expectSingleAssignExpr(p); if (case_body == null_node) return null_node; case_node = addNode(&p->nodes, (AstNodeItem) { .tag = is_inline ? AST_NODE_SWITCH_CASE_INLINE_ONE : AST_NODE_SWITCH_CASE_ONE, .main_token = arrow, .data = { .lhs = items_len >= 1 ? p->scratch.arr[items_old_len] : null_node, .rhs = case_body, }, }); } break; default: { // In Zig, struct literal field init order is guaranteed: // data[0] = parserAddExtra(listToSpan(items)) -- writes items to // extra_data first data[1] = expectSingleAssignExpr() -- parses // body after. // We must maintain this order in C. const AstSubRange span = listToSpan(p, &p->scratch.arr[items_old_len], items_len); const AstNodeIndex extra_idx = parserAddExtra(p, (AstNodeIndex[]) { span.start, span.end }, 2); const AstNodeIndex case_body = expectSingleAssignExpr(p); if (case_body == null_node) return null_node; case_node = addNode(&p->nodes, (AstNodeItem) { .tag = is_inline ? AST_NODE_SWITCH_CASE_INLINE : AST_NODE_SWITCH_CASE, .main_token = arrow, .data = { .lhs = extra_idx, .rhs = case_body, }, }); } break; } p->scratch.len = items_old_len; return case_node; } // Parse.zig:parseSwitchItem static AstNodeIndex parseSwitchItem(Parser* p) { const AstNodeIndex expr = parseExpr(p); if (expr == null_node) return null_node; if (p->token_tags[p->tok_i] == TOKEN_ELLIPSIS3) { const AstTokenIndex range_tok = nextToken(p); const AstNodeIndex range_end = expectExpr(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_SWITCH_RANGE, .main_token = range_tok, .data = { .lhs = expr, .rhs = range_end }, }); } return expr; } // Parse.zig:parsePtrModifiers // `byte_align` is upstream's `.byte_align`: a many-item or slice pointer, on // which a bit range is an error. static PtrModifiers parsePtrModifiers(Parser* p, bool byte_align) { PtrModifiers result = { null_node, null_node, null_node, null_node }; while (true) { switch (p->token_tags[p->tok_i]) { case TOKEN_KEYWORD_ALIGN: { if (result.align_node != null_node) warn(p, "extra align qualifier"); p->tok_i += 1; expectToken(p, TOKEN_L_PAREN); result.align_node = expectExpr(p); const AstTokenIndex colon = eatToken(p, TOKEN_COLON); if (colon != null_token) { if (byte_align) { p->has_warn = true; warnMsgAt(p, colon, false, "bit range only allowed on single item pointers"); } result.bit_range_start = expectExpr(p); expectToken(p, TOKEN_COLON); result.bit_range_end = expectExpr(p); } expectToken(p, TOKEN_R_PAREN); break; } case TOKEN_KEYWORD_ADDRSPACE: if (result.addrspace_node != null_node) warn(p, "extra addrspace qualifier"); result.addrspace_node = parseAddrSpace(p); break; case TOKEN_KEYWORD_ALLOWZERO: case TOKEN_KEYWORD_CONST: case TOKEN_KEYWORD_VOLATILE: p->tok_i += 1; break; default: return result; } } } // Parse.zig:parseSuffixOp static AstNodeIndex parseSuffixOp(Parser* p, AstNodeIndex lhs) { const TokenTag tok = p->token_tags[p->tok_i]; switch (tok) { case TOKEN_L_BRACKET: { const AstTokenIndex lbracket = nextToken(p); const AstNodeIndex index_expr = expectExpr(p); switch (p->token_tags[p->tok_i]) { case TOKEN_R_BRACKET: p->tok_i++; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_ARRAY_ACCESS, .main_token = lbracket, .data = { .lhs = lhs, .rhs = index_expr }, }); case TOKEN_ELLIPSIS2: { p->tok_i++; // consume .. const AstNodeIndex end_expr = parseExpr(p); if (eatToken(p, TOKEN_COLON) != null_token) { const AstNodeIndex sentinel = expectExpr(p); expectToken(p, TOKEN_R_BRACKET); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_SLICE_SENTINEL, .main_token = lbracket, .data = { .lhs = lhs, .rhs = parserAddExtra(p, (AstNodeIndex[]) { index_expr, end_expr, sentinel }, 3), }, }); } expectToken(p, TOKEN_R_BRACKET); if (end_expr == null_node) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_SLICE_OPEN, .main_token = lbracket, .data = { .lhs = lhs, .rhs = index_expr }, }); } return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_SLICE, .main_token = lbracket, .data = { .lhs = lhs, .rhs = parserAddExtra(p, (AstNodeIndex[]) { index_expr, end_expr }, 2), }, }); } default: SET_ERROR(p, "parseSuffixOp: expected ] or .. after index expr"); } return null_node; } case TOKEN_PERIOD_ASTERISK: return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_DEREF, .main_token = nextToken(p), .data = { .lhs = lhs, .rhs = 0 }, }); case TOKEN_PERIOD: if (p->token_tags[p->tok_i + 1] == TOKEN_IDENTIFIER) { const AstTokenIndex dot = nextToken(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_FIELD_ACCESS, .main_token = dot, .data = { .lhs = lhs, .rhs = nextToken(p) }, }); } if (p->token_tags[p->tok_i + 1] == TOKEN_QUESTION_MARK) { const AstTokenIndex dot = nextToken(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_UNWRAP_OPTIONAL, .main_token = dot, .data = { .lhs = lhs, .rhs = nextToken(p) }, }); } SET_ERROR(p, "parseSuffixOp: unsupported period suffix"); return null_node; default: return null_node; } } // Parse.zig:expectContainerDeclAuto static AstNodeIndex expectContainerDeclAuto(Parser* p) { const AstTokenIndex main_token = nextToken(p); AstNodeIndex arg_expr = null_node; switch (p->token_tags[main_token]) { case TOKEN_KEYWORD_OPAQUE: break; case TOKEN_KEYWORD_STRUCT: case TOKEN_KEYWORD_ENUM: if (eatToken(p, TOKEN_L_PAREN) != null_token) { arg_expr = expectExpr(p); expectToken(p, TOKEN_R_PAREN); } break; case TOKEN_KEYWORD_UNION: if (eatToken(p, TOKEN_L_PAREN) != null_token) { if (eatToken(p, TOKEN_KEYWORD_ENUM) != null_token) { if (eatToken(p, TOKEN_L_PAREN) != null_token) { const AstNodeIndex enum_tag_expr = expectExpr(p); expectToken(p, TOKEN_R_PAREN); expectToken(p, TOKEN_R_PAREN); expectToken(p, TOKEN_L_BRACE); const Members members = parseContainerMembers(p); const AstSubRange members_span = membersToSpan(members, p); expectToken(p, TOKEN_R_BRACE); return addNode( &p->nodes, (AstNodeItem) { .tag = members.trailing ? AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING : AST_NODE_TAGGED_UNION_ENUM_TAG, .main_token = main_token, .data = { .lhs = enum_tag_expr, .rhs = parserAddExtra(p, (AstNodeIndex[]) { members_span.start, members_span.end }, 2), }, }); } expectToken(p, TOKEN_R_PAREN); expectToken(p, TOKEN_L_BRACE); const Members members = parseContainerMembers(p); expectToken(p, TOKEN_R_BRACE); if (members.len <= 2) { return addNode(&p->nodes, (AstNodeItem) { .tag = members.trailing ? AST_NODE_TAGGED_UNION_TWO_TRAILING : AST_NODE_TAGGED_UNION_TWO, .main_token = main_token, .data = { .lhs = members.lhs, .rhs = members.rhs }, }); } const AstSubRange span = membersToSpan(members, p); return addNode(&p->nodes, (AstNodeItem) { .tag = members.trailing ? AST_NODE_TAGGED_UNION_TRAILING : AST_NODE_TAGGED_UNION, .main_token = main_token, .data = { .lhs = span.start, .rhs = span.end }, }); } arg_expr = expectExpr(p); expectToken(p, TOKEN_R_PAREN); } break; default: SET_ERROR(p, "expectContainerDeclAuto: unexpected token"); return null_node; } expectToken(p, TOKEN_L_BRACE); const Members members = parseContainerMembers(p); expectToken(p, TOKEN_R_BRACE); if (arg_expr == null_node) { if (members.len <= 2) { return addNode(&p->nodes, (AstNodeItem) { .tag = members.trailing ? AST_NODE_CONTAINER_DECL_TWO_TRAILING : AST_NODE_CONTAINER_DECL_TWO, .main_token = main_token, .data = { .lhs = members.lhs, .rhs = members.rhs }, }); } const AstSubRange span = membersToSpan(members, p); return addNode(&p->nodes, (AstNodeItem) { .tag = members.trailing ? AST_NODE_CONTAINER_DECL_TRAILING : AST_NODE_CONTAINER_DECL, .main_token = main_token, .data = { .lhs = span.start, .rhs = span.end }, }); } const AstSubRange span = membersToSpan(members, p); return addNode( &p->nodes, (AstNodeItem) { .tag = members.trailing ? AST_NODE_CONTAINER_DECL_ARG_TRAILING : AST_NODE_CONTAINER_DECL_ARG, .main_token = main_token, .data = { .lhs = arg_expr, .rhs = parserAddExtra(p, (AstNodeIndex[]) { span.start, span.end }, 2), }, }); } // Parse.zig:parseByteAlign static AstNodeIndex parseByteAlign(Parser* p) { if (eatToken(p, TOKEN_KEYWORD_ALIGN) == null_token) return null_node; expectToken(p, TOKEN_L_PAREN); const AstNodeIndex expr = expectExpr(p); expectToken(p, TOKEN_R_PAREN); return expr; } // Parse.zig:parseSwitchProngList static AstSubRange parseSwitchProngList(Parser* p) { const uint32_t scratch_top = p->scratch.len; while (true) { const AstNodeIndex item = parseSwitchProng(p); if (item == null_node) break; SLICE_APPEND(AstNodeIndex, &p->scratch, item); switch (p->token_tags[p->tok_i]) { case TOKEN_COMMA: p->tok_i += 1; continue; // All possible delimiters. case TOKEN_COLON: case TOKEN_R_PAREN: case TOKEN_R_BRACE: case TOKEN_R_BRACKET: break; default: // Likely just a missing comma; give error but continue parsing. warn(p, "expected ',' after switch prong"); continue; } break; } const AstSubRange span = listToSpan( p, &p->scratch.arr[scratch_top], p->scratch.len - scratch_top); p->scratch.len = scratch_top; return span; } // Parse.zig:parseParamDeclList static SmallSpan parseParamDeclList(Parser* p) { expectToken(p, TOKEN_L_PAREN); const uint32_t scratch_top = p->scratch.len; enum { VARARGS_NONE, VARARGS_SEEN, VARARGS_ERR } varargs = VARARGS_NONE; while (true) { if (eatToken(p, TOKEN_R_PAREN) != null_token) break; if (varargs == VARARGS_SEEN) { p->has_warn = true; warnMsgAt(p, p->tok_i, false, "function prototype has parameter after varargs"); varargs = VARARGS_ERR; } const AstNodeIndex param = expectParamDecl(p); if (param != null_node) { SLICE_APPEND(AstNodeIndex, &p->scratch, param); } else if (p->token_tags[p->tok_i - 1] == TOKEN_ELLIPSIS3) { if (varargs == VARARGS_NONE) varargs = VARARGS_SEEN; } switch (p->token_tags[p->tok_i]) { case TOKEN_COMMA: p->tok_i += 1; continue; case TOKEN_R_PAREN: p->tok_i += 1; break; case TOKEN_COLON: case TOKEN_R_BRACE: case TOKEN_R_BRACKET: failExpected(p, TOKEN_R_PAREN); p->scratch.len = scratch_top; return (SmallSpan) { .tag = SMALL_SPAN_ZERO_OR_ONE, .payload = { .zero_or_one = null_node } }; default: // Likely just a missing comma; give error but continue parsing. warn(p, "expected ',' after parameter"); continue; } break; } const uint32_t params_len = p->scratch.len - scratch_top; SmallSpan result; switch (params_len) { case 0: result = (SmallSpan) { .tag = SMALL_SPAN_ZERO_OR_ONE, .payload = { .zero_or_one = null_node } }; break; case 1: result = (SmallSpan) { .tag = SMALL_SPAN_ZERO_OR_ONE, .payload = { .zero_or_one = p->scratch.arr[scratch_top] } }; break; default: result = (SmallSpan) { .tag = SMALL_SPAN_MULTI, .payload = { .multi = listToSpan(p, &p->scratch.arr[scratch_top], params_len) } }; break; } p->scratch.len = scratch_top; return result; } // Parse.zig:expectBuiltinCall static AstNodeIndex expectBuiltinCall(Parser* p) { const AstTokenIndex builtin_token = assertToken(p, TOKEN_BUILTIN); assertToken(p, TOKEN_L_PAREN); const uint32_t scratch_top = p->scratch.len; while (true) { if (eatToken(p, TOKEN_R_PAREN) != null_token) break; const AstNodeIndex param = expectExpr(p); SLICE_APPEND(AstNodeIndex, &p->scratch, param); switch (p->token_tags[p->tok_i]) { case TOKEN_COMMA: p->tok_i++; break; case TOKEN_R_PAREN: p->tok_i++; goto end_loop; default: SET_ERROR(p, "expected comma after arg"); return null_node; } } end_loop:; const bool comma = (p->token_tags[p->tok_i - 2] == TOKEN_COMMA); const uint32_t params_len = p->scratch.len - scratch_top; p->scratch.len = scratch_top; switch (params_len) { case 0: return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_BUILTIN_CALL_TWO, .main_token = builtin_token, .data = { .lhs = null_node, .rhs = null_node, }, }); case 1: return addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_BUILTIN_CALL_TWO_COMMA : AST_NODE_BUILTIN_CALL_TWO, .main_token = builtin_token, .data = { .lhs = p->scratch.arr[scratch_top], .rhs = null_node, }, }); case 2: return addNode(&p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_BUILTIN_CALL_TWO_COMMA : AST_NODE_BUILTIN_CALL_TWO, .main_token = builtin_token, .data = { .lhs = p->scratch.arr[scratch_top], .rhs = p->scratch.arr[scratch_top+1], }, }); default:; const AstSubRange span = listToSpan(p, &p->scratch.arr[scratch_top], params_len); return addNode( &p->nodes, (AstNodeItem) { .tag = comma ? AST_NODE_BUILTIN_CALL_COMMA : AST_NODE_BUILTIN_CALL, .main_token = builtin_token, .data = { .lhs = span.start, .rhs = span.end, }, }); } } // Parse.zig:expectIf static AstNodeIndex expectIf( Parser* p, AstNodeIndex (*body_parse_fn)(Parser*)) { const AstTokenIndex if_token = assertToken(p, TOKEN_KEYWORD_IF); expectToken(p, TOKEN_L_PAREN); const AstNodeIndex condition = expectExpr(p); expectToken(p, TOKEN_R_PAREN); parsePtrPayload(p); const AstNodeIndex then_expr = body_parse_fn(p); if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) { return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_IF_SIMPLE, .main_token = if_token, .data = { .lhs = condition, .rhs = then_expr }, }); } parsePayload(p); const AstNodeIndex else_expr = body_parse_fn(p); return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_IF, .main_token = if_token, .data = { .lhs = condition, .rhs = parserAddExtra( p, (AstNodeIndex[]) { then_expr, else_expr }, 2), }, }); } // Parse.zig:expectFor static AstNodeIndex expectFor( Parser* p, AstNodeIndex (*body_parse_fn)(Parser*)) { const AstTokenIndex for_token = assertToken(p, TOKEN_KEYWORD_FOR); const uint32_t scratch_top = p->scratch.len; const uint32_t inputs = forPrefix(p); const AstNodeIndex then_expr = body_parse_fn(p); bool has_else = false; if (eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) { SLICE_APPEND(AstNodeIndex, &p->scratch, then_expr); const AstNodeIndex else_expr = body_parse_fn(p); SLICE_APPEND(AstNodeIndex, &p->scratch, else_expr); has_else = true; } else if (inputs == 1) { const AstNodeIndex input = p->scratch.arr[scratch_top]; p->scratch.len = scratch_top; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_FOR_SIMPLE, .main_token = for_token, .data = { .lhs = input, .rhs = then_expr }, }); } else { SLICE_APPEND(AstNodeIndex, &p->scratch, then_expr); } const AstSubRange span = listToSpan( p, &p->scratch.arr[scratch_top], p->scratch.len - scratch_top); p->scratch.len = scratch_top; return addNode(&p->nodes, (AstNodeItem) { .tag = AST_NODE_FOR, .main_token = for_token, .data = { .lhs = span.start, .rhs = (inputs & AST_FOR_INPUTS_MASK) | (has_else ? AST_FOR_HAS_ELSE : 0u), }, }); } // Parse.zig:eatDocComments static AstTokenIndex eatDocComments(Parser* p) { AstTokenIndex first = null_token; AstTokenIndex tok; while ((tok = eatToken(p, TOKEN_DOC_COMMENT)) != null_token) { if (first == null_token) { if (tok > 0 && tokensOnSameLine(p, tok - 1, tok)) { SET_ERROR(p, "same_line_doc_comment"); return null_token; } first = tok; } } return first; } // Parse.zig:tokensOnSameLine static bool tokensOnSameLine( Parser* p, AstTokenIndex tok1, AstTokenIndex tok2) { const uint32_t start1 = p->token_starts[tok1]; const uint32_t start2 = p->token_starts[tok2]; for (uint32_t i = start1; i < start2; i++) { if (p->source[i] == '\n') return false; } return true; } // Parse.zig:eatToken static AstTokenIndex eatToken(Parser* p, TokenTag tag) { if (p->token_tags[p->tok_i] == tag) { return nextToken(p); } else { return null_token; } } // Parse.zig:eatTokens static AstTokenIndex eatTokens( Parser* p, const TokenTag* tags, uint32_t tags_len) { if (p->tok_i + tags_len > p->tokens_len) return null_token; for (uint32_t i = 0; i < tags_len; i++) { if (p->token_tags[p->tok_i + i] != tags[i]) return null_token; } const AstTokenIndex result = p->tok_i; p->tok_i += tags_len; return result; } // Parse.zig:assertToken static AstTokenIndex assertToken(Parser* p, TokenTag tag) { const AstTokenIndex token = nextToken(p); if (p->token_tags[token] != tag) { p->tok_i = token; failExpected(p, tag); return null_token; } return token; } // Parse.zig:expectToken static AstTokenIndex expectToken(Parser* p, TokenTag tag) { if (p->token_tags[p->tok_i] == tag) return nextToken(p); failExpected(p, tag); return null_token; } // Parse.zig:expectSemicolon static AstNodeIndex expectSemicolon(Parser* p) { return expectToken(p, TOKEN_SEMICOLON); } static AstTokenIndex nextToken(Parser* p) { return p->tok_i++; } // This file's macros end with it (zig1.c is one translation unit). #undef SET_ERROR #undef SET_ERROR_FOUND /* ---- end parser.c ---- */ /* ---- begin sema.c ---- */ // sema.c — src/Sema.zig: ZIR to AIR. analyzeBodyInner is // Sema.analyzeBodyInner: a switch over the ZIR tags in upstream's order that // dispatches each to its zirXxx handler, with upstream's control-flow arms // (block_inline, condbr_inline, break, repeat, defer, ...) in the loop. The // handlers, in Sema.zig order just above it, take a SemaCtx (upstream's // `sema: *Sema`) and read their ZIR payloads with zirExtraData. The // functions around them are Sema's helpers under their upstream names // (coerce, elemPtr, fieldPtr, analyzeLoad, ...); a bare name is always an // upstream one, zig1's own helpers carry the sema prefix (semaCastLanes) and // cc funcCommon's calling-convention resolution (AGENTS.txt §1 "Prefixes"). // The declaration-level analysis is here too: member access, the container and // tuple decls, @Type and @export. The comptime-only evaluator ctEvalBody is in // sema_comptime.c, Sema/arith.zig, comptime_ptr_access.zig and // type_resolution.zig in arith.c, comptime_ptr_access.c and type_resolution.c, // the Type.zig and Value.zig queries in type.c and value.c, and PerThread's // file, namespace and function-body driver in zcu_per_thread.c. The section // banners below group the file by topic; declarations are in zig1.h. #ifndef ZIG1_H #include "zig1.h" #endif #include // ── AIR building blocks ───────────────────────────────────────────────────── // Append `v` only if it is not already present. Use only for set-like queues; // AIR/body/extra lists may require duplicate entries. static void semaU32ListAppendUnique(U32List* l, uint32_t v) { for (uint32_t i = 0; i < l->len; i++) if (l->items[i] == v) return; u32ListAppend(l, v); } // Growable list of AIR instructions (tag + 8-byte data), mirroring // Sema.air_instructions. // An AIR `alloc`'s second data word carries upstream's `is_const` bit for the // allocation (`Sema.zirAlloc` / `zirAllocInferred(is_const)` against their // `Mut` siblings). Only the ZIR alloc arms set it; every compiler-internal // scratch alloc leaves it zero and is therefore not store-forwarded from. An // inferred alloc's word is its whole pointer flags (`is_const`, `alignment`). #define AIR_ALLOC_IS_CONST IP_PTR_FLAGS_IS_CONST uint32_t semaAirInstAppend(AirInstList* l, AirInstTag tag, AirInstData data) { if (l->len == l->cap) { l->cap = zig1GrowCap(l->cap, 8u); l->tags = zig1Realloc(l->tags, l->cap * sizeof(uint8_t)); l->datas = zig1Realloc(l->datas, l->cap * sizeof(AirInstData)); } uint32_t idx = l->len; l->tags[idx] = (uint8_t)tag; l->datas[idx] = data; l->len++; return idx; } // InternPool.zig:isErrorUnionType // "ip.indexToKey(ty) == .error_union_type", which both storage tags answer; // false for any other index. static bool semaIsErrorUnionType(const InternPool* ip, uint32_t ty) { return ipItemTagIs(ip, ty, IP_TAG_TYPE_ERROR_UNION) || ipItemTagIs(ip, ty, IP_TAG_TYPE_ANYERROR_UNION); } static uint32_t semaResolveRemoveEuOptDestType(InternPool* ip, uint32_t ty) { if (ztIsZigTypeTag(ip, ty, ZIG_TYPE_TAG_ERROR_UNION)) ty = ipErrorUnionPayload(ip, ty); if (ztIsZigTypeTag(ip, ty, ZIG_TYPE_TAG_OPTIONAL)) ty = ip->item_datas[ty]; return ty; } static uint32_t semaTagNameComptime( InternPool* ip, uint32_t operand, uint32_t* out_ptr_ty) { // Sema.zirTagName switches on the operand's type; zig1 dispatches on // the value's IP item tag instead. // Literal names itself, enum tag names its field; same tail below. uint32_t name_str; if (operand < ip->items_len && ip->item_tags[operand] == IP_TAG_ENUM_LITERAL) { // `const tag_name = ip.indexToKey(val.toIntern()).enum_literal;` — the // literal's own interned string, which zig1 keeps in the item's // `data` word (ipGetEnumLiteral); no enum type is involved. name_str = ip->item_datas[operand]; } else { // `".@\"union\" => operand_ty.unionTagType(zcu) orelse return // sema.fail(block, src, \"union '{f}' is untagged\", ...)"`, then // the coercion to it: the union value's tag. if (operand < ip->items_len && ip->item_tags[operand] == IP_TAG_UNION_VALUE) { if (ipUnionTagUsage(ip, ipTypeOf(ip, operand)) != IP_UNION_TAG_USAGE_TAGGED) COMPILE_ERROR_UPSTREAM("tag_name: union is untagged"); operand = ipUnionValueTag(ip, operand); } if (operand >= ip->items_len || ip->item_tags[operand] != IP_TAG_ENUM_TAG) UNIMPLEMENTED("tag_name: comptime non-enum-tag operand"); uint32_t enum_ty = ipEnumTagType(ip, operand); uint32_t field_idx = ipEnumTagFieldIndex(ip, operand); if (field_idx == UINT32_MAX) UNIMPLEMENTED("tag_name: enum tag has no exact field"); name_str = ipEnumFieldName(ip, enum_ty, field_idx); } return addNullTerminatedStrLit(ip, name_str, out_ptr_ty); } // Sema.zig:addNullTerminatedStrLit // Sema.addNullTerminatedStrLit -> addStrLit: intern the `[len:0]u8` array // type + bytes aggregate, then the `*const [len:0]u8` uav pointer (uavValue). uint32_t addNullTerminatedStrLit( InternPool* ip, uint32_t name_str, uint32_t* out_ptr_ty) { uint32_t len = ipStringLen(ip, name_str); uint32_t arr_ty = ipGetArrayType(ip, len, ZIR_REF_U8_TYPE, ZIR_REF_ZERO_U8); uint32_t bytes = ipInternBytesAggregate(ip, arr_ty, name_str, len); uint32_t ptr_ty = ipGetPtrType(ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); if (out_ptr_ty) *out_ptr_ty = ptr_ty; return ipGetPtrUav(ip, ptr_ty, bytes); } // `ptrCastFull`'s `pre_addrspace_cast` inner block (src/Sema.zig): // // if (src_info.flags.size != .slice) break :inner operand; // if (operand_ty.zigTypeTag(zcu) == .optional) { // break :inner try sema.analyzeOptionalSlicePtr(...); // } else { // break :inner try sema.analyzeSlicePtr(...); // } // // The cast acts on the raw pointer, so a slice operand first yields its `ptr` // field; a bare ptr_cast would otherwise reinterpret the slice STRUCT's // address as the element pointer. `analyzeSlicePtr` (src/Sema.zig) is // AIR slice_ptr; `analyzeOptionalSlicePtr` (src/Sema.zig) is AIR // optional_payload to the slice type followed by that same slice_ptr, whose // result type is `slice_ty.slicePtrFieldType(zcu)` -- the wrapped many-ptr. // Rewrites `*op_ty`/`*op_val` in place; a non-slice operand is untouched. static void semaPtrCastOperandSlicePtr(const InternPool* ip, AirInstList* insts, U32List* block, uint32_t* op_ty, uint32_t* op_val) { uint32_t ty = *op_ty; if (ztIsZigTypeTag(ip, ty, ZIG_TYPE_TAG_OPTIONAL)) { uint32_t child = ipOptionalChild(ip, ty); if (!ztIsSlice(ip, child)) return; AirInstData opp; memset(&opp, 0, sizeof(opp)); opp.pair.a = child; // ty_op.ty = the payload slice type opp.pair.b = (uint32_t)*op_val; uint32_t opp_idx = semaAirInstAppend(insts, AIR_INST_OPTIONAL_PAYLOAD, opp); u32ListAppend(block, opp_idx); *op_val = AIR_REF_INST_BASE + opp_idx; ty = child; } else if (!ztIsSlice(ip, ty)) { return; } uint32_t many = ip->item_datas[ty]; // [*]T AirInstData sp; memset(&sp, 0, sizeof(sp)); sp.pair.a = many; sp.pair.b = (uint32_t)*op_val; uint32_t sp_idx = semaAirInstAppend(insts, AIR_INST_SLICE_PTR, sp); u32ListAppend(block, sp_idx); *op_val = AIR_REF_INST_BASE + sp_idx; *op_ty = many; } static uint32_t semaEmitAirUnionInit(AirInstList* insts, U32List* block, U32List* air_extra, uint32_t union_ty, uint32_t field_index, uint32_t init_ref) { uint32_t payload = air_extra->len; u32ListAppend(air_extra, field_index); // UnionInit.field_index u32ListAppend(air_extra, init_ref); // UnionInit.init (Ref) AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = union_ty; // ty_pl.ty data.pair.b = payload; // ty_pl.payload_index uint32_t air_idx = semaAirInstAppend(insts, AIR_INST_UNION_INIT, data); u32ListAppend(block, air_idx); return AIR_REF_INST_BASE + air_idx; } // `block.addAggregateInit(ty, refs)`: AIR aggregate_init, ty_pl { ty, payload // = the `n` element refs }. static uint32_t semaEmitAggregateInit(AirInstList* insts, U32List* block, U32List* air_extra, uint32_t ty, const uint32_t* refs, uint32_t n) { uint32_t payload = air_extra->len; for (uint32_t i = 0; i < n; i++) u32ListAppend(air_extra, refs[i]); AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = ty; data.pair.b = payload; uint32_t idx = semaAirInstAppend(insts, AIR_INST_AGGREGATE_INIT, data); u32ListAppend(block, idx); return AIR_REF_INST_BASE + idx; } static uint32_t semaEmitAggFieldVal(AirInstList* insts, U32List* block, U32List* air_extra, uint32_t operand, uint32_t field_index, uint32_t field_ty) { uint32_t payload = air_extra->len; u32ListAppend(air_extra, operand); // struct_operand u32ListAppend(air_extra, field_index); // field_index AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = field_ty; // ty_pl result type data.pair.b = payload; uint32_t air_idx = semaAirInstAppend(insts, AIR_INST_AGG_FIELD_VAL, data); u32ListAppend(block, air_idx); return AIR_REF_INST_BASE + air_idx; } // (part of) Air.zig:Inst.Ref.toIndexAllowNone // Is an inst_values entry an AIR instruction Ref (a runtime value)? `.none` // (INST_VALUE_UNSET) has the tag bit set but is not, as upstream's // `.none => null` prong. static bool semaIsAirInstRef(uint32_t val) { return val != INST_VALUE_UNSET && val >= AIR_REF_INST_BASE; } // Whether `body` ends in an instruction `Air.typeOfIndex` types `.noreturn`. static bool semaAirBodyEndsNoReturn( const AirInstList* insts, const U32List* body) { if (body->len == 0) return false; uint8_t t = insts->tags[body->items[body->len - 1]]; return t == AIR_INST_LOOP || t == AIR_INST_REPEAT || t == AIR_INST_BR || t == AIR_INST_COND_BR || t == AIR_INST_SWITCH_BR || t == AIR_INST_LOOP_SWITCH_BR || t == AIR_INST_SWITCH_DISPATCH || t == AIR_INST_RET || t == AIR_INST_RET_SAFE || t == AIR_INST_RET_LOAD || t == AIR_INST_UNREACH || t == AIR_INST_TRAP; } // `Sema.base_allocs`: the local alloc a derived pointer is based on. Upstream // records the mapping as it emits each pointer (`checkKnownAllocPtr`: `"try // sema.base_allocs.put(sema.gpa, new_ptr_inst, alloc_inst);"`); zig1 walks // the emitted AIR back instead, which reads the same chain in reverse. static bool semaAirRefToAllocIndex(const AirInstList* insts, uint32_t ptr_val, uint32_t* out_alloc_idx, uint32_t* out_ptr_ty) { if (!semaIsAirInstRef(ptr_val)) return false; uint32_t idx = ptr_val - AIR_REF_INST_BASE; // Each step names an earlier instruction (an operand is emitted before // its user), so the walk terminates; `insts->len` is the bound that says // so. // Upstream's `base_allocs` map has no depth cap either. for (uint32_t step = 0; step <= insts->len; step++) { if (idx >= insts->len) return false; if (insts->tags[idx] == AIR_INST_ALLOC) { *out_alloc_idx = idx; *out_ptr_ty = insts->datas[idx].pair.a; return true; } if (insts->tags[idx] != AIR_INST_PTR_CAST || insts->datas[idx].pair.b < AIR_REF_INST_BASE) return false; idx = insts->datas[idx].pair.b - AIR_REF_INST_BASE; } return false; } // How a store's target pointer relates to the allocation being resolved. // Upstream has only two answers, because `base_allocs` decides: a pointer it // does not know is a different variable, and a pointer it knows always maps // onto one of the `PointerMethod` shapes (`else => unreachable`). zig1 // recovers the mapping by walking AIR instead of recording it, so it has a // third case upstream cannot have -- a pointer that is based on this // allocation through a derivation this scan does not model -- and the only // safe answer there is upstream's other exit, `"orelse break :known"`: this // allocation has no comptime-known value. Answering "different variable" // Skips the store, and a preceding whole-alloc store then hands back a stale // value. typedef enum { ZPT_STORE_OTHER, // `base_allocs.get(ptr) orelse` -- another variable ZPT_STORE_MATCH, // one of the modeled `PointerMethod` shapes ZPT_STORE_DECLINE, // based on this alloc, shape not modeled } SemaStoreMatch; typedef enum { // The chain ends at an `alloc`; `*out_root_idx` names it. ZPT_ROOT_ALLOC, // The chain ends somewhere that is not an alloc derivation at all (an // argument, a load, a call result, an interned pointer). Upstream's // `base_allocs` was never given such a pointer, so it is another // variable. ZPT_ROOT_OTHER, // The chain runs into a derivation whose operand lives in `air_extra` // (`struct_field_ptr`, `ptr_elem_ptr`), which this scan cannot read. The // pointer MAY be based on the allocation under consideration, so the // caller must take upstream's other exit rather than guess. ZPT_ROOT_UNKNOWN, } SemaPtrRoot; // (part of) Sema.zig:checkKnownAllocPtr // The allocation a pointer instruction is derived from, following the // derivations whose operand rides in the instruction itself (upstream records // the same chain forwards, in `Sema.checkKnownAllocPtr`). static SemaPtrRoot semaPtrDerivationRoot(const AirInstList* insts, const U32List* air_extra, uint32_t ptr_val, uint32_t* out_root_idx) { if (!semaIsAirInstRef(ptr_val)) return ZPT_ROOT_OTHER; uint32_t idx = ptr_val - AIR_REF_INST_BASE; for (uint32_t step = 0; step <= insts->len; step++) { if (idx >= insts->len) return ZPT_ROOT_OTHER; AirInstTag t = (AirInstTag)insts->tags[idx]; if (t == AIR_INST_ALLOC) { *out_root_idx = idx; return ZPT_ROOT_ALLOC; } // `struct_field_ptr`'s `StructField.struct_operand` and // `ptr_elem_ptr`'s `Air.Bin.lhs` are both the first payload word, so // the parent pointer is readable once `air_extra` is in hand; without // it the chain is unreadable and the caller must take the safe exit. if (t == AIR_INST_STRUCT_FIELD_PTR || t == AIR_INST_PTR_ELEM_PTR) { uint32_t payload = insts->datas[idx].pair.b; if (air_extra == NULL || payload >= air_extra->len) return ZPT_ROOT_UNKNOWN; uint32_t parent = air_extra->items[payload]; if (parent < AIR_REF_INST_BASE) return ZPT_ROOT_OTHER; idx = parent - AIR_REF_INST_BASE; continue; } bool ty_op_derived = t == AIR_INST_PTR_CAST || (t >= AIR_INST_STRUCT_FIELD_PTR_INDEX_0 && t <= AIR_INST_STRUCT_FIELD_PTR_INDEX_3) || t == AIR_INST_PTR_SLICE_PTR_PTR || t == AIR_INST_PTR_SLICE_LEN_PTR || t == AIR_INST_OPTIONAL_PAYLOAD_PTR_SET || t == AIR_INST_ERRUNION_PAYLOAD_PTR_SET; if (!ty_op_derived || insts->datas[idx].pair.b < AIR_REF_INST_BASE) return ZPT_ROOT_OTHER; idx = insts->datas[idx].pair.b - AIR_REF_INST_BASE; } return ZPT_ROOT_OTHER; } // (part of) Sema.zig:resolveComptimeKnownAllocPtr // The safe answer for a pointer whose derivation this scan does not model: // `Sema.resolveComptimeKnownAllocPtr`'s `"orelse break :known"` direction. A // pointer rooted at this allocation -- or one whose root cannot be read at // all -- declines it, because skipping such a store would let a preceding // whole-alloc store hand back a stale value; a pointer rooted anywhere else // is simply another variable, which upstream's `base_allocs.get(ptr) orelse` // walks past. Both the store scan and the `set_union_tag` / // `optional_payload_ptr_set` / `errunion_payload_ptr_set` arm ask this // question, so it is asked in one place. static SemaStoreMatch semaUnmodeledPtrMatch(const AirInstList* insts, const U32List* air_extra, uint32_t alloc_idx, uint32_t ptr_val) { uint32_t root_idx = UINT32_MAX; switch (semaPtrDerivationRoot(insts, air_extra, ptr_val, &root_idx)) { case ZPT_ROOT_UNKNOWN: return ZPT_STORE_DECLINE; case ZPT_ROOT_ALLOC: return root_idx == alloc_idx ? ZPT_STORE_DECLINE : ZPT_STORE_OTHER; case ZPT_ROOT_OTHER: break; } return ZPT_STORE_OTHER; } // One place in the comptime allocation `resolveComptimeKnownAllocPtr` // replays the stores into: the value written there as a whole // (IP_INDEX_NONE while undefined), or -- once a store reached inside it -- // its fields / elements in `kids`; a union also records its active field. typedef struct { uint32_t ty; uint32_t whole; uint32_t kids; // first child in the pool, UINT32_MAX when not expanded uint32_t nkids; uint32_t active; // union: the active field, UINT32_MAX when none } SemaCkNode; typedef struct { SemaCkNode* items; uint32_t len, cap; } SemaCkPool; // One step of a store target's derivation from the allocation (upstream's // `PointerMethod` `.field` / `.elem`; `.same_addr` adds no step). typedef struct { bool elem; uint32_t index; } SemaCkStep; #define ZPT_CK_MAX_DEPTH 16u static uint32_t semaCkNew(SemaCkPool* pool, uint32_t ty, uint32_t whole) { if (pool->len == pool->cap) { pool->cap = zig1GrowCap(pool->cap, 16u); pool->items = zig1Realloc(pool->items, pool->cap * sizeof(SemaCkNode)); } pool->items[pool->len] = (SemaCkNode) { ty, whole, UINT32_MAX, 0, UINT32_MAX }; return pool->len++; } // The children of a place of type `ty`: a struct's or tuple's fields, a // sequence's elements, a union's fields. False for any other type. static bool semaCkChildren( const InternPool* ip, uint32_t ty, uint32_t i, uint32_t* n, uint32_t* t) { uint8_t tag = ip->item_tags[ty]; IpArrayInfo seq; if (ipTagIsAnyStruct(tag)) { *n = ipStructFieldsLen(ip, ty); *t = i < *n ? ipStructFieldType(ip, ty, i) : IP_INDEX_NONE; } else if (tag == IP_TAG_TYPE_TUPLE) { *n = ipTupleFieldCount(ip, ty); *t = i < *n ? ipTupleFieldType(ip, ty, i) : IP_INDEX_NONE; } else if (ipTagIsAnyUnion(tag)) { *n = ipUnionFieldsLen(ip, ty); *t = i < *n ? ipUnionFieldType(ip, ty, i) : IP_INDEX_NONE; } else if (ipArrayInfo(ip, ty, &seq) == IP_ARR_OK) { *n = seq.len; *t = seq.child; } else if (tag == IP_TAG_TYPE_OPTIONAL) { *n = 1; *t = ipOptionalChild(ip, ty); } else if (semaIsErrorUnionType(ip, ty)) { *n = 1; *t = ipErrorUnionPayload(ip, ty); } else { return false; } return true; } // Split a place written as a whole into its children (the comptime alloc's // `ptrField` / `ptrElem` into the stored value). False when the type has no // children this replay models. static bool semaCkExpand(InternPool* ip, SemaCkPool* pool, uint32_t node) { if (pool->items[node].kids != UINT32_MAX) return true; uint32_t ty = pool->items[node].ty, n, t; if (!semaCkChildren(ip, ty, 0, &n, &t)) return false; uint32_t whole = pool->items[node].whole; bool is_union = ztZigTypeTag(ip, ty) == ZIG_TYPE_TAG_UNION; // An optional or error union has its payload as the one child. bool is_payload = ztIsZigTypeTag(ip, ty, ZIG_TYPE_TAG_OPTIONAL) || semaIsErrorUnionType(ip, ty); uint32_t active = UINT32_MAX; if (is_union && whole != IP_INDEX_NONE && ipItemTagIs(ip, whole, IP_TAG_UNION_VALUE)) active = semaUnionFieldIndexFromTag(ip, ty, ipUnionValueTag(ip, whole)); else if ((is_union || ipStructIsPacked(ip, ty)) && whole != IP_INDEX_NONE && !ipIsUndef(ip, whole)) return false; // a `.bitpack` whole is not split here uint32_t first = pool->len; for (uint32_t i = 0; i < n; i++) { semaCkChildren(ip, ty, i, &n, &t); uint32_t kw = IP_INDEX_NONE; if (is_payload) { if (whole != IP_INDEX_NONE && (ipItemTagIs(ip, whole, IP_TAG_OPT_PAYLOAD) || ipItemTagIs(ip, whole, IP_TAG_ERROR_UNION_PAYLOAD))) kw = ipPayloadVal(ip, whole); } else if (whole != IP_INDEX_NONE && !ipIsUndef(ip, whole)) { if (is_union) kw = i == active ? ipUnionValuePayload(ip, whole) : IP_INDEX_NONE; else kw = semaAggregateElemValueOrUndef( ip, whole, t, i, "zirMakePtrConst: comptime-known alloc"); } semaCkNew(pool, t, kw); } pool->items[node].kids = first; pool->items[node].nkids = n; pool->items[node].active = active; return true; } // ── Comptime-known allocations, Navs and namespace members ────────────────── // Upstream's `ptr_mapping` walk for one store target: the steps from the // allocation down to it, outermost first. A pointer this scan does not // model answers semaUnmodeledPtrMatch's question. static SemaStoreMatch semaCkPath(const InternPool* ip, const AirInstList* insts, const U32List* air_extra, uint32_t alloc_idx, uint32_t target_ref, SemaCkStep* steps, uint32_t* out_len) { uint32_t alloc_ref = AIR_REF_INST_BASE + alloc_idx; SemaCkStep rev[ZPT_CK_MAX_DEPTH]; uint32_t n = 0; uint32_t ref = target_ref; while (ref != alloc_ref) { if (ref < AIR_REF_INST_BASE) return ZPT_STORE_OTHER; uint32_t idx = ref - AIR_REF_INST_BASE; if (idx >= insts->len || n == ZPT_CK_MAX_DEPTH) return ZPT_STORE_DECLINE; AirInstTag tag = (AirInstTag)insts->tags[idx]; SemaCkStep step = { false, UINT32_MAX }; uint32_t parent = UINT32_MAX; if (tag >= AIR_INST_STRUCT_FIELD_PTR_INDEX_0 && tag <= AIR_INST_STRUCT_FIELD_PTR_INDEX_3) { step.index = (tag - AIR_INST_STRUCT_FIELD_PTR_INDEX_0); parent = insts->datas[idx].pair.b; } else if (tag == AIR_INST_STRUCT_FIELD_PTR || tag == AIR_INST_PTR_ELEM_PTR) { uint32_t payload = insts->datas[idx].pair.b; if (air_extra == NULL || payload + 1 >= air_extra->len) return ZPT_STORE_DECLINE; parent = air_extra->items[payload]; uint32_t second = air_extra->items[payload + 1]; if (tag == AIR_INST_STRUCT_FIELD_PTR) { step.index = second; } else { // `"const idx_val = sema.resolveValue(data.rhs).?;"` if (semaIsAirInstRef(second) || second >= ip->items_len) return semaUnmodeledPtrMatch( insts, air_extra, alloc_idx, target_ref); uint64_t i64 = ipReadIntValue(ip, second); if (i64 >= UINT32_MAX) return ZPT_STORE_DECLINE; step = (SemaCkStep) { true, (uint32_t)i64 }; } } else if (tag == AIR_INST_OPTIONAL_PAYLOAD_PTR_SET || tag == AIR_INST_ERRUNION_PAYLOAD_PTR_SET) { // `.opt_payload` / `.eu_payload`: the payload, the one child. step.index = 0; parent = insts->datas[idx].pair.b; } else if (tag == AIR_INST_PTR_CAST) { // `.same_addr`, except that `elemPtrVector` retypes the vector's // own pointer, so the lane lives in the result type's // `vector_index`. IpPtrInfo pi; uint32_t lane = IP_PTR_VECTOR_INDEX_NONE; if (ipPtrInfo(ip, insts->datas[idx].pair.a, &pi) == IP_PTRI_OK) lane = IP_PTR_FLAGS_VECTOR_INDEX(pi.flags); if (lane != IP_PTR_VECTOR_INDEX_NONE) step = (SemaCkStep) { true, lane }; parent = insts->datas[idx].pair.b; } else { return semaUnmodeledPtrMatch( insts, air_extra, alloc_idx, target_ref); } if (step.index != UINT32_MAX) rev[n++] = step; ref = parent; } for (uint32_t i = 0; i < n; i++) steps[i] = rev[n - 1 - i]; *out_len = n; return ZPT_STORE_MATCH; } // The place `steps` names below `node`, expanding wholes on the way. A union // field step makes that field active (`"As this is a union field, we must // store to the pointer now to set the tag."`, with an undefined payload). static uint32_t semaCkWalk(InternPool* ip, SemaCkPool* pool, uint32_t node, const SemaCkStep* steps, uint32_t len) { for (uint32_t s = 0; s < len; s++) { if (!semaCkExpand(ip, pool, node) || steps[s].index >= pool->items[node].nkids) return UINT32_MAX; uint32_t kid = pool->items[node].kids + steps[s].index; if (ipTagIsAnyUnion(ip->item_tags[pool->items[node].ty]) && pool->items[node].active != steps[s].index) { pool->items[node].active = steps[s].index; pool->items[kid].whole = IP_INDEX_NONE; pool->items[kid].kids = UINT32_MAX; } node = kid; } return node; } static uint32_t semaCkValue(Zcu* zcu, const SemaCkPool* pool, uint32_t n) { InternPool* ip = &zcu->intern_pool; const SemaCkNode* node = &pool->items[n]; if (node->kids == UINT32_MAX) return node->whole != IP_INDEX_NONE ? node->whole : ipGetUndef(ip, node->ty); if (ztZigTypeTag(ip, node->ty) == ZIG_TYPE_TAG_UNION) { if (node->active == UINT32_MAX) return ipGetUndef(ip, node->ty); uint32_t ty = node->ty, active = node->active; uint32_t payload = semaCkValue(zcu, pool, node->kids + active); if (payload == IP_INDEX_NONE) return IP_INDEX_NONE; // A packed union is its payload's bits (zirStructInit's packed arm: // `sema.bitCast(block, resolved_ty, init_inst, src)`). if (ipUnionIsPacked(ip, ty)) return bitCastVal(zcu, payload, ty); uint32_t tag_enum = ipUnionEnumTagType(ip, ty); if (tag_enum == IP_INDEX_NONE) tag_enum = ipEnsureGeneratedUnionTag(ip, ty); uint32_t tag = ipEnumTagFromFieldName( ip, tag_enum, ipUnionFieldName(ip, ty, active)); return ipGetUnionValue(ip, ty, tag, payload); } bool is_opt = ztIsZigTypeTag(ip, node->ty, ZIG_TYPE_TAG_OPTIONAL); if (is_opt || semaIsErrorUnionType(ip, node->ty)) { uint32_t payload = semaCkValue(zcu, pool, node->kids); if (payload == IP_INDEX_NONE) return IP_INDEX_NONE; return is_opt ? ipGetOptPayload(ip, node->ty, payload) : ipGetErrorUnionPayload(ip, node->ty, payload); } uint32_t ty = node->ty, kids = node->kids, nk = node->nkids; uint32_t* elems = ARR_INIT(uint32_t, nk + 1u); for (uint32_t i = 0; i < nk; i++) { elems[i] = semaCkValue(zcu, pool, kids + i); if (elems[i] == IP_INDEX_NONE) { free(elems); return IP_INDEX_NONE; } } // `finishStructInit`'s `.@"packed"` arm: the fields packed into the // `.bitpack`, not an aggregate. uint32_t res = ipStructIsPacked(ip, ty) ? semaPackStructFields(zcu, ty, elems, nk) : ipGetAggregate(ip, ty, elems, nk); free(elems); return res; } // (part of) Sema.zig:zirBlock // The instructions at the `runtime_index` of `block`: `block` itself and, // in emission order, the bodies of the AIR `block`s in it (Sema.zirBlock's // child block keeps `.runtime_index = parent.runtime_index`). static void semaSameRuntimeIndexInsts(const AirInstList* insts, const U32List* air_extra, const uint32_t* body, uint32_t len, U32List* out) { for (uint32_t i = 0; i < len; i++) { uint32_t ai = body[i]; u32ListAppend(out, ai); if (ai < insts->len && insts->tags[ai] == AIR_INST_BLOCK && air_extra != NULL && insts->datas[ai].pair.b < air_extra->len) { uint32_t pl = insts->datas[ai].pair.b; semaSameRuntimeIndexInsts(insts, air_extra, &air_extra->items[pl + 1], air_extra->items[pl], out); } } } // (part of) Sema.zig:resolveComptimeKnownAllocPtr, Sema.zig:zirAlloc // `Sema.resolveComptimeKnownAllocPtr`: the comptime-known value of a local // allocation, reassembled by replaying the stores into it. Upstream keeps the // store list as it goes (`checkComptimeKnownStore` appends to // `maybe_comptime_allocs`), then either takes the single whole-alloc store // (its `simple:` block) or maps every store target onto a pointer into a // comptime alloc and performs the stores in order before loading the // finished value. zig1 has no `maybe_comptime_allocs` side table, so the // same store list is recovered from the AIR: the instructions at the // alloc's `runtime_index` are its block plus, in order, the bodies of the // `block`s nested in it (a labeled block or an inlined call keeps its // parent's index); a `cond_br`, `switch_br` or `loop` body is a new // `runtime_index`, and upstream's checkComptimeKnownStore marks the alloc // runtime for a store there (`"block.runtime_index == // maybe_comptime_alloc.runtime_index"` or `markMaybeComptimeAllocRuntime`). // The comptime alloc is a tree of SemaCkNode places. // // `stop_idx` bounds the scan at an AIR instruction index (UINT32_MAX for the // whole block): a `load` is described only by the stores that precede it. // // One rule gates the whole thing: upstream enters an alloc into // `maybe_comptime_allocs` only `"if (small.is_const)"` (`Sema.zirAlloc`, // both the sized and the inferred arm), and the lookup here is `"orelse // return null"`. A runtime `var`'s storage therefore has no comptime-known // value, and store-forwarding out of one accepts programs stock rejects. A // const allocation is written by nothing but its own initializer, whose // pointer shapes (`struct_field_ptr*`, `ptr_elem_ptr`, `ptr_cast`, // `set_union_tag`, the payload `*_ptr_set`s) are the ones replayed; any // other pointer into it declines the allocation. static uint32_t semaResolveComptimeKnownAllocValue(Zcu* zcu, const AirInstList* insts, const U32List* block, const U32List* air_extra, uint32_t ptr_val, uint32_t stop_idx, uint32_t* out_ty, U32List* out_stores) { InternPool* ip = &zcu->intern_pool; if (insts == NULL || block == NULL) return IP_INDEX_NONE; uint32_t alloc_idx, alloc_ptr_ty; if (!semaAirRefToAllocIndex(insts, ptr_val, &alloc_idx, &alloc_ptr_ty)) return IP_INDEX_NONE; if (alloc_ptr_ty == 0 || alloc_ptr_ty >= ip->items_len || ip->item_tags[alloc_ptr_ty] != IP_TAG_TYPE_POINTER) return IP_INDEX_NONE; if ((insts->datas[alloc_idx].pair.b & AIR_ALLOC_IS_CONST) == 0) return IP_INDEX_NONE; uint32_t elem_ty = ipPtrChild(ip, alloc_ptr_ty); U32List same = { 0 }; semaSameRuntimeIndexInsts( insts, air_extra, block->items, block->len, &same); // A store to this alloc anywhere else is under another runtime_index: // `markMaybeComptimeAllocRuntime`. bool ok = true; bool* at_index = zig1Calloc((size_t)insts->len + 1u, sizeof(bool)); for (uint32_t si = 0; si < same.len; si++) if (same.items[si] < insts->len) at_index[same.items[si]] = true; for (uint32_t ai = alloc_idx + 1; ai < insts->len && ok; ai++) { AirInstTag t = (AirInstTag)insts->tags[ai]; if (at_index[ai] || (t != AIR_INST_STORE && t != AIR_INST_STORE_SAFE && t != AIR_INST_SET_UNION_TAG && t != AIR_INST_OPTIONAL_PAYLOAD_PTR_SET && t != AIR_INST_ERRUNION_PAYLOAD_PTR_SET)) continue; uint32_t target = (t == AIR_INST_OPTIONAL_PAYLOAD_PTR_SET || t == AIR_INST_ERRUNION_PAYLOAD_PTR_SET) ? AIR_REF_INST_BASE + ai : insts->datas[ai].pair.a; if (semaUnmodeledPtrMatch(insts, air_extra, alloc_idx, target) == ZPT_STORE_DECLINE) ok = false; } free(at_index); SemaCkPool pool = { 0 }; semaCkNew(&pool, elem_ty, IP_INDEX_NONE); bool any_store = false; for (uint32_t bi = 0; bi < same.len && ok; bi++) { uint32_t ai = same.items[bi]; if (ai >= insts->len) { ok = false; break; } if (ai == stop_idx) break; AirInstTag tag = (AirInstTag)insts->tags[ai]; // `optional_payload_ptr_set` / `errunion_payload_ptr_set` are stores // for upstream too: mapping the generated pointer sets the optional // non-null (the error union non-error) with the payload's one // possible value, else undefined. if (tag == AIR_INST_OPTIONAL_PAYLOAD_PTR_SET || tag == AIR_INST_ERRUNION_PAYLOAD_PTR_SET) { SemaCkStep set_steps[ZPT_CK_MAX_DEPTH]; uint32_t set_nsteps = 0; SemaStoreMatch set_match = semaCkPath(ip, insts, air_extra, alloc_idx, AIR_REF_INST_BASE + ai, set_steps, &set_nsteps); if (set_match == ZPT_STORE_DECLINE) { ok = false; break; } if (set_match == ZPT_STORE_OTHER) continue; uint32_t payload = semaCkWalk(ip, &pool, 0, set_steps, set_nsteps); if (payload == UINT32_MAX) { ok = false; break; } pool.items[payload].whole = ztOnePossibleValue(ip, pool.items[payload].ty); pool.items[payload].kids = UINT32_MAX; any_store = true; if (out_stores != NULL) u32ListAppend(out_stores, ai); continue; } bool set_tag = tag == AIR_INST_SET_UNION_TAG; if (!set_tag && tag != AIR_INST_STORE && tag != AIR_INST_STORE_SAFE) continue; SemaCkStep steps[ZPT_CK_MAX_DEPTH]; uint32_t nsteps = 0; SemaStoreMatch matched = semaCkPath(ip, insts, air_extra, alloc_idx, insts->datas[ai].pair.a, steps, &nsteps); if (matched == ZPT_STORE_DECLINE) { ok = false; break; } if (matched == ZPT_STORE_OTHER) continue; // a different variable uint32_t rhs = insts->datas[ai].pair.b; // Upstream asserts the stored operand is comptime-known here, because // a runtime one already removed the alloc from `maybe_comptime_allocs` // (`markMaybeComptimeAllocRuntime`). zig1 discovers it at this point // instead and declines the allocation as a whole. if (semaIsAirInstRef(rhs) || rhs >= ip->items_len) { ok = false; break; } // zirValidatePtrArrayInit's "else if (array_ty.sentinel(zcu)) // |sentinel| { ... sentinel_ptr = elemPtrArray(... array_len_ref ...); // storePtr2(... sentinel ...)": the store to index `len` of a // sentineled array writes the value its type already carries. if (!set_tag && nsteps > 0 && steps[nsteps - 1].elem) { uint32_t parent = semaCkWalk(ip, &pool, 0, steps, nsteps - 1); IpArrayInfo seq; if (parent != UINT32_MAX && ipArrayInfo(ip, pool.items[parent].ty, &seq) == IP_ARR_OK && seq.sentinel != IP_INDEX_NONE && steps[nsteps - 1].index == seq.len && semaCoerceComptime(NULL, ip, rhs, seq.child) == seq.sentinel) { any_store = true; if (out_stores != NULL) u32ListAppend(out_stores, ai); continue; } } uint32_t node = semaCkWalk(ip, &pool, 0, steps, nsteps); if (set_tag && node != UINT32_MAX) { // `set_union_tag`: the tag is one more union field step; an OPV // field gets its value here, since no store to it follows. uint32_t uty = pool.items[node].ty; SemaCkStep tag_step = { false, ztZigTypeTag(ip, uty) == ZIG_TYPE_TAG_UNION ? semaUnionFieldIndexFromTag(ip, uty, rhs) : UINT32_MAX }; node = semaCkWalk(ip, &pool, node, &tag_step, 1); } if (node == UINT32_MAX) { ok = false; break; } if (set_tag) { if (pool.items[node].whole == IP_INDEX_NONE && pool.items[node].kids == UINT32_MAX) pool.items[node].whole = ztOnePossibleValue(ip, pool.items[node].ty); } else { pool.items[node].whole = semaCoerceComptime(NULL, ip, rhs, pool.items[node].ty); pool.items[node].kids = UINT32_MAX; } any_store = true; if (out_stores != NULL) u32ListAppend(out_stores, ai); } uint32_t res = IP_INDEX_NONE; if (ok && any_store) res = semaCkValue(zcu, &pool, 0); else if (ok) // Upstream's OPV shortcut: `"if (try elem_ty.onePossibleValue(pt)) // |opv| return ... finishResolveComptimeKnownAllocPtr(... opv ...)"`, // taken before it asserts there was any store at all. res = ztOnePossibleValue(ip, elem_ty); free(pool.items); free(same.items); if (res != IP_INDEX_NONE && out_ty != NULL) *out_ty = elem_ty; return res; } // Sema.zig:resolveValue // `Sema.resolveValue`: the comptime-known value behind an operand, or // IP_INDEX_NONE when there is none. Upstream's body is one line -- an // `Air.Inst.Ref` below `.ref_start_index` is an interned value -- because by // the time it runs, `resolveComptimeKnownAllocPtr` has already replaced a // comptime-known allocation's pointer with a real value. zig1 keeps the AIR // `alloc`/`store`/`load` shape, so the resolution happens here instead: a // `load` whose pointer is such an allocation resolves to the value that // allocation holds. static uint32_t resolveValue(Zcu* zcu, const AirInstList* insts, const U32List* block, const U32List* air_extra, uint32_t val) { const InternPool* ip = &zcu->intern_pool; if (!semaIsAirInstRef(val)) return val < ip->items_len ? val : IP_INDEX_NONE; if (insts == NULL) return IP_INDEX_NONE; uint32_t load_idx = val - AIR_REF_INST_BASE; if (load_idx >= insts->len || insts->tags[load_idx] != AIR_INST_LOAD) return IP_INDEX_NONE; return semaResolveComptimeKnownAllocValue(zcu, insts, block, air_extra, insts->datas[load_idx].pair.b, load_idx, NULL, NULL); } // (part of) Sema.zig:analyzeNavRefInner // Pointer flags for a direct Nav pointer (Sema.analyzeNavRefInner). uint32_t semaNavDirectPtrFlags(const IpNav* nav) { if (!nav->has_resolved || !nav->modifiers_resolved || nav->resolved_alignment > IP_PTR_ALIGN_NONE) UNREACHABLE("forming pointer to unresolved/malformed Nav"); uint32_t flags = IP_PTR_FLAGS_WITH_ALIGN( IP_PTR_FLAGS_MUT_ONE, (uint32_t)nav->resolved_alignment); if (nav->is_const) flags |= IP_PTR_FLAGS_IS_CONST; return flags; } // Validate a comptime-known pointer-to-Nav used as a runtime data address. // PtrNav's payload is { ptr_ty, nav, byte_offset_hi, byte_offset_lo } and may // project a field within the Nav's declared storage. An extern Nav's storage // is the export the interpreter resolves by name; threadlocal Navs require // TLS machinery and therefore abort loudly. A // store requires both a mutable pointer view and a mutable source declaration; // read-only coercions of a mutable global remain valid on the load path. static const IpNav* semaRuntimeDataPtrNav(const InternPool* ip, uint32_t value, bool require_mutable, uint32_t* out_ptr_ty, uint32_t* out_nav) { uint32_t ptr_nav = ipPtrDerivedRoot(ip, value); IpPtrNav direct; if (ipDecodePtrNavAny(ip, ptr_nav, &direct) != IP_PN_OK) UNREACHABLE("analyzeNavRef: malformed pointer"); const IpNav* nav = ipGetNav(ip, direct.nav_index); if (nav->is_threadlocal) UNIMPLEMENTED("analyzeNavRef: threadlocal Nav storage"); bool ptr_is_const = (direct.ptr_flags & IP_PTR_FLAGS_IS_CONST) != 0; if (require_mutable && (nav->is_const || ptr_is_const)) UNIMPLEMENTED("analyzeNavRef: mutation of const Nav"); if (ztZigTypeTag(ip, direct.child_type) == ZIG_TYPE_TAG_FN) UNIMPLEMENTED("analyzeNavRef: function/invalid pointee"); if (nav->has_value_resolved && ipTypeOf(ip, nav->resolved_value) != nav->resolved_type) UNREACHABLE("analyzeNavRef: malformed resolved initializer"); if (out_ptr_ty) *out_ptr_ty = direct.ptr_type; if (out_nav) *out_nav = direct.nav_index; return nav; } // (part of) Type.zig:comptimeOnly // A PtrNav is an AIR-usable address whenever it names runtime-representable // data storage, even though the pointer value itself is comptime-known. This // includes const data: `&const_struct.field` must preserve stable address // identity instead of being replaced by the initializer field value. Type/ // namespace/fully-comptime/no-possible-value Navs remain on the fold-only // path. A partial aggregate still has addressable runtime fields even though // Type.comptimeOnly reports true for the value as a whole. A field or element // pointer of a mutable Nav is one too; of a const Nav it still folds. static bool semaIsRuntimeDataNavPtr(InternPool* ip, uint32_t value) { if (value == INST_VALUE_UNSET) return false; uint32_t ptr_nav = ipPtrDerivedRoot(ip, value); if (ptr_nav >= ip->items_len || ip->item_tags[ptr_nav] != IP_TAG_PTR_NAV) return false; IpPtrNav direct; if (ipDecodePtrNavAny(ip, ptr_nav, &direct) != IP_PN_OK) UNREACHABLE("analyzeNavRef: malformed pointer"); const IpNav* nav = ipGetNav(ip, direct.nav_index); if (nav->is_extern || nav->is_threadlocal) return false; // separate symbol/TLS paths remain unsupported if (nav->is_const && ptr_nav != value) return false; if (ztZigTypeTag(ip, direct.child_type) == ZIG_TYPE_TAG_FN) return false; ZtTypeClass type_class = ztTypeClass(ip, direct.child_type); if (type_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED( "comptimeOnly: runtime data value - unresolved pointee class"); return type_class == ZT_CLASS_OPV || type_class == ZT_CLASS_RUNTIME || type_class == ZT_CLASS_PARTIAL; } // A combined field-pointer load may still fold a const Nav's initializer: // only the address-producing FIELD_PTR forms must preserve const data storage // identity. Mutable Navs, on the other hand, always load from their persistent // runtime storage so writes remain observable across calls. bool semaIsRuntimeMutableDataNavPtr(InternPool* ip, uint32_t value) { if (!semaIsRuntimeDataNavPtr(ip, value)) return false; IpPtrNav direct; if (ipDecodePtrNavAny(ip, ipPtrDerivedRoot(ip, value), &direct) != IP_PN_OK) UNREACHABLE("runtime mutable data ptr_nav: malformed pointer"); return !ipGetNav(ip, direct.nav_index)->is_const; } // fieldPtr's `is_pointer_to`: true with `*out_loaded`/`*out_loaded_ty` set // when `ptr` is a comptime pointer to a single pointer, loaded via // semaAnalyzeLoadComptime; false (outputs untouched) for any other shape or a // runtime load. static bool semaPtrAutoDerefOnePointer( Zcu* zcu, uint32_t ptr, uint32_t* out_loaded, uint32_t* out_loaded_ty) { const InternPool* ip = &zcu->intern_pool; IpPtrInfo outer, inner; if (ptr >= ip->items_len || ipPtrInfo(ip, ipTypeOf(ip, ptr), &outer) != IP_PTRI_OK || outer.size != IP_PTR_SIZE_ONE || ipPtrInfo(ip, outer.child, &inner) != IP_PTRI_OK || inner.size != IP_PTR_SIZE_ONE) return false; *out_loaded = semaAnalyzeLoadComptime(zcu, ptr, out_loaded_ty); return *out_loaded != IP_INDEX_NONE; } static uint32_t structFieldPtrByIndex(Zcu* zcu, uint32_t struct_ptr, uint32_t struct_ptr_ty, uint32_t struct_ty, uint32_t field_index, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t* out_ty); // (part of) Sema.zig:structFieldPtr // Sema.structFieldPtr's tuple `len` arm: `"return sema.analyzeRef(block, // src, len_inst, .none)"` of the comptime usize count, a `*const usize` uav. uint32_t semaTupleLenRef( InternPool* ip, uint32_t tuple_ty, const char* field, uint32_t* out_ty) { if (!ztIsTuple(ip, tuple_ty) || strcmp(field, "len")) return IP_INDEX_NONE; uint32_t len = ipGetIntUsize(ip, ipTupleFieldCount(ip, tuple_ty)); *out_ty = ipGetPtrType(ip, ZIR_REF_USIZE_TYPE, IP_PTR_FLAGS_CONST_ONE); return ipGetPtrUav(ip, *out_ty, len); } // Sema.zig:fieldPtr, Sema.zig:namespaceLookupRef // Address of a member reached through a comptime namespace type. This is the // shared Sema.fieldPtr `.type` path for `ns.field` and `@field(ns, name)`: // namespace decls produce a ptr_nav to the member Nav, matching `decl_ref`; // enum fields fall back to a const UAV pointer to the tag value. The lookup // mirrors Sema.namespaceLookupRef: private decls are only visible same-file. static bool semaComptimeNamespaceFieldPtr(Zcu* zcu, const Zir* access_zir, uint32_t container_ty, uint32_t field_name, uint32_t* out_value, uint32_t* out_type, const char* no_namespace_msg) { InternPool* ip = &zcu->intern_pool; if (ipIsEnumType(ip, container_ty)) { uint32_t ens = ipEnumNamespaceOrNone(ip, container_ty); if (ens != IP_NAMESPACE_NONE) { uint32_t enav = IP_INDEX_NONE; bool status = semaNamespaceLookupAccessible( zcu, access_zir, ens, field_name, &enav); if (status) { uint32_t emem_ptr_ty = IP_INDEX_NONE; *out_value = navDeclRefAddressResolved(zcu, enav, &emem_ptr_ty); *out_type = emem_ptr_ty; return true; } } uint32_t etag = ipEnumTagFromFieldName(ip, container_ty, field_name); if (etag != IP_INDEX_NONE) { uint32_t eptr_ty = ipGetPtrType(ip, container_ty, IP_PTR_FLAGS_CONST_ONE); *out_value = ipGetPtrUav(ip, eptr_ty, etag); *out_type = eptr_ty; return true; } failWithBadMemberAccess(zcu, container_ty, field_name); } // `.@"struct", .@"opaque"` and `.@"union"`, packed or not; the enum arm // is above. if (!ztIsNamespaceType(ip, container_ty)) return false; bool is_union = ztZigTypeTag(ip, container_ty) == ZIG_TYPE_TAG_UNION; uint32_t member_ns = ipContainerNamespaceOrNone(ip, container_ty); uint32_t mnav = IP_INDEX_NONE; bool status = false; if (member_ns != IP_NAMESPACE_NONE) { status = semaNamespaceLookupAccessible( zcu, access_zir, member_ns, field_name, &mnav); } else if (!is_union) { UNIMPLEMENTED(no_namespace_msg); } if (!status && is_union && ipUnionTagUsage(ip, container_ty) == IP_UNION_TAG_USAGE_TAGGED) { semaResolveAllUnionDeferred(zcu, container_ty); uint32_t tag_enum = ipUnionEnumTagType(ip, container_ty); if (tag_enum == IP_INDEX_NONE) tag_enum = ipEnsureGeneratedUnionTag(ip, container_ty); uint32_t tag_val = ipEnumTagFromFieldName(ip, tag_enum, field_name); if (tag_val != IP_INDEX_NONE) { uint32_t tag_ptr_ty = ipGetPtrType(ip, tag_enum, IP_PTR_FLAGS_CONST_ONE); *out_value = ipGetPtrUav(ip, tag_ptr_ty, tag_val); *out_type = tag_ptr_ty; return true; } } if (!status) failWithBadMemberAccess(zcu, container_ty, field_name); uint32_t mem_ptr_ty = IP_INDEX_NONE; *out_value = navDeclRefAddressResolved(zcu, mnav, &mem_ptr_ty); *out_type = mem_ptr_ty; return true; } bool semaComptimeNamespaceFieldVal(Zcu* zcu, const Zir* access_zir, uint32_t container_ty, uint32_t field_name, uint32_t* out_value, uint32_t* out_type, uint32_t* out_nav, const char* no_namespace_msg) { InternPool* ip = &zcu->intern_pool; if (out_nav) *out_nav = IP_INDEX_NONE; // Sema.fieldVal's `.type` arm, `.error_set` child: `".error_set_type => // |err_set| if (err_set.nameIndex(ip, field_name) == null) { return // sema.fail(block, src, \"no error named '{f}' in '{f}'\", ...); } else // child_type"`, then `".err = .{ .ty = err_set_ty.toIntern(), .name = // field_name }"`. The `anyerror` leg, `".simple_type => |t| { // assert(t == .anyerror); _ = try pt.getErrorValue(field_name); }"`, // answers the error in `pt.singleErrorSetType(field_name)`. An inferred // set is `ensureFuncIesResolved` then `"continue :err_set // ip.indexToKey(resolved_ies)"`: the resolved set's legs, the error // still typed by the inferred set. uint32_t err_set_ty = container_ty; if (ipItemTagIs(ip, container_ty, IP_TAG_TYPE_INFERRED_ERROR_SET)) { container_ty = semaResolvedInferredErrorSet(ip, container_ty); if (container_ty == IP_INDEX_NONE) UNIMPLEMENTED("fieldVal: inferred error set not yet resolved"); } if (container_ty == ZIR_REF_ANYERROR_TYPE) { ipGetErrorValue(ip, field_name); uint32_t single = ipGetErrorSetType(ip, &field_name, 1); *out_value = ipGetErrorSetError(ip, single, field_name); if (out_type) *out_type = single; return true; } if (container_ty < ip->items_len && ip->item_tags[container_ty] == IP_TAG_TYPE_ERROR_SET) { if (!ipErrorSetHasName( ip, container_ty, field_name, IP_IES_NAME_ASSUMED)) COMPILE_ERROR_UPSTREAM("field access: no error named in the set"); *out_value = ipGetErrorSetError(ip, err_set_ty, field_name); if (out_type) *out_type = err_set_ty; return true; } if (ipIsEnumType(ip, container_ty)) { uint32_t ens = ipEnumNamespaceOrNone(ip, container_ty); if (ens != IP_NAMESPACE_NONE) { uint32_t enav = IP_INDEX_NONE; bool status = semaNamespaceLookupAccessible( zcu, access_zir, ens, field_name, &enav); if (status) { // namespaceLookupVal is // `"return try sema.analyzeNavVal(block, src, nav);"`, the // same analyzeNavVal a bare `decl_val` is -- so `S.a` reads a // function alias exactly as `a` does. *out_value = navAnalyzeVal(zcu, enav, out_type); if (out_nav) *out_nav = enav; return true; } } uint32_t etag = ipEnumTagFromFieldName(ip, container_ty, field_name); if (etag != IP_INDEX_NONE) { *out_value = etag; if (out_type) *out_type = container_ty; return true; } failWithBadMemberAccess(zcu, container_ty, field_name); } // `.@"struct", .@"opaque"` and `.@"union"`, packed or not; the enum arm // is above. if (!ztIsNamespaceType(ip, container_ty)) return false; bool is_union = ztZigTypeTag(ip, container_ty) == ZIG_TYPE_TAG_UNION; uint32_t member_ns = ipContainerNamespaceOrNone(ip, container_ty); uint32_t mnav = IP_INDEX_NONE; bool status = false; if (member_ns != IP_NAMESPACE_NONE) { status = semaNamespaceLookupAccessible( zcu, access_zir, member_ns, field_name, &mnav); } else if (!is_union) { UNIMPLEMENTED(no_namespace_msg); } if (status) { *out_value = navAnalyzeVal(zcu, mnav, out_type); if (out_nav) *out_nav = mnav; return true; } if (is_union && ipUnionTagUsage(ip, container_ty) == IP_UNION_TAG_USAGE_TAGGED) { semaResolveAllUnionDeferred(zcu, container_ty); uint32_t tag_enum = ipUnionEnumTagType(ip, container_ty); if (tag_enum == IP_INDEX_NONE) tag_enum = ipEnsureGeneratedUnionTag(ip, container_ty); uint32_t tag_val = ipEnumTagFromFieldName(ip, tag_enum, field_name); if (tag_val != IP_INDEX_NONE) { *out_value = tag_val; if (out_type) *out_type = tag_enum; return true; } } failWithBadMemberAccess(zcu, container_ty, field_name); ZIG1_TCC_RETURN(false); } // (part of) Sema.zig:resolveSwitchItem // Sema.resolveSwitchItem's `.enum_literal` arm: `"sema.analyzeDeclLiteral( // block, item_src, name, item_ty, false)"`, fieldVal on the item type (a // namespace decl, else an enum's tag), then the item's coercion to `item_ty`. uint32_t semaSwitchEnumLiteralItem( Zcu* zcu, const Zir* zir, uint32_t item_ty, uint32_t name) { InternPool* ip = &zcu->intern_pool; uint32_t val, ty; if (!semaComptimeNamespaceFieldVal(zcu, zir, item_ty, name, &val, &ty, NULL, "resolveSwitchItem: item type has no namespace")) UNIMPLEMENTED("resolveSwitchItem: switch item - decl literal on a " "type with no namespace"); return ty == item_ty ? val : semaCoerceComptime(zcu, ip, val, item_ty); } // (part of) Sema.zig:namespaceLookupVal // Sema.namespaceLookupVal is namespaceLookupRef followed by analyzeLoad. // Mutable Navs cannot fold through comptime_ptr_access: retain their stable // Nav address in AIR so a later populateTestFunctions replacement is visible // when the already-lowered consumer executes. static bool semaEmitMutableNavLoad(Zcu* zcu, uint32_t nav_idx, AirInstList* insts, U32List* block, uint32_t* out_value, uint32_t* out_type) { InternPool* ip = &zcu->intern_pool; const IpNav* nav = ipGetNav(ip, nav_idx); // An extern is loaded at runtime even when const: analyzeNavVal's // `"return sema.analyzeLoad(block, src, ref, src);"` cannot read an // `.@"extern"` value at comptime. An extern function is a value. uint32_t extern_ty = IP_INDEX_NONE; if (nav->is_extern) zptEnsureNavValUpToDate(zcu, nav_idx, &extern_ty); nav = ipGetNav(ip, nav_idx); bool extern_data = nav->is_extern && extern_ty < ip->items_len && ztZigTypeTag(ip, extern_ty) != ZIG_TYPE_TAG_FN; if (nav->is_const && !extern_data) return false; if (extern_data && nav->is_threadlocal) UNIMPLEMENTED("namespaceLookupVal: namespace field - threadlocal " "extern Nav load"); uint32_t nav_ty = IP_INDEX_NONE; zptEnsureNavValUpToDate(zcu, nav_idx, &nav_ty); // Resolving the value analyzes the decl, which can create navs and // realloc `ip->navs`; the pointer above is then dangling. nav = ipGetNav(ip, nav_idx); if (ztTypeClass(ip, nav_ty) == ZT_CLASS_OPV) { *out_value = ztOnePossibleValue(ip, nav_ty); if (*out_value == IP_INDEX_NONE) UNIMPLEMENTED("namespaceLookupVal: namespace field - mutable OPV " "has no canonical value"); *out_type = nav_ty; return true; } uint32_t ptr_ref; if (nav->is_threadlocal) { uint32_t ptr_ty = ipGetPtrType(ip, nav_ty, semaNavDirectPtrFlags(nav)); AirInstData ptr_data; memset(&ptr_data, 0, sizeof(ptr_data)); ptr_data.pair.a = ptr_ty; ptr_data.pair.b = nav_idx; uint32_t ptr_idx = semaAirInstAppend(insts, AIR_INST_RUNTIME_NAV_PTR, ptr_data); u32ListAppend(block, ptr_idx); ptr_ref = AIR_REF_INST_BASE + ptr_idx; } else { ptr_ref = navDeclRefResolved(zcu, nav_idx, NULL); } AirInstData load_data; memset(&load_data, 0, sizeof(load_data)); load_data.pair.a = nav_ty; load_data.pair.b = ptr_ref; uint32_t load_idx = semaAirInstAppend(insts, AIR_INST_LOAD, load_data); u32ListAppend(block, load_idx); *out_value = AIR_REF_INST_BASE + load_idx; *out_type = nav_ty; return true; } // Block.makeSubBlock, plus `runtime_index.increment()` when the sub-block // sits behind runtime control flow. semaBlockPop restores the parent. static void semaBlockPush(InlineCtx* ictx, SemaBlock* b, bool runtime, uint32_t label, uint32_t inline_block) { b->parent = ictx->blk; b->runtime_index = (ictx->blk != NULL ? ictx->blk->runtime_index : 0) + runtime; b->label = label; b->inline_block = inline_block; ictx->blk = b; } static void semaBlockPop(InlineCtx* ictx, const SemaBlock* b) { ictx->blk = b->parent; } // Enter a capturing prong of switch `inst`: the captures enclosing it are the // first `outer_len` entries, so re-entering from the same site is idempotent. static void semaSwitchCaptureEnter( InlineCtx* ictx, uint32_t outer_len, uint32_t inst, uint32_t merge_air) { if (outer_len >= SWITCH_CAPTURE_DEPTH_MAX) UNIMPLEMENTED("zirSwitchBlock: switch capture - nesting too deep"); ictx->caps[outer_len] = (SwitchCapture) { .switch_inst = inst, .merge_air = merge_air }; ictx->caps_len = outer_len + 1; } // The innermost enclosing capturing prong of switch `inst`, or NULL. static const SwitchCapture* semaSwitchCaptureFind( const InlineCtx* ictx, uint32_t inst) { for (uint32_t i = ictx->caps_len; i > 0; i--) if (ictx->caps[i - 1].switch_inst == inst) return &ictx->caps[i - 1]; return NULL; } // One entry of Sema.Block.Merges (src/Sema.zig). Upstream appends to // `label.merges.results` / `.br_list` at every `br` site (src/Sema.zig) // and `resolveAnalyzedBlock` then resolves one peer type over all of // `results` before coercing each `br` operand to it -- an // n-ary rule that a pairwise fold at the `br` site cannot state. // // zig1 has no `Block` struct to hang a `Merges` off: its "block" is a // `U32List` of AIR indices plus the merge block's own AIR index. So the // entries of every open block share one `InlineCtx`-owned array keyed by // `block_inst` (upstream's `merges.block_inst`), which also removes the // push/pop bookkeeping every early `return` in the four merge finalisers // would otherwise owe. AIR block indices are minted once and never reused // within one analysis unit, so the key is unique. // // A zig1 AIR `br` operand has lost the ZIR ref its type came from, so each // entry records the type the break site computed -- that is upstream's // `sema.typeOf(merges.results[i])`. `result_val` is upstream's // `peer_vals[i]`: the operand's comptime value, or IP_INDEX_NONE. // // `Sema.InferredAlloc.prongs` (src/Sema.zig) is the same structure for a // different owner: a list of placeholder `.store` instructions whose operands // zirResolveInferredAlloc peer-resolves in one go. It shares this // array, keyed by the inferred alloc's own AIR index -- AIR indices are minted // once and never reused within an analysis unit, so the two key spaces cannot // collide -- with `br_inst` holding the placeholder store. struct BlockMerge { uint32_t block_inst; uint32_t br_inst; uint32_t result_ty; uint32_t result_val; }; // (part of) Sema.zig:zirBreak // Sema.zirBreak's `label.merges.results.append(gpa, operand)` + // `label.merges.br_list.append(gpa, br_ref.toIndex().?)` (src/Sema.zig). static void semaBlockMergesAppend(InlineCtx* ictx, uint32_t block_inst, uint32_t br_inst, uint32_t result_ty, uint32_t result_val) { if (ictx->merges_len == ictx->merges_cap) { uint32_t cap = zig1GrowCap(ictx->merges_cap, 32u); ictx->merges = zig1Realloc(ictx->merges, (size_t)cap * sizeof(*ictx->merges)); ictx->merges_cap = cap; } struct BlockMerge* entry = &ictx->merges[ictx->merges_len++]; entry->block_inst = block_inst; entry->br_inst = br_inst; entry->result_ty = result_ty; entry->result_val = result_val; } // `merges.results.len` of `block_inst`. static uint32_t semaBlockMergesCount( const InlineCtx* ictx, uint32_t block_inst) { uint32_t n = 0; for (uint32_t i = 0; i < ictx->merges_len; i++) n += ictx->merges[i].block_inst == block_inst; return n; } // Sema.zig:analyzeRet // Sema.analyzeRet's `block.inlining` arm: `ret` is a break to the inline // call's result block, recording the merge. static void semaAnalyzeRetInlining(InlineCtx* ictx, AirInstList* insts, U32List* block, uint32_t operand, uint32_t ret_ty) { AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = ictx->inline_ret_block; data.pair.b = operand; uint32_t br_idx = semaAirInstAppend(insts, AIR_INST_BR, data); u32ListAppend(block, br_idx); semaBlockMergesAppend(ictx, ictx->inline_ret_block, br_idx, ret_ty, semaIsAirInstRef(operand) ? IP_INDEX_NONE : operand); } // Sema.Block.semaBlockIsComptime (`"fn semaBlockIsComptime(block: Block) bool // {"` / // `"return block.comptime_reason != null;"`): is this body being analyzed in a // comptime scope? zig1 carries only the bit upstream's optional // `comptime_reason` carries, not the reason payload; the raw field stays the // spelling at the save/restore sites, mirroring upstream's own // `"const old_comptime_reason = block.comptime_reason;"`. static bool semaBlockIsComptime(const InlineCtx* ictx) { return ictx->ct_scope; } // Start a new Sema's state (`Zcu.sema_branch`); returns the one to restore // with semaBranchLeave. ZcuSemaBranch* semaBranchEnter(Zcu* zcu, ZcuSemaBranch* fresh) { ZcuSemaBranch* saved = zcu->sema_branch; *fresh = (ZcuSemaBranch) { .quota = ZIG1_DEFAULT_BRANCH_QUOTA, .allow_memoize = true, .owner_func = IP_INDEX_NONE }; zcu->sema_branch = fresh; return saved; } // Sema.deinit: `"sema.comptime_allocs.deinit(gpa);"` and the arena. void semaBranchDeinit(ZcuSemaBranch* b) { free(b->comptime_allocs); for (uint32_t i = 0; i < b->arena_len; i++) free(b->arena[i]); free(b->arena); } void semaBranchLeave(Zcu* zcu, ZcuSemaBranch* saved) { semaBranchDeinit(zcu->sema_branch); zcu->sema_branch = saved; } void* semaArenaAlloc(Zcu* zcu, size_t size) { ZcuSemaBranch* b = zcu->sema_branch; if (b->arena_len == b->arena_cap) { b->arena_cap = zig1GrowCap(b->arena_cap, 16u); b->arena = zig1Realloc(b->arena, b->arena_cap * sizeof(void*)); } void* mem = zig1Malloc(size ? size : 1u); b->arena[b->arena_len++] = mem; return mem; } // Sema.zig:getComptimeAlloc ComptimeAlloc* getComptimeAlloc(Zcu* zcu, uint32_t idx) { ZcuSemaBranch* b = zcu->sema_branch; if (idx >= b->comptime_allocs_len) UNREACHABLE("getComptimeAlloc: not a comptime alloc of this Sema"); return &b->comptime_allocs[idx]; } // (part of) Sema.zig:emitBackwardBranch // Sema.emitBackwardBranch: `"sema.branch_count += 1; if (sema.branch_count > // sema.branch_quota) {"` -> "evaluation exceeded {d} backwards branches", // charged `n` at once for a memoized call (`"if (sema.branch_count + // memoized_call.branch_count > sema.branch_quota)"`). zig1 has no // compile-error channel here, so the overrun is a loud abort. void semaChargeBranches(Zcu* zcu, uint32_t n) { ZcuSemaBranch* b = zcu->sema_branch; if (n > b->quota || b->count > b->quota - n) UNIMPLEMENTED("emitBackwardBranch: evaluation exceeded the backwards " "branch quota"); b->count += n; } // Sema.zig:zirDisableInstrumentation // Only a function's Sema flags its owner (funcSetDisableInstrumentation) and // stops memoizing (`"sema.allow_memoize = false;"`). void zirDisableInstrumentation(Zcu* zcu) { uint32_t func = zcu->sema_branch->owner_func; if (func == IP_INDEX_NONE) return; InternPool* ip = &zcu->intern_pool; ip->extra[ip->item_datas[func]] |= IP_FUNC_ANALYSIS_DISABLE_INSTRUMENTATION; zcu->sema_branch->allow_memoize = false; } // (part of) Sema.zig:zirSetEvalBranchQuota // Sema.zirSetEvalBranchQuota: `"sema.branch_quota = @max(sema.branch_quota, // quota);"`. void semaSetEvalBranchQuota(Zcu* zcu, uint32_t quota) { if (quota > zcu->sema_branch->quota) zcu->sema_branch->quota = quota; if (quota > zcu->sema_branch->quota_request) zcu->sema_branch->quota_request = quota; } // `ct_lexical` (see the InlineCtx field comment): was this body reached // through a zig1 ZIR shape that mirrors an upstream AstGen-time // `gz.is_comptime = true` lexical scope (BLOCK_COMPTIME only), as opposed to // a Sema-time-only comptime force (a comptime-parameter call argument)? static bool semaIsLexicallyComptime(const InlineCtx* ictx) { return ictx->ct_lexical; } // (part of) Value.zig:toBigInt // `Value.intFitsInType` is `big.int.Const.fitsInTwosComp` over // `Value.toBigInt`: One question at both signs and every width -- the same // one `binComptimeIntFits` asks of an interned carrier. The callers hold the // value as a magnitude of up to 128 bits (`lo`, `hi`) and its sign, a one- or // two-limb `Const`, so none restates the bound per sign. static bool semaComptimeIntWordFits(const InternPool* ip, uint64_t lo, uint64_t hi, bool positive, uint32_t dest_ty) { uint64_t limbs[2] = { lo, hi }; BigIntConst v = { limbs, hi != 0 ? 2u : 1u, positive }; return valBigIntFitsInType(ip, v, dest_ty); } // ── Coercion ──────────────────────────────────────────────────────────────── // Signed fit check used when a comptime_int default has already been interned // as a negative i32/i64 carrier. static bool semaComptimeIntI64FitsType( const InternPool* ip, int64_t v, uint32_t dest_ty) { return v >= 0 ? semaComptimeIntWordFits(ip, (uint64_t)v, 0, true, dest_ty) : semaComptimeIntWordFits( ip, UINT64_C(0) - (uint64_t)v, 0, false, dest_ty); } // Coerce a comptime-int value `n` to a fixed-width int type, yielding the // corresponding preinterned sized value. Returns IP_INDEX_NONE if no // preinterned entry exists (the dynamic-InternPool crux). uint32_t semaCoerceComptimeIntLiteral( InternPool* ip, uint64_t n, uint32_t dest_ty) { // The preinterned values first: these indices are the pool's own identity // for those values and callers compare against them directly. Everything // else is `InternPool.get()`'s `.int` arm, which owns every rung and // every carrier choice -- this function restates none of them. switch (dest_ty) { case ZIR_REF_U8_TYPE: switch (n) { case 0: return ZIR_REF_ZERO_U8; case 1: return ZIR_REF_ONE_U8; case 4: return ZIR_REF_FOUR_U8; default: break; } break; case ZIR_REF_U1_TYPE: // `u1` has exactly two values and both are preinterned; anything // else is the caller's loud boundary, not a value to mint. switch (n) { case 0: return ZIR_REF_ZERO_U1; case 1: return ZIR_REF_ONE_U1; default: return IP_INDEX_NONE; } case ZIR_REF_USIZE_TYPE: switch (n) { case 0: return ZIR_REF_ZERO_USIZE; case 1: return ZIR_REF_ONE_USIZE; default: break; } break; case ZIR_REF_COMPTIME_INT_TYPE: switch (n) { case 0: return ZIR_REF_ZERO; case 1: return ZIR_REF_ONE; default: // A `comptime_int` has no width, so the literal is the // magnitude it already is -- there is no pattern to read. return ipGetIntComptimeIntU64(ip, n); } default: break; } return ipInternIntResult(ip, n, dest_ty); } // Sema.zig:coerce, PerThread.zig:getCoerced, PerThread.zig:intValue_big // `Sema.coerce`'s comptime_int -> int arm for a value only the limb carrier // can hold: `intFitsInType` is `big.int.Const.fitsInTwosComp` over the very // limbs the operand already stores, and the coercion itself is // `pt.getCoerced` -> `pt.intValue_big`, re-interning that same value at the // destination type. One rule for both signs. // IP_INDEX_NONE when the value does not fit, which is the caller's loud // boundary. static uint32_t semaCoerceComptimeIntBig( InternPool* ip, uint32_t operand, uint32_t dest_ty) { uint32_t op_ty, limbs_len; const uint64_t* op_limbs; bool op_positive; if (!ipIntBigLimbs( ip, operand, &op_ty, &op_limbs, &limbs_len, &op_positive) || op_ty != ZIR_REF_COMPTIME_INT_TYPE) return IP_INDEX_NONE; if (dest_ty == ZIR_REF_COMPTIME_INT_TYPE) return operand; BigIntConst v = { op_limbs, limbs_len, op_positive }; if (!valBigIntFitsInType(ip, v, dest_ty)) return IP_INDEX_NONE; return ipGetInt(ip, dest_ty, v); } uint32_t semaCoerceComptimeIntI64Literal( InternPool* ip, int64_t v, uint32_t dest_ty) { if (!semaComptimeIntI64FitsType(ip, v, dest_ty)) return IP_INDEX_NONE; if (v >= 0) return semaCoerceComptimeIntLiteral(ip, (uint64_t)v, dest_ty); // `BigIntMutable.init(&buf, x)` for a signed `x`: sign in the flag, the // magnitude in the limb (`~w + 1` rather than `-v`, which is UB at // INT64_MIN). `ipGetInt` picks the carrier at every type. uint64_t mag = ~(uint64_t)v + 1u; return ipGetInt(ip, dest_ty, (BigIntConst) { &mag, 1, false }); } // A `comptime_int` fold result, at the carrier the rest of the pool already // uses: `semaCoerceComptimeIntI64Literal`'s comptime_int arm above is that // ladder (i32 -> i64 for a negative, zero/one -> u32 -> u64 for a magnitude), // so no fold arm in either frame restates it. `negative` is the VALUE's sign, // which the word alone cannot answer: an unbounded positive result may have // bit 63 set (`maxInt(u64) | x`), and only that one rung is above an int64. uint32_t semaInternComptimeIntWord(InternPool* ip, uint64_t w, bool negative) { if (!negative && w > (uint64_t)INT64_MAX) return ipGetIntComptimeIntU64(ip, w); return semaCoerceComptimeIntI64Literal( ip, (int64_t)w, ZIR_REF_COMPTIME_INT_TYPE); } // Coerce a preinterned comptime_int value to a fixed-width int type, yielding // the corresponding interned sized value. Returns IP_INDEX_NONE for other // values. static uint32_t semaCoercePreinternedComptimeInt( InternPool* ip, uint32_t val, uint32_t dest_ty) { if (val == ZIR_REF_ZERO) return semaCoerceComptimeIntLiteral(ip, 0, dest_ty); if (val == ZIR_REF_ONE) return semaCoerceComptimeIntLiteral(ip, 1, dest_ty); if (val == ZIR_REF_NEGATIVE_ONE) return semaCoerceComptimeIntI64Literal(ip, -1, dest_ty); return IP_INDEX_NONE; } static bool checkPtrAttributes(Zcu* zcu, InternPool* ip, const IpPtrInfo* dest_info_p, const IpPtrInfo* src_info_p); static bool semaSrcArrayPtrCoercible(Zcu* zcu, InternPool* ip, uint32_t dest_ty, uint32_t src_ty, IpPtrInfo* out_dest, IpPtrInfo* out_src); // Sema.zig:coerceExtra, Sema.zig:coerceArrayPtrToSlice // Sema.coerceExtra, restricted to the static-preintern range. Coerces an // operand (a preinterned InternPool value index) to `dest_ty` (a preinterned // type index), returning the resulting preinterned value index. Aborts on // any case that would require the dynamic InternPool. // Coerce a comptime `*const [N]T` pointer value to a `[]const T` slice value // (Sema.coerceArrayPtrToSlice / arrayInitSlice). `ptr_val` may be any interned // pointer value whose type is a one-item pointer to an array; the resulting // many-pointer preserves the same base-address carrier (`ptr_uav`, `ptr_int`, // etc.) via `ipRetypePtrValue`. uint32_t coerceArrayPtrToSlice( InternPool* ip, uint32_t ptr_val, uint32_t slice_ty) { if (ptr_val >= ip->items_len) UNIMPLEMENTED("coerceArrayPtrToSlice: non-pointer value"); uint32_t ptr_ty = ipTypeOf(ip, ptr_val); // *const [N]T if (ptr_ty >= ip->items_len || ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) UNREACHABLE("coerceArrayPtrToSlice: malformed pointer type"); uint32_t arr_len, arr_child, arr_sentinel; IpPtrInfo ptr_info; if (ipPtrInfo(ip, ptr_ty, &ptr_info) != IP_PTRI_OK) UNREACHABLE("coerceArrayPtrToSlice: malformed pointer type"); if (!ztArrayTypeInfo( ip, ptr_info.child, &arr_len, &arr_child, &arr_sentinel)) UNIMPLEMENTED("coerceArrayPtrToSlice: pointer child not array"); // type_slice.data == the many-pointer type Index. if (!ztIsSlice(ip, slice_ty)) UNIMPLEMENTED("coerceArrayPtrToSlice: destination not slice"); uint32_t many_ptr_ty = ip->item_datas[slice_ty]; IpPtrInfo many_info; if (ipPtrInfo(ip, many_ptr_ty, &many_info) != IP_PTRI_OK) UNREACHABLE("coerceArrayPtrToSlice: malformed slice pointer type"); if (many_info.size != IP_PTR_SIZE_MANY || many_info.packed_offset != 0) UNIMPLEMENTED("coerceArrayPtrToSlice: destination not many pointer"); uint32_t dest_sentinel = many_info.sentinel; // The destination is the slice type, not its `[*]T` carrier: upstream's // `len0` clause reads `dest_info.flags.size`, and `.slice` is what admits // the empty-array const drop (`""` initializing a mutable `[]u8`) that a // `.many` destination does not get. if (!semaCheckPtrAttributesTy(NULL, ip, slice_ty, ptr_ty)) UNIMPLEMENTED("coerceArrayPtrToSlice: pointer attributes mismatch"); if (many_info.child != arr_child) UNIMPLEMENTED("coerceArrayPtrToSlice: child mismatch"); if (dest_sentinel != IP_INDEX_NONE && dest_sentinel != arr_sentinel) UNIMPLEMENTED("coerceArrayPtrToSlice: sentinel mismatch"); uint32_t many_ptr = ipRetypePtrValue(ip, ptr_val, many_ptr_ty); if (many_ptr == IP_INDEX_NONE) UNIMPLEMENTED("coerceArrayPtrToSlice: pointer remint failed"); uint32_t len_val = ipGetIntUsize(ip, arr_len); return ipGetPtrSlice(ip, slice_ty, many_ptr, len_val); } static void tupleFieldValByIndex(InternPool* ip, uint32_t agg, uint32_t index, uint32_t* out_val, uint32_t* out_ty); static uint32_t semaCoerceComptimeTuplePtrToSlice(Zcu* zcu, InternPool* ip, uint32_t operand, uint32_t operand_ty, uint32_t dest_ty) { if (operand >= ip->items_len || operand_ty >= ip->items_len || dest_ty >= ip->items_len || !ipIsPtrUav(ip, operand) || ip->item_tags[operand_ty] != IP_TAG_TYPE_POINTER || !ztIsSlice(ip, dest_ty)) return IP_INDEX_NONE; uint32_t pod = ip->item_datas[operand_ty]; if (pod > ip->extra_len || ip->extra_len - pod < IP_TYPE_POINTER_LEN) return IP_INDEX_NONE; uint32_t pflags = ip->extra[pod + IP_TYPE_POINTER_FLAGS]; if (IP_PTR_FLAGS_SIZE(pflags) != IP_PTR_SIZE_ONE || ip->extra[pod + IP_TYPE_POINTER_PACKED_OFFSET] != 0) return IP_INDEX_NONE; uint32_t tuple_ty = ip->extra[pod + IP_TYPE_POINTER_CHILD]; if (!ztIsTuple(ip, tuple_ty)) return IP_INDEX_NONE; uint32_t many_ty = ip->item_datas[dest_ty]; if (many_ty >= ip->items_len || ip->item_tags[many_ty] != IP_TAG_TYPE_POINTER) return IP_INDEX_NONE; uint32_t md = ip->item_datas[many_ty]; if (md > ip->extra_len || ip->extra_len - md < IP_TYPE_POINTER_LEN) return IP_INDEX_NONE; uint32_t elem_ty = ip->extra[md + IP_TYPE_POINTER_CHILD]; uint32_t sentinel = ip->extra[md + IP_TYPE_POINTER_SENTINEL]; uint32_t mflags = ip->extra[md + IP_TYPE_POINTER_FLAGS]; if (IP_PTR_FLAGS_SIZE(mflags) != IP_PTR_SIZE_MANY || ip->extra[md + IP_TYPE_POINTER_PACKED_OFFSET] != 0) return IP_INDEX_NONE; uint32_t nfields = ipTupleFieldCount(ip, tuple_ty); if (nfields == 0) { uint32_t array_ty = ipGetArrayType(ip, 0, elem_ty, sentinel); // "try sema.ensureLayoutResolved(empty_array_ty, inst_src, .init);" if (zcu != NULL && !ensureLayoutResolved(zcu, array_ty)) UNIMPLEMENTED("coerceTupleToSlicePtrs: tuple_ptr_to_slice - empty " "array layout"); uint32_t array_val = ipGetAggregate(ip, array_ty, NULL, 0); uint32_t array_ptr_ty = ipGetPtrType(ip, array_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t array_ptr = ipGetPtrUav(ip, array_ptr_ty, array_val); return coerceArrayPtrToSlice(ip, array_ptr, dest_ty); } if (IP_PTR_FLAGS_ALIGN(mflags) != IP_PTR_ALIGN_NONE) UNIMPLEMENTED("coerceTupleToSlicePtrs: tuple_ptr_to_slice - " "explicitly aligned slice"); if ((mflags & IP_PTR_FLAGS_IS_CONST) == 0u) COMPILE_ERROR_UPSTREAM( "tuple_ptr_to_slice: mutable slice destination"); if (sentinel != IP_INDEX_NONE) UNIMPLEMENTED("coerceTupleToSlicePtrs: tuple_ptr_to_slice - " "sentineled slice destination"); // coerceTupleToSlicePtrs: analyzeLoad, then coerceTupleToArray's // tupleField per element. if (zcu == NULL) UNIMPLEMENTED("coerceTupleToSlicePtrs: tuple_ptr_to_slice - no Zcu to " "load the tuple"); uint32_t backing = pointerDeref(zcu, operand, NULL); uint32_t* elems = ARR_INIT(uint32_t, nfields); for (uint32_t i = 0; i < nfields; i++) { uint32_t field_ty; uint32_t field_val; tupleFieldValByIndex(ip, backing, i, &field_val, &field_ty); elems[i] = semaCoerceComptime(NULL, ip, field_val, elem_ty); } uint32_t array_ty = ipGetArrayType(ip, nfields, elem_ty, sentinel); uint32_t array_val = ipGetAggregate(ip, array_ty, elems, nfields); free(elems); uint32_t array_ptr_ty = ipGetPtrType(ip, array_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t array_ptr = ipGetPtrUav(ip, array_ptr_ty, array_val); return coerceArrayPtrToSlice(ip, array_ptr, dest_ty); } // Sema.zig:maybeDerefSliceAsArray // Sema.maybeDerefSliceAsArray: a one-pointer is pointerDeref'd; a slice is // its sliceToArrayPtr pointerDeref'd. IP_INDEX_NONE when the contents are // runtime. uint32_t maybeDerefSliceAsArray(Zcu* zcu, uint32_t slice_val) { InternPool* ip = &zcu->intern_pool; uint32_t slice_ty = ipTypeOf(ip, slice_val); if (!ztIsSlice(ip, slice_ty)) return pointerDeref(zcu, slice_val, NULL); if (ipIsUndef(ip, slice_val)) COMPILE_ERROR_UPSTREAM("use of undefined value"); return pointerDeref(zcu, zptSliceToArrayPtr(ip, slice_val), NULL); } // Sema.zig:sliceToIpString, Value.zig:toIpString // Sema.sliceToIpString for a comptime `[]const u8` slice: interns the compact // `[len]u8` array, pointer and elements maybeDerefSliceAsArray and // Value.toIpString do, in their order, and returns the field-name String. static uint32_t semaSliceToIpStringC(InternPool* ip, uint32_t slice_val) { // ptr_slice { ty, many_ptr, len }. uint32_t sd = ip->item_datas[slice_val]; uint32_t slice_ty = ip->extra[sd]; uint32_t many_ptr = ip->extra[sd + 1]; // `val` sits at extra+1 under both uav tags; a plain ptr_uav's orig_ty is // its own ty (InternPool's `.ptr_uav` arm). if (!ipIsPtrUav(ip, many_ptr)) UNIMPLEMENTED("sliceToIpString: many-ptr not a uav"); uint32_t mpd = ip->item_datas[many_ptr]; uint32_t orig_agg = ip->extra[mpd + 1]; uint32_t orig_ptr_ty = (ip->item_tags[many_ptr] == IP_TAG_PTR_UAV_ALIGNED) ? ip->extra[mpd + 2] : ip->extra[mpd]; // childType([]const u8): slice type data = many-ptr type; its child = u8. uint32_t slice_many_ty = ip->item_datas[slice_ty]; uint32_t elem_ty = ipPtrChild(ip, slice_many_ty); // The bytes and the slice's own length (maybeDerefSliceAsArray's // `slice.len`). char tmp[256]; uint32_t len; uint32_t fname = ctFieldNamedNameString(ip, slice_val, tmp, sizeof(tmp), &len); // derefSliceAsArray: build `[len]u8` (no sentinel), `*const [len]u8`, and // the coerced uav pointer keeping the original aggregate + pointer type. uint32_t array_ty = ipGetArrayType(ip, len, elem_ty, IP_INDEX_NONE); uint32_t ptr_ty = ipGetPtrType(ip, array_ty, IP_PTR_FLAGS_CONST_ONE); // getCoerced(many_ptr, ptr_ty): same uav base, ty rebound to // *const[len]u8, the incoming uav's orig kept. Upstream's tag choice is // `ptrsHaveSameAlignment(ptr_ty, orig_ptr_ty)` (InternPool.zig, anchored // `"fn ptrsHaveSameAlignment"`): false for both the faithful name shape // (orig a many-pointer type -- child u8 vs the deref's [len]u8 child) // and the intermediate array-pointer shape (child [len:0]u8 vs [len]u8), // alignment `.none` on both sides -> ptr_uav_aligned either way. if (orig_ptr_ty >= ip->items_len || ip->item_tags[orig_ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("sliceToIpString: uav origin not a pointer type"); ipGetPtrUavAligned(ip, ptr_ty, orig_agg, orig_ptr_ty); // pointerDeref element reconstruction: intern each byte's int_u8 value. for (uint32_t i = 0; i < len; i++) ipGetIntU8(ip, (uint8_t)tmp[i]); // The compact `[len]u8` aggregate value. ipInternBytesAggregate(ip, array_ty, fname, len); return fname; } // Sema.zig:resolveConstStringIntern // Sema.resolveConstStringIntern on a comptime value: "coerce(block, // wanted_type, air_inst, src)" to `[]const u8`, then sliceToIpString. uint32_t resolveConstStringIntern(Zcu* zcu, uint32_t val) { InternPool* ip = &zcu->intern_pool; return semaSliceToIpStringC( ip, semaCoerceComptime(zcu, ip, val, ZIR_REF_SLICE_CONST_U8_TYPE)); } // (part of) Sema.zig:analyzeErrUnionCode // Sema.analyzeErrUnionCode on a comptime error union `eu`: `.err = .{ .ty = // operand_ty.errorUnionSet(), .name = error_union.val.err_name }`. An // error_set_error is the value itself (comptime calls fold `return error.X` // to it); a payload aborts loudly (only an error branch reaches it). uint32_t semaErrUnionCodeComptime(InternPool* ip, uint32_t eu) { if (ipItemTagIs(ip, eu, IP_TAG_ERROR_UNION_ERROR)) { return ipGetErrorSetError(ip, ipErrorUnionErrorSet(ip, ipTypeOf(ip, eu)), ipErrorName(ip, eu)); } if (ipItemTagIs(ip, eu, IP_TAG_ERROR_SET_ERROR)) return eu; UNIMPLEMENTED("err_union_code: comptime operand holds no error"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // (part of) Sema.zig:zirBuiltinExtern, InternPool.zig:getExtern // Sema.zirBuiltinExtern: `@extern(*T, .{ .name = ..., ... })`. // resolveExternOptions reads std.lang.ExternOptions; the extern // (InternPool.getExtern, source .builtin) gets its owner Nav, and the // result is `analyzeNavRef(owner_nav)` coerced to `ptr_ty` (optional for a // weak symbol). `zir_index` is the owner Nav's declaration, as upstream's // `.func` arm. static uint32_t semaBuiltinExtern( Zcu* zcu, uint32_t ptr_ty, uint32_t options) { InternPool* ip = &zcu->intern_pool; // `ptr_ty.isPtrAtRuntime()`: an optional pointer too; `ptrInfo` reads // through it. uint32_t result_ty = ptr_ty; if (ztIsZigTypeTag(ip, ptr_ty, ZIG_TYPE_TAG_OPTIONAL)) ptr_ty = ipOptionalChild(ip, ptr_ty); IpPtrInfo pi; if (ipPtrInfo(ip, ptr_ty, &pi) != IP_PTRI_OK) COMPILE_ERROR_UPSTREAM("@extern: expected (optional) pointer"); // "try sema.ensureLayoutResolved(elem_ty, src, .@"extern");" if (!ensureLayoutResolved(zcu, pi.child)) UNIMPLEMENTED("@extern: element layout unresolved"); if (ztIsZigTypeTag(ip, pi.child, ZIG_TYPE_TAG_FN) && !pi.is_const) COMPILE_ERROR_UPSTREAM("@extern: pointer to extern function must be " "'const'"); static const char* const eo_path[] = { "ExternOptions" }; uint32_t eo_ty = getStdLangType(zcu, eo_path, 1); uint32_t eo = semaCoerceComptime(zcu, ip, options, eo_ty); if (!ipItemTagIs(ip, eo, IP_TAG_AGGREGATE)) UNIMPLEMENTED("@extern: options not a comptime struct"); #define EO_FIELD(nm) \ valElemValue(ip, eo, \ ipStructFieldIndex( \ ip, eo_ty, ipGetOrPutString(ip, nm, (uint32_t)strlen(nm))), \ "@extern: options field") uint32_t name = semaSliceToIpStringC(ip, EO_FIELD("name")); uint32_t lib_val = EO_FIELD("library_name"); uint32_t lib_name = IP_INDEX_NONE; if (ipItemTagIs(ip, lib_val, IP_TAG_OPT_PAYLOAD)) { lib_name = semaSliceToIpStringC(ip, ipPayloadVal(ip, lib_val)); if (ipStringBytes(ip, lib_name)[0] == '\0') COMPILE_ERROR_UPSTREAM("@extern: library name cannot be empty"); } // interpretStdLangType of the auto-numbered enums: the field index. uint32_t linkage = ipEnumTagFieldIndex(ip, EO_FIELD("linkage")); uint32_t visibility = ipEnumTagFieldIndex(ip, EO_FIELD("visibility")); uint32_t relocation = ipEnumTagFieldIndex(ip, EO_FIELD("relocation")); if (linkage > GLOBAL_LINKAGE_WEAK || visibility > SYMBOL_VISIBILITY_PROTECTED || relocation > EXTERN_RELOCATION_PCREL) UNIMPLEMENTED("@extern: options enum not readable"); bool is_thread_local = EO_FIELD("is_thread_local") == ZIR_REF_BOOL_TRUE; bool is_dll_import = EO_FIELD("is_dll_import") == ZIR_REF_BOOL_TRUE; uint32_t decoration = EO_FIELD("decoration"); #undef EO_FIELD if (!ipItemTagIs(ip, decoration, IP_TAG_OPT_NULL) && decoration != ZIR_REF_NULL_VALUE) UNIMPLEMENTED("@extern: decoration"); if (is_thread_local) UNIMPLEMENTED("@extern: thread-local extern symbol"); // GlobalLinkage is `strong, weak`, both legal for an extern; the // relocation rule is zirExtern's "cannot require a pc-relative // relocation to a symbol with default visibility". if (relocation == EXTERN_RELOCATION_PCREL && visibility == SYMBOL_VISIBILITY_DEFAULT) COMPILE_ERROR_UPSTREAM("@extern: cannot require a pc-relative " "relocation to a symbol with default " "visibility"); // Tag.Extern.Flags: source .builtin (0), decoration_type .none (0). uint32_t flags = (linkage << IP_EXTERN_FLAGS_LINKAGE_SHIFT) | (visibility << IP_EXTERN_FLAGS_VISIBILITY_SHIFT) | (is_dll_import ? IP_EXTERN_FLAGS_IS_DLL_IMPORT : 0u) | (relocation << IP_EXTERN_FLAGS_RELOCATION_SHIFT); if (zcu->sema_owner_nav == IP_INDEX_NONE) UNIMPLEMENTED("@extern: analysis owner unknown"); uint32_t ext = ipGetExtern(ip, name, pi.child, lib_name, flags, ipGetNav(ip, zcu->sema_owner_nav)->analysis_zir_index, pi.is_const, pi.alignment, IP_INDEX_NONE); uint32_t nav = ipExternFunctionOwnerNav(ip, ext); // analyzeNavRef: `*const T` (the Nav's const / alignment), then // getCoerced to the requested pointer type. uint32_t nav_ptr_ty = ipGetPtrType(ip, pi.child, semaNavDirectPtrFlags(ipGetNav(ip, nav))); uint32_t ptr = ipRetypePtrValue(ip, ipGetPtrNav(ip, nav_ptr_ty, nav), ptr_ty); // "For a weak symbol where the given type is not nullable, make the // pointer optional." An optional `ptr_ty` already allows zero. if (linkage == GLOBAL_LINKAGE_WEAK && result_ty == ptr_ty && !(pi.flags & IP_PTR_FLAGS_IS_ALLOWZERO)) result_ty = ipGetOptionalType(ip, ptr_ty); return result_ty != ptr_ty ? ipGetOptPayload(ip, result_ty, ptr) : ptr; } // (part of) Sema.zig:maybeQueueFuncBodyAnalysis // Sema.maybeQueueFuncBodyAnalysis: `"if (!nav_ty.fnHasRuntimeBits(zcu)) // return;"` -- a generic or comptime-only function has no body of its own to // analyze, only its instantiations do. void semaQueueFnPtrPending( InternPool* ip, uint32_t func_ty, uint32_t owner_nav) { if (ip->fnptr_pending == NULL || !ztFnHasRuntimeBits(ip, func_ty)) return; // `"if (!ip.isFuncBody(nav_val.toIntern())) return;"`: an extern has no // body to analyze. const IpNav* nav = ipGetNav(ip, owner_nav); if (nav->has_value_resolved && nav->resolved_value < ip->items_len && ip->item_tags[nav->resolved_value] == IP_TAG_EXTERN) return; semaU32ListAppendUnique(ip->fnptr_pending, owner_nav); } // Sema.zig:validateArrayInitTy // Sema.validateArrayInitTy (src/Sema.zig): the element count rule for // an array-init result type, transcribed whole. Upstream runs it from the // `validate_array_init_ty` / `..._result_ty` / `..._ref_ty` instructions; // zig1 runs it at the init arms themselves, which is the same rule at the // point of use and covers every producer of the result type. Aborting is // mandatory, not defensive: `ipGetAggregate` takes its element count from // the type and indexes the caller's `elems[]` to it, so a short operand list // reads out of bounds. void validateArrayInitTy( const InternPool* ip, uint32_t init_count, uint32_t ty) { IpArrayInfo info; if (ipArrayInfo(ip, ty, &info) == IP_ARR_OK) { // `.array` and `.vector` differ only in the message upstream emits. if (init_count != info.len) COMPILE_ERROR_UPSTREAM( "array_init: element count does not match the array length"); return; } if (ztIsTuple(ip, ty)) { // "expected at most {d} tuple fields; found {d}" -- upstream allows // fewer (the remaining fields take their defaults). if (init_count > ipTupleFieldCount(ip, ty)) COMPILE_ERROR_UPSTREAM( "array_init: more elements than the tuple has fields"); return; } // `failWithArrayInitNotSupported`: each caller keeps its own abort for // a result type that is neither, since it also names the site. } bool semaPtrValueFieldVal(InternPool* ip, uint32_t value, uint32_t value_ty, const char* field, uint32_t* out_value, uint32_t* out_type, const char* slice_field_msg, const char* array_ptr_msg, const char* array_field_msg) { // Sema.fieldVal's `.pointer` prong for a slice: anchor // `"if (ptr_info.flags.size == .slice) {"`, with `.ptr` -> // `analyzeSlicePtr` and `.len` -> `analyzeSliceLen`. A comptime slice is // Tag.PtrSlice { ty, ptr, len }, so both are stored words; the `.ptr` // result type is the slice type's own many-pointer child. Keyed on the // VALUE's tag, not on `value_ty`. if (ip->item_tags[value] == IP_TAG_PTR_SLICE) { uint32_t sd = ip->item_datas[value]; if (strcmp(field, "len") == 0) { *out_value = ip->extra[sd + 2]; *out_type = ZIR_REF_USIZE_TYPE; return true; } if (strcmp(field, "ptr") == 0) { *out_value = ip->extra[sd + 1]; *out_type = ip->item_datas[ip->extra[sd]]; return true; } COMPILE_ERROR_UPSTREAM(slice_field_msg); } // Sema.fieldVal's `.array` prong reached through `is_pointer_to` (anchor // `"Zig allows dereferencing a single pointer during field lookup"`): the // length comes from the type, so `.len` folds with no load of the pointee // (anchor `"pt.intValue(.usize, inner_ty.arrayLen(zcu))"`) and the // sentinel is excluded. `.ptr` (anchor `"return sema.coerce(block, // result_ty, object, src);"`) rebuilds a `.many` pointer type; a comptime // pointer coerces to it by `getCoerced`. if (value_ty != IP_INDEX_NONE && value_ty < ip->items_len && ip->item_tags[value_ty] == IP_TAG_TYPE_POINTER && IP_PTR_FLAGS_SIZE(ip->extra[ip->item_datas[value_ty] + 2]) == IP_PTR_SIZE_ONE) { uint32_t child = ip->extra[ip->item_datas[value_ty]]; uint32_t alen, aelem, asentinel; if (ztArrayTypeInfo(ip, child, &alen, &aelem, &asentinel)) { if (strcmp(field, "ptr") == 0) { uint32_t src_flags = ip->extra[ip->item_datas[value_ty] + 2]; uint32_t many_ty = ipGetPtrTypeSentinel( ip, aelem, (src_flags & ~3u) | 1u, asentinel); bool is_uav = ipIsPtrUav(ip, value); uint32_t many_val = is_uav ? semaCoerceArrayUavPtrToMany(ip, value, value_ty, many_ty) : ipRetypePtrValue(ip, value, many_ty); if (many_val == IP_INDEX_NONE) UNIMPLEMENTED(array_ptr_msg); *out_value = many_val; *out_type = many_ty; return true; } if (strcmp(field, "len") != 0) COMPILE_ERROR_UPSTREAM(array_field_msg); *out_value = ipGetIntUsize(ip, alen); *out_type = ZIR_REF_USIZE_TYPE; return true; } } return false; } static uint32_t semaAggregateElemValueWithAbort(InternPool* ip, uint32_t aggregate, uint32_t index, const char* ctx, bool unported) { // Value.elemValue is ipAggregateElemValue; this adds only the abort // flavour zig1 owes an index upstream would have asserted on. uint32_t elem = IP_INDEX_NONE; switch (ipAggregateElemValue(ip, aggregate, index, &elem)) { case IP_AGG_ELEM_STORED: case IP_AGG_ELEM_SENTINEL: return elem; case IP_AGG_ELEM_OUT_OF_RANGE: case IP_AGG_ELEM_UNSUPPORTED_STORAGE: if (unported) UNIMPLEMENTED(ctx); break; } UNIMPLEMENTED(ctx); ZIG1_TCC_RETURN(IP_INDEX_NONE); } uint32_t semaAggregateElemValueOrUndef(InternPool* ip, uint32_t aggregate, uint32_t elem_ty, uint32_t index, const char* ctx) { if (ipIsUndef(ip, aggregate)) return ipGetUndef(ip, elem_ty); return valElemValue(ip, aggregate, index, ctx); } // (part of) Sema.zig:zirSelect, PerThread.zig:undefRef // `Sema.zirSelect`'s all-comptime fold: an undef predicate or operand is // `pt.undefRef(vec_ty)`, otherwise lane `i` is `a[i]` where `pred[i]` and // `b[i]` elsewhere, re-aggregated at `vec_ty` (`"const should_choose_a = // pred_elem_val.toBool(); elem.* = (try (if (should_choose_a) a_val else // b_val).elemValue(pt, i)).toIntern();"`). The three values are already // coerced to `@Vector(len, bool)` / `vec_ty`. static uint32_t semaSelectValue(InternPool* ip, uint32_t vec_ty, uint32_t len, uint32_t pred, uint32_t a, uint32_t b) { if (ipIsUndef(ip, pred) || ipIsUndef(ip, a) || ipIsUndef(ip, b)) return ipGetUndef(ip, vec_ty); uint32_t* elems = zig1Malloc((len ? (size_t)len : 1u) * sizeof(uint32_t)); for (uint32_t i = 0; i < len; i++) { uint32_t p = valElemValue(ip, pred, i, "@select: predicate"); if (p != ZIR_REF_BOOL_TRUE && p != ZIR_REF_BOOL_FALSE) UNIMPLEMENTED("@select: predicate lane not a bool"); elems[i] = valElemValue( ip, p == ZIR_REF_BOOL_TRUE ? a : b, i, "@select: operand"); } uint32_t res = ipGetAggregate(ip, vec_ty, elems, len); free(elems); return res; } // (part of) Sema.zig:tupleFieldValByIndex // Sema.tupleFieldValByIndex first asks // `tuple_ty.structFieldValueComptime(pt, field_index)`, then // `field_ty.onePossibleValue(pt)`. Keep those two type-derived answers shared; // callers which get false must either read a known aggregate value or emit the // runtime `struct_field_val` path, just like upstream. bool semaTupleFieldValueComptime(InternPool* ip, uint32_t tuple_ty, uint32_t index, uint32_t* out_ty, uint32_t* out_value) { uint32_t field_ty = ipTupleFieldType(ip, tuple_ty, index); uint32_t field_val = ipTupleFieldValue(ip, tuple_ty, index); if (field_val == IP_INDEX_NONE) field_val = ztOnePossibleValue(ip, field_ty); *out_ty = field_ty; *out_value = field_val; return field_val != IP_INDEX_NONE; } // Sema.zig:tupleFieldIndex // Sema.tupleFieldIndex: `NullTerminatedString.toUnsigned` bounded by the // tuple's field count; `len` is the caller's. uint32_t tupleFieldIndex( const InternPool* ip, uint32_t tuple_ty, const char* name) { uint32_t index; if (!ipBytesToUnsigned(name, (uint32_t)strlen(name), &index)) COMPILE_ERROR_UPSTREAM("tupleFieldIndex: no field named"); if (index >= ipTupleFieldCount(ip, tuple_ty)) COMPILE_ERROR_UPSTREAM("tupleFieldIndex: index out of bounds"); return index; } // Sema.zig:tupleFieldValByIndex // Sema.tupleFieldValByIndex on a comptime tuple value: a comptime field's // value, else an undefined tuple's undefined field, else the aggregate's // element. static void tupleFieldValByIndex(InternPool* ip, uint32_t agg, uint32_t index, uint32_t* out_val, uint32_t* out_ty) { if (semaTupleFieldValueComptime( ip, ipTypeOf(ip, agg), index, out_ty, out_val)) return; *out_val = ipIsUndef(ip, agg) ? ipGetUndef(ip, *out_ty) : valElemValue(ip, agg, index, "fieldVal: tuple"); } // (part of) Sema.zig:analyzeTupleCat // Sema.analyzeTupleCat's result type: lhs then rhs field types, each field's // `structFieldDefaultValue` its value (`.none` for a runtime field). // `*all_comptime` is whether every field value is known, which is // analyzeTupleCat's interned-aggregate answer. static uint32_t semaTupleCatType( InternPool* ip, uint32_t lhs_ty, uint32_t rhs_ty, bool* all_comptime) { uint32_t lhs_len = ipTupleFieldCount(ip, lhs_ty); uint32_t n = lhs_len + ipTupleFieldCount(ip, rhs_ty); uint32_t* types = zig1Malloc(2u * (n ? n : 1u) * sizeof(uint32_t)); uint32_t* values = types + n; *all_comptime = true; for (uint32_t i = 0; i < n; i++) { uint32_t ty = i < lhs_len ? lhs_ty : rhs_ty; uint32_t fi = i < lhs_len ? i : i - lhs_len; types[i] = ipTupleFieldType(ip, ty, fi); values[i] = ipTupleFieldValue(ip, ty, fi); *all_comptime &= values[i] != IP_INDEX_NONE; } uint32_t tuple_ty = ipGetTupleType(ip, n, types, values); free(types); return tuple_ty; } // (part of) Sema.zig:analyzeTupleCat // Sema.analyzeTupleCat over two comptime-known tuples: `"if (lhs_len == 0) // return rhs; if (rhs_len == 0) return lhs;"`, then the concatenated // tuple's value, field by field through tupleFieldValByIndex. uint32_t semaTupleCatComptime(InternPool* ip, uint32_t lhs, uint32_t rhs) { uint32_t lhs_ty = ipTypeOf(ip, lhs), rhs_ty = ipTypeOf(ip, rhs); uint32_t lhs_len = ipTupleFieldCount(ip, lhs_ty); if (lhs_len == 0) return rhs; if (ipTupleFieldCount(ip, rhs_ty) == 0) return lhs; bool all_comptime; uint32_t tuple_ty = semaTupleCatType(ip, lhs_ty, rhs_ty, &all_comptime); uint32_t n = ipTupleFieldCount(ip, tuple_ty); uint32_t* elems = zig1Malloc(n * sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { uint32_t ty; tupleFieldValByIndex(ip, i < lhs_len ? lhs : rhs, i < lhs_len ? i : i - lhs_len, &elems[i], &ty); } uint32_t res = zptAggregateValue(ip, tuple_ty, elems, n); free(elems); return res; } // (part of) Sema.zig:fieldVal // Sema.fieldVal's arms that answer by the object's type whatever the value's // storage (`bytes`, `only_possible_value`, `aggregate`): an array's `.len`, // tupleFieldVal's `.len` and tupleFieldValByIndex. False for other types. // The `.len` fieldVal reads off an array or tuple type. static bool semaArrayOrTupleLen( const InternPool* ip, uint32_t ty, uint64_t* len) { IpArrayInfo arr; if (ztIsTuple(ip, ty)) *len = ipTupleFieldCount(ip, ty); else if (ipArrayInfo(ip, ty, &arr) == IP_ARR_OK && !arr.is_vector) *len = arr.len; else return false; return true; } bool semaFieldValByType(InternPool* ip, uint32_t agg, const char* field, uint32_t* out_val, uint32_t* out_ty) { uint32_t ty = ipTypeOf(ip, agg); bool is_tuple = ztIsTuple(ip, ty); uint64_t len; if (!semaArrayOrTupleLen(ip, ty, &len)) return false; if (strcmp(field, "len") == 0) { if (len > UINT32_MAX) UNIMPLEMENTED("fieldVal: wide array length"); *out_val = ipGetIntUsize(ip, (uint32_t)len); *out_ty = ZIR_REF_USIZE_TYPE; return true; } if (!is_tuple) return false; tupleFieldValByIndex( ip, agg, tupleFieldIndex(ip, ty, field), out_val, out_ty); return true; } // (part of) Value.zig:ptrField // A pointer derived from a comptime cell by `Value.ptrField` / // `ptrOptPayload` / `ptrEuPayload` (`.field`, `.opt_payload`, `.eu_payload` // base addresses, or ptrField's packed arm: a cell's host pointer retyped to // a bit pointer): sema_comptime.c's cell pointers, which the runtime frame's // comptime blocks share. static bool semaIsCellChainPtr(const InternPool* ip, uint32_t v) { if (ipItemTagIs(ip, v, IP_TAG_PTR_FIELD) || ipItemTagIs(ip, v, IP_TAG_PTR_ELEM) || ipItemTagIs(ip, v, IP_TAG_PTR_OPT_PAYLOAD) || ipItemTagIs(ip, v, IP_TAG_PTR_EU_PAYLOAD)) return ipIsPtrComptimeAlloc(ip, ipPtrDerivedRoot(ip, v)); IpPtrInfo info; return ipIsPtrComptimeAlloc(ip, v) && ipPtrInfo(ip, ipTypeOf(ip, v), &info) == IP_PTRI_OK && info.packed_offset != 0 && IP_PTR_FLAGS_VECTOR_INDEX(info.flags) == IP_PTR_VECTOR_INDEX_NONE; } // Part of Sema.isComptimeMutablePtr, for a pointer a store or atomic op // receives: a comptime alloc or a field, element or payload link of one. Its // `.comptime_field`, `.slice` and `is_const` arms are the callers' or not // ported. static bool semaIsComptimeMutablePtr(const InternPool* ip, uint32_t v) { return semaIsCellChainPtr(ip, v) || ipIsPtrComptimeAlloc(ip, v); } static SemaCastStatus spliceMemory(Zcu* zcu, uint32_t val, uint32_t splice_val, uint64_t byte_offset, uint32_t* out); // storeComptimePtr's `.reinterpret` strategy: both layouts well defined, the // bounds check, then `spliceMemory(host_val, store_val, byte_offset)` is // the host's new value. An unset host (IP_INDEX_NONE) is `undefined`. uint32_t semaReinterpretStore(Zcu* zcu, uint32_t host_val, uint32_t host_ty, uint32_t store_ty, uint64_t offset, uint32_t store_val) { InternPool* ip = &zcu->intern_pool; if (host_val == IP_INDEX_NONE) host_val = ipGetUndef(ip, host_ty); if (ztOnePossibleValue(ip, store_ty) != IP_INDEX_NONE) return host_val; // "zero-bit store; nothing to do" if (!ztHasWellDefinedLayout(ip, host_ty) || !ztHasWellDefinedLayout(ip, store_ty)) COMPILE_ERROR_UPSTREAM("comptime dereference requires a " "well-defined layout"); if (offset + ztAbiSize(zcu, store_ty) > ztAbiSize(zcu, host_ty)) COMPILE_ERROR_UPSTREAM("dereference exceeds bounds of containing " "decl"); uint32_t new_val; switch (spliceMemory(zcu, host_val, semaCoerceComptime(zcu, ip, store_val, store_ty), offset, &new_val)) { case ZPT_CAST_OK: return new_val; case ZPT_CAST_RUNTIME: UNIMPLEMENTED("storeComptimePtr: runtime store"); default: UNIMPLEMENTED("storeComptimePtr: spliceMemory"); } ZIG1_TCC_RETURN(IP_INDEX_NONE); } // Value.zig:toIpString // A comptime field name, a `[]const u8` slice or a pointer to a `[N]u8`, read // from its uav's aggregate. `ctx` names the caller's operand. static uint32_t semaFieldNameValueToIpString( InternPool* ip, uint32_t name_val, const char* ctx) { if (name_val >= ip->items_len) UNIMPLEMENTED(ctx); bool is_slice = ip->item_tags[name_val] == IP_TAG_PTR_SLICE; uint32_t ptr = is_slice ? ipPtrSlicePtr(ip, name_val) : name_val; if (ptr >= ip->items_len || (ip->item_tags[ptr] != IP_TAG_PTR_UAV && !(is_slice && ip->item_tags[ptr] == IP_TAG_PTR_UAV_ALIGNED))) UNIMPLEMENTED(ctx); uint64_t byte_off = ipPtrUavByteOffset(ip, ptr); if (byte_off > (is_slice ? 255u : 0u)) UNIMPLEMENTED("Value.toIpString: name pointer offset"); uint32_t d = ip->item_datas[ptr]; uint32_t agg = ip->extra[d + 1]; uint32_t child_ty = ipPtrChild(ip, ip->extra[d]); uint32_t arr_len, elem_ty, sentinel; bool is_array = ztArrayTypeInfo(ip, child_ty, &arr_len, &elem_ty, &sentinel); if (is_array ? elem_ty != ZIR_REF_U8_TYPE : !is_slice) UNIMPLEMENTED("Value.toIpString: name pointer child not u8 array"); uint64_t len = is_slice ? ipReadIntValue(ip, ipPtrSliceLen(ip, name_val)) : arr_len; if (len == UINT64_MAX) UNIMPLEMENTED("Value.toIpString: name slice len unreadable"); if (len > 255u) UNIMPLEMENTED("Value.toIpString: name too long"); if (is_array && byte_off + len > (uint64_t)arr_len) UNIMPLEMENTED("Value.toIpString: name slice out of bounds"); char buf[256] = { 0 }; for (uint32_t i = 0; i < (uint32_t)len; i++) { uint32_t elem = semaAggregateElemValueWithAbort(ip, agg, (uint32_t)byte_off + i, "Value.toIpString: name aggregate carrier", true); if (ip->item_tags[elem] != IP_TAG_INT_U8) UNIMPLEMENTED("Value.toIpString: name byte not u8"); buf[i] = (char)(uint8_t)ip->item_datas[elem]; } return ipGetOrPutString(ip, buf, (uint32_t)len); } // Sema.coerceInMemoryAllowed (src/Sema.zig) reduced to the question // its pointer caller asks -- "is this pair in-memory coercible at all" -- // and conservative by construction: it answers true only for pairs it can // decide, never for a pair it has not modelled. Under-accepting is safe // here and nowhere else: the one caller falls through to `coerceExtra`'s // remaining arms and finally to a loud abort, so a missed `.ok` costs a // decline, never a wrong answer. It must therefore not be used to justify a // COMPILE_ERROR_UPSTREAM. static bool coerceInMemoryAllowedPtrs(Zcu* zcu, InternPool* ip, uint32_t dest_ty, uint32_t src_ty, bool dest_is_mut); static bool semaFuncTypeCoercible(Zcu* zcu, InternPool* ip, uint32_t src_ty, uint32_t dest_ty, bool dest_is_mut); static bool coerceInMemoryAllowedErrorSets( InternPool* ip, uint32_t dest_set, uint32_t src_set); // Sema.zig:coerceInMemoryAllowed bool coerceInMemoryAllowed(Zcu* zcu, InternPool* ip, uint32_t dest_ty, uint32_t src_ty, bool dest_is_mut) { // "if (dest_ty.eql(src_ty)) return .{ .ok = .same_type };" if (dest_ty == src_ty) return true; if (dest_ty >= ip->items_len || src_ty >= ip->items_len) return false; // "Differently-named integers with the same number of bits." Upstream // also allows a narrowing pair when `!dest_is_mut` and the source value // is known to fit; that needs the value, which this query does not // carry, so only the same-width arm is modelled. uint32_t dest_bits, src_bits; bool dest_signed, src_signed; if (ztIntInfo(ip, dest_ty, &dest_bits, &dest_signed) && ztIntInfo(ip, src_ty, &src_bits, &src_signed)) return dest_signed == src_signed && dest_bits == src_bits; // "Differently-named floats with the same number of bits." uint32_t dest_fbits, src_fbits; if (ztSimpleFloatBits(dest_ty, &dest_fbits) && ztSimpleFloatBits(src_ty, &src_fbits)) return dest_fbits == src_fbits; // "Pointers / Pointer-like Optionals" (src/Sema.zig) and "Slices" //: two upstream lines, the same callee. The pair rule those two // lines impose is stated once, inside `coerceInMemoryAllowedPtrs`, // because seven other sites ask that function directly (see its header). if (coerceInMemoryAllowedPtrs(zcu, ip, dest_ty, src_ty, dest_is_mut)) return true; // "Functions": `coerceInMemoryAllowedFns`. if (ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_FN) && ztIsZigTypeTag(ip, src_ty, ZIG_TYPE_TAG_FN)) return semaFuncTypeCoercible(zcu, ip, src_ty, dest_ty, dest_is_mut); // "Error Unions": the payloads in-memory coercible (no value: // `null`), then the sets. "Error Sets" is // `coerceInMemoryAllowedErrorSets`; its `dest_is_mut` reverse check is // the subset in the other direction. if (semaIsErrorUnionType(ip, dest_ty) && semaIsErrorUnionType(ip, src_ty)) return coerceInMemoryAllowed(zcu, ip, ipErrorUnionPayload(ip, dest_ty), ipErrorUnionPayload(ip, src_ty), dest_is_mut) && coerceInMemoryAllowed(zcu, ip, ipErrorUnionErrorSet(ip, dest_ty), ipErrorUnionErrorSet(ip, src_ty), dest_is_mut); if (ipIsErrorSetType(ip, dest_ty) && ipIsErrorSetType(ip, src_ty)) return coerceInMemoryAllowedErrorSets(ip, dest_ty, src_ty) && (!dest_is_mut || coerceInMemoryAllowedErrorSets(ip, src_ty, dest_ty)); // "Arrays": same length, in-memory-coercible element type, and the same // sentinel (upstream coerces the source sentinel to the destination // element type before comparing; identity is the modelled subset). uint32_t d_len, d_child, d_sent, s_len, s_child, s_sent; if (ztArrayTypeInfo(ip, dest_ty, &d_len, &d_child, &d_sent) && ztArrayTypeInfo(ip, src_ty, &s_len, &s_child, &s_sent)) return d_len == s_len && d_sent == s_sent && coerceInMemoryAllowed(zcu, ip, d_child, s_child, dest_is_mut); // "Optionals" (src/Sema.zig): a pointer-like side belongs to the // "Pointers" line above ("Only one is, because we already handled when // both are."), otherwise the two children must be in-memory coercible. if (ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_OPTIONAL) && ztIsZigTypeTag(ip, src_ty, ZIG_TYPE_TAG_OPTIONAL)) return !ztTypeIsPtrLikeOptional(ip, dest_ty) && !ztTypeIsPtrLikeOptional(ip, src_ty) && coerceInMemoryAllowed(zcu, ip, ipOptionalChild(ip, dest_ty), ipOptionalChild(ip, src_ty), dest_is_mut); return false; } // Sema.zig:coerceInMemoryAllowedPtrs // Sema.coerceInMemoryAllowedPtrs (src/Sema.zig), transcribed in // upstream's order and reading both sides through `ipPtrInfo` rather than // raw `extra[]` flag masks. Returns true exactly for upstream's // `.ok = .ptr_cast`; every failure member collapses to false, because // zig1 renders no coercion diagnostics. static bool coerceInMemoryAllowedPtrs(Zcu* zcu, InternPool* ip, uint32_t dest_ty, uint32_t src_ty, bool dest_is_mut) { // The pair rule. Upstream reaches this function from exactly two lines of // `coerceInMemoryAllowed`: "Pointers / Pointer-like Optionals" // (src/Sema.zig), guarded by `isPtrAtRuntime` on both sides, and // "Slices", guarded by `isSlice` on both. `isPtrAtRuntime` is // false for a slice and `isSlice` is false for a pointer, so a mixed // slice/pointer pair reaches `coerceInMemoryAllowedPtrs` nowhere // upstream -- it falls past both lines to the arms below them. zig1 // states that rule here rather than at the call sites: upstream's // `coerceExtra` head asks the dispatcher and switches on the // returned strategy, but this boolean answers only `.ok = .ptr_cast`, so // the seven runtime sites that need exactly that strategy ask this // function directly and would otherwise each need their own copy. // Without it `ok_ptr_size` below passes on a `.c` destination alone and a // fat slice lowers as a bare `ptr_cast`: `[]u32` -> `[*c]u32` would be // accepted where upstream reports "expected type '[*c]u32', found // '[]u32'". if (ztIsSlice(ip, dest_ty) != ztIsSlice(ip, src_ty)) return false; IpPtrInfo dest_info, src_info; // `Type.ptrInfo` reports `.flags.size = .slice` for a `[]T`, so upstream // asks this function about slice pairs with the same decoded shape every // pointer pair gets. `ztPtrOrSliceInfo` is that reader; a pointer-like // optional's info is its pointer child's (`.opt_type => |child|`). bool dest_opt = ztTypeIsPtrLikeOptional(ip, dest_ty); bool src_opt = ztTypeIsPtrLikeOptional(ip, src_ty); if (!ztPtrOrSliceInfo( ip, dest_opt ? ipOptionalChild(ip, dest_ty) : dest_ty, &dest_info) || !ztPtrOrSliceInfo( ip, src_opt ? ipOptionalChild(ip, src_ty) : src_ty, &src_info)) return false; bool ok_ptr_size = src_info.size == dest_info.size || src_info.size == IP_PTR_SIZE_C || dest_info.size == IP_PTR_SIZE_C; if (!ok_ptr_size) return false; bool ok_const = src_info.is_const == dest_info.is_const || (!dest_is_mut && dest_info.is_const); if (!ok_const) return false; bool ok_volatile = src_info.is_volatile == dest_info.is_volatile || (!dest_is_mut && dest_info.is_volatile); if (!ok_volatile) return false; // `Type.ptrAllowsZero`: "if (ty.isPtrLikeOptional(zcu)) return true;". bool dest_allowzero = dest_opt || ztPtrInfoAllowsZero(&dest_info); bool src_allowzero = src_opt || ztPtrInfoAllowsZero(&src_info); bool ok_allowzero = src_allowzero == dest_allowzero || (!dest_is_mut && dest_allowzero); if (!ok_allowzero) return false; uint32_t dest_as = (dest_info.flags >> IP_PTR_FLAGS_ADDRSPACE_SHIFT) & IP_PTR_FLAGS_ADDRSPACE_MASK; uint32_t src_as = (src_info.flags >> IP_PTR_FLAGS_ADDRSPACE_SHIFT) & IP_PTR_FLAGS_ADDRSPACE_MASK; if (dest_as != src_as) return false; // "We must also include `dest_is_mut`": a mutable destination pointer // would otherwise let a store through `*Dest` be read back through // `*Src`, performing the reverse coercion. if (!coerceInMemoryAllowed(zcu, ip, dest_info.child, src_info.child, dest_is_mut || !dest_info.is_const)) { // "As a special case, we also allow coercing `*[n:s]T` to `*[n]T`." uint32_t d_len, d_child, d_sent, s_len, s_child, s_sent; if (dest_is_mut || !ztArrayTypeInfo(ip, src_info.child, &s_len, &s_child, &s_sent) || !ztArrayTypeInfo(ip, dest_info.child, &d_len, &d_child, &d_sent) || s_len != d_len || s_sent == IP_INDEX_NONE || d_sent != IP_INDEX_NONE || !coerceInMemoryAllowed( zcu, ip, d_child, s_child, !dest_info.is_const)) return false; } if (src_info.packed_offset != dest_info.packed_offset) return false; bool sentinel_ok; if (src_info.sentinel == IP_INDEX_NONE && dest_info.sentinel == IP_INDEX_NONE) sentinel_ok = true; else if (src_info.sentinel != IP_INDEX_NONE && dest_info.sentinel != IP_INDEX_NONE) // Upstream compares `ds` against `getCoerced(ss, dest_info.child)`; // identity is the modelled subset (a differently-typed sentinel of // the same value declines instead of being accepted). sentinel_ok = src_info.sentinel == dest_info.sentinel; else if (src_info.size == IP_PTR_SIZE_C) // DIVERGENCE (under-accepts): upstream falls through from the // both-sentinels branch into this one (src/Sema.zig), so a // C-pointer source with two differing sentinels is `.ok` there and // declines here. An `else if` chain cannot be reached that way. sentinel_ok = true; else sentinel_ok = !dest_is_mut && dest_info.sentinel == IP_INDEX_NONE; if (!sentinel_ok) return false; // "If both pointers have alignment 0, it means they both want ABI // alignment. In this case, if they share the same child type, no need to // resolve pointee type alignment." if (src_info.alignment != IP_PTR_ALIGN_NONE || dest_info.alignment != IP_PTR_ALIGN_NONE || dest_info.child != src_info.child) { uint32_t src_align = src_info.alignment; uint32_t dest_align = dest_info.alignment; // `"try sema.ensureLayoutResolved(src_child, src_src, // .align_check);"` before each ABI alignment. if (src_align == IP_PTR_ALIGN_NONE && zcu != NULL) ensureLayoutResolved(zcu, src_info.child); if (src_align == IP_PTR_ALIGN_NONE && !ztAbiAlignLog2(zcu, ip, src_info.child, &src_align)) return false; if (dest_align == IP_PTR_ALIGN_NONE && zcu != NULL) ensureLayoutResolved(zcu, dest_info.child); if (dest_align == IP_PTR_ALIGN_NONE && !ztAbiAlignLog2(zcu, ip, dest_info.child, &dest_align)) return false; if (dest_is_mut ? dest_align != src_align : dest_align > src_align) return false; } return true; } // A comptime `*const [N:s]T` uav pointer -> many pointer `[*:s]const T`. // Narrow port of Sema's source-array-pointer coercion branch: check pointer // attrs, require exact array-child identity, and require the source array // sentinel to satisfy the destination pointer sentinel. `.c` destinations and // non-exact in-memory-compatible children are not modeled. The resulting // pointer keeps the same uav backing value and byte offset, retyped at the // destination many-pointer type; the original array-pointer type is kept in // the aligned-uav payload just like coerceArrayPtrToSlice. uint32_t semaCoerceArrayUavPtrToMany( InternPool* ip, uint32_t operand, uint32_t operand_ty, uint32_t dest_ty) { if (!(ipIsPtrUav(ip, operand) && ip->item_tags[operand_ty] == IP_TAG_TYPE_POINTER && ip->item_tags[dest_ty] == IP_TAG_TYPE_POINTER && ipPtrSize(ip, operand_ty) == IP_PTR_SIZE_ONE && ipPtrSize(ip, dest_ty) == IP_PTR_SIZE_MANY)) return IP_INDEX_NONE; uint32_t src_array = ipPtrChild(ip, operand_ty); uint32_t src_len, src_elem, src_sentinel; if (!ztArrayTypeInfo(ip, src_array, &src_len, &src_elem, &src_sentinel)) return IP_INDEX_NONE; uint32_t dest_sentinel = ipPtrSentinel(ip, dest_ty); if (!semaCheckPtrAttributesTy(NULL, ip, dest_ty, operand_ty)) return IP_INDEX_NONE; if (src_elem != ipPtrChild(ip, dest_ty)) return IP_INDEX_NONE; if (dest_sentinel != IP_INDEX_NONE && src_sentinel != dest_sentinel) return IP_INDEX_NONE; uint32_t d = ip->item_datas[operand]; uint32_t val = ip->extra[d + 1]; uint32_t orig_ty = operand_ty; if (ip->item_tags[operand] == IP_TAG_PTR_UAV_ALIGNED) orig_ty = ip->extra[d + 2]; uint64_t byte_offset = ipPtrUavByteOffset(ip, operand); return ipGetPtrUavAlignedOffset(ip, dest_ty, val, orig_ty, byte_offset); } // Sema.zig:coerce, Sema.zig:coerceInMemoryAllowedPtrs, // PerThread.zig:getCoerced // A comptime `*const [N:s]T` pointer value -> `*const // [N]T`. This is the direct Sema.coerce path counterpart of // ctCoerceSentinelArrayPtrToPlain in sema_comptime.c, which handles the same // relation while binding comptime call parameters. // Sema.coerceInMemoryAllowedPtrs permits this special case: "As a special // case, we also allow coercing // `*[n:s]T` to `*[n]T`". Then coerceExtra's comptime `.ok` path calls // `pt.getCoerced(val, dest_ty)`, whose pointer arm preserves the base address // and byte offset and replaces the type. The reached ascii.hexEscape switch // has exact const single pointers whose children are `[16:0]u8` and `[16]u8`; // every broader relation remains loud once it is recognized as this sentinel // drop. uint32_t semaCoerceSentinelArrayPtrToPlain(InternPool* ip, uint32_t operand, uint32_t operand_ty, uint32_t dest_ty, const char* context) { if (operand >= ip->items_len || operand_ty >= ip->items_len || dest_ty >= ip->items_len) return IP_INDEX_NONE; IpPtrInfo src_ptr, dest_ptr; if (ipPtrInfo(ip, operand_ty, &src_ptr) != IP_PTRI_OK || ipPtrInfo(ip, dest_ty, &dest_ptr) != IP_PTRI_OK) return IP_INDEX_NONE; IpArrayInfo src_array, dest_array; if (ipArrayInfo(ip, src_ptr.child, &src_array) != IP_ARR_OK || ipArrayInfo(ip, dest_ptr.child, &dest_array) != IP_ARR_OK || src_array.is_vector || dest_array.is_vector || src_array.len != dest_array.len || src_array.sentinel == IP_INDEX_NONE || dest_array.sentinel != IP_INDEX_NONE) return IP_INDEX_NONE; // The recursive element coercion can be either valid-but-unported or a // stock compile error; this bounded helper cannot classify that boundary // without porting coerceInMemoryAllowed for the element pair. if (src_array.child != dest_array.child) UNIMPLEMENTED(context); if (src_ptr.is_const && !dest_ptr.is_const) COMPILE_ERROR_UPSTREAM(context); if (src_ptr.is_volatile && !dest_ptr.is_volatile) COMPILE_ERROR_UPSTREAM(context); if (src_ptr.is_allowzero && !dest_ptr.is_allowzero) COMPILE_ERROR_UPSTREAM(context); // The remaining pointer-attribute combinations likewise mix valid // widenings with stock-invalid address-space/alignment/packed-offset // changes, so keep the taxonomy neutral until those checks are ported. if (!src_ptr.is_const || !dest_ptr.is_const || src_ptr.size != IP_PTR_SIZE_ONE || dest_ptr.size != IP_PTR_SIZE_ONE || src_ptr.sentinel != dest_ptr.sentinel || src_ptr.flags != dest_ptr.flags || src_ptr.packed_offset != dest_ptr.packed_offset) UNIMPLEMENTED(context); return ctPtrValueRetype(ip, operand, operand_ty, dest_ty, context); } // (part of) Sema.zig:coerceExtra // A comptime `&array` nav pointer (`*align(A) [N]T`, ptr size .one to an // array) -> a slice `[]align(B) T` (`.buffer = &backing` storing a global // aligned array into Dir.Reader-style `[]align(@alignOf(usize)) u8` field). // Mirrors Sema.coerceExtra's pointer `.array_pointer` arm on a comptime-known // operand: checkPtrAttributes (src/Sema.zig), then coerceArrayPtrToSlice // — getCoerced(val, dest.slicePtrFieldType()) re-types the same base // pointer at the dest's many-pointer type and wraps it with the array length. // Attribute compatibility for the shapes zig1 interns: const/volatile may // only widen; allowzero/addrspace/vector-index must match; alignments must be // both none, or both explicit with dest <= src (checkPtrAttributes — // the mixed explicit/none pair needs the child's ABI alignment, unmodeled // here). Element identity required (stock allows in-memory-coercible elems); // sentinel-terminated dest slices and big-array pointees are unmodeled. // Returns the interned slice value, or IP_INDEX_NONE when the shape does not // match so the caller falls through to its own loud abort. static uint32_t semaCoerceArrayNavPtrToSlice( InternPool* ip, uint32_t operand, uint32_t operand_ty, uint32_t dest_ty) { if (!(ip->item_tags[operand] == IP_TAG_PTR_NAV && ip->item_tags[operand_ty] == IP_TAG_TYPE_POINTER && ztIsSlice(ip, dest_ty) && ipPtrSize(ip, operand_ty) == IP_PTR_SIZE_ONE && ipPtrPackedOffset(ip, operand_ty) == 0)) return IP_INDEX_NONE; uint32_t dest_many_ty = ip->item_datas[dest_ty]; if (ipPtrSentinel(ip, dest_many_ty) != IP_INDEX_NONE || ipPtrPackedOffset(ip, dest_many_ty) != 0) return IP_INDEX_NONE; // sentinel/bit-range dest unmodeled bool attrs_compatible = semaCheckPtrAttributesTy(NULL, ip, dest_ty, operand_ty); uint32_t src_array = ipPtrChild(ip, operand_ty); uint32_t dest_elem = ipPtrChild(ip, dest_many_ty); if (ip->item_tags[src_array] != IP_TAG_TYPE_ARRAY_SMALL) return IP_INDEX_NONE; // big-array pointee unmodeled uint32_t src_len, src_elem, src_sentinel; if (!ztArrayTypeInfo(ip, src_array, &src_len, &src_elem, &src_sentinel)) return IP_INDEX_NONE; if (!attrs_compatible || src_elem != dest_elem) return IP_INDEX_NONE; IpPtrNav direct; if (ipDecodePtrNav(ip, operand, &direct) != IP_PN_OK) UNREACHABLE("coerce: malformed array Nav pointer"); uint32_t many_ptr = ipGetPtrNavOffset( ip, dest_many_ty, direct.nav_index, direct.byte_offset); uint32_t len_val = ipGetIntUsize(ip, src_len); return ipGetPtrSlice(ip, dest_ty, many_ptr, len_val); } // Shared by semaCoerceArrayValueToArray's element loop below and // sema_comptime.c's slice-to-slice coercion. bool semaSliceTypesDifferOnlyBySentinel( const InternPool* ip, uint32_t src_ty, uint32_t dest_ty) { uint32_t sd, dd; if (!ztSliceManyPtrExtraOff(ip, src_ty, &sd) || !ztSliceManyPtrExtraOff(ip, dest_ty, &dd)) return false; uint32_t src_child = ip->extra[sd + IP_TYPE_POINTER_CHILD]; uint32_t dest_child = ip->extra[dd + IP_TYPE_POINTER_CHILD]; uint32_t src_sentinel = ip->extra[sd + IP_TYPE_POINTER_SENTINEL]; uint32_t dest_sentinel = ip->extra[dd + IP_TYPE_POINTER_SENTINEL]; uint32_t src_flags = ip->extra[sd + IP_TYPE_POINTER_FLAGS]; uint32_t dest_flags = ip->extra[dd + IP_TYPE_POINTER_FLAGS]; return src_child == dest_child && src_flags == dest_flags && src_sentinel != dest_sentinel && dest_sentinel == IP_INDEX_NONE; } // (part of) PerThread.zig:aggregateValue // coerceExtra's `.array` destination arms on a comptime value // (src/Sema.zig): an `.array` source (`array_to_array`) first needs an // in-memory coercible child and a sentinel that is unchanged or removed // ("Array coercions are allowed only if the child is IMC and the sentinel is // unchanged or removed"); a `.vector` source, and the `.vector` destination's // `.array, .vector` sources, go straight to coerceArrayLike. coerceArrayLike // then requires equal lengths and coerces every element to the // destination child (`"const coerced = try sema.coerce(block, // dest_elem_ty, elem_ref, elem_src);"`); all are comptime-known here, so the // result is `pt.aggregateValue(dest_ty, element_vals)`. Returns // IP_INDEX_NONE when the shape does not match, for the caller's own // decline. `zcu` may be NULL (coerceExtra's comptime callers). uint32_t semaCoerceArrayValueToArray(Zcu* zcu, InternPool* ip, uint32_t operand, uint32_t operand_ty, uint32_t dest_ty) { // A zero-length array value is its type's only possible value. if (operand >= ip->items_len || !(ip->item_tags[operand] == IP_TAG_BYTES || ip->item_tags[operand] == IP_TAG_AGGREGATE || ip->item_tags[operand] == IP_TAG_REPEATED || ip->item_tags[operand] == IP_TAG_ONLY_POSSIBLE_VALUE)) return IP_INDEX_NONE; IpArrayInfo src, dst; if (ipArrayInfo(ip, operand_ty, &src) != IP_ARR_OK || ipArrayInfo(ip, dest_ty, &dst) != IP_ARR_OK) return IP_INDEX_NONE; if (!src.is_vector && !dst.is_vector) { if (!coerceInMemoryAllowed(zcu, ip, dst.child, src.child, false)) return IP_INDEX_NONE; // `"if (dest_ty.sentinel(zcu)) |dest_sent| { const src_sent = // inst_ty.sentinel(zcu) orelse break :array_to_array; if // (dest_sent.toIntern() != (try pt.getCoerced(src_sent, // dest_ty.childType(zcu))).toIntern()) break :array_to_array; }"` if (dst.sentinel != IP_INDEX_NONE && (src.sentinel == IP_INDEX_NONE || dst.sentinel != semaCoerceComptime(zcu, ip, src.sentinel, dst.child))) return IP_INDEX_NONE; } // coerceArrayLike: a length mismatch is a compile error upstream. if (src.len != dst.len) return IP_INDEX_NONE; uint32_t* elems = zig1Malloc(((size_t)dst.len + 1u) * sizeof(uint32_t)); for (uint32_t i = 0; i < dst.len; i++) { // `"const index_ref = Air.internedToRef((try pt.intValue(.usize, // i)).toIntern());"` ipGetIntUsize(ip, i); elems[i] = semaCoerceComptime(zcu, ip, valElemValue(ip, operand, i, "coerceArrayLike: element"), dst.child); } uint32_t res = zptAggregateValue(ip, dest_ty, elems, dst.len); free(elems); return res; } uint32_t semaResolvedInferredErrorSet( const InternPool* ip, uint32_t error_set) { if (error_set >= ip->items_len || ip->item_tags[error_set] != IP_TAG_TYPE_INFERRED_ERROR_SET) return error_set; uint32_t func_index = ip->item_datas[error_set]; uint32_t slot = ipFuncIesResolvedSlot(ip, func_index); if (slot == UINT32_MAX || slot >= ip->extra_len) UNREACHABLE("resolveInferredErrorSetTy: missing resolved slot"); if (ip->extra[slot] == IP_INDEX_NONE && ip->resolve_ies != NULL) ip->resolve_ies(ip->resolve_ies_ctx, func_index); return ip->extra[slot]; } // (part of) Sema.zig:zirMergeErrorSets // Resolve both operands of a ZIR_INST_MERGE_ERROR_SETS `lhs || rhs` before // merging, mirroring Sema.zirMergeErrorSets (src/Sema.zig): the // resolve step belongs at the merge-instruction call site, not inside the // errorSetMerge-equivalent (`semaMergeResolvedErrorSetTypes`), matching // upstream where `ensureFuncIesResolved` is called from zirMergeErrorSets // itself, not from errorSetMerge. // // Order matters, and matches upstream exactly (Sema.zig): Both // operands' shapes are validated first -- each must already be // ZIR_REF_ANYERROR_TYPE, IP_TAG_TYPE_ERROR_SET, or // IP_TAG_TYPE_INFERRED_ERROR_SET (upstream: analyzeAsType then // zigTypeTag(.error_set), Sema.zig) -- before the "merged with // anyerror is anyerror" short-circuit runs. Without this ordering, // `anyerror || ` would silently return anyerror instead of // aborting loudly. Only once both operands are confirmed error-set-shaped does // the literal-anyerror short-circuit fire; then lhs is resolved // (short-circuiting to anyerror without ever resolving rhs if lhs alone // already came back anyerror), then rhs. Resolution mirrors // `ensureFuncIesResolved`'s `ensureFuncBodyUpToDate` forcing via // `semaResolvedInferredErrorSet`'s `InternPool.resolve_ies` hook; a // never-analyzed function body or an unset resolver leaves the slot at // IP_INDEX_NONE, which the caller's `semaMergeResolvedErrorSetTypes` tag check // turns into a loud UNIMPLEMENTED (no separate "unresolvable" case needed // here). `context` names the UNIMPLEMENTED abort for an invalid-shaped operand // (passed through from the caller, consistent with // `semaMergeResolvedErrorSetTypes`'s own `context` parameter). `*lhs`/`*rhs` // are updated to their resolved forms in place; returns true when the caller // should use ZIR_REF_ANYERROR_TYPE directly instead of calling // `semaMergeResolvedErrorSetTypes`. Shared by sema_comptime.c's // ctResolveParamTypeRef ZIR_INST_MERGE_ERROR_SETS arm and this file's // analyzeBodyInner runtime-body arm. bool semaResolveMergeErrorSetOperands( const InternPool* ip, uint32_t* lhs, uint32_t* rhs, const char* context) { if (*lhs >= ip->items_len || ztZigTypeTag(ip, *lhs) != ZIG_TYPE_TAG_ERROR_SET) UNIMPLEMENTED(context); if (*rhs >= ip->items_len || ztZigTypeTag(ip, *rhs) != ZIG_TYPE_TAG_ERROR_SET) UNIMPLEMENTED(context); if (*lhs == ZIR_REF_ANYERROR_TYPE || *rhs == ZIR_REF_ANYERROR_TYPE) return true; *lhs = semaResolvedInferredErrorSet(ip, *lhs); if (*lhs == ZIR_REF_ANYERROR_TYPE) return true; *rhs = semaResolvedInferredErrorSet(ip, *rhs); if (*rhs == ZIR_REF_ANYERROR_TYPE) return true; return false; } // Sema.zig:addToInferredErrorSet static void addToInferredErrorSet( InternPool* ip, uint32_t dest_set, uint32_t src_set) { if (dest_set >= ip->items_len || ip->item_tags[dest_set] != IP_TAG_TYPE_INFERRED_ERROR_SET) return; // Upstream InferredErrorSet.addErrorSet treats an inferred set added to // itself as a no-op ("Adding an inferred error set to itself.", // src/Sema.zig). Do this before resolving the source: resolving the same // pending set recursively walks its dependency list and reports a false // dependency loop. if (dest_set == src_set) return; uint32_t func_index = ip->item_datas[dest_set]; uint32_t slot = ipFuncIesResolvedSlot(ip, func_index); if (slot == UINT32_MAX || slot >= ip->extra_len) UNREACHABLE("addToInferredErrorSet: missing resolved slot"); uint32_t src_res = semaResolvedInferredErrorSet(ip, src_set); if (src_res == IP_INDEX_NONE) return; src_set = src_res; if (src_set == ZIR_REF_ANYERROR_TYPE) { ip->extra[slot] = ZIR_REF_ANYERROR_TYPE; return; } if (src_set >= ip->items_len || ip->item_tags[src_set] != IP_TAG_TYPE_ERROR_SET) UNIMPLEMENTED("addToInferredErrorSet: source not an error set"); uint32_t cur = ip->extra[slot]; if (cur == ZIR_REF_ANYERROR_TYPE) return; if (cur == IP_INDEX_NONE) { ip->extra[slot] = src_set; return; } uint32_t cur_res = semaResolvedInferredErrorSet(ip, cur); if (cur_res == IP_INDEX_NONE) { ip->extra[slot] = src_set; return; } cur = cur_res; if (cur == ZIR_REF_ANYERROR_TYPE) { ip->extra[slot] = ZIR_REF_ANYERROR_TYPE; return; } if (cur >= ip->items_len || ip->item_tags[cur] != IP_TAG_TYPE_ERROR_SET) UNIMPLEMENTED("addToInferredErrorSet: current not an error set"); // Hoist into a local first: Sema.errorSetMerge -> ipGetErrorSetType -> // ipAddExtra can realloc ip->extra, so `ip->extra[slot] = f(...)` would be // an unsequenced store into a pointer/index the callee may have just // invalidated (the compiler is free to compute &ip->extra[slot] before // evaluating the call). uint32_t merged = semaMergeResolvedErrorSetTypes( ip, cur, src_set, "addToInferredErrorSet: merge"); ip->extra[slot] = merged; } // (part of) Sema.zig:resolveConstDefinedValue // The operand rule every `reify_*` extended arm shares: upstream reaches // each of these operands through `Sema.resolveConstDefinedValue` (or // `resolveType`), so a runtime or unset operand is not a legal shape. // `ctx` names the arm. static uint32_t semaReifyConstOperand(uint32_t v, const char* ctx) { if (!semaRefIsInterned(v)) UNIMPLEMENTED(ctx); return v; } static bool semaErrorSetCanCoerceType( const InternPool* ip, uint32_t src_set, uint32_t dest_set) { if (!ipIsErrorSetType(ip, src_set) || !ipIsErrorSetType(ip, dest_set)) return false; if (src_set == dest_set || dest_set == ZIR_REF_ANYERROR_TYPE) return true; if (src_set == ZIR_REF_ANYERROR_TYPE) return false; if (src_set >= ip->items_len || dest_set >= ip->items_len) return false; // Mismatched inferred sets need a real inference constraint. This helper // is only for already-proven, immediate coercions. if (ip->item_tags[src_set] == IP_TAG_TYPE_INFERRED_ERROR_SET || ip->item_tags[dest_set] == IP_TAG_TYPE_INFERRED_ERROR_SET) return false; if (ip->item_tags[src_set] != IP_TAG_TYPE_ERROR_SET || ip->item_tags[dest_set] != IP_TAG_TYPE_ERROR_SET) return false; uint32_t len = ipErrorSetNamesLen(ip, src_set); for (uint32_t i = 0; i < len; i++) { if (!ipErrorSetHasName(ip, dest_set, ipErrorSetName(ip, src_set, i), IP_IES_NAME_ASSUMED)) return false; } return true; } static uint32_t semaResolveInferredErrorSetEmpty( InternPool* ip, uint32_t err_set) { if (err_set < ip->items_len && ip->item_tags[err_set] == IP_TAG_TYPE_INFERRED_ERROR_SET) { err_set = semaResolvedInferredErrorSet(ip, err_set); if (err_set == IP_INDEX_NONE) return ipGetErrorSetType(ip, NULL, 0); } return err_set; } // `dest_err_ty` / `operand_err_ty`: an error union's set, else the set itself. static uint32_t semaErrorCastSet(const InternPool* ip, uint32_t ty) { return semaIsErrorUnionType(ip, ty) ? ipErrorUnionErrorSet(ip, ty) : ty; } // (part of) Sema.zig:zirErrorCast // `Sema.zirErrorCast` (src/Sema.zig) up to its operand: the dest/operand // type checks and the `result` switch over `dest_err_ty`/`operand_err_ty` -- // `.disjoint` (no common errors), `.superset` (a coercion) or `.overlap`. An // inferred operand set is resolved first (`ensureFuncIesResolved`); an // inferred destination set is resolved too, unless it is the analyzed // function's own (`fn_ret_ty`'s), which records the operand's set instead. // The ad-hoc inferred set is not modelled. static SemaErrorCastResult semaErrorCastPrepare(InternPool* ip, uint32_t dest_ty, uint32_t operand_ty, uint32_t fn_ret_ty) { bool dest_eu = semaIsErrorUnionType(ip, dest_ty); bool op_eu = semaIsErrorUnionType(ip, operand_ty); if (!dest_eu && !ipIsErrorSetType(ip, dest_ty)) COMPILE_ERROR_UPSTREAM( "@errorCast: expected error set or error union type"); if (!op_eu && !ipIsErrorSetType(ip, operand_ty)) COMPILE_ERROR_UPSTREAM( "@errorCast: expected error set or error union type"); if (!dest_eu && op_eu) COMPILE_ERROR_UPSTREAM("cannot cast an error union type to error set"); if (dest_eu && op_eu && ipErrorUnionPayload(ip, dest_ty) != ipErrorUnionPayload(ip, operand_ty)) COMPILE_ERROR_UPSTREAM("payload types of error unions must match"); SemaErrorCastResult result; uint32_t op_err_ty = semaResolveInferredErrorSetEmpty( ip, semaErrorCastSet(ip, operand_ty)); bool op_empty = op_err_ty < ip->items_len && ip->item_tags[op_err_ty] == IP_TAG_TYPE_ERROR_SET && ipErrorSetNamesLen(ip, op_err_ty) == 0; uint32_t dest_set = semaErrorCastSet(ip, dest_ty); // `.inferred_error_set_type => |func_index|`: the analyzed function's own // set (`dst_ies.func == func_index`) records the operand's set, `"break // :check .superset"`; another function's set is `ensureFuncIesResolved` // then `"continue :check ip.funcIesResolvedUnordered(func_index)"`. bool own_ies = !op_empty && ipItemTagIs(ip, dest_set, IP_TAG_TYPE_INFERRED_ERROR_SET) && semaIsErrorUnionType(ip, fn_ret_ty) && ipErrorUnionErrorSet(ip, fn_ret_ty) == dest_set; if (own_ies) addToInferredErrorSet(ip, dest_set, op_err_ty); else if (!op_empty) dest_set = semaResolvedInferredErrorSet(ip, dest_set); if (op_empty) { result = ZPT_ERRCAST_DISJOINT; } else if (own_ies || dest_set == ZIR_REF_ANYERROR_TYPE) { result = ZPT_ERRCAST_SUPERSET; } else if (dest_set >= ip->items_len || ip->item_tags[dest_set] != IP_TAG_TYPE_ERROR_SET) { UNIMPLEMENTED("@errorCast: inferred destination error set"); } else if (ipErrorSetNamesLen(ip, dest_set) == 0) { result = ZPT_ERRCAST_DISJOINT; // dest is `error{}` } else if (op_err_ty == ZIR_REF_ANYERROR_TYPE) { result = ZPT_ERRCAST_OVERLAP; } else { if (op_err_ty >= ip->items_len || ip->item_tags[op_err_ty] != IP_TAG_TYPE_ERROR_SET) UNIMPLEMENTED("@errorCast: unresolved operand error set"); bool has_all = true, has_any = false; uint32_t n = ipErrorSetNamesLen(ip, op_err_ty); for (uint32_t i = 0; i < n; i++) { if (ipErrorSetHasName(ip, dest_set, ipErrorSetName(ip, op_err_ty, i), IP_IES_NAME_ASSUMED)) has_any = true; else has_all = false; } result = !has_any ? ZPT_ERRCAST_DISJOINT : has_all ? ZPT_ERRCAST_SUPERSET : ZPT_ERRCAST_OVERLAP; } if (result == ZPT_ERRCAST_DISJOINT && !(op_eu && dest_eu)) COMPILE_ERROR_UPSTREAM("@errorCast: error sets have no common errors"); return result; } // `zirErrorCast`'s comptime-known operand: the error name re-interned at the // destination (`.err` or `.error_union` `.err_name`), or a payload re-wrapped // (`wrapErrorUnionPayload`; the payload types are equal, so its coercion is // the identity). IP_INDEX_NONE for an operand value this does not read. static uint32_t semaErrorCastValue(InternPool* ip, SemaErrorCastResult result, uint32_t dest_ty, uint32_t operand) { if (operand >= ip->items_len) return IP_INDEX_NONE; uint32_t name; switch ((IpTag)ip->item_tags[operand]) { case IP_TAG_UNDEF: COMPILE_ERROR_UPSTREAM("@errorCast: use of undefined value"); case IP_TAG_ERROR_SET_ERROR: case IP_TAG_ERROR_UNION_ERROR: name = ipErrorName(ip, operand); break; case IP_TAG_ERROR_UNION_PAYLOAD: if (!semaIsErrorUnionType(ip, dest_ty)) return IP_INDEX_NONE; return ipGetErrorUnionPayload(ip, dest_ty, ipPayloadVal(ip, operand)); default: return IP_INDEX_NONE; } if (result != ZPT_ERRCAST_SUPERSET && !ipErrorSetHasName( ip, semaErrorCastSet(ip, dest_ty), name, IP_IES_NAME_ASSUMED)) COMPILE_ERROR_UPSTREAM("@errorCast: error not a member of the " "destination error set"); return semaIsErrorUnionType(ip, dest_ty) ? ipGetErrorUnionError(ip, dest_ty, name) : ipGetErrorSetError(ip, dest_ty, name); } // Sema.zig:coerceInMemoryAllowedErrorSets // `Sema.coerceInMemoryAllowedErrorSets` (src/Sema.zig): an inferred // destination first records the source (`InferredErrorSet.addErrorSet`), // then every source name must be in the destination. static bool coerceInMemoryAllowedErrorSets( InternPool* ip, uint32_t dest_set, uint32_t src_set) { addToInferredErrorSet(ip, dest_set, src_set); return semaErrorSetCanCoerceType(ip, semaResolveInferredErrorSetEmpty(ip, src_set), semaResolveInferredErrorSetEmpty(ip, dest_set)); } // (part of) Sema.zig:maybeMergeErrorSets, Sema.zig:errorSetMerge // Mirrors Sema.maybeMergeErrorSets (src/Sema.zig): try each // coercion direction first (cheaper, and preserves a named/inferred set when // possible); only when neither direction coerces, fall back to a true // union-of-names merge (Sema.errorSetMerge, src/Sema.zig) via // semaMergeResolvedErrorSetTypes. Callers are the block-break peer path // (`resolvePeerTypesInner`'s `.error_set` arm) and the switch_block // multi-item capture peer resolution. static uint32_t semaErrorSetPeerType(InternPool* ip, uint32_t a, uint32_t b) { if (semaErrorSetCanCoerceType(ip, a, b)) return b; if (semaErrorSetCanCoerceType(ip, b, a)) return a; // Peer resolution for error unions can meet two different inferred error // sets (`if (cond) a() else b()`, both `!T`). Upstream's // maybeMergeErrorSets resolves inferred sets before falling through to // errorSetMerge; mirror that here, with an unresolved/empty inferred set // contributing the canonical empty error set. a = semaResolveInferredErrorSetEmpty(ip, a); b = semaResolveInferredErrorSetEmpty(ip, b); if (semaErrorSetCanCoerceType(ip, a, b)) return b; if (semaErrorSetCanCoerceType(ip, b, a)) return a; // Neither `a` nor `b` can be ZIR_REF_ANYERROR_TYPE here: anyerror coerces // from anything (semaErrorSetCanCoerceType's dest_set == ANYERROR fast // path), so one of the checks above would already have fired. A merge is // only well-defined between two plain, resolved error-set types. if (a >= ip->items_len || ip->item_tags[a] != IP_TAG_TYPE_ERROR_SET || b >= ip->items_len || ip->item_tags[b] != IP_TAG_TYPE_ERROR_SET) return IP_INDEX_NONE; return semaMergeResolvedErrorSetTypes( ip, a, b, "errorSetMerge: peer error sets"); } // Sema.zig:resolveErrSetIsEmpty // `Sema.resolveErrSetIsEmpty`: `anyerror` is never empty, a declared set is // empty when it names nothing, and an inferred set is resolved first -- // except the function's own set, which "would cause a dependency loop" // upstream and answers "don't know" (false). static bool resolveErrSetIsEmpty( InternPool* ip, uint32_t set, uint32_t fn_ret_ty) { if (ipItemTagIs(ip, set, IP_TAG_TYPE_INFERRED_ERROR_SET)) { if (semaIsErrorUnionType(ip, fn_ret_ty) && ipErrorUnionErrorSet(ip, fn_ret_ty) == set) return false; set = semaResolveInferredErrorSetEmpty(ip, set); } return ipItemTagIs(ip, set, IP_TAG_TYPE_ERROR_SET) && ipErrorSetNamesLen(ip, set) == 0; } // Sema.zig:resolveIsNonErrFromType // `Sema.resolveIsNonErrFromType`: ZIR_REF_BOOL_TRUE / ZIR_REF_BOOL_FALSE when // the operand type decides `is_non_err`, else IP_INDEX_NONE. static uint32_t resolveIsNonErrFromType( InternPool* ip, uint32_t ty, uint32_t fn_ret_ty) { if (!semaIsErrorUnionType(ip, ty)) return ipIsErrorSetType(ip, ty) ? ZIR_REF_BOOL_FALSE : ZIR_REF_BOOL_TRUE; if (ztTypeClass(ip, ipErrorUnionPayload(ip, ty)) == ZT_CLASS_NPV) return ZIR_REF_BOOL_FALSE; return resolveErrSetIsEmpty(ip, ipErrorUnionErrorSet(ip, ty), fn_ret_ty) ? ZIR_REF_BOOL_TRUE : IP_INDEX_NONE; } // Sema.zig:resolveIsNullFromType // `Sema.resolveIsNullFromType`: ZIR_REF_BOOL_TRUE when a value of `ty` is // always null (`?noreturn`, `?error{}`), ZIR_REF_BOOL_FALSE when never, else // IP_INDEX_NONE. static uint32_t resolveIsNullFromType( InternPool* ip, uint32_t ty, uint32_t fn_ret_ty) { if (ty == ZIR_REF_NULL_TYPE) return ZIR_REF_BOOL_TRUE; IpPtrInfo info; if (ipPtrInfo(ip, ty, &info) == IP_PTRI_OK) return info.size == IP_PTR_SIZE_C ? IP_INDEX_NONE : ZIR_REF_BOOL_FALSE; if (!ztIsZigTypeTag(ip, ty, ZIG_TYPE_TAG_OPTIONAL)) return ZIR_REF_BOOL_FALSE; uint32_t payload = ip->item_datas[ty]; if (ztTypeClass(ip, payload) == ZT_CLASS_NPV || (ipIsErrorSetType(ip, payload) && resolveErrSetIsEmpty(ip, payload, fn_ret_ty))) return ZIR_REF_BOOL_TRUE; return IP_INDEX_NONE; } // (part of) Sema.zig:coerceInMemoryAllowedFns // `Sema.coerceInMemoryAllowedFns` (src/Sema.zig): var-args and calling // convention equal (upstream's `callconvCoerceAllowed` narrowed to // equality), the same runtime-bits answer, the return types in-memory // coercible unless the source's is generic or (`!dest_is_mut`) `noreturn`, // equal parameter and noalias counts, a `comptime` annotation dropped only // for a comptime-only destination parameter, and every non-generic parameter // in-memory coercible the reverse way. static bool semaFuncTypeCoercible(Zcu* zcu, InternPool* ip, uint32_t src_ty, uint32_t dest_ty, bool dest_is_mut) { if (src_ty == dest_ty) return true; if (src_ty >= ip->items_len || dest_ty >= ip->items_len || !ztIsZigTypeTag(ip, src_ty, ZIG_TYPE_TAG_FN) || !ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_FN)) return false; // Flags.cc and Flags.is_var_args. if (ipFuncTypeCcPacked(ip, src_ty) != ipFuncTypeCcPacked(ip, dest_ty) || ipFuncTypeIsVarArgs(ip, src_ty) != ipFuncTypeIsVarArgs(ip, dest_ty)) return false; // `"try sema.ensureLayoutResolved(src_ty, src_src, .coerce);"`, then // dest_ty's. if (zcu != NULL) { ensureLayoutResolved(zcu, src_ty); ensureLayoutResolved(zcu, dest_ty); } if (ztFnHasRuntimeBits(ip, src_ty) != ztFnHasRuntimeBits(ip, dest_ty)) return false; // `"if (!switch (src_info.return_type) { .generic_poison_type => true, // .noreturn_type => !dest_is_mut, else => false, })"` uint32_t src_ret = ipFuncTypeReturnType(ip, src_ty); if (!(src_ret == IP_INDEX_GENERIC_POISON_TYPE || (src_ret == ZIR_REF_NORETURN_TYPE && !dest_is_mut)) && !coerceInMemoryAllowed( zcu, ip, ipFuncTypeReturnType(ip, dest_ty), src_ret, dest_is_mut)) return false; uint32_t n = ipFuncTypeParamsLen(ip, src_ty); if (n != ipFuncTypeParamsLen(ip, dest_ty) || ipFuncTypeNoaliasBits(ip, src_ty) != ipFuncTypeNoaliasBits(ip, dest_ty)) return false; uint32_t src_ct = ipFuncTypeComptimeBits(ip, src_ty); uint32_t dest_ct = ipFuncTypeComptimeBits(ip, dest_ty); for (uint32_t i = 0; i < n; i++) { uint32_t dest_param = ipFuncTypeParamType(ip, dest_ty, i); uint32_t src_param = ipFuncTypeParamType(ip, src_ty, i); bool s_ct = ipFuncTypeParamBit(src_ct, i); bool d_ct = ipFuncTypeParamBit(dest_ct, i); if (s_ct != d_ct) { if (dest_is_mut || !s_ct || dest_param == IP_INDEX_GENERIC_POISON_TYPE) return false; // `Type.comptimeOnly`: `.partially_comptime, .fully_comptime // => true`. ZtTypeClass dc = ztTypeClass(ip, dest_param); if (dc != ZT_CLASS_FULL && dc != ZT_CLASS_PARTIAL) return false; } // "Note: Cast direction is reversed here." if (src_param != IP_INDEX_GENERIC_POISON_TYPE && dest_param != IP_INDEX_GENERIC_POISON_TYPE && !coerceInMemoryAllowed( zcu, ip, src_param, dest_param, dest_is_mut)) return false; } return true; } // InternPool.zig:getCoerced, Sema.zig:coerce // A function value re-typed at a coercible function type, or IP_INDEX_NONE // when this is not that coercion. The in-memory check runs against the // operand's current type, which for an already-coerced function is the type // it was last coerced to (coerceExtra tests `inst_ty`), while the re-intern // always starts from the original function: InternPool.getCoerced's own // `.func_coerced` arm reads the `"fieldIndex(Tag.FuncCoerced, \"func\")"` // field and hands `func`, not `val`, to getCoercedFuncDecl / // getCoercedFuncInstance, so a coerced-of-coerced is never interned. // ipUnwrapCoercedFunc is that read, and ipGetFuncCoerced's identity // short-circuit on the unwrapped function's own type is `"if (new_ty == // prev_ty) return val;"`. Reached by test/behavior/cast.zig through // coerceExtra, and by an explicitly typed container-level function alias // through zptAnalyzeNavVal -- one Sema.coerce, two callers. uint32_t semaCoerceFuncValue( InternPool* ip, uint32_t operand, uint32_t dest_ty) { if (operand >= ip->items_len || dest_ty >= ip->items_len) return IP_INDEX_NONE; // PerThread.getCoerced's `.@"extern"` arm: the same extern at the new // type (`getExtern`, a new owner Nav). if (ip->item_tags[operand] == IP_TAG_EXTERN && ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_FN)) { if (!semaFuncTypeCoercible( NULL, ip, ipTypeOf(ip, operand), dest_ty, false)) return IP_INDEX_NONE; const uint32_t* w = &ip->extra[ip->item_datas[operand]]; const IpNav* owner = ipGetNav(ip, w[3]); return ipGetExtern(ip, owner->name, dest_ty, w[1], w[2], w[4], owner->is_const, owner->resolved_alignment, IP_INDEX_NONE); } if ((ip->item_tags[operand] != IP_TAG_FUNC_DECL && ip->item_tags[operand] != IP_TAG_FUNC_INSTANCE && ip->item_tags[operand] != IP_TAG_FUNC_COERCED) || !ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_FN)) return IP_INDEX_NONE; if (!semaFuncTypeCoercible( NULL, ip, ipTypeOf(ip, operand), dest_ty, false)) return IP_INDEX_NONE; return ipGetFuncCoerced(ip, ipUnwrapCoercedFunc(ip, operand), dest_ty); } static uint32_t semaFuncPendingEntry(const InternPool* ip, uint32_t func) { uint32_t body = ipUnwrapCoercedFunc(ip, func); if (body >= ip->items_len) return IP_INDEX_NONE; if (ip->item_tags[body] == IP_TAG_FUNC_DECL) return ipFuncDeclOwnerNav(ip, body); if (ip->item_tags[body] == IP_TAG_FUNC_INSTANCE) return body | PENDING_INSTANCE_FLAG; return IP_INDEX_NONE; } static uint32_t semaFuncDeclBodyValue(const InternPool* ip, uint32_t func) { uint32_t body = ipUnwrapCoercedFunc(ip, func); if (body < ip->items_len && ip->item_tags[body] == IP_TAG_FUNC_DECL) return body; return IP_INDEX_NONE; } static uint32_t semaEnumLiteralVoidUnionFieldIndexResolved( InternPool* ip, uint32_t union_ty, uint32_t literal) { if (union_ty >= ip->items_len || literal >= ip->items_len || ip->item_tags[union_ty] != IP_TAG_TYPE_UNION || ip->item_tags[literal] != IP_TAG_ENUM_LITERAL || ipUnionTagUsage(ip, union_ty) != IP_UNION_TAG_USAGE_TAGGED) return UINT32_MAX; uint32_t field_name = ip->item_datas[literal]; uint32_t fields_len = ipUnionFieldsLen(ip, union_ty); for (uint32_t i = 0; i < fields_len; i++) { if (ipUnionFieldName(ip, union_ty, i) != field_name) continue; return ipUnionFieldType(ip, union_ty, i) == ZIR_REF_VOID_TYPE ? i : UINT32_MAX; } return UINT32_MAX; } static uint32_t semaEnumLiteralVoidUnionFieldIndex( Zcu* zcu, uint32_t union_ty, uint32_t literal) { InternPool* ip = &zcu->intern_pool; if (union_ty >= ip->items_len || literal >= ip->items_len || ip->item_tags[union_ty] != IP_TAG_TYPE_UNION || ip->item_tags[literal] != IP_TAG_ENUM_LITERAL || ipUnionTagUsage(ip, union_ty) != IP_UNION_TAG_USAGE_TAGGED) return UINT32_MAX; semaResolveAllUnionDeferred(zcu, union_ty); return semaEnumLiteralVoidUnionFieldIndexResolved(ip, union_ty, literal); } static uint32_t semaCoerceEnumLiteralToVoidUnion( InternPool* ip, uint32_t literal, uint32_t union_ty) { if (semaEnumLiteralVoidUnionFieldIndexResolved(ip, union_ty, literal) == UINT32_MAX) return IP_INDEX_NONE; uint32_t field_name = ip->item_datas[literal]; uint32_t tag_enum = ipUnionEnumTagType(ip, union_ty); if (tag_enum == IP_INDEX_NONE) tag_enum = ipEnsureGeneratedUnionTag(ip, union_ty); uint32_t tag = ipEnumTagFromFieldName(ip, tag_enum, field_name); if (tag == IP_INDEX_NONE) UNIMPLEMENTED( "coerceExtra: enum literal union coerce - tag field missing"); return ipGetUnionValue(ip, union_ty, tag, ZIR_REF_VOID_VALUE); } static void semaEnsureUnionLayoutForEnumLiteralCoerce( Zcu* zcu, uint32_t union_ty) { InternPool* ip = &zcu->intern_pool; uint64_t size, align, bits; if (!ztLayoutInfo(zcu, ip, union_ty, &size, &align, &bits) || !ipUnionLayoutResolved(ip, union_ty)) UNIMPLEMENTED("coerceExtra: enum literal union coerce - union layout " "unresolved"); } static uint32_t semaCoerceEnumLiteralToVoidUnionLazy( Zcu* zcu, uint32_t literal, uint32_t union_ty) { if (semaEnumLiteralVoidUnionFieldIndex(zcu, union_ty, literal) == UINT32_MAX) return IP_INDEX_NONE; semaEnsureUnionLayoutForEnumLiteralCoerce(zcu, union_ty); InternPool* ip = &zcu->intern_pool; return semaCoerceEnumLiteralToVoidUnion(ip, literal, union_ty); } // ── Comptime folds of literals, casts and builtins ────────────────────────── // (part of) Sema.zig:coerceExtra, PerThread.zig:floatValue // `Value.floatFromInt`, reporting whether the rounding kept the operand // exactly -- `Sema.coerceExtra`'s `fits` (src/Sema.zig), false // when `Mutable.setFloat` is `.inexact` or the result is not finite. A value // is `val.toFloat(f128, zcu)` first, then `pt.floatValue(dest_ty, ...)` // (a `comptime_float` destination keeps the f128). Every u64 magnitude // widens exactly to f128 (113 mantissa bits); a wider one is exact only // when its significant bits fit, the one shape here -- a value `fits` // refuses anyway -- declines. A result that widens back to other words lost // a bit. static uint32_t semaCoerceIntToFloatExact( InternPool* ip, uint32_t operand, uint32_t dest_ty, bool* out_exact) { *out_exact = true; uint32_t dest_bits; uint64_t space[2]; BigIntConst v; if ((dest_ty != ZIR_REF_COMPTIME_FLOAT_TYPE && !ztSimpleFloatBits(dest_ty, &dest_bits)) || operand >= ip->items_len || !ipTagIsIntValue((IpTag)ip->item_tags[operand]) || !valToBigInt(ip, operand, space, &v)) return IP_INDEX_NONE; // `pt.floatValue(dest_ty, operand_val.toFloat(f128, zcu))`: rounded to // f128, then to the destination; exact only if neither step rounded. uint32_t words[4], back[4]; bool f128_exact = bigIntToF128Words(v, words); uint32_t result = ipGetFloatFromF128Bits(ip, dest_ty, words); *out_exact = f128_exact && result != IP_INDEX_NONE && ipReadFloatAsF128Bits(ip, result, back) && memcmp(words, back, sizeof(words)) == 0; return result; } uint32_t semaCoerceIntToFloat( InternPool* ip, uint32_t operand, uint32_t dest_ty) { bool exact; return semaCoerceIntToFloatExact(ip, operand, dest_ty, &exact); } // zirFloatCast's / zirFloatFromInt's destination check, `checkFloatType` // of `dest_ty.scalarType(zcu)`: `comptime_float` (`*out_bits` 0) or a fixed // float; the scalar type. static uint32_t semaFloatDestScalar( const InternPool* ip, uint32_t dest_ty, uint32_t* out_bits) { uint32_t scalar = ipScalarType(ip, dest_ty); *out_bits = 0; if (scalar != ZIR_REF_COMPTIME_FLOAT_TYPE && !ztSimpleFloatBits(scalar, out_bits)) COMPILE_ERROR_UPSTREAM("expected float or vector type"); return scalar; } // (part of) Sema.zig:intFromFloat, PerThread.zig:aggregateValue // The per-lane loop zirFloatCast, zirFloatFromInt and Sema.intFromFloat share // for a vector: "for (new_elems, 0..) |*new_elem, i| { const old_elem = try // operand_val.elemValue(pt, i); new_elem.* = (try old_elem.floatCast( // dest_scalar_ty, pt)).toIntern(); } return .fromValue(try // pt.aggregateValue(dest_ty, new_elems));". A scalar `dest_ty` is one lane. uint32_t semaCastLanes( InternPool* ip, uint32_t val, uint32_t dest_ty, SemaLaneCast lane) { IpArrayInfo vec; if (ipArrayInfo(ip, dest_ty, &vec) != IP_ARR_OK || !vec.is_vector) return lane(ip, val, dest_ty); uint32_t src_elem_ty = ipScalarType(ip, ipTypeOf(ip, val)); uint32_t* elems = zig1Malloc(((size_t)vec.len + 1u) * sizeof(*elems)); uint32_t res = IP_INDEX_NONE; uint32_t i = 0; for (; i < vec.len; i++) { elems[i] = lane(ip, semaAggregateElemValueOrUndef( ip, val, src_elem_ty, i, "Air.Inst.Data.ty_op: vector lane"), vec.child); if (elems[i] == IP_INDEX_NONE) break; } if (i == vec.len) res = zptAggregateValue(ip, dest_ty, elems, vec.len); free(elems); return res; } // (part of) PerThread.zig:undefValue, PerThread.zig:floatValue // zirFloatFromInt's lane: `checkIntType`, then "const casted_elem = if // (orig_elem.isUndef(zcu)) try pt.undefValue(dest_scalar_ty) else try // pt.floatValue(dest_scalar_ty, orig_elem.toFloat(f128, zcu));". static uint32_t semaFloatFromIntLane( InternPool* ip, uint32_t operand, uint32_t dest_ty) { uint32_t op_ty = ipComptimeValueType(ip, operand); uint32_t bits; bool is_signed; if (op_ty != ZIR_REF_COMPTIME_INT_TYPE && !ztIntInfo(ip, op_ty, &bits, &is_signed)) UNIMPLEMENTED("float_from_int: non-int operand type"); return ipIsUndef(ip, operand) ? ipGetUndef(ip, dest_ty) : semaCoerceIntToFloat(ip, operand, dest_ty); } // (part of) Sema.zig:coerceExtra // Sema.coerceExtra's `.int, .comptime_int` operand arm ends in the `fits` // test (src/Sema.zig): the destination float value is converted // back to an integer and must equal the operand exactly, or the coercion is // the compile error "type '{f}' cannot represent integer value '{f}'". So // `@as(f32, 16777217)` does not compile, while `@floatFromInt` -- which has // no such rule and shares `semaCoerceIntToFloat` -- rounds it. // // Sema.coerceExtra's float-destination arm (`.float, .comptime_float =>`): // a comptime_int operand becomes the destination float through // `floatFromInt` (semaCoerceIntToFloat above), a comptime_float operand // through `floatCast` -- and either way the result is a value of `dest_ty`, // not the literal left at its own type. Returns IP_INDEX_NONE when the // destination is not one of the fixed float formats or the operand is // neither, so the caller keeps its own path. Shared with sema_comptime.c's // ctCoerceToType, which had no float arm at all and returned an `f32` field's // `13` as the comptime_int 13. static uint32_t semaCoerceToFloat( InternPool* ip, uint32_t operand, uint32_t dest_ty) { bool int_fits; uint32_t from_int = semaCoerceIntToFloatExact(ip, operand, dest_ty, &int_fits); if (from_int != IP_INDEX_NONE) { if (!int_fits) COMPILE_ERROR_UPSTREAM( "coerce: float type cannot represent integer value"); return from_int; } // The `.float, .comptime_float` destination arm's two float sources: // `.comptime_float => { ... val.floatCast(dest_ty, pt) ... }` and // `.float => { if (maybe_inst_val) |val| { const result_val = try // val.floatCast(dest_ty, pt); if (!val.eql(try // result_val.floatCast(inst_ty, pt), inst_ty, zcu)) { return sema.fail( // ... "type '{f}' cannot represent float value '{f}'" ...`. Interned // values compare by index, which is upstream's `eql` for two floats of // one type. uint32_t operand_ty = ipComptimeValueType(ip, operand); uint32_t dest_bits; // coerceExtra's first rule, "if (dest_ty.eql(inst_ty, zcu)) return // inst;": a float already of the destination type is not re-rounded // (an f80 NaN or pseudo-denormal does not survive an f128 round trip). if (operand_ty == dest_ty) return operand; if (operand >= ip->items_len || !semaTagIsFloat(ip->item_tags[operand]) || (dest_ty != ZIR_REF_COMPTIME_FLOAT_TYPE && !ztSimpleFloatBits(dest_ty, &dest_bits))) return IP_INDEX_NONE; uint32_t result = valFloatCast(ip, operand, dest_ty); if (result != IP_INDEX_NONE && operand_ty != ZIR_REF_COMPTIME_FLOAT_TYPE && valFloatCast(ip, result, operand_ty) != operand) COMPILE_ERROR_UPSTREAM("coerce: type cannot represent float value"); return result; } // (part of) PerThread.zig:intValue_big, Sema.zig:intFromFloatScalar // `Mutable.setFloat(float, .trunc)` then `pt.intValue_big(.comptime_int, ..)` // for a finite f128 given as its words: the comptime_int toward zero, and in // `*out_exact` whether it was exact. Shared by Sema.intFromFloatScalar and // cmpNumeric's float-against-int side. static uint32_t semaFloatWordsToComptimeInt( InternPool* ip, const uint32_t words[4], bool* out_exact) { uint32_t biased = (words[3] >> 16) & 0x7FFFu; uint32_t cap = (biased < 16383u ? 0u : (biased - 16383u + 2u) / 64u) + 3u; uint64_t* limbs = zig1Malloc(cap * sizeof(uint64_t)); BigIntMutable r = { limbs, cap, 0, true }; if (!bigIntMutableSetFloat128Trunc(&r, words, out_exact)) UNIMPLEMENTED("float to integer: limb buffer too short"); uint32_t res = ipGetInt(ip, ZIR_REF_COMPTIME_INT_TYPE, bigIntMutableToConst(r)); free(limbs); return res; } // `Sema.IntFromFloatMode`. typedef enum { IFF_EXACT, IFF_TRUNCATE, IFF_ROUND, IFF_FLOOR, IFF_CEIL, } IntFromFloatMode; // Sema.zig:intFromFloatScalar // `Sema.intFromFloatScalar` (src/Sema.zig): `.exact` (coerceExtra's // `.int, .comptime_int` destination arm), `.truncate` (`zirIntFromFloat`), // and zirRoundCast's `.round` / `.floor` / `.ceil`, which pre-round the f128. static uint32_t intFromFloatScalar( InternPool* ip, uint32_t val, uint32_t int_ty, IntFromFloatMode mode) { // "if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src, // vec_idx);" if (ipIsUndef(ip, val)) COMPILE_ERROR_UPSTREAM("intFromFloat: use of undefined value"); // "var float = val.toFloat(f128, zcu); switch (mode) { .round => float = // @round(float), .floor => ..., .ceil => ..., .truncate, .exact => {} }" // `checkFloatType` admits a `comptime_int` operand, whose `toFloat` // is `big.int.Const.toFloat(f128, .nearest_even)`. uint32_t words[4]; if (val < ip->items_len && ipTagIsIntValue((IpTag)ip->item_tags[val])) { uint64_t int_space[2]; BigIntConst int_val; if (!valToBigInt(ip, val, int_space, &int_val)) UNIMPLEMENTED("intFromFloat: integer operand not readable"); bigIntToF128Words(int_val, words); } else if (!ipReadFloatAsF128Bits(ip, val, words)) UNIMPLEMENTED("intFromFloat: operand is not a float value"); if (mode >= IFF_ROUND) { CrtU128 f = ipFloat128FromWords(words); ipFloat128ToWords(mode == IFF_ROUND ? crtRoundq(f) : crtFloorCeil(128, mode == IFF_CEIL, f), words); } uint32_t biased = (words[3] >> 16) & 0x7FFFu; if (biased == 0x7FFFu) { if ((words[0] | words[1] | words[2] | (words[3] & 0xFFFFu)) != 0) COMPILE_ERROR_UPSTREAM( "float value NaN cannot be stored in integer type"); COMPILE_ERROR_UPSTREAM( "float value Inf cannot be stored in integer type"); } bool exact; uint32_t result = semaFloatWordsToComptimeInt(ip, words, &exact); // ".inexact => switch (mode) { .exact => return sema.fail(block, src, // \"fractional component prevents float value '{f}' from coercion to // type '{f}'\", ...)" if (!exact && mode == IFF_EXACT) COMPILE_ERROR_UPSTREAM("fractional component prevents float value " "from coercion to type"); // "const cti_result = try pt.intValue_big(.comptime_int, // big_int.toConst()); if (int_ty.toIntern() == .comptime_int_type) return // cti_result; ... return pt.getCoerced(cti_result, int_ty);" after the // fits test. if (int_ty == ZIR_REF_COMPTIME_INT_TYPE) return result; uint32_t bits; bool is_signed; uint64_t space[2]; BigIntConst c; if (!ztIntInfo(ip, int_ty, &bits, &is_signed) || !valToBigInt(ip, result, space, &c)) UNIMPLEMENTED("intFromFloat: destination is not an integer type"); if (!valBigIntFitsInType(ip, c, int_ty)) COMPILE_ERROR_UPSTREAM("float value cannot be stored in integer type"); return ipGetInt(ip, int_ty, c); } // (part of) Sema.zig:zirIntFromBool, PerThread.zig:vectorType // Sema.zirIntFromBool's types: "if (operand_scalar_ty.toIntern() != // .bool_type) return sema.fail(...); ... const dest_ty: Type = if (is_vector) // try pt.vectorType(.{ .child = .u1_type, .len = len }) else .u1;". // `u1`, or `@Vector(len, u1)` when `ty` is a vector -- the shape // zirIntFromBool's result and overflowArithmeticTupleType's overflow bit // share. uint32_t semaU1ShapedLike(InternPool* ip, uint32_t ty) { IpArrayInfo vec; if (ty != IP_INDEX_NONE && ipArrayInfo(ip, ty, &vec) == IP_ARR_OK && vec.is_vector) return ipGetVectorType(ip, vec.len, ZIR_REF_U1_TYPE); return ZIR_REF_U1_TYPE; } // Sema.zirIntFromBool: the u1 / @Vector(len, u1) result type, and the // comptime fold (`*out_ty` its type). static uint32_t semaIntFromBoolType(InternPool* ip, uint32_t operand_ty) { if (ipScalarType(ip, operand_ty) != ZIR_REF_BOOL_TYPE) COMPILE_ERROR_UPSTREAM("int_from_bool: expected 'bool'"); return semaU1ShapedLike(ip, operand_ty); } static uint32_t semaIntFromBoolLane( InternPool* ip, uint32_t val, uint32_t dest_ty) { // "if (val.isUndef(zcu)) .undef_u1 else if (val.toBool()) .one_u1 else // .zero_u1". if (val == ZIR_REF_BOOL_TRUE) return ZIR_REF_ONE_U1; if (val == ZIR_REF_BOOL_FALSE) return ZIR_REF_ZERO_U1; if (ipIsUndef(ip, val)) return ipGetUndef(ip, dest_ty); UNIMPLEMENTED("int_from_bool: comptime operand is not a bool"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // (part of) Sema.zig:zirIntFromPtr, PerThread.zig:aggregateValue // Sema.zirIntFromPtr's comptime vector arm: `"for (new_elems, 0..) |*new_elem, // i| { const ptr_val = try operand_val.elemValue(pt, i); if // (ptr_val.isUndef(zcu)) { new_elem.* = .undef_usize; continue; } const addr // = ptr_val.getUnsignedInt(zcu) orelse { break :ct; }; ... }"`, then // `pt.aggregateValue(dest_ty, new_elems)`. IP_INDEX_NONE is upstream's // `break :ct`: a lane that is not an integer address is a runtime operation. static uint32_t semaIntFromPtrVectorComptime( InternPool* ip, uint32_t val, uint32_t dest_ty) { IpArrayInfo vec; if (ipArrayInfo(ip, dest_ty, &vec) != IP_ARR_OK) UNIMPLEMENTED("int_from_ptr: vector destination"); uint32_t* elems = ARR_INIT(uint32_t, vec.len ? vec.len : 1u); uint32_t res = IP_INDEX_NONE; for (uint32_t i = 0; i < vec.len; i++) { uint32_t lane = semaAggregateElemValueWithAbort( ip, val, i, "int_from_ptr: comptime vector lane", false); if (ipIsUndef(ip, lane)) { elems[i] = ipGetUndef(ip, ZIR_REF_USIZE_TYPE); continue; } if (!ipIsPtrInt(ip, lane) || ipPtrIntAddress(ip, lane) > UINT32_MAX) goto done; elems[i] = ipGetIntUsize(ip, (uint32_t)ipPtrIntAddress(ip, lane)); } res = ipGetAggregate(ip, dest_ty, elems, vec.len); done: free(elems); return res; } // Sema.zig:fieldType // `Sema.fieldType` (`@FieldType` and `@unionInit`'s field query, both // frames): `"while (true) { switch (cur_ty.zigTypeTag(zcu)) { .@\"struct\" // => ..., .@\"union\" => ..., .optional => { cur_ty = ...opt_type; // continue; }, .error_union => { cur_ty = cur_ty.errorUnionPayload(zcu); // continue; }, else => {} } return sema.fail(block, ty_src, \"expected // struct or union; found '{f}'\", ...); }"`. uint32_t fieldType(Zcu* zcu, uint32_t aggregate_ty, uint32_t field_name) { InternPool* ip = &zcu->intern_pool; uint32_t cur_ty = aggregate_ty; while (true) { if (ztIsTuple(ip, cur_ty)) return ipTupleFieldType(ip, cur_ty, tupleFieldIndex(ip, cur_ty, ipStringBytes(ip, field_name))); if (cur_ty < ip->items_len && ipTagIsAnyStruct(ip->item_tags[cur_ty])) { // Callers' `"ensureLayoutResolved(aggregate_ty, ..., .init)"`. ensureLayoutResolved(zcu, cur_ty); uint32_t sfidx = ipStructFieldIndex(ip, cur_ty, field_name); if (sfidx == UINT32_MAX) COMPILE_ERROR_UPSTREAM("fieldType: struct field missing"); uint32_t field_ty = ipStructFieldType(ip, cur_ty, sfidx); if (field_ty == IP_INDEX_NONE) UNIMPLEMENTED("fieldType: unresolved struct field"); return field_ty; } if (cur_ty < ip->items_len && ztZigTypeTag(ip, cur_ty) == ZIG_TYPE_TAG_UNION) { semaResolveAllUnionDeferred(zcu, cur_ty); uint32_t ufidx = ipUnionFieldIndex(ip, cur_ty, field_name); if (ufidx == UINT32_MAX) COMPILE_ERROR_UPSTREAM("fieldType: union field missing"); uint32_t field_ty = semaResolveUnionFieldTypeLazy(zcu, cur_ty, ufidx); if (field_ty == IP_INDEX_NONE) UNIMPLEMENTED("fieldType: unresolved union field"); return field_ty; } if (ztIsZigTypeTag(ip, cur_ty, ZIG_TYPE_TAG_OPTIONAL)) { cur_ty = ipOptionalChild(ip, cur_ty); continue; } if (semaIsErrorUnionType(ip, cur_ty)) { cur_ty = ipErrorUnionPayload(ip, cur_ty); continue; } COMPILE_ERROR_UPSTREAM("fieldType: expected struct or union"); } } // Sema.zig:log2IntType, PerThread.zig:intType, PerThread.zig:vectorType // `Sema.log2IntType`: "switch (operand.zigTypeTag(zcu)) { .comptime_int => // return .comptime_int, .int => return pt.intType(.unsigned, switch // (operand.intInfo(zcu).bits) { 0 => 0, else => |b| // std.math.log2_int_ceil(u16, b) }), .vector => { ... log2IntType(elem_ty) // ... pt.vectorType(...) }, else => {} } return sema.fail(...)". // `log2_int_ceil(bits)` is `smallestUnsignedBits(bits - 1)`. static uint32_t log2IntType(InternPool* ip, uint32_t ty) { if (ty == ZIR_REF_COMPTIME_INT_TYPE) return ty; IpArrayInfo vec; if (ipArrayInfo(ip, ty, &vec) == IP_ARR_OK && vec.is_vector) return ipGetVectorType(ip, vec.len, log2IntType(ip, vec.child)); uint32_t bits; bool is_signed; if (!ztIntInfo(ip, ty, &bits, &is_signed)) COMPILE_ERROR_UPSTREAM( "log2IntType: bit shifting operation expected integer type"); return ipSmallestUnsignedInt(ip, bits ? bits - 1u : 0); } // (part of) Sema.zig:zirInplaceArithResultTy // Sema.zirInplaceArithResultTy, both frames: `"const ptr_size = // lhs_ty.ptrSizeOrNull(zcu) orelse break :ty lhs_ty;"`, then a many/C pointer // takes a `usize` offset under `+=` and `generic_poison` under `-=` ("could be // `[*]T - [*]T` or `[*]T - usize`"); a one/slice pointer keeps `lhs_ty`. uint32_t semaInplaceArithResultTy( const InternPool* ip, uint32_t lhs_ty, uint16_t op) { IpPtrInfo pi; if (!ipItemTagIs(ip, lhs_ty, IP_TAG_TYPE_POINTER) || ipPtrInfo(ip, lhs_ty, &pi) != IP_PTRI_OK || (pi.size != IP_PTR_SIZE_MANY && pi.size != IP_PTR_SIZE_C)) return lhs_ty; return op == ZIR_INPLACE_OP_ADD_EQ ? ZIR_REF_USIZE_TYPE : IP_INDEX_GENERIC_POISON_TYPE; } // (part of) PerThread.zig:intValue, Air.zig:internedToRef // zirIntFromFloat's and zirRoundCast's runtime `u0` destination: "const // scalar_val = try pt.intValue(dest_scalar_ty, 0); return // Air.internedToRef((try sema.splat(dest_ty, scalar_val)).toIntern());". static uint32_t semaSplatU0Zero(InternPool* ip, uint32_t dest_ty) { uint32_t zero = ipGetIntSmall(ip, ZIR_REF_U0_TYPE, 0); return dest_ty == ZIR_REF_U0_TYPE ? zero : ipGetRepeated(ip, dest_ty, zero); } // intFromFloatScalar as a lane, one per mode. #define INT_FROM_FLOAT_LANE(NAME, MODE) \ static uint32_t NAME(InternPool* ip, uint32_t val, uint32_t int_ty) { \ return intFromFloatScalar(ip, val, int_ty, MODE); \ } INT_FROM_FLOAT_LANE(semaIntFromFloatExactLane, IFF_EXACT) INT_FROM_FLOAT_LANE(semaIntFromFloatTruncateLane, IFF_TRUNCATE) INT_FROM_FLOAT_LANE(semaIntFromFloatRoundLane, IFF_ROUND) INT_FROM_FLOAT_LANE(semaIntFromFloatFloorLane, IFF_FLOOR) INT_FROM_FLOAT_LANE(semaIntFromFloatCeilLane, IFF_CEIL) #undef INT_FROM_FLOAT_LANE // Sema.zig:intFromFloat // `Sema.intFromFloat`: "if (float_ty.zigTypeTag(zcu) == .vector) { ... for // (result_data, 0..) |*scalar, i| { const elem_val = try val.elemValue(pt, // i); scalar.* = (try sema.intFromFloatScalar(block, src, elem_val, // int_ty.scalarType(zcu), mode, i)).toIntern(); } ..." -- `int_ty` and the // operand are vectors together (checkVectorizableBinaryOperands). uint32_t intFromFloat( InternPool* ip, uint32_t val, uint32_t int_ty, bool exact_mode) { return semaCastLanes(ip, val, int_ty, exact_mode ? semaIntFromFloatExactLane : semaIntFromFloatTruncateLane); } // Sema.zig:intFromFloat // `Sema.intFromFloat` for zirRoundCast's integer destination: `round_op` is // `Zir.Inst.RoundOp` (round, floor, ceil, trunc), a vector operand is done // per element. static uint32_t semaRoundIntFromFloat( InternPool* ip, uint16_t round_op, uint32_t val, uint32_t int_ty) { static const SemaLaneCast lanes[] = { [ZIR_ROUND_OP_ROUND] = semaIntFromFloatRoundLane, [ZIR_ROUND_OP_FLOOR] = semaIntFromFloatFloorLane, [ZIR_ROUND_OP_CEIL] = semaIntFromFloatCeilLane, [ZIR_ROUND_OP_TRUNC] = semaIntFromFloatTruncateLane, }; if (round_op >= sizeof(lanes) / sizeof(lanes[0])) UNIMPLEMENTED("round_op: unknown mode"); return semaCastLanes(ip, val, int_ty, lanes[round_op]); } // (part of) Sema.zig:zirTruncate // `Sema.zirTruncate`'s signedness rule, ahead of both its comptime and // runtime halves: "if (operand_scalar_ty.zigTypeTag(zcu) != .comptime_int) { // const operand_info = operand_ty.intInfo(zcu); if (operand_info.signedness // != dest_info.signedness) { return sema.fail(block, operand_src, \"expected // {s} integer type, found '{f}'\", ...); } ...". Asked of the scalar types, // so a vector pair is covered. static void semaTruncateCheckSignedness( const InternPool* ip, uint32_t operand_ty, uint32_t dest_ty) { uint32_t op_scalar = ipScalarType(ip, operand_ty); uint32_t dest_bits, op_bits; bool dest_signed, op_signed; if (op_scalar != ZIR_REF_COMPTIME_INT_TYPE && ztIntInfo(ip, ipScalarType(ip, dest_ty), &dest_bits, &dest_signed) && ztIntInfo(ip, op_scalar, &op_bits, &op_signed) && op_signed != dest_signed) COMPILE_ERROR_UPSTREAM("truncate: expected integer type of the " "destination's signedness"); } // Sema.zig:ptrCastFull // `Sema.ptrCastFull`'s type validation (src/Sema.zig), which // runs ahead of both halves: with `flags` a `Zir.Inst.FullPtrCastFlags` // word, `"if (!flags.align_cast) { if (dest_align.compare(.gt, src_align)) // ... "{s} increases pointer alignment"`, the address-space, `const` and // `volatile` twins likewise. Operand and destination are maybe-optional // pointers or slices (`ptrInfo` of `?*T` is `*T`'s). void semaPtrCastFullCheck(Zcu* zcu, InternPool* ip, uint32_t operand_ty, uint32_t dest_ty, uint32_t flags) { IpPtrInfo src, dest; if (ztIsZigTypeTag(ip, operand_ty, ZIG_TYPE_TAG_OPTIONAL)) operand_ty = ipOptionalChild(ip, operand_ty); if (ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_OPTIONAL)) dest_ty = ipOptionalChild(ip, dest_ty); if (!ztPtrOrSliceInfo(ip, operand_ty, &src) || !ztPtrOrSliceInfo(ip, dest_ty, &dest)) UNIMPLEMENTED("ptrCastFull: operand or destination not a pointer"); // `"try sema.ensureLayoutResolved(.fromInterned(src_info.child), // operand_src, .align_check);"`, then dest_info.child's. ensureLayoutResolved(zcu, src.child); ensureLayoutResolved(zcu, dest.child); if ((flags & ZIR_PTR_CAST_FLAG_PTR) == 0) { // `"if (!flags.ptr_cast) { ... check_size: ... check_child: ... // check_sent: ..."` -- without `@ptrCast` the pointer may change // only its qualifiers, never its size, element type or sentinel. bool array_ptr_to_slice = false; uint32_t src_child = src.child; uint32_t arr_len = 0, arr_child = IP_INDEX_NONE, arr_sent = IP_INDEX_NONE; if (dest.size == IP_PTR_SIZE_SLICE && src.size == IP_PTR_SIZE_ONE && ztArrayTypeInfo( ip, src.child, &arr_len, &arr_child, &arr_sent)) { array_ptr_to_slice = arr_child == dest.child; src_child = arr_child; // `"*[n]T -> []T"` } if (src.size != dest.size && !array_ptr_to_slice && src.size != IP_PTR_SIZE_C && dest.size != IP_PTR_SIZE_C) COMPILE_ERROR_UPSTREAM("ptrCastFull: cannot implicitly convert " "between pointer sizes"); if (!coerceInMemoryAllowed( zcu, ip, dest.child, src_child, !dest.is_const)) COMPILE_ERROR_UPSTREAM("ptrCastFull: pointer element type " "cannot coerce into element type"); if (dest.sentinel != IP_INDEX_NONE && src.size != IP_PTR_SIZE_C) { uint32_t have = src.sentinel != IP_INDEX_NONE ? src.sentinel : array_ptr_to_slice ? arr_sent : IP_INDEX_NONE; if (have == IP_INDEX_NONE || semaCoerceExtraOpts(zcu, ip, have, dest.child, false) != dest.sentinel) COMPILE_ERROR_UPSTREAM("ptrCastFull: destination pointer " "requires a sentinel"); } } if ((flags & ZIR_PTR_CAST_FLAG_ALIGN) == 0) { uint32_t src_align, dest_align; if (!ztPtrInfoAlignLog2(zcu, ip, &src, &src_align) || !ztPtrInfoAlignLog2(zcu, ip, &dest, &dest_align)) UNIMPLEMENTED("ptrCastFull: pointee alignment unresolved"); if (dest_align > src_align) COMPILE_ERROR_UPSTREAM("ptrCastFull: increases pointer alignment"); } if ((flags & ZIR_PTR_CAST_FLAG_ADDRSPACE) == 0 && ((src.flags >> IP_PTR_FLAGS_ADDRSPACE_SHIFT) & IP_PTR_FLAGS_ADDRSPACE_MASK) != ((dest.flags >> IP_PTR_FLAGS_ADDRSPACE_SHIFT) & IP_PTR_FLAGS_ADDRSPACE_MASK)) COMPILE_ERROR_UPSTREAM("ptrCastFull: changes pointer address space"); if ((flags & ZIR_PTR_CAST_FLAG_CONST) == 0 && src.is_const && !dest.is_const) COMPILE_ERROR_UPSTREAM("ptrCastFull: discards const qualifier"); if ((flags & ZIR_PTR_CAST_FLAG_VOLATILE) == 0 && src.is_volatile && !dest.is_volatile) COMPILE_ERROR_UPSTREAM("ptrCastFull: discards volatile qualifier"); } // (part of) Sema.zig:zirRefDeref // Sema.zirRefDeref (src/Sema.zig, "fn zirRefDeref("): `&ptr.*` of a C // pointer is the single pointer (`size = .one`, not allowzero), a comptime // non-null operand `getCoerced` to it; IP_INDEX_NONE for a null operand, // which takes the runtime `ptr_cast`. A single pointer is returned as is; a // comptime slice is its sliceToArrayPtr. `operand` is IP_INDEX_NONE when // runtime. uint32_t semaRefDerefComptime( InternPool* ip, uint32_t operand, uint32_t operand_ty, uint32_t* out_ty) { // `.slice`: validateDeref's `"index syntax required to access // runtime-known slice"`, then sliceToArrayPtr of the comptime slice. if (ztIsSlice(ip, operand_ty)) { if (operand == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM( "ref_deref: index syntax required to access runtime-known " "slice"); if (ipIsUndef(ip, operand)) COMPILE_ERROR_UPSTREAM( "ref_deref: cannot dereference undefined value"); uint32_t arr_ptr = zptSliceToArrayPtr(ip, operand); *out_ty = ipTypeOf(ip, arr_ptr); return arr_ptr; } IpPtrInfo info; if (ipPtrInfo(ip, operand_ty, &info) != IP_PTRI_OK || (info.size != IP_PTR_SIZE_ONE && info.size != IP_PTR_SIZE_C)) COMPILE_ERROR_UPSTREAM("ref_deref: cannot dereference"); *out_ty = operand_ty; if (info.size == IP_PTR_SIZE_ONE) return operand; *out_ty = ipGetPtrTypeFull(ip, info.child, (info.flags & ~(IP_PTR_FLAGS_SIZE_MASK | IP_PTR_FLAGS_IS_ALLOWZERO)) | IP_PTR_SIZE_ONE, info.sentinel, info.packed_offset); if (operand == IP_INDEX_NONE || (ipIsPtrInt(ip, operand) && ipPtrIntAddress(ip, operand) == 0)) return IP_INDEX_NONE; uint32_t coerced = ipRetypePtrValue(ip, operand, *out_ty); if (coerced == IP_INDEX_NONE) UNIMPLEMENTED("ref_deref: comptime pointer carrier"); return coerced; } // (part of) Sema.zig:zirFieldParentPtr // Sema.zirFieldParentPtr's checks ahead of both halves: the parent pointer // type (an optional result type's child), the field index, and the // ptrCastFull / alignment validation of a field pointer of // `fpp_field_ptr_ty`. Every refusal is upstream's compile error. static SemaFieldParent semaFieldParentPtrPrepare(Zcu* zcu, uint32_t fpp_result_ty, uint32_t fpp_name_val, uint32_t fpp_field_ptr_ty, uint32_t fpp_flags) { InternPool* ip = &zcu->intern_pool; uint32_t fpp_parent_ptr_ty = fpp_result_ty; if (ztZigTypeTag(ip, fpp_parent_ptr_ty) == ZIG_TYPE_TAG_OPTIONAL) fpp_parent_ptr_ty = ipOptionalChild(ip, fpp_parent_ptr_ty); if (ip->item_tags[fpp_parent_ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("field_parent_ptr: result not a pointer"); // Sema rejects non-single pointers ("expected single pointer type"). if (ipPtrSize(ip, fpp_parent_ptr_ty) != IP_PTR_SIZE_ONE) UNIMPLEMENTED("field_parent_ptr: non-single pointer"); uint32_t fpp_parent_ty = ipPtrChild(ip, fpp_parent_ptr_ty); // `"try sema.ensureLayoutResolved(parent_ty, inst_src, .field_used);"` ensureLayoutResolved(zcu, fpp_parent_ty); // `.@"struct", .@"union" => {}`, then the field index per // parent: tupleFieldIndex, structFieldIndex, unionFieldIndex. ZigTypeTag fpp_tag = ztZigTypeTag(ip, fpp_parent_ty); bool fpp_union = fpp_tag == ZIG_TYPE_TAG_UNION; if (fpp_tag != ZIG_TYPE_TAG_STRUCT && !fpp_union) COMPILE_ERROR_UPSTREAM("field_parent_ptr: expected pointer " "to struct or union type"); uint32_t fpp_name_ip = resolveConstStringIntern(zcu, fpp_name_val); uint32_t fpp_fidx; if (ztIsTuple(ip, fpp_parent_ty)) { // "len" is not an unsigned name: tupleFieldIndex refuses. fpp_fidx = tupleFieldIndex( ip, fpp_parent_ty, ipStringBytes(ip, fpp_name_ip)); if (ipTupleFieldValue(ip, fpp_parent_ty, fpp_fidx) != IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM("cannot get @fieldParentPtr of " "a comptime field"); } else { fpp_fidx = fpp_union ? ipUnionFieldIndex(ip, fpp_parent_ty, fpp_name_ip) : ipStructFieldIndex(ip, fpp_parent_ty, fpp_name_ip); if (fpp_fidx == UINT32_MAX) COMPILE_ERROR_UPSTREAM("field_parent_ptr: no field named"); if (!fpp_union && ipStructFieldIsComptime(ip, fpp_parent_ty, fpp_fidx)) COMPILE_ERROR_UPSTREAM("cannot get @fieldParentPtr of " "a comptime field"); } // `"const hypothetical_field_ptr_ty = try // parent_ptr_ty.fieldPtrType(field_index, pt); const // casted_field_ptr = try sema.ptrCastFull(block, flags, ..., // hypothetical_field_ptr_ty, \"@fieldParentPtr\");"` -- the // field pointer may only gain qualifiers (or lose alignment // under `@alignCast`) -- and then `"@fieldParentPtr // increases pointer alignment"` when the field limits the // parent's alignment and no `@alignCast` allows it. For every // parent kind. uint32_t fpp_hyp_ty = ztFieldPtrType(zcu, fpp_parent_ptr_ty, fpp_fidx); semaPtrCastFullCheck(zcu, ip, fpp_field_ptr_ty, fpp_hyp_ty, fpp_flags); if ((fpp_flags & ZIR_PTR_CAST_FLAG_ALIGN) == 0) { uint32_t hyp_al, par_al; IpPtrInfo hyp_pi, par_pi; if (ipPtrInfo(ip, fpp_hyp_ty, &hyp_pi) != IP_PTRI_OK || ipPtrInfo(ip, fpp_parent_ptr_ty, &par_pi) != IP_PTRI_OK || !ztPtrInfoAlignLog2(zcu, ip, &hyp_pi, &hyp_al) || !ztPtrInfoAlignLog2(zcu, ip, &par_pi, &par_al)) UNIMPLEMENTED("field_parent_ptr: pointer alignment unresolved"); if (hyp_al < par_al) COMPILE_ERROR_UPSTREAM( "@fieldParentPtr increases pointer alignment"); } SemaFieldParent r = { fpp_parent_ptr_ty, fpp_parent_ty, fpp_fidx, fpp_union }; return r; } // (part of) Sema.zig:zirFieldParentPtr // Sema.zirFieldParentPtr's comptime-known field pointer: `.@"packed"` and a // union are `getCoerced` to the parent pointer, an `.auto` field pointer's // base is the parent, any other is `ptrSubtract` of the field offset. static uint32_t semaFieldParentPtrComptime(Zcu* zcu, const SemaFieldParent* fp, uint32_t fpp_result_ty, uint32_t fpp_fptr_val) { InternPool* ip = &zcu->intern_pool; uint32_t fpp_parent; if (ipIsPtrField(ip, fpp_fptr_val)) { // `.auto`: `"if (ptr.byte_offset != 0) break :opt_field null; ... // .field => |field| field"`, the base's child must be the parent // and its index the field's, then `getCoerced(field.base, ...)`. IpPtrInfo fpp_base_pi; uint32_t fpp_base = ipPtrFieldBase(ip, fpp_fptr_val); if (ipPtrFieldByteOffset(ip, fpp_fptr_val) != 0 || ipPtrInfo(ip, ipTypeOf(ip, fpp_base), &fpp_base_pi) != IP_PTRI_OK || fpp_base_pi.child != fp->parent_ty) COMPILE_ERROR_UPSTREAM("pointer value not based on parent struct"); if (ipPtrFieldIndex(ip, fpp_fptr_val) != fp->fidx) COMPILE_ERROR_UPSTREAM( "@fieldParentPtr: field has a different index " "than the pointer value"); fpp_parent = ipRetypePtrValue(ip, fpp_base, fp->parent_ptr_ty); } else { // A Nav-rooted field pointer is its base plus the field's byte // offset (projectNavStructField), which a non-`.field` pointer into // an `.auto` struct subtracts too. uint64_t fpp_off = 0; if (!fp->is_union && !ipStructIsPacked(ip, fp->parent_ty)) fpp_off = ztStructFieldOffset(zcu, ip, fp->parent_ty, fp->fidx); uint64_t fpp_have; if (!ipPtrValueByteOffset(ip, fpp_fptr_val, &fpp_have)) UNIMPLEMENTED("field_parent_ptr: comptime field pointer carrier"); if (fpp_have < fpp_off) COMPILE_ERROR_UPSTREAM("@fieldParentPtr: pointer computation " "here causes illegal behavior"); fpp_parent = ipRemintPtrValue( ip, fpp_fptr_val, fp->parent_ptr_ty, fpp_have - fpp_off); } if (fpp_parent == IP_INDEX_NONE) UNIMPLEMENTED("field_parent_ptr: parent pointer not retypable"); return fpp_result_ty == fp->parent_ptr_ty ? fpp_parent : semaCoerceComptime(zcu, ip, fpp_parent, fpp_result_ty); } // (part of) Sema.zig:zirCoercePtrElemTy // Sema.zirCoercePtrElemTy's destination: what a `&` of type // `val_ty` is coerced to under the result pointer type `ptr_ty` // (`resolveTypeOrPoison`, then `optEuBaseType`), or IP_INDEX_NONE where // upstream returns the operand uncoerced. uint32_t semaCoercePtrElemTyWant( InternPool* ip, uint32_t ptr_ty, uint32_t val_ty) { if (ptr_ty == IP_INDEX_GENERIC_POISON_TYPE) return IP_INDEX_NONE; ptr_ty = ipOptEuBaseType(ip, ptr_ty); // A slice's element and sentinel live on its many-pointer. if (ztIsSlice(ip, ptr_ty)) ptr_ty = ip->item_datas[ptr_ty]; IpPtrInfo info; if (ipPtrInfo(ip, ptr_ty, &info) != IP_PTRI_OK) UNIMPLEMENTED("coerce_ptr_elem_ty: result type not a pointer"); if (info.size == IP_PTR_SIZE_ONE) { // "We're trying to initialize a *[1]T with a reference to a T - // don't perform any coercion", and "If the destination type is // anyopaque, don't coerce - the pointer will coerce instead." IpArrayInfo one_arr; if (ipArrayInfo(ip, info.child, &one_arr) == IP_ARR_OK && !one_arr.is_vector && one_arr.child == val_ty) return IP_INDEX_NONE; if (info.child == ZIR_REF_ANYOPAQUE_TYPE) return IP_INDEX_NONE; return info.child; } // "we don't know if this is meant to be a single-pointer or a // many-pointer." if (info.size == IP_PTR_SIZE_C) return IP_INDEX_NONE; // `.slice, .many`: "coerce `uncoerced_val` to an array of `elem_ty`", of // the operand's length and sentinel. uint32_t len; uint32_t sentinel = IP_INDEX_NONE; IpArrayInfo arr; if (ipArrayInfo(ip, val_ty, &arr) == IP_ARR_OK) { len = arr.len; sentinel = arr.sentinel; } else if (ztIsTuple(ip, val_ty)) len = ipTupleFieldCount(ip, val_ty); else COMPILE_ERROR_UPSTREAM("coerce_ptr_elem_ty: expected array operand"); return ipGetArrayType(ip, len, info.child, sentinel); } // (part of) Sema.zig:ptrCastFull, PerThread.zig:getCoerced, // InternPool.zig:getCoerced // `Sema.ptrCastFull`'s `ct:` block (src/Sema.zig) // for a comptime-known operand: undef and null first, then the slice's pointer // field, then `pt.getCoerced(ptr_val, dest_ty)` -- InternPool.getCoerced's // `.ptr` arm, which keeps the base address and byte offset // (`ipRetypePtrValue`). // `@ptrCast` never sets `addrspace_cast`, so the `break :ct` above that // block cannot fire. Shared by both frames. // `ptrCastFull`'s `dest_slice_len` for a comptime operand: the source // element type and count (an array pointee contributes its elements, a slice // its stored len), the same count when the element types agree, else the // byte count re-divided by the destination element size. static uint32_t semaPtrCastDestSliceLen(Zcu* zcu, uint32_t operand, const IpPtrInfo* src_info, uint32_t dest_elem) { InternPool* ip = &zcu->intern_pool; uint32_t src_elem = src_info->child; uint64_t src_len = 1; uint32_t alen, aelem, asent; if (src_info->size == IP_PTR_SIZE_ONE) { if (ztArrayTypeInfo(ip, src_elem, &alen, &aelem, &asent)) { src_elem = aelem; src_len = alen; } } else if (src_info->size == IP_PTR_SIZE_SLICE) { uint32_t len_val = ipPtrSliceLen(ip, operand); if (ipIsUndef(ip, len_val)) UNIMPLEMENTED("ptr_cast: undefined comptime slice length"); src_len = ipReadIntValue(ip, len_val); } else { COMPILE_ERROR_UPSTREAM("ptr_cast: cannot infer length of slice from " "a many or C pointer"); } if (dest_elem == src_elem) return (uint32_t)src_len; if (ztComptimeOnly(zcu, src_elem) || ztComptimeOnly(zcu, dest_elem)) UNIMPLEMENTED("ptr_cast: comptime-only slice length restructure"); if (ztIsZigTypeTag(ip, src_elem, ZIG_TYPE_TAG_OPAQUE)) COMPILE_ERROR_UPSTREAM("ptr_cast: cannot infer length of slice from " "pointer to opaque type with unknown size"); uint64_t ssz, dsz, al, bits; if (!ztLayoutInfo(zcu, ip, src_elem, &ssz, &al, &bits) || !ztLayoutInfo(zcu, ip, dest_elem, &dsz, &al, &bits)) UNIMPLEMENTED("ptr_cast: slice element layout unresolved"); if (dsz == 0) COMPILE_ERROR_UPSTREAM( "ptr_cast: cannot infer length of slice of zero-bit type"); if ((src_len * ssz) % dsz != 0) COMPILE_ERROR_UPSTREAM(src_info->size == IP_PTR_SIZE_SLICE ? "ptr_cast: slice length does not divide exactly into " "destination elements" : "ptr_cast: type does not divide exactly into destination " "elements"); return (uint32_t)(src_len * ssz / dsz); } // (part of) Sema.zig:ptrCastFull // Sema.ptrCastFull's comptime arm (`ct:`) for a maybe-optional pointer or // slice operand and destination, once the type checks passed. uint32_t semaPtrCastComptime( Zcu* zcu, uint32_t operand, uint32_t operand_ty, uint32_t dest_ty) { InternPool* ip = &zcu->intern_pool; if (operand >= ip->items_len) UNIMPLEMENTED("ptr_cast: comptime operand not interned"); bool dest_is_optional = ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_OPTIONAL); uint32_t dest_child = dest_is_optional ? ipOptionalChild(ip, dest_ty) : dest_ty; uint32_t src_child = ztIsZigTypeTag(ip, operand_ty, ZIG_TYPE_TAG_OPTIONAL) ? ipOptionalChild(ip, operand_ty) : operand_ty; // "if (operand_val.isUndef(zcu)) { if (!dest_ty.ptrAllowsZero(zcu)) // return sema.failWithUseOfUndef(...); return pt.undefRef(dest_ty); }" if (ipIsUndef(ip, operand)) { if (!ztPtrAllowsZero(ip, dest_ty)) COMPILE_ERROR_UPSTREAM("ptr_cast: use of undefined value"); return ipGetUndef(ip, dest_ty); } if (ip->item_tags[operand] == IP_TAG_OPT_NULL) { // "if (operand_val.isNull(zcu))": null at an optional destination, // else the zero address (`pt.ptrIntValue(dest_ty, 0)`). if (!ztPtrAllowsZero(ip, dest_ty)) COMPILE_ERROR_UPSTREAM("ptr_cast: null pointer casted to a type " "that does not allow zero"); return dest_is_optional ? ipGetOptNull(ip, dest_ty) : ipGetPtrInt(ip, dest_ty, 0); } // A non-null optional operand is its pointer (getCoerced re-wraps). if (ip->item_tags[operand] == IP_TAG_OPT_PAYLOAD) operand = ipPayloadVal(ip, operand); IpPtrInfo src_info, dest_info; if (!ztPtrOrSliceInfo(ip, src_child, &src_info) || !ztPtrOrSliceInfo(ip, dest_child, &dest_info)) UNIMPLEMENTED("ptr_cast: comptime operand type"); uint32_t ptr = operand; if (src_info.size == IP_PTR_SIZE_SLICE) { // "const ptr_val: Value = switch (src_info.flags.size) { .slice => // ...slice.ptr, ... }": the cast acts on the slice's pointer field, // never on the slice struct. if (ip->item_tags[operand] != IP_TAG_PTR_SLICE) UNIMPLEMENTED("ptr_cast: comptime slice operand kind"); ptr = ipPtrSlicePtr(ip, operand); } // "if (dest_align.compare(.gt, src_align)) { if // (ptr_val.getUnsignedInt(zcu)) |addr| ... 'pointer address 0x{X} is not // aligned to {d} bytes'": an integer address must already be aligned. A // function pointer's address mask (`fnPtrMaskOrNull`) is wasm's none. uint32_t dest_align, src_align; if (ipIsPtrInt(ip, ptr)) { if (!ztPtrInfoAlignLog2(zcu, ip, &dest_info, &dest_align) || !ztPtrInfoAlignLog2(zcu, ip, &src_info, &src_align)) UNIMPLEMENTED("ptr_cast: comptime alignment unresolved"); if (dest_align > src_align && dest_align < 64u && (ipPtrIntAddress(ip, ptr) & ((UINT64_C(1) << dest_align) - 1u)) != 0) COMPILE_ERROR_UPSTREAM("ptr_cast: pointer address is not aligned"); } uint32_t res; if (dest_info.size == IP_PTR_SIZE_SLICE) { uint32_t len = semaPtrCastDestSliceLen( zcu, operand, &src_info, dest_info.child); uint32_t many = ipRetypePtrValue(ip, ptr, ip->item_datas[dest_child]); if (many == IP_INDEX_NONE) UNIMPLEMENTED("ptr_cast: comptime operand shape"); res = ipGetPtrSlice(ip, dest_child, many, ipGetIntUsize(ip, len)); } else { res = ipRetypePtrValue(ip, ptr, dest_child); if (res == IP_INDEX_NONE) UNIMPLEMENTED("ptr_cast: comptime operand shape"); } return dest_is_optional ? ipGetOptPayload(ip, dest_ty, res) : res; } // Sema.zig:coerceEnumToUnion, PerThread.zig:unionValue, Type.zig:classify // Sema.coerceEnumToUnion's comptime-known branch (src/Sema.zig): // a comptime enum tag coerced to a tagged union yields the union value whose // payload is that field's one possible value -- and nothing else is legal. // // switch (field_ty.classify(zcu)) { // .one_possible_value => return .fromValue(try pt.unionValue( // union_ty, val, (try field_ty.onePossibleValue(pt)).?)), // .no_possible_value => ... "cannot initialize union field with // uninstantiable type" // else => ... "coercion from enum ... must initialize ... field ..." // } // // `Type.classify` and `Type.onePossibleValue` are the established // `ztTypeClass` / `ztOnePossibleValue` ports, and `unionTagFieldIndex` is // `ipEnumTagFieldIndex` (upstream indexes `union_obj.field_types` with the // Enum field index, so the two orders are one order). The union's payload // field types must already be resolved; callers that can force the lazy // resolution do so first. uint32_t coerceEnumToUnion( InternPool* ip, uint32_t union_ty, uint32_t enum_tag) { if (ipUnionTagUsage(ip, union_ty) != IP_UNION_TAG_USAGE_TAGGED) // "cannot coerce enum to untagged union". COMPILE_ERROR_UPSTREAM("coerceEnumToUnion: untagged union"); if (ipEnumTagType(ip, enum_tag) != ipUnionEnumTagType(ip, union_ty)) // Upstream reaches this arm only after `sema.coerce(enum_ty, inst)`, // so a foreign enum type never arrives here. UNIMPLEMENTED("coerceEnumToUnion: operand is a foreign enum type"); uint32_t field_index = ipEnumTagFieldIndex(ip, enum_tag); if (field_index == UINT32_MAX) // Upstream's `orelse`: "union '{f}' has no tag with value '{f}'". // `Zcu.unionTagFieldIndex` is `loaded_enum.tagValueIndex`, which // answers null for a value the tag enum does not declare -- reachable // whenever that enum is nonexhaustive. `ipUnionFieldType` does not // bounds-check, so without this the UINT32_MAX sentinel indexes the // union header itself. COMPILE_ERROR_UPSTREAM( "coerceEnumToUnion: union has no tag with this value"); uint32_t field_ty = ipUnionFieldType(ip, union_ty, field_index); if (field_ty == IP_INDEX_NONE) UNIMPLEMENTED("coerceEnumToUnion: union field type unresolved"); switch (ztTypeClass(ip, field_ty)) { case ZT_CLASS_OPV: { uint32_t opv = ztOnePossibleValue(ip, field_ty); if (opv == IP_INDEX_NONE) // Upstream's `.?` on `onePossibleValue`. UNIMPLEMENTED("coerceEnumToUnion: OPV field has no value"); return ipGetUnionValue(ip, union_ty, enum_tag, opv); } case ZT_CLASS_NPV: COMPILE_ERROR_UPSTREAM("coerceEnumToUnion: cannot initialize union " "field with uninstantiable type"); default: COMPILE_ERROR_UPSTREAM("coerceEnumToUnion: coercion from enum to " "union must initialize the field"); } ZIG1_TCC_RETURN(IP_INDEX_NONE); } // `coerceExtra`'s `.pointer` destination arm, `single_item` block // (src/Sema.zig): `*T` -> `*[1]T` -- a `.one` destination whose child // is a length-1 array of the source pointee, gated by upstream's own // `checkPtrAttributes`, then coerceCompatiblePtrs. Element match is // identity, the modelled subset of upstream's `coerceInMemoryAllowed`. // `zcu` is nullable, as for `semaCheckPtrAttributesTy`. static bool semaSingleItemPtrCoercible( Zcu* zcu, InternPool* ip, uint32_t dest_ty, uint32_t src_ty) { if (!ipItemTagIs(ip, src_ty, IP_TAG_TYPE_POINTER) || !ipItemTagIs(ip, dest_ty, IP_TAG_TYPE_POINTER) || ipPtrSize(ip, src_ty) != IP_PTR_SIZE_ONE || ipPtrSize(ip, dest_ty) != IP_PTR_SIZE_ONE || !semaCheckPtrAttributesTy(zcu, ip, dest_ty, src_ty)) return false; uint32_t src_child = ipPtrChild(ip, src_ty); uint32_t dst_child = ipPtrChild(ip, dest_ty); uint32_t dst_len, dst_elem, dst_sentinel; // "if (array_ty.zigTypeTag(zcu) != .array) break :single_item;", a // sentinel-terminated array too. return ztZigTypeTag(ip, dst_child) == ZIG_TYPE_TAG_ARRAY && ztArrayTypeInfo(ip, dst_child, &dst_len, &dst_elem, &dst_sentinel) && dst_len == 1u && dst_elem == src_child; } static bool semaIsPtrToAnyopaque( Zcu* zcu, InternPool* ip, uint32_t src_ty, uint32_t dest_ty); uint32_t semaCoerceExtraOpts(Zcu* zcu, InternPool* ip, uint32_t val, uint32_t dest_ty, bool report_err) { // "if (dest_ty.isGenericPoison()) return inst;" -- an unconstrained // result location (an `anytype` parameter, a bare `comptime ` // whose callee returns `@TypeOf(x)`): the value flows through with its // own type, and the enclosing binding applies the real coercion after. if (dest_ty == ZIR_REF_GENERIC_POISON_TYPE) { return val; } uint32_t operand = val; // `"if (dest_ty.eql(inst_ty, zcu)) return inst;"` -- e.g. an undefined // `?T` stored back as the payload of its own error union. if (ipIsUndef(ip, operand) && ip->item_datas[operand] == dest_ty) return operand; // `coerceExtra`'s `.optional` destination arm (src/Sema.zig): // "undefined sets the optional bit also to undefined", "null to ?T", // then "T to ?T" -- the child coercion wrapped by `wrapOptional`. A value // already of the destination type is the head's `dest_ty.eql(inst_ty)`. if (ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_OPTIONAL)) { if (operand == ZIR_REF_UNDEF) return ipGetUndef(ip, dest_ty); if (operand == ZIR_REF_NULL_VALUE) return ipGetOptNull(ip, dest_ty); if (operand < ip->items_len && (ip->item_tags[operand] == IP_TAG_OPT_NULL ? ip->item_datas[operand] == dest_ty : (ip->item_tags[operand] == IP_TAG_OPT_PAYLOAD && ipTypeOf(ip, operand) == dest_ty))) return operand; // An optional value of another optional type: the in-memory head's // "Optionals" / "Pointers" lines, then `getCoerced` (the payload // retyped). A refused pair falls to "T to ?T", which cannot take an // optional (`?u8` into `?u16` is a compile error). if (ipItemTagIs(ip, operand, IP_TAG_OPT_NULL) && coerceInMemoryAllowed( zcu, ip, dest_ty, ip->item_datas[operand], false)) return ipGetOptNull(ip, dest_ty); if (ipItemTagIs(ip, operand, IP_TAG_OPT_PAYLOAD) && coerceInMemoryAllowed( zcu, ip, dest_ty, ipTypeOf(ip, operand), false)) { uint32_t pl = semaCoerceExtraOpts(zcu, ip, ipPayloadVal(ip, operand), ipOptionalChild(ip, dest_ty), report_err); return pl == IP_INDEX_NONE ? pl : ipGetOptPayload(ip, dest_ty, pl); } // "T to ?T". Upstream runs the child with `.report_err = false` and // reports at the outer type; the child's own abort names the shape. uint32_t child = semaCoerceExtraOpts( zcu, ip, operand, ipOptionalChild(ip, dest_ty), report_err); return child == IP_INDEX_NONE ? child : ipGetOptPayload(ip, dest_ty, child); } // `coerceExtra`'s `.error_union` destination arm (src/Sema.zig). // An error-union value is accepted only by the in-memory head's "Error // Unions" line -- payloads in-memory coercible, sets coercible -- and is // then `getCoerced` (the payload retyped, the error re-interned at // `dest_ty`). Otherwise it falls to `wrapErrorUnionPayload` below like // any other `T`, which declines it unless the payload type takes it. An // error-set operand is `wrapErrorUnionSet`, whose `coerceExtra` // to the destination set is the in-memory "Error Sets" line; anything // else -- `undefined` included -- is `wrapErrorUnionPayload`, // which coerces to the payload type and interns `.{ .error_union = .{ // .val = .{ .payload = val } } }`. if (ztZigTypeTag(ip, dest_ty) == ZIG_TYPE_TAG_ERROR_UNION) { uint32_t eu_err_set = ipErrorUnionErrorSet(ip, dest_ty); if (operand < ip->items_len && (ip->item_tags[operand] == IP_TAG_ERROR_UNION_PAYLOAD || ip->item_tags[operand] == IP_TAG_ERROR_UNION_ERROR) && coerceInMemoryAllowed( zcu, ip, dest_ty, ipTypeOf(ip, operand), false)) { uint32_t d = ip->item_datas[operand]; if (ip->item_tags[operand] == IP_TAG_ERROR_UNION_ERROR) return ipGetErrorUnionError(ip, dest_ty, ip->extra[d + 1]); uint32_t pl = semaCoerceExtraOpts(zcu, ip, ip->extra[d + 1], ipErrorUnionPayload(ip, dest_ty), report_err); return pl == IP_INDEX_NONE ? pl : ipGetErrorUnionPayload(ip, dest_ty, pl); } if (operand < ip->items_len && ip->item_tags[operand] == IP_TAG_ERROR_SET_ERROR) { uint32_t src_set = ipTypeOf(ip, operand); uint32_t err_name = ipErrorName(ip, operand); if (!coerceInMemoryAllowedErrorSets(ip, eu_err_set, src_set)) { if (!report_err) return IP_INDEX_NONE; UNIMPLEMENTED("coerceExtra: error set not coercible to the " "error union's set"); } ipGetErrorSetError(ip, eu_err_set, err_name); return ipGetErrorUnionError(ip, dest_ty, err_name); } // zig1 resolves an inline call's inferred error set by analyzing the // generic callee itself, whose `!generic_poison` has no payload // value; upstream infers into `adhoc_inferred_error_set_type`. if (ipErrorUnionPayload(ip, dest_ty) == IP_INDEX_GENERIC_POISON_TYPE) UNIMPLEMENTED("resolveAdHocInferredErrorSet: a payload returned " "by a generic function analyzed for its inferred " "error set"); uint32_t pl = semaCoerceExtraOpts( zcu, ip, operand, ipErrorUnionPayload(ip, dest_ty), report_err); return pl == IP_INDEX_NONE ? pl : ipGetErrorUnionPayload(ip, dest_ty, pl); } // `undefined` coerces to any type by interning the typed undef // (Sema.coerceExtra's `.undefined` arm -> pt.undefValue(dest_ty); e.g. // `var i: usize = undefined;`). if (operand == ZIR_REF_UNDEF) return ipGetUndef(ip, dest_ty); // Array-pointer -> slice coercion (`g(&a)` where `g` takes `[]const T`): // the operand is a comptime `*const [N]T` pointer value, the destination a // `type_slice`. Sema.coerceArrayPtrToSlice builds the slice value while // preserving the operand's pointer base carrier. if (ztIsSlice(ip, dest_ty) && operand < ip->items_len) { uint32_t ap_ty = ipTypeOf(ip, operand); IpPtrInfo ap_info; uint32_t ap_len, ap_child, ap_sentinel; if (ipPtrInfo(ip, ap_ty, &ap_info) == IP_PTRI_OK && ztArrayTypeInfo( ip, ap_info.child, &ap_len, &ap_child, &ap_sentinel)) return coerceArrayPtrToSlice(ip, operand, dest_ty); } // Coercion to `type`: a type value (e.g. a generic `comptime T: type` // argument like `u32`) is already of type `type`, so this is the identity. // (Sema.coerce: in-memory-coercible / same type -> no-op.) if (dest_ty == ZIR_REF_TYPE_TYPE) return operand; // A Nav-backed bare enum literal is a real InternPool enum_literal item // (not the comptime evaluator's temporary sentinel). Coerce it to the // destination enum by field name exactly like Sema's enum-literal arm. if (ip->item_tags[operand] == IP_TAG_ENUM_LITERAL && ipIsEnumType(ip, dest_ty)) { uint32_t tag = ipEnumTagFromFieldName(ip, dest_ty, ip->item_datas[operand]); if (tag == IP_INDEX_NONE) UNIMPLEMENTED("coerceExtra: enum literal field not found"); return tag; } if (ip->item_tags[operand] == IP_TAG_ENUM_TAG && ipIsEnumType(ip, dest_ty) && ipEnumTagType(ip, operand) != dest_ty) COMPILE_ERROR_UPSTREAM("coerceExtra: enum payload type mismatch"); // Bare enum literal -> tagged union value when already-resolved union // metadata shows the named field has no payload. Block peer rewriting uses // the Zcu-aware wrapper above so alias payloads such as `none: Void` are // resolved before they reach this helper. if (ip->item_tags[operand] == IP_TAG_ENUM_LITERAL && dest_ty < ip->items_len && ip->item_tags[dest_ty] == IP_TAG_TYPE_UNION) { if (!ipUnionLayoutResolved(ip, dest_ty)) UNIMPLEMENTED("coerceExtra: enum literal union layout unresolved"); uint32_t union_val = semaCoerceEnumLiteralToVoidUnion(ip, operand, dest_ty); if (union_val == IP_INDEX_NONE) UNIMPLEMENTED( "coerceExtra: enum literal is not a void union field"); return union_val; } // Function value -> function pointer (`.add = myAdd` where the field type // is `*const fn(...) R`): a bare function reference has type `fn(...) R`; // coercing it to a `*const fn(...) R` pointer yields a pointer to the // function's Nav. Sema.coerce treats this as a pointer coercion whose // in-memory value is the function's address. Represent it as a `ptr_nav` // of the destination pointer type pointing at the function's owner Nav // (the same shape a directly-referenced `*const fn` callee pointer uses). // Only when the destination is a single-item pointer whose child is the // operand's function type; otherwise fall through. if ((ip->item_tags[operand] == IP_TAG_FUNC_DECL || ip->item_tags[operand] == IP_TAG_FUNC_INSTANCE) && ip->item_tags[dest_ty] == IP_TAG_TYPE_POINTER) { uint32_t func_ty = (ip->item_tags[operand] == IP_TAG_FUNC_DECL) ? ipFuncDeclType(ip, operand) : IP_INDEX_NONE; // `"const inst_as_ptr = try sema.analyzeNavRef(block, inst_src, // fn_nav); return sema.coerce(block, dest_ty, inst_as_ptr, // inst_src);"`: the Nav pointer carries the owner Nav's own // alignment, const-ness and address space, and the coercion is the // in-memory pointer line -- const-ness, alignment, and the child // through `coerceInMemoryAllowedFns`. Anything else falls through to // the loud decline below. uint32_t owner = func_ty != IP_INDEX_NONE ? ipFuncDeclOwnerNav(ip, operand) : IP_INDEX_NONE; if (func_ty != IP_INDEX_NONE && coerceInMemoryAllowed(zcu, ip, dest_ty, ipGetPtrType( ip, func_ty, semaNavDirectPtrFlags(ipGetNav(ip, owner))), false)) { // Queue the target function's body for analysis (no direct call // site otherwise reaches it). The drain dedups via `analyzed`. semaQueueFnPtrPending(ip, func_ty, owner); return ipGetPtrNav(ip, dest_ty, owner); } } // Error-set member -> another error-set type (most importantly a named // set member widened to `anyerror`): the in-memory head's "Error Sets" // line, a rule on the types -- `coerceExtra` has no error-set arm, so an // `error{A,B}` value is refused by `error{A}` whatever its name -- then // `getCoerced` re-interns the name in the destination set. if (ip->item_tags[operand] == IP_TAG_ERROR_SET_ERROR && ztZigTypeTag(ip, dest_ty) == ZIG_TYPE_TAG_ERROR_SET) { uint32_t err_name = ipErrorName(ip, operand); if (!coerceInMemoryAllowedErrorSets( ip, dest_ty, ipTypeOf(ip, operand))) UNIMPLEMENTED("coerceExtra: error not in error set"); return ipGetErrorSetError(ip, dest_ty, err_name); } // Operand type: dynamically-interned integer entries (e.g. the `for_len` // int_usize(N) result) carry their type in the InternPool; comptime // pointers/optionals/aggregates carry theirs on the item; bool/void/ // comptime_int literals fall back to the static preintern table. See // ipComptimeValueType (shared with the inferred comptime-cell store). uint32_t operand_ty = ipComptimeValueType(ip, operand); // Identity: operand already has the destination type (no coercion). This // covers `return true`/`return false` (bool) and matching sized ints. if (operand_ty == dest_ty) return operand; // The in-memory head (src/Sema.zig) for a comptime pointer: "if // (maybe_inst_val) |val| return .fromValue(try pt.getCoerced(val, // dest_ty));" on `coerceInMemoryAllowedPtrs`'s `.ptr_cast` -- a // widened const/volatile/alignment, a dropped sentinel. if (coerceInMemoryAllowedPtrs(zcu, ip, dest_ty, operand_ty, false)) { uint32_t im = ipRetypePtrValue(ip, operand, dest_ty); if (im != IP_INDEX_NONE) return im; } // coerceExtra's number arms for a comptime fixed-width integer source // (the comptime_int source has its own block below). The `.int` // destination arm (src/Sema.zig) rejects a value that does not fit // -- "`if (!val.intFitsInType(dest_ty, null, zcu)) ... return // sema.fail(block, inst_src, \"type '{f}' cannot represent integer value // '{f}'\", ...)`", which holds for undef -- and answers an undef with // "`.undef => .fromValue(try dest_ty.onePossibleValue(pt) orelse try // pt.undefValue(dest_ty))`"; the `.float` arm "`if // (val.isUndef(zcu)) return .fromValue(try pt.undefValue(dest_ty));`". // A zero-bit destination (the one-possible-value case) is not modelled. { uint32_t ni_bits, ni_fbits; bool ni_signed; bool ni_undef = ipIsUndef(ip, operand); if (ztIntInfo(ip, operand_ty, &ni_bits, &ni_signed)) { // The fits rejection is the one `valIntFitsInType` gate below. bool to_int = ztIntInfo(ip, dest_ty, &ni_bits, &ni_signed); if (ni_undef && ((to_int && ni_bits != 0) || ztSimpleFloatBits(dest_ty, &ni_fbits))) return ipGetUndef(ip, dest_ty); } } // Sema.coerceExtra's float-destination arm. Keep this before the // comptime_int -> int fallback below. uint32_t ci_float = semaCoerceToFloat(ip, operand, dest_ty); if (ci_float != IP_INDEX_NONE) return ci_float; // coerceExtra's `.int, .comptime_int` destination arm, `.float, // .comptime_float` source: "const result_val = try // sema.intFromFloat(block, inst_src, val, inst_ty, dest_ty, .exact);". { uint32_t ff_bits; bool ff_signed; if (operand < ip->items_len && semaTagIsFloat(ip->item_tags[operand]) && (dest_ty == ZIR_REF_COMPTIME_INT_TYPE || ztIntInfo(ip, dest_ty, &ff_bits, &ff_signed))) return intFromFloat(ip, operand, dest_ty, true); } // Comptime-known `*T -> *anyopaque`: the direct Sema path reaches the // same coerceCompatiblePtrs subset as ctCoerceToType and runtime stores. // Preserve the UAV's backing allocation, original alignment provenance // and byte offset while changing its semantic pointer type. if (semaIsPtrToAnyopaque(zcu, ip, operand_ty, dest_ty)) return ctPtrValueRetype(ip, operand, operand_ty, dest_ty, "coerceExtra: unsupported pointer to anyopaque carrier"); // The comptime half of the `single_item` arm: coerceCompatiblePtrs' // "`return Air.internedToRef((try pt.getCoerced(val, dest_ty)) // .toIntern());`". if (semaSingleItemPtrCoercible(NULL, ip, dest_ty, operand_ty)) { uint32_t si = ipRetypePtrValue(ip, operand, dest_ty); if (si == IP_INDEX_NONE) UNIMPLEMENTED("coerceExtra: single-item pointer carrier"); return si; } // The comptime half of the `src_array_ptr` block's `.c` and `.many` // legs (src/Sema.zig, "*[N]T to [*c]T" / "*[N]T to [*]T"): // coerceCompatiblePtrs's "`pt.getCoerced(val, dest_ty)`" behind the one // shared preamble the runtime dispatcher asks. IpPtrInfo c_dest, cp_src; if (semaSrcArrayPtrCoercible( NULL, ip, dest_ty, operand_ty, &c_dest, &cp_src) && c_dest.size != IP_PTR_SIZE_SLICE) { uint32_t pm = ipRetypePtrValue(ip, operand, dest_ty); if (pm == IP_INDEX_NONE) UNIMPLEMENTED("coerceExtra: array pointer carrier"); return pm; } // "coercion to C pointer" (src/Sema.zig): a `.null` source is a // `.int` zero address; a `.comptime_int` / `.int` one `coerceExtra`s to // `usize` (`isize` for a signed `.int`) and becomes a `.int` base // address; a `.pointer` source is `checkPtrAttributes`, an // in-memory coercible child, then coerceCompatiblePtrs. A slice source // (the sentinel-0 `analyzeSlicePtr` leg) decodes as no pointer here and // keeps the loud abort below. uint32_t ci_bits; // A comptime_int source converts as an unsigned address. bool signed_src = false; if (ipPtrInfo(ip, dest_ty, &c_dest) == IP_PTRI_OK && c_dest.size == IP_PTR_SIZE_C) { if (operand == ZIR_REF_NULL_VALUE) return ipGetPtrInt(ip, dest_ty, 0); if (operand_ty == ZIR_REF_COMPTIME_INT_TYPE || ztIntInfo(ip, operand_ty, &ci_bits, &signed_src)) { uint32_t addr = semaCoerceComptime(zcu, ip, operand, signed_src ? ZIR_REF_ISIZE_TYPE : ZIR_REF_USIZE_TYPE); uint64_t addr_word; if (!ipTryReadIntValue(ip, addr, &addr_word)) UNIMPLEMENTED("coerceExtra: C pointer address unreadable"); // `getCoerced`'s `@intCast` (src/InternPool.zig) of a // negative address is illegal behavior: a compile error here. if (signed_src && (int64_t)addr_word < 0) COMPILE_ERROR_UPSTREAM( "coerceExtra: negative address coerced to C pointer"); return ipGetPtrInt(ip, dest_ty, (uint32_t)addr_word); } if (ipPtrInfo(ip, operand_ty, &cp_src) == IP_PTRI_OK && checkPtrAttributes(NULL, ip, &c_dest, &cp_src) && coerceInMemoryAllowed( NULL, ip, c_dest.child, cp_src.child, !c_dest.is_const)) { uint32_t cp = ipRetypePtrValue(ip, operand, dest_ty); if (cp == IP_INDEX_NONE) UNIMPLEMENTED("coerceExtra: C pointer carrier"); return cp; } } // `Sema.coerceExtra`'s `.int, .comptime_int` destination arm is one rule // over both integer sources -- `".int, .comptime_int => { if (try // sema.resolveValue(inst)) |val| { ... if (!(try // val.intFitsInType(dest_ty, null, zcu))) break :int; return // pt.getCoerced(val, dest_ty); } ... }"` -- so the gate stands in front // of both arms below rather than inside one of them. It guards both the // `comptime_int` source and a sized source: without it `const z: u8 = // 300;` would be truncated to 44, and `const a: u8 = // @as(u9, 300);` would answer 44 where upstream reports "type 'u8' // cannot represent integer value '300'"). if (!valIntFitsInType(ip, operand, dest_ty)) COMPILE_ERROR_UPSTREAM("coerce: type cannot represent integer value"); // comptime_int -> fixed-width int: the comptime-known value is re-interned // at the destination type. In the static regime this yields a preinterned // sized value; anything else is the dynamic-InternPool crux. if (operand_ty == ZIR_REF_COMPTIME_INT_TYPE) { uint32_t res = semaCoercePreinternedComptimeInt(ip, operand, dest_ty); if (res == IP_INDEX_NONE) { // Not a preinterned 0/1: read the interned comptime_int value // (e.g. the `2`/`3` tuple elements of an inline `for (.{1,2,3})`, // or the comptime_int -1 of `@as(u32, @bitCast(@as(i32, -1)))`) // and re-intern at the destination int type. Use the success-flag // reader so a legitimate -1 (whose sign-extension equals the // UINT64_MAX "unreadable" answer) is coerced rather than misread // — semaCoerceComptimeIntLiteral truncates to the dest // width, yielding 0xffffffff for u32 exactly as Sema does. uint64_t mag; if (ipTryReadIntValue(ip, operand, &mag)) { if (ipIntValueIsNegative(ip, operand, mag)) res = semaCoerceComptimeIntI64Literal( ip, (int64_t)mag, dest_ty); else res = semaCoerceComptimeIntLiteral(ip, mag, dest_ty); } else { res = semaCoerceComptimeIntBig(ip, operand, dest_ty); } } if (res == IP_INDEX_NONE) UNIMPLEMENTED("coerce: comptime_int -> non-preinterned int"); return res; } // Fixed-width int -> a different fixed-width int, comptime-known (e.g. an // `@intFromEnum` tag value, typed at the enum's `u2` tag type, re-interned // as the function's wider `u32` return type): `pt.getCoerced(val, // dest_ty)` is `intValue_big` of the value's own big integer, sign // included -- a negative `i8` widened past 64 bits stays negative // (re-interning a u64 magnitude here would zero-extend it). { uint32_t cgi_bits; bool cgi_is_signed; uint64_t cgi_space[2]; BigIntConst cgi_v; if (operand < ip->items_len && ipTagIsIntValue((IpTag)ip->item_tags[operand]) && (dest_ty == ZIR_REF_COMPTIME_INT_TYPE || ztIntInfoInRange(ip, dest_ty, &cgi_bits, &cgi_is_signed)) && valToBigInt(ip, operand, cgi_space, &cgi_v)) return ipGetInt(ip, dest_ty, cgi_v); } // A comptime `&array` nav pointer -> a slice (`.buffer = &backing` where // the field is `[]align(@alignOf(usize)) u8`). See // semaCoerceArrayNavPtrToSlice; any other shape falls to the loud abort. { uint32_t ps = semaCoerceArrayNavPtrToSlice(ip, operand, operand_ty, dest_ty); if (ps != IP_INDEX_NONE) return ps; } // A comptime `*const tuple` value -> `[]const T` via Sema's // coerceTupleToSlicePtrs route: materialize the tuple elements as an // array, take a UAV pointer to that array, then reuse // coerceArrayPtrToSlice. { uint32_t ts = semaCoerceComptimeTuplePtrToSlice( zcu, ip, operand, operand_ty, dest_ty); if (ts != IP_INDEX_NONE) return ts; } // A comptime array value -> another array type, dropping the source's // sentinel (a `[3:0]u8` value stored through a `*[3]u8`). // See semaCoerceArrayValueToArray; any other shape falls to the loud // abort. { uint32_t av = semaCoerceArrayValueToArray( zcu, ip, operand, operand_ty, dest_ty); if (av != IP_INDEX_NONE) return av; } { uint32_t ptr = semaCoerceSentinelArrayPtrToPlain(ip, operand, operand_ty, dest_ty, "coerceExtra: sentinel array pointer outside exact subset"); if (ptr != IP_INDEX_NONE) return ptr; } { uint32_t func = semaCoerceFuncValue(ip, operand, dest_ty); if (func != IP_INDEX_NONE) return func; } // coerceExtra's `.one` pointer destination arm, `"// []T to *[n]T"` // (0.17), on a comptime slice: attributes, an array child of the slice's // length, an in-memory coercible element (here: the same type) and a // matching sentinel, then `"const array_ptr = try // pt.sliceToArrayPtr(slice);"` through coerceCompatiblePtrs. { IpPtrInfo sap_dest, sap_src; IpArrayInfo sap_arr; if (ztIsSlice(ip, operand_ty) && ipPtrInfo(ip, dest_ty, &sap_dest) == IP_PTRI_OK && sap_dest.size == IP_PTR_SIZE_ONE && ipArrayInfo(ip, sap_dest.child, &sap_arr) == IP_ARR_OK && !sap_arr.is_vector && ztPtrOrSliceInfo(ip, operand_ty, &sap_src) && semaCheckPtrAttributesTy(zcu, ip, dest_ty, operand_ty) && sap_src.child == sap_arr.child && (sap_arr.sentinel == IP_INDEX_NONE || sap_arr.sentinel == sap_src.sentinel)) { if (ipIsUndef(ip, operand) || ipIsUndef(ip, ipPtrSliceLen(ip, operand))) { if (!report_err) return IP_INDEX_NONE; COMPILE_ERROR_UPSTREAM("coerceExtra: slice with undefined " "length cannot cast into array " "pointer type"); } if (ipReadIntValue(ip, ipPtrSliceLen(ip, operand)) != sap_arr.len) { if (!report_err) return IP_INDEX_NONE; COMPILE_ERROR_UPSTREAM("coerceExtra: slice length cannot " "cast into array pointer type"); } uint32_t sap = ipRetypePtrValue( ip, zptSliceToArrayPtr(ip, operand), dest_ty); if (sap == IP_INDEX_NONE) UNIMPLEMENTED("coerceExtra: slice pointer carrier"); return sap; } } // "if (inst_ty.zigTypeTag(zcu) == .@\"enum\") return // sema.coerceEnumToUnion(...)" -- coerceExtra's `.@"union"` destination // arm (src/Sema.zig). if (ip->item_tags[dest_ty] == IP_TAG_TYPE_UNION && ipIsEnumType(ip, operand_ty) && operand < ip->items_len && ip->item_tags[operand] == IP_TAG_ENUM_TAG) return coerceEnumToUnion(ip, dest_ty, operand); // The `.array` / `.vector` destination arms' tuple source: `"if // (inst_ty.isTuple(zcu)) return sema.coerceTupleToArray(...)"` -- // equal lengths, then every `tupleField` coerced to the destination's // child. { IpArrayInfo tta_dest; if (ztIsTuple(ip, operand_ty) && ipArrayInfo(ip, dest_ty, &tta_dest) == IP_ARR_OK) { uint32_t n = ipTupleFieldCount(ip, operand_ty); if (n != tta_dest.len) COMPILE_ERROR_UPSTREAM("coerceTupleToArray: length mismatch"); uint32_t* els = zig1Malloc((n ? n : 1u) * sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { uint32_t field, field_ty; tupleFieldValByIndex(ip, operand, i, &field, &field_ty); els[i] = semaCoerceExtraOpts( zcu, ip, field, tta_dest.child, report_err); if (els[i] == IP_INDEX_NONE) { free(els); return IP_INDEX_NONE; } } uint32_t agg = ipGetAggregate(ip, dest_ty, els, n); free(els); return agg; } } // The `.vector` destination arm (and `.array`'s `.vector` source): // `coerceArrayLike` (src/Sema.zig) on a comptime value -- equal // lengths, then "`const coerced = try sema.coerce(block, dest_elem_ty, // elem_ref, elem_src);`" for every element. A `repeated` value keeps // its one element. { IpArrayInfo cal_dest, cal_src; if ((ipItemTagIs(ip, operand, IP_TAG_REPEATED) || ipItemTagIs(ip, operand, IP_TAG_AGGREGATE) || ipItemTagIs(ip, operand, IP_TAG_BYTES)) && ipArrayInfo(ip, dest_ty, &cal_dest) == IP_ARR_OK && ipArrayInfo(ip, operand_ty, &cal_src) == IP_ARR_OK && (ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_VECTOR) || ztIsZigTypeTag(ip, operand_ty, ZIG_TYPE_TAG_VECTOR))) { if (cal_dest.len != cal_src.len) COMPILE_ERROR_UPSTREAM( "coerceExtra: array-like length mismatch"); uint32_t d = ip->item_datas[operand]; if (ipItemTagIs(ip, operand, IP_TAG_REPEATED)) { uint32_t el = semaCoerceExtraOpts( zcu, ip, ip->extra[d + 1], cal_dest.child, report_err); if (el == IP_INDEX_NONE) return el; // The repeated scan covers the destination's sentinel. if (cal_dest.sentinel == IP_INDEX_NONE || cal_dest.sentinel == el) return ipGetRepeated(ip, dest_ty, el); } uint32_t n = cal_dest.len; uint32_t* els = zig1Malloc((n ? n : 1u) * sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { els[i] = semaCoerceExtraOpts(zcu, ip, valElemValue(ip, operand, i, "coerceArrayLike"), cal_dest.child, report_err); if (els[i] == IP_INDEX_NONE) { free(els); return IP_INDEX_NONE; } } uint32_t agg = ipGetAggregate(ip, dest_ty, els, n); free(els); return agg; } } // The `.@"struct"` destination arm's "if (inst_ty.isTuple(zcu) and // dest_ty.isTuple(zcu)) return sema.coerceTupleToTuple(...)" on a // comptime value: "if (src_field_count > dest_field_count) return // error.NotCoercible;", each field coerced to the destination field type, // a comptime destination field requiring its own value, a missing field // taking its default ("missing tuple field" without one), then // `pt.aggregateValue(tuple_ty, field_vals)`. if (ztIsTuple(ip, dest_ty) && ztIsTuple(ip, operand_ty)) { uint32_t n = ipTupleFieldCount(ip, dest_ty); uint32_t src_n = ipTupleFieldCount(ip, operand_ty); if (src_n > n) { if (!report_err) return IP_INDEX_NONE; COMPILE_ERROR_UPSTREAM("coerceExtra: tuple has too many " "fields"); } uint32_t* els = zig1Malloc((n ? n : 1u) * sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { uint32_t def = ipTupleFieldValue(ip, dest_ty, i); if (i >= src_n) { if (def == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM("coerceExtra: missing tuple " "field"); els[i] = def; continue; } uint32_t field, field_ty; tupleFieldValByIndex(ip, operand, i, &field, &field_ty); els[i] = semaCoerceExtraOpts( zcu, ip, field, ipTupleFieldType(ip, dest_ty, i), report_err); if (els[i] == IP_INDEX_NONE) { free(els); return IP_INDEX_NONE; } if (def != IP_INDEX_NONE && els[i] != def) COMPILE_ERROR_UPSTREAM("coerceExtra: value stored in " "comptime field does not match its " "default"); } uint32_t agg = zptAggregateValue(ip, dest_ty, els, n); free(els); return agg; } // The `.@"enum"` destination arm, `.@"union"` source: "union to its own // tag type" (src/Sema.zig) is `unionToTag`, whose comptime half // is "`return Air.internedToRef(un_val.unionTag(zcu).?.toIntern())`" -- // the tag the union value carries. if (ipItemTagIs(ip, operand, IP_TAG_UNION_VALUE) && ipItemTagIs(ip, operand_ty, IP_TAG_TYPE_UNION) && ipIsEnumType(ip, dest_ty) && ipUnionTagUsage(ip, operand_ty) == IP_UNION_TAG_USAGE_TAGGED && ipEnsureGeneratedUnionTag(ip, operand_ty) == dest_ty) return ipUnionValueTag(ip, operand); // `CoerceOpts.report_err = false`: "if (!opts.report_err) return // error.NotCoercible;". if (!report_err) return IP_INDEX_NONE; // Name the value, its type and the destination, as the runtime half // does, so an abort says which coercion is missing. fprintf(stderr, "coerceExtra: %s : %s -> %s\n", operand < ip->items_len ? ipTagName((IpTag)ip->item_tags[operand]) : "", operand_ty < ip->items_len ? ipTagName((IpTag)ip->item_tags[operand_ty]) : "", ipTagName((IpTag)ip->item_tags[dest_ty])); UNIMPLEMENTED("coerceExtra: unsupported coercion"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // (part of) Sema.zig:coerceExtra // coerceExtra's comptime half for an interned operand: semaCoerceExtraOpts // with `report_err`. `coerce` picks it or semaCoerceExtraRuntime. uint32_t semaCoerceComptime( Zcu* zcu, InternPool* ip, uint32_t val, uint32_t dest_ty) { return semaCoerceExtraOpts(zcu, ip, val, dest_ty, true); } // Sema.zig:resolveAddressSpace // The comptime std.lang.AddressSpace `value`; zig1's pointer types carry only // `.generic`. uint32_t resolveAddressSpace(Zcu* zcu, uint32_t value) { uint32_t as = resolveStdLangEnum(zcu, value, "AddressSpace"); if (as != IP_PTR_ADDRSPACE_GENERIC) UNIMPLEMENTED("resolveAddressSpace: a non-generic address space"); return as; } // Sema.zig:resolveStdLangEnum // Sema.resolveStdLangEnum: the declaration field index of a comptime // `std.lang.` enum operand -- `@branchHint`'s hint, `@setFloatMode`'s // mode, the atomics' order, `@atomicRmw`'s and `@reduce`'s op. The field order // is never written down here: the value reports its own index, so lib/std // stays the single source of it. An `enum_literal` is already coerced to an // `enum_tag` (`"const coerced = try sema.coerce(block, ty, air_ref, src);"`). uint32_t resolveStdLangEnum(Zcu* zcu, uint32_t value, const char* name) { InternPool* ip = &zcu->intern_pool; uint32_t enum_ty = getStdLangType(zcu, &name, 1); if (value >= ip->items_len || ip->item_tags[value] != IP_TAG_ENUM_TAG || ipEnumTagType(ip, value) != enum_ty) UNIMPLEMENTED("resolveStdLangEnum: operand is not a tag of the enum"); return ipEnumTagFieldIndex(ip, value); } static bool semaAtomicRmwNonXchgElemSupported( const InternPool* ip, uint32_t ty) { if (ty == ZIR_REF_USIZE_TYPE || ty == ZIR_REF_ISIZE_TYPE) return true; if (ty >= ip->items_len) return false; switch (ip->item_tags[ty]) { case IP_TAG_TYPE_INT_UNSIGNED: case IP_TAG_TYPE_INT_SIGNED: return true; case IP_TAG_SIMPLE_TYPE: { uint32_t c_bits; bool c_signed; return ztCIntTypeInfoFE(ty, &c_bits, &c_signed); } case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: return ipStructIsPacked(ip, ty) && semaAtomicRmwNonXchgElemSupported( ip, ipStructBackingInt(ip, ty)); default: return false; } } static bool semaAtomicRmwXchgElemSupported(const InternPool* ip, uint32_t ty) { if (ty == ZIR_REF_BOOL_TYPE || semaAtomicRmwNonXchgElemSupported(ip, ty)) return true; if (ty >= ip->items_len) return false; uint8_t tag = ip->item_tags[ty]; if (tag == IP_TAG_TYPE_POINTER) return true; if (tag == IP_TAG_TYPE_OPTIONAL) return ztTypeIsPtrLikeOptional(ip, ty); if (ipTagIsAnyStruct(tag)) return ipStructIsPacked(ip, ty); return ipIsEnumType(ip, ty); } static bool semaBodyIsMaybeErrorUnwrapPanic( const Zir* zir, const uint32_t* body, uint32_t body_len) { bool saw_panic = false; for (uint32_t i = 0; i < body_len; i++) { switch (zir->inst_tags[body[i]]) { case ZIR_INST_SAVE_ERR_RET_INDEX: case ZIR_INST_DBG_STMT: case ZIR_INST_STR: case ZIR_INST_AS_NODE: break; case ZIR_INST_PANIC: saw_panic = true; break; default: return false; } } return saw_panic; } static uint32_t semaRuntimeIntCast(AirInstList* insts, U32List* block, uint32_t dest_ty, uint32_t operand_air) { AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = dest_ty; data.pair.b = operand_air; uint32_t idx = semaAirInstAppend(insts, AIR_INST_INT_CAST, data); u32ListAppend(block, idx); return AIR_REF_INST_BASE + idx; } // A `ty_pl` instruction whose payload is `Air.Bin{ lhs, rhs }` (slice, // ptr_add, ptr_elem_ptr). static uint32_t semaEmitTyPlBin(AirInstList* insts, U32List* block, U32List* air_extra, AirInstTag tag, uint32_t ty, uint32_t lhs, uint32_t rhs) { uint32_t payload = air_extra->len; u32ListAppend(air_extra, lhs); u32ListAppend(air_extra, rhs); AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = ty; data.pair.b = payload; uint32_t idx = semaAirInstAppend(insts, tag, data); u32ListAppend(block, idx); return AIR_REF_INST_BASE + idx; } static uint32_t semaRuntimeBinOp(AirInstList* insts, U32List* block, AirInstTag tag, uint32_t lhs, uint32_t rhs) { AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = lhs; data.pair.b = rhs; uint32_t idx = semaAirInstAppend(insts, tag, data); u32ListAppend(block, idx); return AIR_REF_INST_BASE + idx; } // (part of) Sema.zig:analyzeCmpUnionTag, Type.zig:classify // Sema.analyzeCmpUnionTag's no-possible-value shortcut (src/Sema.zig: // 15348-15355): // // if (sema.resolveValue(coerced_tag)) |enum_val| { // if (enum_val.isUndef(zcu)) return .undef_bool; // const field_ty = union_ty.unionFieldType(enum_val, zcu).?; // if (field_ty.classify(zcu) == .no_possible_value) return // .bool_false; // } // // It runs before `cmpSelf`, i.e. before the eq/neq split, so a tag whose // payload type has no possible value makes both `==` and `!=` answer false -- // never the negation. It fires on the comptime-known tag alone, so the union // operand may still be runtime. `Type.classify` is the established // `ztTypeClass` port. True here means "the whole comparison is // `.bool_false`". static bool semaCmpUnionTagNoPossibleValue( InternPool* ip, uint32_t union_ty, uint32_t coerced_tag) { if (coerced_tag >= ip->items_len || ip->item_tags[coerced_tag] != IP_TAG_ENUM_TAG) return false; uint32_t field_ty = ipUnionFieldType(ip, union_ty, ipEnumTagFieldIndex(ip, coerced_tag)); if (field_ty == IP_INDEX_NONE) // Upstream's `.?`: the coerced tag always names a field of this // union, so an unresolved field type is a zig1 invariant break. UNIMPLEMENTED("analyzeCmpUnionTag: union field type unresolved"); return ztTypeClass(ip, field_ty) == ZT_CLASS_NPV; } static uint32_t semaTypeInfoActiveTagValue(Zcu* zcu, uint32_t val) { if (!ctIsTypeInfoValue(zcu, val)) UNIMPLEMENTED("zirTypeInfo: typeInfo value - not std.lang.Type"); const InternPool* ip = &zcu->intern_pool; uint32_t tag_val = ipUnionValueTag(ip, val); if (tag_val >= ip->items_len || ip->item_tags[tag_val] != IP_TAG_ENUM_TAG) UNIMPLEMENTED( "zirTypeInfo: typeInfo value - active tag is not enum_tag"); return tag_val; } // ── @typeInfo, generic bindings, type names, comparisons ──────────────────── const char* semaTypeInfoActiveTagName(Zcu* zcu, uint32_t val) { InternPool* ip = &zcu->intern_pool; uint32_t tag_val = semaTypeInfoActiveTagValue(zcu, val); uint32_t enum_ty = ipEnumTagType(ip, tag_val); uint32_t field_idx = ipEnumTagFieldIndex(ip, tag_val); if (field_idx == UINT32_MAX) UNIMPLEMENTED("zirTypeInfo: typeInfo value - active tag has no field"); return ipStringBytes(ip, ipEnumFieldName(ip, enum_ty, field_idx)); } uint32_t semaTypeInfoPayload( Zcu* zcu, uint32_t val, const char* field, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; const char* active = semaTypeInfoActiveTagName(zcu, val); if (strcmp(field, active) != 0) COMPILE_ERROR_UPSTREAM("typeInfo value: inactive union field"); uint32_t ty = ipTypeOf(ip, val); uint32_t field_name = ipGetOrPutString(ip, field, (uint32_t)strlen(field)); uint32_t field_idx = ipUnionFieldIndex(ip, ty, field_name); if (field_idx == UINT32_MAX) UNIMPLEMENTED( "zirTypeInfo: typeInfo value - active field not in union type"); uint32_t field_ty = ipUnionFieldType(ip, ty, field_idx); if (out_ty) *out_ty = field_ty; return ipUnionValuePayload(ip, val); } // A whole-ZIR inst map with every instruction `.none`. uint32_t* semaInstMapAlloc(const Zir* zir) { uint32_t* map = zig1Malloc((zir->inst_len + 1u) * sizeof(uint32_t)); memset(map, 0xff, (zir->inst_len + 1u) * sizeof(uint32_t)); return map; } // Resolve a ZIR ref to its flowing value: a ref below ZIR_REF_START_INDEX is a // preinterned InternPool index; otherwise it names a prior instruction, whose // resolved value is looked up in `inst_values`. uint32_t semaResolveRef(uint32_t ref, const uint32_t* inst_values) { if (ref < ZIR_REF_START_INDEX) return ref; return inst_values[ref - ZIR_REF_START_INDEX]; } // Sema.zig:analyzeRef, PerThread.zig:ptrType // `Sema.analyzeRef`'s runtime half for a default-aligned operand: the `*const // T` type first, then `"const mut_ptr_type = try pt.ptrType(.{ .child = // operand_ty.toIntern(), .flags = .{ ... .is_const = false ... } }); const // alloc = try block.addTy(.alloc, mut_ptr_type);"`, the store, and the cast // back to `*const T`. Returns the `*const T` AIR ref; `*out_ptr_ty` its type. static uint32_t semaAnalyzeRefRuntime(InternPool* ip, AirInstList* insts, U32List* block, uint32_t operand, uint32_t operand_ty, uint32_t* out_ptr_ty) { uint32_t const_ptr_ty = ipGetPtrType(ip, operand_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t mut_ptr_ty = ipGetPtrType(ip, operand_ty, IP_PTR_FLAGS_MUT_ONE); AirInstData d; memset(&d, 0, sizeof(d)); d.pair.a = mut_ptr_ty; uint32_t alloc_idx = semaAirInstAppend(insts, AIR_INST_ALLOC, d); u32ListAppend(block, alloc_idx); memset(&d, 0, sizeof(d)); d.pair.a = AIR_REF_INST_BASE + alloc_idx; d.pair.b = operand; u32ListAppend(block, semaAirInstAppend(insts, AIR_INST_STORE, d)); memset(&d, 0, sizeof(d)); d.pair.a = const_ptr_ty; d.pair.b = AIR_REF_INST_BASE + alloc_idx; uint32_t cast_idx = semaAirInstAppend(insts, AIR_INST_PTR_CAST, d); u32ListAppend(block, cast_idx); *out_ptr_ty = const_ptr_ty; return AIR_REF_INST_BASE + cast_idx; } // What `Sema.zirShl` / `zirShr` decide about the amount before a runtime // shift is emitted. typedef enum { ZPT_SHIFT_EMIT, // emit the AIR shift ZPT_SHIFT_LHS, // "=> return lhs" (a zero amount, or a zero comptime lhs) ZPT_SHIFT_UNDEF, // `.shl_sat` of an undef amount/lhs: `undefRef(lhs_ty)` } SemaShiftCheck; // (part of) Value.zig:order // One comptime amount lane against `bits`: `"switch (Value.order(rhs_val, // .zero_comptime_int, zcu)) { .gt => { if (air_tag != .shl_sat) { ... if // (rhs_bigint.orderAgainstScalar(bits) != .lt) return // sema.failWithTooLargeShiftAmount(...); } }, .eq => ..., .lt => return // sema.failWithNegativeShiftAmount(...) }"`. Returns true for a positive lane. static bool semaShiftAmountLane( const InternPool* ip, ZirInstTag tag, uint32_t lane, uint32_t bits) { uint64_t space[2]; BigIntConst v; if (!valToBigInt(ip, lane, space, &v)) UNIMPLEMENTED("zirShl: shl/shr: unreadable comptime amount"); if (bigIntEqlZero(v)) return false; if (!v.positive) COMPILE_ERROR_UPSTREAM("shift by negative amount"); uint64_t amt; if (tag != ZIR_INST_SHL_SAT && (!bigIntToU64(v, &amt) || amt >= bits)) COMPILE_ERROR_UPSTREAM("shift amount is too large for the LHS type"); return true; } // (part of) Sema.zig:zirShl // `Sema.zirShl`'s / `zirShr`'s checks between // `checkVectorizableBinaryOperands` and the runtime instruction, for a shift // whose LHS or amount is runtime: // `.shl_sat`'s `checkIntType` of the amount's scalar type; a comptime amount // that is undef, zero, negative or too large (per lane for a vector); a // runtime amount's "shift by signed type" (`.shl_sat`) and "LHS of shift must // be a fixed-width integer type, or RHS must be comptime-known"; and a // comptime LHS that is undef (`.shl_sat`) or zero. `rhs` is an IP value or an // AIR ref, `lhs` likewise. // `.shl_sat` keeps an undef operand (`undefRef`); every other shift // refuses it. static SemaShiftCheck semaShiftUndefOperand(bool sat) { if (!sat) COMPILE_ERROR_UPSTREAM("use of undefined value here causes " "illegal behavior"); return ZPT_SHIFT_UNDEF; } static SemaShiftCheck semaShiftCheck(InternPool* ip, ZirInstTag tag, uint32_t lhs_ty, uint32_t lhs, uint32_t rhs_ty, uint32_t rhs) { bool sat = tag == ZIR_INST_SHL_SAT; uint32_t scalar_rhs_ty = ipScalarType(ip, rhs_ty); uint32_t bits; bool is_signed; if (sat && scalar_rhs_ty != ZIR_REF_COMPTIME_INT_TYPE && !ztIntInfo(ip, scalar_rhs_ty, &bits, &is_signed)) COMPILE_ERROR_UPSTREAM("expected integer type"); // checkIntType if (!semaIsAirInstRef(rhs)) { if (rhs >= ip->items_len) UNIMPLEMENTED("zirShl: shl/shr: unreadable comptime amount"); if (ipIsUndef(ip, rhs)) return semaShiftUndefOperand(sat); if (!ztIntInfo(ip, ipScalarType(ip, lhs_ty), &bits, &is_signed)) UNIMPLEMENTED("zirShl: shl/shr: non-integer lhs"); IpArrayInfo vi; if (ipArrayInfo(ip, rhs_ty, &vi) == IP_ARR_OK && vi.is_vector) { bool any_positive = false; for (uint32_t i = 0; i < vi.len; i++) { uint32_t lane = valElemValue(ip, rhs, i, "zirShl: amount lane"); if (ipIsUndef(ip, lane)) { semaShiftUndefOperand(sat); continue; } if (semaShiftAmountLane(ip, tag, lane, bits)) any_positive = true; } return any_positive ? ZPT_SHIFT_EMIT : ZPT_SHIFT_LHS; } return semaShiftAmountLane(ip, tag, rhs, bits) ? ZPT_SHIFT_EMIT : ZPT_SHIFT_LHS; } if (sat && ztIntInfo(ip, scalar_rhs_ty, &bits, &is_signed) && is_signed) COMPILE_ERROR_UPSTREAM("shift by signed type"); if (ipScalarType(ip, lhs_ty) == ZIR_REF_COMPTIME_INT_TYPE) COMPILE_ERROR_UPSTREAM("LHS of shift must be a fixed-width integer " "type, or RHS must be comptime-known"); if (lhs < ip->items_len) { if (ipIsUndef(ip, lhs)) return semaShiftUndefOperand(sat); // `"if (lhs_val.compareAllWithZero(.eq, zcu)) return lhs;"` uint64_t space[2]; BigIntConst v; if (valToBigInt(ip, lhs, space, &v) && bigIntEqlZero(v)) return ZPT_SHIFT_LHS; } return ZPT_SHIFT_EMIT; } // `semaResolveRef`'s type counterpart: a preinterned ref's static type, else // the `inst_types` entry of the instruction it names (SemaCtx.inst_types). static uint32_t semaResolveRefType(uint32_t ref, const uint32_t* inst_types) { if (ref < ZIR_REF_START_INDEX) return ipTypeOfPreinterned(ref); return inst_types[ref - ZIR_REF_START_INDEX]; } // `sema.typeOf(ref)` for a ZIR ref whose resolved value is `val`: a runtime // AIR operand's recorded type, else the comptime value's own. Total; the // comparison arms' partial twin is semaCmpOperandTypeOrNone. static uint32_t semaResolveRefValType(const InternPool* ip, uint32_t ref, uint32_t val, const uint32_t* inst_types) { return semaIsAirInstRef(val) ? semaResolveRefType(ref, inst_types) : ipTypeOf(ip, val); } static uint32_t semaResolveStructInitContainerRef(uint32_t container_ref, const uint32_t* inst_values, const InlineCtx* ictx) { uint32_t container_val = semaResolveRef(container_ref, inst_values); if (semaIsAirInstRef(container_val) && ictx->swc_active && container_ref >= ZIR_REF_START_INDEX) { uint32_t container_inst = container_ref - ZIR_REF_START_INDEX; if (container_inst == ictx->swc_switch_inst) { // `continue :sw .{ .field = ... }` uses the labeled switch's own // ZIR instruction as the result-location type for the continue // operand's struct/union initializer. The switch slot is // simultaneously the AIR merge block for ordinary break lowering, // so resolve these struct-init container references through the // active switch_continue context instead of reading the // merge-block AIR ref as a type. return ictx->swc_raw_ty; } } return container_val; } typedef struct { const uint32_t* param_insts; const uint32_t* values; const uint32_t* types; uint32_t nparams; } GenericBindings; static uint32_t semaGenericBindingValueInst( const GenericBindings* gb, uint32_t inst) { if (gb == NULL) // no bindings: nothing is bound return INST_VALUE_UNSET; for (uint32_t i = 0; i < gb->nparams; i++) if (gb->param_insts[i] == inst) return gb->values[i]; return INST_VALUE_UNSET; } static uint32_t semaGenericBindingValueRef( const GenericBindings* gb, uint32_t ref) { if (ref < ZIR_REF_START_INDEX) return ref; return semaGenericBindingValueInst(gb, ref - ZIR_REF_START_INDEX); } static uint32_t semaGenericBindingTypeInst( const GenericBindings* gb, uint32_t inst) { if (gb == NULL) return IP_INDEX_NONE; for (uint32_t i = 0; i < gb->nparams; i++) if (gb->param_insts[i] == inst) return gb->types[i]; return IP_INDEX_NONE; } static uint32_t semaResolveGenericPtrSentinelRef(InternPool* ip, const Zir* zir, uint32_t ref, const GenericBindings* gb, uint32_t elem_ty) { uint32_t raw; if (ref < ZIR_REF_START_INDEX) { raw = ref; } else { uint32_t bound = semaGenericBindingValueRef(gb, ref); if (bound != INST_VALUE_UNSET) { if (semaIsAirInstRef(bound)) UNIMPLEMENTED( "analyzeCall: generic type, runtime ptr sentinel"); raw = bound; } else { uint32_t inst = ref - ZIR_REF_START_INDEX; if (zir->inst_tags[inst] == ZIR_INST_INT) { uint64_t n = zir->inst_datas[inst].int_val; uint32_t coerced = semaCoerceComptimeIntLiteral(ip, n, elem_ty); if (coerced == IP_INDEX_NONE) UNIMPLEMENTED("analyzeCall: generic type, ptr sentinel " "int coercion"); return coerced; } if (zir->inst_tags[inst] == ZIR_INST_AS_NODE) { ZirInstAs extra; zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &extra); return semaResolveGenericPtrSentinelRef( ip, zir, extra.operand, gb, elem_ty); } if (zir->inst_tags[inst] == ZIR_INST_BLOCK_COMPTIME) { ZirInstBlockComptime extra; uint32_t extra_end = zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &extra); if (extra.body_len == 1) { uint32_t last = zir->extra[extra_end]; if (zir->inst_tags[last] == ZIR_INST_BREAK_INLINE) return semaResolveGenericPtrSentinelRef(ip, zir, zir->inst_datas[last].break_data.operand, gb, elem_ty); } } if (zir->inst_tags[inst] == ZIR_INST_OPTIONAL_PAYLOAD_SAFE || zir->inst_tags[inst] == ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE) { uint32_t opt_ref = zir->inst_datas[inst].un_node.operand; uint32_t opt_bound = semaGenericBindingValueRef(gb, opt_ref); if (opt_bound != INST_VALUE_UNSET) { if (semaIsAirInstRef(opt_bound)) UNIMPLEMENTED("analyzeCall: generic type, runtime " "optional ptr sentinel"); if (opt_bound >= ip->items_len || ip->item_tags[opt_bound] != IP_TAG_OPT_PAYLOAD) UNIMPLEMENTED("analyzeCall: generic type, optional " "ptr sentinel not payload"); return semaCoerceComptime( NULL, ip, ipPayloadVal(ip, opt_bound), elem_ty); } } UNIMPLEMENTED( "analyzeCall: generic type, ptr sentinel expression"); } } return semaCoerceComptime(NULL, ip, raw, elem_ty); } void semaFormatTypeNameDiagnostic( InternPool* ip, uint32_t ty, TypeNameWriter* w) { bool saved = w->diagnostic; w->diagnostic = true; ztFormatTypeName(ip, ty, w); w->diagnostic = saved; } // Coerce a resolved value (`semaResolveRef` result) to `dest_ty`, returning // the resulting preinterned InternPool value index (also its AIR Ref). Handles // interned operands (via coerceExtra). static uint32_t semaCmpResolveEnumConst( InternPool* ip, uint32_t enum_ty, uint32_t val) { if (!semaRefIsInterned(val)) return IP_INDEX_NONE; uint32_t idx = val; if (idx >= ip->items_len) return IP_INDEX_NONE; if (ip->item_tags[idx] == IP_TAG_ENUM_LITERAL) return ipEnumTagFromFieldName(ip, enum_ty, ip->item_datas[idx]); if (ip->item_tags[idx] == IP_TAG_ENUM_TAG && ipEnumTagType(ip, idx) == enum_ty) return idx; return IP_INDEX_NONE; } // (part of) Sema.zig:compareIntsOnlyPossibleResult // Simulate Sema.compareIntsOnlyPossibleResult's side effect of interning // minInt and maxInt for the given integer type. Sema always creates these // IP entries when one CMP operand is comptime-known, regardless of whether // the comparison result can be determined at comptime. static void semaCmpCreateMinMax(InternPool* ip, uint32_t ty) { uint32_t mn, mx; ztIntTypeMinMax(ip, ty, &mn, &mx); } // Sema.zig:checkSentinelType void checkSentinelType(const InternPool* ip, uint32_t elem_ty) { if (!ztTypeIsSelfComparableEq(ip, elem_ty)) COMPILE_ERROR_UPSTREAM("checkSentinelType: non-scalar sentinel type"); } // (part of) Sema.zig:zirCmpEq, Sema.zig:analyzeCmp, Type.zig:isSelfComparable // Equality of two comptime packed-container values, shared by both evaluators // (`Size{ .resizing = true } == @as(Size, @bitCast(@as(usize, 1)))`). // Sema.zirCmpEq matches none of its special tags for a struct operand and // falls into Sema.analyzeCmp, whose resolvePeerTypes yields the shared packed // type; `Type.isSelfComparable` admits `.@"struct", .@"union"` only as // "is_equality_cmp and ty.containerLayout(zcu) == .@"packed"" (src/Type.zig), // and Sema.cmpSelf -> compareAll -> compareScalar ends at // "Value.eql(coerced_lhs, coerced_rhs, ty)", i.e. "a.toIntern() == // b.toIntern()" -- interned-index identity, which `ipGetBitpack`'s dedup // makes a value comparison. A `.bitpack` exists only for a packed container, // so the layout test is implied by the tag. Differing peer types are a stock // compile error (resolvePeerTypes), not an equality answer. // // Returns false when the pair is not two packed-container values, leaving the // caller's other arms untouched; ordering operators are the caller's own // guard (each evaluator spells its comparison operator differently). static bool semaCmpPackedEq( const InternPool* ip, uint32_t a, uint32_t b, bool* out_eq) { if (a < ZIR_REF_START_INDEX || a >= ip->items_len || b < ZIR_REF_START_INDEX || b >= ip->items_len) return false; if (ip->item_tags[a] != IP_TAG_BITPACK || ip->item_tags[b] != IP_TAG_BITPACK) return false; if (ipBitpackType(ip, a) != ipBitpackType(ip, b)) UNIMPLEMENTED("analyzeCmp: packed peer types differ"); *out_eq = (a == b); return true; } // (part of) Sema.zig:zirCmpEq // `Sema.zirCmpEq`'s `.error_set` pair with both values comptime-known: // `"return if ((lkey.err.name == rkey.err.name) == (op == .eq)) .bool_true // else .bool_false;"` -- the names, whatever the two sets are. False when // either operand is not an `.err` value. Shared by both evaluators. static bool semaCmpErrorSetEq( const InternPool* ip, uint32_t a, uint32_t b, bool* out_eq) { if (a >= ip->items_len || b >= ip->items_len || ip->item_tags[a] != IP_TAG_ERROR_SET_ERROR || ip->item_tags[b] != IP_TAG_ERROR_SET_ERROR) return false; *out_eq = ipErrorName(ip, a) == ipErrorName(ip, b); return true; } // (part of) Sema.zig:resolveComptimeKnownAllocPtr // Sema.resolveComptimeKnownAllocPtr + finishResolveComptimeKnownAllocPtr: a // const alloc every store of which was comptime-known becomes a const pointer // to an anonymous value, the alloc and its stores dead `ptr_from_int(alloc_ty, // 0)`s. IP_INDEX_NONE (nothing rewritten) when the alloc is not // comptime-known. static uint32_t semaFinishComptimeKnownAllocPtr(Zcu* zcu, AirInstList* insts, const U32List* block, const U32List* air_extra, uint32_t alloc_ref, uint32_t alloc_ty) { InternPool* ip = &zcu->intern_pool; U32List stores = { 0 }; uint32_t val = semaResolveComptimeKnownAllocValue( zcu, insts, block, air_extra, alloc_ref, UINT32_MAX, NULL, &stores); if (val == IP_INDEX_NONE || valCanMutateComptimeVarState(ip, val)) { free(stores.items); return IP_INDEX_NONE; } AirInstData dead; memset(&dead, 0, sizeof(dead)); dead.pair.a = alloc_ty; dead.pair.b = ZIR_REF_ZERO_USIZE; insts->tags[alloc_ref - AIR_REF_INST_BASE] = AIR_INST_PTR_FROM_INT; insts->datas[alloc_ref - AIR_REF_INST_BASE] = dead; for (uint32_t s = 0; s < stores.len; s++) { // A payload pointer keeps its own type for the dead uses below it. AirInstData dead_store = dead; AirInstTag st = (AirInstTag)insts->tags[stores.items[s]]; if (st == AIR_INST_OPTIONAL_PAYLOAD_PTR_SET || st == AIR_INST_ERRUNION_PAYLOAD_PTR_SET) dead_store.pair.a = insts->datas[stores.items[s]].pair.a; insts->tags[stores.items[s]] = AIR_INST_PTR_FROM_INT; insts->datas[stores.items[s]] = dead_store; } free(stores.items); uint32_t const_ptr_ty = makePtrTyConst(ip, alloc_ty); uint32_t ptr = ipRetypePtrValue(ip, ipGetPtrUav(ip, alloc_ty, val), const_ptr_ty); if (ptr == IP_INDEX_NONE) UNIMPLEMENTED("resolveComptimeKnownAllocPtr: comptime-known alloc " "pointer not retypable"); return ptr; } // (part of) Sema.zig:zirPtrType, Sema.zig:zirArrayTypeSentinel // The sentinel half of Sema.zirPtrType and Sema.zirArrayTypeSentinel: // `"const sentinel = try sema.coerce(block, elem_type, uncasted_sentinel, // sentinel_src); const sentinel_val = try sema.resolveConstDefinedValue(...); // if (sentinel_val.canMutateComptimeVarState(zcu)) return // sema.failWithContainsReferenceToComptimeVar(...)"` and `checkSentinelType`. // `coerce` is where an out-of-range or mistyped sentinel is refused. uint32_t semaSentinelValue( Zcu* zcu, InternPool* ip, uint32_t raw, uint32_t elem_ty) { uint32_t val = semaCoerceComptime(zcu, ip, raw, elem_ty); if (ipIsUndef(ip, val)) COMPILE_ERROR_UPSTREAM("sentinel: undefined value"); if (valCanMutateComptimeVarState(ip, val)) COMPILE_ERROR_UPSTREAM("sentinel: contains reference to comptime var"); checkSentinelType(ip, elem_ty); return val; } // (part of) Sema.zig:zirTypeInfo // Sema.zirTypeInfo's `.@"fn"` `ret_ty_is_generic`: `"ret_ty.toIntern() == // .generic_poison_type or (ret_ty.zigTypeTag(zcu) == .error_union and // ret_ty.errorUnionPayload(zcu).toIntern() == .generic_poison_type)"` -- a // generic `!T` return is generic too. bool semaFnRetTyIsGeneric(const InternPool* ip, uint32_t rt) { return rt == IP_INDEX_GENERIC_POISON_TYPE || (rt < ip->items_len && ip->item_tags[rt] == IP_TAG_TYPE_ERROR_UNION && ipErrorUnionPayload(ip, rt) == IP_INDEX_GENERIC_POISON_TYPE); } // Sema.zig:validateArrayElemType // Sema.validateArrayElemType: `"array of opaque type '{f}' not allowed"` and // `"array of 'noreturn' not allowed"`. void validateArrayElemType(const InternPool* ip, uint32_t elem_ty) { if (ztIsZigTypeTag(ip, elem_ty, ZIG_TYPE_TAG_OPAQUE)) COMPILE_ERROR_UPSTREAM("array of opaque type not allowed"); if (elem_ty == ZIR_REF_NORETURN_TYPE) COMPILE_ERROR_UPSTREAM("array of 'noreturn' not allowed"); } // Reverse a comparison operator (Sema uses op.reverse() when folding against // the other operand's type in compareIntsOnlyPossibleResult). static ZirInstTag semaCmpReverseOp(ZirInstTag op) { switch (op) { case ZIR_INST_CMP_LT: return ZIR_INST_CMP_GT; case ZIR_INST_CMP_GT: return ZIR_INST_CMP_LT; case ZIR_INST_CMP_LTE: return ZIR_INST_CMP_GTE; case ZIR_INST_CMP_GTE: return ZIR_INST_CMP_LTE; default: return op; // eq / neq are symmetric } } // (part of) Value.zig:compareHetero // Apply an integer comparison (unsigned) — the direct value comparison used // once the bounds analysis is undetermined (Value.compareHetero equivalent). // True for every InternPool tag that holds a float value, `comptime_float` // included -- `Type.isFloat`'s `.float, .comptime_float` for the value side. bool semaTagIsFloat(uint8_t tag) { return tag == IP_TAG_FLOAT_F16 || tag == IP_TAG_FLOAT_F32 || tag == IP_TAG_FLOAT_F64 || tag == IP_TAG_FLOAT_F80 || tag == IP_TAG_FLOAT_F128 || tag == IP_TAG_FLOAT_C_LONGDOUBLE_F128 || tag == IP_TAG_FLOAT_COMPTIME_FLOAT; } // Sema.zig:cmpNumeric, Value.zig:floatCast // `Sema.cmpNumeric`'s "One exception to heterogeneous comparison: // comptime_float needs to coerce to fixed-width float": `"const lhs = if // (lhs_ty_tag == .comptime_float and rhs_ty_tag == .float) try // sema.coerce(block, rhs_ty, uncasted_lhs, lhs_src) else uncasted_lhs;"` and // its mirror for the rhs, ahead of every comparison rule. Both operands are // comptime values here, so the coercion is `Value.floatCast`. static void semaCmpNumericCoerceComptimeFloat( InternPool* ip, uint32_t* lhs, uint32_t* rhs) { if (*lhs >= ip->items_len || *rhs >= ip->items_len) return; uint8_t lt = ip->item_tags[*lhs]; uint8_t rt = ip->item_tags[*rhs]; if (lt == IP_TAG_FLOAT_COMPTIME_FLOAT && semaTagIsFloat(rt) && rt != IP_TAG_FLOAT_COMPTIME_FLOAT) *lhs = semaCoerceToFloat(ip, *lhs, ipTypeOf(ip, *rhs)); else if (rt == IP_TAG_FLOAT_COMPTIME_FLOAT && semaTagIsFloat(lt) && lt != IP_TAG_FLOAT_COMPTIME_FLOAT) *rhs = semaCoerceToFloat(ip, *rhs, ipTypeOf(ip, *lhs)); if (*lhs == IP_INDEX_NONE || *rhs == IP_INDEX_NONE) UNIMPLEMENTED("cmpNumeric: comptime_float has no storage at the " "fixed-width float type"); } static uint32_t semaBoolRef(bool b) { return b ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; } static bool semaValueIsOptional(const InternPool* ip, uint32_t v) { return ipItemTagIs(ip, v, IP_TAG_OPT_NULL) || ipItemTagIs(ip, v, IP_TAG_OPT_PAYLOAD); } // (part of) Sema.zig:compareIntsOnlyPossibleResult // Sema.compareIntsOnlyPossibleResult: the comptime-known integer `lhs` // against every value of the fixed-width integer type `rhs_ty`. Interns // `rhs_ty.minInt` then `.maxInt` (the byte-exact side effect) and returns 1 // (definitely true), 0 (definitely false), or -1 (result depends on the exact // value). Returns -2 for a non-integer `rhs_ty` (nothing interned). static int semaCmpIntsOnlyPossible( InternPool* ip, uint32_t lhs, ZirInstTag op, uint32_t rhs_ty) { uint32_t bits; bool is_signed; if (rhs_ty >= ip->items_len || !ztIntInfo(ip, rhs_ty, &bits, &is_signed)) return -2; uint32_t min_rhs, max_rhs; ztIntTypeMinMax(ip, rhs_ty, &min_rhs, &max_rhs); uint64_t l_space[2], min_space[2], max_space[2]; BigIntConst l, mn, mx; if (!valToBigInt(ip, lhs, l_space, &l) || !valToBigInt(ip, min_rhs, min_space, &mn) || !valToBigInt(ip, max_rhs, max_space, &mx)) return -1; // `"if (min_rhs.toIntern() == max_rhs.toIntern()) { // RHS is // effectively comptime-known. return try Value.compareHeteroSema(lhs_val, // op, min_rhs, pt); }"` if (min_rhs == max_rhs) return valCmpApplyOrder(op, bigIntOrder(l, mn)) ? 1 : 0; int against_min = bigIntOrder(l, mn); int against_max = bigIntOrder(l, mx); switch (op) { case ZIR_INST_CMP_EQ: if (against_min < 0 || against_max > 0) return 0; break; case ZIR_INST_CMP_NEQ: if (against_min < 0 || against_max > 0) return 1; break; case ZIR_INST_CMP_LT: if (against_min < 0) return 1; if (against_max >= 0) return 0; break; case ZIR_INST_CMP_GT: if (against_max > 0) return 1; if (against_min <= 0) return 0; break; case ZIR_INST_CMP_LTE: if (against_min <= 0) return 1; if (against_max > 0) return 0; break; case ZIR_INST_CMP_GTE: if (against_max >= 0) return 1; if (against_min < 0) return 0; break; default: break; } return -1; } static uint32_t semaCmpUnionTagComptime( Zcu* zcu, ZirInstTag op, uint32_t un, uint32_t tg); // (part of) Sema.zig:zirCmpEq, Value.zig:compareHetero, Value.zig:order // `Sema.zirCmpEq` -> `analyzeCmp` -> `cmpNumeric` / `cmpSelf` -> // `compareAll` -> `compareScalar` -> `Value.compareHetero` / `Value.order` / // `Value.eql`, for two comptime-known interned operands: One rule both // frames ask once their own representation-specific arms have passed. // `Value.eql` is `"a.toIntern() == b.toIntern()"` after both operands are // coerced to the peer type, so an equality is answered by value identity // at that type: the pointer key, the optional's payload, the error name, // interned identity for types, enums, bools and `void`. Returns // `.bool_true`, `.bool_false` or `.undef_bool`; a pair it does not model // aborts loudly. uint32_t semaCmpComptime(Zcu* zcu, ZirInstTag op, uint32_t lhs, uint32_t rhs) { InternPool* ip = &zcu->intern_pool; bool is_eq = op == ZIR_INST_CMP_EQ || op == ZIR_INST_CMP_NEQ; bool want_eq = op == ZIR_INST_CMP_EQ; if (lhs >= ip->items_len || rhs >= ip->items_len) UNIMPLEMENTED("analyzeCmp: comptime operand is not an interned value"); // zirCmpEq: `"if (lhs_ty_tag == .null and rhs_ty_tag == .null)"`, then // `analyzeIsNull` for null against an optional. if (is_eq && (lhs == ZIR_REF_NULL_VALUE || rhs == ZIR_REF_NULL_VALUE)) { uint32_t other = lhs == ZIR_REF_NULL_VALUE ? rhs : lhs; if (other == ZIR_REF_NULL_VALUE) return semaBoolRef(want_eq); if (!semaValueIsOptional(ip, other)) UNIMPLEMENTED("analyzeCmp: comptime null against a non-optional"); return semaBoolRef(ipItemTagIs(ip, other, IP_TAG_OPT_NULL) == want_eq); } // zirCmpEq: a union against an enum or enum literal is // analyzeCmpUnionTag. if (is_eq) { uint32_t un_cmp = semaCmpUnionTagComptime(zcu, op, lhs, rhs); if (un_cmp == IP_INDEX_NONE) un_cmp = semaCmpUnionTagComptime(zcu, op, rhs, lhs); if (un_cmp != IP_INDEX_NONE) return un_cmp; } // analyzeCmp: an error union against an error set: `"if // (lhs_val.errorUnionIsPayload(zcu)) return .bool_false;"`, else the // error code against the set's value. if (is_eq && ((ipItemTagIs(ip, lhs, IP_TAG_ERROR_SET_ERROR) && (ipItemTagIs(ip, rhs, IP_TAG_ERROR_UNION_ERROR) || ipItemTagIs(ip, rhs, IP_TAG_ERROR_UNION_PAYLOAD))) || (ipItemTagIs(ip, rhs, IP_TAG_ERROR_SET_ERROR) && (ipItemTagIs(ip, lhs, IP_TAG_ERROR_UNION_ERROR) || ipItemTagIs(ip, lhs, IP_TAG_ERROR_UNION_PAYLOAD))))) { uint32_t eu = ipItemTagIs(ip, lhs, IP_TAG_ERROR_SET_ERROR) ? rhs : lhs; uint32_t es = eu == rhs ? lhs : rhs; // `.bool_false` for `!=` too, as upstream answers it. if (ipItemTagIs(ip, eu, IP_TAG_ERROR_UNION_PAYLOAD)) return ZIR_REF_BOOL_FALSE; bool same = ipErrorName(ip, eu) == ipErrorName(ip, es); return semaBoolRef(same == want_eq); } // An optional operand: analyzeCmp's `resolvePeerTypes` gives the peer // `?T`, both operands are coerced to it (`sema.coerce`, with its range // and exactness checks -- a compile error, as upstream), and cmpSelf's // `Value.eql` is interned identity at that type (so an optional // `-0.0` is not `0.0`). The coercion precedes cmpSelf's undef answer. uint32_t l_ty = ipTypeOf(ip, lhs), r_ty = ipTypeOf(ip, rhs); if (ztIsZigTypeTag(ip, l_ty, ZIG_TYPE_TAG_OPTIONAL) || ztIsZigTypeTag(ip, r_ty, ZIG_TYPE_TAG_OPTIONAL)) { uint32_t tys[2] = { l_ty, r_ty }; uint32_t vals[2] = { lhs, rhs }; uint32_t peer = semaTypeofPeerResolve(zcu, tys, vals, 2u); if (!ztTypeIsSelfComparable(ip, peer, is_eq)) COMPILE_ERROR_UPSTREAM( "analyzeCmp: comptime operator not allowed for type"); // A pointer child: `getCoerced` re-types a pointer keeping its // `base_addr` and `byte_offset`, so identity at the peer is the // pointer arm's key identity of the payloads. if (ipItemTagIs(ip, ipOptionalChild(ip, peer), IP_TAG_TYPE_POINTER) && !ipIsUndef(ip, lhs) && !ipIsUndef(ip, rhs)) { bool l_null = ipItemTagIs(ip, lhs, IP_TAG_OPT_NULL); bool r_null = ipItemTagIs(ip, rhs, IP_TAG_OPT_NULL); if (l_null || r_null) return semaBoolRef((l_null && r_null) == want_eq); return semaCmpComptime(zcu, op, semaValueIsOptional(ip, lhs) ? ipPayloadVal(ip, lhs) : lhs, semaValueIsOptional(ip, rhs) ? ipPayloadVal(ip, rhs) : rhs); } uint32_t cl = semaCoerceComptime(zcu, ip, lhs, peer); uint32_t cr = semaCoerceComptime(zcu, ip, rhs, peer); if (ipIsUndef(ip, cl) || ipIsUndef(ip, cr)) return ZIR_REF_UNDEF_BOOL; return semaBoolRef((cl == cr) == want_eq); } // cmpNumeric's `"// Compare ints: const vs. undefined (or vice versa)"` // -- a defined integer against an integer-typed undef asks // compareIntsOnlyPossibleResult -- then cmpSelf's `"if (v.isUndef(zcu)) // return .undef_bool;"`. bool l_undef = lhs == ZIR_REF_UNDEF || ipIsUndef(ip, lhs); bool r_undef = rhs == ZIR_REF_UNDEF || ipIsUndef(ip, rhs); if (l_undef != r_undef) { uint32_t defined = l_undef ? rhs : lhs; uint32_t undef = l_undef ? lhs : rhs; uint32_t defined_ty = ipTypeOf(ip, defined); uint32_t bits; bool is_signed; if (undef != ZIR_REF_UNDEF && (defined_ty == ZIR_REF_COMPTIME_INT_TYPE || ztIntInfo(ip, defined_ty, &bits, &is_signed)) && ztIntInfo(ip, ipTypeOf(ip, undef), &bits, &is_signed)) { int res = semaCmpIntsOnlyPossible(ip, defined, l_undef ? semaCmpReverseOp(op) : op, ipTypeOf(ip, undef)); if (res >= 0) return semaBoolRef(res != 0); } } if (l_undef || r_undef) return ZIR_REF_UNDEF_BOOL; // Numeric pair: cmpNumeric's NaN answer, then Value.order -- // `toFloat(f128)` when either side is a float, `toBigInt` otherwise. bool l_float = semaTagIsFloat(ip->item_tags[lhs]); bool r_float = semaTagIsFloat(ip->item_tags[rhs]); uint64_t l_space[2], r_space[2]; BigIntConst l_big, r_big; bool l_int = valToBigInt(ip, lhs, l_space, &l_big) && !l_float; bool r_int = valToBigInt(ip, rhs, r_space, &r_big) && !r_float; if ((l_float || l_int) && (r_float || r_int)) { if (!l_float && !r_float) return semaBoolRef( valCmpApplyOrder(op, bigIntOrder(l_big, r_big))); uint32_t cl = lhs, cr = rhs; semaCmpNumericCoerceComptimeFloat(ip, &cl, &cr); if (valIsNan(ip, cl) || valIsNan(ip, cr)) return semaBoolRef(op == ZIR_INST_CMP_NEQ); if (cl == cr) // one interned non-NaN value orders `.eq` to itself return semaBoolRef(valCmpApplyOrder(op, 0)); return semaBoolRef(valCmpNumApply(op, valCmpFloatOperand( ip, cl, "analyzeCmp: comptime float lhs unsupported"), valCmpFloatOperand( ip, cr, "analyzeCmp: comptime float rhs unsupported"))); } // A function body against a pointer: resolvePeerTypes picks the pointer // and coerceExtra's `.@"fn"` operand arm makes it its owner Nav's ref. if (is_eq) { bool l_fn = ztIsZigTypeTag(ip, ipTypeOf(ip, lhs), ZIG_TYPE_TAG_FN); bool r_fn = ztIsZigTypeTag(ip, ipTypeOf(ip, rhs), ZIG_TYPE_TAG_FN); if (l_fn != r_fn && ztIsZigTypeTag( ip, ipTypeOf(ip, l_fn ? rhs : lhs), ZIG_TYPE_TAG_POINTER)) { if (l_fn) lhs = semaCoerceComptime(zcu, ip, lhs, ipTypeOf(ip, rhs)); else rhs = semaCoerceComptime(zcu, ip, rhs, ipTypeOf(ip, lhs)); } } // A C pointer against an integer: the `.c_ptr` peer is the pointer, and // coerceExtra makes the integer its address. { uint32_t lt = ipTypeOf(ip, lhs), rt = ipTypeOf(ip, rhs); if (ztIsCPtr(ip, lt) && ipIsIntegerType(ip, rt)) rhs = semaCoerceComptime(zcu, ip, rhs, lt); else if (ztIsCPtr(ip, rt) && ipIsIntegerType(ip, lt)) lhs = semaCoerceComptime(zcu, ip, lhs, rt); } // Pointers: identity of the re-typed key for `==`/`!=`. An ordering is // `isSelfComparable`'s `".pointer => !ty.isSlice(zcu) and // (is_equality_cmp or ty.isCPtr(zcu))"` (a C-pointer peer), then // `Value.order`'s `toBigInt`, which only an integer address has. IpPtrKey lk, rk; if (ipPtrKey(ip, lhs, &lk) && ipPtrKey(ip, rhs, &rk)) { if (is_eq) { bool same = lk.tag == rk.tag && lk.a == rk.a && lk.b == rk.b && lk.off == rk.off; return semaBoolRef(same == want_eq); } IpPtrInfo li, ri; if (ipPtrInfo(ip, ipTypeOf(ip, lhs), &li) != IP_PTRI_OK || ipPtrInfo(ip, ipTypeOf(ip, rhs), &ri) != IP_PTRI_OK) UNIMPLEMENTED( "analyzeCmp: comptime pointer operand type unreadable"); if (li.size != IP_PTR_SIZE_C && ri.size != IP_PTR_SIZE_C) COMPILE_ERROR_UPSTREAM("analyzeCmp: comptime operator not allowed " "for a non-C pointer"); if (lk.tag != IP_TAG_PTR_INT || rk.tag != IP_TAG_PTR_INT) UNIMPLEMENTED( "analyzeCmp: comptime ordering of non-address pointers"); int ord = lk.off < rk.off ? -1 : (lk.off > rk.off ? 1 : 0); return semaBoolRef(valCmpApplyOrder(op, ord)); } if (!is_eq) UNIMPLEMENTED("analyzeCmp: comptime ordering of a non-numeric pair"); // `Value.eql` on the remaining self-comparable scalars: one interned // item is one value, and types, bools, `void`, functions, enum values and // enum literals intern uniquely, so identity at the peer type is value // equality. An enum literal against an enum tag is first coerced to the // tag's enum type. uint32_t lc = lhs, rc = rhs; if (ipItemTagIs(ip, lc, IP_TAG_ENUM_TAG) && ipItemTagIs(ip, rc, IP_TAG_ENUM_LITERAL)) rc = semaCoerceComptime(zcu, ip, rc, ipTypeOf(ip, lc)); else if (ipItemTagIs(ip, rc, IP_TAG_ENUM_TAG) && ipItemTagIs(ip, lc, IP_TAG_ENUM_LITERAL)) lc = semaCoerceComptime(zcu, ip, lc, ipTypeOf(ip, rc)); bool eq; if (semaCmpErrorSetEq(ip, lc, rc, &eq) || semaCmpPackedEq(ip, lc, rc, &eq)) return semaBoolRef(eq == want_eq); if (lc == rc) { // cmpSelf's gate precedes the answer: an array, a slice or an error // union is not self-comparable, even against itself. if (!ztTypeIsSelfComparable(ip, ipTypeOf(ip, lc), true)) COMPILE_ERROR_UPSTREAM( "analyzeCmp: comptime operator not allowed for type"); return semaBoolRef(want_eq); } bool l_id = ipIndexIsType(ip, lc) || ipTypeOf(ip, lc) == ZIR_REF_BOOL_TYPE || lc == ZIR_REF_VOID_VALUE || ipItemTagIs(ip, lc, IP_TAG_ENUM_TAG) || ipItemTagIs(ip, lc, IP_TAG_ENUM_LITERAL) || ztIsZigTypeTag(ip, ipTypeOf(ip, lc), ZIG_TYPE_TAG_FN); if (!l_id || ipTypeOf(ip, lc) != ipTypeOf(ip, rc)) UNIMPLEMENTED("analyzeCmp: comptime unmodelled operand pair"); // Distinct identities of an identity-compared type are unequal. return semaBoolRef(!want_eq); } // A comptime operand word that names an InternPool value. static bool semaCmpIsInternedValue(const InternPool* ip, uint32_t val) { return val < ip->items_len; } static bool semaCmpReadKnownInt( const InternPool* ip, uint32_t val, uint64_t* out_n, bool* out_neg) { if (!semaCmpIsInternedValue(ip, val)) return false; uint32_t idx = val; if (!ipTryReadIntValue(ip, idx, out_n)) return false; *out_neg = ipIntValueIsNegative(ip, idx, *out_n); return true; } static uint32_t semaCoerceCallArgumentValue( Zcu* zcu, uint32_t val, uint32_t dest_ty) { InternPool* ip = &zcu->intern_pool; if (dest_ty == ZIR_REF_TYPE_TYPE && val != INST_VALUE_UNSET) { uint32_t ptr = val; if (ptr < ip->items_len && ip->item_tags[ptr] == IP_TAG_PTR_NAV) { uint32_t loaded_ty = IP_INDEX_NONE; uint32_t loaded = pointerDeref(zcu, ptr, &loaded_ty); if (loaded_ty != ZIR_REF_TYPE_TYPE || loaded == IP_INDEX_NONE || !ipIndexIsType(ip, loaded)) UNIMPLEMENTED( "call: ptr_nav argument to type param is not a type"); return loaded; } } return semaCoerceComptime(zcu, ip, val, dest_ty); } static uint32_t semaFinalizeComptimeCallResult( InternPool* ip, uint32_t res, uint32_t ret_ty, const char* what) { if (res == ZIR_REF_NULL_VALUE && ztIsZigTypeTag(ip, ret_ty, ZIG_TYPE_TAG_OPTIONAL)) return ipGetOptNull(ip, ret_ty); if (ret_ty == ZIR_REF_COMPTIME_INT_TYPE) { // The value convention is the index itself, so the _U32 arm is in the // pass-through list below (the tag test is the shape gate the // UNIMPLEMENTED below depends on). if (ip->item_tags[res] == IP_TAG_INT_COMPTIME_INT_U32 || ip->item_tags[res] == IP_TAG_INT_POSITIVE || ip->item_tags[res] == IP_TAG_INT_NEGATIVE || ip->item_tags[res] == IP_TAG_INT_COMPTIME_INT_I32) return res; UNIMPLEMENTED(what); } if (ret_ty == ZIR_REF_TYPE_TYPE) return res; if (res >= ZIR_REF_START_INDEX && (ip->item_tags[res] == IP_TAG_OPT_NULL || ip->item_tags[res] == IP_TAG_OPT_PAYLOAD || ip->item_tags[res] == IP_TAG_ERROR_UNION_PAYLOAD || ip->item_tags[res] == IP_TAG_ERROR_UNION_ERROR)) return res; return semaCoerceComptime(NULL, ip, res, ret_ty); } // ── Pointer types, comptime-only types and layout resolution ──────────────── static uint32_t semaPtrFromIntValue( Zcu* zcu, uint32_t result_ty, uint64_t address) { InternPool* ip = &zcu->intern_pool; bool optional; uint32_t flags; uint32_t ptr_ty = ztPtrFromIntDestInfo(ip, result_ty, &optional, &flags); uint32_t ptd = ip->item_datas[ptr_ty]; if (!optional && address == 0 && (flags & IP_PTR_FLAGS_IS_ALLOWZERO) == 0u) COMPILE_ERROR_UPSTREAM( "ptr_from_int: pointer does not allow address zero"); // `"const ptr_align = ptr_ty.ptrAlignment(zcu);"`: the explicit // alignment, else the pointee's `abiAlignment` (1 for `anyopaque` and // every opaque type). uint32_t align_log2 = IP_PTR_FLAGS_ALIGN(flags); if (align_log2 == IP_PTR_ALIGN_NONE && !ztAbiAlignLog2(zcu, ip, ip->extra[ptd], &align_log2)) UNIMPLEMENTED("ptr_from_int: unresolved pointee alignment"); uint64_t align = UINT64_C(1) << align_log2; // The fixed wasm32 target has no function-pointer address mask. This raw // address check is therefore ptrFromIntVal's exact masked-address check. if (address != 0 && align > 1 && address % align != 0) COMPILE_ERROR_UPSTREAM( "ptr_from_int: pointer requires aligned address"); if (optional && address == 0) return ipGetOptNull(ip, result_ty); uint32_t ptr = ipGetPtrInt(ip, ptr_ty, address); return optional ? ipGetOptPayload(ip, result_ty, ptr) : ptr; } static uint32_t semaPtrFromIntComptime( Zcu* zcu, uint32_t dest_ty, uint32_t operand) { InternPool* ip = &zcu->intern_pool; IpArrayInfo vec; bool is_vec = ipArrayInfo(ip, dest_ty, &vec) == IP_ARR_OK && vec.is_vector; uint32_t n = is_vec ? vec.len : 1u; uint32_t usize_op = semaCoerceComptime(zcu, ip, operand, is_vec ? ipGetVectorType(ip, n, ZIR_REF_USIZE_TYPE) : ZIR_REF_USIZE_TYPE); uint32_t* elems = ARR_INIT(uint32_t, n + 1u); for (uint32_t i = 0; i < n; i++) { uint64_t address; if (!ipTryReadIntValue(ip, is_vec ? semaAggregateElemValueOrUndef(ip, usize_op, ZIR_REF_USIZE_TYPE, i, "ptr_from_int lane") : usize_op, &address)) UNIMPLEMENTED("ptr_from_int: unreadable address"); elems[i] = semaPtrFromIntValue(zcu, is_vec ? vec.child : dest_ty, address); } uint32_t out = is_vec ? ipGetAggregate(ip, dest_ty, elems, n) : elems[0]; free(elems); return out; } bool semaOpvPtrInfo( InternPool* ip, uint32_t ptr, uint32_t* out_child, uint32_t* out_value) { uint32_t idx = ptr; if (idx >= ip->items_len) return false; // Sema.analyzeLoad's one_possible_value arm answers solely from the // pointee type, before it resolves pointer backing. In particular, a // typed `undefined` pointer has no byte-offset carrier but still loads // its pointee's only possible value. uint32_t ptr_ty = ipTypeOf(ip, idx); if (ptr_ty >= ip->items_len || ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) return false; uint32_t child = ipPtrChild(ip, ptr_ty); uint32_t opv = ztOnePossibleValue(ip, child); if (opv == IP_INDEX_NONE) return false; if (out_child) *out_child = child; if (out_value) *out_value = opv; return true; } bool semaOpvRootPtrInfo( InternPool* ip, uint32_t ptr, uint32_t* out_child, uint32_t* out_value) { uint32_t child, opv; if (!semaOpvPtrInfo(ip, ptr, &child, &opv)) return false; uint32_t idx = ptr; if (ip->item_tags[idx] != IP_TAG_PTR_UAV || ipPtrUavByteOffset(ip, idx) != 0 || ipPtrUavValue(ip, idx) != opv) return false; if (out_child) *out_child = child; if (out_value) *out_value = opv; return true; } // (part of) Sema.zig:zirSizeOf, Type.zig:abiSize // Sema.zirSizeOf's class gate (src/Sema.zig): after resolving the // operand's layout, `@sizeOf` rejects uninstantiable (NPV) operands with // "no size available for uninstantiable type" and comptime-only (partial / // full) operands with "no size available for comptime-only type"; OPV folds // to zero and runtime reads Type.abiSize. Callers must have the layout // resolved (struct/union classes come from resolved metadata). static void semaSizeOfClassCheck(InternPool* ip, uint32_t ty) { switch (ztTypeClass(ip, ty)) { case ZT_CLASS_NPV: COMPILE_ERROR_UPSTREAM("no size available for uninstantiable type"); case ZT_CLASS_PARTIAL: case ZT_CLASS_FULL: COMPILE_ERROR_UPSTREAM("no size available for comptime-only type"); case ZT_CLASS_OPV: case ZT_CLASS_RUNTIME: return; case ZT_CLASS_UNKNOWN: UNIMPLEMENTED("size_of: unresolved type class"); } } // Sema.zig:validateRuntimeElemAccess // Sema.validateRuntimeElemAccess (src/Sema.zig): `"if // (elem_ty.comptimeOnly(zcu)) { ... "values of type '{f}' must be // comptime-known, but index value is runtime-known"`. static void validateRuntimeElemAccess(Zcu* zcu, uint32_t elem_ty) { if (ztComptimeOnly(zcu, elem_ty)) COMPILE_ERROR_UPSTREAM("values of this type must be comptime-known, " "but index value is runtime-known"); } uint32_t semaPtrTypeBitRange( Zcu* zcu, uint32_t elem_ty, uint64_t bit_offset, uint64_t host_size) { InternPool* ip = &zcu->intern_pool; if (bit_offset > 0xFFFFu || host_size > 0xFFFFu) COMPILE_ERROR_UPSTREAM("type 'u16' cannot represent integer value"); ipGetIntSmall(ip, ZIR_REF_U16_TYPE, (uint32_t)bit_offset); ipGetIntSmall(ip, ZIR_REF_U16_TYPE, (uint32_t)host_size); if (host_size != 0) { if (ztTypeUnpackable(ip, elem_ty)) COMPILE_ERROR_UPSTREAM( "bit-pointer cannot refer to value of type"); uint64_t sz, al, elem_bits; if (!ztLayoutInfo(zcu, ip, elem_ty, &sz, &al, &elem_bits)) UNIMPLEMENTED("ptr_type: bit_range elem layout unresolved"); if (bit_offset >= host_size * 8) COMPILE_ERROR_UPSTREAM("packed type at bit offset starts bits " "after the end of a byte host integer"); if (elem_bits > host_size * 8 - bit_offset) COMPILE_ERROR_UPSTREAM("packed type at bit offset ends bits " "after the end of a byte host integer"); } return ((uint32_t)host_size << IP_PTR_PACKED_OFFSET_HOST_SIZE_SHIFT) | ((uint32_t)bit_offset << IP_PTR_PACKED_OFFSET_BIT_OFFSET_SHIFT); } static bool semaAtomicElemTypeSupported( Zcu* zcu, InternPool* ip, uint32_t ty, uint64_t* out_align) { if (ty >= ip->items_len) return false; uint64_t size = 0, align = 0, bits = 0; switch (ip->item_tags[ty]) { case IP_TAG_TYPE_INT_UNSIGNED: case IP_TAG_TYPE_INT_SIGNED: case IP_TAG_TYPE_POINTER: break; case IP_TAG_TYPE_OPTIONAL: if (!ztTypeIsPtrLikeOptional(ip, ty)) return false; break; case IP_TAG_SIMPLE_TYPE: switch (ty) { case ZIR_REF_BOOL_TYPE: case ZIR_REF_USIZE_TYPE: case ZIR_REF_ISIZE_TYPE: case ZIR_REF_C_CHAR_TYPE: case ZIR_REF_C_SHORT_TYPE: case ZIR_REF_C_USHORT_TYPE: case ZIR_REF_C_INT_TYPE: case ZIR_REF_C_UINT_TYPE: case ZIR_REF_C_LONG_TYPE: case ZIR_REF_C_ULONG_TYPE: case ZIR_REF_C_LONGLONG_TYPE: case ZIR_REF_C_ULONGLONG_TYPE: case ZIR_REF_C_LONGDOUBLE_TYPE: case ZIR_REF_F16_TYPE: case ZIR_REF_F32_TYPE: case ZIR_REF_F64_TYPE: case ZIR_REF_F128_TYPE: break; default: return false; } break; case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: if (!ipStructIsPacked(ip, ty)) return false; break; default: if (!ipIsEnumType(ip, ty)) return false; break; } if (!ztLayoutInfo(zcu, ip, ty, &size, &align, &bits)) return false; // zig1's target is wasm32; upstream's default // max_atomic_bits on wasm32 is the pointer width. if (bits > 32u) return false; *out_align = align; return true; } // (part of) Sema.zig:zirCmpxchg // `Sema.zirCmpxchg`'s element-type checks, both frames: the float refusal // (`"if (elem_ty.zigTypeTag(zcu) == .float) return sema.fail(..., \"expected // bool, integer, enum, packed struct, or pointer type; found '{f}'\", ...)"`) // and `checkAtomicPtrOperand`'s `checkAtomicOperandType`, including the // target's max atomic width. `*out_align` is the element's ABI alignment. bool semaCmpxchgElemSupported(Zcu* zcu, uint32_t ty, uint64_t* out_align) { InternPool* ip = &zcu->intern_pool; return semaAtomicElemTypeSupported(zcu, ip, ty, out_align) && semaAtomicRmwXchgElemSupported(ip, ty); } // (part of) Sema.zig:analyzeAsAlign // A comptime alignment in bytes: analyzeAsInt's coercion to align_ty (u29) // interns the value, then validateAlign; returns its log2. uint32_t analyzeAsAlign(InternPool* ip, uint64_t align, const char* context) { if (align == 0 || (align & (align - 1u)) != 0 || align > 0x1fffffffu) UNIMPLEMENTED(context); ipGetIntSmall(ip, ZIR_REF_U29_TYPE, (uint32_t)align); return ztLog2PowerOfTwoU64(align); } // analyzeAsAlign of `align`, packed into pointer `flags`. uint32_t semaPtrFlagsWithExplicitAlign( InternPool* ip, uint32_t flags, uint64_t align, const char* context) { return IP_PTR_FLAGS_WITH_ALIGN(flags, analyzeAsAlign(ip, align, context)); } // Sema.zig:checkPtrAttributes // Sema.checkPtrAttributes (src/Sema.zig): the const / volatile / // address-space / alignment / bit-range gate every `coerceExtra` pointer arm // runs before it will mint a compatible pointer. Upstream fills an // InMemoryCoercionResult on each refusal to render a diagnostic; zig1 // renders none, so every refusal collapses to false. Both sides arrive // already decoded because a slice destination has no `ipPtrInfo` of its own // (see `ztPtrOrSliceInfo`). `zcu` is nullable for the callers that hold no // `Zcu`; it reaches only `ztAbiAlignLog2`, which then declines an // aggregate child instead of resolving its layout. static bool checkPtrAttributes(Zcu* zcu, InternPool* ip, const IpPtrInfo* dest_info_p, const IpPtrInfo* src_info_p) { IpPtrInfo dest_info = *dest_info_p, src_info = *src_info_p; // `len0`: a zero-length source pointee carries no bytes, so const and // alignment cannot be observed through it. bool len0 = false; IpArrayInfo src_arr; if (ipArrayInfo(ip, src_info.child, &src_arr) == IP_ARR_OK && !src_arr.is_vector) { len0 = (uint64_t)src_arr.len + (src_arr.sentinel != IP_INDEX_NONE ? 1u : 0u) == 0 || (src_arr.len == 0 && dest_info.sentinel == IP_INDEX_NONE && dest_info.size != IP_PTR_SIZE_C && dest_info.size != IP_PTR_SIZE_MANY); } else if (ztIsTuple(ip, src_info.child)) { len0 = ipTupleFieldCount(ip, src_info.child) == 0; } if (!(!src_info.is_const || dest_info.is_const || len0)) return false; if (!(!src_info.is_volatile || dest_info.is_volatile)) return false; if (((dest_info.flags >> IP_PTR_FLAGS_ADDRSPACE_SHIFT) & IP_PTR_FLAGS_ADDRSPACE_MASK) != ((src_info.flags >> IP_PTR_FLAGS_ADDRSPACE_SHIFT) & IP_PTR_FLAGS_ADDRSPACE_MASK)) return false; if (src_info.alignment == IP_PTR_ALIGN_NONE && dest_info.alignment == IP_PTR_ALIGN_NONE) return true; if (len0) return true; // "const inst_align = if (inst_info.flags.alignment != .none) // inst_info.flags.alignment else Type.fromInterned(inst_info.child) // .abiAlignment(zcu);" and its dest twin (src/Sema.zig). uint32_t src_align = src_info.alignment; uint32_t dest_align = dest_info.alignment; if (src_align == IP_PTR_ALIGN_NONE && !ztAbiAlignLog2(zcu, ip, src_info.child, &src_align)) return false; if (dest_align == IP_PTR_ALIGN_NONE && !ztAbiAlignLog2(zcu, ip, dest_info.child, &dest_align)) return false; if (dest_align > src_align) return false; return src_info.packed_offset == dest_info.packed_offset; } // Sema.zig:checkPtrAttributes // The same `Sema.checkPtrAttributes` for callers that hold pointer type // indices rather than decoded info. A slice type decodes through // `ztPtrOrSliceInfo`, so upstream's `len0` size clause sees `.slice` and not // the `[*]T` carrier zig1 stores it as. `zcu` is nullable, as above. bool semaCheckPtrAttributesTy( Zcu* zcu, InternPool* ip, uint32_t dest_ty, uint32_t src_ty) { IpPtrInfo dest_info, src_info; if (!ztPtrOrSliceInfo(ip, dest_ty, &dest_info) || !ztPtrOrSliceInfo(ip, src_ty, &src_info)) return false; return checkPtrAttributes(zcu, ip, &dest_info, &src_info); } // Sema.zig:makePtrTyConst // Sema.makePtrTyConst (src/Sema.zig): "var ptr_info = // ptr_ty.ptrInfo(sema.pt.zcu); ptr_info.flags.is_const = true;". uint32_t makePtrTyConst(InternPool* ip, uint32_t ptr_ty) { uint32_t pd = ip->item_datas[ptr_ty]; return ipGetPtrTypeFull(ip, ip->extra[pd + IP_TYPE_POINTER_CHILD], ip->extra[pd + IP_TYPE_POINTER_FLAGS] | IP_PTR_FLAGS_IS_CONST, ip->extra[pd + IP_TYPE_POINTER_SENTINEL], ip->extra[pd + IP_TYPE_POINTER_PACKED_OFFSET]); } // analyzeSlice's type-only post-start derivation permits the one-past pointer // solely for an empty known-length window. Element operations // keep using ztElemPtrType and retain their ordinary bounds rejection. uint32_t semaSliceElemPtrType(Zcu* zcu, uint32_t parent_ptr_ty, uint32_t index, uint32_t* out_elem_ty, bool empty_window) { return ztElemPtrTypeInner( zcu, parent_ptr_ty, index, out_elem_ty, empty_window, true); } // Sema.zig:elemPtrVector, Type.zig:elemPtrType, PerThread.zig:ptrType // Sema.elemPtrVector's pointer-type derivation: the type of `&v[index]`. // // A vector element pointer is not Type.elemPtrType -- that function's `.one` // arm is `".array_type => |array_type| .fromInterned(array_type.child),"` with // an `else => unreachable`, so a vector child never reaches it. Upstream mints // the type inline in `fn elemPtrVector` (src/Sema.zig), and its key differs // from an array element pointer's in three ways that all matter for // InternPool identity: // * `".vector_index = @enumFromInt(index),"` -- the element index lives in // the pointer type, so `&v[0]` and `&v[1]` are distinct types; // * `".host_size = @intCast(vector_len),"` -- host_size counts elements // here, not bytes (Zcu.PerThread.ptrType asserts exactly that: // `"_ => assert(@intFromEnum(info.flags.vector_index) < // info.packed_offset.host_size),"`), and it is not canonicalized away; // * alignment and `is_allowzero` are copied verbatim from the vector // pointer (`".alignment = vector_ptr_info.flags.alignment,"`, // `".is_allowzero = vector_ptr_info.flags.is_allowzero,"`) -- no // per-index narrowing, unlike Type.elemPtrType's `.minStrict` / // `"index == null or index == 0"`. // `out_elem_ty` receives the vector's child type. uint32_t semaElemPtrVectorType( Zcu* zcu, uint32_t vector_ptr_ty, uint32_t index, uint32_t* out_elem_ty) { InternPool* ip = &zcu->intern_pool; IpPtrInfo info; if (ipPtrInfo(ip, vector_ptr_ty, &info) != IP_PTRI_OK || info.size != IP_PTR_SIZE_ONE) UNIMPLEMENTED("elemPtrVector: source is not a `*@Vector(..)`"); IpArrayInfo vec; if (ipArrayInfo(ip, info.child, &vec) != IP_ARR_OK || !vec.is_vector) UNIMPLEMENTED("elemPtrVector: child is not a vector"); // `"if (vector_len == 0) { return sema.fail(..., "cannot index into empty // vector", .{}); }"` if (vec.len == 0) COMPILE_ERROR_UPSTREAM("elem_ptr: cannot index into empty vector"); // `"if (index >= vector_len) { return sema.fail(..., "index {d} outside // vector of length {d}", ...); }"` if (index >= vec.len) COMPILE_ERROR_UPSTREAM("elem_ptr: index outside vector"); // The VectorIndex enum is a u16 with `none == maxInt(u16)`, so a vector // longer than 65535 elements has no representable element pointer. if (vec.len > IP_PTR_VECTOR_INDEX_NONE) UNIMPLEMENTED("elemPtrVector: length exceeds PtrType.VectorIndex"); uint32_t flags = info.flags; flags &= ~IP_PTR_FLAGS_SIZE_MASK; // `".size = .one,"` (ordinal 0) flags = IP_PTR_FLAGS_WITH_VECTOR_INDEX(flags, index); // PackedOffset is `packed struct(u32) { host_size: u16, bit_offset: u16 }` // -- host_size occupies the low half, and bit_offset is 0 here. uint32_t packed_offset = vec.len; if (out_elem_ty) *out_elem_ty = vec.child; return ipGetPtrTypeFull( ip, vec.child, flags, IP_INDEX_NONE, packed_offset); } uint32_t semaPayloadPtrType( InternPool* ip, uint32_t parent_ptr_ty, uint32_t payload_ty) { if (parent_ptr_ty >= ip->items_len || ip->item_tags[parent_ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED( "Type.ptrInfo: payload pointer source is not a pointer type"); uint32_t pd = ip->item_datas[parent_ptr_ty]; return ipGetPtrTypeFull(ip, payload_ty, ip->extra[pd + IP_TYPE_POINTER_FLAGS], ip->extra[pd + IP_TYPE_POINTER_SENTINEL], ip->extra[pd + IP_TYPE_POINTER_PACKED_OFFSET]); } // ── Comptime values: element and field access, memory, packing ────────────── // `analyzeSlice`'s comptime result: `ptr_val.ptrElem(start)` then // `getCoerced(new_ptr_val, return_ty)`, so a comptime-only element keeps its // `.arr_elem` index rather than a byte offset. uint32_t semaPtrElemCoerced( Zcu* zcu, uint32_t ptr, uint32_t start, uint32_t return_ty) { InternPool* ip = &zcu->intern_pool; IpPtrInfo info; IpArrayInfo array; if (ipPtrInfo(ip, ipTypeOf(ip, ptr), &info) == IP_PTRI_OK && info.size == IP_PTR_SIZE_ONE && ipArrayInfo(ip, info.child, &array) == IP_ARR_OK && !array.is_vector) { // analyzeSlice's `ptr`: an array pointer is first coerced to its // many pointer (`coerceCompatiblePtrs`), so `start` may be the // array length. ptr = ipRetypePtrValue(ip, ptr, ipGetPtrType(ip, array.child, (info.flags & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_MANY)); if (ptr == IP_INDEX_NONE) UNIMPLEMENTED("analyzeSlice: comptime many-pointer carrier"); } uint32_t elem_ty; uint32_t elem_ptr_ty = ztElemPtrType(zcu, ipTypeOf(ip, ptr), start, &elem_ty); uint32_t result = ipRetypePtrValue( ip, valPtrElem(zcu, ip, ptr, elem_ptr_ty, elem_ty, start), return_ty); if (result == IP_INDEX_NONE) UNIMPLEMENTED("analyzeSlice: comptime pointer carrier"); return result; } // `try sema.resolveDefinedValue(block, elem_index_src, elem_index)` followed // by `index_val.toUnsignedInt(zcu)`: the element index as a comptime-known // unsigned integer. `semaInternedIntValueType` is the whole question: its // single unsigned range test rejects AIR refs and INST_VALUE_UNSET, and // the preinterned 0/1 are ordinary int items (112/113/116/117), so they need // no arm of their own. What each caller reports on a false differs (a runtime // index into a `comptime var` is a stock compile error; into a comptime array // pointer it is an unmodeled AIR shape), so the decision stays with the // caller. static bool semaTryReadElemIndex( const InternPool* ip, uint32_t val, uint64_t* out) { if (semaInternedIntValueType(ip, val) == IP_INDEX_NONE) return false; *out = ipReadIntValue(ip, val); return true; } // (part of) Sema.zig:analyzeErrUnionPayloadPtr, Value.zig:ptrEuPayload // `Sema.analyzeErrUnionPayloadPtr`'s comptime half: `pointerDeref` the error // union, then `Value.ptrEuPayload`'s `.eu_payload` pointer. False when `p` is // not an interned pointer; `*out` is IP_INDEX_NONE when the error union holds // an error. Shared by both frames. bool semaErrUnionPayloadPtrComptime( Zcu* zcu, InternPool* ip, uint32_t p, uint32_t* out) { IpPtrInfo pi; if (p < ZIR_REF_START_INDEX || p >= ip->items_len || ipPtrInfo(ip, ipTypeOf(ip, p), &pi) != IP_PTRI_OK) return false; uint32_t cur = pointerDeref(zcu, p, NULL); if (cur == IP_INDEX_NONE) UNIMPLEMENTED( "analyzeErrUnionPayloadPtr: err_union_payload_ptr runtime load"); if (ipItemTagIs(ip, cur, IP_TAG_ERROR_UNION_ERROR) || ipItemTagIs(ip, cur, IP_TAG_ERROR_SET_ERROR)) { *out = IP_INDEX_NONE; return true; } *out = ipGetPtrEuPayload(ip, semaPayloadPtrType( ip, ipTypeOf(ip, p), ipErrorUnionPayload(ip, pi.child)), p); return true; } // Sema.zig:analyzeLoad // `Sema.analyzeLoad` for a comptime-known pointer: the pointee's one possible // value (`typeHasOnePossibleValue(elem_ty)`), else `pointerDeref`; // IP_INDEX_NONE for its `.runtime_load`. uint32_t semaAnalyzeLoadComptime(Zcu* zcu, uint32_t ptr, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; IpPtrInfo info; if (ipPtrInfo(ip, ipTypeOf(ip, ptr), &info) != IP_PTRI_OK) UNIMPLEMENTED("analyzeLoad: comptime operand is not a pointer"); *out_ty = info.child; // `"try sema.ensureLayoutResolved(elem_ty, src, .ptr_access);"` ensureLayoutResolved(zcu, info.child); uint32_t opv = ztOnePossibleValue(ip, info.child); return opv != IP_INDEX_NONE ? opv : pointerDeref(zcu, ptr, NULL); } // `structFieldPtrByIndex`'s comptime-field test: `structFieldIsComptime`, // then `structFieldDefaultValue(field_index).?` -- a tuple's comptime value // or a struct's default; IP_INDEX_NONE for a runtime field. uint32_t semaStructFieldComptimeValue( Zcu* zcu, uint32_t struct_ty, uint32_t field_index) { const InternPool* ip = &zcu->intern_pool; if (ztIsTuple(ip, struct_ty)) return ipTupleFieldValue(ip, struct_ty, field_index); if (!ipStructFieldIsComptime(ip, struct_ty, field_index)) return IP_INDEX_NONE; uint32_t field_val = semaFieldDefaultValue(zcu, struct_ty, field_index); if (field_val == IP_INDEX_NONE) UNIMPLEMENTED("structFieldPtrByIndex: comptime field no value"); return field_val; } // Sema.zig:elemPtr // Sema.elemPtr (`one_layer` false) and elemPtrOneLayerOnly (true) with a // comptime-known pointer and index: elemPtrArray's, elemPtrVector's, // tupleElemPtr's and elemPtrSlice's comptime results, a many / C pointer's // `ptrElem`, and elemPtr's `else` arm, which `analyzeLoad`s a pointer to a // pointer and indexes one layer down. IP_INDEX_NONE when that load is a // runtime load. uint32_t semaElemPtrComptime( Zcu* zcu, uint32_t ptr, uint64_t index, bool one_layer, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; uint32_t ptr_ty = ipTypeOf(ip, ptr); uint32_t elem_ty; if (index > UINT32_MAX) UNIMPLEMENTED("elemPtr: wide comptime index"); if (ztIsSlice(ip, ptr_ty)) { // elemPtrSlice: `slice_ty.elemPtrType(offset)`, the undef slice's // undef element pointer, then the sentinel-aware bounds. uint32_t many_ty = ip->item_datas[ptr_ty]; *out_ty = ztElemPtrType(zcu, many_ty, (uint32_t)index, &elem_ty); if (ipIsUndef(ip, ptr)) return ipGetUndef(ip, *out_ty); uint64_t len_s = ipReadIntValue(ip, ipPtrSliceLen(ip, ptr)) + (ipPtrSentinel(ip, many_ty) != IP_INDEX_NONE ? 1u : 0u); if (len_s == 0) COMPILE_ERROR_UPSTREAM("cannot index into empty slice"); if (index >= len_s) COMPILE_ERROR_UPSTREAM("index outside slice"); return valPtrElem(zcu, ip, ipPtrSlicePtr(ip, ptr), *out_ty, elem_ty, (uint32_t)index); } IpPtrInfo info; if (ipPtrInfo(ip, ptr_ty, &info) != IP_PTRI_OK) UNIMPLEMENTED("elemPtr: comptime operand is not a pointer"); IpArrayInfo array; bool child_seq = ipArrayInfo(ip, info.child, &array) == IP_ARR_OK; if (info.size != IP_PTR_SIZE_ONE || (child_seq && !array.is_vector)) { // elemPtrArray, and the many / C pointer's `ptrElem`. *out_ty = ztElemPtrType(zcu, ptr_ty, (uint32_t)index, &elem_ty); return valPtrElem(zcu, ip, ptr, *out_ty, elem_ty, (uint32_t)index); } if (child_seq) { // elemPtrVector: `pt.getCoerced(ptr_val, elem_ptr_ty)`. if (index >= array.len) COMPILE_ERROR_UPSTREAM("index outside vector"); *out_ty = semaElemPtrVectorType(zcu, ptr_ty, (uint32_t)index, &elem_ty); uint32_t lane = ipRetypePtrValue(ip, ptr, *out_ty); if (lane == IP_INDEX_NONE) UNIMPLEMENTED("elemPtrVector: comptime pointer carrier"); return lane; } if (ztIsTuple(ip, info.child)) { // tupleElemPtr -> structFieldPtrByIndex. if (index >= ipTupleFieldCount(ip, info.child)) COMPILE_ERROR_UPSTREAM("index out of bounds of tuple"); return structFieldPtrByIndex(zcu, ptr, ptr_ty, info.child, (uint32_t)index, NULL, NULL, NULL, out_ty); } if (one_layer) UNIMPLEMENTED("elemPtr: comptime pointer to a non-indexable type"); uint32_t loaded_ty; uint32_t loaded = semaAnalyzeLoadComptime(zcu, ptr, &loaded_ty); return loaded == IP_INDEX_NONE ? IP_INDEX_NONE : semaElemPtrComptime(zcu, loaded, index, true, out_ty); } // Sema.zig:elemVal // Sema.elemVal's `.one` `arr_sent` arm: `p[len]` on a `*[len:s]T` is the // type's sentinel `s`. IP_INDEX_NONE for any other pointer type or index. uint32_t semaElemValArrSent( const InternPool* ip, uint32_t ptr_ty, uint64_t index) { IpPtrInfo info; IpArrayInfo array; if (ipPtrInfo(ip, ptr_ty, &info) != IP_PTRI_OK || info.size != IP_PTR_SIZE_ONE || ipArrayInfo(ip, info.child, &array) != IP_ARR_OK || array.is_vector || index != array.len) return IP_INDEX_NONE; return array.sentinel; } // Sema.zig:elemVal // Sema.elemVal's pointer arm on a comptime pointer: the `arr_sent` arm, else // `elemPtr` then `analyzeLoad`. IP_INDEX_NONE when that load is a runtime // load. uint32_t semaElemValComptime( Zcu* zcu, uint32_t ptr, uint64_t index, uint32_t* out_ty) { const InternPool* ip = &zcu->intern_pool; uint32_t sentinel = semaElemValArrSent(ip, ipTypeOf(ip, ptr), index); if (sentinel != IP_INDEX_NONE) { *out_ty = ipTypeOf(ip, sentinel); return sentinel; } uint32_t elem_ptr_ty; return semaAnalyzeLoadComptime(zcu, semaElemPtrComptime(zcu, ptr, index, false, &elem_ptr_ty), out_ty); } // `ipIntValueType` for a resolved `inst_values[]` entry rather than a known IP // index: answers "is this resolved value an already-interned integer value, // and if so what is its type?". Declines (IP_INDEX_NONE) for everything that // is not an in-range InternPool index: INST_VALUE_UNSET and AIR instruction // refs (>= AIR_REF_INST_BASE, above `items_len` for any real pool) both fail // the one range test. // // An already-interned comptime_int (DECL_VAL provenance) reaches the sites // below with no `inst_types[]` entry; this recovers its type. uint32_t semaInternedIntValueType(const InternPool* ip, uint32_t val) { if (val >= ip->items_len) return IP_INDEX_NONE; return ipIntValueType(ip, val); } // (part of) InternPool.zig:get // A raw two's-complement pattern at `dest_ty`, as the sign-and-magnitude // `big.int.Const` `InternPool.get()` takes: mask the pattern to the type's // width, then negate it if the type is signed and its sign bit is set. This // is `Mutable.truncate` run backwards -- truncate is the rule that turns a // value into the pattern -- and it is the one place a bare word regains the // sign. `space` is the caller's two-limb // scratch (`Value.BigIntSpace`). A width above 128 bits has no sign bit // inside the pattern, so such a value is a magnitude and stays positive. bool semaPatternToBigInt(const InternPool* ip, uint64_t lo, uint64_t hi, uint32_t dest_ty, uint64_t* space, BigIntConst* out) { uint32_t bits; bool is_signed; if (!ztIntInfo(ip, dest_ty, &bits, &is_signed)) return false; // `Mutable.readTwosComplement` lives in big_int.c beside its inverse // `Mutable.truncate`; this is only the type lookup in front of it. The // AIR interpreter reads its own {lo, hi} carrier through the same call. bigIntFromTwosCompU128(lo, hi, bits, is_signed, space, out); return true; } // (part of) Sema.zig:zirTruncate // `Sema.zirTruncate`'s rules ahead of its comptime fold, on the // `.remove_eu_opt` destination: "if (dest_scalar_ty.zigTypeTag(zcu) == // .comptime_int) { return sema.coerce(...); } if (try // dest_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);", the // signedness check, and "if (dest_info.bits >= operand_info.bits) { return // sema.coerce(block, dest_ty, operand, operand_src); }". True when the // answer is the coercion; otherwise `*out_opv` is the OPV or IP_INDEX_NONE. static bool semaTruncateIsCoerce( InternPool* ip, uint32_t operand_ty, uint32_t dest_ty, uint32_t* out_opv) { uint32_t dest_scalar = ipScalarType(ip, dest_ty); *out_opv = IP_INDEX_NONE; if (dest_scalar == ZIR_REF_COMPTIME_INT_TYPE) return true; *out_opv = ztOnePossibleValue(ip, dest_ty); if (*out_opv != IP_INDEX_NONE) return false; semaTruncateCheckSignedness(ip, operand_ty, dest_ty); uint32_t op_scalar = ipScalarType(ip, operand_ty); uint32_t dest_bits, op_bits; bool dest_signed, op_signed; return op_scalar != ZIR_REF_COMPTIME_INT_TYPE && ztIntInfo(ip, dest_scalar, &dest_bits, &dest_signed) && ztIntInfo(ip, op_scalar, &op_bits, &op_signed) && dest_bits >= op_bits; } // Sema.zig:zirTruncate, arith.zig:truncate, PerThread.zig:undefValue // `Sema.zirTruncate` for a comptime-known operand: the coercion or OPV // above, else "const result_val = try arith.truncate(sema, val, operand_ty, // dest_ty, dest_info.signedness, dest_info.bits);" -- whose "if // (val.isUndef(zcu)) return pt.undefValue(dest_ty);" heads the per-lane loop. static uint32_t semaTruncateComptime( Zcu* zcu, uint32_t operand, uint32_t dest_ty) { InternPool* ip = &zcu->intern_pool; uint32_t opv; if (semaTruncateIsCoerce(ip, ipTypeOf(ip, operand), dest_ty, &opv)) return semaCoerceComptime(zcu, ip, operand, dest_ty); if (opv != IP_INDEX_NONE) return opv; uint32_t res = ipIsUndef(ip, operand) ? ipGetUndef(ip, dest_ty) : semaCastLanes(ip, operand, dest_ty, arithIntTruncateLane); if (res == IP_INDEX_NONE) UNIMPLEMENTED("truncate: comptime result intern unsupported type"); return res; } // (part of) Sema.zig:analyzeArithmetic // Read a comptime binop operand's integer magnitude + its comptime type, for // the both-operands-comptime fold. Mirrors Sema.analyzeArithmetic resolving // each operand to a comptime-known integer Value before peer-typing. Handles: // - a `comptime_int` value carried out of a block break-elide: the block // yields the break operand truncated to the 32-bit AIR-ref width; such an // operand has analysis type IP_INDEX_NONE (a `zir int` never records an // inst type), so its low bits are the magnitude and its comptime type is // `comptime_int`; // - an already-interned typed / comptime_int IP integer entry. // Returns false if the operand is not a readable comptime integer. static bool semaBinReadComptimeInt(const InternPool* ip, uint32_t val, uint32_t ty_raw, uint64_t* out_n, uint32_t* out_ty) { if (ty_raw == IP_INDEX_NONE) { // A `zir int` // below 2^63 resolves to an interned index and records no // inst_types[] entry (the ZIR_INST_INT asymmetry), so this // arm receives an index, not a magnitude. Reading // an IP index as a magnitude is a silent miscompile, so the item is // tested first and the raw reading is the fallback, not the rule. // The raw reading exists for a comptime_int carried through a block // break, which truncated the value to the 32-bit AIR-ref width and // dropped bit 63; an interned index is < 2^32 and survives that // truncation unchanged, so the item test covers that shape too. uint64_t nn; if (semaInternedIntValueType(ip, val) != IP_INDEX_NONE && ipTryReadIntValue(ip, val, &nn)) { *out_n = nn; *out_ty = ipIntValueType(ip, val); return true; } *out_n = val; *out_ty = ZIR_REF_COMPTIME_INT_TYPE; return true; } // Success-flag reader: a legitimate -1 / UINT64_MAX value (comptime_int -1 // sign-extended, or a u64 all-ones mask) must not be misread as // "unreadable". uint64_t n; if (!ipTryReadIntValue(ip, val, &n)) return false; *out_n = n; *out_ty = ipIntValueType(ip, val); return true; } // Screen a comptime operand word for an IP index: an already-interned index // passes through, a runtime or unset one returns IP_INDEX_NONE and the // caller keeps its loud abort. static uint32_t semaOperandInternIndex(uint32_t val) { return semaRefIsInterned(val) ? val : IP_INDEX_NONE; } static uint32_t semaArithResultType(Zcu* zcu, ZirInstTag tag, uint32_t a_ty, uint32_t a_val, uint32_t b_ty, uint32_t b_val) { // `shlScalar` / `shrScalar` take the result type from the shifted operand // (`"const lhs_ty = ...; return intShl(sema, lhs_ty, ...)"`): a shift // amount is coerced to the element's `Log2Int` and never peers with it. // Every other op is `Sema.analyzeArithmetic`'s `"const resolved_type = // try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, ...)"` followed by // `sema.coerce` of both operands to it -- the one peer rule, // `semaTypeofPeerResolve`, and not a ladder here (it keeps the // widest of each signedness and asks the legacy `any_comptime_known` // question that makes a mixed `u8`/`i8` pair order-dependent). if (arithTagIsShift(tag)) return a_ty; // Upstream's `peer_tys`/`peer_vals` slices, mutated in place; both // operands are comptime-known here, so each peer carries its own value. uint32_t peer_tys[2] = { a_ty, b_ty }; uint32_t peer_vals[2] = { a_val, b_val }; return semaTypeofPeerResolve(zcu, peer_tys, peer_vals, 2); } // (part of) Sema.zig:analyzeArithmetic, PerThread.zig:intValue_big // `Sema.analyzeArithmetic`'s both-comptime integer branch, over operands // already read as `big.int` values: the peer type the operation is evaluated // at, `arith.` there, and `pt.intValue_big` on the way out. Every // comptime integer binop in both frames ends here, and there is no second // fold in front of it: arith.c answers at any width and either // sign. IP_INDEX_NONE only when the // peer type is not an integer type; each operation's own diagnostics are // raised here, because they are upstream's and not the caller's. uint32_t semaComptimeIntBinOpBig(Zcu* zcu, ZirInstTag tag, BigIntConst a, uint32_t a_ty, uint32_t a_val, BigIntConst b, uint32_t b_ty, uint32_t b_val) { InternPool* ip = &zcu->intern_pool; bool is_shift = arithTagIsShift(tag); uint32_t res_ty = semaArithResultType(zcu, tag, a_ty, a_val, b_ty, b_val); ArithIntType aty; uint32_t bits; bool is_signed; if (res_ty == ZIR_REF_COMPTIME_INT_TYPE) aty = arithIntTypeComptimeInt(); else if (ztIntInfo(ip, res_ty, &bits, &is_signed)) aty = arithIntTypeSized(bits, is_signed); else return IP_INDEX_NONE; // `sema.coerce(block, resolved_type, )`, which every non-shift // operand passes on the way in -- not just a `comptime_int` one: // `Sema.coerceExtra`'s comptime-int arm rejects any value the resolved // type cannot represent, and the legacy `.fixed_int` rule above hands // back a peer that a mixed-signedness operand genuinely does not fit // (`@as(u8, 200) + @as(i8, -5)` resolves to `u8`, and upstream reports // "type 'u8' cannot represent integer value '-5'"). An operand already at // `res_ty` always fits, so one test per side covers both. A shift amount // is coerced to `Log2Int` instead, and arith.c's // `failWithTooLargeShiftAmount` is what bounds it. The coerced operands // are interned at `res_ty`, lhs first. if (aty.sized && !is_shift) { if (!bigIntFitsInTwosComp(a, aty.is_signed, aty.bits) || !bigIntFitsInTwosComp(b, aty.is_signed, aty.bits)) COMPILE_ERROR_UPSTREAM( "analyzeArithmetic: comptime overflow of integer type"); if (a_ty != res_ty) ipGetInt(ip, res_ty, a); if (b_ty != res_ty) ipGetInt(ip, res_ty, b); } uint32_t cap = arithIntBinOpLimbs(tag, a, b, aty); uint64_t* buf = zig1Malloc((size_t)cap * sizeof(uint64_t)); BigIntMutable r = { buf, cap, 0, true }; ArithStatus st = arithIntBinOp(tag, a, b, aty, &r); if (st != ARITH_OK) { free(buf); switch (st) { case ARITH_OVERFLOW: COMPILE_ERROR_UPSTREAM( "analyzeArithmetic: comptime overflow of integer type"); case ARITH_REMAINDER: // `intShlWithOverflow` / `shr_exact` raise "exact shift shifted // out 1 bits" where `divExact` raises the remainder text: one // status, two upstream spellings, stated where it is read. if (is_shift) COMPILE_ERROR_UPSTREAM("analyzeArithmetic: comptime exact " "shift shifted out 1 bits"); COMPILE_ERROR_UPSTREAM("analyzeArithmetic: comptime exact " "division produced remainder"); case ARITH_DIV_ZERO: COMPILE_ERROR_UPSTREAM( "analyzeArithmetic: comptime division by zero"); case ARITH_SHIFT_AMOUNT: COMPILE_ERROR_UPSTREAM( "analyzeArithmetic: comptime invalid shift amount"); case ARITH_SHIFT_TOO_LARGE: COMPILE_ERROR_UPSTREAM( "analyzeArithmetic: comptime shift amount too large " "for fixed-width integer"); default: // ARITH_UNSUPPORTED_OP UNIMPLEMENTED("analyzeArithmetic: bin_op comptime - unhandled op " "for comptime fold"); } } // `pt.intValue_big(ty, result_bigint.toConst())`, which every `int*` // worker upstream ends at: `ipGetInt` picks the carrier, so neither the // sign nor a magnitude past one word can be lost on the way in. uint32_t res = ipGetInt(ip, res_ty, bigIntMutableToConst(r)); free(buf); return res; } // The inverse of `valFloatOperandBits`: a result's bits at `width`, interned // as a value of the float type `ty` (`comptime_float` is width 128). uint32_t semaFloatFromBits( InternPool* ip, uint32_t ty, uint32_t width, CrtU128 res) { switch (width) { case 16: return ipGetFloatF16(ip, (uint32_t)res.lo); case 32: return ipGetFloatF32(ip, (uint32_t)res.lo); case 64: return ipGetFloatF64(ip, res.lo); case 80: { uint32_t words[3]; ipFloatF80WordsFromLow80Bits(res.lo, res.hi, words); return ipGetFloatF80(ip, words); } default: { uint32_t words[4]; ipFloat128ToWords(res, words); return ipGetFloatFromF128Bits(ip, ty, words); } } } // The `buf` every packed-memory user starts from: `"const buf = try // sema.arena.alloc(u8, @intCast((bit_size + 7) / 8)); @memset(buf, 0);"`, // then `val.writeToPackedMemory(zcu, buf, 0)` unless `val` is IP_INDEX_NONE. // NULL when that write declines; the caller frees. uint8_t* semaPackedMemBuf(Zcu* zcu, uint32_t val, uint64_t bits) { uint8_t* buf = zig1Calloc((size_t)((bits + 7) / 8) + 1u, 1); if (val != IP_INDEX_NONE && !valWriteToPackedMemory(zcu, val, buf, bits, 0)) { free(buf); return NULL; } return buf; } // Sema.zig:bitCastVal // Sema.bitCastVal: `"const buf = try sema.arena.alloc(u8, @intCast((bit_size + // 7) / 8)); @memset(buf, 0); val.writeToPackedMemory(zcu, buf, 0); return // .readFromPackedMemory(dest_ty, pt, buf, 0);"` -- one reinterpret rule for // every shape `@bitCast` accepts, scalar and vector alike. Returns // IP_INDEX_NONE when a leaf is not modeled (see valWriteToPackedMemory) or // when the two types disagree on bit size, which for a well-typed operand is // upstream's `"@bitCast size mismatch"` compile error and for a loosely typed // comptime carrier is simply a shape this fold cannot serve. uint32_t bitCastVal(Zcu* zcu, uint32_t val, uint32_t dest_ty) { InternPool* ip = &zcu->intern_pool; if (val >= ip->items_len || dest_ty >= ip->items_len) return IP_INDEX_NONE; uint64_t dsz, dal, dbits; if (!ztLayoutInfo(zcu, ip, dest_ty, &dsz, &dal, &dbits)) return IP_INDEX_NONE; uint32_t src_ty = ipComptimeValueType(ip, val); // `Sema.bitCast` asserts `"old_ty.hasBitRepresentation(zcu)"` because // `coerce` has already typed the operand; a zig1 comptime value can still // be carrying the untyped `comptime_float` shape at this point, so // materialize it at the width that coercion would have used. if (src_ty == ZIR_REF_COMPTIME_FLOAT_TYPE) { val = valComptimeFloatAtWidth(ip, val, dbits); if (val == IP_INDEX_NONE) return IP_INDEX_NONE; src_ty = ipComptimeValueType(ip, val); } uint64_t ssz, sal, sbits; if (src_ty == IP_INDEX_NONE || !ztLayoutInfo(zcu, ip, src_ty, &ssz, &sal, &sbits) || sbits != dbits) return IP_INDEX_NONE; if (ipIsUndef(ip, val)) return ipGetUndef(ip, dest_ty); uint8_t* buf = semaPackedMemBuf(zcu, val, dbits); uint32_t res = buf ? valReadFromPackedMemory(zcu, dest_ty, buf, dbits, 0) : IP_INDEX_NONE; free(buf); return res; } typedef struct { Zcu* zcu; uint64_t skip_bytes; uint64_t remaining_bytes; U32List unpacked; } SemaUnpackValueBytes; static SemaCastStatus semaUnpackSplitPrimitive(SemaUnpackValueBytes* unpack, uint32_t val, uint64_t offset, uint64_t len) { InternPool* ip = &unpack->zcu->intern_pool; unpack->remaining_bytes -= len; if (ipIsUndef(ip, val)) { for (uint64_t i = 0; i < len; i++) u32ListAppend(&unpack->unpacked, ipGetUndef(ip, ZIR_REF_U8_TYPE)); return ZPT_CAST_OK; } uint64_t size = ztAbiSize(unpack->zcu, ipTypeOf(ip, val)); uint8_t* buf = zig1Calloc((size_t)size + 1u, 1); SemaCastStatus status = valWriteToMemory(unpack->zcu, val, buf, size); for (uint64_t i = 0; status == ZPT_CAST_OK && i < len; i++) u32ListAppend(&unpack->unpacked, ipGetIntU8(ip, buf[offset + i])); free(buf); return status; } static SemaCastStatus semaUnpackPrimitive( SemaUnpackValueBytes* unpack, uint32_t val) { if (unpack->remaining_bytes == 0) return ZPT_CAST_OK; uint64_t size = ztAbiSize(unpack->zcu, ipTypeOf(&unpack->zcu->intern_pool, val)); if (size == UINT64_MAX) return ZPT_CAST_UNMODELED; if (unpack->skip_bytes >= size) { unpack->skip_bytes -= size; return ZPT_CAST_OK; } if (unpack->skip_bytes > 0) { uint64_t offset = unpack->skip_bytes; unpack->skip_bytes = 0; uint64_t len = size - offset < unpack->remaining_bytes ? size - offset : unpack->remaining_bytes; return semaUnpackSplitPrimitive(unpack, val, offset, len); } if (unpack->remaining_bytes < size) return semaUnpackSplitPrimitive( unpack, val, 0, unpack->remaining_bytes); unpack->remaining_bytes -= size; u32ListAppend(&unpack->unpacked, val); return ZPT_CAST_OK; } static SemaCastStatus semaUnpackPadding( SemaUnpackValueBytes* unpack, uint64_t num_bytes) { InternPool* ip = &unpack->zcu->intern_pool; for (uint64_t i = 0; i < num_bytes; i++) if (semaUnpackPrimitive(unpack, ipGetUndef(ip, ZIR_REF_U8_TYPE)) != ZPT_CAST_OK) return ZPT_CAST_UNMODELED; return ZPT_CAST_OK; } static SemaCastStatus semaUnpackAdd( SemaUnpackValueBytes* unpack, uint32_t val) { Zcu* zcu = unpack->zcu; InternPool* ip = &zcu->intern_pool; if (unpack->remaining_bytes == 0) return ZPT_CAST_OK; uint32_t ty = ipTypeOf(ip, val); uint64_t size = ztAbiSize(zcu, ty); if (size == UINT64_MAX) return ZPT_CAST_UNMODELED; if (unpack->skip_bytes >= size) { unpack->skip_bytes -= size; return ZPT_CAST_OK; } uint8_t tag = ip->item_tags[val]; if (tag == IP_TAG_BITPACK) return semaUnpackPrimitive(unpack, ipBitpackBackingVal(ip, val)); if (tag == IP_TAG_UNION_VALUE) { uint32_t payload = ipUnionValuePayload(ip, val); uint64_t payload_size = ztAbiSize(zcu, ipTypeOf(ip, payload)); SemaCastStatus status = semaUnpackAdd(unpack, payload); return status != ZPT_CAST_OK ? status : semaUnpackPadding(unpack, size - payload_size); } if (tag != IP_TAG_AGGREGATE && tag != IP_TAG_REPEATED && tag != IP_TAG_BYTES) { // `.undef, .int, .enum_tag, .simple_value, .float, .ptr, .opt` uint32_t bits; bool is_signed; if (ipIsUndef(ip, val) || tag == IP_TAG_ENUM_TAG || ztIntInfo(ip, ty, &bits, &is_signed) || ztSimpleFloatBits(ty, &bits) || ty == ZIR_REF_BOOL_TYPE || ty == ZIR_REF_VOID_TYPE || ztIsPtrAtRuntime(ip, ty)) return semaUnpackPrimitive(unpack, val); return ZPT_CAST_UNMODELED; } IpArrayInfo array; if (ipArrayInfo(ip, ty, &array) == IP_ARR_OK && !array.is_vector) { for (uint32_t i = 0; i < array.storage_len; i++) { SemaCastStatus status = semaUnpackAdd( unpack, valElemValue(ip, val, i, "castMemory: array element")); if (status != ZPT_CAST_OK) return status; } return ZPT_CAST_OK; } if (!ipTagIsAnyStruct(ip->item_tags[ty]) || !ipStructIsExtern(ip, ty)) return ZPT_CAST_UNMODELED; uint64_t offset = 0; for (uint32_t i = 0; i < ipStructFieldsLen(ip, ty); i++) { uint64_t pad_bytes = ipStructFieldOffset(ip, ty, i) - offset; uint32_t field_val = valElemValue(ip, val, i, "castMemory: struct field"); SemaCastStatus status = semaUnpackPadding(unpack, pad_bytes); if (status == ZPT_CAST_OK) status = semaUnpackAdd(unpack, field_val); if (status != ZPT_CAST_OK) return status; offset += pad_bytes + ztAbiSize(zcu, ipStructFieldType(ip, ty, i)); } return semaUnpackPadding(unpack, size - offset); } typedef struct { Zcu* zcu; uint64_t byte_offset; const uint32_t* unpacked; uint32_t len; } SemaPackValueBytes; static bool semaPackPrepareBytes(SemaPackValueBytes* pack, uint64_t need_bytes, const uint32_t** out_vals, uint32_t* out_len, uint64_t* out_offset) { const InternPool* ip = &pack->zcu->intern_pool; *out_vals = pack->unpacked; *out_len = 0; *out_offset = 0; if (need_bytes == 0) return true; uint64_t bytes = 0; uint32_t len = 0; while (bytes < pack->byte_offset + need_bytes) { if (len == pack->len) return false; bytes += ztAbiSize(pack->zcu, ipTypeOf(ip, pack->unpacked[len])); len++; } *out_len = len; *out_offset = pack->byte_offset; uint64_t extra_bytes = bytes - pack->byte_offset - need_bytes; if (extra_bytes == 0) { pack->unpacked += len; pack->len -= len; pack->byte_offset = 0; } else { pack->unpacked += len - 1; pack->len -= len - 1; pack->byte_offset = ztAbiSize(pack->zcu, ipTypeOf(ip, pack->unpacked[0])) - extra_bytes; } return true; } static SemaCastStatus semaPackPrimitive( SemaPackValueBytes* pack, uint32_t want_ty, uint32_t* out) { Zcu* zcu = pack->zcu; InternPool* ip = &zcu->intern_pool; uint32_t opv = ztOnePossibleValue(ip, want_ty); if (opv != IP_INDEX_NONE) { *out = opv; return ZPT_CAST_OK; } uint64_t want_size = ztAbiSize(zcu, want_ty); const uint32_t* vals; uint32_t n; uint64_t byte_offset; if (want_size == UINT64_MAX || !semaPackPrepareBytes(pack, want_size, &vals, &n, &byte_offset)) return ZPT_CAST_UNMODELED; uint32_t defined = 0; while (defined < n && ipIsUndef(ip, vals[defined])) defined++; if (defined == n) { // "All bits of the value are `undefined`." *out = ipGetUndef(ip, want_ty); return ZPT_CAST_OK; } if (n == 1 && ztIsPtrAtRuntime(ip, want_ty) && ztIsPtrAtRuntime(ip, ipTypeOf(ip, vals[0]))) { // `pt.getCoerced(vals[0], want_ty)`: a pointer-like optional's // payload, or the pointer wrapped. uint32_t ptr = vals[0]; bool is_null = ip->item_tags[ptr] == IP_TAG_OPT_NULL; if (ip->item_tags[ptr] == IP_TAG_OPT_PAYLOAD) ptr = ipPayloadVal(ip, ptr); if (ztTypeIsPtrLikeOptional(ip, want_ty)) { ptr = is_null ? IP_INDEX_NONE : ipRetypePtrValue(ip, ptr, ip->item_datas[want_ty]); *out = is_null ? ipGetOptNull(ip, want_ty) : ipGetOptPayload(ip, want_ty, ptr); return is_null || ptr != IP_INDEX_NONE ? ZPT_CAST_OK : ZPT_CAST_UNMODELED; } *out = is_null ? IP_INDEX_NONE : ipRetypePtrValue(ip, ptr, want_ty); return *out != IP_INDEX_NONE ? ZPT_CAST_OK : ZPT_CAST_UNMODELED; } // "Reinterpret via an in-memory buffer." uint64_t buf_len = 0; for (uint32_t i = 0; i < n; i++) buf_len += ztAbiSize(zcu, ipTypeOf(ip, vals[i])); uint8_t* buf = zig1Calloc((size_t)buf_len + 16u, 1); SemaCastStatus status = ZPT_CAST_OK; uint64_t offset = 0; for (uint32_t i = 0; i < n && status == ZPT_CAST_OK; i++) { uint64_t size = ztAbiSize(zcu, ipTypeOf(ip, vals[i])); status = valWriteToMemory(zcu, vals[i], buf + offset, size); offset += size; } const uint8_t* bytes = buf + byte_offset; uint64_t addr = 0; for (uint64_t i = 0; i < want_size && i < 8; i++) addr |= (uint64_t)bytes[i] << (8 * i); uint32_t bits; bool is_signed; *out = IP_INDEX_NONE; if (status != ZPT_CAST_OK) { } else if (want_ty == ZIR_REF_BOOL_TYPE) { *out = bytes[0] != 0 ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; } else if (ztIntInfo(ip, want_ty, &bits, &is_signed) || ztSimpleFloatBits(want_ty, &bits)) { // `readIntFromMemory` / the float's bits, little-endian. *out = valReadFromPackedMemory(zcu, want_ty, bytes, want_size * 8u, 0); } else if (ip->item_tags[want_ty] == IP_TAG_TYPE_POINTER) { *out = ipGetPtrInt(ip, want_ty, addr); } else if (ztTypeIsPtrLikeOptional(ip, want_ty)) { *out = addr == 0 ? ipGetOptNull(ip, want_ty) : ipGetOptPayload(ip, want_ty, ipGetPtrInt(ip, ip->item_datas[want_ty], addr)); } free(buf); if (status == ZPT_CAST_OK && *out == IP_INDEX_NONE) status = ZPT_CAST_UNMODELED; return status; } static SemaCastStatus semaPackGet( SemaPackValueBytes* pack, uint32_t ty, uint32_t* out) { Zcu* zcu = pack->zcu; InternPool* ip = &zcu->intern_pool; const uint32_t* skipped; uint32_t skipped_len; uint64_t skipped_offset; SemaCastStatus status = ZPT_CAST_OK; IpArrayInfo array; if (ipArrayInfo(ip, ty, &array) == IP_ARR_OK && !array.is_vector) { // "Each element is padded up to its ABI size." uint32_t* elems = zig1Calloc((size_t)array.len + 1u, sizeof(uint32_t)); for (uint32_t i = 0; i < array.len && status == ZPT_CAST_OK; i++) status = semaPackGet(pack, array.child, &elems[i]); if (status == ZPT_CAST_OK && array.sentinel != IP_INDEX_NONE && !semaPackPrepareBytes(pack, ztAbiSize(zcu, array.child), &skipped, &skipped_len, &skipped_offset)) status = ZPT_CAST_UNMODELED; if (status == ZPT_CAST_OK) *out = zptAggregateValue(ip, ty, elems, array.len); free(elems); return status; } if (ipTypeIsBitpack(ip, ty)) { uint32_t backing; status = semaPackPrimitive(pack, ipBitpackBackingInt(ip, ty), &backing); if (status == ZPT_CAST_OK) *out = ipIsUndef(ip, backing) ? ipGetUndef(ip, ty) : ipGetBitpack(ip, ty, backing); return status; } if (ipIsEnumType(ip, ty)) { uint32_t tag_int; status = semaPackPrimitive(pack, ipEnumTypeIntTagType(ip, ty), &tag_int); if (status == ZPT_CAST_OK) *out = ipIsUndef(ip, tag_int) ? ipGetUndef(ip, ty) : ipGetEnumTag(ip, ty, tag_int); return status; } if (ipTagIsAnyStruct(ip->item_tags[ty]) && ipStructIsExtern(ip, ty)) { uint32_t fields_len = ipStructFieldsLen(ip, ty); uint32_t* elems = zig1Calloc((size_t)fields_len + 1u, sizeof(uint32_t)); uint64_t offset = 0; for (uint32_t i = 0; i < fields_len && status == ZPT_CAST_OK; i++) { uint32_t field_ty = ipStructFieldType(ip, ty, i); uint64_t pad_bytes = ipStructFieldOffset(ip, ty, i) - offset; if (!semaPackPrepareBytes( pack, pad_bytes, &skipped, &skipped_len, &skipped_offset)) status = ZPT_CAST_UNMODELED; else status = semaPackGet(pack, field_ty, &elems[i]); offset += pad_bytes + ztAbiSize(zcu, field_ty); } if (status == ZPT_CAST_OK && !semaPackPrepareBytes(pack, ztAbiSize(zcu, ty) - offset, &skipped, &skipped_len, &skipped_offset)) status = ZPT_CAST_UNMODELED; if (status == ZPT_CAST_OK) *out = zptAggregateValue(ip, ty, elems, fields_len); free(elems); return status; } if (ip->item_tags[ty] == IP_TAG_TYPE_UNION) { // The extern union: "We will attempt to read as the backing // representation. If this emits `error.ReinterpretDeclRef`, we will // try each union field, preferring larger ones." SemaPackValueBytes prev = *pack; uint64_t size = ztAbiSize(zcu, ty); uint32_t backing_val; SemaCastStatus backing = semaPackGet(pack, ipGetArrayType(ip, (uint32_t)size, ZIR_REF_U8_TYPE, IP_INDEX_NONE), &backing_val); if (backing == ZPT_CAST_OK && !ipIsUndef(ip, backing_val)) { *out = ipGetUnionValue(ip, ty, IP_INDEX_NONE, backing_val); return ZPT_CAST_OK; } if (backing == ZPT_CAST_UNMODELED) return backing; // `field_order`, the largest ABI sizes first (a stable sort, as // `sortUnstable`'s insertion sort is at these lengths). uint32_t fields_len = ipUnionFieldsLen(ip, ty); uint32_t* order = zig1Calloc((size_t)fields_len + 1u, sizeof(uint32_t)); uint64_t* sizes = zig1Calloc((size_t)fields_len + 1u, sizeof(uint64_t)); for (uint32_t i = 0; i < fields_len; i++) { sizes[i] = ztAbiSize(zcu, semaResolveUnionFieldTypeLazy(zcu, ty, i)); uint32_t j = i; for (; j > 0 && sizes[order[j - 1]] < sizes[i]; j--) order[j] = order[j - 1]; order[j] = i; } bool found = false; for (uint32_t k = 0; k < fields_len && !found; k++) { *pack = prev; uint32_t field_ty = semaResolveUnionFieldTypeLazy(zcu, ty, order[k]); uint32_t field_val; status = semaPackGet(pack, field_ty, &field_val); if (status == ZPT_CAST_RUNTIME || (status == ZPT_CAST_OK && ipIsUndef(ip, field_val))) continue; found = true; if (status != ZPT_CAST_OK) break; if (!semaPackPrepareBytes(pack, size - sizes[order[k]], &skipped, &skipped_len, &skipped_offset)) { status = ZPT_CAST_UNMODELED; break; } *out = ipGetUnionValue(ip, ty, ipEnumTagFromFieldName(ip, ipEnsureGeneratedUnionTag(ip, ty), ipUnionFieldName(ip, ty, order[k])), field_val); } free(order); free(sizes); if (found) return status; // "No field could represent the value": `undefined` when the // backing read was, else `error.ReinterpretDeclRef`. if (backing == ZPT_CAST_OK) { *out = ipGetUndef(ip, ty); return ZPT_CAST_OK; } return ZPT_CAST_RUNTIME; } return semaPackPrimitive(pack, ty, out); } // Sema.spliceMemory: `splice_val` written into `val` at `byte_offset`. static SemaCastStatus spliceMemory(Zcu* zcu, uint32_t val, uint32_t splice_val, uint64_t byte_offset, uint32_t* out) { const InternPool* ip = &zcu->intern_pool; uint32_t val_ty = ipTypeOf(ip, val); uint64_t val_size = ztAbiSize(zcu, val_ty); uint64_t splice_len = ztAbiSize(zcu, ipTypeOf(ip, splice_val)); if (val_size == UINT64_MAX || splice_len == UINT64_MAX) return ZPT_CAST_UNMODELED; SemaUnpackValueBytes unpack = { zcu, 0, byte_offset, { 0 } }; SemaCastStatus status = semaUnpackAdd(&unpack, val); if (status == ZPT_CAST_OK) { unpack.remaining_bytes = splice_len; status = semaUnpackAdd(&unpack, splice_val); } if (status == ZPT_CAST_OK) { unpack.skip_bytes = byte_offset + splice_len; unpack.remaining_bytes = val_size * 8u - byte_offset - splice_len; status = semaUnpackAdd(&unpack, val); } if (status == ZPT_CAST_OK) { SemaPackValueBytes pack = { zcu, 0, unpack.unpacked.items, unpack.unpacked.len }; status = semaPackGet(&pack, val_ty, out); } free(unpack.unpacked.items); return status; } // Sema.castMemory: the `dest_ty` value at `byte_offset` into `val`. SemaCastStatus castMemory(Zcu* zcu, uint32_t val, uint32_t dest_ty, uint64_t byte_offset, uint32_t* out) { const InternPool* ip = &zcu->intern_pool; if (dest_ty == ipTypeOf(ip, val) && byte_offset == 0) { *out = val; return ZPT_CAST_OK; } uint64_t dest_size = ztAbiSize(zcu, dest_ty); if (dest_size == UINT64_MAX) return ZPT_CAST_UNMODELED; SemaUnpackValueBytes unpack = { zcu, byte_offset, dest_size, { 0 } }; SemaCastStatus status = semaUnpackAdd(&unpack, val); if (status == ZPT_CAST_OK && unpack.remaining_bytes != 0) status = ZPT_CAST_UNMODELED; if (status == ZPT_CAST_OK) { SemaPackValueBytes pack = { zcu, 0, unpack.unpacked.items, unpack.unpacked.len }; status = semaPackGet(&pack, dest_ty, out); } free(unpack.unpacked.items); return status; } static uint32_t semaFoldComptimeScalarBinOp(Zcu* zcu, ZirInstTag tag, uint32_t lhs_val, uint32_t lhs_ty_raw, uint32_t rhs_val, uint32_t rhs_ty_raw, uint32_t* out_ty); static uint32_t semaFoldComptimeVectorBinOp( Zcu* zcu, ZirInstTag tag, uint32_t lhs, uint32_t rhs, uint32_t* out_ty); // (part of) Sema.zig:zirNegate, Air.zig:internedToRef, PerThread.zig:intValue // `Sema.zirNegate` / `zirNegateWrap` on a comptime-known integer operand: // `"const lhs = Air.internedToRef((try sema.splat(rhs_ty, try // pt.intValue(rhs_scalar_ty, 0))).toIntern());"` then // `analyzeArithmetic(.sub / .subwrap)`, whose comptime fold is `arith.sub` // (its overflow error included) -- nothing about negation is per-lane, only // the shape of `0`. The caller has already taken zirNegate's float arm. static uint32_t semaNegateInt(Zcu* zcu, uint32_t ty, uint32_t val, bool wrap) { InternPool* ip = &zcu->intern_pool; uint32_t scalar_ty = ipScalarType(ip, ty); uint32_t zero = semaCoerceComptime( zcu, ip, ipGetIntComptimeIntI64(ip, 0), scalar_ty); ZirInstTag tag = wrap ? ZIR_INST_SUBWRAP : ZIR_INST_SUB; if (scalar_ty == ty) return semaFoldComptimeScalarBinOp(zcu, tag, zero, ty, val, ty, NULL); uint32_t res = semaFoldComptimeVectorBinOp( zcu, tag, ipGetRepeated(ip, ty, zero), val, NULL); if (res == IP_INDEX_NONE) UNIMPLEMENTED("negate: vector operand"); return res; } // (part of) Sema.zig:zirNegate // `Sema.zirNegate` / `zirNegateWrap` on a comptime-known operand: // zirNegate's float arm first -- "if (rhs_scalar_ty.isAnyFloat()) { // // We handle float negation here to ensure negative zero is represented in // the bits." -- asked of the scalar type so a float vector takes it, and a // float it cannot negate aborts rather than reach the `0 - x` integer fold // (which loses -0.0); `-%` has no float arm. Otherwise semaNegateInt. static uint32_t semaNegateComptime( Zcu* zcu, uint32_t ty, uint32_t val, bool wrap) { const InternPool* ip = &zcu->intern_pool; uint32_t scalar = ipScalarType(ip, ty); uint32_t bits; if (!wrap && (scalar == ZIR_REF_COMPTIME_FLOAT_TYPE || ztSimpleFloatBits(scalar, &bits))) { uint32_t res = negateFloat(zcu, ty, val); if (res == IP_INDEX_NONE) UNIMPLEMENTED("negate: comptime float operand"); return res; } return semaNegateInt(zcu, ty, val, wrap); } // Sema.zig:arrayInitEmpty, PerThread.zig:aggregateValue // `Sema.arrayInitEmpty` (src/Sema.zig): `"const arr_len = obj_ty.arrayLen( // zcu); if (arr_len != 0) { ... }; return .fromValue(try pt.aggregateValue( // obj_ty, &.{}));"`. It is what BOTH of `zirStructInitEmpty`'s `.array, // .vector` arms call, so `[0]T{}` and `@Vector(0, T){}` are one rule -- and // one length decode, since `ipArrayInfo` already reads all three sequence // tags, including `type_array_big`'s two-word length. Returns IP_INDEX_NONE // when `obj_ty` is not a sequence type, so the caller can try its own // categories; a nonzero length is upstream's `"expected {d} array elements; // found 0"` compile error. static uint32_t arrayInitEmpty(Zcu* zcu, uint32_t obj_ty) { InternPool* ip = &zcu->intern_pool; IpArrayInfo ai; IpArrayStatus st = ipArrayInfo(ip, obj_ty, &ai); if (st == IP_ARR_NOT_SEQ) return IP_INDEX_NONE; if (st != IP_ARR_OK || ai.len != 0) COMPILE_ERROR_UPSTREAM( "array_init_empty: expected N array/vector elements, found 0"); return ipGetAggregate(ip, obj_ty, NULL, 0); } // A packed struct's or packed union's field index by name (`Sema. // structFieldIndex` / `unionFieldIndex`); UINT32_MAX when absent. uint32_t semaBitpackFieldIndex(InternPool* ip, uint32_t ty, uint32_t name) { return ipUnionIsPacked(ip, ty) ? ipUnionFieldIndex(ip, ty, name) : ipStructFieldIndex(ip, ty, name); } uint32_t semaReadBitpackFieldValue( Zcu* zcu, uint32_t bitpack, uint32_t field_idx) { InternPool* ip = &zcu->intern_pool; uint32_t bp_ty = ipBitpackType(ip, bitpack); if (ipUnionIsPacked(ip, bp_ty)) { // `Sema.unionFieldVal`'s comptime `.@"packed"` arm: `"return // .fromValue(try sema.bitCastVal(union_val, field_ty));"`. uint32_t pu_val = bitCastVal(zcu, bitpack, ipUnionFieldType(ip, bp_ty, field_idx)); if (pu_val == IP_INDEX_NONE) UNIMPLEMENTED("bitCastVal: packed union field: bitCastVal"); return pu_val; } // `Value.fieldValue`'s `.bitpack` arm: `"int_val.writeToPackedMemory(zcu, // buf, 0); return .readFromPackedMemory(field_ty, pt, buf, // field_bit_offset);"`, over `buf` of `(ty.bitSize(zcu) + 7) / 8` bytes. uint32_t bp_fty = ipStructFieldType(ip, bp_ty, field_idx); uint32_t bp_backing = ipBitpackBackingVal(ip, bitpack); // Upstream asserts `!int_val.isUndef(zcu)`: an undefined packed value is // `.undef` there, whose field is `.undef` too (`fieldValue`'s first arm). if (ipIsUndef(ip, bp_backing)) return ipGetUndef(ip, bp_fty); uint64_t bp_sz, bp_al, bp_bits; if (!ztLayoutInfo(zcu, ip, bp_ty, &bp_sz, &bp_al, &bp_bits)) UNIMPLEMENTED("structFieldVal: packed field - struct bit size"); uint8_t* bp_buf = semaPackedMemBuf(zcu, bp_backing, bp_bits); uint32_t bp_val = bp_buf ? valReadFromPackedMemory(zcu, bp_fty, bp_buf, bp_bits, zcuStructPackedFieldBitOffset(zcu, ip, bp_ty, field_idx)) : IP_INDEX_NONE; free(bp_buf); if (bp_val == IP_INDEX_NONE) UNIMPLEMENTED("structFieldVal: packed field - leaf not modeled in " "packed memory"); return bp_val; } // (part of) Sema.zig:finishStructInit // Sema.finishStructInit's comptime `.@"packed"` arm: `"var bit_offset: u16 = // 0; for (field_inits) |field_init| { const field_val = sema.resolveValue( // field_init).?; field_val.writeToPackedMemory(zcu, buf, bit_offset); // bit_offset += @intCast(field_val.typeOf(zcu).bitSize(zcu)); } ... const // struct_val: Value = try .readFromPackedMemory(struct_ty, pt, buf, 0);"`. // A leaf the packed-memory pair does not model declines loudly. uint32_t semaPackStructFields( Zcu* zcu, uint32_t struct_ty, const uint32_t* elems, uint32_t elems_len) { InternPool* ip = &zcu->intern_pool; uint64_t ssz, sal, sbits; if (!ztLayoutInfo(zcu, ip, struct_ty, &ssz, &sal, &sbits)) UNIMPLEMENTED("struct_init packed: struct bit size"); uint8_t* buf = semaPackedMemBuf(zcu, IP_INDEX_NONE, sbits); uint64_t bit_offset = 0; for (uint32_t f = 0; f < elems_len; f++) { uint32_t fty = ipStructFieldType(ip, struct_ty, f); uint64_t fsz, fal, fbits; if (!ztLayoutInfo(zcu, ip, fty, &fsz, &fal, &fbits)) UNIMPLEMENTED("struct_init packed: field bit size"); if (!valWriteToPackedMemory(zcu, elems[f], buf, sbits, bit_offset)) { free(buf); UNIMPLEMENTED( "struct_init packed: field not writable to packed memory"); } bit_offset += fbits; } // `"assert(bit_offset == struct_ty.bitSize(zcu));"` if (bit_offset != sbits) UNIMPLEMENTED("struct_init packed: field bits do not sum to the " "struct's bit size"); uint32_t res = valReadFromPackedMemory(zcu, struct_ty, buf, sbits, 0); free(buf); if (res == IP_INDEX_NONE) UNIMPLEMENTED( "struct_init packed: backing not readable from packed memory"); return res; } StructDeclSmall semaDecodeStructDeclSmall(uint16_t small) { StructDeclSmall s; s.has_captures = (small & ZIR_STRUCT_SMALL_HAS_CAPTURES_LEN) != 0; s.has_decls = (small & ZIR_STRUCT_SMALL_HAS_DECLS_LEN) != 0; s.has_fields = (small & ZIR_STRUCT_SMALL_HAS_FIELDS_LEN) != 0; s.name_strategy = (small >> ZIR_STRUCT_SMALL_NAME_STRATEGY_SHIFT) & ZIR_STRUCT_SMALL_NAME_STRATEGY_MASK; s.layout = (small >> ZIR_STRUCT_SMALL_LAYOUT_SHIFT) & ZIR_STRUCT_SMALL_LAYOUT_MASK; s.has_backing_int = (small & ZIR_STRUCT_SMALL_HAS_BACKING_INT_TYPE) != 0; s.any_field_aligns = (small & ZIR_STRUCT_SMALL_ANY_FIELD_ALIGNS) != 0; s.any_field_defaults = (small & ZIR_STRUCT_SMALL_ANY_FIELD_DEFAULTS) != 0; s.any_comptime_fields = (small & ZIR_STRUCT_SMALL_ANY_COMPTIME_FIELDS) != 0; return s; } // Evaluate a packed struct field type body that is not the bare-primitive-ref // fast path (body_len == 1 break_inline of a preinterned type). Mirrors stock // Sema running the field type body in a comptime block (resolveInlineBody). // The only non-trivial shape AstGen emits for a *named container-type* field // (`a: Inner` where `Inner` is a container decl) is // `[decl_val(Inner), break_inline]`: resolve the decl_val to the referenced // decl's interned type (zptEnsureNavValUpToDate), then the terminating // break_inline yields it. A small local (inst -> value) map stands in for the // container-level `inst_values` array (which is NULL at container type // analysis). Aborts (UNIMPLEMENTED) on any other instruction so no wrong // output is produced. When `out_defer` is non-NULL (semaBodyStructTypePacked's // eager pass), a shape this resolver cannot handle — or a decl name not found // in the file-root namespace (it may live in the struct's own, not-yet-scanned // namespace) — sets *out_defer and // returns IP_INDEX_NONE instead of aborting; semaBodyStructTypePacked retries // it after the namespace scan via semaResolveFieldTypeBody. Shapes that // resolve immediately are untouched. #define BODY_FIELD_DEFER(msg) \ do { \ if (out_defer) { \ *out_defer = true; \ return IP_INDEX_NONE; \ } \ UNIMPLEMENTED(msg); \ } while (0) static uint32_t semaBodyFieldType(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, bool* out_defer) { InternPool* ip = &zcu->intern_pool; if (body_len > 16) BODY_FIELD_DEFER("struct_decl: field type body too long"); uint32_t map_inst[16]; uint32_t map_val[16]; uint32_t map_len = 0; uint32_t result = IP_INDEX_NONE; bool have_result = false; for (uint32_t b = 0; b < body_len; b++) { uint32_t inst = body[b]; switch (zir->inst_tags[inst]) { case ZIR_INST_DECL_VAL: { // Sema.zirDeclVal: resolve the named decl's comptime value/type. // Only the file root is searched here, where zirDeclVal walks the // scope chain; a miss defers to ctResolveFieldTypeRefInNs. uint32_t name_off = zir->inst_datas[inst].str_tok.start; const char* name = zirNullTerminatedString(zir, name_off); // Resolve against the namespace of the file owning this struct's // ZIR (a struct declared in a sibling file resolves its field-type // decl names in that file's namespace, not the root file's). uint32_t root_ns = zcuNamespaceOfZir(zcu, zir); if (root_ns == IP_NAMESPACE_NONE) UNIMPLEMENTED("struct_decl: field type's decl_val, root " "namespace not populated"); uint32_t name_str = ipGetOrPutString(ip, name, (uint32_t)strlen(name)); uint32_t nav = ipNamespaceLookup(ip, root_ns, name_str); if (nav == IP_INDEX_NONE) BODY_FIELD_DEFER( "struct_decl: field type's decl_val, decl not found"); uint32_t decl_ty = IP_INDEX_NONE; uint32_t decl_val = zptEnsureNavValUpToDate(zcu, nav, &decl_ty); map_inst[map_len] = inst; map_val[map_len] = decl_val; map_len++; break; } case ZIR_INST_BREAK_INLINE: { uint32_t op = zir->inst_datas[inst].break_data.operand; if (op < ZIR_REF_START_INDEX) { result = op; // preinterned type ref } else { uint32_t ref_inst = op - ZIR_REF_START_INDEX; bool found = false; for (uint32_t m = 0; m < map_len; m++) { if (map_inst[m] == ref_inst) { result = map_val[m]; found = true; break; } } if (!found) BODY_FIELD_DEFER("struct_decl: field type body, break " "operand not resolved"); } have_result = true; break; } default: BODY_FIELD_DEFER( "struct_decl: unhandled field type body instruction"); } } if (!have_result) UNREACHABLE("struct_decl: field type body missing break_inline"); return result; } // ── Container types: structs, unions and their layouts ────────────────────── // ProngInfo.is_comptime_unreach: the prong body is `comptime unreachable`. static bool semaSwProngIsComptimeUnreach(uint32_t prong_info) { return (prong_info & ZIR_PRONG_INFO_IS_COMPTIME_UNREACH) != 0; } // ProngInfo.capture: ZIR_PRONG_CAPTURE_NONE, _BY_VAL or _BY_REF. static uint32_t semaSwProngCapture(uint32_t prong_info) { return (prong_info >> ZIR_PRONG_INFO_CAPTURE_SHIFT) & ZIR_PRONG_INFO_CAPTURE_MASK; } // analyzeSwitchProng's case index of the catch-all (`.special`) prong. #define SW_PRONG_SPECIAL UINT32_MAX // A switch capture placeholder, or the switch inst itself when it is `.none`. static uint32_t semaSwCaptureInst(uint32_t placeholder, uint32_t inst) { return placeholder != UINT32_MAX ? placeholder : inst; } // Sema.zig:unionToTag // Port of Sema.unionToTag's runtime-value path. A tagged union whose layout // has no runtime tag has exactly one possible field; its active tag is the // enum value for that declaration index, not an integer read from payload // byte zero. This matters when earlier fields are no-possible-value and the // sole live field is not index 0. For every other shape retain the ordinary // get_union_tag lowering (the zero-possible-field case cannot produce a // runtime value, but keeping the existing loud/runtime path avoids inventing // semantics for malformed IR here). static uint32_t semaUnionToTagRuntime(Zcu* zcu, uint32_t union_ty, uint32_t union_ref, AirInstList* insts, U32List* block, uint32_t* out_enum_tag_ty) { InternPool* ip = &zcu->intern_pool; if (ipUnionTagUsage(ip, union_ty) != IP_UNION_TAG_USAGE_TAGGED) UNIMPLEMENTED("unionToTag: plain union runtime ABI"); semaResolveAllUnionDeferred(zcu, union_ty); uint32_t enum_tag_ty = ipEnsureGeneratedUnionTag(ip, union_ty); if (out_enum_tag_ty) *out_enum_tag_ty = enum_tag_ty; uint32_t possible_fields = 0; uint32_t sole_field = UINT32_MAX; bool payload_has_comptime_state = false; uint32_t fields_len = ipUnionFieldsLen(ip, union_ty); for (uint32_t i = 0; i < fields_len; i++) { uint32_t field_ty = ipUnionFieldType(ip, union_ty, i); ZtTypeClass field_class = ztTypeClass(ip, field_ty); if (field_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED("unionToTag: unresolved field class"); if (field_class == ZT_CLASS_NPV) continue; possible_fields++; sole_field = i; if (field_class == ZT_CLASS_PARTIAL || field_class == ZT_CLASS_FULL) payload_has_comptime_state = true; } if (possible_fields == 1u) { // sole_field is in union declaration order. An explicit tag enum may // use a different field order, so cross the name mapping rather than // indexing the enum with the union field index. uint32_t field_name = ipUnionFieldName(ip, union_ty, sole_field); if (field_name == IP_INDEX_NONE) UNREACHABLE("unionToTag: union field name metadata missing"); uint32_t tag = ipEnumTagFromFieldName(ip, enum_tag_ty, field_name); if (tag == IP_INDEX_NONE) UNIMPLEMENTED("unionToTag: possible field missing from tag enum"); return tag; } if (possible_fields > 1u && payload_has_comptime_state) UNIMPLEMENTED( "unionToTag: runtime value of union with comptime-state tag"); AirInstData gt; memset(>, 0, sizeof(gt)); gt.pair.a = enum_tag_ty; gt.pair.b = union_ref; uint32_t gt_idx = semaAirInstAppend(insts, AIR_INST_GET_UNION_TAG, gt); u32ListAppend(block, gt_idx); return AIR_REF_INST_BASE + gt_idx; } // (part of) Sema.zig:coerceExtra // `Sema.coerceExtra`'s `.@"enum"` destination arm, `.@"union"` source // (src/Sema.zig): "union to its own tag type" -- // `if (inst_ty.unionTagType(zcu)) |enum_tag_ty| if (enum_tag_ty.toIntern() // == dest_ty.toIntern()) return sema.unionToTag(block, inst);". Returns // `IP_INDEX_NONE` when the pair is not this arm's, so the caller falls // through. static uint32_t semaCoerceUnionToTag(Zcu* zcu, uint32_t value_air, uint32_t value_ty, uint32_t dest_ty, AirInstList* insts, U32List* block) { InternPool* ip = &zcu->intern_pool; if (value_ty >= ip->items_len || ip->item_tags[value_ty] != IP_TAG_TYPE_UNION || !ipIsEnumType(ip, dest_ty)) return IP_INDEX_NONE; if (ipUnionTagUsage(ip, value_ty) != IP_UNION_TAG_USAGE_TAGGED) UNIMPLEMENTED("unionToTag: plain union runtime ABI"); semaResolveAllUnionDeferred(zcu, value_ty); uint32_t tag_ty = ipUnionEnumTagType(ip, value_ty); if (tag_ty == IP_INDEX_NONE) tag_ty = ipEnsureGeneratedUnionTag(ip, value_ty); if (tag_ty != dest_ty) return IP_INDEX_NONE; return semaUnionToTagRuntime(zcu, value_ty, value_air, insts, block, NULL); } // Map an enum-tag value back to the corresponding union declaration field by // name. Explicit tag integers are independent from declaration indices, so // using the backing integer as a field index is incorrect. uint32_t semaUnionFieldIndexFromTag( InternPool* ip, uint32_t union_ty, uint32_t tag_idx) { if (tag_idx >= ip->items_len || ip->item_tags[tag_idx] != IP_TAG_ENUM_TAG) UNIMPLEMENTED( "Zcu.unionTagFieldIndex: union tag - value is not an enum tag"); uint32_t enum_ty = ipEnsureGeneratedUnionTag(ip, union_ty); if (ipEnumTagType(ip, tag_idx) != enum_ty) UNIMPLEMENTED( "Zcu.unionTagFieldIndex: union tag - enum tag type mismatch"); uint32_t enum_field = ipEnumTagFieldIndex(ip, tag_idx); if (enum_field == UINT32_MAX) UNIMPLEMENTED("Zcu.unionTagFieldIndex: union tag - enum tag value has " "no declared field"); uint32_t field_name = ipEnumFieldName(ip, enum_ty, enum_field); uint32_t field_index = ipUnionFieldIndex(ip, union_ty, field_name); if (field_index == UINT32_MAX) UNIMPLEMENTED("Zcu.unionTagFieldIndex: union tag - tag missing from " "union fields"); return field_index; } // (part of) Sema.zig:unionFieldPtr // `Sema.unionFieldPtr` / `unionFieldVal`'s comptime `tag_matches` on union // Value `un`: its payload when field `field_idx` is the active one, else, for // the `.auto` layout, "access of union field '{f}' while field '{f}' is // active". An `extern` union reads any field: `castMemory(union_val, // field_ty, 0)`. uint32_t semaUnionActivePayload(Zcu* zcu, uint32_t un, uint32_t field_idx) { InternPool* ip = &zcu->intern_pool; uint32_t union_ty = ipTypeOf(ip, un); if (ipUnionIsExtern(ip, union_ty)) { uint32_t field_val; SemaCastStatus status = castMemory(zcu, un, semaResolveUnionFieldTypeLazy(zcu, union_ty, field_idx), 0, &field_val); if (status == ZPT_CAST_RUNTIME) UNIMPLEMENTED("unionFieldPtr: union field - runtime-known extern " "union field"); if (status != ZPT_CAST_OK) UNIMPLEMENTED( "unionFieldPtr: union field - extern union castMemory"); return field_val; } if (semaUnionFieldIndexFromTag(ip, union_ty, ipUnionValueTag(ip, un)) != field_idx) COMPILE_ERROR_UPSTREAM("access of inactive union field"); return ipUnionValuePayload(ip, un); } // (part of) Sema.zig:zirStructDecl, Zcu.zig:structPackedFieldBitOffset // Port of the packed-layout branch of Sema.zirStructDecl + // Sema/type_resolution.zig resolvePackedStructLayout, for a `packed struct // { ... }` declared inline in a function body (e.g. HashMap's `const Metadata // = packed struct { fingerprint: u7 = 0, used: u1 = 0 }`). Decodes fields // (names // + primitive type bodies) exactly like semaBodyStructType (the ZIR trailing // layout is identical for an inferred-backing packed struct — no extra count // word), but resolves the layout as a bit-packed integer: each field occupies // `@bitSizeOf(field)` bits, LSB-first in declaration order // (Zcu.structPackedFieldBitOffset), and the inferred backing integer is // `u`. The interpreter represents a packed struct as // a scalar of that backing integer (see IP_TAG_BITPACK) — so `@bitCast` // between the struct and its backing int is the identity, and field access is // a shift+mask. An explicit backing integer (`packed struct(u32)`) evaluates // the ZIR backing_int_type_body and validates it like stock (integer type, // bit width == total field bits) — see the final pass. Aborts loudly on the // shapes it does not model (field aligns/comptime, signed explicit backing). // The file this ZIR belongs to, or the root file when it is not one of the // loaded files (the fallback every container-namespace site uses // with its own copy of this loop). uint32_t semaZcuFileIdxOfZir(const Zcu* zcu, const Zir* zir) { for (uint32_t fi = 0; fi < zcu->num_files; fi++) if (zcu->files[fi].zir == zir) return fi; return zcu->root_file_idx; } // (part of) Sema.zig:zirForLen // Sema.zirForLen's `fieldVal(object, "len")`: an indexable input with a // length interns the "len" string (`indexableHasLen`: not a many or C // pointer). void semaForLenInternLen(InternPool* ip, uint32_t obj_ty) { IpPtrInfo pi; if (obj_ty < ip->items_len && ipPtrInfo(ip, obj_ty, &pi) == IP_PTRI_OK && (pi.size == IP_PTR_SIZE_MANY || pi.size == IP_PTR_SIZE_C)) return; ipGetOrPutString(ip, "len", 3); } static bool semaTypeNameCtxIsNone(TypeNameCtx ctx) { return ctx.fqn == 0 || ctx.fqn == IP_INDEX_NONE; } // (part of) Sema.zig:setTypeName // setTypeName's `.anon` arm: `"{f}__{s}_{d}"` of the context, `anon_prefix` // and `zcu.anon_name_counter`, for the name and then the fqn. Without a // context zig1 leaves the type unnamed but still counts it, so later // anonymous names and generic instances keep upstream's numbers. static void semaTypeNameAnon( Zcu* zcu, uint32_t ty, TypeNameCtx ctx, const char* anon_prefix) { InternPool* ip = &zcu->intern_pool; if (!semaTypeNameCtxIsNone(ctx)) { uint32_t name = ipGetOrPutStringFmt(ip, "%s__%s_%u", ipStringBytes(ip, ctx.name), anon_prefix, zcu->anon_name_counter); uint32_t fqn = ipGetOrPutStringFmt(ip, "%s__%s_%u", ipStringBytes(ip, ctx.fqn), anon_prefix, zcu->anon_name_counter); ipSetTypeName(ip, ty, name, fqn, IP_INDEX_NONE); } zcu->anon_name_counter++; } // Sema.zig:setTypeName // Sema.setTypeName for a freshly created struct/union/enum/opaque `ty` // declared by `decl_inst`. `ctx` is the block's type name context (none where // zig1 has no context: the name stays unset), `parent_nav` the Nav a // `.parent`-strategy type is named after, and `func` the function whose // `param` instructions a `.func` name prints (NULL where zig1 cannot read // them: the name stays unset). static void setTypeName(Zcu* zcu, const Zir* zir, uint32_t decl_inst, uint32_t ty, TypeNameCtx ctx, uint32_t parent_nav, const TypeNameFuncArgs* func) { InternPool* ip = &zcu->intern_pool; bool named = !semaTypeNameCtxIsNone(ctx); // Struct, union and enum Small agree on `name_strategy`; opaque's // differs. uint16_t small = zir->inst_datas[decl_inst].extended.small; uint32_t strategy = (small >> ZIR_STRUCT_SMALL_NAME_STRATEGY_SHIFT) & ZIR_STRUCT_SMALL_NAME_STRATEGY_MASK; const char* anon_prefix; switch ((ZirInstExtended)zir->inst_datas[decl_inst].extended.opcode) { case ZIR_EXT_STRUCT_DECL: anon_prefix = "struct"; break; case ZIR_EXT_UNION_DECL: anon_prefix = "union"; break; case ZIR_EXT_ENUM_DECL: anon_prefix = "enum"; break; case ZIR_EXT_OPAQUE_DECL: anon_prefix = "opaque"; strategy = (small >> ZIR_OPAQUE_SMALL_NAME_STRATEGY_SHIFT) & ZIR_OPAQUE_SMALL_NAME_STRATEGY_MASK; break; // zirReifyStruct/Union/Enum: `"const name_strategy: // Zir.Inst.NameStrategy = @fromBackingInt(@intCast(extended.small));"`. case ZIR_EXT_REIFY_STRUCT: anon_prefix = "struct"; strategy = small; break; case ZIR_EXT_REIFY_UNION: anon_prefix = "union"; strategy = small; break; case ZIR_EXT_REIFY_ENUM: anon_prefix = "enum"; strategy = small; break; default: UNIMPLEMENTED("setTypeName: not a container declaration"); } switch (strategy) { case ZIR_NAME_STRATEGY_PARENT: if (named) ipSetTypeName(ip, ty, ctx.name, ctx.fqn, parent_nav); return; case ZIR_NAME_STRATEGY_FUNC: { if (func == NULL || !named) return; TypeNameWriter w = { 0 }; ztTypeNameWriteStr(&w, "("); // `"const arg_val = sema.resolveValue(arg) orelse { continue :strat // .anon; };"` for (uint32_t i = 0; i < func->nparams; i++) { uint32_t v = func->values[func->param_insts[i]]; if (v >= ip->items_len) { free(w.buf.arr); goto anon; } if (i != 0) ztTypeNameWriteStr(&w, ","); if (!ztTypeNameWriteArgValue(ip, v, 1, &w)) { free(w.buf.arr); return; } } ztTypeNameWriteStr(&w, ")"); int len = (int)w.buf.len; uint32_t name = ipGetOrPutStringFmt( ip, "%s%.*s", ipStringBytes(ip, ctx.name), len, w.buf.arr); uint32_t fqn = ipGetOrPutStringFmt( ip, "%s%.*s", ipStringBytes(ip, ctx.fqn), len, w.buf.arr); ipSetTypeName(ip, ty, name, fqn, IP_INDEX_NONE); free(w.buf.arr); return; } case ZIR_NAME_STRATEGY_ANON: break; default: { // ZIR_NAME_STRATEGY_DBG_VAR uint32_t ref = decl_inst + ZIR_REF_START_INDEX; for (uint32_t k = decl_inst + 1; k < zir->inst_len; k++) { ZirInstTag kt = zir->inst_tags[k]; if ((kt == ZIR_INST_DBG_VAR_VAL || kt == ZIR_INST_DBG_VAR_PTR) && zir->inst_datas[k].str_op.operand == ref) { const char* var_name = (const char*)&zir ->string_bytes[zir->inst_datas[k].str_op.str]; if (!named) return; uint32_t name = ipGetOrPutStringFmt( ip, "%s.%s", ipStringBytes(ip, ctx.name), var_name); uint32_t fqn = ipGetOrPutStringFmt( ip, "%s.%s", ipStringBytes(ip, ctx.fqn), var_name); ipSetTypeName(ip, ty, name, fqn, IP_INDEX_NONE); return; } } break; } } anon: semaTypeNameAnon(zcu, ty, ctx, anon_prefix); } // Sema.zig:structInitAnon // Sema.structInitAnon's type (src/Sema.zig, `.anon_init`): the // reified `struct_type` keyed on the tracked ZIR instruction and a Wyhash of // the field types, the comptime field values (`.none` for a runtime field or // one that `canMutateComptimeVarState`) and the names; a fresh type is named // "{type_name_ctx}__struct_{index}", filled, given its empty namespace and // laid out (`ensureLayoutResolved(.init)`). `values[i]` is `.none` for a // runtime-known field. uint32_t semaStructInitAnonType(Zcu* zcu, const Zir* zir, uint32_t inst, TypeNameCtx type_name_ctx, const uint32_t* types, const uint32_t* values, const uint32_t* names, uint32_t fields_len) { InternPool* ip = &zcu->intern_pool; bool any_values = false; for (uint32_t i = 0; i < fields_len; i++) { if (ztIsZigTypeTag(ip, types[i], ZIG_TYPE_TAG_OPAQUE)) COMPILE_ERROR_UPSTREAM( "opaque types have unknown size and therefore cannot be " "directly embedded in structs"); any_values |= values[i] != IP_INDEX_NONE; } uint32_t* values_no_comptime = zig1Malloc((fields_len ? fields_len : 1u) * sizeof(uint32_t)); for (uint32_t i = 0; i < fields_len; i++) values_no_comptime[i] = values[i] != IP_INDEX_NONE && valCanMutateComptimeVarState(ip, values[i]) ? IP_INDEX_NONE : values[i]; Wyhash h; wyhashInit(&h, 0); for (uint32_t i = 0; i < fields_len; i++) wyhashUpdateU32(&h, types[i]); for (uint32_t i = 0; i < fields_len; i++) wyhashUpdateU32(&h, values_no_comptime[i]); for (uint32_t i = 0; i < fields_len; i++) wyhashUpdateU32(&h, names[i]); uint32_t file_idx = semaZcuFileIdxOfZir(zcu, zir); bool existing; uint32_t struct_ty = ipGetReifiedStructType(ip, ipTrackZirInst(ip, file_idx, inst), wyhashFinal(&h), fields_len, IP_STRUCT_LAYOUT_AUTO, any_values, any_values, false, IP_INDEX_NONE, &existing); if (!existing) { semaTypeNameAnon(zcu, struct_ty, type_name_ctx, "struct"); for (uint32_t i = 0; i < fields_len; i++) { ipSetStructFieldName(ip, struct_ty, i, names[i]); ipSetStructFieldType(ip, struct_ty, i, types[i]); if (!any_values) continue; ipSetStructFieldDefault(ip, struct_ty, i, values_no_comptime[i]); ipSetStructFieldIsComptime( ip, struct_ty, i, values_no_comptime[i] != IP_INDEX_NONE); } zptCreateNamespace(zcu, zir, struct_ty, IP_NAMESPACE_NONE, NULL, 0); } free(values_no_comptime); if (!resolveStructLayout(zcu, ip, struct_ty)) UNIMPLEMENTED("struct_init_anon: struct layout resolution failed"); return struct_ty; } // A declared container `ty` published as Nav `nav`'s value before its // fields resolve, so a self-reference through the Nav finds it. void semaNavCacheType(InternPool* ip, uint32_t nav, uint32_t ty) { IpNav* n = &ip->navs[nav]; n->has_resolved = true; n->has_value_resolved = true; n->resolved_alignment = IP_ALIGNMENT_NONE; n->resolved_type = ZIR_REF_TYPE_TYPE; n->resolved_value = ty; } static uint32_t semaBodyStructTypePacked(Zcu* zcu, const Zir* zir, uint32_t struct_inst, const uint32_t* inst_values, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func, uint32_t parent_ns_opt, const CtCap* captures, uint32_t captures_arg_len, uint32_t self_nav) { InternPool* ip = &zcu->intern_pool; uint16_t small = zir->inst_datas[struct_inst].extended.small; StructDeclSmall sd = semaDecodeStructDeclSmall(small); if (sd.any_field_aligns) UNIMPLEMENTED( "resolvePackedStructLayout: packed struct - field aligns"); if (sd.any_comptime_fields) UNIMPLEMENTED( "resolvePackedStructLayout: packed struct - comptime fields"); ZirStructDecl zd = zirGetStructDecl(zir, struct_inst); uint32_t decls_len = zd.decls_len; uint32_t fields_len = zd.fields_len; // Explicit backing integer type (`packed struct(u32) { ... }`): evaluated // in the final pass below, after every field bit width is known, where // resolvePackedStructLayout evaluates it. uint32_t backing_int_body_len = zd.backing_int_type_body_len; // Captures: a packed struct declared inside a generic container (the std // hash map's `Metadata`, nested in `HashMapUnmanaged`) carries its // enclosing container's capture list in ZIR. Decode + skip it exactly like // the auto semaBodyStructType; they are registered under this struct's // namespace below only if the caller resolved them (captures != NULL). // DIVERGENCE D3b: upstream keeps captures in the packed item's own // payload (`bits.captures_len` + a `captures[]` group); zig1 mints // `captures_len = 0`, so 9 of 42 real packed structs lose 2 words each. const uint32_t* decls_ptr = &zir->extra[zd.decls]; // Pass 1: resolve each field's type, or record it as deferred — a field // whose type names a decl of this struct's own namespace (e.g. // `fingerprint: FingerPrint` where `FingerPrint = u7` is a Metadata-scoped // alias) can only resolve after the namespace is scanned below. Bit // offsets are not computed here: a packed layout needs every field width // in declaration order, so they are accumulated in a final pass once the // deferred types have landed. Also record default body locations. // A field-less packed struct (a namespace) has a `u0` backing. uint32_t fscratch_n = fields_len ? fields_len : 1u; uint32_t* fscratch = zig1Malloc((size_t)fscratch_n * 8u * sizeof(uint32_t)); uint32_t* field_ty_arr = fscratch + (size_t)fscratch_n * 0; uint32_t* deferred_field = fscratch + (size_t)fscratch_n * 1; uint32_t* deferred_body_off = fscratch + (size_t)fscratch_n * 3; uint32_t* deferred_body_len = fscratch + (size_t)fscratch_n * 4; uint32_t deferred_len = 0; uint32_t* def_field = fscratch + (size_t)fscratch_n * 5; uint32_t* def_off = fscratch + (size_t)fscratch_n * 6; uint32_t* def_len = fscratch + (size_t)fscratch_n * 7; uint32_t def_count = 0; ZirStructFieldIterator field_it = { 0, zd.field_bodies }; ZirStructField zf; while (zirStructFieldIteratorNext(zir, &zd, &field_it, &zf)) { uint32_t i = zf.idx; uint32_t body_len = zf.type_body_len; const uint32_t* body_ptr = zirBodySlice(zir, zf.type_body); if (zf.default_body_len != 0) { def_field[def_count] = i; def_off[def_count] = zf.default_body; def_len[def_count] = zf.default_body_len; def_count++; } if (body_len == 0) UNIMPLEMENTED("resolvePackedStructLayout: packed struct - empty " "field type body"); uint32_t last_inst = body_ptr[body_len - 1]; if (zir->inst_tags[last_inst] != ZIR_INST_BREAK_INLINE) UNREACHABLE("packed struct: field type body not break_inline"); // The field type body ends in break_inline of the type. A non-standard // integer field (`u7`/`u1`, the HashMap Metadata + repro shape) is a // `[int_type, break_inline]` body — resolve int_type directly // (Sema.zirIntType). A preinterned primitive (`u8`) is a bare break of // a ref. A named container type delegates to semaBodyFieldType // (decl_val); if that names a member of this struct's namespace it // defers. uint32_t fty = IP_INDEX_NONE; uint32_t brk_op = zir->inst_datas[last_inst].break_data.operand; if (brk_op < ZIR_REF_START_INDEX) { fty = semaResolveRef(brk_op, inst_values); } else { uint32_t prod = brk_op - ZIR_REF_START_INDEX; if (zir->inst_tags[prod] == ZIR_INST_INT_TYPE) { uint16_t bits = zir->inst_datas[prod].int_type.bit_count; bool is_signed = (zir->inst_datas[prod].int_type.signedness == SIGNEDNESS_SIGNED); fty = ipGetIntType(ip, is_signed, bits); } else { bool defer_field = false; fty = semaBodyFieldType( zcu, zir, body_ptr, body_len, &defer_field); if (defer_field) { deferred_field[deferred_len] = i; deferred_body_off[deferred_len] = zf.type_body; deferred_body_len[deferred_len] = body_len; deferred_len++; fty = IP_INDEX_NONE; // resolved in pass 2 } } } field_ty_arr[i] = fty; } // Intern the packed struct type with a `.none` backing (as upstream's // `".backing_int_type = .none,"`); the real backing int + per-field bit // offsets are patched in the final pass, mirroring // `resolvePackedStructLayout`, once all field types (incl. deferred) are // known. The type must exist // before its namespace so a deferred field (`fingerprint: FingerPrint`) // can resolve against it. Interning before the layout is final moves only // this type's InternPool position. uint32_t zir_index = ipTrackZir(ip); // The tag's two discriminators, both already decoded above: upstream's // `packed_backing_mode` is ZIR backing-int-body presence // (`".packed_backing_mode = if (struct_decl.backing_int_type_body != null) // .explicit else .auto,"`), and `any_field_defaults` is the struct_decl // `small` bit. uint32_t struct_ty = ipGetDeclaredStructTypePacked(ip, zir_index, fields_len, /*backing_int_ty=*/IP_INDEX_NONE, /*explicit_backing=*/backing_int_body_len != 0, /*any_field_defaults=*/sd.any_field_defaults); ctDedupPublishWip(zcu, zir, struct_inst, struct_ty); // Sema.zirStructDecl names the type (setTypeName) right after // getDeclaredStructType, before any field name is interned. setTypeName( zcu, zir, struct_inst, struct_ty, type_name_ctx, self_nav, tn_func); // resolvePackedStructLayout interns the field names. for (uint32_t i = 0; i < fields_len; i++) { ipSetStructFieldName(ip, struct_ty, i, 0); ipSetStructFieldType(ip, struct_ty, i, field_ty_arr[i]); } ipDeferFieldNames(ip, struct_ty, zir->string_bytes, &zir->extra[zd.field_names], fields_len); // Cache the type on the Nav before pass 2 resolves the deferred field // types, as semaBodyStructType does (`capture: ProngInfo.Capture` names a // decl nested in this packed struct). if (self_nav != IP_INDEX_NONE) semaNavCacheType(ip, self_nav, struct_ty); // Namespace (methods / const decls like Metadata.FingerPrint, .slot_free, // .isUsed): Sema.zirStructDecl creates one for every struct. uint32_t struct_ns = zptCreateNamespace(zcu, zir, struct_ty, parent_ns_opt, decls_ptr, sd.has_decls ? decls_len : 0); // Register the pre-resolved captures under this struct's namespace (mirror // auto semaBodyStructType). Guarded by captures != NULL so a ZIR shape // with a capture list but no resolved array (Metadata's map path) is a // pure skip. if (captures != NULL && captures_arg_len != 0) { if (captures_arg_len != zd.captures_len) UNREACHABLE("packed struct: capture count mismatch"); ctStoreStructCapturesCopy(zcu, struct_ns, captures, captures_arg_len); } // Pass 2: resolve deferred field types against the struct's own namespace // chain (now scanned) — `fingerprint: FingerPrint` finds `Metadata. // FingerPrint = u7`. Mirrors semaBodyStructType's deferred pass, including // its fold gate: a body terminating on a shape single-ref // resolution cannot handle (generic CALL/FIELD_CALL, `if` type // Block, computed-length ARRAY_TYPE, `@Int(...)` REIFY_INT) folds // whole through the comptime evaluator (stock resolves field type // bodies layout-agnostically: resolveInlineBody + analyzeAsType, // src/Sema/type_resolution.zig — resolvePackedStructLayout only // consumes the already-resolved field types — so the same no-split // argument as the auto pass applies). for (uint32_t d = 0; d < deferred_len; d++) { uint32_t ty_val = semaResolveFieldTypeBody(zcu, zir, zirBodySlice(zir, deferred_body_off[d]), deferred_body_len[d], struct_ns, semaContainerTypeNameCtx(ip, struct_ty)); field_ty_arr[deferred_field[d]] = ty_val; ipSetStructFieldType(ip, struct_ty, deferred_field[d], ty_val); } // Final pass: every field type is known. uint32_t explicit_backing_ty = IP_INDEX_NONE; if (backing_int_body_len != 0) { // Sema/type_resolution.zig resolvePackedStructLayout's explicit // backing arm runs the ZIR backing_int_type_body in a comptime block // (resolveInlineBody + analyzeAsType -- ctFoldComptimeTypeBody is the // established mirror) with decl lookups scoped to the struct's own // namespace chain (`packed struct(u32)` is a bare [break_inline], // `packed struct(Backing)` a [decl_val, break_inline] body). uint32_t lookup_ns = (struct_ns != IP_NAMESPACE_NONE) ? struct_ns : zcuNamespaceOfZir(zcu, zir); explicit_backing_ty = ctFoldComptimeTypeBody(zcu, zir, zirBodySlice(zir, zd.backing_int_type_body), backing_int_body_len, lookup_ns, semaContainerTypeNameCtx(ip, struct_ty)); } resolvePackedStructLayout(zcu, struct_ty, explicit_backing_ty); // Register field-default body locations for post-layout materialization. for (uint32_t d = 0; d < def_count; d++) semaRegisterFieldDefault(zcu, struct_ty, def_field[d], struct_ns, zir, def_off[d], def_len[d], struct_inst); free(fscratch); return struct_ty; } uint32_t semaBodyStructType(Zcu* zcu, const Zir* zir, uint32_t struct_inst, const uint32_t* inst_values, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func, uint32_t parent_ns_opt, const CtCap* captures, uint32_t captures_arg_len) { InternPool* ip = &zcu->intern_pool; // Read-AND-CLEAR the self-nav hook: it applies only to this (the Nav's own // top-level) struct_decl; any nested inline anonymous struct a field type // contains re-enters semaBodyStructType with the hook already cleared. uint32_t self_nav = zcu->container_self_nav; zcu->container_self_nav = IP_INDEX_NONE; uint16_t small = zir->inst_datas[struct_inst].extended.small; StructDeclSmall sd = semaDecodeStructDeclSmall(small); bool has_decls = sd.has_decls; uint32_t layout = sd.layout; if (layout == CONTAINER_LAYOUT_PACKED) return semaBodyStructTypePacked(zcu, zir, struct_inst, inst_values, type_name_ctx, tn_func, parent_ns_opt, captures, captures_arg_len, self_nav); if (layout != CONTAINER_LAYOUT_AUTO && layout != CONTAINER_LAYOUT_EXTERN) UNIMPLEMENTED("struct_decl: non-auto/extern layout"); // layout 1 = `extern struct`: only the // interned type is flagged extern, so semaComputeStructLayout keeps // declaration order + C ABI layout (no alignment reorder). ZirStructDecl zd = zirGetStructDecl(zir, struct_inst); uint32_t zir_index = ipTrackZir(ip); uint32_t struct_ty = ipGetDeclaredStructType(ip, zir_index, /*captures_len=*/0, zd.fields_len, (layout == CONTAINER_LAYOUT_EXTERN) ? IP_STRUCT_LAYOUT_EXTERN : IP_STRUCT_LAYOUT_AUTO, sd.any_comptime_fields, sd.any_field_defaults, sd.any_field_aligns); ctDedupPublishWip(zcu, zir, struct_inst, struct_ty); // A self-referential field type (`kids: []const Node`) resolving // `decl_val(Node)` finds the struct through its Nav. Interns nothing. if (self_nav != IP_INDEX_NONE) semaNavCacheType(ip, self_nav, struct_ty); // Sema.zirStructDecl names the type (setTypeName) right after // getDeclaredStructType, then creates and scans its namespace, where the // captures and member decls live. The fields wait for // resolveStructLayout. setTypeName( zcu, zir, struct_inst, struct_ty, type_name_ctx, self_nav, tn_func); uint32_t struct_ns = zptCreateNamespace(zcu, zir, struct_ty, parent_ns_opt, &zir->extra[zd.decls], has_decls ? zd.decls_len : 0); if (captures != NULL && captures_arg_len != 0) { if (captures_arg_len != zd.captures_len) UNREACHABLE("struct_decl: capture count mismatch"); ctStoreStructCapturesCopy(zcu, struct_ns, captures, captures_arg_len); } semaDeferStructFields(zcu, struct_ty, zir, struct_inst); return struct_ty; } static bool semaUnionGeneratedTagBackingFits( uint32_t bits, bool is_signed, uint32_t fields_len) { uint32_t max_tag = fields_len ? fields_len - 1u : 0u; if (!is_signed) return bits >= 32u || max_tag < (1u << bits); if (bits == 0) return max_tag == 0; return bits >= 32u || max_tag < (1u << (bits - 1u)); } // (part of) Sema.zig:zirUnionDecl // Intern a body-local non-packed union type (Sema.zirUnionDecl for the inline // case, e.g. `const U = union(enum) { a: u32, b: u32 };`). Decodes the // `union_decl` extended instruction (fields_len, per-field name + type body), // interns the `type_union`, stores each field type, and records the names for // lazy generated-tag creation. Extern unions reuse the same TypeUnion storage // with layout=.extern and tag_usage=.none. Upstream creates the hypothetical // tag enum in resolveUnionLayout: for a top-level union, its ptr_nav therefore // precedes the enum in IP order. ipEnsureGeneratedUnionTag performs that work // at the first real field/layout demand. `union(enum(T))` carries a pending // explicit integer backing for that generated tag enum, while `union(T)` sets // the union's enum_tag_type directly. Aborts (UNIMPLEMENTED) on captures / // packed union kinds / non-trivial type bodies. The // fixed ReleaseSmall bootstrap profile maps a plain `.auto` union to tag_usage // `.none`; `union(enum)` maps to `.tagged`. Both retain the hypothetical // generated tag enum. Mirrors semaBodyStructType, extended with the tag-enum // generation. uint32_t semaBodyUnionType(Zcu* zcu, const Zir* zir, uint32_t union_inst, const uint32_t* inst_values, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func, uint32_t parent_ns_opt, const CtCap* captures, uint32_t captures_arg_len) { InternPool* ip = &zcu->intern_pool; // Read-AND-CLEAR the self-nav hook (see semaBodyStructType) — the union's // own top-level union_decl only. uint32_t self_nav = zcu->container_self_nav; zcu->container_self_nav = IP_INDEX_NONE; uint16_t small = zir->inst_datas[union_inst].extended.small; uint32_t kind = (small >> ZIR_UNION_SMALL_KIND_SHIFT) & ZIR_UNION_SMALL_KIND_MASK; bool any_field_aligns = (small & ZIR_UNION_SMALL_ANY_FIELD_ALIGNS) != 0; bool any_field_values = (small & ZIR_UNION_SMALL_ANY_FIELD_VALUES) != 0; if (kind > ZIR_UNION_KIND_PACKED_EXPLICIT) UNIMPLEMENTED("union_decl: unsupported union kind"); // `Zir.Inst.UnionDecl.Kind.layout`: `.packed` / `.packed_explicit`. bool is_packed = kind == ZIR_UNION_KIND_PACKED || kind == ZIR_UNION_KIND_PACKED_EXPLICIT; // After the count words: captures/decls, then per-field metadata + // bodies. `.auto` and `.tagged_enum` have no arg_type_body. ZirContainerTrailing tw = zirContainerTrailingWords(zir, union_inst); uint32_t ci = tw.captures; uint32_t captures_len = tw.captures_len, decls_len = tw.decls_len; uint32_t fields_len = tw.fields_len; bool has_arg_type = zirUnionKindHasArgType(kind); uint32_t arg_type_body_len = tw.arg_body_len; ci += 2 * captures_len; // captures[] + capture_names[] uint32_t decls_off = ci; ci += decls_len; // decls[] // field_name[fields_len], field_type_body_len[fields_len], the align // and value body lengths when `any_field_aligns` / `any_field_values`, // the arg type body, and per field `"type body, then align body, then // value body"`. uint32_t field_name_off = ci; uint32_t type_body_len_off = ci + fields_len; uint32_t align_body_len_off = type_body_len_off + fields_len; uint32_t value_body_len_off = align_body_len_off + (any_field_aligns ? fields_len : 0); uint32_t bodies_off = value_body_len_off + (any_field_values ? fields_len : 0); uint32_t body_off = bodies_off + arg_type_body_len; uint32_t zir_index = ipTrackZir(ip); uint32_t tag_usage = (kind == ZIR_UNION_KIND_TAGGED_EXPLICIT || kind == ZIR_UNION_KIND_TAGGED_ENUM || kind == ZIR_UNION_KIND_TAGGED_ENUM_EXPLICIT) ? IP_UNION_TAG_USAGE_TAGGED : IP_UNION_TAG_USAGE_NONE; bool is_extern_layout = kind == ZIR_UNION_KIND_EXTERN; uint32_t union_ty = is_packed ? ipGetDeclaredUnionTypePacked(ip, zir_index, fields_len, kind == ZIR_UNION_KIND_PACKED_EXPLICIT) : ipGetDeclaredUnionType(ip, zir_index, fields_len, tag_usage, is_extern_layout, any_field_aligns); // Sema.zirUnionDecl: setTypeName right after getDeclaredUnionType; a // Nav's own union takes the `.parent` strategy's type_name_ctx. setTypeName( zcu, zir, union_inst, union_ty, type_name_ctx, self_nav, tn_func); // Sema.zirUnionDecl resolves the captures (`sema.getCaptures`) and creates // the union's namespace before anything belonging to the declaration is // analyzed, so a `closure_get` in the tag-argument type body or a field // type body finds them, and `@This()` reads its owner. The caller // (semaDeclaredNamespaceType) resolved the capture descriptors against its // frame and keys the instantiation on them. if (captures_len != 0 && (captures == NULL || captures_arg_len != captures_len)) UNIMPLEMENTED("union_decl: unresolved captures"); uint32_t union_ns = zptCreateNamespace( zcu, zir, union_ty, parent_ns_opt, &zir->extra[decls_off], decls_len); if (captures_len != 0) ctStoreStructCapturesCopy(zcu, union_ns, captures, captures_arg_len); uint32_t explicit_tag_ty = IP_INDEX_NONE; uint32_t packed_backing_ty = IP_INDEX_NONE; uint32_t generated_tag_int_ty = IP_INDEX_NONE; uint32_t generated_tag_int_bits = UINT32_MAX; bool generated_tag_int_signed = false; if (has_arg_type) { const uint32_t arg_type_body_off = bodies_off; if (arg_type_body_len == 0) UNIMPLEMENTED("union_decl: empty tag argument type body"); uint32_t last = zir->extra[arg_type_body_off + arg_type_body_len - 1]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) UNIMPLEMENTED("union_decl: tag argument type body not a break"); uint32_t tag_ref = zir->inst_datas[last].break_data.operand; // The union tag-argument type body routes through // `ctTypeBodyNeedsComptimeFold`, as every signature type body does. // // The lookup namespace mirrors the packed-struct backing-int fold a // few hundred lines above (anchor "explicit backing type is not an "): // the declaring namespace when there is one, else the file root. // // A `&decl` body stays loud on both routes, as stock rejects it. bool arg_needs_fold = ctTypeBodyNeedsComptimeFold(zir, tag_ref); // With captures, the union's own namespace: that is where they are // keyed, and `union(E)` over a captured `E` breaks on a closure_get. uint32_t arg_lookup_ns = union_ns; if (kind == ZIR_UNION_KIND_TAGGED_EXPLICIT || kind == ZIR_UNION_KIND_PACKED_EXPLICIT) { uint32_t tag_ty; if (arg_needs_fold) tag_ty = ctFoldComptimeTypeBody(zcu, zir, zirBodySlice(zir, arg_type_body_off), arg_type_body_len, arg_lookup_ns, semaContainerTypeNameCtx(ip, union_ty)); else tag_ty = ctResolveFieldTypeRefInNs(ip, zcu, zir, tag_ref, arg_lookup_ns, semaContainerTypeNameCtx(ip, union_ty)); if (kind == ZIR_UNION_KIND_PACKED_EXPLICIT) { // `packed union(T)`: `T` is the backing integer, which // `resolvePackedUnionLayout` evaluates and validates // ("expected backing integer type, found '{f}'"). Evaluated // here with the tag-argument resolvers above rather than at // layout time; an int type's interning order is the only // difference. uint32_t pb_bits; bool pb_signed; if (!ztIntInfo(ip, tag_ty, &pb_bits, &pb_signed)) COMPILE_ERROR_UPSTREAM( "packed union: expected backing integer type"); packed_backing_ty = tag_ty; } else if (!ipIsEnumType(ip, tag_ty)) UNIMPLEMENTED("union_decl: explicit tag type is not an enum"); if (kind == ZIR_UNION_KIND_TAGGED_EXPLICIT) { ipSetUnionEnumTagType(ip, union_ty, tag_ty); explicit_tag_ty = tag_ty; } } else { uint32_t bits = 0; bool is_signed = false; if (tag_ref < ZIR_REF_START_INDEX) { generated_tag_int_ty = tag_ref; // a preinterned index if (!ztIntInfo(ip, generated_tag_int_ty, &bits, &is_signed)) UNIMPLEMENTED( "union_decl: generated tag backing is not an int"); } else if (arg_needs_fold) { // The generated tag's backing int type, computed // (`union(enum(math.Log2Int(usize)))`). Mirrors the // preinterned branch above: fold to a type, then read its // width/signedness with the same ztIntInfo call and reuse // its abort message for a non-int result. // resolveEnumLayout of the generated tag: its Block has the // tag's own name. TypeNameCtx tag_ctx; ipGeneratedUnionTagName( ip, union_ty, &tag_ctx.name, &tag_ctx.fqn); generated_tag_int_ty = ctFoldComptimeTypeBody(zcu, zir, zirBodySlice(zir, arg_type_body_off), arg_type_body_len, arg_lookup_ns, tag_ctx); if (!ztIntInfo(ip, generated_tag_int_ty, &bits, &is_signed)) UNIMPLEMENTED( "union_decl: generated tag backing is not an int"); } else { uint32_t prod = tag_ref - ZIR_REF_START_INDEX; if (zir->inst_tags[prod] != ZIR_INST_INT_TYPE) UNIMPLEMENTED( "union_decl: computed generated tag backing"); bits = zir->inst_datas[prod].int_type.bit_count; is_signed = (zir->inst_datas[prod].int_type.signedness == SIGNEDNESS_SIGNED); generated_tag_int_bits = bits; generated_tag_int_signed = is_signed; } if (!semaUnionGeneratedTagBackingFits(bits, is_signed, fields_len)) UNIMPLEMENTED("union_decl: generated tag backing too small"); } } // Cache the interned index on the Nav now, before resolving field types, // so a self-referential slice/pointer payload field (the CType `.function` // node's `param_tys: []const CType`) resolving `decl_val(Self)` finds the // already-interned union type instead of re-entering its Nav's analysis. // See semaBodyStructType for the read-and-clear discipline. if (self_nav != IP_INDEX_NONE) semaNavCacheType(ip, self_nav, union_ty); // Resolve field names. A primitive preinterned type and a bare field's // void payload create no IP items and can be stored immediately. Every // other field is retained as metadata only until a real field/layout // demand, so the generated enum is guaranteed to precede any payload type // items it causes to be interned. uint32_t* deferred_field = zig1Malloc(fields_len * sizeof(uint32_t)); uint32_t* deferred_body_off = zig1Malloc(fields_len * sizeof(uint32_t)); uint32_t* deferred_body_len = zig1Malloc(fields_len * sizeof(uint32_t)); uint32_t deferred_len = 0; uint32_t body_cursor = body_off; if (any_field_values && kind != ZIR_UNION_KIND_TAGGED_ENUM_EXPLICIT) COMPILE_ERROR_UPSTREAM( "union_decl: explicitly valued union needs an integer tag"); // Each field's align and value bodies, evaluated at layout resolution. uint32_t* layout_bodies = NULL; if (any_field_aligns || any_field_values) { layout_bodies = zig1Calloc( (size_t)(fields_len ? fields_len : 1u) * UNION_LAYOUT_BODY_WORDS, sizeof(uint32_t)); } for (uint32_t i = 0; i < fields_len; i++) { uint32_t body_len = zir->extra[type_body_len_off + i]; uint32_t this_body_off = body_cursor; body_cursor += body_len; uint32_t* field_bodies = layout_bodies == NULL ? NULL : &layout_bodies[i * UNION_LAYOUT_BODY_WORDS]; if (any_field_aligns) { field_bodies[0] = body_cursor; field_bodies[1] = zir->extra[align_body_len_off + i]; body_cursor += field_bodies[1]; } if (any_field_values) { field_bodies[2] = body_cursor; field_bodies[3] = zir->extra[value_body_len_off + i]; body_cursor += field_bodies[3]; } if (body_len == 0) { // Bare (payload-less) union field: void payload. ipSetUnionFieldType(ip, union_ty, i, ZIR_REF_VOID_TYPE); continue; } uint32_t body_inst = zir->extra[this_body_off + body_len - 1]; if (zir->inst_tags[body_inst] != ZIR_INST_BREAK_INLINE) UNREACHABLE("union_decl: field type body not break_inline"); uint32_t ty_ref = zir->inst_datas[body_inst].break_data.operand; if (body_len == 1 && ty_ref < ZIR_REF_START_INDEX) { uint32_t ty_val = semaResolveRef(ty_ref, inst_values); ipSetUnionFieldType(ip, union_ty, i, ty_val); } else { // Non-primitive type body (`int: Int`, `tail: []const u16`, ...): // register after the namespace scan, but do not resolve now. deferred_field[deferred_len] = i; deferred_body_off[deferred_len] = this_body_off; deferred_body_len[deferred_len] = body_len; deferred_len++; } } // A declared explicit tag enum must match the union field names in exact // declaration order. Pinned resolveUnionLayout rejects missing, extra, or // reordered names; backing integer values may still be arbitrary. if (explicit_tag_ty != IP_INDEX_NONE) { if (ipEnumFieldsLen(ip, explicit_tag_ty) != fields_len) UNIMPLEMENTED("union_decl: explicit tag field count mismatch"); for (uint32_t i = 0; i < fields_len; i++) if (strcmp( ipStringBytes(ip, ipEnumFieldName(ip, explicit_tag_ty, i)), (const char*)&zir ->string_bytes[zir->extra[field_name_off + i]]) != 0) UNIMPLEMENTED("union_decl: explicit tag field order mismatch"); } // Preserve declaration-order names out-of-band for both generated and // explicit tags. In auto mode the declaration itself must not intern its // generated enum: stock does so during layout, after a top-level Nav // reference has created ptr_nav. In explicit mode // ipEnsureGeneratedUnionTag observes the already-backpatched enum but the // same names map union field indices to arbitrary enum tag values. ipRegisterGeneratedUnionTag(ip, union_ty, fields_len, generated_tag_int_ty, generated_tag_int_bits, generated_tag_int_signed); if (layout_bodies != NULL) { if (any_field_values) ipDeferPendingUnionTagValues(ip, union_ty); semaRegisterUnionLayoutDeferred(zcu, (UnionLayoutDeferred) { union_ty, union_ns, zir, union_inst, fields_len, layout_bodies, any_field_aligns, any_field_values, generated_tag_int_ty, generated_tag_int_bits, generated_tag_int_signed, UNION_LAYOUT_PENDING }); } if (explicit_tag_ty == IP_INDEX_NONE) ipDeferFieldNames(ip, union_ty, zir->string_bytes, &zir->extra[field_name_off], fields_len); if (packed_backing_ty != IP_INDEX_NONE) ipSetPendingPackedUnionBacking(ip, union_ty, packed_backing_ty); // Register every non-primitive field now that its lookup namespace is // known. No payload type is resolved here: std.builtin.Type remains lazy, // while a later CValue/Tree/struct-init/layout demand resolves all fields // in declaration order after ipEnsureGeneratedUnionTag. for (uint32_t d = 0; d < deferred_len; d++) semaRegisterUnionFieldDeferred(zcu, union_ty, deferred_field[d], union_ns, zir, deferred_body_off[d], deferred_body_len[d]); free(deferred_field); free(deferred_body_off); free(deferred_body_len); return union_ty; } // A generic callee's parameter or return type body, `"sema.resolveInlineBody"` // in analyzeCall's block with the comptime arguments in the inst map; runtime // parameters stay unset (their only legal use is under `@TypeOf`, which reads // their types). `gb` is NULL for a callee with no bindings yet. static uint32_t semaCtEvalGenericBody(Zcu* zcu, const Zir* zir, const GenericBindings* gb, uint32_t func_decl, const uint32_t* body, uint32_t body_len) { const InternPool* ip = &zcu->intern_pool; uint32_t* ct_values = semaInstMapAlloc(zir); uint32_t* ct_param_tys = semaInstMapAlloc(zir); for (uint32_t p = 0; gb != NULL && p < gb->nparams; p++) { uint32_t i = gb->param_insts[p]; if (i >= zir->inst_len) UNREACHABLE( "analyzeCall: generic type body, bound param out of range"); ct_param_tys[i] = gb->types[p]; if (semaRefIsInterned(gb->values[p])) ct_values[i] = gb->values[p]; } const IpNav* nav = ipGetNav(ip, ipFuncDeclOwnerNav(ip, func_decl)); uint32_t ty = ctEvalTypeBody(zcu, zir, body, body_len, ct_values, ct_param_tys, nav->analysis_namespace, TYPE_NAME_CTX_NAV(nav)); free(ct_param_tys); free(ct_values); return ty; } // (part of) Sema.zig:analyzeCall // Evaluate a callee's `[generic]` parameter type body (a type that refers to a // previous comptime parameter, e.g. `x: T`) with the comptime arguments bound // in `bound_map` (indexed like `inst_values`: a ZIR Ref >= ZIR_REF_START_INDEX // maps to the callee instruction it names). Mirrors Sema.analyzeCall resolving // a generic parameter type via `resolveInlineBody` over the // `generic_inst_map`. Only a single // terminating `break_inline` whose operand is a preinterned type ref or a // bound comptime type parameter — is handled; anything else aborts (no wrong // output). // If `op` is a `ptr_type` ZIR instruction composing a pointer/slice type from // a bound comptime parameter (e.g. `x: *T` / `slice: []const T`, whose param // or return type body lowers to `[ptr_type(elem=T), break_inline]`), resolve // it against `bound_map` and return the composed IP type. Returns // IP_INDEX_NONE when `op` is not such a `ptr_type` instruction (the caller // falls back to resolving `op` as a plain bound/preinterned type ref). // Shared by semaResolveGenericParamType (param types) and // semaResolveGenericRetType (return types) — mirrors Sema analyzing the // generic type body's composing instruction with the comptime args bound. // Decode mirrors ctResolveParamTypeRef's ptr_type branch (sema_comptime.c), // but resolves the elem ref against the bound map instead of `inst_values`. static uint32_t semaResolveGenericComposedPtrType( InternPool* ip, const Zir* zir, uint32_t op, const GenericBindings* gb) { if (op < ZIR_REF_START_INDEX || zir->inst_tags[op - ZIR_REF_START_INDEX] != ZIR_INST_PTR_TYPE) return IP_INDEX_NONE; uint32_t pi = op - ZIR_REF_START_INDEX; ZirInstPtrType pt_extra; uint32_t pt_extra_end = zirExtraData( zir, zir->inst_datas[pi].ptr_type.payload_index, &pt_extra); uint8_t zir_flags = zir->inst_datas[pi].ptr_type.flags; uint8_t zir_size = zir->inst_datas[pi].ptr_type.size; if (zir_flags & ZIR_PTR_TYPE_FLAGS_HAS_ADDRSPACE) UNIMPLEMENTED("analyzeCall: generic type, ptr_type with addrspace"); if (zir_flags & ZIR_PTR_TYPE_FLAGS_HAS_BIT_RANGE) UNIMPLEMENTED("analyzeCall: generic type, ptr_type with bit_range"); uint32_t ev = semaGenericBindingValueRef(gb, pt_extra.elem_type); if (ev == INST_VALUE_UNSET) // The pointer's elem type is a computed comptime expression (not a // directly-bound param) — e.g. `*const FieldType(field)` / // `[]FieldType(field)`, the return type of MultiArrayList.items and // friends. Fall through (none) so the caller's general comptime-body // evaluator resolves the whole type body. semaResolveGenericRetType // has such a fallback; the param-type caller still aborts loudly (its // own "multi-instruction body" / "unbound operand" abort — no wrong // output). return IP_INDEX_NONE; // an integer value is not a type. The // `AIR_REF_INST_BASE` disjunct catches a runtime ref, but an // already-INTERNED integer value is a small IP index that passes it and // would be returned as `elem_ty` — a `type_pointer` whose child is an // integer value item, a silent accept. Test the item itself, // keeping the same message. if (ev >= AIR_REF_INST_BASE || semaInternedIntValueType(ip, ev) != IP_INDEX_NONE) UNIMPLEMENTED("analyzeCall: generic type, ptr elem not a bound type"); uint32_t elem_ty = ev; uint32_t sentinel_val = IP_INDEX_NONE; if (zir_flags & ZIR_PTR_TYPE_FLAGS_HAS_SENTINEL) { sentinel_val = semaResolveGenericPtrSentinelRef( ip, zir, zir->extra[pt_extra_end], gb, elem_ty); } uint32_t ip_flags = semaPtrTypeFlags(ip, zir_flags, zir_size, elem_ty); if (zir_flags & ZIR_PTR_TYPE_FLAGS_HAS_ALIGN) { uint32_t align_ref = zir->extra[pt_extra_end + ((zir_flags & ZIR_PTR_TYPE_FLAGS_HAS_SENTINEL) ? 1u : 0u)]; uint64_t align_bytes; if (align_ref == ZIR_REF_ONE) { align_bytes = 1; } else if (align_ref >= ZIR_REF_START_INDEX && zir->inst_tags[align_ref - ZIR_REF_START_INDEX] == ZIR_INST_INT) { align_bytes = zir->inst_datas[align_ref - ZIR_REF_START_INDEX].int_val; } else { uint32_t av = semaGenericBindingValueRef(gb, align_ref); if (av == INST_VALUE_UNSET) return IP_INDEX_NONE; if (semaIsAirInstRef(av)) UNIMPLEMENTED( "analyzeCall: generic type, runtime ptr align expression"); if (av != INST_VALUE_UNSET) { align_bytes = ipReadIntValue(ip, av); } else { UNIMPLEMENTED( "analyzeCall: generic type, unset ptr align expression"); } } if (align_bytes == 0 || align_bytes == UINT64_MAX || (align_bytes & (align_bytes - 1)) != 0 || align_bytes > 0x1FFFFFFFu) UNIMPLEMENTED( "analyzeCall: generic type, ptr align not a power of two"); ipGetIntSmall(ip, 6u, (uint32_t)align_bytes); uint32_t align_log2 = 0; while ((align_bytes >> align_log2) > 1) align_log2++; ip_flags = IP_PTR_FLAGS_WITH_ALIGN(ip_flags, align_log2); } return semaPtrTypeIntern(ip, elem_ty, ip_flags, zir_size, sentinel_val, 0); } static uint32_t semaResolveGenericParamType(InternPool* ip, Zcu* zcu, uint32_t func_decl, const Zir* zir, uint32_t param_inst, const GenericBindings* gb) { // Guard against an out-of-range param_inst: a generic callee may present a // param-collection shape not modeled, leaving a stale index here. Abort // loudly rather than dereference past the ZIR instruction array. if (param_inst >= zir->inst_len) UNIMPLEMENTED("analyzeCall: generic param type, param_inst out of " "range (unmodeled generic callee param structure)"); ZirInstParam param_extra; uint32_t param_body_start = zirExtraData( zir, zir->inst_datas[param_inst].pl_tok.payload_index, ¶m_extra); uint32_t plen = param_extra.type & ZIR_PARAM_TYPE_BODY_LEN_MASK; if (plen == 0) UNIMPLEMENTED("analyzeCall: generic param type, empty body"); uint32_t last = zir->extra[param_body_start + plen - 1]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) UNREACHABLE("analyzeCall: generic param type, body not break_inline"); uint32_t op = zir->inst_datas[last].break_data.operand; // `@TypeOf(param)` parameter type (e.g. `fn f(a: anytype, b: // @TypeOf(a))`): resolve to the referenced (earlier) param's monomorphic // type via `bound_ty_map`. `@TypeOf(x)` lowers to a `typeof_builtin` block // whose body ends in `break_inline(%operand)`, where // `%operand` is the expression whose type is wanted; a bound param ref // resolves via `bound_ty_map` (mirror of semaResolveGenericRetType). if (gb != NULL && op >= ZIR_REF_START_INDEX && (zir->inst_tags[op - ZIR_REF_START_INDEX] == ZIR_INST_TYPEOF || zir->inst_tags[op - ZIR_REF_START_INDEX] == ZIR_INST_TYPEOF_BUILTIN)) { uint32_t ti = op - ZIR_REF_START_INDEX; uint32_t operand; if (zir->inst_tags[ti] == ZIR_INST_TYPEOF) { operand = zir->inst_datas[ti].un_node.operand; } else { ZirInstBlock tb_extra; uint32_t tb_end = zirExtraData( zir, zir->inst_datas[ti].pl_node.payload_index, &tb_extra); uint32_t blen = tb_extra.body_len; if (blen == 0) UNIMPLEMENTED("analyzeCall: generic param type, empty " "typeof_builtin body"); uint32_t blast = zir->extra[tb_end + blen - 1]; if (zir->inst_tags[blast] != ZIR_INST_BREAK_INLINE) UNREACHABLE( "analyzeCall: generic param type, typeof_builtin body not " "break_inline"); operand = zir->inst_datas[blast].break_data.operand; } if (operand >= ZIR_REF_START_INDEX) { uint32_t pinst = operand - ZIR_REF_START_INDEX; uint32_t pty = semaGenericBindingTypeInst(gb, pinst); if (pinst < zir->inst_len && pty != IP_INDEX_NONE) return pty; } UNIMPLEMENTED("analyzeCall: generic param type, @TypeOf operand not a " "bound param"); } // A multi-instruction body composes a type from the bound comptime // parameter, e.g. `x: *T` / `slice: []const T` lower the param type body // to `[ptr_type(elem=T), break_inline]`. Evaluate the composing // instruction with the bound map supplying `T` (mirrors Sema analyzing // the generic parameter's type body with the comptime args bound). uint32_t composed = semaResolveGenericComposedPtrType(ip, zir, op, gb); if (composed != IP_INDEX_NONE) return composed; if (plen != 1) { // A multi-instruction param-type body that composes a type from a // generic call over the bound args (`indexes: []Index(I)`: the element // type is a generic instantiation, not a directly-bound param, so // semaResolveGenericComposedPtrType fell through). Comptime-evaluate // the whole body: seed the eval map from the bound comptime params, // scope decl lookups to the callee's namespace. Mirrors // semaResolveGenericRetType's identical fallback. const uint32_t* param_body = zirBodySlice(zir, param_body_start); return semaCtEvalGenericBody( zcu, zir, gb, func_decl, param_body, plen); } uint32_t v = semaGenericBindingValueRef(gb, op); // Class-H (H10): same silent-accept shape as H9 above — an interned // integer value passes the range test and would be returned as a type. if (v >= AIR_REF_INST_BASE || semaInternedIntValueType(ip, v) != IP_INDEX_NONE) UNIMPLEMENTED( "analyzeCall: generic param type, operand is not a comptime type"); return v; } // (part of) Sema.zig:analyzeCall // Evaluate a callee's `[generic]` return type with the comptime arguments // bound in `bound_map` (zirGetFnInfo's ret_ty_ref or ret_ty_body, the body // ending in break_inline). Mirrors Sema.analyzeCall's // generic return-type resolution. A simple Ref may itself be a `ptr_type` // instruction composing the return type from a bound comptime parameter // (e.g. `fn trim(comptime T: type, ...) []const T` — the ret type body is // `T`'s ptr_type), handled via the shared semaResolveGenericComposedPtrType // (also used for param types); otherwise a plain // preinterned/bound-comptime-type operand. static uint32_t semaResolveGenericRetType(InternPool* ip, Zcu* zcu, uint32_t func_decl, const Zir* zir, uint32_t func_inst, const GenericBindings* gb) { // A generic callee's ZIR is fetched whenever any_generic_types is set. if (zir == NULL) UNREACHABLE("analyzeCall: generic ret type without the callee's ZIR"); uint32_t ref; const uint32_t* ret_body = NULL; // multi-instruction ret-type body (for uint32_t ret_body_len = 0; // the general comptime-eval fallback) ZirFnInfo fn_info = zirGetFnInfo(zir, func_inst); if (fn_info.ret_ty_body_len != 0) { uint32_t last = fn_info.ret_ty_body[fn_info.ret_ty_body_len - 1]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) UNREACHABLE( "analyzeCall: generic ret type, body not break_inline"); ref = zir->inst_datas[last].break_data.operand; ret_body = fn_info.ret_ty_body; ret_body_len = fn_info.ret_ty_body_len; } else if (fn_info.ret_ty_ref == ZIR_REF_VOID_TYPE) { UNIMPLEMENTED("analyzeCall: generic ret type, void body"); } else { ref = fn_info.ret_ty_ref; } // `@TypeOf(param)` return type (e.g. `fn id(x: anytype) @TypeOf(x)`): // resolve to the bound param's monomorphic type. `@TypeOf(x)` lowers to a // `typeof_builtin` block whose body ends in `break_inline(%operand)`, // where `%operand` is the expression whose type is wanted; a bound param // ref resolves via `bound_ty_map`. if (gb != NULL && ref >= ZIR_REF_START_INDEX && (zir->inst_tags[ref - ZIR_REF_START_INDEX] == ZIR_INST_TYPEOF || zir->inst_tags[ref - ZIR_REF_START_INDEX] == ZIR_INST_TYPEOF_BUILTIN)) { uint32_t ti = ref - ZIR_REF_START_INDEX; uint32_t operand; if (zir->inst_tags[ti] == ZIR_INST_TYPEOF) { operand = zir->inst_datas[ti].un_node.operand; } else { // typeof_builtin: pl_node -> Block{ body_len, body[] }; last inst // is break_inline, its operand is the typed expression. ZirInstBlock tb_extra; uint32_t tb_end = zirExtraData( zir, zir->inst_datas[ti].pl_node.payload_index, &tb_extra); uint32_t blen = tb_extra.body_len; if (blen == 0) UNIMPLEMENTED("analyzeCall: generic ret type, empty " "typeof_builtin body"); uint32_t blast = zir->extra[tb_end + blen - 1]; if (zir->inst_tags[blast] != ZIR_INST_BREAK_INLINE) UNREACHABLE("analyzeCall: generic ret type, typeof_builtin " "body not break_inline"); operand = zir->inst_datas[blast].break_data.operand; } if (operand >= ZIR_REF_START_INDEX) { uint32_t pinst = operand - ZIR_REF_START_INDEX; uint32_t pty = semaGenericBindingTypeInst(gb, pinst); if (pinst < zir->inst_len && pty != IP_INDEX_NONE) return pty; } // Not a direct param (`@TypeOf(self.field)` etc.). Let the general // return-body evaluator below resolve the expression with the // comptime/bound parameter maps instead of rejecting the form here. } uint32_t composed = semaResolveGenericComposedPtrType(ip, zir, ref, gb); if (composed != IP_INDEX_NONE) return composed; uint32_t v = semaGenericBindingValueRef(gb, ref); if (v == INST_VALUE_UNSET) { // The ret-type body is a general comptime expression over the bound // args (a return-type call `F(@TypeOf(slice))`) — only param insts are // in bound_map, so any other instruction ref lands here // Comptime-evaluate the whole body: comptime param values seed the // eval map; runtime params stay unset (their only legal ret-type use // is under `@TypeOf`, served by bound_ty_map); decl lookups scope to // the callee's namespace. if (ret_body == NULL) UNIMPLEMENTED( "analyzeCall: generic ret type, unbound simple-ref operand"); return semaCtEvalGenericBody( zcu, zir, gb, func_decl, ret_body, ret_body_len); } // Class-H (H11): same silent-accept shape as H9/H10 above. if (v >= AIR_REF_INST_BASE || semaInternedIntValueType(ip, v) != IP_INDEX_NONE) UNIMPLEMENTED( "analyzeCall: generic ret type, operand is not a comptime type"); return v; } // (part of) Sema.zig:analyzeCall // `resolved_ret_ty`: a `[generic]` return type, or the bare payload of an // inferred error set's `!generic_poison` rebuilt around its set, // resolved with the arguments bound; any other return type as declared. static uint32_t semaResolvedRetType(InternPool* ip, Zcu* zcu, uint32_t func_decl, const Zir* zir, uint32_t func_inst, const GenericBindings* gb, uint32_t declared_ret_ty) { if (declared_ret_ty == IP_INDEX_GENERIC_POISON_TYPE) return semaResolveGenericRetType( ip, zcu, func_decl, zir, func_inst, gb); if (!semaIsErrorUnionType(ip, declared_ret_ty) || ipErrorUnionPayload(ip, declared_ret_ty) != IP_INDEX_GENERIC_POISON_TYPE) return declared_ret_ty; uint32_t bare_ty = semaResolveGenericRetType(ip, zcu, func_decl, zir, func_inst, gb); if (ipItemTagIs(ip, declared_ret_ty, IP_TAG_TYPE_ANYERROR_UNION)) return ipGetAnyerrorUnionType(ip, bare_ty); return ipGetErrorUnionType( ip, ipErrorUnionErrorSet(ip, declared_ret_ty), bare_ty); } // Sema.zig:ResolvedFieldCallee // `.direct`, or `.method` with its bound first argument `arg0_inst`. // `object_ptr` is the pointer a by-value `arg0_inst` was loaded through. typedef struct { uint32_t func_inst; uint32_t func_ty; // `sema.typeOf(func_inst)` bool is_method; uint32_t arg0_inst; uint32_t arg0_ty; uint32_t object_ptr; } ResolvedFieldCallee; // Forward declaration: the call tail below is defined after analyzeBodyInner // (they are mutually recursive through argument-body analysis). static void semaAnalyzeRuntimeCall(SemaCtx* sema, U32List* block, uint32_t inst, const ResolvedFieldCallee* resolved, uint32_t func_decl, uint32_t func_ty, uint32_t abase, uint32_t args_len); static void semaAnalyzeRuntimeInlineCall(SemaCtx* sema, U32List* block, uint32_t inst, uint32_t func_decl, uint32_t func_ty, const uint32_t* arg_refs, const uint32_t* arg_types, uint32_t args_len); static void semaAnalyzeInlineCallArgs(SemaCtx* sema, U32List* block, uint32_t inst, uint32_t func_decl, uint32_t func_ty, uint32_t abase, uint32_t args_len, uint32_t po, uint32_t* arg_refs, uint32_t* arg_types); // Sema.analyzeCall's routing once the call is known to be an inline call // ("const is_inline_call = block.semaBlockIsComptime() or inline_requested", // src/Sema.zig): the body is analyzed at the call site either way, and // upstream's comptime scope decides whether the result is folded // (`ctAnalyzeInlineCall`) or lowered into the caller's AIR // (`semaAnalyzeRuntimeInlineCall`). Every call arm routes through here, so the // two shapes and the comptime-argument rule have one implementation; what // each arm passes for `comptime_scope` is documented at its call site. static void semaAnalyzeInlineCallDispatch(SemaCtx* sema, U32List* block, uint32_t inst, uint32_t func_decl, uint32_t func_ty, const uint32_t* arg_refs, const uint32_t* arg_types, uint32_t args_len, bool comptime_scope); // Emit an indirect call through a function-pointer value (`call_operand`): a // comptime `ptr_nav` (a known `*const fn` to a Nav) or a runtime AIR ref (a // function pointer loaded from memory — the Allocator vtable case). `func_ty` // is the pointee `type_function`. Non-generic only (function pointers cannot // be generic). Defined after analyzeBodyInner (mutually recursive via arg // bodies). static void semaAnalyzeIndirectCall(SemaCtx* sema, U32List* block, uint32_t inst, uint32_t call_operand, uint32_t func_ty, uint32_t abase, uint32_t args_len); // (part of) Sema.zig:zirCmpxchg // Sema.zirCmpxchg's ordering checks, then its AIR flags `"@intFromEnum( // success_order) | (@intFromEnum(failure_order) << 3)"`. uint32_t semaCmpxchgFlags( Zcu* zcu, uint32_t success_val, uint32_t failure_val) { uint32_t success = resolveStdLangEnum(zcu, success_val, "AtomicOrder"); uint32_t failure = resolveStdLangEnum(zcu, failure_val, "AtomicOrder"); if (success == ATOMIC_ORDER_UNORDERED) // `< @intFromEnum(.monotonic)`: // `.unordered` COMPILE_ERROR_UPSTREAM("cmpxchg: success atomic ordering must be " "monotonic or stricter"); if (failure == ATOMIC_ORDER_UNORDERED) COMPILE_ERROR_UPSTREAM("cmpxchg: failure atomic ordering must be " "monotonic or stricter"); if (failure > success) COMPILE_ERROR_UPSTREAM("cmpxchg: failure atomic ordering must be no " "stricter than success"); if (failure == ATOMIC_ORDER_RELEASE || failure == ATOMIC_ORDER_ACQ_REL) COMPILE_ERROR_UPSTREAM("cmpxchg: failure atomic ordering must not be " "release or acq_rel"); return success | (failure << AIR_ATOMIC_FLAGS_HIGH_SHIFT); } // `Block.addTyOp` (src/Sema.zig: "fn addTyOp(block: *Block, tag: Air.Inst.Tag, // ty: Type, operand: Air.Inst.Ref)"): append one ty_op instruction to the // block in progress and hand back its Ref. Every `coerceExtra` strategy // lowering is this single append -- `.bit_cast`, `.ptr_cast`, `.error_cast`, // `array_to_slice`, `wrap_optional` -- so it is written once here. static uint32_t semaEmitTyOp(AirInstList* insts, U32List* block, AirInstTag tag, uint32_t dest_ty, uint32_t operand) { AirInstData d; memset(&d, 0, sizeof(d)); d.pair.a = dest_ty; // ty_op dest type d.pair.b = operand; uint32_t idx = semaAirInstAppend(insts, tag, d); u32ListAppend(block, idx); return AIR_REF_INST_BASE + idx; } // `block.addUnOp(tag, operand)`: un_op data. static uint32_t semaEmitUnOp( AirInstList* insts, U32List* block, AirInstTag tag, uint32_t operand) { AirInstData d; memset(&d, 0, sizeof(d)); d.pair.a = operand; uint32_t idx = semaAirInstAppend(insts, tag, d); u32ListAppend(block, idx); return AIR_REF_INST_BASE + idx; } // Sema.zig:analyzeSwitchOperandLoad // Sema.analyzeSwitchOperandLoad: a `.simple` operand is `by_val`; a `.loop` // one loads the dispatched operand from its alloc, then once more for a // by-value capture of a by-ref operand. static uint32_t analyzeSwitchOperandLoad(AirInstList* insts, U32List* block, uint32_t by_val, uint32_t operand_ty, const SwitchLoopOperand* loop, bool by_ref) { if (loop->alloc == UINT32_MAX) return by_val; uint32_t loaded = semaEmitTyOp(insts, block, AIR_INST_LOAD, loop->raw_ty, loop->alloc); if (by_ref || !loop->is_ref) return loaded; return semaEmitTyOp(insts, block, AIR_INST_LOAD, operand_ty, loaded); } // Sema.zig:fieldPtr // Sema.fieldPtr's `.array` arm, through at most one single pointer // (`is_pointer_to`): `len` is `uavRef` of the length, `ptr` (only through a // pointer) is `"sema.coerceCompatiblePtrs(block, result_ty, object_ptr, // object_ptr_src)"`, the pointer to the array pointer read as a pointer to // its many pointer: `getCoerced` when it is comptime-known, else a `ptr_cast` // (its null check is a safety check, off here). False when the object is no // array. static bool semaFieldPtrArrayArm(InternPool* ip, uint32_t object_ptr, uint32_t object_ptr_ty, const char* field, AirInstList* insts, U32List* block, uint32_t* out_val, uint32_t* out_ty) { IpPtrInfo obj, to; IpArrayInfo arr; if (ipPtrInfo(ip, object_ptr_ty, &obj) != IP_PTRI_OK) return false; bool pointer_to = ipPtrInfo(ip, obj.child, &to) == IP_PTRI_OK && to.size == IP_PTR_SIZE_ONE; if (ipArrayInfo(ip, pointer_to ? to.child : obj.child, &arr) != IP_ARR_OK || arr.is_vector) return false; if (strcmp(field, "len") == 0) { *out_ty = ipGetPtrType(ip, ZIR_REF_USIZE_TYPE, IP_PTR_FLAGS_CONST_ONE); *out_val = ipGetPtrUav(ip, *out_ty, ipGetIntUsize(ip, arr.len)); return true; } if (strcmp(field, "ptr") != 0 || !pointer_to) COMPILE_ERROR_UPSTREAM("field_ptr: no member named this in array"); uint32_t many = ipGetPtrTypeFull(ip, arr.child, (to.flags & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_MANY, arr.sentinel, to.packed_offset); *out_ty = ipGetPtrTypeFull( ip, many, obj.flags, obj.sentinel, obj.packed_offset); *out_val = semaIsAirInstRef(object_ptr) ? semaEmitTyOp(insts, block, AIR_INST_PTR_CAST, *out_ty, object_ptr) : ipRetypePtrValue(ip, object_ptr, *out_ty); return true; } // `ptrCastFull`'s `.change_runtime_src_slice` `result_len` from the runtime // slice `operand`'s `slice_len`: `"if (std.math.divExact(u64, // change.bytes_per_src, change.bytes_per_dest)) |dest_per_src| ... .mul"`, // else `"if (std.math.divExact(u64, change.bytes_per_dest, // change.bytes_per_src)) |src_per_dest| ... .div_exact"`, else the total // bytes `.div_exact` by the destination element size. ReleaseSmall omits // the remainder safety checks. static uint32_t semaPtrCastRuntimeSliceLen(Zcu* zcu, AirInstList* insts, U32List* block, uint32_t operand, uint32_t src_elem, uint32_t dest_elem) { InternPool* ip = &zcu->intern_pool; if (ztComptimeOnly(zcu, src_elem) || ztComptimeOnly(zcu, dest_elem)) UNIMPLEMENTED("ptr_cast: comptime-only slice length restructure"); if (ztIsZigTypeTag(ip, src_elem, ZIG_TYPE_TAG_OPAQUE)) COMPILE_ERROR_UPSTREAM("ptr_cast: cannot infer length of slice from " "pointer to opaque type with unknown size"); uint64_t ssz, dsz, al, bits; if (!ztLayoutInfo(zcu, ip, src_elem, &ssz, &al, &bits) || !ztLayoutInfo(zcu, ip, dest_elem, &dsz, &al, &bits)) UNIMPLEMENTED("ptr_cast: slice element layout unresolved"); if (dsz == 0) COMPILE_ERROR_UPSTREAM( "ptr_cast: cannot infer length of slice of zero-bit type"); if (ssz > UINT32_MAX || dsz > UINT32_MAX) UNIMPLEMENTED("ptr_cast: slice element exceeds wasm32"); uint32_t operand_len = semaEmitTyOp( insts, block, AIR_INST_SLICE_LEN, ZIR_REF_USIZE_TYPE, operand); if (ssz % dsz == 0) return semaRuntimeBinOp(insts, block, AIR_INST_MUL, operand_len, ipGetIntUsize(ip, (uint32_t)(ssz / dsz))); if (ssz != 0 && dsz % ssz == 0) return semaRuntimeBinOp(insts, block, AIR_INST_DIV_EXACT, operand_len, ipGetIntUsize(ip, (uint32_t)(dsz / ssz))); uint32_t total = semaRuntimeBinOp(insts, block, AIR_INST_MUL, operand_len, ipGetIntUsize(ip, (uint32_t)ssz)); return semaRuntimeBinOp(insts, block, AIR_INST_DIV_EXACT, total, ipGetIntUsize(ip, (uint32_t)dsz)); } // Sema.zig:ptrCastFull // `Sema.ptrCastFull`, asked by `zirPtrCast` and `zirPtrCastFull` alike: // `resolveDestType(.remove_eu)` (the ret/store coercion wraps the result), // the same-type no-op, the comptime `ct:` block, then a slice operand's // `.ptr` and one AIR `ptr_cast`, or a slice rebuilt around it. // ReleaseSmall strips the safety checks. static uint32_t semaAnalyzePtrCastFull(Zcu* zcu, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t flags, uint32_t dest_ty, uint32_t op_val, uint32_t op_ty, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; if (semaIsErrorUnionType(ip, dest_ty)) dest_ty = ipErrorUnionPayload(ip, dest_ty); *out_ty = dest_ty; // A comptime operand the runtime frame never typed carries its own type. if (!semaIsAirInstRef(op_val) && op_ty == IP_INDEX_NONE && op_val != INST_VALUE_UNSET) op_ty = ipComptimeValueType(ip, op_val); if (op_ty != IP_INDEX_NONE) semaPtrCastFullCheck(zcu, ip, op_ty, dest_ty, flags); if (op_ty == dest_ty) return op_val; if (!semaIsAirInstRef(op_val)) { return semaPtrCastComptime(zcu, op_val, op_ty, dest_ty); } // `"const dest_info = dest_ty.ptrInfo(zcu);"` looks through a // pointer-like optional, so `?[]T` is a slice destination too. uint32_t dest_inner = ztPtrInfoType(ip, dest_ty); bool dest_opt = dest_inner != dest_ty; if (ztIsSlice(ip, dest_inner)) { // `"if (dest_info.flags.size == .slice) { if (src_info.flags.size == // .slice and !flags.addrspace_cast and !slice_needs_len_change) { // // Fast path: just pointer cast! ... } // We need to deconstruct // the slice (if applicable) and reconstruct it."`: the pointer is // `ptr_cast` to `want_ptr_ty` (`"switch (dest_ty.zigTypeTag(zcu)) { // .optional => try pt.optionalType(dest_ty.childType(zcu) // .slicePtrFieldType(zcu)...), .pointer => // dest_ty.slicePtrFieldType(zcu)"`) and AIR `slice` at `dest_ty` // pairs it with the `result_len`. uint32_t src_inner = ztPtrInfoType(ip, op_ty); IpPtrInfo src_info, dest_info; if (!ztPtrOrSliceInfo(ip, src_inner, &src_info) || !ztPtrOrSliceInfo(ip, dest_inner, &dest_info)) UNIMPLEMENTED("ptr_cast: runtime slice cast operand type"); uint32_t len; uint32_t ptr_val = op_val; uint32_t ptr_ty = op_ty; if (src_info.size == IP_PTR_SIZE_SLICE) { // `.equal_runtime_src_slice`: the fast path. if (src_info.child == dest_info.child) return semaEmitTyOp( insts, block, AIR_INST_PTR_CAST, dest_ty, op_val); // `operand_ptr` first (`"break :inner try // sema.analyzeOptionalSlicePtr(...)"` / `analyzeSlicePtr`), then // `operand_len` and the length arithmetic, in upstream's order. semaPtrCastOperandSlicePtr(ip, insts, block, &ptr_ty, &ptr_val); len = semaPtrCastRuntimeSliceLen( zcu, insts, block, op_val, src_info.child, dest_info.child); } else { len = ipGetIntUsize(ip, semaPtrCastDestSliceLen(zcu, 0, &src_info, dest_info.child)); } uint32_t want_ptr_ty = dest_opt ? ipGetOptionalType(ip, ip->item_datas[dest_inner]) : ip->item_datas[dest_inner]; uint32_t ptr = ptr_ty == want_ptr_ty ? ptr_val : semaEmitTyOp( insts, block, AIR_INST_PTR_CAST, want_ptr_ty, ptr_val); return semaEmitTyPlBin( insts, block, air_extra, AIR_INST_SLICE, dest_ty, ptr, len); } if (ztPtrInfoIsSlice(ip, op_ty) && !ztPtrInfoIsSlice(ip, dest_ty)) { semaPtrCastOperandSlicePtr(ip, insts, block, &op_ty, &op_val); if (op_ty == dest_ty) return op_val; } return semaEmitTyOp(insts, block, AIR_INST_PTR_CAST, dest_ty, op_val); } // (part of) Sema.zig:coerceArrayPtrToSlice // Coerce a runtime `*[N]T` array-pointer AIR ref to a `[]const T` slice, // mirroring Sema.coerceArrayPtrToSlice's runtime path: emit AIR // `array_to_slice` (ty_op: dest = slice type, operand = the array pointer). // The interpreter materializes the `{ptr, len=N}` slice struct. Returns the // resulting AIR ref. `ptr_ty` must be a single-item pointer to an array; // `slice_ty` the destination slice type. static uint32_t semaEmitArrayToSlice( uint32_t ptr_air, uint32_t slice_ty, AirInstList* insts, U32List* block) { return semaEmitTyOp( insts, block, AIR_INST_ARRAY_TO_SLICE, slice_ty, ptr_air); } // (part of) Sema.zig:coerce // Wrap a runtime payload operand `T` into `?T` (Sema.coerce -> AIR // wrap_optional; ty_op: a = dest `?T`, b = payload operand). Mirrors the // AS_NODE arm (`const o: ?T = v`) so a store/bind coercing a scalar into an // optional pointee sets the has_value flag rather than dropping the bare // payload into the slot. static uint32_t semaEmitWrapOptional(uint32_t payload_air, uint32_t opt_ty, AirInstList* insts, U32List* block) { return semaEmitTyOp( insts, block, AIR_INST_WRAP_OPTIONAL, opt_ty, payload_air); } // Port of Sema's `Block.addStructFieldPtr` // ("const tag: Air.Inst.Tag = switch (field_index) {"): field indices 0..3 // use the compact `struct_field_ptr_index_N` tags (ty_op { ty = *FieldType, // operand = container pointer }); every wider index uses the generic // `struct_field_ptr` (ty_pl { ty, payload = Air.StructField // { struct_operand, field_index } }). Upstream funnels structs, tuples and // unions through this one function (structFieldPtrByIndex's else branch, // "return block.addStructFieldPtr(struct_ptr, field_index, field_ptr_ty);"), // so no caller re-derives the tag choice — and no container kind is capped at // four fields. static uint32_t semaEmitStructFieldPtr(uint32_t container_ptr_air, uint32_t field_index, uint32_t field_ptr_ty, AirInstList* insts, U32List* block, U32List* air_extra) { AirInstData d; memset(&d, 0, sizeof(d)); uint32_t idx; if (field_index < 4) { d.pair.a = field_ptr_ty; // ty_op.ty = *FieldType d.pair.b = container_ptr_air; // ty_op.operand (auto-derefed) idx = semaAirInstAppend(insts, (AirInstTag)(AIR_INST_STRUCT_FIELD_PTR_INDEX_0 + field_index), d); } else { uint32_t payload = air_extra->len; u32ListAppend(air_extra, container_ptr_air); // struct_operand u32ListAppend(air_extra, field_index); // field_index d.pair.a = field_ptr_ty; // ty_pl.ty = *FieldType d.pair.b = payload; // ty_pl.payload idx = semaAirInstAppend(insts, AIR_INST_STRUCT_FIELD_PTR, d); } u32ListAppend(block, idx); return AIR_REF_INST_BASE + idx; } // Sema.zig:unionFieldPtr // Sema.unionFieldPtr with `initializing` on a runtime union pointer: the // `tag:` block sets the field's tag unless the union is not `.auto` // ("if (union_ty.containerLayout(zcu) != .auto) break :tag;") or its // hypothetical tag type has one possible value. The caller then interns the // field pointer type and adds the struct_field_ptr. static void semaEmitInitUnionTag(InternPool* ip, uint32_t union_ptr, uint32_t union_ty, uint32_t field_name, AirInstList* insts, U32List* block) { if (ipUnionIsPacked(ip, union_ty) || ipUnionIsExtern(ip, union_ty)) return; uint32_t tag_ty = ipEnsureGeneratedUnionTag(ip, union_ty); if (ztOnePossibleValue(ip, tag_ty) == IP_INDEX_NONE) semaEmitTyOp(insts, block, AIR_INST_SET_UNION_TAG, union_ptr, ipEnumTagFromFieldName(ip, tag_ty, field_name)); } // Sema.zig:structFieldPtrByIndex, Type.zig:fieldPtrType, Value.zig:ptrField // Sema.structFieldPtrByIndex for a struct or tuple: `Type.fieldPtrType`, a // comptime field's `.comptime_field` pointer, else `Value.ptrField` of a // comptime `struct_ptr` (`insts` NULL) or `addStructFieldPtr`. static uint32_t structFieldPtrByIndex(Zcu* zcu, uint32_t struct_ptr, uint32_t struct_ptr_ty, uint32_t struct_ty, uint32_t field_index, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; *out_ty = ztFieldPtrType(zcu, struct_ptr_ty, field_index); uint32_t field_val = semaStructFieldComptimeValue(zcu, struct_ty, field_index); if (field_val != IP_INDEX_NONE) return ipGetPtrComptimeField(ip, *out_ty, field_val); if (insts == NULL) return valPtrField(zcu, struct_ptr, field_index); return semaEmitStructFieldPtr( struct_ptr, field_index, *out_ty, insts, block, air_extra); } uint32_t semaStructFieldPtrComptime(Zcu* zcu, uint32_t struct_ptr, const char* field, uint32_t field_ip, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; uint32_t ptr_ty = ipTypeOf(ip, struct_ptr); IpPtrInfo info; if (ipPtrInfo(ip, ptr_ty, &info) != IP_PTRI_OK || info.size != IP_PTR_SIZE_ONE || (!ipTagIsAnyStruct(ip->item_tags[info.child]) && !ztIsTuple(ip, info.child))) return IP_INDEX_NONE; // fieldPtr's `"try sema.ensureLayoutResolved(inner_ty, src, // .ptr_access);"` before structFieldPtr. ensureLayoutResolved(zcu, info.child); uint32_t len_ref = semaTupleLenRef(ip, info.child, field, out_ty); if (len_ref != IP_INDEX_NONE) return len_ref; uint32_t idx = ztIsTuple(ip, info.child) ? tupleFieldIndex(ip, info.child, field) : ipStructFieldIndex(ip, info.child, field_ip); if (idx == UINT32_MAX) COMPILE_ERROR_UPSTREAM("field_ptr: no field with this name"); return structFieldPtrByIndex( zcu, struct_ptr, ptr_ty, info.child, idx, NULL, NULL, NULL, out_ty); } // Port of Sema's `Block.addPtrElemPtr` // ("return block.addPtrElemPtrTypeRef(array_ptr, elem_index, ty_ref);"), the // one emitter every `elemPtrArray` tail reaches: AIR `ptr_elem_ptr`, ty_pl // { ty = *ElemType, payload = Air.Bin { lhs = array_ptr, rhs = elem_index } }. // `elem_index` is an already-interned `usize` value, exactly as upstream // passes an `Air.Inst.Ref`. static uint32_t semaEmitPtrElemPtr(uint32_t array_ptr_air, uint32_t elem_index, uint32_t elem_ptr_ty, AirInstList* insts, U32List* block, U32List* air_extra) { return semaEmitTyPlBin(insts, block, air_extra, AIR_INST_PTR_ELEM_PTR, elem_ptr_ty, array_ptr_air, elem_index); } // (part of) Sema.zig:elemPtrVector // Sema.elemPtrVector with a runtime vector pointer and a comptime index: // `"return block.addTyOp(.ptr_cast, elem_ptr_ty, vector_ptr);"`. One // instruction, because the element's position rides in `elem_ptr_ty`'s // `vector_index` rather than in an address computation -- so unlike // elemPtrArray there is no ptr_elem_ptr and no index operand. Returns the AIR // ref of the element pointer and writes its type to `*out_elem_ptr_ty`. static uint32_t semaEmitVectorElemPtr(Zcu* zcu, uint32_t vec_ptr_ty, uint32_t vec_ptr_air, uint32_t index, uint32_t* out_elem_ptr_ty, AirInstList* insts, U32List* block) { uint32_t elem_ptr_ty = semaElemPtrVectorType(zcu, vec_ptr_ty, index, NULL); AirInstData d; memset(&d, 0, sizeof(d)); d.pair.a = elem_ptr_ty; // ty_op.ty d.pair.b = vec_ptr_air; // ty_op.operand uint32_t idx = semaAirInstAppend(insts, AIR_INST_PTR_CAST, d); u32ListAppend(block, idx); if (out_elem_ptr_ty) *out_elem_ptr_ty = elem_ptr_ty; return AIR_REF_INST_BASE + idx; } // elemPtrVector's index rule, shared by every `v[i]` lowering site: `"The // index must not be undefined since it can be out of bounds."` -- upstream // resolves it with resolveDefinedValue and fails when it is not comptime // known (`"vector index not comptime known"`), so a runtime index never // reaches the pointer-type derivation. `raw` is a semaResolveRef result. static uint32_t semaVectorElemIndex(const InternPool* ip, uint32_t raw) { if (semaIsAirInstRef(raw)) COMPILE_ERROR_UPSTREAM("elem_ptr: vector index not comptime known"); uint64_t v; if (!semaTryReadElemIndex(ip, raw, &v)) UNIMPLEMENTED("elem_ptr: vector index is not an integer value"); if (v > UINT32_MAX) COMPILE_ERROR_UPSTREAM("elem_ptr: index outside vector"); return (uint32_t)v; } // `coerceExtra`'s runtime int -> int leg (`coerceInMemoryAllowed` plus the // `.int` arm), asked in one place: same signedness with a wider dest is // `.int_cast` (ReleaseSmall, so never `int_cast_safe`); same signedness with // equal width is `.bit_cast` (a same-representation differently-named int, // `u32` -> `usize` on wasm32); an unsigned source into a strictly wider // signed dest is `.int_cast`. Every other mix -- narrowing, or signed into // unsigned -- is not value-preserving and answers 0, which the dispatcher // declines. The `.float` arm (src/Sema.zig) is `fpext` into an // at-least-as-wide float. `coerceArrayLike`'s vector widening asks the same // rule of the element types. static AirInstTag semaScalarCoerceCastTag( const InternPool* ip, uint32_t src_ty, uint32_t dest_ty) { uint32_t src_bits, dst_bits; bool src_signed, dst_signed; if (ztSimpleFloatBits(src_ty, &src_bits) && ztSimpleFloatBits(dest_ty, &dst_bits)) return dst_bits >= src_bits ? AIR_INST_FPEXT : 0; if (!ztIntInfo(ip, src_ty, &src_bits, &src_signed) || !ztIntInfo(ip, dest_ty, &dst_bits, &dst_signed)) return 0; if (src_signed != dst_signed) return (dst_signed && dst_bits > src_bits) ? AIR_INST_INT_CAST : 0; if (dst_bits > src_bits) return AIR_INST_INT_CAST; return dst_bits == src_bits ? AIR_INST_BIT_CAST : 0; } static uint32_t semaCoerceExtraRuntime(Zcu* zcu, uint32_t value_air, uint32_t value_ty, uint32_t dest_ty, AirInstList* insts, U32List* block, U32List* air_extra, const char* context); static uint32_t coerce(Zcu* zcu, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t dest_ty, uint32_t inst, uint32_t inst_ty); // `coerceArrayLike`'s element loop for a runtime operand: `"const elem_ref = // try sema.elemValArray(block, src, inst_src, inst, elem_src, index_ref, // true); const coerced = try sema.coerce(block, dest_elem_ty, elem_ref, // elem_src);"` per index, then `block.addAggregateInit(dest_ty, // element_refs)`. Every element is runtime here, so `runtime_src` is set. // Zero-length destinations are not routed here (see the caller's guard): // with `len == 0` upstream's loop never runs, `runtime_src` stays null and // the result is a comptime empty aggregate with no AIR at all. static uint32_t semaEmitCoerceArrayLikeRuntime(Zcu* zcu, uint32_t operand_air, uint32_t src_child, uint32_t dest_ty, uint32_t dest_child, uint32_t len, AirInstList* insts, U32List* block, U32List* air_extra) { InternPool* ip = &zcu->intern_pool; uint32_t* refs = ARR_INIT(uint32_t, len + 1u); for (uint32_t i = 0; i < len; i++) { AirInstData ev_data; memset(&ev_data, 0, sizeof(ev_data)); ev_data.pair.a = operand_air; // bin_op.lhs = the runtime sequence ev_data.pair.b = ipGetIntUsize(ip, i); // bin_op.rhs = comptime index uint32_t ev_idx = semaAirInstAppend(insts, AIR_INST_ARRAY_ELEM_VAL, ev_data); u32ListAppend(block, ev_idx); refs[i] = semaCoerceExtraRuntime(zcu, AIR_REF_INST_BASE + ev_idx, src_child, dest_child, insts, block, air_extra, "coerceArrayLike: element"); } // "if (i == dest_len) { ... ref.* = // Air.internedToRef(sentinel_val.toIntern()); break; }" uint32_t n = len; IpArrayInfo dest_info; if (ipArrayInfo(ip, dest_ty, &dest_info) == IP_ARR_OK && dest_info.sentinel != IP_INDEX_NONE) refs[n++] = dest_info.sentinel; uint32_t agg = semaEmitAggregateInit(insts, block, air_extra, dest_ty, refs, n); free(refs); return agg; } // (part of) Sema.zig:coerceExtra // `Sema.coerceExtra`'s `.array` and `.vector` destination arms // (src/Sema.zig), asked once for every runtime binding site. // Returns `IP_INDEX_NONE` when the pair belongs to some other `coerceExtra` // arm (or fails `array_to_array`) and the caller must fall through. // This arm exists because coerceInMemoryAllowed has array<->array and // vector<->vector arms but no array<->vector arm, so a mixed pair (and a // vector pair whose elements differ) falls into `coerceArrayLike` // (src/Sema.zig). static uint32_t coerceArrayLike(Zcu* zcu, uint32_t operand_air, uint32_t operand_ty, uint32_t dest_ty, AirInstList* insts, U32List* block, U32List* air_extra) { InternPool* ip = &zcu->intern_pool; IpArrayInfo dst, src; if (ipArrayInfo(ip, dest_ty, &dst) == IP_ARR_OK && !dst.is_vector && ipArrayInfo(ip, operand_ty, &src) == IP_ARR_OK && !src.is_vector) { // `.array` destination, `.array` source (`array_to_array`): "Array // coercions are allowed only if the child is IMC and the sentinel is // unchanged or removed", then `coerceArrayLike` below. if (!coerceInMemoryAllowed(zcu, ip, dst.child, src.child, false)) return IP_INDEX_NONE; if (dst.sentinel != IP_INDEX_NONE && (src.sentinel == IP_INDEX_NONE || dst.sentinel != semaCoerceComptime(zcu, ip, src.sentinel, dst.child))) return IP_INDEX_NONE; } else if (!ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_VECTOR) && !ztIsZigTypeTag(ip, operand_ty, ZIG_TYPE_TAG_VECTOR)) { return IP_INDEX_NONE; } else if (ipArrayInfo(ip, dest_ty, &dst) != IP_ARR_OK || ipArrayInfo(ip, operand_ty, &src) != IP_ARR_OK) { // `inst_ty.arrayLen(zcu)` / `dest_ty.childType(zcu)`: any array // (sentineled or not) or vector; a tuple source is // coerceTupleToArray's. return IP_INDEX_NONE; } if (dst.len != src.len) COMPILE_ERROR_UPSTREAM( "coerceArrayLike: array/vector length mismatch"); // `"if (dest_ty.isVector(zcu) and inst_ty.isVector(zcu) and // sema.resolveValue(inst) == null)"`: integer widening is one // `int_cast`, float widening one `fpext`, of the whole vector. AirInstTag widen = semaScalarCoerceCastTag(ip, src.child, dst.child); if (widen != 0 && ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_VECTOR) && ztIsZigTypeTag(ip, operand_ty, ZIG_TYPE_TAG_VECTOR)) return semaEmitTyOp(insts, block, widen, dest_ty, operand_air); // `"Matching element types means no per-element work, so let the // backend lower the conversion."`: an array to a vector of its own // element type is one `array_to_vector`. if (ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_VECTOR) && !ztIsZigTypeTag(ip, operand_ty, ZIG_TYPE_TAG_VECTOR) && src.child == dst.child && semaIsAirInstRef(operand_air)) return semaEmitTyOp( insts, block, AIR_INST_ARRAY_TO_VECTOR, dest_ty, operand_air); // A zero-length destination folds to a comptime empty aggregate with no // AIR upstream (coerceArrayLike -- the loop never sets // runtime_src); emitting an empty aggregate_init would diverge. if (dst.len == 0) UNIMPLEMENTED("coerceArrayLike: empty array/vector coercion"); return semaEmitCoerceArrayLikeRuntime(zcu, operand_air, src.child, dest_ty, dst.child, dst.len, insts, block, air_extra); } // Sema.zig:coerceTupleToArray // Sema.coerceTupleToArray (src/Sema.zig) for the reached runtime // `@as([N]T, tuple)` subset: equal-length tuple-to-array, no destination // sentinel, and identity runtime element coercions. Each tuple field follows // `tupleField`: stored-comptime/default/OPV fields produce a comptime ref; // other fields emit AIR `agg_field_val`. If any element is runtime, upstream // returns AIR `aggregate_init` for the destination array. static bool semaEmitCoerceTupleToArrayRuntime(InternPool* ip, uint32_t tuple_air, uint32_t tuple_ty, uint32_t dest_ty, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t* out_ref) { if (tuple_ty >= ip->items_len || dest_ty >= ip->items_len || !ztIsTuple(ip, tuple_ty)) return false; IpArrayInfo dest_info; if (ipArrayInfo(ip, dest_ty, &dest_info) != IP_ARR_OK || dest_info.is_vector) return false; if (dest_info.sentinel != IP_INDEX_NONE) UNIMPLEMENTED( "coerceTupleToArray: tuple_to_array - sentineled destination"); uint32_t nfields = ipTupleFieldCount(ip, tuple_ty); if (dest_info.len != nfields) COMPILE_ERROR_UPSTREAM("tuple_to_array: length mismatch"); if (nfields == 0) UNIMPLEMENTED( "coerceTupleToArray: tuple_to_array - all-comptime empty tuple"); uint32_t* elem_refs = ARR_INIT(uint32_t, nfields); bool runtime_src = false; for (uint32_t i = 0; i < nfields; i++) { uint32_t field_ty = ipTupleFieldType(ip, tuple_ty, i); uint32_t field_val = ipTupleFieldValue(ip, tuple_ty, i); uint32_t elem_ref; if (field_val != IP_INDEX_NONE) { elem_ref = semaCoerceComptime(NULL, ip, field_val, dest_info.child); } else { field_val = ztOnePossibleValue(ip, field_ty); if (field_val != IP_INDEX_NONE) { elem_ref = semaCoerceComptime(NULL, ip, field_val, dest_info.child); } else { if (field_ty != dest_info.child) UNIMPLEMENTED( "coerceTupleToArray: tuple_to_array - runtime element " "needs a non-identity coercion"); elem_ref = semaEmitAggFieldVal( insts, block, air_extra, tuple_air, i, field_ty); runtime_src = true; } } elem_refs[i] = elem_ref; } if (!runtime_src) { free(elem_refs); UNIMPLEMENTED("coerceTupleToArray: tuple_to_array - all-comptime " "tuple elements"); } *out_ref = semaEmitAggregateInit( insts, block, air_extra, dest_ty, elem_refs, nfields); free(elem_refs); return true; } // Decode the reached subset of `*[N:s]T`: a single-item pointer with no packed // offset whose child is an array type. The length is kept so same-length // pointer peers can stay loud instead of being degraded to a slice. static bool semaPeerArrayPtrInfo(const InternPool* ip, uint32_t ptr_ty, uint32_t* out_len, uint32_t* out_elem, uint32_t* out_sentinel, uint32_t* out_flags) { // Composed from the two shared accessors: the pointer // words come from `ipPtrInfo` (upstream `Type.ptrInfo`), the pointee's // from `ztArrayTypeInfo` above. `IP_INDEX_NONE` and an out-of-range index // both fall into IP_PTRI_NOT_PTR, and the 4-word extent check into // IP_PTRI_MALFORMED; each declines. IpPtrInfo ptr; if (ipPtrInfo(ip, ptr_ty, &ptr) != IP_PTRI_OK) return false; if (ptr.size != IP_PTR_SIZE_ONE || ptr.packed_offset != 0) return false; if (!ztArrayTypeInfo(ip, ptr.child, out_len, out_elem, out_sentinel)) return false; *out_flags = ptr.flags; return true; } static uint32_t semaResolvePeerTypesOfTypes( Zcu* zcu, uint32_t* tys, uint32_t len); // zirArrayCat's `resolved_elem_ty`: "sema.resolvePeerTypes(block, src, // .{ .types = &.{ lhs_info.elem_type, rhs_info.elem_type } }, ...)". uint32_t semaArrayCatResolvedElemType(Zcu* zcu, const uint32_t elem_ty[2]) { uint32_t tys[2] = { elem_ty[0], elem_ty[1] }; return semaResolvePeerTypesOfTypes(zcu, tys, 2); } // zirArrayCat's `res_sent_val`: each operand's sentinel coerced to the // resolved element type; both present keeps the left one only when they // are equal. uint32_t semaArrayCatResultSentinel( Zcu* zcu, const uint32_t sent[2], uint32_t res_elem) { InternPool* ip = &zcu->intern_pool; uint32_t l = sent[0] == IP_INDEX_NONE ? IP_INDEX_NONE : semaCoerceComptime(zcu, ip, sent[0], res_elem); uint32_t r = sent[1] == IP_INDEX_NONE ? IP_INDEX_NONE : semaCoerceComptime(zcu, ip, sent[1], res_elem); if (l != IP_INDEX_NONE && r != IP_INDEX_NONE) return l == r ? l : IP_INDEX_NONE; return l != IP_INDEX_NONE ? l : r; } static uint32_t semaArrayCatPeerElemType( const InternPool* ip, uint32_t peer_ty) { if (peer_ty == IP_INDEX_NONE || peer_ty >= ip->items_len) return IP_INDEX_NONE; if (ztIsSlice(ip, peer_ty)) { uint32_t many_ty = ip->item_datas[peer_ty]; IpPtrInfo ptr; if (ipPtrInfo(ip, many_ty, &ptr) != IP_PTRI_OK) return IP_INDEX_NONE; return ptr.child; } uint32_t len, elem, sentinel, flags; if (semaPeerArrayPtrInfo(ip, peer_ty, &len, &elem, &sentinel, &flags)) { return elem; } if (ztArrayTypeInfo(ip, peer_ty, &len, &elem, &sentinel)) { return elem; } return IP_INDEX_NONE; } // (part of) Sema.zig:coerceExtra // Sema.coerceExtra's `.pointer` destination arm, `src_array_ptr` block // (src/Sema.zig): "Coercions where the source is a single pointer // to an array". Upstream runs `checkPtrAttributes`, the element // `coerceInMemoryAllowed` and the destination-sentinel rule before // `switch (dest_info.flags.size)`, so all three legs -- `.slice` // (coerceArrayPtrToSlice), `.c` and `.many` (coerceCompatiblePtrs) -- are // gated by exactly these checks and by nothing else. This is that preamble, // the one statement every zig1 site performing the coercion asks; it hands // back the decoded infos so the caller can pick its leg without decoding a // second time. `zcu` is nullable, as for `semaCheckPtrAttributesTy`. static bool semaSrcArrayPtrCoercible(Zcu* zcu, InternPool* ip, uint32_t dest_ty, uint32_t src_ty, IpPtrInfo* out_dest, IpPtrInfo* out_src) { IpPtrInfo dest_info, src_info; IpArrayInfo arr; if (dest_ty == IP_INDEX_NONE || src_ty == IP_INDEX_NONE || !ztPtrOrSliceInfo(ip, dest_ty, &dest_info) // "if (!inst_ty.isSinglePointer(zcu)) break :src_array_ptr;" || ipPtrInfo(ip, src_ty, &src_info) != IP_PTRI_OK || src_info.size != IP_PTR_SIZE_ONE // "`*[n]T` -> `*T` isn't valid" || dest_info.size == IP_PTR_SIZE_ONE || ipArrayInfo(ip, src_info.child, &arr) != IP_ARR_OK || arr.is_vector) return false; if (!checkPtrAttributes(zcu, ip, &dest_info, &src_info)) return false; if (!coerceInMemoryAllowed( zcu, ip, dest_info.child, arr.child, !dest_info.is_const)) return false; // "if (dest_info.sentinel != .none) { if (array_ty.sentinel(zcu)) // |inst_sent| { if (dest_info.sentinel != getCoerced(inst_sent, // dst_elem_type)) break; } else break; }" -- identity is the modelled // subset, as everywhere else zig1 compares interned sentinels. if (dest_info.sentinel != IP_INDEX_NONE && dest_info.sentinel != arr.sentinel) return false; *out_dest = dest_info; *out_src = src_info; return true; } // Sema.PeerResolveResult (src/Sema.zig) reduced to its discriminant: // zig1 has no error rendering, so `.conflict`'s peer indices and the // `.field_error` chain -- both consumed only by `PeerResolveResult.report` -- // carry no information here. The third member is a zig1 GAP, which callers // must tell apart from a shape stock Zig itself rejects. typedef enum { PEER_RESOLVE_OK, // upstream's `.success` PEER_RESOLVE_CONFLICT, // upstream's `.conflict`: "incompatible types" PEER_RESOLVE_UNMODELED, // a peer shape zig1 cannot resolve } PeerResolveResult; // (part of) Sema.zig:resolvePeerTypesInner // Sema.resolvePeerTypesInner's `.fixed_int` strategy // (src/Sema.zig) over `len` peers. `tys[i]` is peer i's type and // `known[i]` says whether that peer is comptime-known (upstream's // "peer_vals[i] != null"), which the legacy `any_comptime_known` branch keys // on ("if values were known at comptime, they would be coerced down to the // smallest type where possible"). Upstream never synthesizes a width here -- // it returns one of the peer types -- so neither may this. Returns // IP_INDEX_NONE on failure and writes the reason through `out_why` (optional). // `tys` is upstream's `[]?Type`: an `IP_INDEX_NONE` peer was dropped by the // caller and is skipped ("const ty = opt_ty orelse continue"), so the // `.fixed_int` arm hands its peer array straight through. `vals` is upstream's // `[]?Value` with `IP_INDEX_NONE` for `null`; this arm reads only whether each // peer has a comptime value. static uint32_t semaPeerFixedIntTypeN(const InternPool* ip, const uint32_t* tys, const uint32_t* vals, uint32_t len, PeerResolveResult* out_why) { PeerResolveResult why_sink; PeerResolveResult* why = out_why ? out_why : &why_sink; // "var idx_unsigned: ?usize = null; var idx_signed: ?usize = null;" -- // `len` stands in for `null`, the one index no peer can hold. uint32_t idx_unsigned = len, idx_signed = len; uint32_t uns_bits = 0u, sgn_bits = 0u; bool any_comptime_known = false; *why = PEER_RESOLVE_OK; for (uint32_t i = 0; i < len; i++) { if (tys[i] == IP_INDEX_NONE) continue; if (tys[i] == ZIR_REF_COMPTIME_INT_TYPE) { // "If the value is undefined, we can't refine to a fixed-width // int" -- upstream's test is `opt_val == null or // opt_val.?.isUndef(zcu)` (src/Sema.zig), so an UNDEF // comptime_int peer conflicts exactly as a value-less one does. if (vals[i] == IP_INDEX_NONE || ipIsUndef(ip, vals[i])) { *why = PEER_RESOLVE_CONFLICT; return IP_INDEX_NONE; } any_comptime_known = true; continue; } uint32_t bits; bool is_signed; if (tys[i] >= ip->items_len || !ztIntInfo(ip, tys[i], &bits, &is_signed)) { *why = PEER_RESOLVE_UNMODELED; return IP_INDEX_NONE; } if (vals[i] != IP_INDEX_NONE) any_comptime_known = true; uint32_t* idx_ptr = is_signed ? &idx_signed : &idx_unsigned; uint32_t* bits_ptr = is_signed ? &sgn_bits : &uns_bits; // "const largest_idx = idx_ptr.* orelse { idx_ptr.* = i; continue; }; // if (info.bits > cur_info.bits) idx_ptr.* = i;" if (*idx_ptr == len || bits > *bits_ptr) { *idx_ptr = i; *bits_ptr = bits; } } if (idx_signed == len) { // An all-comptime_int peer set selects `.comptime_int`, never // `.fixed_int`. if (idx_unsigned == len) { *why = PEER_RESOLVE_UNMODELED; return IP_INDEX_NONE; } return tys[idx_unsigned]; } if (idx_unsigned == len) return tys[idx_signed]; if (sgn_bits > uns_bits) return tys[idx_signed]; // Upstream's documented legacy-compatibility branch: with any peer // comptime-known the wider unsigned peer wins, and a width tie keeps the // earlier peer ("const idx = @min(idx_unsigned.?, idx_signed.?)"). if (any_comptime_known) return (uns_bits > sgn_bits) ? tys[idx_unsigned] : tys[idx_unsigned < idx_signed ? idx_unsigned : idx_signed]; *why = PEER_RESOLVE_CONFLICT; return IP_INDEX_NONE; } // The two-peer arity every binop and optional-payload caller resolves. // `a_val`/`b_val` are upstream's `peer_vals`: the peer's comptime value, or // IP_INDEX_NONE when it has none. static uint32_t semaPeerFixedIntType(const InternPool* ip, uint32_t a, uint32_t a_val, uint32_t b, uint32_t b_val, PeerResolveResult* out_why) { const uint32_t tys[2] = { a, b }; const uint32_t vals[2] = { a_val, b_val }; return semaPeerFixedIntTypeN(ip, tys, vals, 2u, out_why); } static uint32_t semaErrorUnionTypeFromSetPayload( InternPool* ip, uint32_t err_set_ty, uint32_t payload_ty) { if (err_set_ty == ZIR_REF_ANYERROR_TYPE) return ipGetAnyerrorUnionType(ip, payload_ty); return ipGetErrorUnionType(ip, err_set_ty, payload_ty); } // The type of an AIR ref while a block's breaks are peer-coerced. static uint32_t semaAirRefTypeForBlockCoerce( InternPool* ip, const AirInstList* insts, uint32_t ref) { if (ref < AIR_REF_INST_BASE) return ipComptimeValueType(ip, ref); uint32_t idx = ref - AIR_REF_INST_BASE; if (idx >= insts->len) UNREACHABLE("block peer coerce: AIR ref out of bounds"); switch ((AirInstTag)insts->tags[idx]) { case AIR_INST_ADD: case AIR_INST_ADD_SAFE: case AIR_INST_ADD_OPTIMIZED: case AIR_INST_ADD_WRAP: case AIR_INST_ADD_SAT: case AIR_INST_SUB: case AIR_INST_SUB_SAFE: case AIR_INST_SUB_OPTIMIZED: case AIR_INST_SUB_WRAP: case AIR_INST_SUB_SAT: case AIR_INST_MUL: case AIR_INST_MUL_SAFE: case AIR_INST_MUL_OPTIMIZED: case AIR_INST_MUL_WRAP: case AIR_INST_MUL_SAT: case AIR_INST_DIV_TRUNC: case AIR_INST_DIV_TRUNC_OPTIMIZED: case AIR_INST_DIV_FLOOR: case AIR_INST_DIV_FLOOR_OPTIMIZED: case AIR_INST_DIV_CEIL: case AIR_INST_DIV_CEIL_OPTIMIZED: case AIR_INST_DIV_EXACT: case AIR_INST_DIV_EXACT_OPTIMIZED: case AIR_INST_MUL_ADD: case AIR_INST_REM: case AIR_INST_REM_OPTIMIZED: case AIR_INST_MOD: case AIR_INST_MOD_OPTIMIZED: case AIR_INST_BIT_AND: case AIR_INST_BIT_OR: case AIR_INST_XOR: case AIR_INST_SHL: case AIR_INST_SHL_EXACT: case AIR_INST_SHL_SAT: case AIR_INST_SHR: case AIR_INST_SHR_EXACT: case AIR_INST_MIN: case AIR_INST_MAX: return semaAirRefTypeForBlockCoerce( ip, insts, insts->datas[idx].pair.a); case AIR_INST_ARG: case AIR_INST_ALLOC: case AIR_INST_RET_PTR: case AIR_INST_LOAD: case AIR_INST_INT_CAST: case AIR_INST_BIT_CAST: case AIR_INST_PTR_CAST: case AIR_INST_CLZ: case AIR_INST_CTZ: case AIR_INST_POPCOUNT: case AIR_INST_BYTE_SWAP: case AIR_INST_BIT_REVERSE: case AIR_INST_ERROR_CAST: case AIR_INST_ARRAY_TO_SLICE: case AIR_INST_OPTIONAL_PAYLOAD: case AIR_INST_OPTIONAL_PAYLOAD_PTR: case AIR_INST_OPTIONAL_PAYLOAD_PTR_SET: case AIR_INST_WRAP_OPTIONAL: case AIR_INST_UNWRAP_ERRUNION_PAYLOAD: case AIR_INST_UNWRAP_ERRUNION_ERR: case AIR_INST_UNWRAP_ERRUNION_PAYLOAD_PTR: case AIR_INST_UNWRAP_ERRUNION_ERR_PTR: case AIR_INST_ERRUNION_PAYLOAD_PTR_SET: case AIR_INST_WRAP_ERRUNION_PAYLOAD: case AIR_INST_WRAP_ERRUNION_ERR: case AIR_INST_STRUCT_FIELD_PTR: case AIR_INST_STRUCT_FIELD_PTR_INDEX_0: case AIR_INST_STRUCT_FIELD_PTR_INDEX_1: case AIR_INST_STRUCT_FIELD_PTR_INDEX_2: case AIR_INST_STRUCT_FIELD_PTR_INDEX_3: case AIR_INST_AGG_FIELD_VAL: case AIR_INST_FIELD_PARENT_PTR: case AIR_INST_SLICE: case AIR_INST_SLICE_LEN: case AIR_INST_SLICE_PTR: case AIR_INST_PTR_SLICE_LEN_PTR: case AIR_INST_PTR_SLICE_PTR_PTR: case AIR_INST_SLICE_ELEM_PTR: case AIR_INST_PTR_ELEM_PTR: case AIR_INST_PTR_ADD: case AIR_INST_PTR_SUB: case AIR_INST_AGGREGATE_INIT: case AIR_INST_UNION_INIT: case AIR_INST_BLOCK: case AIR_INST_RUNTIME_NAV_PTR: return insts->datas[idx].pair.a; case AIR_INST_ARRAY_ELEM_VAL: { uint32_t array_ty = semaAirRefTypeForBlockCoerce( ip, insts, insts->datas[idx].pair.a); IpArrayInfo info; if (ipArrayInfo(ip, array_ty, &info) != IP_ARR_OK) UNIMPLEMENTED("block peer coerce: array elem operand not array"); return info.child; } case AIR_INST_SLICE_ELEM_VAL: { uint32_t slice_ty = semaAirRefTypeForBlockCoerce( ip, insts, insts->datas[idx].pair.a); if (!ztIsSlice(ip, slice_ty)) UNIMPLEMENTED("block peer coerce: slice elem operand not slice"); uint32_t many_ty = ip->item_datas[slice_ty]; IpPtrInfo many_info; if (ipPtrInfo(ip, many_ty, &many_info) != IP_PTRI_OK) UNREACHABLE("block peer coerce: malformed slice pointer type"); return many_info.child; } case AIR_INST_PTR_ELEM_VAL: { uint32_t ptr_ty = semaAirRefTypeForBlockCoerce( ip, insts, insts->datas[idx].pair.a); IpPtrInfo ptr_info; if (ipPtrInfo(ip, ptr_ty, &ptr_info) != IP_PTRI_OK) UNIMPLEMENTED("block peer coerce: ptr elem operand not pointer"); return ptr_info.child; } case AIR_INST_TAG_NAME: case AIR_INST_ERROR_NAME: return ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE; case AIR_INST_TRY: case AIR_INST_TRY_COLD: { // Mirrors src/Air.zig typeOf for .@"try": the AIR result is the // payload type of the error-union operand. uint32_t err_union_ty = semaAirRefTypeForBlockCoerce( ip, insts, insts->datas[idx].pair.a); if (!ztIsZigTypeTag(ip, err_union_ty, ZIG_TYPE_TAG_ERROR_UNION)) UNIMPLEMENTED("block peer coerce: try operand not error union"); return ipErrorUnionPayload(ip, err_union_ty); } case AIR_INST_TRY_PTR: case AIR_INST_TRY_PTR_COLD: return insts->datas[idx].pair.a; case AIR_INST_CALL: case AIR_INST_CALL_ALWAYS_TAIL: case AIR_INST_CALL_NEVER_TAIL: case AIR_INST_CALL_NEVER_INLINE: { uint32_t callee_ref = insts->datas[idx].pair.a; uint32_t func_ty = IP_INDEX_NONE; if (callee_ref >= AIR_REF_INST_BASE) { uint32_t callee_ty = semaAirRefTypeForBlockCoerce(ip, insts, callee_ref); if (callee_ty < ip->items_len && ip->item_tags[callee_ty] == IP_TAG_TYPE_POINTER) func_ty = ipPtrChild(ip, callee_ty); else func_ty = callee_ty; } else if (callee_ref < ip->items_len && ip->item_tags[callee_ref] == IP_TAG_FUNC_DECL) { func_ty = ipFuncDeclType(ip, callee_ref); } else if (callee_ref < ip->items_len && ip->item_tags[callee_ref] == IP_TAG_FUNC_INSTANCE) { func_ty = ipFuncInstanceType(ip, callee_ref); } else if (callee_ref < ip->items_len && ip->item_tags[callee_ref] == IP_TAG_PTR_NAV) { uint32_t ptr_ty = ipTypeOf(ip, callee_ref); if (ptr_ty < ip->items_len && ip->item_tags[ptr_ty] == IP_TAG_TYPE_POINTER) func_ty = ipPtrChild(ip, ptr_ty); } else if (callee_ref < ip->items_len) { uint32_t callee_ty = ipTypeOf(ip, callee_ref); if (callee_ty < ip->items_len && ip->item_tags[callee_ty] == IP_TAG_TYPE_POINTER) func_ty = ipPtrChild(ip, callee_ty); else func_ty = callee_ty; } if (!ztIsZigTypeTag(ip, func_ty, ZIG_TYPE_TAG_FN)) UNIMPLEMENTED("block peer coerce: call callee is not a function"); return ipFuncTypeReturnType(ip, func_ty); } default: UNIMPLEMENTED("block peer coerce: unsupported AIR operand type"); } ZIG1_TCC_RETURN(IP_INDEX_NONE); } // Sema.PeerResolveStrategy (src/Sema.zig), member-for-member in // upstream's declaration order. The order is load-bearing: `merge` sorts its // two strategies by ordinal and switches on the smaller one ("by sorting the // tags and switching first on the smaller, we have half as many cases to // worry about"). typedef enum { PEER_STRAT_UNKNOWN, PEER_STRAT_ERROR_SET, PEER_STRAT_ERROR_UNION, PEER_STRAT_NULLABLE, PEER_STRAT_OPTIONAL, PEER_STRAT_ARRAY, PEER_STRAT_VECTOR, PEER_STRAT_C_PTR, PEER_STRAT_PTR, PEER_STRAT_FUNC, PEER_STRAT_ENUM_OR_UNION, PEER_STRAT_COMPTIME_INT, PEER_STRAT_COMPTIME_FLOAT, PEER_STRAT_FIXED_INT, PEER_STRAT_FIXED_FLOAT, PEER_STRAT_TUPLE, PEER_STRAT_EXACT, } PeerResolveStrategy; // PeerResolveStrategy.merge (src/Sema.zig), transcribed whole. `a` // comes from the peer in `*reason_peer`, `b` from the peer at `b_peer_idx`, // and `*reason_peer` is updated to the peer that justifies the result. static PeerResolveStrategy semaPeerStrategyMerge(PeerResolveStrategy a, PeerResolveStrategy b, uint32_t* reason_peer, uint32_t b_peer_idx) { bool s0_is_a = (int)a <= (int)b; PeerResolveStrategy s0 = s0_is_a ? a : b; PeerResolveStrategy s1 = s0_is_a ? b : a; enum { REASON_ALL_S0, REASON_ALL_S1, REASON_EITHER } reason_method; PeerResolveStrategy strat; switch (s0) { case PEER_STRAT_UNKNOWN: reason_method = REASON_ALL_S1; strat = s1; break; case PEER_STRAT_ERROR_SET: if (s1 == PEER_STRAT_ERROR_SET) { reason_method = REASON_EITHER; strat = PEER_STRAT_ERROR_SET; } else { reason_method = REASON_ALL_S0; strat = PEER_STRAT_ERROR_UNION; } break; case PEER_STRAT_ERROR_UNION: reason_method = s1 == PEER_STRAT_ERROR_UNION ? REASON_EITHER : REASON_ALL_S0; strat = PEER_STRAT_ERROR_UNION; break; case PEER_STRAT_NULLABLE: if (s1 == PEER_STRAT_NULLABLE) { reason_method = REASON_EITHER; strat = PEER_STRAT_NULLABLE; } else if (s1 == PEER_STRAT_C_PTR) { reason_method = REASON_ALL_S1; strat = PEER_STRAT_C_PTR; } else { reason_method = REASON_ALL_S0; strat = PEER_STRAT_OPTIONAL; } break; case PEER_STRAT_OPTIONAL: if (s1 == PEER_STRAT_OPTIONAL) { reason_method = REASON_EITHER; strat = PEER_STRAT_OPTIONAL; } else if (s1 == PEER_STRAT_C_PTR) { reason_method = REASON_ALL_S1; strat = PEER_STRAT_C_PTR; } else { reason_method = REASON_ALL_S0; strat = PEER_STRAT_OPTIONAL; } break; case PEER_STRAT_ARRAY: if (s1 == PEER_STRAT_ARRAY) { reason_method = REASON_EITHER; strat = PEER_STRAT_ARRAY; } else if (s1 == PEER_STRAT_VECTOR) { reason_method = REASON_ALL_S1; strat = PEER_STRAT_VECTOR; } else { reason_method = REASON_ALL_S0; strat = PEER_STRAT_ARRAY; } break; case PEER_STRAT_VECTOR: reason_method = s1 == PEER_STRAT_VECTOR ? REASON_EITHER : REASON_ALL_S0; strat = PEER_STRAT_VECTOR; break; case PEER_STRAT_C_PTR: reason_method = s1 == PEER_STRAT_C_PTR ? REASON_EITHER : REASON_ALL_S0; strat = PEER_STRAT_C_PTR; break; case PEER_STRAT_PTR: reason_method = s1 == PEER_STRAT_PTR ? REASON_EITHER : REASON_ALL_S0; strat = PEER_STRAT_PTR; break; case PEER_STRAT_FUNC: if (s1 == PEER_STRAT_FUNC) { reason_method = REASON_EITHER; strat = PEER_STRAT_FUNC; } else { // "doesn't override anything later" reason_method = REASON_ALL_S1; strat = s1; } break; case PEER_STRAT_ENUM_OR_UNION: reason_method = s1 == PEER_STRAT_ENUM_OR_UNION ? REASON_EITHER : REASON_ALL_S0; strat = PEER_STRAT_ENUM_OR_UNION; break; case PEER_STRAT_COMPTIME_INT: if (s1 == PEER_STRAT_COMPTIME_INT) { reason_method = REASON_EITHER; strat = PEER_STRAT_COMPTIME_INT; } else { reason_method = REASON_ALL_S1; strat = s1; } break; case PEER_STRAT_COMPTIME_FLOAT: if (s1 == PEER_STRAT_COMPTIME_FLOAT) { reason_method = REASON_EITHER; strat = PEER_STRAT_COMPTIME_FLOAT; } else { reason_method = REASON_ALL_S1; strat = s1; } break; case PEER_STRAT_FIXED_INT: if (s1 == PEER_STRAT_FIXED_INT) { reason_method = REASON_EITHER; strat = PEER_STRAT_FIXED_INT; } else { reason_method = REASON_ALL_S1; strat = s1; } break; case PEER_STRAT_FIXED_FLOAT: if (s1 == PEER_STRAT_FIXED_FLOAT) { reason_method = REASON_EITHER; strat = PEER_STRAT_FIXED_FLOAT; } else { reason_method = REASON_ALL_S1; strat = s1; } break; case PEER_STRAT_TUPLE: if (s1 == PEER_STRAT_EXACT) { reason_method = REASON_ALL_S1; strat = PEER_STRAT_EXACT; } else { reason_method = REASON_ALL_S0; strat = PEER_STRAT_TUPLE; } break; default: // PEER_STRAT_EXACT reason_method = REASON_ALL_S0; strat = PEER_STRAT_EXACT; break; } switch (reason_method) { case REASON_ALL_S0: if (!s0_is_a) *reason_peer = b_peer_idx; break; case REASON_ALL_S1: if (s0_is_a) *reason_peer = b_peer_idx; break; case REASON_EITHER: // "Prefer the earliest peer" if (b_peer_idx < *reason_peer) *reason_peer = b_peer_idx; break; } return strat; } // Sema.zig:PeerResolveStrategy.select // "return switch (ty.zigTypeTag(zcu))". Returns false for an absent or // generic-poison peer, which the caller declines loudly on: upstream never // resolves peers of an uninstantiated generic, it analyzes the instantiation. static bool semaPeerStrategySelect( const InternPool* ip, uint32_t ty, PeerResolveStrategy* out) { if (ty >= ip->items_len || ty == ZIR_REF_GENERIC_POISON_TYPE) return false; switch (ztZigTypeTag(ip, ty)) { case ZIG_TYPE_TAG_TYPE: case ZIG_TYPE_TAG_VOID: case ZIG_TYPE_TAG_BOOL: case ZIG_TYPE_TAG_OPAQUE: case ZIG_TYPE_TAG_SPIRV: case ZIG_TYPE_TAG_FRAME: case ZIG_TYPE_TAG_ANYFRAME: *out = PEER_STRAT_EXACT; return true; case ZIG_TYPE_TAG_NORETURN: case ZIG_TYPE_TAG_UNDEFINED: *out = PEER_STRAT_UNKNOWN; return true; case ZIG_TYPE_TAG_NULL: *out = PEER_STRAT_NULLABLE; return true; case ZIG_TYPE_TAG_COMPTIME_INT: *out = PEER_STRAT_COMPTIME_INT; return true; case ZIG_TYPE_TAG_INT: *out = PEER_STRAT_FIXED_INT; return true; case ZIG_TYPE_TAG_COMPTIME_FLOAT: *out = PEER_STRAT_COMPTIME_FLOAT; return true; case ZIG_TYPE_TAG_FLOAT: *out = PEER_STRAT_FIXED_FLOAT; return true; case ZIG_TYPE_TAG_POINTER: { // "if (ty.ptrInfo(zcu).flags.size == .c) .c_ptr else .ptr" IpPtrInfo info; if (!ztPtrOrSliceInfo(ip, ty, &info)) return false; *out = info.size == IP_PTR_SIZE_C ? PEER_STRAT_C_PTR : PEER_STRAT_PTR; return true; } case ZIG_TYPE_TAG_ARRAY: *out = PEER_STRAT_ARRAY; return true; case ZIG_TYPE_TAG_VECTOR: *out = PEER_STRAT_VECTOR; return true; case ZIG_TYPE_TAG_OPTIONAL: *out = PEER_STRAT_OPTIONAL; return true; case ZIG_TYPE_TAG_ERROR_SET: *out = PEER_STRAT_ERROR_SET; return true; case ZIG_TYPE_TAG_ERROR_UNION: *out = PEER_STRAT_ERROR_UNION; return true; case ZIG_TYPE_TAG_ENUM_LITERAL: case ZIG_TYPE_TAG_ENUM: case ZIG_TYPE_TAG_UNION: *out = PEER_STRAT_ENUM_OR_UNION; return true; case ZIG_TYPE_TAG_STRUCT: *out = ztIsTuple(ip, ty) ? PEER_STRAT_TUPLE : PEER_STRAT_EXACT; return true; case ZIG_TYPE_TAG_FN: *out = PEER_STRAT_FUNC; return true; } UNREACHABLE("PeerResolveStrategy.select: zigTypeTag out of range"); ZIG1_TCC_RETURN(false); } // Sema.zig:resolvePairInMemoryCoercible // Sema.resolvePairInMemoryCoercible (src/Sema.zig): the pair's type // if either side coerces in memory to the other, else IP_INDEX_NONE // (upstream's `null`). `coerceInMemoryAllowed` is conservative, so a // IP_INDEX_NONE here means "not modelled as coercible", not "upstream // rejects this pair" -- every caller below declines loudly rather than // reporting upstream's `.conflict` on its word. static uint32_t semaPeerResolvePairInMemoryCoercible( Zcu* zcu, InternPool* ip, uint32_t ty_a, uint32_t ty_b) { // "ty_b -> ty_a" if (coerceInMemoryAllowed(zcu, ip, ty_a, ty_b, false)) return ty_a; // "ty_a -> ty_b" if (coerceInMemoryAllowed(zcu, ip, ty_b, ty_a, false)) return ty_b; return IP_INDEX_NONE; } // Sema.ArrayLike (src/Sema.zig): `elem_ty == noreturn` is upstream's // "`noreturn` indicates that this type is `struct{}` so can coerce to // anything". typedef struct { uint32_t len; uint32_t elem_ty; } PeerArrayLike; // Sema.zig:typeIsArrayLike // Sema.typeIsArrayLike (src/Sema.zig). False is upstream's `null`. // "We abstract array handling slightly so that tuple pointers can work like // array pointers." static bool semaPeerTypeIsArrayLike( const InternPool* ip, uint32_t ty, PeerArrayLike* out) { uint32_t len, child, sentinel; if (ztArrayTypeInfo(ip, ty, &len, &child, &sentinel)) { out->len = len; out->elem_ty = child; return true; } // "`.@\"struct\"` => if (!ty.isTuple(zcu)) return null;" -- zig1 gives // tuples their own item tag, so a non-tuple struct is simply not this. if (!ztIsTuple(ip, ty)) return false; uint32_t field_count = ipTupleFieldCount(ip, ty); if (field_count == 0) { out->len = 0; out->elem_ty = ZIR_REF_NORETURN_TYPE; return true; } uint32_t elem_ty = ipTupleFieldType(ip, ty, 0); for (uint32_t i = 1; i < field_count; i++) if (ipTupleFieldType(ip, ty, i) != elem_ty) return false; out->len = field_count; out->elem_ty = elem_ty; return true; } // The `.ptr` strategy reads every peer as upstream's `Key.PtrType`. zig1 // splits that key into two tags -- `type_pointer` and `type_slice`, which // holds its many-pointer and adds a length -- so a slice peer decodes // through its many-pointer with `size` put back to `.slice`. A `.@"fn"` peer // is upstream's `.{ .child = ty.toIntern(), .flags = .{ .address_space = // defaultAddressSpace(target, .global_constant) } }`: every other flag its // default, and `defaultAddressSpace` is `.generic` off AVR (src/target.zig). // False is upstream's `else => return .conflict`. static bool semaPeerPtrTypeInfo( const InternPool* ip, uint32_t ty, IpPtrInfo* out) { if (ty >= ip->items_len) return false; switch (ip->item_tags[ty]) { case IP_TAG_TYPE_POINTER: return ipPtrInfo(ip, ty, out) == IP_PTRI_OK; case IP_TAG_TYPE_SLICE: if (ipPtrInfo(ip, ip->item_datas[ty], out) != IP_PTRI_OK) return false; out->size = IP_PTR_SIZE_SLICE; return true; case IP_TAG_TYPE_FUNCTION: out->child = ty; out->sentinel = IP_INDEX_NONE; // The flags word carries the two fields this arm never edits -- // address_space `.generic` and vector_index `.none`; size, // alignment and the three bools are recomposed by // `semaPeerPtrTypeIntern` from the decoded members below. out->flags = IP_PTR_FLAGS_MUT_ONE; out->packed_offset = 0; out->size = IP_PTR_SIZE_ONE; out->alignment = IP_PTR_ALIGN_NONE; out->is_const = false; out->is_volatile = false; out->is_allowzero = false; return true; default: return false; } } // PerThread.zig:ptrType, InternPool.zig:get // `pt.ptrType(opt_ptr_info.?)`: InternPool.get's `.ptr_type` arm turns a // `.slice`-size key into the many-pointer plus a `type_slice`, which is // exactly zig1's two tags. The one place the decoded members are put back // into a flags word; address_space and vector_index ride along untouched. static uint32_t semaPeerPtrTypeIntern(InternPool* ip, const IpPtrInfo* info) { uint32_t flags = info->flags & ~(IP_PTR_FLAGS_SIZE_MASK | (IP_PTR_FLAGS_ALIGN_MASK << IP_PTR_FLAGS_ALIGN_SHIFT) | IP_PTR_FLAGS_IS_CONST | IP_PTR_FLAGS_IS_VOLATILE | IP_PTR_FLAGS_IS_ALLOWZERO); bool is_slice = info->size == IP_PTR_SIZE_SLICE; flags |= is_slice ? IP_PTR_SIZE_MANY : info->size; flags |= (info->alignment & IP_PTR_FLAGS_ALIGN_MASK) << IP_PTR_FLAGS_ALIGN_SHIFT; if (info->is_const) flags |= IP_PTR_FLAGS_IS_CONST; if (info->is_volatile) flags |= IP_PTR_FLAGS_IS_VOLATILE; if (info->is_allowzero) flags |= IP_PTR_FLAGS_IS_ALLOWZERO; uint32_t ptr = ipGetPtrTypeFull( ip, info->child, flags, info->sentinel, info->packed_offset); return is_slice ? ipGetSliceType(ip, ptr) : ptr; } // The `peer_sentinel` / `cur_sentinel` switch: a `.one` pointer's sentinel // lives on its array pointee ("else => .none"), every other size carries its // own. `.c` is `unreachable` there and cannot reach this arm at all. static uint32_t semaPeerPtrSentinel( const InternPool* ip, const IpPtrInfo* info) { if (info->size != IP_PTR_SIZE_ONE) return info->sentinel; uint32_t len, child, sentinel; if (!ztArrayTypeInfo(ip, info->child, &len, &child, &sentinel)) return IP_INDEX_NONE; return sentinel; } // Every site where upstream's `good:` switch returns `generic_err` after a // `resolvePairInMemoryCoercible` said `null`. Upstream renders a real // "incompatible types" error there; zig1's in-memory query under-accepts, // so the honest answer is a loud decline, never an invented upstream // rejection. #define PEER_PTR_NOT_COERCIBLE \ UNIMPLEMENTED("typeof_peer: .ptr pointee pair not in-memory coercible") // The `good:` block of the `.ptr` strategy, "just responsible for deciding // the size and pointee (not including single-pointer array sentinel)". // Returns false for the conflicts upstream decides exactly (a `[*]T` peer // against a resolved slice when some peer really was a slice, and the // "Impossible!" `[]T` + `[*]T`); the rest decline loudly above. static bool semaPeerPtrResolveSizeAndPointee(Zcu* zcu, InternPool* ip, IpPtrInfo* ptr_info, const IpPtrInfo* peer_info, bool have_slice_peer) { PeerArrayLike peer_arr, cur_arr; bool has_peer_arr = semaPeerTypeIsArrayLike(ip, peer_info->child, &peer_arr); bool has_cur_arr = semaPeerTypeIsArrayLike(ip, ptr_info->child, &cur_arr); uint32_t pointee; switch (peer_info->size) { case IP_PTR_SIZE_ONE: switch (ptr_info->size) { case IP_PTR_SIZE_ONE: pointee = semaPeerResolvePairInMemoryCoercible( zcu, ip, ptr_info->child, peer_info->child); if (pointee != IP_INDEX_NONE) { ptr_info->child = pointee; return true; } // Upstream's two `orelse return generic_err` here follow a // resolvePairInMemoryCoercible that already said `null`, and // zig1's in-memory query under-accepts -- so unlike the three // sites below, a decline here can be zig1's gap rather than a // real conflict. It stays loud. if (!has_cur_arr || !has_peer_arr) PEER_PTR_NOT_COERCIBLE; pointee = semaPeerResolvePairInMemoryCoercible( zcu, ip, cur_arr.elem_ty, peer_arr.elem_ty); if (pointee != IP_INDEX_NONE) { // "*[n:x]T + *[n:y]T = *[n]T" if (cur_arr.len == peer_arr.len) { ptr_info->child = ipGetArrayType( ip, cur_arr.len, pointee, IP_INDEX_NONE); return true; } // "*[a]T + *[b]T = []T" ptr_info->size = IP_PTR_SIZE_SLICE; ptr_info->child = pointee; return true; } if (peer_arr.elem_ty == ZIR_REF_NORETURN_TYPE) { // "*struct{} + *[a]T = []T" ptr_info->size = IP_PTR_SIZE_SLICE; ptr_info->child = cur_arr.elem_ty; return true; } if (cur_arr.elem_ty == ZIR_REF_NORETURN_TYPE) { // "*[a]T + *struct{} = []T" ptr_info->size = IP_PTR_SIZE_SLICE; ptr_info->child = peer_arr.elem_ty; return true; } PEER_PTR_NOT_COERCIBLE; case IP_PTR_SIZE_MANY: case IP_PTR_SIZE_SLICE: // "Only works for *[n]T + [*]T -> [*]T" and its slice twin. // `const arr = peer_pointee_array orelse return generic_err;` // (src/Sema.zig) -- decided before any // resolvePairInMemoryCoercible runs, so this one is upstream's // conflict exactly, not a conservative decline. if (!has_peer_arr) return false; // `.conflict` pointee = semaPeerResolvePairInMemoryCoercible( zcu, ip, ptr_info->child, peer_arr.elem_ty); if (pointee != IP_INDEX_NONE) { ptr_info->child = pointee; return true; } // "*struct{} + [*]T -> [*]T" if (peer_arr.elem_ty == ZIR_REF_NORETURN_TYPE) return true; PEER_PTR_NOT_COERCIBLE; default: // IP_PTR_SIZE_C: "unreachable" UNIMPLEMENTED("typeof_peer: .ptr strategy saw a C pointer"); } case IP_PTR_SIZE_MANY: switch (ptr_info->size) { case IP_PTR_SIZE_ONE: // "Only works for [*]T + *[n]T -> [*]T"; the `orelse return // generic_err` again precedes every coercible query. if (!has_cur_arr) return false; // `.conflict` pointee = semaPeerResolvePairInMemoryCoercible( zcu, ip, cur_arr.elem_ty, peer_info->child); if (pointee != IP_INDEX_NONE) { ptr_info->size = IP_PTR_SIZE_MANY; ptr_info->child = pointee; return true; } if (cur_arr.elem_ty == ZIR_REF_NORETURN_TYPE) { // "[*]T + *struct{} -> [*]T" ptr_info->size = IP_PTR_SIZE_MANY; ptr_info->child = peer_info->child; return true; } PEER_PTR_NOT_COERCIBLE; case IP_PTR_SIZE_MANY: pointee = semaPeerResolvePairInMemoryCoercible( zcu, ip, ptr_info->child, peer_info->child); if (pointee == IP_INDEX_NONE) PEER_PTR_NOT_COERCIBLE; ptr_info->child = pointee; return true; case IP_PTR_SIZE_SLICE: // "Only works if no peers are actually slices" if (have_slice_peer) return false; // `.conflict` // "Okay, then works for [*]T + \"[]T\" -> [*]T" pointee = semaPeerResolvePairInMemoryCoercible( zcu, ip, ptr_info->child, peer_info->child); if (pointee == IP_INDEX_NONE) PEER_PTR_NOT_COERCIBLE; ptr_info->size = IP_PTR_SIZE_MANY; ptr_info->child = pointee; return true; default: // IP_PTR_SIZE_C: "unreachable" UNIMPLEMENTED("typeof_peer: .ptr strategy saw a C pointer"); } case IP_PTR_SIZE_SLICE: switch (ptr_info->size) { case IP_PTR_SIZE_ONE: // "Only works for []T + *[n]T -> []T"; the `orelse return // generic_err` again precedes every coercible query. if (!has_cur_arr) return false; // `.conflict` pointee = semaPeerResolvePairInMemoryCoercible( zcu, ip, cur_arr.elem_ty, peer_info->child); if (pointee != IP_INDEX_NONE) { ptr_info->size = IP_PTR_SIZE_SLICE; ptr_info->child = pointee; return true; } if (cur_arr.elem_ty == ZIR_REF_NORETURN_TYPE) { // "[]T + *struct{} -> []T" ptr_info->size = IP_PTR_SIZE_SLICE; ptr_info->child = peer_info->child; return true; } PEER_PTR_NOT_COERCIBLE; case IP_PTR_SIZE_MANY: // "Impossible! (current peer is an actual slice)" return false; // `.conflict` case IP_PTR_SIZE_SLICE: pointee = semaPeerResolvePairInMemoryCoercible( zcu, ip, ptr_info->child, peer_info->child); if (pointee == IP_INDEX_NONE) PEER_PTR_NOT_COERCIBLE; ptr_info->child = pointee; return true; default: // IP_PTR_SIZE_C: "unreachable" UNIMPLEMENTED("typeof_peer: .ptr strategy saw a C pointer"); } default: // IP_PTR_SIZE_C: "unreachable" UNIMPLEMENTED("typeof_peer: .ptr strategy saw a C pointer"); } ZIG1_TCC_RETURN(false); } // (part of) Sema.zig:resolvePeerTypesInner // The `.ptr` and `.c_ptr` strategies' join of a peer's pointer attributes // into `ptr_info`: the alignment (implicit if both are, else minStrict of // the two, "e.g. '*u32' + '*align(4) c_uint' -> '*align(4) u32'"), then // const and volatile. False is `generic_err`: the address spaces or the // packed offsets differ. static bool semaPeerPtrJoinAttrs(Zcu* zcu, InternPool* ip, IpPtrInfo* ptr_info, const IpPtrInfo* peer_info) { if (ptr_info->alignment != IP_PTR_ALIGN_NONE || peer_info->alignment != IP_PTR_ALIGN_NONE) { uint32_t cur_align = ptr_info->alignment; uint32_t new_align = peer_info->alignment; if (cur_align == IP_PTR_ALIGN_NONE && !ztAbiAlignLog2(zcu, ip, ptr_info->child, &cur_align)) UNIMPLEMENTED( "typeof_peer: pointer peer's ABI alignment is unresolved"); if (new_align == IP_PTR_ALIGN_NONE && !ztAbiAlignLog2(zcu, ip, peer_info->child, &new_align)) UNIMPLEMENTED( "typeof_peer: pointer peer's ABI alignment is unresolved"); ptr_info->alignment = cur_align < new_align ? cur_align : new_align; } // `packed_offset` is upstream's { bit_offset, host_size } pair in one // word, so one comparison is both of upstream's. if (((ptr_info->flags ^ peer_info->flags) >> IP_PTR_FLAGS_ADDRSPACE_SHIFT) & IP_PTR_FLAGS_ADDRSPACE_MASK || ptr_info->packed_offset != peer_info->packed_offset) return false; ptr_info->is_const = ptr_info->is_const || peer_info->is_const; ptr_info->is_volatile = ptr_info->is_volatile || peer_info->is_volatile; return true; } // (part of) Sema.zig:resolvePeerTypesInner // Sema.resolvePeerTypesInner's `.ptr` strategy (src/Sema.zig), // transcribed whole. Returns the resolved pointer type, or IP_INDEX_NONE for // upstream's `.conflict`. Upstream's `first_idx` / `other_idx` / the // `opt_slice_idx` index exist only to render a conflict's two peer // positions, which zig1 does not render, so the slice index is carried as // the boolean the logic actually reads. static uint32_t semaPeerResolvePtrStrategy( Zcu* zcu, InternPool* ip, const uint32_t* peer_tys, uint32_t len) { // "If we've resolved to a `[]T` but then see a `[*]T`, we can resolve to // a `[*]T` only if there were no actual slices. Else, we want the slice // index to report a conflict." bool have_slice_peer = false; bool have_ptr_info = false; IpPtrInfo ptr_info; memset(&ptr_info, 0, sizeof(ptr_info)); for (uint32_t i = 0; i < len; i++) { uint32_t ty = peer_tys[i]; if (ty == IP_INDEX_NONE) continue; IpPtrInfo peer_info; if (!semaPeerPtrTypeInfo(ip, ty, &peer_info)) return IP_INDEX_NONE; // `.conflict` switch (peer_info.size) { case IP_PTR_SIZE_ONE: case IP_PTR_SIZE_MANY: break; case IP_PTR_SIZE_SLICE: have_slice_peer = true; break; default: // IP_PTR_SIZE_C return IP_INDEX_NONE; // `.conflict` } if (!have_ptr_info) { ptr_info = peer_info; have_ptr_info = true; continue; } if (!semaPeerPtrJoinAttrs(zcu, ip, &ptr_info, &peer_info)) return IP_INDEX_NONE; // `generic_err` ptr_info.is_allowzero = ptr_info.is_allowzero || peer_info.is_allowzero; // Both sentinels are read before the size/pointee switch rewrites // `ptr_info`, exactly as upstream orders it. uint32_t peer_sentinel = semaPeerPtrSentinel(ip, &peer_info); uint32_t cur_sentinel = semaPeerPtrSentinel(ip, &ptr_info); if (!semaPeerPtrResolveSizeAndPointee( zcu, ip, &ptr_info, &peer_info, have_slice_peer)) return IP_INDEX_NONE; // `.conflict` uint32_t arr_len, arr_child, arr_sentinel; bool cur_is_array = ztArrayTypeInfo( ip, ptr_info.child, &arr_len, &arr_child, &arr_sentinel); uint32_t sentinel_ty = (ptr_info.size == IP_PTR_SIZE_ONE && cur_is_array) ? arr_child : ptr_info.child; // "sentinel: { no_sentinel: {" bool sentinels_match = false; if (peer_sentinel != IP_INDEX_NONE && cur_sentinel != IP_INDEX_NONE) { // Upstream coerces both sentinels to `sentinel_ty` before // comparing them. `InternPool.getCoerced`'s first rule is "if // (old_ty == new_ty) return val", so for a sentinel already of // that type the comparison is index equality; any other sentinel // type needs the rest of `getCoerced` and declines loudly. if (ipTypeOf(ip, peer_sentinel) != sentinel_ty || ipTypeOf(ip, cur_sentinel) != sentinel_ty) UNIMPLEMENTED("typeof_peer: .ptr sentinel needs getCoerced"); sentinels_match = peer_sentinel == cur_sentinel; } if (sentinels_match) { // "Sentinels match" if (ptr_info.size == IP_PTR_SIZE_ONE) { if (!cur_is_array) UNIMPLEMENTED( "typeof_peer: .ptr sentinel pointee is not an array"); ptr_info.child = ipGetArrayType(ip, arr_len, arr_child, cur_sentinel); } else { ptr_info.sentinel = cur_sentinel; } } else { // "Clear existing sentinel" ptr_info.sentinel = IP_INDEX_NONE; if (ptr_info.size == IP_PTR_SIZE_ONE && cur_is_array) ptr_info.child = ipGetArrayType(ip, arr_len, arr_child, IP_INDEX_NONE); } } if (!have_ptr_info) UNIMPLEMENTED("typeof_peer: .ptr strategy with no pointer peer"); // "Before we succeed, check the pointee type. If we tried to apply PTR // to (for instance) &.{} and &.{}, we'll currently have a pointer type of // `*[0]noreturn` - we wanted to coerce the empty struct to a specific // type, but no peer provided one. We need to detect this case and emit an // error." uint32_t pointee = ptr_info.child; if (pointee == ZIR_REF_NORETURN_TYPE) return IP_INDEX_NONE; // `.conflict` uint32_t arr_len, arr_child, arr_sentinel; if (ztArrayTypeInfo(ip, pointee, &arr_len, &arr_child, &arr_sentinel) && arr_child == ZIR_REF_NORETURN_TYPE) return IP_INDEX_NONE; // `.conflict` return semaPeerPtrTypeIntern(ip, &ptr_info); } // (part of) Sema.zig:resolvePeerTypesInner // The `.c_ptr` strategy: integers that fit a pointer and null join any C // pointer; every other peer is a pointer at runtime (through optionals) whose // pointee coerces in memory, the result `[*c]` with the joined attributes. // IP_INDEX_NONE is `.conflict`. static uint32_t semaPeerResolveCPtrStrategy(Zcu* zcu, InternPool* ip, const uint32_t* peer_tys, const uint32_t* peer_vals, uint32_t len) { bool have_ptr_info = false; IpPtrInfo ptr_info; memset(&ptr_info, 0, sizeof(ptr_info)); for (uint32_t i = 0; i < len; i++) { uint32_t ty = peer_tys[i]; if (ty == IP_INDEX_NONE) continue; uint32_t bits; bool is_signed; // "comptime-known integers can always coerce to C pointers", and a // runtime one no bigger than a usize (wasm32: 32 bits). if (ty == ZIR_REF_COMPTIME_INT_TYPE || ty == ZIR_REF_NULL_TYPE) continue; if (ztIntInfo(ip, ty, &bits, &is_signed) && (peer_vals[i] != IP_INDEX_NONE || bits <= ZT_PTR_BIT_WIDTH)) continue; if (!ztIsPtrAtRuntime(ip, ty)) return IP_INDEX_NONE; IpPtrInfo peer_info; if (ipPtrInfo(ip, ztPtrInfoType(ip, ty), &peer_info) != IP_PTRI_OK) UNREACHABLE("typeof_peer: .c_ptr peer not a pointer"); if (!have_ptr_info) { ptr_info = peer_info; ptr_info.size = IP_PTR_SIZE_C; have_ptr_info = true; continue; } // "Try peer -> cur, then cur -> peer" uint32_t child = semaPeerResolvePairInMemoryCoercible( zcu, ip, ptr_info.child, peer_info.child); if (child == IP_INDEX_NONE) return IP_INDEX_NONE; ptr_info.child = child; uint32_t cur_sentinel = ptr_info.sentinel; ptr_info.sentinel = IP_INDEX_NONE; if (cur_sentinel != IP_INDEX_NONE && peer_info.sentinel != IP_INDEX_NONE) { // getCoerced of either sentinel to the child: identity when // already of that type. if (ipTypeOf(ip, cur_sentinel) != child || ipTypeOf(ip, peer_info.sentinel) != child) UNIMPLEMENTED("typeof_peer: .c_ptr sentinel needs getCoerced"); if (cur_sentinel == peer_info.sentinel) ptr_info.sentinel = cur_sentinel; } if (!semaPeerPtrJoinAttrs(zcu, ip, &ptr_info, &peer_info)) return IP_INDEX_NONE; } if (!have_ptr_info) UNREACHABLE("typeof_peer: .c_ptr strategy with no pointer peer"); return semaPeerPtrTypeIntern(ip, &ptr_info); } // (part of) Sema.zig:resolvePeerTypesInner // Sema.resolvePeerTypesInner's `.array` strategy (src/Sema.zig), // transcribed. Tuples of the right length join without an element type; // the first array or vector peer sets it, every other one must coerce in // memory either way. static uint32_t semaPeerResolveArrayStrategy( Zcu* zcu, InternPool* ip, const uint32_t* peer_tys, uint32_t len) { bool have_len = false, have_arr = false; uint32_t arr_len = 0, elem_ty = IP_INDEX_NONE, sentinel = IP_INDEX_NONE; for (uint32_t i = 0; i < len; i++) { uint32_t ty = peer_tys[i]; if (ty == IP_INDEX_NONE) continue; IpArrayInfo info; if (ipArrayInfo(ip, ty, &info) != IP_ARR_OK) { // "We allow tuples of the correct length." PeerArrayLike arr_like; if (!semaPeerTypeIsArrayLike(ip, ty, &arr_like)) return IP_INDEX_NONE; // `.conflict` if (have_len && arr_like.len != arr_len) return IP_INDEX_NONE; // `.conflict` arr_len = arr_like.len; have_len = true; sentinel = IP_INDEX_NONE; continue; } if (!have_arr) { if (!have_len) { arr_len = info.len; sentinel = info.sentinel; have_len = true; } have_arr = true; elem_ty = info.child; continue; } if (info.len != arr_len) return IP_INDEX_NONE; // `.conflict` if (info.child != elem_ty) { if (coerceInMemoryAllowed(zcu, ip, elem_ty, info.child, false)) { // `peer_elem_coerces_to_elem` } else if (coerceInMemoryAllowed( zcu, ip, info.child, elem_ty, false)) { elem_ty = info.child; } else { return IP_INDEX_NONE; // `.conflict` } } if (sentinel != IP_INDEX_NONE && sentinel != info.sentinel) sentinel = IP_INDEX_NONE; } if (!have_arr) UNIMPLEMENTED("typeof_peer: .array strategy with no array peer"); return ipGetArrayType(ip, arr_len, elem_ty, sentinel); } // The value projection `resolvePeerTypesInner`'s `.optional` and // `.error_union` arms perform before recursing on their payload peers: // upstream's `if (!opt_val.isUndef(zcu)) opt_val.optionalValue(zcu) else // null` (src/Sema.zig) and its `.payload => |payload_ip| ... , // .err_name => val_ptr.* = null` switch. `wrapper_tag` is the arm's // payload carrier and `payload_ty` the already-projected payload type. // Returns IP_INDEX_NONE for upstream's `null`, i.e. "this peer contributes no // comptime value". static uint32_t semaPeerProjectPayloadValue(InternPool* ip, uint32_t val, uint8_t wrapper_tag, uint32_t payload_ty, const char* unmodelled_msg) { if (val == IP_INDEX_NONE) return IP_INDEX_NONE; // "if (val_ptr.*) |...|": nothing to project if (ipIsUndef(ip, val)) // The two arms differ here and only here. The error-union arm retypes // the undef onto the payload -- `".undef => val_ptr.* = // Value.fromInterned(try pt.intern(.{ .undef = ty_ptr.*.? // .toIntern() }))"` -- while the optional arm nulls it, // because its projection is guarded by `"if (!opt_val.isUndef(zcu)) // ... else null"`. return wrapper_tag == IP_TAG_ERROR_UNION_PAYLOAD ? ipGetUndef(ip, payload_ty) : IP_INDEX_NONE; if (ipItemTagIs(ip, val, wrapper_tag)) { // Both carriers are extra { wrapper_ty, payload_val }. return ipPayloadVal(ip, val); } if (wrapper_tag == IP_TAG_OPT_PAYLOAD && ipItemTagIs(ip, val, IP_TAG_OPT_NULL)) return IP_INDEX_NONE; // a comptime `null` has no payload value if (wrapper_tag == IP_TAG_ERROR_UNION_PAYLOAD && ipItemTagIs(ip, val, IP_TAG_ERROR_UNION_ERROR)) return IP_INDEX_NONE; // ".err_name => val_ptr.* = null" UNIMPLEMENTED(unmodelled_msg); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // (part of) Sema.zig:resolvePeerTypesInner // Sema.resolvePeerTypesInner (src/Sema.zig). `peer_tys` is upstream's // `[]?Type` and `peer_vals` is its `[]?Value`, both with `IP_INDEX_NONE` for // `null`. Both arrays are mutated in place exactly as upstream mutates its // slices, so the recursive arms can hand their sub-peers to the same // function -- including the `.optional` and `.error_union` arms, which // project each peer's value onto its payload before recursing. Returns the // resolved type, or IP_INDEX_NONE for upstream's `.conflict` (which stock Zig // reports as "incompatible types"). Every zig1 GAP declines loudly at its own // site, naming the strategy or peer shape that is missing, so IP_INDEX_NONE // here means `.conflict` and nothing else. static uint32_t resolvePeerTypesInner( Zcu* zcu, uint32_t* peer_tys, uint32_t* peer_vals, uint32_t len) { InternPool* ip = &zcu->intern_pool; // Upstream's `.conflict` peer index, which only // PeerResolveResult.report's error rendering reads; zig1 has none. uint32_t strat_reason = 0; PeerResolveStrategy s = PEER_STRAT_UNKNOWN; for (uint32_t i = 0; i < len; i++) { if (peer_tys[i] == IP_INDEX_NONE) continue; PeerResolveStrategy sel; if (!semaPeerStrategySelect(ip, peer_tys[i], &sel)) UNIMPLEMENTED("typeof_peer: unmodeled peer type shape"); s = semaPeerStrategyMerge(s, sel, &strat_reason, i); } if (s == PEER_STRAT_UNKNOWN) { // "The whole thing was noreturn or undefined - try to do an exact // match" s = PEER_STRAT_EXACT; } else { // "There was something other than noreturn and undefined, so we can // ignore those peers" for (uint32_t i = 0; i < len; i++) { if (peer_tys[i] == ZIR_REF_NORETURN_TYPE || peer_tys[i] == ZIR_REF_UNDEFINED_TYPE) peer_tys[i] = IP_INDEX_NONE; } } switch (s) { case PEER_STRAT_ERROR_SET: { uint32_t final_set = IP_INDEX_NONE; for (uint32_t i = 0; i < len; i++) { uint32_t ty = peer_tys[i]; if (ty == IP_INDEX_NONE) continue; if (!ipIsErrorSetType(ip, ty)) return IP_INDEX_NONE; // `.conflict` // "final_set = try sema.maybeMergeErrorSets(block, src, // cur_set, ty);" (src/Sema.zig) -- the same // `maybeMergeErrorSets` zig1 already spells as // `semaErrorSetPeerType`, which resolves an inferred set before // falling through to `errorSetMerge`. Merging the two resolved // sets directly cannot: it declines on every inferred peer. if (final_set == IP_INDEX_NONE) { final_set = ty; continue; } final_set = semaErrorSetPeerType(ip, final_set, ty); if (final_set == IP_INDEX_NONE) UNIMPLEMENTED("typeof_peer: error-set peers do not merge"); } return final_set; } case PEER_STRAT_ERROR_UNION: { uint32_t final_set = IP_INDEX_NONE; for (uint32_t i = 0; i < len; i++) { uint32_t ty = peer_tys[i]; if (ty == IP_INDEX_NONE) continue; uint32_t set_ty; if (ipIsErrorSetType(ip, ty)) { peer_tys[i] = IP_INDEX_NONE; // "no payload to decide on" peer_vals[i] = IP_INDEX_NONE; set_ty = ty; } else if (semaIsErrorUnionType(ip, ty)) { set_ty = ipErrorUnionErrorSet(ip, ty); peer_tys[i] = ipErrorUnionPayload(ip, ty); // "if (val_ptr.*) |eu_val| switch (...) { .payload => // |payload_ip| val_ptr.* = ..., .err_name => val_ptr.* = // null, .undef => ... }" (src/Sema.zig). peer_vals[i] = semaPeerProjectPayloadValue(ip, peer_vals[i], IP_TAG_ERROR_UNION_PAYLOAD, peer_tys[i], "typeof_peer: unmodelled error-union peer value"); } else { continue; // "whole type is the payload" } // "final_set = try sema.maybeMergeErrorSets(block, src, // cur_set, set_ty);" (src/Sema.zig) -- see the // `.error_set` arm for why this is `semaErrorSetPeerType` and not // a direct merge of two already-resolved sets. if (final_set == IP_INDEX_NONE) { final_set = set_ty; continue; } final_set = semaErrorSetPeerType(ip, final_set, set_ty); if (final_set == IP_INDEX_NONE) UNIMPLEMENTED("typeof_peer: error-set peers do not merge"); } uint32_t final_payload = resolvePeerTypesInner(zcu, peer_tys, peer_vals, len); if (final_payload == IP_INDEX_NONE) return IP_INDEX_NONE; return semaErrorUnionTypeFromSetPayload(ip, final_set, final_payload); } case PEER_STRAT_NULLABLE: { for (uint32_t i = 0; i < len; i++) { if (peer_tys[i] == IP_INDEX_NONE) continue; if (peer_tys[i] != ZIR_REF_NULL_TYPE) return IP_INDEX_NONE; // `.conflict` } return ZIR_REF_NULL_TYPE; } case PEER_STRAT_OPTIONAL: { for (uint32_t i = 0; i < len; i++) { uint32_t ty = peer_tys[i]; if (ty == IP_INDEX_NONE) continue; if (ty == ZIR_REF_NULL_TYPE) { peer_tys[i] = IP_INDEX_NONE; peer_vals[i] = IP_INDEX_NONE; } else if (ty < ip->items_len && ztZigTypeTag(ip, ty) == ZIG_TYPE_TAG_OPTIONAL) { peer_tys[i] = ipOptionalChild(ip, ty); // "if (val_ptr.*) |opt_val| val_ptr.* = if // (!opt_val.isUndef(zcu)) opt_val.optionalValue(zcu) else // null" -- a comptime-known `null` peer contributes no value. peer_vals[i] = semaPeerProjectPayloadValue(ip, peer_vals[i], IP_TAG_OPT_PAYLOAD, peer_tys[i], "typeof_peer: unmodelled optional peer value"); } } uint32_t child_ty = resolvePeerTypesInner(zcu, peer_tys, peer_vals, len); if (child_ty == IP_INDEX_NONE) return IP_INDEX_NONE; return ipGetOptionalType(ip, child_ty); } case PEER_STRAT_COMPTIME_INT: { for (uint32_t i = 0; i < len; i++) { if (peer_tys[i] == IP_INDEX_NONE) continue; if (peer_tys[i] != ZIR_REF_COMPTIME_INT_TYPE) return IP_INDEX_NONE; // `.conflict` } return ZIR_REF_COMPTIME_INT_TYPE; } case PEER_STRAT_COMPTIME_FLOAT: { for (uint32_t i = 0; i < len; i++) { if (peer_tys[i] == IP_INDEX_NONE) continue; if (peer_tys[i] != ZIR_REF_COMPTIME_INT_TYPE && peer_tys[i] != ZIR_REF_COMPTIME_FLOAT_TYPE) return IP_INDEX_NONE; // `.conflict` } return ZIR_REF_COMPTIME_FLOAT_TYPE; } case PEER_STRAT_FIXED_INT: { // The `.fixed_int` arm is `semaPeerFixedIntTypeN`, the same // transcription the arithmetic path resolves its operands with: it // reads this array directly, skipping the peers dropped above. PeerResolveResult why; uint32_t fixed = semaPeerFixedIntTypeN(ip, peer_tys, peer_vals, len, &why); if (fixed == IP_INDEX_NONE && why == PEER_RESOLVE_UNMODELED) UNIMPLEMENTED("typeof_peer: .fixed_int peer is not an integer"); return fixed; } case PEER_STRAT_FIXED_FLOAT: { uint32_t cur_ty = IP_INDEX_NONE; uint32_t cur_bits = 0; for (uint32_t i = 0; i < len; i++) { uint32_t ty = peer_tys[i]; if (ty == IP_INDEX_NONE) continue; uint32_t float_bits; if (ztSimpleFloatBits(ty, &float_bits)) { if (cur_ty == IP_INDEX_NONE) { cur_ty = ty; cur_bits = float_bits; } else if (cur_ty != ty) { // "Recreate the type so we eliminate any c_longdouble" uint32_t bits = float_bits > cur_bits ? float_bits : cur_bits; cur_bits = bits; switch (bits) { case 16: cur_ty = ZIR_REF_F16_TYPE; break; case 32: cur_ty = ZIR_REF_F32_TYPE; break; case 64: cur_ty = ZIR_REF_F64_TYPE; break; case 80: cur_ty = ZIR_REF_F80_TYPE; break; default: cur_ty = ZIR_REF_F128_TYPE; break; } } continue; } uint32_t int_bits; bool int_signed; if (ty == ZIR_REF_COMPTIME_FLOAT_TYPE || ty == ZIR_REF_COMPTIME_INT_TYPE || ztIntInfo(ip, ty, &int_bits, &int_signed)) continue; return IP_INDEX_NONE; // `.conflict` } // "Ensure that any integer peers can coerce safely to the resulting // float." for (uint32_t i = 0; i < len; i++) { uint32_t ty = peer_tys[i]; if (ty == IP_INDEX_NONE || peer_vals[i] != IP_INDEX_NONE) continue; uint32_t int_bits; bool int_signed; if (!ztIntInfo(ip, ty, &int_bits, &int_signed)) continue; uint32_t int_precision = int_bits - (int_signed ? 1u : 0u); if (int_precision > ztFloatSignificandBits(cur_bits)) return IP_INDEX_NONE; // `.conflict` } return cur_ty; } case PEER_STRAT_EXACT: { uint32_t expect_ty = IP_INDEX_NONE; for (uint32_t i = 0; i < len; i++) { if (peer_tys[i] == IP_INDEX_NONE) continue; if (expect_ty == IP_INDEX_NONE) { expect_ty = peer_tys[i]; } else if (peer_tys[i] != expect_ty) { return IP_INDEX_NONE; // `.conflict` } } return expect_ty; } // The strategies below that are not ported decline loudly, each naming // itself. case PEER_STRAT_ARRAY: return semaPeerResolveArrayStrategy(zcu, ip, peer_tys, len); case PEER_STRAT_VECTOR: { // Sema.resolvePeerTypesInner's `.vector` strategy // (src/Sema.zig): every peer is an array or vector of one length // -- or a tuple of it, which "won't participate in the child type // resolution" -- and the children resolve as peers of their own. bool have_len = false; uint32_t vec_len = 0; for (uint32_t i = 0; i < len; i++) { uint32_t ty = peer_tys[i]; if (ty == IP_INDEX_NONE) continue; IpArrayInfo info; if (ipArrayInfo(ip, ty, &info) != IP_ARR_OK) { // "Allow tuples of the correct length" PeerArrayLike arr_like; if (!semaPeerTypeIsArrayLike(ip, ty, &arr_like)) return IP_INDEX_NONE; // `.conflict` if (have_len && arr_like.len != vec_len) return IP_INDEX_NONE; // `.conflict` vec_len = arr_like.len; have_len = true; peer_tys[i] = IP_INDEX_NONE; peer_vals[i] = IP_INDEX_NONE; continue; } if (have_len && info.len != vec_len) return IP_INDEX_NONE; // `.conflict` vec_len = info.len; have_len = true; peer_tys[i] = info.child; // "multiple child vals, so we can't easily use them in PTR" peer_vals[i] = IP_INDEX_NONE; } uint32_t child_ty = resolvePeerTypesInner(zcu, peer_tys, peer_vals, len); if (child_ty == IP_INDEX_NONE) return IP_INDEX_NONE; return ipGetVectorType(ip, vec_len, child_ty); } case PEER_STRAT_C_PTR: return semaPeerResolveCPtrStrategy(zcu, ip, peer_tys, peer_vals, len); case PEER_STRAT_PTR: return semaPeerResolvePtrStrategy(zcu, ip, peer_tys, len); case PEER_STRAT_FUNC: UNIMPLEMENTED("typeof_peer: .func strategy"); case PEER_STRAT_ENUM_OR_UNION: { // Sema.resolvePeerTypesInner's `.enum_or_union` strategy // (src/Sema.zig). `opt_cur_ty` is the running answer and // `cur_ty_idx` the peer that gave it; `ty.eql(cur_ty)` is Index // identity here because every one of these types is interned, and // `unionTagTypeHypothetical` is `ztUnionTagTypeHypothetical`. // Upstream's three `else => unreachable` legs are unreachable for // the same reason: `opt_cur_ty` only ever takes a type this arm // already classified. uint32_t opt_cur_ty = IP_INDEX_NONE; for (uint32_t i = 0; i < len; i++) { uint32_t ty = peer_tys[i]; if (ty == IP_INDEX_NONE) continue; ZigTypeTag tag = ztZigTypeTag(ip, ty); if (tag != ZIG_TYPE_TAG_ENUM_LITERAL && tag != ZIG_TYPE_TAG_ENUM && tag != ZIG_TYPE_TAG_UNION) return IP_INDEX_NONE; // `.conflict` if (opt_cur_ty == IP_INDEX_NONE) { opt_cur_ty = ty; continue; } switch (ztZigTypeTag(ip, opt_cur_ty)) { case ZIG_TYPE_TAG_ENUM_LITERAL: // "opt_cur_ty = ty": any peer beats an enum literal. opt_cur_ty = ty; break; case ZIG_TYPE_TAG_ENUM: if (tag == ZIG_TYPE_TAG_ENUM) { if (ty != opt_cur_ty) return IP_INDEX_NONE; // `generic_err` } else if (tag == ZIG_TYPE_TAG_UNION) { if (ztUnionTagTypeHypothetical(zcu, ty) != opt_cur_ty) return IP_INDEX_NONE; // `generic_err` opt_cur_ty = ty; } // `.enum_literal => {}`: the literal contributes nothing. break; case ZIG_TYPE_TAG_UNION: if (tag == ZIG_TYPE_TAG_ENUM) { if (ty != ztUnionTagTypeHypothetical(zcu, opt_cur_ty)) return IP_INDEX_NONE; // `generic_err` } else if (tag == ZIG_TYPE_TAG_UNION) { if (ty != opt_cur_ty) return IP_INDEX_NONE; // `generic_err` } break; default: break; // `else => unreachable` } } // "return .{ .success = opt_cur_ty.? }" -- the strategy was selected // by at least one peer, so at least one survives here. return opt_cur_ty; } case PEER_STRAT_TUPLE: { // "First, check that every peer has the same approximate structure // (field count)" -- every peer a tuple. uint32_t field_count = UINT32_MAX; for (uint32_t i = 0; i < len; i++) { if (peer_tys[i] == IP_INDEX_NONE) continue; if (!ztIsTuple(ip, peer_tys[i])) return IP_INDEX_NONE; // `.conflict` uint32_t n = ipTupleFieldCount(ip, peer_tys[i]); if (field_count != UINT32_MAX && n != field_count) return IP_INDEX_NONE; // `.conflict` field_count = n; } // "Now, we'll recursively resolve the field types": per field, the // peers' field types and `val.fieldValue(pt, field_index)`; then the // field is comptime iff every peer's `structFieldValueComptime` // coerces (`.report_err = false`) to one equal value. uint32_t* field_types = ARR_INIT(uint32_t, field_count + 1u); uint32_t* field_vals = ARR_INIT(uint32_t, field_count + 1u); uint32_t* sub_tys = ARR_INIT(uint32_t, len); uint32_t* sub_vals = ARR_INIT(uint32_t, len); uint32_t res = IP_INDEX_NONE; uint32_t f = 0; for (; f < field_count; f++) { for (uint32_t i = 0; i < len; i++) { sub_tys[i] = peer_tys[i] == IP_INDEX_NONE ? IP_INDEX_NONE : ipTupleFieldType(ip, peer_tys[i], f); sub_vals[i] = peer_tys[i] == IP_INDEX_NONE || peer_vals[i] == IP_INDEX_NONE ? IP_INDEX_NONE : semaAggregateElemValueOrUndef(ip, peer_vals[i], sub_tys[i], f, "typeof_peer: tuple field value"); } field_types[f] = resolvePeerTypesInner(zcu, sub_tys, sub_vals, len); if (field_types[f] == IP_INDEX_NONE) break; // `.field_error` uint32_t comptime_val = IP_INDEX_NONE; for (uint32_t i = 0; i < len; i++) { if (peer_tys[i] == IP_INDEX_NONE) continue; uint32_t fty, fval; if (!semaTupleFieldValueComptime( ip, peer_tys[i], f, &fty, &fval) || fval == IP_INDEX_NONE) { comptime_val = IP_INDEX_NONE; break; } uint32_t coerced = semaCoerceExtraOpts( zcu, ip, fval, field_types[f], false); if (coerced == IP_INDEX_NONE || (comptime_val != IP_INDEX_NONE && coerced != comptime_val)) { comptime_val = IP_INDEX_NONE; break; } comptime_val = coerced; } field_vals[f] = comptime_val; } if (f == field_count) res = ipGetTupleType(ip, field_count, field_types, field_vals); free(sub_vals); free(sub_tys); free(field_vals); free(field_types); return res; } default: // PEER_STRAT_UNKNOWN, rewritten to `.exact` above UNREACHABLE("typeof_peer: unreachable strategy"); } ZIG1_TCC_RETURN(IP_INDEX_NONE); } // (part of) Sema.zig:resolvePeerTypes // Sema.resolvePeerTypes (src/Sema.zig) for `@TypeOf(a, b, ...)`, // shared by both evaluators. `vals` is upstream's `peer_vals`: each peer's // comptime value, or IP_INDEX_NONE when it has none. uint32_t semaTypeofPeerResolve( Zcu* zcu, uint32_t* tys, uint32_t* vals, uint32_t len) { if (len == 0) return ZIR_REF_NORETURN_TYPE; if (len == 1) return tys[0]; // "Fast path: check if everything has the same type to bypass the main // PTR logic." bool same_type = true; for (uint32_t i = 1; i < len; i++) if (tys[i] != tys[0]) same_type = false; if (same_type) return tys[0]; // Upstream's `peer_tys`/`peer_vals` are arena slices that // `resolvePeerTypesInner` mutates in place; the caller's own scratch // arrays are those slices here, so there is no second copy and no // arity cap of this function's own. uint32_t ty = resolvePeerTypesInner(zcu, tys, vals, len); if (ty == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM("typeof_peer: incompatible peer types"); return ty; } // Sema.zig:resolvePeerTypes // The `.types` peers: "opt_val.* = try ty.onePossibleValue(sema.pt);". static uint32_t semaResolvePeerTypesOfTypes( Zcu* zcu, uint32_t* tys, uint32_t len) { bool same_type = true; for (uint32_t i = 1; i < len; i++) same_type = same_type && tys[i] == tys[0]; if (len == 0 || same_type) return len == 0 ? ZIR_REF_NORETURN_TYPE : tys[0]; uint32_t* vals = ARR_INIT(uint32_t, len); for (uint32_t i = 0; i < len; i++) vals[i] = ztOnePossibleValue(&zcu->intern_pool, tys[i]); uint32_t ty = semaTypeofPeerResolve(zcu, tys, vals, len); free(vals); return ty; } static uint32_t semaEnumFromIntValue( InternPool* ip, uint32_t dest_ty, uint32_t val) { uint32_t tag_ty = ipEnumTypeIntTagType(ip, dest_ty); bool nonexhaustive = ztIsNonexhaustiveEnum(ip, dest_ty); if (!valIntFitsInType(ip, val, tag_ty)) COMPILE_ERROR_UPSTREAM(nonexhaustive ? "enum_from_int: int value out of range of non-exhaustive " "enum" : "enum_from_int: enum has no tag with value"); uint32_t tag = ipGetEnumTag(ip, dest_ty, semaCoerceComptime(NULL, ip, val, tag_ty)); if (!nonexhaustive && ipEnumTagFieldIndex(ip, tag) == UINT32_MAX) COMPILE_ERROR_UPSTREAM("enum_from_int: enum has no tag with value"); return tag; } // Sema.zig:coerce, Sema.zig:analyzeArithmetic // Sema.coerce for the comptime side of a runtime binop, once // Sema.analyzeArithmetic has resolved the peer type ("const casted_lhs = try // sema.coerce(block, resolved_type, lhs, lhs_src)"). Comptime operands come in // four forms: // Preinterned comptime_int (ZIR_REF_ZERO/ONE/NEGATIVE_ONE): coerce via // semaCoercePreinternedComptimeInt; // Interned int_comptime_int_* item (u32/u64/i32/i64/u128): read via the // integer readers and coerce like a literal; // A concretely typed int, or any float, of a different type than the peer: // re-intern at the peer type through coerceExtra; // Anything else: already of the destination type, used directly as an AIR // Ref. // Returns IP_INDEX_NONE when the value is not coercible. static uint32_t semaBinCoerceComptimeOperand( InternPool* ip, uint32_t val, uint32_t dest_ty) { uint32_t v = val; // A float peer (`x * 2` with `x: f64`): coerceExtra's int-to-float arm. uint32_t dest_fbits; if (ztSimpleFloatBits(dest_ty, &dest_fbits)) return semaCoerceComptime(NULL, ip, v, dest_ty); if (v < ZIR_REF_START_INDEX && ipTypeOfPreinterned(v) == ZIR_REF_COMPTIME_INT_TYPE) return semaCoercePreinternedComptimeInt(ip, v, dest_ty); uint32_t val_ty = semaInternedIntValueType(ip, val); if (val_ty == ZIR_REF_COMPTIME_INT_TYPE) { // A dynamically-interned comptime_int (a folded const like // atomic.cache_line read via decl_val) must also re-intern at the // concrete peer type -- passing it through would smuggle a // comptime_int into runtime AIR. Test the item's type and read it // with the tag-complete reader: a comptime_int interned as // `_U64`/`_I32`/`_I64` must not reach the `already typed` case below // and be passed through uncoerced. uint64_t mag; if (ipTryReadIntValue(ip, v, &mag)) return semaCoerceComptimeIntLiteral(ip, mag, dest_ty); // A magnitude past one limb (`@as(comptime_int, 1) << 111` as the // comptime side of a `u128` binop): the same `pt.getCoerced` the // general coercion reaches, not a second transcription of it. return semaCoerceComptimeIntBig(ip, v, dest_ty); } if (val_ty != IP_INDEX_NONE && val_ty != dest_ty) { // A concretely typed comptime int whose type is not the peer type: // peer resolution chose the other operand's type, and Sema.coerce // re-interns the value there. Passing it through instead fed a // foreign-typed constant into the binop -- the same smuggle as the // comptime_int arm above, for `@as(i8, 5) + n` with `n: u32`. return semaCoerceComptime(NULL, ip, v, dest_ty); } // A comptime float whose type is not the peer (a `comptime_float` // literal against a runtime `f64`, say): `Sema.coerce` re-interns it at // the peer type (`Value.floatCast`), exactly as the int arms above do; // passing it through put a `comptime_float` constant into runtime AIR. if (v < ip->items_len && semaTagIsFloat(ip->item_tags[v]) && ipTypeOf(ip, v) != dest_ty) return semaCoerceComptime(NULL, ip, v, dest_ty); return v; // already of the destination type } static void semaReplaceBreakWithTyOpBlock(AirInstList* insts, U32List* air_extra, uint32_t br_idx, uint32_t target_blk, AirInstTag tag, uint32_t dest_ty, uint32_t opref) { AirInstData cast_data; memset(&cast_data, 0, sizeof(cast_data)); cast_data.pair.a = dest_ty; cast_data.pair.b = opref; uint32_t cast_idx = semaAirInstAppend(insts, tag, cast_data); AirInstData new_br_data; memset(&new_br_data, 0, sizeof(new_br_data)); new_br_data.pair.a = target_blk; new_br_data.pair.b = AIR_REF_INST_BASE + cast_idx; uint32_t new_br_idx = semaAirInstAppend(insts, AIR_INST_BR, new_br_data); uint32_t payload = air_extra->len; u32ListAppend(air_extra, 2u); u32ListAppend(air_extra, cast_idx); u32ListAppend(air_extra, new_br_idx); insts->tags[br_idx] = AIR_INST_BLOCK; memset(&insts->datas[br_idx], 0, sizeof(insts->datas[br_idx])); insts->datas[br_idx].pair.a = ZIR_REF_NORETURN_TYPE; insts->datas[br_idx].pair.b = payload; } // Coerce block break `br_idx`'s operand to `dest_ty`, rewriting a nested // block it breaks with; `allow_nested_comptime_int_block` is the // switch_block_err_union peer. static void semaCoerceBlockBreakOperandToTypeInner(Zcu* zcu, AirInstList* insts, U32List* air_extra, uint32_t br_idx, uint32_t dest_ty, bool allow_nested_comptime_int_block) { InternPool* ip = &zcu->intern_pool; uint32_t target_blk = insts->datas[br_idx].pair.a; uint32_t opref = insts->datas[br_idx].pair.b; uint32_t op_ty = semaAirRefTypeForBlockCoerce(ip, insts, opref); if (op_ty == dest_ty) return; if (semaIsAirInstRef(opref)) { uint32_t op_idx = opref - AIR_REF_INST_BASE; uint32_t dst_bits; bool dst_signed; const char* nested_block_no_break = NULL; if (insts->tags[op_idx] == AIR_INST_BLOCK) { if (allow_nested_comptime_int_block && op_ty == ZIR_REF_COMPTIME_INT_TYPE && ztIntInfo(ip, dest_ty, &dst_bits, &dst_signed)) { nested_block_no_break = "resolveAnalyzedBlock: comptime_int block without break"; } else if (op_ty == ZIR_REF_UNDEFINED_TYPE) { // Sema.coerceExtra's final class-sensitive `inst == .undef` // arm retargets undefined values to the destination type. // When the undefined operand is itself an AIR block, rewrite // that block's breaks first, then make the block's result type // the selected peer type. nested_block_no_break = "resolveAnalyzedBlock: undefined block without break"; } } if (nested_block_no_break) { bool saw_break = false; for (uint32_t i = op_idx + 1; i < insts->len; i++) { if (insts->tags[i] != AIR_INST_BR || insts->datas[i].pair.a != op_idx) continue; saw_break = true; semaCoerceBlockBreakOperandToTypeInner(zcu, insts, air_extra, i, dest_ty, allow_nested_comptime_int_block); } if (!saw_break) UNIMPLEMENTED(nested_block_no_break); insts->datas[op_idx].pair.a = dest_ty; return; } } if (semaIsErrorUnionType(ip, dest_ty) && op_ty == ZIR_REF_COMPTIME_INT_TYPE) { uint32_t payload_ty = ipErrorUnionPayload(ip, dest_ty); uint64_t mag = ipReadIntValue(ip, opref); if (mag == UINT64_MAX) UNIMPLEMENTED("block peer coerce: unreadable comptime_int error " "union payload"); uint32_t payload_ip = semaCoerceComptimeIntLiteral(ip, mag, payload_ty); if (payload_ip == IP_INDEX_NONE) UNIMPLEMENTED("block peer coerce: comptime_int error union " "payload out of range"); semaReplaceBreakWithTyOpBlock(insts, air_extra, br_idx, target_blk, AIR_INST_WRAP_ERRUNION_PAYLOAD, dest_ty, payload_ip); return; } if (opref < AIR_REF_INST_BASE) { if (opref < ip->items_len && ip->item_tags[opref] == IP_TAG_ENUM_LITERAL && dest_ty < ip->items_len && ip->item_tags[dest_ty] == IP_TAG_TYPE_UNION) { uint32_t union_val = semaCoerceEnumLiteralToVoidUnionLazy(zcu, opref, dest_ty); if (union_val == IP_INDEX_NONE) UNIMPLEMENTED("block peer coerce: enum literal is not a void " "union field"); insts->datas[br_idx].pair.b = union_val; return; } // The per-`br` coerce is `Sema.coerce` (src/Sema.zig), so it // owes `coerceExtra`'s comptime-known integer range check: `"if // (!val.intFitsInType(dest_ty, null, zcu)) ... return sema.fail(..., // "type '{f}' cannot represent integer value '{f}'", ...)"` // (src/Sema.zig). `semaCoerceComptimeIntLiteral`, which // `coerceExtra` reaches, truncates to the destination width // instead, so `blk: { if (k == 0) break :blk a else break :blk 300; }` // against a `u8` peer would answer 44 where upstream rejects the // program. (An `if` expression with a result type -- `const x: u8 = // if (c) 1 else 300;` -- does not reach this site: it coerces at the // result location, which is `coerceExtra`'s own comptime_int arm, // and that one is gated separately.) `valIntFitsInType` is that // predicate, the same gate the runtime-binop peer asks. uint32_t dst_bits2; bool dst_signed2; if (ztIntInfo(ip, dest_ty, &dst_bits2, &dst_signed2) && (op_ty == ZIR_REF_COMPTIME_INT_TYPE || ztIntInfo(ip, op_ty, &dst_bits2, &dst_signed2)) && !valIntFitsInType(ip, opref, dest_ty)) COMPILE_ERROR_UPSTREAM("block peer coerce: type cannot represent " "integer value"); insts->datas[br_idx].pair.b = semaCoerceComptime(zcu, ip, opref, dest_ty); return; } // A runtime operand: the per-`br` `sema.coerce` (src/Sema.zig) is // the one runtime `coerceExtra`, run into a scratch list; any // instructions it produced replace the `br` with a noreturn block whose // body ends in a fresh `br` of the coerced operand -- the same move // `semaInferredAllocRewriteProng` makes for a store placeholder. U32List repl = { 0 }; uint32_t coerced = semaCoerceExtraRuntime(zcu, opref, op_ty, dest_ty, insts, &repl, air_extra, "block peer coerce: runtime coercion"); if (repl.len == 0) { free(repl.items); insts->datas[br_idx].pair.b = coerced; return; } AirInstData br_data; memset(&br_data, 0, sizeof(br_data)); br_data.pair.a = target_blk; br_data.pair.b = coerced; u32ListAppend(&repl, semaAirInstAppend(insts, AIR_INST_BR, br_data)); uint32_t payload = air_extra->len; u32ListAppend(air_extra, repl.len); // Air.Block.body_len for (uint32_t i = 0; i < repl.len; i++) u32ListAppend(air_extra, repl.items[i]); free(repl.items); insts->tags[br_idx] = AIR_INST_BLOCK; memset(&insts->datas[br_idx], 0, sizeof(insts->datas[br_idx])); insts->datas[br_idx].pair.a = ZIR_REF_NORETURN_TYPE; insts->datas[br_idx].pair.b = payload; } // resolveAnalyzedBlock's `const resolved_ty = try sema.resolvePeerTypes( // parent_block, src, merges.results.items, ...)` (src/Sema.zig): One // n-ary peer resolution over every entry recorded for `block_inst`. Also // zirResolveInferredAlloc's `resolvePeerTypes(block, ty_src, peer_vals, // .none)` over an inferred alloc's recorded stores. static uint32_t semaBlockMergesPeerType( Zcu* zcu, const InlineCtx* ictx, uint32_t block_inst) { uint32_t len = semaBlockMergesCount(ictx, block_inst); if (len == 0) UNIMPLEMENTED("block merge: value-yielding block with no results"); // `semaTypeofPeerResolve` mutates its arrays in place, as upstream's // `resolvePeerTypesInner` mutates the arena `peer_tys`/`peer_vals` // slices, so these are scratch copies rather than the merges array. uint32_t* tys = zig1Malloc((size_t)len * sizeof(*tys)); uint32_t* vals = zig1Malloc((size_t)len * sizeof(*vals)); uint32_t n = 0; for (uint32_t i = 0; i < ictx->merges_len; i++) { if (ictx->merges[i].block_inst != block_inst) continue; tys[n] = ictx->merges[i].result_ty; vals[n] = ictx->merges[i].result_val; n++; } uint32_t resolved = semaTypeofPeerResolve(zcu, tys, vals, len); free(tys); free(vals); return resolved; } // resolveAnalyzedBlock's value-yielding tail (src/Sema.zig), the // one statement every merge owner runs once its body payload is in // `air_extra` at `body_extra_idx`: resolve the n-ary peer type, coerce every // recorded `br` operand to it (`for (merges.br_list.items) |br|`), // then write the merge block's own `ty_pl`. Returns the resolved type. // `semaCoerceBlockBreakOperandToTypeInner` is zig1's `sema.coerce` plus // upstream's br-to-block rewrite when the coercion produces instructions, // which is why the body payload must already be appended. static uint32_t semaBlockMergesFinish(Zcu* zcu, const InlineCtx* ictx, AirInstList* insts, U32List* air_extra, uint32_t block_inst, uint32_t body_extra_idx, uint32_t* out_val) { insts->datas[block_inst].pair.b = body_extra_idx; // `merges.results.items.len == 1` with a comptime-known result: the // block is `void`, its `br` gives `{}`, and the block yields the value. uint32_t n = 0, last = 0; for (uint32_t i = 0; i < ictx->merges_len; i++) if (ictx->merges[i].block_inst == block_inst) { n++; last = i; } if (n == 1 && ictx->merges[last].result_val != IP_INDEX_NONE) { const struct BlockMerge* m = &ictx->merges[last]; insts->datas[block_inst].pair.a = ZIR_REF_VOID_TYPE; insts->datas[m->br_inst].pair.b = ZIR_REF_VOID_VALUE; *out_val = m->result_val; return m->result_ty; } uint32_t dest_ty = semaBlockMergesPeerType(zcu, ictx, block_inst); for (uint32_t i = 0; i < ictx->merges_len; i++) { if (ictx->merges[i].block_inst != block_inst) continue; // "if (br_operand_ty.eql(resolved_ty)) { // No type coercion needed. // continue; }" (src/Sema.zig) -- the recorded result type is // this break's `sema.typeOf(br_operand)`. if (ictx->merges[i].result_ty == dest_ty) continue; semaCoerceBlockBreakOperandToTypeInner( zcu, insts, air_extra, ictx->merges[i].br_inst, dest_ty, true); } insts->datas[block_inst].pair.a = dest_ty; return dest_ty; } // (part of) Sema.zig:coerceExtra // True for the `*const tuple` -> `[]const T` shape that upstream's // `Sema.coerceExtra` routes to `coerceTupleToSlicePtrs`: the destination is a // slice and the source is a single-item pointer whose child is a tuple // (src/Sema.zig — `inst_ty.isSinglePointer` + // `inst_child_ty.isTuple`). Only the shape gate; the const/empty/alignment // boundaries upstream enforces separately live in semaEmitTuplePtrToSlice. static bool semaIsRuntimeTuplePtrToSlice( const InternPool* ip, uint32_t ptr_ty, uint32_t slice_ty) { IpPtrInfo src_info; return ztIsSlice(ip, slice_ty) && ipPtrInfo(ip, ptr_ty, &src_info) == IP_PTRI_OK && src_info.size == IP_PTR_SIZE_ONE && ztIsTuple(ip, src_info.child); } // Sema.zig:analyzeSlicePtr // Sema.analyzeSlicePtr: a comptime slice's `.ptr` value, else slice_ptr. static uint32_t analyzeSlicePtr(const InternPool* ip, uint32_t slice, uint32_t slice_ty, AirInstList* insts, U32List* block) { if (!semaIsAirInstRef(slice)) return ipPtrSlicePtr(ip, slice); return semaEmitTyOp( insts, block, AIR_INST_SLICE_PTR, ip->item_datas[slice_ty], slice); } // (part of) Type.zig:indexableElem // zirMemcpy's indexablePtrLenOrNone plus Type.indexableElem: the length as // an AIR ref or usize value (INST_VALUE_UNSET: a many pointer has none). static uint32_t semaMemcpyIndexableLen(InternPool* ip, uint32_t ptr, uint32_t ptr_ty, uint32_t* out_elem, AirInstList* insts, U32List* block) { if (ztIsSlice(ip, ptr_ty)) { *out_elem = ztIndexableElem(ip, ptr_ty); if (!semaIsAirInstRef(ptr)) return ipPtrSliceLen(ip, ptr); return semaEmitTyOp( insts, block, AIR_INST_SLICE_LEN, ZIR_REF_USIZE_TYPE, ptr); } IpPtrInfo pi; if (ipPtrInfo(ip, ptr_ty, &pi) != IP_PTRI_OK) COMPILE_ERROR_UPSTREAM("memcpy: operand is not a pointer"); IpArrayInfo ai; if (pi.size == IP_PTR_SIZE_ONE && ipArrayInfo(ip, pi.child, &ai) == IP_ARR_OK) { *out_elem = ai.child; return ipGetIntUsize(ip, ai.len); } if (pi.size != IP_PTR_SIZE_MANY) UNIMPLEMENTED( "memcpy: operand is neither slice, array nor many pointer"); *out_elem = pi.child; return INST_VALUE_UNSET; } // Sema.zig:upgradeToArrayPtr // Sema.upgradeToArrayPtr: a non-`.one` pointer becomes `*[len:s]T` (keeping // the sentinel, alignment, constness, volatility and allowzero) through // `slice_ptr` (never folded) and `ptr_cast`. static uint32_t semaMemcpyUpgradeToArrayPtr(InternPool* ip, uint32_t ptr, uint32_t ptr_ty, uint64_t len, uint32_t* out_ty, AirInstList* insts, U32List* block) { bool is_slice = ztIsSlice(ip, ptr_ty); IpPtrInfo pi; if (ipPtrInfo(ip, is_slice ? ip->item_datas[ptr_ty] : ptr_ty, &pi) != IP_PTRI_OK) UNIMPLEMENTED("upgradeToArrayPtr: not a pointer"); if (out_ty) *out_ty = ptr_ty; if (!is_slice && pi.size == IP_PTR_SIZE_ONE) return ptr; if (len > UINT32_MAX) UNIMPLEMENTED("upgradeToArrayPtr: wide length"); uint32_t array_ty = ipGetArrayType(ip, (uint32_t)len, pi.child, pi.sentinel); uint32_t arr_ptr_ty = ipGetPtrType( ip, array_ty, (pi.flags & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_ONE); uint32_t non_slice = is_slice ? semaEmitTyOp( insts, block, AIR_INST_SLICE_PTR, ip->item_datas[ptr_ty], ptr) : ptr; if (out_ty) *out_ty = arr_ptr_ty; return semaEmitTyOp( insts, block, AIR_INST_PTR_CAST, arr_ptr_ty, non_slice); } // Sema.zig:coerceTupleToSlicePtrs, Sema.zig:coerceExtra, // PerThread.zig:arrayType // Sema.coerceTupleToSlicePtrs (src/Sema.zig) for // the reached runtime subset, i.e. the `.slice` destination arm of // `Sema.coerceExtra` at src/Sema.zig. Upstream, in order: // // 1. `const tuple = try sema.analyzeLoad(block, .., ptr_tuple, ..)` // — the tuple pointer is runtime here, so analyzeLoad emits AIR // `load` of the tuple value; // 2. `array_ty = pt.arrayType(.{ .len = tuple_ty.structFieldCount, .sentinel // = slice_info.sentinel, .child = slice_info.child })`; // 3. `coerceTupleToArray(array_ty, tuple)`: per field // `sema.tupleField(..., i)` then // `sema.coerce(dest_elem_ty, elem_ref)`. `tupleField` returns // the field's stored comptime value with no AIR when the tuple type // carries one, else emits `agg_field_val` (the same decode as the // ZIR_INST_ELEM_VAL_IMM arm here). With any runtime // element `runtime_src` is non-null and the result is // `block.addAggregateInit(array_ty, element_refs)`; with // none it is the interned `pt.aggregateValue(...)` constant; // 4. `analyzeRef(array_inst, slice_info.flags.alignment)`: for a runtime // operand `*const [N]T` is interned first, then // the mutable `*[N]T`, then AIR alloc + store + ptr_cast; for a comptime // operand it is a `uav` pointer of the `*const [N]T` type; // 5. `coerceArrayPtrToSlice(slice_ty, ptr_array)` = AIR // `array_to_slice`, already `semaEmitArrayToSlice` here. // // Steps 3(runtime)+4(runtime) reproduce the ZIR_INST_ARRAY_INIT_REF arm's // existing aggregate_init + alloc/store/ptr_cast sequence and step 4's // comptime half reproduces its uav half; the `*const`/mutable pointer flag // words are the same IP_PTR_FLAGS_CONST_ONE / IP_PTR_FLAGS_MUT_ONE constants // the ZIR_INST_REF port uses for `analyzeRef` with `alignment == .none`. // Boundaries upstream would take differently are loud (see the aborts below), // so no unmodeled shape is silently accepted. static uint32_t semaEmitTuplePtrToSlice(Zcu* zcu, uint32_t ptr_air, uint32_t ptr_ty, uint32_t slice_ty, AirInstList* insts, U32List* block, U32List* air_extra) { InternPool* ip = &zcu->intern_pool; uint32_t tuple_ty = ipPtrChild(ip, ptr_ty); uint32_t toff = ip->item_datas[tuple_ty]; if (toff > ip->extra_len || ip->extra_len - toff < 1u) UNREACHABLE("tuple_ptr_to_slice: malformed tuple type payload"); uint32_t nfields = ip->extra[toff]; if ((ip->extra_len - toff - 1u) / 2u < nfields) UNREACHABLE("tuple_ptr_to_slice: malformed tuple type payload"); // `slice_info` (src/Sema.zig) = the slice's many-pointer words. uint32_t many = ip->item_datas[slice_ty]; if (many >= ip->items_len || ip->item_tags[many] != IP_TAG_TYPE_POINTER) UNREACHABLE("tuple_ptr_to_slice: malformed slice type"); uint32_t moff = ip->item_datas[many]; if (moff > ip->extra_len || ip->extra_len - moff < IP_TYPE_POINTER_LEN) UNREACHABLE("tuple_ptr_to_slice: malformed slice type"); uint32_t elem_ty = ip->extra[moff + IP_TYPE_POINTER_CHILD]; uint32_t sentinel = ip->extra[moff + IP_TYPE_POINTER_SENTINEL]; uint32_t slice_flags = ip->extra[moff + IP_TYPE_POINTER_FLAGS]; // "empty tuple to zero-length slice": `uavRef` of the empty // `[0:s]T` value, then the comptime coerceArrayPtrToSlice (no load). if (nfields == 0) { uint32_t empty_ty = ipGetArrayType(ip, 0, elem_ty, sentinel); if (!ensureLayoutResolved(zcu, empty_ty)) UNIMPLEMENTED("coerceTupleToSlicePtrs: tuple_ptr_to_slice - empty " "array layout"); return coerceArrayPtrToSlice(ip, ipGetPtrUav(ip, ipGetPtrType(ip, empty_ty, IP_PTR_FLAGS_CONST_ONE), ipGetAggregate(ip, empty_ty, NULL, 0)), slice_ty); } // "pointers to tuples can only coerce to constant pointers" — upstream // emits a compile error for a mutable slice destination (Sema.zig). if ((slice_flags & IP_PTR_FLAGS_IS_CONST) == 0u) COMPILE_ERROR_UPSTREAM( "tuple_ptr_to_slice: mutable slice destination"); if (sentinel != IP_INDEX_NONE) UNIMPLEMENTED("coerceTupleToSlicePtrs: tuple_ptr_to_slice - " "sentineled slice destination"); if (ip->extra[moff + IP_TYPE_POINTER_PACKED_OFFSET] != 0u) UNIMPLEMENTED("coerceTupleToSlicePtrs: tuple_ptr_to_slice - slice " "with packed offset"); // `analyzeRef` is called with `slice_info.flags.alignment` // (Sema.zig) and zig1 only has the `.none` alignment pointer-flag // constants below. if (IP_PTR_FLAGS_ALIGN(slice_flags) != IP_PTR_ALIGN_NONE) UNIMPLEMENTED("coerceTupleToSlicePtrs: tuple_ptr_to_slice - " "explicitly aligned slice"); // 1. analyzeLoad(ptr_tuple) — runtime pointer, so AIR `load`. AirInstData ld_data; memset(&ld_data, 0, sizeof(ld_data)); ld_data.pair.a = tuple_ty; // ty_op result type = the tuple ld_data.pair.b = ptr_air; uint32_t ld_idx = semaAirInstAppend(insts, AIR_INST_LOAD, ld_data); u32ListAppend(block, ld_idx); uint32_t tuple_air = AIR_REF_INST_BASE + ld_idx; // 2. the destination array type. uint32_t array_ty = ipGetArrayType(ip, nfields, elem_ty, sentinel); // 3. coerceTupleToArray: per-field tupleField + coerce. `runtime_src` is // upstream's "any element is runtime" flag. uint32_t* elem_refs = ARR_INIT(uint32_t, nfields); bool runtime_src = false; for (uint32_t i = 0; i < nfields; i++) { uint32_t field_ty = ip->extra[toff + 1 + i]; uint32_t field_val = ip->extra[toff + 1 + nfields + i]; if (field_val != IP_INDEX_NONE) { // Comptime tuple field: tupleField returns the stored value with // no AIR (Sema.zig); coerce it to the element type. elem_refs[i] = semaCoerceComptime(NULL, ip, field_val, elem_ty); continue; } // A `.none` stored value is not automatically runtime upstream: // tupleField (Sema.zig) goes through // Type.structFieldValueComptime, whose tuple arm falls back to // `types[i].onePossibleValue` (src/Type.zig) — an // OPV-typed field is comptime with no AIR. Following the elem_val // precedent (only void + zero-bit ints modeled as OPV), those shapes // abort loud rather than silently emitting an agg_field_val the interp // never exercises for zero-bit fields; other OPV shapes (empty struct, // single-tag enum) are a known gap. { uint32_t opv_bits; bool opv_signed; if (field_ty == ZIR_REF_VOID_TYPE || (ztIntInfo(ip, field_ty, &opv_bits, &opv_signed) && opv_bits == 0)) UNIMPLEMENTED( "onePossibleValue: tuple_ptr_to_slice: OPV-typed field is " "comptime upstream (onePossibleValue)"); } if (field_ty != elem_ty) UNIMPLEMENTED("coerceTupleToSlicePtrs: tuple_ptr_to_slice - " "runtime element needs a non-identity coercion"); uint32_t fv_payload = air_extra->len; u32ListAppend(air_extra, tuple_air); // StructField.struct_operand u32ListAppend(air_extra, i); // StructField.field_index AirInstData fv_data; memset(&fv_data, 0, sizeof(fv_data)); fv_data.pair.a = field_ty; // ty_pl result type fv_data.pair.b = fv_payload; uint32_t fv_idx = semaAirInstAppend(insts, AIR_INST_AGG_FIELD_VAL, fv_data); u32ListAppend(block, fv_idx); elem_refs[i] = AIR_REF_INST_BASE + fv_idx; runtime_src = true; } uint32_t const_ptr_ty = ipGetPtrType(ip, array_ty, IP_PTR_FLAGS_CONST_ONE); if (!runtime_src) { // All elements comptime: the interned `pt.aggregateValue` constant // (Sema.zig), analyzeRef's comptime `uav` pointer, and // coerceArrayPtrToSlice's comptime fold to a slice value, which is a // constant AIR operand. uint32_t agg = ipGetAggregate(ip, array_ty, elem_refs, nfields); free(elem_refs); return coerceArrayPtrToSlice( ip, ipGetPtrUav(ip, const_ptr_ty, agg), slice_ty); } // 3b/4. aggregate_init then analyzeRef's runtime half (alloc *[N]T // mutable, store, ptr_cast to *const [N]T) — the ZIR_INST_ARRAY_INIT_REF // sequence. uint32_t agg_ref = semaEmitAggregateInit( insts, block, air_extra, array_ty, elem_refs, nfields); free(elem_refs); uint32_t mut_ptr_ty = ipGetPtrType(ip, array_ty, IP_PTR_FLAGS_MUT_ONE); AirInstData alloc_data; memset(&alloc_data, 0, sizeof(alloc_data)); alloc_data.pair.a = mut_ptr_ty; uint32_t alloc_idx = semaAirInstAppend(insts, AIR_INST_ALLOC, alloc_data); u32ListAppend(block, alloc_idx); AirInstData store_data; memset(&store_data, 0, sizeof(store_data)); store_data.pair.a = AIR_REF_INST_BASE + alloc_idx; store_data.pair.b = agg_ref; uint32_t store_idx = semaAirInstAppend(insts, AIR_INST_STORE, store_data); u32ListAppend(block, store_idx); AirInstData cast_data; memset(&cast_data, 0, sizeof(cast_data)); cast_data.pair.a = const_ptr_ty; cast_data.pair.b = AIR_REF_INST_BASE + alloc_idx; uint32_t cast_idx = semaAirInstAppend(insts, AIR_INST_PTR_CAST, cast_data); u32ListAppend(block, cast_idx); // 5. coerceArrayPtrToSlice. return semaEmitArrayToSlice( AIR_REF_INST_BASE + cast_idx, slice_ty, insts, block); } static uint32_t semaEmitRuntimeInMemoryCoerce(InternPool* ip, uint32_t value_air, uint32_t value_ty, uint32_t dest_ty, AirInstList* insts, U32List* block) { uint32_t src_ptr_ty = ipGetPtrType(ip, value_ty, IP_PTR_FLAGS_MUT_ONE); uint32_t dest_ptr_ty = ipGetPtrType(ip, dest_ty, IP_PTR_FLAGS_MUT_ONE); AirInstData alloc_data; memset(&alloc_data, 0, sizeof(alloc_data)); alloc_data.pair.a = src_ptr_ty; uint32_t alloc_idx = semaAirInstAppend(insts, AIR_INST_ALLOC, alloc_data); u32ListAppend(block, alloc_idx); AirInstData store_data; memset(&store_data, 0, sizeof(store_data)); store_data.pair.a = AIR_REF_INST_BASE + alloc_idx; store_data.pair.b = value_air; uint32_t store_idx = semaAirInstAppend(insts, AIR_INST_STORE_SAFE, store_data); u32ListAppend(block, store_idx); AirInstData cast_data; memset(&cast_data, 0, sizeof(cast_data)); cast_data.pair.a = dest_ptr_ty; cast_data.pair.b = AIR_REF_INST_BASE + alloc_idx; uint32_t cast_idx = semaAirInstAppend(insts, AIR_INST_PTR_CAST, cast_data); u32ListAppend(block, cast_idx); AirInstData load_data; memset(&load_data, 0, sizeof(load_data)); load_data.pair.a = dest_ty; load_data.pair.b = AIR_REF_INST_BASE + cast_idx; uint32_t load_idx = semaAirInstAppend(insts, AIR_INST_LOAD, load_data); u32ListAppend(block, load_idx); return AIR_REF_INST_BASE + load_idx; } // `coerceExtra`'s "cast from *T and [*]T to *anyopaque" arm // (src/Sema.zig): "if (dest_info.child == .anyopaque_type and // inst_ty.zigTypeTag(zcu) == .pointer) to_anyopaque: { ... }" as a pure type // predicate -- the arm's whole gate, in upstream's order: // 1. `checkPtrAttributes(dest_ty, inst_ty, ...)` (the one const / volatile / // address-space / alignment / bit-range rule, checkPtrAttributes); // 2. "but don't do it if the source type is a double pointer", i.e. // `elem_ty.zigTypeTag(zcu) == .pointer or elem_ty.isPtrLikeOptional(zcu)` // -- upstream's `.pointer` tag covers a slice pointee too; // 3. `if (dest_ty.isSlice(zcu)) break :to_anyopaque;` -- a slice destination // leaves the arm and is answered by the size switch below, so it is not // this predicate; and `if (inst_ty.isSlice(zcu))` is upstream's // `slice_to_anyopaque` refusal. zig1 stores a slice under its own // `type_slice` tag, so both fall out of the two `type_pointer` tests. // `zcu` is nullable, as for `semaCheckPtrAttributesTy`: it reaches only // `ztAbiAlignLog2`, which declines an aggregate child rather than resolving // its layout. Declared in zig1.h and NON-static because sema_comptime.c's // Comptime coercer (ctCoerceToType) reaches the very same upstream arm and // must accept exactly the same type pairs as this runtime twin. static bool semaIsPtrToAnyopaque( Zcu* zcu, InternPool* ip, uint32_t src_ty, uint32_t dest_ty) { if (src_ty == IP_INDEX_NONE || dest_ty == IP_INDEX_NONE || src_ty == dest_ty || !ipItemTagIs(ip, src_ty, IP_TAG_TYPE_POINTER) || !ipItemTagIs(ip, dest_ty, IP_TAG_TYPE_POINTER)) return false; IpPtrInfo src_info, dest_info; if (ipPtrInfo(ip, src_ty, &src_info) != IP_PTRI_OK || ipPtrInfo(ip, dest_ty, &dest_info) != IP_PTRI_OK) return false; if (dest_info.child != ZIR_REF_ANYOPAQUE_TYPE) return false; if (!checkPtrAttributes(zcu, ip, &dest_info, &src_info)) return false; if (ztZigTypeTag(ip, src_info.child) == ZIG_TYPE_TAG_POINTER) return false; if (ztTypeIsPtrLikeOptional(ip, src_info.child)) return false; return true; } // (part of) Sema.zig:coerceExtra // `Sema.coerceExtra` (src/Sema.zig) for a runtime operand: the one // coercion every runtime site asks -- `storePtr2`'s operand store, the // `as_node` result location, an error-union payload wrap, an inferred alloc's // re-run store, and every call argument. Its arms are transcribed in // upstream's own order, and what it declines is the list of `coerceExtra` // arms zig1 does not port: the abort names the operand and destination item // tags. The comptime operand half is semaCoerceComptime / ctCoerceToType, // which is why this one takes an AIR ref and a block to emit into and they // do not; `coerce` picks between them. static uint32_t semaCoerceExtraRuntime(Zcu* zcu, uint32_t value_air, uint32_t value_ty, uint32_t dest_ty, AirInstList* insts, U32List* block, U32List* air_extra, const char* context) { InternPool* ip = &zcu->intern_pool; if (value_ty == dest_ty) return value_air; // `coerceExtra` opens with `coerceInMemoryAllowed` and lowers its // `.ptr_cast` strategy before any per-destination arm // (src/Sema.zig). if (coerceInMemoryAllowedPtrs(zcu, ip, dest_ty, value_ty, false)) return semaEmitTyOp( insts, block, AIR_INST_PTR_CAST, dest_ty, value_air); // `coerceExtra`'s `.pointer` destination arm, `single_item` block // (src/Sema.zig): `*T` -> `*[1]T` -- a `.one` destination whose // child is a length-1 array of the source pointee, gated by upstream's own // `checkPtrAttributes` and lowered by coerceCompatiblePtrs to a // runtime bitcast. Element match is identity, the modelled subset of // upstream's `coerceInMemoryAllowed`. if (semaSingleItemPtrCoercible(zcu, ip, dest_ty, value_ty)) return semaEmitTyOp( insts, block, AIR_INST_BIT_CAST, dest_ty, value_air); // Then the `.pointer` destination arm's `src_array_ptr` block // (src/Sema.zig). Its shared preamble decides the whole block; the // destination size alone picks the leg: `.slice` => coerceArrayPtrToSlice, // `.c` and `.many` => coerceCompatiblePtrs, `.one` unreachable (the // preamble excluded it). `coerceCompatiblePtrs` guards its // ptr_cast with a null-pointer safety check when the source may be zero // and the destination forbids it; that shape declines loudly rather than // emitting an unmodelled check. { IpPtrInfo sap_dest, sap_src; if (semaSrcArrayPtrCoercible( zcu, ip, dest_ty, value_ty, &sap_dest, &sap_src)) { if (sap_dest.size == IP_PTR_SIZE_SLICE) return semaEmitArrayToSlice(value_air, dest_ty, insts, block); if (ztPtrInfoAllowsZero(&sap_src) && !ztPtrInfoAllowsZero(&sap_dest)) UNIMPLEMENTED("coerceExtra: array-pointer coercion needs a " "null-pointer check"); return semaEmitTyOp( insts, block, AIR_INST_PTR_CAST, dest_ty, value_air); } } // The `.c` destination size's `.int` source: the address coerced to // `usize` or `isize`, then coerceCompatiblePtrs' `ptr_from_int` (an // `isize` bit-cast to `usize` first). { uint32_t bits; bool is_signed; if (ztIsCPtr(ip, dest_ty) && ztIntInfo(ip, value_ty, &bits, &is_signed)) { uint32_t addr = semaCoerceExtraRuntime(zcu, value_air, value_ty, is_signed ? ZIR_REF_ISIZE_TYPE : ZIR_REF_USIZE_TYPE, insts, block, air_extra, context); if (is_signed) addr = semaEmitTyOp( insts, block, AIR_INST_BIT_CAST, ZIR_REF_USIZE_TYPE, addr); return semaEmitTyOp( insts, block, AIR_INST_PTR_FROM_INT, dest_ty, addr); } } // `coerceInMemoryAllowed`'s "Error Sets" (src/Sema.zig) and "Error // Unions" arms, lowered by the in-memory head: a set // pair is `.error_cast`; an error-union pair needs an in-memory payload // and a coercible set, and is `.error_cast` for a `.same_type` payload, // else `.none` (alloc/store/ptr_cast/load). interp.c's error_cast is a // static re-type only (error values are dense codes). if (ipIsErrorSetType(ip, value_ty) && ipIsErrorSetType(ip, dest_ty) && coerceInMemoryAllowedErrorSets(ip, dest_ty, value_ty)) return semaEmitTyOp( insts, block, AIR_INST_ERROR_CAST, dest_ty, value_air); if (semaIsErrorUnionType(ip, value_ty) && semaIsErrorUnionType(ip, dest_ty) && coerceInMemoryAllowed(zcu, ip, dest_ty, value_ty, false)) return ipErrorUnionPayload(ip, value_ty) == ipErrorUnionPayload(ip, dest_ty) ? semaEmitTyOp( insts, block, AIR_INST_ERROR_CAST, dest_ty, value_air) : semaEmitRuntimeInMemoryCoerce( ip, value_air, value_ty, dest_ty, insts, block); // `coerceExtra`'s `.optional` destination arm (src/Sema.zig). The // in-memory head's "Optionals" line lowers `.none`; otherwise // "T to ?T" coerces to the child and wraps -- one recursion, so every arm // below serves a `?T` payload too. if (ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_OPTIONAL)) { if (coerceInMemoryAllowed(zcu, ip, dest_ty, value_ty, false)) return semaEmitRuntimeInMemoryCoerce( ip, value_air, value_ty, dest_ty, insts, block); // "cast from ?*T and ?[*]T to ?*anyopaque": the same predicate as // the `*anyopaque` arm over the optionals' pointer children, then // coerceCompatiblePtrs' ptr_cast for a ptr-like optional source (a // bare pointer source falls through to "T to ?T"). if (ztTypeIsPtrLikeOptional(ip, dest_ty) && ztTypeIsPtrLikeOptional(ip, value_ty) && semaIsPtrToAnyopaque(zcu, ip, ipOptionalChild(ip, value_ty), ipOptionalChild(ip, dest_ty))) return semaEmitTyOp( insts, block, AIR_INST_PTR_CAST, dest_ty, value_air); return semaEmitWrapOptional(semaCoerceExtraRuntime(zcu, value_air, value_ty, ipOptionalChild(ip, dest_ty), insts, block, air_extra, context), dest_ty, insts, block); } // `coerceExtra`'s `.@"enum"` destination arm, `.@"union"` source // (src/Sema.zig) -- the same `unionToTag` the `as_node` frame asks. { uint32_t ut = semaCoerceUnionToTag( zcu, value_air, value_ty, dest_ty, insts, block); if (ut != IP_INDEX_NONE) return ut; } // `coerceExtra`'s `.@"union"` destination arm, `.@"enum"` source: // coerceEnumToUnion's runtime half -- every field OPV, so the union is // its tag (union_from_enum), or OPV itself. if (ipItemTagIs(ip, dest_ty, IP_TAG_TYPE_UNION) && ipIsEnumType(ip, value_ty)) { if (ipUnionTagUsage(ip, dest_ty) != IP_UNION_TAG_USAGE_TAGGED) COMPILE_ERROR_UPSTREAM( "coerceEnumToUnion: cannot coerce enum to untagged union"); semaResolveAllUnionDeferred(zcu, dest_ty); uint32_t ue_tag_ty = ipEnsureGeneratedUnionTag(ip, dest_ty); uint32_t ue_tag = semaCoerceExtraRuntime(zcu, value_air, value_ty, ue_tag_ty, insts, block, air_extra, context); if (ztIsNonexhaustiveEnum(ip, ue_tag_ty)) COMPILE_ERROR_UPSTREAM("coerceEnumToUnion: runtime coercion to " "union from non-exhaustive enum"); uint32_t ue_n = ipUnionFieldsLen(ip, dest_ty); for (uint32_t i = 0; i < ue_n; i++) if (ztTypeClass(ip, semaResolveUnionFieldTypeLazy(zcu, dest_ty, i)) != ZT_CLASS_OPV) COMPILE_ERROR_UPSTREAM("coerceEnumToUnion: runtime coercion " "to union which has non-void fields"); uint32_t ue_opv = ztOnePossibleValue(ip, dest_ty); if (ue_opv != IP_INDEX_NONE) return ue_opv; return semaEmitTyOp( insts, block, AIR_INST_UNION_FROM_ENUM, dest_ty, ue_tag); } // `coerceExtra`'s `.error_union` destination arm (src/Sema.zig): // `wrapErrorUnionSet` for an error-set operand, else // `wrapErrorUnionPayload` -- coerce to the set or the payload, // then wrap. if (semaIsErrorUnionType(ip, dest_ty)) { bool is_set = ipIsErrorSetType(ip, value_ty); uint32_t part = semaCoerceExtraRuntime(zcu, value_air, value_ty, is_set ? ipErrorUnionErrorSet(ip, dest_ty) : ipErrorUnionPayload(ip, dest_ty), insts, block, air_extra, context); return semaEmitTyOp(insts, block, is_set ? AIR_INST_WRAP_ERRUNION_ERR : AIR_INST_WRAP_ERRUNION_PAYLOAD, dest_ty, part); } // `coerceExtra`'s "cast from *T and [*]T to *anyopaque" arm // (src/Sema.zig), which sits after `src_array_ptr` and before the // `switch (dest_info.flags.size)`. `semaIsPtrToAnyopaque` already states // it -- the attribute gate plus upstream's `double_ptr_to_anyopaque` // exclusion -- and the comptime coercer asks the same predicate. if (semaIsPtrToAnyopaque(zcu, ip, value_ty, dest_ty)) return semaEmitTyOp( insts, block, AIR_INST_PTR_CAST, dest_ty, value_air); // `coerceExtra`'s `.slice` destination arm, `to_slice` // (src/Sema.zig), whose reached leg is "pointer to tuple to slice" //: a single-item pointer whose child is a tuple goes through // `coerceTupleToSlicePtrs`. Upstream states this arm inside the same // `.pointer` destination block as `to_anyopaque` above and before the // `.array`/`.vector` destination arms below, so it sits here. // The empty-tuple and mutable-destination boundaries upstream enforces // live in `semaEmitTuplePtrToSlice`. if (semaIsRuntimeTuplePtrToSlice(ip, value_ty, dest_ty)) return semaEmitTuplePtrToSlice( zcu, value_air, value_ty, dest_ty, insts, block, air_extra); // `coerceExtra`'s `.many` destination arm (src/Sema.zig), a slice // source: "// [:x]T to [*:x]T" -- `checkPtrAttributes`, an in-memory // coercible child, and both sentinels present and equal, then // `analyzeSlicePtr` (AIR slice_ptr) and `coerceCompatiblePtrs` (ptr_cast; // its null check is safety-only). if (ztIsSlice(ip, value_ty) && ipItemTagIs(ip, dest_ty, IP_TAG_TYPE_POINTER)) { IpPtrInfo many_dest, many_src; if (ipPtrInfo(ip, dest_ty, &many_dest) == IP_PTRI_OK && many_dest.size == IP_PTR_SIZE_MANY && ztPtrOrSliceInfo(ip, value_ty, &many_src) && checkPtrAttributes(zcu, ip, &many_dest, &many_src) && coerceInMemoryAllowed( zcu, ip, many_dest.child, many_src.child, !many_dest.is_const) && many_dest.sentinel != IP_INDEX_NONE && many_src.sentinel != IP_INDEX_NONE && many_dest.sentinel == semaCoerceComptime( zcu, ip, many_src.sentinel, many_dest.child)) return semaEmitTyOp(insts, block, AIR_INST_PTR_CAST, dest_ty, analyzeSlicePtr(ip, value_air, value_ty, insts, block)); } // `coerceExtra`'s `.array` destination arm, tuple source // (src/Sema.zig -> coerceTupleToArray): `@as([N]T, t)` on a // runtime anonymous tuple. { uint32_t tuple_arr_ref; if (semaEmitCoerceTupleToArrayRuntime(ip, value_air, value_ty, dest_ty, insts, block, air_extra, &tuple_arr_ref)) return tuple_arr_ref; } // `coerceExtra`'s `.array` / `.vector` destination arms // (src/Sema.zig) -- the same `coerceArrayLike` the // `as_node` frame asks, so `@Vector(N,T)` -> `[N]T` is coerced here too // rather than bound as a raw vector. { uint32_t cal = coerceArrayLike( zcu, value_air, value_ty, dest_ty, insts, block, air_extra); if (cal != IP_INDEX_NONE) return cal; } { AirInstTag tag = semaScalarCoerceCastTag(ip, value_ty, dest_ty); if (tag != 0) return semaEmitTyOp(insts, block, tag, dest_ty, value_air); } // The `.float` destination's runtime `.int` source: `"const int_precision // = int_info.bits - @intFromBool(int_info.signedness == .signed); if // (int_precision <= dest_ty.ztFloatSignificandBits(target)) { ... return // block.addTyOp(.float_from_int, dest_ty, inst); }"`. { uint32_t int_bits, float_bits; bool int_signed; if (ztIntInfo(ip, value_ty, &int_bits, &int_signed) && ztSimpleFloatBits(dest_ty, &float_bits) && int_bits - (int_signed ? 1u : 0u) <= ztFloatSignificandBits(float_bits)) return semaEmitTyOp( insts, block, AIR_INST_FLOAT_FROM_INT, dest_ty, value_air); } // Name the operand and destination item tags before aborting, as the // `as_node` arm does for its own coercion. This is `coerceExtra`'s runtime // half, so what it declines is the list of `coerceExtra` arms zig1 does // not port. fprintf(stderr, "%s %s -> %s\n", context, value_ty < ip->items_len ? ipTagName((IpTag)ip->item_tags[value_ty]) : "", dest_ty < ip->items_len ? ipTagName((IpTag)ip->item_tags[dest_ty]) : ""); UNIMPLEMENTED(context); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // Sema.zig:coerce // `sema.coerce(block, dest_ty, inst, inst_src)`: coerceExtra's comptime half // (semaCoerceComptime) for an interned operand, its runtime half // (semaCoerceExtraRuntime) for an AIR ref of type `inst_ty`. static uint32_t coerce(Zcu* zcu, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t dest_ty, uint32_t inst, uint32_t inst_ty) { if (semaIsAirInstRef(inst)) return semaCoerceExtraRuntime( zcu, inst, inst_ty, dest_ty, insts, block, air_extra, "coerce:"); return semaCoerceComptime(zcu, &zcu->intern_pool, inst, dest_ty); } // `CallArgsInfo.analyzeArg`'s tail (src/Sema.zig), which is one statement // for every runtime argument at every call shape: // switch (param_ty.toIntern()) { // .generic_poison_type => return uncoerced_arg, // else => return sema.coerceExtra(block, param_ty, uncoerced_arg, // ...), // } // An `anytype` parameter has no destination type, so it binds uncoerced; every // other parameter type goes through `coerceExtra`'s runtime half. The direct // `call`, `field_call` and function-pointer binders differ only in the abort // context they name, so they share this and cannot drift apart. static uint32_t semaCallArgsInfoAnalyzeArg(Zcu* zcu, uint32_t uncoerced_arg, uint32_t arg_ty, uint32_t param_ty, AirInstList* insts, U32List* block, U32List* air_extra, const char* context) { if (param_ty == IP_INDEX_GENERIC_POISON_TYPE) return uncoerced_arg; return semaCoerceExtraRuntime(zcu, uncoerced_arg, arg_ty, param_ty, insts, block, air_extra, context); } // The comptime-known half of one `@shuffle` operand: its length (0 for an // untyped `undefined`, `"// If the type of `a` is `@TypeOf(undefined)` // ... this is 0"`) and the operand coerced to `@Vector(len, elem_ty)`. static uint32_t semaShuffleOperand(Zcu* zcu, uint32_t val, uint32_t ty, uint32_t elem_ty, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t* out_len) { InternPool* ip = &zcu->intern_pool; IpArrayInfo info; if (ty == ZIR_REF_UNDEFINED_TYPE) *out_len = 0; else if (ipArrayInfo(ip, ty, &info) == IP_ARR_OK) *out_len = info.len; else COMPILE_ERROR_UPSTREAM( "@shuffle: expected vector of the element type"); uint32_t vec_ty = ipGetVectorType(ip, *out_len, elem_ty); return coerce(zcu, insts, block, air_extra, vec_ty, val, ty); } // (part of) Sema.zig:zirShuffle // Sema.zirShuffle + analyzeShuffle: `mask` (comptime) is // coerced to `@Vector(len, i32)`, `a` and `b` to `@Vector(a_len / b_len, // elem_ty)`. Each mask element is ShuffleTwoMask's `aElem(i)` (`i << 1`), // `bElem(~raw)` (`i << 1 | 1`) or `.undef`; two runtime operands give AIR // `shuffle_two`, one gives `shuffle_one` (ShuffleOneMask: an index into it, // or bit 31 and a comptime value), none the comptime aggregate. static uint32_t analyzeShuffle(Zcu* zcu, uint32_t elem_ty, uint32_t a_raw, uint32_t a_raw_ty, uint32_t b_raw, uint32_t b_raw_ty, uint32_t mask_raw, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; checkVectorElemType(ip, elem_ty); IpArrayInfo mask_info; if (ipArrayInfo(ip, ipTypeOf(ip, mask_raw), &mask_info) != IP_ARR_OK) COMPILE_ERROR_UPSTREAM("@shuffle: expected vector or array mask"); uint32_t mask_len = mask_info.len; uint32_t mask = semaCoerceComptime( zcu, ip, mask_raw, ipGetVectorType(ip, mask_len, ZIR_REF_I32_TYPE)); uint32_t a_len, b_len; uint32_t a = semaShuffleOperand( zcu, a_raw, a_raw_ty, elem_ty, insts, block, air_extra, &a_len); uint32_t b = semaShuffleOperand( zcu, b_raw, b_raw_ty, elem_ty, insts, block, air_extra, &b_len); uint32_t result_ty = ipGetVectorType(ip, mask_len, elem_ty); *out_ty = result_ty; uint32_t* two = zig1Malloc((mask_len ? mask_len : 1u) * sizeof(uint32_t)); bool a_used = false, b_used = false; for (uint32_t i = 0; i < mask_len; i++) { uint32_t m = semaAggregateElemValueOrUndef( ip, mask, ZIR_REF_I32_TYPE, i, "@shuffle: mask element"); if (ipIsUndef(ip, m)) { two[i] = UINT32_MAX; // `.undef` continue; } int64_t raw = (int64_t)ipReadIntValue(ip, m); if (raw >= 0) { a_used = true; if ((uint64_t)raw >= a_len) COMPILE_ERROR_UPSTREAM( "@shuffle: mask element selects out-of-bounds index"); two[i] = (uint32_t)raw << AIR_SHUFFLE_TWO_MASK_INDEX_SHIFT; // aElem } else { uint32_t idx = (uint32_t)~raw; b_used = true; if (idx >= b_len) COMPILE_ERROR_UPSTREAM( "@shuffle: mask element selects out-of-bounds index"); two[i] = idx << AIR_SHUFFLE_TWO_MASK_INDEX_SHIFT | AIR_SHUFFLE_TWO_MASK_B_ELEM; } } bool a_rt = a_used && semaIsAirInstRef(a); bool b_rt = b_used && semaIsAirInstRef(b); uint32_t result; if (a_rt || b_rt) { uint32_t payload = air_extra->len; for (uint32_t i = 0; i < mask_len; i++) { uint32_t w = two[i]; if (!(a_rt && b_rt)) { // ShuffleTwoMask -> ShuffleOneMask: the runtime operand's // elements stay indices, the comptime one's become values. bool from_a = w != UINT32_MAX && (w & AIR_SHUFFLE_TWO_MASK_B_ELEM) == 0; uint32_t idx = w >> AIR_SHUFFLE_TWO_MASK_INDEX_SHIFT; if (w == UINT32_MAX) w = ipGetUndef(ip, elem_ty) | AIR_SHUFFLE_ONE_MASK_KIND; else if (from_a == a_rt) w = idx; else w = semaAggregateElemValueOrUndef(ip, (from_a ? a : b), elem_ty, idx, "@shuffle: comptime operand element") | AIR_SHUFFLE_ONE_MASK_KIND; } u32ListAppend(air_extra, w); } if (a_rt) u32ListAppend(air_extra, a); if (b_rt) u32ListAppend(air_extra, b); AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = result_ty; // ty_pl.ty data.pair.b = payload; // ty_pl.payload uint32_t idx = semaAirInstAppend(insts, a_rt && b_rt ? AIR_INST_SHUFFLE_TWO : AIR_INST_SHUFFLE_ONE, data); u32ListAppend(block, idx); result = AIR_REF_INST_BASE + idx; } else { uint32_t* elems = zig1Malloc((mask_len ? mask_len : 1u) * sizeof(uint32_t)); for (uint32_t i = 0; i < mask_len; i++) { uint32_t w = two[i]; elems[i] = w == UINT32_MAX ? ipGetUndef(ip, elem_ty) : semaAggregateElemValueOrUndef(ip, ((w & 1u) ? b : a), elem_ty, w >> 1, "@shuffle: operand element"); } result = ipGetAggregate(ip, result_ty, elems, mask_len); free(elems); } free(two); return result; } // (part of) Sema.zig:zirResolveInferredAlloc // Sema.zirResolveInferredAlloc's prong rewrite (src/Sema.zig): "Now we // need to go back over all the store instructions, and do the logic as if the // new result ptr type was available." Upstream re-runs `storePtr2` for one // recorded placeholder into a fresh `replacement_block`, splices a // single produced instruction over the placeholder, and otherwise replaces // the placeholder with an AIR `block` carrying the whole sequence. // zig1's `storePtr2` coercion is `semaCoerceExtraRuntime` for a runtime // operand and `coerceExtra` for a comptime one; `operand_ty` is the type // the store site recorded on the prong, upstream's `sema.typeOf(bin_op.rhs)`. static void semaInferredAllocRewriteProng(Zcu* zcu, AirInstList* insts, U32List* air_extra, uint32_t store_idx, uint32_t operand_ty, uint32_t elem_ty) { if (operand_ty == elem_ty) return; // storePtr2's coercion is the identity here uint32_t ptr_ref = insts->datas[store_idx].pair.a; uint32_t operand = insts->datas[store_idx].pair.b; U32List repl = { 0 }; // upstream's `replacement_block.instructions` uint32_t new_operand = coerce(zcu, insts, &repl, air_extra, elem_ty, operand, operand_ty); if (repl.len == 0) { // The coercion emitted nothing, so the replacement body is the store // alone: upstream's "If only one instruction is produced then we can // replace the store placeholder instruction with this instruction", // which here is the placeholder itself with its new operand. free(repl.items); insts->datas[store_idx].pair.b = new_operand; return; } AirInstData store_data; memset(&store_data, 0, sizeof(store_data)); store_data.pair.a = ptr_ref; // bin_op.lhs = ptr Ref store_data.pair.b = new_operand; // bin_op.rhs = coerced val Ref u32ListAppend(&repl, semaAirInstAppend(insts, AIR_INST_STORE, store_data)); // "_ = try replacement_block.addBr(placeholder_inst, .void_value);" AirInstData br_data; memset(&br_data, 0, sizeof(br_data)); br_data.pair.a = store_idx; br_data.pair.b = ZIR_REF_VOID_VALUE; u32ListAppend(&repl, semaAirInstAppend(insts, AIR_INST_BR, br_data)); uint32_t payload = air_extra->len; u32ListAppend(air_extra, repl.len); // Air.Block.body_len for (uint32_t i = 0; i < repl.len; i++) u32ListAppend(air_extra, repl.items[i]); free(repl.items); insts->tags[store_idx] = AIR_INST_BLOCK; memset(&insts->datas[store_idx], 0, sizeof(insts->datas[store_idx])); insts->datas[store_idx].pair.a = ZIR_REF_VOID_TYPE; insts->datas[store_idx].pair.b = payload; } static void semaEmitInferredAllocPlaceholder(AirInstList* insts, U32List* block, uint32_t* inst_values, uint32_t* inst_types, uint32_t inst, uint32_t ptr_flags) { AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = 0; // alloc .ty — patched by resolve_inferred_alloc data.pair.b = ptr_flags; // .is_const, .alignment uint32_t air_idx = semaAirInstAppend(insts, AIR_INST_ALLOC, data); u32ListAppend(block, air_idx); inst_values[inst] = AIR_REF_INST_BASE + air_idx; inst_types[inst] = UINT32_MAX; } // `sema.coerce(.usize)` of a comptime index or slice bound: its magnitude // interned as a usize. static uint32_t semaCoerceComptimeSliceBoundToUsize( InternPool* ip, uint32_t val, const char* ctx) { uint64_t mag; if (val == ZIR_REF_ZERO || val == ZIR_REF_ZERO_USIZE) mag = 0; else if (val == ZIR_REF_ONE || val == ZIR_REF_ONE_USIZE) mag = 1; else { uint32_t idx = val; if (idx >= ip->items_len || !ipTryReadIntValue(ip, idx, &mag)) UNIMPLEMENTED(ctx); } if (mag > UINT32_MAX) UNIMPLEMENTED(ctx); return ipGetIntUsize(ip, (uint32_t)mag); } // `sema.coerce(block, .usize, bound, ...)` for an operand whose // `sema.typeOf` is `ty` (read only for a runtime operand). static uint32_t semaCoerceSliceBoundValue(InternPool* ip, uint32_t val, uint32_t ty, AirInstList* insts, U32List* block) { if (semaIsAirInstRef(val)) { if (ty == ZIR_REF_USIZE_TYPE) return val; if (ty == IP_INDEX_NONE || ty >= ip->items_len) UNIMPLEMENTED("slice bound: unresolved runtime operand type"); // Mirror Sema.coerce(.usize, ...)'s integer arm: widening is only // valid when every source value is representable in the (unsigned, // 32-bit on this wasm32 model) destination. A signed or // wider-than-usize source is a compile error upstream ("cannot // represent all possible values"), so it must stay loud here, not // silently int_cast. uint32_t bnd_bits; bool bnd_signed; if (!ztIntInfo(ip, ty, &bnd_bits, &bnd_signed)) UNIMPLEMENTED("slice bound: non-int runtime value"); if (bnd_signed || bnd_bits > 32) UNIMPLEMENTED("slice bound: int not representable in usize"); AirInstData ic; memset(&ic, 0, sizeof(ic)); ic.pair.a = ZIR_REF_USIZE_TYPE; ic.pair.b = (uint32_t)val; uint32_t ic_idx = semaAirInstAppend(insts, AIR_INST_INT_CAST, ic); u32ListAppend(block, ic_idx); return AIR_REF_INST_BASE + ic_idx; } return semaCoerceComptimeSliceBoundToUsize( ip, val, "slice bound: comptime value out of usize range"); } static uint32_t semaCoerceSliceBoundToUsize(InternPool* ip, uint32_t ref, uint32_t val, const uint32_t* inst_types, AirInstList* insts, U32List* block) { return semaCoerceSliceBoundValue(ip, val, semaIsAirInstRef(val) ? semaResolveRefType(ref, inst_types) : IP_INDEX_NONE, insts, block); } // A comptime-known usize slice bound (`resolveDefinedValue(...)` then // `toUnsignedInt`): false for a runtime operand. static bool semaSliceBoundComptime( const InternPool* ip, uint32_t val, uint64_t* out) { return val < ip->items_len && !ipIsUndef(ip, val) && ipTryReadIntValue(ip, val, out); } // Sema.zig:analyzeLoad // Sema.analyzeLoad (src/Sema.zig, "fn analyzeLoad("): the pointee's one // possible value, a comptime pointer's pointerDeref (a comptime slice's // maybeDerefSliceAsArray, its array), else an AIR `load`. static uint32_t analyzeLoad(Zcu* zcu, AirInstList* insts, U32List* block, uint32_t ptr, uint32_t ptr_ty, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; IpPtrInfo info; bool is_slice = ztIsSlice(ip, ptr_ty); // Valid code loads only pointers; the one non-pointer zig1 hands here is // fieldPtr's answer for a field of a comptime @typeInfo value, its value. if (ipPtrInfo(ip, is_slice ? ip->item_datas[ptr_ty] : ptr_ty, &info) != IP_PTRI_OK) UNIMPLEMENTED("fieldPtr: a field pointer into a comptime @typeInfo " "value (zig1 answers the field's value)"); *out_ty = info.child; // `"try sema.ensureLayoutResolved(elem_ty, src, .ptr_access);"` ensureLayoutResolved(zcu, info.child); uint32_t opv = ztOnePossibleValue(ip, info.child); if (opv != IP_INDEX_NONE) return opv; if (!semaIsAirInstRef(ptr)) { uint32_t loaded = is_slice ? maybeDerefSliceAsArray(zcu, ptr) : pointerDeref(zcu, ptr, NULL); if (loaded != IP_INDEX_NONE && is_slice) *out_ty = ipTypeOf(ip, loaded); if (loaded != IP_INDEX_NONE) return loaded; // pointerDeref's `.runtime_load`: into a mutable Nav (validated as // runtime storage) or at an integer address. uint32_t root = ipPtrDerivedRoot(ip, ptr); if (!ipIsPtrInt(ip, root)) { if (!ipItemTagIs(ip, root, IP_TAG_PTR_NAV)) UNIMPLEMENTED( "analyzeLoad: runtime load of a comptime pointer"); semaRuntimeDataPtrNav(ip, ptr, false, NULL, NULL); } } if (is_slice) COMPILE_ERROR_UPSTREAM("analyzeLoad: index syntax required to access " "runtime-known slice"); return semaEmitTyOp(insts, block, AIR_INST_LOAD, info.child, ptr); } // Sema.zig:coerceCompatiblePtrs // Sema.coerceCompatiblePtrs (src/Sema.zig, "fn coerceCompatiblePtrs("): a // comptime pointer is `getCoerced`, a runtime one `ptr_cast` (ReleaseSmall // adds no null check). static uint32_t coerceCompatiblePtrs(Zcu* zcu, AirInstList* insts, U32List* block, uint32_t dest_ty, uint32_t ptr) { if (semaIsAirInstRef(ptr)) return semaEmitTyOp(insts, block, AIR_INST_PTR_CAST, dest_ty, ptr); uint32_t coerced = ipRetypePtrValue(&zcu->intern_pool, ptr, dest_ty); if (coerced == IP_INDEX_NONE) UNIMPLEMENTED("coerceCompatiblePtrs: comptime pointer carrier"); return coerced; } // Sema.zig:analyzePtrArithmetic // Sema.analyzePtrArithmetic (src/Sema.zig, "fn analyzePtrArithmetic("), // `air_tag` AIR_INST_PTR_ADD or AIR_INST_PTR_SUB, with `offset` already a // usize. `*out_ty` is `sema.typeOf` of the result. static uint32_t analyzePtrArithmetic(Zcu* zcu, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t ptr, uint32_t ptr_ty, uint32_t offset, AirInstTag air_tag, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; IpPtrInfo info; if (ipPtrInfo(ip, ptr_ty, &info) != IP_PTRI_OK || (info.size != IP_PTR_SIZE_MANY && info.size != IP_PTR_SIZE_C)) UNIMPLEMENTED("analyzePtrArithmetic: not a many or C pointer"); uint64_t index = 0; bool known = semaSliceBoundComptime(ip, offset, &index); if (known && index > UINT32_MAX) UNIMPLEMENTED("analyzePtrArithmetic: wide comptime offset"); // "switch (elem_ty.classify(zcu)) { .no_possible_value, // .one_possible_value => return ptr," ensureLayoutResolved(zcu, info.child); ZtTypeClass elem_class = ztTypeClass(ip, info.child); *out_ty = ptr_ty; if (elem_class == ZT_CLASS_NPV || elem_class == ZT_CLASS_OPV) return ptr; uint32_t elem_ptr_ty = known ? ztElemPtrType(zcu, ptr_ty, (uint32_t)index, NULL) : ztElemPtrTypeRuntimeIndex(zcu, ptr_ty, NULL); // "info.flags.size = ptr_info.flags.size; info.sentinel = // ptr_info.sentinel;" IpPtrInfo elem_info; if (ipPtrInfo(ip, elem_ptr_ty, &elem_info) != IP_PTRI_OK) UNIMPLEMENTED("elemPtr: element pointer type is not a pointer"); uint32_t new_ptr_ty = ipGetPtrTypeFull(ip, elem_info.child, (elem_info.flags & ~IP_PTR_FLAGS_SIZE_MASK) | info.size, info.sentinel, elem_info.packed_offset); if (!semaIsAirInstRef(ptr)) { if (ipIsUndef(ip, ptr)) { *out_ty = new_ptr_ty; return ipGetUndef(ip, new_ptr_ty); } if (known) { if (index == 0) return ptr; uint32_t moved; if (air_tag == AIR_INST_PTR_SUB) { // Sema.ptrSubtract: the same base address at // `byte_offset - index * elem_size`. uint64_t elem_size, elem_align, elem_bits, byte_offset; if (!ztLayoutInfo(zcu, ip, info.child, &elem_size, &elem_align, &elem_bits) || !ipPtrValueByteOffset(ip, ptr, &byte_offset)) UNIMPLEMENTED( "analyzePtrArithmetic: comptime pointer carrier"); if (byte_offset < index * elem_size) COMPILE_ERROR_UPSTREAM("pointer computation here causes " "illegal behavior"); moved = ipRemintPtrValue( ip, ptr, new_ptr_ty, byte_offset - index * elem_size); } else { moved = ipRetypePtrValue(ip, valPtrElem(zcu, ip, ptr, elem_ptr_ty, info.child, (uint32_t)index), new_ptr_ty); } if (moved == IP_INDEX_NONE) UNIMPLEMENTED( "analyzePtrArithmetic: comptime pointer carrier"); *out_ty = new_ptr_ty; return moved; } } *out_ty = new_ptr_ty; return semaEmitTyPlBin( insts, block, air_extra, air_tag, new_ptr_ty, ptr, offset); } // The element `ptr - ptr` counts in: a one-pointer to an array counts that // array's elements, any other pointer its child. static uint32_t semaPtrSubtractElemTy( const InternPool* ip, const IpPtrInfo* info) { IpArrayInfo arr; if (info->size == IP_PTR_SIZE_ONE && ipArrayInfo(ip, info->child, &arr) == IP_ARR_OK && !arr.is_vector) return arr.child; return info->child; } // Sema.zig:analyzeArithmetic // Sema.analyzeArithmetic's pointer operands (src/Sema.zig, "fn // analyzeArithmetic("): `ptr - ptr` of two non-slice pointers, and a many / // C pointer `+` / `-` an integer (analyzePtrArithmetic). False when `lhs` is // no such pointer, leaving the integer arithmetic to the caller. static bool semaAnalyzeArithmeticPtr(Zcu* zcu, AirInstList* insts, U32List* block, U32List* air_extra, bool is_sub, uint32_t lhs, uint32_t lhs_ty, uint32_t rhs, uint32_t rhs_ty, uint32_t* out_val, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; IpPtrInfo li, ri; if (lhs_ty >= ip->items_len || ipPtrInfo(ip, lhs_ty, &li) != IP_PTRI_OK) return false; if (rhs_ty < ip->items_len && ipPtrInfo(ip, rhs_ty, &ri) == IP_PTRI_OK) { if (!is_sub) COMPILE_ERROR_UPSTREAM( "invalid pointer-pointer arithmetic operator"); uint32_t elem_ty = semaPtrSubtractElemTy(ip, &li); if (elem_ty != semaPtrSubtractElemTy(ip, &ri)) COMPILE_ERROR_UPSTREAM("incompatible pointer arithmetic operands"); ensureLayoutResolved(zcu, elem_ty); uint64_t elem_size, elem_align, elem_bits; if (!ztLayoutInfo( zcu, ip, elem_ty, &elem_size, &elem_align, &elem_bits)) UNIMPLEMENTED("analyzeArithmetic: pointer subtraction - element " "layout unresolved"); if (elem_size == 0) COMPILE_ERROR_UPSTREAM("pointer subtraction requires element " "type to have runtime bits"); *out_ty = ZIR_REF_USIZE_TYPE; uint64_t loff, roff; if (semaRefIsInterned(lhs) && semaRefIsInterned(rhs)) { if (ipIsUndef(ip, lhs) || ipIsUndef(ip, rhs)) COMPILE_ERROR_UPSTREAM("use of undefined value"); // "if (!lhs_ptr.base_addr.eql(rhs_ptr.base_addr)) break // :runtime_src src;" if (ipPtrValueByteOffset(ip, lhs, &loff) && ipPtrValueByteOffset(ip, rhs, &roff) && ipPtrBaseAddrEql(ip, lhs, rhs)) { if (loff < roff || (loff - roff) / elem_size > UINT32_MAX) COMPILE_ERROR_UPSTREAM("operation results in overflow"); *out_val = ipGetIntUsize(ip, (uint32_t)((loff - roff) / elem_size)); return true; } } uint32_t lhs_int = semaEmitTyOp( insts, block, AIR_INST_INT_FROM_PTR, ZIR_REF_USIZE_TYPE, lhs); uint32_t rhs_int = semaEmitTyOp( insts, block, AIR_INST_INT_FROM_PTR, ZIR_REF_USIZE_TYPE, rhs); uint32_t address = semaRuntimeBinOp( insts, block, AIR_INST_SUB_WRAP, lhs_int, rhs_int); if (elem_size > UINT32_MAX) UNIMPLEMENTED( "analyzeArithmetic: pointer subtraction - wide element size"); *out_val = semaRuntimeBinOp(insts, block, AIR_INST_DIV_EXACT, address, ipGetIntUsize(ip, (uint32_t)elem_size)); return true; } if (li.size != IP_PTR_SIZE_MANY && li.size != IP_PTR_SIZE_C) return false; ensureLayoutResolved(zcu, li.child); *out_val = analyzePtrArithmetic(zcu, insts, block, air_extra, lhs, lhs_ty, semaCoerceSliceBoundValue(ip, rhs, rhs_ty, insts, block), is_sub ? AIR_INST_PTR_SUB : AIR_INST_PTR_ADD, out_ty); return true; } // (part of) Sema.zig:analyzeArithmetic // Sema.analyzeArithmetic's `.add` / `.sub` on two usize operands, safety off: // both comptime-known fold, an undefined one is a compile error, anything // else is the AIR bin op. static uint32_t semaUsizeArith(InternPool* ip, AirInstList* insts, U32List* block, AirInstTag tag, uint32_t lhs, uint32_t rhs) { uint64_t l, r; if (semaSliceBoundComptime(ip, lhs, &l) && semaSliceBoundComptime(ip, rhs, &r)) { if (tag == AIR_INST_SUB ? r > l : l + r > UINT32_MAX) COMPILE_ERROR_UPSTREAM("overflow of integer type 'usize'"); return ipGetIntUsize( ip, (uint32_t)(tag == AIR_INST_SUB ? l - r : l + r)); } if ((!semaIsAirInstRef(lhs) && ipIsUndef(ip, lhs)) || (!semaIsAirInstRef(rhs) && ipIsUndef(ip, rhs))) COMPILE_ERROR_UPSTREAM("use of undefined value"); return semaRuntimeBinOp(insts, block, tag, lhs, rhs); } // analyzeSlice's `end` for a `by_length` slice: `start + coerce(len)`. static uint32_t semaSliceEndBound(InternPool* ip, AirInstList* insts, U32List* block, uint32_t start, uint32_t uncasted_end, uint32_t end_ty, bool by_length) { uint32_t end = semaCoerceSliceBoundValue(ip, uncasted_end, end_ty, insts, block); return by_length ? semaUsizeArith(ip, insts, block, AIR_INST_ADD, start, end) : end; } // Sema.zig:analyzeSlice // Sema.analyzeSlice (src/Sema.zig, "fn analyzeSlice("), ReleaseSmall: no // safety checks. `uncasted_end` / `sentinel_opt` are INST_VALUE_UNSET for // `.none`. Every compile error upstream reports stays one here, except that // checkSentinelType is not ported. static uint32_t analyzeSlice(Zcu* zcu, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t ptr_ptr, uint32_t ptr_ptr_ty, uint32_t uncasted_start, uint32_t start_ty, uint32_t uncasted_end, uint32_t end_ty, uint32_t sentinel_opt, bool by_length, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; IpPtrInfo ppi; if (ptr_ptr_ty == IP_INDEX_NONE || ptr_ptr_ty >= ip->items_len || ipPtrInfo(ip, ptr_ptr_ty, &ppi) != IP_PTRI_OK) UNIMPLEMENTED("analyzeSlice: operand is not a pointer"); uint32_t ptr_ptr_child_ty = ppi.child; uint32_t array_ty = ptr_ptr_child_ty; uint32_t slice_ty = ptr_ptr_ty; uint32_t elem_ty; uint32_t ptr_sentinel = IP_INDEX_NONE; bool end_none = uncasted_end == INST_VALUE_UNSET; IpArrayInfo arr; IpPtrInfo child_info; if (ipArrayInfo(ip, ptr_ptr_child_ty, &arr) == IP_ARR_OK && !arr.is_vector) { ptr_sentinel = arr.sentinel; elem_ty = arr.child; } else if (ztIsSlice(ip, ptr_ptr_child_ty)) { uint32_t loaded_ty; ptr_ptr = analyzeLoad(zcu, insts, block, ptr_ptr, ptr_ptr_ty, &loaded_ty); IpPtrInfo many_info; if (ipPtrInfo(ip, ip->item_datas[ptr_ptr_child_ty], &many_info) != IP_PTRI_OK) UNIMPLEMENTED("analyzeSlice: slice type has no many pointer"); ptr_sentinel = many_info.sentinel; slice_ty = ptr_ptr_child_ty; elem_ty = many_info.child; } else if (ipPtrInfo(ip, ptr_ptr_child_ty, &child_info) == IP_PTRI_OK) { uint32_t loaded_ty; ptr_ptr = analyzeLoad(zcu, insts, block, ptr_ptr, ptr_ptr_ty, &loaded_ty); slice_ty = ptr_ptr_child_ty; if (child_info.size == IP_PTR_SIZE_ONE) { uint32_t double_child_ty = child_info.child; if (ipArrayInfo(ip, double_child_ty, &arr) == IP_ARR_OK && !arr.is_vector) { ptr_sentinel = arr.sentinel; array_ty = double_child_ty; elem_ty = arr.child; } else { // "slice of single-item pointer must be bounded" and the // [0..0] / [0..1] / [1..1] bounds rule. uint64_t s, e; if (end_none) COMPILE_ERROR_UPSTREAM( "slice of single-item pointer must be bounded"); if (!semaSliceBoundComptime(ip, uncasted_start, &s) || !semaSliceBoundComptime(ip, uncasted_end, &e)) COMPILE_ERROR_UPSTREAM("slice of single-item pointer " "bounds must be comptime-known"); if (s != e ? s != 0 || e != 1 : e > 1) COMPILE_ERROR_UPSTREAM("slice of single-item pointer " "must have bounds [0..0], [0..1], " "or [1..1]"); array_ty = ipGetArrayType(ip, 1, double_child_ty, IP_INDEX_NONE); slice_ty = ipGetPtrType(ip, array_ty, IP_PTR_FLAGS_WITH_VECTOR_INDEX( child_info.flags & ~IP_PTR_FLAGS_SIZE_MASK, IP_PTR_VECTOR_INDEX_NONE)); elem_ty = double_child_ty; } } else { // `.many, .c` (`.slice` is the IP_TAG_TYPE_SLICE arm above). ptr_sentinel = child_info.sentinel; elem_ty = child_info.child; } } else { COMPILE_ERROR_UPSTREAM("slice of non-array type"); } ensureLayoutResolved(zcu, elem_ty); bool array_is_array = ipArrayInfo(ip, array_ty, &arr) == IP_ARR_OK && !arr.is_vector; bool slice_is_slice = ztIsSlice(ip, slice_ty); uint32_t ptr; uint32_t ptr_ty; if (slice_is_slice) { // analyzeSlicePtr. ptr_ty = ip->item_datas[slice_ty]; if (!semaIsAirInstRef(ptr_ptr) && ipIsUndef(ip, ptr_ptr)) ptr = ipGetUndef(ip, ptr_ty); else ptr = analyzeSlicePtr(ip, ptr_ptr, slice_ty, insts, block); } else if (array_is_array) { // `manyptr_ty_key`: the `.one` array pointer's key with the element // as child and `.many` size. IpPtrInfo sl; if (ipPtrInfo(ip, slice_ty, &sl) != IP_PTRI_OK) UNIMPLEMENTED("analyzeSlice: array operand type is not a pointer"); ptr_ty = ipGetPtrTypeFull(ip, elem_ty, (sl.flags & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_MANY, sl.sentinel, sl.packed_offset); ptr = coerceCompatiblePtrs(zcu, insts, block, ptr_ty, ptr_ptr); } else { ptr = ptr_ptr; ptr_ty = slice_ty; } uint32_t start = semaCoerceSliceBoundValue( ip, uncasted_start, start_ty, insts, block); uint32_t new_ptr_ty; uint32_t new_ptr = analyzePtrArithmetic(zcu, insts, block, air_extra, ptr, ptr_ty, start, AIR_INST_PTR_ADD, &new_ptr_ty); bool end_is_len = end_none; uint32_t end; uint64_t end_int; if (array_is_array) { uint32_t len_val = ipGetIntUsize(ip, arr.len); if (!end_is_len) { end = semaSliceEndBound( ip, insts, block, start, uncasted_end, end_ty, by_length); if (semaSliceBoundComptime(ip, end, &end_int)) { // `arrayLenIncludingSentinel`, interned as upstream does. ipGetIntUsize(ip, arr.storage_len); if (end_int > arr.storage_len) COMPILE_ERROR_UPSTREAM( "end index out of bounds for array"); if (end_int == arr.len) end_is_len = true; } } else { end = len_val; } } else if (slice_is_slice) { if (!end_is_len) { end = semaSliceEndBound( ip, insts, block, start, uncasted_end, end_ty, by_length); if (semaSliceBoundComptime(ip, end, &end_int) && !semaIsAirInstRef(ptr_ptr)) { if (ipIsUndef(ip, ptr_ptr)) COMPILE_ERROR_UPSTREAM("slice of undefined"); uint64_t slice_len = ipReadIntValue(ip, ipPtrSliceLen(ip, ptr_ptr)); if (slice_len > UINT32_MAX - 1u) UNIMPLEMENTED("analyzeSlice: wide comptime slice length"); ipGetIntUsize(ip, (uint32_t)slice_len + (ptr_sentinel != IP_INDEX_NONE ? 1u : 0u)); if (end_int > slice_len + (ptr_sentinel != IP_INDEX_NONE ? 1u : 0u)) COMPILE_ERROR_UPSTREAM( "end index out of bounds for slice"); ipGetIntUsize(ip, (uint32_t)slice_len); if (end_int == slice_len) end_is_len = true; } } else if (!semaIsAirInstRef(ptr_ptr)) { // analyzeSliceLen. end = ipIsUndef(ip, ptr_ptr) ? ZIR_REF_UNDEF_USIZE : ipPtrSliceLen(ip, ptr_ptr); } else { end = semaEmitTyOp( insts, block, AIR_INST_SLICE_LEN, ZIR_REF_USIZE_TYPE, ptr_ptr); } } else if (!end_is_len) { end = semaSliceEndBound( ip, insts, block, start, uncasted_end, end_ty, by_length); } else { // A many / C pointer sliced to its (unknown) end: a provided sentinel // must match the pointer's, and the result is the pointer advanced // by `start`. if (sentinel_opt != INST_VALUE_UNSET) { if (semaIsAirInstRef(sentinel_opt)) COMPILE_ERROR_UPSTREAM( "slice sentinel must be comptime-known"); uint32_t provided = semaCoerceComptime(zcu, ip, sentinel_opt, elem_ty); if (ipIsUndef(ip, provided)) COMPILE_ERROR_UPSTREAM("use of undefined slice sentinel"); if (provided != ptr_sentinel) COMPILE_ERROR_UPSTREAM("sentinel-terminated slicing of " "many-item pointer must match " "existing sentinel"); } return analyzePtrArithmetic(zcu, insts, block, air_extra, ptr, ptr_ty, start, AIR_INST_PTR_ADD, out_ty); } uint32_t sentinel = IP_INDEX_NONE; if (sentinel_opt != INST_VALUE_UNSET) { if (semaIsAirInstRef(sentinel_opt)) COMPILE_ERROR_UPSTREAM("slice sentinel must be comptime-known"); sentinel = semaCoerceComptime(zcu, ip, sentinel_opt, elem_ty); if (ipIsUndef(ip, sentinel)) COMPILE_ERROR_UPSTREAM("use of undefined slice sentinel"); } else if (end_is_len) { sentinel = ptr_sentinel; } // requirement: start <= end, and a comptime pointer's memory holds the // sentinel. uint64_t start_int; if (semaSliceBoundComptime(ip, start, &start_int) && semaSliceBoundComptime(ip, end, &end_int)) { if (start_int != 0 && !by_length && start_int > end_int) COMPILE_ERROR_UPSTREAM("start index is larger than end index"); if (!semaIsAirInstRef(new_ptr) && sentinel != IP_INDEX_NONE) { if (end_int - start_int > UINT32_MAX) UNIMPLEMENTED("analyzeSlice: wide sentinel index"); uint32_t sentinel_index = (uint32_t)(end_int - start_int); uint32_t many_ptr_ty = ipGetPtrType(ip, elem_ty, IP_PTR_FLAGS_CONST_MANY); uint32_t many_ptr_val = ipRetypePtrValue(ip, new_ptr, many_ptr_ty); if (many_ptr_val == IP_INDEX_NONE) UNIMPLEMENTED("analyzeSlice: comptime pointer carrier"); uint32_t elem_ptr = valPtrElem(zcu, ip, many_ptr_val, ztElemPtrType(zcu, many_ptr_ty, sentinel_index, NULL), elem_ty, sentinel_index); // pointerDerefExtra's `.val` arm. Its `.runtime_load` skips the // check; its compile errors are accepted (liberal): zig1's // comptime loads do not yet see every byte upstream does. ComptimeLoadResult res = loadComptimePtr(zcu, elem_ptr); if (res.tag == ZPT_LOAD_SUCCESS && mutableValueIntern(zcu, &res.success) != sentinel) COMPILE_ERROR_UPSTREAM( "value in memory does not match slice sentinel"); } } uint32_t new_len = by_length ? semaCoerceSliceBoundValue(ip, uncasted_end, end_ty, insts, block) : semaUsizeArith(ip, insts, block, AIR_INST_SUB, end, start); uint64_t new_len_int; IpPtrInfo np; if (ipPtrInfo(ip, new_ptr_ty, &np) != IP_PTRI_OK) UNIMPLEMENTED("analyzeSlice: result pointer type is not a pointer"); IpPtrInfo base; bool new_allowzero = np.is_allowzero && !(ipPtrInfo(ip, ptr_ty, &base) == IP_PTRI_OK && base.size == IP_PTR_SIZE_C); // `alignment`, `is_const`, `is_allowzero`, `is_volatile` and // `address_space` of `new_ptr`; everything else default. uint32_t flags = IP_PTR_FLAGS_WITH_VECTOR_INDEX( np.flags & ~(IP_PTR_FLAGS_SIZE_MASK | IP_PTR_FLAGS_IS_ALLOWZERO), IP_PTR_VECTOR_INDEX_NONE); if (new_allowzero) flags |= IP_PTR_FLAGS_IS_ALLOWZERO; if (semaSliceBoundComptime(ip, new_len, &new_len_int)) { if (new_len_int > UINT32_MAX) UNIMPLEMENTED("analyzeSlice: wide comptime length"); uint32_t return_ty = ipGetPtrType(ip, ipGetArrayType(ip, (uint32_t)new_len_int, elem_ty, sentinel), flags | IP_PTR_SIZE_ONE); *out_ty = return_ty; if (semaIsAirInstRef(new_ptr)) return semaEmitTyOp( insts, block, AIR_INST_PTR_CAST, return_ty, new_ptr); if (!ipIsUndef(ip, new_ptr)) { uint32_t result = ipRetypePtrValue(ip, new_ptr, return_ty); if (result == IP_INDEX_NONE) UNIMPLEMENTED("analyzeSlice: comptime pointer carrier"); return result; } if (new_len_int == 0) return ipGetUndef(ip, return_ty); COMPILE_ERROR_UPSTREAM("non-zero length slice of undefined pointer"); } uint32_t return_ty = ipGetSliceType(ip, ipGetPtrTypeSentinel(ip, elem_ty, flags | IP_PTR_SIZE_MANY, sentinel)); *out_ty = return_ty; return semaEmitTyPlBin( insts, block, air_extra, AIR_INST_SLICE, return_ty, new_ptr, new_len); } // analyzeSlice for ctEvalBody, whose operands are all comptime-known: the // same port, with any runtime instruction a loud stop. uint32_t semaAnalyzeSliceComptime(Zcu* zcu, uint32_t ptr_ptr, uint32_t start, uint32_t end, uint32_t sentinel, bool by_length) { const InternPool* ip = &zcu->intern_pool; uint32_t ty; AirInstList insts = { 0 }; U32List block = { 0 }; U32List air_extra = { 0 }; uint32_t result = analyzeSlice(zcu, &insts, &block, &air_extra, ptr_ptr, ipTypeOf(ip, ptr_ptr), start, ipTypeOf(ip, start), end == IP_INDEX_NONE ? INST_VALUE_UNSET : end, end == IP_INDEX_NONE ? IP_INDEX_NONE : ipTypeOf(ip, end), sentinel == IP_INDEX_NONE ? INST_VALUE_UNSET : sentinel, by_length, &ty); bool runtime = block.len != 0; free(insts.tags); free(insts.datas); free(block.items); free(air_extra.items); if (runtime || semaIsAirInstRef(result)) UNIMPLEMENTED("analyzeSlice: runtime operation in a comptime scope"); return result; } // One zirForLen input's length (`arg_len`): a range's `end - start`, or the // indexable's `.len` (fieldVal). A runtime length is an AIR ref. static uint32_t semaForLenArg(InternPool* ip, const uint32_t* inst_values, const uint32_t* inst_types, AirInstList* insts, U32List* block, uint32_t arg0, uint32_t arg1) { if (arg1 != ZIR_REF_NONE) { // Range input `start..end` (Sema.zirForLen range arm). AstGen // coerced both bounds to usize, so no further coerce is // emitted. When `start` is comptime-known zero the length is // simply `end` (the upstream fast path: // `if valuesEqual(start, .zero_usize) break :l range_end`); // otherwise the length is `end - start`. Runtime mixed-bound // cases reuse the same usize coercion and AIR sub shape // already used by slice_start/slice_end. uint32_t start_rv = semaResolveRef(arg0, inst_values); uint32_t end_rv = semaResolveRef(arg1, inst_values); if (!semaIsAirInstRef(start_rv) && start_rv == ZIR_REF_ZERO_USIZE) { // end_rv is either a comptime usize IP index or a runtime // AIR ref (e.g. the int_cast widening of a `u8` bound); // either flows straight through as the loop length. return end_rv; } if (!semaIsAirInstRef(start_rv) && !semaIsAirInstRef(end_rv) && start_rv != INST_VALUE_UNSET && end_rv != INST_VALUE_UNSET) { // Comptime nonzero-start range (`inline for (1..6)`): length = end // - start (Sema.zirForLen range arm's general subtract, folded). uint64_t s_int = ipReadIntValue(ip, start_rv); uint64_t e_int = ipReadIntValue(ip, end_rv); if (e_int < s_int) UNIMPLEMENTED("for_len: negative range length"); return ipGetIntUsize(ip, (uint32_t)(e_int - s_int)); } if (semaIsAirInstRef(start_rv) || semaIsAirInstRef(end_rv)) { uint32_t start_air = semaCoerceSliceBoundToUsize( ip, arg0, start_rv, inst_types, insts, block); uint32_t end_air = semaCoerceSliceBoundToUsize( ip, arg1, end_rv, inst_types, insts, block); return semaUsizeArith( ip, insts, block, AIR_INST_SUB, end_air, start_air); } UNIMPLEMENTED("for_len: runtime nonzero-start range"); } // fieldVal(object, "len"): a slice's length (slice_len at runtime), // else read off the type -- an array or tuple, or through a one-pointer // (`is_pointer_to`) its child's. uint32_t obj_val = semaResolveRef(arg0, inst_values); uint32_t obj_ty = semaResolveRefValType(ip, arg0, obj_val, inst_types); semaForLenInternLen(ip, obj_ty); if (ztIsSlice(ip, obj_ty)) { if (semaIsAirInstRef(obj_val)) return semaEmitTyOp( insts, block, AIR_INST_SLICE_LEN, ZIR_REF_USIZE_TYPE, obj_val); return ipPtrSliceLen(ip, obj_val); } IpPtrInfo pi; if (ipPtrInfo(ip, obj_ty, &pi) == IP_PTRI_OK && pi.size == IP_PTR_SIZE_ONE) obj_ty = pi.child; uint64_t len; if (!semaArrayOrTupleLen(ip, obj_ty, &len) || len > UINT32_MAX) UNIMPLEMENTED("for_len: input length not modeled"); return ipGetIntUsize(ip, (uint32_t)len); } // (part of) Sema.zig:zirSliceSentinelTy // Sema.zirSliceSentinelTy's rule over the lhs pointer type (the lhs is an // lvalue): the element type its sentinel coerces to. uint32_t semaSliceSentinelTy(const InternPool* ip, uint32_t lhs_ptr_ty) { IpPtrInfo lhs_ptr, info; IpArrayInfo arr; if (lhs_ptr_ty >= ip->items_len) UNIMPLEMENTED("slice_sentinel_ty: unresolved lhs type"); if (ipPtrInfo(ip, lhs_ptr_ty, &lhs_ptr) != IP_PTRI_OK) COMPILE_ERROR_UPSTREAM("slice_sentinel_ty: expected pointer"); uint32_t lhs_ty = lhs_ptr.child; if (ipArrayInfo(ip, lhs_ty, &arr) == IP_ARR_OK && !arr.is_vector) return arr.child; if (ztIsSlice(ip, lhs_ty) && ipPtrInfo(ip, ip->item_datas[lhs_ty], &info) == IP_PTRI_OK) return info.child; if (ipPtrInfo(ip, lhs_ty, &info) != IP_PTRI_OK) COMPILE_ERROR_UPSTREAM("slice of non-array type"); if (info.size != IP_PTR_SIZE_ONE) return info.child; if (ipArrayInfo(ip, info.child, &arr) == IP_ARR_OK && !arr.is_vector) return arr.child; COMPILE_ERROR_UPSTREAM( "slice of single-item pointer cannot have sentinel"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // The field_ptr / field_ptr_load item decodes of a switch item body take a // namespace member of a Nav's value; any other object goes to the item // evaluator. static bool semaSwitchItemObjIsNavPtr(const InternPool* ip, const Zir* zir, const uint32_t* inst_values, uint32_t item_inst) { ZirInstField extra; zirExtraData( zir, zir->inst_datas[item_inst].pl_node.payload_index, &extra); uint32_t obj = semaResolveRef(extra.lhs, inst_values); return obj < ip->items_len && ip->item_tags[obj] == IP_TAG_PTR_NAV; } // analyzeSwitchPayloadCaptureTaggedUnion's `capture_ty`: resolvePeerTypes // over the items' field types. `tys` is n_items of scratch. static uint32_t semaSwitchCaptureTy(Zcu* zcu, uint32_t union_ty, const uint32_t* item_tags, uint32_t n_items, uint32_t* tys) { InternPool* ip = &zcu->intern_pool; for (uint32_t i = 0; i < n_items; i++) tys[i] = semaResolveUnionFieldTypeLazy(zcu, union_ty, semaUnionFieldIndexFromTag(ip, union_ty, item_tags[i])); return semaResolvePeerTypesOfTypes(zcu, tys, n_items); } uint32_t semaSwitchByValCapture(Zcu* zcu, uint32_t union_ty, const uint32_t* item_tags, uint32_t n_items, uint32_t payload, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; uint32_t* tys = ARR_INIT(uint32_t, n_items); *out_ty = semaSwitchCaptureTy(zcu, union_ty, item_tags, n_items, tys); free(tys); uint32_t opv = ztOnePossibleValue(ip, *out_ty); return opv != IP_INDEX_NONE ? opv : semaCoerceComptime(zcu, ip, payload, *out_ty); } // analyzeSwitchPayloadCaptureTaggedUnion's by-ref `capture_ptr_ty`: after // `capture_ty`, resolvePeerTypes over the field pointer types ("By-ref // captures of hetereogeneous types are only allowed if all field pointer // types are peer resolvable to each other"). static uint32_t semaSwitchByRefCapturePtrTy(Zcu* zcu, uint32_t operand_ptr_ty, uint32_t union_ty, const uint32_t* item_tags, uint32_t n_items) { InternPool* ip = &zcu->intern_pool; uint32_t* tys = ARR_INIT(uint32_t, n_items); semaSwitchCaptureTy(zcu, union_ty, item_tags, n_items, tys); for (uint32_t i = 0; i < n_items; i++) tys[i] = ztFieldPtrType(zcu, operand_ptr_ty, semaUnionFieldIndexFromTag(ip, union_ty, item_tags[i])); uint32_t capture_ptr_ty = semaResolvePeerTypesOfTypes(zcu, tys, n_items); free(tys); return capture_ptr_ty; } // (part of) PerThread.zig:getCoerced // "const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt); // return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));" uint32_t semaSwitchByRefCapturePtr(Zcu* zcu, uint32_t operand_ptr, uint32_t operand_ptr_ty, uint32_t union_ty, const uint32_t* item_tags, uint32_t n_items, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; uint32_t first_fi = semaUnionFieldIndexFromTag(ip, union_ty, item_tags[0]); *out_ty = semaSwitchByRefCapturePtrTy( zcu, operand_ptr_ty, union_ty, item_tags, n_items); if (ipIsUndef(ip, operand_ptr)) return ipGetUndef(ip, *out_ty); uint32_t ptr = ipRetypePtrValue( ip, valPtrField(zcu, operand_ptr, first_fi), *out_ty); if (ptr == IP_INDEX_NONE) UNIMPLEMENTED("getCoerced: switch by-ref capture - pointer carrier"); return ptr; } // (part of) Sema.zig:validateSwitchItemOrRange, InternPool.zig:indexToKey // The error name an `.error_set` switch item is keyed by // (Sema.validateSwitchItemOrRange's `.error_set` arm: "const error_name = // ip.indexToKey(item_val.toIntern()).err.name"), or IP_INDEX_NONE when the // item is not an error value. An item's set type may differ from the // operand's, so the name -- not the interned index -- is the key. static uint32_t semaSwItemErrorName(const InternPool* ip, uint32_t v) { if (v >= ip->items_len || ip->item_tags[v] != IP_TAG_ERROR_SET_ERROR) return IP_INDEX_NONE; return ipErrorName(ip, v); } // Sema.zig:cmpNumeric // Sema.cmpNumeric's results that one comptime-known float operand decides // alone: "if (lhs_val.isNan(zcu)) return if (op == .neq) .bool_true else // .bool_false;", and an infinity against an `.int` typed other side: the // bool value, or IP_INDEX_NONE when the result needs both values. static uint32_t semaCmpNumericKnownFloat(const InternPool* ip, ZirInstTag op, uint32_t known, bool known_is_lhs, uint32_t other_ty) { if (known >= ip->items_len || !semaTagIsFloat(ip->item_tags[known])) return IP_INDEX_NONE; if (valIsNan(ip, known)) return op == ZIR_INST_CMP_NEQ ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; uint32_t w[4]; if (!ztIsZigTypeTag(ip, other_ty, ZIG_TYPE_TAG_INT) || !ipReadFloatAsF128Bits(ip, known, w) || ((w[3] >> 16) & 0x7FFFu) != 0x7FFFu) return IP_INDEX_NONE; // Finite and NaN are excluded, so this is an infinity: it orders below // (negative) or above every integer. int known_ord = (w[3] >> 31) ? -1 : 1; return valCmpApplyOrder(op, known_is_lhs ? known_ord : -known_ord) ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; } // (part of) Sema.zig:cmpNumeric, PerThread.zig:intType // Sema.cmpNumeric's integer destination for a runtime integer of `rt_ty` // against an integral comptime float (already its comptime_int `ct`): // "dest_int_is_signed = lhs_is_signed or rhs_is_signed", each side's // `bits` (the float's `bitCountTwosComp`) plus one when unsigned under a // signed destination, and "pt.intType(signedness, @max(lhs_bits, // rhs_bits))". static uint32_t semaCmpFloatIntDestType( InternPool* ip, uint32_t rt_ty, uint32_t ct) { uint32_t rt_bits; bool rt_signed; uint64_t space[2]; BigIntConst v; if (!ztIntInfo(ip, rt_ty, &rt_bits, &rt_signed) || !valToBigInt(ip, ct, space, &v)) UNIMPLEMENTED("analyzeCmp: float against int operand shapes"); bool ct_signed = !v.positive && !bigIntEqlZero(v); bool dest_signed = rt_signed || ct_signed; uint64_t lb = rt_bits + (uint64_t)(!rt_signed && dest_signed); uint64_t rb = bigIntBitCountTwosComp(v) + (uint64_t)(!ct_signed && dest_signed); uint64_t bits = lb > rb ? lb : rb; if (bits > UINT16_MAX) COMPILE_ERROR_UPSTREAM( "analyzeCmp: exceeds maximum integer bit count"); return ipGetIntType(ip, dest_signed, (uint16_t)bits); } // (part of) Sema.zig:cmpNumeric // Sema.cmpNumeric's comptime float against a fixed-width integer peer: "const // float = lhs_val.toFloat(f128, zcu); ... switch (big_int.setFloat(float, // .away)) { .inexact => switch (op) { .eq => return .bool_false, .neq => // return .bool_true, else => {} }, .exact => {} }", then an integer dest type // and `sema.coerce` of the float, whose `.exact` intFromFloat refuses a // fraction. True with `*out_fold` set when the answer needs no runtime // operand (a negative float against an unsigned integer, or `==`/`!=` of a // fractional one; a fractional ordering is the coercion's compile error); // otherwise `*out_int` is an integral float's comptime_int, or IP_INDEX_NONE // when `known` is not a finite float against an integer -- NaN and infinity // are semaCmpNumericKnownFloat's, asked first. static bool semaCmpFloatAgainstIntPeer(InternPool* ip, ZirInstTag op, uint32_t known, bool known_is_lhs, uint32_t other_ty, uint32_t* out_fold, uint32_t* out_int) { *out_int = IP_INDEX_NONE; uint32_t w[4]; if (known >= ip->items_len || !semaTagIsFloat(ip->item_tags[known]) || !ztIsZigTypeTag(ip, other_ty, ZIG_TYPE_TAG_INT) || !ipReadFloatAsF128Bits(ip, known, w) || ((w[3] >> 16) & 0x7FFFu) == 0x7FFFu) return false; // Ahead of the float's exactness: "if (!lhs_is_signed) { switch // (Value.order(rhs_val, .zero_comptime_int, zcu)) { ... .lt => switch // (op) { // RHS < 0, LHS is unsigned" -- a negative float orders below // every unsigned integer. uint32_t other_bits; bool other_signed; bool known_neg = (w[3] >> 31) != 0 && (w[0] | w[1] | w[2] | (w[3] & 0x7FFFFFFFu)) != 0; bool other_unsigned = ztIntInfo(ip, other_ty, &other_bits, &other_signed) && !other_signed; if (known_neg && other_unsigned) { *out_fold = valCmpApplyOrder(op, known_is_lhs ? -1 : 1) ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; return true; } // `".eq => switch (op) { // LHS = 0, RHS is unsigned"`: `<=` holds and // `>` fails (mirrored for a zero right-hand side). bool known_zero = (w[0] | w[1] | w[2] | (w[3] & 0x7FFFFFFFu)) == 0; ZirInstTag holds = known_is_lhs ? ZIR_INST_CMP_LTE : ZIR_INST_CMP_GTE; ZirInstTag fails = known_is_lhs ? ZIR_INST_CMP_GT : ZIR_INST_CMP_LT; if (known_zero && other_unsigned && (op == holds || op == fails)) { *out_fold = semaBoolRef(op == holds); return true; } bool exact; uint32_t as_int = semaFloatWordsToComptimeInt(ip, w, &exact); if (exact) { *out_int = as_int; return false; } if (op != ZIR_INST_CMP_EQ && op != ZIR_INST_CMP_NEQ) COMPILE_ERROR_UPSTREAM( "analyzeCmp: fractional component prevents float " "value from coercion to the integer type"); *out_fold = op == ZIR_INST_CMP_NEQ ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; return true; } // (part of) Value.zig:order // RangeSet's one comparison, `Value.order` through the one comptime // comparison rule. static bool semaSwCmp(Zcu* zcu, ZirInstTag op, uint32_t a, uint32_t b) { return semaCmpComptime(zcu, op, a, b) == ZIR_REF_BOOL_TRUE; } // (part of) Sema.zig:validateSwitchBlock, Sema.zig:validateSwitchItemOrRange // Sema.validateSwitchBlock's "Validate for duplicate items and invalid // ranges." pass (src/Sema.zig): one Sema.validateSwitchItemOrRange per // non-`_` item and per range, ending at "duplicate switch value" // (src/Sema.zig). A `_` item is not validated -- upstream takes the // `.under` branch and only does "case_vals.appendAssumeCapacity(.none)" -- // so an item a plain prong shares with the `_` prong's case is a duplicate, // and `item_vals` entries of IP_INDEX_NONE (resolveSwitchItem's `.under` // arm) are skipped here. // // The key, by `item_ty`'s zigTypeTag (src/Sema.zig): // .@"enum" seen_enum_fields[tagValueIndex(int)], or the // `range_set` point [int, int] for a non-exhaustive // value matching no field // .error_set seen_errors, keyed by the error name // .int, .comptime_int range_set over [item, item] / [first, last] // .@"union", .@"struct" range_set over the `.bitpack` backing_int_val // .enum_literal, .@"fn", .pointer, .type // seen_sparse_values, keyed by the interned Index // .bool true_src / false_src ; .void void_src // // Every one of those keys except the error name and an integer range reduces // to the interned index of the coerced item: zig1 interns an enum tag per // (type, int), a `.bitpack` per (type, backing int), and enum literals, // pointers, function and type values and the bool/void preinterns uniquely, // so "equal keys" and "equal indices" name the same pairs. Hence one identity // scan over all items, a name scan when `item_ty` is an error set, and -- // only when the switch actually has ranges -- a RangeSet overlap scan over // the integer points and ranges together. The scan is pairwise (O(items^2)) // where upstream keys a map: bounded by a switch's prong count, and the // RangeSet scan upstream keeps is pairwise too. void semaSwitchValidateItems(Zcu* zcu, uint32_t item_ty, const uint32_t* item_vals, uint32_t items_len, const uint32_t* range_vals, uint32_t ranges_len) { const InternPool* ip = &zcu->intern_pool; // The error name is the key of the `.error_set` arm only, and upstream // picks that arm from `item_ty.zigTypeTag(zcu)` -- never from the item // VALUE's own tag. bool item_ty_is_error_set = ztIsZigTypeTag(ip, item_ty, ZIG_TYPE_TAG_ERROR_SET); for (uint32_t i = 0; i < items_len; i++) { uint32_t vi = item_vals[i]; if (vi == IP_INDEX_NONE) continue; uint32_t ni = item_ty_is_error_set ? semaSwItemErrorName(ip, vi) : IP_INDEX_NONE; for (uint32_t j = 0; j < i; j++) { uint32_t vj = item_vals[j]; if (vj == IP_INDEX_NONE) continue; if (vi == vj || (ni != IP_INDEX_NONE && ni == semaSwItemErrorName(ip, vj))) COMPILE_ERROR_UPSTREAM("switch_block: duplicate switch value"); } } if (ranges_len == 0) return; // The paragraph immediately after that pass (src/Sema.zig): // only `.int` and `.comptime_int` item types admit ranges at all; every // other one -- enum, bool, error set, pointer, packed container -- is // "ranges not allowed when switching on type '{f}'". It runs after the // whole duplicate pass, hence its position here. if (!ztIsZigTypeTag(ip, item_ty, ZIG_TYPE_TAG_INT) && item_ty != ZIR_REF_COMPTIME_INT_TYPE) COMPILE_ERROR_UPSTREAM( "switch_block: ranges not allowed when switching on type"); // RangeSet.add reports the earlier entry that overlaps the new one; a // range whose start exceeds its end is its own error first ("range start // value is greater than the end value", src/Sema.zig). Upstream // validates one range at a time against everything admitted before it, so // a backwards range later in the switch than an overlapping pair never // gets to speak -- check each range's own bounds and its overlaps in the // same step rather than sweeping all the bounds first. for (uint32_t r = 0; r < ranges_len; r++) { uint32_t first = range_vals[2u * r], last = range_vals[2u * r + 1u]; if (semaSwCmp(zcu, ZIR_INST_CMP_GT, first, last)) COMPILE_ERROR_UPSTREAM("switch_block: range start value is " "greater than the end value"); // RangeSet.add: against every item point and every earlier range. for (uint32_t j = 0; j < items_len + r; j++) { uint32_t jf = j < items_len ? item_vals[j] : range_vals[2u * (j - items_len)]; uint32_t jl = j < items_len ? jf : range_vals[2u * (j - items_len) + 1u]; if (semaSwCmp(zcu, ZIR_INST_CMP_LTE, first, jl) && semaSwCmp(zcu, ZIR_INST_CMP_LTE, jf, last)) COMPILE_ERROR_UPSTREAM("switch_block: duplicate switch value"); } } } // RangeSet.spans(min, max) (src/Sema/RangeSet.zig) over the item points and // ranges, which the duplicate pass has already proven pairwise disjoint: // sorted by first, they span iff the first starts at `minInt`, each next // starts one past the previous one's end ("prev.last + 1 == cur.first") and // the final one ends at `maxInt`. Compared as `Value.order` at any width; a // packed item is its `.bitpack` backing int. typedef struct { uint64_t lo_space[2], hi_space[2]; BigIntConst lo, hi; } SwRange; static bool semaSwRangesSpan(const InternPool* ip, uint32_t int_ty, const uint32_t* item_vals, uint32_t items_len, const uint32_t* range_vals, uint32_t ranges_len) { uint32_t bits; bool is_signed; if (!ztIntInfo(ip, int_ty, &bits, &is_signed)) UNIMPLEMENTED("switch_block: spans over a non-int type"); uint32_t cap = items_len + ranges_len; SwRange* r = zig1Malloc((cap ? cap : 1u) * sizeof(SwRange)); uint32_t n = 0; for (uint32_t i = 0; i < cap; i++) { uint32_t a = i < items_len ? item_vals[i] : range_vals[2u * (i - items_len)]; uint32_t b = i < items_len ? a : range_vals[2u * (i - items_len) + 1u]; if (a == IP_INDEX_NONE) continue; if (a < ip->items_len && ip->item_tags[a] == IP_TAG_BITPACK) a = ipBitpackBackingVal(ip, a); if (b < ip->items_len && ip->item_tags[b] == IP_TAG_BITPACK) b = ipBitpackBackingVal(ip, b); if (!valToBigInt(ip, a, r[n].lo_space, &r[n].lo) || !valToBigInt(ip, b, r[n].hi_space, &r[n].hi)) UNIMPLEMENTED("switch_block: spans over an unreadable bound"); n++; } // Sorted through an index: an entry's limbs may live in its own space, // so the entries themselves never move. uint32_t* o = zig1Malloc((n ? n : 1u) * sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { o[i] = i; for (uint32_t j = i; j > 0 && bigIntOrder(r[o[j]].lo, r[o[j - 1]].lo) < 0; j--) { uint32_t t = o[j]; o[j] = o[j - 1]; o[j - 1] = t; } } uint32_t limbs = bits / 64u + 2u; uint64_t* buf = zig1Calloc((size_t)limbs * 3u, sizeof(uint64_t)); BigIntMutable min = { buf, limbs, 0, true }; BigIntMutable max = { buf + limbs, limbs, 0, true }; BigIntMutable next = { buf + 2u * limbs, limbs, 0, true }; if (!bigIntMutableSetTwosCompIntLimit( &min, BIG_INT_LIMIT_MIN, is_signed, bits) || !bigIntMutableSetTwosCompIntLimit( &max, BIG_INT_LIMIT_MAX, is_signed, bits)) UNIMPLEMENTED("switch_block: spans: minInt/maxInt"); bool spans = n != 0 && bigIntOrder(r[o[0]].lo, bigIntMutableToConst(min)) == 0 && bigIntOrder(r[o[n - 1]].hi, bigIntMutableToConst(max)) == 0; for (uint32_t i = 1; spans && i < n; i++) { if (!bigIntMutableAddScalar(&next, r[o[i - 1]].hi, 1)) UNIMPLEMENTED("switch_block: spans: prev.last + 1"); spans = bigIntOrder(bigIntMutableToConst(next), r[o[i]].lo) == 0; } free(buf); free(o); free(r); return spans; } // A small switch item's value as an int64 (bounded by the caller's width). static bool semaSwItemI64(const InternPool* ip, uint32_t v, int64_t* out) { uint64_t space[2]; BigIntConst big; uint64_t mag; if (!valToBigInt(ip, v, space, &big) || !bigIntToU64(bigIntConstAbs(big), &mag) || mag > (uint64_t)INT64_MAX) return false; *out = big.positive ? (int64_t)mag : -(int64_t)mag; return true; } // The widest fixed-width int `inline else` enumerates here: upstream walks // every unhandled value, which past this many cases is a representation // question, not a semantic one, so a wider operand declines loudly. #define SW_INLINE_ELSE_MAX_INT_BITS 16u // `ValidatedSwitchBlock.iterateUnhandledItems` (src/Sema.zig): the // values no explicit item or range names, in upstream's order -- enum // fields by index, `true` then `false`, integers ascending from `minInt`. // Returns the count written to `out` (`cap` entries); a NULL `out` counts // without interning, as validateSwitchBlock's own seen-set reads do. static uint32_t semaSwitchUnhandledItems(InternPool* ip, uint32_t item_ty, const uint32_t* item_vals, uint32_t items_len, const uint32_t* range_vals, uint32_t ranges_len, uint32_t* out, uint32_t cap) { uint32_t n = 0; if (item_ty < ip->items_len && ipTypeIsBitpack(ip, item_ty)) { // `.@"union", .@"struct"`: the backing int's unhandled values, each // `pt.bitpackValue(item_ty, cur_val)`. uint32_t* ints = zig1Malloc((items_len ? items_len : 1u) * sizeof(uint32_t)); for (uint32_t i = 0; i < items_len; i++) ints[i] = ipBitpackBackingVal(ip, item_vals[i]); n = semaSwitchUnhandledItems(ip, ipBitpackBackingInt(ip, item_ty), ints, items_len, range_vals, ranges_len, out, cap); free(ints); for (uint32_t i = 0; out != NULL && i < n; i++) out[i] = ipGetBitpack(ip, item_ty, out[i]); return n; } if (ipItemTagIs(ip, item_ty, IP_TAG_TYPE_ERROR_SET)) { // `.error_set`: `item_ty.errorSetNames(zcu)` minus `seen_errors`, // each `.err = .{ .ty = item_ty, .name = err_name }`. uint32_t es_off = ip->item_datas[item_ty]; uint32_t es_len = ip->extra[es_off]; for (uint32_t ni = 0; ni < es_len && n < cap; ni++) { uint32_t name = ip->extra[es_off + 2 + ni]; bool seen = false; for (uint32_t i = 0; i < items_len && !seen; i++) seen = semaSwItemErrorName(ip, item_vals[i]) == name; if (seen) continue; if (out != NULL) out[n] = ipGetErrorSetError(ip, item_ty, name); n++; } return n; } if (item_ty < ip->items_len && ipIsEnumType(ip, item_ty)) { uint32_t fields_len = ipEnumFieldsLen(ip, item_ty); uint8_t* seen = zig1Calloc(fields_len ? fields_len : 1u, 1); for (uint32_t i = 0; i < items_len; i++) { uint32_t fi = item_vals[i] == IP_INDEX_NONE ? UINT32_MAX : ipEnumTagFieldIndex(ip, item_vals[i]); if (fi < fields_len) seen[fi] = 1; } for (uint32_t fi = 0; fi < fields_len && n < cap; fi++) { if (seen[fi]) continue; if (out == NULL) { n++; continue; } // `"return try pt.enumValueFieldIndex(item_ty, // @intCast(field_i));"` uint32_t tag_int = ipEnumFieldValue(ip, item_ty, fi); if (tag_int == IP_INDEX_NONE) tag_int = ipInternIntResult( ip, fi, ipEnumTypeIntTagType(ip, item_ty)); out[n] = ipGetEnumTag(ip, item_ty, tag_int); if (out[n] == IP_INDEX_NONE) UNIMPLEMENTED("switch_block: unhandled enum item not found"); n++; } free(seen); return n; } if (item_ty == ZIR_REF_BOOL_TYPE) { bool seen_true = false, seen_false = false; for (uint32_t i = 0; i < items_len; i++) { seen_true = seen_true || item_vals[i] == ZIR_REF_BOOL_TRUE; seen_false = seen_false || item_vals[i] == ZIR_REF_BOOL_FALSE; } if (!seen_true && n < cap) out[n++] = ZIR_REF_BOOL_TRUE; if (!seen_false && n < cap) out[n++] = ZIR_REF_BOOL_FALSE; return n; } uint32_t bits; bool is_signed; if (!ztIntInfo(ip, item_ty, &bits, &is_signed) || bits > SW_INLINE_ELSE_MAX_INT_BITS) UNIMPLEMENTED("switch_block: inline else over this item type"); int64_t min = is_signed && bits != 0 ? -((int64_t)1 << (bits - 1u)) : 0; int64_t max = bits == 0 ? 0 : is_signed ? ((int64_t)1 << (bits - 1u)) - 1 : ((int64_t)1 << bits) - 1; for (int64_t v = min; v <= max && n < cap; v++) { bool seen = false; for (uint32_t i = 0; i < items_len + ranges_len && !seen; i++) { int64_t lo, hi; uint32_t a = i < items_len ? item_vals[i] : range_vals[2u * (i - items_len)]; uint32_t b = i < items_len ? a : range_vals[2u * (i - items_len) + 1u]; if (!semaSwItemI64(ip, a, &lo) || !semaSwItemI64(ip, b, &hi)) UNIMPLEMENTED("switch_block: unreadable inline else bound"); seen = lo <= v && v <= hi; } if (seen) continue; uint64_t limb = v < 0 ? (uint64_t)(-v) : (uint64_t)v; out[n++] = ipGetInt(ip, item_ty, (BigIntConst) { &limb, 1, v >= 0 }); } return n; } // analyzeSwitchCaptures' `.item_refs` capture type, // `errorSetFromUnsortedNames` over the prong items' names (for one item, // `singleErrorSetType`). uint32_t semaSwitchErrCaptureTy( InternPool* ip, const uint32_t* items, uint32_t items_len) { uint32_t* names = zig1Malloc((items_len ? items_len : 1u) * sizeof(uint32_t)); for (uint32_t i = 0; i < items_len; i++) { names[i] = semaSwItemErrorName(ip, items[i]); if (names[i] == IP_INDEX_NONE) UNIMPLEMENTED("switch_block: non-error error-set capture item"); } uint32_t cap_ty = zptErrorSetFromUnsortedNames(ip, names, items_len); free(names); return cap_ty; } // (part of) Sema.zig:validateSwitchBlock // Sema.validateSwitchBlock's "Validate for missing special prongs." pass // (src/Sema.zig), by `item_ty`'s zigTypeTag, after the duplicate // pass (`semaSwitchValidateItems`) has run over the same items. `_` items are // IP_INDEX_NONE and count for nothing. `operand_ty` is the switched type (a // tagged union's, whose `item_ty` is its tag enum). Returns `else_err_ty` // (IP_INDEX_NONE: upstream's `null`, or not an error set). uint32_t semaSwitchValidateSpecialProngs(InternPool* ip, uint32_t item_ty, uint32_t operand_ty, const uint32_t* item_vals, uint32_t items_len, const uint32_t* range_vals, uint32_t ranges_len, bool has_else, bool has_under, bool else_is_simple_noreturn) { // The per-item loop's `.under` arm: "'_' prong only allowed when // switching on non-exhaustive enums". if (has_under && !(ztIsNonexhaustiveEnum(ip, operand_ty))) COMPILE_ERROR_UPSTREAM("switch_block: '_' prong only allowed when " "switching on non-exhaustive enums"); // ValidatedSwitchBlock's seen set, asked through the one iterator that // reads it (`iterateUnhandledItems`): is any value left unhandled? if (item_ty < ip->items_len && ipIsEnumType(ip, item_ty)) { bool all_tags_handled = semaSwitchUnhandledItems(ip, item_ty, item_vals, items_len, NULL, 0u, NULL, 1u) == 0; bool nonexhaustive = ztIsNonexhaustiveEnum(ip, item_ty); if (has_else) { if (all_tags_handled && (!nonexhaustive || has_under)) COMPILE_ERROR_UPSTREAM("switch_block: unreachable else prong; " "all cases already handled"); } else if (!all_tags_handled) { COMPILE_ERROR_UPSTREAM( "switch_block: switch must handle all possibilities"); } else if (!has_under && nonexhaustive && !(ztIsZigTypeTag(ip, operand_ty, ZIG_TYPE_TAG_UNION))) { COMPILE_ERROR_UPSTREAM("switch_block: switch on non-exhaustive " "enum must include 'else' or '_' prong"); } return IP_INDEX_NONE; } if (ztIsZigTypeTag(ip, item_ty, ZIG_TYPE_TAG_ERROR_SET)) { uint32_t set = semaResolvedInferredErrorSet(ip, item_ty); if (set == ZIR_REF_ANYERROR_TYPE) { if (!has_else) COMPILE_ERROR_UPSTREAM("switch_block: else prong required " "when switching on type 'anyerror'"); return ZIR_REF_ANYERROR_TYPE; } if (set >= ip->items_len || ip->item_tags[set] != IP_TAG_TYPE_ERROR_SET) UNIMPLEMENTED("switch_block: unresolved switched error set"); uint32_t es_off = ip->item_datas[set]; uint32_t es_len = ip->extra[es_off]; uint32_t* names = zig1Malloc((es_len ? es_len : 1u) * sizeof(uint32_t)); uint32_t names_len = 0; for (uint32_t ni = 0; ni < es_len; ni++) { uint32_t name = ip->extra[es_off + 2 + ni]; bool seen = false; for (uint32_t i = 0; i < items_len && !seen; i++) seen = semaSwItemErrorName(ip, item_vals[i]) == name; if (!seen && !has_else) COMPILE_ERROR_UPSTREAM( "switch_block: switch must handle all possibilities"); if (!seen) names[names_len++] = name; } if (has_else && names_len == 0 && !else_is_simple_noreturn) COMPILE_ERROR_UPSTREAM("switch_block: unreachable else prong; all " "cases already handled"); // `errorSetFromUnsortedNames(names.keys())`: the set's own names are // sorted and filtering keeps their order. uint32_t else_err_ty = has_else && names_len == 0 ? IP_INDEX_NONE : ipGetErrorSetType(ip, names, names_len); free(names); return else_err_ty; } bool is_bitpack = item_ty < ip->items_len && ipTypeIsBitpack(ip, item_ty); if (is_bitpack || ztIsZigTypeTag(ip, item_ty, ZIG_TYPE_TAG_INT)) { uint32_t int_ty = is_bitpack ? ipBitpackBackingInt(ip, item_ty) : item_ty; if (semaSwRangesSpan( ip, int_ty, item_vals, items_len, range_vals, ranges_len)) { if (has_else) COMPILE_ERROR_UPSTREAM("switch_block: unreachable else prong; " "all cases already handled"); } else if (!has_else) { COMPILE_ERROR_UPSTREAM( "switch_block: switch must handle all possibilities"); } return IP_INDEX_NONE; } if (item_ty == ZIR_REF_BOOL_TYPE || item_ty == ZIR_REF_VOID_TYPE) { uint32_t unhandled_bool; // preinterned: counting it interns nothing bool all_values_handled = item_ty == ZIR_REF_BOOL_TYPE ? semaSwitchUnhandledItems(ip, item_ty, item_vals, items_len, NULL, 0u, &unhandled_bool, 1u) == 0 : items_len != 0; if (has_else && all_values_handled) COMPILE_ERROR_UPSTREAM("switch_block: unreachable else prong; all " "cases already handled"); if (!has_else && !all_values_handled) COMPILE_ERROR_UPSTREAM( "switch_block: switch must handle all possibilities"); return IP_INDEX_NONE; } // `.comptime_int, .enum_literal, .@"fn", .pointer, .type`: "else prong // required when switching on type '{f}'". if (!has_else) COMPILE_ERROR_UPSTREAM( "switch_block: else prong required when switching on type"); return IP_INDEX_NONE; } // The type of a load instruction the enclosing type-only body never executed. // Upstream analyzes `@TypeOf`'s operand body in an `.is_typeof` child block // purely to learn the break operand's result type and then discards the child // instructions, so a load over a plain variable has no evaluated result here; // its type is the load POINTER's child. Returns IP_INDEX_NONE when `ref` is // not such an unevaluated load, and aborts loudly when it is one whose // pointer type is missing or is not a pointer. Shared by `typeof_builtin` and // `typeof_peer`, which derived it identically. static uint32_t semaTypeOfUnevaluatedLoad(Zcu* zcu, const Zir* zir, const uint32_t* inst_values, const uint32_t* inst_types, uint32_t ref) { const InternPool* ip = &zcu->intern_pool; if (ref < ZIR_REF_START_INDEX) return IP_INDEX_NONE; uint32_t ai = ref - ZIR_REF_START_INDEX; if (ai >= zir->inst_len || zir->inst_tags[ai] != ZIR_INST_LOAD) return IP_INDEX_NONE; uint32_t lp = zir->inst_datas[ai].un_node.operand; if (lp >= ZIR_REF_START_INDEX && lp - ZIR_REF_START_INDEX >= zir->inst_len) UNREACHABLE("typeof: load pointer ref out of range"); uint32_t lp_ty = semaResolveRefType(lp, inst_types); if (lp_ty == IP_INDEX_NONE) { // A `comptime var` cell: the pointer is a ptr_comptime_alloc whose // element type is the cell's (resolved for an inferred cell by // `resolve_inferred_alloc`). uint32_t lp_val = semaResolveRef(lp, inst_values); if (ipIsPtrComptimeAlloc(ip, lp_val)) return mutableValueTypeOf(ip, &getComptimeAlloc(zcu, ipPtrComptimeAllocIndex(ip, lp_val)) ->val); // Any other comptime pointer value carries its own pointer type. uint32_t lp_val_ty = lp_val < ip->items_len ? ipTypeOf(ip, lp_val) : IP_INDEX_NONE; if (ipItemTagIs(ip, lp_val_ty, IP_TAG_TYPE_POINTER)) return ipPtrChild(ip, lp_val_ty); UNIMPLEMENTED("typeof: load pointer type unknown"); } if (lp_ty >= ip->items_len || ip->item_tags[lp_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("typeof: load pointer type not pointer"); return ipPtrChild(ip, lp_ty); } // Find the single case whose sole item is the `_` catch-all (its item value // was resolved to IP_INDEX_NONE by resolveSwitchItem's `.under` arm). Mirrors // the validation upstream folds into the per-item loop of validateSwitchBlock // (src/Sema.zig: "'_' prong only allowed when switching on // non-exhaustive enums" at) and AstGen's own restrictions (`_` cannot // be `inline`, cannot repeat) -- both re-checked by the caller via the // returned index's ProngInfo bits, since this scan only locates the item. // Aborts loudly (never silently) on any shape this switch_block port doesn't // model: a `_` prong whose operand is anything but a non-exhaustive enum // (stock rejects `_` on ints and exhaustive enums too), or more than one `_` // prong (compile error upstream: "multiple '_' prongs"). // // A `_` sharing its case with named items (`_, .none, .freed =>`) is valid Zig // and needs no extra code here: upstream emits nothing for those items either // -- "If there are any named items specified along with the `_`, we don't have // to actually emit any AIR for them as they will be 'absorbed' by the `_` (the // catch-all prong) anyway" (src/Sema.zig), right before `under_prong = .{ ... // }; continue;`. The caller already excludes the whole located case from the // AIR case map and from the comptime literal-item scan (`if (ci == under_ci) // continue;`), and the absorbed items share the one prong body the catch-all // runs, so both paths reach the same body either way. Those absorbed items are // still validated: semaSwitchValidateItems runs over every non-`_` item before // this scan, the same order upstream uses, so an item shared between a plain // prong and the absorbing case is a `duplicate switch value` compile error // rather than a silently-absorbed item. static uint32_t semaLocateUnderCase(const InternPool* ip, uint32_t operand_ty, uint32_t explicit_cases_len, const uint32_t* case_items_count, const uint32_t* case_item_off, const uint32_t* item_vals) { if (!ztIsNonexhaustiveEnum(ip, operand_ty)) COMPILE_ERROR_UPSTREAM( "switch_block: '_' prong on non-non-exhaustive-enum operand"); uint32_t under_ci = UINT32_MAX; for (uint32_t ci = 0; ci < explicit_cases_len; ci++) { bool this_is_under = false; for (uint32_t it = 0; it < case_items_count[ci]; it++) if (item_vals[case_item_off[ci] + it] == IP_INDEX_NONE) this_is_under = true; if (!this_is_under) continue; if (under_ci != UINT32_MAX) COMPILE_ERROR_UPSTREAM("switch_block: multiple '_' prongs"); under_ci = ci; } if (under_ci == UINT32_MAX) UNIMPLEMENTED("switch_block: has_under bit set but no '_' item found"); return under_ci; } // (part of) Sema.zig:zirAlloc // Can zig1's comptime cell model represent a mutable alloc of `elem_ty`? // A cell holds one interned value; mutation through a derived pointer is // modeled for array cells only (comptimeAllocCellElemInfo: `"cell is not an // array"`). A struct/union/optional/error-union result location is // initialized through field / payload-base pointers, which would need // per-field cell stores that are not ported. Used to bound Sema.zirAlloc's // comptime arm — see the ZIR_INST_ALLOC case for the disclosed divergence. static bool semaComptimeCellModelableType( const InternPool* ip, uint32_t elem_ty) { uint32_t arr_len, arr_child, arr_sentinel; if (ztArrayTypeInfo(ip, elem_ty, &arr_len, &arr_child, &arr_sentinel)) return true; if (elem_ty >= ip->items_len) return false; uint8_t t = ip->item_tags[elem_ty]; return !(ipTagIsAnyStruct(t) || t == IP_TAG_TYPE_TUPLE || t == IP_TAG_TYPE_UNION || t == IP_TAG_TYPE_OPTIONAL || t == IP_TAG_TYPE_ERROR_UNION || t == IP_TAG_TYPE_ANYERROR_UNION); } // (part of) Sema.zig:zirAllocInferredComptime // Sema.zirAllocInferredComptime / zirAllocExtended's comptime arm: the AIR // `inferred_alloc_comptime` placeholder, `"... .alignment = .none, .is_const // = is_const, .ptr = undefined"`, for a comptime `var x = ` whose // element type comes from its one store. It interns nothing and is appended // to the instruction list only, never to a block. pair.a is the pointer // flags (`alignment`, `is_const`), pair.b `iac.ptr` (IP_INDEX_NONE until // storeToInferredAllocComptime sets it). static uint32_t semaInferredAllocComptime( AirInstList* insts, uint32_t ptr_flags) { AirInstData iac_data; memset(&iac_data, 0, sizeof(iac_data)); iac_data.pair.a = ptr_flags; iac_data.pair.b = IP_INDEX_NONE; return AIR_REF_INST_BASE + semaAirInstAppend(insts, AIR_INST_INFERRED_ALLOC_COMPTIME, iac_data); } // Sema.zig:newComptimeAlloc // Sema.newComptimeAlloc: a new comptime alloc of `ty` in the analyzing // Sema, its value `"(try pt.undefValue(ty))"`. uint32_t newComptimeAlloc(Zcu* zcu, uint32_t ty) { ZcuSemaBranch* b = zcu->sema_branch; if (b->comptime_allocs_len == b->comptime_allocs_cap) { b->comptime_allocs_cap = zig1GrowCap(b->comptime_allocs_cap, 16u); b->comptime_allocs = zig1Realloc(b->comptime_allocs, b->comptime_allocs_cap * sizeof(ComptimeAlloc)); } uint32_t idx = b->comptime_allocs_len++; ComptimeAlloc* alloc = &b->comptime_allocs[idx]; alloc->val.tag = MV_INTERNED; alloc->val.u.interned = ipGetUndef(&zcu->intern_pool, ty); return idx; } // The `pt.ptrType(.{ .child = ty, .flags = ... })` of a comptime alloc: a // default `*usize` is the preinterned one. uint32_t semaComptimeAllocPtrType( InternPool* ip, uint32_t elem_ty, uint32_t ptr_flags) { if (elem_ty == ZIR_REF_USIZE_TYPE && ptr_flags == IP_PTR_FLAGS_MUT_ONE) return ZIR_REF_PTR_USIZE_TYPE; return ipGetPtrType(ip, elem_ty, ptr_flags); } // Sema.zig:storeToInferredAllocComptime // Sema.storeToInferredAllocComptime: `"There will be only one // store_to_inferred_ptr because we are running at comptime."` The store // creates the memory and returns `iac.ptr`: a uav of the value when its type // has one possible value or the alloc is const and the value cannot mutate // comptime state, else a new comptime alloc holding it. `ptr_flags` are the // placeholder's alignment and is_const. uint32_t storeToInferredAllocComptime( Zcu* zcu, uint32_t ptr_flags, uint32_t operand_val) { InternPool* ip = &zcu->intern_pool; uint32_t operand_ty = ipComptimeValueType(ip, operand_val); uint32_t alloc_ty = semaComptimeAllocPtrType(ip, operand_ty, ptr_flags); if (ztOnePossibleValue(ip, operand_ty) != IP_INDEX_NONE || ((ptr_flags & IP_PTR_FLAGS_IS_CONST) != 0 && !valCanMutateComptimeVarState(ip, operand_val))) return ipGetPtrUav(ip, alloc_ty, operand_val); uint32_t alloc = newComptimeAlloc(zcu, operand_ty); getComptimeAlloc(zcu, alloc)->val.u.interned = operand_val; return ipGetPtrComptimeAlloc(ip, alloc_ty, alloc); } // Sema.zig:analyzeComptimeAlloc, Sema.zig:zirAlloc, Sema.zig:zirAllocMut // Sema.analyzeComptimeAlloc (`"fn analyzeComptimeAlloc("`): reserve a // comptime-mutable memory slot of element type `elem_ty` and return the // interned `ptr_comptime_alloc` pointing at it (upstream's // `"base_addr = .{ .comptime_alloc = alloc }"`); no AIR is emitted. The // mutable value itself lives in the InlineCtx cell side table, not in the // InternPool. Shared by `alloc_comptime_mut` and — via Sema.zirAlloc / // Sema.zirAllocMut's `"if (block.semaBlockIsComptime())"` arms — by a plain // `alloc`/`alloc_mut` analyzed in a comptime scope. // As upstream does, one-possible-value element types return a `ptr_uav` // rooted at the unique value and allocate no comptime cell. Other types use // a `ptr_comptime_alloc` rooted at the new cell. uint32_t analyzeComptimeAlloc(Zcu* zcu, InternPool* ip, uint32_t elem_ty, uint32_t ptr_flags, uint32_t* out_ptr_ty) { // ptr type: child=elem_ty, the alloc's alignment, address_space // global_constant (= generic on wasm). uint32_t ptr_ty = semaComptimeAllocPtrType(ip, elem_ty, ptr_flags); if (out_ptr_ty) *out_ptr_ty = ptr_ty; uint32_t opv = ztOnePossibleValue(ip, elem_ty); if (opv != IP_INDEX_NONE) return ipGetPtrUav(ip, ptr_ty, opv); uint32_t cell = newComptimeAlloc(zcu, elem_ty); return ipGetPtrComptimeAlloc(ip, ptr_ty, cell); } typedef struct { uint32_t call_operand; uint32_t ret_ty; uint32_t emit_nargs; uint32_t pending_entry; } FuncInstanceCall; static FuncInstanceCall semaBuildFuncInstanceCall(Zcu* zcu, uint32_t func_decl, uint32_t func_ty, uint32_t ret_ty, const uint32_t* arg_refs, const uint32_t* resolved_param_ty, const bool* param_is_comptime, uint32_t total_params, uint32_t* emit_args) { InternPool* ip = &zcu->intern_pool; uint32_t comptime_args_small[8]; uint32_t rt_tys_small[8]; uint32_t* comptime_args = (total_params <= 8) ? comptime_args_small : ARR_INIT(uint32_t, total_params); uint32_t* rt_tys = (total_params <= 8) ? rt_tys_small : ARR_INIT(uint32_t, total_params); uint32_t rt_n = 0; // The generic owner's noalias bits (original param positions) are remapped // to the instance's runtime-argument positions (Sema.analyzeCall: bit // runtime_idx set for each noalias runtime arg, Sema.zig). uint32_t owner_noalias = ipFuncTypeNoaliasBits(ip, func_ty); uint32_t inst_noalias = 0; for (uint32_t ai = 0; ai < total_params; ai++) { if (param_is_comptime[ai]) { // Sema.analyzeCall treats a parameter as comptime when it is // declared comptime or the argument type is comptime-only, and the // func_instance builder omits it from runtime_param_tys with the // same comptimeOnly test. The coerced arg must therefore be a // comptime-known IP index, not an AIR inst ref. if (arg_refs[ai] >= AIR_REF_INST_BASE) UNIMPLEMENTED("call: comptime arg not comptime-known"); comptime_args[ai] = arg_refs[ai]; } else { comptime_args[ai] = IP_INDEX_NONE; if (ipFuncTypeParamBit(owner_noalias, ai)) inst_noalias |= (1u << rt_n); emit_args[rt_n] = arg_refs[ai]; rt_tys[rt_n] = resolved_param_ty[ai]; rt_n++; } } uint32_t func_instance = ipGetFuncInstance(ip, func_decl, ret_ty, rt_tys, rt_n, comptime_args, total_params, inst_noalias, &zcu->anon_name_counter); // Sema.analyzeCall: `"zcu.funcInfo(func_instance).maxBranchQuota(ip, io, // sema.branch_quota);"`. ipFuncInstanceMaxBranchQuota(ip, func_instance, zcu->sema_branch->quota); if (comptime_args != comptime_args_small) free(comptime_args); if (rt_tys != rt_tys_small) free(rt_tys); FuncInstanceCall result; result.call_operand = func_instance; result.ret_ty = ipFuncTypeReturnType(ip, ipFuncInstanceType(ip, func_instance)); result.emit_nargs = rt_n; result.pending_entry = func_instance | PENDING_INSTANCE_FLAG; return result; } // `Scalar`'s float half: `"} else { if (lhs_val.isUndef(zcu)) return // lhs_val; if (rhs_val.isUndef(zcu)) return rhs_val; return floatAdd(sema, // lhs_val, rhs_val, ty); }"` (src/Sema/arith.zig `addScalar` and its // siblings), with `divScalar`'s and `modRemScalar`'s own float arms. The rule // itself lives in arith.c beside the integer one, because upstream // keeps both halves in that single file; this is the InternPool side of it: // resolve the peer type, decode the pair at that type's width, fold, // re-intern. // // A width with no exact carrier here aborts loudly instead of answering at // f64, which would be a silently wrong value rather than a missing one. static uint32_t semaFoldComptimeFloatBinOp(Zcu* zcu, ZirInstTag tag, uint32_t lhs, uint32_t lhs_ty, uint32_t rhs, uint32_t rhs_ty, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; if (lhs >= ip->items_len || rhs >= ip->items_len) return IP_INDEX_NONE; // `Sema.analyzeArithmetic`'s `resolvePeerTypes` -- the one peer rule, // `semaTypeofPeerResolve`, not a second ladder here: its `.fixed_float` // arm is the one that recreates the wider type for a mixed `f32`/`f64` // pair. It is asked once one // operand is a float and the other a float or a `comptime_int` (the // `.fixed_float` strategy's other admissible peer); any other pair is // `Scalar`'s integer half and declines to the caller's integer path. // `sema.typeOf(lhs)`: an untyped literal operand carries no recorded // type, so a comptime value answers with its own. if (lhs_ty == IP_INDEX_NONE) lhs_ty = ipTypeOf(ip, lhs); if (rhs_ty == IP_INDEX_NONE) rhs_ty = ipTypeOf(ip, rhs); uint32_t peer_bits; bool lhs_float = ztSimpleFloatBits(lhs_ty, &peer_bits) || lhs_ty == ZIR_REF_COMPTIME_FLOAT_TYPE; bool rhs_float = ztSimpleFloatBits(rhs_ty, &peer_bits) || rhs_ty == ZIR_REF_COMPTIME_FLOAT_TYPE; if (!(lhs_float || rhs_float) || !(lhs_float || lhs_ty == ZIR_REF_COMPTIME_INT_TYPE) || !(rhs_float || rhs_ty == ZIR_REF_COMPTIME_INT_TYPE)) return IP_INDEX_NONE; // `peer_vals` is upstream's `[]?Value`: both operands are comptime // values here, so each peer carries its own. uint32_t peer_tys[2] = { lhs_ty, rhs_ty }; uint32_t peer_vals[2] = { lhs, rhs }; uint32_t ty = semaTypeofPeerResolve(zcu, peer_tys, peer_vals, 2); uint32_t width; // Not a float type at all: this is the integer half's pair, declined so // the caller's own integer path runs. A `comptime_float` peer is // `Type.floatBits` 128 (`"comptime_float => 128"`). if (ty == ZIR_REF_COMPTIME_FLOAT_TYPE) width = 128; else if (!ztSimpleFloatBits(ty, &width)) return IP_INDEX_NONE; // `"const casted_lhs = try sema.coerce(block, resolved_type, lhs, // lhs_src); const casted_rhs = try sema.coerce(block, resolved_type, rhs, // rhs_src);"` -- a `comptime_int` peer becomes a value of the float type // (coerceExtra's int -> float arm, with its exactness check) before // either operand is read at the width. if (lhs_ty == ZIR_REF_COMPTIME_INT_TYPE) lhs = semaCoerceComptime(zcu, ip, lhs, ty); if (rhs_ty == ZIR_REF_COMPTIME_INT_TYPE) rhs = semaCoerceComptime(zcu, ip, rhs, ty); CrtU128 lb, rb; if (!valFloatOperandBits(ip, lhs, width, &lb) || !valFloatOperandBits(ip, rhs, width, &rb)) return IP_INDEX_NONE; // `arith.divScalar`'s float arm: `"const allow_div_zero = switch (op) { // .div, .div_trunc, .div_floor => ty.toIntern() != .comptime_float_type // and block.float_mode == .strict, .div_exact => false }; if // (!allow_div_zero) { if (rhs_val.eqlScalarNum(.zero_comptime_int, zcu)) // return sema.failWithDivideByZero(block, rhs_src); }"` -- a // `comptime_float` has no IEEE infinity to divide into (`.div_exact` is // arith.c's own ARITH_DIV_ZERO below). -0 is zero too. if (ty == ZIR_REF_COMPTIME_FLOAT_TYPE && (tag == ZIR_INST_DIV || tag == ZIR_INST_DIV_TRUNC || tag == ZIR_INST_DIV_FLOOR || tag == ZIR_INST_DIV_CEIL) && rb.lo == 0 && (rb.hi & ~(UINT64_C(1) << 63)) == 0) COMPILE_ERROR_UPSTREAM("analyzeArithmetic: comptime division by zero " "here causes illegal behavior"); CrtU128 res; switch (arithFloatBinOp(tag, lb, rb, width, &res)) { case ARITH_OK: break; case ARITH_REMAINDER: COMPILE_ERROR_UPSTREAM( "analyzeArithmetic: comptime exact division produced remainder"); case ARITH_DIV_ZERO: COMPILE_ERROR_UPSTREAM("analyzeArithmetic: comptime division by zero"); case ARITH_FLOAT_NO_CARRIER: // A width with no ported compiler_rt routine for this op aborts // loudly instead of answering at f64, which would be a silently // wrong value rather than a missing one. UNIMPLEMENTED("analyzeArithmetic: bin_op comptime - f80/f128 " "operation has no compiler_rt routine yet"); default: // ARITH_UNSUPPORTED_OP return IP_INDEX_NONE; } if (out_ty != NULL) *out_ty = ty; return semaFloatFromBits(ip, ty, width, res); } // arith.zig:intValueAa // `"@memset(buf, 0xAA); ... result_bigint.readTwosComplement(buf, info.bits, // ...); return pt.intValue_big(ty, result_bigint.toConst());"`. static uint32_t intValueAa(InternPool* ip, uint32_t ty) { if (ty == ZIR_REF_U0_TYPE) return ipGetIntSmall(ip, ty, 0); uint32_t bits; bool is_signed; if (!ztIntInfo(ip, ty, &bits, &is_signed)) UNIMPLEMENTED("intValueAa: not a fixed-width integer type"); uint32_t nbytes = (bits + 7u) / 8u; uint8_t* buf = zig1Malloc(nbytes); memset(buf, 0xAA, nbytes); uint32_t cap = bigIntCalcTwosCompLimbCount(bits); uint64_t* limbs = zig1Malloc((size_t)cap * sizeof(uint64_t)); BigIntMutable r = { limbs, cap, 0, true }; if (!bigIntMutableReadTwosComplement(&r, buf, bits, is_signed)) UNREACHABLE("intValueAa: limb buffer too short"); uint32_t res = ipGetInt(ip, ty, bigIntMutableToConst(r)); free(limbs); free(buf); return res; } // (part of) Sema.zig:analyzeArithmetic, arith.zig:divScalar // `Sema.analyzeArithmetic`'s `allow_undef` column (`!is_int` for `.add`, // `.sub`, `.mul`; `true` for the wrapping and saturating ops) and the undef // guards of `arith.divScalar`/`modRemScalar`/`shlScalar`/`shrScalar`, under // the default strict float mode: whether an `undefined` operand yields // `undefined` at `ty` rather than `failWithUseOfUndef`. static bool semaArithAllowsUndef( const InternPool* ip, ZirInstTag tag, uint32_t ty) { uint32_t scalar = ipScalarType(ip, ty); uint32_t bits; bool is_signed; bool is_int = scalar == ZIR_REF_COMPTIME_INT_TYPE || ztIntInfo(ip, scalar, &bits, &is_signed); switch (tag) { case ZIR_INST_ADDWRAP: case ZIR_INST_SUBWRAP: case ZIR_INST_MULWRAP: case ZIR_INST_ADD_SAT: case ZIR_INST_SUB_SAT: case ZIR_INST_MUL_SAT: case ZIR_INST_SHL_SAT: return true; case ZIR_INST_ADD: case ZIR_INST_ADD_UNSAFE: case ZIR_INST_SUB: case ZIR_INST_MUL: return !is_int; case ZIR_INST_DIV: case ZIR_INST_DIV_TRUNC: case ZIR_INST_DIV_FLOOR: case ZIR_INST_DIV_CEIL: return !is_int && scalar != ZIR_REF_COMPTIME_FLOAT_TYPE; case ZIR_INST_MOD: case ZIR_INST_REM: case ZIR_INST_MOD_REM: return !is_int; default: // div_exact, shl, shl_exact, shr, shr_exact return false; } } // (part of) Sema.zig:analyzeArithmetic, arith.zig:add // `Sema.analyzeArithmetic`'s both-comptime branch for one scalar operand // pair: `"const result_val = switch (zir_tag) { .add, .add_unsafe => try // arith.add(sema, block, resolved_type, lhs_val, rhs_val, ...), ... };"`. // Lifted out of the ZIR bin_op arm unchanged so the vector fold below can // reuse it: every `arith.` upstream sends `.vector` to the same per-lane // `Scalar` this is, so there is one rule, not two. `Scalar` is also // where upstream's int/float split lives (`"if (is_int) { ... } else { ... // }"`), so the float arm above is consulted first here. Returns the interned // result and writes its type to `*out_ty`; the shapes it does not model abort // loudly, exactly where they did at the single site it came from. static uint32_t semaFoldComptimeScalarBinOp(Zcu* zcu, ZirInstTag tag, uint32_t lhs_val, uint32_t lhs_ty_raw, uint32_t rhs_val, uint32_t rhs_ty_raw, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; // `intBitwiseAnd` / `intBitwiseOr` / `intBitwiseXor`'s first line, which // `bitwiseBinScalar` reaches whenever the operand type is `bool` -- a // `@Vector(N, bool)` lane, or a scalar `a & b` on bools: `"if // (ty.toIntern() == .bool_type) return .makeBool(lhs.toBool() and // rhs.toBool());"`. It is answered in booleans, never through the integer // carrier, which has no word for `true`. if (lhs_ty_raw == ZIR_REF_BOOL_TYPE && rhs_ty_raw == ZIR_REF_BOOL_TYPE && (tag == ZIR_INST_BIT_AND || tag == ZIR_INST_BIT_OR || tag == ZIR_INST_XOR)) { uint32_t bres = arithBitwiseBinBool(ip, tag, lhs_val, rhs_val); if (bres == IP_INDEX_NONE) UNIMPLEMENTED("analyzeArithmetic: bin_op comptime - bool operand " "is not a comptime-known bool"); if (out_ty != NULL) *out_ty = ZIR_REF_BOOL_TYPE; return bres; } // `Scalar`'s `is_int == false` half. if (!semaIsAirInstRef(lhs_val) && !semaIsAirInstRef(rhs_val)) { uint32_t f_res = semaFoldComptimeFloatBinOp( zcu, tag, lhs_val, lhs_ty_raw, rhs_val, rhs_ty_raw, out_ty); if (f_res != IP_INDEX_NONE) return f_res; } // `Scalar`'s undef arm: where the op allows it, an `undefined` // operand makes the result `undefined` at the resolved type -- the peer // type, or the shifted operand's for a shift; `.shl_sat` of an undef // amount returns the amount. uint32_t lhs_ct = semaOperandInternIndex(lhs_val); uint32_t rhs_ct = semaOperandInternIndex(rhs_val); bool lhs_undef = ipIsUndef(ip, lhs_ct) && ipIndexIsType(ip, ipTypeOf(ip, lhs_ct)); bool rhs_undef = ipIsUndef(ip, rhs_ct) && ipIndexIsType(ip, ipTypeOf(ip, rhs_ct)); if (tag == ZIR_INST_SHL_SAT && rhs_undef && !lhs_undef) { if (out_ty != NULL) *out_ty = ipTypeOf(ip, rhs_ct); return rhs_ct; } if (lhs_undef || rhs_undef) { uint32_t result_ty = semaArithResultType(zcu, tag, ipTypeOf(ip, lhs_ct), lhs_ct, ipTypeOf(ip, rhs_ct), rhs_ct); // `sema.coerce` of both operands to the resolved type, lhs first; a // shift amount is not coerced. if (!arithTagIsShift(tag)) { lhs_ct = lhs_undef ? ipGetUndef(ip, result_ty) : semaBinCoerceComptimeOperand(ip, lhs_ct, result_ty); rhs_ct = rhs_undef ? ipGetUndef(ip, result_ty) : semaBinCoerceComptimeOperand(ip, rhs_ct, result_ty); } bool bitwise = tag == ZIR_INST_BIT_AND || tag == ZIR_INST_BIT_OR || tag == ZIR_INST_XOR; if (!bitwise && !semaArithAllowsUndef(ip, tag, result_ty)) COMPILE_ERROR_UPSTREAM( "analyzeArithmetic: comptime use of undefined value " "here causes illegal behavior"); // `bitwiseBinScalar`: `xor` or two undefined operands are undefined; // the one undefined operand of `and`/`or` becomes `intValueAa`. if (!bitwise || tag == ZIR_INST_XOR || (lhs_undef && rhs_undef)) { if (out_ty != NULL) *out_ty = result_ty; return ipGetUndef(ip, result_ty); } if (lhs_undef) lhs_ct = intValueAa(ip, result_ty); else rhs_ct = intValueAa(ip, result_ty); lhs_val = lhs_ct; rhs_val = rhs_ct; } // Both operands are comptime: fold at compile time. `Scalar`'s // `is_int == true` half is one rule and arith.c holds it, so // this frame only reads the operands and hands them over. uint64_t lhs_space[2], rhs_space[2]; BigIntConst lhs_big, rhs_big; uint32_t lhs_ct_ty, rhs_ct_ty; if (!valBinReadComptimeBigInt(ip, lhs_val, lhs_space, &lhs_big, &lhs_ct_ty) || !valBinReadComptimeBigInt( ip, rhs_val, rhs_space, &rhs_big, &rhs_ct_ty)) UNIMPLEMENTED( "analyzeArithmetic: bin_op comptime - operand not a readable int"); uint32_t ct_res = semaComptimeIntBinOpBig( zcu, tag, lhs_big, lhs_ct_ty, lhs_val, rhs_big, rhs_ct_ty, rhs_val); if (ct_res == IP_INDEX_NONE) UNIMPLEMENTED( "analyzeArithmetic: bin_op comptime - result type not supported"); if (out_ty != NULL) *out_ty = ipTypeOf(ip, ct_res); return ct_res; } void semaEnumImplicitValues( Zcu* zcu, uint32_t int_ty, uint32_t* values, uint32_t n) { for (uint32_t i = 0; i < n; i++) if (values[i] == IP_INDEX_NONE) values[i] = i == 0 ? ipGetIntSmall(&zcu->intern_pool, int_ty, 0) : incrementDefinedInt(zcu, int_ty, values[i - 1]); } // `checkIntOrVector`, then (`zirByteSwap` only) "@byteSwap requires the // number of bits to be evenly divisible by 8". static void semaCheckBitPermuteOperand( const InternPool* ip, ZirInstTag tag, uint32_t operand_ty) { uint32_t bits; bool is_signed; if (!ztIntInfo(ip, ipScalarType(ip, operand_ty), &bits, &is_signed)) COMPILE_ERROR_UPSTREAM("@byteSwap/@bitReverse: expected integer or " "vector"); if (tag == ZIR_INST_BYTE_SWAP && bits % 8u != 0) COMPILE_ERROR_UPSTREAM("@byteSwap requires the number of bits to be " "evenly divisible by 8"); } // (part of) Sema.zig:zirAbs // `Sema.zirAbs`'s result type (src/Sema.zig): `"const result_ty = // switch (scalar_ty.zigTypeTag(zcu)) { .comptime_float, .float, .comptime_int // => operand_ty, .int => if (scalar_ty.isSignedInt(zcu)) try // operand_ty.toUnsigned(pt) else return operand, else => return sema.fail(...) // }"`. `*out_identity` is that `return operand`: an unsigned integer (or // vector of them) is its own magnitude and emits nothing at all. // IP_INDEX_NONE is upstream's compile error ("expected integer, float, or // vector of either integers or floats"). static uint32_t semaAbsResultType( InternPool* ip, uint32_t operand_ty, bool* out_identity) { *out_identity = false; uint32_t scalar_ty = ipScalarType(ip, operand_ty); uint32_t bits; bool is_signed; if (ztIntInfo(ip, scalar_ty, &bits, &is_signed)) { if (!is_signed) { *out_identity = true; return operand_ty; } return ztTypeToUnsigned(ip, operand_ty); } if (scalar_ty == ZIR_REF_COMPTIME_INT_TYPE || scalar_ty == ZIR_REF_COMPTIME_FLOAT_TYPE || ztSimpleFloatBits(scalar_ty, &bits)) return operand_ty; return IP_INDEX_NONE; } // (part of) arith.zig:add, PerThread.zig:aggregateValue // `arith.add` / `sub` / `mul` (and their wrap and saturating siblings) // dispatch a `.vector` type to the same scalar routine per lane and // re-aggregate: `"const elem_vals = try sema.arena.alloc(InternPool.Index, // len); for (elem_vals, 0..) |*result_elem, elem_idx| { const lhs_elem = try // lhs_val.elemValue(pt, elem_idx); const rhs_elem = try rhs_val.elemValue(pt, // elem_idx); result_elem.* = (try addScalar(sema, block, elem_ty, lhs_elem, // rhs_elem, src, lhs_src, rhs_src, is_int, elem_idx)).toIntern(); } return // pt.aggregateValue(ty, elem_vals);"`. Both operands have already been // coerced to the one resolved vector type, so a disagreement here is a shape // this frame cannot name and it declines. Returns IP_INDEX_NONE when the // operands are not two same-typed comptime vectors. static uint32_t semaFoldComptimeVectorBinOp( Zcu* zcu, ZirInstTag tag, uint32_t lhs, uint32_t rhs, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; if (lhs >= ip->items_len || rhs >= ip->items_len) return IP_INDEX_NONE; uint32_t vec_ty = ipTypeOf(ip, lhs); uint32_t rhs_vec_ty = ipTypeOf(ip, rhs); IpArrayInfo ai, rai; if (vec_ty == IP_INDEX_NONE || rhs_vec_ty == IP_INDEX_NONE || ipArrayInfo(ip, vec_ty, &ai) != IP_ARR_OK || !ai.is_vector || ipArrayInfo(ip, rhs_vec_ty, &rai) != IP_ARR_OK || !rai.is_vector || ai.len != rai.len) return IP_INDEX_NONE; // `arith.shr` is the one operation whose two operands are vectors of // different element types -- it takes `rhs_ty` alongside `lhs_ty` and // hands `shrScalar` both children (`"const lhs_elem_ty = // lhs_ty.childType(zcu); const rhs_elem_ty = rhs_ty.childType(zcu);"`), // because a shift amount is the element's `Log2Int`. `arith.shl` reads // only `lhs_ty` and takes the amount as a plain value, so the same // relaxation covers it. Every other op here has had both operands // coerced to one type by `Sema.analyzeArithmetic` before the fold, and a // disagreement is a shape this frame cannot name: it declines. if (vec_ty != rhs_vec_ty && !arithTagIsShift(tag)) return IP_INDEX_NONE; uint32_t* elems = zig1Malloc((ai.len ? (size_t)ai.len : 1u) * sizeof(*elems)); for (uint32_t i = 0; i < ai.len; i++) { uint32_t le = valElemValue( ip, lhs, i, "analyzeArithmetic: comptime vector lane"); uint32_t re = valElemValue( ip, rhs, i, "analyzeArithmetic: comptime vector lane"); // `"result_elem.* = (try addScalar(sema, block, ELEM_TY, lhs_elem, // rhs_elem, ...)).toIntern();"` -- the lane type is the VECTOR's // child, not whatever type the lane value happens to carry. A float // lane can still be wearing the untyped `comptime_float` shape, and // `toFloat` is what gives it the child's width. uint32_t elem_ty; elems[i] = semaFoldComptimeScalarBinOp( zcu, tag, le, ai.child, re, rai.child, &elem_ty); } uint32_t res = ipGetAggregate(ip, vec_ty, elems, ai.len); free(elems); if (out_ty != NULL) *out_ty = vec_ty; return res; } // The `arith.WithOverflow` each overflow builtin folds through. static ZirInstTag semaOverflowArithZirTag(uint16_t ext_op) { switch (ext_op) { case ZIR_EXT_ADD_WITH_OVERFLOW: return ZIR_INST_ADD; case ZIR_EXT_SUB_WITH_OVERFLOW: return ZIR_INST_SUB; case ZIR_EXT_SHL_WITH_OVERFLOW: return ZIR_INST_SHL; default: return ZIR_INST_MUL; } } // (part of) Sema.zig:zirOverflowArithmetic // `Sema.zirOverflowArithmetic`'s `dest_ty`: the LHS type for // `@shlWithOverflow`, else `resolvePeerTypes` over both operands, then // "expected vector of integers or integer tag type" unless its scalar is a // sized integer (a pair of literals is a compile error). static uint32_t semaOverflowArithDestType(Zcu* zcu, bool is_shl, uint32_t lhs_ty, uint32_t lhs_val, uint32_t rhs_ty, uint32_t rhs_val) { const InternPool* ip = &zcu->intern_pool; uint32_t tys[2] = { lhs_ty, rhs_ty }; uint32_t vals[2] = { lhs_val, rhs_val }; uint32_t ty = is_shl ? lhs_ty : semaTypeofPeerResolve(zcu, tys, vals, 2); uint32_t bits; bool is_signed; if (!ztIntInfo(ip, ipScalarType(ip, ty), &bits, &is_signed)) COMPILE_ERROR_UPSTREAM("overflow arithmetic: expected vector of " "integers or integer tag type"); return ty; } // Sema.zig:zirOverflowArithmetic, arith.zig:addWithOverflow, // PerThread.zig:aggregateValue // `Sema.zirOverflowArithmetic`'s fully-comptime // branch: `"const result = try arith.addWithOverflow(sema, dest_ty, lhs_val, // rhs_val);"` and its two siblings, whose `.vector` arm maps // `WithOverflowScalar` over the lanes and re-aggregates -- `"return .{ // .overflow_bit = try pt.aggregateValue(try pt.vectorType(.{ .len = // @intCast(overflow_bits.len), .child = .u1_type }), overflow_bits), // .wrapped_result = try pt.aggregateValue(ty, wrapped_results) };"`. The pair // is returned as the `{ dest_ty, overflow_ty // }` tuple value `pt.overflowArithmeticTupleType(dest_ty)` names. // // The zero/one short-circuits above that call (`"if (!lhs_val.isUndef(zcu) // and lhs_val.compareAllWithZero(.eq, zcu)) break :result .{ .overflow_bit = // ... .zero_u1, .inst = rhs };"`) are value-identical to this fold for // defined operands -- `0 + x`, `0 * x` and `1 * x` neither overflow nor // change the other operand -- so they are not restated here; they exist // upstream to keep an UNDEF operand's result defined, and an UNDEF operand // is declined loudly by `arithWithOverflowScalar`'s reader. static uint32_t semaFoldWithOverflow(InternPool* ip, ZirInstTag tag, uint32_t dest_ty, uint32_t lhs, uint32_t rhs) { IpArrayInfo ai; uint32_t ov_ty; uint32_t tuple_ty = zptOverflowArithmeticTupleType(ip, dest_ty, &ov_ty); uint32_t res_val; uint32_t ov_val; if (dest_ty != IP_INDEX_NONE && ipArrayInfo(ip, dest_ty, &ai) == IP_ARR_OK && ai.is_vector) { uint32_t* wrapped = zig1Malloc(((size_t)ai.len + 1u) * sizeof(*wrapped)); uint32_t* ov_bits = zig1Malloc(((size_t)ai.len + 1u) * sizeof(*ov_bits)); for (uint32_t i = 0; i < ai.len; i++) { uint32_t le = valElemValue( ip, lhs, i, "zirOverflowArithmetic: comptime vector lane"); uint32_t re = valElemValue( ip, rhs, i, "zirOverflowArithmetic: comptime vector lane"); wrapped[i] = arithWithOverflowScalar( ip, tag, ai.child, le, re, &ov_bits[i]); } res_val = ipGetAggregate(ip, dest_ty, wrapped, ai.len); ov_val = ipGetAggregate(ip, ov_ty, ov_bits, ai.len); free(wrapped); free(ov_bits); } else { res_val = arithWithOverflowScalar(ip, tag, dest_ty, lhs, rhs, &ov_val); } const uint32_t elems[2] = { res_val, ov_val }; return ipGetAggregate(ip, tuple_ty, elems, 2); } // Sema.zig:maybeConstantUnaryMath, Value.zig:sqrt, Value.zig:sin // `Sema.maybeConstantUnaryMath` with its `eval` (`Value.sqrt`, `Value.sin`, // ..., `Value.round`): an undef operand is `undefRef(result_ty)`, a vector // is the scalar rule per lane (`val.elemValue(pt, i)`), and the scalar rule // is `arithFloatUnary`, the one the interpreter's runtime ops ask. uint32_t maybeConstantUnaryMath( Zcu* zcu, ZirInstTag tag, uint32_t val, uint32_t ty) { InternPool* ip = &zcu->intern_pool; if (ipIsUndef(ip, val)) return ipGetUndef(ip, ty); IpArrayInfo vec; if (ipArrayInfo(ip, ty, &vec) == IP_ARR_OK && vec.is_vector) { uint32_t* elems = zig1Malloc(((size_t)vec.len + 1u) * sizeof(*elems)); for (uint32_t i = 0; i < vec.len; i++) elems[i] = maybeConstantUnaryMath(zcu, tag, valElemValue(ip, val, i, "float unary math: lane"), vec.child); uint32_t res = ipGetAggregate(ip, ty, elems, vec.len); free(elems); return res; } // `Type.floatBits`: `"comptime_float => 128"`. uint32_t width = 128; CrtU128 bits, out; if ((ty != ZIR_REF_COMPTIME_FLOAT_TYPE && !ztSimpleFloatBits(ty, &width)) || !valFloatOperandBits(ip, val, width, &bits) || arithFloatUnary(tag, bits, width, false, &out) != ARITH_OK) UNIMPLEMENTED( "float unary math: comptime operand has no exact carrier"); return semaFloatFromBits(ip, ty, width, out); } static ZirInstTag semaRoundOpUnaryTag(uint16_t mode) { static const ZirInstTag tags[] = { [ZIR_ROUND_OP_ROUND] = ZIR_INST_ROUND, [ZIR_ROUND_OP_FLOOR] = ZIR_INST_FLOOR, [ZIR_ROUND_OP_CEIL] = ZIR_INST_CEIL, [ZIR_ROUND_OP_TRUNC] = ZIR_INST_TRUNC, }; if (mode >= sizeof(tags) / sizeof(tags[0])) UNIMPLEMENTED("round_op: unknown mode"); return tags[mode]; } // Sema.zig:zirRoundCast, Value.zig:round // `Sema.zirRoundCast` with a comptime operand and a float destination: // `"const coerced_operand = try sema.coerce(block, dest_ty, operand, // operand_src); const result_ref = switch (mode) { .round => try // sema.maybeConstantUnaryMath(coerced_operand, dest_ty, Value.round), ..."`. static uint32_t semaRoundCastComptime( Zcu* zcu, uint16_t mode, uint32_t operand, uint32_t dest_ty) { uint32_t coerced = semaCoerceComptime(zcu, &zcu->intern_pool, operand, dest_ty); if (coerced == IP_INDEX_NONE) UNIMPLEMENTED("round_op: comptime operand not coercible"); return maybeConstantUnaryMath( zcu, semaRoundOpUnaryTag(mode), coerced, dest_ty); } // (part of) Sema.zig:zirModRem // `Sema.zirModRem`'s `"const lhs_maybe_negative = a: { if // (lhs_scalar_ty.isUnsignedInt(zcu)) break :a false; const lhs_val = // maybe_lhs_val orelse break :a true; if (lhs_val.compareAllWithZero(.gte, // zcu)) break :a false; break :a true; };"`. The type answers first, then the // whole value (every lane of a vector); a runtime signed operand cannot be // proven and answers yes. static bool semaModRemMaybeNegative( InternPool* ip, uint32_t ty, uint32_t val) { uint32_t scalar = ty; IpArrayInfo vec; if (scalar != IP_INDEX_NONE && ipArrayInfo(ip, scalar, &vec) == IP_ARR_OK && vec.is_vector) scalar = vec.child; uint32_t bits; bool is_signed; if (scalar != IP_INDEX_NONE && ztIntInfo(ip, scalar, &bits, &is_signed) && !is_signed) return false; if (val >= ip->items_len) return true; return !valCompareAllWithZero(ip, val, false); } // Sema.zig:zirModRem, arith.zig:modRem // `Sema.zirModRem`'s own rejection, which `arith.modRem` does not carry (it // folds a plain `@rem`): with a comptime-known result, `"if // (lhs_maybe_negative or rhs_maybe_negative) { if // (!result.compareAllWithZero(.eq, zcu)) { ... failWithModRemNegative ... } // }"`; with a runtime one, `"// Result not comptime-known, so floats and // signed integers are illegal due to mod/rem ambiguity"` and the same // rejection unconditionally. Pass IP_INDEX_NONE for `result` to ask the // runtime form. Every test reads the whole value, so `@Vector(2, i32){ 5, -6 } // % @splat(3)` is rejected as a unit -- it is not a per-lane question, and // asking it lane by lane would answer `{ 2, 0 }` for a program upstream // refuses. static void semaCheckModRemNegative(InternPool* ip, uint32_t lhs_ty, uint32_t lhs, uint32_t rhs_ty, uint32_t rhs, uint32_t result) { if (!semaModRemMaybeNegative(ip, lhs_ty, lhs) && !semaModRemMaybeNegative(ip, rhs_ty, rhs)) return; if (result != IP_INDEX_NONE && valCompareAllWithZero(ip, result, true)) return; COMPILE_ERROR_UPSTREAM( "analyzeArithmetic: `%` with a negative operand is ambiguous " "between @mod and @rem"); } // (part of) InternPool.zig:typeOf // The type of one comparison operand, or IP_INDEX_NONE when this frontend // cannot name it. `sema.typeOf` is total upstream; here a runtime operand's // type comes from the analysed ZIR instruction and a comptime one's from // InternPool.typeOf (ipTypeOf), which already answers IP_INDEX_NONE for the // bookkeeping items zig1 also routes through `inst_values`. The total // variant is semaResolveRefValType. static uint32_t semaCmpOperandTypeOrNone(const InternPool* ip, const uint32_t* inst_types, uint32_t ref, uint32_t val) { if (semaIsAirInstRef(val)) { return ref < ZIR_REF_START_INDEX ? IP_INDEX_NONE : inst_types[ref - ZIR_REF_START_INDEX]; } return ipTypeOf(ip, val); } // (part of) Sema.zig:compareScalar, PerThread.zig:getCoerced, Value.zig:eql // Sema.compareScalar for one vector lane: `"const coerced_lhs = try // pt.getCoerced(lhs, ty); ... switch (op) { .eq => return // Value.eql(coerced_lhs, coerced_rhs, ty, zcu), .neq => return // !Value.eql(...), else => return Value.compareHetero(...) }"`. Both lanes // already carry the resolved element type (cmpVector coerced both operands to // one peer), so `getCoerced` is the identity and the whole of it is the // ordering of two same-typed integers, or bool equality. bool semaCmpVectorLane( const InternPool* ip, ZirInstTag op, uint32_t lhs, uint32_t rhs) { uint64_t lspace[2], rspace[2]; BigIntConst lbig, rbig; if (valToBigInt(ip, lhs, lspace, &lbig) && valToBigInt(ip, rhs, rspace, &rbig)) return valCmpApplyOrder(op, bigIntOrder(lbig, rbig)); if (semaTagIsFloat(ip->item_tags[lhs]) || semaTagIsFloat(ip->item_tags[rhs])) { // Sema.compareScalar's first test, above its eq/neq arm and for the // reason its own comment gives: `"// Equality comparisons of signed // zero and NaN need to use floating point semantics"` / `"if // (coerced_lhs.isFloat(zcu) or coerced_rhs.isFloat(zcu)) return // Value.compareHetero(coerced_lhs, op, coerced_rhs, zcu);"` // (src/Sema.zig). Interned identity would answer `-0.0 == 0.0` false // and `nan == nan` true; compareHetero answers both the other way. // Same two helpers the scalar comptime fold uses, so one float rule. if (valIsNan(ip, lhs) || valIsNan(ip, rhs)) return op == ZIR_INST_CMP_NEQ; return valCmpNumApply(op, valCmpFloatOperand(ip, lhs, "cmpVector: float lane unsupported"), valCmpFloatOperand(ip, rhs, "cmpVector: float lane unsupported")); } bool lbool = lhs == ZIR_REF_BOOL_TRUE || lhs == ZIR_REF_BOOL_FALSE; bool rbool = rhs == ZIR_REF_BOOL_TRUE || rhs == ZIR_REF_BOOL_FALSE; if (lbool && rbool) { // Ordering a bool lane is legal: Sema.cmpVector has no // isSelfComparable test (that one is analyzeCmp's peer-type tail, // which a vector comparison never reaches), and compareScalar's // non-eq arm hands `<` to Value.compareHetero -> `Value.order`, whose // non-float side is `"lhs_bigint.order(rhs_bigint)"` over a bool's // 0/1. The runtime half of this same feature always agreed; this is // the comptime half being made the same rule. return valCmpApplyU(op, lhs == ZIR_REF_BOOL_TRUE ? 1u : 0u, rhs == ZIR_REF_BOOL_TRUE ? 1u : 0u); } UNIMPLEMENTED("cmpVector: unsupported comptime lane value"); ZIG1_TCC_RETURN(false); } // `analyzeMinMax`'s TypeStrat, in priority order: a lower `kind` overrides. typedef enum { MM_FLOAT = 0, MM_COMPTIME_FLOAT = 1, MM_INT = 2, MM_NONE = 3, } MmStratKind; typedef struct { MmStratKind kind; uint32_t float_ty; bool all_comptime_int; uint32_t result_min, result_max, operand_min, operand_max; } MmStrat; // Sema.zig:analyzeMinMax // Sema.analyzeMinMax. `ops[k]` is an AIR ref (runtime) or an InternPool // value (comptime-known); `op_tys[k]` is a runtime operand's type. With // `insts == NULL` (ctEvalBody) every operand must be comptime-known. // Returns the result ref or value; `*out_ty` is the result type. static uint32_t analyzeMinMax(Zcu* zcu, bool is_max, const uint32_t* ops, const uint32_t* op_tys, uint32_t n, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; bool want_min = !is_max; uint32_t* tys = ARR_INIT(uint32_t, n); for (uint32_t k = 0; k < n; k++) { bool ct = !semaIsAirInstRef(ops[k]); if (ct && (ops[k] >= ip->items_len)) UNIMPLEMENTED( "analyzeMinMax: min/max - unreadable comptime operand"); if (!ct && insts == NULL) UNIMPLEMENTED( "analyzeMinMax: min/max - runtime operand at comptime"); tys[k] = ct ? ipComptimeValueType(ip, ops[k]) : op_tys[k]; } if (n == 1) { *out_ty = tys[0]; free(tys); return ops[0]; } // Vector length and numeric checks. IpArrayInfo first_vec; bool is_vec = ipArrayInfo(ip, tys[0], &first_vec) == IP_ARR_OK && first_vec.is_vector; uint32_t vec_len = is_vec ? first_vec.len : 0; for (uint32_t k = 0; k < n; k++) { IpArrayInfo vi; bool v = ipArrayInfo(ip, tys[k], &vi) == IP_ARR_OK && vi.is_vector; if (v != is_vec || (v && vi.len != vec_len)) COMPILE_ERROR_UPSTREAM("analyzeMinMax: vector operand mismatch"); } MmStrat cur = { .kind = MM_NONE }; for (uint32_t k = 0; k < n; k++) { uint32_t sty = ipScalarType(ip, tys[k]); bool ct = !semaIsAirInstRef(ops[k]); uint32_t val = ct ? ops[k] : IP_INDEX_NONE; MmStrat want = { .kind = MM_NONE }; uint32_t fbits, ibits; bool isigned; if (sty == ZIR_REF_COMPTIME_INT_TYPE) { if (!ct) UNIMPLEMENTED( "analyzeMinMax: min/max - runtime comptime_int operand"); if (!ipIsUndef(ip, val)) { want.kind = MM_INT; want.all_comptime_int = true; want.result_min = want.result_max = val; want.operand_min = want.operand_max = val; } } else if (sty == ZIR_REF_COMPTIME_FLOAT_TYPE) { want.kind = MM_COMPTIME_FLOAT; } else if (ztSimpleFloatBits(sty, &fbits)) { want.kind = MM_FLOAT; want.float_ty = sty; } else if (ztIntInfo(ip, sty, &ibits, &isigned)) { // "If the *value* is comptime-known, we will use that to get // tighter bounds." uint32_t mn = IP_INDEX_NONE, mx = IP_INDEX_NONE; if (ct && is_vec) { mn = semaAggregateElemValueOrUndef( ip, val, sty, 0, "analyzeMinMax: vector lane"); mx = mn; for (uint32_t e = 1; e < vec_len; e++) { uint32_t ev = semaAggregateElemValueOrUndef( ip, val, sty, e, "analyzeMinMax: vector lane"); mn = valNumberMinMax(ip, mn, ev, true); mx = valNumberMinMax(ip, mx, ev, false); } if (ipIsUndef(ip, mn) || ipIsUndef(ip, mx)) mn = mx = IP_INDEX_NONE; } else if (ct && !ipIsUndef(ip, val)) { mn = mx = val; } if (mn == IP_INDEX_NONE) ztIntTypeMinMax(ip, sty, &mn, &mx); want.kind = MM_INT; want.all_comptime_int = false; want.result_min = want.operand_min = mn; want.result_max = want.operand_max = mx; } else { COMPILE_ERROR_UPSTREAM("analyzeMinMax: expected number"); } if (want.kind < cur.kind) { cur = want; } else if (want.kind == cur.kind) { if (cur.kind == MM_FLOAT) { uint32_t cb = 0, wb = 0; ztSimpleFloatBits(cur.float_ty, &cb); ztSimpleFloatBits(want.float_ty, &wb); if (wb > cb || (wb == cb && cur.float_ty == ZIR_REF_C_LONGDOUBLE_TYPE && want.float_ty != ZIR_REF_C_LONGDOUBLE_TYPE)) cur = want; } else if (cur.kind == MM_INT) { if (!want.all_comptime_int) cur.all_comptime_int = false; cur.result_min = valNumberMinMax( ip, cur.result_min, want.result_min, want_min); cur.result_max = valNumberMinMax( ip, cur.result_max, want.result_max, want_min); cur.operand_min = valNumberMinMax( ip, cur.operand_min, want.operand_min, true); cur.operand_max = valNumberMinMax( ip, cur.operand_max, want.operand_max, false); } } } uint32_t result_scalar, inter_scalar; switch (cur.kind) { case MM_FLOAT: result_scalar = inter_scalar = cur.float_ty; break; case MM_COMPTIME_FLOAT: result_scalar = inter_scalar = ZIR_REF_COMPTIME_FLOAT_TYPE; break; case MM_INT: if (cur.all_comptime_int) { result_scalar = inter_scalar = ZIR_REF_COMPTIME_INT_TYPE; } else { result_scalar = zptIntFittingRange(ip, cur.result_min, cur.result_max); inter_scalar = zptIntFittingRange(ip, cur.operand_min, cur.operand_max); } break; default: result_scalar = inter_scalar = ZIR_REF_COMPTIME_INT_TYPE; break; } uint32_t result_ty = is_vec ? ipGetVectorType(ip, vec_len, result_scalar) : result_scalar; uint32_t inter_ty = is_vec ? ipGetVectorType(ip, vec_len, inter_scalar) : inter_scalar; *out_ty = result_ty; // "We might have refined all the way down to an OPV type." uint32_t opv = ztOnePossibleValue(ip, result_ty); if (opv != IP_INDEX_NONE) { free(tys); return opv; } uint32_t elems_len = is_vec ? vec_len : 1u; uint32_t* elems = ARR_INIT(uint32_t, elems_len); bool populated = false; bool any_runtime = false; for (uint32_t k = 0; k < n; k++) { if (semaIsAirInstRef(ops[k])) { any_runtime = true; continue; } uint32_t val = ops[k]; uint32_t sty = ipScalarType(ip, tys[k]); for (uint32_t e = 0; e < elems_len; e++) { uint32_t ev = is_vec ? semaAggregateElemValueOrUndef(ip, val, sty, e, "analyzeMinMax: vector lane") : val; elems[e] = populated ? valNumberMinMax(ip, elems[e], ev, want_min) : ev; } populated = true; } uint32_t elem_ty = any_runtime ? inter_scalar : result_scalar; uint32_t ct_part = IP_INDEX_NONE; if (populated) { for (uint32_t e = 0; e < elems_len; e++) elems[e] = ipIsUndef(ip, elems[e]) ? ipGetUndef(ip, elem_ty) : semaCoerceComptime(zcu, ip, elems[e], elem_ty); ct_part = is_vec ? ipGetAggregate( ip, any_runtime ? inter_ty : result_ty, elems, elems_len) : elems[0]; } free(elems); if (!any_runtime) { free(tys); return ct_part; } // "All runtime-known peers are coerced to `intermediate_ty`, and we cast // down to `result_ty` at the end." uint32_t cur_result = ct_part; for (uint32_t k = 0; k < n; k++) { if (!semaIsAirInstRef(ops[k])) continue; uint32_t coerced = tys[k] == inter_ty ? ops[k] : semaCoerceExtraRuntime(zcu, ops[k], tys[k], inter_ty, insts, block, air_extra, "analyzeMinMax: runtime operand"); cur_result = cur_result == IP_INDEX_NONE ? coerced : semaRuntimeBinOp(insts, block, is_max ? AIR_INST_MAX : AIR_INST_MIN, cur_result, coerced); } free(tys); if (ct_part != IP_INDEX_NONE && ipIsUndef(ip, ct_part)) return ipGetUndef(ip, result_ty); if (result_ty == inter_ty) return cur_result; return semaRuntimeIntCast(insts, block, result_ty, cur_result); } uint32_t semaMinMaxComptime( Zcu* zcu, bool is_max, const uint32_t* ops, uint32_t n) { uint32_t ty; return analyzeMinMax(zcu, is_max, ops, NULL, n, NULL, NULL, NULL, &ty); } // (part of) Value.zig:numberMin, arith.zig:bitwiseBin, arith.zig:addMaybeWrap // zirReduce's combiner: `"Value.numberMin(accum, elem_val, zcu)"` / // `numberMax` for Min/Max, else `arith.bitwiseBin` (And/Or/Xor) and // `arith.addMaybeWrap` / `mulMaybeWrap` -- `+%`/`*%` on an integer, `+`/`*` // on a float -- which are the one comptime scalar fold at the element type. static uint32_t semaReduceCombineScalar(Zcu* zcu, AirReduceKind kind, AirInstTag scalar, uint32_t elem_ty, uint32_t accum, uint32_t elem) { const InternPool* ip = &zcu->intern_pool; if (kind != AIR_REDUCE_COMBINE) return valNumberMinMax(ip, accum, elem, kind == AIR_REDUCE_MIN); uint32_t float_bits; bool is_float = ztSimpleFloatBits(elem_ty, &float_bits); ZirInstTag red_tag; switch (scalar) { case AIR_INST_BIT_AND: red_tag = ZIR_INST_BIT_AND; break; case AIR_INST_BIT_OR: red_tag = ZIR_INST_BIT_OR; break; case AIR_INST_XOR: red_tag = ZIR_INST_XOR; break; case AIR_INST_ADD: red_tag = is_float ? ZIR_INST_ADD : ZIR_INST_ADDWRAP; break; case AIR_INST_MUL: red_tag = is_float ? ZIR_INST_MUL : ZIR_INST_MULWRAP; break; default: return IP_INDEX_NONE; } return semaFoldComptimeScalarBinOp( zcu, red_tag, accum, elem_ty, elem, elem_ty, NULL); } // Sema.zig:compareVector, PerThread.zig:aggregateValue, // PerThread.zig:vectorType // `Sema.compareVector` (src/Sema.zig): `"const // result_data = try sema.arena.alloc(InternPool.Index, ty.vectorLen(zcu)); for // (result_data, 0..) |*scalar, i| { const lhs_elem = try lhs.elemValue(pt, i); // const rhs_elem = try rhs.elemValue(pt, i); if (lhs_elem.isUndef(zcu) or // rhs_elem.isUndef(zcu)) { scalar.* = .undef_bool; } else { ... } } return // pt.aggregateValue(try pt.vectorType(.{ .len = ty.vectorLen(zcu), .child = // .bool_type }), result_data);"`. `ty` is the resolved operand type -- a // vector -- and the result is its `@Vector(N, bool)`. Upstream keeps this // separate from `cmpVector`'s peer resolution and runtime tail, so both of // zig1's comptime frames reach the one per-lane fold. static uint32_t compareVector(InternPool* ip, ZirInstTag cmp_op, uint32_t lhs, uint32_t rhs, uint32_t ty) { IpArrayInfo vec; if (ipArrayInfo(ip, ty, &vec) != IP_ARR_OK || !vec.is_vector) UNIMPLEMENTED("compareVector: operand type is not a vector"); uint32_t res_ty = ipGetVectorType(ip, vec.len, ZIR_REF_BOOL_TYPE); uint32_t* elems = NULL; if (vec.len > 0) { elems = zig1Malloc(vec.len * sizeof(uint32_t)); } for (uint32_t i = 0; i < vec.len; i++) { uint32_t le = valElemValue(ip, lhs, i, "cmpVector: comptime lhs lane"); uint32_t re = valElemValue(ip, rhs, i, "cmpVector: comptime rhs lane"); // An aggregate can hold an undef element even when the whole value // is defined, which `cmpVector`'s whole-operand test cannot see. if (ipIsUndef(ip, le) || ipIsUndef(ip, re)) { elems[i] = ipGetUndef(ip, ZIR_REF_BOOL_TYPE); continue; } elems[i] = semaCmpVectorLane(ip, cmp_op, le, re) ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; } uint32_t agg = ipGetAggregate(ip, res_ty, elems, vec.len); free(elems); return agg; } // Sema.zig:cmpVector, Sema.zig:analyzeCmp, PerThread.zig:vectorType // Sema.cmpVector, reached from Sema.analyzeCmp's `"if (lhs_ty.zigTypeTag(zcu) // == .vector and rhs_ty.zigTypeTag(zcu) == .vector) { return // sema.cmpVector(block, src, lhs, rhs, op, lhs_src, rhs_src); }"` -- before // its numeric arm, so a vector comparison never becomes a scalar `cmp_*`. // Its result is not a bool: `"const result_ty = try pt.vectorType(.{ .len = // lhs_ty.vectorLen(zcu), .child = .bool_type });"`. Returns the ZIR // instruction's value (an interned aggregate when both operands fold, an AIR // ref otherwise) and writes that result type to `*out_ty`. static uint32_t cmpVector(Zcu* zcu, ZirInstTag cmp_op, AirInstTag air_tag, uint32_t lhs_val, uint32_t lhs_ty, uint32_t rhs_val, uint32_t rhs_ty, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; IpArrayInfo lvec, rvec; if (ipArrayInfo(ip, lhs_ty, &lvec) != IP_ARR_OK || !lvec.is_vector || ipArrayInfo(ip, rhs_ty, &rvec) != IP_ARR_OK || !rvec.is_vector) UNIMPLEMENTED("cmpVector: operand is not a vector"); // checkVectorizableBinaryOperands, which cmpVector calls again on its own: // `"if (lhs_len != rhs_len) { ... "vector length mismatch" ... }"`. if (lvec.len != rvec.len) COMPILE_ERROR_UPSTREAM("cmpVector: vector length mismatch"); // `"const resolved_ty = try sema.resolvePeerTypes(...)"` and the two // coerce calls that follow it. if (lhs_ty != rhs_ty) { // The resolver may rewrite its operand arrays. uint32_t peer_tys[2] = { lhs_ty, rhs_ty }; uint32_t peer_vals[2] = { semaIsAirInstRef(lhs_val) ? IP_INDEX_NONE : lhs_val, semaIsAirInstRef(rhs_val) ? IP_INDEX_NONE : rhs_val, }; uint32_t resolved = semaTypeofPeerResolve(zcu, peer_tys, peer_vals, 2); uint32_t* vals[2] = { &lhs_val, &rhs_val }; for (int side = 0; side < 2; side++) { uint32_t side_ty = side == 0 ? lhs_ty : rhs_ty; if (side_ty == resolved) continue; *vals[side] = coerce( zcu, insts, block, air_extra, resolved, *vals[side], side_ty); if (*vals[side] == IP_INDEX_NONE) UNIMPLEMENTED("cmpVector: operand not coercible"); } lhs_ty = resolved; } uint32_t res_ty = ipGetVectorType(ip, lvec.len, ZIR_REF_BOOL_TYPE); *out_ty = res_ty; // `"if (maybe_lhs_val) |v| if (v.isUndef(zcu)) return // pt.undefRef(result_ty);"` / the same for rhs -- before // both the comptime fold and requireRuntimeBlock, so an undef operand // makes the whole result undef and emits no AIR at all. if ((!semaIsAirInstRef(lhs_val) && ipIsUndef(ip, lhs_val)) || (!semaIsAirInstRef(rhs_val) && ipIsUndef(ip, rhs_val))) return ipGetUndef(ip, res_ty); if (!semaIsAirInstRef(lhs_val) && !semaIsAirInstRef(rhs_val)) { // `"const cmp_val = try sema.compareVector(lhs_val, op, rhs_val, // resolved_ty); return Air.internedToRef(cmp_val.toIntern());"`. return compareVector(ip, cmp_op, lhs_val, rhs_val, lhs_ty); } // `"try sema.requireRuntimeBlock(block, src, runtime_src); return // block.addCmpVector(casted_lhs, casted_rhs, op);"`. Block.addCmpVector is // a `ty_pl` whose type is the `@Vector(N, bool)` above and whose payload // is Air.VectorCmp { lhs, rhs, op }. Its `cmp_vector_optimized` twin is // selected by `block.float_mode == .optimized`; zig1 does not track // `@setFloatMode`. uint32_t op_code; if (!airVectorCmpEncodeOp(air_tag, &op_code)) UNIMPLEMENTED("cmpVector: unrecognized comparison"); uint32_t payload = air_extra->len; u32ListAppend(air_extra, lhs_val); u32ListAppend(air_extra, rhs_val); u32ListAppend(air_extra, op_code); AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = res_ty; data.pair.b = payload; uint32_t idx = semaAirInstAppend(insts, AIR_INST_CMP_VECTOR, data); u32ListAppend(block, idx); return AIR_REF_INST_BASE + idx; } // `sema.resolveInt(block, len_src, extra.len, .usize, ...)` over a resolved // array length: a comptime int value (an interned comptime_int / usize, e.g. // MultiArrayList.slice's `[field_names.len][*]u8`). static uint64_t semaArrayTypeLen(const InternPool* ip, uint32_t len_val) { if (len_val == ZIR_REF_ZERO) return 0; if (len_val == ZIR_REF_ONE) return 1; if (!semaRefIsInterned(len_val)) UNIMPLEMENTED("array_type: non-comptime-int length"); uint64_t len_mag; if (!ipTryReadIntValue(ip, len_val, &len_mag)) UNIMPLEMENTED("array_type: length not a readable int"); return len_mag; } // One anonymous-initializer operand (arrayInitAnon / structInitAnon): // `resolveInst(init)`, its `typeOf`, and `resolveValue` -- `.none` for a // runtime operand, whose AIR ref `*raw` keeps. True for a runtime operand. static bool semaAnonInitOperand(const InternPool* ip, uint32_t op_ref, const uint32_t* inst_values, const uint32_t* inst_types, uint32_t* raw, uint32_t* out_ty, uint32_t* out_val) { uint32_t op_val = semaResolveRef(op_ref, inst_values); *raw = op_val; if (semaIsAirInstRef(op_val)) { *out_val = IP_INDEX_NONE; *out_ty = inst_types[op_ref - ZIR_REF_START_INDEX]; return true; } *out_ty = semaResolveRefValType(ip, op_ref, op_val, inst_types); *out_val = op_val; return false; } // `block.addAggregateInit(ty, element_refs)`: a runtime field's AIR ref, a // comptime field's interned value. static void semaAnonInitAggregate(AirInstList* insts, U32List* block, U32List* air_extra, uint32_t ty, const uint32_t* raw, const uint32_t* values, uint32_t n, uint32_t* inst_values, uint32_t* inst_types, uint32_t inst) { uint32_t* refs = ARR_INIT(uint32_t, n + 1u); for (uint32_t e = 0; e < n; e++) refs[e] = values[e] == IP_INDEX_NONE ? raw[e] : values[e]; inst_values[inst] = semaEmitAggregateInit(insts, block, air_extra, ty, refs, n); inst_types[inst] = ty; free(refs); } // arrayInitAnon's and structInitAnon's runtime `is_ref`: "const alloc = try // block.addTy(.alloc, alloc_ty);", a store through `addStructFieldPtr` per // runtime field, then "return sema.makePtrConst(block, alloc);". static void semaAnonInitRef(InternPool* ip, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t ty, const uint32_t* types, const uint32_t* raw, const uint32_t* values, uint32_t n, uint32_t* inst_values, uint32_t* inst_types, uint32_t inst) { uint32_t alloc_ty = ipGetPtrType(ip, ty, IP_PTR_FLAGS_MUT_ONE); uint32_t alloc = semaEmitTyOp(insts, block, AIR_INST_ALLOC, alloc_ty, 0); for (uint32_t f = 0; f < n; f++) { // `field_ptr_ty` is interned for every field, comptime ones too. uint32_t field_ptr_ty = ipGetPtrType(ip, types[f], IP_PTR_FLAGS_MUT_ONE); if (values[f] != IP_INDEX_NONE) continue; uint32_t field_ptr = semaEmitStructFieldPtr( alloc, f, field_ptr_ty, insts, block, air_extra); AirInstData st; memset(&st, 0, sizeof(st)); st.pair.a = field_ptr; st.pair.b = raw[f]; u32ListAppend(block, semaAirInstAppend(insts, AIR_INST_STORE, st)); } inst_types[inst] = makePtrTyConst(ip, alloc_ty); inst_values[inst] = semaEmitTyOp( insts, block, AIR_INST_PTR_CAST, inst_types[inst], alloc); } // Sema.zig:arrayInitAnon // Sema.arrayInitAnon: each operand's type and comptime value // form the tuple type (a runtime operand's value slot is `.none`); the // all-comptime tuple is its only possible value, anything else one // `aggregate_init`, and `is_ref` takes its address. `zirArrayInitAnon` and // `zirArrayInit`'s poison result type both end here. static void arrayInitAnon(InternPool* ip, const uint32_t* operands, uint32_t n, bool is_ref, uint32_t* inst_values, uint32_t* inst_types, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t inst) { uint32_t cap = n ? n : 1u; // Zeroed: an empty init still hands the arrays on. uint32_t* words = zig1Calloc((size_t)cap * 3, sizeof(uint32_t)); uint32_t* raw = zig1Malloc((size_t)cap * sizeof(uint32_t)); uint32_t* types = words; uint32_t* values = words + cap; uint32_t* values_no_comptime = words + 2 * cap; bool has_runtime = false; bool any_mutable = false; for (uint32_t e = 0; e < n; e++) { has_runtime |= semaAnonInitOperand(ip, operands[e], inst_values, inst_types, &raw[e], &types[e], &values[e]); if (ztIsZigTypeTag(ip, types[e], ZIG_TYPE_TAG_OPAQUE)) COMPILE_ERROR_UPSTREAM( "opaque types have unknown size and therefore cannot " "be directly embedded in structs"); // `"A field can't be `comptime` if it references a `comptime // var` but the aggregate can still be comptime-known."` bool mutable_val = values[e] != IP_INDEX_NONE && valCanMutateComptimeVarState(ip, values[e]); values_no_comptime[e] = mutable_val ? IP_INDEX_NONE : values[e]; any_mutable |= mutable_val; } uint32_t tuple_ty = ipGetTupleType(ip, n, types, values_no_comptime); if (!has_runtime) { // `pt.aggregateValue(tuple_ty, values)`: every field comptime is // the tuple's only possible value. inst_values[inst] = any_mutable ? ipGetAggregate(ip, tuple_ty, values, n) : ipGetOnlyPossibleValue(ip, tuple_ty); inst_types[inst] = tuple_ty; if (is_ref) { // `addConstantMaybeRef`: the `*const T` uav pointer. inst_types[inst] = ipGetPtrType(ip, tuple_ty, IP_PTR_FLAGS_CONST_ONE); inst_values[inst] = ipGetPtrUav(ip, inst_types[inst], inst_values[inst]); } } else { // `sema.validateRuntimeValue` per operand. if (any_mutable) COMPILE_ERROR_UPSTREAM( "runtime value contains reference to comptime var"); if (is_ref) semaAnonInitRef(ip, insts, block, air_extra, tuple_ty, types, raw, values, n, inst_values, inst_types, inst); else semaAnonInitAggregate(insts, block, air_extra, tuple_ty, raw, values, n, inst_values, inst_types, inst); } free(words); free(raw); } // Sema.zig:structInitAnon // Sema.structInitAnon: `n` items from `items` -- `{ field_name, init }` // for `.anon_init`, `{ field_type, init }` for `.typed_init` (a typed init // whose result type is generic poison) -- as an anonymous struct value, or // its runtime aggregate_init; `is_ref` takes its address. static void structInitAnon(Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t n, uint32_t items, bool typed, bool is_ref, TypeNameCtx type_name_ctx, AirInstList* insts, U32List* block, U32List* air_extra, uint32_t* inst_values, uint32_t* inst_types) { InternPool* ip = &zcu->intern_pool; uint32_t cap = n ? n : 1u; // Zeroed: an empty init still hands the three arrays on. uint32_t* words = zig1Calloc((size_t)cap * 3, sizeof(uint32_t)); uint32_t* raw = zig1Malloc((size_t)cap * sizeof(uint32_t)); uint32_t* types = words; uint32_t* values = words + cap; uint32_t* names = words + 2 * cap; bool has_runtime = false; uint32_t item_index = items; for (uint32_t f = 0; f < n; f++) { // `.typed_init`: the name is the item's field_type's. uint32_t name_start; uint32_t init; if (typed) { ZirInstStructInitItem item; item_index = zirExtraData(zir, item_index, &item); ZirInstFieldType field_type; zirExtraData(zir, zir->inst_datas[item.field_type].pl_node.payload_index, &field_type); name_start = field_type.name_start; init = item.init; } else { ZirInstStructInitAnonItem item; item_index = zirExtraData(zir, item_index, &item); name_start = item.field_name; init = item.init; } const char* nm = zirNullTerminatedString(zir, name_start); names[f] = ipGetOrPutString(ip, nm, (uint32_t)strlen(nm)); has_runtime |= semaAnonInitOperand( ip, init, inst_values, inst_types, &raw[f], &types[f], &values[f]); } uint32_t struct_ty = semaStructInitAnonType( zcu, zir, inst, type_name_ctx, types, values, names, n); if (!has_runtime) { // `"const struct_val = try pt.aggregateValue(struct_ty, // values); return sema.addConstantMaybeRef(struct_val, // is_ref);"` inst_values[inst] = ipGetAggregate(ip, struct_ty, values, n); inst_types[inst] = struct_ty; if (is_ref) { inst_types[inst] = ipGetPtrType(ip, struct_ty, IP_PTR_FLAGS_CONST_ONE); inst_values[inst] = ipGetPtrUav(ip, inst_types[inst], inst_values[inst]); } } else { for (uint32_t f = 0; f < n; f++) if (values[f] != IP_INDEX_NONE && valCanMutateComptimeVarState(ip, values[f])) COMPILE_ERROR_UPSTREAM("runtime value contains " "reference to comptime var"); if (is_ref) { semaAnonInitRef(ip, insts, block, air_extra, struct_ty, types, raw, values, n, inst_values, inst_types, inst); } else { semaAnonInitAggregate(insts, block, air_extra, struct_ty, raw, values, n, inst_values, inst_types, inst); } } free(words); free(raw); } // (part of) Sema.zig:zirReduce // `Sema.zirReduce` up to and including its comptime fold: // resolves the operation (`*op_out`, the `ReduceOp` ordinal), checks the // operand type (`*elem_ty` is the vector's element type) and folds a // comptime operand. IP_INDEX_NONE means a runtime operand: the caller emits // AIR `reduce`. static uint32_t semaReduce(Zcu* zcu, uint32_t rd_op_val, uint32_t rd_operand_ty, uint32_t rd_operand_val, uint32_t* op_out, uint32_t* elem_ty) { InternPool* ip = &zcu->intern_pool; uint32_t rd_op = resolveStdLangEnum(zcu, rd_op_val, "ReduceOp"); *op_out = rd_op; IpArrayInfo rd_vec; // `"if (operand_ty.zigTypeTag(zcu) != .vector) { return // sema.fail(..., "expected vector, found '{f}'", ...); }"` if (ipArrayInfo(ip, rd_operand_ty, &rd_vec) != IP_ARR_OK || !rd_vec.is_vector) COMPILE_ERROR_UPSTREAM("reduce: expected vector"); // `"if (vec_len == 0) { return sema.fail(..., "@reduce operation // requires a vector with nonzero length", .{}); }"` if (rd_vec.len == 0) COMPILE_ERROR_UPSTREAM("reduce: vector with nonzero length required"); // zirReduce's per-operation element type check. And/Or/Xor take // `.int, .bool`; Min/Max/Add/Mul take `.int, .float`. The // interpreter's own scalar arms cover the integer and bool cases; // a float lane has no scalar execution path yet. Which operations // are the bitwise three is derived from zig1.h's one ReduceOp table // -- they are exactly the ones whose scalar AIR operation is a // bitwise one -- rather than respelling their ordinals here. AirReduceKind rd_kind; AirInstTag rd_scalar; if (!airReduceOp(rd_op, &rd_kind, &rd_scalar)) COMPILE_ERROR_UPSTREAM("reduce: unsupported operation value"); bool rd_bitwise = rd_kind == AIR_REDUCE_COMBINE && (rd_scalar == AIR_INST_BIT_AND || rd_scalar == AIR_INST_BIT_OR || rd_scalar == AIR_INST_XOR); uint32_t rd_ew; bool rd_es; bool rd_is_bool = rd_vec.child == ZIR_REF_BOOL_TYPE; bool rd_is_float = ztSimpleFloatBits(rd_vec.child, &rd_ew); if (!rd_is_bool && !rd_is_float && !ztIntInfo(ip, rd_vec.child, &rd_ew, &rd_es)) COMPILE_ERROR_UPSTREAM("reduce: operand element is not an integer, " "float or bool"); if (rd_is_bool && !rd_bitwise) COMPILE_ERROR_UPSTREAM( "reduce: operation requires integer or float operand"); if (rd_is_float && rd_bitwise) COMPILE_ERROR_UPSTREAM( "reduce: operation requires integer or boolean operand"); *elem_ty = rd_vec.child; if (!semaIsAirInstRef(rd_operand_val)) { // `"if (sema.resolveValue(operand)) |operand_val| { if // (operand_val.isUndef(zcu)) return pt.undefRef(scalar_ty); // var accum: Value = try operand_val.elemValue(pt, 0); var i: // u32 = 1; while (i < vec_len) : (i += 1) { const elem_val = // try operand_val.elemValue(pt, i); accum = switch (operation) // { ... }; } return Air.internedToRef(accum.toIntern()); }"` if (rd_operand_val >= ip->items_len) UNIMPLEMENTED("reduce: comptime operand"); uint32_t rd_val = rd_operand_val; if (ipIsUndef(ip, rd_val)) return ipGetUndef(ip, rd_vec.child); uint32_t rd_accum = valElemValue(ip, rd_val, 0, "reduce: comptime vector element"); for (uint32_t rd_i = 1; rd_i < rd_vec.len; rd_i++) { uint32_t rd_elem = valElemValue( ip, rd_val, rd_i, "reduce: comptime vector element"); rd_accum = semaReduceCombineScalar( zcu, rd_kind, rd_scalar, rd_vec.child, rd_accum, rd_elem); if (rd_accum == IP_INDEX_NONE) UNIMPLEMENTED("reduce: comptime element shape"); } return rd_accum; } return IP_INDEX_NONE; } // (part of) Sema.zig:fieldVal // Sema.fieldVal on the comptime value `agg`. False when `agg` is a pointer // whose pointee is runtime storage: fieldVal's `is_pointer_to` arms then load // through fieldPtr. static bool semaFieldValComptime(Zcu* zcu, const Zir* zir, AirInstList* insts, U32List* block, uint32_t agg, const char* field, uint32_t field_ip, uint32_t* out_val, uint32_t* out_ty); // (part of) Sema.zig:fieldPtrLoad // `Sema.fieldPtrLoad`'s `pointerDeref` + `fieldVal`: `obj_ptr_val` is the // comptime-known object pointer. False for a runtime load. static bool semaFieldPtrLoadComptime(Zcu* zcu, const Zir* zir, AirInstList* insts, U32List* block, uint32_t obj_ptr_val, const char* field, uint32_t field_ip, uint32_t* out_val, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; // A `@typeInfo` value: its payload field. if (ctIsTypeInfoValue(zcu, obj_ptr_val)) { uint32_t tif_ty; uint32_t tif_val = semaTypeInfoPayload(zcu, obj_ptr_val, field, &tif_ty); *out_val = tif_val; *out_ty = tif_ty; return true; } // Comptime path: deref the pointer to its pointee. A // comptime-mutable cell (`s.field` where `s` is a `comptime var` // struct in a runtime body) derefs to the cell's current aggregate // value; the bare-aggregate struct arm below reads the field. uint32_t ptr; // Only the cell's own root pointer reads its whole value; any other // (an element of an array cell) is `pointerDeref`ed below. IpPtrInfo root_info; if (ipIsPtrComptimeAlloc(ip, obj_ptr_val) && ipPtrComptimeAllocByteOffset(ip, obj_ptr_val) == 0 && ipPtrInfo(ip, ipTypeOf(ip, obj_ptr_val), &root_info) == IP_PTRI_OK && root_info.size == IP_PTR_SIZE_ONE && root_info.child == mutableValueTypeOf(ip, &getComptimeAlloc( zcu, ipPtrComptimeAllocIndex(ip, obj_ptr_val)) ->val)) { ptr = mutableValueIntern(zcu, &getComptimeAlloc(zcu, ipPtrComptimeAllocIndex(ip, obj_ptr_val)) ->val); } else { ptr = obj_ptr_val; } // `Sema.fieldPtrLoad`: the pointee's one possible value, else // `pointerDeref` the comptime-known object pointer, then // `fieldVal` on the loaded value. zig1 also hands over some // comptime objects as the value itself (a bare aggregate, slice // or packed value, a comptime cell's contents). uint32_t agg = ptr; IpPtrInfo fpl_info; if (ipPtrInfo(ip, ipTypeOf(ip, ptr), &fpl_info) == IP_PTRI_OK) { uint32_t fpl_ty; agg = semaAnalyzeLoadComptime(zcu, ptr, &fpl_ty); if (agg == IP_INDEX_NONE) return false; } return semaFieldValComptime( zcu, zir, insts, block, agg, field, field_ip, out_val, out_ty); } static bool semaFieldValComptime(Zcu* zcu, const Zir* zir, AirInstList* insts, U32List* block, uint32_t agg, const char* field, uint32_t field_ip, uint32_t* out_val, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; IpPtrInfo fpl_info; field_val_comptime:; // `Sema.fieldVal` on the comptime value `agg`: a slice's // `.len` / `.ptr`, a pointer to an array, and `is_pointer_to`'s // "dereferencing a single pointer during field lookup". if (ip->item_tags[agg] == IP_TAG_PTR_SLICE || ipPtrInfo(ip, ipTypeOf(ip, agg), &fpl_info) == IP_PTRI_OK) { uint32_t pv_val; uint32_t pv_ty; if (semaPtrValueFieldVal(ip, agg, ipTypeOf(ip, agg), field, &pv_val, &pv_ty, "field_ptr_load: slice field neither .len nor " ".ptr", "field_ptr_load: .ptr of a pointer-to-array", "field_ptr_load: pointer-to-array field neither .len " "nor .ptr")) { *out_val = pv_val; *out_ty = pv_ty; return true; } if (fpl_info.size != IP_PTR_SIZE_ONE) UNIMPLEMENTED( "field_ptr_load: field of a non-single pointer value"); agg = pointerDeref(zcu, agg, NULL); if (agg == IP_INDEX_NONE) return false; goto field_val_comptime; } // `.type`: a namespace member, an enum / union tag or an error. uint32_t ns_val; uint32_t ns_ty; uint32_t ns_nav; if ((ztIsNamespaceType(ip, agg) || ztIsZigTypeTag(ip, agg, ZIG_TYPE_TAG_ERROR_SET)) && semaComptimeNamespaceFieldVal(zcu, zir, agg, field_ip, &ns_val, &ns_ty, &ns_nav, "field_ptr_load: namespace type has no namespace")) { if (!(ns_nav != IP_INDEX_NONE && semaEmitMutableNavLoad( zcu, ns_nav, insts, block, out_val, out_ty))) { *out_val = ns_val; *out_ty = ns_ty; } return true; } if (ip->item_tags[agg] == IP_TAG_BITPACK) { // A comptime packed struct value (`f.hi` on a // `const f = Flags{...}`): zig1 stores a packed struct as its // backing-int scalar (IP_TAG_BITPACK), so the field value is // `(backing >> bit_offset) & mask` — the same LSB-first // decode the interp FIELD_PTR arm performs. Upstream needs no // decode here (comptime packed values are ordinary // field-indexed aggregates; Sema.fieldVal projects the stored // field). A bool field maps its bit to the preinterned // true/false refs (the @intFromBool inverse of the packed // init fold); a signed int field would need sign-extension // and aborts loudly. uint32_t bp_ty = ipBitpackType(ip, agg); uint32_t bp_idx = semaBitpackFieldIndex(ip, bp_ty, field_ip); if (bp_idx == UINT32_MAX) UNIMPLEMENTED("field_ptr_load: packed field not found"); uint32_t bp_fty = ipUnionIsPacked(ip, bp_ty) ? ipUnionFieldType(ip, bp_ty, bp_idx) : ipStructFieldType(ip, bp_ty, bp_idx); *out_val = semaReadBitpackFieldValue(zcu, agg, bp_idx); *out_ty = bp_fty; return true; } uint32_t fv_val; if (semaFieldValByType(ip, agg, field, &fv_val, out_ty)) { *out_val = fv_val; return true; } if (ip->item_tags[agg] == IP_TAG_ONLY_POSSIBLE_VALUE) { // `object_ty.onePossibleValue`'s field: its own one possible value. uint32_t opv_ty = ipTypeOf(ip, agg); uint32_t opv_idx = ipTagIsAnyStruct(ip->item_tags[opv_ty]) ? ipStructFieldIndex(ip, opv_ty, field_ip) : ipUnionFieldIndex(ip, opv_ty, field_ip); if (opv_idx == UINT32_MAX) UNIMPLEMENTED("field_ptr_load: field of an opv not found"); *out_ty = ipTagIsAnyStruct(ip->item_tags[opv_ty]) ? ipStructFieldType(ip, opv_ty, opv_idx) : semaResolveUnionFieldTypeLazy(zcu, opv_ty, opv_idx); *out_val = ztOnePossibleValue(ip, *out_ty); return true; } if (ip->item_tags[agg] != IP_TAG_AGGREGATE && ip->item_tags[agg] != IP_TAG_REPEATED && ip->item_tags[agg] != IP_TAG_UNION_VALUE) UNIMPLEMENTED("field_ptr_load: non-aggregate pointee"); if (ctIsTypeInfoValue(zcu, agg)) { uint32_t tif_ty; uint32_t tif_val = semaTypeInfoPayload(zcu, agg, field, &tif_ty); *out_val = tif_val; *out_ty = tif_ty; return true; } // aggregate/repeated/union_value store their type in extra word 0. // Repeated canonicalizes all-equal struct aggregates: every field // projects to the single stored element. uint32_t agg_ty = ip->extra[ip->item_datas[agg]]; if (ipTagIsAnyStruct(ip->item_tags[agg_ty])) { uint32_t fidx = ipStructFieldIndex(ip, agg_ty, field_ip); if (fidx == UINT32_MAX) UNIMPLEMENTED("field_ptr_load: struct field not found"); // structFieldVal: "if (struct_type.field_is_comptime_bits.get(ip, // field_index)) { return .fromIntern(struct_type.field_defaults // .get(ip)[field_index]); }" -- ahead of the value, whose comptime // field was never stored. Aggregate = { ty } followed by element IP // value indices. uint32_t elem = semaStructFieldComptimeValue(zcu, agg_ty, fidx); if (elem == IP_INDEX_NONE) elem = (ip->item_tags[agg] == IP_TAG_REPEATED) ? ip->extra[ip->item_datas[agg] + 1] : ip->extra[ip->item_datas[agg] + 1 + fidx]; *out_val = elem; *out_ty = ipStructFieldType(ip, agg_ty, fidx); return true; } if (ip->item_tags[agg_ty] == IP_TAG_TYPE_UNION) { // Sema.unionFieldVal comptime path: the pointee is a // union_value {ty, tag, val}. Check the requested field is the // active one (its tag index matches the stored tag), then // return the payload value. union_value extra = {ty, tag, // val}; map its enum tag back to the union field by name so // explicit backing integers do not become field indices. uint32_t ufidx = ipUnionFieldIndex(ip, agg_ty, field_ip); if (ufidx == UINT32_MAX) UNIMPLEMENTED("field_ptr_load: union field not found"); *out_val = semaUnionActivePayload(zcu, agg, ufidx); *out_ty = semaResolveUnionFieldTypeLazy(zcu, agg_ty, ufidx); return true; } UNIMPLEMENTED("field_ptr_load: unsupported aggregate type"); ZIG1_TCC_RETURN(false); } // Sema.zig:analyzeErrUnionCode // Sema.analyzeErrUnionCode's runtime half: `"return block.addTyOp( // .unwrap_errunion_err, dest_ty, operand);"` with `dest_ty` the operand's // error set. Returns the AIR ref. static uint32_t semaAnalyzeErrUnionCodeRuntime(const InternPool* ip, AirInstList* insts, U32List* block, uint32_t operand, uint32_t operand_ty) { if (!ztIsZigTypeTag(ip, operand_ty, ZIG_TYPE_TAG_ERROR_UNION)) UNIMPLEMENTED("err_union_code: operand not an error union"); AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = ipErrorUnionErrorSet(ip, operand_ty); data.pair.b = operand; uint32_t idx = semaAirInstAppend(insts, AIR_INST_UNWRAP_ERRUNION_ERR, data); u32ListAppend(block, idx); return AIR_REF_INST_BASE + idx; } // Sema.zig:unionFieldPtr // `Sema.unionFieldPtr`'s comptime half, not initializing: an `.auto` union's // field must be the active one, then `union_ptr_val.ptrField(field_index)`. uint32_t semaUnionFieldPtrComptime( Zcu* zcu, uint32_t optr, uint32_t union_ty, uint32_t name) { InternPool* ip = &zcu->intern_pool; uint32_t fidx = ipUnionFieldIndex(ip, union_ty, name); if (fidx == UINT32_MAX) COMPILE_ERROR_UPSTREAM("field_ptr: no union field with this name"); if (!ipUnionIsPacked(ip, union_ty) && !ipUnionIsExtern(ip, union_ty)) { uint32_t u_val = pointerDeref(zcu, optr, NULL); if (u_val == IP_INDEX_NONE) UNIMPLEMENTED( "field_ptr: comptime union pointer with a runtime pointee"); if (ipIsUndef(ip, u_val)) COMPILE_ERROR_UPSTREAM("field_ptr: use of undefined value"); if (!ipItemTagIs(ip, u_val, IP_TAG_UNION_VALUE)) UNIMPLEMENTED( "field_ptr: comptime union pointee not a union value"); semaUnionActivePayload(zcu, u_val, fidx); } return valPtrField(zcu, optr, fidx); } // Sema.zig:unaryMath // `Sema.unaryMath`, asked by zirUnaryMath and by zirRoundCast with no // result type: validate a float or vector-of-floats operand, fold a comptime // value (`maybeConstantUnaryMath`), else emit the AIR unary op. static uint32_t semaAnalyzeUnaryMath(Zcu* zcu, ZirInstTag zir_tag, uint32_t fum_op_val, uint32_t fum_op_ty, AirInstList* insts, U32List* block, uint32_t* out_ty) { const InternPool* ip = &zcu->intern_pool; AirInstTag fum_air_tag; switch (zir_tag) { case ZIR_INST_SQRT: fum_air_tag = AIR_INST_SQRT; break; case ZIR_INST_SIN: fum_air_tag = AIR_INST_SIN; break; case ZIR_INST_COS: fum_air_tag = AIR_INST_COS; break; case ZIR_INST_TAN: fum_air_tag = AIR_INST_TAN; break; case ZIR_INST_EXP: fum_air_tag = AIR_INST_EXP; break; case ZIR_INST_EXP2: fum_air_tag = AIR_INST_EXP2; break; case ZIR_INST_LOG: fum_air_tag = AIR_INST_LOG; break; case ZIR_INST_LOG2: fum_air_tag = AIR_INST_LOG2; break; case ZIR_INST_LOG10: fum_air_tag = AIR_INST_LOG10; break; case ZIR_INST_FLOOR: fum_air_tag = AIR_INST_FLOOR; break; case ZIR_INST_CEIL: fum_air_tag = AIR_INST_CEIL; break; case ZIR_INST_TRUNC: fum_air_tag = AIR_INST_TRUNC_FLOAT; break; case ZIR_INST_ROUND: fum_air_tag = AIR_INST_ROUND; break; default: UNREACHABLE("float unary math: impossible tag"); } IpArrayInfo fum_vec = { 0 }; if (ipArrayInfo(ip, fum_op_ty, &fum_vec) == IP_ARR_MALFORMED) UNREACHABLE("float unary math: malformed vector type"); uint32_t fum_scalar_ty = ipScalarType(ip, fum_op_ty); uint32_t fum_bits = 0; if (fum_scalar_ty != ZIR_REF_COMPTIME_FLOAT_TYPE && !ztSimpleFloatBits(fum_scalar_ty, &fum_bits)) COMPILE_ERROR_UPSTREAM( "float unary math: expected vector of floats or float " "type"); if (!semaIsAirInstRef(fum_op_val)) { // `unaryMath`'s `maybeConstantUnaryMath`. *out_ty = fum_op_ty; return maybeConstantUnaryMath(zcu, zir_tag, fum_op_val, fum_op_ty); } *out_ty = fum_op_ty; // An un_op is a bin_op's lhs. return semaRuntimeBinOp(insts, block, fum_air_tag, fum_op_val, 0); } // The innermost Sema.Block whose label is `zir_block`, or NULL. static SemaBlock* semaBlockFindLabel( const InlineCtx* ictx, uint32_t zir_block) { for (SemaBlock* b = ictx->blk; b != NULL; b = b->parent) if (b->label == zir_block) return b; return NULL; } // Sema.zig:addRuntimeBreakToBlock, Sema.zig:zirBreak // An AIR `br` from `block` to AIR block `block_air_idx` with `operand_ref`, // its merge recorded and `target`'s runtime_index incremented (NULL for a // post-hoc block, which no Sema.Block in scope labels). static void addRuntimeBreakToBlock(const InternPool* ip, InlineCtx* ictx, AirInstList* insts, U32List* block, const uint32_t* inst_values, const uint32_t* inst_types, SemaBlock* target, uint32_t block_air_idx, uint32_t operand_ref) { uint32_t operand_val = semaResolveRef(operand_ref, inst_values); // The operand's IP index is its AIR Ref. uint32_t operand_air_ref = operand_val; AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = block_air_idx; // br.block_inst (Index, not Ref) data.pair.b = operand_air_ref; // br.operand uint32_t air_idx = semaAirInstAppend(insts, AIR_INST_BR, data); u32ListAppend(block, air_idx); // Sema.zirBreak's merge recording (src/Sema.zig): // `label.merges.results.append(gpa, operand)` + // `label.merges.br_list.append(gpa, br_ref.toIndex().?)`. No peer // resolution happens here. Upstream resolves one peer type over // all of `merges.results` in `resolveAnalyzedBlock` // (src/Sema.zig) and only then coerces each recorded `br` // operand to it; a pairwise fold at the break site is a // different rule, not an n-ary one. { uint32_t brk_operand_ty; if (operand_ref < ZIR_REF_START_INDEX) { brk_operand_ty = ipIndexIsType(ip, operand_val) ? ZIR_REF_TYPE_TYPE : ipTypeOfPreinterned(operand_ref); } else brk_operand_ty = inst_types[operand_ref - ZIR_REF_START_INDEX]; // An already-interned integer break operand with no // `inst_types[]` entry would contribute IP_INDEX_NONE and // silently drop out of peer resolution below. Same recovery // as H1. if (brk_operand_ty == IP_INDEX_NONE) brk_operand_ty = semaInternedIntValueType(ip, operand_val); // Upstream's `peer_vals[i]`, i.e. // `sema.resolveValue(merges.results.items[i])`: a break // operand that is not an AIR instruction ref is its own // comptime value. `comptime_int` has no runtime // representation, so a zig1 AIR carrier typed // `comptime_int` -- a nested block zig1 keeps where // resolveAnalyzedBlock's `merges.results.items.len == 1` // value arm would have folded it to a constant -- is // comptime-known too; `resolvePeerTypesInner`'s `.fixed_int` // arm reads exactly that before it will refine a // `comptime_int` peer to a fixed width. Its value is not // recoverable from the AIR block, so the operand ref stands // in for it, as it does for every other comptime operand. semaBlockMergesAppend(ictx, block_air_idx, air_idx, brk_operand_ty, (!semaIsAirInstRef(operand_val) || brk_operand_ty == ZIR_REF_COMPTIME_INT_TYPE) ? operand_air_ref : IP_INDEX_NONE); } if (target != NULL) target->runtime_index++; } // Sema.zig:resolveAnalyzedBlock // The AIR block `blk_air_idx` over the analyzed `sub_block` (consumed), as // the enclosing `block` takes it: elided, promoted when noreturn, or kept. // Returns whether the block is noreturn, which ends the caller's body. static bool resolveAnalyzedBlock(Zcu* zcu, const InlineCtx* ictx, AirInstList* insts, U32List* air_extra, U32List* block, U32List sub_block, uint32_t blk_air_idx, uint32_t* out_val, uint32_t* out_ty) { // Determine whether to elide, promote (noreturn), or keep the // block. Mirrors resolveAnalyzedBlock's three paths, keyed on // merges.results.len — the number of AIR `br` instructions that // target this block. Count them once (each break to this block // appends to Sema's merges.results/br_list). uint32_t break_count = 0; for (uint32_t k = blk_air_idx + 1; k < insts->len; k++) { if (insts->tags[k] == AIR_INST_BR && insts->datas[k].pair.a == blk_air_idx) break_count++; } // Elide (merges.results.len == 1, trailing break): Only when there // is exactly one break to the block and it is the last instruction // in sub_block. With >1 break (e.g. a `continue`/`break` inside // the body plus the block's implicit trailing break), the block // cannot be elided — an earlier break would dangle — so a real AIR // block must be kept. Mirrors resolveAnalyzedBlock's // `merges.results.items.len == 1` guard on the fast-path. if (break_count == 1 && sub_block.len > 0) { uint32_t last_sub = sub_block.items[sub_block.len - 1]; if (insts->tags[last_sub] == AIR_INST_BR && insts->datas[last_sub].pair.a == blk_air_idx) { for (uint32_t bi = 0; bi < sub_block.len - 1; bi++) u32ListAppend(block, sub_block.items[bi]); // resolveAnalyzedBlock fast path: the block collapses to // its single trailing break, so it yields that break's // operand (a comptime value or a runtime AIR ref) rather // than the dangling block ref, typed by its single // recorded merge result (`merges.results.items[0]`). *out_val = insts->datas[last_sub].pair.b; *out_ty = semaBlockMergesPeerType(zcu, ictx, blk_air_idx); free(sub_block.items); return false; // elided } } // Noreturn (merges.results.len==0): if no AIR `br` targets // blk_air_idx the block never produces a value. Promote sub_block // instructions directly into the outer block list. Mirrors // resolveAnalyzedBlock where merges.results.len==0 causes the // block to be removed from the enclosing body list. bool block_noreturn = (break_count == 0); if (block_noreturn) { // Noreturn: the pre-reserved block slot stays in insts (Sema // keeps it in air_instructions too, with data=undefined). // Promote sub_block instructions directly into the outer block // list, mirroring Sema removing the block from the parent // body. for (uint32_t bi = 0; bi < sub_block.len; bi++) u32ListAppend(block, sub_block.items[bi]); // `"return child_block.instructions.items[child_block // .instructions.items.len - 1].toRef();"`: the block's value // is its noreturn last instruction (what `@TypeOf` reads). if (sub_block.len != 0) { *out_val = AIR_REF_INST_BASE + sub_block.items[sub_block.len - 1]; *out_ty = ZIR_REF_NORETURN_TYPE; } free(sub_block.items); return true; } // Non-noreturn: add block to outer body, build the enclosing // block payload, then coerce each break operand to the // peer-resolved type. Sema emits the block body first, and any // break operand requiring runtime coercion rewrites that `br` // instruction in-place to a tiny noreturn block whose payload is // appended afterward. // Mirrors resolveAnalyzedBlock with merges.results.len >= 1. u32ListAppend(block, blk_air_idx); // Block result type: One `resolvePeerTypes` over every recorded // break result (src/Sema.zig) -- void for statement blocks, // the value type for value-yielding blocks like `orelse`. uint32_t blk_extra_idx = air_extra->len; u32ListAppend(air_extra, sub_block.len); // Air.Block.body_len for (uint32_t bi = 0; bi < sub_block.len; bi++) u32ListAppend(air_extra, sub_block.items[bi]); free(sub_block.items); *out_ty = semaBlockMergesFinish( zcu, ictx, insts, air_extra, blk_air_idx, blk_extra_idx, out_val); return false; } // (part of) Sema.zig:zirTypeofBuiltin // Sema.zirTypeofBuiltin / zirTypeofPeer reached from the comptime frame: the // child block (semaAnalyzeTypeofBody) is analyzed at runtime even inside a // comptime scope, where a call or a mutable load is a runtime value // ctEvalBody cannot hold. Analyze the one `inst` with this frame's handler, // seeded with the comptime frame's values and with `param_tys`' runtime // parameters (a return-type body's runtime arguments) as runtime values of // their types. Returns the resulting type. void semaDetachedFrameInit(SemaDetachedFrame* f, Zcu* zcu, const Zir* zir, uint32_t* inst_values, uint32_t* inst_types, uint32_t fn_ret_ty, TypeNameCtx type_name_ctx) { memset(f, 0, sizeof(*f)); InternPool* ip = &zcu->intern_pool; u32ListAppend(&f->extra, 0); f->sema = (SemaCtx) { .ip = ip, .zir = zir, .fn_ret_ty = fn_ret_ty, .insts = &f->insts, .inst_values = inst_values, .inst_types = inst_types, .air_extra = &f->extra, .zcu = zcu, .pending_navs = ip->fnptr_pending != NULL ? ip->fnptr_pending : &f->scratch_pending, .type_name_ctx = type_name_ctx, .ictx = &f->ictx }; } void semaDetachedFrameDeinit(SemaDetachedFrame* f) { if (f->scratch_pending.len != 0) UNIMPLEMENTED("Sema.deinit: detached Sema frame - pending Navs with " "no work queue"); free(f->ictx.merges); free(f->insts.tags); free(f->insts.datas); free(f->block.items); free(f->extra.items); } uint32_t semaTypeofRuntime(Zcu* zcu, const Zir* zir, uint32_t inst, const uint32_t* ct_values, const uint32_t* param_tys, uint32_t fn_ret_ty) { const InternPool* ip = &zcu->intern_pool; // Whole-ZIR maps, as analyzeBodyInner indexes them by instruction. uint32_t* inst_values = semaInstMapAlloc(zir); uint32_t* inst_types = semaInstMapAlloc(zir); SemaDetachedFrame f; semaDetachedFrameInit( &f, zcu, zir, inst_values, inst_types, fn_ret_ty, TYPE_NAME_CTX_NONE); for (uint32_t i = 0; i < zir->inst_len; i++) { uint32_t v = ct_values[i]; // A parameter of comptime-only type is comptime even in a generic // instantiation (`"const param_is_comptime = declared_comptime or // arg_ty.comptimeOnly(zcu);"`): its argument value, not a dummy. bool param_is_runtime = param_tys != NULL && param_tys[i] != IP_INDEX_NONE && !(v != INST_VALUE_UNSET && ztComptimeOnly(zcu, param_tys[i])); if (param_is_runtime) { AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = param_tys[i]; inst_values[i] = AIR_REF_INST_BASE + semaAirInstAppend(&f.insts, AIR_INST_ARG, data); inst_types[i] = param_tys[i]; } else if (semaRefIsInterned(v)) { // An inferred_alloc_comptime placeholder stays `.none`: the body // names its resolved pointer. inst_values[i] = v; inst_types[i] = ipComptimeValueType(ip, v); } } // AstGen places the `ref` of a parameter in the body that holds the // parameter, outside the `@TypeOf` body; the comptime frame left the ref // of a runtime parameter unset, so analyze it here, as the enclosing // body would have (`@TypeOf(s.method(a))` on an `anytype` `s`). for (uint32_t i = 0; i < zir->inst_len; i++) { if (zir->inst_tags[i] != ZIR_INST_REF || inst_values[i] != INST_VALUE_UNSET) continue; uint32_t r_op = zir->inst_datas[i].un_tok.operand; if (r_op < ZIR_REF_START_INDEX || param_tys == NULL || param_tys[r_op - ZIR_REF_START_INDEX] == IP_INDEX_NONE) continue; const uint32_t ref_body[1] = { i }; analyzeBodyInner(&f.sema, &f.block, ref_body, 1); } const uint32_t body[1] = { inst }; analyzeBodyInner(&f.sema, &f.block, body, 1); uint32_t ty = inst_values[inst]; semaDetachedFrameDeinit(&f); free(inst_values); free(inst_types); return ty; } // ── Member access, imports, sentinels and error sets ──────────────────────── // (part of) Sema.zig:lookupInNamespace // Sema.lookupInNamespace (src/Sema.zig): a hit in `pub_decls` is // accessible; a hit in `priv_decls` only when the looking-up file is the // namespace's file ("accessible = src_file == namespace.file_scope"). The // pub/priv split is the namespace's two nav lists, exactly as upstream's two // maps -- never re-derived from a declaration's flags. bool semaNamespaceLookupAccessible(Zcu* zcu, const Zir* access_zir, uint32_t ns_idx, uint32_t name, uint32_t* out_nav) { InternPool* ip = &zcu->intern_pool; IpNamespace* ns = ipGetNamespace(ip, ns_idx); *out_nav = ipNamespaceLookupPub(ip, ns_idx, name); if (*out_nav != IP_INDEX_NONE) return true; for (uint32_t i = 0; i < ns->priv_navs_len; i++) { uint32_t nav = ns->priv_navs[i]; if (ip->navs[nav].name != name) continue; *out_nav = nav; // Sema.zig:namespaceLookup's `.private` arm. if (ctFileIndexOfZir(zcu, access_zir) != ns->file_scope) zig1CompileErrorFmt( "'%s' is not marked 'pub'", ipStringBytes(ip, name)); return true; } *out_nav = IP_INDEX_NONE; return false; } // Sema.zig:failWithBadMemberAccess void failWithBadMemberAccess(Zcu* zcu, uint32_t agg_ty, uint32_t field_name) { InternPool* ip = &zcu->intern_pool; const char* kw_name = ipTagIsAnyStruct(ip->item_tags[agg_ty]) ? "struct" : ztZigTypeTag(ip, agg_ty) == ZIG_TYPE_TAG_UNION ? "union" : ipIsEnumType(ip, agg_ty) ? "enum" : "opaque"; TypeNameWriter w = { 0 }; semaFormatTypeNameDiagnostic(ip, agg_ty, &w); for (uint32_t fi = 0; fi < zcu->num_files; fi++) if (zcu->files[fi].root_type == agg_ty) zig1CompileErrorFmt( "root source file struct '%s' has no member named '%s'", w.buf.arr, ipStringBytes(ip, field_name)); zig1CompileErrorFmt("%s '%s' has no member named '%s'", kw_name, w.buf.arr, ipStringBytes(ip, field_name)); } // (part of) Sema.zig:fieldCallBind // `"no field or member function named '{f}' in '{f}'"`. void semaFailWithNoFieldOrMemberFn( Zcu* zcu, uint32_t concrete_ty, uint32_t field_name) { InternPool* ip = &zcu->intern_pool; TypeNameWriter w = { 0 }; semaFormatTypeNameDiagnostic(ip, concrete_ty, &w); zig1CompileErrorFmt("no field or member function named '%s' in '%s'", ipStringBytes(ip, field_name), w.buf.arr); } // (part of) Sema.zig:zirImport // Genuine comptime evaluation of a `pub const = @import("");` // declaration Nav: interpret the Nav's ZIR value body, decode its `@import` // instruction, resolve the imported path relative to the importing file's // directory, demand-load it (parse and astGen), and intern its root struct // (`ensureFilePopulated`). Returns the imported file's root struct type — the // InternPool entry created here. Mirrors, for the single-`@import` decl bodies // the program-entry chain evaluates, the path `analyzeNavVal` -> comptime // `analyzeBodyInner` -> `zirImport` -> `doImport` -> `ensureFilePopulated`. // // Only one shape is handled: a value body whose sole type-producing // instruction is `@import` of a string-literal path. Anything else aborts // (UNIMPLEMENTED) rather than guessing. Resolve a single `@import` ZIR // instruction (in `file_idx`'s ZIR) to the imported file's populated root // struct type (writing its namespace through `out_ns` when non-NULL). Shared // by zirImport and the single-ref type resolvers. Mirrors Sema.zirImport -> // Zcu.importFile. // Declared in zig1.h (the runtime body evaluator's ZIR_INST_IMPORT // arm calls it), so it cannot be `static`. uint32_t semaImportInstRootType( Zcu* zcu, uint32_t file_idx, uint32_t import_inst, uint32_t* out_ns) { const Zir* zir = zcu->files[file_idx].zir; const char* importer_path = zcu->files[file_idx].sub_path; uint32_t importer_module_id = zcu->files[file_idx].module_id; ZirInstImport extra; zirExtraData( zir, zir->inst_datas[import_inst].pl_tok.payload_index, &extra); const char* operand = zirNullTerminatedString(zir, extra.path); // `@import("root")` names a module (the root/executable source file), not // a sibling path. It resolves to the compilation's root file (the test // file under analysis), already registered as `zcu->root_file_idx`. // Mirrors `Zcu.importFile` mapping the "root" module to the root source // file. if (strcmp(operand, "root") == 0) { uint32_t rimp = zcu->root_file_idx; PerThread pt = { .zcu = zcu, .zir = zcu->files[rimp].zir }; uint32_t struct_type = zptEnsureFilePopulated(&pt, rimp); if (out_ns) *out_ns = zcu->files[rimp].ns; return struct_type; } // `@import("std")` names the `std` module root file (already demand-loaded // as the first analysis root, entry 128). Resolve to `zcu->std_file_idx` // rather than a sibling path. Mirrors Zcu.importFile's "std" module // mapping. if (strcmp(operand, "std") == 0) { uint32_t simp = zcu->std_file_idx; if (simp == UINT32_MAX) { simp = zptDemandLoadFile(zcu, ZCU_MODULE_STD, STD_ROOT_SUB_PATH); zcu->std_file_idx = simp; } PerThread pt = { .zcu = zcu, .zir = zcu->files[simp].zir }; uint32_t struct_type = zptEnsureFilePopulated(&pt, simp); if (out_ns) *out_ns = zcu->files[simp].ns; return struct_type; } // `@import("builtin")` names the synthesized `builtin` module (generated // in-memory, not read from disk). Demand-load it once and populate its // root struct like any file. if (strcmp(operand, "builtin") == 0) { uint32_t bimp = zptDemandLoadBuiltin(zcu); PerThread pt = { .zcu = zcu, .zir = zcu->files[bimp].zir }; uint32_t struct_type = zptEnsureFilePopulated(&pt, bimp); if (out_ns) *out_ns = zcu->files[bimp].ns; return struct_type; } // Other bare module names are dependencies supplied by the root module's // import table (not paths relative to the importing file). In particular, // src/main.zig's `@import("build_options")` resolves to the pinned // synthesized module of the bootstrap profile // (compilationBootstrapConfig). uint32_t nimp = zptDemandLoadNamedModule(zcu, importer_module_id, operand); if (nimp != UINT32_MAX) { PerThread pt = { .zcu = zcu, .zir = zcu->files[nimp].zir }; uint32_t struct_type = zptEnsureFilePopulated(&pt, nimp); if (out_ns) *out_ns = zcu->files[nimp].ns; return struct_type; } // A non-path operand is a module name. If it did not match the importing // module's dependency table, reject it instead of accidentally probing a // same-named file. `.zig` and `.zon` operands retain relative-file // resolution below. AstMode imp_mode; if (!zcuFileModeFromPath(operand, &imp_mode)) UNIMPLEMENTED("zirImport: unmatched bare module import"); // A ZON file is a value (zirImport's `.zon` arm), never a type. if (imp_mode == AST_MODE_ZON) COMPILE_ERROR_UPSTREAM("zirImport: a .zon file used as a type"); // Resolve the import path relative to the importing file's directory // (`doImport` resolves against the importer's package/dir). size_t olen = strlen(operand); char* sub = zptImporterRelativePath(importer_path, operand, olen); uint32_t imp_idx = zptDemandLoadFile(zcu, importer_module_id, sub); free(sub); PerThread pt = { .zcu = zcu, .zir = zcu->files[imp_idx].zir }; uint32_t struct_type = zptEnsureFilePopulated(&pt, imp_idx); if (out_ns) *out_ns = zcu->files[imp_idx].ns; return struct_type; } // Sema.zig:zirErrorSetDecl, PerThread.zig:errorSetFromUnsortedNames, // InternPool.zig:getErrorSetType // Resolve an `error_set_decl` ZIR instruction // to a named `type_error_set`. Mirrors Sema.zirErrorSetDecl // (src/Sema.zig): for each field, intern the error name into the IP // string arena (and register the global error value — a no-op for the IP dump, // so omitted), then sort the names by IP string index and intern the error set // type (pt.errorSetFromUnsortedNames // -> InternPool.getErrorSetType). Shared by the frame-free type resolver // (sema_comptime.c) and zirErrorSetDecl. Upstream's // zirErrorSetDecl does not branch on block.semaBlockIsComptime(): an inline // `error{...}` decl is always a comptime type regardless of the enclosing // function's runtime/comptime-ness, so one resolver correctly serves both // dispatch paths. uint32_t semaResolveErrorSetDeclInst( InternPool* ip, const Zir* zir, uint32_t inst) { ZirInstErrorSetDecl extra; uint32_t names_start = zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &extra); uint32_t fields_len = extra.fields_len; if (fields_len == 0) return ipGetErrorSetType(ip, NULL, 0); uint32_t* names = zig1Malloc((size_t)fields_len * sizeof(uint32_t)); for (uint32_t j = 0; j < fields_len; j++) { uint32_t name_off = zir->extra[names_start + j]; const char* name = zirNullTerminatedString(zir, name_off); names[j] = ipGetOrPutString(ip, name, (uint32_t)strlen(name)); } // A signature is evaluated more than once here, once upstream: only the // first evaluation takes getErrorSetType's map slot. uint32_t set_ty = ipErrorSetTypeIfExists(ip, names, fields_len); if (set_ty == IP_INDEX_NONE) set_ty = zptErrorSetFromUnsortedNames(ip, names, fields_len); free(names); return set_ty; } // (part of) Sema.zig:errorSetMerge // Merge two already-resolved error-set IP values (Sema.errorSetMerge, // src/Sema.zig): merging with anyerror short-circuits to anyerror; // otherwise combine both name lists (rhs deduped against lhs), sort by IP // string index, and intern (the same canonicalization // semaResolveErrorSetDeclInst applies). `lhs`/`rhs` must each be // `ZIR_REF_ANYERROR_TYPE` or a `type_error_set`; anything else aborts via // `context`. Declared in zig1.h and shared by analyzeBodyInner's runtime-body // ZIR_INST_MERGE_ERROR_SETS case (sema.c) in addition to this // file's comptime ctResolveParamTypeRef arm. uint32_t semaMergeResolvedErrorSetTypes( InternPool* ip, uint32_t lhs, uint32_t rhs, const char* context) { if (lhs != ZIR_REF_ANYERROR_TYPE && (lhs >= ip->items_len || ip->item_tags[lhs] != IP_TAG_TYPE_ERROR_SET)) UNIMPLEMENTED(context); if (rhs != ZIR_REF_ANYERROR_TYPE && (rhs >= ip->items_len || ip->item_tags[rhs] != IP_TAG_TYPE_ERROR_SET)) UNIMPLEMENTED(context); if (lhs == ZIR_REF_ANYERROR_TYPE || rhs == ZIR_REF_ANYERROR_TYPE) return ZIR_REF_ANYERROR_TYPE; uint32_t lhs_len = ipErrorSetNamesLen(ip, lhs); uint32_t rhs_len = ipErrorSetNamesLen(ip, rhs); if (lhs_len + rhs_len == 0) return ipGetErrorSetType(ip, NULL, 0); uint32_t* names = zig1Malloc(((size_t)lhs_len + rhs_len) * sizeof(uint32_t)); uint32_t names_len = 0; for (uint32_t i = 0; i < lhs_len; i++) names[names_len++] = ipErrorSetName(ip, lhs, i); for (uint32_t i = 0; i < rhs_len; i++) { uint32_t name = ipErrorSetName(ip, rhs, i); bool duplicate = false; for (uint32_t j = 0; j < names_len; j++) if (names[j] == name) { duplicate = true; break; } if (!duplicate) names[names_len++] = name; } uint32_t merged = zptErrorSetFromUnsortedNames(ip, names, names_len); free(names); return merged; } // Look each name up in the previous container's namespace and resolve the // final decl's value. Container steps may be structs, unions // (std.lang.Type), or enums. static uint32_t semaStdLangDeclValue( Zcu* zcu, const char* const* path, uint32_t n) { InternPool* ip = &zcu->intern_pool; if (zcu->std_file_idx == UINT32_MAX) { uint32_t std_idx = zptDemandLoadFile(zcu, ZCU_MODULE_STD, STD_ROOT_SUB_PATH); zcu->std_file_idx = std_idx; PerThread pt = { .zcu = zcu, .zir = zcu->files[std_idx].zir }; zptEnsureFilePopulated(&pt, std_idx); } uint32_t std_ns = zcu->files[zcu->std_file_idx].ns; uint32_t lang_str = ipGetOrPutString(ip, "lang", 4); uint32_t lang_nav = ipNamespaceLookup(ip, std_ns, lang_str); if (lang_nav == IP_INDEX_NONE) UNIMPLEMENTED("getStdLangType: std missing 'lang'"); uint32_t ns = ipStructNamespaceOrNone( ip, zptEnsureNavValUpToDate(zcu, lang_nav, NULL)); uint32_t val = IP_INDEX_NONE; for (uint32_t i = 0; i < n; i++) { if (ns == IP_NAMESPACE_NONE) UNIMPLEMENTED("getStdLangType: step has no namespace"); uint32_t s = ipGetOrPutString(ip, path[i], (uint32_t)strlen(path[i])); uint32_t nav = ipNamespaceLookup(ip, ns, s); if (nav == IP_INDEX_NONE) UNIMPLEMENTED("getStdLangType: member not found"); val = i + 1 == n ? navAnalyzeVal(zcu, nav, NULL) : zptEnsureNavValUpToDate(zcu, nav, NULL); if (i + 1 == n) break; ns = ipContainerNamespaceOrNone(ip, val); } return val; } // Zcu.zig:StdLangDecl // The `.main` stage's decls (Zcu.StdLangDecl.stage: everything up to // `Type.Spirv.Image.Access`), in declaration order, from upstream_gen.h. #define STD_LANG_DECL_NAME(c, name) name, static const char* const std_lang_decls[] = { STD_LANG_DECL_NAMES(STD_LANG_DECL_NAME) }; #undef STD_LANG_DECL_NAME // Sema.zig:analyzeMemoizedState // The `.main` stage: each decl's value in order, a type's layout resolved // (`.type => ... ensureLayoutResolved(ty, decl_src, .std_lang_type)`). // `returnError`, the one `.func` (StdLangDecl.kind), already has // getExpectedBuiltinFnType's `noinline fn () void`, so its coercion interns // nothing. static void semaAnalyzeMemoizedStateMain(Zcu* zcu) { for (size_t d = 0; d <= STD_LANG_DECL_TYPE_SPIRV_IMAGE_ACCESS; d++) { const char* path[4]; char buf[4][32]; uint32_t n = 0; for (const char* p = std_lang_decls[d];;) { const char* dot = strchr(p, '.'); size_t len = dot ? (size_t)(dot - p) : strlen(p); memcpy(buf[n], p, len); buf[n][len] = 0; path[n] = buf[n]; n++; if (!dot) break; p = dot + 1; } uint32_t val = semaStdLangDeclValue(zcu, path, n); if (d != STD_LANG_DECL_RETURN_ERROR && !ensureLayoutResolved(zcu, val)) UNIMPLEMENTED("analyzeMemoizedState: std.lang type layout"); } } // (part of) Sema.zig:getStdLangType // Resolves the decl's memoized-state stage (ensureMemoizedStateResolved; // only `.main` is ported), then the decl's value. uint32_t getStdLangType(Zcu* zcu, const char* const* path, uint32_t n) { if (strcmp(path[0], "VaList") != 0 && strcmp(path[0], "assembly") != 0 && strcmp(path[0], "panic") != 0) { if (zcu->std_lang_main_state == STD_LANG_STAGE_RESOLVING) UNIMPLEMENTED("getStdLangType: dependency loop in .main"); if (zcu->std_lang_main_state == STD_LANG_STAGE_UNRESOLVED) { zcu->std_lang_main_state = STD_LANG_STAGE_RESOLVING; semaAnalyzeMemoizedStateMain(zcu); zcu->std_lang_main_state = STD_LANG_STAGE_RESOLVED; } } return semaStdLangDeclValue(zcu, path, n); } // ── Function types and calling conventions (funcCommon) ───────────────────── // (part of) Sema.zig:checkCallConvSupportsVarArgs // Sema.checkCallConvSupportsVarArgs: of the conventions wasm32 accepts, only // `wasm_mvp` is in calling_conventions_supporting_var_args (any other one // fails ccValidate's arch check anyway). void ccCheckSupportsVarArgs(const FuncCallconv* cc) { if (cc->tag == CC_TAG_AUTO || cc->tag == CC_TAG_ASYNC || cc->tag == CC_TAG_NAKED || cc->tag == CC_TAG_INLINE) COMPILE_ERROR_UPSTREAM("variadic function does not support this " "calling convention"); } // Sema.zig:checkVectorElemType, Type.zig:isPtrAtRuntime, // Type.zig:isPtrLikeOptional // Sema.checkVectorElemType (src/Sema.zig), // the whole function: // // switch (ty.zigTypeTag(zcu)) { // .int, .float, .bool => return, // .optional, .pointer => if (ty.isPtrAtRuntime(zcu)) return, // else => {}, // } // return sema.fail(block, ty_src, "expected integer, float, bool, or // pointer for the vector element type; found '{f}'", .{ty.fmt(pt)}); // // `ztIntInfo` is exactly the `.int` tag set (the two fixed-width int // tags plus the `usize`/`isize`/`c_*` preinterns) and `ztSimpleFloatBits` // exactly the `.float` one (`f16`..`f128` plus `c_longdouble`); neither // admits `comptime_int`/`comptime_float`, which carry their own zigTypeTag. // `Type.isPtrAtRuntime` (src/Type.zig) is a `.ptr_type` arm -- // `".slice => false, .one, .many, .c => true"` -- and an `.opt_type` arm // whose body is character-for-character `Type.isPtrLikeOptional`'s, // so the optional side reuses `ztTypeIsPtrLikeOptional` rather // than restating it. zig1 gives a slice its own IP_TAG_TYPE_SLICE, which // is therefore neither of those tags and falls to the error, matching the // `.slice => false` upstream answers for `[]T`. void checkVectorElemType(const InternPool* ip, uint32_t elem_ty) { uint32_t bits; bool is_signed; if (elem_ty < ip->items_len && ztIntInfo(ip, elem_ty, &bits, &is_signed)) return; if (ztSimpleFloatBits(elem_ty, &bits) || elem_ty == ZIR_REF_BOOL_TYPE) return; IpPtrInfo pi; if (ipPtrInfo(ip, elem_ty, &pi) == IP_PTRI_OK && pi.size != IP_PTR_SIZE_SLICE) return; if (ztTypeIsPtrLikeOptional(ip, elem_ty)) return; COMPILE_ERROR_UPSTREAM("vector_type: expected integer, float, bool, or " "pointer for the vector element type"); } // (part of) Sema.zig:zirFuncFancy, Sema.zig:zirStdLangValue // `std.lang.CallingConvention`, the union type an explicit `callconv(...)` // expression is coerced to (Sema.zirFuncFancy: "const cc_ty = try // sema.getStdLangType(cc_src, .CallingConvention)"). // `Sema.zirStdLangValue`'s `std_lang_type` switch (src/Sema.zig), // transcribed whole and in upstream's order: `Zir.Inst.StdLangValue` -> // the dotted `Zcu.StdLangDecl` path under `std.lang`. The two value cases // (`.calling_convention_c`, `.calling_convention_inline`) are not types and // each caller answers them itself; this returns false for them and for any // unknown ordinal, so the caller still aborts loudly. // // One table instead of two if/else ladders: the comptime fold and // analyzeBodyInner each recognized a different subset of the same switch // (comptime knew 2/5/9/11/13/14/16/17, runtime knew 0/1/4/5/7/11), so every // entry either line lacked was an abort in a program the other could handle. bool semaStdLangValueTypePath( uint16_t value, const char* const** out_path, uint32_t* out_len) { static const char* const p_atomic_order[] = { "AtomicOrder" }; static const char* const p_atomic_rmw_op[] = { "AtomicRmwOp" }; static const char* const p_calling_convention[] = { "CallingConvention" }; static const char* const p_address_space[] = { "AddressSpace" }; static const char* const p_float_mode[] = { "FloatMode" }; static const char* const p_signedness[] = { "Signedness" }; static const char* const p_reduce_op[] = { "ReduceOp" }; static const char* const p_call_modifier[] = { "CallModifier" }; static const char* const p_prefetch_options[] = { "PrefetchOptions" }; static const char* const p_export_options[] = { "ExportOptions" }; static const char* const p_extern_options[] = { "ExternOptions" }; static const char* const p_branch_hint[] = { "BranchHint" }; static const char* const p_clobbers[] = { "assembly", "Clobbers" }; static const char* const p_pointer_size[] = { "Type", "Pointer", "Size" }; static const char* const p_pointer_attributes[] = { "Type", "Pointer", "Attributes" }; static const char* const p_fn_attributes[] = { "Type", "Fn", "Attributes" }; static const char* const p_container_layout[] = { "Type", "ContainerLayout" }; static const char* const p_enum_mode[] = { "Type", "Enum", "Mode" }; static const char* const p_spirv_type_options[] = { "Type", "Spirv" }; struct Entry { const char* const* path; uint32_t len; }; static const struct Entry table[] = { { p_atomic_order, 1 }, // .atomic_order => .AtomicOrder { p_atomic_rmw_op, 1 }, // .atomic_rmw_op => .AtomicRmwOp { p_calling_convention, 1 }, // .calling_convention => .CallingConvention { p_address_space, 1 }, // .address_space => .AddressSpace { p_float_mode, 1 }, // .float_mode => .FloatMode { p_signedness, 1 }, // .signedness => .Signedness { p_reduce_op, 1 }, // .reduce_op => .ReduceOp { p_call_modifier, 1 }, // .call_modifier => .CallModifier { p_prefetch_options, 1 }, // .prefetch_options => .PrefetchOptions { p_export_options, 1 }, // .export_options => .ExportOptions { p_extern_options, 1 }, // .extern_options => .ExternOptions { p_branch_hint, 1 }, // .branch_hint => .BranchHint { p_clobbers, 2 }, // .clobbers => .@"assembly.Clobbers" { p_pointer_size, 3 }, // .pointer_size => .@"Type.Pointer.Size" { p_pointer_attributes, 3 }, // .pointer_attributes { p_fn_attributes, 3 }, // .fn_attributes => .@"Type.Fn.Attributes" { p_container_layout, 2 }, // .container_layout { p_enum_mode, 3 }, // .enum_mode => .@"Type.Enum.Mode" { p_spirv_type_options, 2 }, // .spirv_type_options => .@"Type.Spirv" }; if (value >= (uint16_t)(sizeof(table) / sizeof(table[0]))) return false; *out_path = table[value].path; *out_len = table[value].len; return true; } static uint32_t ccType(Zcu* zcu) { const char* const* path; uint32_t len; if (!semaStdLangValueTypePath( STD_LANG_VALUE_CALLING_CONVENTION, &path, &len)) UNIMPLEMENTED("std_lang_value: table missing callconv"); return getStdLangType(zcu, path, len); } // (part of) Sema.zig:zirStdLangValue, Value.zig:uninterpret // Sema.zirStdLangValue's `.calling_convention_c` arm: `c` is a *declaration* // whose value depends on the target ("Cannot use `Value.uninterpret` because // `c` is a *declaration*"), so upstream resolves it with `namespaceLookupVal` // on the CallingConvention namespace. On zig1's wasm32 target that decl's // body is `builtin.target.cCallingConvention().?` == `.{ .wasm_mvp = .{} }`. static uint32_t ccNamespaceValue(Zcu* zcu, const char* name) { InternPool* ip = &zcu->intern_pool; uint32_t cc_ty = ccType(zcu); uint32_t ns = ipUnionNamespaceOrNone(ip, cc_ty); if (ns == IP_NAMESPACE_NONE) UNIMPLEMENTED( "zirStdLangValue: callconv - CallingConvention has no namespace"); uint32_t nav = ipNamespaceLookup( ip, ns, ipGetOrPutString(ip, name, (uint32_t)strlen(name))); if (nav == IP_INDEX_NONE) UNIMPLEMENTED( "zirStdLangValue: callconv - CallingConvention member not found"); return zptEnsureNavValUpToDate(zcu, nav, NULL); } // (part of) Sema.zig:zirStdLangValue, Value.zig:uninterpret // Sema.zirStdLangValue's `.calling_convention_inline` arm: // `Value.uninterpret(@as(std.lang.CallingConvention, .@"inline"), cc_ty)` -- // the union value whose active tag is `inline` and whose payload is void. static uint32_t ccInlineValue(Zcu* zcu) { InternPool* ip = &zcu->intern_pool; uint32_t cc_ty = ccType(zcu); uint32_t tag_enum = ipEnsureGeneratedUnionTag(ip, cc_ty); uint32_t tag = ipEnumTagFromFieldName( ip, tag_enum, ipGetOrPutString(ip, "inline", 6)); if (tag == IP_INDEX_NONE) UNIMPLEMENTED("zirStdLangValue: callconv - CallingConvention has no " "`inline` tag"); return ipGetUnionValue(ip, cc_ty, tag, ZIR_REF_VOID_VALUE); } // (part of) Sema.zig:zirStdLangValue // Sema.zirStdLangValue's two value arms, shared by zirStdLangValue and by a // bare `cc_ref` -- AstGen emits a ref only for the synthesized values // (`extern "c"` without an explicit callconv, `inline fn`). static uint32_t ccStdLangValue(Zcu* zcu, const Zir* zir, uint32_t inst) { if (zir->inst_tags[inst] != ZIR_INST_EXTENDED || zir->inst_datas[inst].extended.opcode != ZIR_EXT_STD_LANG_VALUE) UNIMPLEMENTED( "std_lang_value: callconv: cc ref is not a std_lang_value"); uint16_t small = zir->inst_datas[inst].extended.small; if (small == STD_LANG_VALUE_CALLING_CONVENTION_C) return ccNamespaceValue(zcu, "c"); if (small == STD_LANG_VALUE_CALLING_CONVENTION_INLINE) return ccInlineValue(zcu); UNIMPLEMENTED("std_lang_value: unmodeled value"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // Arch.fromCallingConvention: the architectures a // convention applies to. `CallingConvention` declares its fields grouped by // architecture in the same order that switch groups them, so one row per // group -- the group's last tag ordinal and its arch list, spelled the way // Sema's ArchListFormatter prints it (src/Sema.zig) -- reproduces // the whole switch. The first row is the four conventions the switch asserts // against (`unreachable`); `callconvSupported` never asks them for archs. static const struct { uint8_t last_tag; const char* archs; } cc_arch_groups[] = { { CC_TAG_INLINE, NULL }, { CC_TAG_X86_64_INTERRUPT, "'x86_64'" }, { CC_TAG_X86_INTERRUPT, "'x86'" }, { CC_TAG_X86_16_INTERRUPT, "'x86_16'" }, { CC_TAG_AARCH64_VFABI_SVE, "'aarch64', 'aarch64_be'" }, { CC_TAG_ALPHA_OSF, "'alpha'" }, { CC_TAG_ARM_INTERRUPT, "'arm', 'armeb', 'thumb', 'thumbeb'" }, { CC_TAG_MIPS64_INTERRUPT, "'mips64', 'mips64el'" }, { CC_TAG_MIPS_INTERRUPT, "'mips', 'mipsel'" }, { CC_TAG_RISCV64_INTERRUPT, "'riscv64', 'riscv64be'" }, { CC_TAG_RISCV32_INTERRUPT, "'riscv32', 'riscv32be'" }, { CC_TAG_SPARC64_SYSV, "'sparc64'" }, { CC_TAG_SPARC_SYSV, "'sparc'" }, { CC_TAG_POWERPC64_ELF_V2, "'powerpc64', 'powerpc64le'" }, { CC_TAG_POWERPC_AIX_ALTIVEC, "'powerpc', 'powerpcle'" }, { CC_TAG_WASM_MVP, "'wasm64', 'wasm32'" }, { CC_TAG_ARC_INTERRUPT, "'arc', 'arceb'" }, { CC_TAG_AVR_INTERRUPT, "'avr'" }, { CC_TAG_BPF_STD, "'bpfel', 'bpfeb'" }, { CC_TAG_CSKY_INTERRUPT, "'csky'" }, { CC_TAG_HEXAGON_SYSV_HVX, "'hexagon'" }, { CC_TAG_HPPA_ELF, "'hppa'" }, { CC_TAG_HPPA64_ELF, "'hppa64'" }, { CC_TAG_KVX_ILP32, "'kvx'" }, { CC_TAG_LANAI_SYSV, "'lanai'" }, { CC_TAG_LOONGARCH64_LP64, "'loongarch64'" }, { CC_TAG_LOONGARCH32_ILP32, "'loongarch32'" }, { CC_TAG_M68K_INTERRUPT, "'m68k'" }, { CC_TAG_M88K_SYSV, "'m88k'" }, { CC_TAG_MICROBLAZE_INTERRUPT, "'microblaze', 'microblazeel'" }, { CC_TAG_MSP430_INTERRUPT, "'msp430'" }, { CC_TAG_OR1K_SYSV, "'or1k'" }, { CC_TAG_PROPELLER_SYSV, "'propeller'" }, { CC_TAG_S390X_SYSV_VX, "'s390x'" }, { CC_TAG_SH_INTERRUPT, "'sh', 'sheb'" }, { CC_TAG_VE_SYSV, "'ve'" }, { CC_TAG_XCORE_XS2, "'xcore'" }, { CC_TAG_XTENSA_WINDOWED, "'xtensa', 'xtensaeb'" }, { CC_TAG_AMDGCN_CS, "'amdgcn'" }, { CC_TAG_NVPTX_KERNEL, "'nvptx', 'nvptx64'" }, { CC_TAG_SPIRV_MESH, "'spirv32', 'spirv64'" }, { CC_TAG_EZ80_TIFLAGS, "'ez80'" }, }; // `cc.archs()` (lib/std/lang.zig). static const char* ccArchs(uint32_t tag) { for (size_t i = 0; i < sizeof(cc_arch_groups) / sizeof(cc_arch_groups[0]); i++) if (tag <= cc_arch_groups[i].last_tag) return cc_arch_groups[i].archs; UNIMPLEMENTED( "funcCommon: callconv - tag outside std.lang.CallingConvention"); ZIG1_TCC_RETURN(NULL); } // `@tagName(cc)`, for the `'{t}'` a diagnostic prints. Read at the point of // printing: `ipStringBytes` points into the intern pool's string arena, which // moves on any intern (intern_pool.c's string_bytes growth), so the name must // never be stored across one. static const char* ccTagName(Zcu* zcu, uint32_t tag) { InternPool* ip = &zcu->intern_pool; return ipStringBytes(ip, ipUnionFieldName(ip, ccType(zcu), tag)); } // Decode an interned `std.lang.CallingConvention` union value into the fields // `PackedCallingConvention.pack` (InternPool.zig) reads. `pack` is a // `switch (cc) { inline else => |pl, tag| ... }` over the payload type: // - `void` => no alignment, extra 0; // - `CommonOptions` => `incoming_stack_alignment`, extra 0; // - every other non-SPIR-V option => the same alignment plus `extra` from // that struct's one discriminating // field (`register_params`, or // `@intFromEnum` of `type`/`mode`/ // `save`), which is the second and last // declared field; // - the SPIR-V options structs => workgroup dimensions in trailing // words, which this port cannot mint // (ccPack). They carry no // `incoming_stack_alignment`, so the // payload decode below skips them and // leaves the rejection to ccValidate. static FuncCallconv ccResolveValue(Zcu* zcu, uint32_t cc_val) { InternPool* ip = &zcu->intern_pool; FuncCallconv out = { 0, 0, 0 }; if (cc_val < ZIR_REF_START_INDEX || cc_val >= ip->items_len || ip->item_tags[cc_val] != IP_TAG_UNION_VALUE || ipTypeOf(ip, cc_val) != ccType(zcu)) UNIMPLEMENTED("funcCommon: callconv - value is not a " "std.lang.CallingConvention"); uint32_t cc_ty = ccType(zcu); uint32_t tag_val = ipUnionValueTag(ip, cc_val); uint32_t payload = ipUnionValuePayload(ip, cc_val); if (tag_val >= ip->items_len || ip->item_tags[tag_val] != IP_TAG_ENUM_TAG) UNIMPLEMENTED("funcCommon: callconv - active tag is not an enum_tag"); uint64_t tag_bits = 0; if (!ipTryReadIntLow64Bits(ip, ipEnumTagValue(ip, tag_val), &tag_bits) || tag_bits > 0xFFu) UNIMPLEMENTED( "funcCommon: callconv - tag does not fit CallingConvention.Tag"); out.tag = (uint32_t)tag_bits; uint32_t field_idx = ipEnumTagFieldIndex(ip, tag_val); if (field_idx == UINT32_MAX) UNIMPLEMENTED( "funcCommon: callconv - active tag is not a declared field"); uint32_t payload_ty = ipUnionFieldType(ip, cc_ty, field_idx); if (payload_ty == ZIR_REF_VOID_TYPE) return out; if (payload_ty >= ip->items_len || !ipTagIsAnyStruct(ip->item_tags[payload_ty])) UNIMPLEMENTED( "funcCommon: callconv - payload is not an options struct"); uint32_t isa = ipStructFieldIndex( ip, payload_ty, ipGetOrPutString(ip, "incoming_stack_alignment", 24)); if (isa == UINT32_MAX) return out; // a SPIR-V options struct; see ccPack uint32_t opt = valElemValue(ip, payload, isa, "Value.elemValue: comptime"); if (opt < ip->items_len && ip->item_tags[opt] == IP_TAG_OPT_PAYLOAD) { uint32_t inner = ipPayloadVal(ip, opt); if (!ipTryReadIntLow64Bits(ip, inner, &out.incoming_stack_alignment)) UNIMPLEMENTED( "funcCommon: callconv - incoming_stack_alignment not an int"); } else if (opt >= ip->items_len || (ip->item_tags[opt] != IP_TAG_OPT_NULL && ip->item_tags[opt] != IP_TAG_ONLY_POSSIBLE_VALUE)) { UNIMPLEMENTED( "funcCommon: callconv - incoming_stack_alignment not an optional"); } uint32_t fields_len = ipStructFieldsLen(ip, payload_ty); if (fields_len > 2) UNIMPLEMENTED( "funcCommon: callconv - unmodeled options payload shape"); if (fields_len == 2) { uint32_t ev = valElemValue( ip, payload, isa == 0 ? 1 : 0, "Value.elemValue: comptime"); if (ev < ip->items_len && ip->item_tags[ev] == IP_TAG_ENUM_TAG) ev = ipEnumTagValue(ip, ev); uint64_t ebits = 0; if (!ipTryReadIntLow64Bits(ip, ev, &ebits) || ebits > IP_PACKED_CC_EXTRA_MASK) UNIMPLEMENTED( "funcCommon: callconv - options `extra` does not fit u4"); out.extra = (uint32_t)ebits; } return out; } // InternPool.PackedCallingConvention.pack (InternPool.zig): the packed // u18 { tag: u8, incoming_stack_alignment: Alignment (u6), extra: u4 } that // occupies the low 18 bits of Tag.TypeFunction.Flags. Runs after ccValidate, // exactly as funcCommon packs only once checkReturnTypeAndCallConv has // returned: `Alignment.fromByteUnits` asserts the byte count is a power of // two, which is `validate_incoming_stack_align`'s job to guarantee. uint32_t ccPack(const FuncCallconv* cc) { if (cc->tag == CC_TAG_SPIRV_KERNEL || cc->tag == CC_TAG_SPIRV_TASK || cc->tag == CC_TAG_SPIRV_MESH) UNIMPLEMENTED( "funcCommon: callconv - SPIR-V `extra` words are unmintable"); uint32_t align_enc = ipAlignmentFromByteUnits( cc->incoming_stack_alignment, "funcCommon: alignment not encodable"); return (cc->tag << IP_PACKED_CC_TAG_SHIFT) | (align_enc << IP_PACKED_CC_INCOMING_STACK_ALIGNMENT_SHIFT) | (cc->extra << IP_PACKED_CC_EXTRA_SHIFT); } // (part of) Type.zig:validateExtern // Type.validateExtern (src/Type.zig) at a `.param_ty`/`.ret_ty` // position, ported for the arms this port can both decide from what it // interns and name in the diagnostic: an `extern struct` is // extern-compatible, `void`/`noreturn` are only as a return type, and an // auto-layout struct, an auto-tagged enum and an array are not. Every other // arm -- a tuple, a slice, an error set or error union, an integer at a // non-ABI width, a `packed` container's backing mode, an `extern union` // (`type_union` carries the auto one too, indistinguishably), a ptr-like // optional -- is not ported: `ztFormatTypeName` cannot spell those types, and // a diagnostic this port cannot print is worse than the prior acceptance it // would replace. Accepted residue. static bool ccExternTypeOk(const InternPool* ip, uint32_t ty, bool is_ret_ty) { // `.void` is extern-compatible only as a return type, and `.noreturn` is // `position == .ret_ty` outright (src/Type.zig). if (ty == ZIR_REF_VOID_TYPE || ty == ZIR_REF_NORETURN_TYPE) return is_ret_ty; if (ty >= ip->items_len) return true; switch (ip->item_tags[ty]) { case IP_TAG_TYPE_STRUCT: return ipStructIsExtern(ip, ty); case IP_TAG_TYPE_ENUM_AUTO: case IP_TAG_TYPE_ARRAY_SMALL: return false; default: return true; } } // (part of) Sema.zig:funcCommon, Zcu.zig:callconvSupported // Sema.funcCommon's calling-convention validation, specialized to zig1's // fixed `wasm32-wasi` target and `stage2_wasm` backend. Ported checks, in // upstream's order: // checkParamType -> `!fnCallConvAllowsZigTypes(cc)` rejects a // comptime or generic parameter; // checkReturnTypeAndCallConv -> the same predicate rejects an inferred // error set or a generic return type, then // `validate_incoming_stack_align` (power of // two), then the per-convention switch -- // whose only arm reachable on wasm32 is // `.@"inline"` vs `noinline`, since every // other arm names a convention this target's // arch check rejects below; // Zcu.callconvSupported -> `.auto`/`.@"inline"` are always ok, // `.async` is `bad_backend`, `.naked` depends // only on the backend, and every other // convention must list the target arch // (`Arch.fromCallingConvention`) and then // satisfy the backend, whose `.stage2_wasm` // arm accepts `wasm_mvp` with no incoming // stack alignment and nothing else. void ccValidate(Zcu* zcu, const FuncCallconv* cc, bool is_noinline, uint32_t nparams, uint32_t comptime_bits, const uint32_t* param_types, uint32_t ret_ty, bool is_inferred_error, bool has_body) { InternPool* ip = &zcu->intern_pool; static char msg[256]; // target_util.fnCallConvAllowsZigTypes (src/target.zig). bool allows_zig_types = cc->tag == CC_TAG_AUTO || cc->tag == CC_TAG_ASYNC || cc->tag == CC_TAG_INLINE || cc->tag == CC_TAG_NVPTX_KERNEL; // checkParamType (src/Sema.zig), per parameter in declaration // order: `Type.isValidParamType` first -- it rejects `noreturn` whatever // the convention, and before any convention-specific arm -- then the // convention's own comptime/generic rules. Upstream's other invalid // parameter and return type is an `opaque` one (`isValidParamType` / // `isValidReturnType`, src/Type.zig); that arm is not ported because // `ztFormatTypeName` cannot spell an opaque type, the same residue policy // as ccExternTypeOk. for (uint32_t i = 0; i < nparams; i++) { if (param_types[i] == ZIR_REF_NORETURN_TYPE) { TypeNameWriter param_name = { 0 }; semaFormatTypeNameDiagnostic(ip, param_types[i], ¶m_name); snprintf(msg, sizeof(msg), "parameter of type '%s' not allowed", param_name.buf.arr); free(param_name.buf.arr); COMPILE_ERROR_UPSTREAM(msg); } if (allows_zig_types) continue; if (ipFuncTypeParamBit(comptime_bits, i)) { snprintf(msg, sizeof(msg), "comptime parameters not allowed in function with " "calling convention '%s'", ccTagName(zcu, cc->tag)); COMPILE_ERROR_UPSTREAM(msg); } if (param_types[i] == IP_INDEX_GENERIC_POISON_TYPE) { snprintf(msg, sizeof(msg), "generic parameters not allowed in function with calling " "convention '%s'", ccTagName(zcu, cc->tag)); COMPILE_ERROR_UPSTREAM(msg); } } if (!allows_zig_types) { if (is_inferred_error) { TypeNameWriter ret_name = { 0 }; semaFormatTypeNameDiagnostic(ip, ret_ty, &ret_name); snprintf(msg, sizeof(msg), "return type '!%s' not allowed in function with calling " "convention '%s'", ret_name.buf.arr, ccTagName(zcu, cc->tag)); free(ret_name.buf.arr); COMPILE_ERROR_UPSTREAM(msg); } if (ret_ty == IP_INDEX_GENERIC_POISON_TYPE) { snprintf(msg, sizeof(msg), "generic return type not allowed in function with calling " "convention '%s'", ccTagName(zcu, cc->tag)); COMPILE_ERROR_UPSTREAM(msg); } } // validate_incoming_stack_align (src/Sema.zig), on the byte // count: `PackedCallingConvention` has no encoding for a non-power-of-two. uint64_t isa = cc->incoming_stack_alignment; if (isa != 0 && (isa & (isa - 1u)) != 0) { snprintf(msg, sizeof(msg), "calling convention incoming stack alignment '%llu' is not a " "power of two", (unsigned long long)isa); COMPILE_ERROR_UPSTREAM(msg); } if (cc->tag == CC_TAG_INLINE && is_noinline) COMPILE_ERROR_UPSTREAM( "'noinline' function cannot have calling convention 'inline'"); if (cc->tag == CC_TAG_AUTO || cc->tag == CC_TAG_INLINE) return; // callconvSupported: `.auto, .@"inline" => return .ok` if (cc->tag != CC_TAG_ASYNC && cc->tag != CC_TAG_NAKED) { // `for (cc.archs()) |a| if (a == target.cpu.arch) break else bad_arch` // against this port's fixed `wasm32` target. const char* archs = ccArchs(cc->tag); if (strstr(archs, "'wasm32'") == NULL) { snprintf(msg, sizeof(msg), "calling convention '%s' only available on architectures %s", ccTagName(zcu, cc->tag), archs); COMPILE_ERROR_UPSTREAM(msg); } } // stage2_wasm: `.wasm_mvp => |opts| opts.incoming_stack_alignment == null, // else => false`. A non-null alignment that got this far is a power of // two, so a non-zero byte count is exactly `!= null`. `.async` reaches the // same message through `callconvSupported`'s dedicated `bad_backend` arm. if (cc->tag != CC_TAG_WASM_MVP || isa != 0) { snprintf(msg, sizeof(msg), "calling convention '%s' not supported by compiler backend " "'stage2_wasm'", ccTagName(zcu, cc->tag)); COMPILE_ERROR_UPSTREAM(msg); } // Sema.validateResolvedFuncType (src/Sema.zig): under a // convention that does not allow Zig types every parameter and the return // type must be extern-compatible. funcCommon runs it only `if (has_body)` // (src/Sema.zig) -- naming a function type is legal whatever its // signature; the check is deferred to the declaration or the call. if (!has_body) return; for (uint32_t i = 0; i < nparams; i++) { if (ccExternTypeOk(ip, param_types[i], false)) continue; TypeNameWriter param_name = { 0 }; semaFormatTypeNameDiagnostic(ip, param_types[i], ¶m_name); snprintf(msg, sizeof(msg), "parameter of type '%s' not allowed in function with calling " "convention '%s'", param_name.buf.arr, ccTagName(zcu, cc->tag)); free(param_name.buf.arr); COMPILE_ERROR_UPSTREAM(msg); } if (!ccExternTypeOk(ip, ret_ty, true)) { TypeNameWriter ret_name = { 0 }; semaFormatTypeNameDiagnostic(ip, ret_ty, &ret_name); snprintf(msg, sizeof(msg), "return type '%s' not allowed in function with calling " "convention '%s'", ret_name.buf.arr, ccTagName(zcu, cc->tag)); free(ret_name.buf.arr); COMPILE_ERROR_UPSTREAM(msg); } } // (part of) Sema.zig:zirFuncFancy, AstGen.zig:fnProtoExpr // Sema.zirFuncFancy's explicit calling convention: resolve the `cc_body` // (`resolveGenericBody(block, cc_src, body, inst, cc_ty, .{ .simple = // .@"callconv" })`) or the `cc_ref` (`coerce` + `resolveConstDefinedValue`) // to a comptime `std.lang.CallingConvention` value, then // `analyzeValueAsCallconv` it into the packed convention `funcCommon` stores. // Sema.zirFuncFancy's `has_cc_ref` arm is `sema.resolveInst(cc_ref)`: the // value the enclosing block already produced for that instruction: in a live // frame (a fn-proto type) that frame's value; for a declaration, whose // AstGen.fnProtoExpr emits the callconv expression into its value body, // that body re-run up to and including the cc instruction. static uint32_t ccResolveRefInBody(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t cc_inst, uint32_t ns, TypeNameCtx type_name_ctx) { uint32_t n = 0; while (n < body_len && body[n] != cc_inst) n++; if (n == body_len) UNIMPLEMENTED( "zirFuncFancy: callconv - cc ref is not in the enclosing block"); uint32_t* values = semaInstMapAlloc(zir); ctEvalBodyScoped(zcu, zir, body, n + 1, values, ns, type_name_ctx); uint32_t v = values[cc_inst]; free(values); if (v == INST_VALUE_UNSET) UNIMPLEMENTED("funcCommon: callconv body produced no value"); return v; } FuncCallconv ccResolveFuncFancy(Zcu* zcu, const Zir* zir, uint32_t func_inst, const uint32_t* block_body, uint32_t block_body_len, uint32_t ns, TypeNameCtx type_name_ctx, const uint32_t* live_values) { ZirInstFuncFancy extra; uint32_t extra_index = zirExtraData( zir, zir->inst_datas[func_inst].pl_node.payload_index, &extra); uint32_t val; if ((extra.bits & ZIR_FUNC_FANCY_BITS_HAS_CC_BODY) != 0) { uint32_t cc_body_len = zir->extra[extra_index]; val = ctFoldComptimeBodyValue(zcu, zir, zirBodySlice(zir, extra_index + 1), cc_body_len, ns, type_name_ctx, UINT32_MAX, 0); } else { uint32_t cc_ref = zir->extra[extra_index]; if (cc_ref < ZIR_REF_START_INDEX) UNIMPLEMENTED( "zirFuncFancy: callconv - cc ref is not an instruction"); uint32_t cc_inst = cc_ref - ZIR_REF_START_INDEX; val = (zir->inst_tags[cc_inst] == ZIR_INST_EXTENDED && zir->inst_datas[cc_inst].extended.opcode == ZIR_EXT_STD_LANG_VALUE) ? ccStdLangValue(zcu, zir, cc_inst) : live_values != NULL && live_values[cc_inst] != INST_VALUE_UNSET ? live_values[cc_inst] : ccResolveRefInBody(zcu, zir, block_body, block_body_len, cc_inst, ns, type_name_ctx); } return ccResolveValue(zcu, val); } // ── Enum and tuple declaration types (semaEnumDeclType, semaTupleDeclType) ── // Shared Sema.zirTupleDecl core (defined with the comptime evaluator below): // decodes + interns an inline tuple type declaration. Forward-declared so a // fn-proto return type that is an extended tuple_decl (`*const fn (...) struct // { E, usize }`) can resolve; `values` is the enclosing ctEvalBody frame, or // NULL from the single-ref type resolver. The enum-field validation upstream // performs during type resolution; defined below, next to // `enumFieldValueInternU64`'s range rule. static void semaValidateEnumFields(InternPool* ip, uint32_t int_tag_ty, bool nonexhaustive, uint32_t fields_len, const uint32_t* names_ip, const uint32_t* vals_ip); // (part of) Sema.zig:zirEnumDecl // For an enum (or opaque) with member decls (methods / consts): create the // enum's namespace, backpatch it into the interned type, and scan the // declarations into it so method calls on enum values and member lookups // resolve. Mirrors Sema.zirEnumDecl (src/Sema.zig): `createNamespace(.{ // .parent = block.namespace, ... })` // + `scanNamespace(new_namespace_index, enum_decl.decls)` — stock creates the // namespace and scans the decls uniformly regardless of name strategy, so // semaBodyEnumType parents it at the evaluation scope, stock's // block.namespace (for Cpu.Arch, Cpu's). The scan only registers Navs // (lazy, like stock scanNamespace) — decl bodies are not evaluated here, so // a self-referential member decl (`pub const default: UseSendfile = // .enabled;` whose type annotation names the outer const being resolved // right now) is safe: it evaluates on its first lookup, after the outer // Nav's value is cached. Scoped lookups inside decl bodies walk outward // through `parent_ns` exactly like stock's namespace chain. // Namespaces/Navs live in separate arenas (no IP items). void semaContainerScanDecls(Zcu* zcu, uint32_t parent_ns, uint32_t owner_ty, const uint32_t* decls_ptr, uint32_t decls_len, const Zir* zir, const CtCap* captures, uint32_t captures_len, void (*set_ns)(InternPool*, uint32_t, uint32_t)) { InternPool* ip = &zcu->intern_pool; uint32_t file_idx = ipGetNamespace(ip, parent_ns)->file_scope; uint32_t ns_idx = ipCreateNamespace(ip, parent_ns, owner_ty, file_idx); set_ns(ip, owner_ty, ns_idx); if (captures_len != 0) ctStoreStructCapturesCopy(zcu, ns_idx, captures, captures_len); if (decls_len == 0) return; PerThread pt = { .zcu = zcu, .zir = zir }; zptScanNamespace(&pt, ns_idx, decls_ptr, decls_len, zir); } // (part of) Sema.zig:zirEnumDecl // Shared payload decode + intern core for a ZIR `enum_decl` extended // instruction (semaBodyEnumType). Mirrors Sema.zirEnumDecl + // Sema/type_resolution.zig resolveEnumLayout, which work purely from the // tracked `enum_decl` instruction — stock has no semantic difference between // a decl-bound (`const E = enum(u3) { ... }`) and an inline field-position // (`cancelation: enum(u3) { ... }`) enum; only zirEnumDecl's name_strategy // (setTypeName) differs. Decodes fields_len, defers the field names to // layout resolution (ipDeferFieldNames) and interns: // - the auto shape (no tag type, exhaustive, no field values) as // `type_enum_auto` with a `.none` int tag type — the forward reference // the caller backpatches (`ipSetEnumIntTagType`) with the inferred // `smallestUnsignedBits` tag (resolveEnumLayout's inferred arm); // - the explicit shape (tag type present) as `type_enum_explicit`: the // tag-type body resolves via ctResolveParamTypeRef (plain int ref) or // ctFoldComptimeTypeBody (call / reify_int body), and field values // evaluate via evalEnumFieldValueU64 (explicit value bodies), continue // mixed explicit/implicit values from the previous tag value + 1, or // auto-number 0..N-1 (exhaustive valueless — resolveEnumLayout's // implicit arm). // `name` is the interned type name and fqn and `name_nav` the Nav it names // (`.parent`; all IP_INDEX_NONE for an anonymous // inline enum); `eval_ns` is the enclosing scope, which holds the enum's // captures while its bodies run and is their namespace when the enum has // none. Member decl instructions are handed back via // out_decls_ptr/out_decls_len for the caller to consume (namespace scan for // a Nav; a loud abort on the inline path). uint32_t semaEnumDeclType(Zcu* zcu, const Zir* zir, uint32_t enum_inst, TypeNameCtx name, uint32_t name_nav, uint32_t eval_ns, uint32_t* out_fields_len, const uint32_t** out_decls_ptr, uint32_t* out_decls_len, const CtCap* captures, uint32_t captures_arg_len, uint32_t layout_ty) { InternPool* ip = &zcu->intern_pool; uint16_t small = zir->inst_datas[enum_inst].extended.small; bool has_tag_type = (small & ZIR_ENUM_SMALL_HAS_TAG_TYPE) != 0; bool nonexhaustive = (small & ZIR_ENUM_SMALL_NONEXHAUSTIVE) != 0; bool any_field_values = (small & ZIR_ENUM_SMALL_ANY_FIELD_VALUES) != 0; // After the count words: captures/capture_names, then the `remaining` // scratch in memory order: decls, field_names[fields_len], // field_value_body_lens[fields_len] (iff any_field_values), tag_type body, // then the per-field value bodies concatenated. ZirContainerTrailing tw = zirContainerTrailingWords(zir, enum_inst); uint32_t ci = tw.captures; uint32_t captures_len = tw.captures_len, decls_len = tw.decls_len; uint32_t fields_len = tw.fields_len, tag_type_body_len = tw.arg_body_len; CtCaptureSet saved_captures = { NULL, 0 }; bool pushed_captures = false; if (captures_len != 0) { if (captures == NULL || captures_arg_len != captures_len) UNIMPLEMENTED("zirEnumDecl: unresolved enum captures"); if (eval_ns == IP_NAMESPACE_NONE) UNIMPLEMENTED("zirEnumDecl: enum captures without namespace"); // Tag-type and field-value bodies belong to this enum declaration; // their `closure_get` indexes address the enum's capture list. Bind // that list for the duration of body evaluation, then restore the // enclosing namespace's capture table. ctPushCaptureScope( zcu, eval_ns, captures, captures_arg_len, &saved_captures); pushed_captures = true; } ci += 2 * captures_len; // captures[] + capture_names[] // decls[]: the enum's member declaration instruction indices (methods / // consts). Handed back to the caller for the namespace scan after the type // is interned. One word per decl (same shape semaBodyStructType consumes). const uint32_t* decls_ptr = &zir->extra[ci]; ci += decls_len; // decls[] (WipDecls; 0 words for decls_len==0) // field_names[fields_len] follow. Heap-sized: an enum's field count is // unbounded. uint32_t* field_names_ip = zig1Calloc(fields_len ? fields_len : 1, 4); uint32_t* field_values_ip = zig1Calloc(fields_len ? fields_len : 1, 4); uint32_t names_off = ci; // Declared enums do not yet have field names populated: // resolveEnumLayout interns them (ipDeferFieldNames below). for (uint32_t i = 0; i < fields_len; i++) field_names_ip[i] = IP_INDEX_NONE; if (out_fields_len) *out_fields_len = fields_len; *out_decls_ptr = decls_ptr; *out_decls_len = decls_len; ci += fields_len; // Sema.zirEnumDecl: `"ip.getDeclaredEnumType(...)"` interns the type // before anything of it is analyzed. An auto enum (no explicit tag type, // exhaustive, no field values) gets a `.none` int tag type its callers // backpatch; an explicit one `.none` values too, which resolveEnumLayout // evaluates later (the second call, with `layout_ty`), once the type is // named and its namespace scanned -- so the tag-type body may name a // declaration inside the enum (`enum(E.IntTag) { const IntTag = u8; }`). bool is_auto = !has_tag_type && !nonexhaustive && !any_field_values; if (is_auto || layout_ty == IP_INDEX_NONE) { uint32_t zir_index = ipTrackZir(ip); for (uint32_t i = 0; i < fields_len; i++) field_values_ip[i] = IP_INDEX_NONE; uint32_t enum_ty = is_auto ? ipGetEnumTypeAuto(ip, zir_index, name.name, name.fqn, name_nav, IP_NAMESPACE_NONE, fields_len, field_names_ip) : ipGetEnumTypeExplicit(ip, zir_index, name.name, name.fqn, name_nav, IP_NAMESPACE_NONE, IP_INDEX_NONE, fields_len, field_names_ip, field_values_ip, nonexhaustive); ipDeferFieldNames(ip, enum_ty, zir->string_bytes, &zir->extra[names_off], fields_len); free(field_names_ip); free(field_values_ip); if (pushed_captures) ctPopCaptureScope(zcu, eval_ns, saved_captures); return enum_ty; } // Explicit / nonexhaustive enum. // Layout of the remaining scratch after field_names: the optional // field_value_body_lens[fields_len], then the tag_type body, then the // per-field value bodies concatenated. const uint32_t* fvbl = NULL; if (any_field_values) { fvbl = &zir->extra[ci]; ci += fields_len; } if (!has_tag_type) UNIMPLEMENTED( "zirEnumDecl: nonexhaustive/valued enum without tag type"); // Tag type body: a `break_inline` of a primitive/int type ref. Resolve it // to the interned int tag type (mirrors Sema.zirEnumDecl's tag-type body // eval). Find the terminating break_inline and resolve its operand. uint32_t tag_body_off = ci; ci += tag_type_body_len; uint32_t tag_ty_ref = UINT32_MAX; for (uint32_t k = 0; k < tag_type_body_len; k++) { uint32_t tinst = zir->extra[tag_body_off + k]; if (zir->inst_tags[tinst] == ZIR_INST_BREAK_INLINE || zir->inst_tags[tinst] == ZIR_INST_BREAK) { tag_ty_ref = zir->inst_datas[tinst].break_data.operand; break; } } if (tag_ty_ref == UINT32_MAX) UNIMPLEMENTED("zirEnumDecl: tag type body has no break"); // The tag type is usually a plain int type ref (`enum(u8)`), which // ctResolveParamTypeRef resolves from the single break operand. But it may // be a generic type-returning CALL/FIELD_CALL // (`enum(math.Log2Int(usize))`) or a `@Int(...)` builtin (ZIR reify_int): // ctResolveParamTypeRef only handles single type refs, so fold the whole // tag-type body through the comptime evaluator (which walks the receiver + // arg / signedness + bits sub-instructions) instead. zirReifyInt resolves // the std.builtin.Signedness value + u16 bit count and interns pt.intType. // The routing is `ctTypeBodyNeedsComptimeFold`, as at every signature and // field consult site; a `&decl` tag body stays loud on either route. // resolveEnumLayout's Block uses `".namespace = enum_obj.namespace"`, so // tag-type and value bodies see the enum's and its parents' decls. uint32_t enum_ns = ipEnumNamespaceOrNone(ip, layout_ty); uint32_t block_ns = enum_ns != IP_NAMESPACE_NONE ? enum_ns : eval_ns; const CtEvaluator saved_ct = zcu->ct; zcu->ct = (CtEvaluator) { .type_resolve_ns = block_ns }; uint32_t int_tag_ty = ctTypeBodyNeedsComptimeFold(zir, tag_ty_ref) ? ctFoldComptimeTypeBody(zcu, zir, zirBodySlice(zir, tag_body_off), tag_type_body_len, block_ns, semaContainerTypeNameCtx(ip, layout_ty)) : ctResolveParamTypeRef(ip, zcu, zir, tag_ty_ref); zcu->ct = saved_ct; // `resolveEnumLayout`'s declared arm: every explicit value body first -- // "const uncoerced = try sema.resolveInlineBody(...); const coerced = try // sema.coerce(&block, int_tag_ty, uncoerced, field_val_src);" -- with // `"sema.inst_map.putAssumeCapacity(zir_index, // .fromIntern(int_tag_ty.toIntern()));"`, since AstGen gives every value // the result type `decl_inst.toRef()`; then the implicit ones. uint32_t vb_off = ci; for (uint32_t i = 0; i < fields_len; i++) { uint32_t vb_len = fvbl == NULL ? 0 : fvbl[i]; field_values_ip[i] = vb_len == 0 ? IP_INDEX_NONE : semaCoerceComptime(zcu, ip, ctFoldComptimeBodyValue(zcu, zir, zirBodySlice(zir, vb_off), vb_len, block_ns, semaContainerTypeNameCtx(ip, layout_ty), enum_inst, int_tag_ty), int_tag_ty); vb_off += vb_len; } semaEnumImplicitValues(zcu, int_tag_ty, field_values_ip, fields_len); // AstGen rejects a duplicate declared field name. semaValidateEnumFields( ip, int_tag_ty, nonexhaustive, fields_len, NULL, field_values_ip); // `"ip.resolveEnumLayout(io, enum_ty.toIntern(), int_tag_ty.toIntern())"` // and `"enum_obj.field_values.get(ip)[field_idx] = ..."`. ipSetEnumIntTagType(ip, layout_ty, int_tag_ty); for (uint32_t i = 0; i < fields_len; i++) ipSetEnumFieldValue(ip, layout_ty, i, field_values_ip[i]); free(field_names_ip); free(field_values_ip); if (pushed_captures) ctPopCaptureScope(zcu, eval_ns, saved_captures); return layout_ty; } // Sema/type_resolution.zig resolveEnumLayout for a declared enum: the // explicit tag type and field values of `enum_ty`, interned by // semaEnumDeclType with neither. An auto enum's tag type is its callers' // backpatch. void semaEnumDeclResolveLayout(Zcu* zcu, const Zir* zir, uint32_t enum_inst, uint32_t enum_ty, uint32_t eval_ns, const CtCap* captures, uint32_t captures_arg_len) { if (zcu->intern_pool.item_tags[enum_ty] == IP_TAG_TYPE_ENUM_AUTO) return; uint32_t fields_len = 0, decls_len = 0; const uint32_t* decls_ptr = NULL; semaEnumDeclType(zcu, zir, enum_inst, TYPE_NAME_CTX_NONE, IP_INDEX_NONE, eval_ns, &fields_len, &decls_ptr, &decls_len, captures, captures_arg_len, enum_ty); } // (part of) Sema.zig:zirEnumDecl // Intern an enum type from an inline `enum_decl` extended instruction — a // comptime-evaluated `enum { ... }` type expression (ctEvalBody's // ZIR_EXT_ENUM_DECL case) or an anonymous field/param/return-position type // (ctResolveParamTypeRef's arm) — the direct analog of the struct_decl -> // semaBodyStructType handler. Delegates the payload decode + intern to the // shared semaEnumDeclType core (stock Sema.zirEnumDecl / // resolveEnumLayout treat named and anonymous enum_decls uniformly), so both // the auto shape and the explicit tag-type / field-values shape intern. `ns` // is the enclosing scope (Zcu.ct.type_resolve_ns / SemaCtx.ns at the two call // sites, matching the semaBodyStructType analogs). Member decls scan into a // fresh namespace parented at `ns` via the shared semaContainerScanDecls core, // exactly as stock Sema.zirEnumDecl does for every name strategy (`parent = // block.namespace` + scanNamespace; registration is lazy, so the // self-referential `default: UseSendfile` annotation only evaluates on a // later lookup, after the outer const's value is cached). One declaration // interns one enum type: stock keys a declared namespace type on its tracked // ZIR index plus its captures (the `.declared` arms of InternPool's Key hash // and eql), so a re-evaluated enum_decl returns the existing index instead of // a field-identical copy. uint32_t semaBodyEnumType(Zcu* zcu, const Zir* zir, uint32_t enum_inst, uint32_t ns, const uint32_t* values, const uint32_t* runtime_values, const uint32_t* runtime_types, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func) { InternPool* ip = &zcu->intern_pool; // Read-AND-CLEAR the self-nav hook (see semaBodyStructType). uint32_t self_nav = zcu->container_self_nav; zcu->container_self_nav = IP_INDEX_NONE; uint32_t fields_len = 0; const uint32_t* decls_ptr = NULL; uint32_t decls_len = 0; uint32_t caps_len = 0; CtCap* caps = ctResolveCaptures(zcu, zir, enum_inst, ns, values, runtime_values, runtime_types, &caps_len); uint32_t existing = ctDedupLookup(zcu, zir, enum_inst, caps, caps_len); if (existing != IP_INDEX_NONE) { // The namespace was scanned when this type was first interned, and // an auto tag type waits for its demand, so the hit is complete. free(caps); return existing; } uint32_t enum_ty = semaEnumDeclType(zcu, zir, enum_inst, TYPE_NAME_CTX_NONE, IP_INDEX_NONE, ns, &fields_len, &decls_ptr, &decls_len, caps, caps_len, IP_INDEX_NONE); ctDedupInsert(zcu, zir, enum_inst, caps, caps_len, enum_ty); // Sema.zirEnumDecl: `"try sema.setTypeName(block, &wip, ...)"`. setTypeName( zcu, zir, enum_inst, enum_ty, type_name_ctx, self_nav, tn_func); // Sema.zirEnumDecl creates a namespace for every declared enum. if (ns != IP_NAMESPACE_NONE || decls_len != 0 || caps_len != 0) { // Stock zirEnumDecl parents the enum's namespace at block.namespace; // `ns` is that scope on both call routes (SemaCtx.ns / // Zcu.ct.type_resolve_ns). Without a scope there is nowhere to hang // the namespace chain — abort loudly rather than mis-scope lookups. if (ns == IP_NAMESPACE_NONE) UNIMPLEMENTED( "zirEnumDecl: enum with member decls, no namespace scope"); semaContainerScanDecls(zcu, ns, enum_ty, decls_ptr, decls_len, zir, caps, caps_len, ipSetEnumNamespace); } if (self_nav != IP_INDEX_NONE) semaNavCacheType(ip, self_nav, enum_ty); // Its tag type and field values wait for resolveEnumLayout; an auto // enum's for ipResolveEnumAutoTagType. zptDeferEnumLayout(zcu, zir, enum_inst, enum_ty, ns, caps, caps_len); free(caps); return enum_ty; } // (part of) Sema.zig:zirTupleDecl // Shared decode + validate + intern core of Sema.zirTupleDecl // (src/Sema.zig): a tuple type declaration in type position (`pub const Result // = struct { E, usize };`, or the same shape inline as a fn-proto return // type). Payload (Zir.Inst.TupleDecl, lib/std/zig/Zir.zig / astgen.c // tupleDecl): extended.small = fields_len; extra[operand] = src_node, then // fields_len pairs { type: Ref, init: Ref } — init is .none (ZIR_REF_NONE) // unless the field is `comptime` with a default. Each field type resolves to // a comptime-known type, checked by validateTupleFieldType (opaque / // noreturn field types are compile errors upstream — loud aborts here), and // a non-none init is coerced to the field type and must resolve to an // interned comptime value (Sema resolveConstDefinedValue; a comptime-var cell // pointer, which upstream rejects via // canMutateComptimeVarState — aborts loudly). The tuple interns via // ipGetTupleType == stock zcu.intern_pool.getTupleType(.{ .types, .values }) // with .none (IP_INDEX_NONE) values for runtime fields, so structurally // identical tuples share one identity regardless of route. Callers differ // only in how a field-type ref resolves (upstream all routes run // sema.resolveType over the same enclosing body): the ctEvalBody // comptime-fold arm passes its frame (`values` != NULL; refs resolve via // semaResolveRef), and ctResolveParamTypeRef's extended arm passes NULL — // there the type refs are preceding instructions of the same fn-proto type // body, resolved recursively by the single-ref resolver. Runtime-body analysis // passes its `uint64_t` instruction-value frame, so type-producing // instructions already analyzed in that body (for example `@TypeOf`) can feed // the tuple field list without being reinterpreted as single-ref type // expressions; tuple field defaults use the same active frame and then go // through ctCoerceToType like the ctEval route. A comptime field default has // no evaluated frame on the single-ref type route and aborts loudly. static uint32_t semaTupleDeclFieldType(Zcu* zcu, const Zir* zir, uint32_t ref, const uint32_t* values, const uint32_t* runtime_values) { InternPool* ip = &zcu->intern_pool; if (values) return semaResolveRef(ref, values); if (runtime_values && ref >= ZIR_REF_START_INDEX) { uint32_t ty = runtime_values[ref - ZIR_REF_START_INDEX]; if (ty >= ip->items_len || !ipIndexIsType(ip, ty)) UNIMPLEMENTED("zirTupleDecl: runtime-frame field type not a type"); return ty; } return ctResolveParamTypeRef(ip, zcu, zir, ref); } static uint32_t semaTupleDeclCoerceUnsignedIntInit( InternPool* ip, uint64_t mag, uint32_t field_ty) { if (!semaComptimeIntWordFits(ip, mag, 0, true, field_ty)) COMPILE_ERROR_UPSTREAM("zirTupleDecl: tuple_decl comptime field init " "int coercion failed"); uint32_t init = semaCoerceComptimeIntLiteral(ip, mag, field_ty); if (init == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM("zirTupleDecl: tuple_decl comptime field init " "int coercion failed"); if (!ctValueConformsToType(ip, init, field_ty)) UNIMPLEMENTED( "zirTupleDecl: tuple_decl comptime field init type mismatch"); return init; } static uint32_t semaTupleDeclCoerceSignedIntInit( InternPool* ip, int64_t value, uint32_t field_ty) { uint32_t init = semaCoerceComptimeIntI64Literal(ip, value, field_ty); if (init == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM("zirTupleDecl: tuple_decl comptime field init " "int coercion failed"); if (!ctValueConformsToType(ip, init, field_ty)) UNIMPLEMENTED( "zirTupleDecl: tuple_decl comptime field init type mismatch"); return init; } // (part of) Sema.zig:zirReifyEnum // The enum-field validation upstream performs during type resolution, for // both the declared `enum(T) { ... }` route and `@Enum`'s reified one -- // `Sema.zirReifyEnum` says so itself ("Most validation of this type happens // during type resolution ... duplicate field names ... is handled by type // resolution"). zig1 has no resolution pass, so the rule runs once here, // immediately before either route mints its `type_enum_explicit`. Names and // values arrive as already-interned Indexes, and both the string table and // the InternPool dedup, so a duplicate is an equal Index. A declared enum // passes no names: AstGen already rejected a duplicate. static void semaValidateEnumFields(InternPool* ip, uint32_t int_tag_ty, bool nonexhaustive, uint32_t fields_len, const uint32_t* names_ip, const uint32_t* vals_ip) { uint32_t bits; bool is_signed; // resolveEnumLayout's explicit tag type (0.17): `noreturn` backs exactly // the empty exhaustive enums, an integer every other one. bool empty_exhaustive = fields_len == 0 && !nonexhaustive; if (int_tag_ty == ZIR_REF_NORETURN_TYPE) { if (!empty_exhaustive) COMPILE_ERROR_UPSTREAM( "enum: non-empty enums cannot be backed by 'noreturn'"); return; } // "expected integer tag type, found '{f}'" if (!ztIntInfo(ip, int_tag_ty, &bits, &is_signed)) COMPILE_ERROR_UPSTREAM("enum: tag type is not an integer type"); if (empty_exhaustive) COMPILE_ERROR_UPSTREAM( "enum: empty exhaustive enums must be backed by 'noreturn'"); // `"if (fields_len >= 1 and std.math.log2_int(u64, fields_len) == // int_tag_ty.bitSize(zcu)) return sema.fail(..., "non-exhaustive enum // specifies every value", .{});"` (Sema/type_resolution.zig) -- // the bit size, whatever the signedness: `enum(i1){ z, _ }` is legal. uint32_t log2_len = 0; for (uint32_t n = fields_len; n > 1; n >>= 1) log2_len++; if (nonexhaustive && fields_len >= 1 && log2_len == bits) COMPILE_ERROR_UPSTREAM( "enum: non-exhaustive enum specifies every value"); for (uint32_t i = 0; i < fields_len; i++) { // "enumeration value '{}' too large for type '{f}'" // (`Value.intFitsInType`). if (!valIntFitsInType(ip, vals_ip[i], int_tag_ty)) COMPILE_ERROR_UPSTREAM( "enum field value: exceeds enum tag type range"); for (uint32_t j = i + 1; j < fields_len; j++) { // "duplicate enum field name" if (names_ip != NULL && names_ip[i] == names_ip[j]) COMPILE_ERROR_UPSTREAM("enum: duplicate field name"); // "enum tag value {} already taken" if (vals_ip[i] == vals_ip[j]) COMPILE_ERROR_UPSTREAM("enum: duplicate field value"); } } } static uint64_t semaTupleDeclSignExtend(uint64_t raw, uint32_t bits) { if (bits == 0) return 0; if (bits >= 64) return raw; uint64_t mask = (UINT64_C(1) << bits) - 1u; uint64_t sign_bit = UINT64_C(1) << (bits - 1u); raw &= mask; return (raw & sign_bit) ? (raw | ~mask) : raw; } static uint32_t semaTupleDeclCoerceUnsignedInt128Init( InternPool* ip, uint64_t lo, uint64_t hi, uint32_t field_ty) { if (hi == 0) return semaTupleDeclCoerceUnsignedIntInit(ip, lo, field_ty); if (!semaComptimeIntWordFits(ip, lo, hi, true, field_ty)) COMPILE_ERROR_UPSTREAM("zirTupleDecl: tuple_decl comptime field init " "int coercion failed"); uint64_t limbs[2] = { lo, hi }; uint32_t init = ipGetInt(ip, field_ty, (BigIntConst) { limbs, 2, true }); if (!ctValueConformsToType(ip, init, field_ty)) UNIMPLEMENTED( "zirTupleDecl: tuple_decl comptime field init type mismatch"); return init; } static bool semaTupleDeclTryCoerceIntInit( InternPool* ip, uint32_t raw, uint32_t field_ty, uint32_t* out_init) { if (raw >= ip->items_len) return false; switch ((IpTag)ip->item_tags[raw]) { case IP_TAG_INT_COMPTIME_INT_U32: *out_init = semaTupleDeclCoerceUnsignedIntInit( ip, ip->item_datas[raw], field_ty); return true; case IP_TAG_INT_COMPTIME_INT_I32: *out_init = semaTupleDeclCoerceSignedIntInit( ip, (int64_t)(int32_t)ip->item_datas[raw], field_ty); return true; case IP_TAG_INT_POSITIVE: case IP_TAG_INT_NEGATIVE: { // The limb carrier: the same magnitudes the three // `int_comptime_int_*` arms above carry, at the one tag that // holds every one of them past `u32` -- and every sized wide value // too. `Int.ty` equal to the field's type is the identity the narrower // arms below spell as `ipTypeOf(ip, raw) == field_ty`; otherwise the // magnitude and its sign are the coercion's operands, whatever type // the source wore -- the storage carries both, so no pattern is read // back and no width rule is needed. uint32_t bty, blen; const uint64_t* bl; bool bpos; if (!ipIntBigLimbs(ip, raw, &bty, &bl, &blen, &bpos)) return false; if (bty == field_ty) { *out_init = raw; return true; } if (blen > 2u) return false; if (bpos) { *out_init = blen == 2u ? semaTupleDeclCoerceUnsignedInt128Init( ip, bl[0], bl[1], field_ty) : semaTupleDeclCoerceUnsignedIntInit(ip, bl[0], field_ty); return true; } if (blen > 1u || bl[0] > (UINT64_C(1) << 63)) return false; *out_init = semaTupleDeclCoerceSignedIntInit( ip, (int64_t)((uint64_t)0 - bl[0]), field_ty); return true; } case IP_TAG_INT_U8: case IP_TAG_INT_U16: case IP_TAG_INT_U32: case IP_TAG_INT_USIZE: if (ipTypeOf(ip, raw) == field_ty) { *out_init = raw; return true; } *out_init = semaTupleDeclCoerceUnsignedIntInit( ip, ip->item_datas[raw], field_ty); return true; case IP_TAG_INT_I32: if (ipTypeOf(ip, raw) == field_ty) { *out_init = raw; return true; } *out_init = semaTupleDeclCoerceSignedIntInit( ip, (int64_t)(int32_t)ip->item_datas[raw], field_ty); return true; case IP_TAG_INT_SMALL: { if (ipTypeOf(ip, raw) == field_ty) { *out_init = raw; return true; } uint32_t d = ip->item_datas[raw]; uint32_t src_ty = ip->extra[d]; uint32_t src_raw = ip->extra[d + 1]; uint32_t bits; bool is_signed; if (!ztIntInfoInRange(ip, src_ty, &bits, &is_signed)) UNIMPLEMENTED("zirTupleDecl: tuple_decl comptime field init " "integer type unsupported"); if (is_signed && bits > 0 && bits <= 32 && (src_raw & (UINT32_C(1) << (bits - 1u)))) { *out_init = semaTupleDeclCoerceSignedIntInit(ip, (int64_t)(int32_t)semaTupleDeclSignExtend(src_raw, bits), field_ty); } else { *out_init = semaTupleDeclCoerceUnsignedIntInit(ip, src_raw, field_ty); } return true; } default: return false; } } static uint32_t semaTupleDeclFieldInit(Zcu* zcu, uint32_t ref, uint32_t field_ty, const uint32_t* values, const uint32_t* runtime_values) { InternPool* ip = &zcu->intern_pool; uint32_t raw; if (values) { raw = semaResolveRef(ref, values); } else if (runtime_values) { if (ref < ZIR_REF_START_INDEX) { raw = ref; } else { raw = runtime_values[ref - ZIR_REF_START_INDEX]; if (raw >= AIR_REF_INST_BASE) UNIMPLEMENTED( "zirTupleDecl: runtime-frame field init not comptime"); } } else { UNIMPLEMENTED("zirTupleDecl: comptime field default outside comptime " "evaluation"); } uint32_t int_init = IP_INDEX_NONE; if (semaTupleDeclTryCoerceIntInit(ip, raw, field_ty, &int_init)) return int_init; uint32_t init = ctCoerceToType(zcu, raw, field_ty); if (init >= ip->items_len) UNIMPLEMENTED("zirTupleDecl: tuple_decl comptime field init not an " "interned value"); if (!ctValueConformsToType(ip, init, field_ty)) UNIMPLEMENTED( "zirTupleDecl: tuple_decl comptime field init type mismatch"); return init; } uint32_t semaTupleDecl(Zcu* zcu, const Zir* zir, uint32_t inst, const uint32_t* values, const uint32_t* runtime_values) { InternPool* ip = &zcu->intern_pool; uint32_t td_len = zir->inst_datas[inst].extended.small; uint32_t td_ei = zir->inst_datas[inst].extended.operand + 1; // src_node uint32_t* td_types = zig1Malloc((td_len ? td_len : 1) * 2 * sizeof(uint32_t)); uint32_t* td_vals = td_types + td_len; for (uint32_t td_i = 0; td_i < td_len; td_i++) { uint32_t td_ty_ref = zir->extra[td_ei + 2 * td_i]; uint32_t td_ty = semaTupleDeclFieldType( zcu, zir, td_ty_ref, values, runtime_values); if (td_ty >= ip->items_len || !ipIndexIsType(ip, td_ty)) UNIMPLEMENTED("zirTupleDecl: tuple_decl field type not a type"); // upstream's validateTupleFieldType checks zigTypeTag == .@"opaque", // which covers `anyopaque` as well as a declared opaque type. if (td_ty == ZIR_REF_NORETURN_TYPE || ztIsZigTypeTag(ip, td_ty, ZIG_TYPE_TAG_OPAQUE)) COMPILE_ERROR_UPSTREAM( "zirTupleDecl: tuple_decl noreturn/opaque field type"); td_types[td_i] = td_ty; uint32_t td_init_ref = zir->extra[td_ei + 2 * td_i + 1]; if (td_init_ref == ZIR_REF_NONE) { td_vals[td_i] = IP_INDEX_NONE; continue; } td_vals[td_i] = semaTupleDeclFieldInit( zcu, td_init_ref, td_ty, values, runtime_values); } uint32_t td_res = ipGetTupleType(ip, td_len, td_types, td_vals); free(td_types); return td_res; } // Intern a tuple `struct { T, U, ... }` type from a ZIR extended tuple_decl. // When `runtime_values` is non-NULL, field-type refs may resolve through that // analyzeBodyInner frame; otherwise the single-ref type resolver is used. static uint32_t semaTupleDeclType(Zcu* zcu, const Zir* zir, uint32_t tuple_inst, const uint32_t* runtime_values) { return semaTupleDecl(zcu, zir, tuple_inst, NULL, runtime_values); } // ── Empty inits, @Type (semaReify*), opaque decls and comptime cmp_eq ─────── // Materialize `.{}` of a struct type as its interned aggregate — Sema's // structInitEmpty -> finishStructInit with no explicitly-initialized fields: // Every field is filled from its declared default value. The default body // locations were recorded by semaBodyStructType (metadata only, no interning); // semaFieldDefaultValue folds + field-type-coerces each in its declaring // file/namespace scope. A field with no recorded default aborts loudly (a // genuine "missing field" error). uint32_t semaStructInitEmptyAggregate(Zcu* zcu, uint32_t struct_ty) { InternPool* ip = &zcu->intern_pool; // A tuple's "default" is its comptime field value // (`structFieldDefaultValue`). bool is_tuple = ztIsTuple(ip, struct_ty); uint32_t nf = is_tuple ? ipTupleFieldCount(ip, struct_ty) : ipStructFieldsLen(ip, struct_ty); uint32_t* elems = zig1Calloc(nf ? nf : 1, sizeof(uint32_t)); for (uint32_t f = 0; f < nf; f++) { uint32_t dv = is_tuple ? ipTupleFieldValue(ip, struct_ty, f) : semaFieldDefaultValue(zcu, struct_ty, f); if (dv == IP_INDEX_NONE) { free(elems); UNIMPLEMENTED("structInitEmpty: field has no default value"); } elems[f] = dv; } // `finishStructInit`'s comptime arm: `.auto, .@"extern" => // pt.aggregateValue(struct_ty, elems)`, `.@"packed" =>` every field // written to packed memory and read back as the `.bitpack`. uint32_t agg = !is_tuple && ipStructIsPacked(ip, struct_ty) ? semaPackStructFields(zcu, struct_ty, elems, nf) : ipGetAggregate(ip, struct_ty, elems, nf); free(elems); return agg; } static uint32_t semaArrayInitEmptyAggregate( InternPool* ip, uint32_t ty, const char* nonempty_msg); // (part of) Sema.zig:zirStructInitEmptyResult // Sema.zirStructInitEmptyResult(is_byref = false) below the generic-poison // arm: `.{}` of the result type's opt/eu base -- structInitEmpty for a struct // or tuple, arrayInitEmpty for an array or vector -- before the coercion // back to the result type, which each frame does its own way. uint32_t semaStructInitEmptyResultValue(Zcu* zcu, uint32_t result_ty) { InternPool* ip = &zcu->intern_pool; uint32_t obj_ty = ipOptEuBaseType(ip, result_ty); if (obj_ty >= ip->items_len) UNIMPLEMENTED("struct_init_empty_result: invalid result type"); if (ztZigTypeTag(ip, obj_ty) == ZIG_TYPE_TAG_STRUCT) return semaStructInitEmptyAggregate(zcu, obj_ty); uint32_t empty = semaArrayInitEmptyAggregate( ip, obj_ty, "struct_init_empty_result: nonempty array"); if (empty == IP_INDEX_NONE) UNIMPLEMENTED( "struct_init_empty_result: non-struct/array/vector type"); return empty; } // Sema.arrayInitEmpty: `.{}` of an array/vector type -- a zero length interns // the only possible aggregate, a nonzero one is upstream's "expected {d} // array elements; found 0" compile error (`nonempty_msg`). Returns // IP_INDEX_NONE when `ty` is not an array/vector at all, so callers can fall // through to their own kind. static uint32_t semaArrayInitEmptyAggregate( InternPool* ip, uint32_t ty, const char* nonempty_msg) { if (ty >= ip->items_len) return IP_INDEX_NONE; IpArrayInfo info; IpArrayStatus status = ipArrayInfo(ip, ty, &info); if (status == IP_ARR_NOT_SEQ) return IP_INDEX_NONE; if (status != IP_ARR_OK || info.len != 0) UNIMPLEMENTED(nonempty_msg); return ipGetAggregate(ip, ty, NULL, 0); } // (part of) Sema.zig:zirCmpEq // The boolean ZIR ref a comptime `==`/`!=` yields once an arm has decided // equality. Sema.zirCmpEq answers both instructions from one comparison and // negates for `.neq` ("const op: std.math.CompareOperator = if (is_equality) // .eq else .neq"), so every arm below decides `eq` and hands it here. uint32_t semaCmpEqRef(ZirInstTag tag, bool eq) { if (tag == ZIR_INST_CMP_NEQ) eq = !eq; return eq ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; } // (part of) Sema.zig:analyzeCmpUnionTag, Sema.zig:zirCmpEq, Sema.zig:cmpSelf // Sema.analyzeCmpUnionTag (src/Sema.zig), the arm `Sema.zirCmpEq` // takes when one side is a union and the other an enum or enum literal: // "Coerce both the union and the tag to the union's tag type, and then // execute the enum comparison codepath." `Sema.cmpSelf` then folds two // comptime-known enum tags with `Value.eql`, which on interned values is // index identity. IP_INDEX_NONE means the pair is not that shape and the // caller keeps its own path -- the same decline convention every other // probe in this chain uses. static uint32_t semaCmpUnionTagComptime( Zcu* zcu, ZirInstTag op, uint32_t un, uint32_t tg) { InternPool* ip = &zcu->intern_pool; if (un >= ip->items_len || ip->item_tags[un] != IP_TAG_UNION_VALUE) return IP_INDEX_NONE; const bool tg_is_tag = tg < ip->items_len && ip->item_tags[tg] == IP_TAG_ENUM_TAG; if (!ctIsEnumLiteral(ip, tg) && !tg_is_tag) return IP_INDEX_NONE; // Key.Union = {ty, tag, val}. const uint32_t union_ty = ipTypeOf(ip, un); // `union_ty.unionTagType(zcu) orelse` -> "comparison of union and enum // literal is only valid for tagged union types". if (ipUnionTagUsage(ip, union_ty) != IP_UNION_TAG_USAGE_TAGGED) UNIMPLEMENTED( "analyzeCmpUnionTag: cmp of an untagged union with an enum"); const uint32_t union_tag_ty = ztUnionTagTypeHypothetical(zcu, union_ty); const uint32_t coerced_union = ipUnionValueTag(ip, un); if (tg_is_tag && ipEnumTagType(ip, tg) != union_tag_ty) UNIMPLEMENTED( "analyzeCmpUnionTag: cmp union with a foreign enum type"); // `try sema.coerce(.., tag, union_tag_ty, ..)`: the enum-literal arm of // the one coercion port mints the same tag and states the same "not a // field" rejection, so this does not restate it. const uint32_t coerced_tag = ctCoerceToType(zcu, tg, union_tag_ty); if (ipEnumTagType(ip, coerced_union) != union_tag_ty) UNIMPLEMENTED( "analyzeCmpUnionTag: union value tag is not its tag enum"); // "if (sema.resolveValue(coerced_tag)) |enum_val| { if // (enum_val.isUndef(zcu)) return .undef_bool; const field_ty = // union_ty.unionFieldType(enum_val, zcu).?; if (field_ty.classify(zcu) == // .no_possible_value) return .bool_false; }" (src/Sema.zig). // This runs before the operator split, so both `==` and `!=` answer // false for a tag whose payload type has no possible value -- it returns // `.bool_false` directly, not through the eq/neq negation. Omitting it // made `u != .b` answer true where upstream answers false. if (ipIsUndef(ip, coerced_tag)) UNIMPLEMENTED("analyzeCmpUnionTag: cmp union with an undefined tag"); if (semaCmpUnionTagNoPossibleValue(ip, union_ty, coerced_tag)) return ZIR_REF_BOOL_FALSE; return semaCmpEqRef(op, coerced_union == coerced_tag); } // (part of) Sema.zig:zirReifyTuple // Sema.zirReifyTuple (src/Sema.zig): `@Tuple(&field_types)` builds a new // anonymous tuple type from a comptime-known `[]const type`, with no per-field // comptime defaults (upstream `@memset(field_values, .none)`). `field_types` // is the already-resolved comptime value of the builtin's operand. // // Skipping a coercion of that operand is safe not because AstGen pre-coerced // it (RL_REF_COERCED_TY only element-coerces via // COERCE_PTR_ELEM_TY -- the operand can still arrive as a `*[N]type` cell // pointer, never slice-shaped) but because `ctReifyArgView` subsumes the // missing ptr-to-array -> slice representation step: it is a generic by-ref // decoder over cell-ptr-to-array and slice/interned forms (the same decoder // reify_union's three by-ref args use -- the comptime mirror of // resolveConstDefinedValue + derefSliceAsArray). static uint32_t semaReifyTupleType(Zcu* zcu, uint32_t field_types) { InternPool* ip = &zcu->intern_pool; CtReifyArgView rtv = ctReifyArgView(zcu, ip, field_types); uint32_t rt_len = rtv.len; uint32_t* rt_types = zig1Malloc((rt_len ? rt_len : 1) * sizeof(uint32_t)); uint32_t* rt_vals = zig1Malloc((rt_len ? rt_len : 1) * sizeof(uint32_t)); for (uint32_t fi = 0; fi < rt_len; fi++) { uint32_t fty = valElemValue( ip, rtv.agg, rtv.off + fi, "Value.elemValue: comptime"); if (fty >= ip->items_len || !ipIndexIsType(ip, fty)) // Covers stock's per-element `field_ty_val.isUndef()` -> // failWithUseOfUndef too: an undef element is not a type (same // combined check as the reify_union field_types loop). UNIMPLEMENTED("reify_tuple: field type not a type"); if (fty == ZIR_REF_NORETURN_TYPE || ztIsZigTypeTag(ip, fty, ZIG_TYPE_TAG_OPAQUE)) // Sema.validateTupleFieldType (src/Sema.zig): opaque and // noreturn tuple fields are compile errors upstream. Same two-way // check as zirTupleDecl's field-type guard. COMPILE_ERROR_UPSTREAM("reify_tuple: noreturn/opaque field type"); rt_types[fi] = fty; rt_vals[fi] = IP_INDEX_NONE; // no comptime default value } uint32_t tuple_ty = ipGetTupleType(ip, rt_len, rt_types, rt_vals); free(rt_types); free(rt_vals); return tuple_ty; } // (part of) Sema.zig:zirReifyFn, InternPool.zig:getFuncType // Sema.zirReifyFn: `@Fn(¶m_types, ¶m_attrs, ret_ty, fn_attrs)`. // The param types coerce to `[]const type`, the attributes to `*const // [len]Type.Fn.ParamAttributes` (their `noalias` bits) and // `Type.Fn.Attributes` (`callconv`, `varargs`); then checkParamType / // checkCallConvSupportsVarArgs / checkReturnTypeAndCallConv (ccValidate) and // `ip.getFuncType` with no comptime bits. uint32_t semaReifyFn(Zcu* zcu, uint32_t param_types, uint32_t param_attrs, uint32_t ret_ty, uint32_t fn_attrs) { InternPool* ip = &zcu->intern_pool; static const char* const pap[] = { "Type", "Fn", "ParamAttributes" }; static const char* const fap[] = { "Type", "Fn", "Attributes" }; uint32_t single_param_attrs_ty = getStdLangType(zcu, pap, 3); uint32_t fn_attrs_ty = getStdLangType(zcu, fap, 3); CtReifyArgView ptv = ctReifyArgView(zcu, ip, ctCoerceToType(zcu, param_types, ZIR_REF_SLICE_CONST_TYPE_TYPE)); uint32_t n = ptv.len; uint32_t pa_ty = ipGetPtrType(ip, ipGetArrayType(ip, n, single_param_attrs_ty, IP_INDEX_NONE), IP_PTR_FLAGS_CONST_ONE); CtReifyArgView pav = ctReifyArgView(zcu, ip, ctCoerceToType(zcu, param_attrs, pa_ty)); ret_ty = ctValidatedTypeResult(ip, ret_ty, "reify_fn: ret_ty not a type"); // ctAggElem aborts on a fn_attrs value that is not a comptime aggregate. uint32_t fa = ctCoerceToType(zcu, fn_attrs, fn_attrs_ty); FuncCallconv cc = ccResolveValue(zcu, valElemValue(ip, fa, ipStructFieldIndex( ip, fn_attrs_ty, ipGetOrPutString(ip, "callconv", 8)), "Value.elemValue: comptime")); bool varargs = valElemValue(ip, fa, ipStructFieldIndex(ip, fn_attrs_ty, ipGetOrPutString(ip, "varargs", 7)), "Value.elemValue: comptime") == ZIR_REF_BOOL_TRUE; uint32_t noalias_name = ipGetOrPutString(ip, "noalias", 7); uint32_t* params = zig1Malloc((n ? n : 1) * sizeof(uint32_t)); uint32_t noalias_bits = 0; for (uint32_t i = 0; i < n; i++) { params[i] = ctValidatedTypeResult(ip, ctReifyArgViewElem(ip, ptv, i), "reify_fn: param type not a type"); if (valElemValue(ip, ctReifyArgViewElem(ip, pav, i), ipStructFieldIndex(ip, single_param_attrs_ty, noalias_name), "Value.elemValue: comptime") == ZIR_REF_BOOL_TRUE) { if (i > 31) COMPILE_ERROR_UPSTREAM("reify_fn: 'noalias' on a parameter " "past the first 32"); noalias_bits |= 1u << i; } } if (varargs) ccCheckSupportsVarArgs(&cc); ccValidate(zcu, &cc, false, n, 0, params, ret_ty, false, false); uint32_t func_ty = ipGetFuncType(ip, ret_ty, params, n, ccPack(&cc) | (varargs ? IP_FUNC_FLAGS_IS_VAR_ARGS : 0u), 0, noalias_bits); free(params); return func_ty; } // Sema.zig:zirOpaqueDecl, InternPool.zig:getDeclaredOpaqueType, // Zir.zig:getOpaqueDecl // Sema.zirOpaqueDecl (src/Sema.zig): an `opaque { // ... }` type expression. The value is the interned opaque type itself // (InternPool.getDeclaredOpaqueType); an opaque type is only usable behind // pointers, so nothing ever asks for its layout. Payload decode mirrors // Zir.getOpaqueDecl (lib/std/zig/Zir.zig ~5627) / astgen.c opaqueDeclInner: // OpaqueDecl.Small bit0 has_captures_len, bit1 has_decls_len, bits2..3 // name_strategy; extra = { src_line, src_column, src_node }, [captures_len], // [decls_len], captures[], capture_names[], decls[]. The type is named by // setTypeName right after it is interned, as for the other container decls. // Shared by both evaluators: besides the ZIR instruction it reads (and // writes) only the declared-type dedup table, whose key is that instruction. uint32_t semaOpaqueDeclType(Zcu* zcu, const Zir* zir, uint32_t inst, const SemaCtx* ct, const uint32_t* inst_values, const uint32_t* inst_types, TypeNameCtx type_name_ctx) { InternPool* ip = &zcu->intern_pool; ZirContainerTrailing tw = zirContainerTrailingWords(zir, inst); // Captures, then their names, then the decls. const uint32_t* od_decls = &zir->extra[tw.captures + 2u * tw.captures_len]; // Sema.zirOpaqueDecl: "const captures = try sema.getCaptures(block, // block.namespace, ...)", then `getDeclaredOpaqueType` keyed by // {zir_index, captures} -- one opaque per declaration and capture set, // the one `{zir, inst, captures}` dedup table the other container decls // key into -- then "createNamespace(.{ .parent = // block.namespace.toOptional(), .owner_type = wip_ty.index, ... })" and // "scanNamespace(new_namespace_index, decls)", for every opaque. uint32_t parent_ns = ct != NULL ? ct->ns : zcu->ct.type_resolve_ns; uint32_t caps_len = 0; CtCap* caps = ctResolveCaptures(zcu, zir, inst, parent_ns, ct != NULL ? ct->inst_values : NULL, inst_values, inst_types, &caps_len); uint32_t existing = ctDedupLookup(zcu, zir, inst, caps, caps_len); if (existing != IP_INDEX_NONE) { free(caps); return existing; } uint32_t od_zir_index = ipTrackZir(ip); uint32_t opaque_ty = ipGetOpaqueType(ip, od_zir_index, IP_INDEX_NONE, IP_INDEX_NONE, IP_INDEX_NONE, IP_NAMESPACE_NONE); ctDedupInsert(zcu, zir, inst, caps, caps_len, opaque_ty); setTypeName(zcu, zir, inst, opaque_ty, type_name_ctx, ct != NULL ? ct->owner_nav : IP_INDEX_NONE, ct != NULL ? ct->type_name_func : NULL); if (parent_ns == IP_NAMESPACE_NONE) parent_ns = zcuNamespaceOfZir(zcu, zir); semaContainerScanDecls(zcu, parent_ns, opaque_ty, od_decls, tw.decls_len, zir, caps, caps_len, ipSetOpaqueNamespace); free(caps); return opaque_ty; } // Sema.zig:interpretStdLangType, Value.zig:interpret // Sema.interpretStdLangType over an enum argument (src/Sema.zig, // 20408): the pinned profile is value_interpret_mode = .by_name // (Value.interpret `.@"enum"` .by_name, src/Value.zig), so a std.lang // tag maps to the compiler-native enum by its field name -- // enumTagFieldIndex, then enumFieldName, then stringToEnum -- not by its // backing integer value. Returns the index of the matching `names` entry (in // compiler-native field order). A tag matching no field is stock // error.TypeMismatch -- a compile error -- and a non-tag operand is not a // legal shape; both abort loudly under `what`. One statement for every // std.lang enum argument (`@Union`/`@Struct`'s Type.ContainerLayout, // `@Enum`'s Type.Enum.Mode). static uint32_t semaInterpretStdLangEnum(InternPool* ip, uint32_t tag, const char* const* names, uint32_t names_len, const char* what) { if (tag >= ip->items_len || ip->item_tags[tag] != IP_TAG_ENUM_TAG) UNIMPLEMENTED(what); uint32_t idx = ipEnumTagFieldIndex(ip, tag); if (idx == UINT32_MAX) UNIMPLEMENTED(what); const char* name = ipStringBytes(ip, ipEnumFieldName(ip, ipEnumTagType(ip, tag), idx)); for (uint32_t i = 0; i < names_len; i++) if (strcmp(name, names[i]) == 0) return i; UNIMPLEMENTED(what); ZIG1_TCC_RETURN(0); } // std.lang.Type.ContainerLayout's field order -- the one table `@Union` and // `@Struct` both interpret their layout argument against. static const char* const g_container_layout_names[] = { "auto", "extern", "packed" }; // (part of) Sema.zig:zirReifySliceArgTy, PerThread.zig:ptrType // Sema.zirReifySliceArgTy (src/Sema.zig): the result type // `@Struct`/`@Union` hand to their field_types/field_attrs arguments -- // `*const [field_names.len]out_scalar_ty`, where `info` (a bitcast // Zir.Inst.ReifySliceArgInfo, the extended `small`) selects out_scalar_ty: // string_to_{struct,union}_field_type (1/2) -> `type`; // string_to_{struct,union}_field_attrs (3/4) -> // std.lang.Type.{Struct,Union}.FieldAttributes (by-name getStdLangType, like // the sibling std_lang_value arms). type_to_fn_param_attrs (0) is `@Fn`'s // param_attrs target -- zig1's astgen has no `@Fn` lowering, so it aborts // loudly. For all four modeled infos in_scalar_ty is `[]const u8`, so the // already-resolved `names` operand coerces against `[]const []const u8` // (upstream's pt.ptrType(child=in_scalar, .slice, const) == // .slice_const_slice_const_u8): an interned slice passes through; a comptime // cell pointer-to-array (`&field_names` of a comptime `var [N][]const u8`) // becomes a whole-array cell slice via ctCoerceToType -- its length is the // slice len upstream reads. static uint32_t semaReifySliceArgTy(Zcu* zcu, uint16_t info, uint32_t names) { InternPool* ip = &zcu->intern_pool; uint32_t out_scalar_ty; // in_scalar_ty: `type` for @Fn's param types, else `[]const u8`. uint32_t operand_ty = info == 0 ? ZIR_REF_SLICE_CONST_TYPE_TYPE : ZIR_REF_SLICE_CONST_SLICE_CONST_U8_TYPE; if (info == 0) { static const char* const p[] = { "Type", "Fn", "ParamAttributes" }; out_scalar_ty = getStdLangType(zcu, p, 3); } else if (info == 1 || info == 2) { out_scalar_ty = ZIR_REF_TYPE_TYPE; } else if (info == 3) { static const char* const p[] = { "Type", "Struct", "FieldAttributes" }; out_scalar_ty = getStdLangType(zcu, p, 3); } else if (info == 4) { static const char* const p[] = { "Type", "Union", "FieldAttributes" }; out_scalar_ty = getStdLangType(zcu, p, 3); } else { UNIMPLEMENTED( "zirReifySliceArgTy: reify_slice_arg - unmodeled arg info"); } // The coerced operand's length comes from the same ctReifyArgView reader // the sibling zirReifyEnumValueSliceTy and the three reify statements // use. CtReifyArgView names_view = ctReifyArgView(zcu, ip, ctCoerceToType(zcu, names, operand_ty)); uint32_t arr_ty = ipGetArrayType(ip, names_view.len, out_scalar_ty, IP_INDEX_NONE); return ipGetPtrType(ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); } // (part of) Sema.zig:zirReifyEnumValueSliceTy // Sema.zirReifyEnumValueSliceTy (src/Sema.zig): the result type // `@Enum` hands its field_values argument -- `*const [field_names.len]tag_ty`. // Both operands arrive already resolved. `tag_ty` goes through the shared // ctValidatedTypeResult (as ctFoldComptimeTypeBody / ctBoundFuncReturnType do) // rather than a bespoke `!= 0` check: index 0 is a real preinterned type // (`u0`, ipInitStatic's first static key) and is legitimately the tag type // when the caller's int_tag_ty body folds to `math.IntFittingRange(0, N-1)` // for N == 1. static uint32_t semaReifyEnumValueSliceTy( Zcu* zcu, uint32_t tag_ty, uint32_t names) { InternPool* ip = &zcu->intern_pool; tag_ty = ctValidatedTypeResult(ip, tag_ty, "zirReifyEnum: tag not a type"); // Upstream coerces field_names to `[]const []const u8` before reading // its `len` ("sema.coerce(block, .slice_const_slice_const_u8, ...)"), // exactly as zirReifySliceArgTy does; the sibling reify_struct arm // already spells that coercion the same way. Without it an empty // `&.{}` argument arrives as an anonymous empty tuple whose type is not // an array at all. CtReifyArgView names_view = ctReifyArgView(zcu, ip, ctCoerceToType(zcu, names, ZIR_REF_SLICE_CONST_SLICE_CONST_U8_TYPE)); uint32_t arr_ty = ipGetArrayType(ip, names_view.len, tag_ty, IP_INDEX_NONE); return ipGetPtrType(ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); } // (part of) Sema.zig:zirReifyEnum // Sema.zirReifyEnum (src/Sema.zig): build the explicit-tag enum type // `@Enum(tag_ty, mode, &field_names, &field_values)` declares, from its four // already-resolved operands plus the declaring ZIR instruction (which is the // dedup identity: upstream's `getReifiedEnumType` keys on // `block.trackZir(inst)` plus a hash of tag type, exhaustiveness, field count // and field names). Shared by the comptime fold and runtime-body analysis. uint32_t semaReifyEnumType(Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t r_tag, uint32_t r_mode, uint32_t r_names, uint32_t r_vals, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func, uint32_t parent_nav) { InternPool* ip = &zcu->intern_pool; // Same shared validator as reify_enum_value_slice_ty (this arm's tag_ty // is literally the same ZIR ref evaluated a second time -- astgen.c's // `@Enum` lowering passes one `tag_ty` ZirInstRef into both the // value-slice-ty coercion target and this reify payload): index 0 is a // real preinterned type (`u0`), not an uninitialized slot. r_tag = ctValidatedTypeResult(ip, r_tag, "reify_enum: tag not a type"); // mode: a Type.Enum.Mode tag, read through the same // interpretStdLangType statement the `@Union`/`@Struct` layout argument // goes through (upstream spells both with that one call) -- by field // name, not by the backing integer. static const char* const mode_names[] = { "exhaustive", "nonexhaustive" }; bool nonexhaustive = semaInterpretStdLangEnum(ip, r_mode, mode_names, 2, "reify_enum: mode not a Type.Enum.Mode tag") != 0; // field_names: coerced to `[]const []const u8` (upstream's // "sema.coerce(block, .slice_const_slice_const_u8, ...)") and then // indexed element by element -- the value-slice target only needed its // length. It may be a comptime-cell `&field_names` or an interned // ptr_slice, and `&.{}` needs the coercion to be a slice at all. CtReifyArgView names_view = ctReifyArgView(zcu, ip, ctCoerceToType(zcu, r_names, ZIR_REF_SLICE_CONST_SLICE_CONST_U8_TYPE)); uint32_t fields_len = names_view.len; // field_values: coerced to `*const [len]tag_ty`. In the comptime fold // this may be a cell pointer, a pointer/slice over an aggregate, or the // aggregate value directly. CtReifyArgView vals_view = ctReifyArgView(zcu, ip, r_vals); if (vals_view.len != fields_len) UNIMPLEMENTED("reify_enum: field_values len"); uint32_t* names_ip = zig1Malloc((fields_len ? fields_len : 1) * sizeof(uint32_t)); uint32_t* vals_ip = zig1Malloc((fields_len ? fields_len : 1) * sizeof(uint32_t)); for (uint32_t fi = 0; fi < fields_len; fi++) { // name element fi: a `[:0]const u8` slice -- re-interned via the // shared reify name reader (handles the bytes / aggregate / repeated // backing storage forms). names_ip[fi] = ctReifyNameString( zcu, ip, ctReifyArgViewElem(ip, names_view, fi)); // value element fi: an interned int of tag_ty. vals_ip[fi] = ctReifyArgViewElem(ip, vals_view, fi); } semaValidateEnumFields( ip, r_tag, nonexhaustive, fields_len, names_ip, vals_ip); uint32_t cached_enum = ctReifiedEnumDedupLookup( zcu, zir, inst, r_tag, nonexhaustive, fields_len, names_ip, vals_ip); if (cached_enum != IP_INDEX_NONE) { free(names_ip); free(vals_ip); return cached_enum; } // A reified enum's namespace is always empty (stock: // getReifiedEnumType). Named by setTypeName below; the placeholder only // fills the words until then. zig1 stores the reified enum in its // ipGetEnumTypeExplicit shape (captures_len=0, no type_hash trailing // words) rather than stock's `.reified` layout, so the semantic dedup // table above provides stock's getReifiedEnumType identity before this // declared-explicit mint consumes a tracked-ZIR id or maps. uint32_t zir_index = ipTrackZir(ip); uint32_t name_str = ipGetOrPutString(ip, "enum", 4); uint32_t enum_ty = ipGetEnumTypeExplicit(ip, zir_index, name_str, name_str, IP_INDEX_NONE, IP_NAMESPACE_NONE, r_tag, fields_len, names_ip, vals_ip, nonexhaustive); ctReifiedEnumDedupInsert(zcu, zir, inst, r_tag, nonexhaustive, fields_len, names_ip, vals_ip, enum_ty); // `.wip => |wip|`: `"try sema.setTypeName(block, &wip, name_strategy, // \"enum\", inst);"` (an existing type keeps its name). setTypeName(zcu, zir, inst, enum_ty, type_name_ctx, parent_nav, tn_func); free(names_ip); free(vals_ip); return enum_ty; } // (part of) Sema.zig:zirReifyPointer, PerThread.zig:ptrType // Sema.zirReifyPointer (src/Sema.zig) -> pt.ptrType: compose a pointer // type from `@Pointer(size, attrs, child, sentinel)`'s four already-resolved // comptime operands. `size` is a // coerced Type.Pointer.Size enum tag (auto-numbered: one=0, many=1, slice=2, // c=3); `attrs` a Type.Pointer.Attributes aggregate. A present sentinel is // modeled for `.many`/`.slice`. static uint32_t semaReifyPointerType(Zcu* zcu, uint32_t r_size, uint32_t r_attrs, uint32_t r_elem, uint32_t r_sent) { InternPool* ip = &zcu->intern_pool; if (!ipItemTagIs(ip, r_size, IP_TAG_ENUM_TAG)) UNIMPLEMENTED("reify_pointer: size not an enum tag"); int64_t size_v; bool size_ct; uint32_t size_b; if (!ctReadInt(ip, ipEnumTagValue(ip, r_size), &size_v, &size_ct, &size_b) || size_v < 0 || size_v > PTR_SIZE_C) UNIMPLEMENTED("reify_pointer: size tag unreadable"); if (!semaRefIsInterned(r_attrs) || ip->item_tags[r_attrs] != IP_TAG_AGGREGATE || ipStructFieldsLen(ip, ctAggType(ip, r_attrs)) != 5) UNIMPLEMENTED("reify_pointer: attrs not an Attributes value"); // Same u0-index-0 conflation: `@Pointer`'s elem_ty (`*u0`, // `?*const u0` are stock-legal) folds to index 0 for a // genuine `u0` result, not an uninitialized slot. Upstream // zirReifyPointer (src/Sema.zig) resolves elem_ty via // the same uniform `sema.resolveType` as the sentinel-ty arm // above. r_elem = ctValidatedTypeResult(ip, r_elem, "reify_pointer: child not a type"); uint32_t r_sentinel = IP_INDEX_NONE; if (!semaRefIsInterned(r_sent)) UNIMPLEMENTED("reify_pointer: sentinel sentinel"); if (r_sent == ZIR_REF_NULL_VALUE || (ip->item_tags[r_sent] == IP_TAG_OPT_NULL && ip->item_datas[r_sent] == ipGetOptionalType(ip, r_elem))) { r_sentinel = IP_INDEX_NONE; } else if (ip->item_tags[r_sent] == IP_TAG_OPT_PAYLOAD && ipTypeOf(ip, r_sent) == ipGetOptionalType(ip, r_elem)) { uint32_t so = ip->item_datas[r_sent]; r_sentinel = ip->extra[so + 1]; if (!ctValueConformsToType(ip, r_sentinel, r_elem)) UNIMPLEMENTED("reify_pointer: sentinel child mismatch"); if (size_v != PTR_SIZE_MANY && size_v != PTR_SIZE_SLICE) UNIMPLEMENTED("reify_pointer: sentinel on one/c pointer"); } else { // Some ctEvalBody call sites see the sentinel argument // before the result-location optional wrap. Treat a raw // child-typed value as the optional payload stock Sema // would resolve after coercing to `?elem_ty`. uint32_t coerced_sentinel = ctCoerceToType(zcu, r_sent, r_elem); if (!ctValueConformsToType(ip, coerced_sentinel, r_elem)) UNIMPLEMENTED("reify_pointer: non-optional sentinel"); r_sentinel = coerced_sentinel; if (size_v != PTR_SIZE_MANY && size_v != PTR_SIZE_SLICE) UNIMPLEMENTED("reify_pointer: sentinel on one/c pointer"); } // Attributes fields: 0 is_const, 1 is_volatile, 2 // is_allowzero, 3 address_space (`?AddressSpace`, null or // `.generic` => default), 4 alignment (`?usize`, null => // natural). A non-generic address space (field 3) is // unmodeled and aborts. uint32_t r_addrspace = valElemValue(ip, r_attrs, 3, "Value.elemValue: comptime"); if (ip->item_tags[r_addrspace] != IP_TAG_OPT_NULL) { static const char* const addrspace_path[] = { "AddressSpace" }; uint32_t addrspace_ty = getStdLangType(zcu, addrspace_path, 1); uint32_t opt_addrspace_ty = ipGetOptionalType(ip, addrspace_ty); if (ip->item_tags[r_addrspace] != IP_TAG_OPT_PAYLOAD || ipTypeOf(ip, r_addrspace) != opt_addrspace_ty) UNIMPLEMENTED("reify_pointer: malformed addrspace"); uint32_t addrspace_val = ipPayloadVal(ip, r_addrspace); if (ipEnumTagType(ip, addrspace_val) != addrspace_ty || ipEnumTagFieldIndex(ip, addrspace_val) != IP_PTR_ADDRSPACE_GENERIC) UNIMPLEMENTED("reify_pointer: non-generic addrspace"); } uint32_t rflags = IP_PTR_FLAGS_MUT_ONE; // mutable single base if (valElemValue(ip, r_attrs, 0, "Value.elemValue: comptime") == ZIR_REF_BOOL_TRUE) rflags |= IP_PTR_FLAGS_IS_CONST; if (valElemValue(ip, r_attrs, 1, "Value.elemValue: comptime") == ZIR_REF_BOOL_TRUE) rflags |= IP_PTR_FLAGS_IS_VOLATILE; if (valElemValue(ip, r_attrs, 2, "Value.elemValue: comptime") == ZIR_REF_BOOL_TRUE) rflags |= IP_PTR_FLAGS_IS_ALLOWZERO; // `const @"align": Alignment = if (attrs.@"align") |bytes| // try sema.validateAlign(block, attrs_src, bytes) else .none` // (Sema.zirReifyPointer). validateAlign rejects 0 and // non-powers of two as compile errors, then encodes the byte // count as `Alignment.fromNonzeroByteUnits` -- the log2 that // `ipAlignmentFromByteUnits` mints and that `pt_ip_flags` // packs for `[*]align(N) T` written in source // (sema.c's ZIR_INST_PTR_TYPE align arm). The base // flags already carry `.none` (63) in bits 2..7. uint32_t r_align = valElemValue(ip, r_attrs, 4, "Value.elemValue: comptime"); if (ip->item_tags[r_align] != IP_TAG_OPT_NULL) { if (ip->item_tags[r_align] != IP_TAG_OPT_PAYLOAD) UNIMPLEMENTED("reify_pointer: malformed alignment"); uint64_t align_bytes = 0; if (!ipTryReadIntLow64Bits( ip, ipPayloadVal(ip, r_align), &align_bytes)) UNIMPLEMENTED("reify_pointer: alignment is not a readable int"); if (align_bytes == 0) COMPILE_ERROR_UPSTREAM("@Pointer: alignment must be >= 1"); if ((align_bytes & (align_bytes - 1u)) != 0) COMPILE_ERROR_UPSTREAM( "@Pointer: alignment is not a power of two"); uint32_t align_log2 = ipAlignmentFromByteUnits( align_bytes, "reify_pointer: alignment not encodable"); rflags = IP_PTR_FLAGS_WITH_ALIGN(rflags, align_log2); } if (size_v == PTR_SIZE_SLICE) { // `.slice`: intern the `[*]` many-ptr, wrap in // type_slice (same composition as // semaResolveGenericComposedPtrType). uint32_t many_flags = (rflags & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_MANY; uint32_t many_ptr = ipGetPtrTypeSentinel(ip, r_elem, many_flags, r_sentinel); return ipGetSliceType(ip, many_ptr); } else { rflags = (rflags & ~3u) | (uint32_t)size_v; return ipGetPtrTypeSentinel(ip, r_elem, rflags, r_sentinel); } } // (part of) Sema.zig:zirReifyStruct // Sema.zirReifyStruct: `@Struct(layout, backing_ty, &field_names, // &field_types, &field_attrs)`, both frames: an `.auto` or `.extern` layout // with names, types, `comptime`, `align` and `default_value_ptr` attributes. // `sema.coerce(block, .optional_type, operand, src)` on a reify's `?type` // operand: null in any form is `?type`'s `opt_null`, a type `T` (bare or // already an `opt_payload`) is `opt_payload(?type, T)`. The type hash keys // on this value, so each `?type` has one representation. static uint32_t semaReifyOptionalType(InternPool* ip, uint32_t v) { if (v >= ip->items_len) UNIMPLEMENTED("zirReifyStruct: reify - ?type operand sentinel"); if (v == ZIR_REF_NULL_VALUE || ip->item_tags[v] == IP_TAG_OPT_NULL) return ipGetOptNull(ip, ZIR_REF_OPTIONAL_TYPE_TYPE); if (ip->item_tags[v] == IP_TAG_OPT_PAYLOAD) v = ipPayloadVal(ip, v); if (!ipIndexIsType(ip, v)) UNIMPLEMENTED("zirReifyStruct: reify - ?type operand not a type"); return ipGetOptPayload(ip, ZIR_REF_OPTIONAL_TYPE_TYPE, v); } uint32_t semaReifyStructType(Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t rs_layout, uint32_t rs_backing, uint32_t rs_names_ref, uint32_t rs_types_ref, uint32_t rs_attrs_ref, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func, uint32_t parent_nav) { InternPool* ip = &zcu->intern_pool; IpContainerLayout layout = semaInterpretStdLangEnum(ip, rs_layout, g_container_layout_names, 3, "reify_struct: layout not a ContainerLayout tag"); rs_backing = semaReifyOptionalType(ip, rs_backing); // `"const backing_int_ty: ?Type = if (backing_int_ty_val.optionalValue( // zcu)) |backing| ty: { switch (layout) { .auto, .@\"extern\" => return // sema.fail(...)"`. uint32_t backing_int_ty = IP_INDEX_NONE; if (ip->item_tags[rs_backing] != IP_TAG_OPT_NULL) { if (layout != IP_STRUCT_LAYOUT_PACKED) COMPILE_ERROR_UPSTREAM("reify_struct: non-packed struct does not " "support backing integer type"); backing_int_ty = ipPayloadVal(ip, rs_backing); } CtReifyArgView names_view = ctReifyArgView(zcu, ip, ctCoerceToType( zcu, rs_names_ref, ZIR_REF_SLICE_CONST_SLICE_CONST_U8_TYPE)); CtReifyArgView types_view = ctReifyArgView(zcu, ip, rs_types_ref); CtReifyArgView attrs_view = ctReifyArgView(zcu, ip, rs_attrs_ref); uint32_t fields_len = names_view.len; if (types_view.len != fields_len || attrs_view.len != fields_len) UNIMPLEMENTED("reify_struct: arg length mismatch"); uint32_t* names = zig1Malloc((fields_len ? fields_len : 1) * sizeof(uint32_t)); uint32_t* types = zig1Malloc((fields_len ? fields_len : 1) * sizeof(uint32_t)); uint32_t* defaults = zig1Malloc((fields_len ? fields_len : 1) * sizeof(uint32_t)); uint32_t* aligns = zig1Malloc((fields_len ? fields_len : 1) * sizeof(uint32_t)); bool* comptimes = zig1Calloc(fields_len ? fields_len : 1, sizeof(bool)); bool any_defaults = false; bool any_comptime = false; bool any_aligns = false; // "var hasher = std.hash.Wyhash.init(0); std.hash.autoHash(&hasher, // layout); std.hash.autoHash(&hasher, backing_int_ty_val);" then the // field types and, per field, its name / comptime / align / default: // the inputs getReifiedStructType deduplicates on. Wyhash hasher; wyhashInit(&hasher, 0); wyhashUpdateU32(&hasher, layout); wyhashUpdateU32(&hasher, rs_backing); for (uint32_t fi = 0; fi < fields_len; fi++) wyhashUpdateU32(&hasher, ctReifyArgViewElem(ip, types_view, fi)); for (uint32_t fi = 0; fi < fields_len; fi++) { names[fi] = ctReifyNameString( zcu, ip, ctReifyArgViewElem(ip, names_view, fi)); wyhashUpdateU32(&hasher, names[fi]); uint32_t fty = ctReifyArgViewElem(ip, types_view, fi); if (fty >= ip->items_len || !ipIndexIsType(ip, fty)) UNIMPLEMENTED("reify_struct: field type not type"); types[fi] = fty; defaults[fi] = IP_INDEX_NONE; uint32_t att = ctReifyArgViewElem(ip, attrs_view, fi); if (att >= ip->items_len) UNIMPLEMENTED("reify_struct: attrs element sentinel"); if (ipIsUndef(ip, att)) UNIMPLEMENTED("reify_struct: undefined attrs"); if (ip->item_tags[att] != IP_TAG_AGGREGATE && ip->item_tags[att] != IP_TAG_REPEATED) UNIMPLEMENTED("reify_struct: attrs not a struct"); uint32_t att_ty = ctAggType(ip, att); if (!ipTagIsAnyStruct(ip->item_tags[att_ty])) UNIMPLEMENTED("reify_struct: attrs type not struct"); uint32_t comptime_name = ipGetOrPutString(ip, "comptime", 8); uint32_t align_name = ipGetOrPutString(ip, "align", 5); uint32_t default_name = ipGetOrPutString(ip, "default_value_ptr", 17); uint32_t comptime_idx = ipStructFieldIndex(ip, att_ty, comptime_name); comptimes[fi] = comptime_idx != UINT32_MAX && valElemValue(ip, att, comptime_idx, "Value.elemValue: comptime") == ZIR_REF_BOOL_TRUE; wyhashUpdateU32(&hasher, comptimes[fi]); // `"if (field_attr_align.optionalValue(zcu)) |align_val| { if // (layout == .@\"packed\") ... _ = try sema.validateAlign(block, // field_attrs_src, align_val.toUnsignedInt(zcu)); any_field_aligns = // true; }"`. aligns[fi] = IP_PTR_ALIGN_NONE; uint32_t align_idx = ipStructFieldIndex(ip, att_ty, align_name); if (align_idx != UINT32_MAX) { uint32_t align_val = valElemValue( ip, att, align_idx, "Value.elemValue: comptime"); wyhashUpdateU32(&hasher, align_val); if (align_val != ZIR_REF_NULL_VALUE && ip->item_tags[align_val] != IP_TAG_OPT_NULL) { if (layout == IP_STRUCT_LAYOUT_PACKED) COMPILE_ERROR_UPSTREAM("reify_struct: packed struct " "fields cannot be aligned"); if (ip->item_tags[align_val] == IP_TAG_OPT_PAYLOAD) align_val = ipPayloadVal(ip, align_val); uint64_t align_bytes; if (!ipTryReadIntLow64Bits(ip, align_val, &align_bytes)) UNIMPLEMENTED( "reify_struct: alignment not a readable integer"); if (align_bytes == 0 || (align_bytes & (align_bytes - 1u)) != 0) COMPILE_ERROR_UPSTREAM( "reify_struct: alignment is not a power of two"); aligns[fi] = ipAlignmentFromByteUnits( align_bytes, "reify_struct: alignment not encodable"); any_aligns = true; } } uint32_t default_idx = ipStructFieldIndex(ip, att_ty, default_name); uint32_t def_ptr = default_idx == UINT32_MAX ? ZIR_REF_NULL_VALUE : valElemValue(ip, att, default_idx, "Value.elemValue: comptime"); if (def_ptr == ZIR_REF_NULL_VALUE || ip->item_tags[def_ptr] == IP_TAG_OPT_NULL) { // "comptime field without default initialization value". if (comptimes[fi]) COMPILE_ERROR_UPSTREAM("reify_struct: comptime field without " "default initialization value"); wyhashUpdateU32(&hasher, IP_INDEX_NONE); continue; } if (ip->item_tags[def_ptr] == IP_TAG_OPT_PAYLOAD) def_ptr = ipPayloadVal(ip, def_ptr); // `sema.pointerDeref(ptr_val, singleConstPtrType(ty))`. def_ptr = ipRetypePtrValue( ip, def_ptr, ipGetPtrType(ip, fty, IP_PTR_FLAGS_CONST_ONE)); uint32_t def_val = def_ptr == IP_INDEX_NONE ? IP_INDEX_NONE : pointerDeref(zcu, def_ptr, NULL); if (def_val == IP_INDEX_NONE) UNIMPLEMENTED("reify_struct: default not comptime"); defaults[fi] = def_val; wyhashUpdateU32(&hasher, def_val); any_defaults = true; if (comptimes[fi]) { if (layout != IP_STRUCT_LAYOUT_AUTO) COMPILE_ERROR_UPSTREAM("reify_struct: extern struct fields " "cannot be marked comptime"); any_comptime = true; } } // `ip.getReifiedStructType(.{ .zir_index = tracked_inst, .type_hash = // hasher.final(), ... })`: equal inputs at one instruction are one type. bool existing; uint32_t struct_ty = ipGetReifiedStructType(ip, ipTrackZirInst(ip, semaZcuFileIdxOfZir(zcu, zir), inst), wyhashFinal(&hasher), fields_len, layout, any_comptime, any_defaults, any_aligns, backing_int_ty, &existing); // `.wip => |wip|`: `"try sema.setTypeName(block, &wip, name_strategy, // \"struct\", inst);"`. if (!existing) setTypeName( zcu, zir, inst, struct_ty, type_name_ctx, parent_nav, tn_func); for (uint32_t fi = 0; !existing && fi < fields_len; fi++) { ipSetStructFieldName(ip, struct_ty, fi, names[fi]); ipSetStructFieldType(ip, struct_ty, fi, types[fi]); if (layout != IP_STRUCT_LAYOUT_PACKED) ipSetStructFieldRuntimeOrder(ip, struct_ty, fi, fi); if (any_defaults) ipSetStructFieldDefault(ip, struct_ty, fi, defaults[fi]); if (any_comptime) ipSetStructFieldIsComptime(ip, struct_ty, fi, comptimes[fi]); if (any_aligns) ipSetStructFieldAlignment(ip, struct_ty, fi, aligns[fi]); } if (!existing && layout == IP_STRUCT_LAYOUT_PACKED) resolvePackedStructLayout(zcu, struct_ty, backing_int_ty); free(comptimes); free(aligns); free(defaults); free(types); free(names); return struct_ty; } // (part of) Sema.zig:zirReifyUnion // Sema.zirReifyUnion (src/Sema.zig): build the union type // `@Union(layout, arg_ty, &field_names, &field_types, &field_attrs)` // declares, from its five already-resolved operands. Reached at comptime and // in a runtime body alike. The extended `small` is the name strategy -- // debug-facing only (stock setTypeName): zig1 interns the union with the // same name placeholder semaBodyUnionType leaves, so it is not mirrored. // Shared by the comptime fold and runtime-body analysis. uint32_t semaReifyUnionType(Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t r_layout, uint32_t r_arg_ty, uint32_t r_names, uint32_t r_types, uint32_t r_attrs, TypeNameCtx type_name_ctx, const TypeNameFuncArgs* tn_func, uint32_t parent_nav) { InternPool* ip = &zcu->intern_pool; // layout: a Type.ContainerLayout enum tag (astgen coerced the argument // against the std_lang container_layout type), interpreted by the shared // by-name semaInterpretStdLangEnum. IpContainerLayout layout = semaInterpretStdLangEnum(ip, r_layout, g_container_layout_names, 3, "reify_union: layout not a ContainerLayout tag"); // `"const explicit_tag_ty: ?Type, const explicit_packed_backing_type: // ?Type = ty: { const arg_ty = arg_ty_val.optionalValue(zcu) orelse // break :ty .{ null, null }; switch (layout) { .@\"extern\" => return // sema.fail(...), .@\"packed\" => break :ty .{ null, arg_ty.toType() }, // .auto => break :ty .{ arg_ty.toType(), null }, }"`. r_arg_ty = semaReifyOptionalType(ip, r_arg_ty); uint32_t ru_tag_ty = IP_INDEX_NONE; if (ip->item_tags[r_arg_ty] != IP_TAG_OPT_NULL) { uint32_t arg_ty = ipPayloadVal(ip, r_arg_ty); if (layout == IP_STRUCT_LAYOUT_EXTERN) COMPILE_ERROR_UPSTREAM( "reify_union: extern union does not support enum tag type"); if (layout == IP_STRUCT_LAYOUT_PACKED) UNIMPLEMENTED("reify_union: packed explicit backing integer"); if (!ipIsEnumType(ip, arg_ty)) UNIMPLEMENTED("reify_union: tag not an enum type"); ru_tag_ty = arg_ty; } // The three by-ref args. field_names is coerced against // `[]const []const u8` first (zirReifyUnion's // slice_const_slice_const_u8 coerce — turns the `&field_names` // cell pointer into a whole-array cell-slice view whose // window length is the len stock reads). field_types / // field_attrs are coerced upstream against `*const // [fields_len]...` types built from the field_names len, so a // length mismatch there is a coercion compile error — abort. CtReifyArgView nv = ctReifyArgView(zcu, ip, ctCoerceToType(zcu, r_names, ZIR_REF_SLICE_CONST_SLICE_CONST_U8_TYPE)); CtReifyArgView tv = ctReifyArgView(zcu, ip, r_types); CtReifyArgView av = ctReifyArgView(zcu, ip, r_attrs); uint32_t ru_len = nv.len; if (tv.len != ru_len || av.len != ru_len) UNIMPLEMENTED("reify_union: arg length mismatch"); // First field loop (zirReifyUnion validation pass): re-intern // each name (sliceToIpString) and interpret each attrs // element as std.lang.Type.Union.FieldAttributes — with // .by_name struct interpret semantics (Value.interpret // `.@"struct"` .by_name, src/Value.zig): the // compiler-native field `align` is looked up in the operand's // own struct type by name; a missing field falls back to the // compiler default (null). A non-null align — stock // validateAlign + any_field_aligns — is out of scope (the // shared declared-union interning stores no field aligns). uint32_t* ru_names = zig1Malloc((ru_len ? ru_len : 1) * sizeof(uint32_t)); for (uint32_t fi = 0; fi < ru_len; fi++) { ru_names[fi] = ctReifyNameString(zcu, ip, valElemValue( ip, nv.agg, nv.off + fi, "Value.elemValue: comptime")); uint32_t att = valElemValue( ip, av.agg, av.off + fi, "Value.elemValue: comptime"); if (att >= ip->items_len) UNIMPLEMENTED("reify_union: attrs element sentinel"); if (ipIsUndef(ip, att)) // sema.anyUndef(field_attrs_arr) -> // failWithUseOfUndef: a compile error upstream. UNIMPLEMENTED("reify_union: undefined attrs"); if (ip->item_tags[att] != IP_TAG_AGGREGATE && ip->item_tags[att] != IP_TAG_REPEATED) UNIMPLEMENTED("reify_union: attrs not a struct"); uint32_t att_ty = ctAggType(ip, att); if (!ipTagIsAnyStruct(ip->item_tags[att_ty])) UNIMPLEMENTED("reify_union: attrs not a struct"); uint32_t att_fields = ipStructFieldsLen(ip, att_ty); for (uint32_t aj = 0; aj < att_fields; aj++) { if (strcmp(ipStringBytes(ip, ipStructFieldName(ip, att_ty, aj)), "align") != 0) continue; uint32_t a_val = valElemValue(ip, att, aj, "Value.elemValue: comptime"); if (a_val != ZIR_REF_NULL_VALUE && ip->item_tags[a_val] != IP_TAG_OPT_NULL) { if (layout == IP_STRUCT_LAYOUT_PACKED) COMPILE_ERROR_UPSTREAM("reify_union: packed union fields " "cannot be aligned"); UNIMPLEMENTED("reify_union: explicit field align"); } break; } } // The explicit-tag field match stock defers to // resolveUnionLayout (reified arm, // src/Sema/type_resolution.zig: same count + pairwise // union_field_names[i] == enum field_names[i] in order; // failUnionFieldMismatch otherwise — a compile error). // Validated eagerly at declaration, exactly like // semaBodyUnionType's declared explicit-tag path. if (ru_tag_ty != IP_INDEX_NONE) { if (ipEnumFieldsLen(ip, ru_tag_ty) != ru_len) UNIMPLEMENTED("reify_union: tag field count mismatch"); for (uint32_t fi = 0; fi < ru_len; fi++) if (ipEnumFieldName(ip, ru_tag_ty, fi) != ru_names[fi]) UNIMPLEMENTED("reify_union: tag field mismatch"); } // Intern through the same declared-union shape semaBodyUnionType // uses (ipGetDeclaredUnionType + enum_tag_type backpatch + // field types + out-of-band declaration-order names) so // every downstream accessor — field lookup, layout, // @typeInfo, unionToTag — consumes the reified union // uniformly. tag_usage: explicit tag -> .tagged // (zirReifyUnion's `tag:` branch). All field types are // already-interned comptime values, so populating them here // appends no IP items (the generated-enum-precedes-payloads // ordering that forces semaBodyUnionType's deferral machinery // cannot be violated). A reified union's namespace is always // empty (stock creates an empty one; zig1's 0-placeholder // resolves to none via ipUnionNamespaceOrNone ownership). // getReifiedUnionType's identity is {tracked inst, type_hash}: "var // hasher = std.hash.Wyhash.init(0); std.hash.autoHash(&hasher, layout); // std.hash.autoHash(&hasher, arg_ty_val);" then field types, attrs and // names. The item stores the hash after its header, as upstream's. Wyhash hasher; wyhashInit(&hasher, 0); wyhashUpdateU32(&hasher, layout); wyhashUpdateU32(&hasher, r_arg_ty); for (uint32_t fi = 0; fi < ru_len; fi++) wyhashUpdateU32(&hasher, valElemValue( ip, tv.agg, tv.off + fi, "Value.elemValue: comptime")); for (uint32_t fi = 0; fi < ru_len; fi++) wyhashUpdateU32(&hasher, valElemValue( ip, av.agg, av.off + fi, "Value.elemValue: comptime")); for (uint32_t fi = 0; fi < ru_len; fi++) wyhashUpdateU32(&hasher, ru_names[fi]); uint32_t ru_zir_index = ipTrackZirInst(ip, semaZcuFileIdxOfZir(zcu, zir), inst); uint64_t ru_hash = wyhashFinal(&hasher); bool ru_existing; // `".tag_usage = tag: { if (explicit_tag_ty != null) break :tag .tagged; // if (layout == .auto and block.wantSafeTypes()) break :tag .safety; // break :tag .none; }"` -- the ReleaseSmall profile wants no safe types, // as semaBodyUnionType's plain `union`. uint32_t ru_tag_usage = ru_tag_ty != IP_INDEX_NONE ? IP_UNION_TAG_USAGE_TAGGED : IP_UNION_TAG_USAGE_NONE; uint32_t ru_ty = ipGetReifiedUnionType(ip, ru_zir_index, ru_hash, ru_len, layout, ru_tag_usage, ru_tag_ty, &ru_existing); if (ru_existing) { free(ru_names); return ru_ty; } // `.wip => |wip|`: `"try sema.setTypeName(block, &wip, name_strategy, // \"union\", inst);"`. setTypeName(zcu, zir, inst, ru_ty, type_name_ctx, parent_nav, tn_func); for (uint32_t fi = 0; fi < ru_len; fi++) { ipSetUnionReifiedFieldName(ip, ru_ty, fi, ru_names[fi]); uint32_t fty = valElemValue( ip, tv.agg, tv.off + fi, "Value.elemValue: comptime"); if (fty >= ip->items_len || !ipIndexIsType(ip, fty)) // Covers stock's anyUndef(field_types_arr) too: an // undef element is not a type. UNIMPLEMENTED("reify_union: field type not a type"); ipSetUnionFieldType(ip, ru_ty, fi, fty); } ipRegisterGeneratedUnionTag( ip, ru_ty, ru_len, IP_INDEX_NONE, UINT32_MAX, false); free(ru_names); return ru_ty; } // ── @export (semaExport) and pointerDeref ─────────────────────────────────── // (part of) Sema.zig:zirExport // Sema.zirExport past its operand resolution: `exported` is the comptime // pointer value and `options` the `std.lang.ExportOptions` aggregate. Shared // by ctEvalBody and the runtime body evaluator (upstream's one zirExport). void semaExport(Zcu* zcu, uint32_t exported, uint32_t options) { InternPool* ip = &zcu->intern_pool; if (exported >= ip->items_len) UNIMPLEMENTED("export: target not comptime-known"); if (options >= ip->items_len) UNIMPLEMENTED("export: options not comptime-known"); // "if (ptr_ty.zigTypeTag(zcu) != .pointer) ... expected pointer // type"; "if (ptr_ty_info.flags.size == .slice) ... export target // cannot be slice"; "if (ptr_ty_info.packed_offset.host_size != 0) // ... export target cannot be bit-pointer". A `.slice`-size // pointer is an IP_TAG_TYPE_SLICE here, so it is rejected by the // same message before the pointer payload is decoded. uint32_t xptr_ty = ipTypeOf(ip, exported); if (ztIsSlice(ip, xptr_ty)) COMPILE_ERROR_UPSTREAM("@export: export target cannot be " "slice"); IpPtrInfo xinfo; if (xptr_ty >= ip->items_len || ipPtrInfo(ip, xptr_ty, &xinfo) != IP_PTRI_OK) COMPILE_ERROR_UPSTREAM("@export: expected pointer type"); if (xinfo.size == IP_PTR_SIZE_SLICE) COMPILE_ERROR_UPSTREAM("@export: export target cannot be " "slice"); // host_size is the low half of the packed {host_size, bit_offset} // word. if ((xinfo.packed_offset & 0xFFFFu) != 0) COMPILE_ERROR_UPSTREAM("@export: export target cannot be " "bit-pointer"); // Not ported: "try sema.ensureLayoutResolved(export_ty, ...)" and // the `validateExtern` diagnostic that follows it. Both only turn // an already-invalid program into a structured compile error, // which zig1 does not model; no valid `@export` reaches them. // Sema.resolveExportOptions (src/Sema.zig) reads the four // ExportOptions fields by name off the coerced options value. // AstGen already emitted the operand with a `.coerced_ty` result // type of `std.lang.ExportOptions` (astgen.c's `@export` arm), so // the value interned here is that struct aggregate. uint32_t xopts_ty = ctAggType(ip, options); if (xopts_ty >= ip->items_len || ip->item_tags[xopts_ty] != IP_TAG_TYPE_STRUCT) UNIMPLEMENTED("export: options not a struct value"); uint32_t x_name_f = ipStructFieldIndex(ip, xopts_ty, ipGetOrPutString(ip, "name", 4)); uint32_t x_link_f = ipStructFieldIndex(ip, xopts_ty, ipGetOrPutString(ip, "linkage", 7)); uint32_t x_sect_f = ipStructFieldIndex(ip, xopts_ty, ipGetOrPutString(ip, "section", 7)); uint32_t x_vis_f = ipStructFieldIndex( ip, xopts_ty, ipGetOrPutString(ip, "visibility", 10)); if (x_name_f == UINT32_MAX || x_link_f == UINT32_MAX || x_sect_f == UINT32_MAX || x_vis_f == UINT32_MAX) UNIMPLEMENTED("export: options missing a field"); // "const name = try sema.toConstString(...)": derefSliceAsArray // interns the `[len]u8` array it reads, as sliceToIpString does. uint32_t x_name = semaSliceToIpStringC( ip, valElemValue(ip, options, x_name_f, "Value.elemValue: comptime")); // "if (name.len < 1) ... exported symbol name cannot be empty" if (ipStringLen(ip, x_name) < 1) COMPILE_ERROR_UPSTREAM("@export: exported symbol name cannot " "be empty"); // "const linkage = try sema.interpretStdLangType(..., std.lang // .GlobalLinkage)" / same for SymbolVisibility: `@intFromEnum` of // the resolved tag, which is the declaration field index the // shared reverse mapping already computes. Both enums are // auto-numbered (lib/std/lang.zig), so a value outside // the declared fields is a real decode failure. uint32_t x_linkage = ipEnumTagFieldIndex( ip, valElemValue(ip, options, x_link_f, "Value.elemValue: comptime")); uint32_t x_visibility = ipEnumTagFieldIndex( ip, valElemValue(ip, options, x_vis_f, "Value.elemValue: comptime")); if (x_linkage > GLOBAL_LINKAGE_WEAK || x_visibility > SYMBOL_VISIBILITY_PROTECTED) UNIMPLEMENTED("export: options enum tag"); // "const section = if (section_opt_val.optionalValue(zcu)) |v| // try sema.toConstString(...) else null" uint32_t x_sect_v = valElemValue(ip, options, x_sect_f, "Value.elemValue: comptime"); uint32_t x_section = IP_INDEX_NONE; if (x_sect_v < ip->items_len && ip->item_tags[x_sect_v] == IP_TAG_OPT_PAYLOAD) { x_section = semaSliceToIpStringC(ip, ipPayloadVal(ip, x_sect_v)); } else if (x_sect_v >= ip->items_len || ip->item_tags[x_sect_v] != IP_TAG_OPT_NULL) { UNIMPLEMENTED("export: section not an optional"); } // The `ptr_info.base_addr` switch that produces `Zcu.Exported`. bool x_is_uav = false; uint32_t x_target = IP_INDEX_NONE; IpPtrNav x_pn; if (ipDecodePtrNavAny(ip, exported, &x_pn) == IP_PN_OK) { // ".nav => |orig_nav| ...": resolve the Nav, unwrap a // `.func`/`.@"extern"` value to its `owner_nav`, reject an // extern target, queue the function body, then export that // Nav. "if (ptr_info.byte_offset != 0) ... TODO: export // pointer in middle of value". if (x_pn.byte_offset != 0) UNIMPLEMENTED("@export: export pointer in middle of value"); uint32_t x_orig = x_pn.nav_index; uint32_t x_val = zptEnsureNavValUpToDate(zcu, x_orig, NULL); // "const export_nav = switch (ip.indexToKey(...)) { // .@"extern" => |e| e.owner_nav, .func => |f| f.owner_nav, // else => orig_nav }". x_target = x_orig; if (x_val < ip->items_len && ip->item_tags[x_val] == IP_TAG_EXTERN) { x_target = ipExternFunctionOwnerNav(ip, x_val); } else { uint32_t x_owner = ipFuncValueOwnerNav(ip, x_val); if (x_owner != IP_INDEX_NONE) x_target = x_owner; } // "if (ip.getNav(export_nav).getExtern(ip) != null) ... export // target cannot be extern": the test is on the unwrapped // Nav's own externness, which covers an extern variable as // well as an extern function. zig1 records that on the Nav // (IpNav.is_extern), set from the same `extern` declaration. if (x_target >= ip->navs_len) UNIMPLEMENTED("export: target owner Nav"); if (ipGetNav(ip, x_target)->is_extern) COMPILE_ERROR_UPSTREAM("@export: export target cannot " "be extern"); // Not ported: "try sema.maybeQueueFuncBodyAnalysis(block, // src, export_nav)" (src/Sema.zig). Upstream // needs it because forming `&f` does not queue anything // there -- `analyzeNavRef` only interns the pointer, and // bodies are queued per reference site -- so an exported, // never-called function would otherwise never be analyzed. // zig1 queues every function-typed Nav address as it is // formed (navDeclRefAddressResolved -> semaQueueFnPtrPending), // and the export operand is exactly such an // address, so a second queue call here can never add work. } else if (ipIsPtrUav(ip, exported)) { // ".uav => |uav| .{ .uav = uav.val }" if (ipPtrUavByteOffset(ip, exported) != 0) UNIMPLEMENTED("@export: export pointer in middle of value"); x_is_uav = true; x_target = ipPtrUavValue(ip, exported); } else { // ".comptime_alloc, .int, .comptime_field" (export target must // be a global variable or comptime-known constant) and // ".eu_payload, .opt_payload, .field, .arr_elem" (upstream's // own TODO) both end analysis here. UNIMPLEMENTED("export: target base address"); } zcuAppendExport(zcu, x_name, x_section, (uint8_t)x_linkage, (uint8_t)x_visibility, x_is_uav, x_target); } // Sema.zig:pointerDeref uint32_t pointerDeref(Zcu* zcu, uint32_t ptr, uint32_t* out_type) { const InternPool* ip = &zcu->intern_pool; ComptimeLoadResult r = loadComptimePtr(zcu, ptr); switch (r.tag) { case ZPT_LOAD_SUCCESS: break; case ZPT_LOAD_RUNTIME_LOAD: return IP_INDEX_NONE; case ZPT_LOAD_UNDEF: COMPILE_ERROR_UPSTREAM("pointerDeref: use of undefined value"); case ZPT_LOAD_ERR_PAYLOAD: COMPILE_ERROR_UPSTREAM("pointerDeref: attempt to unwrap error"); case ZPT_LOAD_NULL_PAYLOAD: COMPILE_ERROR_UPSTREAM("pointerDeref: attempt to use null value"); case ZPT_LOAD_INACTIVE_UNION_FIELD: COMPILE_ERROR_UPSTREAM("pointerDeref: access of inactive union field"); case ZPT_LOAD_NEEDED_WELL_DEFINED: COMPILE_ERROR_UPSTREAM("pointerDeref: comptime dereference requires " "a well-defined layout"); case ZPT_LOAD_OUT_OF_BOUNDS: COMPILE_ERROR_UPSTREAM("pointerDeref: dereference exceeds bounds of " "containing decl"); case ZPT_LOAD_EXCEEDS_HOST_SIZE: COMPILE_ERROR_UPSTREAM( "pointerDeref: bit-pointer target exceeds host size"); case ZPT_LOAD_UNMODELED: UNIMPLEMENTED("pointerDeref: unmodeled comptime load"); } // `".success => |mv| return try mv.intern(pt, sema.arena)"`. uint32_t val = mutableValueIntern(zcu, &r.success); // Upstream returns an in-memory-coercible value as is; zig1's callers // type the result by the pointer's child, so it is `getCoerced` to it. IpPtrInfo info; uint32_t child = ipPtrInfo(ip, ipTypeOf(ip, ptr), &info) == IP_PTRI_OK && info.child != IP_INDEX_NONE ? info.child : ipTypeOf(ip, val); val = zptGetCoerced(zcu, val, child); if (val == IP_INDEX_NONE) UNIMPLEMENTED("pointerDeref: loaded value type differs from child"); if (out_type) *out_type = child; return val; } // Sema.zig:storePtrVal // Sema.storePtrVal: `ptr` retyped to point at `operand_ty`, then // storeComptimePtr of the operand; every failure is its compile error. void storePtrVal( Zcu* zcu, uint32_t ptr, uint32_t operand_val, uint32_t operand_ty) { InternPool* ip = &zcu->intern_pool; IpPtrInfo info; if (ipPtrInfo(ip, ipTypeOf(ip, ptr), &info) != IP_PTRI_OK) UNREACHABLE("storePtrVal: not a pointer"); if (info.child != operand_ty) { uint32_t ptr_ty = ipGetPtrTypeFull( ip, operand_ty, info.flags, info.sentinel, info.packed_offset); ptr = ipRetypePtrValue(ip, ptr, ptr_ty); if (ptr == IP_INDEX_NONE) UNIMPLEMENTED("storePtrVal: cannot retype the pointer"); } switch (storeComptimePtr(zcu, ptr, operand_val).tag) { case ZPT_STORE_SUCCESS: return; case ZPT_STORE_RUNTIME_STORE: UNREACHABLE("storePtrVal: runtime store"); // "use sites check this" case ZPT_STORE_COMPTIME_FIELD_MISMATCH: COMPILE_ERROR_UPSTREAM("value stored in comptime field does not " "match the default value of the field"); case ZPT_STORE_UNDEF: COMPILE_ERROR_UPSTREAM("storePtrVal: use of undefined value"); case ZPT_STORE_ERR_PAYLOAD: COMPILE_ERROR_UPSTREAM("storePtrVal: attempt to unwrap error"); case ZPT_STORE_NULL_PAYLOAD: COMPILE_ERROR_UPSTREAM("storePtrVal: attempt to use null value"); case ZPT_STORE_INACTIVE_UNION_FIELD: COMPILE_ERROR_UPSTREAM("storePtrVal: access of inactive union field"); case ZPT_STORE_NEEDED_WELL_DEFINED: COMPILE_ERROR_UPSTREAM("storePtrVal: comptime dereference requires " "a well-defined layout"); case ZPT_STORE_OUT_OF_BOUNDS: COMPILE_ERROR_UPSTREAM("storePtrVal: dereference exceeds bounds of " "containing decl"); case ZPT_STORE_EXCEEDS_HOST_SIZE: COMPILE_ERROR_UPSTREAM( "storePtrVal: bit-pointer target exceeds host size"); case ZPT_STORE_UNMODELED: break; } UNIMPLEMENTED("storePtrVal: unmodeled comptime store"); } // Sema.storePtr2's comptime-mutable arm: `"const operand = try // sema.coerce(block, elem_ty, ...)"`, then storePtrVal at the pointer's // child type. void semaStorePtr2Comptime(Zcu* zcu, uint32_t ptr, uint32_t operand) { InternPool* ip = &zcu->intern_pool; IpPtrInfo info; if (ipPtrInfo(ip, ipTypeOf(ip, ptr), &info) != IP_PTRI_OK) UNREACHABLE("storePtr2: not a pointer"); storePtrVal(zcu, ptr, semaCoerceComptime(zcu, ip, operand, info.child), info.child); } // ── The dispatch's post-hoc blocks, runtime breaks and typeof bodies ──────── // Sema.zig:ensurePostHoc static uint32_t ensurePostHoc( InlineCtx* ictx, AirInstList* insts, uint32_t dest_block) { for (uint32_t i = 0; i < ictx->post_hoc_len; i++) if (ictx->post_hoc_zir[i] == dest_block) return ictx->post_hoc_air[i]; if (ictx->post_hoc_len == INLINE_POST_HOC_MAX) UNIMPLEMENTED("ensurePostHoc: inline blocks nested too deep"); AirInstData data; memset(&data, 0, sizeof(data)); uint32_t air = semaAirInstAppend(insts, AIR_INST_BLOCK, data); ictx->post_hoc_zir[ictx->post_hoc_len] = dest_block; ictx->post_hoc_air[ictx->post_hoc_len] = air; ictx->post_hoc_len++; return air; } // Sema.zig:addRuntimeBreak // analyzeBodyRuntimeBreak's `error.ComptimeBreak` arm: a comptime break that // crosses a runtime branch becomes a runtime `br`, to the runtime labeled // block in scope ("const label = candidate.label orelse continue; ... // .block_inline, .block_comptime => continue,"), else to the target inline // block's post-hoc block. static void addRuntimeBreak(const InternPool* ip, const Zir* zir, InlineCtx* ictx, AirInstList* insts, U32List* block, const uint32_t* inst_values, const uint32_t* inst_types) { if (!ictx->brk) return; uint32_t target = ictx->brk_target; ZirInstTag target_tag = zir->inst_tags[target]; if (target_tag == ZIR_INST_BLOCK || target_tag == ZIR_INST_LOOP) { SemaBlock* b = semaBlockFindLabel(ictx, target); if (b == NULL || !semaIsAirInstRef(inst_values[target])) UNIMPLEMENTED("addRuntimeBreak: runtime block not in scope"); ictx->brk = false; addRuntimeBreakToBlock(ip, ictx, insts, block, inst_values, inst_types, b, (inst_values[target] - AIR_REF_INST_BASE), ictx->brk_operand); return; } if (target_tag != ZIR_INST_BLOCK_INLINE) UNIMPLEMENTED( "addRuntimeBreak: comptime break to an unmodeled target"); ictx->brk = false; addRuntimeBreakToBlock(ip, ictx, insts, block, inst_values, inst_types, NULL, ensurePostHoc(ictx, insts, target), ictx->brk_operand); } // (part of) Sema.zig:zirTypeofBuiltin // The child block of Sema.zirTypeofBuiltin / zirTypeofPeer: `" // .comptime_reason = null, .is_typeof = true"` -- not comptime even inside a // comptime scope, so a call, a mutable load or a branch in it stays // runtime-known -- whose instructions are discarded afterwards: the AIR list // lengths are restored so nothing is published in the parent body. Returns // whether the body ended noreturn, so its trailing break is never reached: // Sema.resolveInlineBody's `"orelse .unreachable_value"`. static bool semaAnalyzeTypeofBody( SemaCtx* sema, const uint32_t* body, uint32_t body_len) { uint32_t saved_inst_len = sema->insts->len; uint32_t saved_extra_len = sema->air_extra->len; uint32_t saved_merges_len = sema->ictx->merges_len; bool saved_ct_scope = sema->ictx->ct_scope; bool saved_is_typeof = sema->ictx->is_typeof; U32List scratch_block = { 0 }; sema->ictx->ct_scope = false; sema->ictx->is_typeof = true; analyzeBodyInner(sema, &scratch_block, body, body_len); sema->ictx->ct_scope = saved_ct_scope; sema->ictx->is_typeof = saved_is_typeof; bool ends_noreturn = semaAirBodyEndsNoReturn(sema->insts, &scratch_block); free(scratch_block.items); sema->insts->len = saved_inst_len; sema->air_extra->len = saved_extra_len; // The child's merges name the AIR indices just freed. sema->ictx->merges_len = saved_merges_len; return ends_noreturn; } // ── The zirXxx handlers, in Sema.zig order ────────────────────────────────── // Sema.zig:zirTupleDecl // Sema.zirTupleDecl: intern an inline tuple type in a runtime analysis body // (`const T = struct { u8, usize };`). The shared sema_comptime.c helper // validates field types and interns the same tuple type used by // comptime/type-position routes. Field defaults are accepted only when their // comptimeExpr has already produced a comptime value in this runtime frame // (an interned value). static void zirTupleDecl(SemaCtx* sema, uint32_t inst) { uint32_t tuple_ty = semaTupleDeclType(sema->zcu, sema->zir, inst, sema->inst_values); sema->inst_values[inst] = tuple_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirErrorSetDecl // Sema.zirErrorSetDecl (src/Sema.zig): an inline `error{...}` declaration // expression. Always a comptime type, independent of the enclosing body's // runtime/comptime-ness (no block.semaBlockIsComptime() branch upstream) — // e.g. reached as a local variable's declared type inside a runtime function // (src/Compilation.zig's `Compilation.update`, `const result: // (... || error{HotSwapUnavailableOnHostOperatingSystem})!void = switch // (...)`). static void zirErrorSetDecl(SemaCtx* sema, uint32_t inst) { uint32_t esd_ty = semaResolveErrorSetDeclInst(sema->ip, sema->zir, inst); sema->inst_values[inst] = esd_ty; // type is its own IP value // inst_types unused for type-value instructions } // Sema.zig:zirRetPtr // Sema.zirRetPtr: for a non-comptime, non-inlining function whose return type // is passed by-reference (e.g. an aggregate), reserve the result-location // pointer. Emits AIR `ret_ptr` with the mutable `*RetTy` pointer type (flags: // size=one, is_const=false, align=none, addrspace=generic => 0xFFFF00FC). The // body stores the return value through this pointer, then `ret_load` returns // it. static void zirRetPtr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ptr_ty = ipGetPtrType(sema->ip, sema->fn_ret_ty, IP_PTR_FLAGS_MUT_ONE); AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = ptr_ty; // AIR .ty field (ret_ptr uses ty) // An inlined body's result location is an ordinary `alloc` // (`"if (block.semaBlockIsComptime() or block.inlining != null ...)"`). uint32_t air_idx = semaAirInstAppend(sema->insts, sema->ictx->inline_ret_active ? AIR_INST_ALLOC : AIR_INST_RET_PTR, data); u32ListAppend(block, air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + air_idx; sema->inst_types[inst] = ptr_ty; } // Sema.zig:zirRef // Sema.zirRef -> analyzeRef: form a `*const T` reference to the operand. ZIR // un_tok { operand }. Two paths: // - Comptime operand: intern `*const T` pointer type then // `.ptr`/`.uav` value. // - Runtime operand: emit ALLOC(*T mutable) + store + PTR_CAST // to *const T. Mirrors Sema.analyzeRef's runtime branch: // creates *const T first (IP entry order), then *T mutable, // then alloc/store/ptr_cast AIR. // The `*const` flags encode as 0xFFFF01FC; mutable as 0xFFFF00FC. static void zirRef(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t operand_ref = sema->zir->inst_datas[inst].un_tok.operand; uint32_t operand_val = semaResolveRef(operand_ref, sema->inst_values); if (semaIsAirInstRef(operand_val)) { // Runtime operand: Sema.analyzeRef runtime path. sema->inst_values[inst] = semaAnalyzeRefRuntime(sema->ip, sema->insts, block, operand_val, semaResolveRefType(operand_ref, sema->inst_types), &sema->inst_types[inst]); return; } // Sema.analyzeRef computes the pointer child from typeOf(operand) // before interning `.ptr = .{ .base_addr = .uav }`. uint32_t operand_ty = semaResolveRefValType( sema->ip, operand_ref, operand_val, sema->inst_types); uint32_t ptr_ty = ipGetPtrType(sema->ip, operand_ty, IP_PTR_FLAGS_CONST_ONE); // `".func => |f| return sema.analyzeNavRef(block, src, f.owner_nav),"`, // and alike for an `.@"extern"`. uint32_t owner = ipFuncValueOwnerNav(sema->ip, operand_val); if (owner != IP_INDEX_NONE) { sema->inst_values[inst] = navDeclRefAddressResolved( sema->zcu, owner, &sema->inst_types[inst]); return; } uint32_t p = ipGetPtrUav(sema->ip, ptr_ty, operand_val); sema->inst_values[inst] = p; // comptime pointer value sema->inst_types[inst] = ptr_ty; } // Sema.zig:zirRefDeref // Sema.zirRefDeref: validates that operand is a pointer and for .one-size // pointers (the common case: *T) returns the operand itself — no AIR // instruction emitted. Used in lvalue context, e.g. `x.* = v` emits // ref_deref(x) whose result is x (the pointer), then store_node { ptr = // result, val = v }. ZIR data: un_node { src_node, operand }. static void zirRefDeref(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t rd_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t rd_val = semaResolveRef(rd_ref, sema->inst_values); uint32_t rd_ty = semaResolveRefValType(sema->ip, rd_ref, rd_val, sema->inst_types); uint32_t rd_folded = semaRefDerefComptime(sema->ip, semaIsAirInstRef(rd_val) ? IP_INDEX_NONE : rd_val, rd_ty, &sema->inst_types[inst]); // A runtime or null C pointer: `ptr_cast` to the single pointer // (no safety check under ReleaseSmall). sema->inst_values[inst] = rd_folded != IP_INDEX_NONE ? rd_folded : sema->inst_types[inst] == rd_ty ? rd_val : semaEmitTyOp(sema->insts, block, AIR_INST_PTR_CAST, sema->inst_types[inst], rd_val); } // Sema.zig:zirEnsureErrUnionPayloadVoid // Sema.zirEnsureErrUnionPayloadVoid: validates an error-union condition with // an error capture but no payload capture (`if (x) { ... } else |err| ...` / // while/switch variants). If the operand is an error union (or pointer to // one), its payload must be void/noreturn; valid source emits no AIR. static void zirEnsureErrUnionPayloadVoid(SemaCtx* sema, uint32_t inst) { uint32_t euv_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t euv_ty = semaResolveRefType(euv_ref, sema->inst_types); if (euv_ty < sema->ip->items_len && sema->ip->item_tags[euv_ty] == IP_TAG_TYPE_POINTER) { IpPtrInfo euv_ptr; if (ipPtrInfo(sema->ip, euv_ty, &euv_ptr) != IP_PTRI_OK) UNREACHABLE("ensure_err_union_payload_void: malformed pointer"); euv_ty = euv_ptr.child; } if (euv_ty < sema->ip->items_len && (ztIsZigTypeTag(sema->ip, euv_ty, ZIG_TYPE_TAG_ERROR_UNION))) { uint32_t euv_payload = ipErrorUnionPayload(sema->ip, euv_ty); if (euv_payload != ZIR_REF_VOID_TYPE && euv_payload != ZIR_REF_NORETURN_TYPE) COMPILE_ERROR_UPSTREAM( "ensure_err_union_payload_void: non-void payload"); } sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; } // (part of) Sema.zig:zirAllocExtended // Sema.zirAllocExtended's pointer flags before alignment, both frames: only // an inferred alloc is const (`"is_const = small.is_const"`). uint32_t semaAllocExtendedPtrFlags(uint16_t small) { return (small & ZIR_ALLOC_SMALL_HAS_TYPE) == 0 && (small & ZIR_ALLOC_SMALL_IS_CONST) != 0 ? IP_PTR_FLAGS_CONST_ONE : IP_PTR_FLAGS_MUT_ONE; } // Sema.zig:zirAllocExtended // Sema.zirAllocExtended, runtime typed path: explicit alignment (`var x: T // align(A)`) is encoded in the extended payload. static void zirAllocExtended(SemaCtx* sema, U32List* block, uint32_t inst) { uint16_t a_small = sema->zir->inst_datas[inst].extended.small; bool a_has_type = (a_small & ZIR_ALLOC_SMALL_HAS_TYPE) != 0; bool a_has_align = (a_small & ZIR_ALLOC_SMALL_HAS_ALIGN) != 0; bool a_is_const = (a_small & ZIR_ALLOC_SMALL_IS_CONST) != 0; bool a_is_comptime = (a_small & ZIR_ALLOC_SMALL_IS_COMPTIME) != 0; ZirInstAllocExtended extra; uint32_t a_ei = zirExtraData( sema->zir, sema->zir->inst_datas[inst].extended.operand, &extra); uint32_t a_ptr_flags = semaAllocExtendedPtrFlags(a_small); if (a_has_align) { uint32_t a_align_val = semaResolveRef(sema->zir->extra[a_ei + (a_has_type ? 1u : 0u)], sema->inst_values); if (!semaRefIsInterned(a_align_val)) UNIMPLEMENTED( "zirAllocExtended: alloc_extended - runtime alignment"); uint64_t a_align; if (!ipTryReadIntValue(sema->ip, a_align_val, &a_align)) UNIMPLEMENTED( "zirAllocExtended: alloc_extended - unreadable alignment"); a_ptr_flags = semaPtrFlagsWithExplicitAlign(sema->ip, a_ptr_flags, a_align, "zirAllocExtended: invalid alignment"); } if (!a_has_type && (semaBlockIsComptime(sema->ictx) || a_is_comptime)) { // "if (block.semaBlockIsComptime() or small.is_comptime) { ... // .tag = .inferred_alloc_comptime, ... .alignment = alignment, // .is_const = small.is_const" sema->inst_values[inst] = semaInferredAllocComptime(sema->insts, a_ptr_flags); sema->inst_types[inst] = UINT32_MAX; return; } if (!a_has_type) { // `"const result_index = try block.addInstAsIndex(.{ .tag // = .inferred_alloc, .data = .{ .inferred_alloc = .{ // .alignment = alignment, .is_const = small.is_const } // } });"` semaEmitInferredAllocPlaceholder(sema->insts, block, sema->inst_values, sema->inst_types, inst, a_ptr_flags); return; } uint32_t a_elem_ty = semaResolveRef(sema->zir->extra[a_ei], sema->inst_values); if (semaIsAirInstRef(a_elem_ty)) UNIMPLEMENTED( "zirAllocExtended: alloc_extended - runtime element type"); if (!ensureLayoutResolved(sema->zcu, a_elem_ty)) UNIMPLEMENTED( "zirAllocExtended: alloc_extended - element layout unresolved"); // "if (block.semaBlockIsComptime() or small.is_comptime or // var_ty.comptimeOnly(zcu)) { return // sema.analyzeComptimeAlloc(block, var_src, var_ty, // alignment); }" if (semaBlockIsComptime(sema->ictx) || a_is_comptime || ztComptimeOnly(sema->zcu, a_elem_ty)) { uint32_t ca_ptr_ty; sema->inst_values[inst] = analyzeComptimeAlloc(sema->zcu, sema->ip, a_elem_ty, (a_ptr_flags & ~IP_PTR_FLAGS_IS_CONST), &ca_ptr_ty); sema->inst_types[inst] = ca_ptr_ty; return; } uint32_t a_ptr_ty = ipGetPtrTypeFull(sema->ip, a_elem_ty, a_ptr_flags, IP_INDEX_NONE, 0); AirInstData a_data; memset(&a_data, 0, sizeof(a_data)); a_data.pair.a = a_ptr_ty; // `"if (small.is_const) { ... maybe_comptime_allocs.put(gpa, // ptr_inst, ...)"`: a `const x: T align(A)` is // store-forwarded. a_data.pair.b = a_is_const ? AIR_ALLOC_IS_CONST : 0u; uint32_t a_air_idx = semaAirInstAppend(sema->insts, AIR_INST_ALLOC, a_data); u32ListAppend(block, a_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + a_air_idx; sema->inst_types[inst] = a_ptr_ty; } // Sema.zig:zirAllocComptime // Sema.zirAllocComptime -> analyzeComptimeAlloc: reserve a comptime-mutable // memory slot for a `comptime var` / inline-loop counter. Interns a // `ptr_comptime_alloc` value (pointer to a fresh comptime alloc) — no AIR is // emitted. The mutable value itself lives in the InlineCtx side table // (upstream: // sema.comptime_allocs holding a MutableValue), not in the InternPool; // store/load through this pointer read/write that cell. ZIR data: un_node with // operand = element type ref. static void zirAllocComptime(SemaCtx* sema, uint32_t inst) { uint32_t type_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t elem_ty = semaResolveRef(type_ref, sema->inst_values); if (semaIsAirInstRef(elem_ty)) UNIMPLEMENTED("alloc_comptime_mut: runtime element type"); // `"try sema.ensureLayoutResolved(var_ty, var_src, .variable);"` ensureLayoutResolved(sema->zcu, elem_ty); uint32_t ptr_ty; sema->inst_values[inst] = analyzeComptimeAlloc( sema->zcu, sema->ip, elem_ty, IP_PTR_FLAGS_MUT_ONE, &ptr_ty); sema->inst_types[inst] = ptr_ty; } // Sema.zig:zirMakePtrConst // Sema.zirMakePtrConst -> makePtrConst: turn a mutable result- location alloc // pointer (`*T`) into a const pointer (`*const T`) once initialization is // complete, so it can be bound to a `const`. ZIR un_node with operand = the // alloc ref. Runtime path (our result-location struct init): emit AIR ptr_cast // from the alloc's `*T` to `*const T` (makePtrTyConst flips is_const and keeps // the rest, an `align(A)` alloc's alignment included). static void zirMakePtrConst(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t operand_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t operand_val = semaResolveRef(operand_ref, sema->inst_values); if (semaRefIsInterned(operand_val)) { uint32_t operand_ip = operand_val; uint32_t operand_ty = ipTypeOf(sema->ip, operand_ip); // `already_ct:` -- a finished comptime alloc whose value // does not reference comptime-mutable memory is promoted to // an anon decl (`.uav`), then made const. if (ipIsPtrComptimeAlloc(sema->ip, operand_ip) && ipPtrComptimeAllocByteOffset(sema->ip, operand_ip) == 0) { uint32_t mpc_val = mutableValueIntern(sema->zcu, &getComptimeAlloc( sema->zcu, ipPtrComptimeAllocIndex(sema->ip, operand_ip)) ->val); if (!valCanMutateComptimeVarState(sema->ip, mpc_val)) operand_ip = ipGetPtrUav(sema->ip, operand_ty, mpc_val); } if (operand_ty < sema->ip->items_len && sema->ip->item_tags[operand_ty] == IP_TAG_TYPE_POINTER) { uint32_t const_ptr_ty = makePtrTyConst(sema->ip, operand_ty); uint32_t const_ptr = ipRetypePtrValue(sema->ip, operand_ip, const_ptr_ty); if (const_ptr == IP_INDEX_NONE) UNIMPLEMENTED("make_ptr_const: unsupported pointer carrier"); sema->inst_values[inst] = const_ptr; sema->inst_types[inst] = const_ptr_ty; return; } } if (!semaIsAirInstRef(operand_val)) UNIMPLEMENTED("make_ptr_const: comptime alloc pointer"); uint32_t const_ptr_ty = makePtrTyConst( sema->ip, semaResolveRefType(operand_ref, sema->inst_types)); // `"if (try sema.resolveComptimeKnownAllocPtr(block, alloc, // null)) |ptr_val| { return sema.makePtrConst(block, // Air.internedToRef(ptr_val)); }"`. The comptime-mutable-memory // arm (a comptime_alloc base) stays runtime here. uint32_t mpc_ptr = semaFinishComptimeKnownAllocPtr(sema->zcu, sema->insts, block, sema->air_extra, operand_val, semaResolveRefType(operand_ref, sema->inst_types)); if (mpc_ptr != IP_INDEX_NONE) { sema->inst_values[inst] = mpc_ptr; sema->inst_types[inst] = const_ptr_ty; return; } AirInstData cast_data; memset(&cast_data, 0, sizeof(cast_data)); cast_data.pair.a = const_ptr_ty; cast_data.pair.b = operand_val; uint32_t cast_idx = semaAirInstAppend(sema->insts, AIR_INST_PTR_CAST, cast_data); u32ListAppend(block, cast_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + cast_idx; sema->inst_types[inst] = const_ptr_ty; } // (part of) Sema.zig:zirAlloc, Sema.zig:zirAllocMut // Sema.zirAllocMut -> block.addTy(.alloc, ptr_type). Creates a mutable // pointer-to-T alloc for a `var x: T` local. Sema.zirAlloc (plain `alloc`, // e.g. the compiler-generated `for` loop index counter) produces the same AIR: // block.addTy(.alloc, ptr_type) with default flags (is_const=false, default // address space). ZIR data: // un_node with operand = element type ref. In non-comptime, non-naked // functions: emits AIR alloc(ptr_type). PtrType.Flags: size=.one(0), // alignment=.none(0x3F<<2), // is_const=false, is_volatile=false, is_allowzero=false, // address_space=.generic(0), vector_index=.none(0xFFFF) // => 0xFFFF00FC. static void semaAlloc( SemaCtx* sema, U32List* block, uint32_t inst, bool is_mut) { uint32_t type_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t type_val = semaResolveRef(type_ref, sema->inst_values); if (semaIsAirInstRef(type_val)) UNIMPLEMENTED("alloc_mut: runtime element type"); uint32_t elem_ty = type_val; // AIR alloc carries only the pointer type; the interpreter reads // persisted aggregate alignment from the InternPool. Force the // complete reached type layout now, matching Sema's alloc layout // demand, so explicit field alignment cannot be lost. if (!ensureLayoutResolved(sema->zcu, elem_ty)) UNIMPLEMENTED("alloc_mut: element layout unresolved"); // Sema.zirAlloc / Sema.zirAllocMut, immediately after // ensureLayoutResolved: `"if (block.semaBlockIsComptime()) {"` -> // `"return sema.analyzeComptimeAlloc(block, var_src, var_ty, // .none);"` (zirAlloc spells it // `"if (block.semaBlockIsComptime() or var_ty.comptimeOnly(zcu)) {"`; the // comptimeOnly half is the `ALLOC` disjunct below). In a comptime // scope there is no runtime storage: the local becomes a comptime // cell and no AIR `alloc` is emitted. This is the alloc half of // plain comptime `for`/`while` unrolling — AstGen gives the loop // index counter a plain `.alloc` (`"if (is_inline) // .alloc_comptime_mut else .alloc"`), and only this arm makes the // per-iteration index a real comptime value rather than a load of // runtime storage. Placed before the comptime-only `var` gate // below, matching upstream's ordering (validateVarType runs only // on the runtime path). // // Disclosed DIVERGENCE (zig1 narrowing): upstream routes every // comptime-scope alloc here; zig1 routes only the element types // its cell model can represent (semaComptimeCellModelableType). A // struct/union/optional/error-union local is initialized through // field / payload-base pointers into the allocation, and per-field // comptime-cell stores are not ported — routing those would turn a // working `comptime { const x: S = .{...}; }` into a loud abort // (`struct_init_field_ptr: comptime object pointer`). Such a local // therefore takes the runtime-alloc lowering. Consuming a runtime // value as a comptime one aborts loudly at every boundary // (`block_comptime: runtime break operand`, `store_node: runtime // value to comptime cell`, and — with the `.condbr` redirect at // the top of this loop — `condbr_inline: runtime condition`), so a // comptime construct that actually depends on such an alloc stops // rather than computing a wrong answer. // zirAlloc's `"or var_ty.comptimeOnly(zcu)"` half: a `const` // local of comptime-only type is a comptime alloc anywhere. if ((semaBlockIsComptime(sema->ictx) && semaComptimeCellModelableType(sema->ip, elem_ty)) || (!is_mut && ztComptimeOnly(sema->zcu, elem_ty))) { uint32_t ct_ptr_ty; sema->inst_values[inst] = analyzeComptimeAlloc( sema->zcu, sema->ip, elem_ty, IP_PTR_FLAGS_MUT_ONE, &ct_ptr_ty); sema->inst_types[inst] = ct_ptr_ty; return; } // Sema.zirAllocMut (Sema.zig) validates a `var` local's // type via validateVarType, whose comptime-only arm is // `sema.fail(block, src, "variable of type '{f}' must be const or // comptime", ...)` -- a stock compile error, not an // unported construct, so it is reported as one. Plain `alloc` // (a const local's storage) has no such check upstream, so only // alloc_mut is gated. if (is_mut && ztComptimeOnly(sema->zcu, elem_ty)) COMPILE_ERROR_UPSTREAM( "alloc_mut: variable of comptime-only type must be " "const or comptime"); uint32_t ptr_ty; if (elem_ty == ZIR_REF_USIZE_TYPE) { // The single non-const `*usize` (the `for`/`while` index // counter's alloc type) is preinterned as // ZIR_REF_PTR_USIZE_TYPE; upstream's pt.ptrType reuses it // rather than appending. wrote the preintern's // real sentinel/flags words the `else` branch would dedup to // 44 too; the shortcut is kept. ptr_ty = ZIR_REF_PTR_USIZE_TYPE; } else { ptr_ty = ipGetPtrType(sema->ip, elem_ty, IP_PTR_FLAGS_MUT_ONE); } AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = ptr_ty; // AIR.Inst.Data.ty (alloc uses .ty field) // Upstream's `is_const` for this allocation, the same bit // `semaEmitInferredAllocPlaceholder` records: `Sema.zirAlloc` is a // `const` local's storage, `Sema.zirAllocMut` a runtime `var`'s. // `resolveValue` store-forwards out of the `const` one only. data.pair.b = is_mut ? 0u : AIR_ALLOC_IS_CONST; uint32_t air_idx = semaAirInstAppend(sema->insts, AIR_INST_ALLOC, data); u32ListAppend(block, air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + air_idx; sema->inst_types[inst] = ptr_ty; } // Sema.zig:zirAlloc static void zirAlloc(SemaCtx* sema, U32List* block, uint32_t inst) { semaAlloc(sema, block, inst, false); } // Sema.zig:zirAllocMut static void zirAllocMut(SemaCtx* sema, U32List* block, uint32_t inst) { semaAlloc(sema, block, inst, true); } // Sema.zig:zirAllocInferredComptime // Sema.zirAllocInferredComptime: a comptime `var x = ` or `const x = // ` whose element type is inferred from its store (`comptime var i = // 0;`). No AIR is emitted. static void zirAllocInferredComptime( SemaCtx* sema, uint32_t inst, bool is_const) { sema->inst_values[inst] = semaInferredAllocComptime( sema->insts, is_const ? IP_PTR_FLAGS_CONST_ONE : IP_PTR_FLAGS_MUT_ONE); sema->inst_types[inst] = UINT32_MAX; // ptr type unknown until resolved } // Sema.zig:zirAllocInferred static void zirAllocInferred( SemaCtx* sema, U32List* block, uint32_t inst, bool is_const) { if (semaBlockIsComptime(sema->ictx)) { zirAllocInferredComptime(sema, inst, is_const); return; } semaEmitInferredAllocPlaceholder(sema->insts, block, sema->inst_values, sema->inst_types, inst, is_const ? IP_PTR_FLAGS_CONST_ONE : IP_PTR_FLAGS_MUT_ONE); } // Sema.zig:zirResolveInferredAlloc // Sema.zirResolveInferredAlloc (src/Sema.zig): finalizes an inferred // alloc. One `resolvePeerTypes` over the operand types of every store recorded // on `InferredAlloc.prongs`, then the placeholder becomes a real `.alloc` of // `*final_elem_ty` and each recorded store is re-run against it. ZIR un_node // -> operand = alloc ref. static void zirResolveInferredAlloc( SemaCtx* sema, const U32List* block, uint32_t inst) { uint32_t operand_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t operand_val = semaResolveRef(operand_ref, sema->inst_values); uint32_t ria_iac = UINT32_MAX; if (semaIsAirInstRef(operand_val)) { uint32_t ria_ref = operand_val - AIR_REF_INST_BASE; if (ria_ref < sema->insts->len && sema->insts->tags[ria_ref] == AIR_INST_INFERRED_ALLOC_COMPTIME) ria_iac = ria_ref; } if (ria_iac != UINT32_MAX) { // zirResolveInferredAlloc's `.inferred_alloc_comptime` arm: "The // work was already done for us by // `Sema.storeToInferredAllocComptime` ... all we need to do here is // return the pointer", a `.uav` or a `.comptime_alloc`. uint32_t resolved_ptr = sema->insts->datas[ria_iac].pair.b; if (resolved_ptr == IP_INDEX_NONE) UNIMPLEMENTED( "resolve_inferred_alloc: comptime cell never stored to"); if (!ipIsPtrComptimeAlloc(sema->ip, resolved_ptr) && !ipIsPtrUav(sema->ip, resolved_ptr)) { UNREACHABLE("resolve_inferred_alloc: invalid comptime pointer"); } // `sema.typeOf` of the returned `iac.ptr`. sema->inst_values[inst] = resolved_ptr; sema->inst_types[inst] = ipTypeOf(sema->ip, resolved_ptr); return; } if (!semaIsAirInstRef(operand_val)) UNIMPLEMENTED("resolve_inferred_alloc: comptime alloc"); if (operand_ref < ZIR_REF_START_INDEX) UNIMPLEMENTED("resolve_inferred_alloc: preinterned alloc"); uint32_t alloc_zir = operand_ref - ZIR_REF_START_INDEX; uint32_t alloc_air_idx = operand_val - AIR_REF_INST_BASE; // "const final_elem_ty = try sema.resolvePeerTypes(block, ty_src, // peer_vals, .none);", whose `peer_vals` are exactly the // prongs' `bin_op.rhs` operands. uint32_t final_elem_ty = semaBlockMergesPeerType(sema->zcu, sema->ictx, alloc_air_idx); uint32_t ria_flags = sema->insts->datas[alloc_air_idx].pair.b; bool ria_is_const = (ria_flags & AIR_ALLOC_IS_CONST) != 0; // "if (!ia1.is_const) { try sema.validateVarType(block, ty_src, // final_elem_ty, false); }" -- validateVarType's // comptime-only arm is the stock compile error "variable of type // '{f}' must be const or comptime". bool ria_ct_only = ztComptimeOnly(sema->zcu, final_elem_ty); if (ria_ct_only && !ria_is_const) COMPILE_ERROR_UPSTREAM( "resolve_inferred_alloc: variable of comptime-only " "type must be const or comptime"); // "const final_ptr_ty = try pt.ptrType(.{ .child = // final_elem_ty.toIntern(), ... });", plus the trailing // `makePtrConst` folded into the flags, as every other // zig1 local alloc spells a `const` pointer. // `.flags = .{ .alignment = ia1.alignment, ... }`. uint32_t final_ptr_ty = ipGetPtrType(sema->ip, final_elem_ty, ria_flags); // "Change it to a normal alloc." sema->insts->datas[alloc_air_idx].pair.a = final_ptr_ty; sema->inst_types[alloc_zir] = final_ptr_ty; for (uint32_t pi = 0; pi < sema->ictx->merges_len; pi++) if (sema->ictx->merges[pi].block_inst == alloc_air_idx) semaInferredAllocRewriteProng(sema->zcu, sema->insts, sema->air_extra, sema->ictx->merges[pi].br_inst, sema->ictx->merges[pi].result_ty, final_elem_ty); if (ria_is_const) { // "} else if (try sema.resolveComptimeKnownAllocPtr(block, // ptr, final_ptr_ty)) |ptr_val| { ... return // Air.internedToRef(new_const_ptr.toIntern()); }" -- read // here off the re-run stores, which are what upstream's // `maybe_comptime_allocs` recorded. uint32_t ria_ptr = semaFinishComptimeKnownAllocPtr(sema->zcu, sema->insts, block, sema->air_extra, operand_val, final_ptr_ty); if (ria_ptr != IP_INDEX_NONE) { sema->inst_values[inst] = ria_ptr; sema->inst_types[inst] = ipTypeOf(sema->ip, ria_ptr); return; } } // A comptime-only element reaches codegen only through a comptime-known // alloc; otherwise it "depends on runtime control flow". if (ria_ct_only) COMPILE_ERROR_UPSTREAM("resolve_inferred_alloc: value " "with comptime-only type depends " "on runtime control flow"); sema->inst_values[inst] = operand_val; sema->inst_types[inst] = final_ptr_ty; } // Sema.zig:zirForLen // Sema.zirForLen: every [indexable_or_start, range_end] input pair (an // open-ended `0..` counter is {.none, .none}) yields a length. The first // comptime-known one is the loop length, and a differing comptime length is // "non-matching for loop lengths"; with none comptime-known, the first length // is. ReleaseSmall elides the runtime length-equality safety checks. static void zirForLen(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstMultiOp extra; uint32_t extra_end = zirExtraData(sema->zir, pp, &extra); uint32_t npairs = extra.operands_len / 2; uint32_t len = INST_VALUE_UNSET; bool len_ct = false; for (uint32_t pi = 0; pi < npairs; pi++) { uint32_t arg0 = sema->zir->extra[extra_end + pi * 2]; if (arg0 == ZIR_REF_NONE) continue; // `if (!object_ty.indexableHasLen(zcu)) continue;`: a many or // C pointer input has no length (a range's start is a usize). IpPtrInfo fl_pi; uint32_t fl_obj = semaResolveRef(arg0, sema->inst_values); if (ipPtrInfo(sema->ip, semaResolveRefValType( sema->ip, arg0, fl_obj, sema->inst_types), &fl_pi) == IP_PTRI_OK && (fl_pi.size == IP_PTR_SIZE_MANY || fl_pi.size == IP_PTR_SIZE_C)) continue; uint32_t arg_len = semaForLenArg(sema->ip, sema->inst_values, sema->inst_types, sema->insts, block, arg0, sema->zir->extra[extra_end + 1 + pi * 2]); if (len == INST_VALUE_UNSET) len = arg_len; if (semaIsAirInstRef(arg_len)) continue; if (!len_ct) { len = arg_len; len_ct = true; } else if (arg_len != len) { COMPILE_ERROR_UPSTREAM("non-matching for loop lengths"); } } sema->inst_values[inst] = len; sema->inst_types[inst] = ZIR_REF_USIZE_TYPE; } // `"try sema.ensureLayoutResolved(ptr_ty.childType(zcu), src, // .ptr_access);"` on a pointer or slice operand; a comptime value standing for // its own pointer has no pointee to resolve. static void semaEnsurePointeeLayoutResolvedTy(SemaCtx* sema, uint32_t ty) { IpPtrInfo info; if (ztPtrOrSliceInfo(sema->ip, ty, &info)) ensureLayoutResolved(sema->zcu, info.child); } static void semaEnsurePointeeLayoutResolved(SemaCtx* sema, uint32_t ref) { uint32_t v = semaResolveRef(ref, sema->inst_values); if (v == INST_VALUE_UNSET) return; semaEnsurePointeeLayoutResolvedTy(sema, semaIsAirInstRef(v) ? semaResolveRefType(ref, sema->inst_types) : ipTypeOf(sema->ip, v)); } // Sema.zig:zirOptEuBasePtrInit // Sema.optEuBasePtrInit: while the pointee is an optional or error union, peel // to its payload with the initializing payload-pointer ops (AIR // `*_payload_ptr_set`; at comptime the non-null / non-error container is // stored with an undefined payload). Any other pointee is the base pointer. static void zirOptEuBasePtrInit(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t operand_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t operand_val = semaResolveRef(operand_ref, sema->inst_values); uint32_t operand_ty = semaResolveRefType(operand_ref, sema->inst_types); semaEnsurePointeeLayoutResolved(sema, operand_ref); // .init // `inst_types[]` may carry IP_INDEX_NONE for a comptime operand. if (!semaIsAirInstRef(operand_val) && !ipItemTagIs(sema->ip, operand_ty, IP_TAG_TYPE_POINTER)) UNIMPLEMENTED( "opt_eu_base_ptr_init: comptime operand of unknown pointer type"); for (;;) { uint32_t child_ty = sema->ip->extra[sema->ip->item_datas[operand_ty]]; bool opt = ztIsZigTypeTag(sema->ip, child_ty, ZIG_TYPE_TAG_OPTIONAL); if (!opt && !semaIsErrorUnionType(sema->ip, child_ty)) break; uint32_t payload_ty = opt ? sema->ip->item_datas[child_ty] : ipErrorUnionPayload(sema->ip, child_ty); if (!semaIsAirInstRef(operand_val)) { uint32_t ct_ptr = operand_val; if (!ipIsPtrComptimeAlloc( sema->ip, ipPtrDerivedRoot(sema->ip, ct_ptr))) UNIMPLEMENTED( "opt_eu_base_ptr_init: comptime optional/error-union " "pointee not a comptime var"); // The payload pointer type first: an optional's // `child_pointer` is the operand's ptrInfo with the child // replaced, an error union's `operand_pointer_ty` keeps // only its constness and address space. uint32_t ct_op_flags = sema->ip ->extra[sema->ip->item_datas[ipTypeOf(sema->ip, ct_ptr)] + 2]; ipGetPtrType(sema->ip, payload_ty, opt ? ct_op_flags : IP_PTR_FLAGS_MUT_ONE | (ct_op_flags & (IP_PTR_FLAGS_IS_CONST | IP_PTR_FLAGS_ADDRSPACE_MASK << IP_PTR_FLAGS_ADDRSPACE_SHIFT))); // `"payload_ty.onePossibleValue(pt) orelse try // pt.undefValue(payload_ty)"`: a zero-bit payload's // element stores are skipped, so `undefined` would stay. uint32_t ct_pl = ztOnePossibleValue(sema->ip, payload_ty); if (ct_pl == IP_INDEX_NONE) ct_pl = ipGetUndef(sema->ip, payload_ty); semaStorePtr2Comptime(sema->zcu, ct_ptr, opt ? ipGetOptPayload(sema->ip, child_ty, ct_pl) : ipGetErrorUnionPayload(sema->ip, child_ty, ct_pl)); uint32_t ct_next = IP_INDEX_NONE; if (opt) ct_next = semaComptimeOptionalPayloadPtr(sema->zcu, ct_ptr); else if (!semaErrUnionPayloadPtrComptime( sema->zcu, sema->ip, ct_ptr, &ct_next)) ct_next = IP_INDEX_NONE; if (ct_next == IP_INDEX_NONE) UNIMPLEMENTED( "opt_eu_base_ptr_init: comptime payload pointer"); operand_val = ct_next; operand_ty = ipTypeOf(sema->ip, ct_next); continue; } uint32_t payload_ptr_ty = ipGetPtrType(sema->ip, payload_ty, IP_PTR_FLAGS_MUT_ONE); AirInstData peel_data; memset(&peel_data, 0, sizeof(peel_data)); peel_data.pair.a = payload_ptr_ty; // ty_op.ty = *payload peel_data.pair.b = operand_val; // the outer ptr uint32_t peel_idx = semaAirInstAppend(sema->insts, opt ? AIR_INST_OPTIONAL_PAYLOAD_PTR_SET : AIR_INST_ERRUNION_PAYLOAD_PTR_SET, peel_data); u32ListAppend(block, peel_idx); operand_val = AIR_REF_INST_BASE + peel_idx; operand_ty = payload_ptr_ty; } sema->inst_values[inst] = operand_val; sema->inst_types[inst] = operand_ty; } // Sema.zig:zirCoercePtrElemTy // Sema.zirCoercePtrElemTy: `&` under a pointer result type coerces // the operand to what the pointer addresses. ZIR pl_node -> Bin { lhs = // ptr_type_ref, rhs = uncoerced_val_ref }. `resolveTypeOrPoison(...) orelse // return uncoerced_val`, then `optEuBaseType` strips result-location optional // / error-union wrappers before switching on the pointer size. static void zirCoercePtrElemTy(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); uint32_t cpe_ptr_ty_ref = extra.lhs; uint32_t cpe_val_ref = extra.rhs; uint32_t cpe_val = semaResolveRef(cpe_val_ref, sema->inst_values); uint32_t cpe_ptr_ty_val = semaResolveRef(cpe_ptr_ty_ref, sema->inst_values); if (semaIsAirInstRef(cpe_ptr_ty_val)) UNIMPLEMENTED("coerce_ptr_elem_ty: non-type lhs"); uint32_t cpe_val_ty = semaResolveRefValType( sema->ip, cpe_val_ref, cpe_val, sema->inst_types); sema->inst_values[inst] = cpe_val; sema->inst_types[inst] = cpe_val_ty; uint32_t cpe_want_ty = semaCoercePtrElemTyWant(sema->ip, cpe_ptr_ty_val, cpe_val_ty); if (cpe_want_ty == IP_INDEX_NONE) return; sema->inst_types[inst] = cpe_want_ty; sema->inst_values[inst] = coerce(sema->zcu, sema->insts, block, sema->air_extra, cpe_want_ty, cpe_val, cpe_val_ty); } // Sema.zig:zirValidateArrayInitRefTy // Sema.zirValidateArrayInitRefTy: compute the result type of a `&.{...}` // by-ref array literal from its coercion pointer type. ZIR pl_node -> // ArrayInitRefTy { ptr_ty, elem_count }. For a slice/many-ptr coercion target // (`&.{bytes}` -> the `data: []const []const u8` param in Writer.write's drain // call) the result is `[elem_count]child` carrying the pointer's sentinel; for // a single-pointer target it is the pointer's child (array) type. static void zirValidateArrayInitRefTy(SemaCtx* sema, uint32_t inst) { uint32_t varp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstArrayInitRefTy extra; zirExtraData(sema->zir, varp, &extra); uint32_t var_ptr_ref = extra.ptr_ty; uint32_t var_elem_count = extra.elem_count; uint32_t var_ptr_val = semaResolveRef(var_ptr_ref, sema->inst_values); if (semaIsAirInstRef(var_ptr_val)) UNIMPLEMENTED("validate_array_init_ref_ty: runtime ptr type"); // `resolveTypeOrPoison(...) orelse return .generic_poison_type`. if (var_ptr_val == ZIR_REF_GENERIC_POISON_TYPE) { sema->inst_values[inst] = ZIR_REF_GENERIC_POISON_TYPE; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; return; } // `maybe_wrapped_ptr_ty.optEuBaseType(zcu)`: the result type may // reach through `?*T` / `E!*T`. uint32_t var_ptr_ty = ipOptEuBaseType(sema->ip, var_ptr_val); uint32_t var_result_ty; if (ztIsSlice(sema->ip, var_ptr_ty)) { uint32_t var_many = sema->ip->item_datas[var_ptr_ty]; uint32_t var_child = sema->ip->extra[sema->ip->item_datas[var_many]]; uint32_t var_sent = sema->ip->extra[sema->ip->item_datas[var_many] + 1]; var_result_ty = ipGetArrayType(sema->ip, var_elem_count, var_child, var_sent); } else if (sema->ip->item_tags[var_ptr_ty] == IP_TAG_TYPE_POINTER && IP_PTR_FLAGS_SIZE( sema->ip->extra[sema->ip->item_datas[var_ptr_ty] + 2]) == IP_PTR_SIZE_MANY) { // many-ptr `[*]T`: child at extra[0], sentinel at extra[1]. uint32_t var_child = sema->ip->extra[sema->ip->item_datas[var_ptr_ty]]; uint32_t var_sent = sema->ip->extra[sema->ip->item_datas[var_ptr_ty] + 1]; var_result_ty = ipGetArrayType(sema->ip, var_elem_count, var_child, var_sent); } else if (sema->ip->item_tags[var_ptr_ty] == IP_TAG_TYPE_POINTER) { // single pointer `*[N]T`: the child is the array type. var_result_ty = sema->ip->extra[sema->ip->item_datas[var_ptr_ty]]; } else { UNIMPLEMENTED("validate_array_init_ref_ty: non-pointer target"); } sema->inst_values[inst] = var_result_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirValidatePtrStructInit // Sema.zirValidatePtrStructInit -> validateStructInit: verifies all struct // fields of a result-location struct init are initialized; its only side // effect is emitting default-value stores for any field left unspecified. ZIR // pl_node -> Block { body_len } followed by the per-field // struct_init_field_ptr instruction indices. When every field is explicitly // initialized it emits no AIR (pure validation); a partial init // (`Writer.fixed`'s `&.{ .drain=…, .flush=…, .rebase=… }` omits `sendFile`, // which defaults to a fn pointer) emits a STRUCT_FIELD_PTR + store of each // omitted field's default. static void zirValidatePtrStructInit( SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBlock extra; uint32_t extra_end = zirExtraData(sema->zir, pp, &extra); uint32_t vp_body_len = extra.body_len; uint32_t first_fp = sema->zir->extra[extra_end]; ZirInstField fp_extra; zirExtraData(sema->zir, sema->zir->inst_datas[first_fp].pl_node.payload_index, &fp_extra); uint32_t obj_ptr_ref = fp_extra.lhs; uint32_t obj_ptr_val = semaResolveRef(obj_ptr_ref, sema->inst_values); bool vp_cell = !semaIsAirInstRef(obj_ptr_val) && (ipIsPtrComptimeAlloc(sema->ip, obj_ptr_val) || semaIsCellChainPtr(sema->ip, obj_ptr_val)); // A comptime pointer to no cell: a one-possible-value alloc's ptr_uav. bool vp_uav = !semaIsAirInstRef(obj_ptr_val) && !vp_cell && !semaIsRuntimeDataNavPtr(sema->ip, obj_ptr_val); uint32_t ptr_ty = vp_cell || vp_uav ? ipTypeOf(sema->ip, obj_ptr_val) : semaResolveRefType(obj_ptr_ref, sema->inst_types); uint32_t agg_ty = sema->ip->extra[sema->ip->item_datas[ptr_ty]]; if (ztZigTypeTag(sema->ip, agg_ty) == ZIG_TYPE_TAG_UNION) { // Sema.validateUnionInit (Sema.zig): a union result-loc // init has exactly one field; for tagged unions the tag was // already set by struct_init_field_ptr's set_union_tag // machinery, while plain unions have no runtime tag to write. // Validation is therefore a pure no-op (no AIR). More than // one field is the "cannot initialize multiple union fields" // compile error — abort loudly (unreachable in valid // programs). if (vp_body_len != 1) UNIMPLEMENTED("validate_ptr_struct_init: multiple union fields"); return; } if (!ipTagIsAnyStruct(sema->ip->item_tags[agg_ty])) UNIMPLEMENTED("validate_ptr_struct_init: non-struct pointee"); uint32_t vp_fields_len = ipStructFieldsLen(sema->ip, agg_ty); if (vp_body_len == vp_fields_len) return; // fully initialized — nothing to default if (vp_uav) UNIMPLEMENTED("validate_ptr_struct_init: comptime object pointer"); if (vp_body_len > vp_fields_len) UNIMPLEMENTED("validate_ptr_struct_init: field count"); // Mark the fields that were explicitly initialized (each body inst // is a struct_init_field_ptr naming its field). Heap-sized by // fields_len, mirroring Sema.validateStructInit's // `found_fields = gpa.alloc(bool, structFieldCount)` + memset // false. Lifetime: this case only — freed at the single // break below (every other exit is an UNIMPLEMENTED process // abort). bool* vp_set = zig1Calloc((vp_fields_len != 0) ? vp_fields_len : 1, sizeof(bool)); for (uint32_t j = 0; j < vp_body_len; j++) { uint32_t fpj = sema->zir->extra[extra_end + j]; ZirInstField fpj_extra; zirExtraData(sema->zir, sema->zir->inst_datas[fpj].pl_node.payload_index, &fpj_extra); uint32_t nm_start = fpj_extra.field_name_start; const char* nm = zirNullTerminatedString(sema->zir, nm_start); uint32_t nm_ip = ipGetOrPutString(sema->ip, nm, (uint32_t)strlen(nm)); for (uint32_t k = 0; k < vp_fields_len; k++) if (ipStructFieldName(sema->ip, agg_ty, k) == nm_ip) { vp_set[k] = true; break; } } // For each omitted field: emit field_ptr + store of its folded // default (Sema's default_val store). The default is comptime, so // the stored operand is an interned value coerced to the field // type (a fn value -> *const fn, an int -> the field int type). for (uint32_t k = 0; k < vp_fields_len; k++) { if (vp_set[k]) continue; // validateStructInit deliberately skips omitted comptime // fields: their value lives in the type and there is no // runtime/default store to emit. if (ipStructFieldIsComptime(sema->ip, agg_ty, k)) continue; uint32_t def_val = semaFieldDefaultValue(sema->zcu, agg_ty, k); if (def_val == IP_INDEX_NONE) UNIMPLEMENTED( "validate_ptr_struct_init: omitted field no default"); if (vp_cell) { // A comptime cell: the default is stored through its // `Value.ptrField` at comptime (`storePtrVal`). semaStorePtr2Comptime(sema->zcu, semaComptimeCellFieldPtr(sema->zcu, obj_ptr_val, ipStructFieldName(sema->ip, agg_ty, k)), def_val); continue; } // `"const field_ptr_ty = try struct_ptr_ty.fieldPtrType( // field_index, pt);"`: a packed field's pointer is a bit // pointer into the host. uint32_t fld_ptr_ty = ztFieldPtrType(sema->zcu, ptr_ty, k); // Sema.structFieldPtrByIndex -> Block.addStructFieldPtr: // field_index 0..3 use the dedicated struct_field_ptr_index_N // tags (ty_op); index >= 4 uses the generic struct_field_ptr // (ty_pl { ty, Air.StructField { struct_operand, field_index } // }). Mirrors the // struct_init_field_ptr emission below. uint32_t fp_idx; if (k < 4) { AirInstData fp_data; memset(&fp_data, 0, sizeof(fp_data)); fp_data.pair.a = fld_ptr_ty; fp_data.pair.b = obj_ptr_val; fp_idx = semaAirInstAppend(sema->insts, (AirInstTag)(AIR_INST_STRUCT_FIELD_PTR_INDEX_0 + k), fp_data); } else { uint32_t sf_payload = sema->air_extra->len; u32ListAppend(sema->air_extra, obj_ptr_val); // struct_operand u32ListAppend(sema->air_extra, k); // field_index AirInstData fp_data; memset(&fp_data, 0, sizeof(fp_data)); fp_data.pair.a = fld_ptr_ty; // ty_pl.ty fp_data.pair.b = sf_payload; // ty_pl.payload fp_idx = semaAirInstAppend( sema->insts, AIR_INST_STRUCT_FIELD_PTR, fp_data); } u32ListAppend(block, fp_idx); AirInstData st_data; memset(&st_data, 0, sizeof(st_data)); st_data.pair.a = AIR_REF_INST_BASE + fp_idx; st_data.pair.b = def_val; uint32_t st_idx = semaAirInstAppend(sema->insts, AIR_INST_STORE, st_data); u32ListAppend(block, st_idx); } free(vp_set); } // Sema.zig:zirValidatePtrArrayInit // Sema.zirValidatePtrArrayInit: validates that every element of a by-ref array // init was stored. Its `.@"struct"` (tuple) and `.vector` arms only count // elements and emit nothing, but the `.array` arm has a side effect the // element inits cannot supply: // the sentinel, which "is never specified so we always need to initialize // here" -- // `"const sentinel_ptr = try sema.elemPtrArray(block, init_src, // init_src, array_ptr, init_src, array_len_ref, true, true);"` // `"try sema.storePtr2(block, init_src, sentinel_ptr, init_src, // .fromValue(sentinel), init_src, .store);"` // Without it the cell one past the last element keeps whatever the alloc held // (zero), so a non-zero sentinel read back a wrong value instead of the // type's. // // The array pointer is the one every element pointer was taken from: AstGen's // arrayInitExprPtr threads the same opt_eu_base_ptr_init result through each // ElemPtrImm and asserts at least one element, so `instrs[0]`'s `ptr` is it, // exactly as upstream reads it. static void zirValidatePtrArrayInit( SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t vpa_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBlock extra; uint32_t extra_end = zirExtraData(sema->zir, vpa_pp, &extra); uint32_t vpa_body_len = extra.body_len; uint32_t vpa_first = sema->zir->extra[extra_end]; ZirInstElemPtrImm vpa_ep_extra; zirExtraData(sema->zir, sema->zir->inst_datas[vpa_first].pl_node.payload_index, &vpa_ep_extra); uint32_t vpa_ptr_ref = vpa_ep_extra.ptr; uint32_t vpa_ptr_val = semaResolveRef(vpa_ptr_ref, sema->inst_values); uint32_t vpa_ptr_ty = semaResolveRefType(vpa_ptr_ref, sema->inst_types); if (vpa_ptr_ty >= sema->ip->items_len || sema->ip->item_tags[vpa_ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("validate_ptr_array_init: non-pointer result location"); uint32_t vpa_pointee = sema->ip->extra[sema->ip->item_datas[vpa_ptr_ty]]; IpArrayInfo vpa_info; if (ipArrayInfo(sema->ip, vpa_pointee, &vpa_info) != IP_ARR_OK) { // The `.@"struct"` (tuple) arm, which opens with the same // `"if (instrs.len != array_len)"` as the two below but // reports per-field: every field from `instrs.len` on whose // `"array_ty.structFieldDefaultValue(i, zcu) == null"` is a // "missing tuple field with index {d}". It stores nothing -- // a tuple's defaults are comptime fields. if (ztIsTuple(sema->ip, vpa_pointee)) { uint32_t vpa_fields = ipTupleFieldCount(sema->ip, vpa_pointee); for (uint32_t fi = vpa_body_len; fi < vpa_fields; fi++) if (ipTupleFieldValue(sema->ip, vpa_pointee, fi) == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM( "validate_ptr_array_init: missing tuple " "field"); } return; } // Both the `.array` and the `.vector` arm open with // `"if (instrs.len != array_len)"` -> "expected {d} array // elements; found {d}". AstGen's arrayInitExprPtr emits exactly // one array_init_elem_ptr per written element, so the validation // body's length is `instrs.len`. if (vpa_body_len != vpa_info.len) COMPILE_ERROR_UPSTREAM("validate_ptr_array_init: wrong number " "of array elements"); if (vpa_info.is_vector || vpa_info.sentinel == IP_INDEX_NONE) return; // nothing to store past the elements if (!semaIsAirInstRef(vpa_ptr_val)) UNIMPLEMENTED("validate_ptr_array_init: comptime sentinel store"); uint32_t vpa_sent_ptr_ty = ztElemPtrType(sema->zcu, vpa_ptr_ty, vpa_info.len, NULL); uint32_t vpa_sent_ptr = semaEmitPtrElemPtr(vpa_ptr_val, ipGetIntUsize(sema->ip, vpa_info.len), vpa_sent_ptr_ty, sema->insts, block, sema->air_extra); AirInstData vpa_st; memset(&vpa_st, 0, sizeof(vpa_st)); vpa_st.pair.a = vpa_sent_ptr; vpa_st.pair.b = vpa_info.sentinel; u32ListAppend( block, semaAirInstAppend(sema->insts, AIR_INST_STORE, vpa_st)); } // Sema.zig:zirValidateDestructure // Sema.zirValidateDestructure: validation-only (emits no AIR) — it asserts the // destructured operand is a destructurable aggregate with exactly `expect_len` // elements, else a compile error. AstGen emits this before the per-component // `elem_val_imm` + store chain for `const a, const b = rhs`. ZIR pl_node -> // ValidateDestructure { operand, destructure_node, expect_len }. Faithful // subset: // Runtime tuples (the `@addWithOverflow` result `{T, u1}`), // arrays/vectors (`const a, b, c = array`), and comptime-known aggregate // values. Verify the destructurable aggregate has the expected length; a // non-aggregate operand aborts loudly. static void zirValidateDestructure(SemaCtx* sema, uint32_t inst) { uint32_t vd_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstValidateDestructure extra; zirExtraData(sema->zir, vd_pp, &extra); uint32_t vd_operand_ref = extra.operand; uint32_t vd_expect_len = extra.expect_len; uint32_t vd_operand = semaResolveRef(vd_operand_ref, sema->inst_values); uint32_t vd_ty = semaIsAirInstRef(vd_operand) ? (semaResolveRefType(vd_operand_ref, sema->inst_types)) : ipTypeOf(sema->ip, vd_operand); uint32_t vd_len; if (ztIsTuple(sema->ip, vd_ty)) { vd_len = ipTupleFieldCount(sema->ip, vd_ty); } else { IpArrayInfo vd_arr; if (ipArrayInfo(sema->ip, vd_ty, &vd_arr) != IP_ARR_OK) UNIMPLEMENTED("validate_destructure: non-tuple operand"); vd_len = vd_arr.len; } if (vd_len != vd_expect_len) UNIMPLEMENTED("validate_destructure: element count mismatch"); // Validation only; no AIR, no result value. } // Sema.zig:zirStoreToInferredPtr // Sema.zirStoreToInferredPtr (src/Sema.zig): a store through a still-untyped // inferred alloc pointer. It records a placeholder `.store` whose operand // feeds the one peer-type resolution zirResolveInferredAlloc runs over every // recorded store. ZIR pl_node -> Bin { lhs = inferred-alloc ptr ref, rhs = // value ref }. static void zirStoreToInferredPtr( SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); uint32_t ptr_ref = extra.lhs; uint32_t val_ref = extra.rhs; uint32_t ptr_val = semaResolveRef(ptr_ref, sema->inst_values); uint32_t val_val = semaResolveRef(val_ref, sema->inst_values); // zirStoreToInferredPtr dispatches on the POINTER's AIR tag // (`.inferred_alloc_comptime` vs `.inferred_alloc`, `else => // unreachable`). The comptime placeholder is the one // alloc_inferred_comptime[_mut] left behind. uint32_t iac_idx = UINT32_MAX; if (semaIsAirInstRef(ptr_val)) { uint32_t iac_ref = ptr_val - AIR_REF_INST_BASE; if (iac_ref < sema->insts->len && sema->insts->tags[iac_ref] == AIR_INST_INFERRED_ALLOC_COMPTIME) iac_idx = iac_ref; } if (iac_idx != UINT32_MAX) { // `"const operand_val = sema.resolveValue(operand) orelse { return // sema.failWithNeededComptime(...)"`. if (semaIsAirInstRef(val_val)) UNIMPLEMENTED( "store_to_inferred_ptr: runtime value to comptime cell"); sema->insts->datas[iac_idx].pair.b = storeToInferredAllocComptime( sema->zcu, sema->insts->datas[iac_idx].pair.a, (uint32_t)val_val); return; } if (!semaIsAirInstRef(ptr_val)) UNIMPLEMENTED("store_to_inferred_ptr: comptime alloc ptr"); if (ptr_ref < ZIR_REF_START_INDEX) UNIMPLEMENTED("store_to_inferred_ptr: preinterned alloc ptr"); // Sema.storeToInferredAlloc (src/Sema.zig): "Create a store // instruction as a placeholder. This will be replaced by a proper // store sequence once we know the stored type", then // `inferred_alloc.prongs.append(sema.arena, // dummy_store.toIndex().?)`. Nothing is resolved here: the element // type is one `resolvePeerTypes` over every prong, run by // zirResolveInferredAlloc, which then re-runs each store. // The prong records what upstream reads back off the placeholder // at resolve time -- `sema.typeOf(bin_op.rhs)` and // `sema.resolveValue(bin_op.rhs)` -- because a zig1 AIR operand // has lost the ZIR ref its type came from (see `BlockMerge`). uint32_t operand_ty = semaIsAirInstRef(val_val) ? semaResolveRefType(val_ref, sema->inst_types) : ipComptimeValueType(sema->ip, val_val); uint32_t operand_ip = semaIsAirInstRef(val_val) ? IP_INDEX_NONE : val_val; AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = (uint32_t)ptr_val; // bin_op.lhs = ptr Ref data.pair.b = (uint32_t)val_val; // bin_op.rhs = val Ref uint32_t store_air_idx = semaAirInstAppend(sema->insts, AIR_INST_STORE, data); u32ListAppend(block, store_air_idx); // `InferredAlloc.prongs` (src/Sema.zig) is a per-alloc peer // prong list keyed by the alloc's own AIR index, so it shares // zig1's one flattened `Merges` array: AIR indices are minted // once and never reused, so the two key spaces cannot collide. semaBlockMergesAppend(sema->ictx, ptr_val - AIR_REF_INST_BASE, store_air_idx, operand_ty, operand_ip); // store is a void statement; inst_values[inst] not set. } // Sema.zig:zirSetEvalBranchQuota // Sema.zirSetEvalBranchQuota: `"const quota: u32 = @intCast(try // sema.resolveInt(block, src, inst_data.operand, .u32, ...)); // sema.branch_quota = @max(sema.branch_quota, quota);"`. No AIR. static void zirSetEvalBranchQuota(SemaCtx* sema, uint32_t inst) { uint32_t q_val = semaResolveRef( sema->zir->inst_datas[inst].un_node.operand, sema->inst_values); uint64_t q; if (!semaRefIsInterned(q_val) || !ipTryReadIntValue(sema->ip, q_val, &q) || q > UINT32_MAX) UNIMPLEMENTED("set_eval_branch_quota: operand not a comptime u32"); semaSetEvalBranchQuota(sema->zcu, (uint32_t)q); } // Sema.zig:zirStoreNode, Sema.zig:storePtr2 // Sema.zirStoreNode -> storePtr2: reject a const pointer before coercion or // the OPV no-op, then block.addBinOp(.store, ptr, val). ZIR data: pl_node // with Bin payload { lhs=ptr_ref, rhs=val_ref }. In ReleaseSmall mode // (!wantSafety()): emits AIR `store` (not store_safe). `store` does not // produce an AIR result ref (void statement); do not append it to inst_values. static void zirStoreNode(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); uint32_t store_ptr_ref = extra.lhs; uint32_t store_val_ref = extra.rhs; uint32_t store_ptr_val = semaResolveRef(store_ptr_ref, sema->inst_values); uint32_t store_val_val = semaResolveRef(store_val_ref, sema->inst_values); if (ztPtrIsConst(sema->ip, store_ptr_val)) COMPILE_ERROR_UPSTREAM("store_node: cannot assign to constant"); // `".one_possible_value => return, // no actual store operation is // necessary"`, after the operand's coercion to the pointee type. uint32_t opv_ptr_ty = semaResolveRefValType( sema->ip, store_ptr_ref, store_ptr_val, sema->inst_types); uint32_t opv_child = ipItemTagIs(sema->ip, opv_ptr_ty, IP_TAG_TYPE_POINTER) ? ipPtrChild(sema->ip, opv_ptr_ty) : IP_INDEX_NONE; if (opv_child != IP_INDEX_NONE && ztTypeClass(sema->ip, opv_child) == ZT_CLASS_OPV) { if (semaIsAirInstRef(store_val_val)) semaCoerceExtraRuntime(sema->zcu, store_val_val, semaResolveRefType(store_val_ref, sema->inst_types), opv_child, sema->insts, block, sema->air_extra, "store_node: runtime operand coercion"); else semaCoerceComptime(sema->zcu, sema->ip, store_val_val, opv_child); return; } // prepareComptimePtrStore's `.comptime_field` strategy holds // through every field, element and payload hop. if (store_ptr_val != INST_VALUE_UNSET && ipIsPtrComptimeField( sema->ip, ipPtrDerivedRoot(sema->ip, store_ptr_val))) { if (semaIsAirInstRef(store_val_val)) COMPILE_ERROR_UPSTREAM("value stored in comptime field " "must be comptime-known"); // storeComptimePtr's `.comptime_field` strategy: `"just // perform a load of the field and see if the store value // matches"` -- the load through this pointer, an element or // field of the default as much as all of it. uint32_t store_child; uint32_t expected = pointerDeref(sema->zcu, store_ptr_val, &store_child); if (expected == IP_INDEX_NONE) UNIMPLEMENTED("store_node: comptime-field load"); uint32_t coerced = semaCoerceComptime( sema->zcu, sema->ip, store_val_val, store_child); if (coerced != expected) COMPILE_ERROR_UPSTREAM("value stored in comptime field " "does not match its default"); return; } if (semaIsComptimeMutablePtr(sema->ip, store_ptr_val)) { // storePtrVal -> storeComptimePtr into a comptime var (the // inline-for counter, an element or field of one): no AIR. if (semaIsAirInstRef(store_val_val)) UNIMPLEMENTED("store_node: runtime value to comptime cell"); semaStorePtr2Comptime(sema->zcu, store_ptr_val, store_val_val); return; } uint32_t store_ptr_air; if (semaIsAirInstRef(store_ptr_val)) { store_ptr_air = store_ptr_val; } else if (store_ptr_val != INST_VALUE_UNSET && ipIsPtrInt(sema->ip, store_ptr_val)) { // storePtr2: a comptime pointer that is not comptime-mutable // ("if (ptr_val.isComptimeMutablePtr(zcu)) ... else break // :rs operand_src") is a runtime store; an integer address // (`@ptrFromInt`) is one. store_ptr_air = store_ptr_val; } else { // A direct mutable global address is a comptime ptr_nav AIR // ref. Stock storePtr2 emits it unchanged as the lhs of AIR // `store`; the interpreter resolves that IP value to the // Nav's persistent global address. Validate the whole shape // so const/extern/TLS pointers cannot be mistaken for // writable global storage. semaRuntimeDataPtrNav(sema->ip, store_ptr_val, true, NULL, NULL); store_ptr_air = store_ptr_val; } uint32_t store_val_air; if (semaIsAirInstRef(store_val_val)) { // `Sema.storePtr2` (src/Sema.zig): "const operand = try // sema.coerce(block, elem_ty, uncasted_operand, operand_src);" // -- one coercion, run for every operand, not a leg list for // the shapes that happen to need AIR. It is the same // `coerceExtra` the comptime frame and `as_node` ask, so the // attribute gates it carries (const, volatile, allowzero, // address space, alignment, sentinel) apply here too: without // it `var q: *u32 = p;` for `p: *const u32` silently dropped // const, and every other attribute with it. uint32_t sp_ty = semaResolveRefType(store_ptr_ref, sema->inst_types); uint32_t sv_ty = semaResolveRefType(store_val_ref, sema->inst_types); if (sp_ty != IP_INDEX_NONE && sema->ip->item_tags[sp_ty] == IP_TAG_TYPE_POINTER && sv_ty != IP_INDEX_NONE) store_val_air = semaCoerceExtraRuntime(sema->zcu, store_val_val, sv_ty, sema->ip->extra[sema->ip->item_datas[sp_ty]], sema->insts, block, sema->air_extra, "store_node: runtime operand coercion"); else store_val_air = store_val_val; } else { // Comptime value: coerce to ptr's pointee type. uint32_t store_ptr_ty = semaResolveRefType(store_ptr_ref, sema->inst_types); // First word of TypePointer extra payload = child (elem type). uint32_t store_elem_ty = sema->ip->extra[sema->ip->item_datas[store_ptr_ty]]; store_val_air = semaCoerceComptime( sema->zcu, sema->ip, store_val_val, store_elem_ty); } AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = store_ptr_air; // bin_op.lhs = ptr Ref data.pair.b = store_val_air; // bin_op.rhs = val Ref uint32_t store_air_idx = semaAirInstAppend(sema->insts, AIR_INST_STORE, data); u32ListAppend(block, store_air_idx); // store is a void statement; inst_values[inst] not set. } // Sema.zig:zirStr // Sema.zirStr -> addStrLit: intern a string literal `"..."` as a comptime // `*const [N:0]u8` uav pointer. ZIR `str { start, len }` indexes the ZIR // string_bytes. Interns (in upstream order): // 1. the bytes into the IP string table (ip.getOrPutString), // 2. `[N:0]u8` array type (sentinel = zero_u8), // 3. the `bytes` aggregate value over that array type, // 4. `*const [N:0]u8` pointer type (flags 0xFFFF01FC), // 5. the `ptr`/`.uav` value (uavValue) -> the result Ref. static void zirStr(SemaCtx* sema, uint32_t inst) { uint32_t s_start = sema->zir->inst_datas[inst].str.start; uint32_t s_len = sema->zir->inst_datas[inst].str.len; uint32_t bytes_str = ipGetOrPutString( sema->ip, zirNullTerminatedString(sema->zir, s_start), s_len); uint32_t arr_ty = ipGetArrayType(sema->ip, s_len, ZIR_REF_U8_TYPE, ZIR_REF_ZERO_U8); // Sema.addStrLit interns `.aggregate` with `.bytes` storage; // InternPool.get canonicalizes an all-equal (or len<=1) byte run // to a `repeated` value, else plain `bytes`. uint32_t bytes_val = ipInternBytesAggregate(sema->ip, arr_ty, bytes_str, s_len); uint32_t ptr_ty = ipGetPtrType(sema->ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t uav = ipGetPtrUav(sema->ip, ptr_ty, bytes_val); sema->inst_values[inst] = uav; // comptime `*const [N:0]u8` value sema->inst_types[inst] = ptr_ty; } // Sema.zig:zirInt // A `zir int` produces a `comptime_int` value. Mirrors Sema.zirInt -> // pt.intValue -> ip.get(.int): intern the comptime_int literal. For 0/1/-1 // this dedups to a static preintern (no new IP item); any other magnitude // interns a dynamic `int_comptime_int_u32` (the first IP entry the function // contributes, e.g. entry 176 for a `return 42`). The interned index is the // value, as Sema.zirInt returns `pt.intRef(.comptime_int, int)`. static void zirInt(SemaCtx* sema, uint32_t inst) { uint64_t int_val = sema->zir->inst_datas[inst].int_val; if (int_val >= (1ULL << 63)) { // Bit 63 set (a u64 literal >= 2^63, e.g. Wyhash's `secret` // constants like 0xa0761d6478bd642f). This is a magnitude // test on a raw ZIR literal, not a carrier test: the operand // comes straight from `zir->inst_datas[]` and never passed // through `inst_values[]`, so it is in a different value space // from every resolved value. sema->inst_values[inst] = ipGetIntComptimeIntU64(sema->ip, int_val); sema->inst_types[inst] = ZIR_REF_COMPTIME_INT_TYPE; return; } // Preserved asymmetry: only the >= 2^63 branch above // records an inst_types[] entry. A `zir int` below 2^63 stays // untyped, exactly as it was while it carried CT_INT_TAG; // semaBinReadComptimeInt's IP_INDEX_NONE arm is what reads the // type back (see its comment). sema->inst_values[inst] = ipGetIntComptimeIntU64(sema->ip, int_val); } // Sema.zig:zirIntBig // Sema.zirIntBig: read the positive little-endian limb payload from ZIR // string_bytes and intern it as a comptime_int. static void zirIntBig(SemaCtx* sema, uint32_t inst) { const Zir* zir = sema->zir; uint32_t limbs_len = zir->inst_datas[inst].str.len; uint32_t start = zir->inst_datas[inst].str.start; uint64_t* limbs = zig1Malloc(limbs_len * sizeof(uint64_t)); const uint8_t* bytes = zir->string_bytes + start; for (uint32_t limb_i = 0; limb_i < limbs_len; limb_i++) { uint64_t limb = 0; for (uint32_t byte_i = 0; byte_i < 8; byte_i++) limb |= ((uint64_t)bytes[limb_i * 8u + byte_i]) << (byte_i * 8u); limbs[limb_i] = limb; } sema->inst_values[inst] = ipGetInt(sema->ip, ZIR_REF_COMPTIME_INT_TYPE, (BigIntConst) { limbs, limbs_len, true }); free(limbs); sema->inst_types[inst] = ZIR_REF_COMPTIME_INT_TYPE; } // Sema.zig:zirFloat // Sema.zirFloat: intern the literal as a comptime_float value. static void zirFloat(SemaCtx* sema, uint32_t inst) { sema->inst_values[inst] = ipGetFloatComptimeFloatFromDouble( sema->ip, sema->zir->inst_datas[inst].float_val); sema->inst_types[inst] = ZIR_REF_COMPTIME_FLOAT_TYPE; } // Sema.zig:zirFloat128, Air.zig:internedToRef // `"const number = extra.get(); return Air.internedToRef((try // sema.pt.floatValue(.comptime_float, number)).toIntern());"`. The ZIR // payload's four pieces are already in the low-word-first order zig1 stores // for IP_TAG_FLOAT_COMPTIME_FLOAT. static void zirFloat128(SemaCtx* sema, uint32_t inst) { ZirInstFloat128 extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &extra); uint32_t words[4] = { extra.piece0, extra.piece1, extra.piece2, extra.piece3 }; sema->inst_values[inst] = ipGetFloatComptimeFloatFromWords(sema->ip, words); sema->inst_types[inst] = ZIR_REF_COMPTIME_FLOAT_TYPE; } // Sema.zig:zirCompileError // Sema.zirCompileError: `"return sema.fail(block, src, \"{s}\", .{msg});"`. In // a runtime block it may sit behind a condition upstream folds and zig1 does // not, so only a comptime block reports it. ZIG1_ATTRIBUTE((noreturn)) static void zirCompileError(SemaCtx* sema, uint32_t inst) { if (!semaBlockIsComptime(sema->ictx)) UNIMPLEMENTED( "compile_error: in a runtime block upstream may fold away"); uint32_t ce_operand = semaResolveRef( sema->zir->inst_datas[inst].un_node.operand, sema->inst_values); if (semaIsAirInstRef(ce_operand)) COMPILE_ERROR_UPSTREAM("unable to resolve comptime value"); char ce_buf[4096]; uint32_t ce_len = 0; ctFieldNamedNameString( sema->ip, ce_operand, ce_buf, sizeof(ce_buf), &ce_len); zig1CompileError(ce_buf); } // Sema.zig:zirPanic static void zirPanic(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t msg_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t msg_val = semaResolveRef(msg_ref, sema->inst_values); if (semaIsAirInstRef(msg_val)) { if (semaBlockIsComptime(sema->ictx)) COMPILE_ERROR_UPSTREAM("panic: runtime message in comptime panic"); semaCoerceExtraRuntime(sema->zcu, msg_val, semaResolveRefType(msg_ref, sema->inst_types), ZIR_REF_SLICE_CONST_U8_TYPE, sema->insts, block, sema->air_extra, "panic: message not coercible to []const u8"); } else { semaCoerceComptime( sema->zcu, sema->ip, msg_val, ZIR_REF_SLICE_CONST_U8_TYPE); } if (semaBlockIsComptime(sema->ictx)) COMPILE_ERROR_UPSTREAM("panic: encountered @panic at comptime"); // Runtime @panic on a backend without panic_fn support follows the // same path as @trap in stock Sema: emit a noreturn trap no-op. // zig1 has no panic handler call support, so this is the emitted noreturn. if (!sema->ictx->branch_hint_set) { sema->ictx->branch_hint_set = true; sema->ictx->branch_hint = BRANCH_HINT_COLD; } AirInstData trap_data; memset(&trap_data, 0, sizeof(trap_data)); uint32_t trap_idx = semaAirInstAppend(sema->insts, AIR_INST_TRAP, trap_data); u32ListAppend(block, trap_idx); return; } // Sema.zig:zirTrap static void zirTrap(SemaCtx* sema, U32List* block) { if (semaBlockIsComptime(sema->ictx)) COMPILE_ERROR_UPSTREAM("trap: encountered @trap at comptime"); // Runtime @trap is a noreturn trap no-op. Unlike @panic, it does // not set an implicit cold branch hint. AirInstData trap_data; memset(&trap_data, 0, sizeof(trap_data)); uint32_t trap_idx = semaAirInstAppend(sema->insts, AIR_INST_TRAP, trap_data); u32ListAppend(block, trap_idx); return; } // Sema.zig:zirLoop // Sema.zirLoop: creates an infinite loop AIR construct. Pre-reserves two // slots: block_inst (outer AIR block, target for `break` to exit the loop) and // loop_inst (the AIR loop body). ZIR `break` instructions that target this // loop's ZIR inst are mapped to the AIR block_inst. The loop body is analyzed // and an implicit AIR `repeat` is appended if it doesn't terminate with // noreturn. Finally, the outer block_inst is resolved similarly to zirBlock // (elide/noreturn/non-noreturn). // Returns whether the body ends here (noreturn or a comptime break). static bool zirLoop(SemaCtx* sema, U32List* block, uint32_t inst) { AirInstData loop_blk_data; memset(&loop_blk_data, 0, sizeof(loop_blk_data)); uint32_t loop_block_air_idx = semaAirInstAppend(sema->insts, AIR_INST_BLOCK, loop_blk_data); AirInstData loop_inst_data; memset(&loop_inst_data, 0, sizeof(loop_inst_data)); loop_inst_data.pair.a = ZIR_REF_NORETURN_TYPE; uint32_t loop_inst_air_idx = semaAirInstAppend(sema->insts, AIR_INST_LOOP, loop_inst_data); // ZIR breaks targeting this inst → AIR br(loop_block_air_idx). sema->inst_values[inst] = AIR_REF_INST_BASE + loop_block_air_idx; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; // Analyze the ZIR loop body (contains cond_block + ZIR_REPEAT). uint32_t lpp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBlock extra; uint32_t extra_end = zirExtraData(sema->zir, lpp, &extra); uint32_t loop_body_len = extra.body_len; const uint32_t* loop_body = zirBodySlice(sema->zir, extra_end); U32List loop_sub_block = { 0 }; SemaBlock loop_blk; semaBlockPush(sema->ictx, &loop_blk, true, inst, UINT32_MAX); analyzeBodyInner(sema, &loop_sub_block, loop_body, loop_body_len); semaBlockPop(sema->ictx, &loop_blk); // Comptime control flow crossing a runtime loop. Upstream's // zirLoop lets it through — `"// Use `analyzeBodyInner` directly // to push any comptime control flow up the stack."` — abandoning // the half-built AIR loop as the error unwinds. zig1 has no // unwinding: returning here would leave this loop's reserved AIR // `loop` instruction with an unwritten payload in a list the // interpreter may still walk, so the shape is UNIMPLEMENTED rather than // half-modeled. (The runtime `block` sibling above is ported.) if (sema->ictx->brk) UNIMPLEMENTED("loop: comptime break escaped into a runtime loop"); // Check if loop body ends with noreturn. If not, append an AIR // `repeat` to keep the loop running. Mirrors zirLoop detecting // that analyzeBodyInner returned due to ZIR_INST_REPEAT. if (!semaAirBodyEndsNoReturn(sema->insts, &loop_sub_block)) { AirInstData rep_data; memset(&rep_data, 0, sizeof(rep_data)); rep_data.pair.a = loop_inst_air_idx; // repeat.loop_inst uint32_t rep_idx = semaAirInstAppend(sema->insts, AIR_INST_REPEAT, rep_data); u32ListAppend(&loop_sub_block, rep_idx); } // Build loop_inst extra: [body_len, body_insts...]. uint32_t loop_extra_idx = sema->air_extra->len; u32ListAppend(sema->air_extra, loop_sub_block.len); for (uint32_t li = 0; li < loop_sub_block.len; li++) u32ListAppend(sema->air_extra, loop_sub_block.items[li]); free(loop_sub_block.items); sema->insts->datas[loop_inst_air_idx].pair.b = loop_extra_idx; // Resolve outer block (loop_block_air_idx): the loop body's break // instructions target loop_block_air_idx. Build child_block = // [loop_inst_air_idx] and resolve exactly as zirBlock does. U32List child_block = { 0 }; u32ListAppend(&child_block, loop_inst_air_idx); // Check elide: last in child_block is loop_inst (not a BR). // Check noreturn: any AIR BR targeting loop_block_air_idx? bool loop_outer_noreturn = true; for (uint32_t k = loop_block_air_idx + 1; k < sema->insts->len; k++) { if (sema->insts->tags[k] == AIR_INST_BR && sema->insts->datas[k].pair.a == loop_block_air_idx) { loop_outer_noreturn = false; break; } } if (loop_outer_noreturn) { // Infinite loop with no break: promote child_block to outer. for (uint32_t ci = 0; ci < child_block.len; ci++) u32ListAppend(block, child_block.items[ci]); free(child_block.items); return true; // noreturn; terminate outer body analysis } // Non-noreturn: create proper outer block with body=[loop_inst]. u32ListAppend(block, loop_block_air_idx); // resolveAnalyzedBlock (src/Sema.zig): One peer type over // every recorded break result. A loop expression can yield a // runtime value (`while (true) { break x; }`), and downstream // `ref`/field access needs the real result type, not `void`. uint32_t lblock_extra_idx = sema->air_extra->len; u32ListAppend(sema->air_extra, child_block.len); for (uint32_t ci = 0; ci < child_block.len; ci++) u32ListAppend(sema->air_extra, child_block.items[ci]); free(child_block.items); sema->inst_types[inst] = semaBlockMergesFinish(sema->zcu, sema->ictx, sema->insts, sema->air_extra, loop_block_air_idx, lblock_extra_idx, &sema->inst_values[inst]); return false; } // Sema.zig:zirBlock // Sema.zirBlock -> resolveBlockBody -> resolveAnalyzedBlock. Pre-reserve an // AIR `block` slot immediately so nested ZIR `break` instructions can // reference it by index before the body is analyzed. blk_air_idx is not yet // added to the outer block list; we decide after analysis whether it is // noreturn. // Returns whether the body ends here (noreturn or a comptime break). static bool zirBlock(SemaCtx* sema, U32List* block, uint32_t inst) { AirInstData blk_data; memset(&blk_data, 0, sizeof(blk_data)); uint32_t blk_air_idx = semaAirInstAppend(sema->insts, AIR_INST_BLOCK, blk_data); sema->inst_values[inst] = AIR_REF_INST_BASE + blk_air_idx; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBlock extra; uint32_t extra_end = zirExtraData(sema->zir, pp, &extra); uint32_t blk_body_len = extra.body_len; const uint32_t* blk_body = zirBodySlice(sema->zir, extra_end); // Analyze the block body into a fresh sub-block list. U32List sub_block = { 0 }; SemaBlock blk_blk; semaBlockPush(sema->ictx, &blk_blk, false, inst, UINT32_MAX); analyzeBodyInner(sema, &sub_block, blk_body, blk_body_len); semaBlockPop(sema->ictx, &blk_blk); // Comptime control flow crossed this runtime block: // Sema.resolveBlockBody's `"error.ComptimeBreak => {"` arm. Only // the inline handlers consume `ictx->brk`, so without this the // break would be abandoned — the target block never yields its // operand and the value it should have produced is read later as // an unset InternPool index. zig1 always takes upstream's // `"// We can copy instructions directly to the parent block."` // path: `need_debug_scope` is a debug-scope demand, and these // bodies are analyzed with debug info stripped, so the alternate // arm (wrapping the child instructions in `merges.block_inst`) // never applies. Then either this block is the break target // (`"const breaks_to_body = extra.block_inst == body_inst;"` -> // `"return sema.resolveInst(break_data.operand);"`) or the break // keeps propagating (`"return error.ComptimeBreak;"`). The // pre-reserved AIR block slot is left unused exactly as upstream // leaves `merges.block_inst` unused on this path. if (sema->ictx->brk) { for (uint32_t bi = 0; bi < sub_block.len; bi++) u32ListAppend(block, sub_block.items[bi]); free(sub_block.items); if (sema->ictx->brk_target == inst) { sema->ictx->brk = false; uint32_t cb_op = sema->ictx->brk_operand; sema->inst_values[inst] = semaResolveRef(cb_op, sema->inst_values); sema->inst_types[inst] = semaResolveRefType(cb_op, sema->inst_types); return false; } return true; // propagate to the enclosing handler } if (resolveAnalyzedBlock(sema->zcu, sema->ictx, sema->insts, sema->air_extra, block, sub_block, blk_air_idx, &sema->inst_values[inst], &sema->inst_types[inst])) return true; return false; } // Sema.zig:zirExport // Sema.zirExport is context-blind: `"const ptr_val = try // sema.resolveConstDefinedValue(block, ptr_src, ptr, ...)"` and `"const // options = try sema.resolveExportOptions(...)"` need comptime-known operands // in a runtime body too, and no AIR is emitted (analyzeBodyInner's // `.@"export"` arm). static void zirExport(SemaCtx* sema, uint32_t inst) { uint32_t xp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstExport extra; zirExtraData(sema->zir, xp, &extra); uint32_t x_exported = semaResolveRef(extra.exported, sema->inst_values); uint32_t x_options = semaResolveRef(extra.options, sema->inst_values); if (semaIsAirInstRef(x_exported) || semaIsAirInstRef(x_options)) COMPILE_ERROR_UPSTREAM("@export: unable to resolve comptime value"); semaExport(sema->zcu, x_exported, x_options); } // Sema.zig:zirSetFloatMode // Sema.zirSetFloatMode: `@setFloatMode(.strict)` changes Block.float_mode and // emits no AIR. zig1 does not model float-mode-sensitive lowering today, but // the operand must still resolve to a comptime std.lang.FloatMode value. static void zirSetFloatMode(SemaCtx* sema, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; ZirInstUnNode extra; zirExtraData(sema->zir, ext_operand, &extra); uint32_t sfm_val = semaResolveRef(extra.operand, sema->inst_values); if (semaIsAirInstRef(sfm_val)) UNIMPLEMENTED("set_float_mode: runtime mode value"); resolveStdLangEnum(sema->zcu, sfm_val, "FloatMode"); } // Sema.zig:zirSetRuntimeSafety // Sema.zirSetRuntimeSafety resolves a comptime bool and stores it on the // current block as `want_safety`. zig1 does not model runtime safety-check // emission yet, so this is a validation-only directive, matching ctEvalBody's // no-effect treatment. static void zirSetRuntimeSafety(SemaCtx* sema, uint32_t inst) { uint32_t operand_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t operand_val = semaResolveRef(operand_ref, sema->inst_values); if (semaIsAirInstRef(operand_val)) UNIMPLEMENTED("set_runtime_safety: runtime operand"); uint32_t safety = semaCoerceComptime( sema->zcu, sema->ip, operand_val, ZIR_REF_BOOL_TYPE); if (safety != ZIR_REF_BOOL_TRUE && safety != ZIR_REF_BOOL_FALSE) UNIMPLEMENTED("set_runtime_safety: non-bool operand"); } // Sema.zig:zirBreak // Sema.zirBreak -> addBr: emit AIR `br{block_inst, operand}` to the AIR block // of the break's target. static void zirBreak(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t operand_ref = sema->zir->inst_datas[inst].break_data.operand; ZirInstBreak extra; zirExtraData(sema->zir, sema->zir->inst_datas[inst].break_data.payload_index, &extra); uint32_t block_zir_inst = extra.block_inst; // Look up the corresponding AIR block instruction index. While a // union-switch payload-capture prong is being analyzed, the switch inst // is temporarily rebound (in inst_values) to the capture value, so // recover its real merge block from the InlineCtx. uint32_t block_air_idx; const SwitchCapture* brk_cap = semaSwitchCaptureFind(sema->ictx, block_zir_inst); if (brk_cap != NULL) { block_air_idx = brk_cap->merge_air; } else { uint32_t block_val = sema->inst_values[block_zir_inst]; if (!semaIsAirInstRef(block_val)) UNIMPLEMENTED("break: target ZIR block not yet analyzed"); block_air_idx = (block_val - AIR_REF_INST_BASE); } addRuntimeBreakToBlock(sema->ip, sema->ictx, sema->insts, block, sema->inst_values, sema->inst_types, semaBlockFindLabel(sema->ictx, block_zir_inst), block_air_idx, operand_ref); } // Sema.zig:zirSwitchContinue // The operand coerced to the switch's raw operand type, as a placeholder `br` // to the switch's merge block, which analyzeSwitchBlock replaces with the // dispatch once the loop_switch_br exists. static void zirSwitchContinue(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t operand_ref = sema->zir->inst_datas[inst].break_data.operand; ZirInstBreak extra; zirExtraData(sema->zir, sema->zir->inst_datas[inst].break_data.payload_index, &extra); if (!sema->ictx->swc_active || extra.block_inst != sema->ictx->swc_switch_inst || sema->ictx->swc_placeholders == NULL) UNIMPLEMENTED("switch_continue: target switch not active"); uint32_t operand_val = semaResolveRef(operand_ref, sema->inst_values); uint32_t raw_ty = sema->ictx->swc_raw_ty; uint32_t operand; if (semaIsAirInstRef(operand_val)) { uint32_t op_ty = semaAirRefTypeForBlockCoerce(sema->ip, sema->insts, operand_val); operand = op_ty == raw_ty ? operand_val : semaCoerceExtraRuntime(sema->zcu, operand_val, op_ty, raw_ty, sema->insts, block, sema->air_extra, "switch_continue: unsupported runtime " "operand coercion"); } else { operand = semaCoerceComptime(sema->zcu, sema->ip, operand_val, raw_ty); } uint32_t br = semaEmitTyOp( sema->insts, block, AIR_INST_BR, sema->ictx->swc_merge_air, operand); u32ListAppend(sema->ictx->swc_placeholders, br - AIR_REF_INST_BASE); } // Sema.zig:zirDeclRef // Sema.zirDeclRef -> analyzeNavRef -> analyzeNavRefInner(is_ref= true): look // up the named decl through the scope chain, resolve the Nav's type // (zptEnsureNavValUpToDate yields both its initializer and its type), then // form the declaration pointer. Its const bit is the Nav's source storage // class (`var` is mutable), matching analyzeNavRefInner's `nav_resolved.const` // pointer flag. Bind the decl_ref inst to that Pointer (unlike decl_val, which // binds to the loaded value). Comptime-known: no AIR is emitted. Used by `E.b` // (decl_ref("E") -> *const type, then field_ptr_load(.., "b")). static void zirDeclRef(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t rname_off = sema->zir->inst_datas[inst].str_tok.start; const char* rname = zirNullTerminatedString(sema->zir, rname_off); // Resolve through the active analysis-namespace scope chain (the // pending callee's container namespace up its parents), then the // namespace of the file owning this body's ZIR — identical to the // DECL_VAL handler below. This lets a method body take the address // of a sibling container member (a const declared in the method's // container, not the file root). With no scope override set this is // exactly the file-root lookup. uint32_t rname_str = ipGetOrPutString(sema->ip, rname, (uint32_t)strlen(rname)); uint32_t rnav = semaScopedDeclLookup(sema->ip, sema->zcu, sema->zir, rname_str); // lookupIdentifier: `"unreachable; // AstGen detects use of undeclared // identifiers."` if (rnav == IP_INDEX_NONE) UNREACHABLE("decl_ref: declaration not in scope"); const IpNav* rnav_data0 = ipGetNav(sema->ip, rnav); if (rnav_data0->is_threadlocal) { // Sema.analyzeNavRefInner(is_ref=true) also uses // runtime_nav_ptr for threadlocal addresses. Unlike the // DECL_VAL path below, no load is emitted here; stores and // field/element projections consume the runtime pointer. if (rnav_data0->is_extern) UNIMPLEMENTED("decl_ref: extern threadlocal Nav pointer"); if (rnav_data0->is_const) UNIMPLEMENTED("decl_ref: const threadlocal Nav pointer"); uint32_t rdecl_ty = navDeclRefTypeOnlyResolved(sema->zcu, rnav); const IpNav* rnav_data = ipGetNav(sema->ip, rnav); uint32_t rptr_ty = ipGetPtrType( sema->ip, rdecl_ty, semaNavDirectPtrFlags(rnav_data)); AirInstData rp_data; memset(&rp_data, 0, sizeof(rp_data)); rp_data.pair.a = rptr_ty; // ty_nav.ty rp_data.pair.b = rnav; // ty_nav.nav uint32_t rp_idx = semaAirInstAppend( sema->insts, AIR_INST_RUNTIME_NAV_PTR, rp_data); u32ListAppend(block, rp_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + rp_idx; sema->inst_types[inst] = rptr_ty; return; } uint32_t rptr_ty = IP_INDEX_NONE; uint32_t rptr_nav = navDeclRefAddressResolved(sema->zcu, rnav, &rptr_ty); sema->inst_values[inst] = rptr_nav; // comptime Nav address sema->inst_types[inst] = rptr_ty; } // Sema.zig:analyzeNavVal // analyzeNavRefInner(is_ref= false): resolve the Nav's initializer // (zptEnsureNavValUpToDate: a function decl, e.g. a // call target `helper`, or a named enum-type decl, // e.g. `E` in `E.b`) then forms the declaration pointer. A const load folds to // its comptime-known value. A mutable Nav is runtime storage, so emit AIR // `load` from its ptr_nav; the Nav's resolved_value remains only the // initializer. static uint32_t analyzeNavVal( SemaCtx* sema, U32List* block, uint32_t nav, uint32_t* out_ty) { uint32_t decl_ty = IP_INDEX_NONE; // analyzeNavVal loads the pointer analyzeNavRefInner built, and // that pointer names the function's owner Nav -- so the value and // its type come from the owner, not from an alias's declared // (possibly coerced) type. uint32_t decl_val = navAnalyzeVal(sema->zcu, nav, &decl_ty); const IpNav* nav_data = ipGetNav(sema->ip, nav); const bool nav_is_const = nav_data->is_const; const bool nav_is_extern = nav_data->is_extern; const bool nav_is_threadlocal = nav_data->is_threadlocal; *out_ty = decl_ty; if (nav_is_extern && decl_ty < sema->ip->items_len && ztZigTypeTag(sema->ip, decl_ty) == ZIG_TYPE_TAG_FN) { // Loading an extern function declaration produces its // comptime-known function value. The normal call tail lowers // supported non-generic imports without queuing a source body. // An extern variable or constant is loaded below. if (decl_val >= sema->ip->items_len || sema->ip->item_tags[decl_val] != IP_TAG_EXTERN) UNIMPLEMENTED("decl_val: extern Nav load"); return decl_val; } if (nav_is_threadlocal && nav_is_const) UNIMPLEMENTED("decl_val: const threadlocal Nav"); uint32_t loaded_value; if (semaEmitMutableNavLoad( sema->zcu, nav, sema->insts, block, &loaded_value, out_ty)) return loaded_value; return decl_val; // folded const value } // Sema.zig:zirDeclVal // Sema.zirDeclVal: look up the named decl in the enclosing namespace and // analyzeNavVal it. static void zirDeclVal(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t name_off = sema->zir->inst_datas[inst].str_tok.start; const char* name = zirNullTerminatedString(sema->zir, name_off); // Resolve through the active analysis-namespace scope chain (the // pending callee's container namespace up its parents — so a // method body naming a sibling member, `growCapacity` in the // ArrayList instance's `ensureTotalCapacity`, resolves), then the // namespace of the file owning this body's ZIR (a callee imported // from a sibling file resolves its siblings in its own file's // namespace, not the root file's). With no override set this is // exactly the previous file-root-only lookup. uint32_t name_str = ipGetOrPutString(sema->ip, name, (uint32_t)strlen(name)); uint32_t nav = semaScopedDeclLookup(sema->ip, sema->zcu, sema->zir, name_str); // As zirDeclRef: AstGen resolved the identifier. if (nav == IP_INDEX_NONE) UNREACHABLE("decl_val: declaration not in scope"); sema->inst_values[inst] = analyzeNavVal(sema, block, nav, &sema->inst_types[inst]); } // Sema.zig:analyzeUnreachable // `block.addNoOp(.unreach)` as `inst`'s noreturn result, which ends the // body (`return .unreachable_value`). static void semaUnreachableResult( SemaCtx* sema, U32List* block, uint32_t inst) { sema->inst_values[inst] = semaEmitTyOp(sema->insts, block, AIR_INST_UNREACH, 0, 0); sema->inst_types[inst] = ZIR_REF_NORETURN_TYPE; } // (part of) Sema.zig:analyzeCall // `switch (actual_ret_ty.classify(zcu))` after the runtime call: a return // type with no possible value is followed by `unreach`. static void semaAddCallResult(SemaCtx* sema, U32List* block, uint32_t inst, uint32_t call_idx, uint32_t ret_ty) { u32ListAppend(block, call_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + call_idx; sema->inst_types[inst] = ret_ty; if (ztTypeClass(sema->ip, ret_ty) == ZT_CLASS_NPV) semaUnreachableResult(sema, block, inst); } // Sema.zig:finishFieldCallBind static ResolvedFieldCallee finishFieldCallBind(SemaCtx* sema, U32List* block, uint32_t ptr_ty, uint32_t container_ty, uint32_t field_index, uint32_t object_ptr) { ResolvedFieldCallee callee = { 0 }; uint32_t ptr_field_ty = ztFieldPtrType(sema->zcu, ptr_ty, field_index); uint32_t default_val = semaStructFieldComptimeValue(sema->zcu, container_ty, field_index); if (default_val != IP_INDEX_NONE) { callee.func_inst = default_val; callee.func_ty = ipTypeOf(sema->ip, default_val); return callee; } uint32_t pointer = semaIsAirInstRef(object_ptr) ? semaEmitStructFieldPtr(object_ptr, field_index, ptr_field_ty, sema->insts, block, sema->air_extra) : valPtrField(sema->zcu, object_ptr, field_index); callee.func_inst = analyzeLoad( sema->zcu, sema->insts, block, pointer, ptr_field_ty, &callee.func_ty); return callee; } // Sema.zig:fieldCallBind // `raw_ptr` may also be a folded receiver with no pointer (zirRef of a // comptime value hands the value): a namespace type goes straight to the // `.type` arm, an aggregate gets the pointer its `ref` would have made. static ResolvedFieldCallee fieldCallBind(SemaCtx* sema, U32List* block, uint32_t raw_ptr, uint32_t raw_ptr_ty, uint32_t field_name) { InternPool* ip = sema->ip; ResolvedFieldCallee callee = { 0 }; uint32_t namespace_ty = IP_INDEX_NONE; uint32_t concrete_ty = IP_INDEX_NONE; uint32_t ptr_ty = raw_ptr_ty; uint32_t object_ptr = raw_ptr; if (!semaIsAirInstRef(raw_ptr) && ztIsNamespaceType(ip, raw_ptr)) { namespace_ty = raw_ptr; } else { uint32_t val_ty = semaIsAirInstRef(raw_ptr) ? IP_INDEX_NONE : ipTypeOf(ip, raw_ptr); if (val_ty < ip->items_len && (ipTagIsAnyStruct(ip->item_tags[val_ty]) || ztIsZigTypeTag(ip, val_ty, ZIG_TYPE_TAG_UNION))) { ptr_ty = ipGetPtrType(ip, val_ty, IP_PTR_FLAGS_CONST_ONE); object_ptr = ipGetPtrUav(ip, ptr_ty, raw_ptr); } if (ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) COMPILE_ERROR_UPSTREAM("field_call: expected single pointer"); // A comptime var's pointer may predate its inferred element type. uint32_t inner_ty = ipIsPtrComptimeAlloc(ip, object_ptr) ? mutableValueTypeOf(ip, &getComptimeAlloc( sema->zcu, ipPtrComptimeAllocIndex(ip, object_ptr)) ->val) : ip->extra[ip->item_datas[ptr_ty]]; // Optionally dereference a second pointer to get the concrete type. IpPtrInfo inner_info; bool is_double_ptr = ipPtrInfo(ip, inner_ty, &inner_info) == IP_PTRI_OK && inner_info.size == IP_PTR_SIZE_ONE; concrete_ty = is_double_ptr ? inner_info.child : inner_ty; ensureLayoutResolved(sema->zcu, concrete_ty); if (is_double_ptr) { uint32_t loaded_ty; object_ptr = analyzeLoad( sema->zcu, sema->insts, block, object_ptr, ptr_ty, &loaded_ty); ptr_ty = inner_ty; } // `"const namespace = try sema.analyzeLoad(...)"` if (concrete_ty == ZIR_REF_TYPE_TYPE) { uint32_t loaded_ty; namespace_ty = analyzeLoad( sema->zcu, sema->insts, block, object_ptr, ptr_ty, &loaded_ty); } } if (namespace_ty != IP_INDEX_NONE) { // `".direct = try sema.fieldVal(...)"`, namespaceLookupVal's decl. uint32_t ns = ipContainerNamespaceOrNone(ip, namespace_ty); uint32_t nav = ns == IP_NAMESPACE_NONE ? IP_INDEX_NONE : ipNamespaceLookup(ip, ns, field_name); if (nav == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM("field_call: no member in namespace"); callee.func_inst = analyzeNavVal(sema, block, nav, &callee.func_ty); return callee; } if (ipTagIsAnyStruct(ip->item_tags[concrete_ty])) { uint32_t field_index = ipStructFieldIndex(ip, concrete_ty, field_name); if (field_index != UINT32_MAX) return finishFieldCallBind( sema, block, ptr_ty, concrete_ty, field_index, object_ptr); } else if (ztIsTuple(ip, concrete_ty)) { uint32_t field_index = tupleFieldIndex(ip, concrete_ty, ipStringBytes(ip, field_name)); if (field_index != UINT32_MAX) return finishFieldCallBind( sema, block, ptr_ty, concrete_ty, field_index, object_ptr); } else if (ztIsZigTypeTag(ip, concrete_ty, ZIG_TYPE_TAG_UNION)) { uint32_t field_index = ipUnionFieldIndex(ip, concrete_ty, field_name); if (field_index != UINT32_MAX) { // unionFieldPtr, not initializing, in a body without safety: // its `tag:` block only interns `want_tag`. A comptime pointer // to runtime memory takes the runtime path (`orelse break :ct`). uint32_t field_ptr; uint32_t field_ptr_ty; bool auto_layout = !ipUnionIsPacked(ip, concrete_ty) && !ipUnionIsExtern(ip, concrete_ty); if (semaIsAirInstRef(object_ptr) || (auto_layout && pointerDeref(sema->zcu, object_ptr, NULL) == IP_INDEX_NONE)) { uint32_t tag_ty = ipEnsureGeneratedUnionTag(ip, concrete_ty); if (auto_layout && ztOnePossibleValue(ip, tag_ty) == IP_INDEX_NONE) ipEnumTagFromFieldName(ip, tag_ty, field_name); field_ptr_ty = ztFieldPtrType(sema->zcu, ptr_ty, field_index); field_ptr = semaEmitStructFieldPtr(object_ptr, field_index, field_ptr_ty, sema->insts, block, sema->air_extra); } else { field_ptr = semaUnionFieldPtrComptime( sema->zcu, object_ptr, concrete_ty, field_name); field_ptr_ty = ipTypeOf(ip, field_ptr); } callee.func_inst = analyzeLoad(sema->zcu, sema->insts, block, field_ptr, field_ptr_ty, &callee.func_ty); return callee; } } // If we get here, we need to look for a decl in the struct type instead. uint32_t ns = ipContainerNamespaceOrNone(ip, concrete_ty); uint32_t nav = ns == IP_NAMESPACE_NONE ? IP_INDEX_NONE : ipNamespaceLookup(ip, ns, field_name); if (nav == IP_INDEX_NONE) semaFailWithNoFieldOrMemberFn(sema->zcu, concrete_ty, field_name); callee.func_inst = analyzeNavVal(sema, block, nav, &callee.func_ty); if (!ztIsZigTypeTag(ip, callee.func_ty, ZIG_TYPE_TAG_FN) || ipFuncTypeParamsLen(ip, callee.func_ty) == 0) COMPILE_ERROR_UPSTREAM("field_call: not a member function"); // `S`, `?S` and `E!S` bind the loaded object, `*S`, `?*S` and `anytype` // the object pointer. uint32_t first_param_ty = ipFuncTypeParamType(ip, callee.func_ty, 0); uint32_t opt_child = ztIsZigTypeTag(ip, first_param_ty, ZIG_TYPE_TAG_OPTIONAL) ? ipOptionalChild(ip, first_param_ty) : IP_INDEX_NONE; IpPtrInfo first_ptr; bool by_value = first_param_ty == concrete_ty || opt_child == concrete_ty || (semaIsErrorUnionType(ip, first_param_ty) && ipErrorUnionPayload(ip, first_param_ty) == concrete_ty); bool by_ptr = first_param_ty == IP_INDEX_GENERIC_POISON_TYPE || (ipPtrInfo(ip, first_param_ty, &first_ptr) == IP_PTRI_OK && (first_ptr.size == IP_PTR_SIZE_ONE || first_ptr.size == IP_PTR_SIZE_C) && first_ptr.child == concrete_ty) || (opt_child != IP_INDEX_NONE && ipPtrInfo(ip, opt_child, &first_ptr) == IP_PTRI_OK && first_ptr.size == IP_PTR_SIZE_ONE && first_ptr.child == concrete_ty); if (!by_value && !by_ptr) COMPILE_ERROR_UPSTREAM("field_call: not a member function"); callee.is_method = true; callee.object_ptr = object_ptr; callee.arg0_ty = ptr_ty; callee.arg0_inst = by_value ? analyzeLoad(sema->zcu, sema->insts, block, object_ptr, ptr_ty, &callee.arg0_ty) : object_ptr; return callee; } // (part of) Sema.zig:analyzeCall // `"if (func_ty_info.cc == .auto) { ... ip.funcSetHasErrorTrace(io, // owner_func, true)"` of a runtime call. static void semaCallSetHasErrorTrace(Zcu* zcu, uint32_t func_ty) { InternPool* ip = &zcu->intern_pool; uint32_t owner = zcu->sema_branch->owner_func; if (owner != IP_INDEX_NONE && ipFuncTypeCcTag(ip, func_ty) == CC_TAG_AUTO) ip->extra[ip->item_datas[owner]] |= IP_FUNC_ANALYSIS_HAS_ERROR_TRACE; } // (part of) Sema.zig:analyzeCall // The `CallArgsInfo.analyzeArg` prologue: the call inst stands for the // parameter type (a `decl_literal` argument names it as a ref to this call), // then the argument body runs, in a comptime scope for `comptime_arg`. Returns // its value; `*out_ref` is its ZIR ref. static uint32_t semaCallArgValue(SemaCtx* sema, U32List* block, uint32_t inst, const uint32_t* arg_body, uint32_t arg_body_len, uint32_t param_ty, bool comptime_arg, uint32_t* out_ref) { sema->inst_values[inst] = param_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; if (arg_body_len > 1) { bool saved_ct_scope = sema->ictx->ct_scope; sema->ictx->ct_scope = saved_ct_scope || comptime_arg; analyzeBodyInner(sema, block, arg_body, arg_body_len - 1); sema->ictx->ct_scope = saved_ct_scope; } *out_ref = sema->zir->inst_datas[arg_body[arg_body_len - 1]].break_data.operand; return semaResolveRef(*out_ref, sema->inst_values); } // (part of) Sema.zig:analyzeCall // The comptime call's fold over its `n` coerced comptime arguments, the // result `inst`'s. static void semaFoldComptimeCall(SemaCtx* sema, uint32_t inst, uint32_t func, uint32_t func_ty, const uint32_t* args, uint32_t n, uint32_t ret_ty) { uint32_t body_func = semaFuncDeclBodyValue(sema->ip, func); if (body_func == IP_INDEX_NONE) UNIMPLEMENTED("call comptime: callee body is not a func_decl"); uint32_t res = ctAnalyzeComptimeCallTyped(sema->zcu, body_func, ipFuncDeclOwnerNav(sema->ip, body_func), func_ty, args, n, &ret_ty); sema->inst_values[inst] = semaFinalizeComptimeCallResult( sema->ip, res, ret_ty, "call comptime: unsupported result"); sema->inst_types[inst] = ret_ty; } // (part of) Sema.zig:analyzeCall // `is_comptime_call`: every argument is comptime-known // (`"block.comptime_reason = ..."` puts their bodies in a comptime scope) and // the call folds. static void semaAnalyzeComptimeCall(SemaCtx* sema, U32List* block, uint32_t inst, uint32_t func, uint32_t func_ty, const ResolvedFieldCallee* callee, uint32_t abase, uint32_t args_len) { uint32_t po = callee->is_method ? 1u : 0u; uint32_t* args = ARR_INIT(uint32_t, po + args_len + 1u); uint32_t body_func = semaFuncDeclBodyValue(sema->ip, func); if (body_func == IP_INDEX_NONE) UNIMPLEMENTED("call comptime: callee body is not a func_decl"); CtCallParamCtx pctx; ctCallParamCtxInit( sema->zcu, ipFuncDeclOwnerNav(sema->ip, body_func), func_ty, &pctx); if (callee->is_method) { args[0] = callee->arg0_inst; ctCallParamCtxBind(&pctx, 0, args[0]); } for (uint32_t ai = 0; ai < args_len; ai++) { const uint32_t* arg_body; uint32_t arg_body_len = semaCallArgBody(sema->zir, abase, args_len, ai, &arg_body); uint32_t param_ty = ctCallProvidedParamType(sema->zcu, func_ty, po + ai, &pctx); uint32_t op; uint32_t av = semaCallArgValue( sema, block, inst, arg_body, arg_body_len, param_ty, true, &op); if (!semaRefIsInterned(av)) UNIMPLEMENTED("call comptime: runtime argument"); args[po + ai] = semaCoerceCallArgumentValue(sema->zcu, av, param_ty); ctCallParamCtxBind(&pctx, po + ai, args[po + ai]); } ctCallParamCtxFree(&pctx); semaFoldComptimeCall(sema, inst, func, func_ty, args, po + args_len, ipFuncTypeReturnType(sema->ip, func_ty)); free(args); } // (part of) Sema.zig:analyzeCall // A comptime-known function pointer callee: `.ptr => pointerDerefExtra`. A // pointer naming a function Nav of exactly `fn_ty` calls the function; any // other is an indirect call through the pointer value (IP_INDEX_NONE). static uint32_t semaComptimeFnPtrTarget( SemaCtx* sema, uint32_t func, uint32_t fn_ty) { const InternPool* ip = sema->ip; IpPtrNav pn; if (ipDecodePtrNav(ip, func, &pn) == IP_PN_OK && pn.byte_offset == 0) { uint32_t target_ty = IP_INDEX_NONE; uint32_t target = zptEnsureNavValUpToDate(sema->zcu, pn.nav_index, &target_ty); if (target_ty == fn_ty) return target; if (ipItemTagIs(ip, target, IP_TAG_FUNC_DECL)) semaU32ListAppendUnique(sema->pending_navs, pn.nav_index); return IP_INDEX_NONE; } // Any other comptime pointer: `pointerDerefExtra`'s `.val`. uint32_t target = pointerDeref(sema->zcu, func, NULL); if (target != IP_INDEX_NONE && !ipItemTagIs(ip, target, IP_TAG_FUNC_DECL) && !ipItemTagIs(ip, target, IP_TAG_FUNC_INSTANCE) && !ipItemTagIs(ip, target, IP_TAG_EXTERN)) UNIMPLEMENTED( "call: pointerDerefExtra of a function pointer is not a function"); return target; } // Sema.zig:analyzeCall // Upstream's one body, by the kind of call: a runtime pointer callee is // semaAnalyzeIndirectCall, an inline call semaAnalyzeInlineCallArgs and // semaAnalyzeInlineCallDispatch, a comptime call semaAnalyzeComptimeCall, and // the rest semaAnalyzeRuntimeCall. static void analyzeCall(SemaCtx* sema, U32List* block, uint32_t inst, ResolvedFieldCallee callee, uint32_t abase, uint32_t args_len) { InternPool* ip = sema->ip; uint32_t func = callee.func_inst; uint32_t func_ty = callee.func_ty; IpPtrInfo fn_ptr; if (semaIsAirInstRef(func)) { if (ipPtrInfo(ip, func_ty, &fn_ptr) != IP_PTRI_OK || !ztIsZigTypeTag(ip, fn_ptr.child, ZIG_TYPE_TAG_FN)) COMPILE_ERROR_UPSTREAM("call: type is not a function"); semaCallSetHasErrorTrace(sema->zcu, fn_ptr.child); semaAnalyzeIndirectCall( sema, block, inst, func, fn_ptr.child, abase, args_len); return; } if (func >= ip->items_len) UNREACHABLE("call: callee value out of bounds"); if (ipPtrInfo(ip, ipTypeOf(ip, func), &fn_ptr) == IP_PTRI_OK && fn_ptr.size == IP_PTR_SIZE_ONE && ztIsZigTypeTag(ip, fn_ptr.child, ZIG_TYPE_TAG_FN)) { uint32_t target = semaComptimeFnPtrTarget(sema, func, fn_ptr.child); if (target == IP_INDEX_NONE) { semaCallSetHasErrorTrace(sema->zcu, fn_ptr.child); semaAnalyzeIndirectCall( sema, block, inst, func, fn_ptr.child, abase, args_len); return; } func = target; func_ty = fn_ptr.child; } if (!ztIsZigTypeTag(ip, func_ty, ZIG_TYPE_TAG_FN)) COMPILE_ERROR_UPSTREAM("call: type is not a function"); bool is_extern = ip->item_tags[func] == IP_TAG_EXTERN; if (is_extern && semaBlockIsComptime(sema->ictx)) UNIMPLEMENTED("call: comptime extern function"); bool inline_requested = !is_extern && ipFuncTypeIsInline(ip, func_ty); bool is_comptime_call = !is_extern && (semaBlockIsComptime(sema->ictx) || (!inline_requested && ztComptimeOnly( sema->zcu, ipFuncTypeReturnType(ip, func_ty)))); uint32_t arg0_param_ty = IP_INDEX_NONE; if (callee.is_method) { // The first `analyzeArg`: `*S` to `*const S`, `S` to `?S`. arg0_param_ty = ipFuncTypeParamType(ip, func_ty, 0); if (arg0_param_ty == IP_INDEX_GENERIC_POISON_TYPE) { arg0_param_ty = callee.arg0_ty; } else if (callee.arg0_ty != arg0_param_ty) { callee.arg0_inst = coerce(sema->zcu, sema->insts, block, sema->air_extra, arg0_param_ty, callee.arg0_inst, callee.arg0_ty); } // A by-value self loaded from a comptime-known alloc. if (is_comptime_call && semaIsAirInstRef(callee.arg0_inst)) { uint32_t known_ty = IP_INDEX_NONE; uint32_t known = semaResolveComptimeKnownAllocValue(sema->zcu, sema->insts, block, sema->air_extra, callee.object_ptr, UINT32_MAX, &known_ty, NULL); if (known == IP_INDEX_NONE || known_ty != arg0_param_ty) UNIMPLEMENTED("call comptime: runtime self"); callee.arg0_inst = known; } } if (inline_requested) { uint32_t po = callee.is_method ? 1u : 0u; uint32_t total = po + args_len; uint32_t* inline_args = ARR_INIT(uint32_t, total + 1u); uint32_t* inline_arg_types = ARR_INIT(uint32_t, total + 1u); // A coerced function calls the original's body. func = ipUnwrapCoercedFunc(ip, func); if (callee.is_method) { if ((ipFuncTypeComptimeBits(ip, func_ty) & 1u) && semaIsAirInstRef(callee.arg0_inst)) UNIMPLEMENTED( "analyzeCall: inline call - runtime comptime self"); inline_args[0] = callee.arg0_inst; inline_arg_types[0] = arg0_param_ty; } semaAnalyzeInlineCallArgs(sema, block, inst, func, func_ty, abase, args_len, po, inline_args, inline_arg_types); semaAnalyzeInlineCallDispatch(sema, block, inst, func, func_ty, inline_args, inline_arg_types, total, semaBlockIsComptime(sema->ictx)); free(inline_arg_types); free(inline_args); return; } if (is_comptime_call) { semaAnalyzeComptimeCall( sema, block, inst, func, func_ty, &callee, abase, args_len); return; } semaCallSetHasErrorTrace(sema->zcu, func_ty); callee.arg0_ty = arg0_param_ty; semaAnalyzeRuntimeCall( sema, block, inst, &callee, func, func_ty, abase, args_len); } // Sema.zig:zirCall // The direct callee, or fieldCallBind's, then analyzeCall. No error return // trace on this target (`block.ownerModule().error_tracing` is false). static void zirCall( SemaCtx* sema, U32List* block, uint32_t inst, bool is_field) { uint32_t pi = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstCall extra; zirExtraData(sema->zir, pi, &extra); uint32_t args_len = extra.flags >> ZIR_CALL_FLAGS_ARGS_LEN_SHIFT; if ((extra.flags & ZIR_CALL_FLAGS_PACKED_MODIFIER_MASK) != CALL_MODIFIER_AUTO) UNIMPLEMENTED("call: non-auto call modifier"); ResolvedFieldCallee callee = { 0 }; if (!is_field) { callee.func_inst = semaResolveRef(extra.callee, sema->inst_values); callee.func_ty = semaResolveRefType(extra.callee, sema->inst_types); analyzeCall(sema, block, inst, callee, pi + 2, args_len); return; } ZirInstFieldCall fextra; zirExtraData(sema->zir, pi, &fextra); const char* field_name = zirNullTerminatedString(sema->zir, fextra.field_name_start); uint32_t object_ptr = semaResolveRef(fextra.obj_ptr, sema->inst_values); if (object_ptr == INST_VALUE_UNSET) UNREACHABLE("call: unresolved object pointer"); callee = fieldCallBind(sema, block, object_ptr, semaResolveRefType(fextra.obj_ptr, sema->inst_types), ipGetOrPutString(sema->ip, field_name, (uint32_t)strlen(field_name))); analyzeCall(sema, block, inst, callee, pi + 3, args_len); } // Sema.zig:zirIntType // Sema.zirIntType -> ipGetIntType: interns a fixed-width integer type (e.g. // `u5`, `i16`). ZIR data: int_type { src_node, bit_count, signedness // (0=signed, 1=unsigned) }. Returns the type's IP index as a type-value (its // IP index is its AIR Ref). Only safe to add here because int_type creates new // types that are not pre-interned during preamble setup; dedup handles // re-encountering them. static void zirIntType(SemaCtx* sema, uint32_t inst) { uint16_t bits = sema->zir->inst_datas[inst].int_type.bit_count; bool is_signed = (sema->zir->inst_datas[inst].int_type.signedness == SIGNEDNESS_SIGNED); uint32_t ty = ipGetIntType(sema->ip, is_signed, bits); sema->inst_values[inst] = ty; // type is its own IP value // inst_types unused for type-value instructions } // Sema.zig:zirOptionalType // Sema.zirOptionalType: resolve the child type, then // pt.optionalType(child). The optional type is its own IP value. ZIR data: // un_node {operand = child type ref}. static void zirOptionalType(SemaCtx* sema, uint32_t inst) { uint32_t opt_child_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t opt_child_val = semaResolveRef(opt_child_ref, sema->inst_values); if (semaIsAirInstRef(opt_child_val)) UNIMPLEMENTED("optional_type: runtime child type"); uint32_t opt_ty = ipGetOptionalType(sema->ip, opt_child_val); sema->inst_values[inst] = opt_ty; // type is its own IP value // inst_types unused for type-value instructions } // Sema.zig:zirArrayInitElemType // Sema.zirArrayInitElemType: for an indexable type, returns the element type // used to coerce init element `rhs` (a per-index Field type for a tuple). ZIR // `bin` data { lhs=indexable_ty_ref, rhs=elem/field index }. Emits no IP entry // / AIR; the result is a type value. The tuple arm (a `.{57}` anon-tuple // literal coerced to a generic call's inferred tuple param — w.print's args) // reads the TypeTuple extra layout {fields_len, types[], values[]} directly, // mirroring the ctEvalBody handler's struct/ tuple arm. static void zirArrayInitElemType(SemaCtx* sema, uint32_t inst) { uint32_t arr_ref = sema->zir->inst_datas[inst].bin.lhs; uint32_t arr_ty = semaResolveRef(arr_ref, sema->inst_values); if (semaIsAirInstRef(arr_ty)) UNIMPLEMENTED("array_init_elem_type: runtime array type"); if (arr_ty == IP_INDEX_GENERIC_POISON_TYPE) { // Sema.zirArrayInitElemType: `resolveTypeOrPoison(...) orelse // return .generic_poison_type` — a `.{...}` literal bound to // an `anytype` param has no result type; each element is // analyzed uncoerced (w.print's args tuple). sema->inst_values[inst] = IP_INDEX_GENERIC_POISON_TYPE; return; } // `"const indexable_ty = maybe_wrapped_indexable_ty // .optEuBaseType(zcu);"` arr_ty = ipOptEuBaseType(sema->ip, arr_ty); if (ztIsTuple(sema->ip, arr_ty)) { uint32_t aiet_idx = sema->zir->inst_datas[inst].bin.rhs; uint32_t aiet_len = sema->ip->extra[sema->ip->item_datas[arr_ty]]; if (aiet_idx >= aiet_len) UNIMPLEMENTED("array_init_elem_type: tuple index OOB"); sema->inst_values[inst] = sema->ip->extra[sema->ip->item_datas[arr_ty] + 1 + aiet_idx]; return; } // `"else => indexable_ty.indexableElem(zcu),"` -- one arm for // every sequence, so a `type_array_big` and a `type_vector` // answer here exactly like `type_array_small`. ipArrayInfo is the // decode for all three spellings. IpArrayInfo aiet_info; if (ipArrayInfo(sema->ip, arr_ty, &aiet_info) != IP_ARR_OK) UNIMPLEMENTED("array_init_elem_type: non-array container"); // element type is its own IP value sema->inst_values[inst] = aiet_info.child; } // Sema.zig:zirElemType // Sema.zirElemType: the operand is a comptime-known pointer type; yield its // child type (used by the `&.{...}` result-location chain — the pointee type // of the ref becomes the anon init's result type). No interning: the child // type index is read out of the existing pointer type item, so IP order is // untouched. An `*anyopaque` child yields generic_poison_type (upstream: "the // pointer's actual child type is effectively unknown"). optEuBaseType // (unwrapping ?E!*T shells) is not modeled — only a bare pointer/slice // operand; anything else aborts loudly. static void zirElemType(SemaCtx* sema, uint32_t inst) { uint32_t et_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t et_pty = semaResolveRef(et_ref, sema->inst_values); if (!semaRefIsInterned(et_pty)) UNIMPLEMENTED("elem_type: non-comptime-type operand"); // "resolveTypeOrPoison(...) orelse return .generic_poison_type" if (et_pty == IP_INDEX_GENERIC_POISON_TYPE) { sema->inst_values[inst] = IP_INDEX_GENERIC_POISON_TYPE; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; return; } et_pty = ipOptEuBaseType(sema->ip, et_pty); uint32_t et_child; // A pointer's item_datas is an extra offset (word 0 = child); a // slice's item_datas is the many-ptr's item index directly (see // ipGetSliceType — no extra payload), whose own extra word 0 is // the element type. Static preinterned pointer/slice types // (indices below ZIR_REF_START_INDEX, e.g. `[]const u8` = 49) // carry the same tags, so no index-range gate is needed — // item_tags alone rejects non-pointer operands loudly. if (sema->ip->item_tags[et_pty] == IP_TAG_TYPE_POINTER) { et_child = sema->ip->extra[sema->ip->item_datas[et_pty]]; } else if (ztIsSlice(sema->ip, et_pty)) { uint32_t et_many = sema->ip->item_datas[et_pty]; // item index et_child = sema->ip->extra[sema->ip->item_datas[et_many]]; } else { UNIMPLEMENTED("elem_type: operand not a pointer type"); } if (et_child == ZIR_REF_ANYOPAQUE_TYPE) et_child = IP_INDEX_GENERIC_POISON_TYPE; sema->inst_values[inst] = et_child; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirIndexablePtrElemType // Sema.zirIndexablePtrElemType: the operand is a comptime-known Pointer type; // the element type is the child for a slice/many/C pointer, or the child's // child for a `.one` pointer-to-array (`ptr[0..n]`'s result-location chain in // Allocator.alloc). No interning — reads existing type items only. static void zirIndexablePtrElemType(SemaCtx* sema, uint32_t inst) { uint32_t ipe_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t ipe_ty = semaResolveRef(ipe_ref, sema->inst_values); if (!semaRefIsInterned(ipe_ty)) UNIMPLEMENTED("indexable_ptr_elem_type: non-comptime-type operand"); uint32_t ipe_elem; if (ztIsSlice(sema->ip, ipe_ty)) { uint32_t ipe_many = sema->ip->item_datas[ipe_ty]; // item index ipe_elem = sema->ip->extra[sema->ip->item_datas[ipe_many]]; } else if (sema->ip->item_tags[ipe_ty] == IP_TAG_TYPE_POINTER) { uint32_t ipe_size = IP_PTR_FLAGS_SIZE( sema->ip->extra[sema->ip->item_datas[ipe_ty] + 2]); ipe_elem = sema->ip->extra[sema->ip->item_datas[ipe_ty]]; if (ipe_size == IP_PTR_SIZE_ONE) { // `.one => ptr_ty.childType(zcu).childType(zcu)`: checkMemOperand // has the child an array, sentinel or not, or a vector. IpArrayInfo ipe_arr; if (ipArrayInfo(sema->ip, ipe_elem, &ipe_arr) != IP_ARR_OK) UNIMPLEMENTED( "indexable_ptr_elem_type: .one pointer to non-array"); ipe_elem = ipe_arr.child; } } else { UNIMPLEMENTED("indexable_ptr_elem_type: operand not a pointer type"); } sema->inst_values[inst] = ipe_elem; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirSplatOpResultType, Air.zig:internedToRef // "const raw_ty = try sema.resolveTypeOrPoison(...) orelse return // .generic_poison_type; const vec_ty = raw_ty.optEuBaseType(zcu); switch // (vec_ty.zigTypeTag(zcu)) { .array, .vector => {}, else => return // sema.fail(...) } return Air.internedToRef(vec_ty.childType(zcu) // .toIntern());". static void zirSplatOpResultType(SemaCtx* sema, uint32_t inst) { uint32_t so_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t raw_ty = semaResolveRef(so_ref, sema->inst_values); if (!semaRefIsInterned(raw_ty)) UNIMPLEMENTED("splat_op_result_ty: non-comptime type operand"); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; if (raw_ty == IP_INDEX_GENERIC_POISON_TYPE) { sema->inst_values[inst] = raw_ty; return; } IpArrayInfo info; if (ipArrayInfo(sema->ip, ipOptEuBaseType(sema->ip, raw_ty), &info) != IP_ARR_OK) COMPILE_ERROR_UPSTREAM( "splat_op_result_ty: expected array or vector type"); sema->inst_values[inst] = info.child; } // Sema.zig:zirVectorType // Sema.zirVectorType (src/Sema.zig): resolveInt(len, .u32) then // checkVectorElemType + pt.vectorType({ .len, .child }). ZIR pl_node -> Bin { // lhs=len_ref, rhs=elem_type_ref }, the same layout as ARRAY_TYPE above // (Sema.zirArrayType) -- vector_type is that sibling's // comptime-len/comptime-elem twin, always comptime regardless of the enclosing // body's runtime-ness (no block.semaBlockIsComptime() branch upstream). // (traced via instrumentation-then-revert: inst's type_name_ctx decodes to // ".removeFeatureSet"; len resolves to the comptime-known usize_count=11 word // count, elem resolves to the `usize` simple-type preintern, IP index 15). The // u32-length walk below is `array_type`'s length walk verbatim (same len-shape // family, `.u32` dest instead of `.usize` -- resolveInt's coerce targets the // ZIR-declared dest type, `Vector(len: u32, ...)` vs `[len: usize]T`). static void zirVectorType(SemaCtx* sema, uint32_t inst) { uint32_t vpp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, vpp, &extra); uint32_t vlen_ref = extra.lhs; uint32_t velem_ref = extra.rhs; uint32_t vlen_val = semaResolveRef(vlen_ref, sema->inst_values); uint64_t vn; if (vlen_val == ZIR_REF_ZERO) { vn = 0; } else if (vlen_val == ZIR_REF_ONE) { vn = 1; } else if (semaRefIsInterned(vlen_val)) { uint64_t vlen_mag; if (!ipTryReadIntValue(sema->ip, vlen_val, &vlen_mag)) UNIMPLEMENTED("vector_type: length not a readable int"); vn = vlen_mag; } else { UNIMPLEMENTED("vector_type: non-comptime-int length"); } uint32_t velem_val = semaResolveRef(velem_ref, sema->inst_values); if (semaIsAirInstRef(velem_val)) UNIMPLEMENTED("vector_type: runtime element type"); // type is its own IP value. The u32 length coerce and the // vectorType intern are zptVectorType's, shared with the // declared-type and comptime frames -- including its // checkVectorElemType element-type gate. sema->inst_values[inst] = zptVectorType(sema->ip, vn, velem_val); } // Sema.zig:zirArrayType // Sema.zirArrayType: resolveInt(len, .usize) then arrayType({ .len, .child }). // ZIR pl_node -> Bin { lhs=len_ref, rhs=elem_ty_ref }. resolveInt coerces the // comptime-int length to usize (interning an `int_usize`), then `pt.arrayType` // interns the (sentinel-less) `type_array_small`. The array type is its own IP // value. static void zirArrayType(SemaCtx* sema, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); uint32_t elem_ref = extra.rhs; uint64_t n = semaArrayTypeLen( sema->ip, semaResolveRef(extra.lhs, sema->inst_values)); ipGetIntUsize(sema->ip, (uint32_t)n); // resolveInt -> usize coerce uint32_t elem_ty = semaResolveRef(elem_ref, sema->inst_values); if (semaIsAirInstRef(elem_ty)) UNIMPLEMENTED("array_type: runtime element type"); validateArrayElemType(sema->ip, elem_ty); uint32_t arr_ty = ipGetArrayType(sema->ip, (uint32_t)n, elem_ty, IP_INDEX_NONE); sema->inst_values[inst] = arr_ty; // type is its own IP value } // Sema.zig:zirArrayTypeSentinel, PerThread.zig:arrayType // ZIR pl_node -> ArrayTypeSentinel { len, sentinel, elem_type }, each // comptime-known: `resolveInt(len, .usize)`, `validateArrayElemType`, the // sentinel (semaSentinelValue), `pt.arrayType`. static void zirArrayTypeSentinel(SemaCtx* sema, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstArrayTypeSentinel extra; zirExtraData(sema->zir, pp, &extra); uint64_t n = semaArrayTypeLen( sema->ip, semaResolveRef(extra.len, sema->inst_values)); uint32_t sentinel = semaResolveRef(extra.sentinel, sema->inst_values); uint32_t elem = semaResolveRef(extra.elem_type, sema->inst_values); if (!semaRefIsInterned(elem)) UNIMPLEMENTED("array_type_sentinel: element type not a type"); // `resolveConstDefinedValue`: "unable to resolve comptime value". if (semaIsAirInstRef(sentinel)) COMPILE_ERROR_UPSTREAM( "array_type_sentinel: sentinel not comptime-known"); // usize is 32 bits on the wasm32 target: `resolveInt` refuses a longer // length rather than truncating it. if (n > UINT32_MAX) COMPILE_ERROR_UPSTREAM("array length does not fit usize"); ipGetIntUsize(sema->ip, (uint32_t)n); // resolveInt -> usize coerce validateArrayElemType(sema->ip, elem); uint32_t coerced = semaSentinelValue(sema->zcu, sema->ip, sentinel, elem); sema->inst_values[inst] = ipGetArrayType(sema->ip, (uint32_t)n, elem, coerced); } // Sema.zig:zirErrorUnionType // Sema.zirErrorUnionType (src/Sema.zig): resolve the error set (lhs) and // payload (rhs), require `"error_set.zigTypeTag(zcu) == .error_set"` (an // inferred set included), then pt.errorUnionType // (semaErrorUnionTypeFromSetPayload). Upstream's // `validateErrorUnionPayloadType` (opaque / error-set payloads) is not ported // in either frame. ZIR data: pl_node with Bin {lhs = error_set ref, rhs = // payload ref}. static void zirErrorUnionType(SemaCtx* sema, uint32_t inst) { uint32_t eut_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, eut_pp, &extra); uint32_t eut_set_ref = extra.lhs; uint32_t eut_pl_ref = extra.rhs; uint32_t eut_set_ty = semaResolveRef(eut_set_ref, sema->inst_values); uint32_t eut_pl_val = semaResolveRef(eut_pl_ref, sema->inst_values); if (semaIsAirInstRef(eut_set_ty) || semaIsAirInstRef(eut_pl_val)) UNIMPLEMENTED("error_union_type: runtime operand"); if (!ipIsErrorSetType(sema->ip, eut_set_ty)) UNIMPLEMENTED("error_union_type: non-error-set lhs"); uint32_t eu_ty = semaErrorUnionTypeFromSetPayload(sema->ip, eut_set_ty, eut_pl_val); sema->inst_values[inst] = eu_ty; // type is its own IP value // inst_types unused for type-value instructions } // Sema.zig:zirErrorValue // Sema.zirErrorValue: `error.Name` as an expression -> a comptime error // value. Intern the name, form the single-name error set `error{Name}`, then // intern the error value in it (Key.err -> error_set_error). The value is its // own IP index (a comptime AIR Ref); its type is that single error set. ZIR // data: str_tok {start} — the IP-string byte offset of the name. Mirrors the // name/set/value interning in ZIR_INST_RET_ERR_VALUE above. static void zirErrorValue(SemaCtx* sema, uint32_t inst) { uint32_t ev_name_off = sema->zir->inst_datas[inst].str_tok.start; const char* ev_name = zirNullTerminatedString(sema->zir, ev_name_off); uint32_t ev_name_str = ipGetOrPutString(sema->ip, ev_name, (uint32_t)strlen(ev_name)); uint32_t ev_set = ipGetErrorSetType(sema->ip, &ev_name_str, 1); uint32_t ev_val = ipGetErrorSetError(sema->ip, ev_set, ev_name_str); sema->inst_values[inst] = ev_val; // comptime error value (own IP Ref) sema->inst_types[inst] = ev_set; } // Sema.zig:zirIntFromError, InternPool.zig:indexToKey, PerThread.zig:intValue // Sema.zirIntFromError: UnNode payload [node, operand], coerced to anyerror. A // comptime operand folds (`"const err_name = ip.indexToKey(val.toIntern()) // .err.name; return ... pt.intValue(err_int_ty, try // pt.getErrorValue(err_name))"`, an undef one staying undef; `errorIntType` is // u16 at the default error limit); a runtime one whose error set has 0 or 1 // names is still comptime (`"0 => ... pt.intValue(err_int_ty, 0)"`, `"1 => ... // ip.getErrorValueIfExists(names.get(ip)[0]).?"`); else `int_from_error`. static void zirIntFromError(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; ZirInstUnNode extra; zirExtraData(sema->zir, ext_operand, &extra); uint32_t ife_ref = extra.operand; uint32_t ife_op = semaResolveRef(ife_ref, sema->inst_values); sema->inst_types[inst] = ZIR_REF_U16_TYPE; if (!semaIsAirInstRef(ife_op)) { InternPool* ip = sema->ip; if (ife_op >= ip->items_len || (!ipIsUndef(ip, ife_op) && ip->item_tags[ife_op] != IP_TAG_ERROR_SET_ERROR)) UNIMPLEMENTED("int_from_error: comptime non-error"); sema->inst_values[inst] = ipIsUndef(ip, ife_op) ? ipGetUndef(ip, ZIR_REF_U16_TYPE) : ipGetIntSmall(ip, ZIR_REF_U16_TYPE, ipGetErrorValue(ip, ipErrorName(ip, ife_op))); return; } uint32_t ife_ty = semaResolveRefType(ife_ref, sema->inst_types); if (ife_ty != ZIR_REF_ANYERROR_TYPE) { if (ife_ty >= sema->ip->items_len || sema->ip->item_tags[ife_ty] != IP_TAG_TYPE_ERROR_SET) UNIMPLEMENTED("int_from_error: operand error set"); const uint32_t* es = &sema->ip->extra[sema->ip->item_datas[ife_ty]]; if (es[0] <= 1u) { sema->inst_values[inst] = ipGetIntSmall(sema->ip, ZIR_REF_U16_TYPE, es[0] == 0 ? 0u : ipGetErrorValueIfExists(sema->ip, es[2])); return; } } uint32_t ife_any = semaCoerceExtraRuntime(sema->zcu, ife_op, ife_ty, ZIR_REF_ANYERROR_TYPE, sema->insts, block, sema->air_extra, "int_from_error"); sema->inst_values[inst] = semaEmitTyOp(sema->insts, block, AIR_INST_INT_FROM_ERROR, ZIR_REF_U16_TYPE, ife_any); } // Sema.zig:zirErrorFromInt // Sema.zirErrorFromInt: UnNode payload [node, operand], coerced to // `errorIntType` (u16). A comptime 0, or a value past the global error set, is // "integer value '{d}' represents no error"; else `.err = .{ .ty = // .anyerror_type, .name = names[int - 1] }`. No safety check in this profile, // so a runtime operand is `error_from_int`. static void zirErrorFromInt(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; ZirInstUnNode extra; zirExtraData(sema->zir, ext_operand, &extra); uint32_t efi_ref = extra.operand; uint32_t efi_op = semaResolveRef(efi_ref, sema->inst_values); sema->inst_types[inst] = ZIR_REF_ANYERROR_TYPE; if (!semaIsAirInstRef(efi_op)) { // `"const operand = try sema.coerce(block, err_int_ty, // uncasted_operand, operand_src);"` comes first. uint64_t v; if (efi_op == INST_VALUE_UNSET || !ipTryReadIntValue(sema->ip, semaCoerceComptime( sema->zcu, sema->ip, efi_op, ZIR_REF_U16_TYPE), &v)) UNIMPLEMENTED("error_from_int: comptime non-int"); if (v == 0 || v > sema->ip->global_error_names_len) COMPILE_ERROR_UPSTREAM( "error_from_int: integer value represents no error"); sema->inst_values[inst] = ipGetErrorSetError(sema->ip, ZIR_REF_ANYERROR_TYPE, sema->ip->global_error_names[v - 1u]); return; } uint32_t efi_int = semaCoerceExtraRuntime(sema->zcu, efi_op, semaResolveRefType(efi_ref, sema->inst_types), ZIR_REF_U16_TYPE, sema->insts, block, sema->air_extra, "error_from_int"); sema->inst_values[inst] = semaEmitTyOp(sema->insts, block, AIR_INST_ERROR_FROM_INT, ZIR_REF_ANYERROR_TYPE, efi_int); } // Sema.zig:zirMergeErrorSets // Sema.zirMergeErrorSets (src/Sema.zig): `lhs || rhs` on error-set types. By // the time this instruction runs, both operands (the namespaced decl access // and the inline error_set_decl above) are already resolved // earlier-instruction values. ZIR data: pl_node -> Bin {lhs, rhs}, same layout // as ERROR_UNION_TYPE above. Shares the exact merge/dedup/sort/intern sequence // (Sema.errorSetMerge, src/Sema.zig) already extracted as // `semaMergeResolvedErrorSetTypes` for the comptime param-type resolver // (sema_comptime.c's ctResolveParamTypeRef ZIR_INST_MERGE_ERROR_SETS arm) // instead of re-implementing the merge. Upstream requires both operands to be // comptime-known error-set types (analyzeAsType, Sema.zig) — error sets are // always comptime, so a runtime operand here is an unmodeled shape, not a // real-path expectation; kept loud. `link.File.OpenError` itself resolves to // an `inferred_error_set_type` (IP tag IP_TAG_TYPE_INFERRED_ERROR_SET), not a // finite `type_error_set` -- upstream's zirMergeErrorSets special-cases this // via `ensureFuncIesResolved` (Sema.zig). Resolved here via // `semaResolveMergeErrorSetOperands`. static void zirMergeErrorSets(SemaCtx* sema, uint32_t inst) { uint32_t mes_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, mes_pp, &extra); uint32_t mes_lhs_ref = extra.lhs; uint32_t mes_rhs_ref = extra.rhs; uint32_t mes_lhs_ty = semaResolveRef(mes_lhs_ref, sema->inst_values); uint32_t mes_rhs_ty = semaResolveRef(mes_rhs_ref, sema->inst_values); if (semaIsAirInstRef(mes_lhs_ty) || semaIsAirInstRef(mes_rhs_ty)) UNIMPLEMENTED("merge_error_sets: runtime operand"); uint32_t mes_ty = semaResolveMergeErrorSetOperands(sema->ip, &mes_lhs_ty, &mes_rhs_ty, "merge_error_sets: non-error-set operand") ? ZIR_REF_ANYERROR_TYPE : semaMergeResolvedErrorSetTypes(sema->ip, mes_lhs_ty, mes_rhs_ty, "merge_error_sets: non-error-set operand"); sema->inst_values[inst] = mes_ty; // type is its own IP value // inst_types unused for type-value instructions } // Sema.zig:zirEnumLiteral // Sema.zirEnumLiteral: intern the `.name` enum literal as a comptime value // (its type is the `enum_literal` type). No AIR is emitted; coercion to a // concrete enum happens at the use site (e.g. the `==` comparison or a call // argument). static void zirEnumLiteral(SemaCtx* sema, uint32_t inst) { uint32_t name_off = sema->zir->inst_datas[inst].str_tok.start; const char* name = zirNullTerminatedString(sema->zir, name_off); uint32_t name_str = ipGetOrPutString(sema->ip, name, (uint32_t)strlen(name)); sema->inst_values[inst] = ipGetEnumLiteral(sema->ip, name_str); sema->inst_types[inst] = ZIR_REF_ENUM_LITERAL_TYPE; } // Sema.zig:analyzeDeclLiteral // `.field` in a known-result-type context (e.g. `const e: E = .b;`). ZIR // data: pl_node with Field payload {lhs=dest_type_ref, field_name_start}. // For an enum dest type, resolves directly to the field's enum tag value // (Sema.fieldVal's `.enum` case -> pt.enumValueFieldIndex, mirrored by // `ipEnumTagFromFieldName`), at the peeled type; zirDeclLiteral coerces it // back to the original one. static void analyzeDeclLiteral(SemaCtx* sema, uint32_t inst, bool do_coerce) { uint32_t dl_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstField extra; zirExtraData(sema->zir, dl_pp, &extra); uint32_t dl_dest_ref = extra.lhs; uint32_t dl_field_name_start = extra.field_name_start; const char* dl_field = zirNullTerminatedString(sema->zir, dl_field_name_start); uint32_t dl_dest_ty = semaResolveRef(dl_dest_ref, sema->inst_values); if (semaIsAirInstRef(dl_dest_ty)) UNIMPLEMENTED("decl_literal: runtime dest type"); if (dl_dest_ty == INST_VALUE_UNSET) // The result-location type is unresolved (e.g. `.little` as an // arg to `mem.readInt(..., .little)` inside an inline call // whose arg result-location type is not seeded yet) or a // comptime sentinel — abort loudly rather than index item_tags // OOB. UNIMPLEMENTED("decl_literal: unresolved/sentinel dest type"); // Sema.analyzeDeclLiteral: an `orig_ty` of `generic_poison_type` // is treated as a plain enum literal before the peel loop even // runs (src/Sema.zig) — checked on the unpeeled dest // type deliberately, matching upstream's ordering. Reached via a // decl-literal argument bound to a still-unresolved `anytype` // parameter's call-instruction placeholder type (Sema.analyzeCall // "give the arg its result type", src/Sema.zig ~6517; zig1's // call-arg tail seeds `inst_values[inst] = param_ty` with // `param_ty == generic_poison_type` for anytype params — see the // call-argument loop's `is_anytype_param` handling), e.g. // a tuple literal passed to a `features: anytype` parameter. do_coerce's // subsequent `sema.coerce(orig_ty, ...)` back into `generic_poison_type` // is then a no-op // (`coerceExtra`'s `if (dest_ty.isGenericPoison()) return inst;`, // src/Sema.zig), so DECL_LITERAL/_NO_COERCE are identical // here too — same reasoning as the enum_literal/error_set arm // below. A hypothetical peeled-into-generic_poison shape (e.g. // `?generic_poison_type`, if that is ever constructible) is not // this upstream arm and is left unmodeled — stays loud via the // enum/struct/union checks below. if (dl_dest_ty == IP_INDEX_GENERIC_POISON_TYPE) { uint32_t gp_field_str = ipGetOrPutString(sema->ip, dl_field, (uint32_t)strlen(dl_field)); sema->inst_values[inst] = ipGetEnumLiteral(sema->ip, gp_field_str); sema->inst_types[inst] = ZIR_REF_ENUM_LITERAL_TYPE; return; } // Sema.analyzeDeclLiteral peels error-union / optional / // single-pointer wrappers off the result-location type before the // namespace lookup (e.g. `gpa.alignedAlloc(u8, .of(IndexHeader), // n)` binds `.of` against `?Alignment` -> peel to the `Alignment` // enum). Faithful to the `while (true) switch (ty.zigTypeTag)` // loop in Sema.zig. for (;;) { uint8_t dl_dt = sema->ip->item_tags[dl_dest_ty]; if (dl_dt == IP_TAG_TYPE_OPTIONAL) { dl_dest_ty = sema->ip->item_datas[dl_dest_ty]; } else if (dl_dt == IP_TAG_TYPE_ERROR_UNION || dl_dt == IP_TAG_TYPE_ANYERROR_UNION) { dl_dest_ty = ipErrorUnionPayload(sema->ip, dl_dest_ty); } else if (dl_dt == IP_TAG_TYPE_POINTER && IP_PTR_FLAGS_SIZE( sema->ip->extra[sema->ip->item_datas[dl_dest_ty] + 2]) == IP_PTR_SIZE_ONE) { dl_dest_ty = sema->ip->extra[sema->ip->item_datas[dl_dest_ty]]; // child } else { break; } } if (dl_dest_ty == ZIR_REF_ENUM_LITERAL_TYPE) { // analyzeDeclLiteral's `.enum_literal, .error_set =>` arm: a plain // enum literal, which zirDeclLiteral coerces back into `orig_ty`. // The `.error_set` half is not ported (the enum check below). uint32_t el_field_str = ipGetOrPutString(sema->ip, dl_field, (uint32_t)strlen(dl_field)); sema->inst_values[inst] = ipGetEnumLiteral(sema->ip, el_field_str); sema->inst_types[inst] = ZIR_REF_ENUM_LITERAL_TYPE; return; } if (ipTagIsAnyStruct(sema->ip->item_tags[dl_dest_ty])) { // Sema.analyzeDeclLiteral on a struct dest type: `.member` // names a decl in the struct's namespace (namespaceLookupVal), // e.g. `var w: W = .fixed(&buf)` binds `.fixed` to W.fixed (a fn the // following call consumes). A struct has no enum-style field tags, so // a decl literal on a struct dest is always a namespace-decl lookup — // mirrors the enum namespace-member branch below. uint32_t sdl_field_str = ipGetOrPutString(sema->ip, dl_field, (uint32_t)strlen(dl_field)); uint32_t sdl_ns = ipStructNamespaceOrNone(sema->ip, dl_dest_ty); if (sdl_ns == IP_NAMESPACE_NONE) UNIMPLEMENTED("decl_literal: struct dest has no namespace"); uint32_t sdl_nav = ipNamespaceLookup(sema->ip, sdl_ns, sdl_field_str); if (sdl_nav == IP_INDEX_NONE) failWithBadMemberAccess(sema->zcu, dl_dest_ty, sdl_field_str); uint32_t sdl_mem_ty = IP_INDEX_NONE; uint32_t sdl_mem_val = navAnalyzeVal(sema->zcu, sdl_nav, &sdl_mem_ty); sema->inst_values[inst] = sdl_mem_val; sema->inst_types[inst] = sdl_mem_ty; return; } if (ipTagIsAnyUnion(sema->ip->item_tags[dl_dest_ty])) { // Sema.fieldVal's `.type` -> `.@"union"` arm (src/Sema.zig // ~25964): a member decl of the union's namespace wins // (namespaceLookupVal first; a decl and a field cannot share // a name — AstGen "duplicate union member name" — so the // order is unobservable, mirrored anyway); else the tag-enum // field yields the bare tag (`pt.enumValueFieldIndex` on // `unionTagType`, non-null only for `.tagged` usage — // Type.zig) which do_coerce wraps via coerceEnumToUnion // (src/Sema.zig ~29831): a void payload interns // union_value {ty, tag, void}; a runtime-sized payload // without an init is a stock compile error ("coercion from // enum ... to union ... must initialize ... field ..."). // Mirrors the ctEvalBody union arm. uint32_t udl_field_str = ipGetOrPutString(sema->ip, dl_field, (uint32_t)strlen(dl_field)); uint32_t udl_ns = ipUnionNamespaceOrNone(sema->ip, dl_dest_ty); if (udl_ns != IP_NAMESPACE_NONE) { uint32_t udl_nav = ipNamespaceLookup(sema->ip, udl_ns, udl_field_str); if (udl_nav != IP_INDEX_NONE) { uint32_t udl_mem_ty = IP_INDEX_NONE; uint32_t udl_mem_val = navAnalyzeVal(sema->zcu, udl_nav, &udl_mem_ty); sema->inst_values[inst] = udl_mem_val; sema->inst_types[inst] = udl_mem_ty; return; } } if (ipUnionTagUsage(sema->ip, dl_dest_ty) != IP_UNION_TAG_USAGE_TAGGED) failWithBadMemberAccess(sema->zcu, dl_dest_ty, udl_field_str); semaResolveAllUnionDeferred(sema->zcu, dl_dest_ty); uint32_t udl_tag_enum = ipUnionEnumTagType(sema->ip, dl_dest_ty); if (udl_tag_enum == IP_INDEX_NONE) udl_tag_enum = ipEnsureGeneratedUnionTag(sema->ip, dl_dest_ty); uint32_t udl_tag = ipEnumTagFromFieldName(sema->ip, udl_tag_enum, udl_field_str); if (udl_tag == IP_INDEX_NONE) failWithBadMemberAccess(sema->zcu, dl_dest_ty, udl_field_str); if (!do_coerce) { // analyzeDeclLiteral without do_coerce: the uncoerced // fieldVal result (the bare tag; the `.name()` callee // path). sema->inst_values[inst] = udl_tag; sema->inst_types[inst] = udl_tag_enum; return; } uint32_t udl_ufidx = ipUnionFieldIndex(sema->ip, dl_dest_ty, udl_field_str); if (udl_ufidx == UINT32_MAX) UNIMPLEMENTED("decl_literal: union field index"); uint32_t udl_fty = semaResolveUnionFieldTypeLazy(sema->zcu, dl_dest_ty, udl_ufidx); if (udl_fty == ZIR_REF_VOID_TYPE) { sema->inst_values[inst] = ipGetUnionValue( sema->ip, dl_dest_ty, udl_tag, ZIR_REF_VOID_VALUE); sema->inst_types[inst] = dl_dest_ty; return; } if (udl_fty != IP_INDEX_NONE && ztTypeClass(sema->ip, udl_fty) == ZT_CLASS_OPV) UNIMPLEMENTED("decl_literal: union non-void OPV payload"); COMPILE_ERROR_UPSTREAM( "decl_literal: union payload field without init"); } if (!ipIsEnumType(sema->ip, dl_dest_ty)) // Remaining categories (error-set dest, or anything else // fieldVal's `.type` switch covers beyond // enum/struct/union/enum_literal) are unported; see the // enum_literal arm above for the disclosed error-set // scoping decision. UNIMPLEMENTED("decl_literal: only enum/struct/union/enum_literal dest " "type ported"); uint32_t dl_field_str = ipGetOrPutString(sema->ip, dl_field, (uint32_t)strlen(dl_field)); uint32_t dl_tag = ipEnumTagFromFieldName(sema->ip, dl_dest_ty, dl_field_str); if (dl_tag == IP_INDEX_NONE) { // Not a field: a member decl of the dest type's namespace — // the actual "decl literal" (Sema.analyzeDeclLiteral -> // namespaceLookupVal), e.g. `.of` in `comptime .of(T)` (a fn the // following call consumes). uint32_t dl_ns = ipEnumNamespaceOrNone(sema->ip, dl_dest_ty); if (dl_ns != IP_NAMESPACE_NONE) { uint32_t dl_nav = ipNamespaceLookup(sema->ip, dl_ns, dl_field_str); if (dl_nav != IP_INDEX_NONE) { uint32_t dl_mem_ty = IP_INDEX_NONE; uint32_t dl_mem_val = navAnalyzeVal(sema->zcu, dl_nav, &dl_mem_ty); sema->inst_values[inst] = dl_mem_val; sema->inst_types[inst] = dl_mem_ty; return; } } UNIMPLEMENTED("decl_literal: enum field not found"); } sema->inst_values[inst] = dl_tag; sema->inst_types[inst] = dl_dest_ty; } // Sema.zig:zirDeclLiteral // `"if (do_coerce) return sema.coerce(block, orig_ty, uncoerced_result, // src);"`: the value found at the peeled type, coerced back to the // error-union, optional or pointer type it was peeled from. static void zirDeclLiteral(SemaCtx* sema, uint32_t inst, bool do_coerce) { analyzeDeclLiteral(sema, inst, do_coerce); ZirInstField extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &extra); uint32_t orig_ty = semaResolveRef(extra.lhs, sema->inst_values); uint32_t val = sema->inst_values[inst]; if (!do_coerce || orig_ty == sema->inst_types[inst] || orig_ty == IP_INDEX_GENERIC_POISON_TYPE || !semaRefIsInterned(val)) return; sema->inst_values[inst] = semaCoerceComptime(sema->zcu, sema->ip, val, orig_ty); sema->inst_types[inst] = orig_ty; } // (part of) Sema.zig:zirIntCast, Sema.zig:zirBitcast, // Sema.zig:zirEnumFromInt, Sema.zig:zirFromBackingInt, Sema.zig:zirFloatCast, // Sema.zig:zirIntFromFloat, Sema.zig:zirFloatFromInt, Sema.zig:zirTruncate // The Bin payload {lhs=dest_type_ref, rhs=operand_ref} of a type cast: // Sema.resolveDestType(.remove_eu_opt) into `*dest_ty` and // inst_types[inst], returning the operand's ZIR ref. The dest comes from the // result location, which may be the fn's error-union / optional return type // (`return @intCast(index)` in a `!LocalIndex` fn -- allocLocalValue): the // error union and/or optional is peeled to the inner cast-target type. static uint32_t semaCastDestOperand( SemaCtx* sema, uint32_t inst, uint32_t* dest_ty) { ZirInstBin extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &extra); uint32_t dest_val = semaResolveRef(extra.lhs, sema->inst_values); if (semaIsAirInstRef(dest_val)) UNIMPLEMENTED("zirIntCast: type cast - runtime dest type"); *dest_ty = semaResolveRemoveEuOptDestType(sema->ip, dest_val); sema->inst_types[inst] = *dest_ty; return extra.rhs; } // Sema.zig:zirEnumFromInt // Sema.zirEnumFromInt: pl_node with Bin payload {lhs=dest_type_ref, // rhs=operand_ref}; the destination is `resolveDestType(..., .remove_eu_opt, // ...)` (a `return @enumFromInt(x)` in a `!E`- or `?E`-returning function // arrives still wrapped). A comptime operand is `semaEnumFromIntValue`; a // runtime one (ReleaseSmall, wantSafety()=false) is AIR int_cast(dest_ty, // operand). static void zirEnumFromInt(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t efi_dest_ty; uint32_t efi_op_val = semaResolveRef( semaCastDestOperand(sema, inst, &efi_dest_ty), sema->inst_values); if (!ipIsEnumType(sema->ip, efi_dest_ty)) UNIMPLEMENTED("enum_from_int: non-enum dest type"); // `"try sema.ensureLayoutResolved(dest_ty, src, .init);"` if (!ensureLayoutResolved(sema->zcu, efi_dest_ty)) UNIMPLEMENTED("enum_from_int: dest layout"); // `"if (try dest_ty.onePossibleValue(pt)) |opv| { ... return // .fromValue(opv); }"` -- a runtime operand, a comptime result // (the safety check is off in ReleaseSmall). if (semaIsAirInstRef(efi_op_val)) { uint32_t efi_opv = ztOnePossibleValue(sema->ip, efi_dest_ty); if (efi_opv != IP_INDEX_NONE) { sema->inst_values[inst] = efi_opv; sema->inst_types[inst] = efi_dest_ty; return; } AirInstData efi_data; memset(&efi_data, 0, sizeof(efi_data)); efi_data.pair.a = efi_dest_ty; efi_data.pair.b = efi_op_val; uint32_t efi_air_idx = semaAirInstAppend(sema->insts, AIR_INST_INT_CAST, efi_data); u32ListAppend(block, efi_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + efi_air_idx; sema->inst_types[inst] = efi_dest_ty; return; } sema->inst_values[inst] = semaEnumFromIntValue(sema->ip, efi_dest_ty, efi_op_val); sema->inst_types[inst] = efi_dest_ty; } // Sema.zig:zirOptionalPayloadPtr // Sema.zirOptionalPayloadPtr -> analyzeOptionalPayloadPtr: // `opt_slot.?` by-ref (e.g. `self.metadata.?[i]` in HashMap) — the operand is // a pointer to the optional; the result is a pointer to its payload with the // operand pointer's own flags (mutability etc.) and the child swapped // (upstream copies ptrInfo and replaces .child). The safe variant only adds an // `is_non_null_ptr` safety check when `block.wantSafety()`; in ReleaseSmall // (no safety) it is byte-identical to the unsafe variant, so both share this // path. Runtime operand -> AIR optional_payload_ptr (ty_op: a = child ptr // type, b = operand). A PtrNav naming runtime data is comptime-known as an // address but still needs runtime payload access; preserve that exact pointer // value as the AIR operand. Fold-only pointers remain unmodeled. static void zirOptionalPayloadPtr( SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t oppp_ref = sema->zir->inst_datas[inst].un_node.operand; semaEnsurePointeeLayoutResolved(sema, oppp_ref); uint32_t oppp_val = semaResolveRef(oppp_ref, sema->inst_values); if (!semaIsAirInstRef(oppp_val) && !semaIsRuntimeDataNavPtr(sema->ip, oppp_val)) { // An immutable optional value standing for its pointer (this // frame's fold-only convention, as field_ptr's aggregate // arm): the payload value stands for the payload pointer, // after `analyzeOptionalPayloadPtr`'s "unable to unwrap null". if (ipItemTagIs(sema->ip, oppp_val, IP_TAG_OPT_NULL)) COMPILE_ERROR_UPSTREAM( "optional_payload_ptr: unable to unwrap null"); if (ipItemTagIs(sema->ip, oppp_val, IP_TAG_OPT_PAYLOAD)) { uint32_t od = sema->ip->item_datas[oppp_val]; sema->inst_values[inst] = sema->ip->extra[od + 1]; sema->inst_types[inst] = ipOptionalChild(sema->ip, sema->ip->extra[od]); return; } // A comptime cell, a field of one, or a const Nav: the // comptime `Value.ptrOptPayload`. IpPtrInfo oppp_pi; if (!ipIsPtrComptimeAlloc(sema->ip, oppp_val) && !semaIsCellChainPtr(sema->ip, oppp_val) && (oppp_val >= sema->ip->items_len || ipPtrInfo(sema->ip, ipTypeOf(sema->ip, oppp_val), &oppp_pi) != IP_PTRI_OK)) UNIMPLEMENTED("optional_payload_unsafe_ptr: comptime operand"); sema->inst_values[inst] = semaComptimeOptionalPayloadPtr(sema->zcu, oppp_val); sema->inst_types[inst] = ipTypeOf(sema->ip, sema->inst_values[inst]); return; } uint32_t oppp_ptr_ty = semaResolveRefType(oppp_ref, sema->inst_types); if (sema->ip->item_tags[oppp_ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("optional_payload_unsafe_ptr: non-ptr operand"); uint32_t oppp_opt_ty = sema->ip->extra[sema->ip->item_datas[oppp_ptr_ty]]; uint32_t oppp_flags = sema->ip->extra[sema->ip->item_datas[oppp_ptr_ty] + 2]; if (!ztIsZigTypeTag(sema->ip, oppp_opt_ty, ZIG_TYPE_TAG_OPTIONAL)) UNIMPLEMENTED("optional_payload_unsafe_ptr: pointee not optional"); uint32_t oppp_child = sema->ip->item_datas[oppp_opt_ty]; uint32_t oppp_res_ty = ipGetPtrType(sema->ip, oppp_child, oppp_flags); AirInstData oppp_data; memset(&oppp_data, 0, sizeof(oppp_data)); oppp_data.pair.a = oppp_res_ty; oppp_data.pair.b = (uint32_t)oppp_val; uint32_t oppp_air_idx = semaAirInstAppend( sema->insts, AIR_INST_OPTIONAL_PAYLOAD_PTR, oppp_data); u32ListAppend(block, oppp_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + oppp_air_idx; sema->inst_types[inst] = oppp_res_ty; } // Sema.zig:zirOptionalPayload // `opt.?` with no safety check (ReleaseSmall). "if `ptr` is a `[*c]T`, then // `ptr.?` is also a `[*c]T`". static void zirOptionalPayload(SemaCtx* sema, U32List* block, uint32_t inst) { InternPool* ip = sema->ip; uint32_t operand_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t operand = semaResolveRef(operand_ref, sema->inst_values); uint32_t operand_ty = semaResolveRefValType(ip, operand_ref, operand, sema->inst_types); uint32_t result_ty; if (ztIsZigTypeTag(ip, operand_ty, ZIG_TYPE_TAG_OPTIONAL)) result_ty = ipOptionalChild(ip, operand_ty); else if (ztIsCPtr(ip, operand_ty)) result_ty = operand_ty; else COMPILE_ERROR_UPSTREAM("zirOptionalPayload: expected optional type"); sema->inst_types[inst] = result_ty; if (!semaIsAirInstRef(operand)) { // resolveDefinedValue: `"if (val.isUndef(zcu)) return // sema.failWithUseOfUndef(block, src, null);"`. if (ipIsUndef(ip, operand)) COMPILE_ERROR_UPSTREAM( "use of undefined value here causes illegal behavior"); uint32_t payload = valOptionalValue(ip, operand); if (payload != IP_INDEX_NONE) { sema->inst_values[inst] = payload; return; } // Comptime-known to be `null`: `.unreach` and `.unreachable_value`. semaUnreachableResult(sema, block, inst); return; } if (resolveIsNullFromType(ip, operand_ty, sema->fn_ret_ty) == ZIR_REF_BOOL_TRUE) { semaUnreachableResult(sema, block, inst); return; } uint32_t opv = ztOnePossibleValue(ip, result_ty); sema->inst_values[inst] = opv != IP_INDEX_NONE ? opv : semaEmitTyOp(sema->insts, block, AIR_INST_OPTIONAL_PAYLOAD, result_ty, operand); } // Sema.zig:analyzeErrUnionPayload // In a body without safety. static uint32_t analyzeErrUnionPayload(SemaCtx* sema, U32List* block, uint32_t err_union_ty, uint32_t operand, uint32_t* out_ty) { InternPool* ip = sema->ip; uint32_t payload_ty = ipErrorUnionPayload(ip, err_union_ty); *out_ty = payload_ty; if (!semaIsAirInstRef(operand)) { if (!ipItemTagIs(ip, operand, IP_TAG_ERROR_UNION_PAYLOAD)) COMPILE_ERROR_UPSTREAM("analyzeErrUnionPayload: caught an error"); return ip->extra[ip->item_datas[operand] + 1]; } uint32_t opv = ztOnePossibleValue(ip, payload_ty); if (opv != IP_INDEX_NONE) return opv; return semaEmitTyOp(sema->insts, block, AIR_INST_UNWRAP_ERRUNION_PAYLOAD, payload_ty, operand); } // Sema.zig:analyzeErrUnionPayloadPtr // `safety_check` and `initializing` false. The pointer type is the operand's // constness at the generic address space. static uint32_t analyzeErrUnionPayloadPtr(SemaCtx* sema, U32List* block, uint32_t operand, uint32_t operand_ty, uint32_t* out_ty) { InternPool* ip = sema->ip; if (ip->item_tags[operand_ty] != IP_TAG_TYPE_POINTER) COMPILE_ERROR_UPSTREAM("analyzeErrUnionPayloadPtr: expected pointer"); uint32_t err_union_ty = ip->extra[ip->item_datas[operand_ty]]; if (!semaIsErrorUnionType(ip, err_union_ty)) COMPILE_ERROR_UPSTREAM( "analyzeErrUnionPayloadPtr: expected error union"); uint32_t payload_ty = ipErrorUnionPayload(ip, err_union_ty); bool is_const = (ip->extra[ip->item_datas[operand_ty] + 2] & IP_PTR_FLAGS_IS_CONST) != 0; uint32_t pointer_ty = ipGetPtrType(ip, payload_ty, is_const ? IP_PTR_FLAGS_CONST_ONE : IP_PTR_FLAGS_MUT_ONE); if (!semaIsAirInstRef(operand) && pointerDeref(sema->zcu, operand, NULL) != IP_INDEX_NONE) { uint32_t ptr = IP_INDEX_NONE; if (!semaErrUnionPayloadPtrComptime(sema->zcu, ip, operand, &ptr) || ptr == IP_INDEX_NONE) UNIMPLEMENTED("analyzeErrUnionPayloadPtr: comptime operand"); *out_ty = ipTypeOf(ip, ptr); return ptr; } *out_ty = pointer_ty; return semaEmitTyOp(sema->insts, block, AIR_INST_UNWRAP_ERRUNION_PAYLOAD_PTR, pointer_ty, operand); } // Sema.zig:analyzeErrUnionCode static uint32_t analyzeErrUnionCode( SemaCtx* sema, U32List* block, uint32_t operand, uint32_t operand_ty) { InternPool* ip = sema->ip; if (!semaIsAirInstRef(operand)) return ipItemTagIs(ip, operand, IP_TAG_ERROR_UNION_PAYLOAD) ? ZIR_REF_UNREACHABLE_VALUE : semaErrUnionCodeComptime(ip, operand); return semaAnalyzeErrUnionCodeRuntime( ip, sema->insts, block, operand, operand_ty); } // Sema.zig:analyzeErrUnionCodePtr static uint32_t analyzeErrUnionCodePtr( SemaCtx* sema, U32List* block, uint32_t operand, uint32_t err_union_ty) { const InternPool* ip = sema->ip; if (!semaIsAirInstRef(operand)) { uint32_t val = pointerDeref(sema->zcu, operand, NULL); if (val != IP_INDEX_NONE) return analyzeErrUnionCode(sema, block, val, err_union_ty); } return semaEmitTyOp(sema->insts, block, AIR_INST_UNWRAP_ERRUNION_ERR_PTR, ipErrorUnionErrorSet(ip, err_union_ty), operand); } // Sema.zig:analyzeIsNonErr // resolveIsNonErrVal's type and value answers, else AIR is_non_err. static uint32_t analyzeIsNonErr( SemaCtx* sema, U32List* block, uint32_t operand, uint32_t ty) { InternPool* ip = sema->ip; uint32_t known = resolveIsNonErrFromType(ip, ty, sema->fn_ret_ty); if (known != IP_INDEX_NONE) return known; if (!semaIsAirInstRef(operand)) { if (ipItemTagIs(ip, operand, IP_TAG_ERROR_UNION_PAYLOAD)) return ZIR_REF_BOOL_TRUE; if (ipItemTagIs(ip, operand, IP_TAG_ERROR_UNION_ERROR)) return ZIR_REF_BOOL_FALSE; UNIMPLEMENTED("analyzeIsNonErr: comptime operand not an error union"); } return semaEmitUnOp(sema->insts, block, AIR_INST_IS_NON_ERR, operand); } // Sema.zig:analyzePtrIsNonErr // resolvePtrIsNonErrVal's answer from the pointee's type or a comptime // pointee's value, else AIR is_non_err_ptr. static uint32_t analyzePtrIsNonErr( SemaCtx* sema, U32List* block, uint32_t operand, uint32_t ptr_ty) { InternPool* ip = sema->ip; uint32_t eu_ty = ip->extra[ip->item_datas[ptr_ty]]; uint32_t known = resolveIsNonErrFromType(ip, eu_ty, sema->fn_ret_ty); if (known != IP_INDEX_NONE) return known; if (!semaIsAirInstRef(operand)) { uint32_t val = pointerDeref(sema->zcu, operand, NULL); if (val != IP_INDEX_NONE) return analyzeIsNonErr(sema, block, val, eu_ty); } return semaEmitUnOp(sema->insts, block, AIR_INST_IS_NON_ERR_PTR, operand); } // Sema.zig:zirErrUnionPayload static void zirErrUnionPayload(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t operand = semaResolveRef(ref, sema->inst_values); sema->inst_values[inst] = analyzeErrUnionPayload(sema, block, semaResolveRefValType(sema->ip, ref, operand, sema->inst_types), operand, &sema->inst_types[inst]); } // Sema.zig:zirErrUnionPayloadPtr static void zirErrUnionPayloadPtr( SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ref = sema->zir->inst_datas[inst].un_node.operand; semaEnsurePointeeLayoutResolved(sema, ref); uint32_t operand = semaResolveRef(ref, sema->inst_values); sema->inst_values[inst] = analyzeErrUnionPayloadPtr(sema, block, operand, semaResolveRefValType(sema->ip, ref, operand, sema->inst_types), &sema->inst_types[inst]); } // Sema.zig:zirErrUnionCode static void zirErrUnionCode(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t operand = semaResolveRef(ref, sema->inst_values); uint32_t ty = semaResolveRefValType(sema->ip, ref, operand, sema->inst_types); sema->inst_values[inst] = analyzeErrUnionCode(sema, block, operand, ty); sema->inst_types[inst] = semaIsAirInstRef(operand) ? ipErrorUnionErrorSet(sema->ip, ty) : ipTypeOf(sema->ip, sema->inst_values[inst]); } // Sema.zig:zirErrUnionCodePtr static void zirErrUnionCodePtr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t operand = semaResolveRef(ref, sema->inst_values); uint32_t ptr_ty = semaResolveRefValType(sema->ip, ref, operand, sema->inst_types); if (sema->ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) COMPILE_ERROR_UPSTREAM("analyzeErrUnionCodePtr: expected pointer"); uint32_t eu_ty = sema->ip->extra[sema->ip->item_datas[ptr_ty]]; sema->inst_values[inst] = analyzeErrUnionCodePtr(sema, block, operand, eu_ty); sema->inst_types[inst] = ipErrorUnionErrorSet(sema->ip, eu_ty); } // Sema.zig:zirAsNode // Sema.zirAsNode -> analyzeAs -> coerce(operand, dest_type). ZIR data: pl_node // with a Zir.Inst.As payload { dest_type, operand }. For comptime operands: // coerces to dest_type and returns IP value. For runtime operands: // UNIMPLEMENTED (full coercion not yet ported). static void zirAsNode(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstAs extra; zirExtraData(sema->zir, pp, &extra); uint32_t as_dest_ref = extra.dest_type; uint32_t as_op_ref = extra.operand; uint32_t as_dest_ty = semaResolveRef(as_dest_ref, sema->inst_values); uint32_t as_op_val = semaResolveRef(as_op_ref, sema->inst_values); if (semaIsAirInstRef(as_dest_ty)) UNIMPLEMENTED("as_node: runtime dest type"); // A dest ref naming a not-yet-bound instruction (e.g. a call // placeholder outside the arg-analysis window) is the same // `resolveTypeOrPoison(...) orelse return operand` path as a // generic-poison destination in upstream Sema.analyzeAs. Return // the operand unchanged and keep the operand's own type. if (as_dest_ty == INST_VALUE_UNSET) { sema->inst_values[inst] = as_op_val; if (semaIsAirInstRef(as_op_val)) { sema->inst_types[inst] = semaResolveRefType(as_op_ref, sema->inst_types); } else { uint32_t as_op_ty = ipTypeOf(sema->ip, as_op_val); if (as_op_ty == IP_INDEX_NONE) as_op_ty = ipTypeOfPreinterned(as_op_val); sema->inst_types[inst] = as_op_ty; } return; } if (semaIsAirInstRef(as_op_val)) { uint32_t as_op_ty = semaResolveRefType(as_op_ref, sema->inst_types); // Sema.coerceExtra's first line, before the dest==operand // fast path or anything else: `if (dest_ty.isGenericPoison()) // return inst;` (src/Sema.zig) -- an unresolved/ // generic dest type (an `anytype` array/tuple element whose container // type is itself generic_poison) performs no coercion; the operand // passes through with its own type, not dest_ty. Sema.analyzeAs // (src/Sema.zig) makes the same decision one level earlier still // (`resolveTypeOrPoison(...) orelse return operand`), i.e. // Above any comptime/runtime distinction -- see the comptime // counterpart of this check at the tail of this arm. // Every other arm below requires as_op_ty != as_dest_ty. // The comptime-tail counterpart records the operand's type // on every comptime as_node with a poison dest, rather than // generic_poison. if (as_dest_ty == IP_INDEX_GENERIC_POISON_TYPE) { sema->inst_values[inst] = as_op_val; sema->inst_types[inst] = as_op_ty; return; } // Every other runtime `@as` is `Sema.coerceExtra`, stated // once in `semaCoerceExtraRuntime`. sema->inst_values[inst] = semaCoerceExtraRuntime(sema->zcu, as_op_val, as_op_ty, as_dest_ty, sema->insts, block, sema->air_extra, "as_node: runtime operand type coercion"); sema->inst_types[inst] = as_dest_ty; return; } // Comptime operand: coerce to dest_type. uint32_t as_coerced = semaCoerceComptime(sema->zcu, sema->ip, as_op_val, as_dest_ty); sema->inst_values[inst] = as_coerced; // Comptime counterpart of the runtime generic_poison arm above: an // unconstrained result location performs no coercion, and the result's // type is the operand's own (Sema.analyzeAs's `resolveTypeOrPoison(...) // orelse return operand`, and coerceExtra's `if // (dest_ty.isGenericPoison()) return inst;`), never generic_poison. // // The type is read off the interned value (ipTypeOf) rather than // off the ZIR operand ref: a comptime operand is frequently a // preinterned ref, and ipTypeOfPreinterned models only the handful // of preinterned refs its callers need (it aborts loudly on the // rest), whereas the value's own type is exactly upstream's // `sema.typeOf(inst)` for a comptime ref. An unmodeled value shape // aborts loudly rather than recording IP_INDEX_NONE as a type. if (as_dest_ty == IP_INDEX_GENERIC_POISON_TYPE) { uint32_t as_poison_ty = ipTypeOf(sema->ip, as_coerced); if (as_poison_ty == IP_INDEX_NONE) UNIMPLEMENTED( "as_node: generic_poison dest with untyped comptime operand"); sema->inst_types[inst] = as_poison_ty; return; } sema->inst_types[inst] = as_dest_ty; } // Sema.zig:zirAsShiftOperand // Sema.zirAsShiftOperand -> analyzeAs -> coerce: cast the shift amount to the // log2 shift type. ZIR data: pl_node with Zir.Inst.As payload {dest_type, // operand}. Handles both: // - comptime int operand (e.g. `a << 2`): intern int_small // - runtime operand coercible to dest_ty (e.g. `a << b: u3` // where the lhs type's log2 type is u6): emit the same // runtime integer cast used by ordinary Sema.coerce. static void zirAsShiftOperand(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstAs extra; zirExtraData(sema->zir, pp, &extra); uint32_t dest_type_ref = extra.dest_type; uint32_t operand_ref = extra.operand; uint32_t dest_ty = semaResolveRef(dest_type_ref, sema->inst_values); uint32_t operand_val = semaResolveRef(operand_ref, sema->inst_values); if (semaIsAirInstRef(dest_ty)) UNIMPLEMENTED("as_shift_operand: runtime dest_type"); if (semaIsAirInstRef(operand_val)) { // Runtime operand: Sema.coerce -> identity if types match, // otherwise runtime integer widening emits AIR int_cast. uint32_t op_ty = semaResolveRefType(operand_ref, sema->inst_types); uint32_t op_bits, dest_bits; bool op_signed, dest_signed; if (op_ty != dest_ty && ztIntInfo(sema->ip, op_ty, &op_bits, &op_signed) && ztIntInfo(sema->ip, dest_ty, &dest_bits, &dest_signed) && op_bits == 0 && !op_signed) { sema->inst_values[inst] = ipGetIntSmall(sema->ip, dest_ty, 0); sema->inst_types[inst] = dest_ty; return; } if (op_ty != dest_ty) operand_val = semaCoerceExtraRuntime(sema->zcu, operand_val, op_ty, dest_ty, sema->insts, block, sema->air_extra, "as_shift_operand: runtime operand type mismatch"); // Identity coerce: pass the AIR ref through unchanged. sema->inst_values[inst] = operand_val; sema->inst_types[inst] = dest_ty; return; } uint64_t n; if (ipIntValueType(sema->ip, operand_val) != IP_INDEX_NONE) { // An interned comptime int (the inline-for range capture `s` // in eqlBytes' `16 << s`). n = ipReadIntValue(sema->ip, operand_val); } else if (operand_val != INST_VALUE_UNSET && ztIsZigTypeTag(sema->ip, dest_ty, ZIG_TYPE_TAG_VECTOR)) { // A comptime vector amount: analyzeAs is `"return // sema.coerceExtra(block, dest_ty, operand, src, ...)"`. sema->inst_values[inst] = semaCoerceComptime(sema->zcu, sema->ip, operand_val, dest_ty); sema->inst_types[inst] = dest_ty; return; } else { UNIMPLEMENTED("as_shift_operand: non-CT_INT operand"); } if (dest_ty == ZIR_REF_COMPTIME_INT_TYPE) { // comptime_int shift amount (comptime_int lhs): intern it, // matching the `zir int` literal representation. `n` came from // an already-interned operand, so this adds no IP item. sema->inst_values[inst] = ipGetIntComptimeIntU64(sema->ip, n); sema->inst_types[inst] = dest_ty; return; } // Intern @as(dest_ty, n) as an int_small IP value. uint32_t coerced = ipGetIntSmall(sema->ip, dest_ty, (uint32_t)n); sema->inst_values[inst] = coerced; sema->inst_types[inst] = dest_ty; } // Sema.zig:zirIntFromPtr // Sema.zirIntFromPtr: a ptr_int operand folds to its stored address; a // data-Nav pointer emits AIR so the interpreter can supply its assigned // address; a runtime pointer emits the same AIR directly. ZIR data: un_node // {operand}. Dest type is usize. static void zirIntFromPtr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ifp_op_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t ifp_op_val = semaResolveRef(ifp_op_ref, sema->inst_values); // `"const dest_ty = if (is_vector) try pt.vectorType(.{ .child // = .usize_type, .len = len }) else .usize;"` uint32_t ifp_op_ty = semaResolveRefType(ifp_op_ref, sema->inst_types); IpArrayInfo ifp_vec; bool ifp_is_vector = ifp_op_ty != IP_INDEX_NONE && ipArrayInfo(sema->ip, ifp_op_ty, &ifp_vec) == IP_ARR_OK && ifp_vec.is_vector; uint32_t ifp_dest_ty = ifp_is_vector ? ipGetVectorType(sema->ip, ifp_vec.len, ZIR_REF_USIZE_TYPE) : ZIR_REF_USIZE_TYPE; if (ifp_is_vector && !semaIsAirInstRef(ifp_op_val)) { uint32_t ifp_ct = semaIntFromPtrVectorComptime(sema->ip, ifp_op_val, ifp_dest_ty); if (ifp_ct == IP_INDEX_NONE) UNIMPLEMENTED( "int_from_ptr: comptime vector of non-integer pointers"); sema->inst_values[inst] = ifp_ct; sema->inst_types[inst] = ifp_dest_ty; return; } if (!semaIsAirInstRef(ifp_op_val)) { // `operand_val.getUnsignedIntSema(pt)`: an optional pointer // reads `.none => 0`, else its payload. uint32_t ifp_opt = ifp_op_val != INST_VALUE_UNSET ? ifp_op_val : IP_INDEX_NONE; if (ipItemTagIs(sema->ip, ifp_opt, IP_TAG_OPT_NULL)) { sema->inst_values[inst] = ipGetIntUsize(sema->ip, 0); sema->inst_types[inst] = ZIR_REF_USIZE_TYPE; return; } if (ipItemTagIs(sema->ip, ifp_opt, IP_TAG_OPT_PAYLOAD)) ifp_op_val = sema->ip->extra[sema->ip->item_datas[ifp_opt] + 1]; if (ipIsPtrInt(sema->ip, ifp_op_val)) { uint64_t address = ipPtrIntAddress(sema->ip, ifp_op_val); if (address > UINT32_MAX) UNIMPLEMENTED("int_from_ptr: integer pointer exceeds wasm32"); sema->inst_values[inst] = ipGetIntUsize(sema->ip, (uint32_t)address); sema->inst_types[inst] = ZIR_REF_USIZE_TYPE; return; } // A direct data-Nav address is a comptime-known pointer value // but its concrete flat-memory address is assigned only by // the runtime interpreter. Emit int_from_ptr with the ptr_nav // constant operand so interpNavAddress supplies that stable, // alignment-respecting address. Other comptime pointer shapes // do not have a concrete address representation here. // An anonymous constant's address (`@intFromPtr(&const_local)` // once the local is comptime-known) is likewise assigned by // the interpreter when it lowers the uav. if (ifp_op_val >= sema->ip->items_len || (sema->ip->item_tags[ifp_op_val] != IP_TAG_PTR_NAV && !ipIsPtrUav(sema->ip, ifp_op_val) && sema->ip->item_tags[ifp_op_val] != IP_TAG_PTR_FIELD)) UNIMPLEMENTED("int_from_ptr: comptime pointer shape"); // semaRuntimeDataPtrNav validates data reached through a data // pointer. Neither other shape is: a function Nav's address // is the interpreter's function handle, and a pointer of // function type is never loaded or stored through, whatever // Nav it names (`@ptrCast(&global_array)` is validated by // nothing). Known gap: the integer of a non-heap Nav's // address does not round-trip through `@ptrFromInt`, which // maps every non-function address into the heap window. IpPtrNav ifp_pn; if (sema->ip->item_tags[ifp_op_val] == IP_TAG_PTR_NAV && ipDecodePtrNav(sema->ip, ifp_op_val, &ifp_pn) == IP_PN_OK && !ztIsZigTypeTag(sema->ip, ifp_pn.nav_type, ZIG_TYPE_TAG_FN) && !ztIsZigTypeTag(sema->ip, ifp_pn.child_type, ZIG_TYPE_TAG_FN)) semaRuntimeDataPtrNav(sema->ip, ifp_op_val, false, NULL, NULL); } // `"return block.addTyOp(.int_from_ptr, dest_ty, operand);"` sema->inst_values[inst] = semaEmitTyOp( sema->insts, block, AIR_INST_INT_FROM_PTR, ifp_dest_ty, ifp_op_val); sema->inst_types[inst] = ifp_dest_ty; } // Sema.zig:fieldPtr // `Sema.fieldPtr`: the pointer to field `field` (interned as `field_ip`) of // the object `object_ptr` points at, typed `*out_ty`; `object_ptr_ty` is a // runtime operand's type. Through a pointer to a single pointer the object // pointer is loaded first (`is_pointer_to`). static uint32_t fieldPtr(SemaCtx* sema, U32List* block, uint32_t object_ptr, uint32_t object_ptr_ty, const char* field, uint32_t field_ip, uint32_t* out_ty) { uint32_t result; uint32_t obj_ptr_val = object_ptr; // Sema.fieldPtr's `.pointer` arm: through a pointer to a single // pointer to a container, `"const inner_ptr = ... try // sema.analyzeLoad(block, src, object_ptr, object_ptr_src)"` and // the field pointer of that (a comptime-known pointer loads to a // comptime pointer). IpPtrInfo object_info, inner_info; while (obj_ptr_val < sema->ip->items_len && ipPtrInfo(sema->ip, ipTypeOf(sema->ip, obj_ptr_val), &object_info) == IP_PTRI_OK && object_info.size == IP_PTR_SIZE_ONE && ipPtrInfo(sema->ip, object_info.child, &inner_info) == IP_PTRI_OK && inner_info.size == IP_PTR_SIZE_ONE && (ipTagIsAnyStruct(sema->ip->item_tags[inner_info.child]) || ztZigTypeTag(sema->ip, inner_info.child) == ZIG_TYPE_TAG_UNION || ztIsTuple(sema->ip, inner_info.child))) { uint32_t object_loaded = pointerDeref(sema->zcu, obj_ptr_val, NULL); if (object_loaded == IP_INDEX_NONE) break; obj_ptr_val = object_loaded; } if (semaRefIsInterned(obj_ptr_val)) { uint32_t optr = obj_ptr_val; uint32_t fo_loaded, fo_loaded_ty; if (semaPtrAutoDerefOnePointer( sema->zcu, optr, &fo_loaded, &fo_loaded_ty)) optr = fo_loaded; uint32_t fn_ptr_ty; uint32_t fn_ptr = valConstNavStructFieldPtr( sema->zcu, optr, field, field_ip, &fn_ptr_ty); if (fn_ptr != IP_INDEX_NONE) { *out_ty = fn_ptr_ty; return fn_ptr; } } // A comptime pointer into a data Nav: its pointee is runtime storage, so // a field pointer stays comptime only where upstream's `ptrField` makes // one; the AIR arms below take the rest with this pointer as operand. bool runtime_nav_ptr = semaIsRuntimeDataNavPtr(sema->ip, obj_ptr_val); if (!semaIsAirInstRef(obj_ptr_val)) { // Comptime object pointer. `a.b` where the object `a` is a // comptime namespace (a container / file-root type reached via // a `ptr_nav`, e.g. an `@import` decl): fieldPtr's `.type` // case (Sema.fieldPtr -> namespaceLookupRef). Load the nav to // get the container type, resolve as a decl of its // namespace, and yield a `ptr_nav` to that member decl — a // `*const `, the same shape a `decl_ref` produces // — so a following field_call / field_ptr_load / field_ptr // consumes it identically. This is the intermediate step of a // nested access `a.b.c` (`a.b` is the receiver of `.c`). // A @typeInfo value's field answers its payload value, not a field // pointer (zig1's own; analyzeSwitchBlock takes a by-ref operand's // address for it). if (ctIsTypeInfoValue(sema->zcu, obj_ptr_val)) { uint32_t tif_ty; uint32_t tif_val = semaTypeInfoPayload(sema->zcu, obj_ptr_val, field, &tif_ty); *out_ty = tif_ty; return tif_val; } uint32_t optr = obj_ptr_val; if (sema->ip->item_tags[optr] == IP_TAG_PTR_UAV && sema->ip->extra[sema->ip->item_datas[optr] + 2] == 0 && sema->ip->extra[sema->ip->item_datas[optr] + 3] == 0) { uint32_t uav_val = sema->ip->extra[sema->ip->item_datas[optr] + 1]; if (ctIsTypeInfoValue(sema->zcu, uav_val)) { uint32_t tif_ty; uint32_t tif_val = semaTypeInfoPayload(sema->zcu, uav_val, field, &tif_ty); *out_ty = tif_ty; return tif_val; } } if (sema->ip->item_tags[obj_ptr_val] == IP_TAG_AGGREGATE || sema->ip->item_tags[obj_ptr_val] == IP_TAG_REPEATED) { // Field of a comptime struct value directly (`kvs.keys` // where `kvs = map.kvs.*` deref'd to the KVs aggregate): // immutable -> the field value stands for its pointer // (same fold-only convention as the nav-decl arm below). // A `.repeated_elem` aggregate (every field one value, // e.g. a `Type.ErrorSet` payload) answers that element // for any field, as `field_ptr_load`'s bare-aggregate // arm reads it. uint32_t fag_ty = sema->ip->extra[sema->ip->item_datas[obj_ptr_val]]; if (!ipTagIsAnyStruct(sema->ip->item_tags[fag_ty])) UNIMPLEMENTED("field_ptr: comptime aggregate not a struct"); ensureLayoutResolved(sema->zcu, fag_ty); // .ptr_access uint32_t fag_idx = ipStructFieldIndex(sema->ip, fag_ty, field_ip); if (fag_idx == UINT32_MAX) UNIMPLEMENTED("field_ptr: comptime aggregate field not found"); *out_ty = ipStructFieldType(sema->ip, fag_ty, fag_idx); return sema->ip->extra[sema->ip->item_datas[optr] + 1 + (sema->ip->item_tags[optr] == IP_TAG_REPEATED ? 0 : fag_idx)]; } uint32_t base_ty; if (sema->ip->item_tags[optr] == IP_TAG_PTR_NAV && !runtime_nav_ptr) { uint32_t base_nav = sema->ip->extra[sema->ip->item_datas[obj_ptr_val] + 1]; base_ty = zptEnsureNavValUpToDate(sema->zcu, base_nav, NULL); } else if (sema->ip->item_tags[obj_ptr_val] == IP_TAG_PTR_UAV && ipIndexIsType(sema->ip, sema->ip->extra[sema->ip->item_datas[obj_ptr_val] + 1]) // A nonzero byte_offset on a type-valued uav is an // unmodeled shape: keep it loud // below rather than silently dropping the offset. && sema->ip->extra[sema->ip->item_datas[obj_ptr_val] + 2] == 0 && sema->ip->extra[sema->ip->item_datas[obj_ptr_val] + 3] == 0) { // Comptime object pointer into an anonymous uav whose val // is itself a type (a generic instantiation has no backing Nav, so // the object pointer for the next field access is a `ptr_uav` // rather than a `ptr_nav`). Sema.fieldPtr's `.type` case // (namespaceLookupRef, via sema.analyzeLoad) treats any pointer // kind uniformly: it loads the addressee value and uses it as the // container type, independent of how that value was interned. The // ptr_uav's `val` word already is the resolved value (no // Nav initializer to load), so it stands in directly for // the PTR_NAV arm's `zptEnsureNavValUpToDate` result, and the // aggregate-decl / semaComptimeNamespaceFieldPtr handling // below is shared unmodified. base_ty = sema->ip->extra[sema->ip->item_datas[obj_ptr_val] + 1]; } else { uint32_t fo_ty; uint32_t cfp = semaStructFieldPtrComptime( sema->zcu, optr, field, field_ip, &fo_ty); if (cfp == IP_INDEX_NONE && semaFieldPtrArrayArm(sema->ip, optr, ipTypeOf(sema->ip, optr), field, sema->insts, block, &result, out_ty)) return result; // Sema.fieldPtr's slice arm, through at most one single // pointer (`is_pointer_to`). IpPtrInfo fo_obj, fo_to; bool fo_is_ptr = ipPtrInfo(sema->ip, ipTypeOf(sema->ip, obj_ptr_val), &fo_obj) == IP_PTRI_OK; bool fo_pointer_to = fo_is_ptr && ipPtrInfo(sema->ip, fo_obj.child, &fo_to) == IP_PTRI_OK && fo_to.size == IP_PTR_SIZE_ONE; uint32_t fo_inner = !fo_is_ptr ? IP_INDEX_NONE : fo_pointer_to ? fo_to.child : fo_obj.child; if (cfp == IP_INDEX_NONE && fo_inner != IP_INDEX_NONE && ztIsSlice(sema->ip, fo_inner)) { // `val.ptrField(Value.slice_ptr_index)` / // `slice_len_index` of the comptime-known slice // pointer (`analyzeLoad` of the object pointer // first, under `is_pointer_to`). uint32_t fo_sidx = strcmp(field, "ptr") == 0 ? 0u : strcmp(field, "len") == 0 ? 1u : 2u; if (fo_sidx == 2u) COMPILE_ERROR_UPSTREAM( "field_ptr: no member named this in slice"); uint32_t fo_sptr = fo_pointer_to ? pointerDeref(sema->zcu, optr, NULL) : optr; if (fo_sptr == IP_INDEX_NONE && runtime_nav_ptr) goto runtime_object; if (fo_sptr == IP_INDEX_NONE || ipIsUndef(sema->ip, fo_sptr)) UNIMPLEMENTED("field_ptr: runtime slice pointer behind a " "comptime pointer"); cfp = valPtrField(sema->zcu, fo_sptr, fo_sidx); } // unionFieldPtr's `pointerDeref` of runtime storage: `break :ct`. if (cfp == IP_INDEX_NONE && runtime_nav_ptr) goto runtime_object; if (cfp == IP_INDEX_NONE && fo_is_ptr && !fo_pointer_to && ztZigTypeTag(sema->ip, fo_obj.child) == ZIG_TYPE_TAG_UNION) cfp = semaUnionFieldPtrComptime( sema->zcu, optr, fo_obj.child, field_ip); if (cfp == IP_INDEX_NONE) UNIMPLEMENTED("field_ptr: unsupported comptime object pointer " "(not a nav or type-valued offset-free uav)"); *out_ty = ipTypeOf(sema->ip, cfp); return cfp; } if (sema->ip->item_tags[base_ty] == IP_TAG_AGGREGATE) { // Field of a comptime struct-value decl (`map.kvs` where // `map` is a folded struct const): the decl is // immutable, so the field "pointer" is the field value // (Sema resolves the comptime field access; the fold-only // convention already used by the ctEvalBody FIELD_PTR // aggregate arm). uint32_t fav_ty = sema->ip->extra[sema->ip->item_datas[base_ty]]; if (!ipTagIsAnyStruct(sema->ip->item_tags[fav_ty])) UNIMPLEMENTED( "field_ptr: comptime aggregate decl not a struct value"); uint32_t fav_idx = ipStructFieldIndex(sema->ip, fav_ty, field_ip); if (fav_idx == UINT32_MAX) UNIMPLEMENTED("field_ptr: comptime aggregate field not found"); uint32_t fav_val = sema->ip->extra[sema->ip->item_datas[base_ty] + 1 + fav_idx]; *out_ty = ipStructFieldType(sema->ip, fav_ty, fav_idx); return fav_val; } uint32_t ns_ptr; uint32_t ns_ptr_ty; if (semaComptimeNamespaceFieldPtr(sema->zcu, sema->zir, base_ty, field_ip, &ns_ptr, &ns_ptr_ty, "field_ptr: namespace type has no namespace")) { *out_ty = ns_ptr_ty; return ns_ptr; } if (base_ty >= sema->ip->items_len || (!ipTagIsAnyStruct(sema->ip->item_tags[base_ty]) && sema->ip->item_tags[base_ty] != IP_TAG_TYPE_UNION)) UNIMPLEMENTED( "field_ptr: comptime object not a namespace container"); } runtime_object:; // `sema.typeOf(object_ptr)`: a comptime operand (possibly the // pointer loaded through `is_pointer_to` above) carries its own. uint32_t ptr_ty = semaIsAirInstRef(obj_ptr_val) ? object_ptr_ty : ipTypeOf(sema->ip, obj_ptr_val); uint32_t slice_ty = sema->ip->extra[sema->ip->item_datas[ptr_ty]]; // pointee // Zig auto-derefs a single pointer during field lookup // (Sema.fieldPtr `is_pointer_to`): for `p.x` where the object `p` // is itself a single pointer `*S` (so the operand pointer is // `**S`, e.g. a by-ref `self: *S` parameter accessed via its // slot), load the inner `*S`/`*U` and take the field ptr on that. // The slice `.ptr` case below is reached directly for a `*[]T` // object. uint32_t struct_ptr = obj_ptr_val; uint32_t struct_ptr_ty = ptr_ty; uint32_t inner_ty = slice_ty; if (sema->ip->item_tags[slice_ty] == IP_TAG_TYPE_POINTER && IP_PTR_FLAGS_SIZE( sema->ip->extra[sema->ip->item_datas[slice_ty] + 2]) == IP_PTR_SIZE_ONE) { uint32_t peeled = sema->ip->extra[sema->ip->item_datas[slice_ty]]; ZigTypeTag peeled_tag = ztZigTypeTag(sema->ip, peeled); if (peeled_tag == ZIG_TYPE_TAG_STRUCT || peeled_tag == ZIG_TYPE_TAG_UNION) { inner_ty = peeled; // S / U / tuple AirInstData ld; memset(&ld, 0, sizeof(ld)); ld.pair.a = slice_ty; // ty_op.ty = loaded *S / *U ld.pair.b = obj_ptr_val; // ty_op.operand = **S uint32_t ld_idx = semaAirInstAppend(sema->insts, AIR_INST_LOAD, ld); u32ListAppend(block, ld_idx); struct_ptr = AIR_REF_INST_BASE + ld_idx; struct_ptr_ty = slice_ty; // loaded pointer supplies field flags } } if (ztIsTuple(sema->ip, inner_ty)) { // Sema.fieldPtr's tuple arm: structFieldPtr's `len` ref, else // tupleFieldPtr -> structFieldPtrByIndex. uint32_t tfp_ty; uint32_t len_ref = semaTupleLenRef(sema->ip, inner_ty, field, &tfp_ty); if (len_ref != IP_INDEX_NONE) { *out_ty = tfp_ty; return len_ref; } uint32_t ti = tupleFieldIndex(sema->ip, inner_ty, field); return structFieldPtrByIndex(sema->zcu, struct_ptr, struct_ptr_ty, inner_ty, ti, semaIsAirInstRef(struct_ptr) ? sema->insts : NULL, block, sema->air_extra, out_ty); } if (ipTagIsAnyStruct(sema->ip->item_tags[inner_ty])) { // Struct pointee: `p.x` in lvalue position (e.g. `p.x += 5`) // takes the address of the field without loading. Mirrors // fieldPtr's runtime struct path -> addStructFieldPtr: // struct_field_ptr_index_N producing a `*FieldType` pointer. // fieldPtrType carries the complete parent pointer metadata // while deriving the field's exact alignment. uint32_t struct_ty = inner_ty; // fieldPtr's `"ensureLayoutResolved(inner_ty, src, .ptr_access)"`. ensureLayoutResolved(sema->zcu, struct_ty); uint32_t fname_ip = ipGetOrPutString(sema->ip, field, (uint32_t)strlen(field)); uint32_t fidx = ipStructFieldIndex(sema->ip, struct_ty, fname_ip); if (fidx == UINT32_MAX) UNIMPLEMENTED("field_ptr struct: field not found"); uint32_t field_ptr_ty = ztFieldPtrType(sema->zcu, struct_ptr_ty, fidx); if (ipStructFieldIsComptime(sema->ip, struct_ty, fidx)) { uint32_t field_val = semaFieldDefaultValue(sema->zcu, struct_ty, fidx); if (field_val == IP_INDEX_NONE) UNIMPLEMENTED("field_ptr struct: comptime field has no value"); *out_ty = field_ptr_ty; return ipGetPtrComptimeField(sema->ip, field_ptr_ty, field_val); } *out_ty = field_ptr_ty; return semaEmitStructFieldPtr(struct_ptr, fidx, field_ptr_ty, sema->insts, block, sema->air_extra); } if (ztZigTypeTag(sema->ip, inner_ty) == ZIG_TYPE_TAG_UNION) { // Union pointee: `p.x` in lvalue position takes the address of // the payload field. Sema.unionFieldPtr emits // addStructFieldPtr; the AIR/interpreter use the union payload // offset for all field indices. This FIELD_PTR path is not a // result-location initializer, so tagged unions do not set the // tag here; struct_init_field_ptr owns initializing tag // writes. In release/no-safety mode stock emits only the // payload pointer. `union_safety_tagged` remains loud until a // safety-checking profile exists here. uint32_t union_ty = inner_ty; if (ipUnionTagUsage(sema->ip, union_ty) == IP_UNION_TAG_USAGE_SAFETY) UNIMPLEMENTED("field_ptr union: safety-tagged union lvalue"); uint32_t ufidx = ipUnionFieldIndex(sema->ip, union_ty, field_ip); if (ufidx == UINT32_MAX) UNIMPLEMENTED("field_ptr union: field not found"); if (!ensureLayoutResolved(sema->zcu, union_ty)) UNIMPLEMENTED("field_ptr union: layout unresolved"); uint32_t field_ty = semaResolveUnionFieldTypeLazy(sema->zcu, union_ty, ufidx); if (field_ty == IP_INDEX_NONE) UNIMPLEMENTED("field_ptr union: field type unresolved"); ZtTypeClass field_class = ztTypeClass(sema->ip, field_ty); if (field_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED("field_ptr union: field class unresolved"); if (field_class == ZT_CLASS_NPV) { *out_ty = ZIR_REF_NORETURN_TYPE; return semaEmitTyOp(sema->insts, block, AIR_INST_UNREACH, 0, 0); } uint32_t field_ptr_ty = ztFieldPtrType(sema->zcu, struct_ptr_ty, ufidx); *out_ty = field_ptr_ty; return semaEmitStructFieldPtr(struct_ptr, ufidx, field_ptr_ty, sema->insts, block, sema->air_extra); } if (semaFieldPtrArrayArm(sema->ip, obj_ptr_val, ptr_ty, field, sema->insts, block, &result, out_ty)) return result; // The slice arm's `is_pointer_to`: `analyzeLoad` the object // pointer, and the loaded pointer's type is `attr_ptr_ty`. if (sema->ip->item_tags[slice_ty] == IP_TAG_TYPE_POINTER && (sema->ip->extra[sema->ip->item_datas[slice_ty] + 2] & IP_PTR_FLAGS_SIZE_MASK) == IP_PTR_SIZE_ONE && ipItemTagIs(sema->ip, sema->ip->extra[sema->ip->item_datas[slice_ty]], IP_TAG_TYPE_SLICE)) { struct_ptr = semaEmitTyOp( sema->insts, block, AIR_INST_LOAD, slice_ty, struct_ptr); struct_ptr_ty = slice_ty; slice_ty = sema->ip->extra[sema->ip->item_datas[slice_ty]]; } if (!ztIsSlice(sema->ip, slice_ty)) UNIMPLEMENTED("field_ptr: non-slice object pointee"); if (strcmp(field, "len") == 0) { // Sema.fieldPtr slice `.len` arm (Sema.zig ~26234): // fieldPtrType(slice_len_index) preserves the pointer flags // other than deriving alignment for the usize field; emit // AIR ptr_slice_len_ptr. // Lvalue `self.items.len += 1` in the ArrayList instance's // addOneAssumeCapacity. uint32_t l_result_ty = ztFieldPtrType(sema->zcu, struct_ptr_ty, 1); AirInstData lp_data; memset(&lp_data, 0, sizeof(lp_data)); lp_data.pair.a = l_result_ty; // ty_op.ty (*usize) lp_data.pair.b = struct_ptr; // slice pointer uint32_t lp_idx = semaAirInstAppend( sema->insts, AIR_INST_PTR_SLICE_LEN_PTR, lp_data); u32ListAppend(block, lp_idx); *out_ty = l_result_ty; return AIR_REF_INST_BASE + lp_idx; } if (strcmp(field, "ptr") != 0) UNIMPLEMENTED("field_ptr slice: only .ptr/.len"); // Type.fieldPtrType(slice_ptr_index): preserve the complete // pointer metadata, swap child for the slice's many-item pointer // type, and derive the field alignment (underalignment survives; // irrelevant overalignment becomes `.none`). uint32_t result_ty = ztFieldPtrType(sema->zcu, struct_ptr_ty, 0); AirInstData sp_data; memset(&sp_data, 0, sizeof(sp_data)); sp_data.pair.a = result_ty; // ty_op.ty (*const [*]const T) sp_data.pair.b = struct_ptr; // slice pointer uint32_t sp_idx = semaAirInstAppend(sema->insts, AIR_INST_PTR_SLICE_PTR_PTR, sp_data); u32ListAppend(block, sp_idx); *out_ty = result_ty; return AIR_REF_INST_BASE + sp_idx; } // Sema.zig:fieldPtrLoad static uint32_t fieldPtrLoad(SemaCtx* sema, U32List* block, uint32_t object_ptr, uint32_t object_ptr_ty, const char* field, uint32_t field_ip, uint32_t* out_ty) { uint32_t field_val; bool object_is_air = semaIsAirInstRef(object_ptr); IpPtrInfo info; if (ipPtrInfo(sema->ip, object_is_air ? object_ptr_ty : ipTypeOf(sema->ip, object_ptr), &info) == IP_PTRI_OK) { // "if (try pointee_ty.onePossibleValue(pt)) |opv|" ensureLayoutResolved(sema->zcu, info.child); uint32_t opv = ztOnePossibleValue(sema->ip, info.child); if (opv != IP_INDEX_NONE && semaFieldValComptime(sema->zcu, sema->zir, sema->insts, block, opv, field, field_ip, &field_val, out_ty)) return field_val; } if (!object_is_air && semaFieldPtrLoadComptime(sema->zcu, sema->zir, sema->insts, block, object_ptr, field, field_ip, &field_val, out_ty)) return field_val; uint32_t field_ptr_ty; uint32_t field_ptr = fieldPtr(sema, block, object_ptr, object_ptr_ty, field, field_ip, &field_ptr_ty); return analyzeLoad( sema->zcu, sema->insts, block, field_ptr, field_ptr_ty, out_ty); } // Sema.zig:zirFieldPtrLoad static void zirFieldPtrLoad(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstField extra; zirExtraData(sema->zir, pp, &extra); const char* field = zirNullTerminatedString(sema->zir, extra.field_name_start); uint32_t field_ip = ipGetOrPutString(sema->ip, field, (uint32_t)strlen(field)); uint32_t object_ptr = semaResolveRef(extra.lhs, sema->inst_values); sema->inst_values[inst] = fieldPtrLoad(sema, block, object_ptr, semaResolveRefType(extra.lhs, sema->inst_types), field, field_ip, &sema->inst_types[inst]); } // Sema.zig:zirFieldPtr static void zirFieldPtr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstField extra; zirExtraData(sema->zir, pp, &extra); const char* field = zirNullTerminatedString(sema->zir, extra.field_name_start); uint32_t field_ip = ipGetOrPutString(sema->ip, field, (uint32_t)strlen(field)); sema->inst_values[inst] = fieldPtr(sema, block, semaResolveRef(extra.lhs, sema->inst_values), semaResolveRefType(extra.lhs, sema->inst_types), field, field_ip, &sema->inst_types[inst]); } // Sema.zig:zirStructInitFieldPtr // Sema.zirStructInitFieldPtr -> fieldPtr: take the address of a field of the // struct-init result pointer (used by result-location struct init, e.g. `const // p: P = .{ .x = a, .y = b }`). ZIR pl_node -> Field { lhs = object_ptr_ref, // field_name_start }. Mirrors fieldPtr's runtime path: emit // STRUCT_FIELD_PTR_INDEX_N producing a *FieldType pointer (no load — the // caller stores the field value through this pointer). The parent pointer is // the mutable alloc, so the field pointer is mutable too (0xFFFF00FC). static void zirStructInitFieldPtr( SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstField extra; zirExtraData(sema->zir, pp, &extra); uint32_t obj_ptr_ref = extra.lhs; uint32_t name_start = extra.field_name_start; const char* fname = zirNullTerminatedString(sema->zir, name_start); uint32_t fname_ip = ipGetOrPutString(sema->ip, fname, (uint32_t)strlen(fname)); uint32_t obj_ptr_val = semaResolveRef(obj_ptr_ref, sema->inst_values); // A pointer to a mutable data Nav is comptime-known as an address // but names runtime storage: the same AIR path as field_ptr's. if (!semaIsAirInstRef(obj_ptr_val) && !semaIsRuntimeDataNavPtr(sema->ip, obj_ptr_val)) { uint32_t cobj_ty = sema->ip->extra[sema->ip ->item_datas[ipTypeOf(sema->ip, obj_ptr_val)]]; if (!ipIsPtrComptimeAlloc(sema->ip, obj_ptr_val) && !semaIsCellChainPtr(sema->ip, obj_ptr_val)) { // unionFieldPtr's comptime `.@"packed", .@"extern" => {}` arm: // `"return .fromValue(try union_ptr_val.ptrField(field_index, // pt));"` (a one-possible-value alloc's ptr_uav). if (ztZigTypeTag(sema->ip, cobj_ty) != ZIG_TYPE_TAG_UNION || !ipUnionIsPacked(sema->ip, cobj_ty)) UNIMPLEMENTED( "struct_init_field_ptr: comptime object pointer"); uint32_t ufp = valPtrField(sema->zcu, obj_ptr_val, ipUnionFieldIndex(sema->ip, cobj_ty, fname_ip)); sema->inst_values[inst] = ufp; sema->inst_types[inst] = ipTypeOf(sema->ip, ufp); return; } uint32_t sfp = semaComptimeCellStructInitFieldPtr( sema->zcu, obj_ptr_val, fname_ip); sema->inst_values[inst] = sfp; sema->inst_types[inst] = ipTypeOf(sema->ip, sfp); return; } uint32_t ptr_ty = semaResolveRefType(obj_ptr_ref, sema->inst_types); uint32_t struct_ty = sema->ip->extra[sema->ip->item_datas[ptr_ty]]; if (ztZigTypeTag(sema->ip, struct_ty) == ZIG_TYPE_TAG_UNION) { // Sema.unionFieldPtr (initializing=true) runtime path // (Sema.zig): the union pointer is a runtime alloc (a // `.{ .field = v }` result location, e.g. codegen's // `const cv: CValue = .{ .local = v }`): the `tag:` block // and addStructFieldPtr (semaEmitInitUnionTag). uint32_t ufidx = ipUnionFieldIndex(sema->ip, struct_ty, fname_ip); if (ufidx == UINT32_MAX) UNIMPLEMENTED("struct_init_field_ptr: union field missing"); // A decl-bearing union keeps its decl_val-aliased payload // field types `.none` (lazy). This is a result-location init // that produces the union value, so its full layout will be // demanded — resolve every deferred field, then read this one. semaResolveAllUnionDeferred(sema->zcu, struct_ty); uint32_t ufield_ty = semaResolveUnionFieldTypeLazy(sema->zcu, struct_ty, ufidx); if (ufield_ty == IP_INDEX_NONE) UNIMPLEMENTED("struct_init_field_ptr: union field type unresolved " "(deferred payload)"); // Pointer to the active field payload (addStructFieldPtr). // `sema.typeOf(union_ptr).fieldPtrType(field_index, pt)`; // the packed arm keeps the parent's key, the others are the // mutable default-aligned pointer this alloc's field is. semaEmitInitUnionTag( sema->ip, obj_ptr_val, struct_ty, fname_ip, sema->insts, block); uint32_t ufield_ptr_ty = ipUnionIsPacked(sema->ip, struct_ty) ? ztFieldPtrType(sema->zcu, ptr_ty, ufidx) : ipGetPtrType(sema->ip, ufield_ty, IP_PTR_FLAGS_MUT_ONE); sema->inst_values[inst] = semaEmitStructFieldPtr(obj_ptr_val, ufidx, ufield_ptr_ty, sema->insts, block, sema->air_extra); sema->inst_types[inst] = ufield_ptr_ty; return; } if (!ipTagIsAnyStruct(sema->ip->item_tags[struct_ty])) UNIMPLEMENTED("struct_init_field_ptr: non-struct pointee"); ensureLayoutResolved(sema->zcu, struct_ty); // fieldPtr's .ptr_access uint32_t fidx = ipStructFieldIndex(sema->ip, struct_ty, fname_ip); if (fidx == UINT32_MAX) UNIMPLEMENTED("struct_init_field_ptr: field not found"); sema->inst_values[inst] = structFieldPtrByIndex(sema->zcu, obj_ptr_val, ptr_ty, struct_ty, fidx, sema->insts, block, sema->air_extra, &sema->inst_types[inst]); } // Sema.zig:zirFieldPtrNamedLoad static void zirFieldPtrNamedLoad( SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstFieldNamed extra; zirExtraData(sema->zir, pp, &extra); uint32_t object_ptr = semaResolveRef(extra.lhs, sema->inst_values); uint32_t field_ip = resolveConstStringIntern( sema->zcu, semaResolveRef(extra.field_name, sema->inst_values)); sema->inst_values[inst] = fieldPtrLoad(sema, block, object_ptr, semaResolveRefType(extra.lhs, sema->inst_types), ipStringBytes(sema->ip, field_ip), field_ip, &sema->inst_types[inst]); } // Sema.zig:zirFieldPtrNamed static void zirFieldPtrNamed(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstFieldNamed extra; zirExtraData(sema->zir, pp, &extra); uint32_t field_ip = resolveConstStringIntern( sema->zcu, semaResolveRef(extra.field_name, sema->inst_values)); sema->inst_values[inst] = fieldPtr(sema, block, semaResolveRef(extra.lhs, sema->inst_values), semaResolveRefType(extra.lhs, sema->inst_types), ipStringBytes(sema->ip, field_ip), field_ip, &sema->inst_types[inst]); } // Sema.zig:zirIntCast, Sema.zig:intCast // ReleaseSmall (wantSafety()=false): AIR int_cast, not int_cast_safe (ty_op: // pair.a = dest type, pair.b = operand). static void zirIntCast(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t dest_ty; uint32_t operand = semaResolveRef( semaCastDestOperand(sema, inst, &dest_ty), sema->inst_values); // `"if (try sema.isComptimeKnown(operand)) { return // sema.coerce(block, dest_ty, operand, operand_src); }"` -- the // coercion's own `fits` test is the range check, and a vector or // `comptime_int` destination is its to answer. sema->inst_values[inst] = semaIsAirInstRef(operand) ? semaEmitTyOp(sema->insts, block, AIR_INST_INT_CAST, dest_ty, operand) : semaCoerceComptime(sema->zcu, sema->ip, operand, dest_ty); } // Sema.zig:bitCastUnchecked static uint32_t bitCastUnchecked( SemaCtx* sema, U32List* block, uint32_t dest_ty, uint32_t operand) { if (!semaIsAirInstRef(operand)) { uint32_t val = bitCastVal(sema->zcu, operand, dest_ty); if (val == IP_INDEX_NONE) UNIMPLEMENTED("bitCastVal: comptime operand"); return val; } return semaEmitTyOp( sema->insts, block, AIR_INST_BIT_CAST, dest_ty, operand); } // Sema.zig:zirBitcast static void zirBitcast(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t dest_ty; uint32_t operand = semaResolveRef( semaCastDestOperand(sema, inst, &dest_ty), sema->inst_values); // Sema.bitCast: `"try sema.ensureLayoutResolved(dest_ty, src, .init);"`, // then bitCastUnchecked. ensureLayoutResolved(sema->zcu, dest_ty); sema->inst_values[inst] = bitCastUnchecked(sema, block, dest_ty, operand); } // Sema.zig:zirIntFromEnum // `@intFromEnum` on an enum operand yields its integer tag value (typed at the // enum's int tag type). ZIR data: un_node {operand}. Comptime operand (an // `enum_tag` value): the tag's backing int is already interned // (ipEnumTagValue) — return it directly, no AIR. Runtime operand: AIR // bit_cast(int_tag_ty, operand) (Sema.zirIntFromEnum -> requireRuntimeBlock // -> block.addTyOp(.bit_cast, ...)). A runtime tagged-union operand is first // extracted via Sema.unionToTag, then follows the same enum-tag conversion // path. static void zirIntFromEnum(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ife_op_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t ife_op_val = semaResolveRef(ife_op_ref, sema->inst_values); if (semaIsAirInstRef(ife_op_val)) { uint32_t ife_enum_ty = semaResolveRefType(ife_op_ref, sema->inst_types); uint32_t ife_enum_tag_ty; uint32_t ife_enum_tag_ref; if (ipIsEnumType(sema->ip, ife_enum_ty)) { // Any enum type (auto, or an explicit/nonexhaustive enum): its int // tag type drives the bit_cast. ipEnumTypeIntTagType reads the // stored tag type regardless of enum kind. ife_enum_tag_ty = ife_enum_ty; ife_enum_tag_ref = ife_op_val; } else if (ife_enum_ty < sema->ip->items_len && sema->ip->item_tags[ife_enum_ty] == IP_TAG_TYPE_UNION) { // Tagged union operand: Sema.zirIntFromEnum first routes // through unionToTag. Reuse the switch_block helper that // already implements Sema.unionToTag's // runtime/no-runtime-tag split and emits get_union_tag // when needed. ife_enum_tag_ref = semaUnionToTagRuntime(sema->zcu, ife_enum_ty, ife_op_val, sema->insts, block, &ife_enum_tag_ty); } else { UNIMPLEMENTED("int_from_enum: runtime non-enum operand"); } uint32_t ife_int_ty = ipEnumTypeIntTagType(sema->ip, ife_enum_tag_ty); if (!semaIsAirInstRef(ife_enum_tag_ref)) { if (sema->ip->item_tags[ife_enum_tag_ref] != IP_TAG_ENUM_TAG) UNIMPLEMENTED("int_from_enum: runtime union tag not enum-tag"); uint32_t ife_int = ipEnumTagValue(sema->ip, ife_enum_tag_ref); sema->inst_values[inst] = ife_int; sema->inst_types[inst] = ipIntValueType(sema->ip, ife_int); return; } AirInstData ife_data; memset(&ife_data, 0, sizeof(ife_data)); ife_data.pair.a = ife_int_ty; ife_data.pair.b = ife_enum_tag_ref; uint32_t ife_air_idx = semaAirInstAppend(sema->insts, AIR_INST_BIT_CAST, ife_data); u32ListAppend(block, ife_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + ife_air_idx; sema->inst_types[inst] = ife_int_ty; return; } if (ife_op_val >= sema->ip->items_len) UNIMPLEMENTED("int_from_enum: comptime operand shape"); // A union operand is `unionToTag`'s comptime `"val.unionTag(zcu)"`, then // `"enum_tag_val.intFromEnum(zcu)"`. const InternPool* ip = sema->ip; if (ipItemTagIs(ip, ife_op_val, IP_TAG_UNION_VALUE)) { if (ipUnionTagUsage(ip, ipTypeOf(ip, ife_op_val)) != IP_UNION_TAG_USAGE_TAGGED) COMPILE_ERROR_UPSTREAM("int_from_enum: untagged union cannot be " "converted to integer"); ife_op_val = ipUnionValueTag(ip, ife_op_val); } if (!ipItemTagIs(ip, ife_op_val, IP_TAG_ENUM_TAG)) UNIMPLEMENTED("int_from_enum: comptime non-enum-tag operand"); uint32_t ife_int = ipEnumTagValue(ip, ife_op_val); sema->inst_values[inst] = ife_int; sema->inst_types[inst] = ipIntValueType(sema->ip, ife_int); } // Sema.zig:zirBackingInt // Sema.zirBackingInt: a packed struct or union is its own int-backed operand, // `"return block.addTyOp(.bit_cast, backing_int_ty, int_backed_ref);"` (a // comptime one `.fromValue(int_backed_val.backingInt(zcu))`); an enum or a // tagged union is zirIntFromEnum's sequence. static void zirBackingInt(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t bi_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t bi_val = semaResolveRef(bi_ref, sema->inst_values); uint32_t bi_ty = semaResolveRefValType(sema->ip, bi_ref, bi_val, sema->inst_types); if (bi_ty < sema->ip->items_len && ipTypeIsBitpack(sema->ip, bi_ty)) { uint32_t bi_int_ty = ipBitpackBackingInt(sema->ip, bi_ty); sema->inst_types[inst] = bi_int_ty; if (!semaIsAirInstRef(bi_val)) { uint32_t bi_res = bitCastVal(sema->zcu, bi_val, bi_int_ty); if (bi_res == IP_INDEX_NONE) UNIMPLEMENTED("backing_int: comptime bitpack"); sema->inst_values[inst] = bi_res; return; } sema->inst_values[inst] = semaEmitTyOp( sema->insts, block, AIR_INST_BIT_CAST, bi_int_ty, bi_val); return; } zirIntFromEnum(sema, block, inst); } // Sema.zig:zirFromBackingIntArgTy // Sema.zirFromBackingIntArgTy: the `.remove_eu_opt` destination's backing // integer type, after `"try sema.ensureLayoutResolved(dest_ty, src, // .init);"`. The non-packed and ambiguous-mode rejections are compile errors. static void zirFromBackingIntArgTy(SemaCtx* sema, uint32_t inst) { uint32_t fbat_val = semaResolveRef( sema->zir->inst_datas[inst].un_node.operand, sema->inst_values); if (semaIsAirInstRef(fbat_val)) UNIMPLEMENTED("from_backing_int_arg_ty: runtime type"); uint32_t fbat_ty = semaResolveRemoveEuOptDestType(sema->ip, fbat_val); if (!ensureLayoutResolved(sema->zcu, fbat_ty)) UNIMPLEMENTED("from_backing_int_arg_ty: dest layout"); sema->inst_values[inst] = semaBackingIntType(sema->ip, fbat_ty); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirFromBackingInt // Sema.zirFromBackingInt: Bin { lhs = dest type, rhs = operand }; the operand // is coerced to the backing integer; a comptime one is `pt.bitpackValue` / // `pt.enumValue`, a runtime one `"return block.addTyOp(.bit_cast, dest_ty, // backing_int_ref);"` (`bit_cast_safe` only under `block.wantSafety()`, off // here). static void zirFromBackingInt(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t fbi_dest_ty; uint32_t fbi_op_ref = semaCastDestOperand(sema, inst, &fbi_dest_ty); uint32_t fbi_op_val = semaResolveRef(fbi_op_ref, sema->inst_values); uint32_t fbi_int_ty = semaBackingIntType(sema->ip, fbi_dest_ty); if (!semaIsAirInstRef(fbi_op_val)) { uint32_t fbi_int = semaCoerceComptime(sema->zcu, sema->ip, fbi_op_val, fbi_int_ty); if (ipIsEnumType(sema->ip, fbi_dest_ty)) { sema->inst_values[inst] = semaEnumFromIntValue(sema->ip, fbi_dest_ty, fbi_int); return; } uint32_t fbi_res = bitCastVal(sema->zcu, fbi_int, fbi_dest_ty); if (fbi_res == IP_INDEX_NONE) UNIMPLEMENTED("from_backing_int: comptime bitpack"); sema->inst_values[inst] = fbi_res; return; } uint32_t fbi_coerced = semaCoerceExtraRuntime(sema->zcu, fbi_op_val, semaResolveRefType(fbi_op_ref, sema->inst_types), fbi_int_ty, sema->insts, block, sema->air_extra, "from_backing_int: operand coercion"); sema->inst_values[inst] = semaEmitTyOp( sema->insts, block, AIR_INST_BIT_CAST, fbi_dest_ty, fbi_coerced); } // Sema.zig:zirFloatCast, Air.zig:internedToRef // Sema.zirFloatCast. ZIR data: pl_node with Bin payload {lhs=dest_type_ref, // rhs=operand_ref}, the same typeCast builtin shape as int_cast/bitcast. // Comptime operands reuse coerceExtra's comptime_float -> fixed-width float // interning path. Runtime floats emit fptrunc on narrowing, otherwise fpext. static void zirFloatCast(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t fc_dest_ty; uint32_t fc_op_ref = semaCastDestOperand(sema, inst, &fc_dest_ty); uint32_t fc_op_val = semaResolveRef(fc_op_ref, sema->inst_values); // `"const dest_is_comptime_float = switch // (dest_scalar_ty.zigTypeTag(zcu)) { .comptime_float => true, // .float => false, else => return sema.fail(...)"` -- asked of // the scalar type, so a vector destination is one too. uint32_t fc_dst_bits; uint32_t fc_dest_scalar = semaFloatDestScalar(sema->ip, fc_dest_ty, &fc_dst_bits); if (!semaIsAirInstRef(fc_op_val)) { // "if (sema.resolveValue(operand)) |operand_val| { if // (!is_vector) { return ... operand_val.floatCast(dest_ty, // pt) ... }" else per lane -- no exactness test, unlike // coerceExtra. uint32_t coerced = semaCastLanes(sema->ip, fc_op_val, fc_dest_ty, valFloatCast); if (coerced == IP_INDEX_NONE) UNIMPLEMENTED("float_cast: comptime operand not coercible"); sema->inst_values[inst] = coerced; sema->inst_types[inst] = fc_dest_ty; return; } if (fc_dest_scalar == ZIR_REF_COMPTIME_FLOAT_TYPE) COMPILE_ERROR_UPSTREAM("float_cast: unable to cast runtime " "value to 'comptime_float'"); uint32_t fc_op_ty = semaResolveRefType(fc_op_ref, sema->inst_types); uint32_t fc_src_bits; if (!ztSimpleFloatBits(ipScalarType(sema->ip, fc_op_ty), &fc_src_bits)) UNIMPLEMENTED("float_cast: non-float operand type"); if (fc_op_ty == fc_dest_ty) { sema->inst_values[inst] = fc_op_val; sema->inst_types[inst] = fc_dest_ty; return; } AirInstData fc_data; memset(&fc_data, 0, sizeof(fc_data)); fc_data.pair.a = fc_dest_ty; fc_data.pair.b = fc_op_val; AirInstTag fc_tag = (fc_dst_bits >= fc_src_bits) ? AIR_INST_FPEXT : AIR_INST_FPTRUNC; uint32_t fc_air_idx = semaAirInstAppend(sema->insts, fc_tag, fc_data); u32ListAppend(block, fc_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + fc_air_idx; sema->inst_types[inst] = fc_dest_ty; } // `sema.resolveDefinedValue(block, src, elem_index)` then `toUnsignedInt`, // for an index already coerced to usize: false for a runtime index. static bool semaResolveDefinedIndex( const InternPool* ip, uint32_t elem_index, uint64_t* out) { if (semaIsAirInstRef(elem_index)) return false; if (ipIsUndef(ip, elem_index)) COMPILE_ERROR_UPSTREAM("use of undefined value here causes illegal " "behavior"); if (!semaTryReadElemIndex(ip, elem_index, out)) UNIMPLEMENTED("elemVal: comptime index is not a readable integer"); return true; } // (part of) Sema.zig:elemPtr // An element pointer into a comptime allocation: zig1's cell pointers carry a // byte offset instead of upstream's `ptrElem` base chain. static uint32_t semaElemPtrComptimeAlloc( SemaCtx* sema, uint32_t ptr, uint32_t elem_index, uint32_t* out_ty) { // Element pointer into a comptime-mutable array allocation // (`inline for (&xs) |*p|` where `xs` is `comptime var`): // Sema.elemPtr returns another comptime_alloc pointer with // the same allocation base and a byte offset to element `i`. // The allocation's contents are not read here; subsequent // load/store through the element pointer handles the offset. uint32_t cell = ipPtrComptimeAllocIndex(sema->ip, ptr); uint32_t ptr_ty = sema->ip->extra[sema->ip->item_datas[ptr]]; if (ptr_ty >= sema->ip->items_len || sema->ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("elem_ptr_node: comptime alloc bad pointer type"); uint32_t ptr_d = sema->ip->item_datas[ptr_ty]; uint32_t array_ty = sema->ip->extra[ptr_d + IP_TYPE_POINTER_CHILD]; uint32_t flags = sema->ip->extra[ptr_d + IP_TYPE_POINTER_FLAGS]; if (IP_PTR_FLAGS_SIZE(flags) != IP_PTR_SIZE_ONE) UNIMPLEMENTED("elem_ptr_node: comptime alloc non-array pointer"); uint64_t index; if (!semaTryReadElemIndex(sema->ip, elem_index, &index)) { // Outside a comptime scope a runtime index into a // `comptime var`'s allocation is a stock compile error // ("values of type 'comptime_int' must be comptime-known" // via the comptime-only pointer), not a missing feature. if (!sema->ictx->ct_scope && semaIsAirInstRef(elem_index)) COMPILE_ERROR_UPSTREAM( "elem_ptr_node: runtime index into comptime " "alloc"); // In a comptime scope the index is a comptime value by // construction (Sema.zirAlloc's comptime arm makes every // local a cell); an AIR ref reaching here means some // operand upstream of it is not modeled. UNIMPLEMENTED("elem_ptr_node: comptime alloc runtime index"); } if (ztZigTypeTag(sema->ip, array_ty) == ZIG_TYPE_TAG_VECTOR) { // Sema.elemPtr's `.vector` arm -> elemPtrVector on a // Comptime vector pointer: `"return .fromValue(try // pt.getCoerced(ptr_val, elem_ptr_ty));"` -- the pointer // Value is unchanged (same allocation, same byte offset; // there is no element hop), only retyped. The element is // the new type's `vector_index`. if (index > UINT32_MAX) COMPILE_ERROR_UPSTREAM("elem_ptr: index outside vector"); uint32_t vec_ptr_ty = semaElemPtrVectorType(sema->zcu, ptr_ty, (uint32_t)index, NULL); *out_ty = vec_ptr_ty; return ipGetPtrComptimeAllocOffset(sema->ip, vec_ptr_ty, cell, ipPtrComptimeAllocByteOffset(sema->ip, ptr)); } uint32_t tuple_ptr = semaComptimeCellTupleElemPtr(sema->zcu, ptr, index); if (tuple_ptr != IP_INDEX_NONE) { *out_ty = ipTypeOf(sema->ip, tuple_ptr); return tuple_ptr; } if (ztZigTypeTag(sema->ip, array_ty) == ZIG_TYPE_TAG_POINTER) { // elemPtr's `else` arm on a comptime var holding a // pointer: analyzeLoad it, then index one layer down. uint32_t loaded = semaElemPtrComptime(sema->zcu, ptr, index, false, out_ty); if (loaded == IP_INDEX_NONE) UNIMPLEMENTED( "elem_ptr_node: comptime alloc runtime pointer load"); return loaded; } uint32_t len, elem_ty, sentinel; if (!ztArrayTypeInfo(sema->ip, array_ty, &len, &elem_ty, &sentinel)) UNIMPLEMENTED("elem_ptr_node: comptime alloc non-array child"); if (index >= len) UNIMPLEMENTED("elem_ptr_node: comptime alloc index out of bounds"); uint64_t elem_size, elem_align, elem_bits; if (!ztLayoutInfo( sema->zcu, sema->ip, elem_ty, &elem_size, &elem_align, &elem_bits) || elem_size == 0) UNIMPLEMENTED("elem_ptr_node: comptime alloc element layout"); uint64_t base_off = ipPtrComptimeAllocByteOffset(sema->ip, ptr); uint64_t byte_off = base_off + index * elem_size; uint32_t elem_ptr_ty = ipGetPtrType(sema->ip, elem_ty, flags); *out_ty = elem_ptr_ty; return ipGetPtrComptimeAllocOffset(sema->ip, elem_ptr_ty, cell, byte_off); } // Type.zig:elemPtrType // `ptr_ty.elemPtrType(maybe_index, pt)` for a usize `elem_index`. static uint32_t semaElemPtrTypeOf( Zcu* zcu, uint32_t ptr_ty, uint32_t elem_index) { uint64_t index; if (!semaResolveDefinedIndex(&zcu->intern_pool, elem_index, &index)) return ztElemPtrTypeRuntimeIndex(zcu, ptr_ty, NULL); if (index > UINT32_MAX) UNIMPLEMENTED("elemPtr: wide comptime index"); return ztElemPtrType(zcu, ptr_ty, (uint32_t)index, NULL); } // Sema.zig:elemPtrVector // D26: a lane pointer into a mutable Nav's vector keeps the runtime form. static uint32_t elemPtrVector(SemaCtx* sema, U32List* block, uint32_t vector_ptr, uint32_t vector_ptr_ty, uint32_t elem_index, uint32_t* out_ty) { uint32_t index = semaVectorElemIndex(sema->ip, elem_index); if (!semaIsAirInstRef(vector_ptr) && !semaIsRuntimeMutableDataNavPtr(sema->ip, vector_ptr)) return semaElemPtrComptime(sema->zcu, vector_ptr, index, true, out_ty); return semaEmitVectorElemPtr(sema->zcu, vector_ptr_ty, vector_ptr, index, out_ty, sema->insts, block); } // Sema.zig:elemPtrArray static uint32_t elemPtrArray(SemaCtx* sema, U32List* block, uint32_t array_ptr, uint32_t array_ptr_ty, uint32_t elem_index, uint32_t elem_ty, uint32_t* out_ty) { uint64_t index; bool index_known = semaResolveDefinedIndex(sema->ip, elem_index, &index); if (!semaIsAirInstRef(array_ptr) && index_known) return semaElemPtrComptime(sema->zcu, array_ptr, index, true, out_ty); *out_ty = semaElemPtrTypeOf(sema->zcu, array_ptr_ty, elem_index); // A comptime index with a comptime-only element means zig1 did not fold // a pointer upstream holds comptime-known, which is not the program's // error. if (!index_known) validateRuntimeElemAccess(sema->zcu, elem_ty); // "zero-bit child type; just bitcast the pointer" if (ztAbiSize(sema->zcu, elem_ty) == 0) return semaEmitTyOp( sema->insts, block, AIR_INST_PTR_CAST, *out_ty, array_ptr); return semaEmitPtrElemPtr( array_ptr, elem_index, *out_ty, sema->insts, block, sema->air_extra); } // Sema.zig:elemPtrSlice static uint32_t elemPtrSlice(SemaCtx* sema, U32List* block, uint32_t slice, uint32_t slice_ty, uint32_t elem_index, uint32_t elem_ty, uint32_t* out_ty) { uint32_t many_ty = sema->ip->item_datas[slice_ty]; uint64_t index; if (!semaIsAirInstRef(slice) && semaResolveDefinedIndex(sema->ip, elem_index, &index)) return semaElemPtrComptime(sema->zcu, slice, index, true, out_ty); *out_ty = semaElemPtrTypeOf(sema->zcu, many_ty, elem_index); // "zero-bit child type; just extract the pointer and bitcast it" if (ztAbiSize(sema->zcu, elem_ty) == 0) return semaEmitTyOp(sema->insts, block, AIR_INST_PTR_CAST, *out_ty, semaEmitTyOp( sema->insts, block, AIR_INST_SLICE_PTR, many_ty, slice)); return semaEmitTyPlBin(sema->insts, block, sema->air_extra, AIR_INST_SLICE_ELEM_PTR, *out_ty, slice, elem_index); } // Sema.zig:tupleElemPtr static uint32_t tupleElemPtr(SemaCtx* sema, U32List* block, uint32_t tuple_ptr, uint32_t tuple_ptr_ty, uint32_t tuple_ty, uint32_t elem_index, uint32_t* out_ty) { uint64_t index; if (!semaResolveDefinedIndex(sema->ip, elem_index, &index)) COMPILE_ERROR_UPSTREAM("unable to resolve comptime value: tuple " "field access index must be comptime-known"); if (index >= ipTupleFieldCount(sema->ip, tuple_ty)) COMPILE_ERROR_UPSTREAM("index out of bounds of tuple"); return structFieldPtrByIndex(sema->zcu, tuple_ptr, tuple_ptr_ty, tuple_ty, (uint32_t)index, semaIsAirInstRef(tuple_ptr) ? sema->insts : NULL, block, sema->air_extra, out_ty); } // `elemPtr`'s and `elemPtrOneLayerOnly`'s shared `.vector`, `.array` and // `.struct` switch over the child of a single-item pointer. False when the // child is none of them. static bool semaElemPtrOfChild(SemaCtx* sema, U32List* block, uint32_t ptr, uint32_t ptr_ty, uint32_t child_ty, uint32_t elem_index, uint32_t* out, uint32_t* out_ty) { IpArrayInfo array; if (ipArrayInfo(sema->ip, child_ty, &array) == IP_ARR_OK) *out = array.is_vector ? elemPtrVector(sema, block, ptr, ptr_ty, elem_index, out_ty) : elemPtrArray( sema, block, ptr, ptr_ty, elem_index, array.child, out_ty); else if (ztIsTuple(sema->ip, child_ty)) *out = tupleElemPtr( sema, block, ptr, ptr_ty, child_ty, elem_index, out_ty); else return false; return true; } // Sema.zig:elemPtrOneLayerOnly // `indexable` is a slice or pointer of type `indexable_ty`, its child's layout // resolved; `elem_index` is a usize. static uint32_t elemPtrOneLayerOnly(SemaCtx* sema, U32List* block, uint32_t indexable, uint32_t indexable_ty, uint32_t elem_index, uint32_t* out_ty) { IpPtrInfo info; if (!ztPtrOrSliceInfo(sema->ip, indexable_ty, &info)) UNIMPLEMENTED("elemPtr: indexable is not a pointer"); if (info.size == IP_PTR_SIZE_SLICE) return elemPtrSlice(sema, block, indexable, indexable_ty, elem_index, info.child, out_ty); if (info.size == IP_PTR_SIZE_ONE) { if (ipIsPtrComptimeAlloc(sema->ip, indexable)) return semaElemPtrComptimeAlloc( sema, indexable, elem_index, out_ty); uint32_t elem_ptr; if (!semaElemPtrOfChild(sema, block, indexable, indexable_ty, info.child, elem_index, &elem_ptr, out_ty)) UNIMPLEMENTED("elemPtr: single pointer to a non-indexable type"); return elem_ptr; } // `.many, .c` uint64_t index; if (!semaIsAirInstRef(indexable) && semaResolveDefinedIndex(sema->ip, elem_index, &index)) return semaElemPtrComptime(sema->zcu, indexable, index, true, out_ty); *out_ty = semaElemPtrTypeOf(sema->zcu, indexable_ty, elem_index); // "zero-bit child type; just bitcast the pointer" if (ztAbiSize(sema->zcu, info.child) == 0) return semaEmitTyOp( sema->insts, block, AIR_INST_PTR_CAST, *out_ty, indexable); return semaEmitPtrElemPtr( indexable, elem_index, *out_ty, sema->insts, block, sema->air_extra); } // Sema.zig:elemPtr // The pointer to element `elem_index` (a usize) of what `indexable_ptr`, of // type `indexable_ptr_ty`, points at; `*out_ty` is its type. static uint32_t elemPtr(SemaCtx* sema, U32List* block, uint32_t indexable_ptr, uint32_t indexable_ptr_ty, uint32_t elem_index, uint32_t* out_ty) { semaEnsurePointeeLayoutResolvedTy(sema, indexable_ptr_ty); if (ipIsPtrComptimeAlloc(sema->ip, indexable_ptr)) return semaElemPtrComptimeAlloc( sema, indexable_ptr, elem_index, out_ty); IpPtrInfo info; if (!ztPtrOrSliceInfo(sema->ip, indexable_ptr_ty, &info)) UNIMPLEMENTED("elemPtr: operand is not a pointer"); // zig1's fieldPtr hands a comptime slice field's value back for its // pointer. if (info.size == IP_PTR_SIZE_SLICE) return elemPtrOneLayerOnly( sema, block, indexable_ptr, indexable_ptr_ty, elem_index, out_ty); uint32_t elem_ptr; if (semaElemPtrOfChild(sema, block, indexable_ptr, indexable_ptr_ty, info.child, elem_index, &elem_ptr, out_ty)) return elem_ptr; uint32_t indexable_ty; uint32_t indexable = analyzeLoad(sema->zcu, sema->insts, block, indexable_ptr, indexable_ptr_ty, &indexable_ty); semaEnsurePointeeLayoutResolvedTy(sema, indexable_ty); return elemPtrOneLayerOnly( sema, block, indexable, indexable_ty, elem_index, out_ty); } // Sema.zig:validateRuntimeValue // `"if (val.canMutateComptimeVarState(zcu)) ... "runtime value contains // reference to comptime var""` for a comptime operand used at runtime. static void validateRuntimeValue(InternPool* ip, uint32_t operand) { if (!semaIsAirInstRef(operand) && valCanMutateComptimeVarState(ip, operand)) COMPILE_ERROR_UPSTREAM( "runtime value contains reference to comptime var"); } // Sema.zig:tupleField static uint32_t tupleField(SemaCtx* sema, U32List* block, uint32_t tuple, uint32_t tuple_ty, uint32_t field_index, uint32_t* out_ty) { uint32_t field_count = ipTupleFieldCount(sema->ip, tuple_ty); if (field_count == 0) COMPILE_ERROR_UPSTREAM("indexing into empty tuple is not allowed"); if (field_index >= field_count) COMPILE_ERROR_UPSTREAM("index outside tuple"); uint32_t field_val; if (semaTupleFieldValueComptime( sema->ip, tuple_ty, field_index, out_ty, &field_val)) return field_val; // comptime field if (!semaIsAirInstRef(tuple)) { tupleFieldValByIndex(sema->ip, tuple, field_index, &field_val, out_ty); return field_val; } validateRuntimeElemAccess(sema->zcu, *out_ty); return semaEmitAggFieldVal( sema->insts, block, sema->air_extra, tuple, field_index, *out_ty); } // Sema.zig:elemValArray // Also the vector arm. ReleaseSmall adds no bounds check. static uint32_t elemValArray(SemaCtx* sema, U32List* block, uint32_t array, uint32_t array_ty, uint32_t elem_index, uint32_t* out_ty) { InternPool* ip = sema->ip; IpArrayInfo info; if (ipArrayInfo(ip, array_ty, &info) != IP_ARR_OK || info.storage_len_overflowed) UNIMPLEMENTED( "elemVal: indexable is no array, vector, pointer or tuple"); *out_ty = info.child; if (info.storage_len == 0) COMPILE_ERROR_UPSTREAM("indexing into empty array is not allowed"); uint64_t index; bool index_known = semaResolveDefinedIndex(ip, elem_index, &index); if (index_known) { if (info.sentinel != IP_INDEX_NONE && index == info.len) return info.sentinel; if (index >= (uint64_t)info.storage_len) COMPILE_ERROR_UPSTREAM("index outside array length"); } if (!semaIsAirInstRef(array)) { if (ipIsUndef(ip, array)) return ipGetUndef(ip, info.child); if (index_known) return valElemValue(ip, array, (uint32_t)index, "elemValArray"); // "Since the array is comptime-known, it might be OPV, in which // case the index is irrelevant." uint32_t opv = ztOnePossibleValue(ip, info.child); if (opv != IP_INDEX_NONE) return opv; } validateRuntimeElemAccess(sema->zcu, info.child); validateRuntimeValue(ip, array); return semaRuntimeBinOp( sema->insts, block, AIR_INST_ARRAY_ELEM_VAL, array, elem_index); } // Sema.zig:elemValSlice // ReleaseSmall adds no bounds check. static uint32_t elemValSlice(SemaCtx* sema, U32List* block, uint32_t slice, uint32_t slice_ty, uint32_t elem_index, uint32_t* out_ty) { InternPool* ip = sema->ip; IpPtrInfo info; if (!ztPtrOrSliceInfo(ip, slice_ty, &info)) UNREACHABLE("elemValSlice: not a slice type"); *out_ty = info.child; uint64_t index; bool index_known = semaResolveDefinedIndex(ip, elem_index, &index); if (!semaIsAirInstRef(slice)) { if (ipIsUndef(ip, slice)) COMPILE_ERROR_UPSTREAM("use of undefined value here causes " "illegal behavior"); uint64_t len_s = ipReadIntValue(ip, ipPtrSliceLen(ip, slice)) + (info.sentinel != IP_INDEX_NONE ? 1u : 0u); if (len_s == 0) COMPILE_ERROR_UPSTREAM("indexing into empty slice is not allowed"); if (index_known) { // `"const elem_ptr_val = try slice_val.ptrElem(index, pt); // return sema.analyzeLoad(...)"` uint32_t elem_ptr_ty; uint32_t elem_ptr = semaElemPtrComptime( sema->zcu, slice, index, false, &elem_ptr_ty); return analyzeLoad( sema->zcu, sema->insts, block, elem_ptr, elem_ptr_ty, out_ty); } } uint32_t opv = ztOnePossibleValue(ip, info.child); if (opv != IP_INDEX_NONE) return opv; validateRuntimeElemAccess(sema->zcu, info.child); validateRuntimeValue(ip, slice); return semaRuntimeBinOp( sema->insts, block, AIR_INST_SLICE_ELEM_VAL, slice, elem_index); } // Sema.zig:elemVal // `indexable[elem_index_uncasted]` for an indexable value of type // `indexable_ty`, typed `*out_ty`. ReleaseSmall adds no bounds checks, so // upstream's `oob_safety` is not a parameter. static uint32_t elemVal(SemaCtx* sema, U32List* block, uint32_t indexable, uint32_t indexable_ty, uint32_t elem_index_uncasted, uint32_t elem_index_ty, uint32_t* out_ty) { InternPool* ip = sema->ip; uint32_t elem_index = coerce(sema->zcu, sema->insts, block, sema->air_extra, ZIR_REF_USIZE_TYPE, elem_index_uncasted, elem_index_ty); IpPtrInfo ptr_info; if (ztPtrOrSliceInfo(ip, indexable_ty, &ptr_info)) { uint32_t child_ty = ptr_info.child; ensureLayoutResolved(sema->zcu, child_ty); if (ptr_info.size == IP_PTR_SIZE_SLICE) return elemValSlice( sema, block, indexable, indexable_ty, elem_index, out_ty); uint64_t index; bool index_known = semaResolveDefinedIndex(ip, elem_index, &index); if (ptr_info.size != IP_PTR_SIZE_ONE) { // `.many, .c` if (!semaIsAirInstRef(indexable) && index_known) { uint32_t many_ptr_ty = ipGetPtrType(ip, child_ty, IP_PTR_FLAGS_CONST_MANY); uint32_t many_ptr_val = ipRetypePtrValue(ip, indexable, many_ptr_ty); if (many_ptr_val == IP_INDEX_NONE) UNIMPLEMENTED("elemVal: comptime pointer carrier"); uint32_t elem_ptr_ty; uint32_t elem_ptr = semaElemPtrComptime( sema->zcu, many_ptr_val, index, false, &elem_ptr_ty); return analyzeLoad(sema->zcu, sema->insts, block, elem_ptr, elem_ptr_ty, out_ty); } validateRuntimeElemAccess(sema->zcu, child_ty); *out_ty = child_ty; switch (ztTypeClass(ip, child_ty)) { case ZT_CLASS_OPV: return ztOnePossibleValue(ip, child_ty); case ZT_CLASS_NPV: COMPILE_ERROR_UPSTREAM("cannot load uninstantiable type"); default: break; } return semaRuntimeBinOp(sema->insts, block, AIR_INST_PTR_ELEM_VAL, indexable, elem_index); } // `.one`: `arr_sent`, then elemPtr and analyzeLoad, whose comptime // halves semaElemValComptime shares with ctEvalBody. if (index_known) { uint32_t sentinel = semaElemValArrSent(ip, indexable_ty, index); if (sentinel != IP_INDEX_NONE) { *out_ty = ipTypeOf(ip, sentinel); return sentinel; } } if (index_known && !semaIsAirInstRef(indexable)) { uint32_t elem = semaElemValComptime(sema->zcu, indexable, index, out_ty); if (elem != IP_INDEX_NONE) return elem; } uint32_t elem_ptr_ty; uint32_t elem_ptr = elemPtr( sema, block, indexable, indexable_ty, elem_index, &elem_ptr_ty); return analyzeLoad( sema->zcu, sema->insts, block, elem_ptr, elem_ptr_ty, out_ty); } if (ztIsTuple(ip, indexable_ty)) { // Tuple field access. uint64_t index; if (!semaResolveDefinedIndex(ip, elem_index, &index)) COMPILE_ERROR_UPSTREAM("unable to resolve comptime value: tuple " "field access index must be " "comptime-known"); if (index > UINT32_MAX) COMPILE_ERROR_UPSTREAM("index outside tuple"); return tupleField( sema, block, indexable, indexable_ty, (uint32_t)index, out_ty); } return elemValArray( sema, block, indexable, indexable_ty, elem_index, out_ty); } // Sema.zig:zirElemVal static void zirElemVal(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); uint32_t array = semaResolveRef(extra.lhs, sema->inst_values); uint32_t elem_index = semaResolveRef(extra.rhs, sema->inst_values); sema->inst_values[inst] = elemVal(sema, block, array, semaResolveRefValType(sema->ip, extra.lhs, array, sema->inst_types), elem_index, semaResolveRefValType( sema->ip, extra.rhs, elem_index, sema->inst_types), &sema->inst_types[inst]); } // Sema.zig:zirElemPtrLoad static void zirElemPtrLoad(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); uint32_t array_ptr = semaResolveRef(extra.lhs, sema->inst_values); uint32_t uncoerced_elem_index = semaResolveRef(extra.rhs, sema->inst_values); uint32_t elem_index_ty = semaResolveRefValType( sema->ip, extra.rhs, uncoerced_elem_index, sema->inst_types); uint32_t array_ptr_ty = semaResolveRefValType( sema->ip, extra.lhs, array_ptr, sema->inst_types); if (!semaIsAirInstRef(array_ptr)) { // fieldPtr's @typeInfo arm hands back a comptime slice field's value // for its pointer. uint32_t array_val = ipItemTagIs(sema->ip, array_ptr, IP_TAG_PTR_SLICE) ? array_ptr : pointerDeref(sema->zcu, array_ptr, NULL); if (array_val != IP_INDEX_NONE) { sema->inst_values[inst] = elemVal(sema, block, array_val, ipTypeOf(sema->ip, array_val), uncoerced_elem_index, elem_index_ty, &sema->inst_types[inst]); return; } } uint32_t elem_index = coerce(sema->zcu, sema->insts, block, sema->air_extra, ZIR_REF_USIZE_TYPE, uncoerced_elem_index, elem_index_ty); uint32_t elem_ptr_ty; uint32_t elem_ptr = elemPtr( sema, block, array_ptr, array_ptr_ty, elem_index, &elem_ptr_ty); sema->inst_values[inst] = analyzeLoad(sema->zcu, sema->insts, block, elem_ptr, elem_ptr_ty, &sema->inst_types[inst]); } // Sema.zig:zirElemValImm static void zirElemValImm(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t operand_ref = sema->zir->inst_datas[inst].elem_val_imm.operand; uint32_t array = semaResolveRef(operand_ref, sema->inst_values); uint32_t elem_index = ipGetIntUsize( sema->ip, sema->zir->inst_datas[inst].elem_val_imm.idx); sema->inst_values[inst] = elemVal(sema, block, array, semaResolveRefValType(sema->ip, operand_ref, array, sema->inst_types), elem_index, ZIR_REF_USIZE_TYPE, &sema->inst_types[inst]); } // Sema.zig:zirElemPtr // A `for` pointer capture, on the loop's usize counter. static void zirElemPtr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); uint32_t array_ptr = semaResolveRef(extra.lhs, sema->inst_values); uint32_t array_ptr_ty = semaResolveRefValType( sema->ip, extra.lhs, array_ptr, sema->inst_types); semaEnsurePointeeLayoutResolvedTy(sema, array_ptr_ty); sema->inst_values[inst] = elemPtrOneLayerOnly(sema, block, array_ptr, array_ptr_ty, semaResolveRef(extra.rhs, sema->inst_values), &sema->inst_types[inst]); } // Sema.zig:zirElemPtrNode static void zirElemPtrNode(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); uint32_t array_ptr = semaResolveRef(extra.lhs, sema->inst_values); uint32_t uncoerced_elem_index = semaResolveRef(extra.rhs, sema->inst_values); uint32_t elem_index = coerce(sema->zcu, sema->insts, block, sema->air_extra, ZIR_REF_USIZE_TYPE, uncoerced_elem_index, semaResolveRefValType( sema->ip, extra.rhs, uncoerced_elem_index, sema->inst_types)); sema->inst_values[inst] = elemPtr(sema, block, array_ptr, semaResolveRefValType( sema->ip, extra.lhs, array_ptr, sema->inst_types), elem_index, &sema->inst_types[inst]); } // Sema.zig:zirArrayInitElemPtr // Sema.zirArrayInitElemPtr -> elemPtr(..., init=true): forms a pointer to // element `index` of a by-ref array result location. ZIR pl_node -> ElemPtrImm // { ptr = array_ptr_ref, index = imm u32 }. The element pointer type mirrors // Type.elemPtrType: *ElemTy with the same constness as the array pointer (here // mutable => flags 0xFFFF00FC). Emits AIR ptr_elem_ptr (ty_pl { ty = // elem_ptr_ty, payload = Air.Bin { array_ptr, index } }); index is a `usize`. static void zirArrayInitElemPtr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ppay = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstElemPtrImm extra; zirExtraData(sema->zir, ppay, &extra); uint32_t arr_ptr_ref = extra.ptr; uint32_t el_index = extra.index; uint32_t arr_ptr_val = semaResolveRef(arr_ptr_ref, sema->inst_values); if (!semaIsAirInstRef(arr_ptr_val)) { // `sema.elemPtr(block, src, array_ptr, elem_index, src, // true, true)` on a comptime result location (a comptime // alloc of comptime-only type). uint32_t aie_ty = IP_INDEX_NONE; uint32_t aie_ptr = (arr_ptr_val == INST_VALUE_UNSET) ? IP_INDEX_NONE : semaElemPtrComptime( sema->zcu, arr_ptr_val, el_index, true, &aie_ty); if (aie_ptr == IP_INDEX_NONE) UNIMPLEMENTED("array_init_elem_ptr: comptime array ptr"); sema->inst_values[inst] = aie_ptr; sema->inst_types[inst] = aie_ty; return; } uint32_t ptr_ty = semaResolveRefType(arr_ptr_ref, sema->inst_types); // type_pointer extra[0] = child (array) type. uint32_t arr_ty = sema->ip->extra[sema->ip->item_datas[ptr_ty]]; if (ztIsTuple(sema->ip, arr_ty)) { // Sema.zirArrayInitElemPtr accepts tuple result locations via // elemPtr -> tupleElemPtr -> structFieldPtrByIndex. The ZIR // payload carries an immediate usize index, so emit the same // dedicated struct_field_ptr_index_N AIR used by the existing // tuple elem_ptr arms. A TypeTuple field with a stored value // is comptime-only; stock would return a ptr_comptime_field, // which zig1 does not model yet, so keep that shape loud. if (el_index >= ipTupleFieldCount(sema->ip, arr_ty)) UNIMPLEMENTED("array_init_elem_ptr: tuple index out of range"); sema->inst_values[inst] = structFieldPtrByIndex(sema->zcu, arr_ptr_val, ptr_ty, arr_ty, el_index, sema->insts, block, sema->air_extra, &sema->inst_types[inst]); return; } // Sema.elemPtr dispatches on the indexable type: `.array` goes // to elemPtrArray and `.vector` to elemPtrVector, whose element // pointer carries a `vector_index` (src/Sema.zig, `".vector_index // = @enumFromInt(index),"`) zig1 does not model. `.array` is // one upstream key, `array_type`, which zig1 splits into the // compact `type_array_small` and the sentineled/64-bit-length // `type_array_big` -- so testing the small tag alone rejected // every `[N:0]T` result location. ipArrayInfo is the decode for // both spellings, and Type.elemPtrType takes the element type // from the same place for either: `".array_type => |array_type| // .fromInterned(array_type.child),"`. IpArrayInfo aiep_info; if (ipArrayInfo(sema->ip, arr_ty, &aiep_info) != IP_ARR_OK) UNIMPLEMENTED("array_init_elem_ptr: non-array result type"); if (aiep_info.is_vector) { // elemPtr's `.vector` arm: `"try sema.elemPtrVector(block, // indexable_ptr_src, indexable_ptr, elem_index_src, // elem_index, init)"`. With a runtime vector pointer // elemPtrVector's whole lowering is one instruction -- // `"return block.addTyOp(.ptr_cast, elem_ptr_ty, // vector_ptr);"` -- because the element's position is carried // by the result TYPE's `vector_index`, not by an address // computation. `init` is true here (zirArrayInitElemPtr), so // the validateRuntimeElemAccess/validateRuntimeValue pair the // `!init` branch runs is skipped. uint32_t vec_el_ptr_ty; sema->inst_values[inst] = semaEmitVectorElemPtr(sema->zcu, ptr_ty, arr_ptr_val, el_index, &vec_el_ptr_ty, sema->insts, block); sema->inst_types[inst] = vec_el_ptr_ty; return; } // `"const elem_ptr_ty = try array_ptr_ty.elemPtrType(maybe_index, // pt);"` -- elemPtrArray's own derivation, which ztElemPtrType // already is, constness and alignment included. uint32_t el_ptr_ty = ztElemPtrType(sema->zcu, ptr_ty, el_index, NULL); sema->inst_values[inst] = semaEmitPtrElemPtr(arr_ptr_val, ipGetIntUsize(sema->ip, el_index), el_ptr_ty, sema->insts, block, sema->air_extra); sema->inst_types[inst] = el_ptr_ty; } // (part of) Sema.zig:zirSliceStart, Sema.zig:zirSliceEnd, // Sema.zig:zirSliceSentinel, Sema.zig:zirSliceLength // The four slice handlers' shared tail: resolve the operands and analyzeSlice. // `end_ref` / `sent_ref` are ZIR_REF_NONE when absent. static void semaSlice(SemaCtx* sema, U32List* block, uint32_t inst, uint32_t lhs_ref, uint32_t start_ref, uint32_t end_ref, uint32_t sent_ref, bool by_length) { uint32_t lhs = semaResolveRef(lhs_ref, sema->inst_values); uint32_t start = semaResolveRef(start_ref, sema->inst_values); uint32_t end = end_ref == ZIR_REF_NONE ? INST_VALUE_UNSET : semaResolveRef(end_ref, sema->inst_values); uint32_t sent = sent_ref == ZIR_REF_NONE ? INST_VALUE_UNSET : semaResolveRef(sent_ref, sema->inst_values); sema->inst_values[inst] = analyzeSlice(sema->zcu, sema->insts, block, sema->air_extra, lhs, semaResolveRefValType(sema->ip, lhs_ref, lhs, sema->inst_types), start, semaResolveRefValType(sema->ip, start_ref, start, sema->inst_types), end, end_ref == ZIR_REF_NONE ? IP_INDEX_NONE : semaResolveRefValType(sema->ip, end_ref, end, sema->inst_types), sent, by_length, &sema->inst_types[inst]); } // Sema.zig:zirSliceStart static void zirSliceStart(SemaCtx* sema, U32List* block, uint32_t inst) { ZirInstSliceStart extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &extra); semaSlice(sema, block, inst, extra.lhs, extra.start, ZIR_REF_NONE, ZIR_REF_NONE, false); } // Sema.zig:zirSliceEnd static void zirSliceEnd(SemaCtx* sema, U32List* block, uint32_t inst) { ZirInstSliceEnd extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &extra); semaSlice(sema, block, inst, extra.lhs, extra.start, extra.end, ZIR_REF_NONE, false); } // Sema.zig:zirSliceSentinel static void zirSliceSentinel(SemaCtx* sema, U32List* block, uint32_t inst) { ZirInstSliceSentinel extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &extra); semaSlice(sema, block, inst, extra.lhs, extra.start, extra.end, extra.sentinel, false); } // Sema.zig:zirSliceLength static void zirSliceLength(SemaCtx* sema, U32List* block, uint32_t inst) { ZirInstSliceLength extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &extra); semaSlice(sema, block, inst, extra.lhs, extra.start, extra.len, extra.sentinel, true); } // Sema.zig:zirSliceSentinelTy // Sema.zirSliceSentinelTy: AstGen asks for the element type used as the result // location of `sentinel` in `lhs[start..end :sentinel]`. Since the lhs is // analyzed as an lvalue, the operand type is a pointer to the sliceable // object; decode the pointee shape and return the sentinel element type as a // comptime-known `type` value. static void zirSliceSentinelTy(SemaCtx* sema, uint32_t inst) { uint32_t ss_operand_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t ss_lhs_ptr_ty = semaResolveRefValType(sema->ip, ss_operand_ref, semaResolveRef(ss_operand_ref, sema->inst_values), sema->inst_types); sema->inst_values[inst] = semaSliceSentinelTy(sema->ip, ss_lhs_ptr_ty); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } static uint32_t resolveSwitchItem(SemaCtx* sema, U32List* block, uint32_t item_ty, uint32_t item_info, uint32_t* cursor, uint32_t inst, bool prong_is_comptime_unreach); // Sema.zig:resolveSwitchItem // One item at ZIR item info `item_info`: an enum literal, an error value, the // `_` catch-all (IP_INDEX_NONE) or an item body (leading comptime insts // followed by a terminating break_inline of a comptime ref, read at `*cursor`, // which advances past it) -- coerced to `item_ty`. A single-inst body is // `break_inline @preinterned`; a range endpoint like `9` is `[int(9), // break_inline(%int)]`. static uint32_t resolveSwitchItem(SemaCtx* sema, U32List* block, uint32_t item_ty, uint32_t item_info, uint32_t* cursor, uint32_t inst, bool prong_is_comptime_unreach) { uint32_t kind = item_info & ZIR_ITEM_INFO_KIND_MASK; if (kind == ZIR_ITEM_INFO_KIND_ENUM_LITERAL) { // enum_literal item (`.a`): SwitchBlock.ItemInfo data is the ZIR // string index of the name (no trailing item body, cursor unchanged). uint32_t sidx = item_info >> ZIR_ITEM_INFO_DATA_SHIFT; const char* fname = zirNullTerminatedString(sema->zir, sidx); return semaSwitchEnumLiteralItem(sema->zcu, sema->zir, item_ty, ipGetOrPutString(sema->ip, fname, (uint32_t)strlen(fname))); } if (kind == ZIR_ITEM_INFO_KIND_ERROR_VALUE) { // error.Name item: resolve the item as the singleton error-set member, // then coerce it to the switch item type (the finite error set being // switched on, anyerror, or an inferred set). Upstream also permits // out-of-set errors when the whole prong is `comptime unreachable`; // keep the singleton in that case. uint32_t sidx = item_info >> ZIR_ITEM_INFO_DATA_SHIFT; const char* ename = zirNullTerminatedString(sema->zir, sidx); uint32_t nm_ip = ipGetOrPutString(sema->ip, ename, (uint32_t)strlen(ename)); uint32_t single = ipGetErrorSetType(sema->ip, &nm_ip, 1); uint32_t err = ipGetErrorSetError(sema->ip, single, nm_ip); if (ztIsZigTypeTag(sema->ip, item_ty, ZIG_TYPE_TAG_ERROR_SET) && !ipErrorSetHasName( sema->ip, item_ty, nm_ip, IP_IES_NAME_ASSUMED) && prong_is_comptime_unreach) return err; return semaCoerceComptime(sema->zcu, sema->ip, err, item_ty); } if (kind == ZIR_ITEM_INFO_KIND_UNDER) { // `_` catch-all item (ItemInfo.Kind.under, Zir.zig): no item value and // no trailing item body (ItemInfo.wrap emits data=0 for `.under`, // lib/std/zig/Zir.zig). Upstream's item loop skips validation and // pushes `.none` (src/Sema.zig); this sentinel flags the owning case // as the switch's catch-all (zirSwitchBlock's semaLocateUnderCase). // IP_INDEX_NONE // cannot collide with a real coerced item (the enum_literal/ // error_value/body arms above never produce it; guarded at their own // sites). return IP_INDEX_NONE; } if (kind != ZIR_ITEM_INFO_KIND_BODY_LEN) UNIMPLEMENTED("switch_block: non-body item"); uint32_t item_body_len = item_info >> ZIR_ITEM_INFO_DATA_SHIFT; const uint32_t* item_body = zirBodySlice(sema->zir, *cursor); *cursor += item_body_len; if (item_body_len == 0) UNIMPLEMENTED("switch_block: empty item body"); // resolveSwitchItem executes the inline body that computes each // scalar/range item before validateSwitchItemOrRange checks it. Keep this // mini-evaluator restricted to comptime item expressions. The item body's // result location is `.coerced_ty = switch_inst`, so the switch inst maps // to item_ty meanwhile. uint32_t sw_saved_iv = sema->inst_values[inst]; uint32_t sw_saved_it = sema->inst_types[inst]; sema->inst_values[inst] = item_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; for (uint32_t i = 0; i + 1 < item_body_len; i++) { uint32_t bi = item_body[i]; if (sema->zir->inst_tags[bi] == ZIR_INST_INT) { uint64_t iv = sema->zir->inst_datas[bi].int_val; sema->inst_values[bi] = semaInternComptimeIntWord(sema->ip, iv, false); sema->inst_types[bi] = ZIR_REF_COMPTIME_INT_TYPE; } else if (sema->zir->inst_tags[bi] == ZIR_INST_DECL_REF) { uint32_t noff = sema->zir->inst_datas[bi].str_tok.start; const char* nm = zirNullTerminatedString(sema->zir, noff); uint32_t ns = ipGetOrPutString(sema->ip, nm, (uint32_t)strlen(nm)); uint32_t nav = semaScopedDeclLookup(sema->ip, sema->zcu, sema->zir, ns); if (nav == IP_INDEX_NONE) UNIMPLEMENTED("switch_block: item body decl_ref not found"); if (ipGetNav(sema->ip, nav)->is_threadlocal) UNIMPLEMENTED("switch_block: item body decl_ref threadlocal"); uint32_t pty = IP_INDEX_NONE; sema->inst_values[bi] = navDeclRefAddressResolved(sema->zcu, nav, &pty); sema->inst_types[bi] = pty; } else if (sema->zir->inst_tags[bi] == ZIR_INST_FIELD_PTR && semaSwitchItemObjIsNavPtr( sema->ip, sema->zir, sema->inst_values, bi)) { ZirInstField fx; zirExtraData(sema->zir, sema->zir->inst_datas[bi].pl_node.payload_index, &fx); uint32_t obj = semaResolveRef(fx.lhs, sema->inst_values); const char* field = zirNullTerminatedString(sema->zir, fx.field_name_start); uint32_t field_ip = ipGetOrPutString(sema->ip, field, (uint32_t)strlen(field)); uint32_t base_nav = sema->ip->extra[sema->ip->item_datas[obj] + 1]; uint32_t base_ty = zptEnsureNavValUpToDate(sema->zcu, base_nav, NULL); uint32_t field_ptr; uint32_t field_ptr_ty; if (!semaComptimeNamespaceFieldPtr(sema->zcu, sema->zir, base_ty, field_ip, &field_ptr, &field_ptr_ty, "switch_block: item namespace has no namespace")) UNIMPLEMENTED("switch_block: item field_ptr namespace"); sema->inst_values[bi] = field_ptr; sema->inst_types[bi] = field_ptr_ty; } else if (sema->zir->inst_tags[bi] == ZIR_INST_FIELD_PTR_LOAD && semaSwitchItemObjIsNavPtr( sema->ip, sema->zir, sema->inst_values, bi)) { ZirInstField fx; zirExtraData(sema->zir, sema->zir->inst_datas[bi].pl_node.payload_index, &fx); uint32_t obj = semaResolveRef(fx.lhs, sema->inst_values); const char* field = zirNullTerminatedString(sema->zir, fx.field_name_start); uint32_t field_ip = ipGetOrPutString(sema->ip, field, (uint32_t)strlen(field)); uint32_t base_val = pointerDeref(sema->zcu, obj, NULL); if (base_val == IP_INDEX_NONE) UNIMPLEMENTED("switch_block: item body runtime load"); uint32_t field_val; uint32_t field_ty; if (!semaComptimeNamespaceFieldVal(sema->zcu, sema->zir, base_val, field_ip, &field_val, &field_ty, NULL, "switch_block: item namespace has no namespace")) UNIMPLEMENTED("switch_block: item field_ptr_load namespace"); sema->inst_values[bi] = field_val; sema->inst_types[bi] = field_ty; } else if (sema->zir->inst_tags[bi] == ZIR_INST_BIT_AND || sema->zir->inst_tags[bi] == ZIR_INST_BIT_OR || sema->zir->inst_tags[bi] == ZIR_INST_XOR) { ZirInstBin bx; zirExtraData(sema->zir, sema->zir->inst_datas[bi].pl_node.payload_index, &bx); uint32_t lref = bx.lhs; uint32_t rref = bx.rhs; uint32_t lv = semaResolveRef(lref, sema->inst_values); uint32_t rv = semaResolveRef(rref, sema->inst_values); uint32_t lty = (lref < ZIR_REF_START_INDEX) ? ipTypeOfPreinterned(lref) : sema->inst_types[lref - ZIR_REF_START_INDEX]; uint32_t rty = (rref < ZIR_REF_START_INDEX) ? ipTypeOfPreinterned(rref) : sema->inst_types[rref - ZIR_REF_START_INDEX]; uint64_t ln, rn; uint32_t lity, rity; if (!semaBinReadComptimeInt(sema->ip, lv, lty, &ln, &lity) || !semaBinReadComptimeInt(sema->ip, rv, rty, &rn, &rity)) UNIMPLEMENTED("switch_block: item bitwise operand"); uint64_t res = sema->zir->inst_tags[bi] == ZIR_INST_BIT_AND ? (ln & rn) : sema->zir->inst_tags[bi] == ZIR_INST_BIT_OR ? (ln | rn) : (ln ^ rn); sema->inst_values[bi] = semaInternComptimeIntWord(sema->ip, res, false); sema->inst_types[bi] = ZIR_REF_COMPTIME_INT_TYPE; } else { // resolveSwitchItem's `.body_len` arm does not enumerate // instructions at all -- it is `"break :ref try // sema.resolveInlineBody(block, body, switch_inst);"` under a // `.switch_item` comptime reason, i.e. the WHOLE evaluator. The // arms above are established decodes kept for their IP order; // anything else goes to the evaluator (a negative item, // `-5...-1`, is a `negate` here). bool saved_item_scope = sema->ictx->ct_scope; sema->ictx->ct_scope = true; analyzeBodyInner(sema, block, &item_body[i], 1); sema->ictx->ct_scope = saved_item_scope; } } sema->inst_values[inst] = sw_saved_iv; sema->inst_types[inst] = sw_saved_it; uint32_t last = item_body[item_body_len - 1]; if (sema->zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) UNIMPLEMENTED("switch_block: item body not break-terminated"); uint32_t item_ref = sema->zir->inst_datas[last].break_data.operand; uint32_t item_v = semaResolveRef(item_ref, sema->inst_values); // resolveSwitchItem's resolveConstDefinedValue on the result. if (semaIsAirInstRef(item_v)) COMPILE_ERROR_UPSTREAM("unable to resolve comptime value"); return semaCoerceComptime(sema->zcu, sema->ip, item_v, item_ty); } // Sema.zig:SwitchOperand, a zig1 layout under upstream's name, with the // facts analyzeSwitchBlock's `operand:` block derives from it: `.simple` is // by_val, by_ref (raw_val when the switch is by-ref) and cond; `.loop` is // `loop`, whose alloc is UINT32_MAX for `.simple`. A comptime union operand // also carries its active tag and payload. typedef struct { uint32_t raw_val; uint32_t by_val; uint32_t operand_ty; uint32_t item_ty; uint32_t cond; uint32_t ct_tag; uint32_t ct_payload; bool is_union; bool is_error_set; bool comptime_known; // analyzeSwitchBlock's resolve_at_comptime SwitchLoopOperand loop; } SwitchOperand; // Sema.zig:ValidatedSwitchBlock, a zig1 layout under upstream's name. // `case_vals` is per case: its items at case_item_off in item_vals, its // ranges' bounds at case_range_off in range_vals. zig1 also lays out here, // after the explicit cases, the cases finishSwitchBr and resolveSwitchBlock // generate (one per `inline else` value, one for `else` beside `_`), and // decides each case's per-item expansion. The else_* fields (`else_case`) // describe the `_` prong when there is one. typedef struct { uint32_t cases_len; const uint32_t* prong_infos; const uint32_t** prong_bodies; uint32_t* prong_body_lens; uint32_t* case_items_count; uint32_t* case_item_off; uint32_t* case_ranges_count; uint32_t* case_range_off; bool* case_inline_expand; uint32_t* item_vals; uint32_t* range_vals; uint32_t* generated_prong_infos; const uint32_t* else_body; uint32_t else_body_len; uint32_t else_capture; bool else_is_inline; bool else_has_tag_capture; bool has_else; uint32_t else_err_ty; uint32_t under_ci; bool else_all_noreturn; bool catch_all_body; } ValidatedSwitchBlock; static void semaValidatedSwitchBlockFree(ValidatedSwitchBlock* vsb) { free(vsb->prong_bodies); free(vsb->prong_body_lens); free(vsb->case_items_count); free(vsb->case_item_off); free(vsb->case_ranges_count); free(vsb->case_range_off); free(vsb->case_inline_expand); free(vsb->item_vals); free(vsb->range_vals); free(vsb->generated_prong_infos); } // Sema.zig:validateSwitchBlock // Resolves and coerces every case item to the item type, validates them, // and lays out the generated cases (ValidatedSwitchBlock). It runs after // analyzeSwitchBlock's operand block, whose load, get_union_tag and loop // alloc are emitted and interned before the items here. static void validateSwitchBlock(SemaCtx* sema, U32List* block, uint32_t inst, const ZirSwitchBlock* zs, const SwitchOperand* op, ValidatedSwitchBlock* out) { bool any_ranges = zs->multi_case_ranges_lens != NULL; // finishSwitchBr's `else_is_named_only`: with both prongs, the // unhandled named values get one extra case running the `else` // body (one case per value for an inline `else`), and `_` is the // catch-all. bool else_is_named_only = zs->has_else && zs->has_under; out->has_else = zs->has_else; out->else_body = zs->else_body; out->else_body_len = zs->else_body_len; out->else_capture = zs->else_capture; out->else_is_inline = zs->else_is_inline; out->else_has_tag_capture = (zs->else_info & ZIR_PRONG_ELSE_HAS_TAG_CAPTURE) != 0; uint32_t total_cases = zs->scalar_cases_len + zs->multi_cases_len; uint32_t total_items_len = zs->item_infos_len; uint32_t item_ty = op->item_ty; // resolveSwitchBlock's "named-only prong": a comptime named value // takes the `else` body, inline or not; `_` takes the rest. bool else_named_case = else_is_named_only && (!zs->else_is_inline || op->comptime_known); uint32_t explicit_cases_len = total_cases; uint32_t explicit_items_len = total_items_len; uint32_t inline_else_values_len = 0; bool inline_else_cases = false; if (out->else_is_inline) { bool item_ty_is_enum = item_ty < sema->ip->items_len && ipIsEnumType(sema->ip, item_ty); uint32_t ie_bits = 0; bool ie_signed; // check_enumerable's `.int` and `.@"union", .@"struct"` arms: // a packed container enumerates its backing int. bool ie_bitpack = item_ty < sema->ip->items_len && ipTypeIsBitpack(sema->ip, item_ty); bool item_ty_small_int = (ie_bitpack || ztIsZigTypeTag(sema->ip, item_ty, ZIG_TYPE_TAG_INT)) && ztIntInfo(sema->ip, ie_bitpack ? ipBitpackBackingInt(sema->ip, item_ty) : item_ty, &ie_bits, &ie_signed) && ie_bits <= SW_INLINE_ELSE_MAX_INT_BITS; // check_enumerable's `.error_set` arm: any set but anyerror. bool item_ty_finite_err = ipItemTagIs(sema->ip, item_ty, IP_TAG_TYPE_ERROR_SET); bool item_ty_runtime_enumerable = item_ty_finite_err || (item_ty_is_enum && (else_is_named_only || op->is_union || !ztIsNonexhaustiveEnum(sema->ip, item_ty))) || item_ty == ZIR_REF_BOOL_TYPE || item_ty_small_int; // finishSwitchBr's check_enumerable (a runtime operand only); a // tagged union's tag enumerates its named fields // (`tagged_union_originally`). if (!op->comptime_known && (item_ty == ZIR_REF_ANYERROR_TYPE || (!else_is_named_only && !op->is_union && ztIsNonexhaustiveEnum(sema->ip, item_ty)))) COMPILE_ERROR_UPSTREAM("switch_block: cannot enumerate " "values of type for 'inline " "else'"); if (!op->comptime_known && ((op->is_error_set && !item_ty_finite_err) || (any_ranges && !item_ty_small_int) || !item_ty_runtime_enumerable)) UNIMPLEMENTED("switch_block: inline else"); if (!out->has_else) UNIMPLEMENTED("switch_block: inline else"); if (!op->comptime_known && !op->is_union && out->else_capture == ZIR_PRONG_CAPTURE_BY_REF) UNIMPLEMENTED("switch_block: inline else by-ref capture"); if (out->else_has_tag_capture && !op->is_union) COMPILE_ERROR_UPSTREAM( "switch_block: inline else tag capture on non-union " "operand"); if (!op->comptime_known) { inline_else_cases = true; inline_else_values_len = item_ty_finite_err ? sema->ip->extra[sema->ip->item_datas[item_ty]] : item_ty_is_enum ? ipEnumFieldsLen(sema->ip, item_ty) : item_ty == ZIR_REF_BOOL_TYPE ? 2u : (1u << ie_bits); // Allocate enough room for explicit scalar cases plus one // synthetic case per value. After explicit items are // resolved, this is trimmed to only the unhandled fields. total_cases = explicit_cases_len + inline_else_values_len; total_items_len = explicit_items_len + inline_else_values_len; } } if (else_named_case) { // Room for the one extra case and its named items. total_cases = explicit_cases_len + 1u; total_items_len = explicit_items_len + ipEnumFieldsLen(sema->ip, item_ty); } // Locate each case's prong body and item body, and resolve item // values (validateSwitchBlock resolves + coerces each item to the // operand type — interning e.g. int_u32 entries — before any prong // body is analyzed). uint32_t total_ranges = 0; if (any_ranges) for (uint32_t m = 0; m < zs->multi_cases_len; m++) total_ranges += zs->multi_case_ranges_lens[m]; uint32_t case_alloc = total_cases ? total_cases : 1u; const uint32_t** prong_bodies = zig1Malloc(case_alloc * sizeof(uint32_t*)); uint32_t* prong_body_lens = zig1Malloc(case_alloc * sizeof(uint32_t)); uint32_t* case_items_count = zig1Malloc(case_alloc * sizeof(uint32_t)); uint32_t* case_item_off = zig1Malloc(case_alloc * sizeof(uint32_t)); uint32_t* case_ranges_count = zig1Malloc(case_alloc * sizeof(uint32_t)); uint32_t* case_range_off = zig1Malloc(case_alloc * sizeof(uint32_t)); // Set for a runtime `inline , => |x|` payload- // capturing prong -- scalar (non-union, non-error-set, no // ranges) or union (by-value, no ranges) -- with any item count // >= 1 (a single-item inline prong is the k=1 degenerate, see // the union arm's comment below): finishSwitchBr expands it into // one AIR switch_br case per item, instead of the shared single-body // multi-item case the plain (non-inline) scalar/union arms use. bool* case_inline_expand = zig1Calloc(case_alloc, sizeof(bool)); uint32_t item_alloc = total_items_len ? total_items_len : 1; uint32_t range_alloc = total_ranges ? 2 * total_ranges : 1; uint32_t* item_vals = zig1Malloc(item_alloc * sizeof(uint32_t)); uint32_t* range_vals = zig1Malloc(range_alloc * sizeof(uint32_t)); uint32_t* generated_prong_infos = NULL; const uint32_t* effective_prong_infos = zs->prong_infos; if (inline_else_cases || else_named_case) { generated_prong_infos = zig1Malloc(case_alloc * sizeof(uint32_t)); for (uint32_t ci = 0; ci < explicit_cases_len; ci++) generated_prong_infos[ci] = zs->prong_infos[ci]; effective_prong_infos = generated_prong_infos; } uint32_t cursor = zs->end; uint32_t item_info_idx = 0; // running index into item_infos uint32_t item_val_idx = 0; // running index into item_vals uint32_t range_val_idx = 0; // running index into range_vals for (uint32_t ci = 0; ci < explicit_cases_len; ci++) { uint32_t items_count = (ci < zs->scalar_cases_len) ? 1u : zs->multi_case_items_lens[ci - zs->scalar_cases_len]; uint32_t ranges_count = (ci < zs->scalar_cases_len || !any_ranges) ? 0u : zs->multi_case_ranges_lens[ci - zs->scalar_cases_len]; case_items_count[ci] = items_count; case_item_off[ci] = item_val_idx; case_ranges_count[ci] = ranges_count; case_range_off[ci] = range_val_idx; // ProngInfo: bits 27..28 capture (0 none, 1 by_val, 2 by_ref), // bit 29 is_inline, bit 30 has_tag_capture. A by-val capture // is supported for tagged unions (extract the active field), // error-set switches (singletons or finite grouped narrowing), // comptime @typeInfo reflection, and single explicit scalar // items (`.INVAL => |err|`, where `err` is the matched item). // Other capture shapes are still ported case by case. uint32_t prong_cap = semaSwProngCapture(effective_prong_infos[ci]); if (prong_cap == ZIR_PRONG_CAPTURE_BY_REF && (!op->loop.is_ref || op->is_error_set)) UNIMPLEMENTED("switch_block: by-ref prong capture"); // A runtime `inline` scalar by-ref prong is analyzed per // item, each capture its item's `uavRef`. if (prong_cap == ZIR_PRONG_CAPTURE_BY_REF && !op->is_union && !op->comptime_known && (effective_prong_infos[ci] & ZIR_PRONG_INFO_IS_INLINE)) case_inline_expand[ci] = true; if (prong_cap == ZIR_PRONG_CAPTURE_BY_VAL) { // A by-val capture on a non-union runtime scalar binds to // the matched item itself. Error-set switches narrow the // capture to the explicit item set: singletons are // comptime values, grouped items are runtime // `error_cast`s. Restrict ordinary scalar captures to a // single explicit item -- unless the prong is `inline` // (checked below) and the scrutinee is genuinely runtime, // in which case finishSwitchBr expands per item instead. if (!op->is_union) { if (op->is_error_set) { if (ranges_count != 0u) UNIMPLEMENTED("switch_block: error-set payload " "capture with ranges"); if (items_count != 1u && (effective_prong_infos[ci] & ZIR_PRONG_INFO_IS_INLINE)) case_inline_expand[ci] = true; } else if (items_count != 1u || ranges_count != 0u) { // Runtime `inline .a, .b => |t|` on a scalar // (non-union/error-set) operand: // Sema.analyzeSwitchBlock's runtime lowering -- // finishSwitchBr (src/Sema.zig) -- // re-analyzes the prong body once per item, // binding `|t|` to that item's own comptime value // each time (resolveSwitchCaptureFromProngItems's // `.@"inline"` arm, src/Sema.zig -> // analyzeSwitchCaptures) -- the same // single-item bind the existing scalar arm below // already performs, just repeated per item. A // Comptime operand instead resolves via // resolveSwitchProng, which selects only the one // taken prong directly and binds the capture to // the active coerced condition value; it // does not need per-item expansion. Runtime // inline ranges are the per-VALUE expansion path // below. bool ci_is_inline = (effective_prong_infos[ci] & ZIR_PRONG_INFO_IS_INLINE) != 0; if (ci_is_inline) case_inline_expand[ci] = true; } } // A non-inline multi-item union capture (`.new_local, // .local => |x|` in codegen's CValue.eql) is well-formed // without expansion: a union overlays all fields, so // extracting a single field per emitted AIR case suffices // -- item[0]'s field when every item's field shares its // type, or (below, `cap_multi_differs`) a per-item // peer-resolved nested switch, using only the error-set // peer-merge strategy (matching the qualifier at the // dedicated site's own comment below: any // other differing-type shape still aborts there), when // they don't (Sema.analyzeSwitchPayloadCaptureTaggedUnion, // src/Sema.zig). A @typeInfo multi-item capture is // still unmodeled. The non-union inline scalar-expand arm // above is excluded here too (handled per item, not via // item[0]). // A runtime `inline` union payload capture (any item // count, e.g. InternPool.get's // `switch (int.storage) { .big_int => ..., inline .u64, // .i64 => |x| ... }`): finishSwitchBr's is_inline loop // (src/Sema.zig) re-analyzes the prong body // once per item regardless of operand kind, and for a // union operand `analyzeSwitchCaptures`'s `.@"union"` arm // (src/Sema.zig) always takes the // `resolveSwitchCaptureFromProngItems` fast path for an // `.@"inline"` prong (that helper returns the item's // comptime value unconditionally for `.@"inline"`, // src/Sema.zig) straight into // `resolveSwitchPayloadCaptureTaggedUnion` // (src/Sema.zig) -- i.e. Each item gets its own // independently-typed field extraction (no peer-type // merge; `analyzeSwitchPayloadCaptureTaggedUnion`, the // non-inline multi-item helper with the peer-merge logic, // is marked `.@"inline" => unreachable` at // src/Sema.zig, i.e. it is provably never reached // from an inline prong). This is exactly the per-item // expansion the non-union arm above already performs; // reuse the same `case_inline_expand` bookkeeping. A // single-item (`items_count == 1`) inline union prong is // the k=1 degenerate: finishSwitchBr's loop still runs // (trivially once) and the non-inline `.item_refs` // resolveSwitchCaptureFromProngItems fast path // (src/Sema.zig: `refs.len == 1 and !by_ref`) // already binds the same comptime item value a plain // (non-inline) single-item union prong would -- so // marking it case_inline_expand is a behavior-preserving // relabeling, not new semantics, and keeps one code path // for both counts. Ranges are excluded (stock rejects // ranges when switching on a tagged-union operand type // with a compile error, src/Sema.zig // "ranges not allowed when switching on type '{}'"; // src/Sema.zig's `.has_ranges => unreachable` is // the corroborating assert inside the capture-resolution // helper, reached only because that compile error // already rejected the ranged prong); a comptime // scrutinee is excluded // (resolveSwitchProng's fold, not finishSwitchBr, and // already collapses the inline aspect -- see the // comment on the inline-prong abort below). By-ref // (cap_kind==2) inline union captures are a separate, unmodeled // shape: the by-ref guard above restricts them to items_count==1 // already, and this case is by-value (prong_cap==1), so // by-ref inline stays loud. if (op->is_union && !op->comptime_known && (effective_prong_infos[ci] & ZIR_PRONG_INFO_IS_INLINE)) { if (ranges_count != 0u) UNIMPLEMENTED( "switch_block: union payload capture with ranges"); case_inline_expand[ci] = true; } } if (prong_cap == ZIR_PRONG_CAPTURE_BY_VAL && !op->is_union && !op->is_error_set && !op->comptime_known && items_count == 1u && ranges_count == 0u && (effective_prong_infos[ci] & ZIR_PRONG_INFO_IS_INLINE)) case_inline_expand[ci] = true; // A runtime `inline` by-ref union capture: finishSwitchBr // re-analyzes the body per item, and each item's capture is // resolveSwitchPayloadCaptureTaggedUnion's own field pointer // (`typeOf(loaded_operand).fieldPtrType(field_index)`). if (prong_cap == ZIR_PRONG_CAPTURE_BY_REF && op->is_union && !op->comptime_known && ranges_count == 0u && (effective_prong_infos[ci] & ZIR_PRONG_INFO_IS_INLINE)) case_inline_expand[ci] = true; // A union tag capture binds each expanded item's tag. if (prong_cap == ZIR_PRONG_CAPTURE_NONE && !op->comptime_known && (effective_prong_infos[ci] & ZIR_PRONG_INFO_IS_INLINE) && (op->is_union || !(effective_prong_infos[ci] & ZIR_PRONG_INFO_HAS_TAG_CAPTURE))) { case_inline_expand[ci] = true; } // A runtime tagged-union tag capture (`|payload, tag|`) is // bound by analyzeSwitchProng. A // single-item (or inline-expanded) prong gets that item's // comptime tag; a grouped non-inline prong gets `cond_ref`, // the runtime get_union_tag result. This is // analyzeSwitchCaptures' unionToTag fallback when // resolveSwitchCaptureFromProngItems cannot determine one // item (src/Sema.zig). Comptime tag captures and // non-union tag captures remain outside this runtime subset. if ((effective_prong_infos[ci] & ZIR_PRONG_INFO_HAS_TAG_CAPTURE) && !op->is_union) UNIMPLEMENTED("switch_block: tag capture"); // An inline prong (bit 29). When the scrutinee is comptime // (a `switch (@typeInfo(T))` analyzed with a comptime-known type // argument), resolveSwitchBlock picks the single taken prong and binds // its capture, so the inline aspect (per-item specialization) // collapses and is a no-op. A genuinely runtime inline switch is // expanded per item by finishSwitchBr (case_inline_expand) when the // scalar by-val capture arm or the union by-val capture arm // above allowed it; any other runtime inline shape (no // capture, tag capture, by-ref union capture, ranges) is // still unmodeled. if (!op->comptime_known && !case_inline_expand[ci] && (effective_prong_infos[ci] & ZIR_PRONG_INFO_IS_INLINE)) { UNIMPLEMENTED("switch_block: inline prong"); } uint32_t prong_body_len = effective_prong_infos[ci] & ZIR_PRONG_INFO_BODY_LEN_MASK; prong_bodies[ci] = zirBodySlice(sema->zir, cursor); prong_body_lens[ci] = prong_body_len; cursor += prong_body_len; // Per case: item bodies, then range bodies (first, last). bool unreach = semaSwProngIsComptimeUnreach(effective_prong_infos[ci]); for (uint32_t it = 0; it < items_count; it++) item_vals[item_val_idx++] = resolveSwitchItem(sema, block, item_ty, zs->item_infos[item_info_idx++], &cursor, inst, unreach); for (uint32_t r = 0; r < ranges_count; r++) { range_vals[range_val_idx++] = resolveSwitchItem(sema, block, item_ty, zs->item_infos[item_info_idx++], &cursor, inst, unreach); // first range_vals[range_val_idx++] = resolveSwitchItem(sema, block, item_ty, zs->item_infos[item_info_idx++], &cursor, inst, unreach); // last } } // validateSwitchBlock validates every resolved item and range for // duplicates before any prong body is analyzed and before the // `_`/inline-else machinery below adds anything to `item_vals`. semaSwitchValidateItems(sema->zcu, item_ty, item_vals, item_val_idx, range_vals, range_val_idx / 2u); uint32_t else_err_ty = semaSwitchValidateSpecialProngs(sema->ip, item_ty, op->operand_ty, item_vals, item_val_idx, range_vals, range_val_idx / 2u, zs->has_else, zs->has_under, (zs->else_info & ZIR_PRONG_ELSE_IS_SIMPLE_NORETURN) != 0); // `_ => ...` without `else`: the sole `_` case is aliased onto the // else_* fields, as Air.SwitchBr has no else-vs-under distinction // and finishSwitchBr's `analyze_catch_all_body` for has_under is // `true` unconditionally (src/Sema.zig), which semaLocateUnderCase's // union/error-set rejection makes identical in effect to the // has_else-driven branch. AstGen forbids `inline _` ("cannot inline // '_' prong"), so that check is defensive. A capturing `_ => |x|` // prong binds through the same catch-all capture path as // `else => |x|`. if (else_named_case) { // `"for (validated_switch.seen_enum_fields, 0..) |seen_field, // field_i| { if (seen_field != null) continue; const item_val // = try pt.enumValueFieldIndex(item_ty, @intCast(field_i)); // ... }"` -- one case, the `else` body, `.special` capture. uint32_t gen_ci = explicit_cases_len; case_ranges_count[gen_ci] = 0; case_range_off[gen_ci] = range_val_idx; prong_bodies[gen_ci] = out->else_body; prong_body_lens[gen_ci] = out->else_body_len; generated_prong_infos[gen_ci] = (out->else_body_len & ZIR_PRONG_INFO_BODY_LEN_MASK) | (out->else_capture << ZIR_PRONG_INFO_CAPTURE_SHIFT); case_item_off[gen_ci] = item_val_idx; case_items_count[gen_ci] = semaSwitchUnhandledItems(sema->ip, item_ty, item_vals, item_val_idx, NULL, 0u, &item_vals[item_val_idx], ipEnumFieldsLen(sema->ip, item_ty)); item_val_idx += case_items_count[gen_ci]; total_cases = gen_ci + 1u; } uint32_t under_ci = UINT32_MAX; if (zs->has_under) { under_ci = semaLocateUnderCase(sema->ip, op->operand_ty, explicit_cases_len, case_items_count, case_item_off, item_vals); if (effective_prong_infos[under_ci] & ZIR_PRONG_INFO_IS_INLINE) COMPILE_ERROR_UPSTREAM("switch_block: inline '_' prong"); out->else_body = prong_bodies[under_ci]; out->else_body_len = prong_body_lens[under_ci]; out->else_capture = semaSwProngCapture(effective_prong_infos[under_ci]); out->else_is_inline = false; out->has_else = true; } if (inline_else_cases) { // finishSwitchBr's `inline else`: `"var unhandled_it = // validated_switch.iterateUnhandledItems(error_names, // min_int); while (try unhandled_it.next(sema, item_ty)) | // item_val| { ... }"` -- one inline case per value. uint32_t n_unhandled = semaSwitchUnhandledItems(sema->ip, item_ty, item_vals, item_val_idx, range_vals, range_val_idx / 2u, &item_vals[item_val_idx], inline_else_values_len); uint32_t gen_ci = explicit_cases_len; for (uint32_t u = 0; u < n_unhandled; u++) { // `"if (emit_bb) try sema.emitBackwardBranch(block, // else_prong_src); emit_bb = true;"` if (u != 0) semaChargeBranches(sema->zcu, 1); case_items_count[gen_ci] = 1u; case_item_off[gen_ci] = item_val_idx++; case_ranges_count[gen_ci] = 0u; case_range_off[gen_ci] = range_val_idx; // The `else` prong itself, not a `_` aliased onto it. prong_bodies[gen_ci] = zs->else_body; prong_body_lens[gen_ci] = zs->else_body_len; generated_prong_infos[gen_ci] = (zs->else_body_len & ZIR_PRONG_INFO_BODY_LEN_MASK) | (zs->else_capture << ZIR_PRONG_INFO_CAPTURE_SHIFT); if (out->else_has_tag_capture) generated_prong_infos[gen_ci] |= ZIR_PRONG_INFO_HAS_TAG_CAPTURE; // A by-ref capture is resolved per item, as an `inline` // prong: resolveSwitchPayloadCaptureTaggedUnion's own // field pointer and a comptime tag. (A by-value one // already resolves its single item that way.) if (out->else_capture == ZIR_PRONG_CAPTURE_BY_REF) { generated_prong_infos[gen_ci] |= ZIR_PRONG_INFO_IS_INLINE; case_inline_expand[gen_ci] = true; } gen_ci++; } total_cases = gen_ci; } // An error-set `else => |e|` capture is typed with // validateSwitchBlock's `else_err_ty` (analyzeSwitchCaptures' // `.special` arm). if (op->is_error_set && out->else_capture == ZIR_PRONG_CAPTURE_BY_REF) COMPILE_ERROR_UPSTREAM( "switch_block: error-set else capture by reference"); bool else_all_noreturn = false; if (!op->comptime_known && out->has_else && op->is_union && !inline_else_cases) { else_all_noreturn = true; bool saw_residual = false; uint32_t fields_len = ipUnionFieldsLen(sema->ip, op->operand_ty); for (uint32_t fi = 0; fi < fields_len; fi++) { bool handled = false; for (uint32_t ci = 0; ci < total_cases && !handled; ci++) { for (uint32_t it = 0; it < case_items_count[ci]; it++) { uint32_t tag_idx = item_vals[case_item_off[ci] + it]; if (semaUnionFieldIndexFromTag( sema->ip, op->operand_ty, tag_idx) == fi) { handled = true; break; } } } if (handled) continue; saw_residual = true; uint32_t field_ty = semaResolveUnionFieldTypeLazy(sema->zcu, op->operand_ty, fi); if (field_ty != ZIR_REF_NORETURN_TYPE) { else_all_noreturn = false; break; } } if (!saw_residual) else_all_noreturn = false; } // A runtime by-value `else => |x|` capture binds to the loaded // switch operand itself, except on an error set, where it binds to // the operand narrowed to `else_err_ty` by an `error_cast` // (analyzeSwitchProng's `.special` arm). Tagged-union catch-alls // are upstream's `.special` path in // analyzeSwitchPayloadCaptureTaggedUnion, anchor // `.special => return loaded_operand`, so they also bind the full // union operand, not its computed tag. // An inline `else` was expanded above; a `_` stays the catch-all. bool catch_all_body = !inline_else_cases || zs->has_under; // validateSwitchBlock's `.int` exhaustiveness check interns minInt // then maxInt for the operand type (range_set.spans side effect). // Enum operands validate exhaustiveness against the field set and // non-int operands (`switch (@TypeOf(value))` over types in // Writer.printInt — the `.type` validation arm) against item // identity, so no minInt/maxInt is interned for them (non-int // non-enum operands skip semaCmpCreateMinMax). if (ztIsZigTypeTag(sema->ip, item_ty, ZIG_TYPE_TAG_INT)) semaCmpCreateMinMax(sema->ip, item_ty); out->cases_len = total_cases; out->prong_infos = effective_prong_infos; out->prong_bodies = prong_bodies; out->prong_body_lens = prong_body_lens; out->case_items_count = case_items_count; out->case_item_off = case_item_off; out->case_ranges_count = case_ranges_count; out->case_range_off = case_range_off; out->case_inline_expand = case_inline_expand; out->item_vals = item_vals; out->range_vals = range_vals; out->generated_prong_infos = generated_prong_infos; out->else_err_ty = else_err_ty; out->under_ci = under_ci; out->else_all_noreturn = else_all_noreturn; out->catch_all_body = catch_all_body; } // (part of) Sema.zig:resolveBlockBody // A comptime-chosen body `child_block`, its captures unbound. static bool semaResolveSwitchBody(SemaCtx* sema, U32List* block, U32List child_block, uint32_t inst, uint32_t merge_blk_idx) { if (sema->ictx->brk) { // The ComptimeBreak to an outer block: the instructions go to the // parent, which consumes ictx->brk. for (uint32_t k = 0; k < child_block.len; k++) u32ListAppend(block, child_block.items[k]); free(child_block.items); return true; } sema->inst_values[inst] = AIR_REF_INST_BASE + merge_blk_idx; return resolveAnalyzedBlock(sema->zcu, sema->ictx, sema->insts, sema->air_extra, block, child_block, merge_blk_idx, &sema->inst_values[inst], &sema->inst_types[inst]); } // Sema.zig:resolveSwitchProng // Binds the captures of case `taken`, or of the `else` when it is negative // (analyzeSwitchCaptures' comptime arms), and analyzes its body as // resolveBlockBody does. Returns whether the body ends here. static bool resolveSwitchProng(SemaCtx* sema, U32List* block, const SwitchOperand* op, const ZirSwitchBlock* zs, uint32_t inst, uint32_t merge_blk_idx, const ValidatedSwitchBlock* vsb, int32_t taken, uint32_t cond_ip) { InternPool* ip = sema->ip; uint32_t payload_inst = semaSwCaptureInst(zs->payload_capture_placeholder, inst); uint32_t tag_inst = semaSwCaptureInst(zs->tag_capture_placeholder, inst); uint32_t pinfo = taken >= 0 ? vsb->prong_infos[taken] : 0; // analyzeSwitchCaptures' `prong_items`: an `inline` prong is its one item, // the condition (`.@"inline" = cond`), and an `else` or `_` is `.special`. bool is_inline = taken >= 0 ? (pinfo & ZIR_PRONG_INFO_IS_INLINE) != 0 : vsb->else_is_inline; uint32_t cap = taken >= 0 ? semaSwProngCapture(pinfo) : vsb->else_capture; const uint32_t* items = is_inline ? &cond_ip : taken >= 0 ? &vsb->item_vals[vsb->case_item_off[taken]] : NULL; uint32_t items_len = is_inline ? 1u : taken >= 0 ? vsb->case_items_count[taken] : 0u; bool special = items_len == 0u; uint32_t saved_caps = sema->ictx->caps_len; // resolveSwitchBlock's `"sema.inst_map.putAssumeCapacity(tag_inst, // .fromValue(item_opv));"`: the active tag. The slot may be the switch // inst itself; a breaking prong then finds the merge block through the // InlineCtx, as the payload capture's does. bool tag_bind = op->is_union && (taken >= 0 ? (pinfo & ZIR_PRONG_INFO_HAS_TAG_CAPTURE) != 0 : vsb->else_has_tag_capture); uint32_t saved_tag_iv = sema->inst_values[tag_inst]; uint32_t saved_tag_it = sema->inst_types[tag_inst]; if (tag_bind) { sema->inst_values[tag_inst] = cond_ip; sema->inst_types[tag_inst] = op->item_ty; semaSwitchCaptureEnter(sema->ictx, saved_caps, inst, merge_blk_idx); } bool cap_bind = false; uint32_t cap_val = 0; uint32_t cap_ty = 0; if (op->is_error_set) { // Error captures are narrowed: to the items' set, or to `else_err_ty`. if (cap != ZIR_PRONG_CAPTURE_NONE) { cap_bind = true; cap_ty = special ? vsb->else_err_ty : semaSwitchErrCaptureTy(ip, items, items_len); cap_val = ipGetErrorSetError( ip, cap_ty, semaSwItemErrorName(ip, cond_ip)); } } else if (cap == ZIR_PRONG_CAPTURE_NONE) { // No payload to bind. } else if (op->is_union && !special) { cap_bind = true; if (cap == ZIR_PRONG_CAPTURE_BY_VAL) { cap_val = semaSwitchByValCapture(sema->zcu, op->operand_ty, items, items_len, op->ct_payload, &cap_ty); } else { // analyzeSwitchPayloadCaptureTaggedUnion's by-ref arm on a // comptime operand pointer: "const field_ptr_val = try // op_ptr_val.ptrField(first_field_index, pt); return // .fromValue(try pt.getCoerced(field_ptr_val, // capture_ptr_ty));" if (!semaRefIsInterned(op->raw_val)) UNIMPLEMENTED("switch_block comptime: by-ref capture of a " "non-comptime operand pointer"); cap_val = semaSwitchByRefCapturePtr(sema->zcu, op->raw_val, op->loop.raw_ty, op->operand_ty, items, items_len, &cap_ty); } } else if (cap == ZIR_PRONG_CAPTURE_BY_VAL) { // The condition, or for a union's `.special` the whole operand. cap_bind = true; cap_val = op->is_union ? op->by_val : cond_ip; cap_ty = op->is_union ? op->operand_ty : op->item_ty; } else { // `uavRef` of an `inline` prong's item, else the operand pointer. cap_bind = true; if (is_inline) { cap_ty = ipGetPtrType(ip, op->item_ty, IP_PTR_FLAGS_CONST_ONE); cap_val = ipGetPtrUav(ip, cap_ty, cond_ip); } else { cap_val = op->raw_val; cap_ty = op->loop.raw_ty; } } uint32_t saved_iv = sema->inst_values[payload_inst]; uint32_t saved_it = sema->inst_types[payload_inst]; if (cap_bind) { sema->inst_values[payload_inst] = cap_val; sema->inst_types[payload_inst] = cap_ty; semaSwitchCaptureEnter(sema->ictx, saved_caps, inst, merge_blk_idx); } // resolveBlockBody: a `break` to this switch is a `br` to its merge // block; a break_inline to an outer inline block sets ictx->brk. U32List cb = { 0 }; if (taken >= 0) analyzeBodyInner( sema, &cb, vsb->prong_bodies[taken], vsb->prong_body_lens[taken]); else analyzeBodyInner(sema, &cb, vsb->else_body, vsb->else_body_len); if (tag_bind) { sema->inst_values[tag_inst] = saved_tag_iv; sema->inst_types[tag_inst] = saved_tag_it; } if (cap_bind) { sema->inst_values[payload_inst] = saved_iv; sema->inst_types[payload_inst] = saved_it; } sema->ictx->caps_len = saved_caps; return semaResolveSwitchBody(sema, block, cb, inst, merge_blk_idx); } // Sema.zig:resolveSwitchBlock // The comptime fold: coerce the comptime cond to the item type // (validateSwitchBlock coerced every item the same way, so equal values // dedup to equal IP indices), pick the taken prong, and resolve only it. // Returns whether the body ends here. static bool resolveSwitchBlock(SemaCtx* sema, U32List* block, const SwitchOperand* op, const ZirSwitchBlock* zs, uint32_t inst, uint32_t merge_blk_idx, const ValidatedSwitchBlock* vsb) { // A union's cond is its active tag, an enum_tag of the item type as // the enum-literal items are. uint32_t cond_ip = op->ct_tag; if (!op->is_union) { cond_ip = semaCoerceComptime(sema->zcu, sema->ip, op->by_val, op->item_ty); } int32_t taken = -1; for (uint32_t ci = 0; ci < vsb->cases_len && taken < 0; ci++) { if (ci == vsb->under_ci) continue; // catch-all: never a literal item match const uint32_t* items = &vsb->item_vals[vsb->case_item_off[ci]]; for (uint32_t it = 0; it < vsb->case_items_count[ci]; it++) if (items[it] == cond_ip) taken = (int32_t)ci; // `"compareAll(cond_val, .gte, first_val, ...) and // compareAll(cond_val, .lte, last_val, ...)"`, the // comparison the duplicate and overlap gate runs. for (uint32_t r = 0; r < vsb->case_ranges_count[ci] && taken < 0; r++) { const uint32_t* rv = &vsb->range_vals[vsb->case_range_off[ci] + 2 * r]; if (semaSwCmp(sema->zcu, ZIR_INST_CMP_GTE, cond_ip, rv[0]) && semaSwCmp(sema->zcu, ZIR_INST_CMP_LTE, cond_ip, rv[1])) taken = (int32_t)ci; } } if (taken < 0 && !vsb->has_else) UNIMPLEMENTED("switch_block comptime: no matching prong (exhaustive " "without else)"); return resolveSwitchProng( sema, block, op, zs, inst, merge_blk_idx, vsb, taken, cond_ip); } // Sema.zig:wantSwitchProngBodyAnalysis static bool wantSwitchProngBodyAnalysis(SemaCtx* sema, uint32_t item, uint32_t operand_ty, bool tagged_union, bool err_set, bool prong_is_comptime_unreach) { if (tagged_union) { uint32_t fi = semaUnionFieldIndexFromTag(sema->ip, operand_ty, item); if (semaResolveUnionFieldTypeLazy(sema->zcu, operand_ty, fi) == ZIR_REF_NORETURN_TYPE) return false; } if (err_set && prong_is_comptime_unreach && item < sema->ip->items_len && sema->ip->item_tags[item] == IP_TAG_ERROR_SET_ERROR && !ipErrorSetHasName(sema->ip, operand_ty, sema->ip->extra[sema->ip->item_datas[item] + 1], IP_IES_NAME_ASSUMED)) return false; return true; } // Sema.zig:analyzeSwitchPayloadCaptureTaggedUnion // The payload capture of runtime case `ci` (its item `expand_item` when the // case is expanded per item): the field value or pointer, and its type. static uint32_t analyzeSwitchPayloadCaptureTaggedUnion(SemaCtx* sema, U32List* cb, const SwitchOperand* op, bool has_continue, const ValidatedSwitchBlock* vsb, uint32_t ci, uint32_t expand_item, uint32_t cap_kind, uint32_t* out_ty) { // The prong's single item is an enum-tag IP index. // Map it to the union field by name (explicit enum // values and declaration order are independent), then // rebind AstGen's payload-capture inst so `|x|` / // `|*x|` resolves to the payload. By-value capture // emits agg_field_val from the loaded union value; // by-ref capture emits struct_field_ptr from the // original operand pointer // (Sema.analyzeSwitchOperandLoad(..., // capture_by_ref=true) -> addStructFieldPtr). // An inline-expanded case (vsb->case_inline_expand[ci]): // this emitted AIR case corresponds to exactly one of // the ZIR prong's items (`expand_item`), mirroring // finishSwitchBr's per-item re-analysis // (src/Sema.zig) -- use that item's tag, // not item[0]. uint32_t tag_idx = vsb->case_inline_expand[ci] ? vsb->item_vals[vsb->case_item_off[ci] + expand_item] : vsb->item_vals[vsb->case_item_off[ci]]; uint32_t field_index = semaUnionFieldIndexFromTag(sema->ip, op->operand_ty, tag_idx); uint32_t field_ty = semaResolveUnionFieldTypeLazy( sema->zcu, op->operand_ty, field_index); // Multi-item capture: every item's field usually // shares this type. When it doesn't, upstream // (Sema.analyzeSwitchPayloadCaptureTaggedUnion, // src/Sema.zig) peer-resolves the differing // field types instead of rejecting the prong; detect // that here and fall into the dedicated runtime // dispatch below rather than aborting. An // inline-expanded case is exempt: it already reduced // to a single item above, and upstream's own // resolveSwitchCaptureFromProngItems fast path for an // `.@"inline"` prong routes to // resolveSwitchPayloadCaptureTaggedUnion // (src/Sema.zig) directly -- the peer-merge // helper (analyzeSwitchPayloadCaptureTaggedUnion) is // marked `.@"inline" => unreachable` at // src/Sema.zig, i.e. provably never reached // from an inline prong, so no merge is needed or // correct here. bool cap_multi_differs = false; if (!vsb->case_inline_expand[ci]) { for (uint32_t mi = 1; mi < vsb->case_items_count[ci]; mi++) { uint32_t m_tag = vsb->item_vals[vsb->case_item_off[ci] + mi]; uint32_t m_fi = semaUnionFieldIndexFromTag(sema->ip, op->operand_ty, m_tag); if (semaResolveUnionFieldTypeLazy(sema->zcu, op->operand_ty, m_fi) != field_ty) { cap_multi_differs = true; break; } } } uint32_t sf_idx; uint32_t cap_ty; uint32_t case_union_operand_ref = cap_kind != ZIR_PRONG_CAPTURE_BY_REF ? analyzeSwitchOperandLoad( sema->insts, cb, op->by_val, op->operand_ty, &op->loop, false) : op->by_val; // A by-value `capture_ty`, the field type or resolvePeerTypes over the // items' field types; `"if (try capture_ty.onePossibleValue(pt)) |opv| // return .fromValue(opv);"`. uint32_t by_val_ty = field_ty; if (cap_kind != ZIR_PRONG_CAPTURE_BY_REF) { if (cap_multi_differs) { uint32_t* tys = ARR_INIT(uint32_t, vsb->case_items_count[ci]); by_val_ty = semaSwitchCaptureTy(sema->zcu, op->operand_ty, &vsb->item_vals[vsb->case_item_off[ci]], vsb->case_items_count[ci], tys); free(tys); } uint32_t opv = ztOnePossibleValue(sema->ip, by_val_ty); if (opv != IP_INDEX_NONE) { *out_ty = by_val_ty; return opv; } } if (cap_kind == ZIR_PRONG_CAPTURE_BY_REF) { // analyzeSwitchPayloadCaptureTaggedUnion's by-ref // arm: `capture_ty` is still peer-resolved over // undef field values when the fields differ, then // `capture_ptr_ty` over undef field pointers // (`"By-ref captures of hetereogeneous types are // only allowed if all field pointer types are peer // resolvable to each other."`), and the capture is // `addStructFieldPtr(loaded_operand, // first_field_index, capture_ptr_ty)`. // An inline-expanded prong captures its one // item (resolveSwitchPayloadCaptureTaggedUnion). uint32_t n_items = vsb->case_inline_expand[ci] ? 1u : vsb->case_items_count[ci]; uint32_t br_first = vsb->case_inline_expand[ci] ? vsb->case_item_off[ci] + expand_item : vsb->case_item_off[ci]; cap_ty = semaSwitchByRefCapturePtrTy(sema->zcu, op->loop.raw_ty, op->operand_ty, &vsb->item_vals[br_first], n_items); sf_idx = semaEmitStructFieldPtr( analyzeSwitchOperandLoad(sema->insts, cb, op->raw_val, op->operand_ty, &op->loop, true), field_index, cap_ty, sema->insts, cb, sema->air_extra) - AIR_REF_INST_BASE; } else if (cap_multi_differs) { // The `.loop` arm's get_union_tag of the loaded // operand inside the capture block is unported; // `op->cond` is only the first dispatch's tag. if (has_continue) UNIMPLEMENTED("switch_block: continue with a differing-type " "multi-item capture"); // Ranged prongs are not modeled here: stock // rejects ranges when switching on a tagged-union // operand type with a compile error // (src/Sema.zig, "ranges not allowed // when switching on type '{}'"; src/Sema.zig: // 12066's `.has_ranges => unreachable` is the // corroborating assert inside the // capture-resolution helper, reached only because // that compile error already rejected the ranged // prong), so a ranged prong reaching this branch // must abort loudly rather than silently fall // into the sub-switch's unreachable else below. if (vsb->case_ranges_count[ci] != 0u) UNIMPLEMENTED( "switch_block: multi-item differing-type capture with ranges"); // analyzeSwitchPayloadCaptureTaggedUnion's // heterogeneous fallback: `capture_ty` is // `resolvePeerTypes` over every item's field // type, then a nested runtime switch // on the same tag loads each item's own field and // `sema.coerce`s it to `capture_ty` ("By-val // capture with heterogeneous types ... fall back // to the ugly method"). uint32_t n_items = vsb->case_items_count[ci]; if (n_items == 0) UNREACHABLE("tagged union capture: differing prong without items"); uint32_t* mi_field_index = zig1Malloc((size_t)n_items * sizeof(uint32_t)); uint32_t* mi_field_ty = zig1Malloc((size_t)n_items * sizeof(uint32_t)); for (uint32_t mi = 0; mi < n_items; mi++) { uint32_t m_tag = vsb->item_vals[vsb->case_item_off[ci] + mi]; mi_field_index[mi] = semaUnionFieldIndexFromTag(sema->ip, op->operand_ty, m_tag); mi_field_ty[mi] = semaResolveUnionFieldTypeLazy( sema->zcu, op->operand_ty, mi_field_index[mi]); } uint32_t cap_ty2 = by_val_ty; AirInstData cap_blk_data; memset(&cap_blk_data, 0, sizeof(cap_blk_data)); uint32_t cap_blk_idx = semaAirInstAppend(sema->insts, AIR_INST_BLOCK, cap_blk_data); U32List* sub_case_blocks = zig1Malloc((size_t)n_items * sizeof(U32List)); for (uint32_t mi = 0; mi < n_items; mi++) { U32List scb = { 0 }; uint32_t fv_payload = sema->air_extra->len; u32ListAppend(sema->air_extra, case_union_operand_ref); // struct_operand u32ListAppend(sema->air_extra, mi_field_index[mi]); AirInstData fv_data; memset(&fv_data, 0, sizeof(fv_data)); fv_data.pair.a = mi_field_ty[mi]; // ty_pl result type fv_data.pair.b = fv_payload; uint32_t fv_idx = semaAirInstAppend( sema->insts, AIR_INST_AGG_FIELD_VAL, fv_data); u32ListAppend(&scb, fv_idx); uint32_t coerced_ref = AIR_REF_INST_BASE + fv_idx; coerced_ref = semaCoerceExtraRuntime(sema->zcu, coerced_ref, mi_field_ty[mi], cap_ty2, sema->insts, &scb, sema->air_extra, "switch_block: heterogeneous capture"); AirInstData br_data; memset(&br_data, 0, sizeof(br_data)); br_data.pair.a = cap_blk_idx; // br.block_inst br_data.pair.b = coerced_ref; // br.operand uint32_t br_idx = semaAirInstAppend(sema->insts, AIR_INST_BR, br_data); u32ListAppend(&scb, br_idx); sub_case_blocks[mi] = scb; } free(mi_field_index); free(mi_field_ty); uint32_t sub_payload = sema->air_extra->len; u32ListAppend(sema->air_extra, n_items); // cases_len u32ListAppend(sema->air_extra, 1u); // else_body_len uint32_t sub_bags = (n_items + AIR_SWITCH_BR_HINTS_PER_WORD) / AIR_SWITCH_BR_HINTS_PER_WORD; for (uint32_t bg = 0; bg < sub_bags; bg++) u32ListAppend(sema->air_extra, 0u); // all hints .none for (uint32_t mi = 0; mi < n_items; mi++) { u32ListAppend(sema->air_extra, 1u); // items_len u32ListAppend(sema->air_extra, 0u); // ranges_len u32ListAppend(sema->air_extra, sub_case_blocks[mi].len); // body_len u32ListAppend(sema->air_extra, vsb->item_vals[vsb->case_item_off[ci] + mi]); // item for (uint32_t k = 0; k < sub_case_blocks[mi].len; k++) u32ListAppend(sema->air_extra, sub_case_blocks[mi].items[k]); free(sub_case_blocks[mi].items); } free(sub_case_blocks); // else prong: every possible tag is one of this // prong's items (the outer switch already routed // exactly this set of tags here), so it is // unreachable, matching src/Sema.zig. semaEmitTyOp(sema->insts, sema->air_extra, AIR_INST_UNREACH, 0, 0); AirInstData subsw_data; memset(&subsw_data, 0, sizeof(subsw_data)); subsw_data.pair.a = op->cond; // same tag as the outer switch subsw_data.pair.b = sub_payload; uint32_t subsw_idx = semaAirInstAppend(sema->insts, AIR_INST_SWITCH_BR, subsw_data); uint32_t cap_blk_extra = sema->air_extra->len; u32ListAppend(sema->air_extra, 1u); // Air.Block.body_len u32ListAppend(sema->air_extra, subsw_idx); sema->insts->datas[cap_blk_idx].pair.a = cap_ty2; sema->insts->datas[cap_blk_idx].pair.b = cap_blk_extra; sf_idx = cap_blk_idx; cap_ty = cap_ty2; } else { cap_ty = field_ty; uint32_t sf_payload = sema->air_extra->len; u32ListAppend(sema->air_extra, case_union_operand_ref); // struct_operand u32ListAppend(sema->air_extra, field_index); // field_index AirInstData sf_data; memset(&sf_data, 0, sizeof(sf_data)); sf_data.pair.a = field_ty; // ty_pl result type sf_data.pair.b = sf_payload; sf_idx = semaAirInstAppend(sema->insts, AIR_INST_AGG_FIELD_VAL, sf_data); } if (cap_kind != ZIR_PRONG_CAPTURE_BY_REF) u32ListAppend(cb, sf_idx); *out_ty = cap_ty; return AIR_REF_INST_BASE + sf_idx; } // Sema.zig:analyzeSwitchProng // Runtime case `ci` (the catch-all `.special` prong when it is // SW_PRONG_SPECIAL), or its item `expand_item` (value `expand_val`) when it // is expanded per item: binds the captures (analyzeSwitchCaptures) and // analyzes the prong body into `cb` with a runtime break. static void analyzeSwitchProng(SemaCtx* sema, U32List* cb, const SwitchOperand* op, const ZirSwitchBlock* zs, uint32_t inst, uint32_t merge_blk_idx, const ValidatedSwitchBlock* vsb, uint32_t ci, uint32_t expand_item, uint32_t expand_val) { bool special = ci == SW_PRONG_SPECIAL; uint32_t pinfo = special ? 0 : vsb->prong_infos[ci]; uint32_t cap_kind = special ? vsb->else_capture : semaSwProngCapture(pinfo); bool cap_here = cap_kind != ZIR_PRONG_CAPTURE_NONE; bool tag_cap_here = special ? vsb->else_has_tag_capture : (pinfo & ZIR_PRONG_INFO_HAS_TAG_CAPTURE) != 0; bool expand = !special && vsb->case_inline_expand[ci]; const uint32_t* items = special ? NULL : &vsb->item_vals[vsb->case_item_off[ci]]; uint32_t items_len = special ? 0 : vsb->case_items_count[ci]; uint32_t ranges_len = special ? 0 : vsb->case_ranges_count[ci]; uint32_t payload_inst = semaSwCaptureInst(zs->payload_capture_placeholder, inst); uint32_t tag_inst = semaSwCaptureInst(zs->tag_capture_placeholder, inst); uint32_t* values = sema->inst_values; uint32_t* types = sema->inst_types; uint32_t saved_caps = sema->ictx->caps_len; uint32_t saved_iv = values[payload_inst]; uint32_t saved_it = types[payload_inst]; uint32_t saved_tag_iv = values[tag_inst]; uint32_t saved_tag_it = types[tag_inst]; bool tag_runtime = false; if (tag_cap_here) { // Out-of-range means malformed ZIR, not a user-selectable // unported source shape. if (tag_inst >= sema->zir->inst_len) UNIMPLEMENTED("switch_block: tag capture placeholder"); if (!op->is_union || ranges_len != 0u) UNIMPLEMENTED("switch_block: tag capture"); uint32_t tag_item = UINT32_MAX; if (expand) { if (expand_item == UINT32_MAX) UNIMPLEMENTED("switch_block: inline tag capture item"); tag_item = items[expand_item]; } else if (items_len == 1u && cap_kind != ZIR_PRONG_CAPTURE_BY_REF) { // resolveSwitchCaptureFromProngItems: only a by-value // single-item capture is comptime-known. tag_item = items[0]; } // Otherwise analyzeSwitchCaptures' `unionToTag` of the // (re)loaded operand, after the payload capture below: // under a labeled `continue` the operand is the loop // slot's current value, not the dispatch's first tag. tag_runtime = tag_item == UINT32_MAX; values[tag_inst] = tag_item; types[tag_inst] = op->item_ty; // A tag-only capture can occupy the switch inst itself; a `break` // to the switch then finds the merge block through the InlineCtx. if (tag_inst == inst && !cap_here) semaSwitchCaptureEnter( sema->ictx, saved_caps, inst, merge_blk_idx); } if (cap_here) { if (special && op->is_error_set) { // The `.special` arm: the loaded operand narrowed to the // residual set by an `error_cast`, even for a one-name // residual. values[payload_inst] = semaEmitTyOp(sema->insts, cb, AIR_INST_ERROR_CAST, vsb->else_err_ty, analyzeSwitchOperandLoad(sema->insts, cb, op->cond, op->operand_ty, &op->loop, false)); types[payload_inst] = vsb->else_err_ty; } else if (special) { // The `.special` arm: the operand pointer, or the loaded // operand (a union's whole value, not its tag). bool by_ref = cap_kind == ZIR_PRONG_CAPTURE_BY_REF; values[payload_inst] = analyzeSwitchOperandLoad(sema->insts, cb, (by_ref ? op->raw_val : op->by_val), op->operand_ty, &op->loop, by_ref); types[payload_inst] = by_ref ? op->loop.raw_ty : op->operand_ty; } else if (op->is_union) { values[payload_inst] = analyzeSwitchPayloadCaptureTaggedUnion(sema, cb, op, zs->has_continue, vsb, ci, expand_item, cap_kind, &types[payload_inst]); } else if (cap_kind == ZIR_PRONG_CAPTURE_BY_REF) { // analyzeSwitchCaptures' scalar by-ref arm: an // `inline` item resolves to `uavRef(item_val)`, // anything else is analyzeSwitchOperandLoad(by_ref), // the operand pointer itself. if (expand) { types[payload_inst] = ipGetPtrType( sema->ip, op->item_ty, IP_PTR_FLAGS_CONST_ONE); values[payload_inst] = ipGetPtrUav(sema->ip, types[payload_inst], expand_val); } else { values[payload_inst] = analyzeSwitchOperandLoad(sema->insts, cb, op->raw_val, op->operand_ty, &op->loop, true); types[payload_inst] = op->loop.raw_ty; } } else if (op->is_error_set) { if (ranges_len != 0u) UNIMPLEMENTED( "switch_block: error-set payload capture with ranges"); // resolveSwitchCaptureFromProngItems: an // inline-expanded case captures its one item. const uint32_t* first = expand ? &items[expand_item] : items; uint32_t n = expand ? 1u : items_len; if (n == 0u) UNIMPLEMENTED("switch_block: empty error-set payload capture"); uint32_t cap_ty = semaSwitchErrCaptureTy(sema->ip, first, n); // analyzeSwitchOperandLoad: the loop slot's current value // under a labeled `continue`. values[payload_inst] = n == 1u ? ipGetErrorSetError(sema->ip, cap_ty, semaSwItemErrorName(sema->ip, first[0])) : semaEmitTyOp(sema->insts, cb, AIR_INST_ERROR_CAST, cap_ty, analyzeSwitchOperandLoad(sema->insts, cb, op->cond, op->operand_ty, &op->loop, false)); types[payload_inst] = cap_ty; } else if (items_len == 1u && ranges_len == 0u) { // A single-item scalar prong's capture is the item itself. values[payload_inst] = items[0]; types[payload_inst] = op->item_ty; } else if (expand_item == UINT32_MAX && !expand) { // A grouped non-inline scalar prong's capture is the runtime // operand itself, narrowed by control flow. values[payload_inst] = analyzeSwitchOperandLoad( sema->insts, cb, op->by_val, op->operand_ty, &op->loop, false); types[payload_inst] = op->operand_ty; } else { // An inline-expanded prong's capture is this AIR case's one // item or range value (`emit_src_item_val`). values[payload_inst] = expand_val; types[payload_inst] = op->item_ty; } // The body may break the capture to the switch's merge block. semaSwitchCaptureEnter(sema->ictx, saved_caps, inst, merge_blk_idx); } if (tag_runtime) values[tag_inst] = semaUnionToTagRuntime(sema->zcu, op->operand_ty, analyzeSwitchOperandLoad( sema->insts, cb, op->by_val, op->operand_ty, &op->loop, false), sema->insts, cb, NULL); // The switch inst is the `continue` operand's result type // (AstGen's `continue_result_info`); a labeled switch keeps // its captures in placeholders, so this never clobbers one. bool switch_continue_rebind = zs->has_continue && !(cap_here && payload_inst == inst) && !(tag_cap_here && tag_inst == inst); uint32_t swc_saved_iv = values[inst]; uint32_t swc_saved_it = types[inst]; if (switch_continue_rebind) { values[inst] = op->loop.raw_ty; types[inst] = ZIR_REF_TYPE_TYPE; semaSwitchCaptureEnter(sema->ictx, saved_caps, inst, merge_blk_idx); } SemaBlock case_blk; semaBlockPush(sema->ictx, &case_blk, true, UINT32_MAX, UINT32_MAX); if (special) analyzeBodyInner(sema, cb, vsb->else_body, vsb->else_body_len); else analyzeBodyInner( sema, cb, vsb->prong_bodies[ci], vsb->prong_body_lens[ci]); addRuntimeBreak( sema->ip, sema->zir, sema->ictx, sema->insts, cb, values, types); semaBlockPop(sema->ictx, &case_blk); if (switch_continue_rebind) { values[inst] = swc_saved_iv; types[inst] = swc_saved_it; } if (cap_here) { values[payload_inst] = saved_iv; types[payload_inst] = saved_it; } if (tag_cap_here) { values[tag_inst] = saved_tag_iv; types[tag_inst] = saved_tag_it; } sema->ictx->caps_len = saved_caps; } // Sema.zig:finishSwitchBr // The runtime switch_br: each case's prong, then the catch-all (else) body, // then the Air.SwitchBr payload. `placeholders` are the switch_continue // `br`s (Merges.extra_insts), which make it a loop_switch_br. static uint32_t finishSwitchBr(SemaCtx* sema, const SwitchOperand* op, const ZirSwitchBlock* zs, uint32_t inst, uint32_t merge_blk_idx, const ValidatedSwitchBlock* vsb, const U32List* placeholders) { // finishSwitchBr: analyze each scalar prong body into its own // sub-block, then the catch-all (else) body. These may append to // air_extra (nested blocks); the SwitchBr payload is appended // after. A `vsb->case_inline_expand[ci]` case (a runtime `inline` // payload capture -- scalar or union, any item count >= 1, or a // scalar range capture in the small readable-integer subset // decoded below) contributes one AIR case per concrete item here // of the usual one; `emit_total_cases` is the resulting AIR case // count (equal to `vsb->cases_len` when nothing expands), and // `emit_src_ci`/`emit_src_item_val` map each emitted AIR case back // to its ZIR case and concrete item value. Every // array below that is genuinely per-AIR-CASE (`case_blocks`, // `branch_hints`) is indexed by the emitted index `e`; arrays // that describe the ZIR case itself (`vsb->case_items_count`, // `vsb->item_vals`, `prong_bodies`, ...) stay indexed by `ci`. uint32_t extra_expand_cases = 0; for (uint32_t ci = 0; ci < vsb->cases_len; ci++) if (ci != vsb->under_ci && vsb->case_inline_expand[ci]) { uint32_t expanded_count = vsb->case_items_count[ci]; for (uint32_t r = 0; r < vsb->case_ranges_count[ci]; r++) { uint64_t first = ipReadIntValue(sema->ip, vsb->range_vals[vsb->case_range_off[ci] + 2 * r]); uint64_t last = ipReadIntValue(sema->ip, vsb->range_vals[vsb->case_range_off[ci] + 2 * r + 1]); if (first == UINT64_MAX || last == UINT64_MAX || first > last || last - first > UINT32_MAX) UNIMPLEMENTED( "switch_block: inline range expansion bounds"); expanded_count += (uint32_t)(last - first + 1u); } if (expanded_count == 0u) UNIMPLEMENTED("switch_block: zero-item expanding case"); extra_expand_cases += expanded_count - 1u; } // A `_` catch-all case (vsb->under_ci) never gets its own AIR Case // entry -- like `else`, it is emitted as Air.SwitchBr's trailing // else-body (validateSwitchBlock aliased the else_* fields onto it), // so it's excluded from both the count and the // emit_src_ci/emit_src_item map. if (vsb->under_ci != UINT32_MAX && vsb->under_ci >= vsb->cases_len) UNREACHABLE("switch_block: `_` case outside the cases"); uint32_t under_case_count = (vsb->under_ci != UINT32_MAX) ? 1u : 0u; uint32_t emit_total_cases = vsb->cases_len - under_case_count + extra_expand_cases; uint32_t emit_alloc = emit_total_cases ? emit_total_cases : 1u; uint32_t* emit_src_ci = zig1Malloc(emit_alloc * sizeof(uint32_t)); uint32_t* emit_src_item = zig1Malloc(emit_alloc * sizeof(uint32_t)); uint32_t* emit_src_item_val = zig1Malloc(emit_alloc * sizeof(uint32_t)); U32List* case_blocks = zig1Malloc(emit_alloc * sizeof(U32List)); uint8_t* branch_hints = zig1Calloc(emit_total_cases + 1u, sizeof(uint8_t)); { uint32_t e = 0; for (uint32_t ci = 0; ci < vsb->cases_len; ci++) { if (ci == vsb->under_ci) continue; if (vsb->case_inline_expand[ci]) { for (uint32_t k = 0; k < vsb->case_items_count[ci]; k++) { emit_src_ci[e] = ci; emit_src_item[e] = k; emit_src_item_val[e] = vsb->item_vals[vsb->case_item_off[ci] + k]; e++; } for (uint32_t r = 0; r < vsb->case_ranges_count[ci]; r++) { uint64_t first = ipReadIntValue(sema->ip, vsb->range_vals[vsb->case_range_off[ci] + 2 * r]); uint64_t last = ipReadIntValue(sema->ip, vsb->range_vals[vsb->case_range_off[ci] + 2 * r + 1]); if (first == UINT64_MAX || last == UINT64_MAX || first > last) UNIMPLEMENTED( "switch_block: inline range expansion bounds"); for (uint64_t v = first; v <= last; v++) { uint32_t item_ip = ipInternIntResult(sema->ip, v, op->item_ty); if (item_ip == IP_INDEX_NONE) UNIMPLEMENTED("switch_block: inline range integer " "item type"); emit_src_ci[e] = ci; emit_src_item[e] = UINT32_MAX; emit_src_item_val[e] = item_ip; e++; if (v == UINT64_MAX) break; } } } else { emit_src_ci[e] = ci; emit_src_item[e] = UINT32_MAX; emit_src_item_val[e] = IP_INDEX_NONE; e++; } } } for (uint32_t e = 0; e < emit_total_cases; e++) { uint32_t ci = emit_src_ci[e]; // finishSwitchBr's inline item and range loops: `"if // (emit_bb) try sema.emitBackwardBranch(block, bb_src); // emit_bb = true;"` -- every expanded value after a case's // first. if (e != 0 && vsb->case_inline_expand[ci] && emit_src_ci[e - 1] == ci) semaChargeBranches(sema->zcu, 1); uint32_t expand_item = emit_src_item[e]; U32List cb = { 0 }; // finishSwitchBr's `any_analyze_body`: an inline-expanded union // case asks for its one item, any other union or `comptime // unreachable` error case for every item. bool want_body = true; bool unreach = semaSwProngIsComptimeUnreach(vsb->prong_infos[ci]); if (op->is_union || (op->is_error_set && unreach && vsb->case_ranges_count[ci] == 0u)) { bool one = op->is_union && vsb->case_inline_expand[ci]; uint32_t first = vsb->case_item_off[ci] + (one ? expand_item : 0u); uint32_t n = one ? 1u : vsb->case_items_count[ci]; want_body = false; for (uint32_t it = 0; it < n && !want_body; it++) want_body = wantSwitchProngBodyAnalysis(sema, vsb->item_vals[first + it], op->operand_ty, op->is_union, op->is_error_set, unreach); } if (want_body) { analyzeSwitchProng(sema, &cb, op, zs, inst, merge_blk_idx, vsb, ci, expand_item, emit_src_item_val[e]); } else { semaEmitTyOp(sema->insts, &cb, AIR_INST_UNREACH, 0, 0); branch_hints[e] = 3u; // std.lang.BranchHint.cold } case_blocks[e] = cb; } // The catch-all: `unreach` when every residual payload is // noreturn, else the `.special` prong. U32List else_block = { 0 }; if (vsb->else_all_noreturn) semaEmitTyOp(sema->insts, &else_block, AIR_INST_UNREACH, 0, 0); // A null `else_err_ty` (an empty resolved set, or every error handled // beside a simple-noreturn else): analyzeSwitchCaptures' // analyzeUnreachable // + `.unreachable_value`. else if (op->is_error_set && vsb->else_capture == ZIR_PRONG_CAPTURE_BY_VAL && vsb->else_err_ty == IP_INDEX_NONE) semaEmitTyOp(sema->insts, &else_block, AIR_INST_UNREACH, 0, 0); else if (vsb->catch_all_body) analyzeSwitchProng(sema, &else_block, op, zs, inst, merge_blk_idx, vsb, SW_PRONG_SPECIAL, UINT32_MAX, IP_INDEX_NONE); else if (vsb->has_else && vsb->else_is_inline) { // An `inline else` analyzed its values above: `unreach`, cold. semaEmitTyOp(sema->insts, &else_block, AIR_INST_UNREACH, 0, 0); branch_hints[emit_total_cases] = 3u; // std.lang.BranchHint.cold } // Assemble the Air.SwitchBr payload (mirrors finishSwitchBr's // final flush): { cases_len, else_body_len }, then branch-hint // bags, then each Case { items_len, ranges_len, body_len } + items // + body, then the else body. uint32_t payload_index = sema->air_extra->len; u32ListAppend(sema->air_extra, emit_total_cases); u32ListAppend(sema->air_extra, else_block.len); uint32_t num_bags = (emit_total_cases + AIR_SWITCH_BR_HINTS_PER_WORD) / AIR_SWITCH_BR_HINTS_PER_WORD; for (uint32_t bg = 0; bg < num_bags; bg++) { uint32_t bag = 0; for (uint32_t j = 0; j < AIR_SWITCH_BR_HINTS_PER_WORD; j++) { uint32_t hi = bg * AIR_SWITCH_BR_HINTS_PER_WORD + j; if (hi >= emit_total_cases + 1u) break; bag |= ((uint32_t)branch_hints[hi] & AIR_BRANCH_HINTS_TRUE_MASK) << (AIR_SWITCH_BR_HINT_BITS * j); } u32ListAppend(sema->air_extra, bag); } for (uint32_t e = 0; e < emit_total_cases; e++) { uint32_t ci = emit_src_ci[e]; if (vsb->case_inline_expand[ci]) { // One expanded item or range value: items_len=1, no ranges. u32ListAppend(sema->air_extra, 1u); // items_len u32ListAppend(sema->air_extra, 0u); // ranges_len u32ListAppend(sema->air_extra, case_blocks[e].len); // body_len u32ListAppend(sema->air_extra, emit_src_item_val[e]); } else { u32ListAppend( sema->air_extra, vsb->case_items_count[ci]); // items_len u32ListAppend( sema->air_extra, vsb->case_ranges_count[ci]); // ranges_len u32ListAppend(sema->air_extra, case_blocks[e].len); // body_len for (uint32_t it = 0; it < vsb->case_items_count[ci]; it++) u32ListAppend(sema->air_extra, vsb->item_vals[vsb->case_item_off[ci] + it]); // Each range is a [first, last] ref pair (no expansion). for (uint32_t r = 0; r < 2 * vsb->case_ranges_count[ci]; r++) u32ListAppend(sema->air_extra, vsb->range_vals[vsb->case_range_off[ci] + r]); } for (uint32_t k = 0; k < case_blocks[e].len; k++) u32ListAppend(sema->air_extra, case_blocks[e].items[k]); free(case_blocks[e].items); } for (uint32_t k = 0; k < else_block.len; k++) u32ListAppend(sema->air_extra, else_block.items[k]); free(else_block.items); free(branch_hints); free(case_blocks); free(emit_src_ci); free(emit_src_item); free(emit_src_item_val); // Emit AIR switch_br{ pl_op: operand=cond, payload=payload_index // }. AirInstData sw_data; memset(&sw_data, 0, sizeof(sw_data)); sw_data.pair.a = op->cond; // union → get_union_tag; else operand sw_data.pair.b = payload_index; AirInstTag sw_air_tag = placeholders->len > 0 ? AIR_INST_LOOP_SWITCH_BR : AIR_INST_SWITCH_BR; uint32_t sw_idx = semaAirInstAppend(sema->insts, sw_air_tag, sw_data); return sw_idx; } // Sema.zig:analyzeSwitchBlock // The operand block, then either the comptime fold (resolveSwitchBlock; // returns true with *ends) or the runtime switch_br appended to // `child_block` (returns false). validateSwitchBlock runs after a by-ref // operand's load and before the rest of the operand block. static bool analyzeSwitchBlock(SemaCtx* sema, U32List* block, U32List* child_block, uint32_t raw_operand, uint32_t raw_operand_ty, bool operand_is_ref, uint32_t merge_blk_idx, uint32_t inst, const ZirSwitchBlock* zs, bool* ends) { InternPool* ip = sema->ip; SwitchOperand op; memset(&op, 0, sizeof(op)); op.raw_val = raw_operand; op.loop.alloc = UINT32_MAX; op.loop.raw_ty = raw_operand_ty; op.loop.is_ref = operand_is_ref; // validateSwitchBlock's by-ref `"try sema.ensureLayoutResolved(child_ty, // operand_src, .ptr_access);"` if (operand_is_ref) semaEnsurePointeeLayoutResolved(sema, zs->main_operand); // fieldPtr answers a field of a comptime @typeInfo value by value; its // address for a by-ref switch is the value's `*const` uav. if (operand_is_ref && semaRefIsInterned(op.raw_val) && !ipItemTagIs(ip, ipTypeOf(ip, op.raw_val), IP_TAG_TYPE_POINTER)) { op.loop.raw_ty = ipGetPtrType( ip, ipTypeOf(ip, op.raw_val), IP_PTR_FLAGS_CONST_ONE); op.raw_val = ipGetPtrUav(ip, op.loop.raw_ty, op.raw_val); } op.by_val = op.raw_val; op.operand_ty = op.loop.raw_ty; if (operand_is_ref) { // switch_block_ref: by_ref is the raw pointer, by_val its load. if (!semaIsAirInstRef(op.raw_val)) op.loop.raw_ty = ipTypeOf(ip, op.raw_val); op.by_val = analyzeLoad(sema->zcu, sema->insts, block, op.raw_val, op.loop.raw_ty, &op.operand_ty); } if (op.operand_ty >= ZIR_REF_START_INDEX && op.operand_ty >= ip->items_len) UNIMPLEMENTED("switch_block: invalid operand type"); // resolve_at_comptime: a `.simple` operand with a comptime cond. op.comptime_known = !zs->has_continue && !semaIsAirInstRef(op.by_val); op.is_union = ip->item_tags[op.operand_ty] == IP_TAG_TYPE_UNION; op.is_error_set = ztIsZigTypeTag(ip, op.operand_ty, ZIG_TYPE_TAG_ERROR_SET); op.item_ty = op.operand_ty; op.cond = op.by_val; op.ct_tag = IP_INDEX_NONE; op.ct_payload = IP_INDEX_NONE; if (op.is_union && op.comptime_known) { // A comptime union value: the fold dispatches on its active tag // and a by-val capture binds its payload. if (!ipItemTagIs(ip, op.by_val, IP_TAG_UNION_VALUE)) UNIMPLEMENTED( "switch_block: comptime union operand not a union value"); uint32_t uv_off = ip->item_datas[op.by_val]; op.ct_tag = ip->extra[uv_off + 1]; op.ct_payload = ip->extra[uv_off + 2]; } else if (op.is_union) { if (ipUnionTagUsage(ip, op.operand_ty) != IP_UNION_TAG_USAGE_TAGGED) UNIMPLEMENTED("switch_block: plain union runtime ABI"); semaResolveAllUnionDeferred(sema->zcu, op.operand_ty); } if (op.is_union) op.item_ty = ipEnsureGeneratedUnionTag(ip, op.operand_ty); // A switch on an empty enum or union is void. if (op.item_ty < ip->items_len && ipIsEnumType(ip, op.item_ty) && ipEnumFieldsLen(ip, op.item_ty) == 0 && !ztIsNonexhaustiveEnum(ip, op.operand_ty)) { sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; *ends = false; return true; } ValidatedSwitchBlock vsb; validateSwitchBlock(sema, block, inst, zs, &op, &vsb); if (op.is_union && !op.comptime_known) op.cond = semaUnionToTagRuntime( sema->zcu, op.operand_ty, op.by_val, sema->insts, block, NULL); // The `.loop` operand's alloc, which a `continue` stores into and the // captures load. if (zs->has_continue && zs->any_maybe_runtime_capture) { op.loop.alloc = semaEmitTyOp(sema->insts, block, AIR_INST_ALLOC, ipGetPtrType(ip, op.loop.raw_ty, IP_PTR_FLAGS_MUT_ONE), 0); semaEmitTyOp( sema->insts, block, AIR_INST_STORE, op.loop.alloc, op.raw_val); } if (op.comptime_known) { *ends = resolveSwitchBlock( sema, block, &op, zs, inst, merge_blk_idx, &vsb); semaValidatedSwitchBlockFree(&vsb); return true; } U32List placeholders = { 0 }; InlineCtx* ictx = sema->ictx; bool saved_active = ictx->swc_active; uint32_t saved_switch_inst = ictx->swc_switch_inst; uint32_t saved_merge_air = ictx->swc_merge_air; uint32_t saved_raw_ty = ictx->swc_raw_ty; U32List* saved_placeholders = ictx->swc_placeholders; if (zs->has_continue) { ictx->swc_active = true; ictx->swc_switch_inst = inst; ictx->swc_merge_air = merge_blk_idx; ictx->swc_raw_ty = op.loop.raw_ty; // cppcheck-suppress autoVariables ; restored below, in scope ictx->swc_placeholders = &placeholders; } uint32_t sw_idx = finishSwitchBr( sema, &op, zs, inst, merge_blk_idx, &vsb, &placeholders); semaValidatedSwitchBlockFree(&vsb); if (zs->has_continue) { ictx->swc_active = saved_active; ictx->swc_switch_inst = saved_switch_inst; ictx->swc_merge_air = saved_merge_air; ictx->swc_raw_ty = saved_raw_ty; ictx->swc_placeholders = saved_placeholders; } u32ListAppend(child_block, sw_idx); // Each zirSwitchContinue placeholder `br` becomes its dispatch: the // store to the operand alloc, the load of a by-ref operand, the union's // tag, then `switch_dispatch`, in a noreturn block unless it is one inst. for (uint32_t k = 0; k < placeholders.len; k++) { uint32_t ph = placeholders.items[k]; uint32_t operand = sema->insts->datas[ph].pair.b; U32List rb = { 0 }; if (op.loop.alloc != UINT32_MAX) semaEmitTyOp( sema->insts, &rb, AIR_INST_STORE, op.loop.alloc, operand); uint32_t val_ty = op.loop.raw_ty; if (operand_is_ref) operand = analyzeLoad( sema->zcu, sema->insts, &rb, operand, op.loop.raw_ty, &val_ty); if (op.is_union) operand = semaUnionToTagRuntime( sema->zcu, val_ty, operand, sema->insts, &rb, NULL); semaEmitTyOp( sema->insts, &rb, AIR_INST_SWITCH_DISPATCH, sw_idx, operand); if (rb.len == 1) { sema->insts->tags[ph] = sema->insts->tags[rb.items[0]]; sema->insts->datas[ph] = sema->insts->datas[rb.items[0]]; } else { sema->insts->tags[ph] = AIR_INST_BLOCK; sema->insts->datas[ph].pair.a = ZIR_REF_NORETURN_TYPE; sema->insts->datas[ph].pair.b = sema->air_extra->len; u32ListAppend(sema->air_extra, rb.len); for (uint32_t i = 0; i < rb.len; i++) u32ListAppend(sema->air_extra, rb.items[i]); } free(rb.items); } free(placeholders.items); return false; } // Sema.zig:zirSwitchBlock // Returns whether the body ends here (noreturn or a comptime break). static bool zirSwitchBlock( SemaCtx* sema, U32List* block, uint32_t inst, bool operand_is_ref) { // Sema.zirSwitchBlock reserves a `block` AIR inst up front (the // merge target for value-yielding `break`s). Here all prongs // return, so it stays dangling (data undefined; the comparator // skips it). AirInstData merge_blk_data; memset(&merge_blk_data, 0, sizeof(merge_blk_data)); uint32_t merge_blk_idx = semaAirInstAppend(sema->insts, AIR_INST_BLOCK, merge_blk_data); sema->inst_values[inst] = AIR_REF_INST_BASE + merge_blk_idx; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; ZirSwitchBlock zs = zirGetSwitchBlock(sema->zir, inst); U32List child_block = { 0 }; bool ends; if (analyzeSwitchBlock(sema, block, &child_block, semaResolveRef(zs.main_operand, sema->inst_values), semaResolveRefType(zs.main_operand, sema->inst_types), operand_is_ref, merge_blk_idx, inst, &zs, &ends)) return ends; return resolveAnalyzedBlock(sema->zcu, sema->ictx, sema->insts, sema->air_extra, block, child_block, merge_blk_idx, &sema->inst_values[inst], &sema->inst_types[inst]); } // Sema.zig:zirSwitchBlockErrUnion // Returns whether the body ends here (noreturn or a comptime break). // Upstream's inst_map entry for the switch, its payload or its error, is // inst_values[inst], and a `break` to the switch finds the merge block through // semaSwitchCaptureEnter. static bool zirSwitchBlockErrUnion( SemaCtx* sema, U32List* block, uint32_t inst) { const InternPool* ip = sema->ip; AirInstData block_data; memset(&block_data, 0, sizeof(block_data)); uint32_t block_inst = semaAirInstAppend(sema->insts, AIR_INST_BLOCK, block_data); uint32_t merge_ref = AIR_REF_INST_BASE + block_inst; sema->inst_values[inst] = merge_ref; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; ZirSwitchBlock zs = zirGetSwitchBlock(sema->zir, inst); U32List child_block = { 0 }; U32List non_err_block = { 0 }; U32List switch_block = { 0 }; uint32_t non_err_body_len = zs.non_err_info & ZIR_PRONG_NON_ERR_BODY_LEN_MASK; uint32_t non_err_capture = (zs.non_err_info >> ZIR_PRONG_NON_ERR_CAPTURE_SHIFT) & ZIR_PRONG_NON_ERR_CAPTURE_MASK; bool operand_is_ref = (zs.non_err_info & ZIR_PRONG_NON_ERR_OPERAND_IS_REF) != 0; uint32_t eu_maybe_ptr = semaResolveRef(zs.main_operand, sema->inst_values); uint32_t raw_operand_ty = semaResolveRefValType( ip, zs.main_operand, eu_maybe_ptr, sema->inst_types); uint32_t err_union_ty = raw_operand_ty; if (operand_is_ref) { IpPtrInfo ptr_info; if (ipPtrInfo(ip, raw_operand_ty, &ptr_info) != IP_PTRI_OK) COMPILE_ERROR_UPSTREAM("switch_block_err_union: expected pointer"); err_union_ty = ptr_info.child; ensureLayoutResolved(sema->zcu, err_union_ty); } if (!semaIsErrorUnionType(ip, err_union_ty)) COMPILE_ERROR_UPSTREAM("switch_block_err_union: expected error union"); uint32_t non_err_cond = operand_is_ref ? analyzePtrIsNonErr(sema, block, eu_maybe_ptr, raw_operand_ty) : analyzeIsNonErr(sema, block, eu_maybe_ptr, err_union_ty); // Don't analyze the non-error body if it's unreachable. uint32_t non_err_hint = 0; if (non_err_cond != ZIR_REF_BOOL_FALSE) { uint32_t saved_caps = sema->ictx->caps_len; if (non_err_capture == ZIR_PRONG_CAPTURE_BY_VAL) { // A by-value capture of a by-ref operand loads it first. uint32_t eu = eu_maybe_ptr; if (operand_is_ref) { uint32_t eu_ty; eu = analyzeLoad(sema->zcu, sema->insts, &non_err_block, eu, raw_operand_ty, &eu_ty); } sema->inst_values[inst] = analyzeErrUnionPayload(sema, &non_err_block, err_union_ty, eu, &sema->inst_types[inst]); } else if (non_err_capture == ZIR_PRONG_CAPTURE_BY_REF) { sema->inst_values[inst] = analyzeErrUnionPayloadPtr(sema, &non_err_block, eu_maybe_ptr, raw_operand_ty, &sema->inst_types[inst]); } if (non_err_capture != ZIR_PRONG_CAPTURE_NONE) { semaSwitchCaptureEnter(sema->ictx, saved_caps, inst, block_inst); } if (non_err_cond == ZIR_REF_BOOL_TRUE) { // Early return; we don't analyze the switch as it's unreachable. analyzeBodyInner( sema, &non_err_block, zs.non_err_body, non_err_body_len); sema->ictx->caps_len = saved_caps; return semaResolveSwitchBody( sema, block, non_err_block, inst, block_inst); } bool parent_hint_set = sema->ictx->branch_hint_set; uint32_t parent_hint = sema->ictx->branch_hint; sema->ictx->branch_hint_set = false; sema->ictx->branch_hint = 0; SemaBlock non_err_blk; semaBlockPush(sema->ictx, &non_err_blk, true, UINT32_MAX, UINT32_MAX); analyzeBodyInner( sema, &non_err_block, zs.non_err_body, non_err_body_len); addRuntimeBreak(ip, sema->zir, sema->ictx, sema->insts, &non_err_block, sema->inst_values, sema->inst_types); semaBlockPop(sema->ictx, &non_err_blk); if (sema->ictx->branch_hint_set) non_err_hint = sema->ictx->branch_hint; sema->ictx->branch_hint_set = parent_hint_set; sema->ictx->branch_hint = parent_hint; sema->ictx->caps_len = saved_caps; } // Emit this into the switch block as it's our error case! uint32_t eu_code_ty = ipErrorUnionErrorSet(ip, err_union_ty); uint32_t raw_switch_operand = operand_is_ref ? analyzeErrUnionCodePtr( sema, &switch_block, eu_maybe_ptr, err_union_ty) : analyzeErrUnionCode(sema, &switch_block, eu_maybe_ptr, err_union_ty); // make err capture (i.e. switch operand) available to switch prong bodies uint32_t saved_caps = sema->ictx->caps_len; sema->inst_values[inst] = raw_switch_operand; sema->inst_types[inst] = eu_code_ty; semaSwitchCaptureEnter(sema->ictx, saved_caps, inst, block_inst); bool ends; bool folded = analyzeSwitchBlock(sema, block, &switch_block, raw_switch_operand, eu_code_ty, false, block_inst, inst, &zs, &ends); sema->ictx->caps_len = saved_caps; if (folded) { if (non_err_cond != ZIR_REF_BOOL_FALSE) UNREACHABLE("switch_block_err_union: comptime error of a runtime " "error union"); return ends; } sema->inst_values[inst] = merge_ref; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; if (non_err_cond == ZIR_REF_BOOL_FALSE) return resolveAnalyzedBlock(sema->zcu, sema->ictx, sema->insts, sema->air_extra, block, switch_block, block_inst, &sema->inst_values[inst], &sema->inst_types[inst]); uint32_t cond_payload = sema->air_extra->len; u32ListAppend(sema->air_extra, non_err_block.len); u32ListAppend(sema->air_extra, switch_block.len); u32ListAppend(sema->air_extra, AIR_COND_BR_HINTS(non_err_hint, BRANCH_HINT_UNLIKELY)); for (uint32_t k = 0; k < non_err_block.len; k++) u32ListAppend(sema->air_extra, non_err_block.items[k]); for (uint32_t k = 0; k < switch_block.len; k++) u32ListAppend(sema->air_extra, switch_block.items[k]); free(non_err_block.items); free(switch_block.items); AirInstData cond_data; memset(&cond_data, 0, sizeof(cond_data)); cond_data.pair.a = non_err_cond; cond_data.pair.b = cond_payload; u32ListAppend(&child_block, semaAirInstAppend(sema->insts, AIR_INST_COND_BR, cond_data)); return resolveAnalyzedBlock(sema->zcu, sema->ictx, sema->insts, sema->air_extra, block, child_block, block_inst, &sema->inst_values[inst], &sema->inst_types[inst]); } // Sema.zig:zirHasField // Sema.zirHasField: comptime-fold `@hasField(Container, "name")` to a bool. // ZIR pl_node -> Bin { lhs=container_type, rhs=name_ref }. static void zirHasField(SemaCtx* sema, uint32_t inst) { uint32_t hpp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, hpp, &extra); uint32_t hty_ref = extra.lhs; uint32_t hname_ref = extra.rhs; uint32_t hty = semaResolveRef(hty_ref, sema->inst_values); if (semaIsAirInstRef(hty)) UNIMPLEMENTED("has_field: runtime container type"); uint32_t hname_val = semaResolveRef(hname_ref, sema->inst_values); if (semaIsAirInstRef(hname_val)) UNIMPLEMENTED("has_field: runtime field name"); uint32_t hfield_ip = resolveConstStringIntern(sema->zcu, hname_val); // `"try sema.ensureLayoutResolved(ty, ty_src, .field_queried);"` ensureLayoutResolved(sema->zcu, hty); const char* hname = ipStringBytes(sema->ip, hfield_ip); uint32_t htuple_index; IpArrayInfo harr; bool has = false; if (ztIsSlice(sema->ip, hty)) { has = strcmp(hname, "ptr") == 0 || strcmp(hname, "len") == 0; } else if (ztIsTuple(sema->ip, hty)) { // `"const field_index = field_name.toUnsigned(ip) orelse // break :hf false; break :hf field_index < // tuple.types.len;"` has = ipBytesToUnsigned(hname, (uint32_t)strlen(hname), &htuple_index) && htuple_index < ipTupleFieldCount(sema->ip, hty); } else if (ipTagIsAnyStruct(sema->ip->item_tags[hty])) { has = ipStructFieldIndex(sema->ip, hty, hfield_ip) != UINT32_MAX; } else if (ztZigTypeTag(sema->ip, hty) == ZIG_TYPE_TAG_UNION) { has = ipUnionFieldIndex(sema->ip, hty, hfield_ip) != UINT32_MAX; } else if (ipIsEnumType(sema->ip, hty)) { has = ipEnumFieldIndexFromName(sema->ip, hty, hfield_ip) != UINT32_MAX; } else if (ipArrayInfo(sema->ip, hty, &harr) == IP_ARR_OK && !harr.is_vector) { has = strcmp(hname, "len") == 0; } else { COMPILE_ERROR_UPSTREAM("has_field: type does not support '@hasField'"); } sema->inst_values[inst] = has ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; } // Sema.zig:zirHasDecl // Sema.zirHasDecl: comptime-fold `@hasDecl(Container, "name")` to a bool. // ZIR pl_node -> Bin { lhs=type, rhs=name_ref }. checkNamespaceType // (`".@\"struct\", .@\"enum\", .@\"union\", .@\"opaque\" => return,"` by // zigTypeTag, so a tuple passes too), then `"getNamespace(zcu).unwrap() // orelse return .bool_false"` and lookupInNamespace, whose hit counts only // when `accessible == .public`: a private declaration is not seen, even from // its own file. static void zirHasDecl(SemaCtx* sema, uint32_t inst) { uint32_t dpp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, dpp, &extra); uint32_t dty_ref = extra.lhs; uint32_t dname_ref = extra.rhs; uint32_t dty = semaResolveRef(dty_ref, sema->inst_values); if (semaIsAirInstRef(dty)) UNIMPLEMENTED("has_decl: runtime container type"); uint32_t dname_val = semaResolveRef(dname_ref, sema->inst_values); if (semaIsAirInstRef(dname_val)) UNIMPLEMENTED("has_decl: runtime decl name"); uint32_t name = resolveConstStringIntern(sema->zcu, dname_val); const InternPool* ip = sema->ip; if (!ztIsNamespaceType(ip, dty) && !ztIsTuple(ip, dty)) COMPILE_ERROR_UPSTREAM( "has_decl: expected struct, enum, union, or opaque"); uint32_t ns = ipContainerNamespaceOrNone(ip, dty); sema->inst_values[inst] = ns != IP_NAMESPACE_NONE && ipNamespaceLookupPub(ip, ns, name) != IP_INDEX_NONE ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; } // Sema.zig:zirImport // Sema.zirImport (src/Sema.zig): an `@import(...)` reached while analyzing a // body. Upstream has exactly one zirImport and it is context-blind — no // `block.semaBlockIsComptime()` branch — so a runtime function body and a // comptime body take the same path: `pt.doImport` resolves the operand against // the block's file scope, `ensureFilePopulated` interns the imported file's // root struct, and the result is `.fromType(ty)`, i.e. the type itself as the // instruction's value. // // Delegation, not a fourth evaluator: the operand -> file -> root-struct-type // resolution already lives in `semaImportInstRootType` (sema.c), shared with // the `const x = @import(...)` Nav path and the fn-signature // `ctResolveParamTypeRef` arm. Its module handling // (root/std/builtin/named dependency, then a relative `.zig`/`.zon` sibling // path with `..` normalization) is exactly Zcu.importFile's, and unreached // import kinds abort loudly inside it. The owning file is identified by ZIR // pointer identity. static void zirImport(SemaCtx* sema, uint32_t inst) { uint32_t imp_file = UINT32_MAX; for (uint32_t fi = 0; fi < sema->zcu->num_files; fi++) { if (sema->zcu->files[fi].zir == sema->zir) { imp_file = fi; break; } } if (imp_file == UINT32_MAX) UNIMPLEMENTED("import: import in an unregistered file"); // The `.zon` arm: LowerZon.run at the import's result type, or with none // when it has no result type or a generic one. { uint32_t imp_payload = sema->zir->inst_datas[inst].pl_tok.payload_index; ZirInstImport extra; zirExtraData(sema->zir, imp_payload, &extra); const char* imp_operand = zirNullTerminatedString(sema->zir, extra.path); AstMode imp_mode; if (zcuFileModeFromPath(imp_operand, &imp_mode) && imp_mode == AST_MODE_ZON) { uint32_t res_ty = IP_INDEX_NONE; if (extra.res_ty != ZIR_REF_NONE) { res_ty = semaResolveRef(extra.res_ty, sema->inst_values); if (semaIsAirInstRef(res_ty) || !ipIndexIsType(sema->ip, res_ty)) COMPILE_ERROR_UPSTREAM( "import: result type is not a type"); if (res_ty == IP_INDEX_GENERIC_POISON_TYPE) res_ty = IP_INDEX_NONE; } const ZcuFile* f = &sema->zcu->files[imp_file]; char* sub = zptImporterRelativePath( f->sub_path, imp_operand, strlen(imp_operand)); uint32_t zon_file = zptDemandLoadFile(sema->zcu, f->module_id, sub); free(sub); uint32_t val = lowerZonRun(sema->zcu, zon_file, res_ty); sema->inst_values[inst] = val; sema->inst_types[inst] = ipTypeOf(sema->ip, val); return; } } sema->inst_values[inst] = semaImportInstRootType(sema->zcu, imp_file, inst, NULL); // inst_types unused for type-value instructions } // Sema.zig:zirEmbedFile // Sema.zirEmbedFile: `resolveConstString` of the operand, then `"return // Air.internedToRef(result.val);"`. static void zirEmbedFile(SemaCtx* sema, uint32_t inst) { uint32_t ef_operand = semaResolveRef( sema->zir->inst_datas[inst].un_node.operand, sema->inst_values); if (semaIsAirInstRef(ef_operand)) COMPILE_ERROR_UPSTREAM("@embedFile: path not comptime-known"); char ef_buf[4096]; uint32_t ef_len = 0; ctFieldNamedNameString( sema->ip, ef_operand, ef_buf, sizeof(ef_buf), &ef_len); uint32_t ef_val = zptEmbedFile(sema->zcu, sema->zir, ef_buf, ef_len); sema->inst_values[inst] = ef_val; sema->inst_types[inst] = ipTypeOf(sema->ip, ef_val); } // Sema.zig:zirShl // (part of) Sema.zig:zirShr // Emit AIR `air_tag` (shl, shl_exact, shl_sat, and zirShr's shr, shr_exact). // The shift amount may be a comptime IP value (e.g. @as(u5, 2)) or a runtime // AIR ref. ZIR data: pl_node with Bin payload {lhs, rhs}. static void zirShl( SemaCtx* sema, U32List* block, uint32_t inst, AirInstTag air_tag) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); uint32_t lhs_ref = extra.lhs; uint32_t rhs_ref = extra.rhs; uint32_t lhs_val = semaResolveRef(lhs_ref, sema->inst_values); uint32_t rhs_val = semaResolveRef(rhs_ref, sema->inst_values); // `"if (air_tag == .shl_sat) { _ = try sema.log2IntType(block, lhs_ty, // lhs_src); ... }"`: AstGen coerces neither `<<|` operand. if (air_tag == AIR_INST_SHL_SAT && (semaIsAirInstRef(lhs_val) || lhs_val != INST_VALUE_UNSET)) log2IntType(sema->ip, semaResolveRefValType( sema->ip, lhs_ref, lhs_val, sema->inst_types)); if (!semaIsAirInstRef(lhs_val)) { // Comptime typed lhs + runtime shift amount (`@as(usize, 1) << // @intFromEnum(a)`): a runtime AIR shl/shr whose lhs operand is the // interned constant (an IP index is its own AIR Ref). Result type = // the lhs's own fixed-width type (Sema.analyzeArithmetic: // the shift result has the lhs type; a comptime_int lhs with // a runtime amount is a compile error upstream, so only a // typed lhs reaches this). if (semaIsAirInstRef(rhs_val) && ipIntValueType(sema->ip, lhs_val) != IP_INDEX_NONE) { uint32_t shc_lhs_ty = ipIntValueType(sema->ip, lhs_val); uint32_t shc_rhs_known = resolveValue( sema->zcu, sema->insts, block, sema->air_extra, rhs_val); SemaShiftCheck shc_chk = semaShiftCheck(sema->ip, sema->zir->inst_tags[inst], shc_lhs_ty, lhs_val, semaResolveRefType(rhs_ref, sema->inst_types), shc_rhs_known != IP_INDEX_NONE ? shc_rhs_known : rhs_val); if (shc_chk != ZPT_SHIFT_EMIT) { sema->inst_values[inst] = shc_chk == ZPT_SHIFT_LHS ? lhs_val : ipGetUndef(sema->ip, shc_lhs_ty); sema->inst_types[inst] = shc_lhs_ty; return; } AirInstData shc_data; memset(&shc_data, 0, sizeof(shc_data)); shc_data.pair.a = lhs_val; // constant lhs shc_data.pair.b = (uint32_t)rhs_val; // runtime amount uint32_t shc_idx = semaAirInstAppend(sema->insts, air_tag, shc_data); u32ListAppend(block, shc_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + shc_idx; sema->inst_types[inst] = shc_lhs_ty; return; } // Both comptime: `arith.shl` / `shr`, per lane for two vectors, else // the scalar fold (`shlScalar` / `shrScalar`, undef arms included), // the result at the shifted operand's type. if (!semaRefIsInterned(rhs_val)) UNIMPLEMENTED( "zirShl: shl/shr: runtime shift amount, comptime lhs"); if (lhs_val == INST_VALUE_UNSET) UNIMPLEMENTED("zirShl: shl/shr: unreadable comptime operand"); uint32_t sh_vty; uint32_t sh_vec = semaFoldComptimeVectorBinOp( sema->zcu, sema->zir->inst_tags[inst], lhs_val, rhs_val, &sh_vty); if (sh_vec != IP_INDEX_NONE) { sema->inst_values[inst] = sh_vec; sema->inst_types[inst] = sh_vty; return; } uint32_t sh_ty; sema->inst_values[inst] = semaFoldComptimeScalarBinOp(sema->zcu, sema->zir->inst_tags[inst], lhs_val, ipTypeOf(sema->ip, lhs_val), rhs_val, ipTypeOf(sema->ip, rhs_val), &sh_ty); sema->inst_types[inst] = sh_ty; return; } uint32_t lhs_ty = semaResolveRefType(lhs_ref, sema->inst_types); { uint32_t sh_rhs_ty = semaIsAirInstRef(rhs_val) ? semaResolveRefType(rhs_ref, sema->inst_types) : (rhs_val != INST_VALUE_UNSET ? ipTypeOf(sema->ip, rhs_val) : IP_INDEX_NONE); // `"const maybe_rhs_val = try // sema.resolveValueResolveLazy(rhs);"`: an amount that is a // load of a comptime-known local is checked as that value // (per lane, undef included). uint32_t sh_rhs_known = resolveValue( sema->zcu, sema->insts, block, sema->air_extra, rhs_val); SemaShiftCheck sh_chk = semaShiftCheck(sema->ip, sema->zir->inst_tags[inst], lhs_ty, lhs_val, sh_rhs_ty, sh_rhs_known != IP_INDEX_NONE ? sh_rhs_known : rhs_val); if (sh_chk != ZPT_SHIFT_EMIT) { sema->inst_values[inst] = sh_chk == ZPT_SHIFT_LHS ? lhs_val : ipGetUndef(sema->ip, lhs_ty); sema->inst_types[inst] = lhs_ty; return; } } if (air_tag == AIR_INST_SHL_SAT && !semaIsAirInstRef(rhs_val)) { // `zirShl`'s `rt_rhs` for `.shl_sat`: a comptime amount is // clamped to the bit count and typed // `smallestUnsignedInt(bit_count)`, "preventing rhs from // being a `comptime_int`". uint32_t ss_bits; bool ss_signed; uint64_t ss_amt; if (!ztIntInfo(sema->ip, lhs_ty, &ss_bits, &ss_signed) || rhs_val == INST_VALUE_UNSET) UNIMPLEMENTED("shl_sat: comptime amount of a vector"); if (!ipTryReadIntValue(sema->ip, rhs_val, &ss_amt) || ipIntValueIsNegative(sema->ip, rhs_val, ss_amt) || ss_amt > ss_bits) ss_amt = ss_bits; rhs_val = ipGetInt(sema->ip, ipSmallestUnsignedInt(sema->ip, ss_bits), (BigIntConst) { &ss_amt, 1, true }); } AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = lhs_val; // bin_op.lhs data.pair.b = (uint32_t)rhs_val; // bin_op.rhs (IP ref or AIR ref) uint32_t air_idx = semaAirInstAppend(sema->insts, air_tag, data); u32ListAppend(block, air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + air_idx; sema->inst_types[inst] = lhs_ty; } // Sema.zig:zirShr static void zirShr(SemaCtx* sema, U32List* block, uint32_t inst, bool exact) { zirShl(sema, block, inst, exact ? AIR_INST_SHR_EXACT : AIR_INST_SHR); } // Sema.zig:analyzeBitNot // `"const result_val = try arith.bitwiseNot(sema, operand_type, val);"` for a // comptime operand, else // block.addTyOp(.not) (ty_op: pair.a = operand type, pair.b = operand). static void analyzeBitNot(SemaCtx* sema, U32List* block, uint32_t inst, uint32_t operand_ref, uint32_t operand_val) { if (!semaIsAirInstRef(operand_val)) { if (operand_val >= sema->ip->items_len) UNIMPLEMENTED("analyzeBitNot: comptime operand not interned"); uint32_t bn_ty = ipComptimeValueType(sema->ip, operand_val); if (bn_ty == ZIR_REF_COMPTIME_INT_TYPE) COMPILE_ERROR_UPSTREAM( "not: bitwise not operation on comptime_int"); sema->inst_values[inst] = bitwiseNot(sema->ip, bn_ty, operand_val); sema->inst_types[inst] = bn_ty; return; } uint32_t operand_ty = semaResolveRefType(operand_ref, sema->inst_types); sema->inst_values[inst] = semaEmitTyOp( sema->insts, block, AIR_INST_NOT, operand_ty, operand_val); sema->inst_types[inst] = operand_ty; } // Sema.zig:zirBitNot static void zirBitNot(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t operand_ref = sema->zir->inst_datas[inst].un_node.operand; analyzeBitNot(sema, block, inst, operand_ref, semaResolveRef(operand_ref, sema->inst_values)); } // Sema.zig:zirBoolNot // A comptime bool folds to its negation (`!@inComptime()`) and a scalar // `undefined` is // `.undef_bool`; a vector takes `"if (uncasted_ty.isVector(zcu)) { ... // return analyzeBitNot(...)"`, and a runtime bool is the same AIR not. static void zirBoolNot(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t operand_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t operand_val = semaResolveRef(operand_ref, sema->inst_values); if (operand_val == ZIR_REF_BOOL_TRUE || operand_val == ZIR_REF_BOOL_FALSE) { sema->inst_values[inst] = (operand_val == ZIR_REF_BOOL_TRUE) ? ZIR_REF_BOOL_FALSE : ZIR_REF_BOOL_TRUE; sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; return; } if (operand_val < sema->ip->items_len && ipComptimeValueType(sema->ip, operand_val) == ZIR_REF_BOOL_TYPE) { if (!ipIsUndef(sema->ip, operand_val)) UNIMPLEMENTED("analyzeBitNot: comptime bool operand shape"); sema->inst_values[inst] = ipGetUndef(sema->ip, ZIR_REF_BOOL_TYPE); sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; return; } analyzeBitNot(sema, block, inst, operand_ref, operand_val); } // Sema.zig:analyzeTupleCat static void analyzeTupleCat(SemaCtx* sema, U32List* block, uint32_t inst, const uint32_t* vals, const uint32_t* tys) { InternPool* ip = sema->ip; uint32_t lhs_len = ipTupleFieldCount(ip, tys[0]); uint32_t rhs_len = ipTupleFieldCount(ip, tys[1]); if (lhs_len == 0 || rhs_len == 0) { int keep = lhs_len == 0 ? 1 : 0; sema->inst_values[inst] = vals[keep]; sema->inst_types[inst] = tys[keep]; return; } bool all_comptime; uint32_t tuple_ty = semaTupleCatType(ip, tys[0], tys[1], &all_comptime); if (all_comptime || (!semaIsAirInstRef(vals[0]) && !semaIsAirInstRef(vals[1]))) { sema->inst_values[inst] = semaTupleCatComptime(ip, vals[0], vals[1]); sema->inst_types[inst] = tuple_ty; return; } // `requireRuntimeBlock`, then `block.addAggregateInit` of each operand's // tupleField. uint32_t n = lhs_len + rhs_len; uint32_t* elems = zig1Malloc(n * sizeof(uint32_t)); for (uint32_t e = 0; e < n; e++) { int side = e < lhs_len ? 0 : 1; uint32_t field_ty; elems[e] = tupleField(sema, block, vals[side], tys[side], e < lhs_len ? e : e - lhs_len, &field_ty); } sema->inst_values[inst] = semaEmitAggregateInit( sema->insts, block, sema->air_extra, tuple_ty, elems, n); sema->inst_types[inst] = tuple_ty; free(elems); } // Sema.zig:getArrayCatInfo // Type.ArrayInfo of a `++` operand: false for upstream's null. static bool getArrayCatInfo(const SemaCtx* sema, uint32_t operand, uint32_t operand_ty, uint32_t peer_ty, uint32_t* elem_type, uint32_t* sentinel, uint64_t* len) { const InternPool* ip = sema->ip; uint32_t len32; IpPtrInfo ptr_info; if (ztArrayTypeInfo(ip, operand_ty, &len32, elem_type, sentinel)) { *len = len32; return true; } if (ztPtrOrSliceInfo(ip, operand_ty, &ptr_info)) { if (ptr_info.size == IP_PTR_SIZE_SLICE) { if (!ipItemTagIs(ip, operand, IP_TAG_PTR_SLICE)) COMPILE_ERROR_UPSTREAM( "array_cat: slice operand not comptime-known"); *elem_type = ptr_info.child; *sentinel = ptr_info.sentinel; *len = ipReadIntValue(ip, ip->extra[ip->item_datas[operand] + 2]); return true; } if (ptr_info.size == IP_PTR_SIZE_ONE && ztArrayTypeInfo( ip, ptr_info.child, &len32, elem_type, sentinel)) { *len = len32; return true; } return false; } if (ztIsTuple(ip, operand_ty)) { *elem_type = semaArrayCatPeerElemType(ip, peer_ty); *sentinel = IP_INDEX_NONE; *len = ipTupleFieldCount(ip, operand_ty); return *elem_type != IP_INDEX_NONE; } return false; } // `"const lhs_sub_val = if (lhs_ty.isSinglePointer(zcu)) try // sema.pointerDeref(...) orelse break :rs lhs_src else if // (lhs_ty.isSlice(zcu)) try sema.maybeDerefSliceAsArray(...) orelse break :rs // lhs_src else lhs_val;"` static uint32_t semaArrayCatSubVal(SemaCtx* sema, uint32_t val, uint32_t ty) { if (semaIsAirInstRef(val)) return IP_INDEX_NONE; if (ztIsSlice(sema->ip, ty)) return maybeDerefSliceAsArray(sema->zcu, val); if (ipItemTagIs(sema->ip, ty, IP_TAG_TYPE_POINTER)) return pointerDeref(sema->zcu, val, NULL); return val; } // `"const init = Air.internedToRef((try pt.getCoerced(sent_val, // lhs_info.elem_type)).toIntern()); try sema.storePtr2(...)"` at `result_len`. static void semaArrayCatStoreSentinel(SemaCtx* sema, U32List* block, uint32_t mutable_alloc, uint64_t result_len, uint32_t elem_ptr_ty, uint32_t sent_val, uint32_t lhs_elem_ty, uint32_t resolved_elem_ty) { uint32_t init = sent_val; if (lhs_elem_ty != resolved_elem_ty) { uint64_t space[2]; BigIntConst big; if (!valToBigInt(sema->ip, sent_val, space, &big)) UNIMPLEMENTED( "array_cat: non-integer sentinel of a mixed element type"); init = coerce(sema->zcu, sema->insts, block, sema->air_extra, resolved_elem_ty, ipGetInt(sema->ip, lhs_elem_ty, big), lhs_elem_ty); } uint32_t elem_ptr = semaEmitPtrElemPtr(mutable_alloc, ipGetIntUsize(sema->ip, (uint32_t)result_len), elem_ptr_ty, sema->insts, block, sema->air_extra); semaRuntimeBinOp(sema->insts, block, AIR_INST_STORE, elem_ptr, init); } // Sema.zig:zirArrayCat static void zirArrayCat(SemaCtx* sema, U32List* block, uint32_t inst) { InternPool* ip = sema->ip; uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); const uint32_t ops[2] = { semaResolveRef(extra.lhs, sema->inst_values), semaResolveRef(extra.rhs, sema->inst_values) }; const uint32_t tys[2] = { semaResolveRefValType(ip, extra.lhs, ops[0], sema->inst_types), semaResolveRefValType(ip, extra.rhs, ops[1], sema->inst_types), }; bool is_tuple[2] = { ztIsTuple(ip, tys[0]), ztIsTuple(ip, tys[1]) }; if (is_tuple[0] && is_tuple[1]) { analyzeTupleCat(sema, block, inst, ops, tys); return; } uint32_t elem_tys[2]; uint32_t sentinels[2]; uint64_t lens[2]; for (int side = 0; side < 2; side++) if (!getArrayCatInfo(sema, ops[side], tys[side], tys[1 - side], &elem_tys[side], &sentinels[side], &lens[side])) COMPILE_ERROR_UPSTREAM("array_cat: expected indexable"); uint32_t resolved_elem_ty = semaArrayCatResolvedElemType(sema->zcu, elem_tys); // When there is a sentinel mismatch, no sentinel on the result. uint32_t res_sent_val = semaArrayCatResultSentinel(sema->zcu, sentinels, resolved_elem_ty); if (lens[0] > UINT32_MAX - lens[1] || lens[0] + lens[1] + (res_sent_val != IP_INDEX_NONE) > UINT32_MAX) COMPILE_ERROR_UPSTREAM("array_cat: produces an array too large"); uint32_t result_len = (uint32_t)(lens[0] + lens[1]); uint32_t result_ty = ipGetArrayType(ip, result_len, resolved_elem_ty, res_sent_val); // `"const ptr_addrspace = p: { if (lhs_ty.zigTypeTag(zcu) == .pointer) // ...": its flags, the generic address space's on a by-value pair too. bool is_ptr[2]; uint32_t addrspace_bits = 0; for (int side = 1; side >= 0; side--) { IpPtrInfo info; is_ptr[side] = ztPtrOrSliceInfo(ip, tys[side], &info); if (is_ptr[side]) addrspace_bits = info.flags & (IP_PTR_FLAGS_ADDRSPACE_MASK << IP_PTR_FLAGS_ADDRSPACE_SHIFT); } bool any_ptr = is_ptr[0] || is_ptr[1]; uint32_t lhs_sub_val = semaArrayCatSubVal(sema, ops[0], tys[0]); uint32_t rhs_sub_val = lhs_sub_val == IP_INDEX_NONE ? IP_INDEX_NONE : semaArrayCatSubVal(sema, ops[1], tys[1]); if (rhs_sub_val != IP_INDEX_NONE) { const uint32_t sub_vals[2] = { lhs_sub_val, rhs_sub_val }; uint32_t* element_vals = ARR_INIT(uint32_t, result_len + 1u); uint32_t elem_i = 0; for (int side = 0; side < 2; side++) { for (uint32_t i = 0; i < (uint32_t)lens[side]; i++, elem_i++) { uint32_t elem_ty = is_tuple[side] ? ipTupleFieldType(ip, tys[side], i) : elem_tys[side]; uint32_t field_ty, elem_val; if (!is_tuple[side] || !semaTupleFieldValueComptime( ip, tys[side], i, &field_ty, &elem_val)) elem_val = semaAggregateElemValueOrUndef(ip, sub_vals[side], elem_ty, i, "array_cat: operand storage"); element_vals[elem_i] = coerce(sema->zcu, sema->insts, block, sema->air_extra, resolved_elem_ty, elem_val, ipTypeOf(ip, elem_val)); } } if (res_sent_val != IP_INDEX_NONE) element_vals[elem_i++] = res_sent_val; uint32_t agg = ipGetAggregate(ip, result_ty, element_vals, elem_i); free(element_vals); // `"return sema.addConstantMaybeRef(...)"`. sema->inst_types[inst] = any_ptr ? ipGetPtrType(ip, result_ty, IP_PTR_FLAGS_CONST_ONE) : result_ty; sema->inst_values[inst] = any_ptr ? ipGetPtrUav(ip, sema->inst_types[inst], agg) : agg; return; } if (any_ptr) { uint32_t constant_alloc_ty = ipGetPtrType( ip, result_ty, IP_PTR_FLAGS_CONST_ONE | addrspace_bits); uint32_t alloc_flags = IP_PTR_FLAGS_MUT_ONE | addrspace_bits; uint32_t alloc_ty = ipGetPtrType(ip, result_ty, alloc_flags); uint32_t elem_ptr_ty = ipGetPtrType(ip, resolved_elem_ty, alloc_flags); uint32_t mutable_alloc = semaEmitTyOp(sema->insts, block, AIR_INST_ALLOC, alloc_ty, 0); // if both the source and destination are arrays // we can hotpath via a memcpy. if (is_ptr[0] && is_ptr[1]) { uint32_t many_ty = ipGetPtrType(ip, resolved_elem_ty, (alloc_flags & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_MANY); uint32_t slice_ty = ipGetSliceType(ip, many_ty); uint32_t many_alloc = semaEmitTyOp( sema->insts, block, AIR_INST_PTR_CAST, many_ty, mutable_alloc); uint32_t lhs_len = ipGetIntUsize(ip, (uint32_t)lens[0]); // lhs_dest_slice = dest[0..lhs.len] if (lens[0] > 0) semaRuntimeBinOp(sema->insts, block, AIR_INST_MEMCPY, semaEmitTyPlBin(sema->insts, block, sema->air_extra, AIR_INST_SLICE, slice_ty, many_alloc, lhs_len), ops[0]); // rhs_dest_slice = dest[lhs.len..][0..rhs.len] if (lens[1] > 0) { uint32_t rhs_dest_offset = semaEmitTyPlBin(sema->insts, block, sema->air_extra, AIR_INST_PTR_ADD, many_ty, many_alloc, lhs_len); semaRuntimeBinOp(sema->insts, block, AIR_INST_MEMCPY, semaEmitTyPlBin(sema->insts, block, sema->air_extra, AIR_INST_SLICE, slice_ty, rhs_dest_offset, ipGetIntUsize(ip, (uint32_t)lens[1])), ops[1]); } } else { uint32_t elem_i = 0; for (int side = 0; side < 2; side++) { for (uint32_t i = 0; i < (uint32_t)lens[side]; i++, elem_i++) { uint32_t elem_index = ipGetIntUsize(ip, elem_i); uint32_t operand_index = ipGetIntUsize(ip, i); uint32_t elem_ptr = semaEmitPtrElemPtr(mutable_alloc, elem_index, elem_ptr_ty, sema->insts, block, sema->air_extra); uint32_t init_ty; uint32_t init = elemVal(sema, block, ops[side], tys[side], operand_index, ZIR_REF_USIZE_TYPE, &init_ty); semaRuntimeBinOp(sema->insts, block, AIR_INST_STORE, elem_ptr, coerce(sema->zcu, sema->insts, block, sema->air_extra, resolved_elem_ty, init, init_ty)); } } } if (res_sent_val != IP_INDEX_NONE) semaArrayCatStoreSentinel(sema, block, mutable_alloc, result_len, elem_ptr_ty, res_sent_val, elem_tys[0], resolved_elem_ty); sema->inst_values[inst] = semaEmitTyOp(sema->insts, block, AIR_INST_PTR_CAST, constant_alloc_ty, mutable_alloc); sema->inst_types[inst] = constant_alloc_ty; return; } uint32_t* element_refs = ARR_INIT(uint32_t, result_len + 1u); uint32_t elem_i = 0; for (int side = 0; side < 2; side++) { for (uint32_t i = 0; i < (uint32_t)lens[side]; i++, elem_i++) { uint32_t init_ty; uint32_t init = elemVal(sema, block, ops[side], tys[side], ipGetIntUsize(ip, i), ZIR_REF_USIZE_TYPE, &init_ty); element_refs[elem_i] = coerce(sema->zcu, sema->insts, block, sema->air_extra, resolved_elem_ty, init, init_ty); } } sema->inst_values[inst] = semaEmitAggregateInit(sema->insts, block, sema->air_extra, result_ty, element_refs, result_len); sema->inst_types[inst] = result_ty; free(element_refs); } // (part of) Sema.zig:zirNegate, Sema.zig:zirNegateWrap // Sema.zirNegate / zirNegateWrap -> analyzeArithmetic(.sub/.subwrap, lhs=0, // rhs). ZIR data: un_node with operand = the runtime value. LHS = // @as(operand_ty, 0): a comptime zero dynamically interned at the operand's // type (mirrors `pt.intValue(rhs_scalar_ty, 0)` in zirNegate/zirNegateWrap). // RHS = runtime operand. In ReleaseSmall mode wantSafety() is false, so negate // -> plain sub (not sub_safe) and negate_wrap -> sub_wrap. static void semaNegate( SemaCtx* sema, U32List* block, uint32_t inst, bool wrap) { uint32_t operand_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t rhs_val = semaResolveRef(operand_ref, sema->inst_values); // `Sema.zirNegate`'s first act, before it looks at any value: // `"if (rhs_scalar_ty.isUnsignedInt(zcu) or switch // (rhs_scalar_ty.zigTypeTag(zcu)) { .int, .comptime_int, .float, // .comptime_float => false, else => true }) { return // sema.fail(block, src, "negation of type '{f}'", ...); }"`. // `Sema.zirNegateWrap` keeps only the tag half -- `-%x` on an // unsigned type is legal and wraps. The question is asked of the // scalar type (so it covers a vector) and once, ahead of the // comptime/runtime split, which is why it lives here and not on // one branch: without it `-@as(u32, x)` and `-v` on a // `@Vector(4, u32)` would wrap where upstream reports "negation // of type 'u32'" / "negation of type '@Vector(4, u32)'". { uint32_t ng_gate_ty; if (semaIsAirInstRef(rhs_val)) { ng_gate_ty = semaResolveRefType(operand_ref, sema->inst_types); } else if (rhs_val < sema->ip->items_len) { ng_gate_ty = ipComptimeValueType(sema->ip, rhs_val); } else { // An unset operand: no type to ask; the fold // below declines it. ng_gate_ty = IP_INDEX_NONE; } uint32_t ng_gate_scalar = ipScalarType(sema->ip, ng_gate_ty); if (ng_gate_scalar != IP_INDEX_NONE) { uint32_t ng_gate_bits; bool ng_gate_signed; bool ng_gate_int = ztIntInfo( sema->ip, ng_gate_scalar, &ng_gate_bits, &ng_gate_signed); bool ng_gate_float = ng_gate_scalar == ZIR_REF_COMPTIME_FLOAT_TYPE || ztSimpleFloatBits(ng_gate_scalar, &ng_gate_bits); bool ng_gate_ct_int = ng_gate_scalar == ZIR_REF_COMPTIME_INT_TYPE; if (!ng_gate_int && !ng_gate_float && !ng_gate_ct_int) COMPILE_ERROR_UPSTREAM("negate: negation of a type " "that is not a number"); if (!wrap && ng_gate_int && !ng_gate_signed) COMPILE_ERROR_UPSTREAM("negate: unsigned integer type"); } } if (!semaIsAirInstRef(rhs_val)) { // Comptime operand: fold `-x` at compile time // (Sema.zirNegate's `0 - x` analyzeArithmetic reduces to a // comptime value when x is comptime-known — e.g. a `-42` // literal in `var v: i32 = -42`). Read the operand as a // comptime integer. if (rhs_val >= sema->ip->items_len) UNIMPLEMENTED("negate: comptime operand not interned"); sema->inst_types[inst] = ipComptimeValueType(sema->ip, rhs_val); sema->inst_values[inst] = semaNegateComptime( sema->zcu, sema->inst_types[inst], rhs_val, wrap); return; } uint32_t rhs_ty = semaResolveRefType(operand_ref, sema->inst_types); uint32_t rhs_scalar_ty = rhs_ty; IpArrayInfo rhs_vec_info; if (ipArrayInfo(sema->ip, rhs_ty, &rhs_vec_info) == IP_ARR_OK && rhs_vec_info.is_vector) rhs_scalar_ty = rhs_vec_info.child; uint32_t rhs_float_bits; if (!wrap && ztSimpleFloatBits(rhs_scalar_ty, &rhs_float_bits)) { AirInstData neg_data; memset(&neg_data, 0, sizeof(neg_data)); neg_data.pair.a = (uint32_t)rhs_val; // un_op operand uint32_t neg_idx = semaAirInstAppend(sema->insts, AIR_INST_NEG, neg_data); u32ListAppend(block, neg_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + neg_idx; sema->inst_types[inst] = rhs_ty; return; } // `"const lhs = Air.internedToRef((try sema.splat(rhs_ty, try // pt.intValue(rhs_scalar_ty, 0))).toIntern());"` -- the zero is // interned at the scalar type, and `Sema.splat` then gives it the // operand's shape (`"return pt.aggregateSplatValue(ty, val);"`, // == ipGetRepeated). Reading `rhs_ty` for it interns nothing at // all when the operand is a vector, which is where `-v` stopped. uint32_t lhs_ip = semaCoerceComptimeIntLiteral(sema->ip, 0, rhs_scalar_ty); if (lhs_ip != IP_INDEX_NONE && rhs_scalar_ty != rhs_ty) lhs_ip = ipGetRepeated(sema->ip, rhs_ty, lhs_ip); if (lhs_ip == IP_INDEX_NONE) UNIMPLEMENTED("negate: zero not internable for operand type"); uint8_t air_tag = wrap ? AIR_INST_SUB_WRAP : AIR_INST_SUB; AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = lhs_ip; // @as(rhs_ty, 0) as AIR Ref data.pair.b = (uint32_t)rhs_val; // runtime AIR ref uint32_t air_idx = semaAirInstAppend(sema->insts, (AirInstTag)air_tag, data); u32ListAppend(block, air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + air_idx; sema->inst_types[inst] = rhs_ty; } // Sema.zig:zirNegate static void zirNegate(SemaCtx* sema, U32List* block, uint32_t inst) { semaNegate(sema, block, inst, false); } // Sema.zig:zirNegateWrap static void zirNegateWrap(SemaCtx* sema, U32List* block, uint32_t inst) { semaNegate(sema, block, inst, true); } // Sema.zig:analyzeArithmetic // (part of) Sema.zig:zirBitwise, Sema.zig:zirDiv, Sema.zig:zirDivTrunc, // Sema.zig:zirDivFloor, Sema.zig:zirDivCeil, Sema.zig:zirDivExact, // Sema.zig:zirModRem, Sema.zig:zirRem, Sema.zig:zirMod // The binary arithmetic and bitwise handlers' shared body: peer-resolve and // coerce the operands, fold two comptime ones by `tag`, else emit the AIR // bin_op `air_tag` (ReleaseSmall, no safety). Pointer arithmetic comes first. static void analyzeArithmetic(SemaCtx* sema, U32List* block, uint32_t inst, ZirInstTag tag, AirInstTag air_tag) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); uint32_t lhs_ref = extra.lhs; uint32_t rhs_ref = extra.rhs; uint32_t lhs_val = semaResolveRef(lhs_ref, sema->inst_values); uint32_t rhs_val = semaResolveRef(rhs_ref, sema->inst_values); uint32_t lhs_ty_raw = semaResolveRefType(lhs_ref, sema->inst_types); uint32_t rhs_ty_raw = semaResolveRefType(rhs_ref, sema->inst_types); if (lhs_val < sema->ip->items_len) { uint32_t value_ty = ipTypeOf(sema->ip, lhs_val); uint32_t bits; bool is_signed; if (value_ty != IP_INDEX_NONE && ztIntInfo(sema->ip, value_ty, &bits, &is_signed)) lhs_ty_raw = value_ty; } if (rhs_val < sema->ip->items_len) { uint32_t value_ty = ipTypeOf(sema->ip, rhs_val); uint32_t bits; bool is_signed; if (value_ty != IP_INDEX_NONE && ztIntInfo(sema->ip, value_ty, &bits, &is_signed)) rhs_ty_raw = value_ty; } // analyzeArithmetic's pointer operands come first: `ptr - ptr` // and a many / C pointer `+` / `-` an integer. if ((tag == ZIR_INST_ADD || tag == ZIR_INST_SUB) && (semaIsAirInstRef(lhs_val) || (lhs_val < sema->ip->items_len)) && (semaIsAirInstRef(rhs_val) || (rhs_val < sema->ip->items_len)) && semaAnalyzeArithmeticPtr(sema->zcu, sema->insts, block, sema->air_extra, tag == ZIR_INST_SUB, lhs_val, semaResolveRefValType( sema->ip, lhs_ref, lhs_val, sema->inst_types), rhs_val, semaResolveRefValType( sema->ip, rhs_ref, rhs_val, sema->inst_types), &sema->inst_values[inst], &sema->inst_types[inst])) return; // Resolve operands: both runtime, or one comptime coerced to the // other's type (mirrors Sema.zirArithmetic -> resolvePeerTypes). uint32_t lhs_air, rhs_air, bin_result_ty; if (semaIsAirInstRef(lhs_val) && semaIsAirInstRef(rhs_val)) { if (lhs_ty_raw != rhs_ty_raw) { // Two runtime ints of different types (`a % base` in // Writer.printIntAny: u32 % u8; `a + b` with i64/i32 in // std.Progress): Sema.analyzeArithmetic's resolvePeerTypes // picks the wider same-signedness type and coerces the // narrower operand through an AIR intcast (ReleaseSmall: // int_cast, no safety). When the two are the same width // but distinct type indices (the std hash map's `Size`=u32 // arith against a preinterned u32 — the same // representation, an interning artifact), peer resolution // keeps one type and the same-width int_cast on the other // is a no-op reinterpret. Mixed signedness remains // unmodeled unless another dedicated arm proved the exact // value-preserving route. uint32_t l_bits, r_bits; bool l_signed, r_signed; if (!ztIntInfo(sema->ip, lhs_ty_raw, &l_bits, &l_signed) || !ztIntInfo(sema->ip, rhs_ty_raw, &r_bits, &r_signed)) { UNIMPLEMENTED( "analyzeArithmetic: bin_op - mismatched operand types"); } // Neither peer is comptime-known, so upstream's legacy // `any_comptime_known` branch cannot fire: mixed // signedness resolves only when the signed peer is // strictly wider, and every other such pair is upstream's // `.conflict` ("incompatible types: 'u32' and 'i8'"). PeerResolveResult bin_why; uint32_t wide_ty = semaPeerFixedIntType(sema->ip, lhs_ty_raw, IP_INDEX_NONE, rhs_ty_raw, IP_INDEX_NONE, &bin_why); if (bin_why == PEER_RESOLVE_CONFLICT) COMPILE_ERROR_UPSTREAM( "analyzeArithmetic: incompatible integer operand types"); if (bin_why != PEER_RESOLVE_OK) UNIMPLEMENTED( "analyzeArithmetic: bin_op - mismatched operand types"); if (l_signed != r_signed) { lhs_air = lhs_val; rhs_air = rhs_val; if (lhs_ty_raw != wide_ty) { lhs_air = semaCoerceExtraRuntime(sema->zcu, lhs_val, lhs_ty_raw, wide_ty, sema->insts, block, sema->air_extra, "analyzeArithmetic: mixed signed lhs coercion"); } if (rhs_ty_raw != wide_ty) { rhs_air = semaCoerceExtraRuntime(sema->zcu, rhs_val, rhs_ty_raw, wide_ty, sema->insts, block, sema->air_extra, "analyzeArithmetic: mixed signed rhs coercion"); } bin_result_ty = wide_ty; } else { // Widen the narrower operand; on a width tie, keep lhs // and reinterpret rhs. uint32_t narrow_val = (l_bits >= r_bits) ? rhs_val : lhs_val; AirInstData wic; memset(&wic, 0, sizeof(wic)); wic.pair.a = wide_ty; wic.pair.b = (uint32_t)narrow_val; uint32_t wic_idx = semaAirInstAppend(sema->insts, AIR_INST_INT_CAST, wic); u32ListAppend(block, wic_idx); if (l_bits >= r_bits) { // narrow = rhs (incl. the width tie): cast rhs. lhs_air = lhs_val; rhs_air = AIR_REF_INST_BASE + wic_idx; } else { lhs_air = AIR_REF_INST_BASE + wic_idx; rhs_air = rhs_val; } bin_result_ty = wide_ty; } } else { lhs_air = lhs_val; rhs_air = rhs_val; bin_result_ty = lhs_ty_raw; } } else if (semaIsAirInstRef(lhs_val) || semaIsAirInstRef(rhs_val)) { // One operand is comptime. Sema.analyzeArithmetic resolves the // peer of the two operand types before coercing either // ("const resolved_type = try sema.resolvePeerTypes(block, // src, instructions, ...)", then "const casted_lhs = try // sema.coerce(block, resolved_type, lhs, lhs_src)"). A // comptime_int peer takes the runtime operand's type -- the // overwhelmingly common shape, unchanged -- but a concretely // typed comptime operand widens the runtime one exactly as two // runtime operands do: `@as(usize, 8) * @intFromBool(b)` is // usize arithmetic, not u1 arithmetic // (semaBinCoerceComptimeOperand's `already typed` case would // feed a usize value into a u1 binop and type the result u1). bool lhs_rt = semaIsAirInstRef(lhs_val); uint32_t rt_ty = lhs_rt ? lhs_ty_raw : rhs_ty_raw; uint32_t ct_ty = lhs_rt ? rhs_ty_raw : lhs_ty_raw; uint32_t ct_val = lhs_rt ? rhs_val : lhs_val; PeerResolveResult ct_why; // Exactly one operand is comptime-known; it contributes // upstream's `peer_vals[i]`, the other contributes `null`. uint32_t peer_ty = semaPeerFixedIntType(sema->ip, lhs_ty_raw, lhs_rt ? IP_INDEX_NONE : lhs_val, rhs_ty_raw, lhs_rt ? rhs_val : IP_INDEX_NONE, &ct_why); if (ct_why == PEER_RESOLVE_CONFLICT) COMPILE_ERROR_UPSTREAM( "analyzeArithmetic: incompatible integer operand types"); // A peer zig1 cannot classify as an integer (floats, and // anything else) keeps the runtime operand's type. if (ct_why != PEER_RESOLVE_OK) peer_ty = rt_ty; uint32_t rt_air = lhs_rt ? lhs_val : rhs_val; if (rt_ty != peer_ty) { rt_air = semaCoerceExtraRuntime(sema->zcu, rt_air, rt_ty, peer_ty, sema->insts, block, sema->air_extra, "analyzeArithmetic: runtime peer coercion"); } uint32_t ct_air; if (ct_ty == peer_ty) { ct_air = ct_val; } else { // Sema.coerce rejects a comptime operand the peer type // cannot represent instead of wrapping it into the peer // width ("type 'u32' cannot represent integer value // '-5'"). if (!valIntFitsInType(sema->ip, ct_val, peer_ty)) COMPILE_ERROR_UPSTREAM("analyzeArithmetic: peer type cannot " "represent integer value"); ct_air = semaBinCoerceComptimeOperand(sema->ip, ct_val, peer_ty); if (ct_air == IP_INDEX_NONE) UNIMPLEMENTED("analyzeArithmetic: bin_op - comptime operand " "not coercible"); } lhs_air = lhs_rt ? rt_air : ct_air; rhs_air = lhs_rt ? ct_air : rt_air; bin_result_ty = peer_ty; } else { // Both operands are comptime: fold at compile time. Two // comptime vectors take `arith.`'s `.vector` arm, which // is the scalar fold applied per lane and re-aggregated. uint32_t bf_ty = IP_INDEX_NONE; uint32_t bf_res = semaFoldComptimeVectorBinOp( sema->zcu, tag, lhs_val, rhs_val, &bf_ty); if (bf_res == IP_INDEX_NONE) bf_res = semaFoldComptimeScalarBinOp(sema->zcu, tag, lhs_val, lhs_ty_raw, rhs_val, rhs_ty_raw, &bf_ty); if (tag == ZIR_INST_MOD_REM && bf_res != IP_INDEX_NONE) semaCheckModRemNegative( sema->ip, lhs_ty_raw, lhs_val, rhs_ty_raw, rhs_val, bf_res); sema->inst_values[inst] = bf_res; sema->inst_types[inst] = bf_ty; return; } // Runtime path: emit the AIR bin_op instruction. // (Only reachable when at least one operand is a runtime AIR ref.) if (tag == ZIR_INST_MOD_REM) { // `Sema.zirModRem` asks `sema.resolveValue` for each operand // before deciding it may be negative: `"const lhs_val = // maybe_lhs_val orelse break :a true;"`. Only a genuinely // unknown operand answers yes, and an AIR ref is not that // proof here. Resolve first, and when both operands resolve, // fold the result the rejection actually tests -- upstream's // comptime-known branch, which accepts a `%` whose remainder // is zero on every lane. uint32_t mr_l = resolveValue( sema->zcu, sema->insts, block, sema->air_extra, lhs_val); uint32_t mr_r = resolveValue( sema->zcu, sema->insts, block, sema->air_extra, rhs_val); uint32_t mr_lv = (mr_l == IP_INDEX_NONE) ? lhs_val : mr_l; uint32_t mr_rv = (mr_r == IP_INDEX_NONE) ? rhs_val : mr_r; uint32_t mr_lty = (mr_l == IP_INDEX_NONE) ? lhs_ty_raw : ipTypeOf(sema->ip, mr_l); uint32_t mr_rty = (mr_r == IP_INDEX_NONE) ? rhs_ty_raw : ipTypeOf(sema->ip, mr_r); uint32_t mr_res = IP_INDEX_NONE; if (mr_l != IP_INDEX_NONE && mr_r != IP_INDEX_NONE) { uint32_t mr_res_ty = IP_INDEX_NONE; mr_res = semaFoldComptimeVectorBinOp( sema->zcu, tag, mr_lv, mr_rv, &mr_res_ty); } semaCheckModRemNegative( sema->ip, mr_lty, mr_lv, mr_rty, mr_rv, mr_res); } if (tag == ZIR_INST_DIV) { // `Sema.zirDiv`'s own tail: `"const air_tag: Air.Inst.Tag = // if (is_int) blk: { ... break :blk .div_trunc; } else switch // (block.float_mode) { .optimized => .div_float_optimized, // .strict => .div_float, };"`. The tag switch above cannot // make that choice -- it runs before the peer type exists -- // so `/` was emitted as `div_trunc` for floats too and // `7.5 / 2.0` answered 3.0. `is_int` is asked of the resolved // type's scalar, exactly as upstream asks it. uint32_t div_scalar = bin_result_ty; IpArrayInfo div_vec; if (div_scalar != IP_INDEX_NONE && ipArrayInfo(sema->ip, div_scalar, &div_vec) == IP_ARR_OK && div_vec.is_vector) div_scalar = div_vec.child; uint32_t div_fbits; if (div_scalar == ZIR_REF_COMPTIME_FLOAT_TYPE || ztSimpleFloatBits(div_scalar, &div_fbits)) air_tag = AIR_INST_DIV_FLOAT; } { AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = lhs_air; // bin_op.lhs Ref data.pair.b = rhs_air; // bin_op.rhs Ref uint32_t air_idx = semaAirInstAppend(sema->insts, air_tag, data); u32ListAppend(block, air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + air_idx; sema->inst_types[inst] = bin_result_ty; } } // Sema.zig:zirArithmetic // ReleaseSmall (!wantSafety): add and add_unsafe (the `for`/`while` index // increment) both pick the plain AIR `.add`. static void zirArithmetic( SemaCtx* sema, U32List* block, uint32_t inst, ZirInstTag zir_tag) { AirInstTag air_tag; switch (zir_tag) { case ZIR_INST_ADD: case ZIR_INST_ADD_UNSAFE: air_tag = AIR_INST_ADD; break; case ZIR_INST_ADDWRAP: air_tag = AIR_INST_ADD_WRAP; break; case ZIR_INST_ADD_SAT: air_tag = AIR_INST_ADD_SAT; break; case ZIR_INST_SUB: air_tag = AIR_INST_SUB; break; case ZIR_INST_SUBWRAP: air_tag = AIR_INST_SUB_WRAP; break; case ZIR_INST_SUB_SAT: air_tag = AIR_INST_SUB_SAT; break; case ZIR_INST_MUL: air_tag = AIR_INST_MUL; break; case ZIR_INST_MULWRAP: air_tag = AIR_INST_MUL_WRAP; break; case ZIR_INST_MUL_SAT: air_tag = AIR_INST_MUL_SAT; break; default: UNREACHABLE("zirArithmetic: not an arithmetic tag"); } analyzeArithmetic(sema, block, inst, zir_tag, air_tag); } // Sema.zig:zirBitwise static void zirBitwise( SemaCtx* sema, U32List* block, uint32_t inst, AirInstTag air_tag) { analyzeArithmetic(sema, block, inst, air_tag == AIR_INST_BIT_AND ? ZIR_INST_BIT_AND : air_tag == AIR_INST_BIT_OR ? ZIR_INST_BIT_OR : ZIR_INST_XOR, air_tag); } // Sema.zig:zirDiv // div_trunc for an integer; analyzeArithmetic turns a float `/` into // div_float. static void zirDiv(SemaCtx* sema, U32List* block, uint32_t inst) { analyzeArithmetic(sema, block, inst, ZIR_INST_DIV, AIR_INST_DIV_TRUNC); } // Sema.zig:zirDivExact static void zirDivExact(SemaCtx* sema, U32List* block, uint32_t inst) { analyzeArithmetic( sema, block, inst, ZIR_INST_DIV_EXACT, AIR_INST_DIV_EXACT); } // Sema.zig:zirDivFloor static void zirDivFloor(SemaCtx* sema, U32List* block, uint32_t inst) { analyzeArithmetic( sema, block, inst, ZIR_INST_DIV_FLOOR, AIR_INST_DIV_FLOOR); } // Sema.zig:zirDivCeil static void zirDivCeil(SemaCtx* sema, U32List* block, uint32_t inst) { analyzeArithmetic(sema, block, inst, ZIR_INST_DIV_CEIL, AIR_INST_DIV_CEIL); } // Sema.zig:zirDivTrunc static void zirDivTrunc(SemaCtx* sema, U32List* block, uint32_t inst) { analyzeArithmetic( sema, block, inst, ZIR_INST_DIV_TRUNC, AIR_INST_DIV_TRUNC); } // Sema.zig:zirModRem // `.rem` for integers (truncated; ReleaseSmall: no safety). static void zirModRem(SemaCtx* sema, U32List* block, uint32_t inst) { analyzeArithmetic(sema, block, inst, ZIR_INST_MOD_REM, AIR_INST_REM); } // Sema.zig:zirMod static void zirMod(SemaCtx* sema, U32List* block, uint32_t inst) { analyzeArithmetic(sema, block, inst, ZIR_INST_MOD, AIR_INST_MOD); } // Sema.zig:zirRem static void zirRem(SemaCtx* sema, U32List* block, uint32_t inst) { analyzeArithmetic(sema, block, inst, ZIR_INST_REM, AIR_INST_REM); } // Sema.zig:zirOverflowArithmetic // Sema.zirOverflowArithmetic: `@addWithOverflow(a, b)` -> AIR *_with_overflow, // ty_pl { tuple {T, u1}, Bin{lhs, rhs} }. "const lhs = try sema.coerce(block, // dest_ty, uncasted_lhs, lhs_src); const rhs = try sema.coerce(block, // rhs_dest_ty, uncasted_rhs, rhs_src);", then the zero/one short-circuits of // one comptime operand, the fold of two. static void zirOverflowArithmetic( SemaCtx* sema, U32List* block, uint32_t inst, uint16_t awo_op) { bool awo_shl = awo_op == (uint16_t)ZIR_EXT_SHL_WITH_OVERFLOW; uint32_t awo_p = sema->zir->inst_datas[inst].extended.operand; ZirInstBinNode extra; zirExtraData(sema->zir, awo_p, &extra); uint32_t awo_lhs_ref = extra.lhs; uint32_t awo_rhs_ref = extra.rhs; uint32_t awo_lhs = semaResolveRef(awo_lhs_ref, sema->inst_values); uint32_t awo_rhs = semaResolveRef(awo_rhs_ref, sema->inst_values); bool awo_lct = !semaIsAirInstRef(awo_lhs); bool awo_rct = !semaIsAirInstRef(awo_rhs); if ((awo_lct && awo_lhs == INST_VALUE_UNSET) || (awo_rct && awo_rhs == INST_VALUE_UNSET)) UNIMPLEMENTED("add_with_overflow: unreadable operand"); uint32_t awo_lty = awo_lct ? ipTypeOf(sema->ip, awo_lhs) : semaResolveRefType(awo_lhs_ref, sema->inst_types); uint32_t awo_rty = awo_rct ? ipTypeOf(sema->ip, awo_rhs) : semaResolveRefType(awo_rhs_ref, sema->inst_types); uint32_t awo_ty = semaOverflowArithDestType(sema->zcu, awo_shl, awo_lty, awo_lct ? awo_lhs : IP_INDEX_NONE, awo_rty, awo_rct ? awo_rhs : IP_INDEX_NONE); if (awo_lct && awo_rct) { // Both comptime-known: the fold the comptime frame calls. uint32_t awo_folded = semaFoldWithOverflow(sema->ip, semaOverflowArithZirTag(awo_op), awo_ty, awo_lhs, awo_rhs); sema->inst_values[inst] = awo_folded; sema->inst_types[inst] = ipTypeOf(sema->ip, awo_folded); return; } uint32_t awo_bits = 0; bool awo_signed = false; bool awo_scalar = ztIntInfo(sema->ip, awo_ty, &awo_bits, &awo_signed); if (!awo_scalar && (awo_lct || awo_rct)) UNIMPLEMENTED("zirOverflowArithmetic: overflow arithmetic - vector " "with a comptime operand"); // `rhs_dest_ty`: `log2IntType(lhs_ty)` for a shift. uint32_t awo_rdest = awo_shl ? log2IntType(sema->ip, awo_ty) : awo_ty; if (awo_lct) awo_lhs = semaCoerceComptime(sema->zcu, sema->ip, awo_lhs, awo_ty); else if (awo_lty != awo_ty) awo_lhs = semaRuntimeIntCast(sema->insts, block, awo_ty, awo_lhs); if (awo_rct) awo_rhs = semaCoerceComptime(sema->zcu, sema->ip, awo_rhs, awo_rdest); else if (awo_rty != awo_rdest) awo_rhs = semaRuntimeIntCast(sema->insts, block, awo_rdest, awo_rhs); if (awo_lhs == IP_INDEX_NONE || awo_rhs == IP_INDEX_NONE) UNIMPLEMENTED("add_with_overflow: comptime operand not coercible"); uint32_t awo_tuple = zptOverflowArithmeticTupleType(sema->ip, awo_ty, NULL); if (awo_lct || awo_rct) { // The short-circuits whose result is the other operand // with a zero overflow bit. uint64_t awo_n = 0; bool awo_neg = false; bool awo_def = semaCmpReadKnownInt( sema->ip, awo_lct ? awo_lhs : awo_rhs, &awo_n, &awo_neg); bool awo_zero = awo_def && awo_n == 0; bool awo_one = awo_def && awo_n == 1 && !awo_neg && !(awo_bits == 1 && awo_signed); uint32_t awo_short = INST_VALUE_UNSET; switch (awo_op) { case ZIR_EXT_ADD_WITH_OVERFLOW: if (awo_zero) awo_short = awo_lct ? awo_rhs : awo_lhs; break; case ZIR_EXT_SUB_WITH_OVERFLOW: if (awo_rct && !awo_def) UNIMPLEMENTED("sub_with_overflow: undefined rhs"); if (awo_rct && awo_zero) awo_short = awo_lhs; break; case ZIR_EXT_MUL_WITH_OVERFLOW: if (awo_zero) awo_short = awo_lct ? awo_lhs : awo_rhs; else if (awo_one) awo_short = awo_lct ? awo_rhs : awo_lhs; break; default: // shl_with_overflow if (awo_rct) { if (!awo_def) COMPILE_ERROR_UPSTREAM( "shl_with_overflow: use of undefined " "shift amount"); if (awo_neg) COMPILE_ERROR_UPSTREAM( "shl_with_overflow: negative shift"); if (awo_n != 0 && awo_n >= awo_bits) COMPILE_ERROR_UPSTREAM( "shl_with_overflow: shift amount too " "large"); } else if (!awo_def) { COMPILE_ERROR_UPSTREAM( "shl_with_overflow: use of undefined lhs"); } if (awo_zero) awo_short = awo_lhs; break; } if (awo_short != INST_VALUE_UNSET) { if (!semaIsAirInstRef(awo_short)) { const uint32_t awo_el[2] = { awo_short, ZIR_REF_ZERO_U1 }; sema->inst_values[inst] = ipGetAggregate(sema->ip, awo_tuple, awo_el, 2); sema->inst_types[inst] = awo_tuple; return; } const uint32_t awo_raw[2] = { awo_short, 0 }; const uint32_t awo_vals[2] = { IP_INDEX_NONE, ZIR_REF_ZERO_U1 }; semaAnonInitAggregate(sema->insts, block, sema->air_extra, awo_tuple, awo_raw, awo_vals, 2, sema->inst_values, sema->inst_types, inst); return; } } AirInstTag awo_tag = awo_op == ZIR_EXT_ADD_WITH_OVERFLOW ? AIR_INST_ADD_WITH_OVERFLOW : awo_op == ZIR_EXT_SUB_WITH_OVERFLOW ? AIR_INST_SUB_WITH_OVERFLOW : awo_shl ? AIR_INST_SHL_WITH_OVERFLOW : AIR_INST_MUL_WITH_OVERFLOW; sema->inst_values[inst] = semaEmitTyPlBin(sema->insts, block, sema->air_extra, awo_tag, awo_tuple, awo_lhs, awo_rhs); sema->inst_types[inst] = awo_tuple; } // Sema.zig:zirLoad // Sema.zirLoad -> analyzeLoad -> block.addTyOp(.load, elem_ty, ptr). ZIR data: // un_node with operand = ptr_ref. Gets elem_ty from the pointer type's child // field. In ReleaseSmall (non-safety): emits AIR load(elem_ty, ptr) (ty_op // format). static void zirLoad(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t load_ptr_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t load_ptr_val = semaResolveRef(load_ptr_ref, sema->inst_values); if (!semaIsAirInstRef(load_ptr_val)) { // `Sema.analyzeLoad` on a comptime-known pointer. sema->inst_values[inst] = analyzeLoad(sema->zcu, sema->insts, block, load_ptr_val, ipTypeOf(sema->ip, load_ptr_val), &sema->inst_types[inst]); return; } uint32_t load_ptr_ty = semaResolveRefType(load_ptr_ref, sema->inst_types); // First word of TypePointer extra payload = child (elem type). uint32_t load_elem_ty = sema->ip->extra[sema->ip->item_datas[load_ptr_ty]]; // analyzeLoad's `"try sema.ensureLayoutResolved(elem_ty, src, // .ptr_access);"`, then `.one_possible_value => return // .fromValue(elem_ty.onePossibleValue(pt))`, runtime pointer or not. ensureLayoutResolved(sema->zcu, load_elem_ty); uint32_t load_opv = ztOnePossibleValue(sema->ip, load_elem_ty); if (load_opv != IP_INDEX_NONE) { sema->inst_values[inst] = load_opv; sema->inst_types[inst] = load_elem_ty; return; } if (load_ptr_ref >= ZIR_REF_START_INDEX && sema->zir->inst_tags[load_ptr_ref - ZIR_REF_START_INDEX] == ZIR_INST_RESOLVE_INFERRED_ALLOC) { uint32_t resolve_inst = load_ptr_ref - ZIR_REF_START_INDEX; uint32_t inferred_ref = sema->zir->inst_datas[resolve_inst].un_node.operand; if (inferred_ref >= ZIR_REF_START_INDEX && sema->zir->inst_tags[inferred_ref - ZIR_REF_START_INDEX] == ZIR_INST_ALLOC_INFERRED) { uint32_t folded_ty = IP_INDEX_NONE; uint32_t folded_val = semaResolveComptimeKnownAllocValue(sema->zcu, sema->insts, block, sema->air_extra, load_ptr_val, UINT32_MAX, &folded_ty, NULL); if (folded_val != IP_INDEX_NONE) { if (folded_ty != load_elem_ty) UNIMPLEMENTED("load: inferred alloc folded type mismatch"); sema->inst_values[inst] = folded_val; sema->inst_types[inst] = folded_ty; return; } } } sema->inst_values[inst] = analyzeLoad(sema->zcu, sema->insts, block, load_ptr_val, load_ptr_ty, &sema->inst_types[inst]); } // Sema.zig:zirDeref // validateDeref only rejects invalid input; the rest is zirLoad's // analyzeLoad of the operand. static void zirDeref(SemaCtx* sema, U32List* block, uint32_t inst) { zirLoad(sema, block, inst); } // Sema.zig:runtimeBoolCmp static uint32_t runtimeBoolCmp( SemaCtx* sema, U32List* block, ZirInstTag op, uint32_t lhs, uint32_t rhs) { if ((op == ZIR_INST_CMP_NEQ) == (rhs == ZIR_REF_BOOL_TRUE)) return semaEmitTyOp( sema->insts, block, AIR_INST_NOT, ZIR_REF_BOOL_TYPE, lhs); return lhs; } // Sema.zig:cmpSelf // The comptime answer is semaCmpComptime's, compareAll and the vector arm's // compareVector are in it and cmpVector. `*out_ty` is the result's type. static uint32_t cmpSelf(SemaCtx* sema, U32List* block, uint32_t casted_lhs, uint32_t casted_rhs, uint32_t resolved_type, ZirInstTag op, AirInstTag air_tag, uint32_t* out_ty) { InternPool* ip = sema->ip; *out_ty = ZIR_REF_BOOL_TYPE; if (ztIsZigTypeTag(ip, resolved_type, ZIG_TYPE_TAG_VECTOR)) return cmpVector(sema->zcu, op, air_tag, casted_lhs, resolved_type, casted_rhs, resolved_type, sema->insts, block, sema->air_extra, out_ty); // `sema.resolveValue` of a runtime `void`: its one possible value. if (ztOnePossibleValue(ip, resolved_type) != IP_INDEX_NONE) return semaBoolRef(op == ZIR_INST_CMP_EQ); bool lhs_known = !semaIsAirInstRef(casted_lhs); bool rhs_known = !semaIsAirInstRef(casted_rhs); if ((lhs_known && ipIsUndef(ip, casted_lhs)) || (rhs_known && ipIsUndef(ip, casted_rhs))) return ipGetUndef(ip, ZIR_REF_BOOL_TYPE); if (lhs_known && rhs_known) return semaCmpComptime(sema->zcu, op, casted_lhs, casted_rhs); if (resolved_type == ZIR_REF_BOOL_TYPE && (lhs_known || rhs_known)) return lhs_known ? runtimeBoolCmp(sema, block, op, casted_rhs, casted_lhs) : runtimeBoolCmp(sema, block, op, casted_lhs, casted_rhs); return semaRuntimeBinOp( sema->insts, block, air_tag, casted_lhs, casted_rhs); } // `Type.isNumeric`. static bool semaTypeIsNumeric(const InternPool* ip, uint32_t ty) { uint32_t bits; bool is_signed; return ty == ZIR_REF_COMPTIME_INT_TYPE || ty == ZIR_REF_COMPTIME_FLOAT_TYPE || ztSimpleFloatBits(ty, &bits) || ztIntInfo(ip, ty, &bits, &is_signed); } // cmpNumeric's `sema.coerce` to its `dest_ty`. A comptime-known integer is // `pt.getCoerced`, since the width ensures `intFitsInType`, whatever its type. static uint32_t semaCmpNumericCoerce( SemaCtx* sema, U32List* block, uint32_t val, uint32_t ty, uint32_t dest) { InternPool* ip = sema->ip; uint32_t bits; uint64_t space[2]; BigIntConst big; if (semaIsAirInstRef(val) || ztSimpleFloatBits(dest, &bits)) return coerce(sema->zcu, sema->insts, block, sema->air_extra, dest, val, semaIsAirInstRef(val) ? ty : ipTypeOf(ip, val)); if (!valToBigInt(ip, val, space, &big)) UNIMPLEMENTED("cmpNumeric: comptime integer operand shape"); return ipGetInt(ip, dest, big); } // Sema.zig:cmpNumeric // `compareIntsOnlyPossibleResult` also decides a comptime zero or negative // against an unsigned operand; `semaCmpFloatAgainstIntPeer` is the comptime // float against an integer operand, its `setFloat` and its bit count. static uint32_t cmpNumeric(SemaCtx* sema, U32List* block, uint32_t uncasted_lhs, uint32_t lhs_ty, uint32_t uncasted_rhs, uint32_t rhs_ty, ZirInstTag op, AirInstTag air_tag) { InternPool* ip = sema->ip; uint32_t unused_bits; bool unused_signed; bool lhs_is_float = lhs_ty == ZIR_REF_COMPTIME_FLOAT_TYPE || ztSimpleFloatBits(lhs_ty, &unused_bits); bool rhs_is_float = rhs_ty == ZIR_REF_COMPTIME_FLOAT_TYPE || ztSimpleFloatBits(rhs_ty, &unused_bits); bool lhs_is_int = !lhs_is_float; bool rhs_is_fixed_int = !rhs_is_float && ztIntInfo(ip, rhs_ty, &unused_bits, &unused_signed); bool lhs_is_fixed_int = !lhs_is_float && ztIntInfo(ip, lhs_ty, &unused_bits, &unused_signed); bool rhs_is_int = !rhs_is_float; // comptime_float needs to coerce to fixed-width float. uint32_t lhs = lhs_ty == ZIR_REF_COMPTIME_FLOAT_TYPE && rhs_is_float && rhs_ty != ZIR_REF_COMPTIME_FLOAT_TYPE ? coerce(sema->zcu, sema->insts, block, sema->air_extra, rhs_ty, uncasted_lhs, lhs_ty) : uncasted_lhs; uint32_t rhs = rhs_ty == ZIR_REF_COMPTIME_FLOAT_TYPE && lhs_is_float && lhs_ty != ZIR_REF_COMPTIME_FLOAT_TYPE ? coerce(sema->zcu, sema->insts, block, sema->air_extra, lhs_ty, uncasted_rhs, rhs_ty) : uncasted_rhs; bool lhs_known = !semaIsAirInstRef(lhs); bool rhs_known = !semaIsAirInstRef(rhs); bool lhs_undef = lhs_known && ipIsUndef(ip, lhs); bool rhs_undef = rhs_known && ipIsUndef(ip, rhs); // A guaranteed result which does not depend on the other value. if (lhs_known) { if (!lhs_undef && lhs_is_int && rhs_is_fixed_int) { int res = semaCmpIntsOnlyPossible(ip, lhs, op, rhs_ty); if (res >= 0) return semaBoolRef(res != 0); } uint32_t known = semaCmpNumericKnownFloat(ip, op, lhs, true, rhs_ty); if (known != IP_INDEX_NONE) return known; } if (rhs_known) { if (!rhs_undef && rhs_is_int && lhs_is_fixed_int) { int res = semaCmpIntsOnlyPossible( ip, rhs, semaCmpReverseOp(op), lhs_ty); if (res >= 0) return semaBoolRef(res != 0); } uint32_t known = semaCmpNumericKnownFloat(ip, op, rhs, false, lhs_ty); if (known != IP_INDEX_NONE) return known; } if (lhs_undef || rhs_undef) return ipGetUndef(ip, ZIR_REF_BOOL_TYPE); if (lhs_known && rhs_known) return semaCmpComptime(sema->zcu, op, lhs, rhs); // compareHetero uint32_t dest_ty; if (lhs_is_float && rhs_is_float) { // Smaller fixed-width floats coerce to larger fixed-width floats. uint32_t lhs_bits = 0, rhs_bits = 0; ztSimpleFloatBits(lhs_ty, &lhs_bits); ztSimpleFloatBits(rhs_ty, &rhs_bits); if (lhs_ty == ZIR_REF_COMPTIME_FLOAT_TYPE) dest_ty = rhs_ty; else if (rhs_ty == ZIR_REF_COMPTIME_FLOAT_TYPE) dest_ty = lhs_ty; else dest_ty = lhs_bits >= rhs_bits ? lhs_ty : rhs_ty; uint32_t casted_lhs = coerce(sema->zcu, sema->insts, block, sema->air_extra, dest_ty, lhs, lhs_ty == ZIR_REF_COMPTIME_FLOAT_TYPE ? dest_ty : lhs_ty); uint32_t casted_rhs = coerce(sema->zcu, sema->insts, block, sema->air_extra, dest_ty, rhs, rhs_ty == ZIR_REF_COMPTIME_FLOAT_TYPE ? dest_ty : rhs_ty); return semaRuntimeBinOp( sema->insts, block, air_tag, casted_lhs, casted_rhs); } // A comptime float against a runtime integer: its integer value, the // comparison's whole answer when it has a fractional part. if (lhs_known && lhs_is_float) { uint32_t fold, as_int; if (semaCmpFloatAgainstIntPeer( ip, op, lhs, true, rhs_ty, &fold, &as_int)) return fold; lhs = as_int; lhs_is_float = false; } if (rhs_known && rhs_is_float) { uint32_t fold, as_int; if (semaCmpFloatAgainstIntPeer( ip, op, rhs, false, lhs_ty, &fold, &as_int)) return fold; rhs = as_int; rhs_is_float = false; } if (lhs_is_float || rhs_is_float) { dest_ty = lhs_is_float ? lhs_ty : rhs_ty; } else if (lhs_known) { dest_ty = semaCmpFloatIntDestType(ip, rhs_ty, lhs); } else if (rhs_known) { dest_ty = semaCmpFloatIntDestType(ip, lhs_ty, rhs); } else { // For mixed unsigned integer sizes, implicit cast both operands to // the larger integer. For mixed signed and unsigned integers, // implicit cast both operands to a signed integer with + 1 bit. uint32_t lhs_bits, rhs_bits; bool lhs_signed, rhs_signed; if (!ztIntInfo(ip, lhs_ty, &lhs_bits, &lhs_signed) || !ztIntInfo(ip, rhs_ty, &rhs_bits, &rhs_signed)) UNIMPLEMENTED("cmpNumeric: runtime operand is not an integer"); bool dest_signed = lhs_signed || rhs_signed; lhs_bits += !lhs_signed && dest_signed; rhs_bits += !rhs_signed && dest_signed; uint32_t max_bits = lhs_bits > rhs_bits ? lhs_bits : rhs_bits; if (max_bits > UINT16_MAX) COMPILE_ERROR_UPSTREAM( "analyzeCmp: exceeds maximum integer bit count"); dest_ty = ipGetIntType(ip, dest_signed, (uint16_t)max_bits); } uint32_t casted_lhs = semaCmpNumericCoerce(sema, block, lhs, lhs_ty, dest_ty); uint32_t casted_rhs = semaCmpNumericCoerce(sema, block, rhs, rhs_ty, dest_ty); return semaRuntimeBinOp( sema->insts, block, air_tag, casted_lhs, casted_rhs); } // Sema.zig:analyzeCmp // `*out_ty` is the result's type, a bool or cmpVector's vector of them. Both // operands comptime-known, non-numeric: semaCmpComptime's own peer type. static uint32_t analyzeCmp(SemaCtx* sema, U32List* block, uint32_t lhs, uint32_t lhs_ty, uint32_t rhs, uint32_t rhs_ty, ZirInstTag op, AirInstTag air_tag, bool is_equality_cmp, uint32_t* out_ty) { const InternPool* ip = sema->ip; *out_ty = ZIR_REF_BOOL_TYPE; bool lhs_vector = ztIsZigTypeTag(ip, lhs_ty, ZIG_TYPE_TAG_VECTOR); bool rhs_vector = ztIsZigTypeTag(ip, rhs_ty, ZIG_TYPE_TAG_VECTOR); if (lhs_vector && rhs_vector) return cmpVector(sema->zcu, op, air_tag, lhs, lhs_ty, rhs, rhs_ty, sema->insts, block, sema->air_extra, out_ty); if (semaTypeIsNumeric(ip, lhs_ty) && semaTypeIsNumeric(ip, rhs_ty)) return cmpNumeric(sema, block, lhs, lhs_ty, rhs, rhs_ty, op, air_tag); if (is_equality_cmp) { bool lhs_eu = semaIsErrorUnionType(ip, lhs_ty); bool rhs_eu = semaIsErrorUnionType(ip, rhs_ty); if ((lhs_eu && ipIsErrorSetType(ip, rhs_ty)) || (rhs_eu && ipIsErrorSetType(ip, lhs_ty))) { uint32_t eu = lhs_eu ? lhs : rhs; uint32_t eu_ty = lhs_eu ? lhs_ty : rhs_ty; if (!semaIsAirInstRef(eu) && ipItemTagIs(ip, eu, IP_TAG_ERROR_UNION_PAYLOAD)) return ZIR_REF_BOOL_FALSE; uint32_t code = analyzeErrUnionCode(sema, block, eu, eu_ty); return cmpSelf(sema, block, lhs_eu ? code : lhs, lhs_eu ? rhs : code, ipErrorUnionErrorSet(ip, eu_ty), op, air_tag, out_ty); } } if (!lhs_vector && !rhs_vector && !semaIsAirInstRef(lhs) && !semaIsAirInstRef(rhs)) return semaCmpComptime(sema->zcu, op, lhs, rhs); uint32_t tys[2] = { lhs_ty, rhs_ty }; uint32_t vals[2] = { semaIsAirInstRef(lhs) ? IP_INDEX_NONE : lhs, semaIsAirInstRef(rhs) ? IP_INDEX_NONE : rhs }; uint32_t resolved_type = semaTypeofPeerResolve(sema->zcu, tys, vals, 2u); if (!ztTypeIsSelfComparable(ip, resolved_type, is_equality_cmp)) COMPILE_ERROR_UPSTREAM("analyzeCmp: operator not allowed for type"); uint32_t casted_lhs = coerce(sema->zcu, sema->insts, block, sema->air_extra, resolved_type, lhs, lhs_ty); uint32_t casted_rhs = coerce(sema->zcu, sema->insts, block, sema->air_extra, resolved_type, rhs, rhs_ty); return cmpSelf(sema, block, casted_lhs, casted_rhs, resolved_type, op, air_tag, out_ty); } // Sema.zig:zirCmp // semaCmpOperandTypeOrNone, not semaResolveRefValType: an unresolved operand // has no type to read. static void zirCmp( SemaCtx* sema, U32List* block, uint32_t inst, AirInstTag air_tag) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); uint32_t lhs = semaResolveRef(extra.lhs, sema->inst_values); uint32_t rhs = semaResolveRef(extra.rhs, sema->inst_values); sema->inst_values[inst] = analyzeCmp(sema, block, lhs, semaCmpOperandTypeOrNone(sema->ip, sema->inst_types, extra.lhs, lhs), rhs, semaCmpOperandTypeOrNone(sema->ip, sema->inst_types, extra.rhs, rhs), sema->zir->inst_tags[inst], air_tag, false, &sema->inst_types[inst]); } // Sema.zig:analyzeIsNull // A bool: resolveIsNullFromType of `ty`, else a comptime-known optional or C // pointer folds through Value.isNull, else AIR is_null / is_non_null (un_op). static uint32_t analyzeIsNull(SemaCtx* sema, U32List* block, uint32_t operand, uint32_t ty, bool invert_logic) { uint32_t from_ty = ty == IP_INDEX_NONE ? IP_INDEX_NONE : resolveIsNullFromType(sema->ip, ty, sema->fn_ret_ty); if (from_ty != IP_INDEX_NONE) return semaBoolRef((from_ty == ZIR_REF_BOOL_TRUE) != invert_logic); if (!semaIsAirInstRef(operand)) { if (ipIsUndef(sema->ip, operand)) return ipGetUndef(sema->ip, ZIR_REF_BOOL_TYPE); return semaBoolRef(valIsNull(sema->ip, operand) != invert_logic); } return semaEmitUnOp(sema->insts, block, invert_logic ? AIR_INST_IS_NON_NULL : AIR_INST_IS_NULL, operand); } // Sema.zig:analyzeCmpUnionTag static uint32_t analyzeCmpUnionTag(SemaCtx* sema, U32List* block, uint32_t un, uint32_t union_ty, uint32_t tag, uint32_t tag_ty, ZirInstTag op, AirInstTag air_tag) { InternPool* ip = sema->ip; if (!semaIsAirInstRef(un) && !semaIsAirInstRef(tag)) { uint32_t folded = semaCmpUnionTagComptime(sema->zcu, op, un, tag); if (folded == IP_INDEX_NONE) UNIMPLEMENTED("analyzeCmpUnionTag: comptime operand shape"); return folded; } if (ipUnionTagUsage(ip, union_ty) != IP_UNION_TAG_USAGE_TAGGED) COMPILE_ERROR_UPSTREAM("analyzeCmp: comparison of union and enum " "literal needs a tagged union"); uint32_t union_tag_ty = ipUnionEnumTagType(ip, union_ty); // Coerce both the union and the tag to the union's tag type, and then // execute the enum comparison codepath. uint32_t coerced_tag = semaIsAirInstRef(tag) ? tag : semaCmpResolveEnumConst(ip, union_tag_ty, tag); if (coerced_tag == IP_INDEX_NONE || (semaIsAirInstRef(tag) && tag_ty != union_tag_ty)) UNIMPLEMENTED( "analyzeCmpUnionTag: tag operand not of the union's tag"); uint32_t coerced_union; if (semaIsAirInstRef(un)) { coerced_union = semaUnionToTagRuntime( sema->zcu, union_ty, un, sema->insts, block, NULL); } else { if (!ipItemTagIs(ip, un, IP_TAG_UNION_VALUE)) UNIMPLEMENTED("analyzeCmpUnionTag: comptime union operand shape"); coerced_union = ip->extra[ip->item_datas[un] + 1]; } if (!semaIsAirInstRef(coerced_tag) && semaCmpUnionTagNoPossibleValue(ip, union_ty, coerced_tag)) return ZIR_REF_BOOL_FALSE; uint32_t res_ty; return cmpSelf(sema, block, coerced_union, coerced_tag, union_tag_ty, op, air_tag, &res_ty); } // Sema.zig:zirCmpEq static void zirCmpEq( SemaCtx* sema, U32List* block, uint32_t inst, AirInstTag air_tag) { const InternPool* ip = sema->ip; ZirInstTag op = sema->zir->inst_tags[inst]; uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pp, &extra); uint32_t lhs_ref = extra.lhs; uint32_t rhs_ref = extra.rhs; uint32_t lhs = semaResolveRef(lhs_ref, sema->inst_values); uint32_t rhs = semaResolveRef(rhs_ref, sema->inst_values); sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; // `@typeInfo(T) == .tag` (Sema.zirCmpEq: a tagged-union value vs an enum // or enum literal is analyzeCmpUnionTag). if (ctIsTypeInfoValue(sema->zcu, lhs) || ctIsTypeInfoValue(sema->zcu, rhs)) { bool lhs_ti = ctIsTypeInfoValue(sema->zcu, lhs); uint32_t ti = lhs_ti ? lhs : rhs; uint32_t other = lhs_ti ? rhs : lhs; // `Type.zigTypeTag(zcu) == @typeInfo(T)`: the left side may // be a runtime `TypeId` AIR call while the right side is the // concrete `std.lang.Type` union value. Compare against that // union value's active enum tag, matching Sema.cmpSelf after // analyzeCmpUnionTag's tag extraction. if (semaIsAirInstRef(other)) { uint32_t tag_val = semaTypeInfoActiveTagValue(sema->zcu, ti); uint32_t tag_ty = ipEnumTagType(sema->ip, tag_val); uint32_t other_ty = semaAirRefTypeForBlockCoerce(sema->ip, sema->insts, other); if (other_ty != tag_ty) UNIMPLEMENTED( "zirCmpEq: @typeInfo runtime operand not TypeId"); uint32_t cmp = semaRuntimeBinOp( sema->insts, block, air_tag, other, tag_val); sema->inst_values[inst] = cmp; sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; return; } uint32_t ti_cmp = semaCmpUnionTagComptime(sema->zcu, op, ti, other); if (ti_cmp == IP_INDEX_NONE) UNIMPLEMENTED("zirCmpEq: @typeInfo other operand not an enum"); sema->inst_values[inst] = ti_cmp; sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; return; } uint32_t lhs_ty = semaCmpOperandTypeOrNone(ip, sema->inst_types, lhs_ref, lhs); uint32_t rhs_ty = semaCmpOperandTypeOrNone(ip, sema->inst_types, rhs_ref, rhs); if (lhs == INST_VALUE_UNSET || rhs == INST_VALUE_UNSET) UNREACHABLE("analyzeCmp: unresolved operand"); // `null == null`, else null against an optional or C pointer is // analyzeIsNull, `!=` inverting. bool lhs_null = lhs_ty == ZIR_REF_NULL_TYPE; bool rhs_null = rhs_ty == ZIR_REF_NULL_TYPE; if (lhs_null && rhs_null) { sema->inst_values[inst] = semaBoolRef(op == ZIR_INST_CMP_EQ); return; } if (lhs_null || rhs_null) { sema->inst_values[inst] = analyzeIsNull(sema, block, lhs_null ? rhs : lhs, lhs_null ? rhs_ty : lhs_ty, air_tag == AIR_INST_CMP_NEQ); return; } bool lhs_is_tag = ipIsEnumType(ip, lhs_ty) || lhs_ty == ZIR_REF_ENUM_LITERAL_TYPE; bool rhs_is_tag = ipIsEnumType(ip, rhs_ty) || rhs_ty == ZIR_REF_ENUM_LITERAL_TYPE; if (ztIsZigTypeTag(ip, lhs_ty, ZIG_TYPE_TAG_UNION) && rhs_is_tag) { sema->inst_values[inst] = analyzeCmpUnionTag( sema, block, lhs, lhs_ty, rhs, rhs_ty, op, air_tag); return; } if (ztIsZigTypeTag(ip, rhs_ty, ZIG_TYPE_TAG_UNION) && lhs_is_tag) { sema->inst_values[inst] = analyzeCmpUnionTag( sema, block, rhs, rhs_ty, lhs, lhs_ty, op, air_tag); return; } if (ipIsErrorSetType(ip, lhs_ty) && ipIsErrorSetType(ip, rhs_ty)) { bool eq; sema->inst_values[inst] = !semaIsAirInstRef(lhs) && !semaIsAirInstRef(rhs) && semaCmpErrorSetEq(ip, lhs, rhs, &eq) ? semaBoolRef(eq == (op == ZIR_INST_CMP_EQ)) : semaRuntimeBinOp(sema->insts, block, air_tag, lhs, rhs); return; } if (lhs_ty == ZIR_REF_TYPE_TYPE && rhs_ty == ZIR_REF_TYPE_TYPE && !semaIsAirInstRef(lhs) && !semaIsAirInstRef(rhs)) { sema->inst_values[inst] = semaBoolRef((lhs == rhs) == (op == ZIR_INST_CMP_EQ)); return; } sema->inst_values[inst] = analyzeCmp(sema, block, lhs, lhs_ty, rhs, rhs_ty, op, air_tag, true, &sema->inst_types[inst]); } // (part of) Sema.zig:zirSizeOf, Sema.zig:zirAlignOf, Sema.zig:zirBitSizeOf // The type operand of @sizeOf / @alignOf / @bitSizeOf with its layout // resolved: ABI size, alignment and bit size. Each handler comptime-folds its // answer to a `comptime_int` (pt.intValue(.comptime_int, _)), with no AIR, so // a subsequent coercion re-interns it at the destination type; // inst_types[inst] is written here. static uint32_t semaLayoutOfOperand( SemaCtx* sema, uint32_t inst, uint64_t* sz, uint64_t* al, uint64_t* bts) { uint32_t so_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t so_val = semaResolveRef(so_ref, sema->inst_values); if (semaIsAirInstRef(so_val)) UNIMPLEMENTED("size_of: runtime type operand"); // zirSizeOf/zirAlignOf/zirBitSizeOf's `"try sema.ensureLayoutResolved(ty, // operand_src, .size_of);"` (`.align_of` for @alignOf). ensureLayoutResolved(sema->zcu, so_val); // Persist struct layout into the IP header (size/align/offsets) // before reading it back, mirroring Sema resolving the layout on // first @sizeOf/@alignOf reference (no-op for non-structs / // already resolved). if (ipTagIsAnyStruct(sema->ip->item_tags[so_val])) if (!resolveStructLayout(sema->zcu, sema->ip, so_val)) UNIMPLEMENTED("size_of: struct layout resolution failed"); if (!ztLayoutInfo(sema->zcu, sema->ip, so_val, sz, al, bts)) UNIMPLEMENTED("size_of: unsupported type layout"); // The result is interned as a comptime_int, which keeps the full // 63-bit value where an AIR ref would truncate it to 32 bits. sema->inst_types[inst] = ZIR_REF_COMPTIME_INT_TYPE; return so_val; } // Sema.zig:zirSizeOf static void zirSizeOf(SemaCtx* sema, uint32_t inst) { uint64_t sz, al, bts; uint32_t ty = semaLayoutOfOperand(sema, inst, &sz, &al, &bts); // zirSizeOf rejects NPV and comptime-only operands after layout // resolution (zirAlignOf accepts them; it only rejects noreturn). semaSizeOfClassCheck(sema->ip, ty); sema->inst_values[inst] = ipGetIntComptimeIntU64(sema->ip, sz); } // Sema.zig:zirAlignOf static void zirAlignOf(SemaCtx* sema, uint32_t inst) { uint64_t sz, al, bts; semaLayoutOfOperand(sema, inst, &sz, &al, &bts); sema->inst_values[inst] = ipGetIntComptimeIntU64(sema->ip, al); } // Sema.zig:zirBitSizeOf static void zirBitSizeOf(SemaCtx* sema, uint32_t inst) { uint64_t sz, al, bts; semaLayoutOfOperand(sema, inst, &sz, &al, &bts); sema->inst_values[inst] = ipGetIntComptimeIntU64(sema->ip, bts); } // Sema.zig:zirThis // Sema.zirThis: `@This()` is the owner type of the active block // namespace. Runtime bodies reach this in ordinary value-position type // expressions (`const T = @This();`); type-position `@This()` is handled by // ctResolveParamTypeRef. static void zirThis(SemaCtx* sema, uint32_t inst) { uint32_t this_ty = semaScopedThisType(sema->zcu, sema->zir); if (this_ty == IP_INDEX_NONE) UNIMPLEMENTED("analyzeBodyInner: @This() with no analysis namespace"); sema->inst_values[inst] = this_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirClosureGet // Sema.zirClosureGet in a runtime body: the capture is a comptime-known value // resolved when the container was instantiated (registered under its // namespace); bind the instruction to it. Only type captures are modeled (`T` // inside an ArrayList(u8) method body — `@sizeOf(T)`, `[]T` locals); a // non-type capture aborts loudly. static void zirClosureGet(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t cg_idx = sema->zir->inst_datas[inst].extended.small; // `".nav_val => |nav| return sema.analyzeNavVal(block, src, nav),"`: a // mutable Nav is loaded, not read as its initializer. uint32_t cg_nav = semaScopedCaptureNavVal(sema->zcu, cg_idx); if (cg_nav != IP_INDEX_NONE) { sema->inst_values[inst] = analyzeNavVal(sema, block, cg_nav, &sema->inst_types[inst]); return; } uint32_t cg_v = semaScopedCaptureGet(sema->zcu, cg_idx); if (cg_v == IP_INDEX_NONE && sema->ictx->is_typeof) { // `.runtime => ... assert(block.is_typeof); return // block.addTy(.alloc, Type.fromInterned(index));"`: a // dummy runtime instruction of the captured type. uint32_t cg_ty = semaScopedCaptureTypeOfGet(sema->zcu, cg_idx); if (cg_ty == IP_INDEX_NONE) UNIMPLEMENTED( "analyzeBodyInner: typeof closure_get unresolved capture"); AirInstData cg_d; memset(&cg_d, 0, sizeof(cg_d)); cg_d.pair.a = cg_ty; uint32_t cg_air = semaAirInstAppend(sema->insts, AIR_INST_ALLOC, cg_d); u32ListAppend(block, cg_air); sema->inst_values[inst] = AIR_REF_INST_BASE + cg_air; sema->inst_types[inst] = cg_ty; return; } if (cg_v == IP_INDEX_NONE) UNIMPLEMENTED("analyzeBodyInner: closure_get unresolved capture"); if (cg_v >= sema->ip->items_len) UNIMPLEMENTED("analyzeBodyInner: closure_get non-InternPool capture"); if (ipIndexIsType(sema->ip, cg_v)) { sema->inst_values[inst] = cg_v; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } else if (sema->ip->item_tags[cg_v] == IP_TAG_OPT_NULL) { // A comptime optional-null capture (`alignment: // ?mem.Alignment` = null in Aligned(T, null)), passed // through as a comptime arg. Its type is the optional // type (item.data). sema->inst_values[inst] = cg_v; sema->inst_types[inst] = sema->ip->item_datas[cg_v]; } else { // A comptime-known value capture (e.g. HashMap's // `comptime max_load_percentage: u64` = 80, an INT_SMALL, // referenced in a runtime method body, or an enum-tag // capture): bind the interned value verbatim and derive // its type. Mirrors Sema.zirClosureGet returning the // captured comptime value; downstream runtime consumers // already handle an interned comptime operand. A capture // whose type cannot be derived aborts loudly. uint32_t cg_ty = ipComptimeValueType(sema->ip, cg_v); if (cg_ty == IP_INDEX_NONE) UNIMPLEMENTED( "analyzeBodyInner: closure_get unmodeled capture value"); sema->inst_values[inst] = cg_v; sema->inst_types[inst] = cg_ty; } } // Sema.zig:zirRetAddr // `"if (block.semaBlockIsComptime()) { ... return .zero_usize; } else { return // block.addNoOp(.ret_addr); }"`. The interpreter yields 0 -- the value is only // threaded through as the allocator `ret_addr` bookkeeping parameter, never // dereferenced. static void zirRetAddr(SemaCtx* sema, U32List* block, uint32_t inst) { sema->inst_types[inst] = ZIR_REF_USIZE_TYPE; if (semaBlockIsComptime(sema->ictx)) { sema->inst_values[inst] = ZIR_REF_ZERO_USIZE; return; } AirInstData ra_data; memset(&ra_data, 0, sizeof(ra_data)); uint32_t ra_idx = semaAirInstAppend(sema->insts, AIR_INST_RET_ADDR, ra_data); u32ListAppend(block, ra_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + ra_idx; sema->inst_types[inst] = ZIR_REF_USIZE_TYPE; } // Sema.zig:zirBuiltinSrc // Sema.zirBuiltinSrc, anchor `const fields = .{`: intern the five fields of // std.lang.SourceLocation at the @src() call site. The ZIR payload is // Zir.Inst.Src { node, line, column }; line/column are zero-based and the // language value is one-based. `node` is diagnostic provenance only. These // payload checks are internal AstGen/ZIR invariants; valid source cannot // select either malformed shape. static void zirBuiltinSrc(SemaCtx* sema, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; ZirInstSrc extra; zirExtraData(sema->zir, ext_operand, &extra); if (sema->zir->inst_datas[inst].extended.small != 0) UNREACHABLE("builtin_src: unexpected small payload"); if (ext_operand > sema->zir->extra_len || sema->zir->extra_len - ext_operand < 3) UNIMPLEMENTED("builtin_src: truncated Src payload"); // The active function name and current Zir registration are // internal analysis-frame/Zcu invariants, not source shapes. if (sema->ictx->fn_name_ctx == 0 || sema->ictx->fn_name_ctx == IP_INDEX_NONE) UNIMPLEMENTED("builtin_src: missing function name"); // block.getFileScope supplies the current function body's // file, not necessarily the compilation root. Find it by the // stable Zir pointer (the same identity used by // zcuNamespaceOfZir). uint32_t bs_file_idx = UINT32_MAX; for (uint32_t fi = 0; fi < sema->zcu->num_files; fi++) { if (sema->zcu->files[fi].zir == sema->zir) { bs_file_idx = fi; break; } } if (bs_file_idx == UINT32_MAX) UNIMPLEMENTED("builtin_src: file scope not registered"); const ZcuFile* bs_file = &sema->zcu->files[bs_file_idx]; if (bs_file->sub_file_path == NULL || bs_file->module_name == NULL) UNIMPLEMENTED("builtin_src: file source identity missing"); size_t bs_module_len = strlen(bs_file->module_name); size_t bs_file_len = strlen(bs_file->sub_file_path); if (bs_module_len > UINT32_MAX || bs_file_len > UINT32_MAX) UNIMPLEMENTED("builtin_src: source name too long"); uint32_t bs_line = extra.line; uint32_t bs_column = extra.column; if (bs_line == UINT32_MAX || bs_column == UINT32_MAX) UNIMPLEMENTED("builtin_src: source coordinate overflow"); // Match zirBuiltinSrc's interning order exactly: function // name value first, then module, then file, then resolve the // SourceLocation type. Keep these as sequential statements; // side-effecting C initializer expressions have no portable // evaluation order. uint32_t bs_func_val = ipGetNullTerminatedStringSlice(sema->ip, sema->ictx->fn_name_ctx); uint32_t bs_module_val = ipGetNullTerminatedBytesSlice( sema->ip, bs_file->module_name, (uint32_t)bs_module_len); uint32_t bs_file_val = ipGetNullTerminatedBytesSlice( sema->ip, bs_file->sub_file_path, (uint32_t)bs_file_len); static const char* const bs_src_path[] = { "SourceLocation" }; uint32_t bs_src_ty = getStdLangType(sema->zcu, bs_src_path, 1); uint32_t bs_fields[5]; bs_fields[0] = bs_module_val; bs_fields[1] = bs_file_val; bs_fields[2] = bs_func_val; bs_fields[3] = ipGetIntU32(sema->ip, bs_line + 1u); bs_fields[4] = ipGetIntU32(sema->ip, bs_column + 1u); sema->inst_values[inst] = ipGetAggregate(sema->ip, bs_src_ty, bs_fields, 5); sema->inst_types[inst] = bs_src_ty; } // Sema.zig:zirTypeInfo // Sema.zirTypeInfo — fold-only. Shares ctEvalBody's real `std.lang.Type` union // producer for @typeInfo(T). static void zirTypeInfo(SemaCtx* sema, uint32_t inst) { uint32_t ti_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t ti_ty = semaResolveRef(ti_ref, sema->inst_values); if (!semaRefIsInterned(ti_ty)) UNIMPLEMENTED("type_info: non-type operand"); sema->inst_values[inst] = ctTypeInfoValueForType(sema->zcu, ti_ty); static const char* const type_path[] = { "Type" }; sema->inst_types[inst] = getStdLangType(sema->zcu, type_path, 1); } // Sema.zig:zirTypeof // Sema.zirTypeof: returns the type of the operand as a comptime type value. No // AIR instruction is emitted. Data: un_node, operand is a ZIR Ref. static void zirTypeof(SemaCtx* sema, uint32_t inst) { uint32_t typeof_operand_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t typeof_operand_val = semaResolveRef(typeof_operand_ref, sema->inst_values); uint32_t typeof_ty; if (ipIsPtrComptimeAlloc(sema->ip, typeof_operand_val) && typeof_operand_ref >= ZIR_REF_START_INDEX) { // Comptime-mutable alloc pointer (alloc_comptime_mut, e.g. a // `comptime var` in a runtime body): Sema.typeOf of a comptime // alloc pointer yields its pointer type `*T`, which the // producing instruction already recorded in inst_types. (The // downstream ELEM_TYPE strips it back to the element type for // the `.{...}` result-location coercion.) An inferred comptime // alloc records UINT32_MAX until its first store resolves the // element type -- abort loudly rather than propagate it as a // real IP index. typeof_ty = sema->inst_types[typeof_operand_ref - ZIR_REF_START_INDEX]; if (typeof_ty == UINT32_MAX) UNIMPLEMENTED("typeof: @TypeOf of inferred comptime alloc " "(unresolved cell ptr type)"); } else { if (typeof_operand_ref >= ZIR_REF_START_INDEX && sema->inst_types[typeof_operand_ref - ZIR_REF_START_INDEX] != IP_INDEX_NONE) { // Instruction operands carry their analyzed result type // even when the value is a static preinterned constant // such as `0` (which cannot itself distinguish // comptime_int from `u0`). typeof_ty = sema->inst_types[typeof_operand_ref - ZIR_REF_START_INDEX]; } else if (semaIsAirInstRef(typeof_operand_val)) { // Runtime operand: look up its type from inst_types. typeof_ty = semaResolveRefType(typeof_operand_ref, sema->inst_types); } else { // Comptime operand: recover its type from the complete // InternPool value, not only the static-preintern table. // DECL_REF of a mutable global produces a dynamic // `ptr_nav` (for example the lhs of `global_ptr = // &local`); upstream InternPool.typeOf's `.ptr_nav` arm // returns the `ty` word carried by that value. // ipComptimeValueType mirrors that read without // re-interning the pointer or perturbing IP order. if (typeof_operand_val >= sema->ip->items_len) UNREACHABLE("typeof: comptime operand IP out of range"); typeof_ty = ipComptimeValueType(sema->ip, typeof_operand_val); } } sema->inst_values[inst] = typeof_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirTypeofBuiltin // Sema.zirTypeofBuiltin: `@TypeOf(expr)` — the payload is a Block { body_len, // body[] } ending in `break_inline(%operand)`, analyzed in a type-only scope // (never executed). Only the directly-typeable single-result shape is modeled: // the operand's type is read exactly like the TYPEOF handler above (a runtime // ref via inst_types, a comptime int value via its interned type). static void zirTypeofBuiltin(SemaCtx* sema, uint32_t inst) { uint32_t tb_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBlock extra; uint32_t extra_end = zirExtraData(sema->zir, tb_pp, &extra); uint32_t tb_len = extra.body_len; if (tb_len == 0) UNIMPLEMENTED("typeof_builtin: empty body"); uint32_t tb_last = sema->zir->extra[extra_end + tb_len - 1]; if (sema->zir->inst_tags[tb_last] != ZIR_INST_BREAK_INLINE) UNREACHABLE("typeof_builtin: body not break_inline"); uint32_t tb_op = sema->zir->inst_datas[tb_last].break_data.operand; if (tb_op >= ZIR_REF_START_INDEX) { uint32_t tb_oi = tb_op - ZIR_REF_START_INDEX; if (sema->zir->inst_tags[tb_oi] == ZIR_INST_EXTENDED && sema->zir->inst_datas[tb_oi].extended.opcode == (uint16_t)ZIR_EXT_CLOSURE_GET) { uint32_t cg_idx = sema->zir->inst_datas[tb_oi].extended.small; uint32_t cg_ty = semaScopedCaptureTypeOfGet(sema->zcu, cg_idx); if (cg_ty == IP_INDEX_NONE) UNIMPLEMENTED( "typeof_builtin: closure_get unresolved capture"); sema->inst_values[inst] = cg_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; return; } } // `"try sema.analyzeBodyInner(&child_block, body);"` runs on // every visit: a body instruction's value from an earlier visit // (the previous unrolled iteration) is not this one's. for (uint32_t k = 0; k + 1 < tb_len; k++) sema->inst_values[sema->zir->extra[extra_end + k]] = INST_VALUE_UNSET; uint32_t tb_val = semaResolveRef(tb_op, sema->inst_values); if (tb_val == INST_VALUE_UNSET) { // `@TypeOf(local)` where `local` is a runtime inferred allocation: // the typeof body breaks on a load that is not otherwise evaluated // in this type-only scope. uint32_t tb_load_ty = semaTypeOfUnevaluatedLoad( sema->zcu, sema->zir, sema->inst_values, sema->inst_types, tb_op); if (tb_load_ty != IP_INDEX_NONE) { sema->inst_values[inst] = tb_load_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; return; } if (tb_op >= ZIR_REF_START_INDEX && (sema->zir->inst_tags[tb_op - ZIR_REF_START_INDEX] == ZIR_INST_DECL_VAL || sema->zir->inst_tags[tb_op - ZIR_REF_START_INDEX] == ZIR_INST_DECL_REF)) { uint32_t di = tb_op - ZIR_REF_START_INDEX; uint32_t name_off = sema->zir->inst_datas[di].str_tok.start; const char* name = zirNullTerminatedString(sema->zir, name_off); uint32_t name_str = ipGetOrPutString(sema->ip, name, (uint32_t)strlen(name)); uint32_t nav = semaScopedDeclLookup( sema->ip, sema->zcu, sema->zir, name_str); if (nav == IP_INDEX_NONE) UNIMPLEMENTED("typeof_builtin: declaration not found"); uint32_t tb_ty = IP_INDEX_NONE; if (sema->zir->inst_tags[di] == ZIR_INST_DECL_VAL) navAnalyzeVal(sema->zcu, nav, &tb_ty); else navDeclRefAddressResolved(sema->zcu, nav, &tb_ty); if (!ipIndexIsType(sema->ip, tb_ty)) UNIMPLEMENTED("typeof_builtin: declaration type unresolved"); sema->inst_values[inst] = tb_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; return; } if (tb_op >= ZIR_REF_START_INDEX && tb_op - ZIR_REF_START_INDEX < sema->zir->inst_len) { // `@TypeOf(a.b.c)` where `a` is a runtime value: analyze // the child block and read the operand type populated by // the normal handlers. bool tb_noreturn = semaAnalyzeTypeofBody( sema, zirBodySlice(sema->zir, extra_end), tb_len - 1); uint32_t tb_ty = tb_noreturn ? ZIR_REF_NORETURN_TYPE : sema->inst_types[tb_op - ZIR_REF_START_INDEX]; if (tb_ty != IP_INDEX_NONE) { sema->inst_values[inst] = tb_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; return; } } UNIMPLEMENTED("typeof_builtin: operand not directly typeable"); } if (tb_val == IP_INDEX_NONE) UNIMPLEMENTED("typeof_builtin: operand is IP_INDEX_NONE"); uint32_t tb_ty; if (semaIsAirInstRef(tb_val)) { tb_ty = semaResolveRefType(tb_op, sema->inst_types); } else { if (tb_op >= ZIR_REF_START_INDEX && sema->inst_types[tb_op - ZIR_REF_START_INDEX] != IP_INDEX_NONE) { tb_ty = sema->inst_types[tb_op - ZIR_REF_START_INDEX]; sema->inst_values[inst] = tb_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; return; } uint32_t tb_ip = tb_val; if (tb_ip >= sema->ip->items_len) UNREACHABLE("typeof_builtin: comptime operand IP out of range"); // Like TYPEOF above, this is the same upstream // InternPool.typeOf operation. In particular an outer-body // local bound to `&global` is a dynamic ptr_nav, not a static // preinterned value. tb_ty = ipComptimeValueType(sema->ip, tb_ip); } sema->inst_values[inst] = tb_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirTypeofLog2IntType // Sema.zirTypeofLog2IntType -> log2IntType: for an N-bit integer operand, // returns the type u. For u32 -> u5 (since log2_int_ceil(32) = // 5). The result is a type-value IP index. static void zirTypeofLog2IntType(SemaCtx* sema, uint32_t inst) { uint32_t operand_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t operand_ty = semaResolveRefType(operand_ref, sema->inst_types); if (operand_ty == IP_INDEX_NONE) { // An untyped comptime_int operand (`16 << s` -- a `zir int` // below 2^63 records no inst_types entry). uint32_t olv = semaResolveRef(operand_ref, sema->inst_values); // Class-H (H1): an already-INTERNED integer value can also // arrive with no `inst_types[]` entry (DECL_VAL provenance). // Recover its type from the item -- the same `ipIntValueType` // pattern used by the shift and cmp arms (anchor // "ipIntValueType(ip, (uint32_t)" -- cite by anchor per // 3a.10a). Declines (IP_INDEX_NONE) for every non-integer // value, leaving `operand_ty` as it was. operand_ty = semaInternedIntValueType(sema->ip, olv); } if (operand_ty == IP_INDEX_NONE) UNIMPLEMENTED("typeof_log2_int_type: operand type unknown"); sema->inst_values[inst] = log2IntType(sema->ip, operand_ty); } // Sema.zig:zirTypeofPeer // `@TypeOf(a, b, ...)` in a runtime body (Sema.zirTypeofPeer). `operand` is // the payload index of Zir.Inst.TypeOfPeer { src_node, body_len, body_index } // and `small` is the arg count, with the arg refs trailing the payload. The // body is analyzed in a discarded child block ("Ignore the result, we only // care about the instructions in `args`"), then each arg is typed. The peer // rule is the shared `semaTypeofPeerResolve`. static void zirTypeofPeer(SemaCtx* sema, uint32_t inst) { uint32_t tp_pl = sema->zir->inst_datas[inst].extended.operand; ZirInstTypeOfPeer extra; uint32_t extra_end = zirExtraData(sema->zir, tp_pl, &extra); uint32_t tp_args_len = sema->zir->inst_datas[inst].extended.small; if (tp_args_len < 2) UNREACHABLE("typeof_peer: fewer than two arguments"); uint32_t tp_body_len = extra.body_len; const uint32_t* tp_body = zirBodySlice(sema->zir, extra.body_index); // The body ends in its `break_inline` to this instruction. // `"_ = try sema.analyzeInlineBody(&child_block, body, // inst);"`: the args' types are peer-resolved either way. if (tp_body_len != 0) semaAnalyzeTypeofBody(sema, tp_body, tp_body_len - 1); uint32_t* tp_tys = zig1Malloc(2u * tp_args_len * sizeof(uint32_t)); uint32_t* tp_vals = tp_tys + tp_args_len; for (uint32_t tk = 0; tk < tp_args_len; tk++) { uint32_t tp_arg = sema->zir->extra[extra_end + tk]; uint32_t tp_ty; // upstream's `peer_vals[i]`: the argument's comptime // value, or IP_INDEX_NONE when it has none. uint32_t tp_val; if (tp_arg < ZIR_REF_START_INDEX) { tp_ty = ipTypeOfPreinterned(tp_arg); tp_val = tp_arg; } else { uint32_t tp_v = semaResolveRef(tp_arg, sema->inst_values); tp_val = (!semaRefIsInterned(tp_v)) ? IP_INDEX_NONE : tp_v; tp_ty = sema->inst_types[tp_arg - ZIR_REF_START_INDEX]; uint32_t tp_ai = tp_arg - ZIR_REF_START_INDEX; // The argument expressions live in the typeof body, // which (as above) is never executed, so a // `@TypeOf(x, y)` over plain variables breaks on // LOADs that nothing else evaluated -- the same // derivation the typeof_builtin handler makes. uint32_t tp_load_ty = IP_INDEX_NONE; if (tp_ty == IP_INDEX_NONE && tp_v == INST_VALUE_UNSET) tp_load_ty = semaTypeOfUnevaluatedLoad(sema->zcu, sema->zir, sema->inst_values, sema->inst_types, tp_arg); if (tp_load_ty != IP_INDEX_NONE) { tp_ty = tp_load_ty; tp_val = IP_INDEX_NONE; } else if (tp_ty == IP_INDEX_NONE && tp_v == INST_VALUE_UNSET && sema->zir->inst_tags[tp_ai] == ZIR_INST_INT) { // An integer literal argument, likewise emitted // into the never-executed typeof body: // Sema.zirInt interns a `comptime_int`, which is // the peer this argument contributes. tp_ty = ZIR_REF_COMPTIME_INT_TYPE; // Sema.zirInt interns the literal; that interned // `comptime_int` is this peer's value, which // `.fixed_int` needs before it will refine a // `comptime_int` peer to a fixed width. Interned // exactly as the `ZIR_INST_INT` handler does. uint64_t tp_lit = sema->zir->inst_datas[tp_ai].int_val; tp_val = tp_lit > 0xFFFFFFFFu ? ipGetIntComptimeIntU64(sema->ip, tp_lit) : ipGetIntComptimeIntU32(sema->ip, (uint32_t)tp_lit); } else if (tp_ty == IP_INDEX_NONE && tp_val != IP_INDEX_NONE) { tp_ty = ipTypeOf(sema->ip, tp_val); } } if (tp_ty == IP_INDEX_NONE) UNIMPLEMENTED("typeof_peer: operand not typeable"); tp_tys[tk] = tp_ty; tp_vals[tk] = tp_val; } sema->inst_values[inst] = semaTypeofPeerResolve(sema->zcu, tp_tys, tp_vals, tp_args_len); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; free(tp_tys); } // Sema.zig:zirBoolBr // Sema.zirBoolBr: short-circuiting `and`/`or`. ZIR pl_node -> BoolBr { lhs: // Ref, body_len } then the rhs inline body (terminated by a break_inline // targeting this inst). Two arms: // - runtime lhs: lower to a bool `block` + `cond_br` (Sema's // runtime arm — see below). // - comptime-known lhs: no AIR block — `false and _` -> false // and // `true or _` -> true short-circuit without analyzing the rhs; // otherwise the result is the rhs body's value coerced to // bool. // Returns whether the body ends here (noreturn or a comptime break). static bool zirBoolBr( SemaCtx* sema, U32List* block, uint32_t inst, bool is_or) { uint32_t bp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBoolBr extra; uint32_t extra_end = zirExtraData(sema->zir, bp, &extra); uint32_t bb_lhs_ref = extra.lhs; uint32_t bb_body_len = extra.body_len; const uint32_t* bb_body = zirBodySlice(sema->zir, extra_end); uint32_t bb_lhs = semaResolveRef(bb_lhs_ref, sema->inst_values); if (semaIsAirInstRef(bb_lhs)) { // Runtime lhs: mirror Sema.zirBoolBr's runtime arm exactly. A // bool-typed AIR `block` wraps a single `cond_br` on the lhs. // One branch immediately breaks the block with the // short-circuit constant (`false` for `and`, `true` for `or`); // the other evaluates the rhs body inline and breaks with its // (already-bool) result. The AIR instruction allocation order // — block, lhs-br, rhs body, rhs-br, cond_br — matches Sema // (lhs_block.addBr precedes resolveInlineBody(rhs) precedes // finishCondBr's cond_br), which upstream's AIR order depends // on. // // `and` (is_or==false): then_body = rhs, else_body = lhs-br // `or` (is_or==true): then_body = lhs-br, else_body = rhs // where lhs-br = `br block, `. // 1. Reserve the bool `block` (ty_pl.ty = bool; payload // later). AirInstData bb_blk_data; memset(&bb_blk_data, 0, sizeof(bb_blk_data)); uint32_t bb_blk_idx = semaAirInstAppend(sema->insts, AIR_INST_BLOCK, bb_blk_data); sema->inst_values[inst] = AIR_REF_INST_BASE + bb_blk_idx; sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; // 2. lhs branch `br block, ` — allocated // before the rhs body (Sema's lhs_block.addBr order). AirInstData bb_lhs_br_data; memset(&bb_lhs_br_data, 0, sizeof(bb_lhs_br_data)); bb_lhs_br_data.pair.a = bb_blk_idx; // br.block_inst bb_lhs_br_data.pair.b = is_or ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; uint32_t bb_lhs_br_idx = semaAirInstAppend(sema->insts, AIR_INST_BR, bb_lhs_br_data); // 3. Evaluate the rhs body inline. Value-producing rhs bodies // terminate in a `break_inline` targeting this inst // (handled like the comptime rhs — sets ictx->brk with the // rhs operand). A noreturn rhs (`and @panic(...)`, `or // @trap()`) instead leaves a terminating AIR instruction in // the rhs body and produces no break operand. bool bb_parent_hint_set = sema->ictx->branch_hint_set; uint32_t bb_parent_hint = sema->ictx->branch_hint; sema->ictx->branch_hint_set = false; sema->ictx->branch_hint = 0; U32List bb_rhs_body = { 0 }; SemaBlock bb_blk; semaBlockPush(sema->ictx, &bb_blk, true, UINT32_MAX, UINT32_MAX); analyzeBodyInner(sema, &bb_rhs_body, bb_body, bb_body_len); semaBlockPop(sema->ictx, &bb_blk); uint32_t bb_rhs_hint = sema->ictx->branch_hint_set ? sema->ictx->branch_hint : 0u; sema->ictx->branch_hint_set = bb_parent_hint_set; sema->ictx->branch_hint = bb_parent_hint; bool bb_rhs_has_value = false; uint32_t bb_rhs_val = 0; bool bb_rhs_comptime = false; if (sema->ictx->brk && sema->ictx->brk_target == inst) { sema->ictx->brk = false; bb_rhs_has_value = true; uint32_t bb_rhs_ref = sema->ictx->brk_operand; bb_rhs_val = semaResolveRef(bb_rhs_ref, sema->inst_values); // The rhs is either a runtime bool (coerce is identity) or // a comptime-known bool (`len == 0 and `). // Sema emits the same block/cond_br either way (rhs_block.addBr // with the constant), and only after finishCondBr replaces the // Result with a constant when the rhs short-circuits the // whole expression (zirBoolBr's trailing // resolveDefinedValue check) — mirrored below. bb_rhs_comptime = !semaIsAirInstRef(bb_rhs_val); if (bb_rhs_comptime) { if (bb_rhs_val != ZIR_REF_BOOL_TRUE && bb_rhs_val != ZIR_REF_BOOL_FALSE) { free(bb_rhs_body.items); UNIMPLEMENTED("zirBoolBr: bool_br runtime - comptime rhs " "not a bool constant"); } } else { uint32_t bb_rhs_ty = semaResolveRefType(bb_rhs_ref, sema->inst_types); if (bb_rhs_ty != ZIR_REF_BOOL_TYPE) { free(bb_rhs_body.items); UNIMPLEMENTED("zirBoolBr: bool_br runtime - non-bool rhs"); } } } else if (sema->ictx->brk) { free(bb_rhs_body.items); return true; // break to an outer inline block: propagate } else if (!semaAirBodyEndsNoReturn(sema->insts, &bb_rhs_body)) { free(bb_rhs_body.items); UNIMPLEMENTED( "zirBoolBr: bool_br runtime - rhs body did not break to it"); } // 4. rhs branch `br block, rhs_result` (after the rhs body), // if // the rhs produced a bool value. Noreturn rhs bodies // already terminate their branch. uint32_t bb_rhs_br_idx = UINT32_MAX; if (bb_rhs_has_value) { AirInstData bb_rhs_br_data; memset(&bb_rhs_br_data, 0, sizeof(bb_rhs_br_data)); bb_rhs_br_data.pair.a = bb_blk_idx; bb_rhs_br_data.pair.b = (uint32_t)bb_rhs_val; bb_rhs_br_idx = semaAirInstAppend(sema->insts, AIR_INST_BR, bb_rhs_br_data); } // 5. Assemble then/else branch bodies. For `and` the rhs is // the // Then branch; for `or` it is the else branch. U32List bb_then = { 0 }; U32List bb_else = { 0 }; U32List* bb_rhs_dest = is_or ? &bb_else : &bb_then; U32List* bb_lhs_dest = is_or ? &bb_then : &bb_else; for (uint32_t ri = 0; ri < bb_rhs_body.len; ri++) u32ListAppend(bb_rhs_dest, bb_rhs_body.items[ri]); if (bb_rhs_has_value) u32ListAppend(bb_rhs_dest, bb_rhs_br_idx); u32ListAppend(bb_lhs_dest, bb_lhs_br_idx); free(bb_rhs_body.items); // 6. cond_br payload {then_len, else_len, hints} + bodies. // The rhs branch receives the hint collected while // analyzing the rhs; the short-circuit branch keeps .none. uint32_t bb_true_hint = is_or ? 0u : bb_rhs_hint; uint32_t bb_false_hint = is_or ? bb_rhs_hint : 0u; uint32_t bb_cbr_extra = sema->air_extra->len; u32ListAppend(sema->air_extra, bb_then.len); u32ListAppend(sema->air_extra, bb_else.len); u32ListAppend( sema->air_extra, AIR_COND_BR_HINTS(bb_true_hint, bb_false_hint)); for (uint32_t ti = 0; ti < bb_then.len; ti++) u32ListAppend(sema->air_extra, bb_then.items[ti]); for (uint32_t ei = 0; ei < bb_else.len; ei++) u32ListAppend(sema->air_extra, bb_else.items[ei]); free(bb_then.items); free(bb_else.items); // 7. cond_br{operand=lhs, payload} — the sole child_block // inst. AirInstData bb_cbr_data; memset(&bb_cbr_data, 0, sizeof(bb_cbr_data)); bb_cbr_data.pair.a = (uint32_t)bb_lhs; // pl_op.operand (cond) bb_cbr_data.pair.b = bb_cbr_extra; // pl_op.payload uint32_t bb_cbr_idx = semaAirInstAppend(sema->insts, AIR_INST_COND_BR, bb_cbr_data); // 8. block payload: Block{body_len=1} + [cond_br]. uint32_t bb_blk_extra = sema->air_extra->len; u32ListAppend(sema->air_extra, 1u); u32ListAppend(sema->air_extra, bb_cbr_idx); sema->insts->datas[bb_blk_idx].pair.a = ZIR_REF_BOOL_TYPE; // ty sema->insts->datas[bb_blk_idx].pair.b = bb_blk_extra; // payload // 9. The block joins the parent body and yields the bool // result. u32ListAppend(block, bb_blk_idx); // Sema.zirBoolBr's trailing resolveDefinedValue: when the // Comptime rhs short-circuits the whole expression (`or` with // rhs true / `and` with rhs false), the result is that // constant — the emitted block stays as dead AIR, exactly as // upstream leaves it. if (bb_rhs_has_value && bb_rhs_comptime) { bool bb_rhs_true = (bb_rhs_val == ZIR_REF_BOOL_TRUE); if (is_or && bb_rhs_true) sema->inst_values[inst] = ZIR_REF_BOOL_TRUE; else if (!is_or && !bb_rhs_true) sema->inst_values[inst] = ZIR_REF_BOOL_FALSE; } return false; } if (bb_lhs != ZIR_REF_BOOL_TRUE && bb_lhs != ZIR_REF_BOOL_FALSE) UNIMPLEMENTED("zirBoolBr: bool_br - non-bool comptime lhs"); bool lhs_true = (bb_lhs == ZIR_REF_BOOL_TRUE); if (is_or ? lhs_true : !lhs_true) { // Short-circuit: `true or _` -> true; `false and _` -> false. sema->inst_values[inst] = lhs_true ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; return false; } // No short-circuit: the result is the rhs body's break operand. analyzeBodyInner(sema, block, bb_body, bb_body_len); if (sema->ictx->brk && sema->ictx->brk_target == inst) { sema->ictx->brk = false; uint32_t op_ref = sema->ictx->brk_operand; sema->inst_values[inst] = semaResolveRef(op_ref, sema->inst_values); sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; return false; } if (sema->ictx->brk) return true; // break to an outer inline block: propagate UNIMPLEMENTED("zirBoolBr: bool_br - rhs body did not break to it"); ZIG1_TCC_RETURN(false); } // Sema.zig:zirIsNonNull static void zirIsNonNull(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t op_ref = sema->zir->inst_datas[inst].un_node.operand; sema->inst_values[inst] = analyzeIsNull(sema, block, semaResolveRef(op_ref, sema->inst_values), semaResolveRefType(op_ref, sema->inst_types), true); sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; } // Sema.zig:zirIsNonNullPtr // `if (opt_slot) |x|` with the subject kept by-ref: the operand points to // the optional. static void zirIsNonNullPtr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ptr_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t ptr = semaResolveRef(ptr_ref, sema->inst_values); IpPtrInfo info; if (ipPtrInfo( sema->ip, semaResolveRefType(ptr_ref, sema->inst_types), &info) != IP_PTRI_OK) UNREACHABLE("is_non_null_ptr: non-pointer operand"); // `"try sema.ensureLayoutResolved(nullable_ty, src, .ptr_access);"` ensureLayoutResolved(sema->zcu, info.child); sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; uint32_t is_null = resolveIsNullFromType(sema->ip, info.child, sema->fn_ret_ty); if (is_null != IP_INDEX_NONE) { sema->inst_values[inst] = semaBoolRef(is_null != ZIR_REF_BOOL_TRUE); return; } if (!semaIsAirInstRef(ptr)) { uint32_t nullable_val = pointerDeref(sema->zcu, ptr, NULL); if (nullable_val != IP_INDEX_NONE) { sema->inst_values[inst] = analyzeIsNull(sema, block, nullable_val, info.child, true); return; } } AirInstData d; memset(&d, 0, sizeof(d)); d.pair.a = ptr; uint32_t idx = semaAirInstAppend(sema->insts, AIR_INST_IS_NON_NULL_PTR, d); u32ListAppend(block, idx); sema->inst_values[inst] = AIR_REF_INST_BASE + idx; } // Sema.zig:zirIsNonErr static void zirIsNonErr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t op_ref = sema->zir->inst_datas[inst].un_node.operand; sema->inst_values[inst] = analyzeIsNonErr(sema, block, semaResolveRef(op_ref, sema->inst_values), semaResolveRefType(op_ref, sema->inst_types)); sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; } // Sema.zig:zirRetIsNonErr static void zirRetIsNonErr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t op_ref = sema->zir->inst_datas[inst].un_node.operand; sema->inst_values[inst] = analyzeIsNonErr(sema, block, semaResolveRef(op_ref, sema->inst_values), semaResolveRefType(op_ref, sema->inst_types)); sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; } // Sema.zig:zirIsNonErrPtr // The operand points to an error union (`if (x) |v|` / `catch` with the // error union kept by-ref): analyzeLoad, then analyzeIsNonErr -- not AIR // is_non_err_ptr, which is analyzePtrIsNonErr's. static void zirIsNonErrPtr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ptr_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t loaded_ty; uint32_t loaded = analyzeLoad(sema->zcu, sema->insts, block, semaResolveRef(ptr_ref, sema->inst_values), semaResolveRefType(ptr_ref, sema->inst_types), &loaded_ty); sema->inst_values[inst] = analyzeIsNonErr(sema, block, loaded, loaded_ty); sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; } // Sema.zig:zirCondbr // Sema.zirCondbr: runtime condition -> analyzeBodyRuntimeBreak for each // branch, then emit AIR CondBr extra + cond_br inst. Restricted to runtime // conditions; comptime-folded condbr is UNIMPLEMENTED. static void zirCondbr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstCondBr extra; uint32_t extra_end = zirExtraData(sema->zir, pp, &extra); uint32_t cond_ref = extra.condition; uint32_t then_body_len = extra.then_body_len; uint32_t else_body_len = extra.else_body_len; const uint32_t* then_body = zirBodySlice(sema->zir, extra_end); const uint32_t* else_body = zirBodySlice(sema->zir, extra_end + then_body_len); uint32_t cond_val = semaResolveRef(cond_ref, sema->inst_values); if (!semaIsAirInstRef(cond_val)) { // Sema.zirCondbr comptime path (resolveDefinedValue succeeds): // analyze only the taken branch, inline into the current // block. No AIR cond_br is emitted. The taken body ends in a // `break` to the enclosing `block`, which the block handler // folds. bool ct_taken; if (cond_val == ZIR_REF_BOOL_TRUE) ct_taken = true; else if (cond_val == ZIR_REF_BOOL_FALSE) ct_taken = false; else UNIMPLEMENTED("condbr: non-bool comptime condition"); const uint32_t* ct_body = ct_taken ? then_body : else_body; uint32_t ct_len = ct_taken ? then_body_len : else_body_len; bool ct_parent_hint_set = sema->ictx->branch_hint_set; uint32_t ct_parent_hint = sema->ictx->branch_hint; analyzeBodyInner(sema, block, ct_body, ct_len); bool ct_child_hint_set = sema->ictx->branch_hint_set; uint32_t ct_child_hint = sema->ictx->branch_hint; if (ct_parent_hint_set) { sema->ictx->branch_hint_set = true; sema->ictx->branch_hint = ct_parent_hint; } else if (ct_child_hint_set && ct_child_hint == BRANCH_HINT_COLD) { sema->ictx->branch_hint_set = true; sema->ictx->branch_hint = BRANCH_HINT_COLD; // only cold propagates } else { sema->ictx->branch_hint_set = false; sema->ictx->branch_hint = 0; } return; } // Analyze then branch; `true_instructions` list from Sema. bool parent_hint_set = sema->ictx->branch_hint_set; uint32_t parent_hint = sema->ictx->branch_hint; U32List then_block = { 0 }; sema->ictx->branch_hint_set = false; sema->ictx->branch_hint = 0; // zirCondbr's one sub_block serves both branches. SemaBlock cb_blk; semaBlockPush(sema->ictx, &cb_blk, true, UINT32_MAX, UINT32_MAX); analyzeBodyInner(sema, &then_block, then_body, then_body_len); addRuntimeBreak(sema->ip, sema->zir, sema->ictx, sema->insts, &then_block, sema->inst_values, sema->inst_types); uint32_t true_hint = sema->ictx->branch_hint_set ? sema->ictx->branch_hint : 0u; // Sema.zirCondbr `err_cond`: when the condition is an `is_non_err` // ZIR instruction (i.e. a `catch`/`try` on an error union), Sema // prepends an `unwrap_errunion_err` to the else (error) branch so // the extracted error is available (for capture / error-return // trace). It is emitted after the then body and before the else // body, even when unused. U32List else_block = { 0 }; bool err_unwrap_panic = false; if (cond_ref >= ZIR_REF_START_INDEX) { uint32_t cond_zir = cond_ref - ZIR_REF_START_INDEX; if (sema->zir->inst_tags[cond_zir] == ZIR_INST_IS_NON_ERR) { uint32_t ec_op_ref = sema->zir->inst_datas[cond_zir].un_node.operand; uint32_t ec_op_val = semaResolveRef(ec_op_ref, sema->inst_values); uint32_t ec_op_ty = semaResolveRefType(ec_op_ref, sema->inst_types); if (!ztIsZigTypeTag(sema->ip, ec_op_ty, ZIG_TYPE_TAG_ERROR_UNION)) UNIMPLEMENTED("condbr err_cond: operand not an error union"); AirInstData ec_data; memset(&ec_data, 0, sizeof(ec_data)); // unwrap_errunion_err result type = the union's error set. ec_data.pair.a = ipErrorUnionErrorSet(sema->ip, ec_op_ty); ec_data.pair.b = ec_op_val; uint32_t ec_air_idx = semaAirInstAppend( sema->insts, AIR_INST_UNWRAP_ERRUNION_ERR, ec_data); u32ListAppend(&else_block, ec_air_idx); err_unwrap_panic = semaBodyIsMaybeErrorUnwrapPanic( sema->zir, else_body, else_body_len); } } // Analyze else branch (appended after any err_cond). sema->ictx->branch_hint_set = false; sema->ictx->branch_hint = 0; analyzeBodyInner(sema, &else_block, else_body, else_body_len); addRuntimeBreak(sema->ip, sema->zir, sema->ictx, sema->insts, &else_block, sema->inst_values, sema->inst_types); semaBlockPop(sema->ictx, &cb_blk); uint32_t false_hint = err_unwrap_panic ? BRANCH_HINT_UNLIKELY : (sema->ictx->branch_hint_set ? sema->ictx->branch_hint : 0u); sema->ictx->branch_hint_set = parent_hint_set; sema->ictx->branch_hint = parent_hint; // Build Air.CondBr extra (mirroring Sema.zirCondbr): write struct // then then-body then else-body. Base branch_hints = 192 (u32 // bits): then_cov=poi(bit6), else_cov=poi(bit7). BranchHint is // enum(u3), true in bits 0..2 and false in bits 3..5. // The condbr_extra_idx becomes cond_br.pl_op.payload. uint32_t condbr_extra_idx = sema->air_extra->len; u32ListAppend(sema->air_extra, then_block.len); u32ListAppend(sema->air_extra, else_block.len); u32ListAppend(sema->air_extra, AIR_COND_BR_HINTS(true_hint, false_hint)); for (uint32_t ti = 0; ti < then_block.len; ti++) u32ListAppend(sema->air_extra, then_block.items[ti]); for (uint32_t ei = 0; ei < else_block.len; ei++) u32ListAppend(sema->air_extra, else_block.items[ei]); free(then_block.items); free(else_block.items); // Emit AIR cond_br{pl_op: operand=cond, payload=condbr_extra_idx}. AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = cond_val; // pl_op.operand data.pair.b = condbr_extra_idx; // pl_op.payload uint32_t air_idx = semaAirInstAppend(sema->insts, AIR_INST_COND_BR, data); u32ListAppend(block, air_idx); // cond_br is noreturn; terminate this branch. return; } // Sema.zig:zirTry // Sema.zirTry: `try operand`. Analyze the error-handling body (the `else |e| // return e` path, noreturn) into a sub-block, then emit AIR `try` (pl_op { // operand = err_union, payload = Air.Try{ body_len } + sub-block insts }). The // try's result is the unwrapped payload. Data: pl_node -> Zir.Inst.Try { // operand, body_len }, then the body. // Returns whether the body ends here (noreturn or a comptime break). static bool zirTry(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t tpi = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstTry extra; uint32_t extra_end = zirExtraData(sema->zir, tpi, &extra); uint32_t operand_ref = extra.operand; uint32_t tbl = extra.body_len; const uint32_t* tbody = zirBodySlice(sema->zir, extra_end); uint32_t eu_val = semaResolveRef(operand_ref, sema->inst_values); if (!semaIsAirInstRef(eu_val)) { // Sema.zirTry's comptime arm: "if (try // sema.resolveIsNonErrVal(parent_block, operand_src, // err_union)) |is_non_err_val| { ... if // (is_non_err_val.toBool()) { return // sema.analyzeErrUnionPayload(parent_block, src, // err_union_ty, err_union, operand_src, false); } ... }". // `Sema.analyzeErrUnionPayload` on a comptime operand is // "return Air.internedToRef(zcu.intern_pool // .indexToKey(val.toIntern()).error_union.val.payload)" -- // the interned payload, with no AIR emitted and the error // body never analyzed. `error_union_payload`'s two words are // exactly {eu_ty, payload}. // An error value: ctEvalBody folds `return error.X` from a // comptime call to a bare error, as its own `try` accepts. if (ipItemTagIs(sema->ip, eu_val, IP_TAG_ERROR_UNION_ERROR) || ipItemTagIs(sema->ip, eu_val, IP_TAG_ERROR_SET_ERROR)) { // "We can analyze the body directly in the parent block // because we know there are no breaks from the body // possible, and that the body is noreturn." -- the // `.unreachable_value` result ends this body too. analyzeBodyInner(sema, block, tbody, tbl); return true; } if (eu_val >= sema->ip->items_len || sema->ip->item_tags[eu_val] != IP_TAG_ERROR_UNION_PAYLOAD) UNIMPLEMENTED("try: comptime error union operand"); uint32_t ct_eu_d = sema->ip->item_datas[eu_val]; sema->inst_values[inst] = sema->ip->extra[ct_eu_d + 1]; sema->inst_types[inst] = ipErrorUnionPayload(sema->ip, sema->ip->extra[ct_eu_d]); return false; } uint32_t eu_ty = semaResolveRefType(operand_ref, sema->inst_types); if (!ztIsZigTypeTag(sema->ip, eu_ty, ZIG_TYPE_TAG_ERROR_UNION)) UNIMPLEMENTED("try: operand not an error union"); uint32_t payload_ty = ipErrorUnionPayload(sema->ip, eu_ty); // "if (try sema.resolveIsNonErrVal(parent_block, operand_src, // err_union)) |is_non_err_val| { ... if (is_non_err_val.toBool()) // { return sema.analyzeErrUnionPayload(...); }" -- for a runtime // operand the answer comes from the type alone // (resolveIsNonErrFromType): an empty error set means the error // body is never analyzed, so `return try e` in a function that // returns `u32` is valid. if (resolveIsNonErrFromType(sema->ip, eu_ty, sema->fn_ret_ty) == ZIR_REF_BOOL_TRUE) { sema->inst_values[inst] = payload_ty == ZIR_REF_VOID_TYPE ? ZIR_REF_VOID_VALUE : semaEmitTyOp(sema->insts, block, AIR_INST_UNWRAP_ERRUNION_PAYLOAD, payload_ty, eu_val); sema->inst_types[inst] = payload_ty; return false; } // Analyze the noreturn error body into a fresh sub-block; its AIR // instructions become the try's error-handling body (in `extra`), // not part of the enclosing block. U32List try_sub = { 0 }; analyzeBodyInner(sema, &try_sub, tbody, tbl); uint32_t payload_idx = sema->air_extra->len; u32ListAppend(sema->air_extra, try_sub.len); // Air.Try.body_len for (uint32_t k = 0; k < try_sub.len; k++) u32ListAppend(sema->air_extra, try_sub.items[k]); free(try_sub.items); AirInstData tdata; memset(&tdata, 0, sizeof(tdata)); tdata.pair.a = eu_val; // pl_op.operand = err_union tdata.pair.b = payload_idx; // pl_op.payload = Air.Try index uint32_t try_air = semaAirInstAppend(sema->insts, AIR_INST_TRY, tdata); u32ListAppend(block, try_air); sema->inst_values[inst] = AIR_REF_INST_BASE + try_air; sema->inst_types[inst] = payload_ty; return false; } // Sema.zig:zirTryPtr // Sema.zirTryPtr: by-ref `try operand` where operand is a pointer to an error // union. The error body is identical to ZIR_INST_TRY, but AIR `try_ptr` // returns a pointer to the payload rather than loading the payload value. Data // layout matches Zir.Inst.Try: // { operand, body_len } followed by the noreturn error body. // Returns whether the body ends here (noreturn or a comptime break). static bool zirTryPtr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t tpp_pi = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstTry extra; uint32_t extra_end = zirExtraData(sema->zir, tpp_pi, &extra); uint32_t tpp_operand_ref = extra.operand; uint32_t tpp_bl = extra.body_len; const uint32_t* tpp_body = zirBodySlice(sema->zir, extra_end); uint32_t tpp_ptr_val = semaResolveRef(tpp_operand_ref, sema->inst_values); if (!semaIsAirInstRef(tpp_ptr_val)) { // Sema.zirTryPtr's comptime arm: `analyzeLoad` of the // pointer, then on a payload `"analyzeErrUnionPayloadPtr( // parent_block, src, operand, false, false)"`, on an error // the noreturn body in the parent block. uint32_t tpp_eu = (tpp_ptr_val == INST_VALUE_UNSET) ? IP_INDEX_NONE : pointerDeref(sema->zcu, tpp_ptr_val, NULL); if (ipItemTagIs(sema->ip, tpp_eu, IP_TAG_ERROR_UNION_ERROR)) { analyzeBodyInner(sema, block, tpp_body, tpp_bl); return true; } uint32_t tpp_pl = IP_INDEX_NONE; if (!ipItemTagIs(sema->ip, tpp_eu, IP_TAG_ERROR_UNION_PAYLOAD) || !semaErrUnionPayloadPtrComptime( sema->zcu, sema->ip, tpp_ptr_val, &tpp_pl) || tpp_pl == IP_INDEX_NONE) UNIMPLEMENTED("try_ptr: comptime error union pointer"); sema->inst_values[inst] = tpp_pl; sema->inst_types[inst] = ipTypeOf(sema->ip, tpp_pl); return false; } uint32_t tpp_ptr_ty = semaResolveRefType(tpp_operand_ref, sema->inst_types); if (sema->ip->item_tags[tpp_ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("try_ptr: operand not a pointer"); uint32_t tpp_eu_ty = sema->ip->extra[sema->ip->item_datas[tpp_ptr_ty]]; if (!ztIsZigTypeTag(sema->ip, tpp_eu_ty, ZIG_TYPE_TAG_ERROR_UNION)) UNIMPLEMENTED("try_ptr: pointee not an error union"); uint32_t tpp_payload_ty = ipErrorUnionPayload(sema->ip, tpp_eu_ty); uint32_t tpp_ptr_flags = sema->ip->extra[sema->ip->item_datas[tpp_ptr_ty] + 2]; uint32_t tpp_ptr_sentinel = sema->ip->extra[sema->ip->item_datas[tpp_ptr_ty] + 1]; uint32_t tpp_ptr_packed_offset = sema->ip->extra[sema->ip->item_datas[tpp_ptr_ty] + 3]; uint32_t tpp_res_ty = ipGetPtrTypeFull(sema->ip, tpp_payload_ty, tpp_ptr_flags, tpp_ptr_sentinel, tpp_ptr_packed_offset); U32List tpp_sub = { 0 }; analyzeBodyInner(sema, &tpp_sub, tpp_body, tpp_bl); uint32_t tpp_payload = sema->air_extra->len; u32ListAppend(sema->air_extra, tpp_ptr_val); u32ListAppend(sema->air_extra, tpp_sub.len); for (uint32_t k = 0; k < tpp_sub.len; k++) u32ListAppend(sema->air_extra, tpp_sub.items[k]); free(tpp_sub.items); AirInstData tpp_data; memset(&tpp_data, 0, sizeof(tpp_data)); tpp_data.pair.a = tpp_res_ty; // ty_pl.ty = *payload tpp_data.pair.b = tpp_payload; // ty_pl.payload = Air.TryPtr uint32_t tpp_air = semaAirInstAppend(sema->insts, AIR_INST_TRY_PTR, tpp_data); u32ListAppend(block, tpp_air); sema->inst_values[inst] = AIR_REF_INST_BASE + tpp_air; sema->inst_types[inst] = tpp_res_ty; return false; } // Sema.zig:zirUnreachable // Sema.zirUnreachable -> analyzeUnreachable: with wantSafety()==false // (ReleaseSmall — our mode), the safety panic is stripped and it's just // `block.addNoOp(.unreach)`, a plain terminator. Reaching it // at runtime is undefined behavior for a valid program (the interpreter aborts // loudly if it ever does — see interp.c AIR_INST_UNREACH). static void zirUnreachable(SemaCtx* sema, U32List* block) { semaEmitTyOp(sema->insts, block, AIR_INST_UNREACH, 0, 0); } // Sema.zig:analyzeRet // Coerce the operand to the function return type, then emit an AIR `ret` (or // the inline call's break). A preinterned operand (a ZIR ref below // ZIR_REF_START_INDEX) is, after coercion, also its AIR Ref. static void analyzeRet( SemaCtx* sema, U32List* block, uint32_t val, uint32_t val_ty) { uint32_t coerced; if (semaIsAirInstRef(val)) { // Sema.analyzeRet: "const operand = try // sema.coerceExtra(block, sema.fn_ret_ty, uncasted_operand, // src, ...)" -- the one runtime coercion, which declines what // it does not model. coerced = semaCoerceExtraRuntime(sema->zcu, val, val_ty, sema->fn_ret_ty, sema->insts, block, sema->air_extra, "ret: runtime operand coercion"); } else if (val == ZIR_REF_NULL_VALUE && ztIsZigTypeTag(sema->ip, sema->fn_ret_ty, ZIG_TYPE_TAG_OPTIONAL)) { // `return null` into `?T`: Sema.coerce lifts the comptime // `null` literal to the optional-none value of the return // type. Intern that `(?T) null` value; `ret` returns it // directly (its IP index is its own AIR Ref). coerced = ipGetOptNull(sema->ip, sema->fn_ret_ty); } else { // A plain interned comptime value (e.g. a comptime array // element read via elem_ptr_load). Its type is known from // inst_types (or the preinterned operand). When it already // matches the return type the coercion is the identity; else // fall back to coerceExtra. if (val_ty == sema->fn_ret_ty) coerced = val; else coerced = semaCoerceComptime( sema->zcu, sema->ip, val, sema->fn_ret_ty); } if (sema->ictx->inline_ret_active) { semaAnalyzeRetInlining( sema->ictx, sema->insts, block, coerced, sema->fn_ret_ty); return; // noreturn in this body } AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = coerced; // un_op uint32_t air_idx = semaAirInstAppend(sema->insts, AIR_INST_RET, data); u32ListAppend(block, air_idx); } // Sema.zig:zirRetErrValue // Sema.zirRetErrValue: `return error.Name`. Intern the error name, form the // single-name error set type `error{Name}` (dedups to the fn's declared set), // intern the error value (Key.err -> error_set_error), then analyzeRet coerces // it into the fn return type. For a bare error set this is a re-typed // error_set_error; for an error union this is an error_union_error. Either // value is returned directly (its IP index is its own AIR Ref). ZIR data: // str_tok {src_tok}; `start` is the IP-string byte offset. static void zirRetErrValue(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t name_off = sema->zir->inst_datas[inst].str_tok.start; const char* name = zirNullTerminatedString(sema->zir, name_off); uint32_t name_str = ipGetOrPutString(sema->ip, name, (uint32_t)strlen(name)); // singleErrorSetType(name) + intern `.err` in that single set // (Sema.zirRetErrValue result_inst). uint32_t single_set = ipGetErrorSetType(sema->ip, &name_str, 1); ipGetErrorSetError(sema->ip, single_set, name_str); uint32_t ret_val; if (ipIsErrorSetType(sema->ip, sema->fn_ret_ty)) { if (!ipErrorSetHasName( sema->ip, sema->fn_ret_ty, name_str, IP_IES_NAME_ASSUMED)) UNIMPLEMENTED("ret_err_value: error not in fn error set"); addToInferredErrorSet(sema->ip, sema->fn_ret_ty, single_set); ret_val = ipGetErrorSetError(sema->ip, sema->fn_ret_ty, name_str); } else if (ztIsZigTypeTag( sema->ip, sema->fn_ret_ty, ZIG_TYPE_TAG_ERROR_UNION)) { // analyzeRet -> coerceExtra -> wrapErrorUnionSet: first // coerce the error value into the union's error set (interns a // `.err` in that set, deduping to the single-set value when // they are the same set), then wrap into the union. uint32_t fn_err_set = ipErrorUnionErrorSet(sema->ip, sema->fn_ret_ty); if (!ipErrorSetHasName( sema->ip, fn_err_set, name_str, IP_IES_NAME_ASSUMED)) UNIMPLEMENTED("ret_err_value: error not in fn error set"); addToInferredErrorSet(sema->ip, fn_err_set, single_set); ipGetErrorSetError(sema->ip, fn_err_set, name_str); ret_val = ipGetErrorUnionError(sema->ip, sema->fn_ret_ty, name_str); } else { UNIMPLEMENTED("ret_err_value: fn ret type not an error set"); } analyzeRet(sema, block, ret_val, sema->fn_ret_ty); } // Sema.zig:zirRetImplicit // The naked-fn and non-void return type arms reject invalid input only. static void zirRetImplicit(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t op_ref = sema->zir->inst_datas[inst].un_tok.operand; analyzeRet(sema, block, semaResolveRef(op_ref, sema->inst_values), semaResolveRefType(op_ref, sema->inst_types)); } // Sema.zig:zirRetNode static void zirRetNode(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t op_ref = sema->zir->inst_datas[inst].un_node.operand; analyzeRet(sema, block, semaResolveRef(op_ref, sema->inst_values), semaResolveRefType(op_ref, sema->inst_types)); } // Sema.zig:zirRetLoad // Sema.zirRetLoad: in non-comptime, non-inlining, non-naked fns emits AIR // `ret_load(ptr)` — a single instruction that loads from ptr and returns the // value (no separate load + ret). ZIR data: // un_node with operand = ptr_ref (alloc ptr or ret_ptr). static void zirRetLoad(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t rl_ptr_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t rl_ptr_val = semaResolveRef(rl_ptr_ref, sema->inst_values); if (!semaIsAirInstRef(rl_ptr_val)) UNIMPLEMENTED("ret_load: comptime ptr"); AirInstData data; memset(&data, 0, sizeof(data)); if (sema->ictx->inline_ret_active) { // `"if (block.semaBlockIsComptime() or block.inlining != null) { // const operand = try sema.analyzeLoad(...); return // sema.analyzeRet(block, operand, src, .none); }"`. data.pair.a = sema->fn_ret_ty; data.pair.b = rl_ptr_val; uint32_t ld_idx = semaAirInstAppend(sema->insts, AIR_INST_LOAD, data); u32ListAppend(block, ld_idx); semaAnalyzeRetInlining(sema->ictx, sema->insts, block, AIR_REF_INST_BASE + ld_idx, sema->fn_ret_ty); return; } data.pair.a = rl_ptr_val; // un_op uint32_t air_idx = semaAirInstAppend(sema->insts, AIR_INST_RET_LOAD, data); u32ListAppend(block, air_idx); // ret_load is noreturn; terminate this branch. return; } // Sema.zig:zirPtrType // Sema.zirPtrType: build a pointer/slice type. ZIR data: // ptr_type { payload_index, flags, size }. Extra[payload_index] = elem_type // ref; size: One=0, Many=1, Slice=2, C=3. `[*c]T` packs its size bits like any // other and needs no slice wrapper, and ipGetPtrTypeFull applies // Zcu.PerThread.ptrType's `.c` => is_allowzero canonicalization — the same // treatment ctResolveParamTypeRef's ptr_type arm already gives it. flags: bit0 // allowzero, bit1 mutable(!const), bit2 volatile, bit3 has_sentinel, bit4 // has_align, bit5 has_addrspace, bit6 has_bit_range. This handler mirrors the // ctResolveParamTypeRef ptr_type porting for runtime-body type expressions. static void zirPtrType(SemaCtx* sema, uint32_t inst) { ZirInstPtrType extra; uint32_t extra_end = zirExtraData( sema->zir, sema->zir->inst_datas[inst].ptr_type.payload_index, &extra); uint8_t pt_flags = sema->zir->inst_datas[inst].ptr_type.flags; uint8_t pt_size = sema->zir->inst_datas[inst].ptr_type.size; uint32_t pt_sentinel = IP_INDEX_NONE; if (pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_SENTINEL) { // `[:0]const u8` / `[*:0]const u8`: the sentinel ref is the first // trailing word. uint32_t pt_sref = sema->zir->extra[extra_end]; pt_sentinel = pt_sref; if (pt_sref >= ZIR_REF_START_INDEX) { // `"const coerced = try sema.coerce(block, elem_ty, // sema.resolveInst(ref), sentinel_src); const val = try // sema.resolveConstDefinedValue(...)"`. uint32_t pt_sv = semaResolveRef(pt_sref, sema->inst_values); uint32_t pt_ev = semaResolveRef(extra.elem_type, sema->inst_values); if (semaIsAirInstRef(pt_sv)) COMPILE_ERROR_UPSTREAM( "ptr_type: sentinel not comptime-known"); if (semaIsAirInstRef(pt_ev)) UNIMPLEMENTED("ptr_type: runtime elem type"); pt_sentinel = semaSentinelValue(sema->zcu, sema->ip, pt_sv, pt_ev); } else { // `"try checkSentinelType(sema, block, sentinel_src, // elem_ty);"` for a preinterned sentinel (`[*:null]const // ?S`) too. uint32_t pt_sent_elem = semaResolveRef(extra.elem_type, sema->inst_values); if (!semaIsAirInstRef(pt_sent_elem)) checkSentinelType(sema->ip, pt_sent_elem); } } uint32_t pt_elem_ref = extra.elem_type; uint32_t pt_elem_ty = semaResolveRef(pt_elem_ref, sema->inst_values); if (semaIsAirInstRef(pt_elem_ty)) UNIMPLEMENTED("ptr_type: runtime elem type"); uint32_t pt_ip_flags = semaPtrTypeFlags(sema->ip, pt_flags, pt_size, pt_elem_ty); if (pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_ALIGN) { // `align(N)` with a comptime align expression // (`[*]align(a.toByteUnits()) T` in Allocator.allocAdvanced- // WithRetAddr — `a` is a comptime Alignment): pack log2(N) // into flags bits 2..7 and intern the `@as(u29, N)` // side-effect int, mirroring ctResolveParamTypeRef's align arm // (u29 is static IP index 6). A runtime align expression // aborts loudly. uint32_t pta_ref = sema->zir->extra[extra_end + ((pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_SENTINEL) ? 1 : 0)]; uint32_t pta_v = semaResolveRef(pta_ref, sema->inst_values); uint64_t pta_n; if (semaIsAirInstRef(pta_v)) UNIMPLEMENTED("ptr_type: runtime align expression"); if (pta_v != INST_VALUE_UNSET) pta_n = ipReadIntValue(sema->ip, pta_v); else UNIMPLEMENTED("ptr_type: sentinel align expression"); if (pta_n == 0 || pta_n == UINT64_MAX || (pta_n & (pta_n - 1)) != 0) UNIMPLEMENTED("ptr_type: align not a power of two"); ipGetIntSmall(sema->ip, 6u, (uint32_t)pta_n); uint32_t pta_log2 = 0; while ((pta_n >> pta_log2) > 1) pta_log2++; pt_ip_flags = IP_PTR_FLAGS_WITH_ALIGN(pt_ip_flags, pta_log2); } if (pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_ADDRSPACE) { // `resolveAddressSpace(block, addrspace_src, ref, .pointer)`. uint32_t ptas_v = semaResolveRef( sema->zir->extra[extra_end + !!(pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_SENTINEL) + !!(pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_ALIGN)], sema->inst_values); if (semaIsAirInstRef(ptas_v)) COMPILE_ERROR_UPSTREAM("ptr_type: addrspace not comptime-known"); resolveAddressSpace(sema->zcu, ptas_v); } uint32_t pt_packed = 0; if (pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_BIT_RANGE) { uint32_t ptb_i = extra_end + !!(pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_SENTINEL) + !!(pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_ALIGN) + !!(pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_ADDRSPACE); uint32_t ptb_off = semaResolveRef(sema->zir->extra[ptb_i], sema->inst_values); uint32_t ptb_host = semaResolveRef(sema->zir->extra[ptb_i + 1], sema->inst_values); if (semaIsAirInstRef(ptb_off) || semaIsAirInstRef(ptb_host)) COMPILE_ERROR_UPSTREAM("ptr_type: bit range not comptime-known"); // `"if (host_size != 0) try sema.ensureLayoutResolved(elem_ty, // elem_ty_src, .bit_ptr_child);"` if (ipReadIntValue(sema->ip, ptb_host) != 0) ensureLayoutResolved(sema->zcu, pt_elem_ty); pt_packed = semaPtrTypeBitRange(sema->zcu, pt_elem_ty, ipReadIntValue(sema->ip, ptb_off), ipReadIntValue(sema->ip, ptb_host)); } sema->inst_values[inst] = semaPtrTypeIntern( sema->ip, pt_elem_ty, pt_ip_flags, pt_size, pt_sentinel, pt_packed); // type is its own IP value; inst_types unused for type-values. } // Sema.zig:zirStructInitEmpty // Sema.zirStructInitEmpty: typed `T{}`. Structs materialize the default-filled // aggregate; `.array, .vector` share `arrayInitEmpty`, which accepts only a // zero length (`[0]u8{}`, `@Vector(0, i32){}`). static void zirStructInitEmpty(SemaCtx* sema, uint32_t inst) { uint32_t se_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t se_ty = semaResolveRef(se_ref, sema->inst_values); if (!semaRefIsInterned(se_ty)) UNIMPLEMENTED("struct_init_empty: operand not a type"); // `"try sema.ensureLayoutResolved(obj_ty, ty_src, .init);"` if (!ensureLayoutResolved(sema->zcu, se_ty)) UNIMPLEMENTED("struct_init_empty: layout unresolved"); if (ipTagIsAnyStruct(sema->ip->item_tags[se_ty])) { sema->inst_values[inst] = semaStructInitEmptyAggregate(sema->zcu, se_ty); sema->inst_types[inst] = se_ty; return; } uint32_t se_empty = arrayInitEmpty(sema->zcu, se_ty); if (se_empty == IP_INDEX_NONE) UNIMPLEMENTED("struct_init_empty: non-struct/sequence type"); sema->inst_values[inst] = se_empty; sema->inst_types[inst] = se_ty; } // (part of) Sema.zig:zirStructInitEmptyResult // Sema.zirStructInitEmptyResult(is_byref=true), the runtime analog of the // ctEvalBody handler (sema_comptime.c ZIR_INST_STRUCT_INIT_EMPTY_REF_RESULT): // `&.{}` with either a slice result type or a ONE-pointer-to-STRUCT result // type. The slice result is a `*const [0:s]child` empty-array UAV whose // address coerces to the empty slice. The one-pointer result is a `*const // child` UAV of the child struct's default-filled empty aggregate. The result // type may be error-union-wrapped; Sema uses optEuBaseType before selecting // the initializer type, so mirror that first. Pure comptime value; no AIR. // Other pointer sizes / child kinds abort loudly. static void semaStructInitEmptyRefResult(SemaCtx* sema, uint32_t inst) { uint32_t serr_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t serr_val = semaResolveRef(serr_ref, sema->inst_values); if (!semaRefIsInterned(serr_val)) UNIMPLEMENTED("struct_init_empty_ref_result: operand not a type"); // "Generic poison means this is an untyped anonymous empty // struct/array init": `uavRef(.empty_tuple)`, as for a // `*anyopaque` result below. uint32_t serr_init_ty = ZIR_REF_EMPTY_TUPLE_TYPE; uint32_t serr_agg = ZIR_REF_EMPTY_TUPLE; if (serr_val != ZIR_REF_GENERIC_POISON_TYPE) { uint32_t serr_ptr_ty = ipOptEuBaseType(sema->ip, serr_val); // A slice type's data is its many-pointer type. if (ztIsSlice(sema->ip, serr_ptr_ty)) serr_ptr_ty = sema->ip->item_datas[serr_ptr_ty]; IpPtrInfo serr_pi; if (ipPtrInfo(sema->ip, serr_ptr_ty, &serr_pi) != IP_PTRI_OK) UNIMPLEMENTED("struct_init_empty_ref_result: non-pointer type"); // `.slice, .many`: a zero-length array keeping the sentinel; // `.one, .c`: the child, unless it is `anyopaque`. if (serr_pi.size == IP_PTR_SIZE_SLICE || serr_pi.size == IP_PTR_SIZE_MANY) serr_init_ty = ipGetArrayType(sema->ip, 0, serr_pi.child, serr_pi.sentinel); else if (serr_pi.child != ZIR_REF_ANYOPAQUE_TYPE) serr_init_ty = serr_pi.child; if (serr_init_ty != ZIR_REF_EMPTY_TUPLE_TYPE) { uint32_t serr_obj = ipOptEuBaseType(sema->ip, serr_init_ty); if (serr_obj != serr_init_ty) UNIMPLEMENTED("struct_init_empty_ref_result: " "optional/error-union init type"); if (ipTagIsAnyStruct(sema->ip->item_tags[serr_obj])) { serr_agg = semaStructInitEmptyAggregate(sema->zcu, serr_obj); } else { serr_agg = semaArrayInitEmptyAggregate(sema->ip, serr_obj, "struct_init_empty_ref_result: expected array " "elements, found 0"); if (serr_agg == IP_INDEX_NONE) UNIMPLEMENTED("struct_init_empty_ref_result: init type is " "neither struct nor array"); } } } // uavRef: a `*const T` to the interned value. uint32_t serr_pty = ipGetPtrType(sema->ip, serr_init_ty, IP_PTR_FLAGS_CONST_ONE); sema->inst_values[inst] = ipGetPtrUav(sema->ip, serr_pty, serr_agg); sema->inst_types[inst] = serr_pty; } // Sema.zig:zirStructInitEmptyResult // Runtime analog of ctEvalBody's STRUCT_INIT_EMPTY_RESULT (sema_comptime.c): // `.{}` with a result type (`var map: Self = .{};` in HashMapUnmanaged.grow, // or `[0]void` in Compilation's disabled failed_win32_resources map). Struct // fields are filled from their declared defaults; zero-length arrays/vectors // become their only possible aggregate. Optional/error-union shells are peeled // to find the init type, then the comptime-known value is coerced back to the // original result type. ZIR un_node -> operand = the result type. Nonzero // arrays/vectors and unsupported result types abort loudly. static void zirStructInitEmptyResult( SemaCtx* sema, uint32_t inst, bool is_byref) { if (is_byref) { semaStructInitEmptyRefResult(sema, inst); return; } uint32_t sier_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t sier_val = semaResolveRef(sier_ref, sema->inst_values); if (!semaRefIsInterned(sier_val)) UNIMPLEMENTED("struct_init_empty_result: operand not a type"); uint32_t sier_ty = sier_val; if (sier_ty == IP_INDEX_GENERIC_POISON_TYPE) { // Sema.zirStructInitEmptyResult: "Generic poison means this // is an untyped anonymous empty struct/array init" -- // resolveTypeOrPoison returns null and the is_byref=false // path returns `.empty_tuple` (the `.{}` argument bound to // an `anytype` parameter, e.g. `fatal("...", .{})` in // src/main.zig's main). Pure comptime value; no AIR. sema->inst_values[inst] = ZIR_REF_EMPTY_TUPLE; sema->inst_types[inst] = ZIR_REF_EMPTY_TUPLE_TYPE; return; } sema->inst_values[inst] = semaCoerceComptime(sema->zcu, sema->ip, semaStructInitEmptyResultValue(sema->zcu, sier_ty), sier_ty); sema->inst_types[inst] = sier_ty; } // Sema.zig:zirUnionInit // Sema.zirUnionInit: payload is Zir.Inst.UnionInit { union_type, field_name, // init }. Upstream anchors in src/Sema.zig: `const union_ty = try // sema.resolveType(...)`, `const field_index = try sema.unionFieldIndex(...)`, // and `return block.addUnionInit(union_ty, field_index, payload)`. Narrow // real-path slice: direct @unionInit following the FIELD_TYPE_REF just above, // with a small tagged union and a PTR_UAV field-name string. static void zirUnionInit(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstUnionInit extra; zirExtraData(sema->zir, pp, &extra); uint32_t union_ref = extra.union_type; uint32_t name_ref = extra.field_name; uint32_t init_ref = extra.init; uint32_t union_ty = semaResolveRef(union_ref, sema->inst_values); uint32_t name_val = semaResolveRef(name_ref, sema->inst_values); uint32_t init_val = semaResolveRef(init_ref, sema->inst_values); if (semaIsAirInstRef(union_ty)) UNIMPLEMENTED("union_init: runtime union type"); if (semaIsAirInstRef(name_val)) UNIMPLEMENTED("union_init: runtime field name"); if (!ipIndexIsType(sema->ip, union_ty)) COMPILE_ERROR_UPSTREAM("union_init: container not a type"); if (ztZigTypeTag(sema->ip, union_ty) != ZIG_TYPE_TAG_UNION) COMPILE_ERROR_UPSTREAM("union_init: container not a union"); uint32_t field_name = semaFieldNameValueToIpString( sema->ip, name_val, "union_init: name not a string"); uint32_t ufidx = ipUnionFieldIndex(sema->ip, union_ty, field_name); if (ufidx == UINT32_MAX) COMPILE_ERROR_UPSTREAM("union_init: union field missing"); semaResolveAllUnionDeferred(sema->zcu, union_ty); uint32_t field_ty = semaResolveUnionFieldTypeLazy(sema->zcu, union_ty, ufidx); if (field_ty == IP_INDEX_NONE) UNIMPLEMENTED("union_init: unresolved union field"); ZtTypeClass field_class = ztTypeClass(sema->ip, field_ty); if (field_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED("union_init: unknown field type class"); if (field_class == ZT_CLASS_NPV) COMPILE_ERROR_UPSTREAM("union_init: no possible field value"); if (ipUnionIsPacked(sema->ip, union_ty)) { // `"return sema.bitCastUnchecked(block, union_ty, payload);"` of the // payload coerced to the field type. uint32_t payload = semaIsAirInstRef(init_val) ? semaCoerceExtraRuntime(sema->zcu, init_val, semaResolveRefType(init_ref, sema->inst_types), field_ty, sema->insts, block, sema->air_extra, "union_init: runtime payload needs coercion") : semaCoerceComptime(sema->zcu, sema->ip, init_val, field_ty); sema->inst_values[inst] = bitCastUnchecked(sema, block, union_ty, payload); sema->inst_types[inst] = union_ty; return; } if (semaIsAirInstRef(init_val)) { ZtTypeClass union_class = ztTypeClass(sema->ip, union_ty); if (union_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED("union_init: unknown union type class"); if (union_class != ZT_CLASS_RUNTIME && union_class != ZT_CLASS_PARTIAL) UNIMPLEMENTED("union_init: comptime-only union"); uint32_t init_ty = semaResolveRefType(init_ref, sema->inst_types); uint32_t init_air = semaCoerceExtraRuntime(sema->zcu, init_val, init_ty, field_ty, sema->insts, block, sema->air_extra, "union_init: runtime payload needs coercion"); sema->inst_values[inst] = semaEmitAirUnionInit( sema->insts, block, sema->air_extra, union_ty, ufidx, init_air); sema->inst_types[inst] = union_ty; return; } // `"const tag_ty = union_ty.unionTagTypeHypothetical(zcu);"`. uint32_t tag_enum = ztUnionTagTypeHypothetical(sema->zcu, union_ty); uint32_t tag_val = ipEnumTagFromFieldName(sema->ip, tag_enum, field_name); // R-105 remediation: an already-materialized enum_tag payload // (typically an `@enumFromInt` result reached through ZIR's // result-location `as_node` wrapper, not directly as this // instruction's init operand -- so the ENUM_FROM_INT check in the // repair arm below does not fire for it) whose int does not fit // the selected nonexhaustive field's backing type is a compile // error upstream: Sema.zirEnumFromInt's non-exhaustive arm calls // `int_val.intFitsInType(int_tag_ty, null, zcu)` and fails with // `"int value '{f}' out of range of non-exhaustive enum '{f}'"` // (src/Sema.zig) when it does not fit. Checked here, before // either branch below re-interns the int at the destination // width, because re-interning (coerceExtra's fixed-width path, // shared with the repair arm) silently wraps an out-of-range // magnitude instead of rejecting it (`@unionInit(U, tag, // @enumFromInt(42))` into a `u4` non-exhaustive field would // become 42 & 15). Exhaustive enum // fields are unaffected (a differently-typed payload there is // already rejected by the mismatch arm below); in-range payloads // of any backing width are unaffected (checked, not narrowed). // // A signed-backing out-of-range payload (`enum(i4){_}` with // `@enumFromInt(8)`) would wrap to -8 in the general // @enumFromInt re-intern path, but // `coerceExtra`'s comptime_int arm states `coerceExtra`'s // `intFitsInType`, so the operand coercion rejects both signs // before this site is reached. This check is retained for an // already-materialized `enum_tag` payload that arrives without // passing that coercion. if (init_val < sema->ip->items_len && sema->ip->item_tags[init_val] == IP_TAG_ENUM_TAG && ztIsNonexhaustiveEnum(sema->ip, field_ty)) { uint32_t chk_int = ipEnumTagValue(sema->ip, init_val); uint32_t chk_dest_int_ty = ipEnumTypeIntTagType(sema->ip, field_ty); if (!valIntFitsInType(sema->ip, chk_int, chk_dest_int_ty)) COMPILE_ERROR_UPSTREAM("union_init: int value out of " "range of non-exhaustive enum"); } uint32_t coerced; if (init_val < sema->ip->items_len && sema->ip->item_tags[init_val] == IP_TAG_ENUM_TAG && ztIsNonexhaustiveEnum(sema->ip, field_ty) && init_ref >= ZIR_REF_START_INDEX && sema->zir->inst_tags[init_ref - ZIR_REF_START_INDEX] == ZIR_INST_ENUM_FROM_INT) { // Narrow @enumFromInt result-location repair for the real // AnalUnit.unwrap payload. This is intentionally not a general // enum-to-enum coercion: only the source ZIR builtin proves // the integer payload should be wrapped as the selected field // enum type. The range check above already rejected an // out-of-range int_val, so the coercion below only ever // widens/re-types an in-range value. uint32_t int_val = ipEnumTagValue(sema->ip, init_val); uint32_t dest_int_ty = ipEnumTypeIntTagType(sema->ip, field_ty); if (ipComptimeValueType(sema->ip, int_val) != dest_int_ty) int_val = semaCoerceComptime( sema->zcu, sema->ip, int_val, dest_int_ty); coerced = ipGetEnumTag(sema->ip, field_ty, int_val); } else if (init_val < sema->ip->items_len && sema->ip->item_tags[init_val] == IP_TAG_ENUM_TAG && field_ty < sema->ip->items_len && ipIsEnumType(sema->ip, field_ty) && ipEnumTagType(sema->ip, init_val) != field_ty) { COMPILE_ERROR_UPSTREAM("union_init: enum payload type mismatch"); } else { coerced = semaCoerceComptime(sema->zcu, sema->ip, init_val, field_ty); } sema->inst_values[inst] = ipGetUnionValue(sema->ip, union_ty, tag_val, coerced); sema->inst_types[inst] = union_ty; } // Sema.zig:zirStructInit // Sema.zirStructInit: resolve the struct type from the first field's // container, coerce each field init to its field type (in declaration // order). Two paths: // - All comptime: intern `aggregate` value (no AIR). // - Any runtime: emit AIR `aggregate_init` (elements in extra). // ZIR pl_node -> StructInit { abs_node, src_line, src_column, fields_len } // followed by fields_len Items { field_type inst, init Ref }. STRUCT_INIT_REF // (`&.{...}`, e.g. the Allocator vtable literal in // FixedBufferAllocator.allocator) is the same analysis with is_ref=true: a // comptime result is wrapped in a `*const T` uav pointer // (Sema.addConstantMaybeRef -> uavRef); a runtime/union is_ref result is // unmodeled and aborts loudly. static void zirStructInit( SemaCtx* sema, U32List* block, uint32_t inst, bool si_is_ref) { ZirInstStructInit extra; uint32_t items_off = zirExtraData( sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &extra); uint32_t si_fields_len = extra.fields_len; // The struct type is the container of the first field's // struct_init_field_type instruction. ZirInstStructInitItem first_item; zirExtraData(sema->zir, items_off, &first_item); ZirInstFieldType first_field_type; zirExtraData(sema->zir, sema->zir->inst_datas[first_item.field_type].pl_node.payload_index, &first_field_type); uint32_t container_ref = first_field_type.container_type; uint32_t container_val = semaResolveStructInitContainerRef( container_ref, sema->inst_values, sema->ictx); if (semaIsAirInstRef(container_val)) UNIMPLEMENTED("struct_init: runtime container type"); if (container_val == IP_INDEX_GENERIC_POISON_TYPE) { // "The type wasn't actually known, so treat this as an anon // struct init." structInitAnon(sema->zcu, sema->zir, inst, si_fields_len, items_off, true, si_is_ref, sema->type_name_ctx, sema->insts, block, sema->air_extra, sema->inst_values, sema->inst_types); return; } // `"try sema.ensureLayoutResolved(result_ty, src, .init);"` ensureLayoutResolved(sema->zcu, container_val); uint32_t struct_ty = ipOptEuBaseType(sema->ip, container_val); if (ztZigTypeTag(sema->ip, struct_ty) == ZIG_TYPE_TAG_UNION) { // Sema.zirStructInit union path: exactly one field. Compute // the tag value (enum_tag) first, then coerce the init to the // field type, then intern the union_value. Only the comptime // path is handled. if (si_fields_len != 1) UNIMPLEMENTED("struct_init: union expects one field"); uint32_t init_ref = first_item.init; uint32_t init_val = semaResolveRef(init_ref, sema->inst_values); const char* fname = zirNullTerminatedString(sema->zir, first_field_type.name_start); uint32_t fname_ip = ipGetOrPutString(sema->ip, fname, (uint32_t)strlen(fname)); uint32_t ufidx = ipUnionFieldIndex(sema->ip, struct_ty, fname_ip); if (ufidx == UINT32_MAX) UNIMPLEMENTED("struct_init: union field not found"); // tag_val = enumValueFieldIndex(tag_ty, field_index). uint32_t tag_enum = ipUnionEnumTagType(sema->ip, struct_ty); uint32_t tag_val = ipEnumTagFromFieldName(sema->ip, tag_enum, fname_ip); // A decl-bearing union keeps its decl_val-aliased payload // field types `.none` (lazy). struct_init produces the union // Value (its full layout will be demanded) — resolve every // deferred field, then read this one. CValue's `local: // LocalIndex` (decl alias) / `identifier: []const u8` etc. // materialize here. semaResolveAllUnionDeferred(sema->zcu, struct_ty); uint32_t field_ty = semaResolveUnionFieldTypeLazy(sema->zcu, struct_ty, ufidx); if (field_ty == IP_INDEX_NONE) UNIMPLEMENTED("struct_init: union field type unresolved (deferred " "aggregate/slice payload field)"); if (ipUnionIsPacked(sema->ip, struct_ty)) { // `"if (resolved_ty.containerLayout(zcu) == .@\"packed\") // { const union_val = try sema.bitCast(block, // resolved_ty, init_inst, src); ..."`: the coerced init // reinterpreted as the union, no tag (Sema.bitCast's // bitCastUnchecked), then analyzeRef for `&.{...}`. if (struct_ty != container_val) UNIMPLEMENTED("struct_init: packed union result location"); uint32_t pu_init = semaIsAirInstRef(init_val) ? semaCoerceExtraRuntime(sema->zcu, init_val, semaResolveRefType(init_ref, sema->inst_types), field_ty, sema->insts, block, sema->air_extra, "struct_init: packed union init needs coercion") : semaCoerceComptime(sema->zcu, sema->ip, init_val, field_ty); uint32_t pu_val = bitCastUnchecked(sema, block, struct_ty, pu_init); sema->inst_types[inst] = struct_ty; if (!si_is_ref) sema->inst_values[inst] = pu_val; else if (semaIsAirInstRef(pu_val)) sema->inst_values[inst] = semaAnalyzeRefRuntime(sema->ip, sema->insts, block, pu_val, struct_ty, &sema->inst_types[inst]); else { // `sema.analyzeRef` of a comptime value: the `*const T` // uav pointer. sema->inst_types[inst] = ipGetPtrType( sema->ip, struct_ty, IP_PTR_FLAGS_CONST_ONE); sema->inst_values[inst] = ipGetPtrUav(sema->ip, sema->inst_types[inst], pu_val); } return; } if (semaIsAirInstRef(init_val)) { // Runtime union init: emit AIR `union_init`. Mirrors // Sema.zirStructInit union runtime path, where the // uncoerced init is always run through the same generic // `sema.coerce(block, field_ty, uncoerced_init_inst, // field_src)` (src/Sema.zig) before // `block.addUnionInit(resolved_ty, field_index, // init_inst)`. Plain/extern unions follow the // same AIR op; their ABI difference is carried by // ztGetUnionLayout's tag_size=0/payload offset 0 result, // not by a Sema-side tag-usage gate. Share that generic // coercion here via semaCoerceExtraRuntime // (the same helper used by every other runtime store // site: error-set narrowing, array-ptr-to-slice, // pointer-attribute widening, optional wrapping, int // widening/bitcast) rather than re-deriving one kind // (error sets) locally. ty_pl.ty = union type, payload = // UnionInit{ field_index, init } (two words in // air_extra). Type of the runtime init operand comes // from its ZIR ref (inst_types is keyed by ZIR ref // index). uint32_t init_ty = semaResolveRefType(init_ref, sema->inst_types); init_val = semaCoerceExtraRuntime(sema->zcu, init_val, init_ty, field_ty, sema->insts, block, sema->air_extra, "struct_init: runtime union init needs coercion"); if (si_is_ref) { // "const alloc = try block.addTy(.alloc, alloc_ty); // ... unionFieldPtr(..., true); storePtr(...); // return sema.makePtrConst(block, alloc);" -- // unionFieldPtr's initializing `tag:` block sets a // non-trivial hypothetical tag of an `.auto` union. uint32_t sr_alloc_ty = ipGetPtrType(sema->ip, struct_ty, IP_PTR_FLAGS_MUT_ONE); AirInstData sr_alloc; memset(&sr_alloc, 0, sizeof(sr_alloc)); sr_alloc.pair.a = sr_alloc_ty; uint32_t sr_alloc_ref = AIR_REF_INST_BASE + semaAirInstAppend(sema->insts, AIR_INST_ALLOC, sr_alloc); u32ListAppend(block, sr_alloc_ref - AIR_REF_INST_BASE); semaEmitInitUnionTag(sema->ip, sr_alloc_ref, struct_ty, fname_ip, sema->insts, block); uint32_t sr_field_ptr = semaEmitStructFieldPtr(sr_alloc_ref, ufidx, ztFieldPtrType(sema->zcu, sr_alloc_ty, ufidx), sema->insts, block, sema->air_extra); AirInstData sr_store; memset(&sr_store, 0, sizeof(sr_store)); sr_store.pair.a = sr_field_ptr; sr_store.pair.b = (uint32_t)init_val; u32ListAppend(block, semaAirInstAppend(sema->insts, AIR_INST_STORE, sr_store)); uint32_t sr_const_ty = makePtrTyConst(sema->ip, sr_alloc_ty); sema->inst_values[inst] = semaEmitTyOp(sema->insts, block, AIR_INST_PTR_CAST, sr_const_ty, sr_alloc_ref); sema->inst_types[inst] = sr_const_ty; return; } sema->inst_values[inst] = semaEmitAirUnionInit(sema->insts, block, sema->air_extra, struct_ty, ufidx, (uint32_t)init_val); sema->inst_types[inst] = struct_ty; return; } uint32_t coerced = semaCoerceComptime(sema->zcu, sema->ip, init_val, field_ty); uint32_t un = ipGetUnionValue(sema->ip, struct_ty, tag_val, coerced); sema->inst_values[inst] = un; sema->inst_types[inst] = struct_ty; if (si_is_ref) { // `sema.addConstantMaybeRef(final_val, is_ref)`: the // `uavRef` of the union value. sema->inst_types[inst] = ipGetPtrType(sema->ip, struct_ty, IP_PTR_FLAGS_CONST_ONE); sema->inst_values[inst] = ipGetPtrUav(sema->ip, sema->inst_types[inst], un); } return; } // A tuple's fields are addressed by `tupleFieldIndex`. bool si_tuple = ztIsTuple(sema->ip, struct_ty); if (!si_tuple && !ipTagIsAnyStruct(sema->ip->item_tags[struct_ty])) UNIMPLEMENTED("struct_init: non-struct type"); uint32_t sfields_len = si_tuple ? ipTupleFieldCount(sema->ip, struct_ty) : ipStructFieldsLen(sema->ip, struct_ty); // A partial init (`si_fields_len < sfields_len`) leaves the // omitted fields to their declared defaults (`.fixed(&buf)` sets // vtable + buffer, letting `end: usize = 0` default). Filled below // from the struct's default-body side table. More inits than // fields is a real error. if (si_fields_len > sfields_len) UNIMPLEMENTED("struct_init: more inits than fields"); // Resolve all field inits (by name, into declaration order). // elem_vals[k] = resolved value for field k (IP index or AIR ref). uint32_t* elem_vals = zig1Malloc((sfields_len ? sfields_len : 1) * sizeof(uint32_t)); bool any_runtime = false; for (uint32_t k = 0; k < sfields_len; k++) elem_vals[k] = INST_VALUE_UNSET; uint32_t item_index = items_off; for (uint32_t fi = 0; fi < si_fields_len; fi++) { ZirInstStructInitItem item; item_index = zirExtraData(sema->zir, item_index, &item); uint32_t init_ref = item.init; ZirInstFieldType field_type; zirExtraData(sema->zir, sema->zir->inst_datas[item.field_type].pl_node.payload_index, &field_type); const char* fname = zirNullTerminatedString(sema->zir, field_type.name_start); uint32_t fname_ip = ipGetOrPutString(sema->ip, fname, (uint32_t)strlen(fname)); uint32_t fidx = si_tuple ? tupleFieldIndex(sema->ip, struct_ty, fname) : ipStructFieldIndex(sema->ip, struct_ty, fname_ip); if (fidx == UINT32_MAX) UNIMPLEMENTED("struct_init: field not found"); uint32_t field_ty = si_tuple ? ipTupleFieldType(sema->ip, struct_ty, fidx) : ipStructFieldType(sema->ip, struct_ty, fidx); uint32_t init_val = semaResolveRef(init_ref, sema->inst_values); if (semaIsAirInstRef(init_val)) { // `"field_inits[field_index] = try sema.coerce(block, field_ty, // uncoerced_init, field_src);"` any_runtime = true; elem_vals[fidx] = semaCoerceExtraRuntime(sema->zcu, init_val, semaResolveRefType(init_ref, sema->inst_types), field_ty, sema->insts, block, sema->air_extra, "struct_init: runtime field init coercion"); } else { // Comptime: coerce to field type and store as IP index. uint32_t coerced = semaCoerceComptime(sema->zcu, sema->ip, init_val, field_ty); elem_vals[fidx] = coerced; } } // Fill any omitted field from its declared default value (folded // lazily from the default ZIR body recorded when the struct type // was resolved). Mirrors Sema.zirStructInit resolving a missing // field's default_val. A field with no recorded default that is // still unset is a genuine "missing field" error. for (uint32_t k = 0; k < sfields_len; k++) { if (elem_vals[k] != INST_VALUE_UNSET) continue; uint32_t def_val = si_tuple ? ipTupleFieldValue(sema->ip, struct_ty, k) : semaFieldDefaultValue(sema->zcu, struct_ty, k); if (def_val == IP_INDEX_NONE) UNIMPLEMENTED("struct_init: missing field (no default)"); elem_vals[k] = def_val; } if (!si_tuple && ipStructIsPacked(sema->ip, struct_ty)) { // A packed struct value = the backing integer with each field // placed at its LSB-first bit offset (Sema.zirStructInit -> // structInitPacked bitcasts the fields into the backing int). if (!any_runtime) { // Comptime: fold the backing int and intern a `.bitpack`. uint32_t* packed_elems = zig1Malloc( (sfields_len ? sfields_len : 1) * sizeof(uint32_t)); for (uint32_t k = 0; k < sfields_len; k++) packed_elems[k] = elem_vals[k]; uint32_t bp = semaPackStructFields( sema->zcu, struct_ty, packed_elems, sfields_len); free(packed_elems); if (si_is_ref) { uint32_t bp_ptr_ty = ipGetPtrType( sema->ip, struct_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t bp_uav = ipGetPtrUav(sema->ip, bp_ptr_ty, bp); sema->inst_values[inst] = bp_uav; sema->inst_types[inst] = bp_ptr_ty; free(elem_vals); return; } sema->inst_values[inst] = bp; sema->inst_types[inst] = struct_ty; free(elem_vals); return; } // Runtime: emit AIR aggregate_init over the packed struct // type; the interp AGGREGATE_INIT packed arm folds the // backing int from the element values + bit offsets (yielding // a scalar) and keeps its own host-width fence. uint32_t pagg_ref = semaEmitAggregateInit(sema->insts, block, sema->air_extra, struct_ty, elem_vals, sfields_len); if (si_is_ref) { // Runtime by-ref packed struct init: build the packed // scalar with aggregate_init, then materialize a temporary // pointer exactly like analyzeRef's runtime path. Do not // store fields individually: packed fields are bit // ranges, and the aggregate_init already folded them into // the backing integer representation the interpreter // stores for a packed struct. sema->inst_values[inst] = semaAnalyzeRefRuntime(sema->ip, sema->insts, block, pagg_ref, struct_ty, &sema->inst_types[inst]); free(elem_vals); return; } sema->inst_values[inst] = pagg_ref; sema->inst_types[inst] = struct_ty; free(elem_vals); return; } if (!any_runtime) { // All-comptime: intern aggregate IP value (no AIR). uint32_t* elems = zig1Malloc((sfields_len ? sfields_len : 1) * sizeof(uint32_t)); for (uint32_t k = 0; k < sfields_len; k++) elems[k] = elem_vals[k]; uint32_t agg = ipGetAggregate(sema->ip, struct_ty, elems, sfields_len); free(elems); // `sema.coerce(block, result_ty, struct_val)`: the value at // the optional / error-union result type it initializes. uint32_t si_result_ty = container_val; if (si_result_ty != struct_ty) agg = semaCoerceComptime(sema->zcu, sema->ip, agg, si_result_ty); if (si_is_ref) { // `&.{...}` comptime: Sema.addConstantMaybeRef -> uavRef // — a `*const T` pointer to the interned aggregate. uint32_t si_ptr_ty = ipGetPtrType(sema->ip, si_result_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t si_uav = ipGetPtrUav(sema->ip, si_ptr_ty, agg); sema->inst_values[inst] = si_uav; sema->inst_types[inst] = si_ptr_ty; free(elem_vals); return; } sema->inst_values[inst] = agg; sema->inst_types[inst] = si_result_ty; free(elem_vals); } else { if (si_is_ref && struct_ty != container_val) UNIMPLEMENTED("struct_init_ref: runtime optional or error-union " "result type"); if (si_is_ref) { // Runtime by-ref struct init: Sema.finishStructInit's // is_ref path allocates the destination struct, stores // each initialized/defaulted field through // structFieldPtrByIndex, then makePtrConst-casts the // mutable slot to `*const T`. Do this directly rather // than building an aggregate_init and taking its address: // stock's AIR shape stores fields individually. uint32_t const_ptr_ty = ipGetPtrType(sema->ip, struct_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t mut_ptr_ty = ipGetPtrType(sema->ip, struct_ty, IP_PTR_FLAGS_MUT_ONE); AirInstData alloc_data; memset(&alloc_data, 0, sizeof(alloc_data)); alloc_data.pair.a = mut_ptr_ty; uint32_t alloc_idx = semaAirInstAppend(sema->insts, AIR_INST_ALLOC, alloc_data); u32ListAppend(block, alloc_idx); uint32_t alloc_ref = AIR_REF_INST_BASE + alloc_idx; for (uint32_t k = 0; k < sfields_len; k++) { uint32_t field_ptr_ty = ztFieldPtrType(sema->zcu, mut_ptr_ty, k); uint32_t field_ptr_ref = semaEmitStructFieldPtr(alloc_ref, k, field_ptr_ty, sema->insts, block, sema->air_extra); AirInstData store_data; memset(&store_data, 0, sizeof(store_data)); store_data.pair.a = field_ptr_ref; store_data.pair.b = (uint32_t)elem_vals[k]; uint32_t store_idx = semaAirInstAppend( sema->insts, AIR_INST_STORE, store_data); u32ListAppend(block, store_idx); } AirInstData cast_data; memset(&cast_data, 0, sizeof(cast_data)); cast_data.pair.a = const_ptr_ty; cast_data.pair.b = alloc_ref; uint32_t cast_idx = semaAirInstAppend(sema->insts, AIR_INST_PTR_CAST, cast_data); u32ListAppend(block, cast_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + cast_idx; sema->inst_types[inst] = const_ptr_ty; free(elem_vals); return; } // Runtime: emit AIR aggregate_init. Elements (AIR Refs, each // u32) are appended to air_extra at the current position. // Mirrors Sema.zirStructInit runtime value path. sema->inst_values[inst] = semaEmitAggregateInit(sema->insts, block, sema->air_extra, struct_ty, elem_vals, sfields_len); sema->inst_types[inst] = struct_ty; free(elem_vals); } } // Sema.zig:zirStructInitAnon // Sema.zirStructInitAnon -> structInitAnon(.anon_init, is_ref = false). ZIR // pl_node -> StructInitAnon { abs_node, src_line, src_column, fields_len } + // Item { field_name, init } per field. static void zirStructInitAnon(SemaCtx* sema, U32List* block, uint32_t inst) { ZirInstStructInitAnon extra; uint32_t extra_end = zirExtraData( sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &extra); structInitAnon(sema->zcu, sema->zir, inst, extra.fields_len, extra_end, false, false, sema->type_name_ctx, sema->insts, block, sema->air_extra, sema->inst_values, sema->inst_types); } // Sema.zig:zirArrayInit // Sema.zirArrayInit(is_ref): ZIR pl_node -> MultiOp { operands_len }, args[0] // = the result type, args[1..] = the elements. `"const elem = try // sema.coerce(block, elem_ty, sema.resolveInst(arg), elem_src);"` for every // element, then an interned aggregate when all are comptime-known, else one // `aggregate_init`; `is_ref` takes its address (a `*const T` uav pointer, or // analyzeRef's runtime alloc/store). static void zirArrayInit( SemaCtx* sema, U32List* block, uint32_t inst, bool is_ref) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstMultiOp extra; uint32_t extra_end = zirExtraData(sema->zir, pp, &extra); uint32_t n = extra.operands_len - 1; const uint32_t* args = &sema->zir->extra[extra_end + 1]; uint32_t arr_val = semaResolveRef(sema->zir->extra[extra_end], sema->inst_values); if (semaIsAirInstRef(arr_val)) UNIMPLEMENTED("array_init: runtime array type"); uint32_t arr_ty = arr_val; if (arr_ty == IP_INDEX_GENERIC_POISON_TYPE) { // `"resolveTypeOrPoison orelse return // sema.arrayInitAnon(block, src, args[1..], is_ref)"`: a // `.{...}` bound to an `anytype` param (w.print's args). arrayInitAnon(sema->ip, args, n, is_ref, sema->inst_values, sema->inst_types, sema->insts, block, sema->air_extra, inst); return; } // `"const array_ty = result_ty.optEuBaseType(zcu);"`, the result // coerced back to `result_ty` at the end. uint32_t result_ty = arr_ty; arr_ty = ipOptEuBaseType(sema->ip, result_ty); bool is_tuple = ztIsTuple(sema->ip, arr_ty); IpArrayInfo ainit_info = { 0 }; if (is_tuple) { validateArrayInitTy(sema->ip, n, arr_ty); if (n != ipTupleFieldCount(sema->ip, arr_ty)) UNIMPLEMENTED("array_init: tuple element count mismatch"); } else if (ipArrayInfo(sema->ip, arr_ty, &ainit_info) != IP_ARR_OK) { UNIMPLEMENTED("array_init: non-array result type"); } else { validateArrayInitTy(sema->ip, n, arr_ty); } uint32_t const_ptr_ty = is_ref ? ipGetPtrType(sema->ip, result_ty, IP_PTR_FLAGS_CONST_ONE) : IP_INDEX_NONE; uint32_t* elems = ARR_INIT(uint32_t, n + 1u); bool has_runtime = false; for (uint32_t e = 0; e < n; e++) { uint32_t elem_ty = ainit_info.child; if (is_tuple) { elem_ty = ipTupleFieldType(sema->ip, arr_ty, e); // `"if (array_ty.structFieldValueComptime(pt, i)) | // field_val| { const init_val = try sema.resolveValue( // resolved_args[i]) orelse { return sema // .failWithNeededComptime(...) }; if (!field_val.eql( // init_val, elem_ty, zcu)) { return sema // .failWithInvalidComptimeFieldStore(...) } }"` uint32_t field_val = ipTupleFieldValue(sema->ip, arr_ty, e); if (field_val != IP_INDEX_NONE) { uint32_t fv = semaResolveRef(args[e], sema->inst_values); if (!semaRefIsInterned(fv)) COMPILE_ERROR_UPSTREAM("array_init: value stored " "in comptime field must " "be comptime-known"); if (semaCoerceComptime(sema->zcu, sema->ip, fv, elem_ty) != field_val) COMPILE_ERROR_UPSTREAM("array_init: value stored " "in comptime field does " "not match its default"); elems[e] = field_val; continue; } ZtTypeClass field_class = ztTypeClass(sema->ip, elem_ty); if (field_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED("array_init: unresolved tuple field class"); if (field_class != ZT_CLASS_OPV && field_class != ZT_CLASS_RUNTIME && field_class != ZT_CLASS_PARTIAL) UNIMPLEMENTED("array_init: comptime-only tuple field"); } uint32_t ev = semaResolveRef(args[e], sema->inst_values); if (semaIsAirInstRef(ev)) { has_runtime = true; elems[e] = semaCoerceExtraRuntime(sema->zcu, ev, semaResolveRefType(args[e], sema->inst_types), elem_ty, sema->insts, block, sema->air_extra, "array_init: runtime element coercion"); continue; } elems[e] = semaCoerceComptime(sema->zcu, sema->ip, ev, elem_ty); } if (!has_runtime) { // `"const result_ref = try sema.coerce(block, result_ty, // Air.internedToRef(arr_val.toIntern()), src);"` uint32_t agg = ipGetAggregate(sema->ip, arr_ty, elems, n); if (result_ty != arr_ty) agg = semaCoerceComptime(sema->zcu, sema->ip, agg, result_ty); sema->inst_values[inst] = is_ref ? ipGetPtrUav(sema->ip, const_ptr_ty, agg) : agg; sema->inst_types[inst] = is_ref ? const_ptr_ty : result_ty; free(elems); return; } if (is_ref && result_ty != arr_ty) UNIMPLEMENTED("array_init_ref: runtime optional/error-union result"); uint32_t agg_ref = semaEmitAggregateInit( sema->insts, block, sema->air_extra, arr_ty, elems, n); free(elems); sema->inst_types[inst] = arr_ty; if (result_ty != arr_ty) { // `"return sema.coerce(block, result_ty, arr_ref, src);"` agg_ref = semaCoerceExtraRuntime(sema->zcu, agg_ref, arr_ty, result_ty, sema->insts, block, sema->air_extra, "array_init: runtime result coercion"); sema->inst_types[inst] = result_ty; } sema->inst_values[inst] = is_ref ? semaAnalyzeRefRuntime(sema->ip, sema->insts, block, agg_ref, arr_ty, &sema->inst_types[inst]) : agg_ref; } // Sema.zig:zirArrayInitAnon // Sema.zirArrayInitAnon: `inline for (.{ ... })` and `@call`'s args tuple (`.{ // hasher, mem.asBytes(&key) }`). static void zirArrayInitAnon(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstMultiOp extra; uint32_t extra_end = zirExtraData(sema->zir, pp, &extra); arrayInitAnon(sema->ip, &sema->zir->extra[extra_end], extra.operands_len, false, sema->inst_values, sema->inst_types, sema->insts, block, sema->air_extra, inst); } // Sema.zig:zirFieldTypeRef // Sema.zirFieldTypeRef: `@FieldType(Container, name)` and the field-type query // AstGen emits for `@unionInit`. The result is a comptime type value even in a // runtime body. Narrow real-path slice: InternPool.AnalUnit.unwrap lowers // `@unionInit(Unwrapped, @tagName(tag), ...)` with a union container and a // `PTR_UAV` field-name string. Upstream anchors in src/Sema.zig: // `try sema.ensureLayoutResolved(aggregate_ty, ty_src, .field_queried);` // `return sema.fieldType(block, aggregate_ty, field_name, field_src, // ty_src);`; `union_obj.field_types.get(ip)`. static void zirFieldTypeRef(SemaCtx* sema, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstFieldTypeRef extra; zirExtraData(sema->zir, pp, &extra); uint32_t container_ref = extra.container_type; uint32_t name_ref = extra.field_name; uint32_t container_ty = semaResolveRef(container_ref, sema->inst_values); uint32_t name_val = semaResolveRef(name_ref, sema->inst_values); if (semaIsAirInstRef(container_ty)) UNIMPLEMENTED("field_type_ref: runtime container type"); if (semaIsAirInstRef(name_val)) UNIMPLEMENTED("field_type_ref: runtime field name"); if (!ipIndexIsType(sema->ip, container_ty)) COMPILE_ERROR_UPSTREAM("field_type_ref: container not a type"); uint32_t field_name = semaFieldNameValueToIpString( sema->ip, name_val, "field_type_ref: name not a string"); // `"try sema.ensureLayoutResolved(aggregate_ty, ty_src, // .field_queried);"`; a failed resolution stays loud at its use. ensureLayoutResolved(sema->zcu, container_ty); sema->inst_values[inst] = fieldType(sema->zcu, container_ty, field_name); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirStructInitFieldType // Sema.zirStructInitFieldType -> fieldType: given the container (struct) type // and a field name, return the field's type. ZIR pl_node -> FieldType { // container_type, name_start }. No IP entry / AIR; the result is a type value. static void zirStructInitFieldType(SemaCtx* sema, uint32_t inst) { uint32_t pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstFieldType extra; zirExtraData(sema->zir, pp, &extra); uint32_t container_ref = extra.container_type; uint32_t name_start = extra.name_start; uint32_t container_val = semaResolveStructInitContainerRef( container_ref, sema->inst_values, sema->ictx); if (semaIsAirInstRef(container_val)) UNIMPLEMENTED("struct_init_field_type: runtime container type"); // `resolveTypeOrPoison(...) orelse return .generic_poison_type`: // an anonymous init with no result type (an `anytype` argument). if (container_val == IP_INDEX_GENERIC_POISON_TYPE) { sema->inst_values[inst] = IP_INDEX_GENERIC_POISON_TYPE; return; } uint32_t struct_ty = ipOptEuBaseType(sema->ip, container_val); const char* fname = zirNullTerminatedString(sema->zir, name_start); uint32_t fname_ip = ipGetOrPutString(sema->ip, fname, (uint32_t)strlen(fname)); // `"try sema.ensureLayoutResolved(aggregate_ty, ty_src, .init);"` ensureLayoutResolved(sema->zcu, struct_ty); if (ztZigTypeTag(sema->ip, struct_ty) == ZIG_TYPE_TAG_UNION) { // Union field type: look the field up in the tag enum's field // names and return the union's stored field_type (resolving a // deferred decl_val-aliased type on first use). uint32_t ufidx = ipUnionFieldIndex(sema->ip, struct_ty, fname_ip); if (ufidx == UINT32_MAX) UNIMPLEMENTED("struct_init_field_type: union field missing"); // zirStructInitFieldType first ensures union layout for // initialization: generated enum, then every payload field in // declaration order, before selecting the requested type. semaResolveAllUnionDeferred(sema->zcu, struct_ty); if (ipUnionIsPacked(sema->ip, struct_ty) && !resolvePackedUnionLayout(sema->zcu, struct_ty)) UNIMPLEMENTED("struct_init_field_type: packed union layout"); uint32_t ufty = semaResolveUnionFieldTypeLazy(sema->zcu, struct_ty, ufidx); if (ufty == IP_INDEX_NONE) UNIMPLEMENTED("struct_init_field_type: unresolved union field"); sema->inst_values[inst] = ufty; return; } if (ztIsTuple(sema->ip, struct_ty)) { // fieldType's `.tuple_type` arm: tupleFieldIndex. sema->inst_values[inst] = ipTupleFieldType( sema->ip, struct_ty, tupleFieldIndex(sema->ip, struct_ty, fname)); return; } if (!ipTagIsAnyStruct(sema->ip->item_tags[struct_ty])) UNIMPLEMENTED("struct_init_field_type: non-struct container"); // `"try sema.ensureLayoutResolved(aggregate_ty, ty_src, .init);"` ensureLayoutResolved(sema->zcu, struct_ty); uint32_t fidx = ipStructFieldIndex(sema->ip, struct_ty, fname_ip); if (fidx == UINT32_MAX) UNIMPLEMENTED("struct_init_field_type: field not found"); sema->inst_values[inst] = ipStructFieldType(sema->ip, struct_ty, fidx); } // Sema.zig:zirErrorReturnTrace // Sema.zirErrorReturnTrace -> getErrorReturnTrace. In the bootstrap profile, // error return tracing is disabled, so upstream returns a comptime null of // type `?*StackTrace` instead of emitting AIR `.err_return_trace`. static void zirErrorReturnTrace(SemaCtx* sema, uint32_t inst) { static const char* const ert_path[] = { "StackTrace" }; uint32_t stack_trace_ty = getStdLangType(sema->zcu, ert_path, 1); uint32_t ptr_stack_trace_ty = ipGetPtrType(sema->ip, stack_trace_ty, IP_PTR_FLAGS_MUT_ONE); uint32_t opt_ptr_stack_trace_ty = ipGetOptionalType(sema->ip, ptr_stack_trace_ty); sema->inst_values[inst] = ipGetOptNull(sema->ip, opt_ptr_stack_trace_ty); sema->inst_types[inst] = opt_ptr_stack_trace_ty; } // Sema.zig:zirIntFromBool, PerThread.zig:undefRef // A comptime operand folds, a scalar lane or a vector's per-lane loop ("if // (val.isUndef(zcu)) return pt.undefRef(dest_ty);" first); a runtime one is // `"block.addTyOp(.bit_cast, dest_ty, operand)"` with `dest_ty` u1 or // `@Vector(len, u1)`. ZIR data: un_node {operand}. static void zirIntFromBool(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ifb_op_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t ifb_op_val = semaResolveRef(ifb_op_ref, sema->inst_values); if (!semaIsAirInstRef(ifb_op_val)) { if (ifb_op_val >= sema->ip->items_len) UNIMPLEMENTED("int_from_bool: comptime operand shape"); uint32_t ty = semaIntFromBoolType( sema->ip, ipComptimeValueType(sema->ip, ifb_op_val)); sema->inst_types[inst] = ty; sema->inst_values[inst] = ty != ZIR_REF_U1_TYPE && ipIsUndef(sema->ip, ifb_op_val) ? ipGetUndef(sema->ip, ty) : semaCastLanes(sema->ip, ifb_op_val, ty, semaIntFromBoolLane); return; } uint32_t ifb_dest_ty = semaIntFromBoolType( sema->ip, semaResolveRefType(ifb_op_ref, sema->inst_types)); AirInstData ifb_data; memset(&ifb_data, 0, sizeof(ifb_data)); ifb_data.pair.a = ifb_dest_ty; ifb_data.pair.b = ifb_op_val; uint32_t ifb_air_idx = semaAirInstAppend(sema->insts, AIR_INST_BIT_CAST, ifb_data); u32ListAppend(block, ifb_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + ifb_air_idx; sema->inst_types[inst] = ifb_dest_ty; } // Sema.zig:zirErrorName, InternPool.zig:indexToKey // Sema.zirErrorName: `@errorName(e)` on an error value yields the error's name // string (`[:0]const u8`), via addNullTerminatedStrLit — identical string-lit // machinery to @tagName. ZIR un_node {operand}. Only the comptime-known // error-value path is ported. static void zirErrorName(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t er_op_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t er_op_val = semaResolveRef(er_op_ref, sema->inst_values); if (semaIsAirInstRef(er_op_val)) { // Runtime path (Sema.zirErrorName): coerce operand to // `.anyerror` then `block.addUnOp(.error_name, operand)`. An // `anyerror`-typed operand (err_union_code of an `anyerror!T`) // needs no coercion and no AIR. Emit AIR error_name (un_op: // pair.a = operand); result type is always `[:0]const u8` // (slice_const_u8_sentinel_0). uint32_t er_op_ty = semaResolveRefType(er_op_ref, sema->inst_types); uint32_t er_op_air = er_op_val; if (er_op_ty != ZIR_REF_ANYERROR_TYPE) { if (!semaErrorSetCanCoerceType( sema->ip, er_op_ty, ZIR_REF_ANYERROR_TYPE)) UNIMPLEMENTED("error_name: runtime non-anyerror operand"); AirInstData ec_data; memset(&ec_data, 0, sizeof(ec_data)); ec_data.pair.a = ZIR_REF_ANYERROR_TYPE; ec_data.pair.b = er_op_air; uint32_t ec_idx = semaAirInstAppend(sema->insts, AIR_INST_ERROR_CAST, ec_data); u32ListAppend(block, ec_idx); er_op_air = AIR_REF_INST_BASE + ec_idx; } AirInstData ern_data; memset(&ern_data, 0, sizeof(ern_data)); ern_data.pair.a = er_op_air; uint32_t ern_air_idx = semaAirInstAppend(sema->insts, AIR_INST_ERROR_NAME, ern_data); u32ListAppend(block, ern_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + ern_air_idx; sema->inst_types[inst] = ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE; return; } // `"const err_name = ip.indexToKey(val.toIntern()).err.name; return // sema.addNullTerminatedStrLit(err_name);"`, after the operand is coerced // to `anyerror` (the same-named anyerror value). InternPool* ip = sema->ip; if (er_op_val >= ip->items_len || ip->item_tags[er_op_val] != IP_TAG_ERROR_SET_ERROR) UNIMPLEMENTED("error_name: comptime non-error operand"); uint32_t name = ipErrorName(ip, er_op_val); ipGetErrorSetError(ip, ZIR_REF_ANYERROR_TYPE, name); sema->inst_values[inst] = addNullTerminatedStrLit(ip, name, &sema->inst_types[inst]); } // Sema.zig:zirAbs // `Sema.zirAbs` (src/Sema.zig): the result type, then // `maybeConstantUnaryMath(operand, result_ty, Value.abs)` for a comptime-known // operand, else `block.addTyOp(.abs, result_ty, operand)`. The two folds are // `semaAbsResultType`/`valAbs`, the same rule the comptime frame calls. static void zirAbs(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t abs_op_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t abs_op_val = semaResolveRef(abs_op_ref, sema->inst_values); uint32_t abs_op_ty = semaResolveRefType(abs_op_ref, sema->inst_types); bool abs_identity; uint32_t abs_result_ty = semaAbsResultType(sema->ip, abs_op_ty, &abs_identity); if (abs_result_ty == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM("abs: @abs expected integer, float, " "or vector of either"); if (abs_identity) { sema->inst_values[inst] = abs_op_val; sema->inst_types[inst] = abs_op_ty; return; } if (!semaIsAirInstRef(abs_op_val)) { uint32_t abs_folded = abs_op_val == INST_VALUE_UNSET ? IP_INDEX_NONE : valAbs(sema->ip, abs_op_val, abs_result_ty); if (abs_folded == IP_INDEX_NONE) UNIMPLEMENTED("abs: comptime operand"); sema->inst_values[inst] = abs_folded; sema->inst_types[inst] = abs_result_ty; return; } AirInstData abs_data; memset(&abs_data, 0, sizeof(abs_data)); abs_data.pair.a = abs_result_ty; abs_data.pair.b = abs_op_val; uint32_t abs_air_idx = semaAirInstAppend(sema->insts, AIR_INST_ABS, abs_data); u32ListAppend(block, abs_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + abs_air_idx; sema->inst_types[inst] = abs_result_ty; } // Sema.zig:zirUnaryMath // Sema.zirUnaryMath: validate a float or vector-of-floats operand, fold // comptime values if known, otherwise emit the AIR unary math op. Comptime // float folding is not ported. static void zirUnaryMath( SemaCtx* sema, U32List* block, uint32_t inst, ZirInstTag tag) { uint32_t fum_op_ref = sema->zir->inst_datas[inst].un_node.operand; sema->inst_values[inst] = semaAnalyzeUnaryMath(sema->zcu, tag, semaResolveRef(fum_op_ref, sema->inst_values), semaResolveRefType(fum_op_ref, sema->inst_types), sema->insts, block, &sema->inst_types[inst]); } // Sema.zig:zirTagName // Sema.zirTagName: `@tagName(e)` on an enum operand yields the field-name // string of e's tag (addNullTerminatedStrLit -> addStrLit -> uavRef). ZIR // data: un_node {operand}. Only the comptime-known plain-enum operand path is // ported (the enum-literal, union, and runtime-value AIR `tag_name` paths are // not). static void zirTagName(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t tn_op_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t tn_op_val = semaResolveRef(tn_op_ref, sema->inst_values); if (semaIsAirInstRef(tn_op_val)) { // Runtime path (Sema.zirTagName): coerce the operand to its // enum type (identity for a plain enum operand, or the active // tag extracted from a tagged-union operand), then // `block.addUnOp(.tag_name, operand)`. The C backend // renders this via a runtime switch over the enum's field // names; the interpreter executes AIR tag_name to materialize // the name slice. Result type is always `[:0]const u8` // (slice_const_u8_sentinel_0). Mirrors the ERROR_NAME runtime // arm above. uint32_t tn_op_ty = semaResolveRefType(tn_op_ref, sema->inst_types); if (!ipIsEnumType(sema->ip, tn_op_ty)) { if (tn_op_ty >= sema->ip->items_len || sema->ip->item_tags[tn_op_ty] != IP_TAG_TYPE_UNION) UNIMPLEMENTED("tag_name: runtime non-enum operand"); uint32_t enum_ty; uint32_t tag_ref = semaUnionToTagRuntime( sema->zcu, tn_op_ty, tn_op_val, sema->insts, block, &enum_ty); if (!semaIsAirInstRef(tag_ref)) { uint32_t ptr_ty; sema->inst_values[inst] = semaTagNameComptime(sema->ip, tag_ref, &ptr_ty); sema->inst_types[inst] = ptr_ty; return; } tn_op_val = tag_ref; } AirInstData tnr_data; memset(&tnr_data, 0, sizeof(tnr_data)); tnr_data.pair.a = tn_op_val; uint32_t tnr_air_idx = semaAirInstAppend(sema->insts, AIR_INST_TAG_NAME, tnr_data); u32ListAppend(block, tnr_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + tnr_air_idx; sema->inst_types[inst] = ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE; return; } uint32_t tn_ptr_ty; uint32_t tn_ptr_uav = semaTagNameComptime(sema->ip, tn_op_val, &tn_ptr_ty); sema->inst_values[inst] = tn_ptr_uav; sema->inst_types[inst] = tn_ptr_ty; } // Sema.zig:zirReifyInt // `@Int(signedness, bits)` in a runtime body (Sema.zirReifyInt — `const MinInt // = @Int(.unsigned, min_int_bits)` in Writer.printIntAny): both operands are // comptime-known; fold to the interned integer type (a pure type value, no // AIR). Operand shapes: signedness = a real std.lang.Signedness enum_tag (the // coerced literal), bits = an interned int. static void zirReifyInt(SemaCtx* sema, uint32_t inst) { uint32_t rip = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, rip, &extra); uint32_t ri_sgn = semaResolveRef(extra.lhs, sema->inst_values); uint32_t ri_bits_v = semaResolveRef(extra.rhs, sema->inst_values); if (!semaRefIsInterned(ri_sgn) || ri_sgn < ZIR_REF_START_INDEX || sema->ip->item_tags[ri_sgn] != IP_TAG_ENUM_TAG) UNIMPLEMENTED("reify_int: signedness operand"); bool ri_signed = ipEnumTagFieldIndex(sema->ip, ri_sgn) == SIGNEDNESS_SIGNED; uint64_t ri_bits; if (semaRefIsInterned(ri_bits_v)) { ri_bits = ipReadIntValue(sema->ip, ri_bits_v); if (ri_bits == UINT64_MAX) UNIMPLEMENTED("reify_int: unreadable bits operand"); } else { UNIMPLEMENTED("reify_int: runtime bits operand"); } if (ri_bits > 0xFFFF || (ri_signed && ri_bits == 0)) UNIMPLEMENTED("reify_int: invalid bit count"); sema->inst_values[inst] = ipGetIntType(sema->ip, ri_signed, (uint16_t)ri_bits); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirReifySliceArgTy // Sema.zirReifySliceArgTy: the `*const [names.len]scalar` result type // `@Struct`/`@Union`'s field_types/field_attrs argument is analyzed against, // in a runtime analysis body (`const U = @Union(.auto, E, &.{"a"}, &.{u32}, // &.{.{}});` inside a test block). UnNode { node, operand=field_names }; // upstream resolves the operand with resolveConstDefinedValue after coercing // it, so a runtime operand is not a legal shape here. The shared // sema_comptime.c port states the rule. static void zirReifySliceArgTy(SemaCtx* sema, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; ZirInstUnNode extra; zirExtraData(sema->zir, ext_operand, &extra); uint32_t rsa_names_val = semaResolveRef(extra.operand, sema->inst_values); sema->inst_values[inst] = semaReifySliceArgTy(sema->zcu, sema->zir->inst_datas[inst].extended.small, semaReifyConstOperand( rsa_names_val, "zirReifySliceArgTy: runtime field-names operand")); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // (part of) Sema.zig:zirReifyPointer, Sema.zig:zirReifyFn, // Sema.zig:zirReifyStruct, Sema.zig:zirReifyUnion, Sema.zig:zirReifyEnum, // Sema.zig:zirReifyEnumValueSliceTy // `@Pointer` / `@Fn` / `@Struct` / `@Union` / `@Enum` in a runtime analysis // body (`const Slice = @Pointer(.slice, .{ ... }, ?*i32, null);` inside a // test block): every operand is resolved by a comptimeExpr or // resolveConstDefinedValue, so a runtime operand is not a legal shape here. // The shared sema_comptime.c ports compose the type; inst_types[inst] is // written here. static void semaReifyConstOperands(SemaCtx* sema, uint32_t inst, const uint32_t* refs, uint32_t n, uint32_t* ops) { for (uint32_t k = 0; k < n; k++) ops[k] = semaReifyConstOperand(semaResolveRef(refs[k], sema->inst_values), "zirReifyStruct: runtime operand"); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirReifyEnumValueSliceTy // Sema.zirReifyEnumValueSliceTy: the `*const [names.len]tag_ty` result type // `@Enum`'s field_values argument is analyzed against, in a runtime analysis // body (`const E = @Enum(u8, .exhaustive, &.{"a"}, &.{1});` inside a test // block). BinNode { node, lhs=tag_ty, rhs=field_names }; upstream resolves // tag_ty with `sema.resolveType` and field_names with // `resolveConstDefinedValue`, so neither may be runtime. static void zirReifyEnumValueSliceTy(SemaCtx* sema, uint32_t inst) { ZirInstBinNode extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].extended.operand, &extra); const uint32_t rev_refs[2] = { extra.lhs, extra.rhs }; uint32_t rev_ops[2]; semaReifyConstOperands(sema, inst, rev_refs, 2, rev_ops); sema->inst_values[inst] = semaReifyEnumValueSliceTy(sema->zcu, rev_ops[0], rev_ops[1]); } // Sema.zig:zirReifyPointerSentinelTy // Sema.zirReifyPointerSentinelTy: the result type handed to `@Pointer`'s // sentinel argument, in a runtime analysis body (`@Pointer(ptr.size, // ptr.attrs, ptr.child, ptr.sentinel())` inside a test block). UnNode { node, // operand=elem_ty }; upstream resolves the operand with `sema.resolveType`, so // a runtime operand is not a legal shape here. Normally `?elem_ty`, but an // opaque or `null`-typed element cannot be an optional's child, so those // become `?noreturn` ("to allow reifying pointers to them still") -- // upstream's `.optional_noreturn`, which `ipGetOptionalType(noreturn)` // canonicalizes to. static void zirReifyPointerSentinelTy(SemaCtx* sema, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; ZirInstUnNode extra; zirExtraData(sema->zir, ext_operand, &extra); uint32_t rs_elem_ref = extra.operand; uint32_t rs_elem_val = semaResolveRef(rs_elem_ref, sema->inst_values); uint32_t elem_ty = ctValidatedTypeResult(sema->ip, semaReifyConstOperand( rs_elem_val, "reify_pointer_sentinel_ty: runtime operand"), "reify_pointer_sentinel_ty: operand not a type"); sema->inst_values[inst] = ipGetOptionalType(sema->ip, ztIsZigTypeTag(sema->ip, elem_ty, ZIG_TYPE_TAG_OPAQUE) || elem_ty == ZIR_REF_NULL_TYPE ? ZIR_REF_NORETURN_TYPE : elem_ty); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirReifyTuple // Sema.zirReifyTuple: `@Tuple(&field_types)` in a runtime analysis body // (`const T = @Tuple(&.{ u8, u16 });`). The shared sema_comptime.c port does // the work; the operand must be comptime-known, which the builtin's // `comptimeExpr` guarantees. static void zirReifyTuple(SemaCtx* sema, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; ZirInstUnNode extra; zirExtraData(sema->zir, ext_operand, &extra); uint32_t rt_types_ref = extra.operand; uint32_t rt_types_val = semaResolveRef(rt_types_ref, sema->inst_values); sema->inst_values[inst] = semaReifyTupleType(sema->zcu, semaReifyConstOperand( rt_types_val, "reify_tuple: runtime field-types operand")); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirReifyPointer static void zirReifyPointer(SemaCtx* sema, uint32_t inst) { ZirInstReifyPointer extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].extended.operand, &extra); const uint32_t refs[4] = { extra.size, extra.attrs, extra.elem_ty, extra.sentinel }; uint32_t ops[4]; semaReifyConstOperands(sema, inst, refs, 4, ops); sema->inst_values[inst] = semaReifyPointerType(sema->zcu, ops[0], ops[1], ops[2], ops[3]); } // Sema.zig:zirReifyFn static void zirReifyFn(SemaCtx* sema, uint32_t inst) { ZirInstReifyFn extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].extended.operand, &extra); const uint32_t refs[4] = { extra.param_types, extra.param_attrs, extra.ret_ty, extra.fn_attrs }; uint32_t ops[4]; semaReifyConstOperands(sema, inst, refs, 4, ops); sema->inst_values[inst] = semaReifyFn(sema->zcu, ops[0], ops[1], ops[2], ops[3]); } // Sema.zig:zirReifyStruct static void zirReifyStruct(SemaCtx* sema, uint32_t inst) { ZirInstReifyStruct extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].extended.operand, &extra); const uint32_t refs[5] = { extra.layout, extra.backing_ty, extra.field_names, extra.field_types, extra.field_attrs }; uint32_t ops[5]; semaReifyConstOperands(sema, inst, refs, 5, ops); sema->inst_values[inst] = semaReifyStructType(sema->zcu, sema->zir, inst, ops[0], ops[1], ops[2], ops[3], ops[4], sema->type_name_ctx, NULL, IP_INDEX_NONE); } // Sema.zig:zirReifyUnion static void zirReifyUnion(SemaCtx* sema, uint32_t inst) { ZirInstReifyUnion extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].extended.operand, &extra); const uint32_t refs[5] = { extra.layout, extra.arg_ty, extra.field_names, extra.field_types, extra.field_attrs }; uint32_t ops[5]; semaReifyConstOperands(sema, inst, refs, 5, ops); sema->inst_values[inst] = semaReifyUnionType(sema->zcu, sema->zir, inst, ops[0], ops[1], ops[2], ops[3], ops[4], sema->type_name_ctx, NULL, IP_INDEX_NONE); } // Sema.zig:zirReifyEnum // Sema.zirReifyEnum: `@Enum(tag_ty, mode, &field_names, &field_values)` in a // runtime analysis body (`const Foo = @Enum(u8, .exhaustive, &.{"a"}, &.{1});` // inside a test block). Payload ReifyEnum { src_line, src_column, node, // tag_ty, mode, field_names, field_values }; every operand reaches upstream // through resolveConstDefinedValue, so a runtime operand is not a legal // shape. The shared sema_comptime.c port builds the type, including its // getReifiedEnumType dedup identity. static void zirReifyEnum(SemaCtx* sema, uint32_t inst) { ZirInstReifyEnum extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].extended.operand, &extra); const uint32_t re_refs[4] = { extra.tag_ty, extra.mode, extra.field_names, extra.field_values }; uint32_t re_ops[4]; semaReifyConstOperands(sema, inst, re_refs, 4, re_ops); sema->inst_values[inst] = semaReifyEnumType(sema->zcu, sema->zir, inst, re_ops[0], re_ops[1], re_ops[2], re_ops[3], sema->type_name_ctx, NULL, IP_INDEX_NONE); } // Sema.zig:zirTypeName // Sema.zirTypeName -> addNullTerminatedStrLit -> addStrLit: format the operand // type's fully-qualified name and intern it as a comptime `*const [N:0]u8` uav // — identical machinery to zirStr, only the bytes come from the type name // rather than ZIR source. static void zirTypeName(SemaCtx* sema, uint32_t inst) { uint32_t tn_operand_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t tn_operand_val = semaResolveRef(tn_operand_ref, sema->inst_values); if (semaIsAirInstRef(tn_operand_val)) UNIMPLEMENTED("type_name: runtime operand"); TypeNameWriter w = { 0 }; ztFormatTypeName(sema->ip, tn_operand_val, &w); uint32_t name = ipGetOrPutString(sema->ip, w.buf.len ? w.buf.arr : "", w.buf.len); free(w.buf.arr); sema->inst_values[inst] = addNullTerminatedStrLit(sema->ip, name, &sema->inst_types[inst]); } // Sema.zig:zirIntFromFloat // Sema.zirIntFromFloat. ZIR data: pl_node with Bin payload {lhs=dest_type_ref, // rhs=operand_ref}. static void zirIntFromFloat(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t iff_dest_ty; uint32_t iff_op_ref = semaCastDestOperand(sema, inst, &iff_dest_ty); uint32_t iff_op_val = semaResolveRef(iff_op_ref, sema->inst_values); // "if (sema.resolveValue(operand)) |operand_val| { const // result_val = try sema.intFromFloat(block, operand_src, // operand_val, operand_ty, dest_ty, .truncate); ..." -- // `intFromFloat` states `checkIntType`'s comptime_int case. if (!semaIsAirInstRef(iff_op_val)) { sema->inst_values[inst] = intFromFloat(sema->ip, iff_op_val, iff_dest_ty, false); sema->inst_types[inst] = iff_dest_ty; return; } uint32_t iff_dest_scalar = ipScalarType(sema->ip, iff_dest_ty); if (iff_dest_scalar == ZIR_REF_COMPTIME_INT_TYPE) COMPILE_ERROR_UPSTREAM( "int_from_float: casted to comptime_int needs comptime"); uint32_t iff_dst_bits; bool iff_dst_signed; if (!ztIntInfo(sema->ip, iff_dest_scalar, &iff_dst_bits, &iff_dst_signed)) UNIMPLEMENTED("int_from_float: non-int dest type"); // `"if (dest_scalar_ty.toIntern() == .u0_type) { ... const // scalar_val = try pt.intValue(dest_scalar_ty, 0); return // Air.internedToRef((try sema.splat(dest_ty, // scalar_val)).toIntern()); }"` -- its safety check is off in // ReleaseSmall. if (iff_dest_scalar == ZIR_REF_U0_TYPE) { sema->inst_values[inst] = semaSplatU0Zero(sema->ip, iff_dest_ty); sema->inst_types[inst] = iff_dest_ty; return; } uint32_t iff_op_ty = semaResolveRefType(iff_op_ref, sema->inst_types); uint32_t iff_op_bits; if (!ztSimpleFloatBits(ipScalarType(sema->ip, iff_op_ty), &iff_op_bits)) UNIMPLEMENTED("int_from_float: non-float operand type"); AirInstData iff_data; memset(&iff_data, 0, sizeof(iff_data)); iff_data.pair.a = iff_dest_ty; iff_data.pair.b = iff_op_val; uint32_t iff_air_idx = semaAirInstAppend(sema->insts, AIR_INST_INT_FROM_FLOAT, iff_data); u32ListAppend(block, iff_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + iff_air_idx; sema->inst_types[inst] = iff_dest_ty; } // Sema.zig:zirRoundCast // Sema.zirRoundCast: validate destination and operand float/vector scalar // types, fold a comptime operand, then emit the mode's AIR unary op // (`block.addUnOp`). static void zirRoundCast(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; uint32_t rop_payload = ext_operand; ZirInstBinNode extra; zirExtraData(sema->zir, rop_payload, &extra); uint32_t rop_dest_ref = extra.lhs; uint32_t rop_operand_ref = extra.rhs; uint32_t rop_dest_ty = semaResolveRef(rop_dest_ref, sema->inst_values); if (!semaRefIsInterned(rop_dest_ty)) UNIMPLEMENTED("round_op: runtime destination type"); if (rop_dest_ty == IP_INDEX_GENERIC_POISON_TYPE) { // `"orelse switch (mode) { .round => return // sema.unaryMath(block, operand_src, operand, .round, // Value.round), ..."`: no result type, the operand's own. sema->inst_values[inst] = semaAnalyzeUnaryMath(sema->zcu, semaRoundOpUnaryTag(sema->zir->inst_datas[inst].extended.small), semaResolveRef(rop_operand_ref, sema->inst_values), semaResolveRefType(rop_operand_ref, sema->inst_types), sema->insts, block, &sema->inst_types[inst]); return; } rop_dest_ty = ipOptEuBaseType(sema->ip, rop_dest_ty); uint32_t rop_dest_scalar = rop_dest_ty; IpArrayInfo rop_dest_vec = { 0 }; IpArrayStatus rop_dest_arr_status = ipArrayInfo(sema->ip, rop_dest_ty, &rop_dest_vec); if (rop_dest_arr_status == IP_ARR_MALFORMED) UNREACHABLE("round_op: malformed destination vector"); if (rop_dest_arr_status == IP_ARR_OK && rop_dest_vec.is_vector) rop_dest_scalar = rop_dest_vec.child; uint32_t rop_operand_ty = semaResolveRefType(rop_operand_ref, sema->inst_types); uint32_t rop_operand_scalar = rop_operand_ty; IpArrayInfo rop_operand_vec = { 0 }; IpArrayStatus rop_operand_arr_status = ipArrayInfo(sema->ip, rop_operand_ty, &rop_operand_vec); if (rop_operand_arr_status == IP_ARR_MALFORMED) UNREACHABLE("round_op: malformed operand vector"); if (rop_operand_arr_status == IP_ARR_OK && rop_operand_vec.is_vector) rop_operand_scalar = rop_operand_vec.child; if (rop_dest_arr_status == IP_ARR_OK || rop_operand_arr_status == IP_ARR_OK) { if (rop_dest_arr_status != IP_ARR_OK || rop_operand_arr_status != IP_ARR_OK || !rop_dest_vec.is_vector || !rop_operand_vec.is_vector || rop_dest_vec.len != rop_operand_vec.len) UNIMPLEMENTED("round_op: vector/scalar mismatch"); } uint32_t rop_operand_bits = 0; if (rop_operand_scalar != ZIR_REF_COMPTIME_FLOAT_TYPE && !ztSimpleFloatBits(rop_operand_scalar, &rop_operand_bits)) COMPILE_ERROR_UPSTREAM("round_op: expected float or vector type"); uint32_t rop_operand_val = semaResolveRef(rop_operand_ref, sema->inst_values); uint16_t rop_mode = sema->zir->inst_datas[inst].extended.small; ZirInstTag rop_unary = semaRoundOpUnaryTag(rop_mode); uint32_t rop_dest_bits = 0; uint32_t rop_int_bits; bool rop_int_signed; if (rop_dest_scalar != ZIR_REF_COMPTIME_FLOAT_TYPE && !ztSimpleFloatBits(rop_dest_scalar, &rop_dest_bits)) { // The `.int, .comptime_int` destination. if (rop_dest_scalar != ZIR_REF_COMPTIME_INT_TYPE && !ztIntInfo( sema->ip, rop_dest_scalar, &rop_int_bits, &rop_int_signed)) COMPILE_ERROR_UPSTREAM("round_op: expected integer, " "float, or vector type"); // `"if (sema.resolveValue(operand)) |operand_val| { // const result_val = try sema.intFromFloat(block, // operand_src, operand_val, operand_ty, dest_ty, mode);"` if (!semaIsAirInstRef(rop_operand_val)) { sema->inst_values[inst] = semaRoundIntFromFloat( sema->ip, rop_mode, rop_operand_val, rop_dest_ty); sema->inst_types[inst] = rop_dest_ty; return; } if (rop_dest_scalar == ZIR_REF_COMPTIME_INT_TYPE) COMPILE_ERROR_UPSTREAM( "round_op: casted to comptime_int needs comptime"); // `u0`: the splatted zero (its safety check is off in // ReleaseSmall). if (rop_dest_scalar == ZIR_REF_U0_TYPE) { sema->inst_values[inst] = semaSplatU0Zero(sema->ip, rop_dest_ty); sema->inst_types[inst] = rop_dest_ty; return; } // `uncasted_result`: the operand for `.truncate`, else // round/floor/ceil of it; then `int_from_float` // (`.strict`, not safe). uint32_t rop_uncasted = rop_operand_val; if (rop_unary != ZIR_INST_TRUNC) rop_uncasted = semaAnalyzeUnaryMath(sema->zcu, rop_unary, rop_operand_val, rop_operand_ty, sema->insts, block, &sema->inst_types[inst]); sema->inst_values[inst] = semaEmitTyOp(sema->insts, block, AIR_INST_INT_FROM_FLOAT, rop_dest_ty, rop_uncasted); sema->inst_types[inst] = rop_dest_ty; return; } if (!semaIsAirInstRef(rop_operand_val)) { sema->inst_values[inst] = semaRoundCastComptime( sema->zcu, rop_mode, rop_operand_val, rop_dest_ty); sema->inst_types[inst] = rop_dest_ty; return; } uint32_t rop_coerced_val = rop_operand_val; if (rop_operand_ty != rop_dest_ty) { if (rop_dest_scalar == ZIR_REF_COMPTIME_FLOAT_TYPE || rop_operand_scalar == ZIR_REF_COMPTIME_FLOAT_TYPE) UNIMPLEMENTED("round_op: comptime_float coercion"); AirInstData rop_cast_data; memset(&rop_cast_data, 0, sizeof(rop_cast_data)); rop_cast_data.pair.a = rop_dest_ty; rop_cast_data.pair.b = rop_operand_val; AirInstTag rop_cast_tag; if (rop_dest_bits > rop_operand_bits) rop_cast_tag = AIR_INST_FPEXT; else if (rop_dest_bits == rop_operand_bits) rop_cast_tag = AIR_INST_BIT_CAST; else COMPILE_ERROR_UPSTREAM("round_op: narrowing float destination"); uint32_t rop_cast_idx = semaAirInstAppend(sema->insts, rop_cast_tag, rop_cast_data); u32ListAppend(block, rop_cast_idx); rop_coerced_val = AIR_REF_INST_BASE + rop_cast_idx; } sema->inst_values[inst] = semaAnalyzeUnaryMath(sema->zcu, rop_unary, rop_coerced_val, rop_dest_ty, sema->insts, block, &sema->inst_types[inst]); } // Sema.zig:zirFloatFromInt, PerThread.zig:undefValue // Sema.zirFloatFromInt. ZIR data: pl_node with Bin payload {lhs=dest_type_ref, // rhs=operand_ref}. Runtime operands lower to AIR float_from_int after // validating both types. static void zirFloatFromInt(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ffi_dest_ty; uint32_t ffi_op_ref = semaCastDestOperand(sema, inst, &ffi_dest_ty); uint32_t ffi_op_val = semaResolveRef(ffi_op_ref, sema->inst_values); // `"try sema.checkFloatType(block, src, dest_scalar_ty);"`: // comptime_float or a fixed float, of the scalar type. uint32_t ffi_dst_bits; uint32_t ffi_dest_scalar = semaFloatDestScalar(sema->ip, ffi_dest_ty, &ffi_dst_bits); if (!semaIsAirInstRef(ffi_op_val)) { // `"if (operand_val.isUndef(zcu)) return .fromValue(try // pt.undefValue(dest_ty));"`, then one scalar or the per-lane loop. uint32_t res = ipIsUndef(sema->ip, ffi_op_val) ? ipGetUndef(sema->ip, ffi_dest_ty) : semaCastLanes( sema->ip, ffi_op_val, ffi_dest_ty, semaFloatFromIntLane); if (res == IP_INDEX_NONE) UNIMPLEMENTED("float_from_int: comptime conversion"); sema->inst_values[inst] = res; sema->inst_types[inst] = ffi_dest_ty; return; } if (ffi_dest_scalar == ZIR_REF_COMPTIME_FLOAT_TYPE) COMPILE_ERROR_UPSTREAM( "float_from_int: casted to comptime_float needs comptime"); uint32_t ffi_op_ty = semaResolveRefType(ffi_op_ref, sema->inst_types); uint32_t ffi_op_bits; bool ffi_op_signed; if (!ztIntInfo(sema->ip, ipScalarType(sema->ip, ffi_op_ty), &ffi_op_bits, &ffi_op_signed)) UNIMPLEMENTED("float_from_int: non-int operand type"); AirInstData ffi_data; memset(&ffi_data, 0, sizeof(ffi_data)); ffi_data.pair.a = ffi_dest_ty; ffi_data.pair.b = ffi_op_val; uint32_t ffi_air_idx = semaAirInstAppend(sema->insts, AIR_INST_FLOAT_FROM_INT, ffi_data); u32ListAppend(block, ffi_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + ffi_air_idx; sema->inst_types[inst] = ffi_dest_ty; } // Sema.zig:zirPtrFromInt // Sema.zirPtrFromInt: a comptime address becomes ptr_int (or null optional); a // runtime address emits AIR ptr_from_int. The fixed wasm32 target has no // function-pointer address mask, so alignment is checked on the raw address // for every pointee. ZIR data: pl_node with Bin { lhs=dest type, rhs=operand // }. static void zirPtrFromInt(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pfi_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pfi_pp, &extra); uint32_t pfi_dest_ref = extra.lhs; uint32_t pfi_op_ref = extra.rhs; uint32_t pfi_dest_ty = semaResolveRef(pfi_dest_ref, sema->inst_values); uint32_t pfi_op_val = semaResolveRef(pfi_op_ref, sema->inst_values); if (semaIsAirInstRef(pfi_dest_ty)) UNIMPLEMENTED("ptr_from_int: runtime dest type"); // `"try sema.ensureLayoutResolved(elem_ty, src, .align_check);"` { IpPtrInfo pfi_info; uint32_t pfi_ptr_ty = pfi_dest_ty; if (ztIsZigTypeTag(sema->ip, pfi_ptr_ty, ZIG_TYPE_TAG_OPTIONAL)) pfi_ptr_ty = ipOptionalChild(sema->ip, pfi_ptr_ty); if (ztPtrOrSliceInfo(sema->ip, pfi_ptr_ty, &pfi_info)) ensureLayoutResolved(sema->zcu, pfi_info.child); } if (!semaIsAirInstRef(pfi_op_val)) { sema->inst_values[inst] = semaPtrFromIntComptime(sema->zcu, pfi_dest_ty, pfi_op_val); sema->inst_types[inst] = pfi_dest_ty; return; } AirInstData pfi_data; memset(&pfi_data, 0, sizeof(pfi_data)); pfi_data.pair.a = pfi_dest_ty; // ty_op.ty = dest pointer type pfi_data.pair.b = pfi_op_val; // ty_op.operand uint32_t pfi_air_idx = semaAirInstAppend(sema->insts, AIR_INST_PTR_FROM_INT, pfi_data); u32ListAppend(block, pfi_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + pfi_air_idx; sema->inst_types[inst] = pfi_dest_ty; } // Sema.zig:zirErrorCast // Sema.zirErrorCast: BinNode payload [node, lhs = dest type, rhs = operand]; // `resolveDestType(.remove_opt)`. No safety check in this profile // (`block.wantSafety()` is false), so a runtime operand is `error_cast` to the // destination -- or, from an error set into an error union, `error_cast` to // the destination set then `wrap_errunion_err`. static void zirErrorCast(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ec_p = sema->zir->inst_datas[inst].extended.operand; ZirInstBinNode extra; zirExtraData(sema->zir, ec_p, &extra); uint32_t ec_dest = semaResolveRef(extra.lhs, sema->inst_values); uint32_t ec_op_ref = extra.rhs; uint32_t ec_op = semaResolveRef(ec_op_ref, sema->inst_values); if (!semaRefIsInterned(ec_dest)) UNIMPLEMENTED("error_cast: runtime dest type"); if (ztIsZigTypeTag(sema->ip, ec_dest, ZIG_TYPE_TAG_OPTIONAL)) ec_dest = sema->ip->item_datas[ec_dest]; uint32_t ec_op_ty = semaResolveRefType(ec_op_ref, sema->inst_types); if (!semaIsAirInstRef(ec_op)) { ec_op_ty = ipTypeOf(sema->ip, ec_op); } SemaErrorCastResult ec = semaErrorCastPrepare(sema->ip, ec_dest, ec_op_ty, sema->fn_ret_ty); if (!semaIsAirInstRef(ec_op)) { uint32_t ec_val = semaErrorCastValue(sema->ip, ec, ec_dest, ec_op); if (ec_val == IP_INDEX_NONE) UNIMPLEMENTED("error_cast: comptime operand"); sema->inst_values[inst] = ec_val; sema->inst_types[inst] = ec_dest; return; } bool ec_wrap = !semaIsErrorUnionType(sema->ip, ec_op_ty) && semaIsErrorUnionType(sema->ip, ec_dest); AirInstData ec_data; memset(&ec_data, 0, sizeof(ec_data)); ec_data.pair.a = ec_wrap ? semaErrorCastSet(sema->ip, ec_dest) : ec_dest; ec_data.pair.b = ec_op; uint32_t ec_idx = semaAirInstAppend(sema->insts, AIR_INST_ERROR_CAST, ec_data); u32ListAppend(block, ec_idx); if (ec_wrap) { memset(&ec_data, 0, sizeof(ec_data)); ec_data.pair.a = ec_dest; ec_data.pair.b = AIR_REF_INST_BASE + ec_idx; ec_idx = semaAirInstAppend( sema->insts, AIR_INST_WRAP_ERRUNION_ERR, ec_data); u32ListAppend(block, ec_idx); } sema->inst_values[inst] = AIR_REF_INST_BASE + ec_idx; sema->inst_types[inst] = ec_dest; } // Sema.zig:zirPtrCastFull // Sema.zirPtrCastFull -> ptrCastFull. ZIR extended: small = FullPtrCastFlags, // operand = payload index of a BinNode { node (i32), lhs=dest_type_ref, // rhs=operand_ref }. static void zirPtrCastFull(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; ZirInstBinNode extra; zirExtraData(sema->zir, ext_operand, &extra); uint32_t pcf_dest_ref = extra.lhs; uint32_t pcf_op_ref = extra.rhs; uint32_t pcf_dest_val = semaResolveRef(pcf_dest_ref, sema->inst_values); if (semaIsAirInstRef(pcf_dest_val)) UNIMPLEMENTED("ptr_cast_full: runtime dest type"); sema->inst_values[inst] = semaAnalyzePtrCastFull(sema->zcu, sema->insts, block, sema->air_extra, sema->zir->inst_datas[inst].extended.small, pcf_dest_val, semaResolveRef(pcf_op_ref, sema->inst_values), semaResolveRefType(pcf_op_ref, sema->inst_types), &sema->inst_types[inst]); } // Sema.zig:zirPtrCast // Sema.zirPtrCast -> ptrCastFull(.{.ptr_cast=true}). ZIR data: // pl_node with Bin { lhs=dest_type_ref, rhs=operand_ref }. static void zirPtrCast(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t pc_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, pc_pp, &extra); uint32_t pc_dest_val = semaResolveRef(extra.lhs, sema->inst_values); if (semaIsAirInstRef(pc_dest_val)) UNIMPLEMENTED("ptr_cast: runtime dest type"); sema->inst_values[inst] = semaAnalyzePtrCastFull(sema->zcu, sema->insts, block, sema->air_extra, ZIR_PTR_CAST_FLAG_PTR, pc_dest_val, semaResolveRef(extra.rhs, sema->inst_values), semaResolveRefType(extra.rhs, sema->inst_types), &sema->inst_types[inst]); } // Sema.zig:zirPtrCastNoDest // Sema.zirPtrCastNoDest: @constCast/@volatileCast derive the destination by // clearing the requested flags from the operand pointer type, then either // retype a comptime pointer value or emit AIR ptr_cast for a runtime pointer. static void zirPtrCastNoDest(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; ZirInstUnNode extra; zirExtraData(sema->zir, ext_operand, &extra); uint16_t pcnd_flags = sema->zir->inst_datas[inst].extended.small; if (pcnd_flags == 0 || (pcnd_flags & ~(ZIR_PTR_CAST_FLAG_CONST | ZIR_PTR_CAST_FLAG_VOLATILE)) != 0) UNIMPLEMENTED("ptr_cast_no_dest: unsupported flags"); uint32_t pcnd_op_ref = extra.operand; uint32_t pcnd_op_val = semaResolveRef(pcnd_op_ref, sema->inst_values); uint32_t pcnd_op_ty = semaResolveRefType(pcnd_op_ref, sema->inst_types); if (!semaIsAirInstRef(pcnd_op_val)) { uint32_t pcnd_val_ty = ipTypeOf(sema->ip, pcnd_op_val); if (pcnd_val_ty != IP_INDEX_NONE) pcnd_op_ty = pcnd_val_ty; uint32_t pcnd_casted = ctPtrNoDestCastValue(sema->ip, pcnd_op_val, pcnd_op_ty, pcnd_flags, "ptr_cast_no_dest: unsupported comptime pointer"); if (pcnd_casted == IP_INDEX_NONE) UNIMPLEMENTED("ptr_cast_no_dest: comptime non-pointer operand"); sema->inst_values[inst] = pcnd_casted; sema->inst_types[inst] = ipTypeOf(sema->ip, pcnd_casted); return; } if (ztIsSlice(sema->ip, pcnd_op_ty)) { uint32_t pcnd_many = sema->ip->item_datas[pcnd_op_ty]; uint32_t pcnd_dest_many = ctPtrNoDestCastType(sema->ip, pcnd_many, pcnd_flags); if (pcnd_dest_many == IP_INDEX_NONE) UNIMPLEMENTED("ptr_cast_no_dest: slice inner pointer"); uint32_t pcnd_dest_ty = ipGetSliceType(sema->ip, pcnd_dest_many); AirInstData pcnd_data; memset(&pcnd_data, 0, sizeof(pcnd_data)); pcnd_data.pair.a = pcnd_dest_ty; pcnd_data.pair.b = pcnd_op_val; uint32_t pcnd_air_idx = semaAirInstAppend(sema->insts, AIR_INST_PTR_CAST, pcnd_data); u32ListAppend(block, pcnd_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + pcnd_air_idx; sema->inst_types[inst] = pcnd_dest_ty; return; } if (ztIsZigTypeTag(sema->ip, pcnd_op_ty, ZIG_TYPE_TAG_OPTIONAL)) UNIMPLEMENTED("ptr_cast_no_dest: runtime optional operand"); uint32_t pcnd_dest_ty = ctPtrNoDestCastType(sema->ip, pcnd_op_ty, pcnd_flags); if (pcnd_dest_ty == IP_INDEX_NONE) UNIMPLEMENTED("ptr_cast_no_dest: non-pointer operand"); if (pcnd_dest_ty == pcnd_op_ty) { sema->inst_values[inst] = pcnd_op_val; sema->inst_types[inst] = pcnd_dest_ty; return; } AirInstData pcnd_data; memset(&pcnd_data, 0, sizeof(pcnd_data)); pcnd_data.pair.a = pcnd_dest_ty; pcnd_data.pair.b = pcnd_op_val; uint32_t pcnd_air_idx = semaAirInstAppend(sema->insts, AIR_INST_PTR_CAST, pcnd_data); u32ListAppend(block, pcnd_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + pcnd_air_idx; sema->inst_types[inst] = pcnd_dest_ty; } // Sema.zig:zirTruncate // Sema.zirTruncate. ZIR data: pl_node with Bin payload { lhs=dest_type_ref, // rhs=operand_ref }; the destination is `resolveDestType(..., .remove_eu_opt, // ...)`. A runtime operand ends in block.addTyOp(.trunc, dest_ty, operand). static void zirTruncate(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t dest_ty; uint32_t operand_ref = semaCastDestOperand(sema, inst, &dest_ty); uint32_t operand_val = semaResolveRef(operand_ref, sema->inst_values); if (!semaIsAirInstRef(operand_val)) { sema->inst_values[inst] = semaTruncateComptime(sema->zcu, operand_val, dest_ty); return; } uint32_t tr_op_ty = semaResolveRefType(operand_ref, sema->inst_types); uint32_t tr_opv; if (semaTruncateIsCoerce(sema->ip, tr_op_ty, dest_ty, &tr_opv)) { sema->inst_values[inst] = semaCoerceExtraRuntime(sema->zcu, operand_val, tr_op_ty, dest_ty, sema->insts, block, sema->air_extra, "truncate"); return; } if (tr_opv != IP_INDEX_NONE) { sema->inst_values[inst] = tr_opv; return; } AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = dest_ty; // ty_op.ty data.pair.b = operand_val; // ty_op.operand uint32_t air_idx = semaAirInstAppend(sema->insts, AIR_INST_TRUNC, data); u32ListAppend(block, air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + air_idx; sema->inst_types[inst] = dest_ty; } // Sema.zig:zirBitCount // The result is `smallestUnsignedInt(bits)` (a vector of it for a vector // operand); a comptime operand folds per element, an undef one is undef, a // runtime one is the ty_op. static void zirBitCount( SemaCtx* sema, U32List* block, uint32_t inst, ZirInstTag tag) { InternPool* ip = sema->ip; uint32_t bc_op_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t operand = semaResolveRef(bc_op_ref, sema->inst_values); uint32_t operand_ty = semaIsAirInstRef(operand) ? semaResolveRefType(bc_op_ref, sema->inst_types) : ipComptimeValueType(ip, (uint32_t)operand); uint32_t scalar_ty = ipScalarType(ip, operand_ty); uint32_t bits; bool is_signed; if (!ztIntInfo(ip, scalar_ty, &bits, &is_signed)) COMPILE_ERROR_UPSTREAM("zirBitCount: expected integer or vector"); uint32_t result_scalar = ipSmallestUnsignedInt(ip, bits); IpArrayInfo vec; bool is_vec = ipArrayInfo(ip, operand_ty, &vec) == IP_ARR_OK && vec.is_vector; uint32_t result_ty = is_vec ? ipGetVectorType(ip, vec.len, result_scalar) : result_scalar; sema->inst_types[inst] = result_ty; if (semaIsAirInstRef(operand)) { AirInstTag air_tag = tag == ZIR_INST_CLZ ? AIR_INST_CLZ : tag == ZIR_INST_CTZ ? AIR_INST_CTZ : AIR_INST_POPCOUNT; sema->inst_values[inst] = semaEmitTyOp(sema->insts, block, air_tag, result_ty, operand); return; } if (operand >= ip->items_len) UNIMPLEMENTED("zirBitCount: unreadable comptime operand"); uint32_t val = operand; if (ipIsUndef(ip, val)) { sema->inst_values[inst] = ipGetUndef(ip, result_ty); return; } if (!is_vec) { sema->inst_values[inst] = ipInternIntResult( ip, valBitCount(ip, tag, val, bits), result_scalar); return; } uint32_t* elems = ARR_INIT(uint32_t, vec.len ? vec.len : 1u); for (uint32_t i = 0; i < vec.len; i++) elems[i] = ipInternIntResult(ip, valBitCount(ip, tag, valElemValue(ip, val, i, "zirBitCount: vector lane"), bits), result_scalar); sema->inst_values[inst] = ipGetAggregate(ip, result_ty, elems, vec.len); free(elems); } // (part of) Sema.zig:zirByteSwap, Sema.zig:zirBitReverse // Sema.zirByteSwap / zirBitReverse → block.addTyOp(tag, ty, op). ZIR data: // un_node {operand}. Result type == operand type. static void semaBitPermute( SemaCtx* sema, U32List* block, uint32_t inst, AirInstTag air_tag) { ZirInstTag tag = air_tag == AIR_INST_BYTE_SWAP ? ZIR_INST_BYTE_SWAP : ZIR_INST_BIT_REVERSE; uint32_t bsbr_op_ref = sema->zir->inst_datas[inst].un_node.operand; uint32_t bsbr_op_val = semaResolveRef(bsbr_op_ref, sema->inst_values); uint32_t bsbr_op_ty = semaResolveRefType(bsbr_op_ref, sema->inst_types); semaCheckBitPermuteOperand(sema->ip, tag, bsbr_op_ty); if (!semaIsAirInstRef(bsbr_op_val)) { uint32_t bsbr_folded = bsbr_op_val == INST_VALUE_UNSET ? IP_INDEX_NONE : arithValueBitPermute(sema->ip, tag, bsbr_op_val, bsbr_op_ty); if (bsbr_folded == IP_INDEX_NONE) UNIMPLEMENTED( "byte_swap: byte_swap/bit_reverse: comptime operand"); sema->inst_values[inst] = bsbr_folded; sema->inst_types[inst] = bsbr_op_ty; return; } sema->inst_values[inst] = semaEmitTyOp(sema->insts, block, air_tag, bsbr_op_ty, bsbr_op_val); sema->inst_types[inst] = bsbr_op_ty; } // Sema.zig:zirByteSwap static void zirByteSwap(SemaCtx* sema, U32List* block, uint32_t inst) { semaBitPermute(sema, block, inst, AIR_INST_BYTE_SWAP); } // Sema.zig:zirBitReverse static void zirBitReverse(SemaCtx* sema, U32List* block, uint32_t inst) { semaBitPermute(sema, block, inst, AIR_INST_BIT_REVERSE); } // Sema.zig:bitOffsetOf // The bit offset of a named struct field, ZIR pl_node -> Bin { lhs = type, // rhs = field_name }. zirBitOffsetOf and zirOffsetOf comptime-fold it to a // `comptime_int` with no AIR, so a subsequent coercion re-interns at the dest // type (e.g. `usize`). static uint64_t bitOffsetOf(SemaCtx* sema, uint32_t inst) { uint32_t oo_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, oo_pp, &extra); uint32_t oo_ty_ref = extra.lhs; uint32_t oo_name_ref = extra.rhs; uint32_t oo_struct_ty = semaResolveRef(oo_ty_ref, sema->inst_values); if (semaIsAirInstRef(oo_struct_ty)) UNIMPLEMENTED("offset_of: runtime type operand"); bool oo_tuple = ztIsTuple(sema->ip, oo_struct_ty); if (!ipTagIsAnyStruct(sema->ip->item_tags[oo_struct_ty]) && !oo_tuple) UNIMPLEMENTED("offset_of: non-struct type"); // resolveConstStringIntern: str result (*const [N:0]u8 uav) -> // coerce to `[]const u8` -> sliceToIpString (the field-name // String). uint32_t oo_name_val = semaResolveRef(oo_name_ref, sema->inst_values); if (semaIsAirInstRef(oo_name_val)) UNIMPLEMENTED("offset_of: runtime field name"); uint32_t oo_fname = resolveConstStringIntern(sema->zcu, oo_name_val); uint32_t oo_fidx; if (oo_tuple) { // `"if (ty.isTuple(zcu)) blk: { ... break :blk try // sema.tupleFieldIndex(...)"`. oo_fidx = tupleFieldIndex( sema->ip, oo_struct_ty, ipStringBytes(sema->ip, oo_fname)); } else { // ensureLayoutResolved(ty): persist the struct layout (offsets). if (!resolveStructLayout(sema->zcu, sema->ip, oo_struct_ty)) UNIMPLEMENTED("offset_of: struct layout resolution failed"); oo_fidx = ipStructFieldIndex(sema->ip, oo_struct_ty, oo_fname); if (oo_fidx == UINT32_MAX) UNIMPLEMENTED("offset_of: field not found"); } // The packed arm sums preceding field bit widths; every other layout is // `"return ty.structFieldOffset(field_index, zcu) * 8;"`. return ipStructIsPacked(sema->ip, oo_struct_ty) ? zcuStructPackedFieldBitOffset( sema->zcu, sema->ip, oo_struct_ty, oo_fidx) : ztStructFieldOffset(sema->zcu, sema->ip, oo_struct_ty, oo_fidx) * 8u; } // Sema.zig:zirBitOffsetOf static void zirBitOffsetOf(SemaCtx* sema, uint32_t inst) { sema->inst_values[inst] = ipGetIntComptimeIntU64(sema->ip, bitOffsetOf(sema, inst)); } // Sema.zig:zirOffsetOf static void zirOffsetOf(SemaCtx* sema, uint32_t inst) { sema->inst_values[inst] = ipGetIntComptimeIntU64(sema->ip, bitOffsetOf(sema, inst) / 8u); } // Sema.zig:zirCmpxchg // Sema.zirCmpxchg: @cmpxchgWeak/Strong(T, ptr, expected, new, success_order, // failure_order). The ZIR payload omits T; AstGen typed `expected` under that // result location, so its type is the atomic element type. Runtime path emits // AIR cmpxchg_weak/strong with ty_pl.ty = ?T and extra Air.Cmpxchg { ptr, // expected, new, flags }. The interpreter executes it single-threaded. static void zirCmpxchg(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t cx_pp = sema->zir->inst_datas[inst].extended.operand; ZirInstCmpxchg extra; zirExtraData(sema->zir, cx_pp, &extra); uint32_t cx_ptr_ref = extra.ptr; uint32_t cx_expected_ref = extra.expected_value; uint32_t cx_new_ref = extra.new_value; uint32_t cx_success_ref = extra.success_order; uint32_t cx_failure_ref = extra.failure_order; uint32_t cx_ptr_val = semaResolveRef(cx_ptr_ref, sema->inst_values); uint32_t cx_expected_val = semaResolveRef(cx_expected_ref, sema->inst_values); uint32_t cx_new_val = semaResolveRef(cx_new_ref, sema->inst_values); uint32_t cx_success_val = semaResolveRef(cx_success_ref, sema->inst_values); uint32_t cx_failure_val = semaResolveRef(cx_failure_ref, sema->inst_values); if (semaIsAirInstRef(cx_success_val)) UNIMPLEMENTED("cmpxchg: runtime success order"); if (semaIsAirInstRef(cx_failure_val)) UNIMPLEMENTED("cmpxchg: runtime failure order"); uint32_t cx_expected_air; uint32_t cx_elem_ty; if (semaIsAirInstRef(cx_expected_val)) { cx_elem_ty = semaResolveRefType(cx_expected_ref, sema->inst_types); cx_expected_air = cx_expected_val; } else { cx_elem_ty = ipComptimeValueType(sema->ip, cx_expected_val); if (cx_elem_ty == ZIR_REF_COMPTIME_INT_TYPE) UNIMPLEMENTED("cmpxchg: untyped expected value"); cx_expected_air = cx_expected_val; } ensureLayoutResolved(sema->zcu, cx_elem_ty); // checkAtomicPtrOperand uint32_t cx_ptr_ty = IP_INDEX_NONE; uint32_t cx_ptr_air = cx_ptr_val; if (!semaIsAirInstRef(cx_ptr_val)) { if (cx_ptr_val >= sema->ip->items_len || sema->ip->item_tags[cx_ptr_val] != IP_TAG_PTR_NAV) UNIMPLEMENTED("cmpxchg: comptime pointer shape"); semaRuntimeDataPtrNav(sema->ip, cx_ptr_val, true, &cx_ptr_ty, NULL); } else { cx_ptr_ty = semaResolveRefType(cx_ptr_ref, sema->inst_types); } if (sema->ip->item_tags[cx_ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("cmpxchg: pointer operand not pointer"); uint32_t cx_ptr_payload = sema->ip->item_datas[cx_ptr_ty]; uint32_t cx_ptr_flags = sema->ip->extra[cx_ptr_payload + IP_TYPE_POINTER_FLAGS]; if (IP_PTR_FLAGS_SIZE(cx_ptr_flags) != IP_PTR_SIZE_ONE) UNIMPLEMENTED("cmpxchg: pointer operand not single pointer"); if ((cx_ptr_flags & IP_PTR_FLAGS_IS_CONST) != 0) UNIMPLEMENTED("cmpxchg: pointer operand is const"); if (sema->ip->extra[cx_ptr_payload + IP_TYPE_POINTER_PACKED_OFFSET] != 0) UNIMPLEMENTED("cmpxchg: packed pointer operand"); uint32_t cx_child_ty = sema->ip->extra[cx_ptr_payload + IP_TYPE_POINTER_CHILD]; if (cx_child_ty != cx_elem_ty) UNIMPLEMENTED("cmpxchg: pointer child/type mismatch"); uint64_t cx_atomic_align; if (!semaCmpxchgElemSupported(sema->zcu, cx_elem_ty, &cx_atomic_align)) UNIMPLEMENTED("cmpxchg: unsupported atomic element type"); uint32_t cx_ptr_align = IP_PTR_FLAGS_ALIGN(cx_ptr_flags); if (cx_ptr_align != IP_PTR_ALIGN_NONE && cx_ptr_align < ztLog2PowerOfTwoU64(cx_atomic_align)) UNIMPLEMENTED("cmpxchg: pointer alignment too small"); uint32_t cx_new_air; if (semaIsAirInstRef(cx_new_val)) { uint32_t cx_new_ty = semaResolveRefType(cx_new_ref, sema->inst_types); cx_new_air = cx_new_val; if (cx_new_ty != cx_elem_ty) { // Sema.zirCmpxchg coerces the new value to the // atomic element type after validating the pointer. // Reuse the bounded runtime store coercions zig1 // already models: the compiler's shape is a pointer // payload wrapped into a pointer-like optional. cx_new_air = semaCoerceExtraRuntime(sema->zcu, cx_new_air, cx_new_ty, cx_elem_ty, sema->insts, block, sema->air_extra, "cmpxchg: runtime new value type mismatch"); } } else { cx_new_air = semaCoerceComptime(sema->zcu, sema->ip, cx_new_val, cx_elem_ty); } uint32_t cx_flags = semaCmpxchgFlags(sema->zcu, cx_success_val, cx_failure_val); uint32_t cx_opt_ty = ipGetOptionalType(sema->ip, cx_elem_ty); uint32_t cx_payload = sema->air_extra->len; u32ListAppend(sema->air_extra, cx_ptr_air); u32ListAppend(sema->air_extra, cx_expected_air); u32ListAppend(sema->air_extra, cx_new_air); u32ListAppend(sema->air_extra, cx_flags); AirInstData cx_data; memset(&cx_data, 0, sizeof(cx_data)); cx_data.pair.a = cx_opt_ty; cx_data.pair.b = cx_payload; AirInstTag cx_tag = sema->zir->inst_datas[inst].extended.small == 0 ? AIR_INST_CMPXCHG_WEAK : AIR_INST_CMPXCHG_STRONG; uint32_t cx_air_idx = semaAirInstAppend(sema->insts, cx_tag, cx_data); u32ListAppend(block, cx_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + cx_air_idx; sema->inst_types[inst] = cx_opt_ty; } // Sema.zig:zirSplat // Sema.zirSplat. The destination is `".array, .vector => {},"` and, for a // comptime-known scalar, a vector leaves the `full:` block immediately (`"if // (dest_ty.zigTypeTag(zcu) == .vector) break :full;"`) and interns // `"pt.aggregateSplatValue(dest_ty, scalar_val)"` -- the same repeated // aggregate an array without a mismatched sentinel gets, which is what // ipGetAggregate produces from all-equal elements. A runtime scalar is // `".vector, .array => return block.addTyOp(.splat, dest_ty, scalar)"`. The // destination is `resolveDestType(..., .remove_eu_opt, ...)`. static void zirSplat(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t sp_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, sp_pp, &extra); uint32_t sp_ty_ref = extra.lhs; uint32_t sp_scalar_ref = extra.rhs; uint32_t sp_ty_val = semaResolveRef(sp_ty_ref, sema->inst_values); uint32_t sp_scalar_val = semaResolveRef(sp_scalar_ref, sema->inst_values); if (!semaRefIsInterned(sp_ty_val)) UNIMPLEMENTED("splat: non-comptime result type"); uint32_t sp_ty = semaResolveRemoveEuOptDestType(sema->ip, sp_ty_val); IpArrayInfo sp_info; if (ipArrayInfo(sema->ip, sp_ty, &sp_info) != IP_ARR_OK) COMPILE_ERROR_UPSTREAM("splat: expected array or vector type"); uint32_t sp_len = sp_info.len; uint32_t sp_elem_ty = sp_info.child; // Sema.zirSplat: an empty array/vector has one possible value, // even when its already-coerced scalar operand is runtime. Do // not retain a meaningless runtime dependency in AIR. if (sp_len == 0) { sema->inst_values[inst] = ipGetAggregate(sema->ip, sp_ty, NULL, 0); sema->inst_types[inst] = sp_ty; return; } if (semaIsAirInstRef(sp_scalar_val)) { // `"const scalar = try sema.coerce(block, scalar_ty, // operand, scalar_src);"`, then one `splat` of the array or // vector (a sentinel comes from the result type). sp_scalar_val = semaCoerceExtraRuntime(sema->zcu, sp_scalar_val, semaResolveRefType(sp_scalar_ref, sema->inst_types), sp_elem_ty, sema->insts, block, sema->air_extra, "splat"); sema->inst_values[inst] = semaEmitTyOp( sema->insts, block, AIR_INST_SPLAT, sp_ty, sp_scalar_val); sema->inst_types[inst] = sp_ty; return; } // `splat_op_result_ty` makes the element type the operand's // result location, so the scalar reaches it through // Sema.coerceExtra's `.int, .comptime_int` arm -- which refuses // a value the destination cannot represent // (`"if (!val.intFitsInType(dest_ty, null, zcu))"`) instead of // truncating it, as the interning helpers below would. if (!valIntFitsInType(sema->ip, sp_scalar_val, sp_elem_ty)) COMPILE_ERROR_UPSTREAM( "splat: element type cannot represent integer value"); uint32_t sp_elem = semaCoerceComptime(sema->zcu, sema->ip, sp_scalar_val, sp_elem_ty); sema->inst_types[inst] = sp_ty; if (sp_info.sentinel == IP_INDEX_NONE || sp_info.sentinel == sp_elem) { // `"pt.aggregateSplatValue(dest_ty, scalar_val)"` sema->inst_values[inst] = ipGetRepeated(sema->ip, sp_ty, sp_elem); return; } // `full:` "This is a array with non-zero length and a sentinel which // does not match the element. We have to use the full `elems` // representation." uint32_t* sp_elems = ARR_INIT(uint32_t, sp_len + 1u); for (uint32_t i = 0; i < sp_len; i++) sp_elems[i] = sp_elem; sp_elems[sp_len] = sp_info.sentinel; sema->inst_values[inst] = ipGetAggregate(sema->ip, sp_ty, sp_elems, sp_len + 1u); free(sp_elems); } // Sema.zig:zirReduce // Sema.zirReduce: `@reduce(.Op, v)` folds a vector to one scalar. ZIR pl_node // -> Bin { lhs = operation, rhs = operand }. static void zirReduce(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t rd_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, rd_pp, &extra); uint32_t rd_op_ref = extra.lhs; uint32_t rd_operand_ref = extra.rhs; uint32_t rd_op_val = semaResolveRef(rd_op_ref, sema->inst_values); uint32_t rd_operand_val = semaResolveRef(rd_operand_ref, sema->inst_values); if (!semaRefIsInterned(rd_op_val)) UNIMPLEMENTED("reduce: runtime operation"); uint32_t rd_op, rd_child; uint32_t rd_folded = semaReduce(sema->zcu, rd_op_val, semaResolveRefType(rd_operand_ref, sema->inst_types), rd_operand_val, &rd_op, &rd_child); if (rd_folded != IP_INDEX_NONE) { sema->inst_values[inst] = rd_folded; sema->inst_types[inst] = rd_child; return; } // `"return block.addReduce(operand, operation);"` -- AIR // `reduce` is `.{ .operand, .operation }` (src/Air.zig), and // `reduce_optimized` is float-only, so this chain never picks it. AirInstData rd_data; memset(&rd_data, 0, sizeof(rd_data)); rd_data.pair.a = rd_operand_val; rd_data.pair.b = rd_op; uint32_t rd_idx = semaAirInstAppend(sema->insts, AIR_INST_REDUCE, rd_data); u32ListAppend(block, rd_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + rd_idx; sema->inst_types[inst] = rd_child; } // Sema.zig:zirShuffle // Sema.zirShuffle: pl_node -> Shuffle { elem_type, a, b, mask }; the mask is // comptime (resolveConstValue), `a` / `b` either. static void zirShuffle(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t sh_p = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstShuffle extra; zirExtraData(sema->zir, sh_p, &extra); uint32_t sh_elem = semaResolveRef(extra.elem_type, sema->inst_values); uint32_t sh_mask = semaResolveRef(extra.mask, sema->inst_values); if (!semaRefIsInterned(sh_elem)) UNIMPLEMENTED("@shuffle: runtime element type"); if (semaIsAirInstRef(sh_mask) || sh_mask >= sema->ip->items_len) UNIMPLEMENTED("@shuffle: mask not comptime-known"); const uint32_t sh_refs[2] = { extra.a, extra.b }; uint32_t sh_v[2]; uint32_t sh_t[2]; for (uint32_t k = 0; k < 2; k++) { uint32_t ref = sh_refs[k]; sh_v[k] = semaResolveRef(ref, sema->inst_values); if (semaIsAirInstRef(sh_v[k])) sh_t[k] = semaResolveRefType(ref, sema->inst_types); else if (sh_v[k] == INST_VALUE_UNSET) UNIMPLEMENTED("@shuffle: unreadable operand"); else if (sh_v[k] < sema->ip->items_len) sh_t[k] = ipTypeOf(sema->ip, sh_v[k]); else sh_t[k] = ipTypeOfPreinterned(sh_v[k]); } sema->inst_values[inst] = analyzeShuffle(sema->zcu, sh_elem, sh_v[0], sh_t[0], sh_v[1], sh_t[1], sh_mask, sema->insts, block, sema->air_extra, &sema->inst_types[inst]); } // Sema.zig:zirSelect // Sema.zirSelect: Select payload [node, elem_type, pred, a, b]. `pred` coerces // to `@Vector(len, bool)` and `a`/`b` to `@Vector(len, elem_ty)`, `len` being // the predicate's own array/vector length; all three comptime-known fold // (`semaSelectValue`), otherwise AIR `select` (pl_op: operand = pred, payload // = Bin{a, b}). static void zirSelect(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t sl_p = sema->zir->inst_datas[inst].extended.operand; ZirInstSelect extra; zirExtraData(sema->zir, sl_p, &extra); uint32_t sl_elem = semaResolveRef(extra.elem_type, sema->inst_values); if (!semaRefIsInterned(sl_elem)) UNIMPLEMENTED("select: runtime element type"); checkVectorElemType(sema->ip, sl_elem); const uint32_t sl_refs[3] = { extra.pred, extra.a, extra.b }; uint32_t sl_vals[3]; uint32_t sl_tys[3]; bool sl_rt = false; for (uint32_t k = 0; k < 3; k++) { sl_vals[k] = semaResolveRef(sl_refs[k], sema->inst_values); sl_tys[k] = semaResolveRefType(sl_refs[k], sema->inst_types); if (semaIsAirInstRef(sl_vals[k])) sl_rt = true; else if (sl_vals[k] == INST_VALUE_UNSET) UNIMPLEMENTED("select: unreadable operand"); else sl_tys[k] = ipTypeOf(sema->ip, sl_vals[k]); } IpArrayInfo sl_pi; if (ipArrayInfo(sema->ip, sl_tys[0], &sl_pi) != IP_ARR_OK) COMPILE_ERROR_UPSTREAM("@select: expected vector or array"); uint32_t sl_len = sl_pi.len; uint32_t sl_want[3] = { ipGetVectorType(sema->ip, sl_len, ZIR_REF_BOOL_TYPE), ipGetVectorType(sema->ip, sl_len, sl_elem), 0, }; sl_want[2] = sl_want[1]; uint32_t sl_ops[3]; for (uint32_t k = 0; k < 3; k++) { if (semaIsAirInstRef(sl_vals[k])) { sl_ops[k] = semaCoerceExtraRuntime(sema->zcu, sl_vals[k], sl_tys[k], sl_want[k], sema->insts, block, sema->air_extra, "select: operand coercion"); } else { sl_ops[k] = semaCoerceComptime( sema->zcu, sema->ip, sl_vals[k], sl_want[k]); } } // `"// special case zero bit types"`: `"if (try // vec_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);"` uint32_t sl_opv = ztOnePossibleValue(sema->ip, sl_want[1]); if (sl_opv != IP_INDEX_NONE) { sema->inst_values[inst] = sl_opv; sema->inst_types[inst] = sl_want[1]; return; } // `"if (pred_val.isUndef(zcu)) return pt.undefRef(vec_ty);"` // and its `a_val`/`b_val` siblings: Any comptime-known undef // operand makes the result undef, runtime operands or not. bool sl_undef = false; for (uint32_t k = 0; k < 3; k++) if (!semaIsAirInstRef(sl_ops[k]) && ipIsUndef(sema->ip, sl_ops[k])) sl_undef = true; if (sl_rt && sl_undef) { sema->inst_values[inst] = ipGetUndef(sema->ip, sl_want[1]); sema->inst_types[inst] = sl_want[1]; return; } if (!sl_rt) { sema->inst_values[inst] = semaSelectValue( sema->ip, sl_want[1], sl_len, sl_ops[0], sl_ops[1], sl_ops[2]); sema->inst_types[inst] = sl_want[1]; return; } uint32_t sl_extra = sema->air_extra->len; u32ListAppend(sema->air_extra, sl_ops[1]); // Bin.lhs = a u32ListAppend(sema->air_extra, sl_ops[2]); // Bin.rhs = b AirInstData sl_data; memset(&sl_data, 0, sizeof(sl_data)); sl_data.pair.a = sl_ops[0]; // pl_op.operand = pred sl_data.pair.b = sl_extra; // pl_op.payload uint32_t sl_idx = semaAirInstAppend(sema->insts, AIR_INST_SELECT, sl_data); u32ListAppend(block, sl_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + sl_idx; sema->inst_types[inst] = sl_want[1]; } // Sema.zig:zirAtomicLoad // Sema.zirAtomicLoad: @atomicLoad(T, ptr, order). ZIR payload is AtomicLoad { // elem_type, ptr, ordering }. ReleaseSmall still emits an AIR atomic_load for // runtime pointers. zig1 models the valid atomic element, pointer and order // shapes, leaving user-error validation details to upstream. static void zirAtomicLoad(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t al_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstAtomicLoad extra; zirExtraData(sema->zir, al_pp, &extra); uint32_t al_elem_ref = extra.elem_type; uint32_t al_ptr_ref = extra.ptr; uint32_t al_order_ref = extra.ordering; uint32_t al_elem_ty = semaResolveRef(al_elem_ref, sema->inst_values); uint32_t al_ptr_val = semaResolveRef(al_ptr_ref, sema->inst_values); uint32_t al_order_val = semaResolveRef(al_order_ref, sema->inst_values); if (semaIsAirInstRef(al_elem_ty)) UNIMPLEMENTED("atomic_load: runtime element type"); if (semaIsAirInstRef(al_order_val)) UNIMPLEMENTED("atomic_load: runtime order"); // checkAtomicPtrOperand's `"try sema.ensureLayoutResolved(elem_ty, // elem_ty_src, .ptr_access);"` ensureLayoutResolved(sema->zcu, al_elem_ty); uint32_t al_ptr_ty = IP_INDEX_NONE; uint32_t al_ptr_air = al_ptr_val; if (!semaIsAirInstRef(al_ptr_val)) { // `zirAtomicLoad`: the one possible value, else // `pointerDeref` of any comptime pointer; its // `.runtime_load` emits the AIR below. uint32_t al_load_ty; uint32_t folded = semaAnalyzeLoadComptime(sema->zcu, al_ptr_val, &al_load_ty); if (al_load_ty != al_elem_ty) UNIMPLEMENTED("atomic_load: pointer child mismatch"); if (folded != IP_INDEX_NONE) { sema->inst_values[inst] = folded; sema->inst_types[inst] = al_elem_ty; return; } if (sema->ip->item_tags[al_ptr_val] != IP_TAG_PTR_NAV) UNIMPLEMENTED("atomic_load: runtime load of a comptime pointer"); semaRuntimeDataPtrNav(sema->ip, al_ptr_val, false, &al_ptr_ty, NULL); al_ptr_air = al_ptr_val; } else { al_ptr_ty = semaResolveRefType(al_ptr_ref, sema->inst_types); } if (sema->ip->item_tags[al_ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("atomic_load: pointer operand not pointer"); uint32_t al_child = sema->ip->extra[sema->ip->item_datas[al_ptr_ty]]; if (al_child != al_elem_ty) UNIMPLEMENTED("atomic_load: pointer child/type mismatch"); uint32_t al_order = resolveStdLangEnum(sema->zcu, al_order_val, "AtomicOrder"); if (al_order == ATOMIC_ORDER_RELEASE || al_order == ATOMIC_ORDER_ACQ_REL) UNIMPLEMENTED("atomic_load: invalid release/acq_rel order"); AirInstData al_data; memset(&al_data, 0, sizeof(al_data)); al_data.pair.a = al_ptr_air; al_data.pair.b = al_order; uint32_t al_air_idx = semaAirInstAppend(sema->insts, AIR_INST_ATOMIC_LOAD, al_data); u32ListAppend(block, al_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + al_air_idx; sema->inst_types[inst] = al_elem_ty; } // Sema.zig:zirAtomicRmw // Sema.zirAtomicRmw: @atomicRmw(T, ptr, op, operand, order). ZIR payload is // AtomicRmw { ptr, operation, operand, ordering }. The operand has already // been analyzed under the explicit element type result location; its type is // therefore the atomic element type. ReleaseSmall keeps this as AIR atomic_rmw // for runtime pointers; zig1's interpreter executes it single-threaded. static void zirAtomicRmw(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ar_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstAtomicRmw extra; zirExtraData(sema->zir, ar_pp, &extra); uint32_t ar_ptr_ref = extra.ptr; uint32_t ar_op_ref = extra.operation; uint32_t ar_operand_ref = extra.operand; uint32_t ar_order_ref = extra.ordering; uint32_t ar_ptr_val = semaResolveRef(ar_ptr_ref, sema->inst_values); uint32_t ar_op_val = semaResolveRef(ar_op_ref, sema->inst_values); uint32_t ar_operand_val = semaResolveRef(ar_operand_ref, sema->inst_values); uint32_t ar_order_val = semaResolveRef(ar_order_ref, sema->inst_values); semaEnsurePointeeLayoutResolved(sema, ar_ptr_ref); // checkAtomicPtrOperand if (semaIsAirInstRef(ar_op_val)) UNIMPLEMENTED("atomic_rmw: runtime operation"); if (semaIsAirInstRef(ar_order_val)) UNIMPLEMENTED("atomic_rmw: runtime order"); uint32_t ar_ptr_ty = IP_INDEX_NONE; uint32_t ar_ptr_air = ar_ptr_val; bool ar_ct_mut = semaIsComptimeMutablePtr(sema->ip, ar_ptr_val); if (!semaIsAirInstRef(ar_ptr_val)) { if (ar_ct_mut) { ar_ptr_ty = ipTypeOf(sema->ip, ar_ptr_val); } else { if (ar_ptr_val >= sema->ip->items_len || sema->ip->item_tags[ar_ptr_val] != IP_TAG_PTR_NAV) UNIMPLEMENTED("atomic_rmw: comptime pointer shape"); semaRuntimeDataPtrNav( sema->ip, ar_ptr_val, true, &ar_ptr_ty, NULL); } } else { ar_ptr_ty = semaResolveRefType(ar_ptr_ref, sema->inst_types); } if (sema->ip->item_tags[ar_ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("atomic_rmw: pointer operand not pointer"); uint32_t ar_ptr_payload = sema->ip->item_datas[ar_ptr_ty]; uint32_t ar_ptr_flags = sema->ip->extra[ar_ptr_payload + IP_TYPE_POINTER_FLAGS]; if (IP_PTR_FLAGS_SIZE(ar_ptr_flags) != IP_PTR_SIZE_ONE) UNIMPLEMENTED("atomic_rmw: pointer operand not single pointer"); if ((ar_ptr_flags & IP_PTR_FLAGS_IS_CONST) != 0) UNIMPLEMENTED("atomic_rmw: pointer operand is const"); if (sema->ip->extra[ar_ptr_payload + IP_TYPE_POINTER_PACKED_OFFSET] != 0) UNIMPLEMENTED("atomic_rmw: packed pointer operand"); uint32_t ar_operand_air; uint32_t ar_operand_ty; if (semaIsAirInstRef(ar_operand_val)) { ar_operand_ty = semaResolveRefType(ar_operand_ref, sema->inst_types); ar_operand_air = ar_operand_val; } else { ar_operand_ty = ipComptimeValueType(sema->ip, ar_operand_val); ar_operand_air = ar_operand_val; } uint32_t ar_elem_ty = ar_operand_ty; uint32_t ar_child_ty = sema->ip->extra[ar_ptr_payload + IP_TYPE_POINTER_CHILD]; if (ar_child_ty != ar_elem_ty) UNIMPLEMENTED("atomic_rmw: pointer child/type mismatch"); uint64_t ar_atomic_align; if (!semaAtomicElemTypeSupported( sema->zcu, sema->ip, ar_elem_ty, &ar_atomic_align)) UNIMPLEMENTED("atomic_rmw: unsupported atomic element type"); uint32_t ar_ptr_align = IP_PTR_FLAGS_ALIGN(ar_ptr_flags); if (ar_ptr_align != IP_PTR_ALIGN_NONE && ar_ptr_align < ztLog2PowerOfTwoU64(ar_atomic_align)) UNIMPLEMENTED("atomic_rmw: pointer alignment too small"); uint32_t ar_op = resolveStdLangEnum(sema->zcu, ar_op_val, "AtomicRmwOp"); // Upstream also accepts float RMW for Xchg/Add/Sub/Max/Min. // zig1's interpreter has no float scalar execution yet, so // keep those forms as a loud unsupported boundary. if (ar_op == 0u) { if (!semaAtomicRmwXchgElemSupported(sema->ip, ar_elem_ty)) UNIMPLEMENTED("atomic_rmw: unsupported xchg element type"); } else if (!semaAtomicRmwNonXchgElemSupported(sema->ip, ar_elem_ty)) UNIMPLEMENTED("atomic_rmw: unsupported element/op pair"); uint32_t ar_order = resolveStdLangEnum(sema->zcu, ar_order_val, "AtomicOrder"); if (ar_order == ATOMIC_ORDER_UNORDERED) UNIMPLEMENTED("atomic_rmw: invalid unordered order"); if (ar_ct_mut) { // The comptime arm: `stored_val` = pointerDeref, `new_val = // switch (op)`, storePtrVal, and the result is `stored_val`. if (semaIsAirInstRef(ar_operand_val)) UNIMPLEMENTED("atomic_rmw: runtime operand, comptime pointer"); uint32_t ar_old = pointerDeref(sema->zcu, ar_ptr_air, NULL); if (ar_old == IP_INDEX_NONE) UNIMPLEMENTED("atomic_rmw: comptime pointer load"); static const ZirInstTag ar_int_tags[] = { [ATOMIC_RMW_OP_ADD] = ZIR_INST_ADDWRAP, [ATOMIC_RMW_OP_SUB] = ZIR_INST_SUBWRAP, [ATOMIC_RMW_OP_AND] = ZIR_INST_BIT_AND, [ATOMIC_RMW_OP_NAND] = ZIR_INST_BIT_AND, [ATOMIC_RMW_OP_OR] = ZIR_INST_BIT_OR, [ATOMIC_RMW_OP_XOR] = ZIR_INST_XOR, }; uint32_t ar_new; if (ar_op == ATOMIC_RMW_OP_XCHG) { ar_new = ar_operand_air; } else if (ar_op == ATOMIC_RMW_OP_MAX || ar_op == ATOMIC_RMW_OP_MIN) { ar_new = valNumberMinMax( sema->ip, ar_old, ar_operand_air, ar_op == ATOMIC_RMW_OP_MIN); } else if (ar_op >= sizeof(ar_int_tags) / sizeof(ar_int_tags[0])) { UNIMPLEMENTED("atomic_rmw: unknown operation"); } else { // arith.addMaybeWrap / subMaybeWrap / bitwiseBin. ar_new = valComptimeIntBinOp( sema->zcu, ar_int_tags[ar_op], ar_old, ar_operand_air); if (ar_new == IP_INDEX_NONE) UNIMPLEMENTED("atomic_rmw: non-integer comptime operand"); if (ar_op == ATOMIC_RMW_OP_NAND) ar_new = bitwiseNot(sema->ip, ar_elem_ty, ar_new); } semaStorePtr2Comptime(sema->zcu, ar_ptr_air, ar_new); sema->inst_values[inst] = ar_old; sema->inst_types[inst] = ar_elem_ty; return; } uint32_t payload_idx = sema->air_extra->len; u32ListAppend(sema->air_extra, ar_operand_air); u32ListAppend( sema->air_extra, ar_order | (ar_op << AIR_ATOMIC_FLAGS_HIGH_SHIFT)); AirInstData ar_data; memset(&ar_data, 0, sizeof(ar_data)); ar_data.pair.a = ar_ptr_air; ar_data.pair.b = payload_idx; uint32_t ar_air_idx = semaAirInstAppend(sema->insts, AIR_INST_ATOMIC_RMW, ar_data); u32ListAppend(block, ar_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + ar_air_idx; sema->inst_types[inst] = ar_elem_ty; } // Sema.zig:zirAtomicStore // Sema.zirAtomicStore: @atomicStore(T, ptr, operand, order). ZIR payload is // AtomicStore { ptr, operand, ordering }. The operand has already been // analyzed under the explicit element type result location; its type is // therefore the atomic element type, and the pointer child must match it. static void zirAtomicStore(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t as_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstAtomicStore extra; zirExtraData(sema->zir, as_pp, &extra); uint32_t as_ptr_ref = extra.ptr; uint32_t as_operand_ref = extra.operand; uint32_t as_order_ref = extra.ordering; uint32_t as_ptr_val = semaResolveRef(as_ptr_ref, sema->inst_values); uint32_t as_operand_val = semaResolveRef(as_operand_ref, sema->inst_values); uint32_t as_order_val = semaResolveRef(as_order_ref, sema->inst_values); semaEnsurePointeeLayoutResolved(sema, as_ptr_ref); // checkAtomicPtrOperand if (semaIsAirInstRef(as_order_val)) UNIMPLEMENTED("atomic_store: runtime order"); uint32_t as_ptr_ty = IP_INDEX_NONE; uint32_t as_ptr_air = as_ptr_val; bool as_ct_mut = semaIsComptimeMutablePtr(sema->ip, as_ptr_val); if (!semaIsAirInstRef(as_ptr_val)) { if (as_ct_mut) { as_ptr_ty = ipTypeOf(sema->ip, as_ptr_val); } else { if (as_ptr_val >= sema->ip->items_len || sema->ip->item_tags[as_ptr_val] != IP_TAG_PTR_NAV) UNIMPLEMENTED("atomic_store: comptime pointer shape"); semaRuntimeDataPtrNav( sema->ip, as_ptr_val, true, &as_ptr_ty, NULL); } } else { as_ptr_ty = semaResolveRefType(as_ptr_ref, sema->inst_types); } if (sema->ip->item_tags[as_ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("atomic_store: pointer operand not pointer"); uint32_t as_ptr_payload = sema->ip->item_datas[as_ptr_ty]; uint32_t as_ptr_flags = sema->ip->extra[as_ptr_payload + IP_TYPE_POINTER_FLAGS]; if (IP_PTR_FLAGS_SIZE(as_ptr_flags) != IP_PTR_SIZE_ONE) UNIMPLEMENTED("atomic_store: pointer operand not single pointer"); if ((as_ptr_flags & IP_PTR_FLAGS_IS_CONST) != 0) UNIMPLEMENTED("atomic_store: pointer operand is const"); if (sema->ip->extra[as_ptr_payload + IP_TYPE_POINTER_PACKED_OFFSET] != 0) UNIMPLEMENTED("atomic_store: packed pointer operand"); uint32_t as_operand_air; uint32_t as_operand_ty; if (semaIsAirInstRef(as_operand_val)) { as_operand_ty = semaResolveRefType(as_operand_ref, sema->inst_types); as_operand_air = as_operand_val; } else { as_operand_ty = ipComptimeValueType(sema->ip, as_operand_val); as_operand_air = as_operand_val; } uint32_t as_elem_ty = as_operand_ty; uint32_t as_child_ty = sema->ip->extra[as_ptr_payload + IP_TYPE_POINTER_CHILD]; if (as_child_ty != as_elem_ty) UNIMPLEMENTED("atomic_store: pointer child/type mismatch"); uint64_t as_atomic_align; if (!semaAtomicElemTypeSupported( sema->zcu, sema->ip, as_elem_ty, &as_atomic_align)) UNIMPLEMENTED("atomic_store: unsupported atomic element type"); uint32_t as_ptr_align = IP_PTR_FLAGS_ALIGN(as_ptr_flags); if (as_ptr_align != IP_PTR_ALIGN_NONE && as_ptr_align < ztLog2PowerOfTwoU64(as_atomic_align)) UNIMPLEMENTED("atomic_store: pointer alignment too small"); uint32_t as_order = resolveStdLangEnum(sema->zcu, as_order_val, "AtomicOrder"); AirInstTag as_air_tag; if (as_order == ATOMIC_ORDER_UNORDERED) as_air_tag = AIR_INST_ATOMIC_STORE_UNORDERED; else if (as_order == ATOMIC_ORDER_MONOTONIC) as_air_tag = AIR_INST_ATOMIC_STORE_MONOTONIC; else if (as_order == ATOMIC_ORDER_RELEASE) as_air_tag = AIR_INST_ATOMIC_STORE_RELEASE; else if (as_order == ATOMIC_ORDER_SEQ_CST) as_air_tag = AIR_INST_ATOMIC_STORE_SEQ_CST; else UNIMPLEMENTED("atomic_store: invalid acquire/acq_rel order"); if (as_ct_mut) { // storePtr2's comptime-mutable arm: storePtrVal, no AIR. if (semaIsAirInstRef(as_operand_val)) UNIMPLEMENTED("atomic_store: runtime value to comptime cell"); semaStorePtr2Comptime(sema->zcu, as_ptr_air, as_operand_air); return; } AirInstData as_data; memset(&as_data, 0, sizeof(as_data)); as_data.pair.a = as_ptr_air; as_data.pair.b = as_operand_air; uint32_t as_air_idx = semaAirInstAppend(sema->insts, as_air_tag, as_data); u32ListAppend(block, as_air_idx); } // Sema.zig:zirMulAdd // Sema.zirMulAdd: @mulAdd(T, a, b, c). AstGen supplies the same destination // type to all three operand result locations; mirror the reached runtime path // by validating that addend/result type is a float or vector-of-floats, // coercing comptime-known operands to it, and emitting AIR mul_add as // pl_op{addend, Bin{a,b}}. All three comptime-known fold through valMulAdd. static void zirMulAdd(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ma_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstMulAdd extra; zirExtraData(sema->zir, ma_pp, &extra); if (ma_pp > sema->zir->extra_len || sema->zir->extra_len - ma_pp < 3u) UNREACHABLE("mul_add: malformed payload"); uint32_t ma_mulend1_ref = extra.mulend1; uint32_t ma_mulend2_ref = extra.mulend2; uint32_t ma_addend_ref = extra.addend; uint32_t ma_mulend1_val = semaResolveRef(ma_mulend1_ref, sema->inst_values); uint32_t ma_mulend2_val = semaResolveRef(ma_mulend2_ref, sema->inst_values); uint32_t ma_addend_val = semaResolveRef(ma_addend_ref, sema->inst_values); uint32_t ma_ty = semaIsAirInstRef(ma_addend_val) ? sema->inst_types[ma_addend_ref - ZIR_REF_START_INDEX] : ipComptimeValueType(sema->ip, ma_addend_val); IpArrayInfo ma_vec = { 0 }; if (ipArrayInfo(sema->ip, ma_ty, &ma_vec) == IP_ARR_MALFORMED) UNREACHABLE("mul_add: malformed vector type"); uint32_t ma_scalar_ty = ipScalarType(sema->ip, ma_ty); uint32_t ma_bits = 0; if (ma_scalar_ty != ZIR_REF_COMPTIME_FLOAT_TYPE && !ztSimpleFloatBits(ma_scalar_ty, &ma_bits)) COMPILE_ERROR_UPSTREAM( "mul_add: expected vector of floats or float type"); if (!semaIsAirInstRef(ma_mulend1_val)) { ma_mulend1_val = semaCoerceComptime(sema->zcu, sema->ip, ma_mulend1_val, ma_ty); if (ma_mulend1_val == IP_INDEX_NONE) UNIMPLEMENTED("mul_add: mulend1 not coercible"); } if (!semaIsAirInstRef(ma_mulend2_val)) { ma_mulend2_val = semaCoerceComptime(sema->zcu, sema->ip, ma_mulend2_val, ma_ty); if (ma_mulend2_val == IP_INDEX_NONE) UNIMPLEMENTED("mul_add: mulend2 not coercible"); } if (!semaIsAirInstRef(ma_addend_val)) { ma_addend_val = semaCoerceComptime(sema->zcu, sema->ip, ma_addend_val, ma_ty); if (ma_addend_val == IP_INDEX_NONE) UNIMPLEMENTED("mul_add: addend not coercible"); } if (!semaIsAirInstRef(ma_mulend1_val) && !semaIsAirInstRef(ma_mulend2_val) && !semaIsAirInstRef(ma_addend_val)) { sema->inst_values[inst] = valMulAdd( sema->zcu, ma_ty, ma_mulend1_val, ma_mulend2_val, ma_addend_val); sema->inst_types[inst] = ma_ty; return; } // A comptime-known undef `mulend2` / `addend` beside a runtime // operand is still `undefRef(ty)`. if ((!semaIsAirInstRef(ma_mulend2_val) && ipIsUndef(sema->ip, ma_mulend2_val)) || (!semaIsAirInstRef(ma_addend_val) && ipIsUndef(sema->ip, ma_addend_val))) { sema->inst_values[inst] = ipGetUndef(sema->ip, ma_ty); sema->inst_types[inst] = ma_ty; return; } uint32_t ma_payload = sema->air_extra->len; u32ListAppend(sema->air_extra, ma_mulend1_val); u32ListAppend(sema->air_extra, ma_mulend2_val); AirInstData ma_data; memset(&ma_data, 0, sizeof(ma_data)); ma_data.pair.a = ma_addend_val; // pl_op.operand ma_data.pair.b = ma_payload; // pl_op.payload = Air.Bin uint32_t ma_air_idx = semaAirInstAppend(sema->insts, AIR_INST_MUL_ADD, ma_data); u32ListAppend(block, ma_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + ma_air_idx; sema->inst_types[inst] = ma_ty; } // Sema.zig:zirBuiltinCall // Sema.zirBuiltinCall: `@call(modifier, callee, args_tuple)`. // BuiltinCall extra: [pp+0]=flags, [pp+1]=modifier ref, [pp+2]=callee ref, // [pp+3]=args ref. The modifier was resolved to std.lang.CallModifier by the // preceding std_lang_value + coerce, and decides -- with the callee's cc and // the enclosing block -- whether this call is inline, comptime or a runtime // AIR call. Sema forms `resolved_args[i] = tupleFieldValByIndex(args, i)`; // since the args tuple is always a literal `.{...}` (an array_init_anon), we // read its element operands directly and coerce each to the callee's parameter // type (the same array-ptr -> slice coercion `mem.asBytes(&key)` -> `[]const // u8` needs). A callee with comptime parameters is lowered like an ordinary // generic/value-specialized call: comptime args are captured in a // func_instance and omitted from the runtime AIR call. static void zirBuiltinCall(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t bc_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBuiltinCall extra; zirExtraData(sema->zir, bc_pp, &extra); uint32_t bc_mod_val = semaResolveRef(extra.modifier, sema->inst_values); if (semaIsAirInstRef(bc_mod_val) || bc_mod_val >= sema->ip->items_len) COMPILE_ERROR_UPSTREAM("@call: modifier is not comptime-known"); uint32_t bc_modifier = ipEnumTagFieldIndex(sema->ip, bc_mod_val); if (bc_modifier > CALL_MODIFIER_COMPILE_TIME) UNIMPLEMENTED("builtin_call: unknown call modifier"); // "switch (modifier)" in Sema.zirBuiltinCall (src/Sema.zig) // normalizes the modifier against the enclosing block // before analyzeCall: inside a comptime block every modifier // becomes `.compile_time`, except the two that contradict it. if (semaBlockIsComptime(sema->ictx)) { if (bc_modifier == CALL_MODIFIER_NEVER_TAIL) COMPILE_ERROR_UPSTREAM("@call: unable to perform " "'never_tail' call at " "compile-time"); if (bc_modifier == CALL_MODIFIER_NEVER_INLINE) COMPILE_ERROR_UPSTREAM("@call: unable to perform " "'never_inline' call at " "compile-time"); bc_modifier = CALL_MODIFIER_COMPILE_TIME; } // analyzeCall's `call_tag`: `.always_tail` also coerces the result to // the caller's return type and ends the body (handleTailCall). if (bc_modifier == CALL_MODIFIER_ALWAYS_TAIL) UNIMPLEMENTED("@call: always_tail (Sema.handleTailCall)"); AirInstTag bc_call_tag = bc_modifier == CALL_MODIFIER_NEVER_TAIL ? AIR_INST_CALL_NEVER_TAIL : bc_modifier == CALL_MODIFIER_NEVER_INLINE ? AIR_INST_CALL_NEVER_INLINE : AIR_INST_CALL; uint32_t bc_callee_ref = extra.callee; uint32_t bc_args_ref = extra.args; uint32_t bc_callee_val = semaResolveRef(bc_callee_ref, sema->inst_values); uint32_t bc_func_ty = semaResolveRefValType( sema->ip, bc_callee_ref, bc_callee_val, sema->inst_types); // A function pointer callee: a comptime one naming a function Nav calls // the function, any other is an indirect call through the pointer. bool bc_indirect = false; uint32_t bc_callee = bc_callee_val; IpPtrInfo bc_fn_ptr; if (ipPtrInfo(sema->ip, bc_func_ty, &bc_fn_ptr) == IP_PTRI_OK && bc_fn_ptr.size == IP_PTR_SIZE_ONE && ztIsZigTypeTag(sema->ip, bc_fn_ptr.child, ZIG_TYPE_TAG_FN)) { uint32_t target = semaIsAirInstRef(bc_callee_val) ? IP_INDEX_NONE : semaComptimeFnPtrTarget(sema, bc_callee_val, bc_fn_ptr.child); bc_indirect = target == IP_INDEX_NONE; if (!bc_indirect) bc_callee_val = target; bc_func_ty = bc_fn_ptr.child; } else if (semaIsAirInstRef(bc_callee_val)) { COMPILE_ERROR_UPSTREAM("@call: type is not a function"); } if (bc_indirect && (bc_modifier == CALL_MODIFIER_ALWAYS_INLINE || bc_modifier == CALL_MODIFIER_COMPILE_TIME)) // "modifier '{s}' requires a comptime-known function". COMPILE_ERROR_UPSTREAM( "@call: modifier requires a comptime-known function"); if (bc_indirect && semaBlockIsComptime(sema->ictx)) UNIMPLEMENTED("@call: comptime call through a function pointer"); bool bc_is_extern = !bc_indirect && sema->ip->item_tags[bc_callee_val] == IP_TAG_EXTERN; if (!bc_indirect && sema->ip->item_tags[bc_callee_val] != IP_TAG_FUNC_DECL && sema->ip->item_tags[bc_callee_val] != IP_TAG_FUNC_COERCED && !bc_is_extern) UNIMPLEMENTED("builtin_call: callee not a function value"); if (!ztIsZigTypeTag(sema->ip, bc_func_ty, ZIG_TYPE_TAG_FN)) COMPILE_ERROR_UPSTREAM("@call: type is not a function"); // Sema.analyzeCall (src/Sema.zig): "const inline_requested = // func_ty_info.cc == .@"inline" or modifier == .always_inline" // and "const is_inline_call = block.semaBlockIsComptime() or // inline_requested" -- neither shape is lowered to an AIR // call. A `.compile_time` modifier first enters a comptime scope // ("if (modifier == .compile_time) block.comptime_reason = ...",), // which is what makes `block.semaBlockIsComptime()` true for it. // Defer the routing until the arguments below are resolved; the // inline helpers take values, not the tuple. bool bc_inline_requested = ipFuncTypeIsInline(sema->ip, bc_func_ty) || bc_modifier == CALL_MODIFIER_ALWAYS_INLINE; uint32_t bc_comptime_bits = ipFuncTypeComptimeBits(sema->ip, bc_func_ty); uint32_t bc_ret_ty = ipFuncTypeReturnType(sema->ip, bc_func_ty); uint32_t bc_nparams = ipFuncTypeParamsLen(sema->ip, bc_func_ty); // The comptime scope entered at when the block is not // already comptime: `.compile_time`, or -- for a call that is not // inline-requested -- a comptime-only return type. bool bc_ret_generic = bc_ret_ty == IP_INDEX_GENERIC_POISON_TYPE; bool bc_comptime_scope = semaBlockIsComptime(sema->ictx) || bc_modifier == CALL_MODIFIER_COMPILE_TIME || (!bc_inline_requested && !bc_ret_generic && ztComptimeOnly(sema->zcu, bc_ret_ty)); bool bc_is_inline = bc_inline_requested || bc_comptime_scope; if (bc_indirect && bc_is_inline) UNIMPLEMENTED("@call: inline call through a function pointer"); // A generic return type (`fn f(a: anytype) @TypeOf(a)`) is // resolved from the bound arguments while the body is analyzed // ("we might still learn that this call is inline *after* // evaluating the generic return type"), so only the routes // that need it up front stop here: a runtime AIR call, and the // comptime fold, which binds the result type before the body. if (bc_ret_generic && (!bc_is_inline || bc_comptime_scope)) UNIMPLEMENTED("builtin_call: generic callee"); bool bc_any_generic_types = false; uint32_t bc_param_tys_small[8]; bool bc_param_generic_small[8]; uint32_t* bc_param_tys = (bc_nparams <= 8) ? bc_param_tys_small : ARR_INIT(uint32_t, bc_nparams); bool* bc_param_generic = (bc_nparams <= 8) ? bc_param_generic_small : ARR_INIT(bool, bc_nparams); for (uint32_t a = 0; a < bc_nparams; a++) { bc_param_tys[a] = ipFuncTypeParamType(sema->ip, bc_func_ty, a); bc_param_generic[a] = bc_param_tys[a] == IP_INDEX_GENERIC_POISON_TYPE; if (bc_param_generic[a]) bc_any_generic_types = true; } // The args operand is any tuple value; AstGen spells `.{}` as // the static `empty_tuple` ref, which is no instruction. uint32_t bc_args_val = semaResolveRef(bc_args_ref, sema->inst_values); uint32_t bc_args_len = 0; uint32_t bc_args_ty = semaResolveRefType(bc_args_ref, sema->inst_types); uint32_t bc_args_operands = UINT32_MAX; bool bc_args_literal = bc_args_ref >= ZIR_REF_START_INDEX && sema->zir->inst_tags[bc_args_ref - ZIR_REF_START_INDEX] == ZIR_INST_ARRAY_INIT_ANON; if (bc_args_literal) { ZirInstMultiOp bc_args_extra; bc_args_operands = zirExtraData(sema->zir, sema->zir->inst_datas[bc_args_ref - ZIR_REF_START_INDEX] .pl_node.payload_index, &bc_args_extra); bc_args_len = bc_args_extra.operands_len; } else { // A runtime tuple value: Sema extracts each field by index before // forming the call. A one-possible-value tuple's fields are answered // by its type below (tupleFieldValByIndex). if (!ztIsTuple(sema->ip, bc_args_ty)) COMPILE_ERROR_UPSTREAM("builtin_call: runtime args not a tuple"); bc_args_len = ipTupleFieldCount(sema->ip, bc_args_ty); } if (bc_args_len != bc_nparams) UNIMPLEMENTED("builtin_call: arg/param count mismatch"); uint32_t bc_emit_args_small[8]; uint32_t* bc_emit_args = (bc_args_len <= 8) ? bc_emit_args_small : ARR_INIT(uint32_t, bc_args_len); for (uint32_t a = 0; a < bc_args_len; a++) { uint32_t param_ty = bc_param_tys[a]; if (bc_args_literal) { uint32_t op_ref = sema->zir->extra[bc_args_operands + a]; uint32_t av = semaResolveRef(op_ref, sema->inst_values); if (semaIsAirInstRef(av)) { uint32_t av_ty = semaResolveRefType(op_ref, sema->inst_types); if (param_ty == IP_INDEX_GENERIC_POISON_TYPE) param_ty = av_ty; bc_emit_args[a] = semaCallArgsInfoAnalyzeArg(sema->zcu, av, av_ty, param_ty, sema->insts, block, sema->air_extra, "builtin_call: runtime argument"); } else { if (param_ty == IP_INDEX_GENERIC_POISON_TYPE) { param_ty = ipTypeOf(sema->ip, av); if (param_ty == IP_INDEX_NONE) param_ty = ipTypeOfPreinterned(av); bc_emit_args[a] = av; } else { bc_emit_args[a] = semaCoerceCallArgumentValue(sema->zcu, av, param_ty); } } } else { uint32_t field_ty = ipTupleFieldType(sema->ip, bc_args_ty, a); uint32_t field_val = ipTupleFieldValue(sema->ip, bc_args_ty, a); // `tupleFieldValByIndex`: a comptime-known tuple's field // is `"tuple_val.fieldValue(pt, field_index)"`. if (field_val == IP_INDEX_NONE && !semaIsAirInstRef(bc_args_val) && ztOnePossibleValue(sema->ip, bc_args_ty) == IP_INDEX_NONE) field_val = semaAggregateElemValueWithAbort(sema->ip, bc_args_val, a, "builtin_call: comptime args tuple field", true); if (field_val != IP_INDEX_NONE) { if (param_ty == IP_INDEX_GENERIC_POISON_TYPE) { param_ty = ipTypeOf(sema->ip, field_val); if (param_ty == IP_INDEX_NONE) param_ty = field_ty; bc_emit_args[a] = field_val; } else { bc_emit_args[a] = semaCoerceCallArgumentValue( sema->zcu, field_val, param_ty); } bc_param_tys[a] = param_ty; continue; } field_val = ztOnePossibleValue(sema->ip, field_ty); if (field_val != IP_INDEX_NONE) { if (param_ty == IP_INDEX_GENERIC_POISON_TYPE) { param_ty = ipTypeOf(sema->ip, field_val); if (param_ty == IP_INDEX_NONE) param_ty = field_ty; bc_emit_args[a] = field_val; } else { bc_emit_args[a] = semaCoerceCallArgumentValue( sema->zcu, field_val, param_ty); } bc_param_tys[a] = param_ty; continue; } if (param_ty == IP_INDEX_GENERIC_POISON_TYPE) param_ty = field_ty; uint32_t field_payload = sema->air_extra->len; u32ListAppend(sema->air_extra, bc_args_val); u32ListAppend(sema->air_extra, a); AirInstData field_data; memset(&field_data, 0, sizeof(field_data)); field_data.pair.a = field_ty; // ty_pl result type field_data.pair.b = field_payload; uint32_t field_air = semaAirInstAppend( sema->insts, AIR_INST_AGG_FIELD_VAL, field_data); u32ListAppend(block, field_air); uint32_t field_ref = AIR_REF_INST_BASE + field_air; bc_emit_args[a] = semaCallArgsInfoAnalyzeArg(sema->zcu, field_ref, field_ty, param_ty, sema->insts, block, sema->air_extra, "builtin_call: runtime tuple argument"); } bc_param_tys[a] = param_ty; } if (bc_is_inline) { // "is_inline_call" continued: Sema analyzes the callee body // at the call site instead of emitting a call. Which of the // two shapes applies is decided by the block, exactly as // upstream decides it (`block.semaBlockIsComptime()`), not by whether // the arguments happen to be comptime-known. Both shapes are // the ones ZIR_INST_CALL's own inline arm already routes to; // this reuses those helpers rather than adding a third path. // // "if (func_ty_info.is_noinline and !block.semaBlockIsComptime()) // ... inline call of noinline function", then "if // (modifier == .never_inline) ... cannot perform inline call // with 'never_inline' modifier". The comptime // spelling of both is unreachable from `@call`: the switch // above already rejected `.never_inline` in a comptime block. if (ipFuncTypeIsNoinline(sema->ip, bc_func_ty) && !semaBlockIsComptime(sema->ictx)) COMPILE_ERROR_UPSTREAM("@call: inline call of noinline function"); if (bc_modifier == CALL_MODIFIER_NEVER_INLINE) COMPILE_ERROR_UPSTREAM("@call: cannot perform inline call " "with 'never_inline' modifier"); if (bc_is_extern) UNIMPLEMENTED("builtin_call: inline extern function"); // An inline call does not instantiate a generic function: the // callee body is analyzed with the arguments bound, so an // `anytype` parameter binds monomorphically at the actual // argument's type (the args loop above resolved that into // bc_param_tys) and a comptime parameter binds its value. uint32_t bc_inline_arg_tys_small[8]; uint32_t* bc_inline_arg_tys = (bc_args_len <= 8) ? bc_inline_arg_tys_small : ARR_INIT(uint32_t, bc_args_len); for (uint32_t a = 0; a < bc_args_len; a++) bc_inline_arg_tys[a] = bc_param_generic[a] ? bc_param_tys[a] : IP_INDEX_NONE; if (bc_param_tys != bc_param_tys_small) free(bc_param_tys); if (bc_param_generic != bc_param_generic_small) free(bc_param_generic); // This arm carries the ported rule: the block decides. semaAnalyzeInlineCallDispatch(sema, block, inst, bc_callee_val, bc_func_ty, bc_emit_args, bc_inline_arg_tys, bc_args_len, bc_comptime_scope); if (bc_inline_arg_tys != bc_inline_arg_tys_small) free(bc_inline_arg_tys); if (bc_emit_args != bc_emit_args_small) free(bc_emit_args); return; } // analyzeCall's `runtime_func`: the callee itself, a function pointer // included, unless a generic instantiation replaces it. uint32_t bc_call_operand = bc_callee; uint32_t bc_emit_nargs = bc_args_len; uint32_t bc_pending_entry = bc_is_extern || bc_indirect ? IP_INDEX_NONE : semaFuncPendingEntry(sema->ip, bc_callee_val); if (bc_comptime_bits != 0 || bc_any_generic_types) { if (bc_is_extern) UNIMPLEMENTED("builtin_call: generic extern function"); bool bc_param_is_ct_small[8] = { false }; bool* bc_param_is_ct = (bc_nparams <= 8) ? bc_param_is_ct_small : ARR_INIT(bool, bc_nparams); for (uint32_t a = 0; a < bc_nparams; a++) { bc_param_is_ct[a] = ipFuncTypeParamBit(bc_comptime_bits, a) || ztComptimeOnly(sema->zcu, bc_param_tys[a]) || (bc_param_generic[a] && ztIsZigTypeTag( sema->ip, bc_param_tys[a], ZIG_TYPE_TAG_FN) && bc_emit_args[a] < AIR_REF_INST_BASE); } FuncInstanceCall bc_instance = semaBuildFuncInstanceCall(sema->zcu, bc_callee_val, bc_func_ty, bc_ret_ty, bc_emit_args, bc_param_tys, bc_param_is_ct, bc_nparams, bc_emit_args); if (bc_param_is_ct != bc_param_is_ct_small) free(bc_param_is_ct); bc_call_operand = bc_instance.call_operand; bc_ret_ty = bc_instance.ret_ty; bc_emit_nargs = bc_instance.emit_nargs; bc_pending_entry = bc_instance.pending_entry; } if (bc_param_tys != bc_param_tys_small) free(bc_param_tys); if (bc_param_generic != bc_param_generic_small) free(bc_param_generic); // AIR call: pl_op { operand = callee func value, payload = extra // index of Air.Call{ args_len } followed by the runtime arg refs // }. uint32_t bc_payload_idx = sema->air_extra->len; u32ListAppend(sema->air_extra, bc_emit_nargs); for (uint32_t a = 0; a < bc_emit_nargs; a++) u32ListAppend(sema->air_extra, bc_emit_args[a]); if (bc_emit_args != bc_emit_args_small) free(bc_emit_args); AirInstData bc_data; memset(&bc_data, 0, sizeof(bc_data)); bc_data.pair.a = bc_call_operand; // pl_op.operand bc_data.pair.b = bc_payload_idx; // pl_op.payload semaCallSetHasErrorTrace(sema->zcu, bc_func_ty); semaAddCallResult(sema, block, inst, semaAirInstAppend(sema->insts, bc_call_tag, bc_data), bc_ret_ty); if (bc_pending_entry != IP_INDEX_NONE) semaU32ListAppendUnique(sema->pending_navs, bc_pending_entry); } // Sema.zig:zirFieldParentPtr // Sema.zirFieldParentPtr. FieldParentPtr payload: [node, parent_ptr_type, // field_name, field_ptr]; small is the FullPtrCastFlags. A runtime field // pointer emits AIR `field_parent_ptr` { field_ptr, field_index }. static void zirFieldParentPtr(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; uint32_t fpp_ex = ext_operand; ZirInstFieldParentPtr extra; zirExtraData(sema->zir, fpp_ex, &extra); uint32_t fpp_ptr_ty_ref = extra.parent_ptr_type; uint32_t fpp_name_ref = extra.field_name; uint32_t fpp_field_ptr_ref = extra.field_ptr; uint32_t fpp_result_ty = semaResolveRef(fpp_ptr_ty_ref, sema->inst_values); if (!semaRefIsInterned(fpp_result_ty)) UNIMPLEMENTED("field_parent_ptr: runtime result type"); uint32_t fpp_name_val = semaResolveRef(fpp_name_ref, sema->inst_values); if (semaIsAirInstRef(fpp_name_val)) UNIMPLEMENTED("field_parent_ptr: runtime field name"); SemaFieldParent fp = semaFieldParentPtrPrepare(sema->zcu, fpp_result_ty, fpp_name_val, semaResolveRefType(fpp_field_ptr_ref, sema->inst_types), sema->zir->inst_datas[inst].extended.small); uint32_t fpp_fptr_val = semaResolveRef(fpp_field_ptr_ref, sema->inst_values); if (!semaIsAirInstRef(fpp_fptr_val)) { sema->inst_values[inst] = semaFieldParentPtrComptime( sema->zcu, &fp, fpp_result_ty, fpp_fptr_val); sema->inst_types[inst] = fpp_result_ty; return; } if (ipTypeIsBitpack(sema->ip, fp.parent_ty)) { // `.@"packed"`: the field pointer is a bit pointer at // its host's address (`Type.fieldPtrType`), which // CodeGen.airFieldParentPtr's packed arm undoes. Upstream // first `ptrCastFull`s the operand to // `hypothetical_field_ptr_ty`; only the no-op cast is // modelled. That type's alignment is the parent's, so // the alignment check takes its `.eq` arm. uint32_t fpp_fptr_ty = semaResolveRefType(fpp_field_ptr_ref, sema->inst_types); if (fpp_fptr_ty != ztFieldPtrType(sema->zcu, fp.parent_ptr_ty, fp.fidx)) UNIMPLEMENTED( "field_parent_ptr: packed field pointer needs ptrCastFull"); } uint32_t fpp_payload = sema->air_extra->len; u32ListAppend(sema->air_extra, fpp_fptr_val); // .field_ptr (Ref) u32ListAppend(sema->air_extra, fp.fidx); // .field_index AirInstData fpp_air; memset(&fpp_air, 0, sizeof(fpp_air)); fpp_air.pair.a = fpp_result_ty; // ty_pl.ty fpp_air.pair.b = fpp_payload; // ty_pl.payload_index uint32_t fpp_idx = semaAirInstAppend(sema->insts, AIR_INST_FIELD_PARENT_PTR, fpp_air); u32ListAppend(block, fpp_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + fpp_idx; sema->inst_types[inst] = fpp_result_ty; } // Sema.zig:zirMinMax // Sema.zirMinMax -> analyzeMinMax over the two operands. static void zirMinMax( SemaCtx* sema, U32List* block, uint32_t inst, AirInstTag air_tag) { ZirInstBin extra; zirExtraData( sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &extra); const uint32_t mm_refs[2] = { extra.lhs, extra.rhs }; uint32_t mm_ops[2]; uint32_t mm_tys[2]; for (uint32_t k = 0; k < 2; k++) { mm_ops[k] = semaResolveRef(mm_refs[k], sema->inst_values); mm_tys[k] = semaResolveRefType(mm_refs[k], sema->inst_types); } sema->inst_values[inst] = analyzeMinMax(sema->zcu, air_tag == AIR_INST_MAX, mm_ops, mm_tys, 2, sema->insts, block, sema->air_extra, &sema->inst_types[inst]); } // Sema.zig:zirMinMaxMulti // Sema.zirMinMaxMulti -> analyzeMinMax over N operands. static void zirMinMaxMulti( SemaCtx* sema, U32List* block, uint32_t inst, AirInstTag air_tag) { uint32_t mmm_n = sema->zir->inst_datas[inst].extended.small; uint32_t mmm_payload = sema->zir->inst_datas[inst].extended.operand; ZirInstNodeMultiOp extra; uint32_t extra_end = zirExtraData(sema->zir, mmm_payload, &extra); const uint32_t* mmm_refs = &sema->zir->extra[extra_end]; uint32_t* mmm_ops = ARR_INIT(uint32_t, mmm_n); uint32_t* mmm_tys = ARR_INIT(uint32_t, mmm_n); for (uint32_t k = 0; k < mmm_n; k++) { mmm_ops[k] = semaResolveRef(mmm_refs[k], sema->inst_values); mmm_tys[k] = semaResolveRefType(mmm_refs[k], sema->inst_types); } sema->inst_values[inst] = analyzeMinMax(sema->zcu, air_tag == AIR_INST_MAX, mmm_ops, mmm_tys, mmm_n, sema->insts, block, sema->air_extra, &sema->inst_types[inst]); free(mmm_ops); free(mmm_tys); } // Sema.zig:zirMemcpy // Sema.zirMemcpy. ReleaseSmall wantSafety() is false, so no matching-length or // aliasing checks are emitted. static void zirMemcpy(SemaCtx* sema, U32List* block, uint32_t inst, AirInstTag air_tag, bool check_aliasing) { uint32_t mc_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, mc_pp, &extra); uint32_t mc_dest_ref = extra.lhs; uint32_t mc_src_ref = extra.rhs; uint32_t mc_dest = semaResolveRef(mc_dest_ref, sema->inst_values); uint32_t mc_src = semaResolveRef(mc_src_ref, sema->inst_values); uint32_t mc_dest_ty = semaResolveRefType(mc_dest_ref, sema->inst_types); uint32_t mc_src_ty = semaResolveRefType(mc_src_ref, sema->inst_types); // `"try sema.ensureLayoutResolved(dest_elem_ty, dest_src, .ptr_access);"`, // then src_elem_ty's. semaEnsurePointeeLayoutResolved(sema, mc_dest_ref); semaEnsurePointeeLayoutResolved(sema, mc_src_ref); // indexablePtrLenOrNone's `fieldVal(operand, "len")`. semaForLenInternLen(sema->ip, mc_dest_ty); semaForLenInternLen(sema->ip, mc_src_ty); // indexablePtrLenOrNone (`fieldVal(operand, "len")`, so a // runtime slice emits slice_len) and Type.indexableElem. uint32_t mc_dest_elem, mc_src_elem; uint32_t mc_dest_len = semaMemcpyIndexableLen( sema->ip, mc_dest, mc_dest_ty, &mc_dest_elem, sema->insts, block); uint32_t mc_src_len = semaMemcpyIndexableLen( sema->ip, mc_src, mc_src_ty, &mc_src_elem, sema->insts, block); if (mc_dest_len == INST_VALUE_UNSET && mc_src_len == INST_VALUE_UNSET) COMPILE_ERROR_UPSTREAM("memcpy: unknown copy length"); if (!coerceInMemoryAllowed( sema->zcu, sema->ip, mc_dest_elem, mc_src_elem, false)) COMPILE_ERROR_UPSTREAM("memcpy: pointer element type cannot " "coerce into element type"); // `len_val`: the comptime-known length, from either side; two // comptime lengths must agree. bool mc_len_known = false; uint64_t mc_len = 0; uint64_t mc_lv; if (semaRefIsInterned(mc_dest_len) && ipTryReadIntValue(sema->ip, mc_dest_len, &mc_lv)) { mc_len_known = true; mc_len = mc_lv; if (semaRefIsInterned(mc_src_len) && ipTryReadIntValue(sema->ip, mc_src_len, &mc_lv) && mc_lv != mc_len) COMPILE_ERROR_UPSTREAM("memcpy: non-matching copy lengths"); } else if (semaRefIsInterned(mc_src_len) && ipTryReadIntValue(sema->ip, mc_src_len, &mc_lv)) { mc_len_known = true; mc_len = mc_lv; } // zero_bit: nothing to copy for a zero-bit element. if (ztOnePossibleValue(sema->ip, mc_dest_elem) != IP_INDEX_NONE) { sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; return; } // A comptime-known zero length copies nothing: a comptime-mutable // destination stores a `[0]T`, any other takes the runtime arm's // `"if (len == 0) { ... return; }"` (and zero bytes never alias). if (mc_len_known && mc_len == 0) { sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; return; } if (!semaIsAirInstRef(mc_dest) && !semaIsAirInstRef(mc_src)) { semaMemcpyComptime(sema->zcu, mc_dest, mc_src, check_aliasing); sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; return; } // "If the length is comptime-known, then upgrade src and // destination types into pointer-to-array." uint32_t mc_dest_air = mc_dest; uint32_t mc_src_air = mc_src; uint32_t mc_src_air_ty = mc_src_ty; if (mc_len_known) { if (mc_len == 0) { sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; return; } mc_dest_air = semaMemcpyUpgradeToArrayPtr( sema->ip, mc_dest, mc_dest_ty, mc_len, NULL, sema->insts, block); mc_src_air = semaMemcpyUpgradeToArrayPtr(sema->ip, mc_src, mc_src_ty, mc_len, &mc_src_air_ty, sema->insts, block); } else if (mc_dest_len == INST_VALUE_UNSET) { // "Change the dest to a slice, since its type must have the // length": analyzeSlice(dest, 0, src_len) of a many pointer // is `slice{ptr, len}` without a sentinel. IpPtrInfo mc_dpi; if (ipPtrInfo(sema->ip, mc_dest_ty, &mc_dpi) != IP_PTRI_OK) UNIMPLEMENTED("memcpy: destination is not a pointer"); uint32_t mc_dslice_ty = ipGetSliceType( sema->ip, ipGetPtrType(sema->ip, mc_dpi.child, mc_dpi.flags)); uint32_t mc_dslice_payload = sema->air_extra->len; u32ListAppend(sema->air_extra, mc_dest); u32ListAppend(sema->air_extra, mc_src_len); AirInstData mc_dslice; memset(&mc_dslice, 0, sizeof(mc_dslice)); mc_dslice.pair.a = mc_dslice_ty; mc_dslice.pair.b = mc_dslice_payload; uint32_t mc_dslice_idx = semaAirInstAppend(sema->insts, AIR_INST_SLICE, mc_dslice); u32ListAppend(block, mc_dslice_idx); mc_dest_air = AIR_REF_INST_BASE + mc_dslice_idx; } // "Change the src from slice to a many pointer". if (ztIsSlice(sema->ip, mc_src_air_ty)) mc_src_air = analyzeSlicePtr( sema->ip, mc_src_air, mc_src_air_ty, sema->insts, block); AirInstData mc_data; memset(&mc_data, 0, sizeof(mc_data)); mc_data.pair.a = (uint32_t)mc_dest_air; mc_data.pair.b = (uint32_t)mc_src_air; uint32_t mc_idx = semaAirInstAppend(sema->insts, air_tag, mc_data); u32ListAppend(block, mc_idx); sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; } // Sema.zig:zirMemset // Sema.zirMemset -> AIR memset (bin_op: lhs = dest slice/array ptr, rhs = // element value; ReleaseSmall wantSafety()=false so plain `memset`, not // memset_safe). `@memset(ptr[0..n], undefined)` in Allocator.alloc: an // undefined element value is legal ("ignoring this instruction is legal // lowering") — the interpreter no-ops it. ZIR pl_node -> Bin { lhs, rhs }. static void zirMemset(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ms_pp = sema->zir->inst_datas[inst].pl_node.payload_index; ZirInstBin extra; zirExtraData(sema->zir, ms_pp, &extra); uint32_t ms_dest_ref = extra.lhs; uint32_t ms_val_ref = extra.rhs; uint32_t ms_dest = semaResolveRef(ms_dest_ref, sema->inst_values); uint32_t ms_val = semaResolveRef(ms_val_ref, sema->inst_values); // zirMemset: "if (!ptr_val.isComptimeMutablePtr(zcu)) break :rs // dest_src;" -- a pointer to a runtime global is a runtime // memset whose operand is the constant pointer. if (!semaIsAirInstRef(ms_dest) && !semaIsRuntimeDataNavPtr(sema->ip, ms_dest)) { if (ms_dest == INST_VALUE_UNSET || !semaRefIsInterned(ms_val)) UNIMPLEMENTED("memset: comptime destination"); if (ms_dest < sema->ip->items_len && sema->ip->item_tags[ms_dest] == IP_TAG_PTR_SLICE) { uint64_t ms_len = ipReadIntValue(sema->ip, ipPtrSliceLen(sema->ip, ms_dest)); if (ms_len == 0) { sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; return; } } semaMemsetComptime(sema->zcu, ms_dest, ms_val); sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; return; } // Sema.zirMemset: `"const elem = try sema.coerce(block, // dest_elem_ty, uncoerced_elem, value_src);"`, with the element // type of a slice or a single pointer to an array. uint32_t ms_dest_ty = semaResolveRefType(ms_dest_ref, sema->inst_types); uint32_t ms_el_ty; IpPtrInfo ms_pi; IpArrayInfo ms_ai; if (ztIsSlice(sema->ip, ms_dest_ty)) { uint32_t ms_many = sema->ip->item_datas[ms_dest_ty]; ms_el_ty = sema->ip->extra[sema->ip->item_datas[ms_many]]; } else if (ipPtrInfo(sema->ip, ms_dest_ty, &ms_pi) == IP_PTRI_OK) { ms_el_ty = ipArrayInfo(sema->ip, ms_pi.child, &ms_ai) == IP_ARR_OK && !ms_ai.is_vector ? ms_ai.child : ms_pi.child; // many-item pointer element } else { UNIMPLEMENTED("memset: non-slice/ptr dest"); } if (semaIsAirInstRef(ms_val)) { uint32_t ms_val_ty = semaResolveRefType(ms_val_ref, sema->inst_types); if (ms_val_ty != ms_el_ty) ms_val = semaCoerceExtraRuntime(sema->zcu, ms_val, ms_val_ty, ms_el_ty, sema->insts, block, sema->air_extra, "memset: runtime element coercion"); } else if (ipTypeOf(sema->ip, ms_val) != ms_el_ty) { ms_val = semaCoerceComptime(sema->zcu, sema->ip, ms_val, ms_el_ty); } AirInstData ms_data; memset(&ms_data, 0, sizeof(ms_data)); ms_data.pair.a = ms_dest; // bin_op.lhs = dest ms_data.pair.b = ms_val; // bin_op.rhs = elem value uint32_t ms_idx = semaAirInstAppend(sema->insts, AIR_INST_MEMSET, ms_data); u32ListAppend(block, ms_idx); sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; } // Sema.zig:zirWasmMemorySize // Sema.zirWasmMemorySize: payload is UnNode { node, operand=index }. AstGen // already emits the index as a comptime u32 expression; AIR pl_op stores // operand=.none and payload=memory index. static void zirWasmMemorySize(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; uint32_t wms_p = ext_operand; ZirInstUnNode extra; zirExtraData(sema->zir, wms_p, &extra); uint32_t wms_index_val = semaResolveRef(extra.operand, sema->inst_values); if (semaIsAirInstRef(wms_index_val)) UNIMPLEMENTED("wasm_memory_size: runtime memory index"); uint64_t wms_index = 0; if (!ipTryReadIntValue(sema->ip, wms_index_val, &wms_index) || wms_index > UINT32_MAX) UNIMPLEMENTED("wasm_memory_size: invalid memory index"); AirInstData wms_data; memset(&wms_data, 0, sizeof(wms_data)); wms_data.pair.a = UINT32_MAX; // Air.Inst.Ref.none wms_data.pair.b = (uint32_t)wms_index; // pl_op.payload uint32_t wms_air_idx = semaAirInstAppend(sema->insts, AIR_INST_WASM_MEMORY_SIZE, wms_data); u32ListAppend(block, wms_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + wms_air_idx; sema->inst_types[inst] = ZIR_REF_USIZE_TYPE; } // Sema.zig:zirWasmMemoryGrow // Sema.zirWasmMemoryGrow: payload is BinNode { node, lhs=index, rhs=delta }. // AstGen coerces lhs to comptime u32 and rhs to usize. AIR pl_op stores // operand=delta and payload=memory index. static void zirWasmMemoryGrow(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; uint32_t wmg_p = ext_operand; ZirInstBinNode extra; zirExtraData(sema->zir, wmg_p, &extra); uint32_t wmg_index_val = semaResolveRef(extra.lhs, sema->inst_values); if (semaIsAirInstRef(wmg_index_val)) UNIMPLEMENTED("wasm_memory_grow: runtime memory index"); uint64_t wmg_index = 0; if (!ipTryReadIntValue(sema->ip, wmg_index_val, &wmg_index) || wmg_index > UINT32_MAX) UNIMPLEMENTED("wasm_memory_grow: invalid memory index"); uint32_t wmg_delta_val = semaResolveRef(extra.rhs, sema->inst_values); uint32_t wmg_delta_ref; if (semaIsAirInstRef(wmg_delta_val)) { wmg_delta_ref = wmg_delta_val; } else { uint32_t wmg_delta = semaCoerceComptime( sema->zcu, sema->ip, wmg_delta_val, ZIR_REF_USIZE_TYPE); if (wmg_delta == IP_INDEX_NONE) UNIMPLEMENTED("wasm_memory_grow: invalid delta"); wmg_delta_ref = wmg_delta; } AirInstData wmg_data; memset(&wmg_data, 0, sizeof(wmg_data)); wmg_data.pair.a = wmg_delta_ref; // pl_op.operand wmg_data.pair.b = (uint32_t)wmg_index; // pl_op.payload uint32_t wmg_air_idx = semaAirInstAppend(sema->insts, AIR_INST_WASM_MEMORY_GROW, wmg_data); u32ListAppend(block, wmg_air_idx); sema->inst_values[inst] = AIR_REF_INST_BASE + wmg_air_idx; sema->inst_types[inst] = ZIR_REF_ISIZE_TYPE; } // Sema.zig:zirPrefetch // Sema.zirPrefetch: BinNode [node, lhs = ptr, rhs = options]. checkPtrOperand, // then resolvePrefetchOptions reads `rw`, `locality` and `cache` off the // comptime PrefetchOptions value, and a runtime block gets AIR `prefetch` // (Air.Inst.Data.prefetch: {ptr, rw: u1, locality: u2, cache: u1}). The result // is `.void_value`. static void zirPrefetch(SemaCtx* sema, U32List* block, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; ZirInstBinNode extra; zirExtraData(sema->zir, ext_operand, &extra); uint32_t pf_ptr_ref = extra.lhs; uint32_t pf_ptr = semaResolveRef(pf_ptr_ref, sema->inst_values); uint32_t pf_ptr_ty = semaResolveRefValType( sema->ip, pf_ptr_ref, pf_ptr, sema->inst_types); // checkPtrOperand: `".pointer => return, ... .optional => // if (ty.childType(zcu).zigTypeTag(zcu) == .pointer) // return,"`. uint32_t pf_chk_ty = pf_ptr_ty; if (ztIsZigTypeTag(sema->ip, pf_chk_ty, ZIG_TYPE_TAG_OPTIONAL)) pf_chk_ty = ipOptionalChild(sema->ip, pf_chk_ty); if (!ztIsZigTypeTag(sema->ip, pf_chk_ty, ZIG_TYPE_TAG_POINTER)) COMPILE_ERROR_UPSTREAM("prefetch: expected pointer"); uint32_t pf_opts = semaResolveRef(extra.rhs, sema->inst_values); if (semaIsAirInstRef(pf_opts) || pf_opts >= sema->ip->items_len) UNIMPLEMENTED("prefetch: options not comptime-known"); static const char* const pf_names[3] = { "rw", "locality", "cache" }; uint32_t pf_bits[3]; uint32_t pf_opts_ty = ipTypeOf(sema->ip, pf_opts); for (uint32_t k = 0; k < 3; k++) { uint32_t fi = ipStructFieldIndex(sema->ip, pf_opts_ty, ipGetOrPutString( sema->ip, pf_names[k], (uint32_t)strlen(pf_names[k]))); uint32_t fv; uint64_t bits; if (fi == UINT32_MAX || ipAggregateElemValue(sema->ip, pf_opts, fi, &fv) != IP_AGG_ELEM_STORED) UNIMPLEMENTED("prefetch: options field"); if (k != 1) // `rw`, `cache`: `@intFromEnum` of the tag fv = ipEnumTagFieldIndex(sema->ip, fv); else if (!ipTryReadIntValue(sema->ip, fv, &bits)) UNIMPLEMENTED("prefetch: locality not an int"); else fv = (uint32_t)bits; pf_bits[k] = fv; } // This frame is always a runtime block // (`!block.semaBlockIsComptime()`). AirInstData pf_data; memset(&pf_data, 0, sizeof(pf_data)); pf_data.pair.a = pf_ptr; pf_data.pair.b = (pf_bits[0] << AIR_PREFETCH_RW_SHIFT) | (pf_bits[1] << AIR_PREFETCH_LOCALITY_SHIFT) | (pf_bits[2] << AIR_PREFETCH_CACHE_SHIFT); u32ListAppend( block, semaAirInstAppend(sema->insts, AIR_INST_PREFETCH, pf_data)); sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; } // Sema.zig:zirBuiltinExtern // Sema.zirBuiltinExtern; both operands are comptimeExprs. static void zirBuiltinExtern(SemaCtx* sema, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; ZirInstBinNode extra; zirExtraData(sema->zir, ext_operand, &extra); uint32_t be_ty = semaResolveRef(extra.lhs, sema->inst_values); uint32_t be_opts = semaResolveRef(extra.rhs, sema->inst_values); sema->inst_values[inst] = semaBuiltinExtern(sema->zcu, semaReifyConstOperand(be_ty, "@extern: runtime type"), semaReifyConstOperand(be_opts, "@extern: runtime options")); sema->inst_types[inst] = ipTypeOf(sema->ip, sema->inst_values[inst]); } // Sema.zig:zirInComptime // Sema.zirInComptime: `@inComptime()` -> block.semaBlockIsComptime(). // Three-way // (`lib/std/zig/AstGen.zig` `.in_comptime => if (gz .is_comptime) { return // astgen.failNode(node, "redundant '@inComptime' in comptime scope", .{}); // }`): // // 1. `ct_lexical` (astgen.c reached this runtime body // through BLOCK_COMPTIME -- an explicit `comptime { }`, // a comptime-required builtin argument, or a // `comptime`-forced identifier reference, all of which // astgen.c wraps the same way `AstGen.comptimeExpr` // does, with `block_scope.is_comptime = true`; see the // `ct_lexical` field comment for the full picture -- // container-level decl/type/field-default/param-type // bodies are separately lexically comptime too, but // never reach `analyzeBodyInner`, so they are out of // scope here and covered by `sema_comptime.c`'s // the comptime frame's `InlineCtx.ct_lexical` instead): upstream's AstGen // has already rejected this source, before Sema ever runs. astgen.c does // not implement that lexical check, so porting // `block.semaBlockIsComptime()` faithfully here (`true`) would silently // accept a program stock rejects. The upstream-faithful answer is the // rejection itself, ported one layer later, at the only layer zig1 has it: // COMPILE_ERROR_UPSTREAM. // 2. `ct_scope` but not `ct_lexical` (a comptime-PARAMETER // call argument, e.g. `f(@inComptime())` where `f`'s // param is `comptime`): upstream's AstGen cannot know a // plain user function's parameter is `comptime` without // resolving the callee's type first (Sema's job, not // AstGen's), so it emits the ZIR instruction unwrapped // -- no rejection -- and Sema's `block.semaBlockIsComptime()` // genuinely evaluates `true` when the argument is // force-analyzed at comptime: upstream compiles a // `comptime b: bool` parameter fed `@inComptime()` // directly and returns `true`. // `.bool_true` here is upstream-faithful, not a silent // accept -- upstream accepts this shape too. // 3. Neither: a genuinely runtime block (the std.mem // `use_vectors` gate's `!@inComptime()` operand) folds // to `false`. static void zirInComptime(SemaCtx* sema, uint32_t inst) { if (semaIsLexicallyComptime(sema->ictx)) { COMPILE_ERROR_UPSTREAM( "in_comptime: redundant '@inComptime' in comptime " "scope"); } sema->inst_values[inst] = semaBlockIsComptime(sema->ictx) ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; sema->inst_types[inst] = ZIR_REF_BOOL_TYPE; } // Sema.zig:zirStdLangValue // The named std.lang type, or one of the two calling-convention values // (ccStdLangValue). Other std.lang types abort loudly. static void zirStdLangValue(SemaCtx* sema, uint32_t inst) { uint16_t slv_idx = sema->zir->inst_datas[inst].extended.small; const char* const* slv_path; uint32_t slv_len; uint32_t slv_val = semaStdLangValueTypePath(slv_idx, &slv_path, &slv_len) ? getStdLangType(sema->zcu, slv_path, slv_len) : ccStdLangValue(sema->zcu, sema->zir, inst); sema->inst_values[inst] = slv_val; sema->inst_types[inst] = ipTypeOf(sema->ip, slv_val); } // Sema.zig:zirInplaceArithResultTy // Sema.zirInplaceArithResultTy (`x += 1`, `ptr -= n`): // extended.operand is the loaded LHS, extended.small the InplaceOp; the rule // is zirInplaceArithResultTy's. static void zirInplaceArithResultTy(SemaCtx* sema, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; uint32_t iarty_res = semaInplaceArithResultTy(sema->ip, semaResolveRefType(ext_operand, sema->inst_types), sema->zir->inst_datas[inst].extended.small); // Result is the type itself (IP index, valid AIR Ref since // type indices < AIR_REF_INST_BASE). sema->inst_values[inst] = iarty_res; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirBranchHint // Sema.zirBranchHint: `@branchHint(.likely)` records an optimization hint on // the current branch. The first hint in a branch wins; this lets explicit user // hints override implicit @panic cold hints, matching upstream. static void zirBranchHint(SemaCtx* sema, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; ZirInstUnNode extra; zirExtraData(sema->zir, ext_operand, &extra); if (!sema->ictx->branch_hint_set) { uint32_t bh_ref = extra.operand; uint32_t bh_val = semaResolveRef(bh_ref, sema->inst_values); if (semaIsAirInstRef(bh_val)) UNIMPLEMENTED("branch_hint: runtime hint value"); sema->ictx->branch_hint_set = true; sema->ictx->branch_hint = resolveStdLangEnum(sema->zcu, bh_val, "BranchHint"); } } // Sema.zig:zirFloatOpResultType // Sema.zirFloatOpResultType: validate the result-location type supplied to a // float unary operation, then return the peeled float type. The type may be // wrapped in optionals or error unions (optEuBaseType), and vectors are // validated by their scalar element type (scalarType); generic poison passes // through. The payload is an UnNode whose operand is the result-location type // ref. static void zirFloatOpResultType(SemaCtx* sema, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; uint32_t fop_payload = ext_operand; ZirInstUnNode extra; zirExtraData(sema->zir, fop_payload, &extra); uint32_t fop_ty_ref = extra.operand; uint32_t raw_ty = semaResolveRef(fop_ty_ref, sema->inst_values); if (!semaRefIsInterned(raw_ty)) UNIMPLEMENTED("float_op_result_ty: runtime result type"); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; if (raw_ty == IP_INDEX_GENERIC_POISON_TYPE) { sema->inst_values[inst] = raw_ty; return; } const InternPool* ip = sema->ip; uint32_t base = ipOptEuBaseType(ip, raw_ty); IpArrayInfo vec = { 0 }; IpArrayStatus st = ipArrayInfo(ip, base, &vec); if (st == IP_ARR_MALFORMED) UNREACHABLE("float_op_result_ty: malformed vector"); uint32_t scalar = (st == IP_ARR_OK && vec.is_vector) ? vec.child : base; uint32_t bits = 0; if (scalar >= ip->items_len || (scalar != ZIR_REF_COMPTIME_FLOAT_TYPE && !ztSimpleFloatBits(scalar, &bits))) COMPILE_ERROR_UPSTREAM("float_op_result_ty: expected vector of floats " "or float type"); sema->inst_values[inst] = base; } // Sema.zig:zirRoundOpType // Sema.zirRoundOpType: when @floor/@ceil/@trunc/@round has a result-location // type, choose the operand coercion type. A float destination coerces the // operand to the peeled float type; an integer destination leaves the operand // without a result type (generic poison), and zirRoundCast performs the // float-to-int conversion: `"const dest_base_ty = dest_ty.optEuBaseType(zcu); // switch (dest_base_ty.scalarType(zcu).zigTypeTag(zcu)) { .float, // .comptime_float => return .fromType(dest_base_ty), else => return // .generic_poison_type, }"`. static void zirRoundOpType(SemaCtx* sema, uint32_t inst) { uint32_t ext_operand = sema->zir->inst_datas[inst].extended.operand; uint32_t roty_payload = ext_operand; ZirInstUnNode extra; zirExtraData(sema->zir, roty_payload, &extra); uint32_t roty_dest_ref = extra.operand; uint32_t dest_ty = semaResolveRef(roty_dest_ref, sema->inst_values); if (!semaRefIsInterned(dest_ty)) UNIMPLEMENTED("round_op_ty: runtime destination type"); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; if (dest_ty == IP_INDEX_GENERIC_POISON_TYPE) { sema->inst_values[inst] = dest_ty; return; } uint32_t float_ty = ipOptEuBaseType(sema->ip, dest_ty); uint32_t scalar = ipScalarType(sema->ip, float_ty); uint32_t bits; sema->inst_values[inst] = scalar == ZIR_REF_COMPTIME_FLOAT_TYPE || ztSimpleFloatBits(scalar, &bits) ? float_ty : IP_INDEX_GENERIC_POISON_TYPE; } // The sequence every `struct_decl` / `union_decl` arm runs, in either frame: // resolve the captures the type closes over, look them up in the one // `{zir, inst, captures}` dedup table, otherwise intern through the shared // body routine and insert. `ct` is the comptime frame // (NULL in a runtime body, which instead carries `inst_values`/`inst_types` // and resolves captures against `Zcu.ct.type_resolve_ns`, the // `block.namespace` a runtime body's struct_decl / union_decl also parents its // namespace at). InternPool.getStructType returns `.wip` with its key already // in the map, so a field naming its own instantiation (`next: ?*GenNode(T)`) // finds the type before the fields resolve. semaDeclaredNamespaceType holds // the key here and the struct mint publishes it (ctDedupPublishWip); nested // declarations save and restore it. uint32_t semaDeclaredNamespaceType(Zcu* zcu, const Zir* zir, uint32_t decl_inst, const SemaCtx* ct, const uint32_t* inst_values, const uint32_t* inst_types, uint32_t parent_ns, TypeNameCtx type_name_ctx, bool is_union) { uint32_t caps_len = 0; CtCap* caps = ctResolveCaptures(zcu, zir, decl_inst, ct != NULL ? ct->ns : zcu->ct.type_resolve_ns, ct != NULL ? ct->inst_values : NULL, inst_values, inst_types, &caps_len); uint32_t existing = ctDedupLookup(zcu, zir, decl_inst, caps, caps_len); if (existing != IP_INDEX_NONE) { free(caps); return existing; } CtDedupWip saved_wip = zcu->comptime_tables.dedup_wip; zcu->comptime_tables.dedup_wip = (CtDedupWip) { zir, decl_inst, caps, caps_len, true }; // The comptime frame names a `.func`-strategy type after its call. const TypeNameFuncArgs* tn_func = ct != NULL ? ct->type_name_func : NULL; uint32_t ty = is_union ? semaBodyUnionType(zcu, zir, decl_inst, inst_values, type_name_ctx, tn_func, parent_ns, caps, caps_len) : semaBodyStructType(zcu, zir, decl_inst, inst_values, type_name_ctx, tn_func, parent_ns, caps, caps_len); if (zcu->comptime_tables.dedup_wip.pending) ctDedupInsert(zcu, zir, decl_inst, caps, caps_len, ty); zcu->comptime_tables.dedup_wip = saved_wip; free(caps); return ty; } // (part of) Sema.zig:zirEnumDecl // Sema.zirEnumDecl parents the enum's namespace at `block.namespace`, the // active scope (no scope is the file root, as semaScopedThisType reads it). uint32_t semaRuntimeEnumDeclType(Zcu* zcu, const Zir* zir, uint32_t enum_inst, const uint32_t* inst_values, const uint32_t* inst_types, TypeNameCtx type_name_ctx) { uint32_t ns = zcu->ct.type_resolve_ns != IP_NAMESPACE_NONE ? zcu->ct.type_resolve_ns : zcuNamespaceOfZir(zcu, zir); return semaBodyEnumType(zcu, zir, enum_inst, ns, NULL, inst_values, inst_types, type_name_ctx, NULL); } // Sema.zig:zirStructDecl // Sema.zirStructDecl: intern the declared struct type (and resolve its field // types). The struct type is its own IP value. static void zirStructDecl(SemaCtx* sema, uint32_t inst) { uint32_t struct_ty = semaDeclaredNamespaceType(sema->zcu, sema->zir, inst, NULL, sema->inst_values, sema->inst_types, sema->zcu->ct.type_resolve_ns, sema->type_name_ctx, false); sema->inst_values[inst] = struct_ty; } // Sema.zig:zirUnionDecl // Sema.zirUnionDecl: intern the declared `union(enum)` type (and its generated // tag enum). The union type is its own IP value. static void zirUnionDecl(SemaCtx* sema, uint32_t inst) { uint32_t union_ty = semaDeclaredNamespaceType(sema->zcu, sema->zir, inst, NULL, sema->inst_values, sema->inst_types, sema->zcu->ct.type_resolve_ns, sema->type_name_ctx, true); sema->inst_values[inst] = union_ty; } // Sema.zig:zirEnumDecl // Sema.zirEnumDecl in a runtime function body: intern a local anonymous enum // type used as a result-location / switch type. (`const mode: enum { ... } = // switch (...)`.) static void zirEnumDecl(SemaCtx* sema, uint32_t inst) { uint32_t enum_ty = semaRuntimeEnumDeclType(sema->zcu, sema->zir, inst, sema->inst_values, sema->inst_types, sema->type_name_ctx); sema->inst_values[inst] = enum_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // Sema.zig:zirOpaqueDecl // Sema.zirOpaqueDecl: `const Opaque = opaque {};` in a runtime analysis // body. The opaque type is its own IP value, interned by the shared // sema_comptime.c port. static void zirOpaqueDecl(SemaCtx* sema, uint32_t inst) { sema->inst_values[inst] = semaOpaqueDeclType(sema->zcu, sema->zir, inst, NULL, sema->inst_values, sema->inst_types, sema->type_name_ctx); sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; } // ── analyzeBodyInner ──────────────────────────────────────────────────────── // Sema.zig:analyzeBodyInner void analyzeBodyInner( SemaCtx* sema, U32List* block, const uint32_t* body, uint32_t body_len) { uint32_t i = 0; while (i < body_len) { uint32_t inst = body[i]; ZirInstTag tag = sema->zir->inst_tags[inst]; // Sema.analyzeBodyInner's comptime redirects: `".loop => if // (block.semaBlockIsComptime()) {"` / `".block => if // (block.semaBlockIsComptime()) {"`, both continuing to // `.block_inline`. In a comptime scope a `loop` or a `block` is // analyzed inline into the current block — no AIR block/loop // instruction exists — and its `break`/`repeat` are comptime control // flow. All three tags carry the same `Zir.Inst.Block` pl_node // payload, so retargeting `tag` here is upstream's `continue :inst // .block_inline`. This is what unrolls a plain (non-`inline`) comptime // `for`/`while`: the loop body is re-analyzed per iteration with the // index cell advanced, instead of being lowered once to a runtime AIR // loop. if (semaBlockIsComptime(sema->ictx) && (tag == ZIR_INST_LOOP || tag == ZIR_INST_BLOCK)) tag = ZIR_INST_BLOCK_INLINE; // The same redirect for `".condbr => if (block.semaBlockIsComptime()) // {"` -> // `"continue :inst .condbr_inline;"`. The runtime `condbr` handler // already folds a comptime-known condition, but only `condbr_inline` // rejects a runtime condition — and in a comptime scope, where every // `break`/`repeat` above is now comptime control flow, a runtime // condition must not be allowed to emit an AIR `cond_br` whose // branches then break at comptime. Upstream cannot reach that state // at all (it would already have failed to resolve the condition); // zig1 keeps it loud (`condbr_inline: runtime condition`). if (semaBlockIsComptime(sema->ictx) && tag == ZIR_INST_CONDBR) tag = ZIR_INST_CONDBR_INLINE; // `repeat_inline` sets this to restart the current body from the top // (comptime loop unroll); mirrors Sema's `i = 0; continue`. bool ct_restart = false; // clang-format off switch (tag) { case ZIR_INST_ALLOC: zirAlloc(sema, block, inst); break; case ZIR_INST_ALLOC_INFERRED: zirAllocInferred(sema, block, inst, true); break; case ZIR_INST_ALLOC_INFERRED_MUT: zirAllocInferred(sema, block, inst, false); break; case ZIR_INST_ALLOC_INFERRED_COMPTIME: zirAllocInferredComptime(sema, inst, true); break; case ZIR_INST_ALLOC_INFERRED_COMPTIME_MUT: zirAllocInferredComptime(sema, inst, false); break; case ZIR_INST_RESOLVE_INFERRED_ALLOC: zirResolveInferredAlloc(sema, block, inst); break; case ZIR_INST_ALLOC_MUT: zirAllocMut(sema, block, inst); break; case ZIR_INST_ALLOC_COMPTIME_MUT: zirAllocComptime(sema, inst); break; case ZIR_INST_MAKE_PTR_CONST: zirMakePtrConst(sema, block, inst); break; case ZIR_INST_ARRAY_CAT: zirArrayCat(sema, block, inst); break; case ZIR_INST_ARRAY_TYPE: zirArrayType(sema, inst); break; case ZIR_INST_ARRAY_TYPE_SENTINEL: zirArrayTypeSentinel(sema, inst); break; case ZIR_INST_REIFY_INT: zirReifyInt(sema, inst); break; case ZIR_INST_VECTOR_TYPE: zirVectorType(sema, inst); break; case ZIR_INST_AS_NODE: zirAsNode(sema, block, inst); break; case ZIR_INST_AS_SHIFT_OPERAND: zirAsShiftOperand(sema, block, inst); break; case ZIR_INST_BIT_AND: zirBitwise(sema, block, inst, AIR_INST_BIT_AND); break; case ZIR_INST_BIT_NOT: zirBitNot(sema, block, inst); break; case ZIR_INST_BIT_OR: zirBitwise(sema, block, inst, AIR_INST_BIT_OR); break; case ZIR_INST_BITCAST: zirBitcast(sema, block, inst); break; case ZIR_INST_BOOL_NOT: zirBoolNot(sema, block, inst); break; case ZIR_INST_BOOL_BR_AND: if (zirBoolBr(sema, block, inst, false)) return; break; case ZIR_INST_BOOL_BR_OR: if (zirBoolBr(sema, block, inst, true)) return; break; case ZIR_INST_CALL: zirCall(sema, block, inst, false); break; case ZIR_INST_FIELD_CALL: zirCall(sema, block, inst, true); break; case ZIR_INST_CMP_LT: zirCmp(sema, block, inst, AIR_INST_CMP_LT); break; case ZIR_INST_CMP_LTE: zirCmp(sema, block, inst, AIR_INST_CMP_LTE); break; case ZIR_INST_CMP_EQ: zirCmpEq(sema, block, inst, AIR_INST_CMP_EQ); break; case ZIR_INST_CMP_GTE: zirCmp(sema, block, inst, AIR_INST_CMP_GTE); break; case ZIR_INST_CMP_GT: zirCmp(sema, block, inst, AIR_INST_CMP_GT); break; case ZIR_INST_CMP_NEQ: zirCmpEq(sema, block, inst, AIR_INST_CMP_NEQ); break; case ZIR_INST_DECL_REF: zirDeclRef(sema, block, inst); break; case ZIR_INST_DECL_VAL: zirDeclVal(sema, block, inst); break; case ZIR_INST_LOAD: zirLoad(sema, block, inst); break; case ZIR_INST_ELEM_PTR: zirElemPtr(sema, block, inst); break; case ZIR_INST_ELEM_PTR_NODE: zirElemPtrNode(sema, block, inst); break; case ZIR_INST_ELEM_VAL: zirElemVal(sema, block, inst); break; case ZIR_INST_ELEM_PTR_LOAD: zirElemPtrLoad(sema, block, inst); break; case ZIR_INST_ELEM_VAL_IMM: zirElemValImm(sema, block, inst); break; case ZIR_INST_ELEM_TYPE: zirElemType(sema, inst); break; case ZIR_INST_INDEXABLE_PTR_ELEM_TYPE: zirIndexablePtrElemType(sema, inst); break; case ZIR_INST_SPLAT_OP_RESULT_TY: zirSplatOpResultType(sema, inst); break; case ZIR_INST_FROM_BACKING_INT_ARG_TY: zirFromBackingIntArgTy(sema, inst); break; case ZIR_INST_ENUM_LITERAL: zirEnumLiteral(sema, inst); break; case ZIR_INST_DECL_LITERAL: zirDeclLiteral(sema, inst, true); break; case ZIR_INST_DECL_LITERAL_NO_COERCE: zirDeclLiteral(sema, inst, false); break; case ZIR_INST_INT_FROM_ENUM: zirIntFromEnum(sema, block, inst); break; case ZIR_INST_ENUM_FROM_INT: zirEnumFromInt(sema, block, inst); break; case ZIR_INST_BACKING_INT: zirBackingInt(sema, block, inst); break; case ZIR_INST_FROM_BACKING_INT: zirFromBackingInt(sema, block, inst); break; case ZIR_INST_ERR_UNION_CODE: zirErrUnionCode(sema, block, inst); break; case ZIR_INST_ERR_UNION_CODE_PTR: zirErrUnionCodePtr(sema, block, inst); break; case ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE: zirErrUnionPayload(sema, block, inst); break; case ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE_PTR: zirErrUnionPayloadPtr(sema, block, inst); break; case ZIR_INST_ERROR_UNION_TYPE: zirErrorUnionType(sema, inst); break; case ZIR_INST_ERROR_VALUE: zirErrorValue(sema, inst); break; case ZIR_INST_FIELD_PTR: zirFieldPtr(sema, block, inst); break; case ZIR_INST_FIELD_PTR_NAMED: zirFieldPtrNamed(sema, block, inst); break; case ZIR_INST_FIELD_PTR_LOAD: zirFieldPtrLoad(sema, block, inst); break; case ZIR_INST_FIELD_PTR_NAMED_LOAD: zirFieldPtrNamedLoad(sema, block, inst); break; case ZIR_INST_IMPORT: zirImport(sema, inst); break; case ZIR_INST_INT: zirInt(sema, inst); break; case ZIR_INST_INT_BIG: zirIntBig(sema, inst); break; case ZIR_INST_FLOAT: zirFloat(sema, inst); break; case ZIR_INST_FLOAT128: zirFloat128(sema, inst); break; case ZIR_INST_INT_TYPE: zirIntType(sema, inst); break; case ZIR_INST_IS_NON_ERR: zirIsNonErr(sema, block, inst); break; case ZIR_INST_IS_NON_ERR_PTR: zirIsNonErrPtr(sema, block, inst); break; case ZIR_INST_RET_IS_NON_ERR: zirRetIsNonErr(sema, block, inst); break; case ZIR_INST_IS_NON_NULL: zirIsNonNull(sema, block, inst); break; case ZIR_INST_IS_NON_NULL_PTR: zirIsNonNullPtr(sema, block, inst); break; case ZIR_INST_MERGE_ERROR_SETS: zirMergeErrorSets(sema, inst); break; case ZIR_INST_NEGATE: zirNegate(sema, block, inst); break; case ZIR_INST_NEGATE_WRAP: zirNegateWrap(sema, block, inst); break; case ZIR_INST_OPTIONAL_PAYLOAD_SAFE: zirOptionalPayload(sema, block, inst); break; case ZIR_INST_OPTIONAL_PAYLOAD_SAFE_PTR: zirOptionalPayloadPtr(sema, block, inst); break; case ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE: zirOptionalPayload(sema, block, inst); break; case ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE_PTR: zirOptionalPayloadPtr(sema, block, inst); break; case ZIR_INST_OPTIONAL_TYPE: zirOptionalType(sema, inst); break; case ZIR_INST_PTR_TYPE: zirPtrType(sema, inst); break; case ZIR_INST_REF: zirRef(sema, block, inst); break; case ZIR_INST_DEREF: zirDeref(sema, block, inst); break; case ZIR_INST_REF_DEREF: zirRefDeref(sema, block, inst); break; case ZIR_INST_SHR: zirShr(sema, block, inst, false); break; case ZIR_INST_SHR_EXACT: zirShr(sema, block, inst, true); break; case ZIR_INST_SLICE_END: zirSliceEnd(sema, block, inst); break; case ZIR_INST_SLICE_SENTINEL: zirSliceSentinel(sema, block, inst); break; case ZIR_INST_SLICE_START: zirSliceStart(sema, block, inst); break; case ZIR_INST_SLICE_LENGTH: zirSliceLength(sema, block, inst); break; case ZIR_INST_SLICE_SENTINEL_TY: zirSliceSentinelTy(sema, inst); break; case ZIR_INST_STR: zirStr(sema, inst); break; case ZIR_INST_SWITCH_BLOCK: if (zirSwitchBlock(sema, block, inst, false)) return; break; case ZIR_INST_SWITCH_BLOCK_REF: if (zirSwitchBlock(sema, block, inst, true)) return; break; case ZIR_INST_SWITCH_BLOCK_ERR_UNION: if (zirSwitchBlockErrUnion(sema, block, inst)) return; break; case ZIR_INST_TYPE_INFO: zirTypeInfo(sema, inst); break; case ZIR_INST_SIZE_OF: zirSizeOf(sema, inst); break; case ZIR_INST_BIT_SIZE_OF: zirBitSizeOf(sema, inst); break; case ZIR_INST_TYPEOF: zirTypeof(sema, inst); break; case ZIR_INST_TYPEOF_BUILTIN: zirTypeofBuiltin(sema, inst); break; case ZIR_INST_TYPEOF_LOG2_INT_TYPE: zirTypeofLog2IntType(sema, inst); break; case ZIR_INST_XOR: zirBitwise(sema, block, inst, AIR_INST_XOR); break; case ZIR_INST_STRUCT_INIT_EMPTY: zirStructInitEmpty(sema, inst); break; case ZIR_INST_STRUCT_INIT_EMPTY_RESULT: zirStructInitEmptyResult(sema, inst, false); break; case ZIR_INST_STRUCT_INIT_EMPTY_REF_RESULT: zirStructInitEmptyResult(sema, inst, true); break; case ZIR_INST_STRUCT_INIT_ANON: zirStructInitAnon(sema, block, inst); break; case ZIR_INST_STRUCT_INIT: zirStructInit(sema, block, inst, false); break; case ZIR_INST_STRUCT_INIT_REF: zirStructInit(sema, block, inst, true); break; case ZIR_INST_STRUCT_INIT_FIELD_TYPE: zirStructInitFieldType(sema, inst); break; case ZIR_INST_STRUCT_INIT_FIELD_PTR: zirStructInitFieldPtr(sema, block, inst); break; case ZIR_INST_ARRAY_INIT_ANON: zirArrayInitAnon(sema, block, inst); break; case ZIR_INST_ARRAY_INIT: zirArrayInit(sema, block, inst, false); break; case ZIR_INST_ARRAY_INIT_REF: zirArrayInit(sema, block, inst, true); break; case ZIR_INST_ARRAY_INIT_ELEM_TYPE: zirArrayInitElemType(sema, inst); break; case ZIR_INST_ARRAY_INIT_ELEM_PTR: zirArrayInitElemPtr(sema, block, inst); break; case ZIR_INST_UNION_INIT: zirUnionInit(sema, block, inst); break; case ZIR_INST_FIELD_TYPE_REF: zirFieldTypeRef(sema, inst); break; case ZIR_INST_INT_FROM_PTR: zirIntFromPtr(sema, block, inst); break; case ZIR_INST_ALIGN_OF: zirAlignOf(sema, inst); break; case ZIR_INST_INT_FROM_BOOL: zirIntFromBool(sema, block, inst); break; case ZIR_INST_EMBED_FILE: zirEmbedFile(sema, inst); break; case ZIR_INST_ERROR_NAME: zirErrorName(sema, block, inst); break; case ZIR_INST_TAG_NAME: zirTagName(sema, block, inst); break; case ZIR_INST_TYPE_NAME: zirTypeName(sema, inst); break; case ZIR_INST_INT_FROM_FLOAT: zirIntFromFloat(sema, block, inst); break; case ZIR_INST_FLOAT_FROM_INT: zirFloatFromInt(sema, block, inst); break; case ZIR_INST_PTR_FROM_INT: zirPtrFromInt(sema, block, inst); break; case ZIR_INST_FLOAT_CAST: zirFloatCast(sema, block, inst); break; case ZIR_INST_INT_CAST: zirIntCast(sema, block, inst); break; case ZIR_INST_PTR_CAST: zirPtrCast(sema, block, inst); break; case ZIR_INST_TRUNCATE: zirTruncate(sema, block, inst); break; case ZIR_INST_HAS_DECL: zirHasDecl(sema, inst); break; case ZIR_INST_HAS_FIELD: zirHasField(sema, inst); break; case ZIR_INST_BYTE_SWAP: zirByteSwap(sema, block, inst); break; case ZIR_INST_BIT_REVERSE: zirBitReverse(sema, block, inst); break; case ZIR_INST_BIT_OFFSET_OF: zirBitOffsetOf(sema, inst); break; case ZIR_INST_OFFSET_OF: zirOffsetOf(sema, inst); break; case ZIR_INST_SPLAT: zirSplat(sema, block, inst); break; case ZIR_INST_REDUCE: zirReduce(sema, block, inst); break; case ZIR_INST_SHUFFLE: zirShuffle(sema, block, inst); break; case ZIR_INST_ATOMIC_LOAD: zirAtomicLoad(sema, block, inst); break; case ZIR_INST_ATOMIC_RMW: zirAtomicRmw(sema, block, inst); break; case ZIR_INST_MUL_ADD: zirMulAdd(sema, block, inst); break; case ZIR_INST_BUILTIN_CALL: zirBuiltinCall(sema, block, inst); break; case ZIR_INST_FOR_LEN: zirForLen(sema, block, inst); break; case ZIR_INST_VALIDATE_ARRAY_INIT_REF_TY: zirValidateArrayInitRefTy(sema, inst); break; case ZIR_INST_OPT_EU_BASE_PTR_INIT: zirOptEuBasePtrInit(sema, block, inst); break; case ZIR_INST_COERCE_PTR_ELEM_TY: zirCoercePtrElemTy(sema, block, inst); break; case ZIR_INST_CLZ: zirBitCount(sema, block, inst, tag); break; case ZIR_INST_CTZ: zirBitCount(sema, block, inst, tag); break; case ZIR_INST_POP_COUNT: zirBitCount(sema, block, inst, tag); break; case ZIR_INST_ABS: zirAbs(sema, block, inst); break; case ZIR_INST_SQRT: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_SIN: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_COS: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_TAN: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_EXP: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_EXP2: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_LOG: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_LOG2: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_LOG10: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_FLOOR: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_CEIL: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_ROUND: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_TRUNC: zirUnaryMath(sema, block, inst, tag); break; case ZIR_INST_ERROR_SET_DECL: zirErrorSetDecl(sema, inst); break; case ZIR_INST_ADD: zirArithmetic(sema, block, inst, ZIR_INST_ADD); break; case ZIR_INST_ADDWRAP: zirArithmetic(sema, block, inst, ZIR_INST_ADDWRAP); break; case ZIR_INST_ADD_SAT: zirArithmetic(sema, block, inst, ZIR_INST_ADD_SAT); break; case ZIR_INST_ADD_UNSAFE: zirArithmetic(sema, block, inst, ZIR_INST_ADD_UNSAFE); break; case ZIR_INST_MUL: zirArithmetic(sema, block, inst, ZIR_INST_MUL); break; case ZIR_INST_MULWRAP: zirArithmetic(sema, block, inst, ZIR_INST_MULWRAP); break; case ZIR_INST_MUL_SAT: zirArithmetic(sema, block, inst, ZIR_INST_MUL_SAT); break; case ZIR_INST_SUB: zirArithmetic(sema, block, inst, ZIR_INST_SUB); break; case ZIR_INST_SUBWRAP: zirArithmetic(sema, block, inst, ZIR_INST_SUBWRAP); break; case ZIR_INST_SUB_SAT: zirArithmetic(sema, block, inst, ZIR_INST_SUB_SAT); break; case ZIR_INST_DIV: zirDiv(sema, block, inst); break; case ZIR_INST_DIV_EXACT: zirDivExact(sema, block, inst); break; case ZIR_INST_DIV_FLOOR: zirDivFloor(sema, block, inst); break; case ZIR_INST_DIV_CEIL: zirDivCeil(sema, block, inst); break; case ZIR_INST_DIV_TRUNC: zirDivTrunc(sema, block, inst); break; case ZIR_INST_MOD_REM: zirModRem(sema, block, inst); break; case ZIR_INST_MOD: zirMod(sema, block, inst); break; case ZIR_INST_REM: zirRem(sema, block, inst); break; case ZIR_INST_MAX: zirMinMax(sema, block, inst, AIR_INST_MAX); break; case ZIR_INST_MIN: zirMinMax(sema, block, inst, AIR_INST_MIN); break; case ZIR_INST_SHL: zirShl(sema, block, inst, AIR_INST_SHL); break; case ZIR_INST_SHL_EXACT: zirShl(sema, block, inst, AIR_INST_SHL_EXACT); break; case ZIR_INST_SHL_SAT: zirShl(sema, block, inst, AIR_INST_SHL_SAT); break; case ZIR_INST_RET_PTR: zirRetPtr(sema, block, inst); break; // Instructions that we know to *always* be noreturn based solely on // their tag. case ZIR_INST_COMPILE_ERROR: zirCompileError(sema, inst); // noreturn case ZIR_INST_RET_IMPLICIT: zirRetImplicit(sema, block, inst); return; case ZIR_INST_RET_NODE: zirRetNode(sema, block, inst); return; case ZIR_INST_RET_LOAD: zirRetLoad(sema, block, inst); return; case ZIR_INST_RET_ERR_VALUE: zirRetErrValue(sema, block, inst); return; case ZIR_INST_UNREACHABLE: zirUnreachable(sema, block); return; case ZIR_INST_PANIC: zirPanic(sema, block, inst); return; case ZIR_INST_TRAP: zirTrap(sema, block); return; // Instructions that we know can *never* be noreturn based solely on // their tag. case ZIR_INST_ENSURE_ERR_UNION_PAYLOAD_VOID: zirEnsureErrUnionPayloadVoid(sema, inst); break; case ZIR_INST_SET_EVAL_BRANCH_QUOTA: zirSetEvalBranchQuota(sema, inst); break; case ZIR_INST_ATOMIC_STORE: zirAtomicStore(sema, block, inst); break; case ZIR_INST_STORE_NODE: zirStoreNode(sema, block, inst); break; case ZIR_INST_STORE_TO_INFERRED_PTR: zirStoreToInferredPtr(sema, block, inst); break; case ZIR_INST_VALIDATE_PTR_STRUCT_INIT: zirValidatePtrStructInit(sema, block, inst); break; case ZIR_INST_VALIDATE_PTR_ARRAY_INIT: zirValidatePtrArrayInit(sema, block, inst); break; case ZIR_INST_VALIDATE_DESTRUCTURE: zirValidateDestructure(sema, inst); break; case ZIR_INST_EXPORT: zirExport(sema, inst); break; case ZIR_INST_SET_RUNTIME_SAFETY: zirSetRuntimeSafety(sema, inst); break; case ZIR_INST_MEMCPY: zirMemcpy(sema, block, inst, AIR_INST_MEMCPY, true); break; case ZIR_INST_MEMMOVE: zirMemcpy(sema, block, inst, AIR_INST_MEMMOVE, false); break; case ZIR_INST_MEMSET: zirMemset(sema, block, inst); break; // Special case instructions to handle comptime control flow. case ZIR_INST_SWITCH_CONTINUE: zirSwitchContinue(sema, block, inst); return; case ZIR_INST_LOOP: if (zirLoop(sema, block, inst)) return; break; case ZIR_INST_BLOCK: if (zirBlock(sema, block, inst)) return; break; case ZIR_INST_CONDBR: zirCondbr(sema, block, inst); return; case ZIR_INST_TRY: if (zirTry(sema, block, inst)) return; break; case ZIR_INST_TRY_PTR: if (zirTryPtr(sema, block, inst)) return; break; // clang-format on case ZIR_INST_EXTENDED: { // Dispatch on the extended opcode. uint16_t ext_opcode = sema->zir->inst_datas[inst].extended.opcode; // clang-format off switch ((ZirInstExtended)ext_opcode) { case ZIR_EXT_STRUCT_DECL: zirStructDecl(sema, inst); break; case ZIR_EXT_ENUM_DECL: zirEnumDecl(sema, inst); break; case ZIR_EXT_UNION_DECL: zirUnionDecl(sema, inst); break; case ZIR_EXT_OPAQUE_DECL: zirOpaqueDecl(sema, inst); break; case ZIR_EXT_TUPLE_DECL: zirTupleDecl(sema, inst); break; case ZIR_EXT_THIS: zirThis(sema, inst); break; case ZIR_EXT_RET_ADDR: zirRetAddr(sema, block, inst); break; case ZIR_EXT_BUILTIN_SRC: zirBuiltinSrc(sema, inst); break; case ZIR_EXT_ERROR_RETURN_TRACE: zirErrorReturnTrace(sema, inst); break; case ZIR_EXT_ALLOC: zirAllocExtended(sema, block, inst); break; case ZIR_EXT_BUILTIN_EXTERN: zirBuiltinExtern(sema, inst); break; case ZIR_EXT_TYPEOF_PEER: zirTypeofPeer(sema, inst); break; case ZIR_EXT_ROUND_OP: zirRoundCast(sema, block, inst); break; case ZIR_EXT_ROUND_OP_TY: zirRoundOpType(sema, inst); break; case ZIR_EXT_MIN_MULTI: zirMinMaxMulti(sema, block, inst, AIR_INST_MIN); break; case ZIR_EXT_MAX_MULTI: zirMinMaxMulti(sema, block, inst, AIR_INST_MAX); break; case ZIR_EXT_ADD_WITH_OVERFLOW: zirOverflowArithmetic(sema, block, inst, ext_opcode); break; case ZIR_EXT_SUB_WITH_OVERFLOW: zirOverflowArithmetic(sema, block, inst, ext_opcode); break; case ZIR_EXT_MUL_WITH_OVERFLOW: zirOverflowArithmetic(sema, block, inst, ext_opcode); break; case ZIR_EXT_SHL_WITH_OVERFLOW: zirOverflowArithmetic(sema, block, inst, ext_opcode); break; case ZIR_EXT_WASM_MEMORY_SIZE: zirWasmMemorySize(sema, block, inst); break; case ZIR_EXT_WASM_MEMORY_GROW: zirWasmMemoryGrow(sema, block, inst); break; case ZIR_EXT_PREFETCH: zirPrefetch(sema, block, inst); break; case ZIR_EXT_ERROR_CAST: zirErrorCast(sema, block, inst); break; case ZIR_EXT_SELECT: zirSelect(sema, block, inst); break; case ZIR_EXT_INT_FROM_ERROR: zirIntFromError(sema, block, inst); break; case ZIR_EXT_ERROR_FROM_INT: zirErrorFromInt(sema, block, inst); break; case ZIR_EXT_CMPXCHG: zirCmpxchg(sema, block, inst); break; case ZIR_EXT_PTR_CAST_FULL: zirPtrCastFull(sema, block, inst); break; case ZIR_EXT_PTR_CAST_NO_DEST: zirPtrCastNoDest(sema, block, inst); break; case ZIR_EXT_IN_COMPTIME: zirInComptime(sema, inst); break; case ZIR_EXT_CLOSURE_GET: zirClosureGet(sema, block, inst); break; case ZIR_EXT_REIFY_SLICE_ARG_TY: zirReifySliceArgTy(sema, inst); break; case ZIR_EXT_REIFY_ENUM_VALUE_SLICE_TY: zirReifyEnumValueSliceTy(sema, inst); break; case ZIR_EXT_REIFY_POINTER_SENTINEL_TY: zirReifyPointerSentinelTy(sema, inst); break; case ZIR_EXT_REIFY_TUPLE: zirReifyTuple(sema, inst); break; case ZIR_EXT_REIFY_POINTER: zirReifyPointer(sema, inst); break; case ZIR_EXT_REIFY_FN: zirReifyFn(sema, inst); break; case ZIR_EXT_REIFY_STRUCT: zirReifyStruct(sema, inst); break; case ZIR_EXT_REIFY_UNION: zirReifyUnion(sema, inst); break; case ZIR_EXT_REIFY_ENUM: zirReifyEnum(sema, inst); break; case ZIR_EXT_SET_FLOAT_MODE: zirSetFloatMode(sema, inst); break; case ZIR_EXT_BRANCH_HINT: zirBranchHint(sema, inst); break; case ZIR_EXT_FIELD_PARENT_PTR: zirFieldParentPtr(sema, block, inst); break; case ZIR_EXT_STD_LANG_VALUE: zirStdLangValue(sema, inst); break; case ZIR_EXT_INPLACE_ARITH_RESULT_TY: zirInplaceArithResultTy(sema, inst); break; case ZIR_EXT_FLOAT_OP_RESULT_TY: zirFloatOpResultType(sema, inst); break; // clang-format on case ZIR_EXT_REIFY_SPIRV_TYPE: // Sema.zirReifySpirvType: `"builtin @SpirvType is only // available when targeting SPIR-V"` -- the target is wasm32. COMPILE_ERROR_UPSTREAM("builtin @SpirvType is only available " "when targeting SPIR-V"); case ZIR_EXT_DISABLE_INSTRUMENTATION: zirDisableInstrumentation(sema->zcu); break; case ZIR_EXT_RESTORE_ERR_RET_INDEX: // Sema.zirRestoreErrRetIndex -> restoreErrRetIndex. With the // error return trace disabled (ReleaseSmall, no runtime // safety), start_block.error_return_trace_index == // error_return_trace_index_on_fn_entry, so the function // returns early ("No need to restore") without emitting AIR. // No-op. break; case ZIR_EXT_DBG_EMPTY_STMT: // Sema.zirDbgEmptyStmt: emits dbg_empty_stmt, but with // strip=true (ReleaseSmall) this is a no-op. break; default: zirAbortUnhandled( sema->zir->inst_datas[inst].extended.opcode, true); } break; } case ZIR_INST_RET_TYPE: // Sema: return Air.internedToRef(sema.fn_ret_ty.toIntern()). The // function's declared return type as a comptime type value (IP // index used directly as an AIR Ref since < AIR_REF_INST_BASE). // Used by builtins like @truncate that infer their dest type from // context. sema->inst_values[inst] = sema->fn_ret_ty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; break; case ZIR_INST_DBG_STMT: case ZIR_INST_DBG_VAR_VAL: case ZIR_INST_DBG_VAR_PTR: // Debug info is stripped in ReleaseSmall (the bootstrap's mode, // strip=true). Sema.zirDbgVar -> addDbgVar early-returns // when block.ownerModule().strip, so these emit no AIR. break; case ZIR_INST_ENSURE_RESULT_NON_ERROR: // Sema.zirEnsureResultNonError: fails only if the discarded // (`_ = expr;`) operand's type is an error set/union. Every // operand type ported so far is neither, so this is a no-op (no // AIR/IP entries), matching the `else => return` fallthrough. break; case ZIR_INST_ENSURE_RESULT_USED: // Sema.zirEnsureResultUsed -> ensureResultUsed: checks that the // expression result is consumed (type is void or noreturn). For // if-statement blocks the result type is void, so ensureResultUsed // returns immediately without emitting any AIR. break; case ZIR_INST_VALIDATE_STRUCT_INIT_TY: case ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY: // Sema.zirValidateStructInitTy: checks the type supports struct // init syntax and returns void. For a struct/union type it is a // pure validation (no IP entry, no AIR). break; case ZIR_INST_VALIDATE_ARRAY_INIT_TY: case ZIR_INST_VALIDATE_ARRAY_INIT_RESULT_TY: // Sema.zirValidateArrayInitTy: resolves the (already-interned) // array init type and validates the element count matches. Emits // no IP entries and no AIR; a pure check that always holds for // valid Zig. break; case ZIR_INST_VALIDATE_REF_TY: // Sema.zirValidateRefTy: checks that a `&x` result-location ref // has a valid pointer type. A pure validation; emits no IP entries // and no AIR for valid Zig (e.g. passing `&a` to a slice // parameter). break; case ZIR_INST_VALIDATE_CONST: // Sema.zirValidateConst: no-op in runtime (non-comptime) blocks. // Only fails if the operand is not comptime-known inside a // comptime context; in our export-fn bodies // block.semaBlockIsComptime() is false, so this is always a no-op. break; case ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW: { // Sema.analyzeBodyInner .check_comptime_control_flow: a // comptime `continue` must not cross runtime control flow newer // than its inline block. if (semaBlockIsComptime(sema->ictx)) break; uint32_t inline_block = sema->zir->inst_datas[inst].un_node.operand - ZIR_REF_START_INDEX; const SemaBlock* check_block = sema->ictx->blk; while (check_block->inline_block != inline_block) check_block = check_block->parent; if (check_block->runtime_index < sema->ictx->blk->runtime_index) COMPILE_ERROR_UPSTREAM( "comptime control flow inside runtime block"); break; } case ZIR_INST_SAVE_ERR_RET_INDEX: // Sema.zirSaveErrRetIndex: `if (!error_tracing) return;`. In // ReleaseSmall error tracing is off, so this is a no-op and emits // no AIR. Appears in a `try` when the enclosing fn returns an // error union (AstGen saves the trace index before the call). break; case ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL: case ZIR_INST_RESTORE_ERR_RET_INDEX_FN_ENTRY: // For a function without an active error-return trace (no error // unions, non-`.auto` calling conventions), // Sema.restoreErrRetIndex returns early (start-block trace index // == fn-entry trace index), so the save/restore is a no-op and // emits no AIR. The `fn_entry` variant additionally appears when // the returned expression *may* evaluate to an error (e.g. a bare // identifier like `return true`), which AstGen classifies as // `.maybe`. break; case ZIR_INST_BREAK: case ZIR_INST_BREAK_INLINE: { uint32_t operand_ref = sema->zir->inst_datas[inst].break_data.operand; ZirInstBreak break_extra; zirExtraData(sema->zir, sema->zir->inst_datas[inst].break_data.payload_index, &break_extra); uint32_t block_zir_inst = break_extra.block_inst; // A `break_inline` targeting an enclosing inline block is comptime // control flow (Sema.analyzeBodyInner .break_inline -> // error.ComptimeBreak): record the target + operand and propagate // it up the stack. The owning `block_inline` consumes it. (A // break_inline to a declaration or a runtime-crossing branch falls // through to the runtime br, zirBreak.) // // In a comptime scope a plain `break` is the same comptime control // flow — Sema.analyzeBodyInner `.@"break"`: // `"if (block.semaBlockIsComptime()) {"` / // `"sema.comptime_break_inst = inst;"` / `"return // error.ComptimeBreak;"`. zig1 narrows that to the two target // shapes whose handlers consume `ictx->brk`, i.e. the // `block`/`loop` instructions the redirect at the top of this loop // turns into `block_inline`; a comptime `break` to any other merge // owner (a capturing/labelled `switch_block`) keeps the runtime-br // lowering, zirBreak, rather than silently losing the break. bool ct_break_target = semaBlockIsComptime(sema->ictx) && (sema->zir->inst_tags[block_zir_inst] == ZIR_INST_BLOCK || sema->zir->inst_tags[block_zir_inst] == ZIR_INST_LOOP); if (ct_break_target || (tag == ZIR_INST_BREAK_INLINE && (sema->zir->inst_tags[block_zir_inst] == ZIR_INST_BLOCK_INLINE || sema->zir->inst_tags[block_zir_inst] == ZIR_INST_BOOL_BR_AND || sema->zir->inst_tags[block_zir_inst] == ZIR_INST_BOOL_BR_OR))) { sema->ictx->brk = true; sema->ictx->brk_target = block_zir_inst; sema->ictx->brk_operand = operand_ref; return; } zirBreak(sema, block, inst); return; } case ZIR_INST_REPEAT: { // Sema.analyzeBodyInner `.repeat`: `"if // (block.semaBlockIsComptime()) {"` // -> `"// Send comptime control flow back to the beginning of // this block."` (`i = 0; continue;`). At comptime a plain // `repeat` unrolls exactly like `repeat_inline`; the loop-carried // comptime cells advanced by this iteration are what make the // next pass see new values. The restart is charged at // `ct_restart`. if (semaBlockIsComptime(sema->ictx)) { ct_restart = true; break; } // Runtime context: repeat signals the end of the current loop // body analysis — zirLoop handles the implicit AIR `repeat` // instruction emission. Just return here. return; } case ZIR_INST_REPEAT_INLINE: { // Sema.analyzeBodyInner .repeat_inline: send comptime control flow // back to the beginning of the current inline body (the enclosing // block_inline body). Restart this analyzeBodyInner loop from i=0; // the comptime-mutable counter has been advanced, so the next pass // sees the new value (loop unroll). ct_restart = true; break; } case ZIR_INST_BLOCK_COMPTIME: { // Sema.zirBlockComptime: analyze the body in a comptime scope; the // terminating `break_inline` yields the block's comptime result // value. Everything inside is comptime-known, so no AIR is emitted // (the comptime type/value instructions add IP entries but no // AIR). BlockComptime payload: { reason, body_len }, then body // insts. ZirInstBlockComptime bc_extra; uint32_t bc_extra_end = zirExtraData(sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &bc_extra); uint32_t bc_body_len = bc_extra.body_len; const uint32_t* bc_body = zirBodySlice(sema->zir, bc_extra_end); if (bc_body_len == 0) UNIMPLEMENTED("block_comptime: empty body"); uint32_t last = bc_body[bc_body_len - 1]; // resolveInlineBody: a body that ends in anything but its own // `break_inline` leaves through a comptime break to an outer // block, which propagates ("return error.ComptimeBreak;"). bool bc_own_break = false; if (sema->zir->inst_tags[last] == ZIR_INST_BREAK_INLINE) { ZirInstBreak bc_break; zirExtraData(sema->zir, sema->zir->inst_datas[last].break_data.payload_index, &bc_break); bc_own_break = bc_break.block_inst == inst; } // Analyze all but the terminating break_inline (comptime ops emit // no AIR; they flow values through inst_values). The body runs in // a comptime scope (Sema: zirBlockComptime's block // semaBlockIsComptime): calls inside are forced to comptime // evaluation. bool bc_saved_ct = sema->ictx->ct_scope; bool bc_saved_lexical = sema->ictx->ct_lexical; sema->ictx->ct_scope = true; // BLOCK_COMPTIME is astgen.c's mirror of every upstream ZIR // shape built via `AstGen.comptimeExpr`'s `block_scope // .is_comptime = true` wrapper -- the one writer of `ct_scope` // that is also a lexical AstGen-time comptime scope. See the // InlineCtx field comment. sema->ictx->ct_lexical = true; uint32_t bc_block_len = block->len; analyzeBodyInner(sema, block, bc_body, bc_own_break ? bc_body_len - 1 : bc_body_len); sema->ictx->ct_scope = bc_saved_ct; sema->ictx->ct_lexical = bc_saved_lexical; if (sema->ictx->brk) return; if (!bc_own_break) UNIMPLEMENTED( "block_comptime: body not terminated by break_inline"); // A `return` inside the comptime block (an inline callee's // `comptime { ...; return s; }`) ended the body with its // noreturn `br`/`ret`: the block is noreturn and analysis of // this body stops there, as for any noreturn instruction. if (block->len > bc_block_len && semaAirBodyEndsNoReturn(sema->insts, block)) { sema->inst_values[inst] = AIR_REF_INST_BASE + block->items[block->len - 1]; sema->inst_types[inst] = ZIR_REF_NORETURN_TYPE; return; } // The terminating break_inline's operand is the comptime result. uint32_t brk_ref = sema->zir->inst_datas[last].break_data.operand; uint32_t res = semaResolveRef(brk_ref, sema->inst_values); if (semaIsAirInstRef(res)) UNIMPLEMENTED("block_comptime: runtime break operand"); sema->inst_values[inst] = res; sema->inst_types[inst] = (brk_ref < ZIR_REF_START_INDEX) ? ipTypeOfPreinterned(brk_ref) : sema->inst_types[brk_ref - ZIR_REF_START_INDEX]; break; } case ZIR_INST_BLOCK_INLINE: { // Sema.analyzeBodyInner .block_inline: analyze the body inline // into the current block — no AIR block instruction is emitted // (unless a runtime break crosses it). Any runtime AIR the body // produces (e.g. the unrolled `sum // += v`) lands directly in the parent block. A `break_inline` // targeting this inst yields the block's comptime result; a break // to an outer inline block propagates; a `repeat_inline` inside // re-runs the body (comptime loop unroll). ZirInstBlock bi_extra; uint32_t bi_extra_end = zirExtraData(sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &bi_extra); uint32_t bi_body_len = bi_extra.body_len; const uint32_t* bi_body = zirBodySlice(sema->zir, bi_extra_end); // A fn-proto type expression (`*const fn(A,B) R` pointee, or a // local `const f: *const fn(...) = ...` annotation) lowers to a // block_inline whose body is [param..., body-less func, // break_inline]. Analyzing it as runtime AIR would hit the `param` // instructions; instead resolve it as a comptime function type // (ctResolveParamTypeRef's BLOCK_INLINE case -> // ctResolveFuncTypeFromBody). if (bi_body_len >= 1) { uint32_t bi_last = bi_body[bi_body_len - 1]; if (sema->zir->inst_tags[bi_last] == ZIR_INST_BREAK_INLINE) { uint32_t bop = sema->zir->inst_datas[bi_last].break_data.operand; if (bop >= ZIR_REF_START_INDEX && (sema->zir->inst_tags[bop - ZIR_REF_START_INDEX] == ZIR_INST_FUNC || sema->zir->inst_tags[bop - ZIR_REF_START_INDEX] == ZIR_INST_FUNC_INFERRED || sema->zir->inst_tags[bop - ZIR_REF_START_INDEX] == ZIR_INST_FUNC_FANCY)) { uint32_t fty = semaFuncTypeFromRuntimeBlock(sema->zcu, sema->zir, bi_body, bi_body_len, sema->inst_values, sema->type_name_ctx); sema->inst_values[inst] = fty; sema->inst_types[inst] = ZIR_REF_TYPE_TYPE; break; } } } uint32_t bi_block_index = block->len; SemaBlock bi_blk; semaBlockPush(sema->ictx, &bi_blk, false, UINT32_MAX, inst); analyzeBodyInner(sema, block, bi_body, bi_body_len); semaBlockPop(sema->ictx, &bi_blk); // "A runtime conditional branch that needs a post-hoc block to // be emitted communicates this by mapping the block index into // the inst map." The instructions from `block_index` on move into // it, after a pending comptime break becomes a runtime one. bool bi_post_hoc = false; for (uint32_t ph = 0; ph < sema->ictx->post_hoc_len && !bi_post_hoc; ph++) { if (sema->ictx->post_hoc_zir[ph] != inst) continue; bi_post_hoc = true; uint32_t ph_air = sema->ictx->post_hoc_air[ph]; addRuntimeBreak(sema->ip, sema->zir, sema->ictx, sema->insts, block, sema->inst_values, sema->inst_types); sema->ictx->post_hoc_len--; sema->ictx->post_hoc_zir[ph] = sema->ictx->post_hoc_zir[sema->ictx->post_hoc_len]; sema->ictx->post_hoc_air[ph] = sema->ictx->post_hoc_air[sema->ictx->post_hoc_len]; U32List ph_body = { 0 }; for (uint32_t k = bi_block_index; k < block->len; k++) u32ListAppend(&ph_body, block->items[k]); block->len = bi_block_index; sema->inst_values[inst] = AIR_REF_INST_BASE + ph_air; if (resolveAnalyzedBlock(sema->zcu, sema->ictx, sema->insts, sema->air_extra, block, ph_body, ph_air, &sema->inst_values[inst], &sema->inst_types[inst])) return; } if (bi_post_hoc) break; if (sema->ictx->brk && sema->ictx->brk_target == inst) { // Break targets this inline block: consume it. The block's // result is the break operand. sema->ictx->brk = false; uint32_t op_ref = sema->ictx->brk_operand; sema->inst_values[inst] = semaResolveRef(op_ref, sema->inst_values); sema->inst_types[inst] = semaResolveRefType(op_ref, sema->inst_types); break; } if (sema->ictx->brk) return; // break to an outer inline block: propagate // Fell through without breaking (body ended in a runtime // noreturn): the inline block has no value. sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; break; } case ZIR_INST_CONDBR_INLINE: { // Sema.analyzeBodyInner .condbr_inline: the condition is // comptime-known; analyze only the taken branch, inline into the // current block. Used to select each inline-for iteration (the // `counter < len` test's then/else). ZirInstCondBr cbi_extra; uint32_t cbi_extra_end = zirExtraData(sema->zir, sema->zir->inst_datas[inst].pl_node.payload_index, &cbi_extra); uint32_t cond_ref = cbi_extra.condition; uint32_t then_len = cbi_extra.then_body_len; uint32_t else_len = cbi_extra.else_body_len; const uint32_t* then_body = zirBodySlice(sema->zir, cbi_extra_end); const uint32_t* else_body = zirBodySlice(sema->zir, cbi_extra_end + then_len); uint32_t cond_val = semaResolveRef(cond_ref, sema->inst_values); if (semaIsAirInstRef(cond_val)) UNIMPLEMENTED("condbr_inline: runtime condition"); bool taken; if (cond_val == ZIR_REF_BOOL_TRUE) taken = true; else if (cond_val == ZIR_REF_BOOL_FALSE) taken = false; else UNIMPLEMENTED("condbr_inline: non-bool comptime condition"); const uint32_t* cbi_body = taken ? then_body : else_body; uint32_t cbi_len = taken ? then_len : else_len; analyzeBodyInner(sema, block, cbi_body, cbi_len); if (sema->ictx->brk && sema->ictx->brk_target == inst) { // Break targets this condbr: yield its operand and continue. sema->ictx->brk = false; uint32_t op_ref = sema->ictx->brk_operand; sema->inst_values[inst] = semaResolveRef(op_ref, sema->inst_values); sema->inst_types[inst] = semaResolveRefType(op_ref, sema->inst_types); break; } // The taken branch broke to an outer inline block (propagate) or // ended in a runtime noreturn — either way this straight-line body // is done. return; } case ZIR_INST_DEFER: { // Sema.analyzeBodyInner .@"defer": inline the defer body into the // current block at this scope-exit point. AstGen (genDefers) // duplicates a `defer` ZIR instruction at every exit of the // enclosing scope (each `return`, block end, etc.), all pointing // at the one shared body in `extra`; so the deferred statements // run in LIFO order at each exit. The defer body ends with a // `break_inline` marker (target inst 0) that stock treats as // ComptimeBreak and the handler recognises as the defer's own // terminator; here we analyze all but that marker, emitting the // deferred runtime AIR (e.g. `n *= 3`) directly into `block`. // defer_data: {index, len} slices the body out of `zir->extra` // (Sema.bodySlice(index, len)). uint32_t d_index = sema->zir->inst_datas[inst].defer_data.index; uint32_t d_len = sema->zir->inst_datas[inst].defer_data.len; if (d_len == 0) UNIMPLEMENTED("defer: empty body"); const uint32_t* d_body = zirBodySlice(sema->zir, d_index); uint32_t d_last = d_body[d_len - 1]; if (sema->zir->inst_tags[d_last] != ZIR_INST_BREAK_INLINE) UNIMPLEMENTED("defer: body not terminated by break_inline"); analyzeBodyInner(sema, block, d_body, d_len - 1); // An inner comptime break that propagated out of the defer body is // a break past the defer: mirror stock re-raising ComptimeBreak. if (sema->ictx->brk) return; sema->inst_values[inst] = ZIR_REF_VOID_VALUE; sema->inst_types[inst] = ZIR_REF_VOID_TYPE; break; } default: // Name the missing tag in the abort so the next work item is // identifiable straight from the failure output. fprintf(stderr, "analyzeBodyInner: unhandled ZIR tag %u\n", (unsigned)tag); UNIMPLEMENTED("analyzeBodyInner: unhandled ZIR instruction"); } // A comptime break_inline is propagating: stop analyzing this body so // the enclosing block_inline / condbr_inline handler can observe it. if (sema->ictx->brk) return; // Sema.analyzeBodyInner loop tail (src/Sema.zig ~1964, the // `.no_possible_value` classify arm): an instruction whose result is // a runtime AIR inst of type `noreturn` terminates body analysis — // "the body itself now ends with a noreturn instruction". The // remaining ZIR is never analyzed; e.g. in a value-yielding // `catch |err| fatal(...)` the else-branch's `break` after the // noreturn call never executes, so the merge block's break peers // never include a noreturn operand (stock's resolvePeerTypes never // sees one from this shape). Only the `noreturn` case of stock's // `.no_possible_value` classify is modeled: no zig1 handler // produces an AIR inst result of the other no-possible-value types // (anyopaque / opaque), and interned (comptime) values of type // noreturn cannot exist. Handlers that are noreturn by ZIR tag // (`ret`/`unreachable`/`br`/`cond_br`/...) `return` directly and // never reach this check, mirroring stock's by-tag break arms. if (semaIsAirInstRef(sema->inst_values[inst]) && sema->inst_types[inst] == ZIR_REF_NORETURN_TYPE) return; // repeat/repeat_inline: restart this body from index 0 (comptime // unroll). Both of upstream's restart arms first charge the backward // branch against the evaluation quota — Sema.emitBackwardBranch: // `"sema.branch_count += 1;"` / // `"if (sema.branch_count > sema.branch_quota) {"` -> // `"evaluation exceeded {d} backwards branches"`, against the one // budget this Sema shares with its callees (`Zcu.sema_branch`). if (ct_restart) { semaChargeBranches(sema->zcu, 1); i = 0; continue; } i++; } } // ── Calls ─────────────────────────────────────────────────────────────────── // (part of) Sema.zig:analyzeCall // The runtime call of comptime-known `func_decl`: the argument loop (a // method's bound arg0 is parameter 0, typed `resolved->arg0_ty`), a generic // instance, then the AIR `call` with `resolved->func_inst` as operand when no // instance is made. `abase` is the ZIR extra index of the per-arg end offsets. static void semaAnalyzeRuntimeCall(SemaCtx* sema, U32List* block, uint32_t inst, const ResolvedFieldCallee* resolved, uint32_t func_decl, uint32_t func_ty, uint32_t abase, uint32_t args_len) { uint32_t callee = resolved->func_inst; uint32_t self_arg = resolved->arg0_inst; uint32_t self_ty = resolved->arg0_ty; bool has_self = resolved->is_method; // Sema.analyzeCall resolves the callee value through // InternPool.unwrapCoercedFunc (`"const orig_fn_index = zcu.intern_pool // .unwrapCoercedFunc(val);"`): a function coerced to another function // type keeps its original body and ZIR, while `func_ty` -- the type the // arguments are coerced to and the call is emitted at -- stays the // coerced one. A non-generic call still emits `callee` itself // (`break :func .{ callee, args }`), so the coerced value, or the function // pointer `callee` was, is kept. func_decl = ipUnwrapCoercedFunc(sema->ip, func_decl); if (func_decl == IP_INDEX_NONE) UNREACHABLE("call: malformed coerced callee"); uint32_t comptime_bits = ipFuncTypeComptimeBits(sema->ip, func_ty); uint32_t po = has_self ? 1u : 0u; // param offset of the first ZIR arg uint32_t total_params = args_len + po; bool is_extern = sema->ip->item_tags[func_decl] == IP_TAG_EXTERN; // `"const param_ty: ?Type = if (arg_idx < // func_ty_info.param_types.len) ... else null; // vararg"`. uint32_t declared_params = ipFuncTypeParamsLen(sema->ip, func_ty); if (is_extern && (ipFuncTypeIsVarArgs(sema->ip, func_ty) ? total_params < declared_params : total_params != declared_params)) UNIMPLEMENTED("call: extern function argument count mismatch"); // `any_generic_types` (Sema.analyzeCall): a parameter type, return type, // or error-union payload type is `generic_poison`, so the concrete types // must be discovered per-instantiation by evaluating the callee's generic // type bodies with the comptime arguments bound. (Distinct from // `comptime_bits`: a generic-VALUE parameter has a comptime bit but no // generic type, and emits no dummy alloc.) uint32_t declared_ret_ty = ipFuncTypeReturnType(sema->ip, func_ty); bool any_generic_types = declared_ret_ty == IP_INDEX_GENERIC_POISON_TYPE; if (!any_generic_types && semaIsErrorUnionType(sema->ip, declared_ret_ty) && ipErrorUnionPayload(sema->ip, declared_ret_ty) == IP_INDEX_GENERIC_POISON_TYPE) any_generic_types = true; for (uint32_t ai = 0; ai < declared_params; ai++) if (ipFuncTypeParamType(sema->ip, func_ty, ai) == IP_INDEX_GENERIC_POISON_TYPE) any_generic_types = true; // `any_comptime_params`: a comptime bit, or a parameter of comptime-only // type. Its third disjunct, a comptime-only return, is the comptime call // below; an inline callee is lowered by its call site and never gets here. bool any_comptime_params = comptime_bits != 0; for (uint32_t ai = 0; ai < declared_params && !any_comptime_params; ai++) { uint32_t pty = ipFuncTypeParamType(sema->ip, func_ty, ai); any_comptime_params = pty != IP_INDEX_GENERIC_POISON_TYPE && ztComptimeOnly(sema->zcu, pty); } if (is_extern && (any_comptime_params || any_generic_types)) UNIMPLEMENTED("call: generic extern function"); // For a generic-type callee, fetch its ZIR + parameter // instructions so the `[generic]` parameter/return type bodies can // be evaluated with the comptime args bound (`generic_inst_map` // upstream). The bound map is indexed like `inst_values` (over the // callee ZIR). const Zir* callee_zir = NULL; uint32_t callee_param_insts_small[8]; uint32_t* callee_param_insts = NULL; uint32_t callee_nparams = 0; uint32_t callee_func_inst = UINT32_MAX; uint32_t bound_values_small[8]; uint32_t bound_types_small[8]; uint32_t* bound_values = NULL; uint32_t* bound_types = NULL; GenericBindings generic_bindings = { 0 }; if (any_generic_types) { uint32_t callee_param_cap = ipFuncTypeParamsLen(sema->ip, func_ty); if (callee_param_cap == 0) UNIMPLEMENTED("call: generic callee has no parameters"); callee_param_insts = (callee_param_cap <= 8) ? callee_param_insts_small : ARR_INIT(uint32_t, callee_param_cap); if (!semaCalleeFuncZir(sema->zcu, func_decl, &callee_zir, callee_param_insts, callee_param_cap, &callee_nparams, &callee_func_inst)) UNIMPLEMENTED("call: generic callee not decodable"); if (callee_nparams < total_params) UNIMPLEMENTED("call: generic callee parameter count mismatch"); bound_values = (callee_nparams <= 8) ? bound_values_small : ARR_INIT(uint32_t, callee_nparams); bound_types = (callee_nparams <= 8) ? bound_types_small : ARR_INIT(uint32_t, callee_nparams); for (uint32_t i = 0; i < callee_nparams; i++) { bound_values[i] = INST_VALUE_UNSET; bound_types[i] = IP_INDEX_NONE; } generic_bindings.param_insts = callee_param_insts; generic_bindings.values = bound_values; generic_bindings.types = bound_types; generic_bindings.nparams = callee_nparams; } #define CALLEE_PARAM_IS_COMPTIME(_idx) \ (ipFuncTypeParamBit(comptime_bits, (_idx)) \ || (callee_param_insts != NULL \ && (callee_zir->inst_tags[callee_param_insts[(_idx)]] \ == ZIR_INST_PARAM_COMPTIME \ || callee_zir->inst_tags[callee_param_insts[(_idx)]] \ == ZIR_INST_PARAM_ANYTYPE_COMPTIME))) #define ANYTYPE_ARG_IS_COMPTIME_FUNCTION(_idx) \ (callee_param_insts != NULL \ && (callee_zir->inst_tags[callee_param_insts[(_idx)]] \ == ZIR_INST_PARAM_ANYTYPE \ || callee_zir->inst_tags[callee_param_insts[(_idx)]] \ == ZIR_INST_PARAM_ANYTYPE_COMPTIME) \ && resolved_param_ty[(_idx)] < sema->ip->items_len \ && sema->ip->item_tags[resolved_param_ty[(_idx)]] \ == IP_TAG_TYPE_FUNCTION \ && arg_refs[(_idx)] < AIR_REF_INST_BASE) #define INSTANCE_PARAM_IS_COMPTIME(_idx) \ (CALLEE_PARAM_IS_COMPTIME(_idx) \ || (resolved_param_ty[(_idx)] != IP_INDEX_NONE \ && ztComptimeOnly(sema->zcu, resolved_param_ty[(_idx)])) \ || ANYTYPE_ARG_IS_COMPTIME_FUNCTION(_idx)) // Resolve each argument body. args_body = extra[pi+2 ..]: the // first `args_len` words are per-arg end offsets; the arg bodies // follow. `resolved_param_ty[ai]` records the concrete parameter // type (a generic one is discovered here), reused when building // the instance signature. uint32_t arg_refs_small[8]; uint32_t resolved_param_ty_small[8]; uint32_t* arg_refs = (total_params <= 8) ? arg_refs_small : ARR_INIT(uint32_t, total_params); uint32_t* resolved_param_ty = (total_params <= 8) ? resolved_param_ty_small : ARR_INIT(uint32_t, total_params); if (has_self) { // Parameter 0 is the caller-bound receiver. A generic/anytype self is // not modeled (the caller guards). If self is a comptime parameter, // bind its comptime value exactly like an explicit comptime argument: // it is stored in the func_instance's comptime_args and omitted from // the emitted runtime argument list below. if (comptime_bits & 1u) { // This generic instance path stores interned values only in // comptime_args. if (!semaRefIsInterned(self_arg)) UNIMPLEMENTED("call: runtime comptime self parameter"); if (self_ty != ZIR_REF_COMPTIME_INT_TYPE && semaInternedIntValueType(sema->ip, self_arg) == ZIR_REF_COMPTIME_INT_TYPE) // Class-H (H8): as H7 — the carrier branch coerces a // comptime_int self argument to the declared `self_ty`; an // already-interned one was stored in comptime_args // Uncoerced. (`self_ty == comptime_int` is the carrier's own // intern-only case, an identity for an interned value.) self_arg = semaCoerceComptime( sema->zcu, sema->ip, self_arg, self_ty); } arg_refs[0] = self_arg; resolved_param_ty[0] = self_ty; if (any_generic_types) { bound_types[0] = self_ty; if (comptime_bits & 1u) { bound_values[0] = self_arg; } else { // Runtime self: the generic inst-map binds a dummy typed alloc // (Sema.zig), same as any runtime argument. AirInstData self_dummy; memset(&self_dummy, 0, sizeof(self_dummy)); self_dummy.pair.a = self_ty; uint32_t self_dummy_idx = semaAirInstAppend( sema->insts, AIR_INST_ALLOC, self_dummy); bound_values[0] = AIR_REF_INST_BASE + self_dummy_idx; } } } // The argument arrays a heap buffer holds when the small one was short. #define SEMA_RUNTIME_CALL_FREE() \ do { \ if (callee_param_insts != callee_param_insts_small) \ free(callee_param_insts); \ if (arg_refs != arg_refs_small) \ free(arg_refs); \ if (resolved_param_ty != resolved_param_ty_small) \ free(resolved_param_ty); \ if (bound_values != bound_values_small) \ free(bound_values); \ if (bound_types != bound_types_small) \ free(bound_types); \ } while (0) // CallArgsInfo.analyzeArg: a `noreturn` argument "terminates resolution // of arguments", and the call is that argument. bool arg_noreturn = false; for (uint32_t ai = 0; ai < args_len && !arg_noreturn; ai++) { const uint32_t* arg_body; uint32_t arg_body_len = semaCallArgBody(sema->zir, abase, args_len, ai, &arg_body); uint32_t p_idx = ai + po; // parameter index (self occupies 0) bool is_var_arg = p_idx >= declared_params; // A var-arg has no parameter type: its body sees generic_poison // (`"maybe_param_ty orelse .generic_poison"`). uint32_t param_ty = is_var_arg ? IP_INDEX_GENERIC_POISON_TYPE : ipFuncTypeParamType(sema->ip, func_ty, p_idx); bool is_anytype_param = false; if (param_ty == IP_INDEX_GENERIC_POISON_TYPE && !is_var_arg) { // any_generic_types saw this parameter and fetched the callee. if (callee_zir == NULL) UNIMPLEMENTED("call: generic parameter without callee ZIR"); ZirInstTag pk = callee_zir->inst_tags[callee_param_insts[p_idx]]; if (pk == ZIR_INST_PARAM_ANYTYPE || pk == ZIR_INST_PARAM_ANYTYPE_COMPTIME) { // `anytype` parameter: it has no type body — its monomorphic // type is the argument's own type (Sema binds an anytype param // to `@TypeOf(arg)`). Discovered after analyzing the arg body // below; leave `param_ty` as generic_poison so the arg carries // no result-location coercion (a plain // `[break_inline(%arg)]`). is_anytype_param = true; } else { // Discover the generic parameter type by evaluating the // callee's parameter type body with the comptime args // bound (Sema.analyzeCall param_ty resolution). param_ty = semaResolveGenericParamType(sema->ip, sema->zcu, func_decl, callee_zir, callee_param_insts[p_idx], &generic_bindings); } } resolved_param_ty[p_idx] = param_ty; bool arg_comptime = CALLEE_PARAM_IS_COMPTIME(p_idx); // Enum-literal argument via `decl_literal` (e.g. `g(.a)`): the // arg body is `[decl_literal(res_ty, "a"), break_inline]`. // Sema.analyzeDeclLiteral resolves `.a` against the (enum) // parameter type directly -> `enum_tag` const; pass that // interned value as the AIR arg ref. (The decl_literal's // res_ty operand is the call placeholder, whose type is the // param type, so we skip the generic body analysis.) if (arg_body_len == 2 && sema->zir->inst_tags[arg_body[0]] == ZIR_INST_DECL_LITERAL && sema->ip->item_tags[param_ty] == IP_TAG_TYPE_ENUM_AUTO) { ZirInstField dl_extra; zirExtraData(sema->zir, sema->zir->inst_datas[arg_body[0]].pl_node.payload_index, &dl_extra); const char* fname = zirNullTerminatedString( sema->zir, dl_extra.field_name_start); uint32_t fname_str = ipGetOrPutString(sema->ip, fname, (uint32_t)strlen(fname)); uint32_t enum_tag_arg = ipEnumTagFromFieldName(sema->ip, param_ty, fname_str); if (enum_tag_arg == IP_INDEX_NONE) UNIMPLEMENTED("call: enum arg field not in enum"); arg_refs[p_idx] = enum_tag_arg; if (any_generic_types) { // This fast path bypasses the normal argument tail below; keep // the generic return/parameter type maps populated exactly as // the shared tail would. bound_types[p_idx] = resolved_param_ty[p_idx]; if (arg_comptime) { bound_values[p_idx] = arg_refs[p_idx]; } else { AirInstData dummy; memset(&dummy, 0, sizeof(dummy)); dummy.pair.a = resolved_param_ty[p_idx]; uint32_t dummy_idx = semaAirInstAppend( sema->insts, AIR_INST_ALLOC, dummy); bound_values[p_idx] = AIR_REF_INST_BASE + dummy_idx; } } continue; } // "Give the arg its result type" (Sema.analyzeCall, Sema.zig): // bind the call instruction itself to the parameter type before // analyzing the arg body — the body's coercion instructions // (`as_node`) name their destination type as a ref to the call inst // (e.g. `f(may(3) catch 0)`: the catch result's `as_node` dest is // the call). Overwritten with the call's AIR ref after the args. uint32_t op; uint32_t av = semaCallArgValue(sema, block, inst, arg_body, arg_body_len, param_ty, arg_comptime, &op); // The arg body can return early without ever resolving its terminating // `break_inline` — a comptime `break` inside the body targeting an // enclosing block propagates out of analyzeBodyInner — leaving `av` at // INST_VALUE_UNSET. The comptime `call` sibling rejects that too // ("call comptime: runtime argument"). if (av == INST_VALUE_UNSET) UNIMPLEMENTED("call: comptime arg not comptime-known"); if (semaIsAirInstRef(av) && semaResolveRefType(op, sema->inst_types) == ZIR_REF_NORETURN_TYPE) { sema->inst_values[inst] = av; sema->inst_types[inst] = ZIR_REF_NORETURN_TYPE; arg_noreturn = true; continue; } if (is_var_arg) { // Sema.coerceVarArgParam: `"if (block.is_typeof) return // inst;"`; its C-ABI promotions are not ported. if (!sema->ictx->is_typeof) UNIMPLEMENTED("call: variadic argument outside @TypeOf"); arg_refs[p_idx] = av; resolved_param_ty[p_idx] = semaResolveRefValType(sema->ip, op, av, sema->inst_types); continue; } if (semaIsAirInstRef(av)) { // The one `CallArgsInfo.analyzeArg` every binder asks. uint32_t av_ty = semaResolveRefType(op, sema->inst_types); arg_refs[p_idx] = semaCallArgsInfoAnalyzeArg(sema->zcu, av, av_ty, param_ty, sema->insts, block, sema->air_extra, "call: runtime argument coercion"); } else if (is_anytype_param) { // Comptime-known `anytype` argument: bind its interned value // verbatim (Sema.analyzeArg: `.generic_poison_type` returns the // uncoerced arg). arg_refs[p_idx] = av; } else { // Comptime-known argument (e.g. a literal `helper(5)`): // coerce it to the callee's parameter type and pass the // interned constant directly as the AIR arg ref (an IP // index below AIR_REF_INST_BASE is its own AIR Ref). arg_refs[p_idx] = semaCoerceCallArgumentValue(sema->zcu, av, param_ty); } if (is_anytype_param) { // The anytype param's monomorphic type = the argument's own type // (Sema binds it to `@TypeOf(arg)`). Derive it now from the // resolved arg ref so the instance signature + generic return // type (`@TypeOf(slice)` composites) see the concrete type. if (semaIsAirInstRef(av)) { param_ty = semaResolveRefType(op, sema->inst_types); } else { // Interned comptime value: its own // type. Use the general InternPool.typeOf mirror so anytype // binding handles dynamic values such as enum tags, error // values, type values, aggregates, and pointer values instead // of only the small preinterned ZIR-ref set. param_ty = ipTypeOf(sema->ip, av); if (param_ty == IP_INDEX_NONE) param_ty = ipTypeOfPreinterned(av); } resolved_param_ty[p_idx] = param_ty; } if (any_generic_types) { bound_types[p_idx] = resolved_param_ty[p_idx]; // Populate the generic inst-map so a later parameter (or // the return type) can refer to this argument's value/type // (Sema.analyzeCall's generic_inst_map). A comptime arg // binds its interned value; a runtime arg needs a dummy // AIR instruction carrying its type (Sema.zig), which // is emitted into `insts` but not into any block — it is // never referenced at runtime. Its `.data` is the arg type // directly (`.{ .tag = .alloc, .data = .{ .ty = arg_ty // }}`). if (INSTANCE_PARAM_IS_COMPTIME(p_idx)) { if (arg_refs[p_idx] >= AIR_REF_INST_BASE) UNIMPLEMENTED("call: comptime arg not comptime-known"); bound_values[p_idx] = arg_refs[p_idx]; } else { AirInstData dummy; memset(&dummy, 0, sizeof(dummy)); dummy.pair.a = resolved_param_ty[p_idx]; // .ty = arg_ty uint32_t dummy_idx = semaAirInstAppend(sema->insts, AIR_INST_ALLOC, dummy); bound_values[p_idx] = AIR_REF_INST_BASE + dummy_idx; } } } if (arg_noreturn) { SEMA_RUNTIME_CALL_FREE(); return; } // The call's result type is the callee's return type — resolved from the // generic return body (with args bound) if `[generic]`. For inferred-error // returns `!T`, the declared type is `!generic_poison`; // upstream resolves the bare payload and then rebuilds the error union // around the original set before interning the func_instance. uint32_t ret_ty = semaResolvedRetType(sema->ip, sema->zcu, func_decl, callee_zir, callee_func_inst, &generic_bindings, declared_ret_ty); // Monomorphize a generic callee (Sema.analyzeCall generic path): // comptime args are bound into the instance, runtime args are // passed in the AIR call, and we intern a `func_instance`. Both a // comptime-VALUE parameter (`comptime_bits`) and a generic-TYPE // parameter (`any_generic_types`) trigger instantiation. uint32_t emit_args_small[8]; uint32_t* emit_args = (total_params <= 8) ? emit_args_small : ARR_INIT(uint32_t, total_params); if (!is_extern && ztComptimeOnly(sema->zcu, ret_ty)) { for (uint32_t ai = 0; ai < total_params; ai++) if (semaIsAirInstRef(arg_refs[ai])) UNIMPLEMENTED( "call: comptime-only return with runtime argument"); semaFoldComptimeCall( sema, inst, func_decl, func_ty, arg_refs, total_params, ret_ty); } else { uint32_t call_operand; uint32_t emit_nargs; uint32_t pending_entry; if (any_comptime_params || any_generic_types) { bool param_is_comptime_small[8] = { false }; bool* param_is_comptime = (total_params <= 8) ? param_is_comptime_small : ARR_INIT(bool, total_params); for (uint32_t ai = 0; ai < total_params; ai++) { param_is_comptime[ai] = INSTANCE_PARAM_IS_COMPTIME(ai); } FuncInstanceCall instance = semaBuildFuncInstanceCall(sema->zcu, func_decl, func_ty, ret_ty, arg_refs, resolved_param_ty, param_is_comptime, total_params, emit_args); if (param_is_comptime != param_is_comptime_small) free(param_is_comptime); call_operand = instance.call_operand; ret_ty = instance.ret_ty; emit_nargs = instance.emit_nargs; pending_entry = instance.pending_entry; } else { call_operand = callee; for (uint32_t ai = 0; ai < total_params; ai++) emit_args[ai] = arg_refs[ai]; emit_nargs = total_params; pending_entry = is_extern ? IP_INDEX_NONE : semaFuncPendingEntry(sema->ip, func_decl); } // AIR call: pl_op { operand = callee func value, payload = extra // index of Air.Call{ args_len } followed by the arg refs }. uint32_t payload_idx = sema->air_extra->len; u32ListAppend(sema->air_extra, emit_nargs); for (uint32_t ai = 0; ai < emit_nargs; ai++) u32ListAppend(sema->air_extra, emit_args[ai]); AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = call_operand; // pl_op.operand data.pair.b = payload_idx; // pl_op.payload semaAddCallResult(sema, block, inst, semaAirInstAppend(sema->insts, AIR_INST_CALL, data), ret_ty); // Queue the callee's body for analysis (mirrors analyzeCall's // `ref_func` block -> ensureFuncBodyAnalysisQueued). A plain // callee enqueues its Nav; a generic call enqueues the instance. if (pending_entry != IP_INDEX_NONE) semaU32ListAppendUnique(sema->pending_navs, pending_entry); } if (emit_args != emit_args_small) free(emit_args); SEMA_RUNTIME_CALL_FREE(); #undef SEMA_RUNTIME_CALL_FREE #undef CALLEE_PARAM_IS_COMPTIME #undef ANYTYPE_ARG_IS_COMPTIME_FUNCTION #undef INSTANCE_PARAM_IS_COMPTIME } static void semaAnalyzeIndirectCall(SemaCtx* sema, U32List* block, uint32_t inst, uint32_t call_operand, uint32_t func_ty, uint32_t abase, uint32_t args_len) { if (ipFuncTypeComptimeBits(sema->ip, func_ty) != 0) UNIMPLEMENTED("analyzeCall: indirect, generic function pointer"); // Resolve each argument body (Zir.Inst.Call layout: the first `args_len` // words at `abase` are per-arg end offsets; the arg bodies follow). No // self argument, no generic instantiation. Mirrors the non-generic arm of // semaAnalyzeRuntimeCall's argument loop. uint32_t* arg_refs = ARR_INIT(uint32_t, args_len + 1u); for (uint32_t ai = 0; ai < args_len; ai++) { const uint32_t* arg_body; uint32_t arg_body_len = semaCallArgBody(sema->zir, abase, args_len, ai, &arg_body); uint32_t param_ty = ipFuncTypeParamType(sema->ip, func_ty, ai); uint32_t op; uint32_t av = semaCallArgValue( sema, block, inst, arg_body, arg_body_len, param_ty, false, &op); if (semaIsAirInstRef(av)) { // The one `CallArgsInfo.analyzeArg` every binder asks. uint32_t av_ty = semaResolveRefType(op, sema->inst_types); arg_refs[ai] = semaCallArgsInfoAnalyzeArg(sema->zcu, (uint32_t)av, av_ty, param_ty, sema->insts, block, sema->air_extra, "analyzeCall: indirect, runtime argument coercion"); } else if (av == INST_VALUE_UNSET) { UNIMPLEMENTED("analyzeCall: indirect, unset argument"); } else { // Comptime-known argument (incl. an interned comptime_int): // coerce to the parameter type. arg_refs[ai] = semaCoerceCallArgumentValue(sema->zcu, av, param_ty); } } uint32_t ret_ty = ipFuncTypeReturnType(sema->ip, func_ty); // AIR call: pl_op { operand = the function-pointer value (comptime ptr_nav // or a runtime AIR ref), payload = extra index of Air.Call{args_len} + // arg refs }. The interpreter resolves the callee from the operand. uint32_t payload_idx = sema->air_extra->len; u32ListAppend(sema->air_extra, args_len); for (uint32_t ai = 0; ai < args_len; ai++) u32ListAppend(sema->air_extra, arg_refs[ai]); free(arg_refs); AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = call_operand; data.pair.b = payload_idx; semaAddCallResult(sema, block, inst, semaAirInstAppend(sema->insts, AIR_INST_CALL, data), ret_ty); } static bool semaAirRefIsBoundInlineArg( uint32_t ref, const uint32_t* arg_refs, uint32_t args_len) { for (uint32_t i = 0; i < args_len; i++) if (arg_refs[i] == ref) return true; return false; } static bool semaInlineLocalAllocRef(const InternPool* ip, const AirInstList* insts, uint32_t ref, uint32_t first_local_idx, uint32_t before_idx) { if (ref < AIR_REF_INST_BASE) return false; uint32_t idx = ref - AIR_REF_INST_BASE; if (idx < first_local_idx || idx >= before_idx || idx >= insts->len) return false; if (insts->tags[idx] != AIR_INST_ALLOC) return false; uint32_t ptr_ty = insts->datas[idx].pair.a; if (ptr_ty >= ip->items_len || ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) return false; return (ipPtrTypeFlags(ip, ptr_ty) & IP_PTR_FLAGS_IS_VOLATILE) == 0; } static bool semaInlineLocalPtrCastRef(const InternPool* ip, const AirInstList* insts, uint32_t ref, uint32_t first_local_idx, uint32_t before_idx) { if (ref < AIR_REF_INST_BASE) return false; uint32_t idx = ref - AIR_REF_INST_BASE; if (idx < first_local_idx || idx >= before_idx || idx >= insts->len) return false; if (insts->tags[idx] != AIR_INST_PTR_CAST) return false; uint32_t ptr_ty = insts->datas[idx].pair.a; if (ptr_ty >= ip->items_len || ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) return false; if ((ipPtrTypeFlags(ip, ptr_ty) & IP_PTR_FLAGS_IS_VOLATILE) != 0) return false; return semaInlineLocalAllocRef( ip, insts, insts->datas[idx].pair.b, first_local_idx, idx); } static bool semaRuntimeInlineConstReturnFoldable(const InternPool* ip, const AirInstList* insts, const U32List* sub, uint32_t blk_idx, const uint32_t* arg_refs, uint32_t args_len) { if (sub->len == 0) return false; uint32_t br_idx = sub->items[sub->len - 1]; if (br_idx >= insts->len || br_idx + 1 != insts->len) return false; if (insts->tags[br_idx] != AIR_INST_BR || insts->datas[br_idx].pair.a != blk_idx || semaIsAirInstRef(insts->datas[br_idx].pair.b)) return false; uint32_t first_local_idx = blk_idx + 1; for (uint32_t ai = first_local_idx; ai < br_idx; ai++) { switch ((AirInstTag)insts->tags[ai]) { case AIR_INST_ALLOC: if (!semaInlineLocalAllocRef(ip, insts, AIR_REF_INST_BASE + ai, first_local_idx, ai + 1)) return false; break; case AIR_INST_STORE: case AIR_INST_STORE_SAFE: if (!semaInlineLocalAllocRef( ip, insts, insts->datas[ai].pair.a, first_local_idx, ai)) return false; if (!semaAirRefIsBoundInlineArg( insts->datas[ai].pair.b, arg_refs, args_len)) return false; break; case AIR_INST_PTR_CAST: if (!semaInlineLocalPtrCastRef(ip, insts, AIR_REF_INST_BASE + ai, first_local_idx, ai + 1)) return false; break; case AIR_INST_BLOCK: // A comptime-taken `if (...) return ;` leaves behind its // pre-reserved noreturn block slot, with the actual `br` promoted // into `sub`. The shell has no runtime behavior and is rolled back // with the rest of this inlined constant-return prologue. break; case AIR_INST_INFERRED_ALLOC_COMPTIME: // A `comptime var x = ` in the inlined body leaves the // alloc_inferred_comptime placeholder in the instruction list. It // is analysis state, never a body instruction (upstream keeps it // out of block.instructions too), so it carries no runtime // behavior and must not veto the constant-return fold. break; default: return false; } } return true; } // (part of) Sema.zig:analyzeCall // Sema.analyzeCall's argument loop for an inline call: the `args_len` ZIR // argument bodies at `abase` bind parameters `po..`, and arg_refs/arg_types // below `po` are the caller's (a bound receiver). A comptime parameter's // argument is evaluated in a comptime scope; a generic `.param` / // `.param_comptime` type is resolved from the earlier arguments before its // argument is analyzed ("give the arg its result type"), and a runtime // argument is analyzeArg-coerced to a concrete parameter type. arg_types // holds `sema.typeOf(arg)`, which an `anytype` parameter binds at. static void semaAnalyzeInlineCallArgs(SemaCtx* sema, U32List* block, uint32_t inst, uint32_t func_decl, uint32_t func_ty, uint32_t abase, uint32_t args_len, uint32_t po, uint32_t* arg_refs, uint32_t* arg_types) { uint32_t total = po + args_len; uint32_t cbits = ipFuncTypeComptimeBits(sema->ip, func_ty); const Zir* callee_zir = NULL; uint32_t* param_insts = ARR_INIT(uint32_t, total + 1u); uint32_t* bound_values = ARR_INIT(uint32_t, total + 1u); uint32_t* bound_types = ARR_INIT(uint32_t, total + 1u); const GenericBindings gb = { .param_insts = param_insts, .values = bound_values, .types = bound_types, .nparams = total, }; for (uint32_t a = 0; a < args_len; a++) { uint32_t p = po + a; uint32_t pty = ipFuncTypeParamType(sema->ip, func_ty, p); if (pty == IP_INDEX_GENERIC_POISON_TYPE) { uint32_t np, func_inst; if (callee_zir == NULL && (!semaCalleeFuncZir(sema->zcu, func_decl, &callee_zir, param_insts, total, &np, &func_inst) || np != total)) UNIMPLEMENTED( "analyzeCall: inline call - callee params not decodable"); ZirInstTag pk = callee_zir->inst_tags[param_insts[p]]; if (pk == ZIR_INST_PARAM || pk == ZIR_INST_PARAM_COMPTIME) { // The generic bindings: every earlier argument, prefix or // analyzed above, binds its comptime value and type. for (uint32_t i = 0; i < total; i++) { // cppcheck-suppress unreadVariable ; read through gb bound_values[i] = i < p && !semaIsAirInstRef(arg_refs[i]) ? arg_refs[i] : INST_VALUE_UNSET; // cppcheck-suppress unreadVariable ; read through gb bound_types[i] = i < p ? arg_types[i] : IP_INDEX_NONE; } pty = semaResolveGenericParamType(sema->ip, sema->zcu, func_decl, callee_zir, param_insts[p], &gb); } } bool concrete = pty != IP_INDEX_GENERIC_POISON_TYPE && pty != IP_INDEX_NONE; const uint32_t* abody; uint32_t alen = semaCallArgBody(sema->zir, abase, args_len, a, &abody); uint32_t aref; uint32_t av = semaCallArgValue(sema, block, inst, abody, alen, pty, ipFuncTypeParamBit(cbits, p), &aref); uint32_t aty; if (semaIsAirInstRef(av)) { aty = semaResolveRefType(aref, sema->inst_types); if (concrete && aty != pty) { av = semaCallArgsInfoAnalyzeArg(sema->zcu, av, aty, pty, sema->insts, block, sema->air_extra, "analyzeCall: inline, runtime argument"); aty = pty; } } else if (concrete) { // Sema.coerceExtra to the parameter type: a comptime_int // literal, a `*const [N]T` to a slice (an anytype parameter // keeps the argument as it is). av = semaCoerceCallArgumentValue(sema->zcu, av, pty); aty = pty; } else { aty = ipTypeOf(sema->ip, av); } arg_refs[p] = av; arg_types[p] = aty; } free(bound_types); free(bound_values); free(param_insts); } // (part of) Sema.zig:analyzeCall // Sema.analyzeCall's is_inline_call with runtime arguments (an `inline fn` // callee): the callee body is analyzed at the call site into the caller's AIR, // parameters bound to the caller's argument refs, and each `ret` lowered to // AIR `br` targeting a fresh result block (Sema's inlining.merges). The callee // gets a fresh value/type frame over its own ZIR; AIR instructions land in the // shared caller list, so the bound arg refs stay valid. static void semaAnalyzeInlineCallDispatch(SemaCtx* sema, U32List* block, uint32_t inst, uint32_t func_decl, uint32_t func_ty, const uint32_t* arg_refs, const uint32_t* arg_types, uint32_t args_len, bool comptime_scope) { if (!comptime_scope) { semaAnalyzeRuntimeInlineCall(sema, block, inst, func_decl, func_ty, arg_refs, arg_types, args_len); return; } // A comptime scope evaluates the arguments comptime before the call // ("if (modifier == .compile_time) block.comptime_reason = ...", // src/Sema.zig), so an argument that is only known at runtime is // upstream's "unable to resolve comptime value" there. for (uint32_t a = 0; a < args_len; a++) if (semaIsAirInstRef(arg_refs[a])) COMPILE_ERROR_UPSTREAM( "analyzeCall: inline, unable to resolve comptime value"); uint32_t ret_ty = ipFuncTypeReturnType(sema->ip, func_ty); sema->inst_values[inst] = ctAnalyzeInlineCall( sema->zcu, func_decl, arg_refs, args_len, &ret_ty); sema->inst_types[inst] = ret_ty; } static void semaAnalyzeRuntimeInlineCall(SemaCtx* sema, U32List* block, uint32_t inst, uint32_t func_decl, uint32_t func_ty, const uint32_t* arg_refs, const uint32_t* arg_types, uint32_t args_len) { const Zir* callee_zir = NULL; uint32_t* param_insts = ARR_INIT(uint32_t, args_len + 1u); uint32_t nparams = 0; uint32_t func_inst = UINT32_MAX; if (!semaCalleeFuncZir(sema->zcu, func_decl, &callee_zir, param_insts, args_len, &nparams, &func_inst)) UNIMPLEMENTED( "analyzeCall: runtime inline call - callee not decodable"); if (nparams != args_len) UNIMPLEMENTED("analyzeCall: runtime inline call - arg count mismatch"); // Sema.analyzeCall charges every inline call against the caller's Sema: // `"try sema.emitBackwardBranch(block, call_src);"`. semaChargeBranches(sema->zcu, 1); ZirFnInfo fn_info = zirGetFnInfo(callee_zir, func_inst); const uint32_t* body = fn_info.body; uint32_t body_len = fn_info.body_len; // Fresh frame over the callee ZIR; bind params to the caller's arg refs. uint32_t* values = semaInstMapAlloc(callee_zir); uint32_t* types = semaInstMapAlloc(callee_zir); // Sparse bindings for resolving generic parameter/return types (`value: // T`, ret `T`) from the preceding arguments. Only parameter ZIR // instructions can be bound; fallback comptime-body evaluation // materializes dense maps locally when required. uint32_t ret_bound_values_small[8]; uint32_t ret_bound_types_small[8]; uint32_t* ret_bound_values = (nparams <= 8) ? ret_bound_values_small : ARR_INIT(uint32_t, nparams); uint32_t* ret_bound_types = (nparams <= 8) ? ret_bound_types_small : ARR_INIT(uint32_t, nparams); for (uint32_t i = 0; i < nparams; i++) { ret_bound_values[i] = INST_VALUE_UNSET; ret_bound_types[i] = IP_INDEX_NONE; } GenericBindings ret_bindings = { .param_insts = param_insts, .values = ret_bound_values, .types = ret_bound_types, .nparams = nparams, }; // Sema.analyzeCall's argument loop: a generic `.param`/`.param_comptime` // type is evaluated with the earlier arguments bound and the argument is // `analyzeArg`-coerced to it; only `.param_anytype*` stays // `.generic_poison` and binds at the argument's own type. uint32_t* bound_args = ARR_INIT(uint32_t, args_len + 1u); for (uint32_t i = 0; i < nparams; i++) { uint32_t pty = ipFuncTypeParamType(sema->ip, func_ty, i); uint32_t arg = arg_refs[i]; if (pty == IP_INDEX_GENERIC_POISON_TYPE) { ZirInstTag pk = callee_zir->inst_tags[param_insts[i]]; uint32_t arg_ty = arg_types != NULL ? arg_types[i] : IP_INDEX_NONE; if (pk == ZIR_INST_PARAM || pk == ZIR_INST_PARAM_COMPTIME) { pty = semaResolveGenericParamType(sema->ip, sema->zcu, func_decl, callee_zir, param_insts[i], &ret_bindings); if (pty != IP_INDEX_GENERIC_POISON_TYPE && pty != arg_ty) arg = semaIsAirInstRef(arg) ? semaCallArgsInfoAnalyzeArg(sema->zcu, arg, arg_ty, pty, sema->insts, block, sema->air_extra, "analyzeCall: runtime inline, generic argument") : semaCoerceCallArgumentValue(sema->zcu, arg, pty); } else if (arg_ty != IP_INDEX_NONE) { pty = arg_ty; } } if (pty == IP_INDEX_GENERIC_POISON_TYPE) UNIMPLEMENTED( "analyzeCall: runtime inline call - generic param type"); bound_args[i] = arg; values[param_insts[i]] = arg; types[param_insts[i]] = pty; if (!semaIsAirInstRef(arg)) ret_bound_values[i] = arg; ret_bound_types[i] = pty; } // Resolve a `[generic]` return type (`T` for readInt) with the comptime // args bound (mirrors Sema.analyzeCall's per-instance return-type // resolution). A concrete return type stays as-is. uint32_t ret_ty = semaResolvedRetType(sema->ip, sema->zcu, func_decl, callee_zir, func_inst, &ret_bindings, ipFuncTypeReturnType(sema->ip, func_ty)); if (ret_bound_values != ret_bound_values_small) free(ret_bound_values); if (ret_bound_types != ret_bound_types_small) free(ret_bound_types); if (ret_ty == IP_INDEX_GENERIC_POISON_TYPE || ret_ty == IP_INDEX_NONE) UNIMPLEMENTED("analyzeCall: runtime inline call - unresolved generic " "return type"); // Reserve the result block; `ret` in the body breaks to it. AirInstData blk_data; memset(&blk_data, 0, sizeof(blk_data)); uint32_t blk_idx = semaAirInstAppend(sema->insts, AIR_INST_BLOCK, blk_data); uint32_t inline_extra_wm = sema->air_extra->len; U32List sub = { 0 }; InlineCtx ictx2; memset(&ictx2, 0, sizeof(ictx2)); uint32_t inline_callee_ns = IP_NAMESPACE_NONE; // The inline child block's `".type_name_ctx = fn_nav.name, // .type_fqn_ctx = fn_nav.fqn"`. TypeNameCtx inline_name_ctx = sema->type_name_ctx; if (func_decl < sema->ip->items_len && sema->ip->item_tags[func_decl] == IP_TAG_FUNC_DECL) { uint32_t owner_nav = ipFuncDeclOwnerNav(sema->ip, func_decl); const IpNav* nav = ipGetNav(sema->ip, owner_nav); ictx2.fn_name_ctx = nav->name; inline_callee_ns = nav->analysis_namespace; inline_name_ctx = TYPE_NAME_CTX_NAV(nav); } else if (func_decl < sema->ip->items_len && sema->ip->item_tags[func_decl] == IP_TAG_FUNC_INSTANCE) { uint32_t owner_nav = ipFuncInstanceOwnerNav(sema->ip, func_decl); const IpNav* nav = ipGetNav(sema->ip, owner_nav); ictx2.fn_name_ctx = nav->name; inline_callee_ns = nav->analysis_namespace; inline_name_ctx = TYPE_NAME_CTX_NAV(nav); } ictx2.branch_hint_set = sema->ictx->branch_hint_set; ictx2.branch_hint = sema->ictx->branch_hint; ictx2.inline_ret_active = true; ictx2.inline_ret_block = blk_idx; uint32_t saved_type_ns = sema->zcu->ct.type_resolve_ns; bool pushed_type_ns = inline_callee_ns != IP_NAMESPACE_NONE; if (pushed_type_ns) sema->zcu->ct.type_resolve_ns = inline_callee_ns; SemaCtx callee_sema = *sema; callee_sema.zir = callee_zir; callee_sema.fn_ret_ty = ret_ty; callee_sema.inst_values = values; callee_sema.inst_types = types; callee_sema.type_name_ctx = inline_name_ctx; callee_sema.ictx = &ictx2; analyzeBodyInner(&callee_sema, &sub, body, body_len); if (pushed_type_ns) sema->zcu->ct.type_resolve_ns = saved_type_ns; sema->ictx->branch_hint_set = ictx2.branch_hint_set; sema->ictx->branch_hint = ictx2.branch_hint; if (semaRuntimeInlineConstReturnFoldable( sema->ip, sema->insts, &sub, blk_idx, bound_args, args_len)) { uint32_t br_idx = sub.items[sub.len - 1]; // An inline call with runtime arguments can still fold before it // observes them (`debugIncremental` returns `false` when debug // extensions are disabled). Upstream resolves the call to the comptime // return value, so the caller's condbr prunes the dead branch instead // of analyzing a void receiver inside it. Drop only local parameter // setup (`alloc`/`store`/`ptr_cast`) plus the result block; any real // runtime work falls through to materialized inline AIR below. sema->inst_values[inst] = sema->insts->datas[br_idx].pair.b; sema->inst_types[inst] = ret_ty; sema->insts->len = blk_idx; // drops the result block too sema->air_extra->len = inline_extra_wm; free(ictx2.merges); free(sub.items); free(values); free(types); free(bound_args); free(param_insts); return; } sema->inst_values[inst] = AIR_REF_INST_BASE + blk_idx; resolveAnalyzedBlock(sema->zcu, &ictx2, sema->insts, sema->air_extra, block, sub, blk_idx, &sema->inst_values[inst], &sema->inst_types[inst]); free(ictx2.merges); // Block.Merges storage for this inline frame free(values); free(types); free(bound_args); free(param_insts); } // This file's macros end with it (zig1.c is one translation unit). #undef AIR_ALLOC_IS_CONST #undef ZPT_CK_MAX_DEPTH #undef BODY_FIELD_DEFER #undef PEER_PTR_NOT_COERCIBLE #undef SW_INLINE_ELSE_MAX_INT_BITS /* ---- end sema.c ---- */ /* ---- begin sema_comptime.c ---- */ // sema_comptime.c — zig1's own comptime evaluator, with no upstream file. // ctEvalBody runs a declaration's value, type and modifier bodies, field type // and default bodies and comptime calls without emitting AIR, which upstream // Sema does with a comptime block in analyzeBodyInner; the instructions // ctAnalyzedInstOperands lists run through analyzeBodyInner itself // (ctAnalyzeInst). Its frame is a SemaCtx over an inst map (ctFrameInit; // zig1.h "Value encodings"); its comptime-mutable memory is the analyzing // Sema's comptime allocations (getComptimeAlloc), addressed by interned // ptr_comptime_alloc chains. Everything here is zig1's: the evaluator; the // CtCellPtr decode that derives field, element, payload and slice pointers; // its captures and container dedup; the type-ref resolution of signatures and // field types outside an AIR frame (ctResolveParamTypeRef, // ctResolveFuncTypeFromBody); @typeInfo's std.lang.Type values // (ctTypeInfoValueForType, shared with zirTypeInfo); its coercion and pointer // casts; and the comptime and inline call frames (Sema.analyzeCall's comptime // path). Prefixes: ct for the evaluator and its values, sema for an entry // point other files call. Each arm names the zirXxx or Value/Type function it // transcribes. Declarations are in zig1.h. #ifndef ZIG1_H #include "zig1.h" #endif // ── Container member access ───────────────────────────────────────────────── uint32_t ctFileIndexOfZir(const Zcu* zcu, const Zir* zir) { for (uint32_t fi = 0; fi < zcu->num_files; fi++) if (zcu->files[fi].zir == zir) return fi; UNIMPLEMENTED("namespaceLookup: access ZIR is not registered"); ZIG1_TCC_RETURN(0); } static uint32_t ctContainerMemberRef( Zcu* zcu, const Zir* zir, uint32_t container_ty, uint32_t field_name) { InternPool* ip = &zcu->intern_pool; bool is_struct = ipTagIsAnyStruct(ip->item_tags[container_ty]); bool is_union = ipTagIsAnyUnion(ip->item_tags[container_ty]); bool is_enum = ipIsEnumType(ip, container_ty); uint32_t member_ns = IP_NAMESPACE_NONE; if (is_struct) member_ns = ipStructNamespaceOrNone(ip, container_ty); else if (is_union) member_ns = ipUnionNamespaceOrNone(ip, container_ty); else if (is_enum) member_ns = ipEnumNamespaceOrNone(ip, container_ty); else if (ip->item_tags[container_ty] == IP_TAG_TYPE_OPAQUE) member_ns = ipOpaqueNamespaceOrNone(ip, container_ty); else UNIMPLEMENTED("field_ptr: object not a namespace"); if (member_ns == IP_NAMESPACE_NONE && is_struct) UNIMPLEMENTED("field_ptr: namespace has no ns"); uint32_t nav = IP_INDEX_NONE; bool status = false; if (member_ns != IP_NAMESPACE_NONE) status = semaNamespaceLookupAccessible( zcu, zir, member_ns, field_name, &nav); if (status) return navDeclRefAddressResolved(zcu, nav, NULL); if (is_enum) { uint32_t tag_val = ipEnumTagFromFieldName(ip, container_ty, field_name); if (tag_val != IP_INDEX_NONE) { uint32_t ptr_ty = ipGetPtrType(ip, container_ty, IP_PTR_FLAGS_CONST_ONE); return ipGetPtrUav(ip, ptr_ty, tag_val); } } else if (is_union && ipUnionTagUsage(ip, container_ty) == IP_UNION_TAG_USAGE_TAGGED) { uint32_t tag_enum = ztUnionTagTypeHypothetical(zcu, container_ty); uint32_t tag_val = ipEnumTagFromFieldName(ip, tag_enum, field_name); if (tag_val != IP_INDEX_NONE) { uint32_t ptr_ty = ipGetPtrType(ip, tag_enum, IP_PTR_FLAGS_CONST_ONE); return ipGetPtrUav(ip, ptr_ty, tag_val); } } failWithBadMemberAccess(zcu, container_ty, field_name); ZIG1_TCC_RETURN(IP_INDEX_NONE); } static uint32_t ctContainerMemberVal(Zcu* zcu, const Zir* zir, uint32_t container_ty, uint32_t field_name, const char* no_ns_msg) { InternPool* ip = &zcu->intern_pool; bool is_struct = ipTagIsAnyStruct(ip->item_tags[container_ty]); bool is_union = ipTagIsAnyUnion(ip->item_tags[container_ty]); bool is_enum = ipIsEnumType(ip, container_ty); uint32_t member_ns = IP_NAMESPACE_NONE; if (is_struct) member_ns = ipStructNamespaceOrNone(ip, container_ty); else if (is_union) member_ns = ipUnionNamespaceOrNone(ip, container_ty); else if (is_enum) member_ns = ipEnumNamespaceOrNone(ip, container_ty); else if (ip->item_tags[container_ty] == IP_TAG_TYPE_OPAQUE) member_ns = ipOpaqueNamespaceOrNone(ip, container_ty); else UNIMPLEMENTED("field_ptr_load: object not a namespace"); if (member_ns == IP_NAMESPACE_NONE && is_struct) UNIMPLEMENTED(no_ns_msg); uint32_t nav = IP_INDEX_NONE; bool status = false; if (member_ns != IP_NAMESPACE_NONE) status = semaNamespaceLookupAccessible( zcu, zir, member_ns, field_name, &nav); if (status) return navAnalyzeVal(zcu, nav, NULL); // namespaceLookupVal if (is_enum) { uint32_t tag_val = ipEnumTagFromFieldName(ip, container_ty, field_name); if (tag_val != IP_INDEX_NONE) return tag_val; } else if (is_union && ipUnionTagUsage(ip, container_ty) == IP_UNION_TAG_USAGE_TAGGED) { uint32_t tag_enum = ztUnionTagTypeHypothetical(zcu, container_ty); uint32_t tag_val = ipEnumTagFromFieldName(ip, tag_enum, field_name); if (tag_val != IP_INDEX_NONE) return tag_val; } failWithBadMemberAccess(zcu, container_ty, field_name); ZIG1_TCC_RETURN(IP_INDEX_NONE); } static uint32_t ctTypeExprDeclLookup( InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t name_str); // ── Sentinel and frame refs ───────────────────────────────────────────────── static bool ctSentinelRefFrameParamValue(InternPool* ip, const Zir* zir, uint32_t ref, const uint32_t* values, uint32_t* out_value); static bool ctFrameRefValue(InternPool* ip, const Zir* zir, uint32_t ref, const uint32_t* values, uint32_t* out_value); // Resolve the standalone comptime value expression carried by // ArrayTypeSentinel / PtrType. The declared-type path covers the literal and // named-const forms zig1 can otherwise evaluate; a comptime type-body caller // may pass its active frame so a bound `comptime` sentinel parameter resolves // before element-type coercion. Runtime or otherwise compound expressions // still abort loudly. static uint32_t ctResolveSentinelRef(InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t ref, uint32_t elem_ty, const uint32_t* values) { uint32_t raw_value; if (ref < ZIR_REF_START_INDEX) { raw_value = ref; } else { // A value this frame already evaluated (a `.sentinel` decl literal, a // local const): Sema.zirPtrType's `sema.resolveInst(ref)`. if (ctFrameRefValue(ip, zir, ref, values, &raw_value)) { if (raw_value == IP_INDEX_GENERIC_POISON_TYPE) UNIMPLEMENTED("resolveInst: compound sentinel expression"); if (raw_value >= ZIR_REF_START_INDEX && raw_value >= ip->items_len) UNIMPLEMENTED("resolveInst: compound sentinel expression"); } else { uint32_t inst = ref - ZIR_REF_START_INDEX; switch (zir->inst_tags[inst]) { case ZIR_INST_INT: { uint64_t value = zir->inst_datas[inst].int_val; raw_value = ipGetIntComptimeIntU64(ip, value); break; } case ZIR_INST_DECL_VAL: { uint32_t name_off = zir->inst_datas[inst].str_tok.start; const char* name = (const char*)&zir->string_bytes[name_off]; uint32_t name_str = ipGetOrPutString(ip, name, (uint32_t)strlen(name)); uint32_t nav = ctTypeExprDeclLookup(ip, zcu, zir, name_str); if (nav == IP_INDEX_NONE) UNIMPLEMENTED("resolveInst: named sentinel not found"); raw_value = navAnalyzeVal(zcu, nav, NULL); break; } case ZIR_INST_AS_NODE: { ZirInstAs as; zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &as); return ctResolveSentinelRef( ip, zcu, zir, as.operand, elem_ty, values); } default: UNIMPLEMENTED("resolveInst: compound sentinel expression"); } } } return semaSentinelValue(zcu, ip, raw_value, elem_ty); } static bool ctSentinelRefFrameParamValue(InternPool* ip, const Zir* zir, uint32_t ref, const uint32_t* values, uint32_t* out_value) { if (values == NULL || ref < ZIR_REF_START_INDEX) return false; uint32_t inst = ref - ZIR_REF_START_INDEX; switch (zir->inst_tags[inst]) { case ZIR_INST_AS_NODE: { ZirInstAs as; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &as); return ctSentinelRefFrameParamValue( ip, zir, as.operand, values, out_value); } case ZIR_INST_BLOCK_COMPTIME: { ZirInstBlockComptime bc; uint32_t body = zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &bc); if (bc.body_len != 1) return false; uint32_t last = zirBodySlice(zir, body)[0]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) return false; return ctSentinelRefFrameParamValue(ip, zir, zir->inst_datas[last].break_data.operand, values, out_value); } case ZIR_INST_OPTIONAL_PAYLOAD_SAFE: case ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE: { // `[:sentinel.?]const T` in a comptime type body. The optional is a // bound comptime parameter in the same frame; mirror ctEvalBody's // optional-payload fold here so the sentinel resolver sees the payload // value before element-type coercion. Computed sentinel expressions // still fall through to the existing compound-expression abort. uint32_t opt_value; if (!ctSentinelRefFrameParamValue(ip, zir, zir->inst_datas[inst].un_node.operand, values, &opt_value)) return false; if (opt_value >= ip->items_len || ip->item_tags[opt_value] != IP_TAG_OPT_PAYLOAD) return false; if (out_value != NULL) *out_value = ipPayloadVal(ip, opt_value); return true; } case ZIR_INST_PARAM_COMPTIME: case ZIR_INST_PARAM_ANYTYPE_COMPTIME: if (values[inst] == INST_VALUE_UNSET || values[inst] == IP_INDEX_GENERIC_POISON_TYPE) return false; if (out_value != NULL) *out_value = values[inst]; return true; default: return false; } } static bool ctFrameRefValue(InternPool* ip, const Zir* zir, uint32_t ref, const uint32_t* values, uint32_t* out_value) { if (values == NULL || ref < ZIR_REF_START_INDEX) return false; uint32_t inst = ref - ZIR_REF_START_INDEX; if (values[inst] != INST_VALUE_UNSET) { if (out_value != NULL) *out_value = values[inst]; return true; } return ctSentinelRefFrameParamValue(ip, zir, ref, values, out_value); } // ── Scoped lookup and closure captures ────────────────────────────────────── // Resolve a namespace-member type expression (`a.b.T`) used as a // parameter/return/const type. The terminal ZIR instruction is a // `field_ptr_load`/`field_ptr` whose object is itself a member chain rooted at // a `decl_val`/`decl_ref`. Walks the chain, resolving each container to its // struct type (a namespace) and each field to the member decl's value (a // type), mirroring the value-side FIELD_PTR(_LOAD) namespace path. Only // reached for functions whose signature names such a type. // Read + materialize a comptime container's capture (defined with the cell // helpers below). Forward-declared so a param/return type expression that is a // `closure_get` (a method of a generic container returning a captured type) // can resolve. A deferred decl_val/decl_ref capture is resolved to its value // here (lazily); `ctGetCaptureRaw` returns the raw CtCap descriptor (for // nested propagation). static uint32_t ctGetCapture(Zcu* zcu, uint32_t ns, uint32_t index); static bool ctTryGetCaptureRaw( const Zcu* zcu, uint32_t ns, uint32_t index, CtCap* out); static CtCap ctGetCaptureRaw(const Zcu* zcu, uint32_t ns, uint32_t index); static uint32_t ctMaterializeCap(Zcu* zcu, CtCap c); static uint32_t ctTypeOfCapture(Zcu* zcu, CtCap c); static uint32_t ctInstructionLoadCaptureType( const InternPool* ip, uint32_t ptr_ty); static CtCap* ctResolveCaptureWords(Zcu* zcu, const Zir* zir, uint32_t lookup_ns, const uint32_t* caps, uint32_t captures_len, const uint32_t* values, const uint32_t* runtime_values, const uint32_t* runtime_types); static uint32_t ctBoundTypeRef( const InternPool* ip, uint32_t ref, const uint32_t* values); // Look `name_str` up for a type expression: the override namespace chain // first (if set), then the ZIR file's root namespace. Returns a Nav index or // IP_INDEX_NONE. static uint32_t ctTypeExprDeclLookup( InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t name_str) { for (uint32_t ns = zcu->ct.type_resolve_ns; ns != IP_NAMESPACE_NONE; ns = ipGetNamespace(ip, ns)->parent) { uint32_t nav = ipNamespaceLookup(ip, ns, name_str); if (nav != IP_INDEX_NONE) return nav; } uint32_t root_ns = zcuNamespaceOfZir(zcu, zir); if (root_ns == IP_NAMESPACE_NONE) return IP_INDEX_NONE; return ipNamespaceLookup(ip, root_ns, name_str); } // Exported wrapper (zig1.h): the runtime-body DECL_VAL handler // resolves a name through the same scope chain — the active analysis // namespace (set per pending callee) up its parents, then the file root — so // a method body naming a sibling member of its container (`growCapacity` in // the ArrayList instance's `ensureTotalCapacity`) resolves. With no override // set this is exactly the file-root lookup. uint32_t semaScopedDeclLookup( InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t name_str) { return ctTypeExprDeclLookup(ip, zcu, zir, name_str); } uint32_t semaScopedThisType(Zcu* zcu, const Zir* zir) { InternPool* ip = &zcu->intern_pool; uint32_t ns = zcu->ct.type_resolve_ns; if (ns == IP_NAMESPACE_NONE) ns = zcuNamespaceOfZir(zcu, zir); if (ns == IP_NAMESPACE_NONE) return IP_INDEX_NONE; return ipGetNamespace(ip, ns)->owner_type; } // (part of) Sema.zig:zirClosureGet // Exported (zig1.h): read capture[index] of the active analysis // namespace, walking the parent chain (a nested container's method sees the // enclosing generic's captures). The runtime-body CLOSURE_GET handler uses // this to resolve `T` inside an instantiated generic's method body // (Sema.zirClosureGet). Returns IP_INDEX_NONE when no scope is set or the // capture is unresolved (the caller aborts loudly). static bool ctScopedCaptureRaw(Zcu* zcu, uint32_t index, CtCap* out) { InternPool* ip = &zcu->intern_pool; for (uint32_t ns = zcu->ct.type_resolve_ns; ns != IP_NAMESPACE_NONE; ns = ipGetNamespace(ip, ns)->parent) { // Slot[index] at this scope; a miss (no slot) or an unresolvable // capture reads none; do not satisfy a present-but-unresolved child // slot from a parent slot with the same numeric index. if (ctTryGetCaptureRaw(zcu, ns, index, out)) return true; } return false; } uint32_t semaScopedCaptureGet(Zcu* zcu, uint32_t index) { CtCap c; return ctScopedCaptureRaw(zcu, index, &c) ? ctMaterializeCap(zcu, c) : IP_INDEX_NONE; } // `.nav_val => |nav|`: the Nav such a capture names, IP_INDEX_NONE for any // other capture. uint32_t semaScopedCaptureNavVal(Zcu* zcu, uint32_t index) { CtCap c; return ctScopedCaptureRaw(zcu, index, &c) && c.kind == CAP_DECL_VAL ? c.v : IP_INDEX_NONE; } // Sema.zig:zirClosureGet, InternPool.zig:typeOf // Exported (zig1.h): `@TypeOf(closure_get)` reads the capture's // Type. For a runtime instruction capture, Sema.zirClosureGet in a typeof // block supplies a dummy value with the captured instruction's monomorphic // type; zig1 records that type in CAP_RUNTIME_TYPE and returns it here. For // comptime/nav captures, materialize the existing capture and ask // InternPool.typeOf. uint32_t semaScopedCaptureTypeOfGet(Zcu* zcu, uint32_t index) { InternPool* ip = &zcu->intern_pool; for (uint32_t ns = zcu->ct.type_resolve_ns; ns != IP_NAMESPACE_NONE; ns = ipGetNamespace(ip, ns)->parent) { CtCap c; if (!ctTryGetCaptureRaw(zcu, ns, index, &c)) continue; return ctTypeOfCapture(zcu, c); } return IP_INDEX_NONE; } // Sema.zig:zirThis // The enclosing container type: the owner of Zcu.ct.type_resolve_ns. static uint32_t ctThisType(InternPool* ip, const Zcu* zcu) { if (zcu->ct.type_resolve_ns == IP_NAMESPACE_NONE) UNIMPLEMENTED("@This: ctThisType: @This() with no container ns"); return ipGetNamespace(ip, zcu->ct.type_resolve_ns)->owner_type; } static uint32_t ctResolveNamespaceTypeRef( InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t ref) { if (ref < ZIR_REF_START_INDEX) return ref; // preinterned type uint32_t pi = ref - ZIR_REF_START_INDEX; ZirInstTag t = zir->inst_tags[pi]; if (t == ZIR_INST_DECL_VAL || t == ZIR_INST_DECL_REF) { uint32_t name_off = zir->inst_datas[pi].str_tok.start; const char* name = (const char*)&zir->string_bytes[name_off]; uint32_t name_str = ipGetOrPutString(ip, name, (uint32_t)strlen(name)); uint32_t nav = ctTypeExprDeclLookup(ip, zcu, zir, name_str); if (nav == IP_INDEX_NONE) UNIMPLEMENTED("funcCommon: decl not found"); if (t == ZIR_INST_DECL_VAL) return navAnalyzeVal(zcu, nav, NULL); return navAnalyzeRefLoad(zcu, nav); } if (t == ZIR_INST_REF) { // Address-of in a namespace type chain can wrap a comptime type value // (`F(T).Slice`'s object side). Single-ref namespace // resolution treats this comptime pointer as transparent. return ctResolveNamespaceTypeRef( ip, zcu, zir, zir->inst_datas[pi].un_tok.operand); } // `@This().State` as a namespace chain's object. if (t == ZIR_INST_EXTENDED && zir->inst_datas[pi].extended.opcode == (uint16_t)ZIR_EXT_THIS) return ctThisType(ip, zcu); if (t == ZIR_INST_IMPORT) { // `@import("dep.zig").Member` in type position: resolve the import to // the imported file's root struct type, then let the FIELD_PTR(_LOAD) // namespace chain below walk into its members. This mirrors the direct // `@import("dep.zig")` type arm in ctResolveParamTypeRef and uses the // same import helper as ctEvalBody/analyzeBodyInner. uint32_t imp_file = ctFileIndexOfZir(zcu, zir); return semaImportInstRootType(zcu, imp_file, pi, NULL); } if (t == ZIR_INST_FIELD_PTR || t == ZIR_INST_FIELD_PTR_LOAD) { ZirInstField fp; zirExtraData(zir, zir->inst_datas[pi].pl_node.payload_index, &fp); const char* field = zirNullTerminatedString(zir, fp.field_name_start); uint32_t obj_ty = ctResolveNamespaceTypeRef(ip, zcu, zir, fp.lhs); uint32_t member_ns = ipContainerNamespaceOrNone(ip, obj_ty); if (member_ns == IP_NAMESPACE_NONE) UNIMPLEMENTED("funcCommon: namespace has no ns"); uint32_t field_str = ipGetOrPutString(ip, field, (uint32_t)strlen(field)); uint32_t mnav = IP_INDEX_NONE; bool status = semaNamespaceLookupAccessible( zcu, zir, member_ns, field_str, &mnav); if (!status) failWithBadMemberAccess(zcu, obj_ty, field_str); // namespaceLookupVal for a load, namespaceLookupRef otherwise. if (t == ZIR_INST_FIELD_PTR_LOAD) return navAnalyzeVal(zcu, mnav, NULL); return navAnalyzeRefLoad(zcu, mnav); } UNIMPLEMENTED("funcCommon: unsupported ref instruction"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // A ptr_type's elem_type and its trailing refs in Zir.Inst.PtrType's order // (sentinel, align, address_space, bit_start, host_size), each ZIR_REF_NONE // when its flag is clear. typedef struct { uint32_t elem_type; uint32_t sentinel; uint32_t align; uint32_t address_space; uint32_t bit_start; uint32_t host_size; } CtPtrTypeRefs; static CtPtrTypeRefs ctPtrTypeRefs(const Zir* zir, uint32_t inst) { uint8_t flags = zir->inst_datas[inst].ptr_type.flags; ZirInstPtrType extra; uint32_t i = zirExtraData( zir, zir->inst_datas[inst].ptr_type.payload_index, &extra); CtPtrTypeRefs r = { extra.elem_type, ZIR_REF_NONE, ZIR_REF_NONE, ZIR_REF_NONE, ZIR_REF_NONE, ZIR_REF_NONE }; if (flags & ZIR_PTR_TYPE_FLAGS_HAS_SENTINEL) r.sentinel = zir->extra[i++]; if (flags & ZIR_PTR_TYPE_FLAGS_HAS_ALIGN) r.align = zir->extra[i++]; if (flags & ZIR_PTR_TYPE_FLAGS_HAS_ADDRSPACE) r.address_space = zir->extra[i++]; if (flags & ZIR_PTR_TYPE_FLAGS_HAS_BIT_RANGE) { r.bit_start = zir->extra[i++]; r.host_size = zir->extra[i]; } return r; } // An array_type's Bin { len, elem } or an array_type_sentinel's // ArrayTypeSentinel { len, sentinel, elem_type }; `sentinel` may be NULL. static void ctArrayTypeRefs(const Zir* zir, uint32_t inst, uint32_t* len, uint32_t* elem, uint32_t* sentinel) { uint32_t payload = zir->inst_datas[inst].pl_node.payload_index; if (zir->inst_tags[inst] == ZIR_INST_ARRAY_TYPE_SENTINEL) { ZirInstArrayTypeSentinel extra; zirExtraData(zir, payload, &extra); *len = extra.len; *elem = extra.elem_type; if (sentinel != NULL) *sentinel = extra.sentinel; } else { ZirInstBin extra; zirExtraData(zir, payload, &extra); *len = extra.lhs; *elem = extra.rhs; if (sentinel != NULL) *sentinel = ZIR_REF_NONE; } } // (part of) Sema.zig:analyzeCall, Zcu.zig:resolveReferences, // Sema.zig:zirReifyInt // Type positions. Upstream analyzes every type, tag, align, default and // callconv body in a Block with resolveInlineBody or resolveGenericBody, then // analyzeAsType or analyzeAsAlign (zirParam, zirFunc, zirFuncFancy, // analyzeNavType, analyzeCall, type_resolution.zig). zig1 has three // evaluators for them: // S ctResolveParamTypeRef, with no frame: it reads the body's break // operand and the instructions that operand names (preinterned refs, // decl_val, field chains, @import, @This, @TypeOf of a parameter, // closure_get, int, pointer, array, vector, optional, error-union, // error-set, tuple, fn-proto and inline struct, enum and union types). // F ctEvalBody over the whole body (ctFoldComptimeTypeBody, // ctFoldNavTypeBody, ctFoldComptimeBodyValue, ctEvalBodyInBlock), which // runs most instructions through analyzeBodyInner (ctAnalyzeInst). // R analyzeBodyInner itself, in a function body. // This predicate chooses F over S from the body's shape alone, never from the // position. S stays wherever it can read the shape because the InternPool // order is byte-identical through it; moving a position to F is IP-parity // work. // - param type (zirParam): navFuncDecl and compilationDeclFuncInfo give // `self: @This()` the owner type; a pointer whose align is not an int // literal (ctRetTypeHasComputedAlign) is generic_poison, which upstream // types concretely (CONSOLIDATION D46); then F when this predicate holds, // else S. // - return type (zirFunc, zirFuncFancy): a body goes to ctResolveRetTypeBody, // F or S; a bare ret_ty_ref is S, or generic_poison on a computed align in // navFuncDecl (non-fancy) and in a comptime call. // - a fn-proto type (ctResolveFuncTypeFromBody): a param is a preinterned ref, // a frame binding (ctBoundTypeRef), F when this predicate holds and there is // no live frame, else ctFuncProtoBodyType; that and a return type body take // a binding, then F in a live ctEvalBody frame (ctEvalBodyInFrame, no // predicate), else S; a bare return ref takes a binding, else S. So // `fn (u8) Gen(u8)` as a field type or in a runtime body aborts in S // (CONSOLIDATION "Known defects"). // - a generic callee's param and return types at a runtime call (analyzeCall's // generic_inst_map): semaResolveGenericParamType and // semaResolveGenericRetType read a bound `T` or `@TypeOf(param)` from the // bindings, compose a pointer over a bound type directly // (semaResolveGenericComposedPtrType), and fold any other multi-instruction // body in F (semaCtEvalGenericBody). // - the same at a comptime call: ctParamInstConcreteType reads a // one-instruction body's binding (ctBoundTypeRef), else F; // ctAnalyzeComptimeCallTyped types a non-generic return as a return type // above, ctBoundFuncReturnType a generic one (F for a body, the binding for // a bare ref). // - a declaration's type (analyzeNavType): zptEnsureNavTypeUpToDateInner, F or // S; // its align, linksection and addrspace: navModifierBodyValue, F. // - struct and union field types (resolveStructLayout, resolveUnionLayout): // semaResolveFieldTypeBody at the first layout demand, F or S in the // container's namespace. A packed struct resolves its field types at the // declaration (semaBodyStructTypePacked): int_type directly, a decl_val by // semaBodyFieldType, anything else deferred to semaResolveFieldTypeBody. // - field defaults (resolveStructDefaults), enum and union tag values // (resolveEnumLayout): always F (ctFoldComptimeBodyValue), with the // declaration's inst mapped to the result type as upstream maps it. Field // aligns: always F, with the same mapping, which upstream does not make // (CONSOLIDATION D47). // - union tag type (resolveUnionLayout), explicit packed union backing // (resolvePackedUnionLayout), enum tag type (resolveEnumLayout): // semaBodyUnionType and semaEnumDeclType, F or S; a `union(enum(T))`'s T: a // preinterned ref, F, or an int_type read directly; a packed struct's // backing (resolvePackedStructLayout): always F. // - callconv (zirFuncFancy): ccResolveFuncFancy; a cc body is F; a cc ref is a // `std.lang` literal read directly (ccStdLangValue), the live frame's value, // or the declaration's value body re-run in F up to that instruction. // - align inside a pointer type, beyond the params and bare returns above: the // evaluator of the enclosing type; S reads an int, @alignOf or a field // chain, and any other align operand folds. // - a type in a function body: R (zirPtrType and the rest); a fn-proto block // goes through semaFuncTypeFromRuntimeBlock to ctResolveFuncTypeFromBody, // with a live frame only for a cc ref. // Inside F, ctEvalPtrType hands a pointer type with a named pointee to S // (ctFrameFreeTypeRef). // Folded shapes: a generic CALL/FIELD_CALL result (bare, as a namespace-member // object, or under optional/error-union/pointer/array wrappers); a comptime // SWITCH_BLOCK type; an optional unwrap of a type-info field; a field chain // whose object is a `@typeInfo` value; block (an `if` type expression, not // BLOCK_INLINE); REIFY_INT; @FieldType; closure_get, @Tuple and peer @TypeOf; // an ARRAY_TYPE(_SENTINEL) whose element folds or whose length S cannot read; // and a PTR_TYPE whose align operand S cannot read. DECL_REF is never folded: // ctResolveNamespaceTypeRef treats it as transparent, which would silently // accept a `&decl` array length upstream rejects, while F aborts loudly. bool ctTypeBodyNeedsComptimeFold(const Zir* zir, uint32_t ref) { if (ref < ZIR_REF_START_INDEX) return false; uint32_t pi = ref - ZIR_REF_START_INDEX; ZirInstTag t = zir->inst_tags[pi]; if (t == ZIR_INST_CALL || t == ZIR_INST_FIELD_CALL || t == ZIR_INST_BLOCK || t == ZIR_INST_REIFY_INT || t == ZIR_INST_SWITCH_BLOCK || t == ZIR_INST_OPTIONAL_PAYLOAD_SAFE || t == ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE || t == ZIR_INST_TYPE_INFO || t == ZIR_INST_FIELD_TYPE_REF) return true; // Sema.resolveInlineBody evaluates these like any other type body: // `@Tuple(...)` and peer `@TypeOf(a, b)` among them. if (t == ZIR_INST_EXTENDED && (zir->inst_datas[pi].extended.opcode == (uint16_t)ZIR_EXT_CLOSURE_GET || zir->inst_datas[pi].extended.opcode == (uint16_t)ZIR_EXT_REIFY_TUPLE || zir->inst_datas[pi].extended.opcode == (uint16_t)ZIR_EXT_TYPEOF_PEER)) return true; if (t == ZIR_INST_REF) return ctTypeBodyNeedsComptimeFold( zir, zir->inst_datas[pi].un_tok.operand); if (t == ZIR_INST_FIELD_PTR || t == ZIR_INST_FIELD_PTR_LOAD) { ZirInstField fp; zirExtraData(zir, zir->inst_datas[pi].pl_node.payload_index, &fp); return ctTypeBodyNeedsComptimeFold(zir, fp.lhs); } if (t == ZIR_INST_OPTIONAL_TYPE) return ctTypeBodyNeedsComptimeFold( zir, zir->inst_datas[pi].un_node.operand); if (t == ZIR_INST_ERROR_UNION_TYPE) { // Bin { lhs=error set, rhs=payload }: either side may hide the // foldable shape (a finite/merged error-set lhs is itself handled by // ctResolveParamTypeRef's own ERROR_UNION_TYPE arm without folding, // but a lhs or rhs containing a nested CALL/FIELD_CALL/etc. must still // be detected here). ZirInstBin eu; zirExtraData(zir, zir->inst_datas[pi].pl_node.payload_index, &eu); return ctTypeBodyNeedsComptimeFold(zir, eu.lhs) || ctTypeBodyNeedsComptimeFold(zir, eu.rhs); } if (t == ZIR_INST_PTR_TYPE) { CtPtrTypeRefs ptr = ctPtrTypeRefs(zir, pi); if (ptr.align != ZIR_REF_NONE) { // The align placement decision, see above: fold when the align // operand is an instruction ctResolveParamTypeRef's align arm // cannot read. It reads a preinterned ref (`align(1)` lowers to // ZIR_REF_ONE), INT, ALIGN_OF, and a FIELD_PTR(_LOAD) chain via // ctResolveNamespaceTypeRef; a chain is only readable when its own // object side is, so that case recurses instead of being excluded // outright. uint32_t align_ref = ptr.align; if (align_ref >= ZIR_REF_START_INDEX) { ZirInstTag at = zir->inst_tags[align_ref - ZIR_REF_START_INDEX]; if (at == ZIR_INST_FIELD_PTR || at == ZIR_INST_FIELD_PTR_LOAD) { if (ctTypeBodyNeedsComptimeFold(zir, align_ref)) return true; } else if (at != ZIR_INST_INT && at != ZIR_INST_ALIGN_OF) { return true; } } } return ctTypeBodyNeedsComptimeFold(zir, ptr.elem_type); } if (t == ZIR_INST_ARRAY_TYPE || t == ZIR_INST_ARRAY_TYPE_SENTINEL) { // Fold when the element needs folding, or when the length is an // instruction the single-ref resolver's array_type arm cannot read // directly — everything except a literal INT and a bare DECL_VAL. uint32_t len_ref, elem_ref; ctArrayTypeRefs(zir, pi, &len_ref, &elem_ref, NULL); if (ctTypeBodyNeedsComptimeFold(zir, elem_ref)) return true; if (len_ref >= ZIR_REF_START_INDEX) { ZirInstTag lt = zir->inst_tags[len_ref - ZIR_REF_START_INDEX]; if (lt != ZIR_INST_INT && lt != ZIR_INST_DECL_VAL) return true; } } return false; } // ── Function types (zirFunc, zirFuncFancy) ────────────────────────────────── // (part of) Sema.zig:zirFunc // Resolve a bare function type expression (`fn (A, B) R`, no body) to its // interned `type_function`. Mirrors Sema.zirFunc for the body-less case: it // returns a function type (not a function value/decl). The `body` is the ZIR // sub-block that AstGen emits for the fn-proto type expression — a run of // `param`/`param_anytype` instructions followed by a body-less `func` (or // `func_fancy`) instruction. Params + return type resolve via the same // ctResolveParamTypeRef machinery the fn-decl path uses; when `values` is not // NULL, direct refs to an enclosing generic type param are first substituted // from that active comptime-eval frame. The callconv is `.auto`; other shapes // abort, and no `func_decl` or Nav is interned. static uint32_t ctResolveFuncTypeRefInBody(InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t ref, const uint32_t* values) { if (values) { uint32_t bound_ty = ctBoundTypeRef(ip, ref, values); if (bound_ty != IP_INDEX_NONE) return bound_ty; } return ctResolveParamTypeRef(ip, zcu, zir, ref); } static uint32_t ctEvalBodyInFrame( SemaCtx* frame, const uint32_t* body, uint32_t body_len); static void ctFrameInit(SemaDetachedFrame* f, Zcu* zcu, const Zir* zir, uint32_t* values, uint32_t fn_ret_ty, CtBlockScope scope); static void ctFrameDeinit(SemaDetachedFrame* f); // A fn proto's parameter or return type body (ending in `break_inline` of // `ref`). zirFunc / zirFuncFancy resolve it by analyzing the body // (`sema.resolveInlineBody` then `resolveType`); `live` non-NULL is // ctEvalBody's frame, whose Block and inst map the body runs in (a generic // parameter's bound type inside `?*const fn () error{}!T`, a container in the // proto named after the frame's type_name_ctx). Otherwise the single-ref // resolver answers from `ref`. static uint32_t ctFuncProtoBodyType(InternPool* ip, Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t ref, SemaCtx* live, const uint32_t* values) { if (live != NULL && ref >= ZIR_REF_START_INDEX && ctBoundTypeRef(ip, ref, live->inst_values) == IP_INDEX_NONE) { uint32_t rv = ctEvalBodyInFrame(live, body, body_len); if (rv == IP_INDEX_NONE) UNIMPLEMENTED("funcCommon: fn proto type body"); return ctValidatedTypeResult(ip, rv, "funcCommon: fn proto type body"); } return ctResolveFuncTypeRefInBody(ip, zcu, zir, ref, values); } // `FuncType.comptime_bits` is a u32 (`"comptime_bits |= @as(u32, 1) << // @intCast(i)"`): a `comptime` parameter past index 31 has no bit, which // upstream's `@intCast` rejects too. uint32_t ctFuncTypeComptimeBit(uint32_t param_index) { if (param_index >= IP_FUNC_TYPE_PARAM_BITS) UNIMPLEMENTED( "funcCommon: comptime parameter past FuncType.comptime_bits"); return 1u << param_index; } static uint32_t ctResolveFuncTypeFromBody(InternPool* ip, Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, const uint32_t* values, SemaCtx* live) { uint32_t* param_types = ARR_INIT(uint32_t, body_len ? body_len : 1); uint32_t nparams = 0; uint32_t comptime_bits = 0; uint32_t func_inst = UINT32_MAX; for (uint32_t b = 0; b < body_len; b++) { uint32_t inst = body[b]; ZirInstTag t = zir->inst_tags[inst]; if (t == ZIR_INST_PARAM || t == ZIR_INST_PARAM_COMPTIME) { if (t == ZIR_INST_PARAM_COMPTIME) comptime_bits |= ctFuncTypeComptimeBit(nparams); ZirInstParam param; const uint32_t* pbody = zirBodySlice(zir, zirExtraData( zir, zir->inst_datas[inst].pl_tok.payload_index, ¶m)); uint32_t param_ty; if (param.type & ZIR_PARAM_TYPE_IS_GENERIC) { param_ty = IP_INDEX_GENERIC_POISON_TYPE; } else { uint32_t plen = param.type & ZIR_PARAM_TYPE_BODY_LEN_MASK; if (plen == 0) UNIMPLEMENTED("funcCommon: empty param body"); uint32_t last = pbody[plen - 1]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) UNREACHABLE("ctResolveFuncTypeFromBody: param body not " "break_inline"); uint32_t ref = zir->inst_datas[last].break_data.operand; if (ref < ZIR_REF_START_INDEX) { param_ty = ref; } else if (values && (param_ty = ctBoundTypeRef(ip, ref, values)) != IP_INDEX_NONE) { // Bound generic type parameter. } else if (live == NULL && ctTypeBodyNeedsComptimeFold(zir, ref)) { // Generic call in type position (`*Queue(...)`): fold // the whole param body comptime, scoped to the current // type-resolution namespace (the vtable struct's ns set // by ctResolveFieldTypeRefInNs), where the generic-call // instantiation dedups on {zir, struct_inst, captures} // exactly like the field-type fold. param_ty = ctFoldComptimeTypeBody(zcu, zir, pbody, plen, zcu->ct.type_resolve_ns, TYPE_NAME_CTX_NONE); } else { param_ty = ctFuncProtoBodyType( ip, zcu, zir, pbody, plen, ref, live, values); } } param_types[nparams++] = param_ty; } else if (t == ZIR_INST_PARAM_ANYTYPE || t == ZIR_INST_PARAM_ANYTYPE_COMPTIME) { if (t == ZIR_INST_PARAM_ANYTYPE_COMPTIME) comptime_bits |= ctFuncTypeComptimeBit(nparams); param_types[nparams++] = IP_INDEX_GENERIC_POISON_TYPE; } else if (t == ZIR_INST_FUNC || t == ZIR_INST_FUNC_INFERRED) { func_inst = inst; break; } else if (t == ZIR_INST_FUNC_FANCY) { func_inst = inst; break; } else if (live != NULL) { // Any other instruction of the proto block (the `error{}!T` a // return-type REF names) is analyzed in order like the rest of // the block, in the live comptime frame. if (ctEvalBodyInFrame(live, &body[b], 1) != IP_INDEX_NONE) UNIMPLEMENTED( "funcCommon: proto block instruction did not complete"); } } if (func_inst == UINT32_MAX) UNIMPLEMENTED("funcCommon: no func instruction in body"); if (zir->inst_tags[func_inst] == ZIR_INST_FUNC_INFERRED) UNIMPLEMENTED("funcCommon: inferred error set proto"); ZirFnInfo fn_info = zirGetFnInfo(zir, func_inst); if (fn_info.body_len != 0) UNIMPLEMENTED("funcCommon: func has a body (not a type)"); // A bare fn-proto type: callconv `.auto` (non-export), no noalias. uint32_t bits = 0; uint32_t noalias_bits = 0; FuncCallconv proto_cc = { 0, 0, 0 }; if (zir->inst_tags[func_inst] == ZIR_INST_FUNC_FANCY) { ZirInstFuncFancy extra; zirExtraData( zir, zir->inst_datas[func_inst].pl_node.payload_index, &extra); bits = extra.bits; if (fn_info.inferred_error_set || fn_info.ret_ty_is_generic) UNIMPLEMENTED("func_fancy: ctResolveFuncTypeFromBody: unsupported " "func_fancy proto"); if ((bits & ZIR_FUNC_FANCY_BITS_HAS_ANY_NOALIAS) != 0) noalias_bits = fn_info.body[-1]; // the word before the body if ((bits & (ZIR_FUNC_FANCY_BITS_HAS_CC_BODY | ZIR_FUNC_FANCY_BITS_HAS_CC_REF)) != 0) proto_cc = ccResolveFuncFancy(zcu, zir, func_inst, body, body_len, zcu->ct.type_resolve_ns, live != NULL ? live->type_name_ctx : TYPE_NAME_CTX_NONE, live != NULL ? live->inst_values : NULL); } uint32_t ret_ty; if (fn_info.ret_ty_is_generic) { ret_ty = IP_INDEX_GENERIC_POISON_TYPE; } else if (fn_info.ret_ty_body_len != 0) { uint32_t last = fn_info.ret_ty_body[fn_info.ret_ty_body_len - 1]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) UNREACHABLE( "ctResolveFuncTypeFromBody: ret body not break_inline"); ret_ty = ctFuncProtoBodyType(ip, zcu, zir, fn_info.ret_ty_body, fn_info.ret_ty_body_len, zir->inst_datas[last].break_data.operand, live, values); } else { ret_ty = ctResolveFuncTypeRefInBody( ip, zcu, zir, fn_info.ret_ty_ref, values); } bool is_var_args = (bits & ZIR_FUNC_FANCY_BITS_IS_VAR_ARGS) != 0; uint32_t base_flags = ipTypeFunctionFlags(false); // funcCommon: `if (var_args) checkCallConvSupportsVarArgs(cc)`. if (is_var_args) ccCheckSupportsVarArgs(&proto_cc); if ((bits & (ZIR_FUNC_FANCY_BITS_HAS_CC_BODY | ZIR_FUNC_FANCY_BITS_HAS_CC_REF)) != 0) { ccValidate(zcu, &proto_cc, (bits & ZIR_FUNC_FANCY_BITS_IS_NOINLINE) != 0, nparams, comptime_bits, param_types, ret_ty, false, false); base_flags = ccPack(&proto_cc); } if (is_var_args) base_flags |= IP_FUNC_FLAGS_IS_VAR_ARGS; uint32_t func_ty = ipGetFuncType(ip, ret_ty, param_types, nparams, base_flags, comptime_bits, noalias_bits); free(param_types); return func_ty; } // A fn-proto type block in a runtime analysis body: its parameter and // return type refs may name that body's comptime-known type values (a local // `const P = *T;`), so they are handed to the resolver like ctEvalBody's. A // `callconv(x)` operand is an instruction of the block, so a proto that has // one runs the block as a live frame over the body's comptime-known values, // in the current Sema (Sema.zirFuncFancy's `resolveInst(cc_ref)`). uint32_t semaFuncTypeFromRuntimeBlock(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, const uint32_t* inst_values, TypeNameCtx type_name_ctx) { InternPool* ip = &zcu->intern_pool; bool has_cc_ref = false; for (uint32_t b = 0; b < body_len; b++) { if (zir->inst_tags[body[b]] != ZIR_INST_FUNC_FANCY) continue; ZirInstFuncFancy extra; zirExtraData( zir, zir->inst_datas[body[b]].pl_node.payload_index, &extra); has_cc_ref = (extra.bits & ZIR_FUNC_FANCY_BITS_HAS_CC_REF) != 0; } uint32_t* values = semaInstMapAlloc(zir); for (uint32_t i = 0; i < zir->inst_len; i++) { uint32_t v = inst_values[i]; if (v < ip->items_len && (has_cc_ref || ipIndexIsType(ip, v))) values[i] = v; } uint32_t ty; if (has_cc_ref) { SemaDetachedFrame f; ctFrameInit(&f, zcu, zir, values, IP_INDEX_NONE, (CtBlockScope) { zcu->ct.type_resolve_ns, type_name_ctx, NULL, IP_INDEX_NONE, false }); ty = ctResolveFuncTypeFromBody( ip, zcu, zir, body, body_len, values, &f.sema); ctFrameDeinit(&f); } else { ty = ctResolveFuncTypeFromBody( ip, zcu, zir, body, body_len, values, NULL); } free(values); return ty; } static bool ctBlockInlineBreaksFuncType( const Zir* zir, const uint32_t* body, uint32_t body_len) { if (body_len < 2) return false; uint32_t last = body[body_len - 1]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) return false; uint32_t ref = zir->inst_datas[last].break_data.operand; if (ref < ZIR_REF_START_INDEX) return false; uint32_t func_inst = ref - ZIR_REF_START_INDEX; if (func_inst >= zir->inst_len || !zirIsFuncInst(zir->inst_tags[func_inst])) return false; if (zirGetFnInfo(zir, func_inst).body_len != 0) return false; for (uint32_t i = 0; i + 1 < body_len; i++) if (body[i] == func_inst) return true; return false; } // (part of) Builtin.zig:hash, Sema.zig:zirArrayType, Sema.zig:zirVectorType // The `len` operand of a `[len]T` / `@Vector(len, T)` type expression, as a // frame with no inst map can see it: the `0`/`1` preinterns, a ZIR int // literal, or a ref naming a comptime-int const -- either a bare identifier // (`[count]usize`, a DECL_VAL naming a container-scoped sibling) or a // namespace-member chain // (`[a.b.len]u8`, a FIELD_PTR_LOAD whose object side is itself a chain). // // Upstream has one evaluator for every length: `sema.resolveInt(block, // len_src, ref, dest_ty, ...)` is just `resolveInst(zir_ref)` + // `analyzeAsInt` (src/Sema.zig) -- the length instruction's value // was already produced by analyzeBodyInner and is read out of the inst map. // ctResolveParamTypeRef has no inst map, so the stand-in for `resolveInst` on // a decl/namespace-chain ref is the existing chain resolver // `ctResolveNamespaceTypeRef` (which ends in the same // `zptEnsureNavValUpToDate` the bare DECL_VAL shape used). No new evaluator is // introduced: this copies the ptr_type align arm's precedent (`align(a.b)`), // which resolves the very same "ref naming a namespace-chain comptime int" // shape the very same way. The gate deliberately excludes DECL_REF (which // ctResolveNamespaceTypeRef treats as transparent at inner chain levels): a // length is an rvalue position, so a `&decl` length stays loud rather than // silently yielding the pointee. // // What upstream does not share between the two spellings is the dest type its // coerce interns -- `[len: usize]T` vs `Vector(len: u32, ...)` // (Sema.zirArrayType passes `.usize`, Sema.zirVectorType passes // `.u32`) -- so that intern stays with the caller. static uint64_t ctResolveTypeLenRef(InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t len_ref, const char* what) { char msg[96]; if (len_ref == ZIR_REF_ZERO) return 0; if (len_ref == ZIR_REF_ONE) return 1; if (len_ref >= ZIR_REF_START_INDEX && zir->inst_tags[len_ref - ZIR_REF_START_INDEX] == ZIR_INST_INT) { uint64_t n = zir->inst_datas[len_ref - ZIR_REF_START_INDEX].int_val; ipGetIntComptimeIntU32(ip, (uint32_t)n); return n; } if (len_ref >= ZIR_REF_START_INDEX && (zir->inst_tags[len_ref - ZIR_REF_START_INDEX] == ZIR_INST_DECL_VAL || zir->inst_tags[len_ref - ZIR_REF_START_INDEX] == ZIR_INST_FIELD_PTR || zir->inst_tags[len_ref - ZIR_REF_START_INDEX] == ZIR_INST_FIELD_PTR_LOAD)) { uint32_t lval = ctResolveNamespaceTypeRef(ip, zcu, zir, len_ref); int64_t lv; bool lct; uint32_t lbits; if (!ctReadInt(ip, lval, &lv, &lct, &lbits) || lv < 0) { snprintf(msg, sizeof msg, "funcCommon: param type, %s length decl not a non-negative " "int", what); UNIMPLEMENTED(msg); } ipGetIntComptimeIntU32(ip, (uint32_t)lv); return (uint64_t)lv; } snprintf( msg, sizeof msg, "funcCommon: param type, %s non-int length", what); UNIMPLEMENTED(msg); ZIG1_TCC_RETURN(0); } // Evaluator S of the type-position routing (ctTypeBodyNeedsComptimeFold): the // type a type body's break operand `ref` names, read without a frame. // A container the ref declares is named in `type_name_ctx`, the field or // union tag position's container (TYPE_NAME_CTX_NONE: unnamed). static uint32_t ctResolveTypeRef(InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t ref, TypeNameCtx type_name_ctx) { if (ref < ZIR_REF_START_INDEX) return ref; // preinterned type uint32_t pi = ref - ZIR_REF_START_INDEX; if (zir->inst_tags[pi] == ZIR_INST_TYPEOF_BUILTIN) { // An explicit declaration type such as `@TypeOf(target)` is a // type-only block ending in break_inline(%operand). Resolve the // operand just far enough to recover its exact type. The function // target shape is deliberately handled through general Nav value // analysis so aliases and generated instances retain canonical // owner/type identity. ZirInstBlock tb; const uint32_t* tbody = zirBodySlice(zir, zirExtraData(zir, zir->inst_datas[pi].pl_node.payload_index, &tb)); if (tb.body_len == 0) UNIMPLEMENTED("funcCommon: empty typeof body"); uint32_t last = tbody[tb.body_len - 1]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) UNREACHABLE( "funcCommon: param type, typeof body not break_inline"); uint32_t operand = zir->inst_datas[last].break_data.operand; if (zcu->ct.param_tys != NULL && operand >= ZIR_REF_START_INDEX && operand - ZIR_REF_START_INDEX < zir->inst_len && zcu->ct.param_tys[operand - ZIR_REF_START_INDEX] != IP_INDEX_NONE) return zcu->ct.param_tys[operand - ZIR_REF_START_INDEX]; if (operand < ZIR_REF_START_INDEX) { uint32_t operand_ty = ipTypeOf(ip, operand); if (operand_ty == IP_INDEX_NONE) UNIMPLEMENTED("funcCommon: typeof preintern has no type"); return operand_ty; } uint32_t oi = operand - ZIR_REF_START_INDEX; if (zir->inst_tags[oi] == ZIR_INST_EXTENDED && zir->inst_datas[oi].extended.opcode == (uint16_t)ZIR_EXT_CLOSURE_GET) { // Sema.zirTypeofBuiltin analyzes the operand body with // `is_typeof = true`; a closure_get then contributes the // captured value's type (runtime captures supply their saved // monomorphic type, comptime/nav captures materialize normally). uint32_t idx = zir->inst_datas[oi].extended.small; uint32_t operand_ty = semaScopedCaptureTypeOfGet(zcu, idx); if (operand_ty == IP_INDEX_NONE) UNIMPLEMENTED("closure_get: ctResolveParamTypeRef: typeof " "closure_get unresolved capture"); return operand_ty; } if (zir->inst_tags[oi] == ZIR_INST_DECL_VAL || zir->inst_tags[oi] == ZIR_INST_DECL_REF) { uint32_t name_off = zir->inst_datas[oi].str_tok.start; const char* name = (const char*)&zir->string_bytes[name_off]; uint32_t name_str = ipGetOrPutString(ip, name, (uint32_t)strlen(name)); uint32_t nav = ctTypeExprDeclLookup(ip, zcu, zir, name_str); if (nav == IP_INDEX_NONE) UNIMPLEMENTED("funcCommon: typeof declaration not found"); uint32_t operand_ty = IP_INDEX_NONE; if (zir->inst_tags[oi] == ZIR_INST_DECL_VAL) { navAnalyzeVal(zcu, nav, &operand_ty); } else { navDeclRefAddressResolved(zcu, nav, &operand_ty); } if (!ipIndexIsType(ip, operand_ty)) UNIMPLEMENTED("funcCommon: typeof operand type unresolved"); return operand_ty; } // Sema.zirTypeofBuiltin: any other operand body (a runtime // arithmetic, an `.*` of a runtime pointer, an extern call) is // analyzed in the runtime child block, with this frame's bound // values and runtime parameters as its inputs. uint32_t* values = semaInstMapAlloc(zir); uint32_t ty = semaTypeofRuntime( zcu, zir, pi, values, zcu->ct.param_tys, IP_INDEX_NONE); free(values); return ty; } if (zir->inst_tags[pi] == ZIR_INST_FIELD_PTR || zir->inst_tags[pi] == ZIR_INST_FIELD_PTR_LOAD) { // A namespace-member type expression (`a.b.T`). return ctResolveNamespaceTypeRef(ip, zcu, zir, ref); } if (zir->inst_tags[pi] == ZIR_INST_IMPORT) { // `@import("file.zig")` in type position (e.g. // `*const @import("Air.zig")` inside a function-pointer parameter): // resolve to the imported file's populated root struct type. Same // import helper as nav/ctEvalBody import handling, but this path is a // single type ref rather than a value-body instruction stream. uint32_t imp_file = UINT32_MAX; for (uint32_t fi = 0; fi < zcu->num_files; fi++) { if (zcu->files[fi].zir == zir) { imp_file = fi; break; } } if (imp_file == UINT32_MAX) UNIMPLEMENTED("funcCommon: import in an unregistered file"); return semaImportInstRootType(zcu, imp_file, pi, NULL); } if (zir->inst_tags[pi] == ZIR_INST_DECL_VAL) { // A named type decl referenced as a parameter/return type, e.g. // `fn g(e: E) ...` where `E` is a top-level `enum { ... }`, or // `fn mk(...) P` where `P` is a `const P = struct {...}`. Mirrors // Sema.zirDeclVal -> analyzeNavVal for a type value: resolve the decl // to its type (cached on the Nav) and form the `*const type` nav // pointer as a side effect (analyzeNavRefInner), exactly as the value // decl_val handler does for a callee function. uint32_t name_off = zir->inst_datas[pi].str_tok.start; const char* name = (const char*)&zir->string_bytes[name_off]; // Resolve against the namespace of the file owning this ZIR (a // parameter/return/const type naming a decl in a sibling file resolves // in that file's namespace, not the root file's), preceded by the // override namespace chain when set (container-scoped field types). uint32_t name_str = ipGetOrPutString(ip, name, (uint32_t)strlen(name)); uint32_t nav = ctTypeExprDeclLookup(ip, zcu, zir, name_str); if (nav == IP_INDEX_NONE) { fprintf(stderr, "funcCommon: param type, decl_val '%s' not found\n", name); UNIMPLEMENTED( "decl_val: ctResolveParamTypeRef: decl_val decl not found"); } return navAnalyzeVal(zcu, nav, NULL); } if (zir->inst_tags[pi] == ZIR_INST_OPTIONAL_TYPE) { // `?T`: resolve the child type, then intern the optional type // (Sema.zirOptionalType -> pt.optionalType). uint32_t child_ref = zir->inst_datas[pi].un_node.operand; uint32_t child = ctResolveParamTypeRef(ip, zcu, zir, child_ref); return ipGetOptionalType(ip, child); } if (zir->inst_tags[pi] == ZIR_INST_ERROR_SET_DECL) { // A bare named error set `error{...}` used directly as a type. return semaResolveErrorSetDeclInst(ip, zir, pi); } if (zir->inst_tags[pi] == ZIR_INST_MERGE_ERROR_SETS) { // `A || B` as a type expression (including the lhs of `(A || B)!T`): // resolve both operands as error set types, then validate both // operands' shapes and force-resolve any inferred operand // (Sema.ensureFuncIesResolved, Sema.zig, via // semaResolveMergeErrorSetOperands, which checks shape before the // anyerror short-circuit -- Sema.zig) with its anyerror // short-circuit, then merge/dedup/sort names exactly like // Sema.errorSetMerge. ZirInstBin bin; zirExtraData(zir, zir->inst_datas[pi].pl_node.payload_index, &bin); uint32_t lhs = ctResolveParamTypeRef(ip, zcu, zir, bin.lhs); uint32_t rhs = ctResolveParamTypeRef(ip, zcu, zir, bin.rhs); if (semaResolveMergeErrorSetOperands(ip, &lhs, &rhs, "funcCommon: param type, merge_error_sets non-error-set " "operand")) return ZIR_REF_ANYERROR_TYPE; return semaMergeResolvedErrorSetTypes(ip, lhs, rhs, "funcCommon: param type, merge_error_sets non-error-set operand"); } if (zir->inst_tags[pi] == ZIR_INST_ERROR_UNION_TYPE) { // `E!T`: resolve error set (lhs) and payload (rhs), then // pt.errorUnionType. The `anyerror` error set → type_anyerror_union; a // finite set (declared, named, or merged) → type_error_union. // ZIR pl_node -> Bin { lhs = error set, rhs = payload }. ZirInstBin eu; zirExtraData(zir, zir->inst_datas[pi].pl_node.payload_index, &eu); uint32_t errset_ref = eu.lhs; uint32_t payload_ref = eu.rhs; if (errset_ref == ZIR_REF_ANYERROR_TYPE) { uint32_t payload = ctResolveParamTypeRef(ip, zcu, zir, payload_ref); return ipGetAnyerrorUnionType(ip, payload); } if (errset_ref < ZIR_REF_START_INDEX) UNIMPLEMENTED("funcCommon: error union non-set-decl lhs"); uint32_t error_set; ZirInstTag es_tag = zir->inst_tags[errset_ref - ZIR_REF_START_INDEX]; if (es_tag == ZIR_INST_ERROR_SET_DECL) { error_set = semaResolveErrorSetDeclInst( ip, zir, errset_ref - ZIR_REF_START_INDEX); } else if (es_tag == ZIR_INST_MERGE_ERROR_SETS) { error_set = ctResolveParamTypeRef(ip, zcu, zir, errset_ref); } else if (es_tag == ZIR_INST_DECL_VAL || es_tag == ZIR_INST_FIELD_PTR || es_tag == ZIR_INST_FIELD_PTR_LOAD) { // `Error![]u8` where `Error` names an error-set const: // resolve the decl to its interned type_error_set (the same // namespace-chain lookup the DECL_VAL type arm uses). The lhs may // also be a namespace-member chain (`Allocator.Error!void` in // array_list's `append` — a FIELD_PTR(_LOAD) whose object is the // `Allocator` decl), which ctResolveParamTypeRef routes through // ctResolveNamespaceTypeRef. A non-error-set result aborts loudly. error_set = ctResolveParamTypeRef(ip, zcu, zir, errset_ref); if (error_set != ZIR_REF_ANYERROR_TYPE && (error_set >= ip->items_len || ip->item_tags[error_set] != IP_TAG_TYPE_ERROR_SET)) UNIMPLEMENTED("funcCommon: error union lhs not an error set"); } else { UNIMPLEMENTED("funcCommon: error union non-set-decl lhs"); } // Payload resolved after the error set (matches upstream IP order: // error_set_type then error_union_type, payload already interned). uint32_t payload = ctResolveParamTypeRef(ip, zcu, zir, payload_ref); if (error_set == ZIR_REF_ANYERROR_TYPE) return ipGetAnyerrorUnionType(ip, payload); return ipGetErrorUnionType(ip, error_set, payload); } if (zir->inst_tags[pi] == ZIR_INST_INT_TYPE) { uint16_t bits = zir->inst_datas[pi].int_type.bit_count; bool is_signed = (zir->inst_datas[pi].int_type.signedness == SIGNEDNESS_SIGNED); return ipGetIntType(ip, is_signed, bits); } if (zir->inst_tags[pi] == ZIR_INST_ARRAY_TYPE || zir->inst_tags[pi] == ZIR_INST_ARRAY_TYPE_SENTINEL) { // `[N]T` / `[N:s]T`: resolveInt(len, .usize), resolve the element // type, then (for the sentinel form) coerce the standalone comptime // sentinel to that element type before interning the array. bool has_sentinel = zir->inst_tags[pi] == ZIR_INST_ARRAY_TYPE_SENTINEL; uint32_t len_ref, elem_ref, sentinel_ref; ctArrayTypeRefs(zir, pi, &len_ref, &elem_ref, &sentinel_ref); uint64_t n = ctResolveTypeLenRef(ip, zcu, zir, len_ref, "array_type"); uint32_t elem_ty = ctResolveParamTypeRef(ip, zcu, zir, elem_ref); // The element type's own ZIR instruction runs before `array_type` // does, so anything it interns (`[29]*u8`'s `*u8`) lands ahead of the // `.usize` coerce `resolveInt` performs inside zirArrayType // (src/Sema.zig). This frame has no inst map and must reproduce // that body order itself; the coerce still precedes the sentinel // coercion below, which upstream also runs last. ipGetIntUsize(ip, (uint32_t)n); // resolveInt -> usize coerce validateArrayElemType(ip, elem_ty); uint32_t sentinel = has_sentinel ? ctResolveSentinelRef(ip, zcu, zir, sentinel_ref, elem_ty, NULL) : IP_INDEX_NONE; return ipGetArrayType(ip, (uint32_t)n, elem_ty, sentinel); } if (zir->inst_tags[pi] == ZIR_INST_VECTOR_TYPE) { // `@Vector(len, T)` in a declared-type position -- a function // parameter (`fn f(v: @Vector(4, u8))`) or a return type // (`fn f() @Vector(4, u8)`), both of which reach this resolver from a // single ref (zcu_per_thread.c's navFuncDecl param/ret walks). // Sema.zirVectorType (src/Sema.zig) is array_type's sibling down // to the ZIR payload: pl_node -> Bin { lhs = len, rhs = elem type }. // It differs only in // `.u32` for `.usize` as resolveInt's dest and in having no sentinel, // so the length walk and the type tail are both the shared ones -- // including the order: the element is resolved first because its own // ZIR instruction precedes `vector_type` in the body, and only then // does zptVectorType intern the `.u32` coerce; with the coerce above // the element walk, `@Vector(4, *u8)` and `*const [29]*u16` both give // an InternPool divergence (air-compare-zig1). ZirInstBin vec; zirExtraData(zir, zir->inst_datas[pi].pl_node.payload_index, &vec); uint64_t vn = ctResolveTypeLenRef(ip, zcu, zir, vec.lhs, "vector_type"); uint32_t velem = ctResolveParamTypeRef(ip, zcu, zir, vec.rhs); return zptVectorType(ip, vn, velem); } if (zir->inst_tags[pi] == ZIR_INST_PTR_TYPE) { // ZIR ptr_type: { payload_index, flags, size }. // Extra[payload_index + 0] = elem_type_ref. // flags bit 1 = is_mutable → IP is_const = !is_mutable. // IP flags: bits[0..1]=size, bits[2..7]=align.none(0x3F), // bit[8]=is_const, bits[11..15]=addrspace.generic(0), // bits[16..31]=vector_index.none(0xFFFF). CtPtrTypeRefs ptr = ctPtrTypeRefs(zir, pi); uint8_t zir_flags = zir->inst_datas[pi].ptr_type.flags; uint8_t zir_size = zir->inst_datas[pi].ptr_type.size; if (zir_flags & ZIR_PTR_TYPE_FLAGS_HAS_ADDRSPACE) UNIMPLEMENTED( "ptr_type: ctResolveParamTypeRef: ptr_type with addrspace"); if (zir_flags & ZIR_PTR_TYPE_FLAGS_HAS_BIT_RANGE) UNIMPLEMENTED( "ptr_type: ctResolveParamTypeRef: ptr_type with bit_range"); // size: One=0, Many=1, Slice=2, C=3. The existing pointer builder // below faithfully packs C's size bits; C needs no slice wrapper. uint32_t elem_ty = ctResolveParamTypeRef(ip, zcu, zir, ptr.elem_type); // Sentinel-terminated pointer/slice (`[:0]const u8`, // `[:sentinel]T`): the sentinel is the first trailing ref after // {elem_type, src_node}. Coerce the comptime expression to the // resolved element type, matching Sema.zirPtrType; preinterned refs // remain the fast path. uint32_t sentinel_val = IP_INDEX_NONE; if (zir_flags & ZIR_PTR_TYPE_FLAGS_HAS_SENTINEL) { sentinel_val = ctResolveSentinelRef( ip, zcu, zir, ptr.sentinel, elem_ty, NULL); } uint32_t ip_flags = semaPtrTypeFlags(ip, zir_flags, zir_size, elem_ty); if (zir_flags & ZIR_PTR_TYPE_FLAGS_HAS_ALIGN) { // ZIR PtrType extra: [elem_type, src_node, sentinel?, align]. The // align expr is a comptime power-of-two byte count; pack its // log2 into IP flags bits[2..7] (Alignment; .none == 0x3F is the // default in the 0xFFFF00FC base). `align(1)` lowers to the // preinterned `@one` ref, so no separate IP entry is created. uint32_t align_ref = ptr.align; uint64_t align_bytes; if (align_ref == ZIR_REF_ONE) { align_bytes = 1; } else if (align_ref >= ZIR_REF_START_INDEX && zir->inst_tags[align_ref - ZIR_REF_START_INDEX] == ZIR_INST_INT) { align_bytes = zir->inst_datas[align_ref - ZIR_REF_START_INDEX].int_val; } else if (align_ref >= ZIR_REF_START_INDEX && zir->inst_tags[align_ref - ZIR_REF_START_INDEX] == ZIR_INST_ALIGN_OF) { // `align(@alignOf(T))`, a folded align clause. The ZIR align // ref names an `align_of` instruction (un_node) preceding the // ptr_type in the same type body. Mirrors Sema.zirAlignOf // (src/Sema.zig): resolve the operand type, resolve // its layout, and produce the ABI alignment as a // `comptime_int` (pt.intValue(.comptime_int, abiAlignment)); // zirPtrType's align arm then coerces that value to u29 and // validates power-of-two below, shared with the literal // path so the packed attrs word is identical. uint32_t ao_i = align_ref - ZIR_REF_START_INDEX; uint32_t ao_ty = ctResolveParamTypeRef( ip, zcu, zir, zir->inst_datas[ao_i].un_node.operand); uint64_t ao_sz, ao_al, ao_bits; if (!ztLayoutInfo(zcu, ip, ao_ty, &ao_sz, &ao_al, &ao_bits)) UNIMPLEMENTED("ptr_type: ctResolveParamTypeRef: ptr_type " "@alignOf unsupported operand layout"); if (ao_al == 0 || ao_al > 0xFFFFFFFFu) UNIMPLEMENTED("ptr_type: ctResolveParamTypeRef: ptr_type " "@alignOf result out of range"); ipGetIntComptimeIntU32(ip, (uint32_t)ao_al); align_bytes = ao_al; } else if (align_ref >= ZIR_REF_START_INDEX && (zir->inst_tags[align_ref - ZIR_REF_START_INDEX] == ZIR_INST_FIELD_PTR_LOAD || zir->inst_tags[align_ref - ZIR_REF_START_INDEX] == ZIR_INST_FIELD_PTR)) { // `align(a.b)`, a namespace-member comptime integer. Sema // zirPtrType resolves the field value, coerces it to u29, // then validates it as an Alignment. The existing namespace // resolver walks the same decl/member chain and returns the // member Nav value; the shared validation/packing below // performs the align coercion side effect. uint32_t av = ctResolveNamespaceTypeRef(ip, zcu, zir, align_ref); int64_t iv; bool ict; uint32_t ibits; if (!ctReadInt(ip, av, &iv, &ict, &ibits) || iv < 0 || iv > 0x1FFFFFFF) UNIMPLEMENTED("ptr_type: ctResolveParamTypeRef: ptr_type " "namespace align not a u29 integer"); align_bytes = (uint64_t)iv; } else { UNIMPLEMENTED("ptr_type: ctResolveParamTypeRef: ptr_type " "align non-literal"); } if (align_bytes == 0) UNIMPLEMENTED("ptr_type: ctResolveParamTypeRef: ptr_type " "align not power of two"); // Resolving `align(N)` runs the align body: `@as(u29, N)` interns // an int_small(u29, N) IP entry (the byte count, not log2) before // the pointer type. u29 is IP index 6 (see ipInitStatic order). ipGetIntSmall(ip, 6u, (uint32_t)align_bytes); uint32_t align_log2 = ipAlignmentFromByteUnits(align_bytes, "funcCommon: param type, ptr_type align not power of two"); ip_flags = IP_PTR_FLAGS_WITH_ALIGN(ip_flags, align_log2); } return semaPtrTypeIntern( ip, elem_ty, ip_flags, zir_size, sentinel_val, 0); } if (zir->inst_tags[pi] == ZIR_INST_BLOCK_INLINE) { // A type expression evaluated inline. AstGen wraps a fn-proto type // (`*const fn (A, B) R` pointee) in a block_inline whose body is // [param..., func (body-less), break_inline]. Decode the body and // intern its function type. Block payload = { body_len }, body starts // at bp+1. Only the fn-proto-type shape is modeled; any other // block_inline type body aborts loudly inside // ctResolveFuncTypeFromBody. ZirInstBlock blk; uint32_t body = zirExtraData( zir, zir->inst_datas[pi].pl_node.payload_index, &blk); return ctResolveFuncTypeFromBody( ip, zcu, zir, zirBodySlice(zir, body), blk.body_len, NULL, NULL); } if (zir->inst_tags[pi] == ZIR_INST_EXTENDED && zir->inst_datas[pi].extended.opcode == (uint16_t)ZIR_EXT_CLOSURE_GET) { // A param/return type that is a captured value (a container // capture). Read the current container's // resolved capture (keyed by Zcu.ct.type_resolve_ns = the fn's // namespace). uint32_t idx = zir->inst_datas[pi].extended.small; uint32_t cv = ctGetCapture(zcu, zcu->ct.type_resolve_ns, idx); if (cv == IP_INDEX_NONE) UNIMPLEMENTED("closure_get: ctResolveParamTypeRef: closure_get " "unresolved capture"); return cv; } if (zir->inst_tags[pi] == ZIR_INST_EXTENDED && zir->inst_datas[pi].extended.opcode == (uint16_t)ZIR_EXT_THIS) { // `@This()` inside a parameter/return type — e.g. `self: *@This()` // (the bare `self: @This()` case is handled directly in // navFuncDecl; the pointer-wrapped form reaches here via the // ptr_type element). return ctThisType(ip, zcu); } if (zir->inst_tags[pi] == ZIR_INST_EXTENDED && zir->inst_datas[pi].extended.opcode == (uint16_t)ZIR_EXT_STRUCT_DECL) { // An inline anonymous `struct { ... }` type expression used as a // field/param/return type -- e.g. the CType union's payload fields // `pointer: struct { @"const": bool, elem_ty: *const CType, ... }`. // Sema.zirStructDecl resolves its captures (`Sema.getCaptures`) // against the current scope -- a field-type struct inside a // function-local container captures the decls it names -- through // the one zirStructDecl sequence (captures, dedup, intern), exactly // as the union_decl arm below. return semaDeclaredNamespaceType(zcu, zir, pi, NULL, NULL, NULL, zcu->ct.type_resolve_ns, type_name_ctx, false); } if (zir->inst_tags[pi] == ZIR_INST_EXTENDED && zir->inst_datas[pi].extended.opcode == (uint16_t)ZIR_EXT_ENUM_DECL) { // An inline anonymous `enum { ... }` type expression used as a // field/return/param type — e.g. CType.kind()'s `enum { specifier, // postfix_op, pointer }` return type (auto shape) or Io/Threaded.zig // Thread.Status's `cancelation: enum(u3) { none = 0b000, ... }` field // type (explicit tag type + field values). Mirror the ctEvalBody // ENUM_DECL handler: intern the enum type via semaBodyEnumType (no // captures / decls; aborts loudly on any unmodeled shape), scoping // computed tag-type/value bodies at Zcu.ct.type_resolve_ns (the // current type-resolution scope, like the struct_decl arm above). return semaBodyEnumType(zcu, zir, pi, zcu->ct.type_resolve_ns, NULL, NULL, NULL, type_name_ctx, NULL); } if (zir->inst_tags[pi] == ZIR_INST_EXTENDED && zir->inst_datas[pi].extended.opcode == (uint16_t)ZIR_EXT_UNION_DECL) { // An inline anonymous union type expression used as a // field/param/return type, such as Compilation's plain // `status: union(enum) { ... }` field; semaBodyUnionType keeps // unsupported variants behind its existing loud guards. Mirror // declared union type resolution: decode, intern, and defer // non-primitive field types against the current type-resolution // namespace. Its captures (`Sema.getCaptures`) resolve against that // same scope, through the one zirUnionDecl sequence (captures, dedup, // intern). return semaDeclaredNamespaceType(zcu, zir, pi, NULL, NULL, NULL, zcu->ct.type_resolve_ns, type_name_ctx, true); } if (zir->inst_tags[pi] == ZIR_INST_EXTENDED && zir->inst_datas[pi].extended.opcode == (uint16_t)ZIR_EXT_TUPLE_DECL) { // An inline tuple `struct { T, U }` type expression used as a // fn-proto return type (a fn-pointer proto reaches here via the // block_inline arm -> ctResolveFuncTypeFromBody's ret arm). Upstream // the ret body is evaluated comptime and Sema.zirTupleDecl resolves // each field type ref via sema.resolveType; here each type ref is // either a preinterned ref (`usize`) or a preceding instruction of the // same fn-proto type body (the `?net.Socket.SendError` optional_type // chain), which the single-ref resolver handles recursively. The // shared zirTupleDecl core (also ctEvalBody's TUPLE_DECL arm) // validates + interns via ipGetTupleType, so the inline tuple dedups // against an identical standalone tuple declaration. return semaTupleDecl(zcu, zir, pi, NULL, NULL); } UNIMPLEMENTED("funcCommon: unsupported param type instruction"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } uint32_t ctResolveParamTypeRef( InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t ref) { return ctResolveTypeRef(ip, zcu, zir, ref, TYPE_NAME_CTX_NONE); } uint32_t ctResolveFieldTypeRefInNs(InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t ref, uint32_t ns, TypeNameCtx type_name_ctx) { uint32_t saved = ctSwapNs(&zcu->ct.type_resolve_ns, ns); uint32_t ty = ctResolveTypeRef(ip, zcu, zir, ref, type_name_ctx); zcu->ct.type_resolve_ns = saved; return ty; } // True when a type ref resolves to a pointer with a comptime-computed // alignment (not a literal `align(1)`), possibly under error-union or optional // wrappers. The inline-call return-type and signature param-type resolvers // then fall back to generic_poison. bool ctRetTypeHasComputedAlign(const Zir* zir, uint32_t ref) { for (uint32_t guard = 0; guard < 8; guard++) { if (ref < ZIR_REF_START_INDEX) return false; uint32_t pi = ref - ZIR_REF_START_INDEX; ZirInstTag tag = zir->inst_tags[pi]; if (tag == ZIR_INST_PTR_TYPE) { uint32_t align_ref = ctPtrTypeRefs(zir, pi).align; if (align_ref == ZIR_REF_NONE) return false; if (align_ref == ZIR_REF_ONE) return false; if (align_ref >= ZIR_REF_START_INDEX && zir->inst_tags[align_ref - ZIR_REF_START_INDEX] == ZIR_INST_INT) return false; return true; } if (tag == ZIR_INST_ERROR_UNION_TYPE) { // Bin { lhs=error set, rhs=payload }: follow the payload. ZirInstBin eu; zirExtraData(zir, zir->inst_datas[pi].pl_node.payload_index, &eu); ref = eu.rhs; continue; } if (tag == ZIR_INST_OPTIONAL_TYPE) { ref = zir->inst_datas[pi].un_node.operand; continue; } return false; } return false; } // Resolve a function return-type body: if ctTypeBodyNeedsComptimeFold says the // body cannot be resolved from its break operand alone — a pointer whose align // operand the single-ref resolver cannot read // (`[]align(sizes.big_align.toByteUnits()) u8` in // MultiArrayList.allocatedBytes), a generic type call, a comptime-selected // switch type, and the rest of that predicate's shape set — fold the whole // body through the comptime evaluator scoped to the current resolve namespace // (Zcu.ct.type_resolve_ns - set by the caller to the fn's analysis namespace); // otherwise the byte-identical single-ref resolver. // Exported so zptAnalyzeFuncBody (which re-resolves the return type at // body-analysis time) shares navFuncDecl's exact logic. uint32_t ctResolveRetTypeBody(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t break_ref, TypeNameCtx type_name_ctx) { InternPool* ip = &zcu->intern_pool; if (ctTypeBodyNeedsComptimeFold(zir, break_ref)) return ctFoldComptimeTypeBody( zcu, zir, body, body_len, zcu->ct.type_resolve_ns, type_name_ctx); return ctResolveParamTypeRef(ip, zcu, zir, break_ref); } // ── Evaluator state, value tags and lookups ───────────────────────────────── // ctEvalBody's decl_val/decl_ref name lookup: the scope-chain override first, // then the ZIR file's root namespace. static uint32_t ctDeclLookup(InternPool* ip, Zcu* zcu, const Zir* zir, uint32_t lookup_ns, uint32_t name_str) { for (uint32_t ns = lookup_ns; ns != IP_NAMESPACE_NONE; ns = ipGetNamespace(ip, ns)->parent) { uint32_t nav = ipNamespaceLookup(ip, ns, name_str); if (nav != IP_INDEX_NONE) return nav; } uint32_t root_ns = zcuNamespaceOfZir(zcu, zir); if (root_ns == IP_NAMESPACE_NONE) return IP_INDEX_NONE; return ipNamespaceLookup(ip, root_ns, name_str); } // (part of) Sema.zig:zirTypeofBuiltin // Sema.zirTypeofBuiltin / zirTypeofPeer: the child block has // `".comptime_reason = null, .is_typeof = true"`, so its body is analyzed at // runtime even in a comptime scope -- by the runtime frame // (semaTypeofRuntime), with names resolved in this frame's scope. static uint32_t ctTypeofRuntime(const SemaCtx* sema, uint32_t inst) { Zcu* zcu = sema->zcu; uint32_t saved_ns = ctSwapNs(&zcu->ct.type_resolve_ns, sema->ns); uint32_t ty = semaTypeofRuntime(zcu, sema->zir, inst, sema->inst_values, zcu->ct.param_tys, sema->fn_ret_ty); zcu->ct.type_resolve_ns = saved_ns; return ty; } static bool ctParamInstConcreteType(Zcu* zcu, const Zir* zir, uint32_t pinst, uint32_t* values, uint32_t ns, TypeNameCtx type_name_ctx, uint32_t* out_ty); // The ZIR operands of an instruction ctAnalyzeInst runs: its refs into // `refs`, their count returned; UINT32_MAX for one it does not run. static uint32_t ctAnalyzedInstOperands( const Zir* zir, uint32_t inst, uint32_t refs[4]) { const uint32_t pi = zir->inst_datas[inst].pl_node.payload_index; switch (zir->inst_tags[inst]) { case ZIR_INST_EXTENDED: { uint32_t n; switch (zir->inst_datas[inst].extended.opcode) { case ZIR_EXT_THIS: case ZIR_EXT_CLOSURE_GET: case ZIR_EXT_DISABLE_INSTRUMENTATION: case ZIR_EXT_IN_COMPTIME: case ZIR_EXT_RET_ADDR: case ZIR_EXT_STD_LANG_VALUE: case ZIR_EXT_REIFY_SPIRV_TYPE: return 0; case ZIR_EXT_INT_FROM_ERROR: case ZIR_EXT_PTR_CAST_NO_DEST: case ZIR_EXT_SET_FLOAT_MODE: case ZIR_EXT_ERROR_FROM_INT: case ZIR_EXT_ROUND_OP_TY: case ZIR_EXT_FLOAT_OP_RESULT_TY: case ZIR_EXT_REIFY_POINTER_SENTINEL_TY: case ZIR_EXT_REIFY_TUPLE: case ZIR_EXT_REIFY_SLICE_ARG_TY: n = 1; break; case ZIR_EXT_ADD_WITH_OVERFLOW: case ZIR_EXT_SUB_WITH_OVERFLOW: case ZIR_EXT_MUL_WITH_OVERFLOW: case ZIR_EXT_SHL_WITH_OVERFLOW: case ZIR_EXT_PTR_CAST_FULL: case ZIR_EXT_ERROR_CAST: case ZIR_EXT_ROUND_OP: case ZIR_EXT_REIFY_ENUM_VALUE_SLICE_TY: n = 2; break; case ZIR_EXT_FIELD_PARENT_PTR: n = 3; break; case ZIR_EXT_REIFY_POINTER: case ZIR_EXT_REIFY_FN: case ZIR_EXT_SELECT: n = 4; break; case ZIR_EXT_ALLOC: { // AllocExtended: [src_node, type?, align?]. uint16_t small = zir->inst_datas[inst].extended.small; n = ((small & ZIR_ALLOC_SMALL_HAS_TYPE) != 0 ? 1u : 0u) + ((small & ZIR_ALLOC_SMALL_HAS_ALIGN) != 0 ? 1u : 0u); break; } default: return UINT32_MAX; } // UnNode, BinNode and the like: [node, operand...]. memcpy(refs, &zir->extra[zir->inst_datas[inst].extended.operand + 1], n * sizeof(uint32_t)); return n; } case ZIR_INST_DBG_STMT: case ZIR_INST_RET_TYPE: case ZIR_INST_DBG_VAR_VAL: case ZIR_INST_DBG_VAR_PTR: case ZIR_INST_VALIDATE_CONST: case ZIR_INST_SAVE_ERR_RET_INDEX: case ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL: case ZIR_INST_RESTORE_ERR_RET_INDEX_FN_ENTRY: case ZIR_INST_VALIDATE_STRUCT_INIT_TY: case ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY: case ZIR_INST_VALIDATE_ARRAY_INIT_TY: case ZIR_INST_VALIDATE_ARRAY_INIT_RESULT_TY: case ZIR_INST_VALIDATE_REF_TY: case ZIR_INST_DECL_VAL: case ZIR_INST_DECL_REF: case ZIR_INST_ALLOC_INFERRED: case ZIR_INST_ALLOC_INFERRED_MUT: case ZIR_INST_ALLOC_INFERRED_COMPTIME: case ZIR_INST_ALLOC_INFERRED_COMPTIME_MUT: case ZIR_INST_INT: case ZIR_INST_INT_BIG: case ZIR_INST_FLOAT: case ZIR_INST_FLOAT128: case ZIR_INST_STR: case ZIR_INST_ENUM_LITERAL: case ZIR_INST_ERROR_VALUE: case ZIR_INST_INT_TYPE: case ZIR_INST_ERROR_SET_DECL: case ZIR_INST_IMPORT: return 0; case ZIR_INST_ALLOC_COMPTIME_MUT: case ZIR_INST_SET_EVAL_BRANCH_QUOTA: case ZIR_INST_SET_RUNTIME_SAFETY: case ZIR_INST_ENSURE_RESULT_USED: case ZIR_INST_ENSURE_RESULT_NON_ERROR: case ZIR_INST_TYPE_INFO: case ZIR_INST_COMPILE_ERROR: case ZIR_INST_IS_NON_NULL: case ZIR_INST_OPTIONAL_PAYLOAD_SAFE: case ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE: case ZIR_INST_TYPEOF: case ZIR_INST_OPT_EU_BASE_PTR_INIT: case ZIR_INST_IS_NON_ERR: case ZIR_INST_ERR_UNION_CODE: case ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE: case ZIR_INST_IS_NON_ERR_PTR: case ZIR_INST_ERR_UNION_CODE_PTR: case ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE_PTR: case ZIR_INST_REF_DEREF: case ZIR_INST_OPTIONAL_TYPE: case ZIR_INST_STRUCT_INIT_EMPTY: case ZIR_INST_STRUCT_INIT_EMPTY_RESULT: case ZIR_INST_STRUCT_INIT_EMPTY_REF_RESULT: case ZIR_INST_SLICE_SENTINEL_TY: case ZIR_INST_RESOLVE_INFERRED_ALLOC: case ZIR_INST_INDEXABLE_PTR_ELEM_TYPE: case ZIR_INST_SPLAT_OP_RESULT_TY: case ZIR_INST_FROM_BACKING_INT_ARG_TY: case ZIR_INST_ELEM_TYPE: case ZIR_INST_TYPEOF_LOG2_INT_TYPE: case ZIR_INST_BACKING_INT: case ZIR_INST_INT_FROM_PTR: case ZIR_INST_SIZE_OF: case ZIR_INST_ALIGN_OF: case ZIR_INST_BIT_SIZE_OF: case ZIR_INST_INT_FROM_BOOL: case ZIR_INST_TAG_NAME: case ZIR_INST_ERROR_NAME: case ZIR_INST_TYPE_NAME: case ZIR_INST_BOOL_NOT: case ZIR_INST_BIT_NOT: case ZIR_INST_NEGATE: case ZIR_INST_NEGATE_WRAP: case ZIR_INST_ABS: case ZIR_INST_CLZ: case ZIR_INST_CTZ: case ZIR_INST_POP_COUNT: case ZIR_INST_BYTE_SWAP: case ZIR_INST_BIT_REVERSE: case ZIR_INST_SQRT: case ZIR_INST_SIN: case ZIR_INST_COS: case ZIR_INST_TAN: case ZIR_INST_EXP: case ZIR_INST_EXP2: case ZIR_INST_LOG: case ZIR_INST_LOG2: case ZIR_INST_LOG10: case ZIR_INST_FLOOR: case ZIR_INST_CEIL: case ZIR_INST_TRUNC: case ZIR_INST_ROUND: case ZIR_INST_INT_FROM_ENUM: case ZIR_INST_EMBED_FILE: case ZIR_INST_LOAD: case ZIR_INST_DEREF: refs[0] = zir->inst_datas[inst].un_node.operand; return 1; case ZIR_INST_ARRAY_TYPE: case ZIR_INST_VECTOR_TYPE: case ZIR_INST_REIFY_INT: case ZIR_INST_AS_SHIFT_OPERAND: case ZIR_INST_HAS_DECL: case ZIR_INST_HAS_FIELD: case ZIR_INST_FIELD_TYPE_REF: case ZIR_INST_XOR: case ZIR_INST_BIT_AND: case ZIR_INST_BIT_OR: case ZIR_INST_ADD: case ZIR_INST_ADD_UNSAFE: case ZIR_INST_SUB: case ZIR_INST_MUL: case ZIR_INST_ADDWRAP: case ZIR_INST_SUBWRAP: case ZIR_INST_MULWRAP: case ZIR_INST_ADD_SAT: case ZIR_INST_SUB_SAT: case ZIR_INST_MUL_SAT: case ZIR_INST_DIV_EXACT: case ZIR_INST_DIV: case ZIR_INST_DIV_TRUNC: case ZIR_INST_DIV_FLOOR: case ZIR_INST_DIV_CEIL: case ZIR_INST_MOD: case ZIR_INST_REM: case ZIR_INST_MOD_REM: case ZIR_INST_SHL: case ZIR_INST_SHL_EXACT: case ZIR_INST_SHL_SAT: case ZIR_INST_SHR: case ZIR_INST_SHR_EXACT: case ZIR_INST_FROM_BACKING_INT: case ZIR_INST_REDUCE: case ZIR_INST_PTR_FROM_INT: case ZIR_INST_BITCAST: case ZIR_INST_MIN: case ZIR_INST_MAX: case ZIR_INST_INT_CAST: case ZIR_INST_TRUNCATE: case ZIR_INST_FLOAT_CAST: case ZIR_INST_INT_FROM_FLOAT: case ZIR_INST_FLOAT_FROM_INT: case ZIR_INST_ENUM_FROM_INT: case ZIR_INST_SPLAT: case ZIR_INST_CMP_EQ: case ZIR_INST_CMP_NEQ: case ZIR_INST_CMP_LT: case ZIR_INST_CMP_LTE: case ZIR_INST_CMP_GT: case ZIR_INST_CMP_GTE: case ZIR_INST_MERGE_ERROR_SETS: case ZIR_INST_STORE_NODE: case ZIR_INST_EXPORT: case ZIR_INST_MEMSET: case ZIR_INST_MEMCPY: case ZIR_INST_MEMMOVE: case ZIR_INST_COERCE_PTR_ELEM_TY: case ZIR_INST_ERROR_UNION_TYPE: case ZIR_INST_PTR_CAST: case ZIR_INST_STORE_TO_INFERRED_PTR: refs[0] = zir->extra[pi]; refs[1] = zir->extra[pi + 1]; return 2; case ZIR_INST_VALIDATE_ARRAY_INIT_REF_TY: case ZIR_INST_STRUCT_INIT_FIELD_TYPE: case ZIR_INST_ARRAY_INIT_ELEM_PTR: refs[0] = zir->extra[pi]; return 1; case ZIR_INST_UNION_INIT: case ZIR_INST_ARRAY_TYPE_SENTINEL: case ZIR_INST_MUL_ADD: case ZIR_INST_ATOMIC_LOAD: case ZIR_INST_ATOMIC_STORE: refs[0] = zir->extra[pi]; refs[1] = zir->extra[pi + 1]; refs[2] = zir->extra[pi + 2]; return 3; case ZIR_INST_SHUFFLE: case ZIR_INST_ATOMIC_RMW: memcpy(refs, &zir->extra[pi], 4 * sizeof(uint32_t)); return 4; default: return UINT32_MAX; } } // An instruction with any number of operands: `n` refs, `stride` words apart. static bool ctAnalyzedInstOperandList(const Zir* zir, uint32_t inst, const uint32_t** refs, uint32_t* n, uint32_t* stride) { const uint32_t pi = zir->inst_datas[inst].pl_node.payload_index; switch (zir->inst_tags[inst]) { case ZIR_INST_STRUCT_INIT_REF: // StructInit: [abs_node, src_line, src_column, fields_len], then // (field_type, init) pairs. *n = zir->extra[pi + 3]; *stride = 2; *refs = &zir->extra[pi + 5]; return true; default: return false; } } // Sema.analyzeBodyInner on a comptime block, for one instruction of this // frame given its operand refs: the handler sees the frame's inst map. Only an // instruction that makes an `inferred_alloc_comptime` adds AIR, kept with the // frame as upstream keeps it in the Sema; any other AIR is a runtime value. static bool ctAnalyzeInst(SemaCtx* sema, uint32_t inst) { uint32_t refs_small[4]; const uint32_t* refs = refs_small; uint32_t stride = 1; const Zir* zir = sema->zir; uint32_t n = ctAnalyzedInstOperands(zir, inst, refs_small); if (n == UINT32_MAX && !ctAnalyzedInstOperandList(zir, inst, &refs, &n, &stride)) return false; const InternPool* ip = sema->ip; uint32_t* values = sema->inst_values; uint32_t* types = sema->inst_types; for (uint32_t i = 0; i < n * stride; i += stride) { if (refs[i] < ZIR_REF_START_INDEX) continue; uint32_t ri = refs[i] - ZIR_REF_START_INDEX; if (semaRefIsInterned(values[ri])) types[ri] = ipComptimeValueType(ip, values[ri]); else if (values[ri] == INST_VALUE_UNSET) UNIMPLEMENTED("analyzeBodyInner: unset operand"); } values[inst] = INST_VALUE_UNSET; types[inst] = IP_INDEX_NONE; uint32_t air_len = sema->insts->len; const uint32_t body[1] = { inst }; U32List block = { 0 }; uint32_t saved_ns = ctSwapNs(&sema->zcu->ct.type_resolve_ns, sema->ns); analyzeBodyInner(sema, &block, body, 1); sema->zcu->ct.type_resolve_ns = saved_ns; free(block.items); uint32_t v = values[inst]; bool placeholder = v != INST_VALUE_UNSET && !semaRefIsInterned(v) && v - AIR_REF_INST_BASE == air_len && sema->insts->tags[air_len] == AIR_INST_INFERRED_ALLOC_COMPTIME; // A statement leaves `.none`; only the placeholder is an AIR ref. if (sema->insts->len != air_len + (placeholder ? 1u : 0u) || (v != INST_VALUE_UNSET && !semaRefIsInterned(v) && !placeholder)) UNIMPLEMENTED("analyzeBodyInner: handler result not comptime-known"); return true; } // A comptime evaluation's Sema and Block over the inst map `values`: its AIR // (the inferred_alloc_comptime placeholders) lives as long as the evaluation. static void ctFrameInit(SemaDetachedFrame* f, Zcu* zcu, const Zir* zir, uint32_t* values, uint32_t fn_ret_ty, CtBlockScope scope) { semaDetachedFrameInit(f, zcu, zir, values, semaInstMapAlloc(zir), fn_ret_ty, scope.type_name_ctx); f->sema.ns = scope.ns; f->sema.type_name_func = scope.type_name_func; f->sema.owner_nav = scope.owner_nav; f->ictx.ct_scope = true; f->ictx.ct_lexical = scope.lexical; } static void ctFrameDeinit(SemaDetachedFrame* f) { free(f->sema.inst_types); semaDetachedFrameDeinit(f); } static bool ctIsCellPointerClassValue(const InternPool* ip, uint32_t v); // An interned comptime pointer outside the cell model: its slice is the // shared analyzeSlice port (semaAnalyzeSliceComptime). static bool ctSliceOperandIsInternedPtr(const InternPool* ip, uint32_t v) { return v < ip->items_len && !ctIsCellPointerClassValue(ip, v) && ipItemTagIs(ip, ipTypeOf(ip, v), IP_TAG_TYPE_POINTER); } // ctEvalBody's slice_start / slice_end / slice_sentinel / slice_length on // such an operand (`end` / `sentinel` ZIR_REF_NONE for `.none`). static uint32_t ctAnalyzeSliceRefs(Zcu* zcu, uint32_t lhs, uint32_t start_ref, uint32_t end_ref, uint32_t sent_ref, bool by_length, const uint32_t* values) { return semaAnalyzeSliceComptime(zcu, lhs, semaResolveRef(start_ref, values), end_ref == ZIR_REF_NONE ? IP_INDEX_NONE : semaResolveRef(end_ref, values), sent_ref == ZIR_REF_NONE ? IP_INDEX_NONE : semaResolveRef(sent_ref, values), by_length); } // True when `v` is a real interned `enum_literal` value (`.name`), the // representation both evaluators use — upstream's one // `Key.enum_literal: NullTerminatedString`. bool ctIsEnumLiteral(const InternPool* ip, uint32_t v) { return v < ip->items_len && ip->item_tags[v] == IP_TAG_ENUM_LITERAL; } // The literal's field-name string index. Callers must have established the // shape with `ctIsEnumLiteral` first (upstream reads the same one word: // `".enum_literal => .{ .enum_literal = @enumFromInt(data) }"`). static uint32_t ctEnumLiteralName(const InternPool* ip, uint32_t v) { if (!ctIsEnumLiteral(ip, v)) UNIMPLEMENTED("enum_literal: not an enum_literal value"); return ip->item_datas[v]; } uint32_t ctValidatedTypeResult( const InternPool* ip, uint32_t rv, const char* ctx) { if (!ipIndexIsType(ip, rv)) UNIMPLEMENTED(ctx); return rv; } static bool ctTargetSupportsFunctionAlignment(const Zcu* zcu) { if (zcu == NULL || zcu->comp == NULL || zcu->comp->config.target_cpu_arch == NULL) return false; const char* arch = zcu->comp->config.target_cpu_arch; return strcmp(arch, "nvptx") != 0 && strcmp(arch, "nvptx64") != 0 && strcmp(arch, "spirv32") != 0 && strcmp(arch, "spirv64") != 0 && strcmp(arch, "wasm32") != 0 && strcmp(arch, "wasm64") != 0; } SemaNavModifierStatus semaNavValueModifierStatus( Zcu* zcu, uint32_t nav_idx, bool has_explicit_alignment) { if (zcu == NULL) return SEMA_NAV_MODIFIERS_MALFORMED; const InternPool* ip = &zcu->intern_pool; if (nav_idx >= ip->navs_len) return SEMA_NAV_MODIFIERS_MALFORMED; const IpNav* nav = ipGetNav(ip, nav_idx); if (!nav->has_resolved || !nav->has_value_resolved || !nav->modifiers_resolved || nav->resolved_type >= ip->items_len || nav->resolved_value >= ip->items_len) return SEMA_NAV_MODIFIERS_MALFORMED; if (!has_explicit_alignment) return SEMA_NAV_MODIFIERS_OK; uint32_t owner = ipFuncValueOwnerNav(ip, nav->resolved_value); if (owner != IP_INDEX_NONE) { if (owner >= ip->navs_len) return SEMA_NAV_MODIFIERS_MALFORMED; if (owner != nav_idx) return SEMA_NAV_MODIFIERS_ALIGN_FUNCTION_ALIAS; // Alignment is a function-declaration property when this Nav owns the // function value. Some targets do not support function alignment // greater than 1; "`align(1)` is a no-op, so it is allowed even on // those targets" (log2 alignment 0). if (nav->resolved_alignment != 0 && !ctTargetSupportsFunctionAlignment(zcu)) return SEMA_NAV_MODIFIERS_ALIGN_FUNCTION_TARGET_UNSUPPORTED; return SEMA_NAV_MODIFIERS_OK; } ZtComptimeOnlyStatus comptime_status = ztComptimeOnlyStatus(zcu, nav->resolved_type); if (comptime_status == ZT_COMPTIME_ONLY_UNKNOWN) return SEMA_NAV_MODIFIERS_MALFORMED; // Pinned analyzeNavVal rejects storage modifiers whenever // nav_ty.comptimeOnly is true. This includes partial aggregates even // though their runtime fields still receive ordinary unmodified storage. if (comptime_status == ZT_COMPTIME_ONLY_YES) return SEMA_NAV_MODIFIERS_ALIGN_COMPTIME_ONLY; return SEMA_NAV_MODIFIERS_OK; } // --- Comptime call evaluator (Sema.analyzeInlineCallInner / analyzeFnBody) // --- // // Genuine comptime execution of a function body over a ZIR instruction body, // far enough to run `debug.assert(ok: bool)`. Mirrors Sema.analyzeBodyInner's // comptime path: values flow as InternPool indices (static refs are their own // value), control flow (`block`/`condbr`/`break`/`ret`) is followed by // resolving comptime-known conditions and taking only the live branch. Aborts // (UNIMPLEMENTED) on any unhandled instruction rather than guessing. // Resolve a ZIR ref to its comptime IP value. Refs below ZIR_REF_START_INDEX // are static InternPool indices (their own value); higher refs name an // instruction whose evaluated value lives in `values` (indexed by inst index). // (part of) Value.zig:toBigInt // `Value.toBigInt(...).toInt(u64)`: the whole value, or nothing. static bool ctReadNonNegativeInt64( const InternPool* ip, uint32_t idx, uint64_t* out) { if (idx >= ip->items_len) return false; uint64_t space[2]; BigIntConst v; return valToBigInt(ip, idx, space, &v) && v.positive && bigIntToU64(v, out); } // ── Type conformance and @typeInfo values (zirTypeInfo) ───────────────────── bool ctValueConformsToType(const InternPool* ip, uint32_t val, uint32_t ty) { if (ty == IP_INDEX_NONE || ty == IP_INDEX_GENERIC_POISON_TYPE) return true; // An `enum_literal` item is answered by the general tail below, as // `ipTypeOf` maps IP_TAG_ENUM_LITERAL to ZIR_REF_ENUM_LITERAL_TYPE. if (val >= ip->items_len || ty >= ip->items_len) return false; if (ztZigTypeTag(ip, ty) == ZIG_TYPE_TAG_OPTIONAL) { if (ip->item_tags[val] == IP_TAG_OPT_NULL) return ip->item_datas[val] == ty; if (ip->item_tags[val] != IP_TAG_OPT_PAYLOAD) return false; uint32_t d = ip->item_datas[val]; return ip->extra[d] == ty && ctValueConformsToType(ip, ip->extra[d + 1], ip->item_datas[ty]); } if (ztZigTypeTag(ip, ty) == ZIG_TYPE_TAG_ERROR_UNION) { uint32_t err_set = ipErrorUnionErrorSet(ip, ty); if (ip->item_tags[val] == IP_TAG_ERROR_UNION_ERROR) { uint32_t d = ip->item_datas[val]; return ip->extra[d] == ty && ipErrorSetHasName( ip, err_set, ip->extra[d + 1], IP_IES_NAME_RESOLVED); } if (ip->item_tags[val] != IP_TAG_ERROR_UNION_PAYLOAD) return false; uint32_t d = ip->item_datas[val]; return ip->extra[d] == ty && ctValueConformsToType( ip, ip->extra[d + 1], ipErrorUnionPayload(ip, ty)); } return ipTypeOf(ip, val) == ty; } // Decode the { child, flags } of a pointer/slice type: a slice's pointer // attributes live on its inner many-item pointer (ipGetSliceType stores that // pointer's Index directly in item.data). Tag.TypePointer extra layout: // { child, sentinel, flags } (see ipGetPtrTypeSentinel). void ctPtrTypeParts(const InternPool* ip, uint32_t ty, uint32_t* out_child, uint32_t* out_flags) { uint32_t pty = ty; if (ztIsSlice(ip, pty)) pty = ip->item_datas[pty]; // the inner `[*]T` many-pointer IpPtrInfo info; if (ipPtrInfo(ip, pty, &info) != IP_PTRI_OK) UNIMPLEMENTED("zirTypeInfo: not a pointer/slice type"); *out_child = info.child; *out_flags = info.flags; } // Materialize the real `std.builtin.Type.Pointer.Attributes` struct value for // a pointer/slice type (the `.attrs` field of a real `Type.Pointer` reflection // value; Sema.typeInfo's Pointer branch builds the same aggregate). Field // order per lib/std/lang.zig Attributes: @"const", @"volatile", @"allowzero", // @"addrspace" (?AddressSpace = .generic), @"align" (?usize = null). zig1 // never interns pointer types with an explicit non-generic addrspace (that // shape aborts at type creation), so this always wraps `.generic`. // `@"align"` mirrors Sema.typeInfo's Pointer branch (src/Sema.zig): // `info.flags.alignment.toByteUnits() orelse null` — the log2 alignment in // flags bits [2..7] (0x3F = .none) decodes to an optional usize byte count. uint32_t ctPtrAttrsValue(Zcu* zcu, uint32_t ptr_ty) { InternPool* ip = &zcu->intern_pool; static const char* const attrs_path[] = { "Type", "Pointer", "Attributes" }; uint32_t attrs_ty = getStdLangType(zcu, attrs_path, 3); if (!ipTagIsAnyStruct(ip->item_tags[attrs_ty]) || ipStructFieldsLen(ip, attrs_ty) != 5) UNIMPLEMENTED("zirTypeInfo: unexpected Attributes shape"); uint32_t child, flags; ctPtrTypeParts(ip, ptr_ty, &child, &flags); uint32_t elems[5]; elems[0] = (flags & IP_PTR_FLAGS_IS_CONST) ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; elems[1] = (flags & IP_PTR_FLAGS_IS_VOLATILE) ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; elems[2] = (flags & IP_PTR_FLAGS_IS_ALLOWZERO) ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; uint32_t addrspace_bits = (flags >> IP_PTR_FLAGS_ADDRSPACE_SHIFT) & IP_PTR_FLAGS_ADDRSPACE_MASK; if (addrspace_bits != IP_PTR_ADDRSPACE_GENERIC) UNIMPLEMENTED("zirTypeInfo: non-generic addrspace"); static const char* const addrspace_path[] = { "AddressSpace" }; uint32_t addrspace_ty = getStdLangType(zcu, addrspace_path, 1); uint32_t generic_str = ipGetOrPutString(ip, "generic", 7); uint32_t generic_tag = ipEnumTagFromFieldName(ip, addrspace_ty, generic_str); if (generic_tag == IP_INDEX_NONE) UNIMPLEMENTED("zirTypeInfo: AddressSpace.generic missing"); elems[3] = ipGetOptPayload(ip, ipStructFieldType(ip, attrs_ty, 3), generic_tag); uint32_t align_log2 = IP_PTR_FLAGS_ALIGN(flags); if (align_log2 == IP_PTR_ALIGN_NONE) { // Alignment.none elems[4] = ipGetOptNull(ip, ipStructFieldType(ip, attrs_ty, 4)); } else { if (align_log2 >= 32) UNIMPLEMENTED("zirTypeInfo: alignment out of range"); uint32_t align_val = ipGetIntUsize(ip, 1u << align_log2); elems[4] = ipGetOptPayload( ip, ipStructFieldType(ip, attrs_ty, 4), align_val); } return ipGetAggregate(ip, attrs_ty, elems, 5); } static uint32_t ctPtrSizeValue(Zcu* zcu, uint32_t ptr_ty) { InternPool* ip = &zcu->intern_pool; static const char* const size_path[] = { "Type", "Pointer", "Size" }; uint32_t size_ty = getStdLangType(zcu, size_path, 3); const char* sname; if (ztIsSlice(ip, ptr_ty)) { sname = "slice"; } else { uint32_t child, flags; ctPtrTypeParts(ip, ptr_ty, &child, &flags); switch (IP_PTR_FLAGS_SIZE(flags)) { case IP_PTR_SIZE_ONE: sname = "one"; break; case IP_PTR_SIZE_MANY: sname = "many"; break; case IP_PTR_SIZE_C: sname = "c"; break; default: UNIMPLEMENTED("zirTypeInfo: invalid non-slice pointer size"); } } uint32_t s_str = ipGetOrPutString(ip, sname, (uint32_t)strlen(sname)); uint32_t s_tag = ipEnumTagFromFieldName(ip, size_ty, s_str); if (s_tag == IP_INDEX_NONE) UNIMPLEMENTED("zirTypeInfo: Size enum field missing"); return s_tag; } static uint32_t ctSentinelAnyopaquePtrValue(Zcu* zcu, uint32_t sent) { InternPool* ip = &zcu->intern_pool; uint32_t any_ptr = ipGetPtrType(ip, ZIR_REF_ANYOPAQUE_TYPE, IP_PTR_FLAGS_CONST_ONE); uint32_t opt_any_ptr = ipGetOptionalType(ip, any_ptr); if (sent == IP_INDEX_NONE) return ipGetOptNull(ip, opt_any_ptr); uint32_t sent_ty = ipComptimeValueType(ip, sent); uint32_t sent_ptr_ty = ipGetPtrType(ip, sent_ty, IP_PTR_FLAGS_CONST_ONE); ipGetPtrUav(ip, sent_ptr_ty, sent); uint32_t any_uav = ipGetPtrUavAligned(ip, any_ptr, sent, sent_ptr_ty); return ipGetOptPayload(ip, opt_any_ptr, any_uav); } static uint32_t ctPtrSentinelPtrValue(Zcu* zcu, uint32_t ptr_ty) { InternPool* ip = &zcu->intern_pool; uint32_t sp_ty = ptr_ty; if (ztIsSlice(ip, sp_ty)) sp_ty = ip->item_datas[sp_ty]; if (ip->item_tags[sp_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("zirTypeInfo: non-pointer type"); uint32_t sent = ipPtrSentinel(ip, sp_ty); return ctSentinelAnyopaquePtrValue(zcu, sent); } // `pt.internUnion(.{ .tag = enumValueFieldIndex(...), .val = // aggregateValue(payload_ty, elems) })`, the tag interned first. static uint32_t ctWrapTypeInfoPayload(Zcu* zcu, const char* tag_name, uint32_t tag_name_len, uint32_t payload_ty, const uint32_t* elems, uint32_t elems_len, const char* no_tag_msg, const char* no_field_msg, const char* payload_mismatch_msg) { InternPool* ip = &zcu->intern_pool; static const char* const type_path[] = { "Type" }; uint32_t ti_ty = getStdLangType(zcu, type_path, 1); if (ip->item_tags[ti_ty] != IP_TAG_TYPE_UNION) UNIMPLEMENTED("zirTypeInfo: std.lang.Type not a union"); uint32_t tag_enum = ztUnionTagTypeHypothetical(zcu, ti_ty); uint32_t tag_str = ipGetOrPutString(ip, tag_name, tag_name_len); uint32_t tag_val = ipEnumTagFromFieldName(ip, tag_enum, tag_str); if (tag_val == IP_INDEX_NONE) UNIMPLEMENTED(no_tag_msg); uint32_t field_idx = ipUnionFieldIndex(ip, ti_ty, tag_str); if (field_idx == UINT32_MAX) UNIMPLEMENTED(no_field_msg); if (ipUnionFieldType(ip, ti_ty, field_idx) != payload_ty) UNIMPLEMENTED(payload_mismatch_msg); return ipGetUnionValue( ip, ti_ty, tag_val, ipGetAggregate(ip, payload_ty, elems, elems_len)); } static uint32_t ctPtrInfoValue(Zcu* zcu, uint32_t ptr_ty) { const InternPool* ip = &zcu->intern_pool; static const char* const ptr_path[] = { "Type", "Pointer" }; uint32_t ptr_info_ty = getStdLangType(zcu, ptr_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[ptr_info_ty]) || ipStructFieldsLen(ip, ptr_info_ty) != 4) UNIMPLEMENTED("zirTypeInfo: unexpected Type.Pointer shape"); uint32_t child, flags; ctPtrTypeParts(ip, ptr_ty, &child, &flags); uint32_t elems[4]; elems[0] = ctPtrSizeValue(zcu, ptr_ty); elems[1] = ctPtrAttrsValue(zcu, ptr_ty); elems[2] = child; elems[3] = ctPtrSentinelPtrValue(zcu, ptr_ty); return ctWrapTypeInfoPayload(zcu, "pointer", 7, ptr_info_ty, elems, 4, "zirTypeInfo: no `pointer` std.lang.Type tag", "zirTypeInfo: no `pointer` std.lang.Type field", "zirTypeInfo: `.pointer` payload type mismatch"); } // ── @typeInfo(struct/enum) reflection-slice builders ──────────────────────── // // Materialize comptime slice values exposed by std.builtin.Type reflection. // The struct/tuple builders mirror Sema.typeInfo's `.@"struct"` branch for the // comptime ctEvalBody path. Each slice interns as an IP value so a comptime / // inline `for` can walk and index it. // // ctBuildFieldNamesSlice interns upstream's direct shape: per name a // `[k:0]u8` bytes aggregate wrapped in a `[*:0]const u8` uav slice value, and // the outer slice as a direct many-pointer uav over the `[n][:0]const u8` // aggregate plus a usize len (as ctBuildDeclNamesSlice and // ctBuildEnumFieldNamesSlice do). The builders whose elements are not name // slices (ctBuildFieldTypesSlice, ctBuildFieldAttrsSlice, // ctBuildEnumFieldValuesSlice, ctBuildUnionFieldTypesSlice) still use an // intermediate array-pointer + coerceArrayPtrToSlice shape, each noted at its // definition. static uint32_t ctFieldCount(const InternPool* ip, uint32_t ti_src) { return (ztIsTuple(ip, ti_src)) ? ipTupleFieldCount(ip, ti_src) : ipStructFieldsLen(ip, ti_src); } static uint32_t ctBuildFieldNameSliceDirect(InternPool* ip, uint32_t name_str); static uint32_t ctBuildNameSlices( InternPool* ip, const uint32_t* elems, uint32_t n) { uint32_t names_arr_ty = ipGetArrayType( ip, n, ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE, IP_INDEX_NONE); uint32_t names_agg = ipGetAggregate(ip, names_arr_ty, elems, n); uint32_t many_names = ipGetPtrTypeSentinel(ip, ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE, IP_PTR_FLAGS_CONST_MANY, IP_INDEX_NONE); uint32_t names_slice_ty = ipGetSliceType(ip, many_names); uint32_t names_uav = ipGetPtrUav(ip, many_names, names_agg); uint32_t len_val = ipGetIntUsize(ip, n); return ipGetPtrSlice(ip, names_slice_ty, names_uav, len_val); } // (part of) Sema.zig:zirTypeInfo // `[]const [:0]const u8` of the field names (tuple names are the decimal // indices "0".."N-1", per upstream fieldName). Upstream's shape, verbatim // from Sema.zirTypeInfo's `.@"struct"` branch (both its `.tuple_type` and // `.struct_type` arms): each name is a `[k:0]u8` bytes aggregate wrapped by a // direct `[*:0]const u8` uav into a `[:0]const u8` slice value — the // `name_val` block anchored `"const name_val = v: {"` with // `".ty = .manyptr_const_u8_sentinel_0_type,"` and // `"orig_ty = .slice_const_u8_sentinel_0_type,"` (the uav's differing // orig_ty folds to the plain ptr_uav tag: `ptrsHaveSameAlignment` in // InternPool.zig normalizes orig_ty to the ptr type when alignment and // child match, anchored `"fn ptrsHaveSameAlignment"`, so the interned item // is the 4-word ptr_uav `ipGetPtrUav` mints) — and the outer slice is the // `field_names_val` block anchored `"const field_names_val = v: {"` with // `".child = .slice_const_u8_sentinel_0_type,"` and `"slicePtrFieldType"` // (a `[n][:0]const u8` aggregate under a direct many-pointer uav, usize // len). The direct per-name shape is `ctBuildFieldNameSliceDirect` below (the // one ctBuildDeclNamesSlice pins byte-for-byte against upstream // typeInfoNamespaceDecls' `name_val`). uint32_t ctBuildFieldNamesSlice(Zcu* zcu, uint32_t ti_src) { InternPool* ip = &zcu->intern_pool; bool is_tuple = ztIsTuple(ip, ti_src); uint32_t n = ctFieldCount(ip, ti_src); uint32_t* elems = zig1Calloc(n ? n : 1u, sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { uint32_t name_str; if (is_tuple) { char buf[16]; int w = snprintf(buf, sizeof buf, "%u", i); name_str = ipGetOrPutString(ip, buf, (uint32_t)w); } else { name_str = ipStructFieldName(ip, ti_src, i); } elems[i] = ctBuildFieldNameSliceDirect(ip, name_str); } uint32_t names = ctBuildNameSlices(ip, elems, n); free(elems); return names; } // `[]const type` of the field types (each element is the type-index value). // Known intern-shape divergence (comptime-only): this uses the intermediate // array-pointer uav + coerceArrayPtrToSlice outer shape, while upstream's // struct branch `field_types_val` is the same direct many-pointer-uav + // usize-len construction as `field_names_val`. uint32_t ctBuildFieldTypesSlice(Zcu* zcu, uint32_t ti_src) { InternPool* ip = &zcu->intern_pool; bool is_tuple = ztIsTuple(ip, ti_src); uint32_t n = ctFieldCount(ip, ti_src); uint32_t* elems = zig1Malloc((n ? n : 1u) * sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { elems[i] = is_tuple ? ipTupleFieldType(ip, ti_src, i) : ipStructFieldType(ip, ti_src, i); } uint32_t arr_ty = ipGetArrayType(ip, n, ZIR_REF_TYPE_TYPE, IP_INDEX_NONE); uint32_t agg = ipGetAggregate(ip, arr_ty, elems, n); free(elems); uint32_t ptr_ty = ipGetPtrType(ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t uav = ipGetPtrUav(ip, ptr_ty, agg); uint32_t many = ipGetPtrTypeSentinel( ip, ZIR_REF_TYPE_TYPE, IP_PTR_FLAGS_CONST_MANY, IP_INDEX_NONE); uint32_t slice_ty = ipGetSliceType(ip, many); return coerceArrayPtrToSlice(ip, uav, slice_ty); } // A `FieldAttributes.@"align"` value: `null` for `.none`, else the byte // count (`"if (a == .none) null else a.toByteUnits()"`). static uint32_t ctFieldAttrAlign( InternPool* ip, uint32_t align_opt_ty, uint32_t align_log2) { if (align_log2 == IP_PTR_ALIGN_NONE) return ipGetOptNull(ip, align_opt_ty); if (align_log2 >= 32) UNIMPLEMENTED("zirTypeInfo: FieldAttributes - huge field alignment"); return ipGetOptPayload( ip, align_opt_ty, ipGetIntUsize(ip, 1u << align_log2)); } // `[]const FieldAttributes` (lang.zig Type.Struct.FieldAttributes: { // @"comptime": bool = false, @"align": ?usize = null, default_value_ptr: // ?*const anyopaque = null }). The `.@"struct"` TypeInfo payload is a real // aggregate, so this builder cannot be a lazy, all-default shortcut: upstream // constructs the complete field_attrs slice // while building Type.Struct, before any downstream field decides whether it // will read it. Decode the metadata zig1 already stores/resolves: // ipStructFieldIsComptime, ipStructFieldAlignment, semaFieldDefaultValue, and // tuple value slots. // Same known intern-shape divergence as ctBuildFieldTypesSlice (upstream's // `field_attributes_val` is the direct construction). uint32_t ctBuildFieldAttrsSlice(Zcu* zcu, uint32_t ti_src) { InternPool* ip = &zcu->intern_pool; uint32_t n = ctFieldCount(ip, ti_src); static const char* const fa_path[] = { "Type", "Struct", "FieldAttributes" }; uint32_t fa_ty = getStdLangType(zcu, fa_path, 3); if (!ipTagIsAnyStruct(ip->item_tags[fa_ty]) || ipStructFieldsLen(ip, fa_ty) != 3) UNIMPLEMENTED("zirTypeInfo: unexpected FieldAttributes shape"); uint32_t* elems = zig1Malloc((n ? n : 1u) * sizeof(uint32_t)); bool is_tuple = ztIsTuple(ip, ti_src); uint32_t tuple_data = is_tuple ? ip->item_datas[ti_src] : 0; for (uint32_t i = 0; i < n; i++) { uint32_t default_val = IP_INDEX_NONE; bool is_comptime = false; uint32_t align_log2 = IP_PTR_ALIGN_NONE; // Alignment.none if (is_tuple) { default_val = ip->extra[tuple_data + 1 + n + i]; is_comptime = default_val != IP_INDEX_NONE; } else if (ipTagIsAnyStruct(ip->item_tags[ti_src])) { is_comptime = ipStructFieldIsComptime(ip, ti_src, i); default_val = semaFieldDefaultValue(zcu, ti_src, i); if (!ipStructIsPacked(ip, ti_src)) align_log2 = ipStructFieldAlignment(ip, ti_src, i); } else { UNIMPLEMENTED("zirTypeInfo: non-struct source type"); } uint32_t fe[3]; fe[0] = is_comptime ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; // @"comptime" fe[1] = ctFieldAttrAlign( ip, ipStructFieldType(ip, fa_ty, 1), align_log2); fe[2] = ctSentinelAnyopaquePtrValue(zcu, default_val); elems[i] = ipGetAggregate(ip, fa_ty, fe, 3); } uint32_t arr_ty = ipGetArrayType(ip, n, fa_ty, IP_INDEX_NONE); uint32_t agg = ipGetAggregate(ip, arr_ty, elems, n); free(elems); uint32_t ptr_ty = ipGetPtrType(ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t uav = ipGetPtrUav(ip, ptr_ty, agg); uint32_t many = ipGetPtrTypeSentinel( ip, fa_ty, IP_PTR_FLAGS_CONST_MANY, IP_INDEX_NONE); uint32_t slice_ty = ipGetSliceType(ip, many); return coerceArrayPtrToSlice(ip, uav, slice_ty); } // (part of) Sema.zig:typeInfoDecls, Type.zig:getNamespace // `[]const [:0]const u8` of a namespace's public declaration names, in // pub_navs order (Sema.typeInfoDecls / typeInfoNamespaceDecls, // src/Sema.zig: walks only `namespace.pub_decls.keys()` -- // private decls are excluded, matching zig1's own pub_navs/priv_navs // split populated by zptScanNamespace, zcu_per_thread.c). // `ns` may be IP_NAMESPACE_NONE (a tuple type has no namespace -- upstream // Type.getNamespace's `else => .none`, src/Type.zig), which yields // the empty slice, matching typeInfoDecls' `opt_namespace.unwrap() orelse // return` early-out (src/Sema.zig). Same direct per-name + outer shape // as ctBuildFieldNamesSlice (ctBuildFieldNamesSlice matches this one). // Dynamically allocated (calloc), not a fixed // stack cap -- a namespace's decl count is unbounded in general, so this // mirrors ctBuildEnumFieldNamesSlice's dynamic-array precedent rather than the // fixed-64 cap used by the sibling per-FIELD slice builders above (whose upper // bound is a source struct's field count, not a namespace's decl count). static uint32_t ctBuildDeclNamesSlice(Zcu* zcu, uint32_t ns) { InternPool* ip = &zcu->intern_pool; IpNamespace* nsp = ns == IP_NAMESPACE_NONE ? NULL : ipGetNamespace(ip, ns); uint32_t n = nsp ? nsp->pub_navs_len : 0; uint32_t* elems = zig1Calloc(n ? n : 1u, sizeof(uint32_t)); // Per-name and outer shapes both use the direct many-pointer uav form // (ctBuildFieldNameSliceDirect / the ctBuildEnumFieldNamesSlice outer // pattern), matching upstream typeInfoNamespaceDecls' name_val // construction (src/Sema.zig) byte-for-byte. The nav name is already an // interned string, so it feeds the direct builder without a re-intern. for (uint32_t i = 0; i < n; i++) { const IpNav* nav = ipGetNav(ip, nsp->pub_navs[i]); elems[i] = ctBuildFieldNameSliceDirect(ip, nav->name); } uint32_t names = ctBuildNameSlices(ip, elems, n); free(elems); return names; } static uint32_t ctArrayInfoValue(Zcu* zcu, uint32_t src_ty) { InternPool* ip = &zcu->intern_pool; static const char* const array_path[] = { "Type", "Array" }; uint32_t array_info_ty = getStdLangType(zcu, array_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[array_info_ty]) || ipStructFieldsLen(ip, array_info_ty) != 3) UNIMPLEMENTED("zirTypeInfo: unexpected Type.Array shape"); uint32_t len, child, sent; if (!ztArrayTypeInfo(ip, src_ty, &len, &child, &sent)) UNIMPLEMENTED("zirTypeInfo: source is not an array type"); uint32_t elems[3]; elems[0] = ipGetIntComptimeIntU32(ip, len); elems[1] = child; elems[2] = ctSentinelAnyopaquePtrValue(zcu, sent); return ctWrapTypeInfoPayload(zcu, "array", 5, array_info_ty, elems, 3, "zirTypeInfo: no `array` std.lang.Type tag", "zirTypeInfo: no `array` std.lang.Type field", "zirTypeInfo: `.array` payload type mismatch"); } // (part of) Sema.zig:zirTypeInfo, PerThread.zig:intValue // Sema.zirTypeInfo's `.vector` arm: `"const info = ty.arrayInfo(zcu); const // field_values = .{ // len: comptime_int // (try pt.intValue(.comptime_int, info.len)).toIntern(), // child: type // info.elem_type.toIntern(), };"` wrapped in the `.vector` tag of the // `std.lang.Type` union. `Type.Vector` has no sentinel field -- that is the // only thing separating it from the `.array` arm above. static uint32_t ctVectorInfoValue(Zcu* zcu, uint32_t src_ty) { InternPool* ip = &zcu->intern_pool; static const char* const vector_path[] = { "Type", "Vector" }; uint32_t vector_info_ty = getStdLangType(zcu, vector_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[vector_info_ty]) || ipStructFieldsLen(ip, vector_info_ty) != 2) UNIMPLEMENTED("zirTypeInfo: unexpected Type.Vector shape"); IpArrayInfo vi; if (ipArrayInfo(ip, src_ty, &vi) != IP_ARR_OK || !vi.is_vector) UNIMPLEMENTED("zirTypeInfo: source is not a vector type"); uint32_t elems[2]; elems[0] = ipGetIntComptimeIntU32(ip, vi.len); elems[1] = vi.child; return ctWrapTypeInfoPayload(zcu, "vector", 6, vector_info_ty, elems, 2, "zirTypeInfo: no `vector` std.lang.Type tag", "zirTypeInfo: no `vector` std.lang.Type field", "zirTypeInfo: `.vector` payload type mismatch"); } // zirTypeInfo's `.@"opaque"` arm: `Type.Opaque{ .decl_names = // sema.typeInfoDecls(ty.getNamespace(zcu)) }` under the `opaque` tag. static uint32_t ctOpaqueInfoValue(Zcu* zcu, uint32_t src_ty) { const InternPool* ip = &zcu->intern_pool; static const char* const opaque_path[] = { "Type", "Opaque" }; uint32_t opaque_info_ty = getStdLangType(zcu, opaque_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[opaque_info_ty]) || ipStructFieldsLen(ip, opaque_info_ty) != 1) UNIMPLEMENTED("zirTypeInfo: unexpected Type.Opaque shape"); uint32_t decl_names = ctBuildDeclNamesSlice(zcu, ipContainerNamespaceOrNone(ip, src_ty)); return ctWrapTypeInfoPayload(zcu, "opaque", 6, opaque_info_ty, &decl_names, 1, "zirTypeInfo: no `opaque` std.lang.Type tag", "zirTypeInfo: no `opaque` std.lang.Type field", "zirTypeInfo: `.opaque` payload type mismatch"); } uint32_t ctStructLayoutValue(Zcu* zcu, uint32_t ti_src) { InternPool* ip = &zcu->intern_pool; bool is_tuple = ztIsTuple(ip, ti_src); const char* lname = "auto"; if (!is_tuple && ipStructIsPacked(ip, ti_src)) lname = "packed"; else if (!is_tuple && ipStructIsExtern(ip, ti_src)) lname = "extern"; static const char* const lay_path[] = { "Type", "ContainerLayout" }; uint32_t lay_ty = getStdLangType(zcu, lay_path, 2); uint32_t lstr = ipGetOrPutString(ip, lname, (uint32_t)strlen(lname)); uint32_t ltag = ipEnumTagFromFieldName(ip, lay_ty, lstr); if (ltag == IP_INDEX_NONE) UNIMPLEMENTED("zirTypeInfo: ContainerLayout field missing"); return ltag; } // One field of `@typeInfo(S).@"struct"` (Sema.typeInfo's Struct branch), // built from the source type. Aborts loudly on unmodeled fields. static uint32_t ctStructInfoPayloadField( Zcu* zcu, uint32_t src_ty, const char* field) { InternPool* ip = &zcu->intern_pool; bool sti_tuple = ztIsTuple(ip, src_ty); bool sti_packed = !sti_tuple && ipStructIsPacked(ip, src_ty); if (strcmp(field, "struct") == 0) UNIMPLEMENTED("zirTypeInfo: payload has no struct field"); if (strcmp(field, "is_tuple") == 0) return sti_tuple ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; if (strcmp(field, "field_names") == 0) return ctBuildFieldNamesSlice(zcu, src_ty); if (strcmp(field, "field_types") == 0) return ctBuildFieldTypesSlice(zcu, src_ty); if (strcmp(field, "field_attrs") == 0) return ctBuildFieldAttrsSlice(zcu, src_ty); if (strcmp(field, "layout") == 0) { // Shared with the runtime Type.Struct reflection path so extern layout // cannot drift between the two @typeInfo consumers. return ctStructLayoutValue(zcu, src_ty); } if (strcmp(field, "backing_integer") == 0) { // `?type`: the backing int for a packed struct, else null. uint32_t bi_opt = ipGetOptionalType(ip, ZIR_REF_TYPE_TYPE); return sti_packed ? ipGetOptPayload(ip, bi_opt, ipStructBackingInt(ip, src_ty)) : ipGetOptNull(ip, bi_opt); } if (strcmp(field, "decl_names") == 0) { // A tuple type has no namespace (Type.getNamespace's `else => .none`, // src/Type.zig) -- mirrors upstream's // `ty.getNamespace(zcu)` feeding typeInfoDecls directly (struct arm, // src/Sema.zig). uint32_t ns = sti_tuple ? IP_NAMESPACE_NONE : ipStructNamespaceOrNone(ip, src_ty); return ctBuildDeclNamesSlice(zcu, ns); } UNIMPLEMENTED("zirTypeInfo: unsupported Type.Struct field"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } static uint32_t ctStructInfoValue(Zcu* zcu, uint32_t src_ty) { const InternPool* ip = &zcu->intern_pool; static const char* const struct_path[] = { "Type", "Struct" }; uint32_t struct_info_ty = getStdLangType(zcu, struct_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[struct_info_ty]) || ipStructFieldsLen(ip, struct_info_ty) != 7) UNIMPLEMENTED("zirTypeInfo: unexpected Type.Struct shape"); uint32_t elems[7]; elems[0] = ctStructInfoPayloadField(zcu, src_ty, "is_tuple"); elems[1] = ctStructInfoPayloadField(zcu, src_ty, "layout"); elems[2] = ctStructInfoPayloadField(zcu, src_ty, "backing_integer"); elems[3] = ctStructInfoPayloadField(zcu, src_ty, "field_names"); elems[4] = ctStructInfoPayloadField(zcu, src_ty, "field_types"); elems[5] = ctStructInfoPayloadField(zcu, src_ty, "field_attrs"); elems[6] = ctStructInfoPayloadField(zcu, src_ty, "decl_names"); return ctWrapTypeInfoPayload(zcu, "struct", 6, struct_info_ty, elems, 7, "zirTypeInfo: no `struct` std.lang.Type tag", "zirTypeInfo: no `struct` std.lang.Type field", "zirTypeInfo: `.struct` payload type mismatch"); } static uint32_t ctBuildFieldNameSliceDirect( InternPool* ip, uint32_t name_str) { return ipGetNullTerminatedStringSlice(ip, name_str); } // `[]const [:0]const u8` of an enum type's field names (Sema.typeInfo's // `.@"enum"` branch). The inner name slices use the direct many-pointer uav // shape described by upstream's `name_val` construction; the outer slice is // likewise a direct many-pointer over the `[N][:0]const u8` aggregate. uint32_t ctBuildEnumFieldNamesSlice(Zcu* zcu, uint32_t src_ty) { InternPool* ip = &zcu->intern_pool; uint32_t n = ipEnumFieldsLen(ip, src_ty); uint32_t* elems = zig1Calloc(n ? n : 1u, sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { uint32_t s = ipEnumFieldName(ip, src_ty, i); elems[i] = ctBuildFieldNameSliceDirect(ip, s); } uint32_t names_arr_ty = ipGetArrayType( ip, n, ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE, IP_INDEX_NONE); uint32_t names_agg = ipGetAggregate(ip, names_arr_ty, elems, n); free(elems); uint32_t many_names = ipGetPtrTypeSentinel(ip, ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE, IP_PTR_FLAGS_CONST_MANY, IP_INDEX_NONE); uint32_t names_slice_ty = ipGetSliceType(ip, many_names); uint32_t names_uav = ipGetPtrUav(ip, many_names, names_agg); uint32_t len_val = ipGetIntUsize(ip, n); return ipGetPtrSlice(ip, names_slice_ty, names_uav, len_val); } // (part of) Sema.zig:zirTypeInfo // `[]const comptime_int` of an enum type's field values (the underlying tag // integers rendered as comptime_int, mirroring Sema.zirTypeInfo's Enum branch // `field_values` with getCoercedInts to comptime_int). Reads each field's // typed backing int (int_small or int_large at the enum's int_tag_type) and // re-interns it as comptime_int. Signed tag types are accepted for // non-negative values; negative enum field values abort. // Same known intern-shape divergence as ctBuildFieldTypesSlice (upstream's // Enum branch `field_values_val`, Sema.zig ~16296-16322, is the direct // construction). static uint32_t ctBuildEnumFieldValuesSlice(Zcu* zcu, uint32_t src_ty) { InternPool* ip = &zcu->intern_pool; uint32_t n = ipEnumFieldsLen(ip, src_ty); uint32_t* elems = zig1Calloc(n ? n : 1u, sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { uint32_t nstr = ipEnumFieldName(ip, src_ty, i); uint32_t tag = ipEnumTagFromFieldName(ip, src_ty, nstr); if (tag == IP_INDEX_NONE) { free(elems); UNIMPLEMENTED("zirTypeInfo: field lookup failed"); } uint32_t iv = ipEnumTagValue(ip, tag); uint64_t v; if (!ctReadNonNegativeInt64(ip, iv, &v)) { free(elems); UNIMPLEMENTED("zirTypeInfo: unreadable value"); } elems[i] = ipGetIntComptimeIntU64(ip, v); } uint32_t arr_ty = ipGetArrayType(ip, n, ZIR_REF_COMPTIME_INT_TYPE, IP_INDEX_NONE); uint32_t agg = ipGetAggregate(ip, arr_ty, elems, n); free(elems); uint32_t ptr_ty = ipGetPtrType(ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t uav = ipGetPtrUav(ip, ptr_ty, agg); uint32_t many = ipGetPtrTypeSentinel( ip, ZIR_REF_COMPTIME_INT_TYPE, IP_PTR_FLAGS_CONST_MANY, IP_INDEX_NONE); uint32_t slice_ty = ipGetSliceType(ip, many); return coerceArrayPtrToSlice(ip, uav, slice_ty); } // (part of) Sema.zig:zirTypeInfo // One field of `@typeInfo(E).@"enum"` (Sema.zirTypeInfo's Enum branch), // built from the source type. Aborts loudly on unmodeled fields. static uint32_t ctEnumInfoPayloadField( Zcu* zcu, uint32_t src_ty, const char* field) { InternPool* ip = &zcu->intern_pool; if (strcmp(field, "enum") == 0) UNIMPLEMENTED("zirTypeInfo: payload has no enum field"); if (strcmp(field, "tag_type") == 0) return ipEnumTypeIntTagType(ip, src_ty); if (strcmp(field, "mode") == 0) { // Type.Enum.Mode enum tag: nonexhaustive vs exhaustive (the enum's IP // tag distinguishes them). bool nonexh = ztIsNonexhaustiveEnum(ip, src_ty); const char* mname = nonexh ? "nonexhaustive" : "exhaustive"; static const char* const mode_path[] = { "Type", "Enum", "Mode" }; uint32_t mode_ty = getStdLangType(zcu, mode_path, 3); uint32_t mstr = ipGetOrPutString(ip, mname, (uint32_t)strlen(mname)); uint32_t mtag = ipEnumTagFromFieldName(ip, mode_ty, mstr); if (mtag == IP_INDEX_NONE) UNIMPLEMENTED("zirTypeInfo: Enum.Mode field missing"); return mtag; } if (strcmp(field, "field_names") == 0) return ctBuildEnumFieldNamesSlice(zcu, src_ty); if (strcmp(field, "field_values") == 0) return ctBuildEnumFieldValuesSlice(zcu, src_ty); if (strcmp(field, "decl_names") == 0) return ctBuildDeclNamesSlice(zcu, ipEnumNamespaceOrNone(ip, src_ty)); UNIMPLEMENTED("zirTypeInfo: unsupported Type.Enum field"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } static uint32_t ctEnumInfoValue(Zcu* zcu, uint32_t src_ty) { const InternPool* ip = &zcu->intern_pool; static const char* const enum_path[] = { "Type", "Enum" }; uint32_t enum_info_ty = getStdLangType(zcu, enum_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[enum_info_ty]) || ipStructFieldsLen(ip, enum_info_ty) != 5) UNIMPLEMENTED("zirTypeInfo: unexpected Type.Enum shape"); uint32_t elems[5]; elems[0] = ctEnumInfoPayloadField(zcu, src_ty, "tag_type"); elems[1] = ctEnumInfoPayloadField(zcu, src_ty, "mode"); elems[2] = ctEnumInfoPayloadField(zcu, src_ty, "field_names"); elems[3] = ctEnumInfoPayloadField(zcu, src_ty, "field_values"); elems[4] = ctEnumInfoPayloadField(zcu, src_ty, "decl_names"); return ctWrapTypeInfoPayload(zcu, "enum", 4, enum_info_ty, elems, 5, "zirTypeInfo: no `enum` std.lang.Type tag", "zirTypeInfo: no `enum` std.lang.Type field", "zirTypeInfo: `.enum` payload type mismatch"); } // (part of) Sema.zig:zirTypeInfo // `[]const type` of a union type's field types (Sema.zirTypeInfo's Union // branch `field_types_val` over `union_obj.field_types`; mirrors // ctBuildFieldTypesSlice reading ipUnionFieldType). Same known intern-shape // divergence as ctBuildFieldTypesSlice (upstream's Union branch // `field_types_val` is the direct construction). static uint32_t ctBuildUnionFieldTypesSlice(Zcu* zcu, uint32_t src_ty) { InternPool* ip = &zcu->intern_pool; semaResolveAllUnionDeferred(zcu, src_ty); uint32_t n = ipUnionFieldsLen(ip, src_ty); uint32_t* elems = zig1Calloc(n ? n : 1u, sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { elems[i] = ipUnionFieldType(ip, src_ty, i); if (elems[i] == IP_INDEX_NONE) { free(elems); UNIMPLEMENTED("zirTypeInfo: unresolved field type"); } } uint32_t arr_ty = ipGetArrayType(ip, n, ZIR_REF_TYPE_TYPE, IP_INDEX_NONE); uint32_t agg = ipGetAggregate(ip, arr_ty, elems, n); free(elems); uint32_t ptr_ty = ipGetPtrType(ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t uav = ipGetPtrUav(ip, ptr_ty, agg); uint32_t many = ipGetPtrTypeSentinel( ip, ZIR_REF_TYPE_TYPE, IP_PTR_FLAGS_CONST_MANY, IP_INDEX_NONE); uint32_t slice_ty = ipGetSliceType(ip, many); return coerceArrayPtrToSlice(ip, uav, slice_ty); } static uint32_t ctUnionLayoutValue(Zcu* zcu, uint32_t src_ty) { InternPool* ip = &zcu->intern_pool; static const char* const lay_path[] = { "Type", "ContainerLayout" }; // `union_obj.layout`: a packed union has no TypeUnion flags word. const char* lname = ipUnionIsPacked(ip, src_ty) ? "packed" : ipUnionIsExtern(ip, src_ty) ? "extern" : "auto"; uint32_t lay_ty = getStdLangType(zcu, lay_path, 2); uint32_t lstr = ipGetOrPutString(ip, lname, (uint32_t)strlen(lname)); uint32_t ltag = ipEnumTagFromFieldName(ip, lay_ty, lstr); if (ltag == IP_INDEX_NONE) UNIMPLEMENTED("zirTypeInfo: ContainerLayout field missing"); return ltag; } static uint32_t ctBuildUnionFieldAttrsSlice(Zcu* zcu, uint32_t src_ty) { InternPool* ip = &zcu->intern_pool; semaResolveAllUnionDeferred(zcu, src_ty); uint32_t n = ipUnionFieldsLen(ip, src_ty); static const char* const fa_path[] = { "Type", "Union", "FieldAttributes" }; uint32_t fa_ty = getStdLangType(zcu, fa_path, 3); if (!ipTagIsAnyStruct(ip->item_tags[fa_ty]) || ipStructFieldsLen(ip, fa_ty) != 1) UNIMPLEMENTED("zirTypeInfo: unexpected FieldAttributes shape"); uint32_t align_opt_ty = ipStructFieldType(ip, fa_ty, 0); uint32_t* elems = zig1Calloc(n ? n : 1u, sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { // `".auto, .@\"extern\" => ty.explicitFieldAlignment(field_index, // zcu), .@\"packed\" => .none"`. uint32_t fe[1]; fe[0] = ctFieldAttrAlign( ip, align_opt_ty, ipUnionFieldAlignment(ip, src_ty, i)); elems[i] = ipGetAggregate(ip, fa_ty, fe, 1); } uint32_t arr_ty = ipGetArrayType(ip, n, fa_ty, IP_INDEX_NONE); uint32_t agg = ipGetAggregate(ip, arr_ty, elems, n); free(elems); uint32_t many = ipGetPtrTypeSentinel( ip, fa_ty, IP_PTR_FLAGS_CONST_MANY, IP_INDEX_NONE); uint32_t slice_ty = ipGetSliceType(ip, many); uint32_t uav = ipGetPtrUav(ip, many, agg); uint32_t len_val = ipGetIntUsize(ip, n); return ipGetPtrSlice(ip, slice_ty, uav, len_val); } // (part of) Sema.zig:zirTypeInfo // One field of `@typeInfo(U).@"union"` (Sema.zirTypeInfo's Union branch; // lang.zig Type.Union), built from the source type. Unmodeled fields abort // loudly. static uint32_t ctUnionInfoPayloadField( Zcu* zcu, uint32_t src_ty, const char* field) { InternPool* ip = &zcu->intern_pool; if (strcmp(field, "union") == 0) UNIMPLEMENTED("zirTypeInfo: payload has no union field"); if (strcmp(field, "layout") == 0) return ctUnionLayoutValue(zcu, src_ty); if (strcmp(field, "tag_type") == 0) { // `?type` (Type.unionTagType, src/Type.zig): the enum tag type // when tag_usage == tagged, else null (none/safety). uint32_t tt_opt = ipGetOptionalType(ip, ZIR_REF_TYPE_TYPE); if (ipUnionTagUsage(ip, src_ty) != IP_UNION_TAG_USAGE_TAGGED) return ipGetOptNull(ip, tt_opt); return ipGetOptPayload( ip, tt_opt, ztUnionTagTypeHypothetical(zcu, src_ty)); } if (strcmp(field, "backing_integer") == 0) { // `".val = if (layout == .@\"packed\") ... union_obj. // packed_backing_int_type else .none"`. uint32_t tt_opt = ipGetOptionalType(ip, ZIR_REF_TYPE_TYPE); if (!ipUnionIsPacked(ip, src_ty)) return ipGetOptNull(ip, tt_opt); if (!resolvePackedUnionLayout(zcu, src_ty)) UNIMPLEMENTED("zirTypeInfo: packed union layout"); return ipGetOptPayload(ip, tt_opt, ipUnionBackingInt(ip, src_ty)); } if (strcmp(field, "field_names") == 0) // Upstream sources the names from the tag enum's field_names // (`loadEnumType(union_obj.enum_tag_type)`); the generated/explicit // tag enum carries them in declaration order. return ctBuildEnumFieldNamesSlice( zcu, ztUnionTagTypeHypothetical(zcu, src_ty)); if (strcmp(field, "field_types") == 0) return ctBuildUnionFieldTypesSlice(zcu, src_ty); if (strcmp(field, "field_attrs") == 0) return ctBuildUnionFieldAttrsSlice(zcu, src_ty); if (strcmp(field, "decl_names") == 0) return ctBuildDeclNamesSlice(zcu, ipUnionNamespaceOrNone(ip, src_ty)); UNIMPLEMENTED("zirTypeInfo: unsupported Type.Union field"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } static uint32_t ctUnionInfoValue(Zcu* zcu, uint32_t src_ty) { const InternPool* ip = &zcu->intern_pool; static const char* const union_path[] = { "Type", "Union" }; uint32_t union_info_ty = getStdLangType(zcu, union_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[union_info_ty]) || ipStructFieldsLen(ip, union_info_ty) != 7) UNIMPLEMENTED("zirTypeInfo: unexpected Type.Union shape"); uint32_t elems[7]; elems[0] = ctUnionInfoPayloadField(zcu, src_ty, "layout"); elems[1] = ctUnionInfoPayloadField(zcu, src_ty, "tag_type"); elems[2] = ctUnionInfoPayloadField(zcu, src_ty, "backing_integer"); elems[3] = ctUnionInfoPayloadField(zcu, src_ty, "field_names"); elems[4] = ctUnionInfoPayloadField(zcu, src_ty, "field_types"); elems[5] = ctUnionInfoPayloadField(zcu, src_ty, "field_attrs"); elems[6] = ctUnionInfoPayloadField(zcu, src_ty, "decl_names"); return ctWrapTypeInfoPayload(zcu, "union", 5, union_info_ty, elems, 7, "zirTypeInfo: no `union` std.lang.Type tag", "zirTypeInfo: no `union` std.lang.Type field", "zirTypeInfo: `.union` payload type mismatch"); } // (part of) Value.zig:uninterpret, PerThread.zig:unionValue // Materialize the real `std.lang.CallingConvention` union value for a function // type's packed calling convention. Two upstream steps are fused here: // // 1. `PackedCallingConvention.unpack` (src/InternPool.zig) turns the // packed {tag, incoming_stack_alignment, extra} back into a // `std.lang.CallingConvention`. For a tag whose payload type is `void` it // is `@unionInit(CallingConvention, @tagName(tag), {})` — the packed // `incoming_stack_alignment` / `extra` words are ignored (they are the // "unused" placeholders `pack` wrote at), so nothing else // needs reading. // 2. `Value.uninterpret`'s `.@"union"` arm (src/Value.zig): // `pt.unionValue(ty, tag_val, uninterpret(payload, field_ty))`, where the // tag value comes from the `.@"enum"` arm's `.by_name` branch // (`enumValueFieldIndex` of the field whose name matches), // and a `void` payload uninterprets to `.void`. // // Only `.auto` (Tag ordinal 0) is modeled: it is the sole calling convention // the compiler reflects on, and it is one of the four void-payload tags, // so no payload struct has to be built. Every other tag aborts loud — the // payload-carrying ones (CommonOptions, X86RegparmOptions, the interrupt // option structs, the SpirV option structs) additionally need `unpack`'s // per-tag field reconstruction, which is not ported. static uint32_t ctUnionVoidValue(Zcu* zcu, uint32_t union_ty, const char* field_name, const char* not_union_msg, const char* no_field_msg, const char* nonvoid_msg) { InternPool* ip = &zcu->intern_pool; if (ip->item_tags[union_ty] != IP_TAG_TYPE_UNION) UNIMPLEMENTED(not_union_msg); uint32_t tag_enum = ztUnionTagTypeHypothetical(zcu, union_ty); uint32_t field_str = ipGetOrPutString(ip, field_name, (uint32_t)strlen(field_name)); uint32_t tag_val = ipEnumTagFromFieldName(ip, tag_enum, field_str); if (tag_val == IP_INDEX_NONE) UNIMPLEMENTED(no_field_msg); if (ipUnionFieldType( ip, union_ty, ipUnionFieldIndex(ip, union_ty, field_str)) != ZIR_REF_VOID_TYPE) UNIMPLEMENTED(nonvoid_msg); return ipGetUnionValue(ip, union_ty, tag_val, ZIR_REF_VOID_VALUE); } static uint32_t ctCallingConventionValue( Zcu* zcu, uint32_t cc_ty, uint32_t packed_cc) { InternPool* ip = &zcu->intern_pool; uint32_t cc_tag = packed_cc & IP_PACKED_CC_TAG_MASK; uint32_t field_name = ipUnionFieldName(ip, cc_ty, cc_tag); if (field_name == IP_INDEX_NONE) UNIMPLEMENTED("zirTypeInfo: tag out of range"); uint32_t payload_ty = ipUnionFieldType(ip, cc_ty, cc_tag); // `"void => {},"` if (payload_ty == ZIR_REF_VOID_TYPE) return ctUnionVoidValue(zcu, cc_ty, ipStringBytes(ip, field_name), "zirTypeInfo: not a union type", "zirTypeInfo: no such tag field", "zirTypeInfo: payload not void"); // `"std.lang.CallingConvention.CommonOptions => .{ // .incoming_stack_alignment = cc.incoming_stack_alignment.toByteUnits(), // },"`; the option structs with an `extra` field are not ported. static const char* const common_path[] = { "CallingConvention", "CommonOptions" }; if (payload_ty != getStdLangType(zcu, common_path, 2) || ipStructFieldsLen(ip, payload_ty) != 1) UNIMPLEMENTED("zirTypeInfo: unsupported calling convention payload"); uint32_t align_log2 = (packed_cc >> IP_PACKED_CC_ALIGN_SHIFT) & IP_PACKED_CC_ALIGN_MASK; uint32_t opt_ty = ipStructFieldType(ip, payload_ty, 0); uint32_t align_val = align_log2 == IP_PTR_ALIGN_NONE ? ipGetOptNull(ip, opt_ty) : ipGetOptPayload(ip, opt_ty, ipInternIntResult( ip, UINT64_C(1) << align_log2, ipOptionalChild(ip, opt_ty))); uint32_t payload = ipGetAggregate(ip, payload_ty, &align_val, 1); uint32_t tag_val = ipEnumTagFromFieldName( ip, ztUnionTagTypeHypothetical(zcu, cc_ty), field_name); return ipGetUnionValue(ip, cc_ty, tag_val, payload); } // (part of) Sema.zig:zirTypeInfo, PerThread.zig:unionValue, // Value.zig:uninterpret // Materialize the real `std.lang.Type` union value for // one of the nine payload-less reflection tags — Sema.zirTypeInfo's first // switch prong (src/Sema.zig): // // .type, .void, .bool, .noreturn, .comptime_float, .comptime_int, // .undefined, .null, .enum_literal => |type_info_tag| // return .fromValue(try pt.unionValue( // type_info_ty, // Value.uninterpret(type_info_tag, type_info_tag_ty, pt) ..., // .void, // )), // // with `type_info_ty = sema.getStdLangType(src, .Type)`. The tag // value is `Value.uninterpret` of the tag enum value — the `.@"enum"` arm's // by-name path (src/Value.zig): the `enum_tag` of the field whose // name matches, exactly what ipEnumTagFromFieldName mints for the // auto-numbered tag enum of the `union(enum)`. All nine tags carry a `void` // payload, so the payload word is the interned void value — the same shape // ctCallingConventionValue builds for `.auto`. static uint32_t ctTypeInfoSimpleValue(Zcu* zcu, const char* tag_name) { if (tag_name == NULL) UNIMPLEMENTED("zirTypeInfo: null tag name"); static const char* const type_path[] = { "Type" }; uint32_t ti_ty = getStdLangType(zcu, type_path, 1); return ctUnionVoidValue(zcu, ti_ty, tag_name, "zirTypeInfo: std.lang.Type not a union", "zirTypeInfo: no such std.lang.Type tag", "zirTypeInfo: payload type not void"); } // True for a real `std.lang.Type` union value minted by ctTypeInfoSimpleValue // an interned union_value whose union type is std.lang.Type. The typeInfo // consumers below key on this real value. // Only a resolved `.main` stage can have produced a std.lang.Type value, so // these two never force that stage's analysis for an unrelated value. bool ctIsTypeInfoValue(Zcu* zcu, uint32_t val) { const InternPool* ip = &zcu->intern_pool; static const char* const type_path[] = { "Type" }; if (zcu->std_lang_main_state != STD_LANG_STAGE_RESOLVED) return false; if (val >= ip->items_len) return false; if (ip->item_tags[val] != IP_TAG_UNION_VALUE) return false; return ipTypeOf(ip, val) == getStdLangType(zcu, type_path, 1); } // (part of) Sema.zig:zirTypeInfo, PerThread.zig:enumValueFieldIndex // Materialize the real `std.lang.Signedness` enum_tag value for an int // type's signedness — the `.signedness` field value of the `Type.Int` // reflection payload, Sema.zirTypeInfo's `.int` prong (src/Sema.zig: // 15972-15987): // // const signedness_ty = try sema.getStdLangType(src, .Signedness); // // signedness: Signedness, // (try pt.enumValueFieldIndex(signedness_ty, // @intFromEnum(info.signedness))).toIntern(), // // `enumValueFieldIndex` (src/Zcu/PerThread.zig) interns // `enum_tag { ty, int }` where the int is the field index for // auto-numbered enums — the exact item ipEnumTagFromFieldName mints once // the name resolves to that index (its auto-numbered arm uses the field // index as the tag int, the explicit arm the field's value — both agree // here). `std.lang.Signedness` is `enum(u1) { signed, unsigned }` // (lib/std/lang.zig): signed = field 0, unsigned = field 1. static uint32_t ctSignednessValue(Zcu* zcu, bool is_signed) { InternPool* ip = &zcu->intern_pool; static const char* const sgn_path[] = { "Signedness" }; uint32_t sgn_ty = getStdLangType(zcu, sgn_path, 1); if (!ipIsEnumType(ip, sgn_ty)) UNIMPLEMENTED("zirTypeInfo: std.lang.Signedness not enum"); const char* name = is_signed ? "signed" : "unsigned"; uint32_t name_str = ipGetOrPutString(ip, name, (uint32_t)strlen(name)); uint32_t tag_val = ipEnumTagFromFieldName(ip, sgn_ty, name_str); if (tag_val == IP_INDEX_NONE) UNIMPLEMENTED("zirTypeInfo: no such Signedness field"); return tag_val; } // True for a real `std.lang.Signedness` enum_tag value minted by // ctSignednessValue: an interned enum_tag whose enum type is // std.lang.Signedness. The signedness consumers below key on this real value. static bool ctIsSignednessValue(Zcu* zcu, uint32_t val) { const InternPool* ip = &zcu->intern_pool; static const char* const sgn_path[] = { "Signedness" }; if (val < ZIR_REF_START_INDEX || val >= ip->items_len) return false; if (ip->item_tags[val] != IP_TAG_ENUM_TAG) return false; return ipEnumTagType(ip, val) == getStdLangType(zcu, sgn_path, 1); } // The active field name of an interned enum_tag value, decoded off the // value's own tag item: ipEnumTagFieldIndex maps the tag value back to its // field index, ipEnumFieldName the index to its name string. Shared decode // for the signedness consumers (the cmp arm and the switch-scrutinee // arm read the `Signedness` value's field name off the real value). static const char* ctEnumTagName(InternPool* ip, uint32_t tag_val) { uint32_t tag_enum = ipEnumTagType(ip, tag_val); uint32_t field_idx = ipEnumTagFieldIndex(ip, tag_val); if (field_idx == UINT32_MAX || field_idx >= ipEnumFieldsLen(ip, tag_enum)) UNREACHABLE("ctEnumTagName: corrupt enum tag"); return ipStringBytes(ip, ipEnumFieldName(ip, tag_enum, field_idx)); } // Sema.zig:zirTypeInfo, PerThread.zig:enumValueFieldIndex, // PerThread.zig:intValue // Materialize the real `std.lang.Type` union value with // the `.int` payload for an integer type's `@typeInfo` — Sema.zirTypeInfo's // `.int` prong, word-for-word (src/Sema.zig): // // .int => { // const int_info_ty = try sema.getStdLangType(src, .@"Type.Int"); // const signedness_ty = try sema.getStdLangType(src, .Signedness); // const info = ty.intInfo(zcu); // const field_values = .{ // // signedness: Signedness, // (try pt.enumValueFieldIndex(signedness_ty, // @intFromEnum(info.signedness))).toIntern(), // // bits: u16, // (try pt.intValue(.u16, info.bits)).toIntern(), // }; // return Air.internedToRef((try pt.internUnion(.{ // .ty = type_info_ty.toIntern(), // .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, // @intFromEnum(std.lang.TypeId.int))).toIntern(), // .val = (try pt.aggregateValue(int_info_ty, // &field_values)).toIntern(), // }))); // }, // // The payload is the two-field `Type.Int` aggregate (`lib/std/lang.zig` // `struct { signedness: Signedness, bits: u16 }`, declaration order): field // 0 rides the `ctSignednessValue` route (upstream's // `enumValueFieldIndex`), field 1 is upstream's typed u16 value — // `pt.intValue(.u16, info.bits)` (src/Zcu/PerThread.zig) interns an // `.int{ .ty = u16 }` item, exactly ipGetIntU16's IP_TAG_INT_U16. // `aggregateValue` (PerThread.zig) is ipGetAggregate over the field-order // elems; `internUnion` the same ipGetUnionValue ctTypeInfoSimpleValue uses. // The {signedness, bits} pair is the caller's — the TYPE_INFO arm's three // producer routes (usize/isize, the C ABI types via ztCIntTypeInfoFE, sized // INT_SIGNED/INT_UNSIGNED) stand in for upstream's `ty.intInfo(zcu)`. static uint32_t ctIntInfoValue(Zcu* zcu, bool is_signed, uint32_t bits) { InternPool* ip = &zcu->intern_pool; static const char* const int_path[] = { "Type", "Int" }; if (bits > 0xFFFFu) UNIMPLEMENTED("zirTypeInfo: int bits exceed u16"); uint32_t int_info_ty = getStdLangType(zcu, int_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[int_info_ty]) || ipStructFieldsLen(ip, int_info_ty) != 2) UNIMPLEMENTED("zirTypeInfo: unexpected Type.Int shape"); uint32_t elems[2]; elems[0] = ctSignednessValue(zcu, is_signed); elems[1] = ipGetIntU16(ip, bits); return ctWrapTypeInfoPayload(zcu, "int", 3, int_info_ty, elems, 2, "zirTypeInfo: no `int` std.lang.Type tag", "zirTypeInfo: no `int` std.lang.Type field", "zirTypeInfo: `.int` payload type mismatch"); } static uint32_t ctFloatInfoValue(Zcu* zcu, uint32_t bits) { InternPool* ip = &zcu->intern_pool; static const char* const float_path[] = { "Type", "Float" }; if (bits > 0xFFFFu) UNIMPLEMENTED("zirTypeInfo: float bits exceed u16"); uint32_t float_info_ty = getStdLangType(zcu, float_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[float_info_ty]) || ipStructFieldsLen(ip, float_info_ty) != 1) UNIMPLEMENTED("zirTypeInfo: unexpected Type.Float shape"); uint32_t elems[1]; elems[0] = ipGetIntU16(ip, bits); return ctWrapTypeInfoPayload(zcu, "float", 5, float_info_ty, elems, 1, "zirTypeInfo: no `float` std.lang.Type tag", "zirTypeInfo: no `float` std.lang.Type field", "zirTypeInfo: `.float` payload type mismatch"); } // (part of) Sema.zig:zirTypeInfo, Value.zig:makeBool, // PerThread.zig:aggregateValue // Materialize the real // `std.lang.Type.Fn.Attributes` struct value for a function type (the `.attrs` // field of a `@typeInfo(F).@"fn"` reflection value). Mirrors // Sema.zirTypeInfo's `.@"fn"` branch (src/Sema.zig- 15955): // // const fn_attrs_values = .{ // // @"callconv": CallingConvention = .auto, // callconv_val.toIntern(), // // varargs: bool = false, // Value.makeBool(func_ty_info.is_var_args).toIntern(), // }; // (try pt.aggregateValue(fn_attr_ty, &fn_attrs_values)).toIntern() // // with `fn_attr_ty = getStdLangType(.@"Type.Fn.Attributes")` and // `callconv_val = Value.uninterpret(func_ty_info.cc, callconv_ty, pt)` //. Field order is lib/std/lang.zig's declaration order // (`@"callconv"`, then `varargs`); the shape is checked rather than assumed. static uint32_t ctFnAttrsValue(Zcu* zcu, uint32_t fn_ty) { InternPool* ip = &zcu->intern_pool; static const char* const attrs_path[] = { "Type", "Fn", "Attributes" }; uint32_t attrs_ty = getStdLangType(zcu, attrs_path, 3); if (!ipTagIsAnyStruct(ip->item_tags[attrs_ty]) || ipStructFieldsLen(ip, attrs_ty) != 2) UNIMPLEMENTED("zirTypeInfo: unexpected Type.Fn.Attributes shape"); uint32_t elems[2]; elems[0] = ctCallingConventionValue(zcu, ipStructFieldType(ip, attrs_ty, 0), ipFuncTypeCcPacked(ip, fn_ty)); elems[1] = ipFuncTypeIsVarArgs(ip, fn_ty) ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; return ipGetAggregate(ip, attrs_ty, elems, 2); } // (part of) Sema.zig:zirTypeInfo, PerThread.zig:intern, // PerThread.zig:arrayType // `[]const ?type` of a function type's parameter types, one `?type` element // per parameter (lib/std/lang.zig `param_types: []const ?type`, whose // doc comment says "A `null` element represents either an `anytype` // parameter, or a parameter with a generic type"). Mirrors Sema.zirTypeInfo's // `.@"fn"` branch element construction (src/Sema.zig): // // const param_ty = func_ty_info.param_types.get(ip)[param_index]; // const is_generic = param_ty == .generic_poison_type; // // ... // const param_ty_val = try pt.intern(.{ .opt = .{ // // .ty = try pt.intern(.{ .opt_type = .type_type }), // .val = if (is_generic) .none else param_ty, // } }); // const param_types_val = v: { // // const new_decl_ty = try pt.arrayType(.{ // .len = param_type_vals.len, // .child = try pt.intern(.{ .opt_type = .type_type }) }); // const new_decl_val = (try pt.aggregateValue( // new_decl_ty, param_type_vals)).toIntern(); // const slice_ty = (try pt.ptrType(.{ // .child = try pt.intern(.{ .opt_type = .type_type }), // .flags = .{ .size = .slice, .is_const = true } })).toIntern(); // const manyptr_ty = Type.fromInterned(slice_ty) // .slicePtrFieldType(zcu).toIntern(); // break :v try pt.intern(.{ .slice = .{ // .ty = slice_ty, // .ptr = try pt.intern(.{ .ptr = .{ .ty = manyptr_ty, // .base_addr = .{ .uav = .{ .orig_ty = manyptr_ty, // .val = new_decl_val } }, .byte_offset = 0 } }), // .len = (try pt.intValue(.usize, param_type_vals.len)) // .toIntern() } }); // }; // // The `?type` element representation is the one `return_type: ?type` already // uses in ctFnInfoField below (`ipGetOptionalType(ZIR_REF_TYPE_TYPE)` + // ipGetOptPayload / ipGetOptNull) — upstream builds both from the same // `pt.intern(.{ .opt_type = .type_type })`, so no extra optional machinery is // needed. // // The outer slice uses the direct many-pointer uav form (`base_addr.uav` over // the `[N]?type` array aggregate, with `orig_ty` = the many-pointer) — the // ctBuildDeclNamesSlice / ctBuildEnumFieldNamesSlice shape, which matches // upstream byte-for-byte — not the intermediate array-pointer + // coerceArrayPtrToSlice shape the remaining element-non-name slice builders // above still disclose (ctBuildFieldTypesSlice / ctBuildFieldAttrsSlice). // // Dynamically allocated (calloc), not a fixed stack cap: a // function's parameter count is unbounded in the source language, so this // mirrors ctBuildDeclNamesSlice's dynamic array rather than a fixed cap. static uint32_t ctBuildParamTypesSlice(Zcu* zcu, uint32_t fn_ty) { InternPool* ip = &zcu->intern_pool; uint32_t n = ipFuncTypeParamsLen(ip, fn_ty); uint32_t opt_type_ty = ipGetOptionalType(ip, ZIR_REF_TYPE_TYPE); uint32_t* elems = zig1Calloc(n ? n : 1u, sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { uint32_t param_ty = ipFuncTypeParamType(ip, fn_ty, i); // Upstream's `is_generic` predicate is exactly // `param_ty == .generic_poison_type`; a param slot zig1 // left unpopulated (IP_INDEX_NONE) is not a shape upstream can // produce, so it aborts rather than being folded into `null`. if (param_ty == IP_INDEX_NONE) { free(elems); UNIMPLEMENTED("zirTypeInfo: parameter type is none"); } elems[i] = (param_ty == IP_INDEX_GENERIC_POISON_TYPE) ? ipGetOptNull(ip, opt_type_ty) : ipGetOptPayload(ip, opt_type_ty, param_ty); } uint32_t arr_ty = ipGetArrayType(ip, n, opt_type_ty, IP_INDEX_NONE); uint32_t agg = ipGetAggregate(ip, arr_ty, elems, n); free(elems); uint32_t many = ipGetPtrTypeSentinel( ip, opt_type_ty, IP_PTR_FLAGS_CONST_MANY, IP_INDEX_NONE); uint32_t slice_ty = ipGetSliceType(ip, many); uint32_t uav = ipGetPtrUav(ip, many, agg); uint32_t len_val = ipGetIntUsize(ip, n); return ipGetPtrSlice(ip, slice_ty, uav, len_val); } static uint32_t ctBuildParamAttrsSlice(Zcu* zcu, uint32_t fn_ty) { InternPool* ip = &zcu->intern_pool; uint32_t n = ipFuncTypeParamsLen(ip, fn_ty); static const char* const pa_path[] = { "Type", "Fn", "ParamAttributes" }; uint32_t pa_ty = getStdLangType(zcu, pa_path, 3); if (!ipTagIsAnyStruct(ip->item_tags[pa_ty]) || ipStructFieldsLen(ip, pa_ty) != 1) UNIMPLEMENTED("zirTypeInfo: unexpected ParamAttributes shape"); uint32_t noalias_bits = ipFuncTypeNoaliasBits(ip, fn_ty); uint32_t* elems = zig1Calloc(n ? n : 1u, sizeof(uint32_t)); for (uint32_t i = 0; i < n; i++) { uint32_t pe[1]; bool is_noalias = i < 32 && ((noalias_bits >> i) & 1u) != 0; pe[0] = is_noalias ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; elems[i] = ipGetAggregate(ip, pa_ty, pe, 1); } uint32_t arr_ty = ipGetArrayType(ip, n, pa_ty, IP_INDEX_NONE); uint32_t agg = ipGetAggregate(ip, arr_ty, elems, n); free(elems); uint32_t many = ipGetPtrTypeSentinel( ip, pa_ty, IP_PTR_FLAGS_CONST_MANY, IP_INDEX_NONE); uint32_t slice_ty = ipGetSliceType(ip, many); uint32_t uav = ipGetPtrUav(ip, many, agg); uint32_t len_val = ipGetIntUsize(ip, n); return ipGetPtrSlice(ip, slice_ty, uav, len_val); } static bool ctFnTypeIsGeneric(Zcu* zcu, uint32_t fn_ty) { const InternPool* ip = &zcu->intern_pool; uint32_t n = ipFuncTypeParamsLen(ip, fn_ty); uint32_t comptime_bits = ipFuncTypeComptimeBits(ip, fn_ty); for (uint32_t i = 0; i < n; i++) { uint32_t param_ty = ipFuncTypeParamType(ip, fn_ty, i); if (param_ty == IP_INDEX_NONE) UNIMPLEMENTED("zirTypeInfo: parameter type is none"); if (param_ty == IP_INDEX_GENERIC_POISON_TYPE) return true; if (ipFuncTypeParamBit(comptime_bits, i)) return true; if (ztComptimeOnly(zcu, param_ty)) return true; } uint32_t rt = ipFuncTypeReturnType(ip, fn_ty); if (rt == IP_INDEX_NONE) return false; return semaFnRetTyIsGeneric(ip, rt) || ztComptimeOnly(zcu, rt); } // One field of `@typeInfo(F).@"fn"` (lang.zig Type.Fn), built from the // source function type. static uint32_t ctFnInfoPayloadField( Zcu* zcu, uint32_t src_ty, const char* field) { InternPool* ip = &zcu->intern_pool; if (strcmp(field, "fn") == 0) UNIMPLEMENTED("zirTypeInfo: payload has no fn field"); if (strcmp(field, "return_type") == 0) { uint32_t rt_opt = ipGetOptionalType(ip, ZIR_REF_TYPE_TYPE); uint32_t rt = ipFuncTypeReturnType(ip, src_ty); return (rt == IP_INDEX_NONE || semaFnRetTyIsGeneric(ip, rt)) ? ipGetOptNull(ip, rt_opt) : ipGetOptPayload(ip, rt_opt, rt); } if (strcmp(field, "attrs") == 0) return ctFnAttrsValue(zcu, src_ty); if (strcmp(field, "is_generic") == 0) return ctFnTypeIsGeneric(zcu, src_ty) ? ZIR_REF_BOOL_TRUE : ZIR_REF_BOOL_FALSE; if (strcmp(field, "param_types") == 0) return ctBuildParamTypesSlice(zcu, src_ty); if (strcmp(field, "param_attrs") == 0) return ctBuildParamAttrsSlice(zcu, src_ty); UNIMPLEMENTED("zirTypeInfo: unsupported Type.Fn field"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } static uint32_t ctFnInfoValue(Zcu* zcu, uint32_t src_ty) { const InternPool* ip = &zcu->intern_pool; static const char* const fn_path[] = { "Type", "Fn" }; uint32_t fn_info_ty = getStdLangType(zcu, fn_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[fn_info_ty]) || ipStructFieldsLen(ip, fn_info_ty) != 5) UNIMPLEMENTED("zirTypeInfo: unexpected Type.Fn shape"); uint32_t elems[5]; elems[0] = ctFnInfoPayloadField(zcu, src_ty, "attrs"); elems[1] = ctFnInfoPayloadField(zcu, src_ty, "is_generic"); elems[2] = ctFnInfoPayloadField(zcu, src_ty, "return_type"); elems[3] = ctFnInfoPayloadField(zcu, src_ty, "param_types"); elems[4] = ctFnInfoPayloadField(zcu, src_ty, "param_attrs"); return ctWrapTypeInfoPayload(zcu, "fn", 2, fn_info_ty, elems, 5, "zirTypeInfo: no `fn` std.lang.Type tag", "zirTypeInfo: no `fn` std.lang.Type field", "zirTypeInfo: `.fn` payload type mismatch"); } static uint32_t ctErrorUnionInfoValue(Zcu* zcu, uint32_t src_ty) { const InternPool* ip = &zcu->intern_pool; static const char* const eu_path[] = { "Type", "ErrorUnion" }; uint32_t eu_info_ty = getStdLangType(zcu, eu_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[eu_info_ty]) || ipStructFieldsLen(ip, eu_info_ty) != 2) UNIMPLEMENTED("zirTypeInfo: unexpected Type.ErrorUnion shape"); uint32_t elems[2]; elems[0] = ipErrorUnionErrorSet(ip, src_ty); elems[1] = ipErrorUnionPayload(ip, src_ty); return ctWrapTypeInfoPayload(zcu, "error_union", 11, eu_info_ty, elems, 2, "zirTypeInfo: no `error_union` std.lang.Type tag", "zirTypeInfo: no `error_union` std.lang.Type field", "zirTypeInfo: `.error_union` payload type mismatch"); } // (part of) Sema.zig:zirTypeInfo // `?[]const [:0]const u8` for Type.ErrorSet.error_names. Upstream's // Sema.zirTypeInfo `.error_set` branch returns null only for `anyerror`; // finite sets, including `error{}`, get a non-null slice whose elements are // direct `[:0]const u8` name slices in error-set order. static uint32_t ctErrorSetNamesOptValue(Zcu* zcu, uint32_t src_ty) { InternPool* ip = &zcu->intern_pool; uint32_t many_names = ipGetPtrTypeSentinel(ip, ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE, IP_PTR_FLAGS_CONST_MANY, IP_INDEX_NONE); uint32_t names_slice_ty = ipGetSliceType(ip, many_names); uint32_t opt_names_slice_ty = ipGetOptionalType(ip, names_slice_ty); uint32_t resolved = src_ty; if (resolved == ZIR_REF_ANYERROR_TYPE) return ipGetOptNull(ip, opt_names_slice_ty); // `sema.resolveInferredErrorSetTy`, which forces the owner's analysis. if (ipItemTagIs(ip, resolved, IP_TAG_TYPE_INFERRED_ERROR_SET)) { resolved = semaResolvedInferredErrorSet(ip, resolved); if (resolved == ZIR_REF_ANYERROR_TYPE) return ipGetOptNull(ip, opt_names_slice_ty); } if (resolved >= ip->items_len || ip->item_tags[resolved] != IP_TAG_TYPE_ERROR_SET) UNIMPLEMENTED("zirTypeInfo: unsupported error set type"); uint32_t n = ipErrorSetNamesLen(ip, resolved); uint32_t* elems = ARR_INIT(uint32_t, n + 1u); for (uint32_t i = 0; i < n; i++) elems[i] = ctBuildFieldNameSliceDirect(ip, ipErrorSetName(ip, resolved, i)); uint32_t names = ctBuildNameSlices(ip, elems, n); free(elems); return ipGetOptPayload(ip, opt_names_slice_ty, names); } static uint32_t ctErrorSetInfoValue(Zcu* zcu, uint32_t src_ty) { const InternPool* ip = &zcu->intern_pool; static const char* const es_path[] = { "Type", "ErrorSet" }; uint32_t es_info_ty = getStdLangType(zcu, es_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[es_info_ty]) || ipStructFieldsLen(ip, es_info_ty) != 1) UNIMPLEMENTED("zirTypeInfo: unexpected Type.ErrorSet shape"); uint32_t elems[1]; elems[0] = ctErrorSetNamesOptValue(zcu, src_ty); return ctWrapTypeInfoPayload(zcu, "error_set", 9, es_info_ty, elems, 1, "zirTypeInfo: no `error_set` std.lang.Type tag", "zirTypeInfo: no `error_set` std.lang.Type field", "zirTypeInfo: `.error_set` payload type mismatch"); } static uint32_t ctOptionalInfoValue(Zcu* zcu, uint32_t src_ty) { InternPool* ip = &zcu->intern_pool; static const char* const opt_path[] = { "Type", "Optional" }; uint32_t opt_info_ty = getStdLangType(zcu, opt_path, 2); if (!ipTagIsAnyStruct(ip->item_tags[opt_info_ty]) || ipStructFieldsLen(ip, opt_info_ty) != 1) UNIMPLEMENTED("zirTypeInfo: unexpected Type.Optional shape"); uint32_t elems[1]; elems[0] = ip->item_datas[src_ty]; return ctWrapTypeInfoPayload(zcu, "optional", 8, opt_info_ty, elems, 1, "zirTypeInfo: no `optional` std.lang.Type tag", "zirTypeInfo: no `optional` std.lang.Type field", "zirTypeInfo: `.optional` payload type mismatch"); } // (part of) Sema.zig:zirTypeInfo // Single category-to-value dispatcher for the modeled `@typeInfo(T)` surface. // The shared real-value producer for both ctEvalBody and the runtime // `ZIR_INST_TYPE_INFO` arm, so Sema.zirTypeInfo's category chain exists once. uint32_t ctTypeInfoValueForType(Zcu* zcu, uint32_t ti_ty) { InternPool* ip = &zcu->intern_pool; // zirTypeInfo: `"try sema.ensureLayoutResolved(ty, src, .type_info);"` // after `"sema.getStdLangType(src, .Type)"`. static const char* const type_info_path[] = { "Type" }; getStdLangType(zcu, type_info_path, 1); ensureLayoutResolved(zcu, ti_ty); if (ti_ty == ZIR_REF_COMPTIME_INT_TYPE) return ctTypeInfoSimpleValue(zcu, "comptime_int"); if (ti_ty == ZIR_REF_ANYERROR_TYPE) return ctErrorSetInfoValue(zcu, ti_ty); if (ti_ty == ZIR_REF_TYPE_TYPE || ti_ty == ZIR_REF_VOID_TYPE || ti_ty == ZIR_REF_BOOL_TYPE || ti_ty == ZIR_REF_NORETURN_TYPE || ti_ty == ZIR_REF_COMPTIME_FLOAT_TYPE || ti_ty == ZIR_REF_UNDEFINED_TYPE || ti_ty == ZIR_REF_NULL_TYPE || ti_ty == ZIR_REF_ENUM_LITERAL_TYPE) { const char* simple_name; switch (ti_ty) { case ZIR_REF_TYPE_TYPE: simple_name = "type"; break; case ZIR_REF_VOID_TYPE: simple_name = "void"; break; case ZIR_REF_BOOL_TYPE: simple_name = "bool"; break; case ZIR_REF_NORETURN_TYPE: simple_name = "noreturn"; break; case ZIR_REF_COMPTIME_FLOAT_TYPE: simple_name = "comptime_float"; break; case ZIR_REF_UNDEFINED_TYPE: simple_name = "undefined"; break; case ZIR_REF_NULL_TYPE: simple_name = "null"; break; case ZIR_REF_ENUM_LITERAL_TYPE: simple_name = "enum_literal"; break; default: simple_name = NULL; break; } return ctTypeInfoSimpleValue(zcu, simple_name); } uint32_t bits; bool is_signed; if (ztIntInfo(ip, ti_ty, &bits, &is_signed)) return ctIntInfoValue(zcu, is_signed, bits); if (ztSimpleFloatBits(ti_ty, &bits)) return ctFloatInfoValue(zcu, bits); uint8_t tt = ip->item_tags[ti_ty]; if (tt == IP_TAG_TYPE_POINTER || tt == IP_TAG_TYPE_SLICE) return ctPtrInfoValue(zcu, ti_ty); if (ipTagIsAnyStruct(tt) || tt == IP_TAG_TYPE_TUPLE) return ctStructInfoValue(zcu, ti_ty); if (tt == IP_TAG_TYPE_ARRAY_SMALL || tt == IP_TAG_TYPE_ARRAY_BIG) return ctArrayInfoValue(zcu, ti_ty); if (tt == IP_TAG_TYPE_VECTOR) return ctVectorInfoValue(zcu, ti_ty); if (ipTagIsAnyUnion(tt)) return ctUnionInfoValue(zcu, ti_ty); if (ipIsEnumType(ip, ti_ty)) return ctEnumInfoValue(zcu, ti_ty); if (tt == IP_TAG_TYPE_FUNCTION) return ctFnInfoValue(zcu, ti_ty); if (tt == IP_TAG_TYPE_OPTIONAL) return ctOptionalInfoValue(zcu, ti_ty); if (tt == IP_TAG_TYPE_ERROR_UNION || tt == IP_TAG_TYPE_ANYERROR_UNION) return ctErrorUnionInfoValue(zcu, ti_ty); if (tt == IP_TAG_TYPE_ERROR_SET || tt == IP_TAG_TYPE_INFERRED_ERROR_SET) return ctErrorSetInfoValue(zcu, ti_ty); if (ztIsZigTypeTag(ip, ti_ty, ZIG_TYPE_TAG_OPAQUE)) return ctOpaqueInfoValue(zcu, ti_ty); UNIMPLEMENTED("zirTypeInfo: unsupported source type"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // ── Integer reads and switch items ────────────────────────────────────────── // Read a comptime-known integer's value + comptime_int-ness + bit width from // its InternPool index, for ctEvalBody arithmetic. Values are sign-aware // (signed operands are read as their two's-complement magnitude). Returns // false if `idx` is not an integer item. `*out_bits` is 0 for a // `comptime_int`. bool ctReadInt(const InternPool* ip, uint32_t idx, int64_t* out_val, bool* out_is_ct, uint32_t* out_bits) { // One reader, not a second per-tag transcription of the pool: the value // comes out as `Value.toBigInt` (`valToBigInt`) and is asked for a // 64-bit working word by `big.int.Const.toInt` -- `bigIntToI64` at a // signed type or `comptime_int`, `bigIntToU64` at an unsigned one, whose // pattern is what a two's-complement word is. `toInt` returns // `error.TargetTooSmall` for a value the word cannot hold, so this // declines (the consumer aborts loudly) where the per-tag reader it // replaces silently handed back one limb of a wider magnitude. // `out_bits` is the type's own width and 0 for `comptime_int`, which has // none. *out_is_ct = false; *out_bits = 0; uint64_t space[2]; BigIntConst v; if (!valToBigInt(ip, idx, space, &v)) return false; uint32_t ty = ipTypeOf(ip, idx); if (ty == ZIR_REF_COMPTIME_INT_TYPE) { if (!bigIntToI64(v, out_val)) return false; *out_is_ct = true; return true; } uint32_t bits; bool is_signed; if (ty == IP_INDEX_NONE || !ztIntInfo(ip, ty, &bits, &is_signed)) return false; if (is_signed) { if (!bigIntToI64(v, out_val)) return false; } else { uint64_t word; if (!bigIntToU64(v, &word)) return false; *out_val = (int64_t)word; } *out_bits = bits; return true; } // A comptime slice value's length, false when unreadable or negative. static bool ctReadSliceLen( const InternPool* ip, uint32_t slice, uint64_t* out) { int64_t len; bool is_ct; uint32_t bits; if (!ctReadInt(ip, ipPtrSliceLen(ip, slice), &len, &is_ct, &bits) || len < 0) return false; *out = (uint64_t)len; return true; } // (part of) Sema.zig:coerceExtra, PerThread.zig:getCoerced // `Sema.coerceExtra`'s `.int, .comptime_int` arm at any width: the range // check (`intFitsInType`, a compile error when it fails) and `pt.getCoerced`, // the value re-interned at `ty`. IP_INDEX_NONE when `val` is not an integer // or `ty` not an integer type. uint32_t ctCoerceIntAnyWidth(InternPool* ip, uint32_t val, uint32_t ty) { uint64_t space[2]; BigIntConst big; if (!valToBigInt(ip, val, space, &big)) return IP_INDEX_NONE; if (ty != ZIR_REF_COMPTIME_INT_TYPE) { uint32_t bits; bool is_signed; if (!ztIntInfo(ip, ty, &bits, &is_signed)) return IP_INDEX_NONE; if (!valBigIntFitsInType(ip, big, ty)) COMPILE_ERROR_UPSTREAM("type cannot represent integer value"); } return ipGetInt(ip, ty, big); } // (part of) Sema.zig:resolveSwitchItem // Sema.resolveSwitchItem: one comptime `switch` item (an enum literal, an // error value or an item body, advancing *cursor past the body) coerced to // `item_ty`. static uint32_t ctSwitchItemBodyValue(SemaCtx* sema, uint32_t switch_inst, uint32_t item_info, uint32_t* cursor, uint32_t item_ty, bool prong_is_comptime_unreach) { Zcu* zcu = sema->zcu; const Zir* zir = sema->zir; uint32_t* values = sema->inst_values; InternPool* ip = &zcu->intern_pool; if ((item_info & ZIR_ITEM_INFO_KIND_MASK) == ZIR_ITEM_INFO_KIND_ENUM_LITERAL) { const char* lname = (const char*)&zir ->string_bytes[item_info >> ZIR_ITEM_INFO_DATA_SHIFT]; return semaSwitchEnumLiteralItem(zcu, zir, item_ty, ipGetOrPutString(ip, lname, (uint32_t)strlen(lname))); } if ((item_info & ZIR_ITEM_INFO_KIND_MASK) == ZIR_ITEM_INFO_KIND_ERROR_VALUE) { // resolveSwitchItem's `.error_value` arm: `"pt.intern(.{ .err = .{ // .ty = err_set_ty.toIntern(), .name = name } })"`, then coerced to // `item_ty`; an error value's identity is its (set, name) pair. if (!ipIsErrorSetType(ip, item_ty)) UNIMPLEMENTED( "resolveSwitchItem: switch error item, non-error operand"); const char* ename = (const char*)&zir ->string_bytes[item_info >> ZIR_ITEM_INFO_DATA_SHIFT]; uint32_t name = ipGetOrPutString(ip, ename, (uint32_t)strlen(ename)); // `"We allow prongs with errors which are not part of the error set // being switched on if their prong body is `=> comptime // unreachable,`"`: `.missing_error => if (prong_is_comptime_unreach) // break :item_ref uncoerced;` -- the item keeps its single-error set // type; any other prong's coercion is the compile error. if (!ipErrorSetHasName(ip, item_ty, name, IP_IES_NAME_RESOLVED)) { if (!prong_is_comptime_unreach) COMPILE_ERROR_UPSTREAM("switch: error item not in the set"); return ipGetErrorSetError( ip, ipGetErrorSetType(ip, &name, 1), name); } return ipGetErrorSetError(ip, item_ty, name); } if ((item_info & ZIR_ITEM_INFO_KIND_MASK) != ZIR_ITEM_INFO_KIND_BODY_LEN) UNIMPLEMENTED("resolveSwitchItem: switch int item not a value body"); uint32_t body_len = item_info >> ZIR_ITEM_INFO_DATA_SHIFT; const uint32_t* body = zirBodySlice(zir, *cursor); *cursor += body_len; if (body_len == 0) UNIMPLEMENTED("resolveSwitchItem: empty switch item body"); // `"The result location of item bodies is `.{ .coerce_ty = switch_inst // }`. sema.inst_map.putAssumeCapacity(switch_inst, .fromType(item_ty)); // defer assert(sema.inst_map.remove(switch_inst));"` uint32_t saved_switch_val = values[switch_inst]; values[switch_inst] = item_ty; // resolveSwitchItem's `.body_len` arm enumerates nothing: it is // `"break :ref try sema.resolveInlineBody(block, body, switch_inst);"` // -- the whole evaluator -- which here is ctEvalBody itself. The runtime // twin already delegates; a whitelist of item-body instructions is what // refused a negative item (`negate`) on this side. // Any exit but falling off the end aborts, so its out-values are unread. uint32_t unread_ret, unread_block, unread_val; if (ctEvalBody( sema, body, body_len - 1, &unread_ret, &unread_block, &unread_val) != CT_EVAL_NORMAL) UNIMPLEMENTED("resolveSwitchItem: switch item body left its own body"); values[switch_inst] = saved_switch_val; uint32_t last = body[body_len - 1]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) UNIMPLEMENTED( "resolveSwitchItem: switch item body not break-terminated"); uint32_t raw = semaResolveRef(zir->inst_datas[last].break_data.operand, values); if (ctValueConformsToType(ip, raw, item_ty)) return raw; // `"break :item_ref try sema.coerce(block, item_ty, uncoerced, // item_src)"` for an integer item. uint32_t int_item = ctCoerceIntAnyWidth(ip, raw, item_ty); if (int_item != IP_INDEX_NONE) return int_item; uint32_t coerced = ctCoerceToType(zcu, raw, item_ty); if (!ctValueConformsToType(ip, coerced, item_ty)) UNIMPLEMENTED("resolveSwitchItem: switch item coercion mismatch"); return coerced; } // --- Comptime mutable cells (Sema comptime-alloc / MutableValue) ------------ // // A comptime `var` is a ComptimeAlloc of the analyzing Sema holding a // MutableValue, addressed by an interned ptr_comptime_alloc chain // (analyzeComptimeAlloc, storeToInferredAllocComptime) that loadComptimePtr // and storeComptimePtr read and write. A CtCellPtr is the decode of such a // chain into the allocation, an access path (struct, tuple and union fields, // array elements, optional and error-union payloads) and a slice, // bit-pointer or reinterpreted view; the field, element, payload and slice // arms derive the next pointer from it. CONSOLIDATION.txt 5.4.1 maps each // piece onto comptime_ptr_access.zig. // ── Comptime cells, cell pointers and namespace captures ──────────────────── // A path component descriptor kind. Struct-field and array-elem components // are CTPK_INDEXED (value 0, so a zero-initialized `kind[]` is all indexed). // The result-location init machinery (opt_eu_base_ptr_init / // struct_init_field_ptr on a union or an error-union/optional result location) // adds the other kinds below. `comp` holds the field/elem index for // indexed and union; OPT/EU carry no index (they descend into the sole // payload). #define CTPK_INDEXED \ 0 // struct field (is_elem=false) or array elem (is_elem=true) #define CTPK_UNION 1 // active union field (comp = field index) #define CTPK_OPT 2 // optional payload #define CTPK_EU 3 // error-union payload typedef struct CtPathComp { struct CtPathComp* parent; uint32_t comp; uint8_t kind; bool is_elem; } CtPathComp; typedef struct CtCellPtr { uint32_t cell; // the ComptimeAllocIndex (getComptimeAlloc) uint32_t depth; // An immutable cons list makes descriptor copies safe and append O(1). // Its length is exactly the source-derived path length, with no cap. CtPathComp* path; // --- comptime cell-slice view (Sema comptime slice of a mutable alloc) // --- When `is_slice` is set, the descriptor is not a pointer to the // location the path addresses; it is a slice / pointer-to-array view over // the array that location holds, windowed to `[win_start, win_start + // win_len)`. Element `i` of the view aliases element `win_start + i` of // that array (elem_ptr/elem_val add the offset + append an indexed // component to the path; a write through such an element rebuilds the // array back to the cell root, so the mutation aliases the cell). The zero // default (is_slice=false) keeps every non-slice descriptor byte-for-byte // unchanged. A slice over a cell writes through to it. bool is_slice; uint32_t win_start; uint32_t win_len; uint32_t slice_ty; // The allocation's source pointer attributes. Chain links may canonicalize // their base, but Type.elemPtrType derives from this original root. // Decoded carriers do not add a second source anchor: their canonical // bases replay this root through the logical path below. uint32_t root_ptr_flags; // Value.ptrField's packed arms, `"return pt.getCoerced(parent_ptr, // field_ptr_ty)"`: a field pointer into a packed container is its parent // pointer retyped. The path addresses that host container and this is // the pointer's own type, whose `packed_offset` places the field in it. // 0 (the memset default; never a pointer type) for every other pointer. uint32_t bit_ptr_ty; // storeComptimePtr's `.reinterpret` strategy: `bit_ptr_ty` is the // pointer's own type, reading or splicing the memory of the location the // path addresses at `reint_offset`. bool reinterpret; uint64_t reint_offset; } CtCellPtr; static CtCellPtr ctCellPtrResolveDescriptorAt(Zcu* zcu, InternPool* ip, uint32_t v, uint32_t depth, bool slice_base, bool for_store); // The type of comptime allocation `idx` (`alloc.val.typeOf(zcu)`). static uint32_t ctAllocType(Zcu* zcu, uint32_t idx) { return mutableValueTypeOf( &zcu->intern_pool, &getComptimeAlloc(zcu, idx)->val); } static uint32_t ctCellPtrType(Zcu* zcu, InternPool* ip, uint32_t ptr); static uint32_t ctCellPtrLevelType( Zcu* zcu, const InternPool* ip, const CtCellPtr* d, uint32_t level); static uint32_t ctCellPtrLoad(Zcu* zcu, InternPool* ip, uint32_t ptr); // A cell-slice's logical `.len` excludes its optional physical sentinel // element. Keep validation and the physical extent in one place for every // view consumer. static IpPtrInfo ctCellPtrSliceManyInfo( const InternPool* ip, const CtCellPtr* d) { if (!d->is_slice || !ztIsSlice(ip, d->slice_ty)) UNREACHABLE("zirTypeInfo: malformed cell-slice type"); IpPtrInfo info; if (ipPtrInfo(ip, ip->item_datas[d->slice_ty], &info) != IP_PTRI_OK || info.size != IP_PTR_SIZE_MANY) UNREACHABLE("zirTypeInfo: malformed many pointer"); return info; } static uint64_t ctCellPtrViewExtent(const InternPool* ip, const CtCellPtr* d) { IpPtrInfo info = ctCellPtrSliceManyInfo(ip, d); return (uint64_t)d->win_len + (info.sentinel != IP_INDEX_NONE ? 1u : 0u); } static void ctCellPtrAppend( CtCellPtr* d, uint8_t kind, bool is_elem, uint32_t comp) { if (d->reinterpret) UNIMPLEMENTED("Value.ptrElem: hop off a reinterpreted pointer"); if (d->depth == UINT32_MAX) UNIMPLEMENTED("Value.ptrElem: path length overflow"); CtPathComp* node = zig1Malloc(sizeof(CtPathComp)); node->parent = d->path; node->comp = comp; node->kind = kind; node->is_elem = is_elem; d->path = node; d->depth++; } // Element `i` of `d`: on a cell-slice view it is element `win_start + i` of // the backing array, and the result is a plain element pointer. static void ctCellPtrAppendElem( const InternPool* ip, CtCellPtr* d, uint32_t i) { if (d->is_slice) { if ((uint64_t)i >= ctCellPtrViewExtent(ip, d)) UNIMPLEMENTED("Value.ptrElem: slice index oob"); i += d->win_start; d->is_slice = false; d->win_start = 0; d->win_len = 0; } ctCellPtrAppend(d, CTPK_INDEXED, true, i); } static CtPathComp* ctCellPtrPathAt(const CtCellPtr* d, uint32_t index) { CtPathComp* node = d->path; for (uint32_t k = d->depth - 1; k > index; k--) node = node->parent; return node; } static CtCellPtr ctCellPtrResolveDescriptor( Zcu* zcu, InternPool* ip, uint32_t v); // Comptime container captures (Sema getCaptures / closure_get): a generic // container (the struct a `fn (comptime T: type) type` returns) closes over // enclosing comptime values. Resolved when the struct type is interned and // stored keyed by the struct's namespace index; a `closure_get` inside a // method of that container reads capture[i] using the method's own analysis // namespace // (== the container's namespace). static void ctEnsureCaptureSlot(Zcu* zcu, uint32_t ns) { if (ns < zcu->comptime_tables.ns_captures_cap) return; uint32_t nc = zig1GrowCap(zcu->comptime_tables.ns_captures_cap, 64u); while (ns >= nc) nc *= 2; zcu->comptime_tables.ns_captures = zig1Realloc( zcu->comptime_tables.ns_captures, nc * sizeof(CtCaptureSet)); for (uint32_t k = zcu->comptime_tables.ns_captures_cap; k < nc; k++) { zcu->comptime_tables.ns_captures[k].vals = NULL; zcu->comptime_tables.ns_captures[k].len = 0; } zcu->comptime_tables.ns_captures_cap = nc; } static CtCap* ctCopyCaps(const CtCap* caps, uint32_t len) { CtCap* copy = zig1Malloc((len ? len : 1) * sizeof(CtCap)); for (uint32_t k = 0; k < len; k++) copy[k] = caps[k]; return copy; } static void ctStoreCaptures(Zcu* zcu, uint32_t ns, CtCap* vals, uint32_t len) { ctEnsureCaptureSlot(zcu, ns); zcu->comptime_tables.ns_captures[ns].vals = vals; zcu->comptime_tables.ns_captures[ns].len = len; } void ctPushCaptureScope(Zcu* zcu, uint32_t ns, const CtCap* caps, uint32_t len, CtCaptureSet* saved) { ctEnsureCaptureSlot(zcu, ns); *saved = zcu->comptime_tables.ns_captures[ns]; zcu->comptime_tables.ns_captures[ns].vals = ctCopyCaps(caps, len); zcu->comptime_tables.ns_captures[ns].len = len; } void ctPopCaptureScope(Zcu* zcu, uint32_t ns, CtCaptureSet saved) { if (ns >= zcu->comptime_tables.ns_captures_cap) UNREACHABLE("ctPopCaptureScope: invalid namespace"); free(zcu->comptime_tables.ns_captures[ns].vals); zcu->comptime_tables.ns_captures[ns] = saved; } static CtCap* ctResolveCaptureWords(Zcu* zcu, const Zir* zir, uint32_t lookup_ns, const uint32_t* caps, uint32_t captures_len, const uint32_t* values, const uint32_t* runtime_values, const uint32_t* runtime_types) { InternPool* ip = &zcu->intern_pool; CtCap* out = zig1Malloc((captures_len ? captures_len : 1) * sizeof(CtCap)); for (uint32_t k = 0; k < captures_len; k++) { uint32_t w = caps[k]; uint32_t ctag = (w >> ZIR_CAPTURE_TAG_SHIFT) & ZIR_CAPTURE_TAG_MASK; uint32_t cdata = w >> ZIR_CAPTURE_DATA_SHIFT; if (ctag == ZIR_CAPTURE_TAG_NESTED) { // nested: propagate the parent closure's capture descriptor // verbatim (keeping a deferred decl deferred, preserving the // Nav-identity dedup key). out[k] = ctGetCaptureRaw(zcu, lookup_ns, cdata); } else if (ctag == ZIR_CAPTURE_TAG_INSTRUCTION) { // instruction: enclosing frame // value/type uint32_t v = IP_INDEX_NONE; if (values != NULL) { // An enum-literal frame value is already the real // `enum_literal` item, so the capture descriptor takes it // verbatim. v = values[cdata]; } else if (runtime_values != NULL) { uint32_t rv = runtime_values[cdata]; if (rv < AIR_REF_INST_BASE) v = rv; else if (rv != INST_VALUE_UNSET && runtime_types != NULL && runtime_types[cdata] != IP_INDEX_NONE) { CtCap c = { runtime_types[cdata], CAP_RUNTIME_TYPE }; out[k] = c; continue; } } CtCap c = { v, CAP_VALUE }; out[k] = c; } else if (ctag == 3) { // decl_val: defer (record the Nav, do not eval) const char* nm = (const char*)&zir->string_bytes[cdata]; uint32_t nm_ip = ipGetOrPutString(ip, nm, (uint32_t)strlen(nm)); uint32_t nav = ctDeclLookup(ip, zcu, zir, lookup_ns, nm_ip); CtCap c = { nav, CAP_DECL_VAL }; // nav may be IP_INDEX_NONE out[k] = c; } else if (ctag == ZIR_CAPTURE_TAG_DECL_REF) { // decl_ref: defer (record the Nav) const char* nm = (const char*)&zir->string_bytes[cdata]; uint32_t nm_ip = ipGetOrPutString(ip, nm, (uint32_t)strlen(nm)); uint32_t nav = ctDeclLookup(ip, zcu, zir, lookup_ns, nm_ip); CtCap c = { nav, CAP_DECL_REF }; out[k] = c; } else if (ctag == ZIR_CAPTURE_TAG_INSTRUCTION_LOAD) { // instruction_load: load // from captured pointer // Sema.getCaptures' `.instruction_load`: a comptime-known // pointer whose `pointerDeref` succeeds captures the loaded // value as `.@"comptime"`; otherwise `.runtime` of the pointee. if (runtime_values != NULL) { uint32_t rp = runtime_values[cdata]; if (rp < ip->items_len && ipItemTagIs( ip, ipTypeOf(ip, rp), IP_TAG_TYPE_POINTER)) { uint32_t loaded = pointerDeref(zcu, rp, NULL); if (loaded != IP_INDEX_NONE) { CtCap c = { loaded, CAP_VALUE }; out[k] = c; continue; } } } if (runtime_types != NULL && runtime_types[cdata] != IP_INDEX_NONE && runtime_types[cdata] != UINT32_MAX) { CtCap c = { ctInstructionLoadCaptureType(ip, runtime_types[cdata]), CAP_RUNTIME_TYPE }; out[k] = c; continue; } // No pointer type map is available (declared-nav/comptime // container path) or the pointer's type is still inferred: // not modeled. Deferred as an unresolvable slot; a member that // reads it aborts loudly at the closure_get consumer. CtCap c = { IP_INDEX_NONE, CAP_VALUE }; out[k] = c; } else { // Unknown capture tag: not modeled. CtCap c = { IP_INDEX_NONE, CAP_VALUE }; out[k] = c; } } return out; } // Raw capture descriptor for slot `index` of namespace `ns`, or a // {CAP_VALUE, IP_INDEX_NONE} miss sentinel when the slot does not exist. Used // for nested-capture propagation (a child container's capture that references // the parent's — the descriptor, deferred or not, flows through unchanged so // the child's dedup key stays Nav-identity-faithful). static bool ctTryGetCaptureRaw( const Zcu* zcu, uint32_t ns, uint32_t index, CtCap* out) { if (ns < zcu->comptime_tables.ns_captures_cap && index < zcu->comptime_tables.ns_captures[ns].len) { if (out) *out = zcu->comptime_tables.ns_captures[ns].vals[index]; return true; } return false; } static CtCap ctGetCaptureRaw(const Zcu* zcu, uint32_t ns, uint32_t index) { CtCap c; if (ctTryGetCaptureRaw(zcu, ns, index, &c)) return c; CtCap miss = { IP_INDEX_NONE, CAP_VALUE }; return miss; } // Materialize a capture descriptor to its interned value. CAP_VALUE passes // through (may be IP_INDEX_NONE — an unmodeled capture; the closure_get // consumer aborts loudly). CAP_RUNTIME_TYPE is deliberately not materialized: // it can answer `@TypeOf(capture)` but a direct capture value read remains // unsupported. A deferred decl_val evaluates the Nav's value now; a deferred // decl_ref forms the `*decl` ptr_nav. A self-referential decl whose struct // type is still being resolved (Nav == Zcu.navstruct_resolving, not yet // cached) is a field naming its own type — abort loudly rather than recurse (a // method reads it only after the type is cached, so the guard is inactive // then). static uint32_t ctMaterializeCap(Zcu* zcu, CtCap c) { if (c.kind == CAP_VALUE) return c.v; if (c.kind == CAP_RUNTIME_TYPE) return IP_INDEX_NONE; if (c.v == IP_INDEX_NONE) return IP_INDEX_NONE; // deferred but the decl was not found const InternPool* ip = &zcu->intern_pool; if (c.v == zcu->navstruct_resolving && !ip->navs[c.v].has_resolved) UNIMPLEMENTED("zirClosureGet: self-referential capture materialized " "during its own struct-type resolution"); if (c.kind == CAP_DECL_VAL) return navAnalyzeVal(zcu, c.v, NULL); // CAP_DECL_REF: a `*decl` pointer (the decl_ref arm). return navDeclRefAddressResolved(zcu, c.v, NULL); } static uint32_t ctGetCapture(Zcu* zcu, uint32_t ns, uint32_t index) { return ctMaterializeCap(zcu, ctGetCaptureRaw(zcu, ns, index)); } static uint32_t ctInstructionLoadCaptureType( const InternPool* ip, uint32_t ptr_ty) { if (ptr_ty == IP_INDEX_NONE || ptr_ty >= ip->items_len || ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("zirClosureGet: instruction_load capture source is not " "a pointer type"); uint32_t child = ipPtrChild(ip, ptr_ty); if (!ipIndexIsType(ip, child)) UNIMPLEMENTED("zirClosureGet: instruction_load child is not a type"); return child; } static uint32_t ctTypeOfCapture(Zcu* zcu, CtCap c) { const InternPool* ip = &zcu->intern_pool; if (c.kind == CAP_RUNTIME_TYPE) { if (c.v == IP_INDEX_NONE) return IP_INDEX_NONE; if (!ipIndexIsType(ip, c.v)) UNIMPLEMENTED("zirClosureGet: runtime capture type is not a type"); return c.v; } uint32_t cv = ctMaterializeCap(zcu, c); if (cv == IP_INDEX_NONE) return IP_INDEX_NONE; uint32_t ty = ipComptimeValueType(ip, cv); if (!ipIndexIsType(ip, ty)) UNIMPLEMENTED("zirClosureGet: materialized capture has non-type type"); return ty; } // The number of elements in a comptime aggregate type (struct field count / // array length). Other shapes abort. static uint32_t ctAggTypeLen(const InternPool* ip, uint32_t ty) { if (ipTagIsAnyStruct(ip->item_tags[ty])) return ipStructFieldsLen(ip, ty); IpArrayInfo ai; if (ipArrayInfoInline(ip, ty, &ai) == IP_ARR_OK) return ai.len; if (ztIsTuple(ip, ty)) return ipTupleFieldCount(ip, ty); // fields_len (comptime `for` // over a tuple `kvs_list`) UNIMPLEMENTED("Type.arrayLen: unsupported aggregate type"); ZIG1_TCC_RETURN(0); } // Type.isIndexable's array, vector and tuple types (also a `.one` pointer's // child): Sema.fieldVal's `.len` of one (Sema.zirForLen). static bool ctIndexableTypeLen( const InternPool* ip, uint32_t child, uint32_t* out_len) { uint32_t elem_ty, sentinel; if (ztArrayTypeInfo(ip, child, out_len, &elem_ty, &sentinel)) return true; if (child >= ip->items_len || (!ztIsTuple(ip, child) && ztZigTypeTag(ip, child) != ZIG_TYPE_TAG_VECTOR)) return false; *out_len = ctAggTypeLen(ip, child); return true; } uint32_t ctAggType(const InternPool* ip, uint32_t agg) { // ty is the first extra word of Repeated/Aggregate/Bytes; an // only_possible_value's data is the type. if (ip->item_tags[agg] == IP_TAG_ONLY_POSSIBLE_VALUE) return ip->item_datas[agg]; return ipTypeOf(ip, agg); } // --- explicit calling conventions ----------------------------------------- // Sema.zirFuncFancy's `has_cc_body` / `has_cc_ref` arms resolve the callconv // expression to a comptime `std.lang.CallingConvention` value and hand it to // `funcCommon`, which validates it and stores `PackedCallingConvention.pack` // in the interned `type_function`'s Flags. The helpers below port that chain. // ── Comptime coercion and pointer casts ───────────────────────────────────── uint32_t ctFieldNamedNameString(InternPool* ip, uint32_t fn_name_v, char* fn_buf, uint32_t fn_buf_cap, uint32_t* out_fn_len) { // Extract the name string. Upstream funnels both shapes through one path: // zirFieldPtrNamed/zirFieldPtrNamedLoad (src/Sema.zig) call // resolveConstStringIntern (src/Sema.zig), which coerces the // operand to `slice_const_u8` and calls sliceToIpString (src/Sema.zig: // 34188-34203) -> maybeDerefSliceAsArray's `.slice` case // (src/Sema.zig): read the slice's {ptr, len}, recast `ptr` // to a one-pointer-to-array, and deref. ctEvalBody has no general // `coerce`, so it special-cases each interned shape directly instead // (matching the ZIR_INST_FIELD_TYPE_REF / ctReifyArgView precedents in // this file): either a direct // `*const [N:0]u8` uav (array-pointer shape, e.g. `@field(Target, // @tagName(family))`), or a `[:0]const u8` slice over a direct // many-pointer uav -- the ctBuildFieldNameSliceDirect shape. // `fn_start` is the name's first byte in the backing aggregate: a // sub-slice's pointer sits that many u8 elements past the uav's start. uint32_t fn_agg; uint32_t fn_slice_len = UINT32_MAX; // UINT32_MAX = no override uint64_t fn_start = 0; if (semaRefIsInterned(fn_name_v) && (ipIsPtrUav(ip, fn_name_v))) { if (ipPtrUavByteOffset(ip, fn_name_v) != 0) UNIMPLEMENTED("zirFieldPtrNamed: field_named offset pointer name"); uint32_t fn_ptr_ty = ipTypeOf(ip, fn_name_v); fn_agg = ipPtrUavValue(ip, fn_name_v); if (fn_ptr_ty < ip->items_len && ip->item_tags[fn_ptr_ty] == IP_TAG_TYPE_POINTER) { uint32_t fn_child; uint32_t fn_flags; ctPtrTypeParts(ip, fn_ptr_ty, &fn_child, &fn_flags); uint32_t fn_arr_len, fn_arr_child, fn_arr_sentinel; if (IP_PTR_FLAGS_SIZE(fn_flags) == IP_PTR_SIZE_ONE && ztArrayTypeInfo(ip, fn_child, &fn_arr_len, &fn_arr_child, &fn_arr_sentinel)) fn_slice_len = fn_arr_len; } } else if (semaRefIsInterned(fn_name_v) && ip->item_tags[fn_name_v] == IP_TAG_PTR_SLICE) { uint32_t fn_many = ipPtrSlicePtr(ip, fn_name_v); // Defensive tag check beyond the field_type_ref/ctReifyArgView // precedents (which trust the +1 offset unconditionally): loudly // reject rather than silently misdecode an unmodeled many-pointer // shape. if (fn_many >= ip->items_len || (!ipIsPtrUav(ip, fn_many))) UNIMPLEMENTED( "zirFieldPtrNamed: field_named slice name backing pointer"); // Mirror ctReifyArgView's PTR_SLICE arm fully -- a // nonzero backing byte_offset (a SLICE_END sub-slice with a != 0) must // stay loud, and the name length comes from the slice's own len word // (extra[+2]), not the backing aggregate's type (a zero-offset // sub-slice narrows len below the aggregate length; upstream derefs // *[slice.len]u8). The aggregate-length fallback below only applies to // the array-pointer arm. fn_start = ipPtrUavByteOffset(ip, fn_many); int64_t fn_len_v; bool fn_len_ct; uint32_t fn_len_b; if (!ctReadInt(ip, ipPtrSliceLen(ip, fn_name_v), &fn_len_v, &fn_len_ct, &fn_len_b) || fn_len_v < 0) UNIMPLEMENTED( "zirFieldPtrNamed: field_named unreadable slice len"); fn_slice_len = (uint32_t)fn_len_v; fn_agg = ipPtrUavValue(ip, fn_many); } else { UNIMPLEMENTED("zirFieldPtrNamed: field_named non-string name"); } uint32_t fn_arr_ty = ctAggType(ip, fn_agg); uint32_t fn_len = ctAggTypeLen(ip, fn_arr_ty); if (fn_start > fn_len) UNIMPLEMENTED("zirFieldPtrNamed: field_named slice past its backing"); if (fn_slice_len != UINT32_MAX) { if (fn_slice_len > fn_len - fn_start) UNIMPLEMENTED( "zirFieldPtrNamed: field_named slice len exceeds backing"); fn_len = fn_slice_len; } else if (fn_start != 0) { UNREACHABLE("zirFieldPtrNamed: offset array pointer name"); } if (fn_len >= fn_buf_cap) UNIMPLEMENTED("zirFieldPtrNamed: field_named name too long"); if (ip->item_tags[fn_agg] == IP_TAG_BYTES) { const char* fb = ipStringBytes(ip, ip->extra[ip->item_datas[fn_agg] + 1]); memcpy(fn_buf, fb + fn_start, fn_len); } else { for (uint32_t z = 0; z < fn_len; z++) fn_buf[z] = (char)(uint8_t)ip->item_datas[valElemValue(ip, fn_agg, (uint32_t)fn_start + z, "Value.elemValue: comptime")]; } fn_buf[fn_len] = '\0'; *out_fn_len = fn_len; return ipGetOrPutString(ip, fn_buf, fn_len); } static uint32_t ctTupleFieldValue( InternPool* ip, uint32_t tuple_val, uint32_t field_index) { uint32_t tuple_ty = ipTypeOf(ip, tuple_val); if (tuple_ty == IP_INDEX_NONE || !ztIsTuple(ip, tuple_ty)) UNIMPLEMENTED("tupleFieldValByIndex: value is not a tuple"); uint32_t field_ty, comptime_field; if (semaTupleFieldValueComptime( ip, tuple_ty, field_index, &field_ty, &comptime_field)) return comptime_field; if (ip->item_tags[tuple_val] == IP_TAG_ONLY_POSSIBLE_VALUE) UNIMPLEMENTED("tupleFieldValByIndex: opv tuple has runtime field"); return valElemValue( ip, tuple_val, field_index, "Value.elemValue: comptime"); } // Root comptime cells of zero-length array type can materialize raw // `undefined` as the type's only-possible aggregate. static uint32_t semaMaybeMaterializeZeroLenArrayUndef( InternPool* ip, uint32_t val, uint32_t ty) { if (val != ZIR_REF_UNDEF) return val; uint32_t arr_len, arr_elem, arr_sentinel; if (!ztArrayTypeInfo(ip, ty, &arr_len, &arr_elem, &arr_sentinel) || arr_len != 0) return val; return ipGetAggregate(ip, ty, NULL, 0); } uint32_t ctPtrNoDestCastType( InternPool* ip, uint32_t ptr_ty, uint16_t cast_flags) { if (ptr_ty >= ip->items_len || ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) return IP_INDEX_NONE; uint32_t pd = ip->item_datas[ptr_ty]; uint32_t child = ip->extra[pd + IP_TYPE_POINTER_CHILD]; uint32_t sentinel = ip->extra[pd + IP_TYPE_POINTER_SENTINEL]; uint32_t ptr_flags = ip->extra[pd + IP_TYPE_POINTER_FLAGS]; uint32_t packed = ip->extra[pd + IP_TYPE_POINTER_PACKED_OFFSET]; if ((cast_flags & ZIR_PTR_CAST_FLAG_CONST) != 0) ptr_flags &= ~IP_PTR_FLAGS_IS_CONST; if ((cast_flags & ZIR_PTR_CAST_FLAG_VOLATILE) != 0) ptr_flags &= ~IP_PTR_FLAGS_IS_VOLATILE; return ipGetPtrTypeFull(ip, child, ptr_flags, sentinel, packed); } // (part of) InternPool.zig:getCoerced // InternPool.getCoerced's `.ptr` arm (src/InternPool.zig): a // comptime pointer value re-interned at `dest_ty` keeps its base address and // its byte offset unchanged -- `.{ .ty = new_ty, .base_addr = ptr.base_addr, // .byte_offset = ptr.byte_offset }`. Upstream has ONE `.ptr` key; zig1 // splits it into per-base carriers, so the retype dispatches on the carrier // tag. Callers must have already established that `ptr_val` is a live item and // that `ptr_ty != dest_ty` (upstream's `old_ty == new_ty` early return, // InternPool.zig, is the callers' identity fast path). uint32_t ctPtrValueRetype(InternPool* ip, uint32_t ptr_val, uint32_t ptr_ty, uint32_t dest_ty, const char* unsupported_msg) { if (ipTypeOf(ip, ptr_val) != ptr_ty) UNIMPLEMENTED("Value.ptrElem: pointer type mismatch"); uint32_t result = ipRetypePtrValue(ip, ptr_val, dest_ty); if (result == IP_INDEX_NONE) UNIMPLEMENTED(unsupported_msg); return result; } uint32_t ctPtrNoDestCastValue(InternPool* ip, uint32_t ptr_val, uint32_t ptr_ty, uint16_t cast_flags, const char* unsupported_msg) { if (ztIsZigTypeTag(ip, ptr_ty, ZIG_TYPE_TAG_OPTIONAL)) { // zirPtrCastNoDest: "if (operand_ty.zigTypeTag(zcu) == .optional) // break :blk try pt.optionalType(dest_ty.toIntern());", then // getCoerced, which keeps null and re-wraps a payload. uint32_t child_ty = ip->item_datas[ptr_ty]; uint32_t dest_child = ctPtrNoDestCastType(ip, child_ty, cast_flags); if (dest_child == IP_INDEX_NONE || ptr_val >= ip->items_len) return IP_INDEX_NONE; uint32_t dest_opt = ipGetOptionalType(ip, dest_child); if (ip->item_tags[ptr_val] == IP_TAG_OPT_NULL) return ipGetOptNull(ip, dest_opt); if (ip->item_tags[ptr_val] != IP_TAG_OPT_PAYLOAD) return IP_INDEX_NONE; uint32_t payload = ctPtrNoDestCastValue(ip, ipPayloadVal(ip, ptr_val), child_ty, cast_flags, unsupported_msg); return payload == IP_INDEX_NONE ? IP_INDEX_NONE : ipGetOptPayload(ip, dest_opt, payload); } uint32_t dest_ty = ctPtrNoDestCastType(ip, ptr_ty, cast_flags); if (dest_ty == IP_INDEX_NONE) return IP_INDEX_NONE; if (ptr_ty == dest_ty) return ptr_val; if (ptr_val >= ip->items_len) return IP_INDEX_NONE; return ctPtrValueRetype(ip, ptr_val, ptr_ty, dest_ty, unsupported_msg); } static uint32_t ctCoerceSentinelArrayPtrToPlain( InternPool* ip, uint32_t val, uint32_t dest_ty) { uint32_t src_ty = ipTypeOf(ip, val); if (src_ty == IP_INDEX_NONE || src_ty == dest_ty) return IP_INDEX_NONE; // The semantics live in semaCoerceSentinelArrayPtrToPlain; this wrapper // adds a caller-specific loud boundary. return semaCoerceSentinelArrayPtrToPlain(ip, val, src_ty, dest_ty, "analyzeCall: sentinel array pointer outside exact " "subset"); } static uint32_t ctCoerceArrayAggregateToSlice( InternPool* ip, uint32_t val, uint32_t dest_ty) { if (!ztIsSlice(ip, dest_ty) || val >= ip->items_len || !valIsAggregate(ip, val)) return IP_INDEX_NONE; uint32_t arr_ty = ctAggType(ip, val); uint32_t arr_len, arr_child, arr_sentinel; if (!ztArrayTypeInfo(ip, arr_ty, &arr_len, &arr_child, &arr_sentinel)) UNIMPLEMENTED("coerceExtra: non-array aggregate to slice"); uint32_t many_ty = ip->item_datas[dest_ty]; if (many_ty >= ip->items_len || ip->item_tags[many_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("coerceExtra: slice destination has no many pointer"); uint32_t md = ip->item_datas[many_ty]; uint32_t many_child = ip->extra[md + IP_TYPE_POINTER_CHILD]; uint32_t many_sentinel = ip->extra[md + IP_TYPE_POINTER_SENTINEL]; uint32_t many_flags = ip->extra[md + IP_TYPE_POINTER_FLAGS]; uint32_t many_packed = ip->extra[md + IP_TYPE_POINTER_PACKED_OFFSET]; if (IP_PTR_FLAGS_SIZE(many_flags) != IP_PTR_SIZE_MANY) UNIMPLEMENTED("coerceExtra: slice destination pointer is not many"); if (many_packed != 0) UNIMPLEMENTED("coerceExtra: aggregate to packed slice pointer"); if (many_flags != IP_PTR_FLAGS_CONST_MANY) UNIMPLEMENTED("coerceExtra: aggregate to non-default const slice"); if (arr_child != many_child) UNIMPLEMENTED("coerceExtra: aggregate/slice child mismatch"); if (many_sentinel != IP_INDEX_NONE && arr_sentinel != many_sentinel) UNIMPLEMENTED("coerceExtra: aggregate/slice sentinel mismatch"); uint32_t arr_ptr_ty = ipGetPtrType(ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t uav = ipGetPtrUav(ip, arr_ptr_ty, val); return coerceArrayPtrToSlice(ip, uav, dest_ty); } // semaCoerceArrayUavPtrToMany on an evaluator operand: an out-of-range operand // or destination declines, and a uav pointer already of the destination // pointer type is itself. static uint32_t ctCoerceArrayUavPtrToMany( Zcu* zcu, uint32_t operand, uint32_t dest_ty) { InternPool* ip = &zcu->intern_pool; if (operand >= ip->items_len || dest_ty >= ip->items_len) return IP_INDEX_NONE; uint32_t operand_ty = ipTypeOf(ip, operand); if (operand_ty >= ip->items_len) return IP_INDEX_NONE; if (operand_ty == dest_ty && (ipIsPtrUav(ip, operand)) && ip->item_tags[dest_ty] == IP_TAG_TYPE_POINTER) return operand; return semaCoerceArrayUavPtrToMany(ip, operand, operand_ty, dest_ty); } // Follow a PtrNav byte offset through a comptime aggregate initializer. A // PtrNav deliberately retains the owning Nav as its allocation identity; when // comptime evaluation needs the pointee value, stock Value.pointerDeref walks // the aggregate base representation to the projected field. The C bootstrap // stores absolute byte offsets instead, so recover the same value by matching // persisted struct field offsets and descending through nested aggregates. // Keep this bounded and reject ambiguous/unrepresented shapes loudly. bool ctProjectValueAtOffset(Zcu* zcu, uint32_t value, uint32_t value_ty, uint64_t byte_offset, uint32_t child_ty, uint32_t depth, uint32_t* out_value) { InternPool* ip = &zcu->intern_pool; if (depth >= 32u || value_ty >= ip->items_len || child_ty >= ip->items_len) return false; if (byte_offset == 0 && value_ty == child_ty) { *out_value = value; return true; } // Upstream's byte-level reinterpret tail (`castMemory`) on a // primitive, at the top level and at every field the walk below descends // to. if (value < ip->items_len && ip->item_tags[value] != IP_TAG_AGGREGATE && ip->item_tags[value] != IP_TAG_REPEATED && ip->item_tags[value] != IP_TAG_BYTES && castMemory(zcu, value, child_ty, byte_offset, out_value) == ZPT_CAST_OK) return true; if (value >= ip->items_len || !valIsAggregate(ip, value) || ctAggType(ip, value) != value_ty) return false; uint32_t array_len, array_child, array_sentinel; if (ztArrayTypeInfo( ip, value_ty, &array_len, &array_child, &array_sentinel)) { uint64_t elem_size, elem_align, elem_bits; if (!ztLayoutInfo( zcu, ip, array_child, &elem_size, &elem_align, &elem_bits) || elem_size == 0) return false; uint64_t elem_index = byte_offset / elem_size; uint64_t elem_relative = byte_offset % elem_size; uint64_t storage_len = (uint64_t)array_len + (array_sentinel != IP_INDEX_NONE); if (elem_index >= storage_len || elem_index > UINT32_MAX) return false; uint32_t elem_value = (elem_index == array_len && array_sentinel != IP_INDEX_NONE) ? array_sentinel : valElemValue(ip, value, (uint32_t)elem_index, "Value.elemValue: comptime"); if (elem_relative == 0 && array_child == child_ty) { *out_value = elem_value; return true; } return ctProjectValueAtOffset(zcu, elem_value, array_child, elem_relative, child_ty, depth + 1u, out_value); } if (!ipTagIsAnyStruct(ip->item_tags[value_ty]) || ipStructIsPacked(ip, value_ty) || !ensureLayoutResolved(zcu, value_ty)) return false; uint32_t fields_len = ipStructFieldsLen(ip, value_ty); for (uint32_t i = 0; i < fields_len; i++) { uint32_t field_ty = ipStructFieldType(ip, value_ty, i); if (field_ty == IP_INDEX_NONE || ipStructFieldIsComptime(ip, value_ty, i)) continue; ZtTypeClass field_class = ztTypeClass(ip, field_ty); if (field_class == ZT_CLASS_NPV || field_class == ZT_CLASS_FULL || field_class == ZT_CLASS_UNKNOWN) continue; uint64_t field_offset = ipStructFieldOffset(ip, value_ty, i); if (byte_offset < field_offset) continue; uint64_t relative = byte_offset - field_offset; uint64_t field_size, field_align, field_bits; if (!ztLayoutInfo( zcu, ip, field_ty, &field_size, &field_align, &field_bits)) continue; if (relative != 0 && relative >= field_size) continue; if (field_size == 0 && relative != 0) continue; uint32_t field_value = valElemValue(ip, value, i, "Value.elemValue: comptime"); if (relative == 0 && field_ty == child_ty) { *out_value = field_value; return true; } if (ctProjectValueAtOffset(zcu, field_value, field_ty, relative, child_ty, depth + 1u, out_value)) return true; } return false; } // ── Pointer children and optional payload pointers ────────────────────────── // (part of) Sema.zig:fieldCallBind // Upstream's `ty.zigTypeTag(zcu) == .pointer and ty.ptrSize(zcu) == .one` // predicate, returning `ty.childType(zcu)` on a match and IP_INDEX_NONE // otherwise. This is exactly the shape test `Sema.fieldCallBind` applies to // its `inner_ty` to decide `is_double_ptr` (src/Sema.zig). // The `size` field of PtrType.Flags (IP_PTR_FLAGS_SIZE, zig1.h, which // carries the upstream packing citation); zig1 gives slices their own // IP_TAG_TYPE_SLICE, // so a `.slice` size never reaches this tag. `.many` and `.c` sizes are // rejected, matching upstream's `== .one` (note this is stricter than the // `.one or .c` test upstream uses further down for the self parameter type; // the two tests are deliberately different upstream). static uint32_t ctSinglePointerChild(const InternPool* ip, uint32_t ty) { IpPtrInfo info; if (ipPtrInfo(ip, ty, &info) != IP_PTRI_OK || info.size != IP_PTR_SIZE_ONE) return IP_INDEX_NONE; return info.child; } // (part of) Sema.zig:analyzeOptionalPayloadPtr, Value.zig:ptrOptPayload // Comptime `opt.?` / `if (opt) |*p|` in REF position whose operand is an // interned comptime pointer rather than a comptime-cell pointer: mirrors // Sema.analyzeOptionalPayloadPtr's comptime-known-pointer branch // (src/Sema.zig, the `resolveDefinedValue` arm at with // `initializing = false`, which is the only value the ZIR dispatch // zirOptionalPayloadPtr passes). // // Upstream, in order: // opt_type = optional_ptr_ty.childType; a non-optional child is the // compile error failWithExpectedOptionalType. // pointerDeref + isNull: a comptime NULL optional is the // compile error "unable to unwrap null". // ptrOptPayload (src/Value.zig) forms the payload pointer. // // zig1 models only Value.ptrOptPayload's ptr-like fast path // (src/Value.zig): `getCoerced(parent_ptr, result_ty)`, i.e. the // same pointer reinterpreted with the payload child type, which for a // ptr_nav is exactly a re-typed ptr_nav over the same Nav and byte offset // (the ctPtrCastNoDest / ipGetPtrNavOffset retype precedent, intern_pool.c). // `result_ty` is the parent pointer type with `child` replaced and every // other ptrInfo word preserved (src/Value.zig, the same rebuild as // Sema.zig). The general path (src/Value.zig) interns a // pointer with an `.opt_payload` base address; this InternPool has no such // pointer tag, so a non-ptr-like optional aborts loudly. static uint32_t ctOptionalPayloadPtrInterned(Zcu* zcu, uint32_t ptr) { InternPool* ip = &zcu->intern_pool; IpPtrNav pn; if (ipDecodePtrNav(ip, ptr, &pn) != IP_PN_OK) UNREACHABLE("optional_payload_ptr: malformed nav pointer"); uint32_t opt_ty = pn.child_type; if (ztZigTypeTag(ip, opt_ty) != ZIG_TYPE_TAG_OPTIONAL) COMPILE_ERROR_UPSTREAM( "optional_payload_ptr: on non-optional pointee"); // pointerDeref + isNull; a mutable Nav's `.runtime_load` is // unrepresentable in a comptime body. uint32_t cur = pointerDeref(zcu, ptr, NULL); if (cur == IP_INDEX_NONE) UNIMPLEMENTED("optional_payload_ptr: runtime load"); if (ip->item_tags[cur] == IP_TAG_OPT_NULL) COMPILE_ERROR_UPSTREAM("optional_payload_ptr: unwrap null"); if (ip->item_tags[cur] != IP_TAG_OPT_PAYLOAD) UNIMPLEMENTED("optional_payload_ptr: of non-optional value"); if (!ztTypeIsPtrLikeOptional(ip, opt_ty)) UNIMPLEMENTED( "optional_payload_ptr: on a non-ptr-like optional pointee"); uint32_t res_ty = semaPayloadPtrType(ip, pn.ptr_type, ip->item_datas[opt_ty]); return ipGetPtrNavOffset(ip, res_ty, pn.nav_index, pn.byte_offset); } // ── Aggregates, indexing and concatenation ────────────────────────────────── // An all-`undef` aggregate value of a struct/tuple/array type. Result-location // aggregate init (field/element pointer + per-slot store) writes one slot at a // time into an initially-undefined cell; the first store into a none cell // materializes this undef aggregate so subsequent stores refine it (mirrors // Sema's MutableValue starting an aggregate init as `undef`). static uint32_t ctAggUndef(InternPool* ip, uint32_t agg_ty) { uint32_t len = ctAggTypeLen(ip, agg_ty); uint32_t* elems = zig1Malloc((len ? len : 1) * sizeof(uint32_t)); IpArrayInfo agg_info; for (uint32_t j = 0; j < len; j++) { uint32_t ety; if (ipTagIsAnyStruct(ip->item_tags[agg_ty])) ety = ipStructFieldType(ip, agg_ty, j); else if (ztIsTuple(ip, agg_ty)) ety = ipTupleFieldType(ip, agg_ty, j); else if (ipArrayInfoInline(ip, agg_ty, &agg_info) == IP_ARR_OK) ety = agg_info.child; else { free(elems); UNIMPLEMENTED("Type.arrayLen: unsupported aggregate type"); } elems[j] = ipGetUndef(ip, ety); } uint32_t r = ipGetAggregate(ip, agg_ty, elems, len); free(elems); return r; } static uint32_t ctAggValueLen(const InternPool* ip, uint32_t agg) { if (!ipItemTagIs(ip, agg, IP_TAG_ONLY_POSSIBLE_VALUE) && !valIsAggregate(ip, agg)) UNIMPLEMENTED("Type.arrayLen: value is not an aggregate"); return ctAggTypeLen(ip, ctAggType(ip, agg)); } static bool ctIndexCellPointerValue( Zcu* zcu, InternPool* ip, uint32_t val, uint32_t index, uint32_t* out) { if (!ctIsCellPointerClassValue(ip, val)) return false; if (!zcu) UNIMPLEMENTED("Value.ptrElem: cell pointer without zcu"); CtCellPtr d = ctCellPtrResolveDescriptorAt(zcu, ip, val, 0, true, false); uint32_t backing_ty = ctCellPtrLevelType(zcu, ip, &d, d.depth); IpArrayInfo array; if (ipArrayInfo(ip, backing_ty, &array) != IP_ARR_OK || array.is_vector) return false; uint64_t start = d.is_slice ? d.win_start : 0; uint64_t bound = d.is_slice ? ctCellPtrViewExtent(ip, &d) : (uint64_t)array.len + (array.sentinel != IP_INDEX_NONE ? 1u : 0u); if ((uint64_t)index >= bound) UNIMPLEMENTED("Value.ptrElem: index out of bounds"); if (start > UINT64_MAX - index) UNIMPLEMENTED("Value.ptrElem: index overflow"); uint64_t absolute = start + index; uint64_t physical_len = (uint64_t)array.len + (array.sentinel != IP_INDEX_NONE ? 1u : 0u); if (absolute >= physical_len || absolute > UINT32_MAX) UNIMPLEMENTED("Value.ptrElem: backing index out of bounds"); if (absolute == array.len && array.sentinel != IP_INDEX_NONE) { *out = array.sentinel; return true; } uint32_t backing = ctCellPtrLoad(zcu, ip, val); if (ipIsUndef(ip, backing)) backing = ctAggUndef(ip, backing_ty); *out = valElemValue( ip, backing, (uint32_t)absolute, "Value.elemValue: comptime"); return true; } // (part of) Sema.zig:elemVal // Sema.elemVal on a comptime indexable value: the `arr_sent` arm, a cell // pointer through its descriptor, any other pointer `semaElemValComptime`, an // aggregate directly. static uint32_t ctIndexAnyValue( Zcu* zcu, InternPool* ip, uint32_t val, uint32_t index) { if (val < ip->items_len) { uint32_t sentinel = semaElemValArrSent(ip, ipTypeOf(ip, val), index); if (sentinel != IP_INDEX_NONE) return sentinel; } uint32_t cell_elem = IP_INDEX_NONE; if (ctIndexCellPointerValue(zcu, ip, val, index, &cell_elem)) return cell_elem; IpPtrInfo info; if (ipPtrInfo(ip, ipTypeOf(ip, val), &info) == IP_PTRI_OK || ztIsSlice(ip, ipTypeOf(ip, val))) { uint32_t elem_ty; uint32_t loaded = semaElemValComptime(zcu, val, index, &elem_ty); if (loaded == IP_INDEX_NONE) UNIMPLEMENTED("elemVal: runtime load"); return loaded; } return valElemValue(ip, val, index, "Value.elemValue: comptime"); } static uint32_t ctIndexableElemType(Zcu* zcu, InternPool* ip, uint32_t val) { if (ctIsCellPointerClassValue(ip, val)) { uint32_t ty = ctCellPtrType(zcu, ip, val); if (ztIsSlice(ip, ty)) { uint32_t many_ty = ip->item_datas[ty]; if (many_ty < ip->items_len && ip->item_tags[many_ty] == IP_TAG_TYPE_POINTER) return ipPtrChild(ip, many_ty); } IpArrayInfo ai; if (ipArrayInfo(ip, ty, &ai) == IP_ARR_OK && !ai.is_vector) return ai.child; return IP_INDEX_NONE; } if (val >= ip->items_len) return IP_INDEX_NONE; uint32_t ty = ipTypeOf(ip, val); if (ty < ip->items_len && ip->item_tags[ty] == IP_TAG_TYPE_POINTER) { IpPtrInfo pi; if (ipPtrInfo(ip, ty, &pi) != IP_PTRI_OK) return IP_INDEX_NONE; if (pi.size == IP_PTR_SIZE_MANY || pi.size == IP_PTR_SIZE_C) return pi.child; if (pi.size != IP_PTR_SIZE_ONE) return IP_INDEX_NONE; if (ztIsSlice(ip, pi.child)) { uint32_t many_ty = ip->item_datas[pi.child]; if (many_ty < ip->items_len && ip->item_tags[many_ty] == IP_TAG_TYPE_POINTER) return ipPtrChild(ip, many_ty); } IpArrayInfo ai; if (ipArrayInfo(ip, pi.child, &ai) == IP_ARR_OK && !ai.is_vector) return ai.child; return IP_INDEX_NONE; } if (ztIsSlice(ip, ty)) { uint32_t many_ty = ip->item_datas[ty]; if (many_ty < ip->items_len && ip->item_tags[many_ty] == IP_TAG_TYPE_POINTER) return ipPtrChild(ip, many_ty); } IpArrayInfo ai; if (ipArrayInfo(ip, ty, &ai) == IP_ARR_OK && !ai.is_vector) return ai.child; return IP_INDEX_NONE; } typedef struct { uint32_t len; uint32_t elem_ty; uint32_t sentinel; uint32_t backing; bool is_pointer; bool is_tuple; } CtArrayCatInfo; static uint32_t ctArrayCatPeerElemType( const InternPool* ip, uint32_t peer_ty) { if (peer_ty >= ip->items_len) return IP_INDEX_NONE; if (ztIsSlice(ip, peer_ty)) { uint32_t many_ty = ip->item_datas[peer_ty]; if (many_ty >= ip->items_len || ip->item_tags[many_ty] != IP_TAG_TYPE_POINTER) return IP_INDEX_NONE; return ipPtrChild(ip, many_ty); } if (ip->item_tags[peer_ty] == IP_TAG_TYPE_POINTER) { uint32_t pd = ip->item_datas[peer_ty]; uint32_t flags = ip->extra[pd + IP_TYPE_POINTER_FLAGS]; if (IP_PTR_FLAGS_SIZE(flags) != IP_PTR_SIZE_ONE) return IP_INDEX_NONE; uint32_t child = ip->extra[pd + IP_TYPE_POINTER_CHILD]; uint32_t len, elem, sentinel; if (!ztArrayTypeInfo(ip, child, &len, &elem, &sentinel)) return IP_INDEX_NONE; return elem; } uint32_t len, elem, sentinel; if (ztArrayTypeInfo(ip, peer_ty, &len, &elem, &sentinel)) { return elem; } return IP_INDEX_NONE; } static bool ctArrayCatOperandInfo( Zcu* zcu, uint32_t val, uint32_t peer_ty, CtArrayCatInfo* out_info) { InternPool* ip = &zcu->intern_pool; if (val >= ip->items_len) UNIMPLEMENTED("array_cat: operand not interned"); uint32_t ty = ipTypeOf(ip, val); if (ty == IP_INDEX_NONE || ty >= ip->items_len) UNIMPLEMENTED("array_cat: operand type unknown"); memset(out_info, 0, sizeof(*out_info)); out_info->sentinel = IP_INDEX_NONE; out_info->backing = val; if (ztIsSlice(ip, ty)) { if (ip->item_tags[val] != IP_TAG_PTR_SLICE) UNIMPLEMENTED("array_cat: slice value shape"); uint32_t many_ty = ip->item_datas[ty]; if (many_ty >= ip->items_len || ip->item_tags[many_ty] != IP_TAG_TYPE_POINTER) UNREACHABLE("array_cat: malformed slice type"); uint32_t md = ip->item_datas[many_ty]; if (ip->extra[md + IP_TYPE_POINTER_FLAGS] != IP_PTR_FLAGS_CONST_MANY || ip->extra[md + IP_TYPE_POINTER_PACKED_OFFSET] != 0) UNIMPLEMENTED("array_cat: non-default slice pointer"); uint32_t backing = maybeDerefSliceAsArray(zcu, val); if (backing == IP_INDEX_NONE) UNIMPLEMENTED("array_cat: runtime slice"); out_info->len = ctAggTypeLen(ip, ipTypeOf(ip, backing)); out_info->elem_ty = ip->extra[md + IP_TYPE_POINTER_CHILD]; out_info->sentinel = ip->extra[md + IP_TYPE_POINTER_SENTINEL]; out_info->backing = backing; out_info->is_pointer = true; return true; } // zirArrayCat's `.one` pointer arm: the array that pointerDeref loads. bool is_pointer = false; if (ip->item_tags[ty] == IP_TAG_TYPE_POINTER) { IpPtrInfo pinfo = ipPtrInfoKnown(ip, ty); uint32_t plen, pelem, psentinel; if (pinfo.size != IP_PTR_SIZE_ONE || !ztArrayTypeInfo(ip, pinfo.child, &plen, &pelem, &psentinel)) return false; val = pointerDeref(zcu, val, NULL); if (val == IP_INDEX_NONE) UNIMPLEMENTED("array_cat: runtime array pointer"); ty = pinfo.child; is_pointer = true; } uint32_t len, elem, sentinel; if (ztArrayTypeInfo(ip, ty, &len, &elem, &sentinel)) { bool undef = ipIsUndef(ip, val); // A zero-length array is its only possible value. if (!undef && len != 0 && !valIsAggregate(ip, val)) UNIMPLEMENTED("array_cat: array value shape"); if (!undef && len > ctAggValueLen(ip, val)) UNIMPLEMENTED("array_cat: array length exceeds backing"); out_info->len = len; out_info->elem_ty = elem; out_info->sentinel = sentinel; out_info->backing = val; out_info->is_pointer = is_pointer; return true; } if (ztIsTuple(ip, ty)) { if (!valIsAggregate(ip, val)) UNIMPLEMENTED("array_cat: tuple value shape"); uint32_t peer_elem = ctArrayCatPeerElemType(ip, peer_ty); if (peer_elem == IP_INDEX_NONE) UNIMPLEMENTED("array_cat: tuple peer type"); uint32_t len2 = ipTupleFieldCount(ip, ty); if (len2 > ctAggValueLen(ip, val)) UNIMPLEMENTED("array_cat: tuple length exceeds backing"); out_info->len = len2; out_info->elem_ty = peer_elem; out_info->is_tuple = true; return true; } return false; } // ── Cell pointer views, descriptors, loads and stores ─────────────────────── // (part of) Type.zig:elemPtrType, Sema.zig:elemPtrVector // `Type.elemPtrType`'s `.one` arm is arrays-only, so an element hop over a // `*@Vector(..)` owes Sema.elemPtrVector's own derivation // (`semaElemPtrVectorType`) instead: the index lives in the pointer TYPE's // `vector_index`, over a `host_size` counting elements, and the pointer value // does not move. One test, so every cell-pointer chain below takes the same // fork and neither rule is spelled twice. static bool ctPtrChildIsVector(const InternPool* ip, uint32_t ptr_ty) { IpPtrInfo info; IpArrayInfo vec; return ipPtrInfo(ip, ptr_ty, &info) == IP_PTRI_OK && info.size == IP_PTR_SIZE_ONE && ipArrayInfo(ip, info.child, &vec) == IP_ARR_OK && vec.is_vector; } // (part of) PerThread.zig:vectorType // The inverse view, from comptime_ptr_access.zig: `loadComptimePtr` / // `storeComptimePtr` ignore a lane pointer's own child type and access // `"pt.vectorType(.{ .len = host_size, .child = elem_ty.toIntern() })"` at // the same address, then pick (`"vector_val.elemValue"`) or replace // (`"elems_buf[@intFromEnum(ptr_info.flags.vector_index)] = store_val"`) that // lane. Decoding one therefore resolves the VECTOR's location and appends the // lane as an ordinary element component, which is exactly what zig1's own // indexed load and rebuild already do. static bool ctVectorLanePtr( InternPool* ip, uint32_t v, uint32_t* out_vector_ptr, uint32_t* out_lane) { IpPtrInfo info; if (ipPtrInfo(ip, ipTypeOf(ip, v), &info) != IP_PTRI_OK) return false; uint32_t lane = IP_PTR_FLAGS_VECTOR_INDEX(info.flags); if (lane == IP_PTR_VECTOR_INDEX_NONE) return false; uint32_t host_size = IP_PTR_PACKED_HOST_SIZE(info.packed_offset); if (lane >= host_size) UNIMPLEMENTED("Value.ptrElem: lane outside the host vector"); uint32_t vector_ptr_ty = ipGetPtrTypeFull(ip, ipGetVectorType(ip, host_size, info.child), IP_PTR_FLAGS_WITH_VECTOR_INDEX(info.flags, IP_PTR_VECTOR_INDEX_NONE), IP_INDEX_NONE, 0); uint32_t vector_ptr = ipRetypePtrValue(ip, v, vector_ptr_ty); if (vector_ptr == IP_INDEX_NONE) UNIMPLEMENTED("Value.ptrElem: cannot retype a lane pointer"); *out_vector_ptr = vector_ptr; *out_lane = lane; return true; } // The pointee type reached after descending `level` (<= depth) path components // from the cell root. Factored out so ctCellPtrType, ctCellPtrStore, and the // load helper agree on the per-component type stepping (needed because a union // / opt / eu rebuild has to know the destination type, which an uninitialized // value can't supply). static uint32_t ctCellPtrLevelType( Zcu* zcu, const InternPool* ip, const CtCellPtr* d, uint32_t level) { // A bit or reinterpreted pointer's pointee is not the location its path // addresses; only the load/store/field sites below read through it, each // on a host copy, so any other consumer declines here. if (d->bit_ptr_ty != 0 && level == d->depth) UNIMPLEMENTED("Value.ptrElem: retyped pointer pointee"); uint32_t ty = ctAllocType(zcu, d->cell); for (uint32_t k = 0; k < level; k++) { const CtPathComp* c = ctCellPtrPathAt(d, k); switch (c->kind) { case CTPK_UNION: ty = ipUnionFieldType(ip, ty, c->comp); break; case CTPK_OPT: // type_optional stores the child type directly in item.data. ty = ip->item_datas[ty]; break; case CTPK_EU: ty = ipErrorUnionPayload(ip, ty); break; default: // CTPK_INDEXED if (c->is_elem) { IpArrayInfo elem_arr; if (ipArrayInfoInline(ip, ty, &elem_arr) == IP_ARR_OK) ty = elem_arr.child; else if (ztIsSlice(ip, ty)) { uint32_t many_ty = ip->item_datas[ty]; IpPtrInfo many_info; if (ipPtrInfo(ip, many_ty, &many_info) != IP_PTRI_OK || many_info.size != IP_PTR_SIZE_MANY || many_info.packed_offset != 0) UNREACHABLE( "ctCellPtrLevelType: malformed slice type"); ty = many_info.child; } else UNIMPLEMENTED("Value.ptrElem: elem of non-array"); } else if (ztIsSlice(ip, ty)) { // `slice_ptr_index` is the many pointer, `slice_len_index` // the usize. ty = c->comp == 0 ? ip->item_datas[ty] : ZIR_REF_USIZE_TYPE; } else { ty = (ztIsTuple(ip, ty)) ? ipTupleFieldType(ip, ty, c->comp) : ipStructFieldType(ip, ty, c->comp); } break; } } return ty; } // (part of) Value.zig:ptrField, PerThread.zig:getCoerced // The decode side of Value.ptrField's packed struct arm: an offset-free bit // pointer (`host_size != 0`) whose address names a packed container is // `pt.getCoerced(parent_ptr, field_ptr_ty)` over that host, and // storeComptimePtr / loadComptimePtr read and write it through the host. A // `host_size == 0` pointer takes their `.reinterpret` strategy instead. static bool ctCellPtrTryBitPtr(Zcu* zcu, const InternPool* ip, CtCellPtr* d, uint32_t ptr_ty, uint64_t byte_offset) { IpPtrInfo info; if (byte_offset != 0 || ipPtrInfo(ip, ptr_ty, &info) != IP_PTRI_OK || IP_PTR_FLAGS_VECTOR_INDEX(info.flags) != IP_PTR_VECTOR_INDEX_NONE || IP_PTR_PACKED_HOST_SIZE(info.packed_offset) == 0) return false; uint32_t host_ty = ctCellPtrLevelType(zcu, ip, d, d->depth); if (host_ty >= ip->items_len || !ipTypeIsBitpack(ip, host_ty)) return false; d->bit_ptr_ty = ptr_ty; return true; } // The `.direct` strategy's test: loadComptimePtrInner asks // `coerceInMemoryAllowed(load_ty, base_ty, false)` (comptime_ptr_access.zig), // prepareComptimePtrStore `(base_ty, store_ty, true)`. static bool ctCellPtrIsDirect(Zcu* zcu, InternPool* ip, uint32_t base_ty, uint32_t ptr_child, bool for_store) { return for_store ? coerceInMemoryAllowed(zcu, ip, base_ty, ptr_child, true) : coerceInMemoryAllowed(zcu, ip, ptr_child, base_ty, false); } // (part of) Value.zig:ptrElem, Value.zig:ptrField // Invert Value.ptrElem's runtime-representable path. Such an element access // preserves the parent's base-address carrier and changes only its pointer // type and byte offset, so decoding the carrier must recover the array hops // from the cell's declared types. Zero-size elements all alias and carry no // index in the pointer artifact; choosing index zero is semantically exact // because the child has no runtime state. The descent ends at `ptr_ty`'s // child, or at the packed host a bit pointer (`Value.ptrField`'s retyped // parent) names -- which may itself be an array element. static void ctCellPtrAppendByteOffsetPath(Zcu* zcu, InternPool* ip, CtCellPtr* d, uint32_t ptr_ty, uint64_t byte_offset, bool for_store) { uint32_t target_ty = ipPtrChild(ip, ptr_ty); uint32_t cur_ty = ctCellPtrLevelType(zcu, ip, d, d->depth); CtCellPtr base = *d; uint64_t base_offset = byte_offset; // `.direct` once the location is in-memory coercible. while (!ctCellPtrIsDirect(zcu, ip, cur_ty, target_ty, for_store)) { if (ctCellPtrTryBitPtr(zcu, ip, d, ptr_ty, byte_offset)) return; IpArrayInfo array; if (ipArrayInfo(ip, cur_ty, &array) != IP_ARR_OK || array.is_vector) break; uint64_t elem_size, elem_align, elem_bits; if (!ztLayoutInfo( zcu, ip, array.child, &elem_size, &elem_align, &elem_bits)) UNIMPLEMENTED("ptrElem: offset element layout"); uint64_t index = 0; if (elem_size == 0) { if (byte_offset != 0) UNIMPLEMENTED("ptrElem: nonzero offset for zero-size element"); } else { index = byte_offset / elem_size; byte_offset %= elem_size; } uint64_t limit = (uint64_t)array.len + (array.sentinel != IP_INDEX_NONE ? 1u : 0u); if (index >= limit || index > UINT32_MAX) UNIMPLEMENTED("ptrElem: offset element out of bounds"); ctCellPtrAppend(d, CTPK_INDEXED, true, (uint32_t)index); cur_ty = array.child; } if (byte_offset == 0 && ctCellPtrIsDirect(zcu, ip, cur_ty, target_ty, for_store)) return; // Neither `.direct` nor a restructured array: reinterpret the memory of // the pointer's base location. *d = base; d->bit_ptr_ty = ptr_ty; d->reinterpret = true; d->reint_offset = base_offset; } // A known-length slice is a one-pointer to a smaller array, but retyping its // carrier preserves that carrier's tag. Decode the view before generic // byte-offset descent when the result and some backing suffix share an // element type and the offset/extent checks establish a valid window. Do not // exclude an ordinary nested-array element merely because it has the same // structural relationship; the bounds and path checks decide the case. static bool ctCellPtrTryFixedView(Zcu* zcu, InternPool* ip, CtCellPtr* d, uint32_t result_ptr_ty, uint64_t byte_offset) { IpPtrInfo result; if (ipPtrInfo(ip, result_ptr_ty, &result) != IP_PTRI_OK || result.size != IP_PTR_SIZE_ONE) return false; IpArrayInfo fixed; if (ipArrayInfo(ip, result.child, &fixed) != IP_ARR_OK || fixed.is_vector) return false; CtCellPtr candidate = *d; uint64_t remaining = byte_offset; for (;;) { uint32_t backing_ty = ctCellPtrLevelType(zcu, ip, &candidate, candidate.depth); IpArrayInfo backing; if (ipArrayInfo(ip, backing_ty, &backing) != IP_ARR_OK || backing.is_vector) return false; if (backing.child == fixed.child) { uint64_t elem_size, elem_align, elem_bits; if (!ztLayoutInfo( zcu, ip, fixed.child, &elem_size, &elem_align, &elem_bits)) UNIMPLEMENTED("loadComptimePtr: fixed view element layout"); uint64_t start = 0; if (elem_size == 0) { if (remaining != 0) UNIMPLEMENTED( "loadComptimePtr: fixed zero-size view offset"); } else { if (remaining % elem_size != 0) UNIMPLEMENTED( "loadComptimePtr: fixed view misaligned offset"); start = remaining / elem_size; } uint64_t backing_extent = (uint64_t)backing.len + (backing.sentinel != IP_INDEX_NONE ? 1u : 0u); uint64_t fixed_extent = (uint64_t)fixed.len + (fixed.sentinel != IP_INDEX_NONE ? 1u : 0u); if (start > backing_extent || fixed_extent > backing_extent - start || start > UINT32_MAX) UNIMPLEMENTED("loadComptimePtr: fixed view bounds"); d->path = candidate.path; d->depth = candidate.depth; d->is_slice = true; d->win_start = (uint32_t)start; d->win_len = fixed.len; break; } uint64_t child_size, child_align, child_bits; if (!ztLayoutInfo(zcu, ip, backing.child, &child_size, &child_align, &child_bits)) UNIMPLEMENTED("loadComptimePtr: fixed view suffix layout"); uint64_t index = 0; if (child_size == 0) { if (remaining != 0) return false; } else { index = remaining / child_size; remaining %= child_size; } uint64_t backing_extent = (uint64_t)backing.len + (backing.sentinel != IP_INDEX_NONE ? 1u : 0u); if (index >= backing_extent || index > UINT32_MAX) return false; ctCellPtrAppend(&candidate, CTPK_INDEXED, true, (uint32_t)index); } uint32_t many_flags = (result.flags & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_MANY; uint32_t many_ty = ipGetPtrTypeFull( ip, fixed.child, many_flags, fixed.sentinel, result.packed_offset); d->slice_ty = ipGetSliceType(ip, many_ty); return true; } // The one cell-pointer decode: a comptime-var pointer value (an interned // `ptr_comptime_alloc` chain) to // its CtCellPtr descriptor. A root is a comptime allocation of the analyzing // Sema; a pointer whose pointee is not the cell's own type is an // element, field or retyped pointer, decoded by its byte offset, never as the // whole cell (an element-0 pointer has byte_offset 0 too). A `ptr_field` walks // its base, then appends indexed (struct/tuple) or union by the PARENT's // pointee type. A mismatch rejects loudly. static CtCellPtr ctCellPtrResolveDescriptorAt(Zcu* zcu, InternPool* ip, uint32_t v, uint32_t depth, bool slice_base, bool for_store) { uint32_t lane_vector_ptr, lane; if (v < ip->items_len && ctVectorLanePtr(ip, v, &lane_vector_ptr, &lane)) { CtCellPtr d = ctCellPtrResolveDescriptorAt( zcu, ip, lane_vector_ptr, depth + 1, slice_base, for_store); ctCellPtrAppend(&d, CTPK_INDEXED, true, lane); return d; } if (ipIsPtrComptimeAlloc(ip, v)) { uint32_t alloc_index = ipPtrComptimeAllocIndex(ip, v); uint32_t v_ptr_ty = ipTypeOf(ip, v); uint32_t v_child = ipPtrChild(ip, v_ptr_ty); // `slice_base` only: Also accept the canonical many // reinterpretation of a whole-cell pointer -- the win_start==0 // slice `.ptr` shape upstream mints (InternPool.zig's arr_elem doc // forbids index 0: "simply reinterpret the aggregate pointer"; // Value.ptrElem's getCoerced / analyzeSlice's coerceCompatiblePtrs // produce it). Same pointer content, retyped to a many pointer // whose child is the array cell's element type. The size==many + // array-cell + child==elem conjuncts exclude the rcorrupt2 hazard // class (element-0 pointers are ONE-pointers over the element type, // not many-pointers) -- a mis-shaped operand still aborts loudly // below. Not // widened outside slice-base resolution: a load/store through a // many-pointer is a stock compile error, not a whole-cell access, // so the plain path keeps rejecting it. bool whole_cell = v_child == ctAllocType(zcu, alloc_index); bool many_reinterpret = false; if (slice_base && !whole_cell && ipPtrSize(ip, v_ptr_ty) == IP_PTR_SIZE_MANY) { IpArrayInfo ainfo; if (ipArrayInfo(ip, ctAllocType(zcu, alloc_index), &ainfo) == IP_ARR_OK) { uint32_t array_base_ty; uint64_t array_base_len; if (ainfo.child == v_child || (ztArrayBase(ip, ctAllocType(zcu, alloc_index), &array_base_ty, &array_base_len) && array_base_ty == v_child)) many_reinterpret = true; } } if (!whole_cell && !many_reinterpret) { CtCellPtr d; memset(&d, 0, sizeof(d)); d.slice_ty = IP_INDEX_NONE; d.cell = alloc_index; d.root_ptr_flags = ipPtrTypeFlags(ip, v_ptr_ty); if (ctCellPtrTryFixedView(zcu, ip, &d, v_ptr_ty, ipPtrComptimeAllocByteOffset(ip, v))) return d; ctCellPtrAppendByteOffsetPath(zcu, ip, &d, v_ptr_ty, ipPtrComptimeAllocByteOffset(ip, v), for_store); return d; } if ((whole_cell || (many_reinterpret && !slice_base)) && ipPtrComptimeAllocByteOffset(ip, v) != 0) UNIMPLEMENTED("loadComptimePtr: offset whole base"); CtCellPtr d; memset(&d, 0, sizeof(d)); d.slice_ty = IP_INDEX_NONE; d.cell = alloc_index; d.root_ptr_flags = ipPtrTypeFlags(ip, v_ptr_ty); if (many_reinterpret) d.root_ptr_flags &= ~IP_PTR_FLAGS_SIZE_MASK; return d; } if (ipIsPtrField(ip, v)) { CtCellPtr d = ctCellPtrResolveDescriptorAt( zcu, ip, ipPtrFieldBase(ip, v), depth + 1, slice_base, for_store); uint32_t base_ty = ctCellPtrLevelType(zcu, ip, &d, d.depth); uint8_t kind; if (ipTypeIsBitpack(ip, base_ty)) UNIMPLEMENTED("ptrField: ptr_field into a packed container " "(Value.ptrField retypes)"); // A slice's `.ptr` / `.len` field (Value.ptrField's slice arm). if (ipTagIsAnyStruct(ip->item_tags[base_ty]) || ztIsTuple(ip, base_ty) || ztIsSlice(ip, base_ty)) kind = CTPK_INDEXED; else if (ip->item_tags[base_ty] == IP_TAG_TYPE_UNION) kind = CTPK_UNION; else UNIMPLEMENTED("loadComptimePtr: field pointer non-struct base"); ctCellPtrAppend(&d, kind, false, ipPtrFieldIndex(ip, v)); if (ctCellPtrTryFixedView( zcu, ip, &d, ipTypeOf(ip, v), ipPtrFieldByteOffset(ip, v))) return d; if (!slice_base) ctCellPtrAppendByteOffsetPath(zcu, ip, &d, ipTypeOf(ip, v), ipPtrFieldByteOffset(ip, v), for_store); return d; } // ptr_elem: simpler than ptr_field's decode -- no kind // ambiguity to derive (an array element hop is always // CTPK_INDEXED with is_elem=true, never CTPK_UNION), so unlike the // ptr_field branch above this one does not need to inspect the base's // own type to pick a kind. It also does not itself re-validate that // the base is actually array-shaped: that check already lives, // loudly, in ctCellPtrLevelType's own is_elem arm (`UNIMPLEMENTED( // "ctCellPtrLevelType: elem of non-array")`), which every consumer of // the returned descriptor calls before touching the element -- adding // a second copy of the same check here would duplicate, not add, // coverage (unlike ptr_field's struct-vs-union ambiguity, which has to // be resolved here because it picks which kind to store). if (ipIsPtrElem(ip, v)) { CtCellPtr d = ctCellPtrResolveDescriptorAt( zcu, ip, ipPtrElemBase(ip, v), depth + 1, true, for_store); uint64_t flat = ipPtrElemIndex(ip, v); uint32_t result_ptr_ty = ipTypeOf(ip, v); uint32_t target_ty = ipPtrChild(ip, result_ptr_ty); uint32_t cur_ty = ctCellPtrLevelType(zcu, ip, &d, d.depth); // A known-length slice reminted through arr_elem has a fixed-array // child over some backing suffix's element type. Treat that shape // structurally: suffix traversal plus the offset/extent checks below // decide whether this is a view, without an ordinary-element veto. IpArrayInfo fixed_view; if (ipArrayInfo(ip, target_ty, &fixed_view) == IP_ARR_OK && !fixed_view.is_vector) { CtCellPtr candidate = d; uint64_t candidate_flat = flat; for (;;) { uint32_t candidate_ty = ctCellPtrLevelType(zcu, ip, &candidate, candidate.depth); IpArrayInfo base_array; if (ipArrayInfo(ip, candidate_ty, &base_array) != IP_ARR_OK || base_array.is_vector) break; if (base_array.child == fixed_view.child) { uint32_t base_units_ty; uint64_t base_units; if (!ztArrayBase( ip, base_array.child, &base_units_ty, &base_units) || base_units == 0 || candidate_flat % base_units != 0) break; uint64_t start = candidate_flat / base_units; uint64_t base_extent = (uint64_t)base_array.len + (base_array.sentinel != IP_INDEX_NONE ? 1u : 0u); uint64_t fixed_extent = (uint64_t)fixed_view.len + (fixed_view.sentinel != IP_INDEX_NONE ? 1u : 0u); if (start > base_extent || fixed_extent > base_extent - start || start > UINT32_MAX || ipPtrElemByteOffset(ip, v) != 0) break; uint32_t result_flags = ipPtrTypeFlags(ip, result_ptr_ty); uint32_t many_flags = (result_flags & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_MANY; uint32_t many_ty = ipGetPtrTypeFull(ip, fixed_view.child, many_flags, fixed_view.sentinel, 0); d = candidate; d.is_slice = true; d.win_start = (uint32_t)start; d.win_len = fixed_view.len; d.slice_ty = ipGetSliceType(ip, many_ty); return d; } uint32_t child_base; uint64_t child_units; if (!ztArrayBase( ip, base_array.child, &child_base, &child_units) || child_units == 0) break; uint64_t index = candidate_flat / child_units; candidate_flat %= child_units; uint64_t limit = (uint64_t)base_array.len + (base_array.sentinel != IP_INDEX_NONE ? 1u : 0u); if (index >= limit || index > UINT32_MAX) break; ctCellPtrAppend( &candidate, CTPK_INDEXED, true, (uint32_t)index); } } while (cur_ty != target_ty) { IpArrayInfo array; if (ipArrayInfo(ip, cur_ty, &array) != IP_ARR_OK || array.is_vector) UNIMPLEMENTED( "loadComptimePtr: arr_elem base is not an array"); uint32_t child_base; uint64_t child_units; if (!ztArrayBase(ip, array.child, &child_base, &child_units)) UNIMPLEMENTED("loadComptimePtr: arr_elem units overflow"); uint64_t index = 0; if (child_units == 0) { if (flat != 0) UNIMPLEMENTED( "loadComptimePtr: nonzero zero-unit arr_elem"); } else { index = flat / child_units; flat %= child_units; } uint64_t limit = (uint64_t)array.len + (array.sentinel != IP_INDEX_NONE ? 1u : 0u); if (index >= limit || index > UINT32_MAX) UNIMPLEMENTED("loadComptimePtr: arr_elem index out of bounds"); ctCellPtrAppend(&d, CTPK_INDEXED, true, (uint32_t)index); cur_ty = array.child; } if (flat != 0) UNIMPLEMENTED("loadComptimePtr: residual arr_elem index"); ctCellPtrAppendByteOffsetPath( zcu, ip, &d, result_ptr_ty, ipPtrElemByteOffset(ip, v), for_store); return d; } // ptr_eu_payload / ptr_opt_payload: even simpler than ptr_elem's // decode -- no kind ambiguity to derive (which accessor matched // already says whether this is an error-union or optional payload // hop, unlike ptr_field's struct-vs-union derivation from the base's // own type) and no index to unwrap (`PtrBase` carries none -- a // payload hop always addresses "the one payload slot", position 0 by // construction, matching the existing sentinel sites' own // `comp[depth] = 0`). if (ipIsPtrEuPayload(ip, v)) { CtCellPtr d = ctCellPtrResolveDescriptorAt(zcu, ip, ipPtrEuPayloadBase(ip, v), depth + 1, slice_base, for_store); ctCellPtrAppend(&d, CTPK_EU, false, 0); if (ctCellPtrTryFixedView( zcu, ip, &d, ipTypeOf(ip, v), ipPtrEuPayloadByteOffset(ip, v))) return d; if (!slice_base) ctCellPtrAppendByteOffsetPath(zcu, ip, &d, ipTypeOf(ip, v), ipPtrEuPayloadByteOffset(ip, v), for_store); return d; } if (ipIsPtrOptPayload(ip, v)) { CtCellPtr d = ctCellPtrResolveDescriptorAt(zcu, ip, ipPtrOptPayloadBase(ip, v), depth + 1, slice_base, for_store); ctCellPtrAppend(&d, CTPK_OPT, false, 0); if (ctCellPtrTryFixedView(zcu, ip, &d, ipTypeOf(ip, v), ipPtrOptPayloadByteOffset(ip, v))) return d; if (!slice_base) ctCellPtrAppendByteOffsetPath(zcu, ip, &d, ipTypeOf(ip, v), ipPtrOptPayloadByteOffset(ip, v), for_store); return d; } // `ptr_slice`: Not a // pointer-chain hop like the five arms above -- a // `PtrSlice{ ty, ptr, len }` value that WINDOWS the array the path // already addresses (mirrors `ctCellSliceMake`'s own // is_slice/win_start/win_len semantics: the descriptor's `depth`/ // `comp`/`kind` path is unchanged, only the window fields are set). // `.ptr` carries one of upstream's two canonical shapes (match upstream // exactly, never mint `arr_elem{index=0}`): (a) a many-pointer `ptr_elem` // whose index is the window start (win_start > 0; upstream // `Value.ptrElem`'s arr_elem for a nonzero index) or (b) the reinterpreted // base pointer itself -- the chain pointer at the array level retyped to // the destination's many-pointer type (mutable on the derive-from-operand // path; constness carried from the coercion destination on the // override path), win_start == 0 (upstream's own // convention: InternPool.zig's arr_elem doc forbids index 0 -- "to // refer to the element at index 0, simply reinterpret the aggregate // pointer" -- and `Value.ptrElem`/`Sema.analyzeSlice` produce exactly // this shape). `ctIsCellPointerClassValue`'s own `IP_TAG_PTR_SLICE` // arm gates on `.ptr` being cell-pointer-class before any caller // reaches here, so the shape // checks below are internal-consistency re-checks (defense-in-depth, // F-4's own precedent), not new coverage. `depth` is passed through // unchanged to the base recursion (not depth+1): resolving `.ptr`'s // base consumes no new path-component budget of its own, since // `is_slice` never appends a `comp`/`kind` entry -- the existing // `depth>=2` check inside that recursive call (for a // field/elem/payload base) already bounds it correctly on its own // terms. The recursion runs with `slice_base = true` so the depth-0 // arm accepts the canonical many reinterpretation (shape (b) and // shape (a)'s base when the array sits at the cell root). if (ipIsPtrSlice(ip, v)) { uint32_t vptr = ipPtrSlicePtr(ip, v); uint32_t vlen = ipPtrSliceLen(ip, v); uint32_t slice_ty = ipTypeOf(ip, v); if (!ztIsSlice(ip, slice_ty)) UNREACHABLE("ctCellPtrResolveDescriptor: malformed slice type"); uint32_t many_ty = ip->item_datas[slice_ty]; if (many_ty >= ip->items_len || ip->item_tags[many_ty] != IP_TAG_TYPE_POINTER) UNREACHABLE( "ctCellPtrResolveDescriptor: malformed slice many pointer"); uint32_t slice_elem_ty = ipPtrChild(ip, many_ty); uint64_t byte_offset; if (!ipPtrValueByteOffset(ip, vptr, &byte_offset)) UNIMPLEMENTED("loadComptimePtr: unsupported slice pointer"); uint32_t win_start = 0; CtCellPtr d; if (ipIsPtrElem(ip, vptr)) { uint64_t flat = ipPtrElemIndex(ip, vptr); d = ctCellPtrResolveDescriptorAt( zcu, ip, ipPtrElemBase(ip, vptr), depth, true, for_store); for (;;) { uint32_t cur_ty = ctCellPtrLevelType(zcu, ip, &d, d.depth); IpArrayInfo cur_array; if (ipArrayInfo(ip, cur_ty, &cur_array) != IP_ARR_OK || cur_array.is_vector) UNIMPLEMENTED( "loadComptimePtr: folded slice base is not an array"); uint32_t child_base_ty; uint64_t child_base_len; if (!ztArrayBase( ip, cur_array.child, &child_base_ty, &child_base_len)) UNIMPLEMENTED( "loadComptimePtr: folded slice array-base overflow"); if (cur_array.child == slice_elem_ty) { if (child_base_len == 0) { if (flat != 0) UNIMPLEMENTED("loadComptimePtr: nonzero flat " "zero-size index"); win_start = 0; } else { if (flat % child_base_len != 0 || flat / child_base_len > UINT32_MAX) UNIMPLEMENTED("loadComptimePtr: misaligned folded " "slice index"); win_start = (uint32_t)(flat / child_base_len); } break; } if (child_base_len == 0) { if (flat != 0) UNIMPLEMENTED("loadComptimePtr: nonzero flat nested " "zero-size index"); ctCellPtrAppend(&d, CTPK_INDEXED, true, 0); } else { uint64_t hop = flat / child_base_len; if (hop > UINT32_MAX || hop >= cur_array.len) UNIMPLEMENTED("loadComptimePtr: folded element hop " "out of bounds"); flat %= child_base_len; ctCellPtrAppend(&d, CTPK_INDEXED, true, (uint32_t)hop); } } } else { uint32_t zero_ptr = ipRemintPtrValue(ip, vptr, ipTypeOf(ip, vptr), 0); if (zero_ptr == IP_INDEX_NONE) UNIMPLEMENTED("loadComptimePtr: cannot clear slice offset"); d = ctCellPtrResolveDescriptorAt( zcu, ip, zero_ptr, depth, true, for_store); } { uint64_t remaining = byte_offset; for (;;) { uint32_t cur_ty = ctCellPtrLevelType(zcu, ip, &d, d.depth); IpArrayInfo cur_array; if (ipArrayInfo(ip, cur_ty, &cur_array) != IP_ARR_OK || cur_array.is_vector) UNIMPLEMENTED("loadComptimePtr: byte-offset slice path is " "not an array"); uint64_t elem_size, elem_align, elem_bits; if (!ztLayoutInfo(zcu, ip, cur_array.child, &elem_size, &elem_align, &elem_bits)) UNIMPLEMENTED( "loadComptimePtr: byte-offset slice element layout"); if (cur_array.child == slice_elem_ty) { uint64_t index = 0; if (elem_size == 0) { if (remaining != 0) UNIMPLEMENTED("loadComptimePtr: nonzero zero-size " "slice offset"); } else { index = remaining / elem_size; remaining %= elem_size; } if (remaining != 0 || index > UINT32_MAX || win_start > UINT32_MAX - index) UNIMPLEMENTED("loadComptimePtr: invalid runtime-bit " "slice byte offset"); win_start += (uint32_t)index; break; } uint64_t hop = 0; if (elem_size == 0) { if (remaining != 0) UNIMPLEMENTED("loadComptimePtr: nonzero nested " "zero-size slice offset"); } else { hop = remaining / elem_size; remaining %= elem_size; } uint64_t limit = (uint64_t)cur_array.len + (cur_array.sentinel != IP_INDEX_NONE ? 1u : 0u); if (hop >= limit || hop > UINT32_MAX) UNIMPLEMENTED("loadComptimePtr: byte-offset slice hop out " "of bounds"); ctCellPtrAppend(&d, CTPK_INDEXED, true, (uint32_t)hop); } } int64_t len_v; bool len_ct; uint32_t len_b; if (!ctReadInt(ip, vlen, &len_v, &len_ct, &len_b) || len_v < 0) UNIMPLEMENTED("loadComptimePtr: unreadable cell-slice length"); if ((uint64_t)len_v > UINT32_MAX) UNIMPLEMENTED("loadComptimePtr: cell-slice length too wide"); uint32_t resolved_arr_ty = ctCellPtrLevelType(zcu, ip, &d, d.depth); IpArrayInfo resolved_array; if (ipArrayInfo(ip, resolved_arr_ty, &resolved_array) != IP_ARR_OK) UNIMPLEMENTED("loadComptimePtr: cell-slice base not array"); uint64_t resolved_limit = (uint64_t)resolved_array.len + (resolved_array.sentinel != IP_INDEX_NONE ? 1u : 0u); if (resolved_array.is_vector || resolved_array.child != slice_elem_ty || win_start > resolved_limit || (uint32_t)len_v > resolved_limit - win_start) UNIMPLEMENTED("loadComptimePtr: cell-slice window out of bounds"); d.is_slice = true; d.win_start = win_start; d.win_len = (uint32_t)len_v; d.slice_ty = slice_ty; return d; } UNIMPLEMENTED("loadComptimePtr: operand is not a cell pointer"); ZIG1_TCC_RETURN((CtCellPtr) { 0 }); } static CtCellPtr ctCellPtrResolveDescriptor( Zcu* zcu, InternPool* ip, uint32_t v) { return ctCellPtrResolveDescriptorAt(zcu, ip, v, 0, false, false); } // Mint a real upstream pointer-chain link at any input-derived path depth. // The three minters recurse through ctCellPtrMintBaseAt, so every prefix is // interned and the outer link names that exact base. static uint32_t ctCellPtrMintAfterFieldHop( Zcu* zcu, InternPool* ip, const CtCellPtr* d, uint32_t depth_before); static uint32_t ctCellPtrMintAfterElemHop(Zcu* zcu, InternPool* ip, const CtCellPtr* d, uint32_t depth_before, uint32_t source_index); static uint32_t ctCellPtrMintAfterPayloadHop(Zcu* zcu, InternPool* ip, const CtCellPtr* d, uint32_t depth_before, uint8_t kind); // BaseAddr is canonicalized independently from the type used to derive the // next link. Replay the latter from the root's preserved attributes and the // logical path; no decoded-carrier source anchor is retained. The real links // below still embed only canonical bases. static uint32_t ctCellPtrSourceTypeAt( Zcu* zcu, InternPool* ip, const CtCellPtr* d, uint32_t level) { if (level > d->depth) UNREACHABLE("ctCellPtrSourceTypeAt: level outside path"); if (d->bit_ptr_ty != 0 && level == d->depth) return d->bit_ptr_ty; uint32_t flags = d->root_ptr_flags; if (flags == 0) flags = IP_PTR_FLAGS_MUT_ONE; uint32_t ptr_ty = ipGetPtrType(ip, ctAllocType(zcu, d->cell), flags); for (uint32_t k = 0; k < level; k++) { const CtPathComp* c = ctCellPtrPathAt(d, k); if (c->kind == CTPK_OPT || c->kind == CTPK_EU) { uint32_t payload_ty = ctCellPtrLevelType(zcu, ip, d, k + 1); ptr_ty = semaPayloadPtrType(ip, ptr_ty, payload_ty); } else if (c->is_elem) { ptr_ty = ctPtrChildIsVector(ip, ptr_ty) ? semaElemPtrVectorType(zcu, ptr_ty, c->comp, NULL) : ztElemPtrType(zcu, ptr_ty, c->comp, NULL); } else { ptr_ty = ztFieldPtrType(zcu, ptr_ty, c->comp); } } return ptr_ty; } static uint32_t ctCellPtrCanonicalOneFlags(uint32_t source_flags) { uint32_t addr_mask = IP_PTR_FLAGS_ADDRSPACE_MASK << IP_PTR_FLAGS_ADDRSPACE_SHIFT; return (IP_PTR_FLAGS_MUT_ONE & ~addr_mask) | (source_flags & addr_mask); } // Return the canonical real base pointer for a hop at `depth_before`. Source // attributes are deliberately not needed while rebuilding BaseAddr prefixes: // each link below derives its visible type from ctCellPtrSourceTypeAt, then // embeds this canonical carrier. static uint32_t ctCellPtrMintBaseAt(Zcu* zcu, InternPool* ip, const CtCellPtr* d, uint32_t depth_before, uint32_t* out_base_ty) { if (d->reinterpret && depth_before == d->depth) UNIMPLEMENTED("Value.ptrElem: reinterpreted pointer"); if (depth_before == 0) { *out_base_ty = ctAllocType(zcu, d->cell); return ipGetPtrComptimeAlloc(ip, semaComptimeAllocPtrType(ip, *out_base_ty, ctCellPtrCanonicalOneFlags(d->root_ptr_flags)), d->cell); } uint32_t i = depth_before - 1; CtCellPtr prefix = *d; prefix.depth = depth_before; prefix.path = ctCellPtrPathAt(d, i); const CtPathComp* c = prefix.path; uint32_t parent_ty = IP_INDEX_NONE; uint32_t parent = ctCellPtrMintBaseAt(zcu, ip, &prefix, i, &parent_ty); uint32_t canonical_parent = valCanonicalizeBasePtr(ip, parent, IP_PTR_SIZE_ONE, parent_ty); IpPtrInfo parent_info; if (ipPtrInfo(ip, ipTypeOf(ip, canonical_parent), &parent_info) != IP_PTRI_OK) UNIMPLEMENTED("Value.ptrElem: canonical parent type"); uint32_t target_ty = ctCellPtrLevelType(zcu, ip, d, depth_before); uint32_t canonical_ty = ipGetPtrType(ip, target_ty, parent_info.flags); uint32_t link; if (c->kind == CTPK_OPT) { if (parent_ty >= ip->items_len || ztZigTypeTag(ip, parent_ty) != ZIG_TYPE_TAG_OPTIONAL) UNIMPLEMENTED("Value.ptrElem: opt base type"); link = ipGetPtrOptPayload(ip, canonical_ty, canonical_parent); } else if (c->kind == CTPK_EU) { if (parent_ty >= ip->items_len || (ztZigTypeTag(ip, parent_ty) != ZIG_TYPE_TAG_ERROR_UNION)) UNIMPLEMENTED("Value.ptrElem: error-union base type"); link = ipGetPtrEuPayload(ip, canonical_ty, canonical_parent); } else if (c->is_elem) { uint32_t canonical_parent_ty = ipTypeOf(ip, canonical_parent); if (ctPtrChildIsVector(ip, canonical_parent_ty)) { link = ipRetypePtrValue(ip, canonical_parent, semaElemPtrVectorType( zcu, canonical_parent_ty, c->comp, NULL)); } else { IpArrayInfo parent_array; if (ipArrayInfo(ip, parent_ty, &parent_array) != IP_ARR_OK) UNIMPLEMENTED("Value.ptrElem: element base type"); link = valPtrElem( zcu, ip, canonical_parent, canonical_ty, target_ty, c->comp); } } else { if (parent_ty >= ip->items_len || (!ipTagIsAnyStruct(ip->item_tags[parent_ty]) && ip->item_tags[parent_ty] != IP_TAG_TYPE_UNION)) UNIMPLEMENTED("Value.ptrElem: field base type"); link = ipGetPtrField(ip, canonical_ty, canonical_parent, c->comp); } if (!ctIsCellPointerClassValue(ip, link)) UNIMPLEMENTED("Value.ptrElem: prefix did not intern"); *out_base_ty = ctCellPtrLevelType(zcu, ip, d, depth_before); return link; } // `ipGetPtrField`, base = // `ctCellPtrMintBaseAt`'s result (the root at depth_before==0, or the // already-real depth-1 link at depth_before==1), index = the field // position, ty = the FIELD's own pointer type. Not "struct/tuple/union" // (`ipTagIsAnyStruct` excludes `IP_TAG_TYPE_TUPLE` by construction). Tuples // share the same indexed-field pointer encoding: upstream lowers // tupleElemPtr through structFieldPtrByIndex/addStructFieldPtr. Re-check it // here so a caller-invariant violation rejects loudly instead of interning a // type-confused pointer or laundering it into a non-interned word. static uint32_t ctCellPtrMintAfterFieldHop( Zcu* zcu, InternPool* ip, const CtCellPtr* d, uint32_t depth_before) { uint32_t base_ty = IP_INDEX_NONE; uint32_t base = ctCellPtrMintBaseAt(zcu, ip, d, depth_before, &base_ty); const CtPathComp* c = ctCellPtrPathAt(d, depth_before); if (base_ty < ip->items_len && ipTypeIsBitpack(ip, base_ty)) UNIMPLEMENTED("ptrField: ptr_field into a packed container " "(Value.ptrField retypes)"); // A slice's `ptr` and `len` are fields too (Value.ptrField's `.pointer` // arm, slice_ptr_index and slice_len_index). bool base_is_field_container = base_ty < ip->items_len && (ipTagIsAnyStruct(ip->item_tags[base_ty]) || ztIsTuple(ip, base_ty) || ip->item_tags[base_ty] == IP_TAG_TYPE_UNION || ztIsSlice(ip, base_ty)); if (base_is_field_container) { uint32_t source_ptr_ty = ctCellPtrSourceTypeAt(zcu, ip, d, depth_before); uint32_t field_ptr_ty = ztFieldPtrType(zcu, source_ptr_ty, c->comp); uint32_t canonical_base = valCanonicalizeBasePtr(ip, base, IP_PTR_SIZE_ONE, base_ty); return ipGetPtrField(ip, field_ptr_ty, canonical_base, c->comp); } UNREACHABLE("ctCellPtrMintAfterFieldHop: invalid pointer chain"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // The pointee type a cell pointer addresses (for method receiver dispatch). static uint32_t ctCellPtrDescType( Zcu* zcu, const InternPool* ip, const CtCellPtr* d) { if (d->bit_ptr_ty != 0) return ipPtrChild(ip, d->bit_ptr_ty); return ctCellPtrLevelType(zcu, ip, d, d->depth); } // Value.zig:ptrField, PerThread.zig:getCoerced, Type.zig:fieldPtrType // `Value.ptrField` (src/Value.zig) over a comptime cell pointer `d`. // `.@"packed"` arms, `"return pt.getCoerced(parent_ptr, field_ptr_ty)"`: a // packed container's field pointer is its host pointer retyped, // `Type.fieldPtrType` placing the field in `packed_offset` (`d` is itself a // host or a bit-pointer into one). An `.auto` struct, tuple or union: the // `.field = .{ .base = canonicalizeBasePtr(parent), .index }` pointer; the // minted link carries only the index (the decoder re-derives CTPK_UNION). // An extern union's field pointer is its parent retyped, and so is an // extern struct's off a reinterpreted parent, advanced by the field offset; // any other extern struct's is a `.field` link (CONSOLIDATION.txt 5.4.1). static uint32_t ctCellPtrFieldPtr( Zcu* zcu, InternPool* ip, CtCellPtr d, uint32_t field_idx) { uint32_t parent_ty = ctCellPtrDescType(zcu, ip, &d); bool extern_union = ipItemTagIs(ip, parent_ty, IP_TAG_TYPE_UNION) && ipUnionIsExtern(ip, parent_ty); bool extern_struct = d.reinterpret && ipTagIsAnyStruct(ip->item_tags[parent_ty]) && ipStructIsExtern(ip, parent_ty); if (ipTypeIsBitpack(ip, parent_ty) || extern_union || extern_struct) { uint32_t field_ptr_ty = ztFieldPtrType( zcu, ctCellPtrSourceTypeAt(zcu, ip, &d, d.depth), field_idx); // A reinterpreted parent sits `reint_offset` bytes into the location // its path addresses. uint64_t parent_offset = (d.reinterpret ? d.reint_offset : 0) + (extern_struct ? ipStructFieldOffset(ip, parent_ty, field_idx) : 0); d.bit_ptr_ty = 0; d.reinterpret = false; uint32_t host_ty; uint32_t host = ctCellPtrMintBaseAt(zcu, ip, &d, d.depth, &host_ty); uint64_t host_offset; uint32_t ptr = !ipPtrValueByteOffset(ip, host, &host_offset) ? IP_INDEX_NONE : ipRemintPtrValue( ip, host, field_ptr_ty, host_offset + parent_offset); if (ptr == IP_INDEX_NONE) UNIMPLEMENTED("ptrField: packed host pointer"); return ptr; } uint32_t depth_before = d.depth; ctCellPtrAppend(&d, CTPK_INDEXED, false, field_idx); return ctCellPtrMintAfterFieldHop(zcu, ip, &d, depth_before); } // Sema.zig:structFieldPtrByIndex // `Sema.structFieldPtrByIndex`'s comptime half on a cell pointer `d`: a // comptime field is its `.comptime_field` pointer, any other `ptrField`. static uint32_t ctCellStructFieldPtrByIndex( Zcu* zcu, InternPool* ip, CtCellPtr d, uint32_t struct_ty, uint32_t fidx) { uint32_t field_val = semaStructFieldComptimeValue(zcu, struct_ty, fidx); if (field_val == IP_INDEX_NONE) return ctCellPtrFieldPtr(zcu, ip, d, fidx); return ipGetPtrComptimeField(ip, ztFieldPtrType(zcu, ctCellPtrSourceTypeAt(zcu, ip, &d, d.depth), fidx), field_val); } // `ipGetPtrElem`, base = `ctCellPtrMintBaseAt`'s // result -- the same shape and index-minting convention as `ptr_field` // (upstream's own `InternPool.zig` dispatch shares one // `arr_elem, field => |base_index|` arm for both). No container-shape // guard needed here (unlike field/payload): an element hop has no // struct-vs-union ambiguity, and the array/vector shape check already // lives, loudly, in `ctCellPtrLevelType`'s own `is_elem` arm -- called // unconditionally below to derive `elem_ty`, so a bad shape still aborts // loudly from here rather than reading garbage. static uint32_t ctCellPtrMintAfterElemHop(Zcu* zcu, InternPool* ip, const CtCellPtr* d, uint32_t depth_before, uint32_t source_index) { uint32_t base_ty = IP_INDEX_NONE; uint32_t base = ctCellPtrMintBaseAt(zcu, ip, d, depth_before, &base_ty); const CtPathComp* c = ctCellPtrPathAt(d, depth_before); uint32_t elem_ty; uint32_t source_ptr_ty = ctCellPtrSourceTypeAt(zcu, ip, d, depth_before); if (ctPtrChildIsVector(ip, source_ptr_ty)) { uint32_t lane_ptr_ty = semaElemPtrVectorType( zcu, source_ptr_ty, source_index, &elem_ty); if (elem_ty != ctCellPtrLevelType(zcu, ip, d, d->depth)) UNIMPLEMENTED("Value.ptrElem: vector element type mismatch"); uint32_t lane_ptr = ipRetypePtrValue(ip, base, lane_ptr_ty); if (lane_ptr == IP_INDEX_NONE) UNIMPLEMENTED("Value.ptrElem: cannot retype a vector pointer"); return lane_ptr; } IpPtrInfo source_info; if (ipPtrInfo(ip, source_ptr_ty, &source_info) == IP_PTRI_OK && source_info.size == IP_PTR_SIZE_ONE && ztIsSlice(ip, source_info.child)) { uint32_t slice_val = ctCellPtrLoad(zcu, ip, base); if (!ipIsPtrSlice(ip, slice_val)) UNIMPLEMENTED("Value.ptrElem: slice load"); uint32_t slice_ptr = ipPtrSlicePtr(ip, slice_val); uint32_t slice_ptr_ty = ipTypeOf(ip, slice_ptr); IpPtrInfo slice_ptr_info; if (ipPtrInfo(ip, slice_ptr_ty, &slice_ptr_info) != IP_PTRI_OK || slice_ptr_info.size != IP_PTR_SIZE_MANY || slice_ptr_info.packed_offset != 0) UNREACHABLE("ctCellPtrMintAfterElemHop: malformed slice pointer"); uint64_t slice_len; if (!ctReadSliceLen(ip, slice_val, &slice_len) || (uint64_t)source_index >= slice_len + (slice_ptr_info.sentinel != IP_INDEX_NONE ? 1u : 0u)) UNIMPLEMENTED("Value.ptrElem: slice index oob"); uint32_t elem_ptr_ty = ztElemPtrType(zcu, slice_ptr_ty, source_index, &elem_ty); if (elem_ty != ctCellPtrLevelType(zcu, ip, d, d->depth)) UNIMPLEMENTED("Value.ptrElem: slice element type mismatch"); return valPtrElem( zcu, ip, slice_ptr, elem_ptr_ty, elem_ty, source_index); } uint32_t elem_ptr_ty = ztElemPtrType(zcu, source_ptr_ty, source_index, &elem_ty); if (elem_ty != ctCellPtrLevelType(zcu, ip, d, d->depth)) UNIMPLEMENTED("Value.ptrElem: element type mismatch"); return valPtrElem(zcu, ip, base, elem_ptr_ty, elem_ty, c->comp); } // `ipGetPtrEuPayload`/`ipGetPtrOptPayload`, base = // `ctCellPtrMintBaseAt`'s result. Does need its own explicit container-shape // guard (unlike elem): `ctCellPtrLevelType`'s CTPK_OPT arm and // `ipErrorUnionPayload` (CTPK_EU) do not validate their input tag before // reading -- a wrong-shaped base would silently read garbage, the same risk // class as for fields. A mismatch rejects loudly. static uint32_t ctCellPtrMintAfterPayloadHop(Zcu* zcu, InternPool* ip, const CtCellPtr* d, uint32_t depth_before, uint8_t kind) { uint32_t base_ty = IP_INDEX_NONE; uint32_t base = ctCellPtrMintBaseAt(zcu, ip, d, depth_before, &base_ty); bool base_ty_valid = base_ty < ip->items_len && ((kind == CTPK_OPT && ztZigTypeTag(ip, base_ty) == ZIG_TYPE_TAG_OPTIONAL) || (kind == CTPK_EU && (ztZigTypeTag(ip, base_ty) == ZIG_TYPE_TAG_ERROR_UNION))); if (base_ty_valid) { uint32_t payload_ty = ctCellPtrLevelType(zcu, ip, d, d->depth); uint32_t source_ptr_ty = ctCellPtrSourceTypeAt(zcu, ip, d, depth_before); uint32_t payload_ptr_ty = semaPayloadPtrType(ip, source_ptr_ty, payload_ty); uint32_t canonical_base = valCanonicalizeBasePtr(ip, base, IP_PTR_SIZE_ONE, base_ty); return kind == CTPK_EU ? ipGetPtrEuPayload(ip, payload_ptr_ty, canonical_base) : ipGetPtrOptPayload(ip, payload_ptr_ty, canonical_base); } UNREACHABLE("ctCellPtrMintAfterPayloadHop: invalid pointer chain"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // (part of) Value.zig:ptrElem // Mint the real `PtrSlice{ty, ptr, len}` for a validated cell window. Its // pointer is Value.ptrElem's exact result: index zero is a reinterpretation, // runtime elements use byte offsets, and comptime-only nested arrays use one // flattened arr_elem. Invalid child, sentinel, packed, and pointer-attribute // combinations reject loudly; legal windows never fall back to a sentinel. static uint32_t ctCellSliceTryRealMint(Zcu* zcu, InternPool* ip, const CtCellPtr* d, uint32_t override_slice_ty, uint32_t source_sentinel, bool end_is_len, uint32_t explicit_sentinel, uint32_t source_start) { uint32_t arr_ty = ctCellPtrLevelType(zcu, ip, d, d->depth); IpArrayInfo ainfo; if (ipArrayInfo(ip, arr_ty, &ainfo) != IP_ARR_OK) UNIMPLEMENTED("Value.ptrElem: non-array cell location"); if (ainfo.is_vector) COMPILE_ERROR_UPSTREAM("ctCellSliceTryRealMint: vector slice"); uint32_t elem_ty = ainfo.child; uint32_t slice_ty; uint32_t many_ptr_ty; uint32_t desired_sentinel = explicit_sentinel != IP_INDEX_NONE ? explicit_sentinel : (end_is_len ? source_sentinel : IP_INDEX_NONE); uint32_t source_many_ty = IP_INDEX_NONE; if (ztIsSlice(ip, d->slice_ty)) { source_many_ty = ip->item_datas[d->slice_ty]; if (source_many_ty >= ip->items_len || ip->item_tags[source_many_ty] != IP_TAG_TYPE_POINTER) UNREACHABLE( "ctCellSliceTryRealMint: malformed source slice pointer"); uint32_t source_many_d = ip->item_datas[source_many_ty]; if (ip->extra[source_many_d + IP_TYPE_POINTER_CHILD] != elem_ty || IP_PTR_FLAGS_SIZE( ip->extra[source_many_d + IP_TYPE_POINTER_FLAGS]) != IP_PTR_SIZE_MANY) COMPILE_ERROR_UPSTREAM( "ctCellSliceTryRealMint: incompatible source slice child"); if (ip->extra[source_many_d + IP_TYPE_POINTER_SENTINEL] != source_sentinel) UNIMPLEMENTED("Value.ptrElem: source sentinel mismatch"); if (ip->extra[source_many_d + IP_TYPE_POINTER_PACKED_OFFSET] != 0) COMPILE_ERROR_UPSTREAM( "ctCellSliceTryRealMint: packed source slice pointer"); } if (override_slice_ty != IP_INDEX_NONE) { if (!ztIsSlice(ip, override_slice_ty)) COMPILE_ERROR_UPSTREAM( "ctCellSliceTryRealMint: destination is not a slice"); many_ptr_ty = ip->item_datas[override_slice_ty]; if (many_ptr_ty >= ip->items_len || ip->item_tags[many_ptr_ty] != IP_TAG_TYPE_POINTER) COMPILE_ERROR_UPSTREAM( "ctCellSliceTryRealMint: malformed slice pointer type"); uint32_t mp_d = ip->item_datas[many_ptr_ty]; if (ip->extra[mp_d + IP_TYPE_POINTER_CHILD] != elem_ty || IP_PTR_FLAGS_SIZE(ip->extra[mp_d + IP_TYPE_POINTER_FLAGS]) != IP_PTR_SIZE_MANY) COMPILE_ERROR_UPSTREAM( "ctCellSliceTryRealMint: incompatible slice child"); uint32_t dest_sentinel = ip->extra[mp_d + IP_TYPE_POINTER_SENTINEL]; if (dest_sentinel != IP_INDEX_NONE && dest_sentinel != desired_sentinel) COMPILE_ERROR_UPSTREAM( "ctCellSliceTryRealMint: incompatible slice sentinel"); if (ip->extra[mp_d + IP_TYPE_POINTER_PACKED_OFFSET] != 0) COMPILE_ERROR_UPSTREAM( "ctCellSliceTryRealMint: packed slice pointer"); slice_ty = override_slice_ty; } else { many_ptr_ty = IP_INDEX_NONE; slice_ty = IP_INDEX_NONE; } uint32_t base_ty_unused; uint32_t link = ctCellPtrMintBaseAt(zcu, ip, d, d->depth, &base_ty_unused); uint32_t link_ty = ipTypeOf(ip, link); if (link_ty >= ip->items_len || ip->item_tags[link_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("Value.ptrElem: base has no pointer type"); uint32_t link_d = ip->item_datas[link_ty]; if (ip->extra[link_d + IP_TYPE_POINTER_PACKED_OFFSET] != 0) COMPILE_ERROR_UPSTREAM( "ctCellSliceTryRealMint: packed source pointer"); if (many_ptr_ty == IP_INDEX_NONE) { uint32_t source_ptr_ty = source_many_ty != IP_INDEX_NONE ? source_many_ty : ctCellPtrSourceTypeAt(zcu, ip, d, d->depth); IpPtrInfo source_info; if (ipPtrInfo(ip, source_ptr_ty, &source_info) != IP_PTRI_OK || source_info.packed_offset != 0) UNREACHABLE("ctCellSliceTryRealMint: malformed source pointer"); uint32_t many_flags; uint32_t packed_offset; if (source_start == 0) { // Sema.analyzePtrArithmetic returns its input pointer unchanged // for a known zero index. Type.elemPtrType is therefore not // consulted: retain the source alignment and every other pointer // attribute, changing only size for the slice's many carrier. many_flags = source_info.flags; packed_offset = source_info.packed_offset; } else { uint32_t elem_ptr_ty = semaSliceElemPtrType( zcu, source_ptr_ty, source_start, NULL, d->win_len == 0); IpPtrInfo elem_ptr_info; if (ipPtrInfoInline(ip, elem_ptr_ty, &elem_ptr_info) != IP_PTRI_OK) UNREACHABLE("cell slice: element pointer type"); many_flags = elem_ptr_info.flags; packed_offset = elem_ptr_info.packed_offset; } many_flags = (many_flags & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_MANY; many_ptr_ty = ipGetPtrTypeFull( ip, elem_ty, many_flags, desired_sentinel, packed_offset); slice_ty = ipGetSliceType(ip, many_ptr_ty); } else { uint32_t attrs_source_ty = source_many_ty != IP_INDEX_NONE ? source_many_ty : link_ty; if (!semaCheckPtrAttributesTy(NULL, ip, many_ptr_ty, attrs_source_ty)) COMPILE_ERROR_UPSTREAM( "ctCellSliceTryRealMint: incompatible pointer attributes"); } uint32_t slice_ptr = valPtrElem(zcu, ip, link, many_ptr_ty, elem_ty, d->win_start); if (slice_ptr == IP_INDEX_NONE) UNIMPLEMENTED("Value.ptrElem: pointer remint failed"); uint32_t len_val = ipGetIntUsize(ip, d->win_len); return ipGetPtrSlice(ip, slice_ty, slice_ptr, len_val); } // The value a cell pointer addresses, interned: Sema.pointerDeref, except // that a slice view's is the array its window is over, read down the path // of the comptime allocation's MutableValue. static uint32_t ctCellPtrLoad(Zcu* zcu, InternPool* ip, uint32_t ptr) { CtCellPtr d = ctCellPtrResolveDescriptor(zcu, ip, ptr); if (!d.is_slice) { uint32_t val = pointerDeref(zcu, ptr, NULL); if (val == IP_INDEX_NONE) UNREACHABLE("ctCellPtrLoad: runtime load of a comptime alloc"); return val; } if (d.bit_ptr_ty != 0 || d.reinterpret) UNIMPLEMENTED("loadComptimePtr: retyped slice view"); MutableValue mv = getComptimeAlloc(zcu, d.cell)->val; for (uint32_t k = 0; k < d.depth; k++) { const CtPathComp* c = ctCellPtrPathAt(&d, k); MutableValueUnpack kind = MV_UNPACK_PAYLOAD; if (c->kind == CTPK_OPT || c->kind == CTPK_EU) mv = mutableValueUnpack(ip, &mv, &kind, NULL); else mv = mutableValueGetElem(zcu, &mv, c->comp); if (kind != MV_UNPACK_PAYLOAD) UNIMPLEMENTED("loadComptimePtr: payload of null, error or undef"); } return mutableValueIntern(zcu, &mv); } static uint32_t ctCellPtrLoadForValue(Zcu* zcu, InternPool* ip, uint32_t ptr) { CtCellPtr d_val = ctCellPtrResolveDescriptor(zcu, ip, ptr); const CtCellPtr* d = &d_val; uint32_t val = ctCellPtrLoad(zcu, ip, ptr); if (d->depth == 0 && d->bit_ptr_ty == 0) val = semaMaybeMaterializeZeroLenArrayUndef( ip, val, ctAllocType(zcu, d->cell)); return val; } // Sema.storePtrVal through a cell pointer, at its pointee type. static void ctCellPtrStore( Zcu* zcu, const InternPool* ip, uint32_t ptr, uint32_t new_val) { IpPtrInfo info; if (ipPtrInfo(ip, ipTypeOf(ip, ptr), &info) != IP_PTRI_OK) UNREACHABLE("ctCellPtrStore: not a pointer"); storePtrVal(zcu, ptr, new_val, info.child); } static uint32_t ctCellPtrType(Zcu* zcu, InternPool* ip, uint32_t ptr) { CtCellPtr d_val = ctCellPtrResolveDescriptor(zcu, ip, ptr); return ctCellPtrDescType(zcu, ip, &d_val); } // The runtime frame's comptime allocations (`comptime { var x: S = ...; x.f // = 1; }`) are the same Sema's this evaluator addresses, so its // `Sema.structFieldPtr` / `pointerDeref` / `storePtrVal` halves on a // `ptr_comptime_alloc` or `.field` pointer are these three. A field pointer // of a non-struct or packed pointee answers IP_INDEX_NONE. static uint32_t ctCellStructInitFieldPtr( Zcu* zcu, InternPool* ip, uint32_t sf_obj, uint32_t sf_name_ip); uint32_t semaComptimeCellFieldPtr(Zcu* zcu, uint32_t ptr, uint32_t name) { InternPool* ip = &zcu->intern_pool; uint32_t base_ty = ctCellPtrType(zcu, ip, ptr); if (!ipTagIsAnyStruct(ip->item_tags[base_ty]) || ipStructIsPacked(ip, base_ty)) return IP_INDEX_NONE; uint32_t fidx = ipStructFieldIndex(ip, base_ty, name); if (fidx == UINT32_MAX) COMPILE_ERROR_UPSTREAM("field_ptr: no field with this name"); return ctCellStructFieldPtrByIndex( zcu, ip, ctCellPtrResolveDescriptor(zcu, ip, ptr), base_ty, fidx); } // Sema.zig:elemPtr // Sema.elemPtr's tuple arm (tupleFieldPtr -> structFieldPtrByIndex) on a // comptime-cell pointer; IP_INDEX_NONE when the pointee is not a tuple. uint32_t semaComptimeCellTupleElemPtr(Zcu* zcu, uint32_t ptr, uint64_t index) { InternPool* ip = &zcu->intern_pool; CtCellPtr d = ctCellPtrResolveDescriptor(zcu, ip, ptr); uint32_t ty = ctCellPtrDescType(zcu, ip, &d); if (!ztIsTuple(ip, ty)) return IP_INDEX_NONE; if (index >= ipTupleFieldCount(ip, ty)) COMPILE_ERROR_UPSTREAM("index out of bounds of tuple"); return ctCellStructFieldPtrByIndex(zcu, ip, d, ty, (uint32_t)index); } uint32_t semaComptimeCellStructInitFieldPtr( Zcu* zcu, uint32_t ptr, uint32_t name) { InternPool* ip = &zcu->intern_pool; return ctCellStructInitFieldPtr(zcu, ip, ptr, name); } static uint32_t ctOptionalPayloadPtr(Zcu* zcu, InternPool* ip, uint32_t opp); uint32_t semaComptimeOptionalPayloadPtr(Zcu* zcu, uint32_t ptr) { return ctOptionalPayloadPtr(zcu, &zcu->intern_pool, ptr); } // The array type a (non-slice) cell pointer's path addresses, or IP_INDEX_NONE // if that location does not hold an array (so callers can decide whether a // slice/pointer-to-array view is possible). static uint32_t ctCellPtrArrayType(Zcu* zcu, InternPool* ip, uint32_t ptr) { uint32_t ty = ctCellPtrType(zcu, ip, ptr); if (ztZigTypeTag(ip, ty) == ZIG_TYPE_TAG_ARRAY) return ty; return IP_INDEX_NONE; } static bool ctCellPtrIsSliceableView(Zcu* zcu, InternPool* ip, uint32_t ptr) { CtCellPtr d_val = ctCellPtrResolveDescriptor(zcu, ip, ptr); return d_val.is_slice || ctCellPtrArrayType(zcu, ip, ptr) != IP_INDEX_NONE; } // ── Cell memory: memset, memcpy and slices ────────────────────────────────── static uint32_t ctMemArrayPtr( InternPool* ip, uint32_t ptr, uint32_t array_ty, uint32_t sentinel); // (part of) Sema.zig:zirMemset // Sema.zirMemset on a comptime-mutable destination: the element coerced, // `fieldVal(dest, "len")`, then storePtrVal of the splatted array through // the raw pointer `getCoerced` to `*[len]T`. void semaMemsetComptime(Zcu* zcu, uint32_t dest, uint32_t fill_val) { InternPool* ip = &zcu->intern_pool; uint32_t dest_ty = ipTypeOf(ip, dest); bool is_slice = ztIsSlice(ip, dest_ty); IpPtrInfo info; IpArrayInfo pointee; if (ipPtrInfo(ip, is_slice ? ip->item_datas[dest_ty] : dest_ty, &info) != IP_PTRI_OK) UNREACHABLE("memset: destination is not a pointer"); uint32_t array_len = 0; if (!is_slice) { if (info.size != IP_PTR_SIZE_ONE || ipArrayInfo(ip, info.child, &pointee) != IP_ARR_OK || pointee.is_vector) COMPILE_ERROR_UPSTREAM("unknown @memset length"); array_len = pointee.len; } uint32_t elem_ty = ztIndexableElem(ip, dest_ty); uint32_t elem; if (fill_val == ZIR_REF_UNDEF || (ipIsUndef(ip, fill_val))) { elem = ipGetUndef(ip, elem_ty); } else { elem = ctCoerceToType(zcu, fill_val, elem_ty); if (!ctValueConformsToType(ip, elem, elem_ty)) UNIMPLEMENTED("memset: fill value type mismatch"); } if (ztOnePossibleValue(ip, elem_ty) != IP_INDEX_NONE) return; // "no work to do" ipGetOrPutString(ip, "len", 3); uint64_t len; if (is_slice) { if (!ctReadSliceLen(ip, dest, &len)) UNIMPLEMENTED("memset: slice length unreadable"); } else { len = array_len; ipGetIntUsize(ip, array_len); } if (len == 0) return; // `"if (!sema.isComptimeMutablePtr(ptr_val)) break :rs dest_src;"`. if (!ctIsCellPointerClassValue(ip, dest)) UNIMPLEMENTED("memset: comptime destination is not comptime-mutable"); if (len > UINT32_MAX) UNIMPLEMENTED("memset: wide comptime length"); uint32_t arr_ty = ipGetArrayType(ip, (uint32_t)len, elem_ty, IP_INDEX_NONE); // Zcu.PerThread.aggregateSplatValue: an undefined element splats to an // undefined array. uint32_t array_val = ipIsUndef(ip, elem) ? ipGetUndef(ip, arr_ty) : ipGetRepeated(ip, arr_ty, elem); storePtrVal(zcu, ctMemArrayPtr(ip, dest, arr_ty, info.sentinel), array_val, arr_ty); } // `indexablePtrLenOrNone` of a comptime pointer: a slice's length or a // pointer-to-array's; false for a many-pointer. static bool ctComptimeIndexableLen( const InternPool* ip, uint32_t ptr, uint64_t* out) { uint32_t ty = ipTypeOf(ip, ptr); IpPtrInfo info; IpArrayInfo arr; if (ztIsSlice(ip, ty)) return ctReadSliceLen(ip, ptr, out); if (ipPtrInfo(ip, ty, &info) == IP_PTRI_OK && info.size == IP_PTR_SIZE_ONE && ipArrayInfo(ip, info.child, &arr) == IP_ARR_OK) { *out = arr.len; return true; } return false; } // zirMemset's and zirMemcpy's coercion of a raw comptime pointer to // `*[len]T` with `sentinel`, keeping its other pointer attributes. static uint32_t ctMemArrayPtr( InternPool* ip, uint32_t ptr, uint32_t array_ty, uint32_t sentinel) { uint32_t ty = ipTypeOf(ip, ptr); if (ztIsSlice(ip, ty)) { ptr = ipPtrSlicePtr(ip, ptr); ty = ip->item_datas[ty]; } IpPtrInfo info; if (ipPtrInfo(ip, ty, &info) != IP_PTRI_OK) UNIMPLEMENTED("memcpy: comptime operand is not a pointer"); uint32_t array_ptr_ty = ipGetPtrTypeFull(ip, array_ty, (info.flags & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_ONE, sentinel, info.packed_offset); uint32_t coerced = ipRetypePtrValue(ip, ptr, array_ptr_ty); if (coerced == IP_INDEX_NONE) UNIMPLEMENTED( "@memset: comptime @memset/@memcpy: cannot retype the pointer"); return coerced; } // (part of) Sema.zig:zirAlloc // semaAlloc's `"if (block.isComptime())"` arm without its narrowing to the // element types a function body's frame models as comptime cells. static void ctEvalAlloc( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; uint32_t a_ty = semaResolveRef(zir->inst_datas[inst].un_node.operand, values); if (!semaRefIsInterned(a_ty)) UNIMPLEMENTED("zirAlloc: alloc element type not interned"); // `"try sema.ensureLayoutResolved(var_ty, ...)"`; a failed // resolution stays loud at its use. ensureLayoutResolved(zcu, a_ty); values[inst] = analyzeComptimeAlloc(zcu, ip, a_ty, IP_PTR_FLAGS_MUT_ONE, NULL); } // (part of) Sema.zig:zirMemcpy // zirMemcpy's comptime arm: `"we implement @memcpy as one load and store of // an array"` -- both raw pointers coerced to `*[len]T`, the source // dereferenced and stored through the destination. void semaMemcpyComptime( Zcu* zcu, uint32_t dest, uint32_t src, bool check_aliasing) { InternPool* ip = &zcu->intern_pool; if (!ctIsCellPointerClassValue(ip, dest)) UNIMPLEMENTED("memcpy: comptime destination is not comptime-mutable"); uint64_t len; if (!ctComptimeIndexableLen(ip, dest, &len) && !ctComptimeIndexableLen(ip, src, &len)) COMPILE_ERROR_UPSTREAM("memcpy: unknown copy length"); if (len > UINT32_MAX) UNIMPLEMENTED("memcpy: wide comptime length"); uint32_t array_ty = ipGetArrayType(ip, (uint32_t)len, ztIndexableElem(ip, ipTypeOf(ip, dest)), IP_INDEX_NONE); uint32_t dest_ptr = ctMemArrayPtr(ip, dest, array_ty, IP_INDEX_NONE); uint32_t src_ptr = ctMemArrayPtr(ip, src, array_ty, IP_INDEX_NONE); if (check_aliasing && valDoPointersOverlap(zcu, src_ptr, dest_ptr, len)) COMPILE_ERROR_UPSTREAM("'@memcpy' arguments alias"); uint32_t array_val = pointerDeref(zcu, src_ptr, NULL); if (array_val == IP_INDEX_NONE) UNIMPLEMENTED("memcpy: runtime source"); storePtrVal(zcu, dest_ptr, array_val, array_ty); } // (part of) Sema.zig:analyzeSlice // Sema.analyzeSlice's "const casted = try sema.coerce" happens before // "try checkSentinelType". Keep that common ordering for every explicit-slice // carrier, including a ptr_int whose backing cannot be inspected. static uint32_t ctSliceCoerceExplicitSentinel(Zcu* zcu, const InternPool* ip, uint32_t explicit_sentinel, uint32_t elem_ty) { uint32_t coerced = ctCoerceToType(zcu, explicit_sentinel, elem_ty); if (!ctValueConformsToType(ip, coerced, elem_ty)) COMPILE_ERROR_UPSTREAM( "ctSliceLength: explicit sentinel type mismatch"); checkSentinelType(ip, elem_ty); return coerced; } // Validate the memory element selected as an explicit slice sentinel. Both // cell-backed and directly-backed slice views use this same final comparison; // only their preceding backing/offset discovery differs. static uint32_t ctSliceExplicitSentinelValue(Zcu* zcu, InternPool* ip, uint32_t backing, uint32_t backing_len, uint32_t backing_child, uint32_t backing_sentinel, uint64_t sentinel_index, uint32_t explicit_sentinel) { uint32_t coerced = ctSliceCoerceExplicitSentinel( zcu, ip, explicit_sentinel, backing_child); uint32_t actual; if (sentinel_index < backing_len) { if (backing == IP_INDEX_NONE || backing >= ip->items_len || ipIsUndef(ip, backing)) COMPILE_ERROR_UPSTREAM( "ctSliceExplicitSentinel: sentinel is undefined"); actual = valElemValue(ip, backing, (uint32_t)sentinel_index, "Value.elemValue: comptime"); } else if (sentinel_index == backing_len && backing_sentinel != IP_INDEX_NONE) { actual = backing_sentinel; } else { COMPILE_ERROR_UPSTREAM( "ctSliceExplicitSentinel: explicit sentinel out of bounds"); } if (actual != coerced) COMPILE_ERROR_UPSTREAM( "ctSliceExplicitSentinel: explicit sentinel mismatch"); return coerced; } // Build a real interned `PtrSlice{ty, ptr, len}` view over the array reached // by `base`. A slice base makes `start` relative to its current window; an // array base makes it relative to the full array. Bounds, sentinel // inheritance, and pointer-attribute compatibility are checked here. Every // legal view mints its canonical real pointer carrier; malformed or // incompatible inputs reject loudly. // `override_slice_ty` is IP_INDEX_NONE for SLICE_* and the destination type // for the coercion arm. static uint32_t ctCellSliceMakeExplicit(Zcu* zcu, InternPool* ip, uint32_t base, uint32_t start, uint32_t len, uint32_t override_slice_ty, uint32_t explicit_sentinel) { CtCellPtr d = ctCellPtrResolveDescriptor(zcu, ip, base); uint32_t bound; uint32_t sentinel_end; uint32_t abs_start; uint32_t source_sentinel = IP_INDEX_NONE; if (d.is_slice) { source_sentinel = ctCellPtrSliceManyInfo(ip, &d).sentinel; // A sentineled view has one physical element past `.len`. Its // operation bounds follow analyzeSlice's pointer extent, while the // logical end remains `.len` for inherited-sentinel selection. if (d.win_len == UINT32_MAX && source_sentinel != IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM( "ctCellSliceMake: sentineled slice exceeds address space"); bound = (uint32_t)ctCellPtrViewExtent(ip, &d); sentinel_end = d.win_len; if (start > UINT32_MAX - d.win_start) COMPILE_ERROR_UPSTREAM("ctCellSliceMake: slice start overflow"); abs_start = d.win_start + start; } else { uint32_t arr_ty = ctCellPtrLevelType(zcu, ip, &d, d.depth); if (arr_ty >= ip->items_len || (ztZigTypeTag(ip, arr_ty) != ZIG_TYPE_TAG_ARRAY)) UNIMPLEMENTED("Value.ptrElem: cell location is not an array"); IpArrayInfo source_array; if (ipArrayInfo(ip, arr_ty, &source_array) != IP_ARR_OK || source_array.is_vector) UNREACHABLE("ctCellSliceMake: malformed array type"); if (source_array.len == UINT32_MAX && source_array.sentinel != IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM( "ctCellSliceMake: sentineled array exceeds address space"); bound = source_array.len + (source_array.sentinel != IP_INDEX_NONE ? 1u : 0u); sentinel_end = source_array.len; source_sentinel = source_array.sentinel; abs_start = start; } if (start > bound || len > bound - start) UNIMPLEMENTED("Value.ptrElem: slice bounds out of range"); bool end_is_len = start + len == sentinel_end; d.is_slice = true; d.win_start = abs_start; d.win_len = len; if (explicit_sentinel != IP_INDEX_NONE) { uint32_t arr_ty = ctCellPtrLevelType(zcu, ip, &d, d.depth); IpArrayInfo backing_array; if (ipArrayInfo(ip, arr_ty, &backing_array) != IP_ARR_OK || backing_array.is_vector) UNIMPLEMENTED("Value.ptrElem: explicit sentinel backing array"); uint64_t sentinel_index = (uint64_t)abs_start + len; uint32_t aggregate = sentinel_index < backing_array.len ? ctCellPtrLoad(zcu, ip, base) : IP_INDEX_NONE; explicit_sentinel = ctSliceExplicitSentinelValue(zcu, ip, aggregate, backing_array.len, backing_array.child, backing_array.sentinel, sentinel_index, explicit_sentinel); } uint32_t real = ctCellSliceTryRealMint(zcu, ip, &d, override_slice_ty, source_sentinel, end_is_len, explicit_sentinel, start); if (real == IP_INDEX_NONE) UNIMPLEMENTED("Value.ptrElem: legal slice failed to mint"); return real; } static uint32_t ctCellSliceMake(Zcu* zcu, InternPool* ip, uint32_t base, uint32_t start, uint32_t len, uint32_t override_slice_ty) { return ctCellSliceMakeExplicit( zcu, ip, base, start, len, override_slice_ty, IP_INDEX_NONE); } // `analyzeSlice` turns a comptime-known slice length into a one-pointer to an // array. The validated slice view already owns the exact many-pointer carrier, // attributes, sentinel, and byte offset; only its type changes here. static uint32_t ctSliceLengthRemintView( InternPool* ip, uint32_t view, uint32_t len) { if (!ipIsPtrSlice(ip, view)) UNIMPLEMENTED("analyzeSlice: not a slice view"); uint32_t many_ptr = ipPtrSlicePtr(ip, view); uint32_t many_ty = ipTypeOf(ip, many_ptr); IpPtrInfo many_info; if (ipPtrInfo(ip, many_ty, &many_info) != IP_PTRI_OK || many_info.size != IP_PTR_SIZE_MANY) UNREACHABLE("ctSliceLengthRemintView: malformed many pointer"); if (many_info.packed_offset != 0) UNIMPLEMENTED("analyzeSlice: packed slice pointer"); uint64_t view_len; if (!ctReadSliceLen(ip, view, &view_len) || view_len != len) UNIMPLEMENTED("analyzeSlice: view length mismatch"); uint32_t arr_ty = ipGetArrayType(ip, len, many_info.child, many_info.sentinel); uint32_t ret_ty = ipGetPtrType(ip, arr_ty, many_info.flags & ~IP_PTR_FLAGS_SIZE_MASK); uint32_t result = ipRetypePtrValue(ip, many_ptr, ret_ty); if (result == IP_INDEX_NONE) UNIMPLEMENTED("analyzeSlice: pointer carrier"); IpPtrInfo result_info; if (ipTypeOf(ip, result) != ret_ty || ipPtrInfo(ip, ret_ty, &result_info) != IP_PTRI_OK || result_info.size != IP_PTR_SIZE_ONE || result_info.child != arr_ty || result_info.flags != (many_info.flags & ~IP_PTR_FLAGS_SIZE_MASK) || result_info.packed_offset != many_info.packed_offset) UNIMPLEMENTED("analyzeSlice: result type mismatch"); uint64_t source_offset, result_offset; if (!ipPtrValueByteOffset(ip, many_ptr, &source_offset) || !ipPtrValueByteOffset(ip, result, &result_offset) || source_offset != result_offset) UNIMPLEMENTED("analyzeSlice: result offset mismatch"); return result; } // Reuse coerceArrayPtrToSlice's established one-pointer-to-array lowering // before applying the direct fixed-length view. It carries the backing // identity, byte offset, and compatible pointer attributes unchanged. static uint32_t ctSliceLengthArrayPtrToSlice(InternPool* ip, uint32_t ptr) { if (ptr >= ip->items_len || (!ipIsPtrUav(ip, ptr))) UNIMPLEMENTED("analyzeSlice: non-uav pointer"); IpPtrInfo info; if (ipPtrInfo(ip, ipTypeOf(ip, ptr), &info) != IP_PTRI_OK || info.size != IP_PTR_SIZE_ONE) UNIMPLEMENTED("analyzeSlice: non-array pointer"); if (info.packed_offset != 0) UNIMPLEMENTED("analyzeSlice: packed pointer"); uint32_t arr_len, elem_ty, sentinel; if (!ztArrayTypeInfo(ip, info.child, &arr_len, &elem_ty, &sentinel)) UNIMPLEMENTED("analyzeSlice: child not array"); uint32_t many_flags = (info.flags & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_MANY; uint32_t many_ty = ipGetPtrTypeFull(ip, elem_ty, many_flags, sentinel, 0); return coerceArrayPtrToSlice(ip, ptr, ipGetSliceType(ip, many_ty)); } // analyzeSlice's `sentinel_check` for a comptime pointer: the coerced // sentinel, and the element `start + len` past `source_ptr` read through a // `[*]const T` (`getCoerced` + `ptrElem` + `pointerDerefExtra`) must equal // it; a runtime load skips the comparison. static uint32_t ctSliceLengthPointerExplicitSentinel(Zcu* zcu, InternPool* ip, uint32_t source_ptr, uint32_t elem_ty, uint32_t start, uint32_t len, uint32_t explicit_sentinel) { uint32_t expected = ctSliceCoerceExplicitSentinel(zcu, ip, explicit_sentinel, elem_ty); if (len > UINT32_MAX - start) COMPILE_ERROR_UPSTREAM("ctSliceLength: explicit sentinel overflow"); uint32_t many_ty = ipGetPtrType(ip, elem_ty, (IP_PTR_FLAGS_CONST_ONE & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_MANY); uint32_t many = ipRetypePtrValue(ip, source_ptr, many_ty); if (many == IP_INDEX_NONE) UNIMPLEMENTED("analyzeSlice: explicit sentinel carrier"); uint32_t elem_ptr_ty; elem_ptr_ty = ztElemPtrType(zcu, many_ty, start + len, &elem_ty); ComptimeLoadResult r = loadComptimePtr( zcu, valPtrElem(zcu, ip, many, elem_ptr_ty, elem_ty, start + len)); switch (r.tag) { case ZPT_LOAD_SUCCESS: break; case ZPT_LOAD_RUNTIME_LOAD: return expected; case ZPT_LOAD_OUT_OF_BOUNDS: COMPILE_ERROR_UPSTREAM("ctSliceLength: slice end index exceeds bounds " "of containing decl"); case ZPT_LOAD_UNDEF: COMPILE_ERROR_UPSTREAM("ctSliceLength: sentinel is undefined"); default: UNIMPLEMENTED("analyzeSlice: explicit sentinel load"); } if (mutableValueIntern(zcu, &r.success) != expected) COMPILE_ERROR_UPSTREAM( "ctSliceLength: value in memory does not match slice sentinel"); return expected; } // (part of) Sema.zig:analyzeSlice // Upstream `Sema.analyzeSlice`'s `.many, .c =>` arm sets // `array_ty = ptr_ptr_child_ty`, deliberately leaving no source length. Its // `if (opt_new_len_val)` arm instead remints `*[len:sentinel]T`. Do not form a // PtrSlice or impose invented source bounds. This accepts direct pointer // carriers so valPtrElem preserves a UAV backing or ptr_int address. static uint32_t ctSliceLengthDirectManyOrCPtr(Zcu* zcu, InternPool* ip, uint32_t source_ptr, uint32_t start, uint32_t len, uint32_t explicit_sentinel) { if (source_ptr >= ip->items_len || (!ipIsPtrUav(ip, source_ptr) && !ipIsPtrInt(ip, source_ptr))) return IP_INDEX_NONE; IpPtrInfo source_info; if (ipPtrInfo(ip, ipTypeOf(ip, source_ptr), &source_info) != IP_PTRI_OK || (source_info.size != IP_PTR_SIZE_MANY && source_info.size != IP_PTR_SIZE_C)) return IP_INDEX_NONE; if (source_info.packed_offset != 0) UNIMPLEMENTED("analyzeSlice: packed many/C pointer"); if (source_info.size == IP_PTR_SIZE_C && ipIsPtrInt(ip, source_ptr) && ipPtrIntAddress(ip, source_ptr) == 0) COMPILE_ERROR_UPSTREAM("ctSliceLength: slice of null pointer"); uint32_t elem_ty; IpPtrInfo elem_info; if (start == 0) { // The known-zero analyzePtrArithmetic fast path preserves the source // pointer type; only the fixed-length result changes its size below. elem_ty = source_info.child; elem_info = source_info; } else { uint32_t elem_ptr_ty = ztElemPtrType(zcu, ipTypeOf(ip, source_ptr), start, &elem_ty); if (ipPtrInfo(ip, elem_ptr_ty, &elem_info) != IP_PTRI_OK || elem_info.size != IP_PTR_SIZE_ONE) UNREACHABLE("ctSliceLength: malformed element pointer type"); } uint32_t result_sentinel = IP_INDEX_NONE; if (explicit_sentinel != IP_INDEX_NONE) result_sentinel = ctSliceLengthPointerExplicitSentinel( zcu, ip, source_ptr, elem_ty, start, len, explicit_sentinel); uint32_t result_flags = elem_info.flags & ~IP_PTR_FLAGS_SIZE_MASK; // Sema's new_allowzero excludes a C pointer even when its source flags // carry allowzero: the result is an ordinary one-pointer. if (source_info.size == IP_PTR_SIZE_C) result_flags &= ~IP_PTR_FLAGS_IS_ALLOWZERO; uint32_t array_ty = ipGetArrayType(ip, len, elem_ty, result_sentinel); uint32_t result_ty = ipGetPtrType(ip, array_ty, result_flags); return valPtrElem(zcu, ip, source_ptr, result_ty, elem_ty, start); } // Form the validated `PtrSlice` view which is immediately retyped by // ctSliceLengthRemintView. This shares the cell arm's explicit-sentinel // checks and accepts only direct carriers with an inspectable backing value. static uint32_t ctSliceLengthDirectView(Zcu* zcu, InternPool* ip, uint32_t lhs, uint32_t start, uint32_t len, uint32_t explicit_sentinel) { uint32_t source_slice = lhs; if (lhs < ip->items_len && valIsAggregate(ip, lhs)) { uint32_t arr_ty = ctAggType(ip, lhs); uint32_t arr_len, elem_ty, sentinel; if (!ztArrayTypeInfo(ip, arr_ty, &arr_len, &elem_ty, &sentinel)) UNIMPLEMENTED("analyzeSlice: aggregate not array"); uint32_t ptr_ty = ipGetPtrType(ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); source_slice = ctSliceLengthArrayPtrToSlice(ip, ipGetPtrUav(ip, ptr_ty, lhs)); } else if (lhs < ip->items_len && (ipIsPtrUav(ip, lhs))) { IpPtrInfo lhs_info; if (ipPtrInfo(ip, ipTypeOf(ip, lhs), &lhs_info) != IP_PTRI_OK || lhs_info.size != IP_PTR_SIZE_ONE) UNREACHABLE("ctSliceLengthDirectView: malformed uav pointer"); uint32_t lhs_arr_len, lhs_arr_child, lhs_arr_sentinel; if (ztArrayTypeInfo(ip, lhs_info.child, &lhs_arr_len, &lhs_arr_child, &lhs_arr_sentinel)) { source_slice = ctSliceLengthArrayPtrToSlice(ip, lhs); } else if (ztIsSlice(ip, lhs_info.child)) { ComptimeLoadResult slot = loadComptimePtr(zcu, lhs); if (slot.tag != ZPT_LOAD_SUCCESS) UNIMPLEMENTED("analyzeSlice: unprojectable slice slot"); source_slice = mutableValueIntern(zcu, &slot.success); } else { source_slice = ctSliceLengthArrayPtrToSlice(ip, lhs); } } if (!ipIsPtrSlice(ip, source_slice)) UNIMPLEMENTED("analyzeSlice: unsupported operand"); uint32_t slice_ty = ipTypeOf(ip, source_slice); if (!ztIsSlice(ip, slice_ty)) UNREACHABLE("ctSliceLengthDirectView: malformed slice type"); uint32_t many_ty = ip->item_datas[slice_ty]; IpPtrInfo many_info; if (ipPtrInfo(ip, many_ty, &many_info) != IP_PTRI_OK || many_info.size != IP_PTR_SIZE_MANY || many_info.packed_offset != 0) UNIMPLEMENTED("analyzeSlice: unsupported slice pointer"); uint32_t source_ptr = ipPtrSlicePtr(ip, source_slice); if (ipTypeOf(ip, source_ptr) != many_ty) UNIMPLEMENTED("analyzeSlice: slice pointer mismatch"); uint64_t source_len_u64; if (!ctReadSliceLen(ip, source_slice, &source_len_u64) || source_len_u64 > UINT32_MAX) UNIMPLEMENTED("analyzeSlice: unreadable slice length"); uint32_t source_len = (uint32_t)source_len_u64; uint32_t source_bound = source_len; if (many_info.sentinel != IP_INDEX_NONE) { if (source_bound == UINT32_MAX) COMPILE_ERROR_UPSTREAM( "ctSliceLengthDirectView: sentineled slice exceeds address " "space"); source_bound += 1u; } if (start > source_bound || len > source_bound - start) COMPILE_ERROR_UPSTREAM("ctSliceLengthDirectView: slice bounds"); uint32_t result_sentinel = IP_INDEX_NONE; if (explicit_sentinel != IP_INDEX_NONE) { result_sentinel = ctSliceLengthPointerExplicitSentinel(zcu, ip, source_ptr, many_info.child, start, len, explicit_sentinel); } else if (start + len == source_len) { result_sentinel = many_info.sentinel; } uint32_t result_many_ty = ipGetPtrTypeFull( ip, many_info.child, many_info.flags, result_sentinel, 0); uint32_t result_ptr = valPtrElem( zcu, ip, source_ptr, result_many_ty, many_info.child, start); return ipGetPtrSlice(ip, ipGetSliceType(ip, result_many_ty), result_ptr, ipGetIntUsize(ip, len)); } // (part of) Sema.zig:analyzeSlicePtr // Sema.analyzeSlicePtr of a comptime cell slice view: the view's many // pointer, element `win_start` of the array it windows. static uint32_t ctCellSliceViewPtr( Zcu* zcu, InternPool* ip, const CtCellPtr* view) { uint32_t arr_ptr_ty = IP_INDEX_NONE; uint32_t arr_ptr = ctCellPtrMintBaseAt(zcu, ip, view, view->depth, &arr_ptr_ty); uint32_t many_ty = ip->item_datas[view->slice_ty]; return valPtrElem( zcu, ip, arr_ptr, many_ty, ipPtrChild(ip, many_ty), view->win_start); } // ── Coercion (coerceExtra) ────────────────────────────────────────────────── // Sema.zig:coerce // Coerce a comptime-known init value to a destination type (Sema.coerce, the // shapes struct/array-init field inits + method-arg coercion reach): // enum-literal -> enum_tag, int magnitude -> sized int, optional wrap; // anything already the right kind (pointer / aggregate / enum_tag / matching // int) passes through. Aborts on unmodeled shapes. uint32_t ctCoerceToType(Zcu* zcu, uint32_t val, uint32_t dest_ty) { InternPool* ip = &zcu->intern_pool; if (dest_ty == IP_INDEX_GENERIC_POISON_TYPE || dest_ty == IP_INDEX_NONE) return val; // generic/anytype param: bind by value // Sema.coerce preserves `undefined` while giving it the destination // type. The destination's class still matters: an OPV must become its // actual value, and an uninstantiable destination has no undefined value. // Only runtime and comptime-only destinations carry a typed undef into the // ordinary type-directed cases below. if (val == ZIR_REF_UNDEF || (ipIsUndef(ip, val))) { // coerceExtra's `ensureLayoutResolved(dest_ty, ..., .coerce)`. ensureLayoutResolved(zcu, dest_ty); switch (ztTypeClass(ip, dest_ty)) { case ZT_CLASS_NPV: COMPILE_ERROR_UPSTREAM( "ctCoerceToType: undefined coerced to uninstantiable type"); case ZT_CLASS_OPV: { uint32_t opv = ztOnePossibleValue(ip, dest_ty); if (opv == IP_INDEX_NONE) UNIMPLEMENTED("coerce: OPV without value"); return opv; } case ZT_CLASS_RUNTIME: case ZT_CLASS_PARTIAL: case ZT_CLASS_FULL: return ipGetUndef(ip, dest_ty); case ZT_CLASS_UNKNOWN: UNIMPLEMENTED("coerce: undefined destination type class"); } } // Cell pointer-to-array / cell-slice -> slice (`&sorted_keys` coerced to a // `[][]const u8` param in StaticStringMap.initSortedKVs; `&field_names` // coerced to the preinterned `[]const []const u8` — index 52, below // ZIR_REF_START_INDEX — by reify_slice_arg_ty): produce a // comptime cell-slice view over the whole array (or carry the existing // window). The slice aliases the mutable cell -- element writes / mem.swap // through it mutate the backing comptime `var` array. Any dest index with // the TYPE_SLICE item tag qualifies (preinterned slice types are real // pool items too); a non-slice dest still falls through to the abort. // A depth-0 root's own real ptr_comptime_alloc, a real ptr_field chain // (e.g. `&obj.arr_field`), or a real `ptr_slice` value reaches `val` // here; the gate is class-based and the arm resolves either // representation itself. It fires only when dest_ty is a slice: a real // tag being coerced to some other (e.g. its own matching pointer) type // must reach the generic paths below unchanged (normalizing // unconditionally turned `explicitly typed function alias address // canonicalization` into a spurious "operand not interned" // abort; a real, already correctly-typed // pointer simply does not need this arm at all). if (ctIsCellPointerClassValue(ip, val) && dest_ty > 0 && ztIsSlice(ip, dest_ty)) { // coerceExtra's `"if (dest_ty.eql(inst_ty, zcu)) return inst;"`. if (semaRefIsInterned(val) && ipTypeOf(ip, val) == dest_ty) return val; // A one-pointer to an array at any base: coerceArrayPtrToSlice. if (semaRefIsInterned(val) && ip->item_tags[val] != IP_TAG_PTR_SLICE) return coerceArrayPtrToSlice(ip, val, dest_ty); CtCellPtr cvd = ctCellPtrResolveDescriptor(zcu, ip, val); if (cvd.is_slice) { // Already a windowed cell-slice. Identity is legal only when its // recorded source type matches the destination. Otherwise remint // the same window under the destination type; the shared slice // minter performs the full child/sentinel/packed/attribute check. if (cvd.slice_ty == dest_ty) return val; return ctCellSliceMake(zcu, ip, val, 0, cvd.win_len, dest_ty); } uint32_t arr_ty = ctCellPtrArrayType(zcu, ip, val); if (arr_ty == IP_INDEX_NONE) UNIMPLEMENTED("coerce: cell pointer to non-array as slice"); return ctCellSliceMake( zcu, ip, val, 0, ctAggTypeLen(ip, arr_ty), dest_ty); } if (!semaRefIsInterned(val)) UNIMPLEMENTED("coerce: operand not interned"); // A ptr_comptime_alloc falls into the generic paths below, several of // which end in an identity pass-through, where it would be silently // accepted type-confused (`S{.p=&x}` with `x: u32` read through `*u64` // must abort, as stock rejects "expected type '*u64', found '*u32'"). // `ipTypeOf` -- zig1's one port of `InternPool.typeOf` -- reads the // pointer's own encoded type. A mismatch is an unmodeled destination for // this function -- reject loudly, do not silently return an // untyped-checked value. // A real comptime pointer of another type goes through the one comptime // `coerceExtra`, `coerceExtra` (`.report_err = false`): its in-memory // head retypes it (`getCoerced`), its optional arm wraps it, its pointer // arms coerce it. What it cannot model stays the loud abort. // coerceExtra's slice -> many / C pointer arm: coerceCompatiblePtrs of // analyzeSlicePtr, here a cell slice view's element pointer. IpPtrInfo dest_info; if (ctIsCellPointerClassValue(ip, val) && ipPtrInfo(ip, dest_ty, &dest_info) == IP_PTRI_OK && (dest_info.size == IP_PTR_SIZE_MANY || dest_info.size == IP_PTR_SIZE_C)) { CtCellPtr view = ctCellPtrResolveDescriptor(zcu, ip, val); if (view.is_slice) { uint32_t many = ctCellSliceViewPtr(zcu, ip, &view); if (coerceInMemoryAllowed( zcu, ip, dest_ty, ipTypeOf(ip, many), false)) return ipRetypePtrValue(ip, many, dest_ty); } } if (ctIsCellPointerClassValue(ip, val) && ipTypeOf(ip, val) != dest_ty) { uint32_t coerced = semaCoerceExtraOpts(zcu, ip, val, dest_ty, false); if (coerced == IP_INDEX_NONE) UNIMPLEMENTED("coerce: cell pointer to unmodeled destination"); return coerced; } // Anonymous array pointer -> many pointer (`@constCast(&uav.bytes)` into // `[*]align(A) u8` in InternPool.Local.List.empty). Retype the same UAV // backing allocation once normal pointer compatibility accepts the array // pointee as the many pointer source. uint32_t uav_many_coerced = ctCoerceArrayUavPtrToMany(zcu, val, dest_ty); if (uav_many_coerced != IP_INDEX_NONE) return uav_many_coerced; // Array aggregate -> slice (`return &final` from a comptime body whose // declared return type is `[]const E`). ctEvalBody's REF path carries the // aggregate value directly for fold-only consumers, so rematerialize the // missing `*const [N]E` uav and delegate to the existing array-ptr -> // slice coercer after checking that the destination is the default // `[]const T`/`[:s]const T` many-pointer shape. Element retyping and // non-default pointer attributes are deliberately out of scope and abort // loudly. uint32_t agg_slice_coerced = ctCoerceArrayAggregateToSlice(ip, val, dest_ty); if (agg_slice_coerced != IP_INDEX_NONE) return agg_slice_coerced; // Array aggregate -> vector value (an array-shaped comptime aggregate // under a return type of `@Vector(N, T)`). Sema coerces the aggregate's // elements into the vector result location; zig1's aggregate value // representation is shared, so re-intern the same element list at the // vector type after checking the array and vector agree on length and // child type. Any dest index carrying the TYPE_VECTOR item tag qualifies: // upstream preinterns the common vector types (`vector_4_i32_type` and its // ~48 siblings, src/InternPool.zig), so `@Vector(4, i32)` is a real pool // item below ZIR_REF_START_INDEX -- the same thing the cell-slice arm // above already says about preinterned slice types. Demanding a dynamic // index here skipped this arm for every preinterned shape and returned the // array-typed aggregate unchanged, so `@as(@Vector(4, i32), [4]i32{...})` // kept an array type and the arithmetic that followed had no vector to // fold. if (ztIsZigTypeTag(ip, dest_ty, ZIG_TYPE_TAG_VECTOR) && val >= ZIR_REF_START_INDEX && valIsAggregate(ip, val)) { uint32_t av_src_ty = ctAggType(ip, val); if (av_src_ty == dest_ty) return val; // `Sema.coerceExtra`'s `.vector` destination arm switches on the // SOURCE's type, never on its storage: `".vector => switch // (inst_ty.zigTypeTag(zcu)) { .array, .vector => return // sema.coerceArrayLike(...), .@"struct" => { if // (inst_ty.isTuple(zcu)) return sema.coerceTupleToArray(...); }, else // => {} }"`, and `Type.isTuple` is `indexToKey(ty) == .tuple_type` and // nothing else -- so a named or packed struct falls out of that switch // to `typeMismatchErrMsg`. Asking the length reader instead accepts // one: `ctAggTypeLen`'s first arm is `ipTagIsAnyStruct`, and a named // struct's value carries the same `aggregate` storage tag an array's // does, so `const v: @Vector(4, u32) = S{ .a = 7, ... }` would compile // where upstream reports "expected type '@Vector(4, u32)', found // 'S'". `ipArrayInfo` is the `.array`/`.vector` half of that // switch -- the same predicate `coerceArrayLike` and // `semaCoerceArrayValueToArray` already state -- and the tuple half is // spelled beside it rather than folded into a fourth length rule. IpArrayInfo av_src_seq; bool av_seq = ipArrayInfo(ip, av_src_ty, &av_src_seq) != IP_ARR_NOT_SEQ; if (!av_seq && !ztIsTuple(ip, av_src_ty)) COMPILE_ERROR_UPSTREAM("ctCoerceToType: vector destination from a " "source that is not array-like"); // `coerceArrayLike` and `coerceTupleToArray`, one copy each: // `coerceExtra`'s. return semaCoerceComptime(zcu, ip, val, dest_ty); } // An array value landing in a pointer-typed slot (`self.len_indexes = // &final_len_indexes` / `.keys = &final_keys` in StaticStringMap's // comptime init): ctEvalBody's REF is the fold-only identity (`&x` == x // for comptime consumers), so by the time the coerce runs the missing // indirection must be re-materialized — intern a uav pointer over the // value at the destination pointer type (a `[*]const T` many-ptr or a // `*const [N]T`), offset 0. Element identity is required for a many // dest (array child == dest elem, Sema's ptr compatibility); a one-ptr // dest requires the array type itself as child. Known debt: the fresh uav // will not match stock's ptr-into-parent interning if it ever reaches a // byte-compared dump. Skipping this arm stores a bare aggregate in a // pointer slot, so the dest test must admit preinterned pointer types // (index < 128) too. if (dest_ty > 0 && dest_ty < ip->items_len && ip->item_tags[dest_ty] == IP_TAG_TYPE_POINTER && val >= ZIR_REF_START_INDEX && valIsAggregate(ip, val)) { uint32_t av_arr_ty = ctAggType(ip, val); uint32_t av_dest_child = ipPtrChild(ip, dest_ty); uint32_t av_size = ipPtrSize(ip, dest_ty); if (av_size == IP_PTR_SIZE_ONE && av_dest_child == av_arr_ty) return ipGetPtrUav(ip, dest_ty, val); // *const [N]T if (av_size == IP_PTR_SIZE_MANY) { uint32_t av_elem = IP_INDEX_NONE; uint32_t av_len = 0; uint32_t av_sentinel; ztArrayTypeInfo(ip, av_arr_ty, &av_len, &av_elem, &av_sentinel); if (av_elem == av_dest_child && ipPtrSentinel(ip, dest_ty) == IP_INDEX_NONE) return ipGetPtrUav(ip, dest_ty, val); // [*]const T if (ipPtrSentinel(ip, dest_ty) == IP_INDEX_NONE && semaSliceTypesDifferOnlyBySentinel( ip, av_elem, av_dest_child)) { uint32_t av_new_arr_ty = ipGetArrayType(ip, av_len, av_dest_child, IP_INDEX_NONE); uint32_t* av_elems = zig1Calloc(av_len ? av_len : 1, sizeof(uint32_t)); for (uint32_t i = 0; i < av_len; i++) { uint32_t elem = ctCoerceToType(zcu, valElemValue(ip, val, i, "Value.elemValue: comptime"), av_dest_child); if (!ctValueConformsToType(ip, elem, av_dest_child)) { free(av_elems); UNIMPLEMENTED( "coerce: array pointer element coercion mismatch"); } av_elems[i] = elem; } uint32_t av_new_agg = ipGetAggregate(ip, av_new_arr_ty, av_elems, av_len); free(av_elems); return ipGetPtrUav(ip, dest_ty, av_new_agg); } } UNIMPLEMENTED("coerce: array value to pointer shape"); } // A comptime array value -> another array type (`fn g() [3]u8 { return // "abc".*; }` folded at comptime: a `[3:0]u8` value into a `[3]u8` slot), // through the same semaCoerceArrayValueToArray coerceExtra uses -- // upstream reaches Sema.coerceArrayLike from one `.array` destination arm // (`"Array coercions are allowed only if the child is IMC and the sentinel // is unchanged or removed."`) whichever evaluator is running. // Equal types short-circuit as upstream's `"// If the types are the same, // we can return the operand."`, so an already-typed aggregate is never // re-interned. A shape the helper declines goes to `coerceExtra` below. if (dest_ty < ip->items_len && (ztZigTypeTag(ip, dest_ty) == ZIG_TYPE_TAG_ARRAY) && val >= ZIR_REF_START_INDEX && valIsAggregate(ip, val)) { uint32_t arr_src_ty = ctAggType(ip, val); if (arr_src_ty == dest_ty) return val; // `.@"struct" => { ... if (inst_ty.isTuple(zcu)) return // sema.coerceTupleToArray(...); }`. if (ztIsTuple(ip, arr_src_ty)) return semaCoerceComptime(zcu, ip, val, dest_ty); uint32_t arr_coerced = semaCoerceArrayValueToArray(zcu, ip, val, arr_src_ty, dest_ty); if (arr_coerced != IP_INDEX_NONE) return arr_coerced; return semaCoerceComptime(zcu, ip, val, dest_ty); } // Error-union destination: an error value or an error-union value is // `coerceExtra`'s arm; any other operand is the payload // (`wrapErrorUnionPayload`). if (ztZigTypeTag(ip, dest_ty) == ZIG_TYPE_TAG_ERROR_UNION) { if (ipItemTagIs(ip, val, IP_TAG_ERROR_UNION_ERROR) || ipItemTagIs(ip, val, IP_TAG_ERROR_UNION_PAYLOAD) || ipItemTagIs(ip, val, IP_TAG_ERROR_SET_ERROR)) return semaCoerceComptime(zcu, ip, val, dest_ty); uint32_t payload_ty = ipErrorUnionPayload(ip, dest_ty); uint32_t payload = ctCoerceToType(zcu, val, payload_ty); if (!ctValueConformsToType(ip, payload, payload_ty)) UNIMPLEMENTED("coerce: error-union payload type mismatch"); return ipGetErrorUnionPayload(ip, dest_ty, payload); } // Optional destination: wrap the payload (or pass an already-optional). // Static optionals such as ?type and ?noreturn are below // ZIR_REF_START_INDEX and must take this path too. if (ztZigTypeTag(ip, dest_ty) == ZIG_TYPE_TAG_OPTIONAL) { uint32_t child = ip->item_datas[dest_ty]; if (val == ZIR_REF_NULL_VALUE) return ipGetOptNull(ip, dest_ty); // An optional value is `coerceExtra`'s: the in-memory // "Optionals" line, else "T to ?T" of the whole optional. if (ip->item_tags[val] == IP_TAG_OPT_NULL || ip->item_tags[val] == IP_TAG_OPT_PAYLOAD) return semaCoerceComptime(zcu, ip, val, dest_ty); uint32_t payload = ctCoerceToType(zcu, val, child); if (!ctValueConformsToType(ip, payload, child)) UNIMPLEMENTED("coerce: optional payload type mismatch"); return ipGetOptPayload(ip, dest_ty, payload); } // Everything else is the one comptime `coerceExtra`: its in-memory head // retypes, its arms coerce, and what it cannot model aborts loudly. return semaCoerceComptime(zcu, ip, val, dest_ty); } // ── Captures and container type dedup ─────────────────────────────────────── // (part of) Sema.zig:getCaptures // Resolve a comptime container's capture descriptors (Sema.getCaptures) into a // freshly malloc'd array (caller owns/frees). `instruction`/nested captures // resolve to a value (CAP_VALUE); `decl_val`/`decl_ref` captures are deferred // (CAP_DECL_VAL/CAP_DECL_REF carrying the captured Nav — not evaluated here), // mirroring stock's lazy `CaptureValue.nav_val`/`.nav_ref`. `values` is the // enclosing comptime frame (for `.instruction` captures; NULL for the // declared-nav path); `lookup_ns` is the scope names resolve in for the // duration. Returns NULL with *out_len=0 when the struct declares no // captures. The array is the generic-instantiation dedup key: Nav identity for // deferred decls, resolved value for instruction/nested — so `ArrayList(u8)` // twice keys identically and `ArrayList(u16)` differently, exactly as stock. // An unresolvable slot becomes {CAP_VALUE, IP_INDEX_NONE}: never eager-aborts // — the closure_get consumer aborts loudly if (and only if) a member reads it. CtCap* ctResolveCaptures(Zcu* zcu, const Zir* zir, uint32_t decl_inst, uint32_t lookup_ns, const uint32_t* values, const uint32_t* runtime_values, const uint32_t* runtime_types, uint32_t* out_len) { ZirContainerTrailing tw = zirContainerTrailingWords(zir, decl_inst); if (tw.captures_len == 0) { *out_len = 0; return NULL; // no captures } // capture_names[] follow the captures (unused: names are only for // diagnostics; decl_val/ref captures carry the name in `data`). CtCap* out = ctResolveCaptureWords(zcu, zir, lookup_ns, zirBodySlice(zir, tw.captures), tw.captures_len, values, runtime_values, runtime_types); *out_len = tw.captures_len; return out; } // The struct mint's half of InternPool.getStructType's `.wip` key: publish the // key semaDeclaredNamespaceType holds once {zir, inst} has its type `ty`. void ctDedupPublishWip(Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t ty) { CtDedupWip* w = &zcu->comptime_tables.dedup_wip; if (!w->pending || w->zir != zir || w->inst != inst) return; ctDedupInsert(zcu, zir, inst, w->caps, w->caps_len, ty); w->pending = false; } // Store a copy of the resolved capture descriptors under namespace `ns` // (exported so semaBodyStructType, in sema.c, can register them once it // has created the instantiated struct's namespace). ctStoreCaptures keeps the // pointer, so hand it an owned copy. void ctStoreStructCapturesCopy( Zcu* zcu, uint32_t ns, const CtCap* caps, uint32_t len) { ctStoreCaptures(zcu, ns, ctCopyCaps(caps, len), len); } // Dedup table for declared namespace types (upstream // InternPool.getStructType / getEnumType keyed by {tracked zir_index, // captures}). Keyed here by {zir file, decl instruction, resolved // captures[]}: `ArrayList(u8)` evaluated twice yields the same interned type; // `ArrayList(u16)` a distinct one. // // Upstream keys unconditionally, and semaBodyEnumType does too. The two struct // call sites (zirStructDecl, and ctEvalBody's `struct_decl` arm) // deliberately consult it only when captures!=0: a capture-free struct is // minted per evaluation there, which keeps the InternPool order of programs // without generic instantiations as upstream's. That narrowing is a // divergence from the key rule, not an equivalence. // The table is the Zcu's (Zcu.comptime_tables.struct_dedup), so its indices // never outlive their InternPool. typedef struct StructInstDedup { const Zir* zir; uint32_t struct_inst; uint32_t caps_len; CtCap* caps; // owned (Nav-identity key for deferred decl captures) uint32_t struct_ty; uint32_t next; } StructInstDedup; // Dedup table for `@Type(.enum)` / `@Enum` comptime reification. Upstream // interns reified enums with a key `{tracked_inst, type_hash}` where the hash // covers the tag type, nonexhaustive mode, field count, field values and // canonical field names. zig1 stores these in the simpler declared-explicit // enum shape, so add the same semantic memoization one layer above the mint. // The key is intentionally tied to the originating ZIR instruction: repeated // evaluation of the same generic/comptime body yields one enum type, while a // separate source reification remains separate unless it reaches stock's own // tracked-inst key. Lookup runs only after field names/values are // canonicalized and before `ipTrackZir`/map/item allocation, so hits do not // perturb IP order. typedef struct ReifiedEnumDedup { const Zir* zir; uint32_t inst; uint32_t tag_ty; bool nonexhaustive; uint32_t fields_len; uint32_t* names; // owned NullTerminatedString indices uint32_t* vals; // owned enum backing integer values uint32_t enum_ty; } ReifiedEnumDedup; static uint32_t ctDedupHashKey( const Zir* zir, uint32_t inst, const CtCap* caps, uint32_t caps_len) { Wyhash h; wyhashInit(&h, 0); wyhashUpdateU64(&h, (uint64_t)(uintptr_t)zir); wyhashUpdateU32(&h, inst); wyhashUpdateU32(&h, caps_len); for (uint32_t k = 0; k < caps_len; k++) { wyhashUpdateU32(&h, caps[k].v); wyhashUpdateU32(&h, caps[k].kind); } return (uint32_t)wyhashFinal(&h); } static void ctDedupBucketInit(Zcu* zcu, uint32_t cap) { zcu->comptime_tables.struct_dedup_buckets = zig1Realloc( zcu->comptime_tables.struct_dedup_buckets, cap * sizeof(uint32_t)); zcu->comptime_tables.struct_dedup_buckets_cap = cap; memset(zcu->comptime_tables.struct_dedup_buckets, 0xff, cap * sizeof(uint32_t)); } static void ctDedupRehash(Zcu* zcu, uint32_t new_cap) { ctDedupBucketInit(zcu, new_cap); uint32_t mask = zcu->comptime_tables.struct_dedup_buckets_cap - 1; for (uint32_t i = 0; i < zcu->comptime_tables.struct_dedup_len; i++) { StructInstDedup* e = &zcu->comptime_tables.struct_dedup[i]; uint32_t bucket = ctDedupHashKey(e->zir, e->struct_inst, e->caps, e->caps_len) & mask; e->next = zcu->comptime_tables.struct_dedup_buckets[bucket]; zcu->comptime_tables.struct_dedup_buckets[bucket] = i; } } static bool ctDedupEntryMatches(const StructInstDedup* e, const Zir* zir, uint32_t inst, const CtCap* caps, uint32_t caps_len) { if (e->zir != zir || e->struct_inst != inst || e->caps_len != caps_len) return false; for (uint32_t k = 0; k < caps_len; k++) if (e->caps[k].v != caps[k].v || e->caps[k].kind != caps[k].kind) return false; return true; } uint32_t ctReifiedEnumDedupLookup(const Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t tag_ty, bool nonexhaustive, uint32_t fields_len, const uint32_t* names, const uint32_t* vals) { for (uint32_t i = 0; i < zcu->comptime_tables.reified_enum_dedup_len; i++) { ReifiedEnumDedup* e = &zcu->comptime_tables.reified_enum_dedup[i]; if (e->zir != zir || e->inst != inst || e->tag_ty != tag_ty || e->nonexhaustive != nonexhaustive || e->fields_len != fields_len) continue; bool eq = true; for (uint32_t k = 0; k < fields_len; k++) if (e->names[k] != names[k] || e->vals[k] != vals[k]) { eq = false; break; } if (eq) return e->enum_ty; } return IP_INDEX_NONE; } void ctReifiedEnumDedupInsert(Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t tag_ty, bool nonexhaustive, uint32_t fields_len, const uint32_t* names, const uint32_t* vals, uint32_t enum_ty) { if (zcu->comptime_tables.reified_enum_dedup_len == zcu->comptime_tables.reified_enum_dedup_cap) { zcu->comptime_tables.reified_enum_dedup_cap = zig1GrowCap(zcu->comptime_tables.reified_enum_dedup_cap, 16u); zcu->comptime_tables.reified_enum_dedup = zig1Realloc(zcu->comptime_tables.reified_enum_dedup, zcu->comptime_tables.reified_enum_dedup_cap * sizeof(ReifiedEnumDedup)); } uint32_t* names_copy = zig1Malloc((fields_len ? fields_len : 1) * sizeof(uint32_t)); uint32_t* vals_copy = zig1Malloc((fields_len ? fields_len : 1) * sizeof(uint32_t)); for (uint32_t k = 0; k < fields_len; k++) { names_copy[k] = names[k]; vals_copy[k] = vals[k]; } ReifiedEnumDedup* e = &zcu->comptime_tables.reified_enum_dedup[zcu ->comptime_tables.reified_enum_dedup_len++]; e->zir = zir; e->inst = inst; e->tag_ty = tag_ty; e->nonexhaustive = nonexhaustive; e->fields_len = fields_len; e->names = names_copy; e->vals = vals_copy; e->enum_ty = enum_ty; } uint32_t ctDedupLookup(const Zcu* zcu, const Zir* zir, uint32_t inst, const CtCap* caps, uint32_t caps_len) { // An unresolvable capture slot ({none, CAP_VALUE} = instruction_load, // not modeled) makes the dedup key incomparable: two distinct // instantiations differing only in that capture would key equal and // silently merge (wrong cached type; stock distinguishes by the loaded // value, Sema.zig). Abort loudly at the dedup decision point. for (uint32_t k = 0; k < caps_len; k++) if (caps[k].v == IP_INDEX_NONE && caps[k].kind == CAP_VALUE) UNIMPLEMENTED("getDeclaredStructType: declared type dedup - " "unresolvable instruction_load capture in the key"); if (zcu->comptime_tables.struct_dedup_buckets_cap == 0) return IP_INDEX_NONE; uint32_t mask = zcu->comptime_tables.struct_dedup_buckets_cap - 1; for (uint32_t i = zcu->comptime_tables .struct_dedup_buckets[ctDedupHashKey(zir, inst, caps, caps_len) & mask]; i != UINT32_MAX; i = zcu->comptime_tables.struct_dedup[i].next) { StructInstDedup* e = &zcu->comptime_tables.struct_dedup[i]; if (ctDedupEntryMatches(e, zir, inst, caps, caps_len)) return e->struct_ty; } return IP_INDEX_NONE; } void ctDedupInsert(Zcu* zcu, const Zir* zir, uint32_t inst, const CtCap* caps, uint32_t caps_len, uint32_t struct_ty) { if (zcu->comptime_tables.struct_dedup_len == zcu->comptime_tables.struct_dedup_cap) { zcu->comptime_tables.struct_dedup_cap = zig1GrowCap(zcu->comptime_tables.struct_dedup_cap, 16u); zcu->comptime_tables.struct_dedup = zig1Realloc( zcu->comptime_tables.struct_dedup, zcu->comptime_tables.struct_dedup_cap * sizeof(StructInstDedup)); } CtCap* cp = zig1Malloc((caps_len ? caps_len : 1) * sizeof(CtCap)); for (uint32_t k = 0; k < caps_len; k++) cp[k] = caps[k]; if (zcu->comptime_tables.struct_dedup_buckets_cap == 0) ctDedupBucketInit(zcu, 1024); if ((zcu->comptime_tables.struct_dedup_len + 1) * 10 >= zcu->comptime_tables.struct_dedup_buckets_cap * 7) ctDedupRehash(zcu, zcu->comptime_tables.struct_dedup_buckets_cap * 2); uint32_t index = zcu->comptime_tables.struct_dedup_len++; StructInstDedup* e = &zcu->comptime_tables.struct_dedup[index]; e->zir = zir; e->struct_inst = inst; e->caps_len = caps_len; e->caps = cp; e->struct_ty = struct_ty; uint32_t mask = zcu->comptime_tables.struct_dedup_buckets_cap - 1; uint32_t bucket = ctDedupHashKey(zir, inst, caps, caps_len) & mask; e->next = zcu->comptime_tables.struct_dedup_buckets[bucket]; zcu->comptime_tables.struct_dedup_buckets[bucket] = index; } // ── @Type argument views, table deinit and @TypeOf // ─────────────────────────── // Sema.sliceToIpString over an interned `[]const u8` / `[:0]const u8` name: // `derefSliceAsArray` (ctReifyArgView below), then the bytes re-interned as // a NullTerminatedString. Shared by the reify_struct / reify_enum / // reify_union field-name decoders. Names beyond 255 bytes abort (no // container field name approaches this). #define CT_REIFY_NAME_MAX 255u uint32_t ctReifyNameString(Zcu* zcu, InternPool* ip, uint32_t nslice) { if (nslice >= ip->items_len || ip->item_tags[nslice] != IP_TAG_PTR_SLICE) UNIMPLEMENTED("zirReifyStruct: field name not a slice"); CtReifyArgView bytes = ctReifyArgView(zcu, ip, nslice); if (bytes.len > CT_REIFY_NAME_MAX) UNIMPLEMENTED("zirReifyStruct: field name length"); char nbuf[CT_REIFY_NAME_MAX + 1u] = { 0 }; for (uint32_t bj = 0; bj < bytes.len; bj++) { int64_t cv; bool cct; uint32_t cb; if (!ctReadInt(ip, valElemValue(ip, bytes.agg, bytes.off + bj, "Value.elemValue: comptime"), &cv, &cct, &cb)) UNIMPLEMENTED("zirReifyStruct: field name byte unreadable"); nbuf[bj] = (char)(unsigned char)cv; } return ipGetOrPutString(ip, nbuf, bytes.len); } // (part of) Sema.zig:zirReifyUnion // The element-readable view of one of `@Union`'s by-ref slice/array // arguments — the comptime mirror of Sema.zirReifyUnion's coerce + // resolveConstDefinedValue + derefSliceAsArray step: the backing element // container (an aggregate/repeated/bytes value), the first element's index // within it, and the element count. Accepts the shapes the comptime fold // produces: a comptime cell pointer (a plain pointer to the whole array — // `&field_types` — or an is_slice view, e.g. after the // `[]const []const u8` coercion of `&field_names`), an interned slice or // zero-offset uav pointer, or a bare aggregate (`&@splat(.{})`: // ZIR_INST_REF is the comptime identity, so the `&` yields the aggregate // Value itself). Anything else aborts loudly. CtReifyArgView ctReifyArgView(Zcu* zcu, InternPool* ip, uint32_t v) { CtReifyArgView out = { IP_INDEX_NONE, 0, 0 }; if (ctIsCellPointerClassValue(ip, v)) { CtCellPtr d_v = ctCellPtrResolveDescriptor(zcu, ip, v); const CtCellPtr* d = &d_v; uint32_t agg = ctCellPtrLoad(zcu, ip, v); if (agg == IP_INDEX_NONE || ipIsUndef(ip, agg)) // An uninitialized element would be stock's use-of-undefined // compile error (sema.anyUndef / sliceToIpString). UNIMPLEMENTED("zirReifyStruct: uninitialized comptime cell"); if (d->is_slice) { out.agg = agg; out.off = d->win_start; out.len = d->win_len; return out; } uint32_t arr_ty = ctCellPtrArrayType(zcu, ip, v); if (arr_ty == IP_INDEX_NONE) UNIMPLEMENTED("zirReifyStruct: cell pointer to non-array"); out.agg = agg; out.len = ctAggTypeLen(ip, arr_ty); return out; } if (v < ZIR_REF_START_INDEX || v >= ip->items_len) UNIMPLEMENTED("zirReifyStruct: unsupported operand"); switch ((IpTag)ip->item_tags[v]) { case IP_TAG_AGGREGATE: case IP_TAG_REPEATED: case IP_TAG_BYTES: out.agg = v; out.len = ctAggTypeLen(ip, ctAggType(ip, v)); return out; default: break; } // `derefSliceAsArray`: a slice's pointer is retyped to `*const // [len]elem` (`"const casted_ptr = try sema.pt.getCoerced(Value // .fromInterned(slice.ptr), ptr_ty);"`) and dereferenced; a pointer to // an array dereferences as it is. uint32_t ptr = v; uint32_t ptr_ty = ipTypeOf(ip, v); if (ip->item_tags[v] == IP_TAG_PTR_SLICE) { int64_t len_v; bool len_ct; uint32_t len_b; if (!ctReadInt(ip, ipPtrSliceLen(ip, v), &len_v, &len_ct, &len_b) || len_v < 0) UNIMPLEMENTED("zirReifyStruct: unreadable slice len"); uint32_t many_ty = ip->item_datas[ipTypeOf(ip, v)]; uint32_t arr_ty = ipGetArrayType( ip, (uint32_t)len_v, ipPtrChild(ip, many_ty), IP_INDEX_NONE); ptr = ipPtrSlicePtr(ip, v); ptr_ty = ipGetPtrType(ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); } // A window into an unnamed array whose elements have the window's own // element size, at a whole-element offset: its elements in place -- the // in-memory reinterpretation a `[N][:0]const u8` read as `[N][]const u8` // needs. // Any other window (bytes of a `[3]u16`) takes the pointerDeref below. IpPtrInfo window; IpArrayInfo backing, window_arr; uint64_t esize, ealign, ebits, wsize, walign, wbits; if ((ipIsPtrUav(ip, ptr)) && valIsAggregate(ip, ipPtrUavValue(ip, ptr)) && ipPtrInfo(ip, ptr_ty, &window) == IP_PTRI_OK && ipArrayInfo(ip, window.child, &window_arr) == IP_ARR_OK && ipArrayInfo(ip, ctAggType(ip, ipPtrUavValue(ip, ptr)), &backing) == IP_ARR_OK && ztLayoutInfo(zcu, ip, backing.child, &esize, &ealign, &ebits) && ztLayoutInfo(zcu, ip, window_arr.child, &wsize, &walign, &wbits) && wsize == esize && esize != 0 && ipPtrUavByteOffset(ip, ptr) % esize == 0) { out.agg = ipPtrUavValue(ip, ptr); out.off = (uint32_t)(ipPtrUavByteOffset(ip, ptr) / esize); out.len = ctAggTypeLen(ip, window.child); if ((uint64_t)out.off + out.len > ctAggValueLen(ip, out.agg)) UNIMPLEMENTED("zirReifyStruct: window past its array"); return out; } if (ptr != v) ptr = ipRetypePtrValue(ip, ptr, ptr_ty); out.agg = ptr == IP_INDEX_NONE ? IP_INDEX_NONE : pointerDeref(zcu, ptr, NULL); // The array value, or a zero-length array's only possible value. IpArrayInfo arr; if (out.agg == IP_INDEX_NONE || ipArrayInfo(ip, ipTypeOf(ip, out.agg), &arr) != IP_ARR_OK || (arr.len != 0 && !valIsAggregate(ip, out.agg))) UNIMPLEMENTED("zirReifyStruct: unsupported operand"); out.len = arr.len; return out; } uint32_t ctReifyArgViewElem( InternPool* ip, CtReifyArgView view, uint32_t index) { if (index >= view.len || view.off > UINT32_MAX - index || view.off + index >= ctAggValueLen(ip, view.agg)) UNIMPLEMENTED("zirReifyStruct: index out of bounds"); return valElemValue( ip, view.agg, view.off + index, "Value.elemValue: comptime"); } void ctDeinitTables(CtTables* t) { for (uint32_t i = 0; i < t->struct_dedup_len; i++) free(t->struct_dedup[i].caps); free(t->struct_dedup); free(t->struct_dedup_buckets); for (uint32_t i = 0; i < t->reified_enum_dedup_len; i++) { free(t->reified_enum_dedup[i].names); free(t->reified_enum_dedup[i].vals); } free(t->reified_enum_dedup); for (uint32_t i = 0; i < t->ns_captures_cap; i++) free(t->ns_captures[i].vals); free(t->ns_captures); } // (part of) Sema.zig:zirTypeofBuiltin, Sema.zig:zirTypeofPeer // The type of a comptime-body operand REF. `@TypeOf(x)` // (Sema.zirTypeofBuiltin) and `@TypeOf(a, b, ...)` (Sema.zirTypeofPeer) both // need an operand's type without needing its value, and both analyze their // operands in a type-only block for exactly that. The four shapes are the // ones the typeof_builtin arm already carried: (a) a bound generic parameter // whose monomorphic type the call site recorded in `Zcu.ct.param_tys`; (b) a // `closure_get` capture; (c) a `decl_val`/`decl_ref` declaration; (d) an // already-computed comptime value, whose interned type answers directly. // Returns IP_INDEX_NONE when the ref is none of those -- callers keep their // own loud abort (typeof_builtin falls back to analyzing its body first). // `out_val`, when non-NULL, receives the operand's comptime value when it was // shape (d) and IP_INDEX_NONE otherwise -- upstream's `peer_vals[i]`, which // `resolvePeerTypesInner`'s `.fixed_int`, `.fixed_float`, `.optional` and // `.error_union` arms all read. static uint32_t ctTypeOfOperandRef( const SemaCtx* sema, uint32_t op, uint32_t* out_val) { Zcu* zcu = sema->zcu; const Zir* zir = sema->zir; const uint32_t* values = sema->inst_values; InternPool* ip = &zcu->intern_pool; if (out_val != NULL) *out_val = IP_INDEX_NONE; if (zcu->ct.param_tys != NULL && op >= ZIR_REF_START_INDEX && op - ZIR_REF_START_INDEX < zir->inst_len && zcu->ct.param_tys[op - ZIR_REF_START_INDEX] != IP_INDEX_NONE) return zcu->ct.param_tys[op - ZIR_REF_START_INDEX]; if (op >= ZIR_REF_START_INDEX) { uint32_t oi = op - ZIR_REF_START_INDEX; ZirInstTag ot = zir->inst_tags[oi]; if (ot == ZIR_INST_EXTENDED && zir->inst_datas[oi].extended.opcode == (uint16_t)ZIR_EXT_CLOSURE_GET) { uint32_t cg_idx = zir->inst_datas[oi].extended.small; uint32_t cg_ty = semaScopedCaptureTypeOfGet(zcu, cg_idx); if (cg_ty == IP_INDEX_NONE) UNIMPLEMENTED("typeof: closure_get unresolved capture"); return cg_ty; } if (ot == ZIR_INST_DECL_VAL || ot == ZIR_INST_DECL_REF) { uint32_t name_off = zir->inst_datas[oi].str_tok.start; const char* name = (const char*)&zir->string_bytes[name_off]; uint32_t name_str = ipGetOrPutString(ip, name, (uint32_t)strlen(name)); uint32_t nav = ctDeclLookup(ip, zcu, zir, sema->ns, name_str); if (nav == IP_INDEX_NONE) UNIMPLEMENTED("typeof: declaration not found"); uint32_t operand_ty = IP_INDEX_NONE; if (ot == ZIR_INST_DECL_VAL) navAnalyzeVal(zcu, nav, &operand_ty); else navDeclRefAddressResolved(zcu, nav, &operand_ty); if (!ipIndexIsType(ip, operand_ty)) UNIMPLEMENTED("typeof: declaration type unresolved"); return operand_ty; } } uint32_t val = semaResolveRef(op, values); if (val == IP_INDEX_GENERIC_POISON_TYPE) { // An unbound generic parameter carries `Value.generic_poison`, whose // `InternPool.typeOf` is `generic_poison_type` -- not `type`, which // is what index 106 would otherwise answer as a type-valued value. // Every caller must see the poison, not a plausible-looking `type`. if (out_val != NULL) *out_val = IP_INDEX_GENERIC_POISON_TYPE; return IP_INDEX_GENERIC_POISON_TYPE; } if (semaRefIsInterned(val)) { uint32_t vty = ipTypeOf(ip, val); if (vty != IP_INDEX_NONE && out_val != NULL) *out_val = val; return vty; } return IP_INDEX_NONE; } // ── Comparison, arithmetic, loads and stores ──────────────────────────────── // (part of) Sema.zig:analyzeOptionalPayloadPtr, Value.zig:ptrOptPayload // `Sema.analyzeOptionalPayloadPtr`'s comptime half (initializing = false): // `pointerDeref` the optional, "unable to unwrap null" on null, then // `Value.ptrOptPayload` -- the `.opt_payload` pointer of a cell, or of an // interned Nav pointer. Shared by both frames. static uint32_t ctOptionalPayloadPtr(Zcu* zcu, InternPool* ip, uint32_t opp) { // An interned comptime pointer (`&some_const_decl` formed by // decl_ref, an IP_TAG_PTR_NAV) is the other carrier upstream's // single analyzeOptionalPayloadPtr accepts on its comptime path: // it resolves the operand to a Value and derives the payload // pointer with Value.ptrOptPayload, with no notion of a "cell". if (opp >= ZIR_REF_START_INDEX && opp < ip->items_len && ip->item_tags[opp] == IP_TAG_PTR_NAV) { return ctOptionalPayloadPtrInterned(zcu, opp); } // Any other interned pointer (a uav, a field of one): // `analyzeOptionalPayloadPtr`'s comptime arm, `pointerDeref` then // `Value.ptrOptPayload` -- the parent retyped for a pointer-like // optional, else an `.opt_payload` base. IpPtrInfo opp_pi; if (opp >= ZIR_REF_START_INDEX && opp < ip->items_len && ipPtrInfo(ip, ipTypeOf(ip, opp), &opp_pi) == IP_PTRI_OK && ztIsZigTypeTag(ip, opp_pi.child, ZIG_TYPE_TAG_OPTIONAL)) { uint32_t cur = pointerDeref(zcu, opp, NULL); if (cur == IP_INDEX_NONE) UNIMPLEMENTED("optional_payload_ptr: runtime load"); if (ip->item_tags[cur] == IP_TAG_OPT_NULL) COMPILE_ERROR_UPSTREAM("optional_payload_ptr: unwrap null"); uint32_t res_ty = semaPayloadPtrType( ip, ipTypeOf(ip, opp), ip->item_datas[opp_pi.child]); uint32_t res = ztTypeIsPtrLikeOptional(ip, opp_pi.child) ? ipRetypePtrValue(ip, opp, res_ty) : ipGetPtrOptPayload(ip, res_ty, opp); if (res == IP_INDEX_NONE) UNIMPLEMENTED("optional_payload_ptr: carrier"); return res; } // An immutable optional value standing for its own pointer (the // fold-only convention of a field "pointer" into a comptime aggregate, // e.g. `@typeInfo(E).error_set.error_names.?`): its payload stands for // the payload pointer. if (opp >= ZIR_REF_START_INDEX && opp < ip->items_len && ztIsZigTypeTag(ip, ipTypeOf(ip, opp), ZIG_TYPE_TAG_OPTIONAL)) { if (ip->item_tags[opp] == IP_TAG_OPT_NULL) COMPILE_ERROR_UPSTREAM("optional_payload_ptr: unwrap null"); if (ip->item_tags[opp] != IP_TAG_OPT_PAYLOAD) UNIMPLEMENTED("optional_payload_ptr: optional value carrier"); return ipPayloadVal(ip, opp); } if (!ctIsCellPointerClassValue(ip, opp)) UNIMPLEMENTED("optional_payload_ptr: on non-cell pointer"); uint32_t opp_ty = ctCellPtrType(zcu, ip, opp); if (ztZigTypeTag(ip, opp_ty) != ZIG_TYPE_TAG_OPTIONAL) UNIMPLEMENTED("optional_payload_ptr: on non-optional cell"); uint32_t opp_cur = ctCellPtrLoad(zcu, ip, opp); if (ip->item_tags[opp_cur] == IP_TAG_OPT_NULL) COMPILE_ERROR_UPSTREAM("optional_payload_ptr: unwrap null"); if (ip->item_tags[opp_cur] != IP_TAG_OPT_PAYLOAD) UNIMPLEMENTED("optional_payload_ptr: of non-optional value"); CtCellPtr opp_d = ctCellPtrResolveDescriptor(zcu, ip, opp); uint32_t opp_depth_before = opp_d.depth; ctCellPtrAppend(&opp_d, CTPK_OPT, false, 0); return ctCellPtrMintAfterPayloadHop( zcu, ip, &opp_d, opp_depth_before, CTPK_OPT); } // (part of) Sema.zig:zirStructInitFieldPtr, Value.zig:ptrField // `Sema.zirStructInitFieldPtr` -> `fieldPtr` on a comptime cell pointer // `sf_obj`: a union field first stores the union // with its tag (`unionFieldPtr`'s "Store to the union to initialize the // tag."), then `Value.ptrField`. Shared by both frames. static uint32_t ctCellStructInitFieldPtr( Zcu* zcu, InternPool* ip, uint32_t sf_obj, uint32_t sf_name_ip) { CtCellPtr sfd = ctCellPtrResolveDescriptor(zcu, ip, sf_obj); uint32_t sf_base_ty = ctCellPtrType(zcu, ip, sf_obj); if (ipTypeIsBitpack(ip, sf_base_ty)) { // A packed struct's or union's field pointer (no tag store for a // packed union). uint32_t pk_fidx = semaBitpackFieldIndex(ip, sf_base_ty, sf_name_ip); if (pk_fidx == UINT32_MAX) COMPILE_ERROR_UPSTREAM("struct_init_field_ptr: unknown " "initializer field"); return ctCellPtrFieldPtr(zcu, ip, sfd, pk_fidx); } IpTag sf_bt = (IpTag)ip->item_tags[sf_base_ty]; uint32_t sf_fidx; if (ipTagIsAnyStruct((uint8_t)sf_bt)) sf_fidx = ipStructFieldIndex(ip, sf_base_ty, sf_name_ip); else if (sf_bt == IP_TAG_TYPE_UNION) sf_fidx = ipUnionFieldIndex(ip, sf_base_ty, sf_name_ip); else UNIMPLEMENTED("struct_init_field_ptr: non-struct pointee"); if (sf_fidx == UINT32_MAX) COMPILE_ERROR_UPSTREAM("struct_init_field_ptr: unknown initializer " "field"); if (sf_bt == IP_TAG_TYPE_UNION) { // `Sema.unionFieldPtr`'s "Store to the union to initialize // the tag." path is an effect of struct_init_field_ptr, before // the following store. It matters when the selected field is // one-possible-value (such as void): storePtr2 then correctly // emits no leaf store, but the enclosing union still exists. uint32_t sf_tag_ty = ztUnionTagTypeHypothetical(zcu, sf_base_ty); uint32_t sf_field_ty = ipUnionFieldType(ip, sf_base_ty, sf_fidx); ZtTypeClass sf_field_class = ztTypeClass(ip, sf_field_ty); if (sf_field_class == ZT_CLASS_NPV) COMPILE_ERROR_UPSTREAM("cannot: initialize " "union field with uninstantiable " "type"); if (!ipUnionIsExtern(ip, sf_base_ty)) { uint32_t sf_tag = ipEnumTagFromFieldName(ip, sf_tag_ty, sf_name_ip); if (sf_tag == IP_INDEX_NONE) UNIMPLEMENTED("struct_init_field_ptr: union tag missing"); uint32_t sf_payload = ztOnePossibleValue(ip, sf_field_ty); if (sf_payload == IP_INDEX_NONE) sf_payload = ipGetUndef(ip, sf_field_ty); ctCellPtrStore(zcu, ip, sf_obj, ipGetUnionValue(ip, sf_base_ty, sf_tag, sf_payload)); } else if (sf_field_class == ZT_CLASS_OPV) { // Upstream's extern/packed unionFieldPtr has no tag-store // effect. Its no-byte OPV field store leaves the result as // `@as(U, undefined)`. zig1 has no packed TypeUnion; // write that same undef only into its CtCell, so RET_LOAD // has a value without inventing a semantic extern tag. // Non-OPV extern fields retain their ordinary STORE_NODE // path, which builds the existing CtCell representation. ctCellPtrStore(zcu, ip, sf_obj, ipGetUndef(ip, sf_base_ty)); } } // Struct, tuple and union field hops together. return ipTagIsAnyStruct((uint8_t)sf_bt) ? ctCellStructFieldPtrByIndex(zcu, ip, sfd, sf_base_ty, sf_fidx) : ctCellPtrFieldPtr(zcu, ip, sfd, sf_fidx); } // (part of) Sema.zig:analyzeLoad // Sema.analyzeLoad on a comptime pointer: the pointee type's one possible // value, a comptime cell's current value, else `pointerDeref`. Used by // field_call's by-value self and err_union_code_ptr. static uint32_t ctLoadPtr(Zcu* zcu, InternPool* ip, uint32_t d_op) { uint32_t d_opv; if (semaOpvPtrInfo(ip, d_op, NULL, &d_opv)) { return d_opv; } // `analyzeLoad` -> `pointerDeref` of any comptime pointer. IpPtrInfo d_info; if (!semaRefIsInterned(d_op) || ipPtrInfo(ip, ipTypeOf(ip, d_op), &d_info) != IP_PTRI_OK) UNIMPLEMENTED("deref: of unsupported operand"); uint32_t d_val = pointerDeref(zcu, d_op, NULL); if (d_val == IP_INDEX_NONE) UNIMPLEMENTED("deref: of runtime memory"); return d_val; } // Sema.zig:structInitAnon, PerThread.zig:aggregateValue // Sema.structInitAnon with every field comptime-known: `n` items from // `items` -- `{ field_name, init }` for `.anon_init`, `{ field_type, init }` // for `.typed_init` (a typed init whose result type is generic poison, named // by the field_type instruction) -- interned as `pt.aggregateValue( // struct_ty, values)`. static uint32_t ctStructInitAnon(const SemaCtx* sema, uint32_t inst, uint32_t n, uint32_t items, bool typed) { Zcu* zcu = sema->zcu; const Zir* zir = sema->zir; const uint32_t* values = sema->inst_values; InternPool* ip = sema->ip; uint32_t cap = n ? n : 1u; uint32_t* words = zig1Malloc((size_t)cap * 3 * sizeof(uint32_t)); uint32_t* types = words; uint32_t* elems = words + cap; uint32_t* names = words + 2 * cap; for (uint32_t f = 0; f < n; f++) { uint32_t name_start, init; if (typed) { ZirInstStructInitItem item; zirExtraData(zir, items + 2 * f, &item); ZirInstFieldType ft; zirExtraData(zir, zir->inst_datas[item.field_type].pl_node.payload_index, &ft); name_start = ft.name_start; init = item.init; } else { ZirInstStructInitAnonItem item; zirExtraData(zir, items + 2 * f, &item); name_start = item.field_name; init = item.init; } const char* nm = zirNullTerminatedString(zir, name_start); names[f] = ipGetOrPutString(ip, nm, (uint32_t)strlen(nm)); uint32_t v = semaResolveRef(init, values); if (!semaRefIsInterned(v)) UNIMPLEMENTED("struct_init_anon: field not interned"); elems[f] = v; types[f] = ipTypeOf(ip, v); if (types[f] == IP_INDEX_NONE) UNIMPLEMENTED("struct_init_anon: field type unknown"); } uint32_t anon_ty = semaStructInitAnonType( zcu, zir, inst, sema->type_name_ctx, types, elems, names, n); uint32_t res = ipGetAggregate(ip, anon_ty, elems, n); free(words); return res; } // analyzeSlice's `analyzeLoad(ptr_ptr)` when the operand is a comptime cell // holding a one, many or C pointer: a uav of the held pointer, which the // interned arm slices. Any other operand is returned unchanged. static uint32_t ctSliceOperandLoadCellPtr( Zcu* zcu, InternPool* ip, uint32_t v) { if (!ctIsCellPointerClassValue(ip, v) || ctCellPtrIsSliceableView(zcu, ip, v)) return v; uint32_t held_ty = ctCellPtrType(zcu, ip, v); if (!ipItemTagIs(ip, held_ty, IP_TAG_TYPE_POINTER)) return v; uint32_t held = ctCellPtrLoad(zcu, ip, v); if (held >= ip->items_len || ipTypeOf(ip, held) != held_ty) return v; return ipGetPtrUav( ip, ipGetPtrType(ip, held_ty, IP_PTR_FLAGS_CONST_ONE), held); } // ── ctEvalBody ────────────────────────────────────────────────────────────── // ── ctEvalBody arms // ────────────────────────────────────────────────────────── Each is the // comptime frame's twin of the zirXxx its comment names. // The frame-free resolver for a type instruction's own ref. static uint32_t ctFrameFreeTypeRef(const SemaCtx* sema, uint32_t inst) { return ctResolveFieldTypeRefInNs(sema->ip, sema->zcu, sema->zir, inst + ZIR_REF_START_INDEX, sema->ns, TYPE_NAME_CTX_NONE); } // (part of) Sema.zig:analyzeBodyInner static void ctEvalExtended(SemaCtx* sema, uint32_t inst) { Zcu* zcu = sema->zcu; const Zir* zir = sema->zir; uint32_t* values = sema->inst_values; InternPool* ip = &zcu->intern_pool; uint16_t ext_op = zir->inst_datas[inst].extended.opcode; if (ext_op == ZIR_EXT_REIFY_UNION || ext_op == ZIR_EXT_REIFY_STRUCT) { // Sema.zirReifyUnion / zirReifyStruct, evaluated at comptime. // Payloads ReifyUnion / ReifyStruct, whose words agree // (arg_ty | backing_ty); the rules are the shared // semaReifyUnionType / semaReifyStructType. ZirInstReifyStruct extra; zirExtraData(zir, zir->inst_datas[inst].extended.operand, &extra); values[inst] = (ext_op == ZIR_EXT_REIFY_UNION ? semaReifyUnionType : semaReifyStructType)( zcu, zir, inst, semaResolveRef(extra.layout, values), semaResolveRef(extra.backing_ty, values), semaResolveRef(extra.field_names, values), semaResolveRef(extra.field_types, values), semaResolveRef(extra.field_attrs, values), sema->type_name_ctx, sema->type_name_func, sema->owner_nav); return; } if (ext_op == ZIR_EXT_REIFY_ENUM) { // Sema.zirReifyEnum, evaluated at comptime. Payload ReifyEnum; the // rule itself is the shared semaReifyEnumType. ZirInstReifyEnum extra; zirExtraData(zir, zir->inst_datas[inst].extended.operand, &extra); values[inst] = semaReifyEnumType(zcu, zir, inst, semaResolveRef(extra.tag_ty, values), semaResolveRef(extra.mode, values), semaResolveRef(extra.field_names, values), semaResolveRef(extra.field_values, values), sema->type_name_ctx, sema->type_name_func, sema->owner_nav); return; } if (ext_op == ZIR_EXT_STRUCT_DECL) { // A `struct { ... }` type expression evaluated at comptime // (Sema.zirStructDecl) — e.g. the anonymous struct a generic // function returns, whose namespace holds its methods. Intern the // struct type (creating + scanning its namespace) via // semaBodyStructType, parented at the current lookup scope. For a // generic instantiation (`ArrayList(u8)`) the struct closes over the // enclosing comptime frame: resolve its captures from `values`, dedup // on {zir, inst, captures} so repeated `ArrayList(u8)` yields one // type, and hand the resolved captures to semaBodyStructType so // capture-typed fields // (`items: []T`) and method signatures (`fn get() T`) resolve. values[inst] = semaDeclaredNamespaceType(zcu, zir, inst, sema, NULL, NULL, sema->ns, sema->type_name_ctx, false); return; } if (ext_op == ZIR_EXT_ENUM_DECL) { // An `enum { ... }` type expression evaluated at comptime (the // analog of the struct_decl case above) (auto shape or // explicit-tag-type/valued), inline in a comptime body walk. Intern // the enum type, scoping computed tag-type/value bodies at SemaCtx.ns // (like the struct_decl case above). values[inst] = semaBodyEnumType(zcu, zir, inst, sema->ns, values, NULL, NULL, sema->type_name_ctx, sema->type_name_func); return; } if (ext_op == ZIR_EXT_UNION_DECL) { // A `union { ... }` / `union(enum) { ... }` type expression // evaluated at comptime (Sema.zirUnionDecl) — the sibling of // the two arms above, and analyzed the same way: stock passes // `block.namespace` to createTypeNamespace in all three. So // resolve the captures the union closes over, key them into // the one `{zir, inst, captures}` dedup table and intern // through the shared semaBodyUnionType. values[inst] = semaDeclaredNamespaceType(zcu, zir, inst, sema, NULL, NULL, sema->ns, sema->type_name_ctx, true); return; } if (ext_op == ZIR_EXT_OPAQUE_DECL) { // An `opaque { ... }` type expression evaluated at comptime // — e.g. `pub const AnyFuture = opaque {};` // (lib/std/Io.zig). The rule itself is the shared semaOpaqueDeclType. values[inst] = semaOpaqueDeclType( zcu, zir, inst, sema, NULL, NULL, sema->type_name_ctx); return; } if (ext_op == ZIR_EXT_INPLACE_ARITH_RESULT_TY) { // Sema.zirInplaceArithResultTy: the result type for `a op= b` // (`set.ints[i] |= x`). For a non-pointer lvalue the type is // the lvalue's own type. ZIR extended.operand = the lvalue // ref. uint32_t io_op = semaResolveRef(zir->inst_datas[inst].extended.operand, values); uint32_t io_ty; if (ctIsCellPointerClassValue(ip, io_op)) io_ty = ctCellPtrType(zcu, ip, io_op); else if (io_op < ip->items_len) io_ty = ipComptimeValueType(ip, io_op); else UNIMPLEMENTED("analyzeBodyInner: inplace_arith lvalue kind"); values[inst] = semaInplaceArithResultTy( ip, io_ty, zir->inst_datas[inst].extended.small); return; } if (ext_op == ZIR_EXT_TUPLE_DECL) { // Sema.zirTupleDecl (src/Sema.zig): a tuple type // declaration folded comptime (`pub const Result = struct { E, usize // };`). Decode + validate + intern live in the shared zirTupleDecl // core (also ctResolveParamTypeRef's extended arm — the inline // fn-proto-return-type route); here field refs resolve // against this frame's values. values[inst] = semaTupleDecl(zcu, zir, inst, values, NULL); return; } if (ext_op == ZIR_EXT_TYPEOF_PEER) { // `@TypeOf(a, b, ...)` -- Sema.zirTypeofPeer. `operand` is // the payload index of Zir.Inst.TypeOfPeer { src_node, // body_len, body_index } (Zir.zig) and `small` is the // arg count, the arg refs trailing the payload. Upstream // analyzes the body in a type-only child block -- "Ignore the // result, we only care about the instructions in `args`" -- // then "for (args, 0..) |arg_ref, i| { inst_list[i] = // sema.resolveInst(arg_ref); }" and returns // "sema.resolvePeerTypes(block, src, inst_list, ...)". ZirInstTypeOfPeer tp; const uint32_t* tp_args = zirBodySlice(zir, zirExtraData(zir, zir->inst_datas[inst].extended.operand, &tp)); uint32_t tp_args_len = zir->inst_datas[inst].extended.small; if (tp_args_len < 2) UNREACHABLE("typeof_peer: fewer than two arguments"); // Arguments computed in the body are analyzed at runtime // (ctTypeofRuntime); the rest are this frame's values. if (tp.body_len != 0) { values[inst] = ctTypeofRuntime(sema, inst); return; } // The peer rule itself is shared with the runtime arm // (semaTypeofPeerResolve, sema.c) so the two // evaluators cannot drift apart on it. uint32_t tp_small_tys[8]; uint32_t tp_small_vals[8]; uint32_t* tp_tys = (tp_args_len <= 8) ? tp_small_tys : (uint32_t*)zig1Malloc(tp_args_len * sizeof(uint32_t)); uint32_t* tp_vals = (tp_args_len <= 8) ? tp_small_vals : (uint32_t*)zig1Malloc(tp_args_len * sizeof(uint32_t)); for (uint32_t tk = 0; tk < tp_args_len; tk++) { uint32_t tp_arg = tp_args[tk]; uint32_t tp_val = IP_INDEX_NONE; uint32_t tp_ty = ctTypeOfOperandRef(sema, tp_arg, &tp_val); if (tp_ty == IP_INDEX_NONE) UNIMPLEMENTED("typeof_peer: operand not typeable"); tp_tys[tk] = tp_ty; tp_vals[tk] = tp_val; } values[inst] = semaTypeofPeerResolve(zcu, tp_tys, tp_vals, tp_args_len); if (tp_args_len > 8) { free(tp_tys); free(tp_vals); } return; } if (ext_op == ZIR_EXT_MIN_MULTI || ext_op == ZIR_EXT_MAX_MULTI) { // Sema.zirMinMaxMulti -> analyzeMinMax over N comptime // operands, the same port as the two-operand arm. uint32_t n = zir->inst_datas[inst].extended.small; ZirInstNodeMultiOp mm; const uint32_t* mm_operands = zirBodySlice(zir, zirExtraData(zir, zir->inst_datas[inst].extended.operand, &mm)); uint32_t* mm_ops = ARR_INIT(uint32_t, n + 1u); for (uint32_t k = 0; k < n; k++) { uint32_t v = semaResolveRef(mm_operands[k], values); if (!semaRefIsInterned(v)) UNIMPLEMENTED( "analyzeBodyInner: min/max operand not interned"); mm_ops[k] = v; } values[inst] = semaMinMaxComptime(zcu, ext_op == ZIR_EXT_MAX_MULTI, mm_ops, n); free(mm_ops); return; } if (ext_op == ZIR_EXT_CMPXCHG) { // `Sema.zirCmpxchg`'s comptime arm: `"const result_val = ... // .val = if (stored_val.eql(expected_val, elem_ty, zcu)) // blk: { try sema.storePtr(block, src, ptr, new_value); break // :blk .none; } else stored_val.toIntern()"`, a `?T`; an undef // expected/new value is an undef `?T`. Values compare by // interned identity, so only int/bool/enum elements. ZirInstCmpxchg cx; zirExtraData(zir, zir->inst_datas[inst].extended.operand, &cx); semaCmpxchgFlags(zcu, semaResolveRef(cx.success_order, values), semaResolveRef(cx.failure_order, values)); uint32_t cx_ptr = semaResolveRef(cx.ptr, values); uint32_t cx_exp = semaResolveRef(cx.expected_value, values); uint32_t cx_new = semaResolveRef(cx.new_value, values); if (!ctIsCellPointerClassValue(ip, cx_ptr)) UNIMPLEMENTED("cmpxchg: on a non-cell pointer"); // A typed pointer value keeps its qualifiers: `*const T` does // not coerce to `checkAtomicPtrOperand`'s `*T`. IpPtrInfo cx_pi; if (semaRefIsInterned(cx_ptr) && ipPtrInfo(ip, ipTypeOf(ip, cx_ptr), &cx_pi) == IP_PTRI_OK && cx_pi.is_const) COMPILE_ERROR_UPSTREAM("cmpxchg: pointer does not coerce to *T"); uint32_t cx_ty = ctCellPtrType(zcu, ip, cx_ptr); // `"const elem_ty = sema.typeOf(expected_value);"` -- the // builtin's `T`, which AstGen made `expected_value`'s result // type -- checked as the runtime frame checks it, then // `checkAtomicPtrOperand` coerces the pointer to `*elem_ty`, // which a cell of another type cannot satisfy. uint64_t cx_align; uint32_t cx_elem = ipComptimeValueType(ip, cx_exp); if (!semaCmpxchgElemSupported(zcu, cx_elem, &cx_align)) COMPILE_ERROR_UPSTREAM("cmpxchg: expected bool, integer, " "enum, packed struct, or pointer " "type of at most 32 bits"); if (cx_ty != cx_elem) COMPILE_ERROR_UPSTREAM("cmpxchg: pointer does not coerce to *T"); if (cx_ty >= ip->items_len || (ip->item_tags[cx_ty] != IP_TAG_TYPE_INT_SIGNED && ip->item_tags[cx_ty] != IP_TAG_TYPE_INT_UNSIGNED && cx_ty != ZIR_REF_BOOL_TYPE && !ipIsEnumType(ip, cx_ty))) UNIMPLEMENTED("cmpxchg: element type"); uint32_t cx_opt = ipGetOptionalType(ip, cx_ty); cx_exp = ctCoerceToType(zcu, cx_exp, cx_ty); cx_new = ctCoerceToType(zcu, cx_new, cx_ty); if (ipIsUndef(ip, cx_exp) || ipIsUndef(ip, cx_new)) { values[inst] = ipGetUndef(ip, cx_opt); return; } uint32_t cx_stored = ctCellPtrLoadForValue(zcu, ip, cx_ptr); if (cx_stored == cx_exp) { ctCellPtrStore(zcu, ip, cx_ptr, cx_new); values[inst] = ipGetOptNull(ip, cx_opt); } else { values[inst] = ipGetOptPayload(ip, cx_opt, cx_stored); } return; } if (ext_op != ZIR_EXT_DBG_EMPTY_STMT && ext_op != ZIR_EXT_RESTORE_ERR_RET_INDEX && ext_op != ZIR_EXT_BRANCH_HINT) { zirAbortUnhandled(ext_op, true); } // No-ops at comptime: dbg_empty_stmt, restore_err_ret_index (it // trims only the comptime error trace zig1 does not keep) and // @branchHint (a codegen hint). } // (part of) Sema.zig:zirFieldPtr // Address-of a namespace member (`a.b` in ref position — the // intermediate of a value chain `a.b.c`, e.g. `std.lang` / // `std.lang.CompilerBackend`). Mirrors the runtime comptime // FIELD_PTR namespace path (Sema.fieldPtr's `.type` case -> // namespaceLookupRef): the object is a `ptr_nav` to a container // Type; resolve in its namespace and yield a `ptr_nav` to // that member decl — the same shape decl_ref produces, so the next // field_ptr / field_ptr_load consumes it identically. static void ctEvalFieldPtr( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstField fp; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &fp); uint32_t fobj = semaResolveRef(fp.lhs, values); const char* ffield = zirNullTerminatedString(zir, fp.field_name_start); uint32_t ffield_ip = ipGetOrPutString(ip, ffield, (uint32_t)strlen(ffield)); // Auto-deref through a pointer-VALUED comptime allocation. // Sema.fieldVal: "Zig allows dereferencing a single pointer // during field lookup"; Sema.fieldPtrLoad reaches it via // `pointerDeref(object_ptr)` -> `fieldVal(object_val)`. A // pointer parameter bound by the comptime call machinery // (`builtin.target.cCallingConvention()`, whose `target` is // `*const Target`) is held in an anonymous allocation, so the // access object is a `ptr_uav` whose pointee type is itself a // single pointer: load it and resolve the member on the stored // pointer. Exactly one deref, as upstream's `is_pointer_to`; // every shape reached before has a non-pointer pointee and // leaves `fobj` untouched. if (fobj >= ZIR_REF_START_INDEX && fobj < ip->items_len && (ipIsPtrUav(ip, fobj)) && ipPtrUavByteOffset(ip, fobj) == 0) { uint32_t d_pd = ip->item_datas[fobj]; uint32_t d_ptr_ty = ip->extra[d_pd]; uint32_t d_child = d_ptr_ty < ip->items_len && ip->item_tags[d_ptr_ty] == IP_TAG_TYPE_POINTER ? ip->extra[ip->item_datas[d_ptr_ty]] : IP_INDEX_NONE; // pointerDeref, not the uav's value: a field pointer at // offset 0 (`&self.kvs`) is the parent's uav retyped. if (d_child < ip->items_len && ip->item_tags[d_child] == IP_TAG_TYPE_POINTER) fobj = pointerDeref(zcu, fobj, NULL); } // Address of a field of a comptime mutable cell (`set.ints` where // `set: *Set` is a cell pointer): extend the access path by the // struct field index (Sema.structFieldPtr comptime). if (ctIsCellPointerClassValue(ip, fobj)) { CtCellPtr d = ctCellPtrResolveDescriptor(zcu, ip, fobj); uint32_t base_ty = ctCellPtrType(zcu, ip, fobj); if (ip->item_tags[base_ty] == IP_TAG_TYPE_POINTER) { // Auto-deref (Sema.fieldPtr: field access through a // single pointer derefs it first): `self.kvs.keys` where // the cell path holds a `*const KVs` whose value is an // interned ptr_uav over the KVs aggregate. The pointee is // immutable (a uav const), so the "field pointer" is the // field value itself (the fold-only convention used for // every immutable comptime ref here). Only the // ptr_uav(offset 0) -> struct aggregate shape is modeled; // others abort loudly. uint32_t fp_pv = ctCellPtrLoad(zcu, ip, fobj); uint32_t fp_child = ip->extra[ip->item_datas[base_ty]]; if (ip->item_tags[fp_pv] != IP_TAG_PTR_UAV || ipPtrUavByteOffset(ip, fp_pv) != 0 || !ipTagIsAnyStruct(ip->item_tags[fp_child])) UNIMPLEMENTED("field_ptr: cell ptr auto-deref shape"); uint32_t fp_uval = ip->extra[ip->item_datas[fp_pv] + 1]; uint32_t fp_fidx = ipStructFieldIndex(ip, fp_child, ffield_ip); if (fp_fidx == UINT32_MAX) UNIMPLEMENTED("field_ptr: cell ptr field not found"); if (ipStructFieldIsComptime(ip, fp_child, fp_fidx)) { uint32_t field_ptr_ty = ztFieldPtrType(zcu, base_ty, fp_fidx); uint32_t field_val = semaFieldDefaultValue(zcu, fp_child, fp_fidx); if (field_val == IP_INDEX_NONE) UNIMPLEMENTED("zirFieldPtr: comptime field has no value"); values[inst] = ipGetPtrComptimeField(ip, field_ptr_ty, field_val); } else { values[inst] = valElemValue( ip, fp_uval, fp_fidx, "Value.elemValue: comptime"); } return; } if (d.bit_ptr_ty != 0 || ipTypeIsBitpack(ip, base_ty)) { // Value.ptrField's `.@"packed"` arms: `"return // pt.getCoerced(parent_ptr, field_ptr_ty)"` -- the host // pointer retyped, `Type.fieldPtrType` placing the field // in `packed_offset`. uint32_t pfidx = semaBitpackFieldIndex(ip, base_ty, ffield_ip); if (pfidx == UINT32_MAX) UNIMPLEMENTED("field_ptr: packed field not found"); values[inst] = ctCellPtrFieldPtr(zcu, ip, d, pfidx); return; } if (ip->item_tags[base_ty] == IP_TAG_TYPE_UNION && semaRefIsInterned(fobj)) { values[inst] = semaUnionFieldPtrComptime(zcu, fobj, base_ty, ffield_ip); return; } if (ztIsTuple(ip, base_ty)) { // Sema.structFieldPtr on a tuple: the `len` ref, else // tupleFieldIndex -> structFieldPtrByIndex. uint32_t len_ty; uint32_t len_ref = semaTupleLenRef(ip, base_ty, ffield, &len_ty); values[inst] = len_ref != IP_INDEX_NONE ? len_ref : ctCellStructFieldPtrByIndex(zcu, ip, d, base_ty, tupleFieldIndex(ip, base_ty, ffield)); return; } if (ztIsSlice(ip, base_ty)) { // Sema.fieldPtr's slice arm: `ptrField(Value.slice_ptr_index)` // or `slice_len_index` of the cell's slice. uint32_t sidx; if (strcmp(ffield, "ptr") == 0) sidx = 0u; else if (strcmp(ffield, "len") == 0) sidx = 1u; else COMPILE_ERROR_UPSTREAM( "field_ptr: no member named this in slice"); values[inst] = ctCellPtrFieldPtr(zcu, ip, d, sidx); return; } if (!ipTagIsAnyStruct(ip->item_tags[base_ty])) UNIMPLEMENTED("field_ptr: on non-struct cell"); uint32_t fidx = ipStructFieldIndex(ip, base_ty, ffield_ip); if (fidx == UINT32_MAX) UNIMPLEMENTED("field_ptr: cell field not found"); values[inst] = ctCellStructFieldPtrByIndex(zcu, ip, d, base_ty, fidx); return; } // A real `std.lang.Type` union value in ref position // (`@typeInfo(T).int` — the intermediate of `.int.bits`): the // "pointer" to the active payload is the payload value itself // (the fold-only convention). The `.int` payload is the real // `Type.Int` aggregate (ctIntInfoValue), and `.bits`/`.signedness` // on it resolve via the generic aggregate arm below, exactly as // the FIELD_PTR_LOAD fold's payload does. if (ctIsTypeInfoValue(zcu, fobj)) { const char* active = semaTypeInfoActiveTagName(zcu, fobj); if (strcmp(ffield, active) != 0) UNIMPLEMENTED("field_ptr: unsupported Type union field"); values[inst] = semaTypeInfoPayload(zcu, fobj, active, NULL); return; } // Field of a comptime aggregate value (`cpu.arch` as the receiver // of `.isX86()`): yield the field value directly. A subsequent // field_call dispatches the method on it (value receiver); no real // pointer is needed for the comptime method-call path. if (fobj < ip->items_len && valIsAggregate(ip, fobj)) { uint32_t agg_ty = ctAggType(ip, fobj); // `.len` of a comptime tuple/array value in REF position // (`kvs_list.len` where astgen chose field_ptr): the count, // exactly as the FIELD_PTR_LOAD `.len` special-case above — // the "pointer" to a fold-only value is the value itself. if (strcmp(ffield, "len") == 0 && (ztIsTuple(ip, agg_ty) || ip->item_tags[agg_ty] == IP_TAG_TYPE_ARRAY_SMALL)) { uint32_t falen = ip->extra[ip->item_datas[agg_ty]]; values[inst] = ipGetIntUsize(ip, falen); return; } if (ztIsTuple(ip, agg_ty)) { // Tuple element by numeric name in REF position // (`kv.@"0"` in initSortedKVs). Mirror the // FIELD_PTR_LOAD tuple arm (Sema.tupleFieldIndex), then // the element value (fold-only; the "pointer" is the // value itself). uint32_t tuple_index = tupleFieldIndex(ip, agg_ty, ffield); uint32_t field_val = ipTupleFieldValue(ip, agg_ty, tuple_index); if (field_val != IP_INDEX_NONE) { uint32_t parent_ptr_ty = ipGetPtrType(ip, agg_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t field_ptr_ty = ztFieldPtrType(zcu, parent_ptr_ty, tuple_index); values[inst] = ipGetPtrComptimeField(ip, field_ptr_ty, field_val); } else { values[inst] = valElemValue( ip, fobj, tuple_index, "Value.elemValue: comptime"); } return; } if (!ipTagIsAnyStruct(ip->item_tags[agg_ty])) UNIMPLEMENTED("field_ptr: non-struct aggregate"); uint32_t fidx = ipStructFieldIndex(ip, agg_ty, ffield_ip); if (fidx == UINT32_MAX) UNIMPLEMENTED("field_ptr: aggregate field"); if (ipStructFieldIsComptime(ip, agg_ty, fidx)) { uint32_t parent_ptr_ty = ipGetPtrType(ip, agg_ty, IP_PTR_FLAGS_CONST_ONE); uint32_t field_ptr_ty = ztFieldPtrType(zcu, parent_ptr_ty, fidx); uint32_t field_val = semaFieldDefaultValue(zcu, agg_ty, fidx); if (field_val == IP_INDEX_NONE) UNIMPLEMENTED( "zirFieldPtr: aggregate comptime field has no value"); values[inst] = ipGetPtrComptimeField(ip, field_ptr_ty, field_val); } else { values[inst] = valElemValue(ip, fobj, fidx, "Value.elemValue: comptime"); } return; } if (semaRefIsInterned(fobj) && fobj >= ZIR_REF_START_INDEX && ztIsNamespaceType(ip, fobj)) { // Direct namespace type in ref position // (`Link.Queue(...)` inside a comptime-evaluated type body): // fieldPtr's `.type` path returns a reference to the member // Nav, the same shape as the ptr_nav-rooted chain below. values[inst] = ctContainerMemberRef(zcu, zir, fobj, ffield_ip); return; } if (fobj >= ZIR_REF_START_INDEX && fobj < ip->items_len && (ipIsPtrUav(ip, fobj))) { // Address of a field inside an anonymous comptime aggregate // (`&(extern struct { ... }{ ... }).bytes` in // InternPool.Local.List.empty). Preserve the UAV backing // allocation and move the pointer by the field byte offset. uint32_t pd = ip->item_datas[fobj]; uint32_t ptr_ty = ip->extra[pd]; uint32_t backing = ip->extra[pd + 1]; uint32_t orig_ty = (ip->item_tags[fobj] == IP_TAG_PTR_UAV_ALIGNED) ? ip->extra[pd + 2] : ptr_ty; if (ptr_ty >= ip->items_len || ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("field_ptr: uav non-pointer type"); uint32_t parent_ty = ip->extra[ip->item_datas[ptr_ty]]; if (ctIsTypeInfoValue(zcu, backing)) { const char* active = semaTypeInfoActiveTagName(zcu, backing); if (strcmp(ffield, active) != 0) UNIMPLEMENTED("field_ptr: uav unsupported Type union field"); values[inst] = semaTypeInfoPayload(zcu, backing, active, NULL); return; } if (ctIsCellPointerClassValue(ip, backing)) { if (ipPtrUavByteOffset(ip, fobj) != 0) UNIMPLEMENTED("field_ptr: uav cell offset"); uint32_t cell_ty = ctCellPtrType(zcu, ip, backing); if (cell_ty >= ip->items_len || !ipTagIsAnyStruct(ip->item_tags[cell_ty])) UNIMPLEMENTED("field_ptr: uav cell non-struct"); uint32_t cell_fidx = ipStructFieldIndex(ip, cell_ty, ffield_ip); if (cell_fidx == UINT32_MAX) UNIMPLEMENTED("field_ptr: uav cell field"); values[inst] = ctCellStructFieldPtrByIndex(zcu, ip, ctCellPtrResolveDescriptor(zcu, ip, backing), cell_ty, cell_fidx); return; } if (ipPtrUavByteOffset(ip, fobj) == 0 && ztIsNamespaceType(ip, backing)) { values[inst] = ctContainerMemberRef(zcu, zir, backing, ffield_ip); return; } // Sema.structFieldPtr on a tuple (its `len` ref included). if (ztIsTuple(ip, parent_ty)) { uint32_t tf_ty; values[inst] = semaStructFieldPtrComptime( zcu, fobj, ffield, ffield_ip, &tf_ty); return; } if (parent_ty < ip->items_len && ipTagIsAnyUnion(ip->item_tags[parent_ty])) { // Sema.unionFieldPtr's comptime half on the uav. values[inst] = semaUnionFieldPtrComptime(zcu, fobj, parent_ty, ffield_ip); return; } if (parent_ty >= ip->items_len || !ipTagIsAnyStruct(ip->item_tags[parent_ty])) UNIMPLEMENTED("field_ptr: uav non-struct pointee"); uint64_t base_off = ipPtrUavByteOffset(ip, fobj); uint32_t backing_value_ty = ipTypeOf(ip, backing); if (backing >= ip->items_len) UNIMPLEMENTED("field_ptr: uav backing type mismatch"); if (backing_value_ty != parent_ty) { uint32_t projected = IP_INDEX_NONE; uint32_t project_source = backing; if (ipIsUndef(ip, project_source)) project_source = ctAggUndef(ip, backing_value_ty); if (!ctProjectValueAtOffset(zcu, project_source, backing_value_ty, base_off, parent_ty, 0, &projected)) UNIMPLEMENTED("field_ptr: uav backing type mismatch"); } // fieldPtr's `"ensureLayoutResolved(inner_ty, src, // .ptr_access)"`. ensureLayoutResolved(zcu, parent_ty); uint32_t fidx = ipStructFieldIndex(ip, parent_ty, ffield_ip); if (fidx == UINT32_MAX) UNIMPLEMENTED("field_ptr: uav field not found"); uint32_t field_ptr_ty = ztFieldPtrType(zcu, ptr_ty, fidx); if (ipStructFieldIsComptime(ip, parent_ty, fidx)) { uint32_t field_val = semaFieldDefaultValue(zcu, parent_ty, fidx); if (field_val == IP_INDEX_NONE) UNIMPLEMENTED("zirFieldPtr: uav comptime field has no value"); values[inst] = ipGetPtrComptimeField(ip, field_ptr_ty, field_val); return; } if (ipStructIsPacked(ip, parent_ty)) UNIMPLEMENTED("field_ptr: packed uav"); uint64_t field_off = ipStructFieldOffset(ip, parent_ty, fidx); if (base_off > UINT64_MAX - field_off) UNIMPLEMENTED("field_ptr: uav offset overflow"); values[inst] = ipGetPtrUavRetypedOffset( ip, field_ptr_ty, backing, orig_ty, base_off + field_off); return; } if (fobj >= ip->items_len || ip->item_tags[fobj] != IP_TAG_PTR_NAV) UNIMPLEMENTED("field_ptr: object not a nav pointer"); uint32_t fbase_nav = ip->extra[ip->item_datas[fobj] + 1]; uint32_t fbase_decl_ty; uint32_t fbase_value = zptEnsureNavValUpToDate(zcu, fbase_nav, &fbase_decl_ty); IpPtrNav fbase_ptr; if (ipDecodePtrNav(ip, fobj, &fbase_ptr) != IP_PN_OK) UNREACHABLE("malformed: field_ptr nav pointer"); // `const_decl.field` in ref position where the decl value is a // aggregate. A full aggregate (`sizes.big_align`) and a full or // explicit-comptime field have no runtime storage, so retain the // existing value fold. Runtime/PARTIAL storage instead projects // the original Nav pointer: the Nav remains the allocation // identity and the persisted struct layout contributes a checked // byte offset. This is the fixed-target equivalent of stock // Value.ptrField's field-base representation. if (semaRefIsInterned(fbase_value) && fbase_value >= ZIR_REF_START_INDEX && valIsAggregate(ip, fbase_value)) { uint32_t fa_ty = fbase_ptr.child_type; if (!ipTagIsAnyStruct(ip->item_tags[fa_ty])) UNIMPLEMENTED("field_ptr: nav-aggregate non-struct"); ensureLayoutResolved(zcu, fa_ty); // fieldPtr's .ptr_access uint32_t fa_idx = ipStructFieldIndex(ip, fa_ty, ffield_ip); if (fa_idx == UINT32_MAX) UNIMPLEMENTED("field_ptr: nav-aggregate field"); uint32_t fa_field_ty = ipStructFieldType(ip, fa_ty, fa_idx); if (fa_field_ty == IP_INDEX_NONE) UNIMPLEMENTED("zirFieldPtr: unresolved nav-aggregate field type"); ZtTypeClass fa_class = ztTypeClass(ip, fa_ty); ZtTypeClass fa_field_class = ztTypeClass(ip, fa_field_ty); bool fa_fold = fa_class == ZT_CLASS_FULL || fa_field_class == ZT_CLASS_FULL || ipStructFieldIsComptime(ip, fa_ty, fa_idx); if (ipStructFieldIsComptime(ip, fa_ty, fa_idx)) { uint32_t fa_ptr_ty = ztFieldPtrType(zcu, fbase_ptr.ptr_type, fa_idx); uint32_t field_val = semaFieldDefaultValue(zcu, fa_ty, fa_idx); if (field_val == IP_INDEX_NONE) UNIMPLEMENTED("zirFieldPtr: Nav comptime field has no value"); values[inst] = ipGetPtrComptimeField(ip, fa_ptr_ty, field_val); return; } if (fa_fold) { if (fbase_ptr.byte_offset != 0 || fbase_ptr.nav_type != fa_ty || fbase_decl_ty != fa_ty || ctAggType(ip, fbase_value) != fa_ty) UNIMPLEMENTED("zirFieldPtr: nested comptime Nav field fold"); values[inst] = valElemValue( ip, fbase_value, fa_idx, "Value.elemValue: comptime"); return; } if (fa_class == ZT_CLASS_NPV || fa_class == ZT_CLASS_UNKNOWN || fa_field_class == ZT_CLASS_NPV || fa_field_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED("zirFieldPtr: nav field has no concrete storage"); if (!ensureLayoutResolved(zcu, fa_ty)) UNIMPLEMENTED( "zirFieldPtr: nav field aggregate layout unresolved"); if (ipStructIsPacked(ip, fa_ty)) UNIMPLEMENTED("zirFieldPtr: packed Nav field pointer projection"); uint32_t fa_ptr_ty = ztFieldPtrType(zcu, fbase_ptr.ptr_type, fa_idx); uint32_t fa_offset = ipStructFieldOffset(ip, fa_ty, fa_idx); if (fbase_ptr.byte_offset > UINT64_MAX - fa_offset) UNIMPLEMENTED("zirFieldPtr: nav field offset overflow"); values[inst] = ipGetPtrNavOffset(ip, fa_ptr_ty, fbase_ptr.nav_index, fbase_ptr.byte_offset + fa_offset); return; } // `Container.member` in ref position: resolve in the // container's namespace. The container may be a struct, a union, or an // enum — each has its own namespace accessor. // Forming `&member` needs only the member's type (analyzeNavRef is // lazy about the pointee value). If the member has an explicit // type annotation (`lime1: Model`), resolve just that — avoids // eagerly materializing a large typed struct literal that is never // read through this pointer. Otherwise (type-inferred decls, whose // value is a type, e.g. `const lang = @import(...)`) resolve the // value. values[inst] = ctContainerMemberRef(zcu, zir, fbase_value, ffield_ip); } // (part of) Sema.zig:zirCall // Comptime call within a comptime body (Sema.analyzeCall reached during // comptime evaluation). Resolve the (comptime-known) callee to its func_decl, // evaluate each argument body, and comptime-evaluate the callee via // ctAnalyzeComptimeCallTyped. ZIR pl_node -> Call { flags, callee, [arg // end-offsets, arg bodies] } (same layout as the runtime call handler). static void ctEvalCall(SemaCtx* sema, uint32_t inst, uint32_t* ct_ret_val, uint32_t* ct_break_block, uint32_t* ct_break_val) { Zcu* zcu = sema->zcu; const Zir* zir = sema->zir; uint32_t* values = sema->inst_values; InternPool* ip = &zcu->intern_pool; ZirInstCall call; uint32_t cabase = zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &call); uint32_t cflags = call.flags; uint32_t ccallee_ref = call.callee; uint32_t cargs_len = cflags >> ZIR_CALL_FLAGS_ARGS_LEN_SHIFT; if ((cflags & ZIR_CALL_FLAGS_PACKED_MODIFIER_MASK) != CALL_MODIFIER_AUTO) UNIMPLEMENTED("zirCall: call non-auto modifier"); uint32_t ccallee = semaResolveRef(ccallee_ref, values); // Sema.analyzeCall: a function pointer callee is // `"sema.pointerDerefExtra(block, func_src, c)"`'s `.func`. if (ccallee < ip->items_len && ipItemTagIs(ip, ipTypeOf(ip, ccallee), IP_TAG_TYPE_POINTER)) { uint32_t cpointee = pointerDeref(zcu, ccallee, NULL); if (cpointee == IP_INDEX_NONE) UNIMPLEMENTED("zirCall: call through a runtime function pointer"); ccallee = cpointee; } // The call is made at the callee VALUE's type, a coerced // function's own (`func_ty`), not its body's. uint32_t ccall_fty = ipItemTagIs(ip, ccallee, IP_TAG_FUNC_COERCED) ? ipTypeOf(ip, ccallee) : IP_INDEX_NONE; // Sema.analyzeCall resolves the callee through // InternPool.unwrapCoercedFunc (`"const orig_fn_index = zcu // .intern_pool.unwrapCoercedFunc(val);"`) before reaching its // body: a function coerced to another function type (a parameter // left generic on either side) still calls the original body. // A comptime function pointer calls its pointee: `".ptr => // switch (try sema.pointerDerefExtra(block, func_src, c)) { ... // .val => |pointee| ... .func => .{ pointee, false }"`, the // function a Nav pointer names whatever its pointer type. IpPtrNav cpn; if (ipDecodePtrNavAny(ip, ccallee, &cpn) == IP_PN_OK && cpn.byte_offset == 0) ccallee = navAnalyzeVal(zcu, cpn.nav_index, NULL); if (semaRefIsInterned(ccallee)) ccallee = ipUnwrapCoercedFunc(ip, ccallee); if (!semaRefIsInterned(ccallee) || ccallee == IP_INDEX_NONE || ip->item_tags[ccallee] != IP_TAG_FUNC_DECL) UNIMPLEMENTED("zirCall: call callee not a func_decl"); uint32_t ccnav = ipFuncDeclOwnerNav(ip, ccallee); // The callee's signature: each arg body's coercions (`as_node` / // `decl_literal`) name their destination type as a ref to this // call instruction ("give the arg its result type" — same // contract as the runtime call handler binding inst_values[inst] // = param_ty before each arg body). A generic param type binds // generic_poison (the decl_literal/as_node handlers defer). uint32_t cc_fty = IP_INDEX_NONE; zptEnsureNavValUpToDate(zcu, ccnav, &cc_fty); if (ccall_fty != IP_INDEX_NONE) cc_fty = ccall_fty; uint32_t cc_nparams = (cc_fty != IP_INDEX_NONE) ? ipFuncTypeParamsLen(ip, cc_fty) : 0; CtCallParamCtx cctx; ctCallParamCtxInit(zcu, ccnav, cc_fty, &cctx); uint32_t* cargv = ARR_INIT(uint32_t, cargs_len + 1u); for (uint32_t ai = 0; ai < cargs_len; ai++) { const uint32_t* abody; uint32_t alen = semaCallArgBody(zir, cabase, cargs_len, ai, &abody); uint32_t alast = abody[alen - 1]; uint32_t provided_ty = ctCallProvidedParamType(zcu, cc_fty, ai, &cctx); values[inst] = provided_ty; if (alen > 1) { CtEvalStatus ast = ctEvalBody(sema, abody, alen - 1, ct_ret_val, ct_break_block, ct_break_val); if (ast != CT_EVAL_NORMAL) UNIMPLEMENTED("zirCall: call arg body control flow"); } cargv[ai] = semaResolveRef( zir->inst_datas[alast].break_data.operand, values); // Coerce a comptime_int argument to its declared concrete // parameter type before binding (Sema.analyzeCall coerces // each arg; `fromByteUnits(@alignOf(T))` binds usize, so the // body's `@ctz(byte_units)` sees a width-carrying int). A // generic/comptime_int param binds the raw value as before. if (provided_ty != IP_INDEX_GENERIC_POISON_TYPE && provided_ty != ZIR_REF_COMPTIME_INT_TYPE && semaRefIsInterned(cargv[ai])) { if (provided_ty < ip->items_len && ipIndexIsType(ip, provided_ty)) { cargv[ai] = ctCoerceToType(zcu, cargv[ai], provided_ty); } else if (ai < cc_nparams) { int64_t cc_av; bool cc_act; uint32_t cc_ab; if (ctReadInt(ip, cargv[ai], &cc_av, &cc_act, &cc_ab) && cc_act && cc_av >= 0) { uint32_t cc_cr = semaCoerceComptimeIntLiteral( ip, (uint64_t)cc_av, provided_ty); if (cc_cr != IP_INDEX_NONE) cargv[ai] = cc_cr; } } } ctCallParamCtxBind(&cctx, ai, cargv[ai]); } ctCallParamCtxFree(&cctx); values[inst] = ctAnalyzeComptimeCallTyped( zcu, ccallee, ccnav, ccall_fty, cargv, cargs_len, NULL); free(cargv); } // (part of) Sema.zig:zirTypeofBuiltin // `@TypeOf(expr)` inside a comptime body (Sema.zirTypeofBuiltin). // The payload is a Block { body_len, body[] } ending in // `break_inline(%operand)`; the result is the operand's type (the // body is analyzed in a type-only scope — never executed). Two // resolvable operand shapes: (a) a bound generic parameter whose // monomorphic type the call site recorded in `Zcu.ct.param_tys` // (the ret-type-body evaluation, e.g. `RetTy(@TypeOf(slice))` on // a runtime arg); (b) a comptime-known interned value -> its // interned type. Anything else goes to (c) below. static void ctEvalTypeofBuiltin(SemaCtx* sema, uint32_t inst) { const Zir* zir = sema->zir; uint32_t* values = sema->inst_values; ZirInstBlock tb; const uint32_t* tb_body = zirBodySlice(zir, zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &tb)); uint32_t tb_len = tb.body_len; if (tb_len == 0) UNIMPLEMENTED("typeof_builtin: empty typeof_builtin body"); uint32_t tb_last = tb_body[tb_len - 1]; if (zir->inst_tags[tb_last] != ZIR_INST_BREAK_INLINE) UNREACHABLE("typeof_builtin: body not break_inline"); uint32_t tb_op = zir->inst_datas[tb_last].break_data.operand; // The body is analyzed on every visit: a value an earlier visit // (the previous loop iteration) left is not this one's. for (uint32_t k = 0; k + 1 < tb_len; k++) values[tb_body[k]] = INST_VALUE_UNSET; uint32_t tb_ty = ctTypeOfOperandRef(sema, tb_op, NULL); if (tb_ty != IP_INDEX_NONE) { values[inst] = tb_ty; return; } // (c) any other body: analyzed at runtime (ctTypeofRuntime), // afresh -- not from what the attempt above left in the body. for (uint32_t k = 0; k + 1 < tb_len; k++) values[tb_body[k]] = INST_VALUE_UNSET; values[inst] = ctTypeofRuntime(sema, inst); } // (part of) Sema.zig:zirFieldPtrLoad // Field access (value load through a pointer). ZIR pl_node -> // Field { lhs=object_ptr_ref, field_name_start }. Two shapes: // (a) a real `std.lang.Type` union value from @typeInfo // the active-tag field folds to the payload // (`.int` -> the real `Type.Int` aggregate, whose // `.bits`/`.signedness` resolve via the aggregate arm). // (b) a `ptr_nav` to a container/enum decl (the feature-gating // loads): mirror the runtime comptime FIELD_PTR_LOAD path — // load the nav to its pointee type, then a struct namespace // yields the member decl's value (zptEnsureNavValUpToDate) and an // enum type yields the field's `enum_tag` // (ipEnumTagFromFieldName). Resolves `builtin.zig_backend` // (namespace member) and `.field` (the final step // of `std.lang.CompilerBackend.stage2_wasm`). // Sema.fieldVal's comptime `.len` of an array or tuple of type `ty`, a usize; // IP_INDEX_NONE for another field or type. static uint32_t ctTypeLenField( InternPool* ip, uint32_t ty, const char* field) { if (strcmp(field, "len") != 0) return IP_INDEX_NONE; if (ztIsTuple(ip, ty)) return ipGetIntUsize(ip, ipTupleFieldCount(ip, ty)); IpArrayInfo arr; if (ipArrayInfo(ip, ty, &arr) == IP_ARR_OK && !arr.is_vector) return ipGetIntUsize(ip, arr.len); return IP_INDEX_NONE; } static void ctEvalFieldPtrLoad( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstField fp; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &fp); uint32_t obj = semaResolveRef(fp.lhs, values); // A generic type body's runtime parameter is unbound here, but // Sema.fieldVal's `.len` of an array or tuple is comptime-known from its // type alone (`[op_refs.len]Temp` with `op_refs: anytype`). if (obj == INST_VALUE_UNSET && zcu->ct.param_tys != NULL) { uint32_t pref = fp.lhs; if (pref >= ZIR_REF_START_INDEX && zir->inst_tags[pref - ZIR_REF_START_INDEX] == ZIR_INST_REF) pref = zir->inst_datas[pref - ZIR_REF_START_INDEX].un_tok.operand; uint32_t pty = pref >= ZIR_REF_START_INDEX ? zcu->ct.param_tys[pref - ZIR_REF_START_INDEX] : IP_INDEX_NONE; uint32_t plen = pty == IP_INDEX_NONE ? IP_INDEX_NONE : ctTypeLenField( ip, pty, zirNullTerminatedString(zir, fp.field_name_start)); if (plen != IP_INDEX_NONE) { values[inst] = plen; return; } } field_ptr_load_object: // zirFieldPtrLoad on a comptime pointer: pointerDeref, then the // value's field. if (ctIsCellPointerClassValue(ip, obj) && ip->item_tags[obj] != IP_TAG_PTR_SLICE) obj = pointerDeref(zcu, obj, NULL); // A `ref` of a comptime cell slice or array pointer is a uav // holding it: its load is that pointer, whose `.ptr` / `.len` // the view arms below answer. else if (ipIsPtrUav(ip, obj) && ipPtrUavByteOffset(ip, obj) == 0 && ctIsCellPointerClassValue(ip, ipPtrUavValue(ip, obj)) && ctCellPtrIsSliceableView(zcu, ip, ipPtrUavValue(ip, obj))) obj = ipPtrUavValue(ip, obj); const char* field = zirNullTerminatedString(zir, fp.field_name_start); if (ctIsCellPointerClassValue(ip, obj) && ctCellPtrResolveDescriptor(zcu, ip, obj).is_slice) { // Field access on a comptime cell-slice view: `.len` is the // window length, not the backing array's. CtCellPtr csd_v = ctCellPtrResolveDescriptor(zcu, ip, obj); const CtCellPtr* csd = &csd_v; if (strcmp(field, "len") == 0) { values[inst] = ipGetIntUsize(ip, csd->win_len); return; } if (strcmp(field, "ptr") == 0) { values[inst] = ctCellSliceViewPtr(zcu, ip, csd); return; } UNIMPLEMENTED("zirFieldPtrLoad: unsupported field on a cell-slice"); } if (ctIsCellPointerClassValue(ip, obj)) { // Sema.fieldVal's `.array` arm under `is_pointer_to`: `.ptr` // coerces the array pointer itself to a many pointer. uint32_t cp_ty; if (strcmp(field, "ptr") == 0 && semaRefIsInterned(obj) && semaPtrValueFieldVal(ip, obj, ipTypeOf(ip, obj), field, &values[inst], &cp_ty, "slice: field neither .len nor .ptr", "field_ptr_load: .ptr of a cell array pointer", "array: pointer field neither .len nor " ".ptr")) return; if (strcmp(field, "len") == 0) { uint32_t cell_ty = ctCellPtrType(zcu, ip, obj); if (cell_ty >= ZIR_REF_START_INDEX && (ztZigTypeTag(ip, cell_ty) == ZIG_TYPE_TAG_ARRAY || ztIsTuple(ip, cell_ty))) { values[inst] = ipGetIntUsize(ip, ctAggTypeLen(ip, cell_ty)); return; } } // Field load through a comptime mutable cell pointer // (`attrs.@"align"` where `attrs` is a comptime `var`): load // the cell's current value at its access path, then fall // through to the value-shaped field arms below, mirroring Sema's // load-then-fieldVal. obj = ctCellPtrLoad(zcu, ip, obj); // The loaded field value could itself be a real // ptr_comptime_alloc (e.g. `mystruct.field_ptr = &other_var;` // stored a raw root pointer as ordinary data, not through // this destination pointer). if (ctIsCellPointerClassValue(ip, obj) && ctCellPtrResolveDescriptor(zcu, ip, obj).is_slice) { // A struct field can itself store a comptime cell-slice // view (`Writer.buffer: []u8 = buf[0..]`); after loading // the field, `.len` is the view window length. CtCellPtr loaded_slice_v = ctCellPtrResolveDescriptor(zcu, ip, obj); const CtCellPtr* loaded_slice = &loaded_slice_v; if (strcmp(field, "len") == 0) { values[inst] = ipGetIntUsize(ip, loaded_slice->win_len); return; } UNIMPLEMENTED( "zirFieldPtrLoad: unsupported field on a loaded cell-slice"); } } // Sema.fieldVal's arms answered by an array or tuple value's type // whatever its storage (`undefined` and one-possible-value too). uint32_t fvt_ty; if (obj >= ZIR_REF_START_INDEX && obj < ip->items_len && semaFieldValByType(ip, obj, field, &values[inst], &fvt_ty)) return; if (ctIsTypeInfoValue(zcu, obj)) { // A real `std.lang.Type` union value: field // access on the active tag folds to the union's payload word // (upstream Sema.fieldVal on a union value yields the active // field's value). For the payload-less tags that word // is the interned void value; for `.int` it is the // real `Type.Int` aggregate, whose `.signedness`/`.bits` // resolve via the aggregate arm below. const char* active = semaTypeInfoActiveTagName(zcu, obj); if (strcmp(field, active) == 0) { values[inst] = semaTypeInfoPayload(zcu, obj, active, NULL); return; } COMPILE_ERROR_UPSTREAM("unsupported: std.lang.Type union field"); } uint32_t fl_field_ip = ipGetOrPutString(ip, field, (uint32_t)strlen(field)); // `Sema.unionFieldVal`'s comptime arm on a union value. if (obj < ip->items_len && ip->item_tags[obj] == IP_TAG_UNION_VALUE) { uint32_t ufidx = ipUnionFieldIndex(ip, ipTypeOf(ip, obj), fl_field_ip); if (ufidx == UINT32_MAX) UNIMPLEMENTED("field_ptr_load: union field not found"); values[inst] = semaUnionActivePayload(zcu, obj, ufidx); return; } // (c) obj is directly a container type (`Generic(T).member`): a // namespace member decl. if (semaRefIsInterned(obj) && obj >= ZIR_REF_START_INDEX && ipTagIsAnyStruct(ip->item_tags[obj])) { values[inst] = ctContainerMemberVal( zcu, zir, obj, fl_field_ip, "field_ptr_load: type has no ns"); return; } if (semaRefIsInterned(obj) && obj >= ZIR_REF_START_INDEX && ipTagIsAnyUnion(ip->item_tags[obj])) { // A union TYPE's member decl — namespace member, like the struct arm. values[inst] = ctContainerMemberVal(zcu, zir, obj, fl_field_ip, "field_ptr_load: union type has no ns"); return; } if (semaRefIsInterned(obj) && obj >= ZIR_REF_START_INDEX && ipIsEnumType(ip, obj)) { uint32_t et_ns = ipEnumNamespaceOrNone(ip, obj); if (et_ns != IP_NAMESPACE_NONE) { uint32_t et_nav = IP_INDEX_NONE; bool et_status = semaNamespaceLookupAccessible( zcu, zir, et_ns, fl_field_ip, &et_nav); if (et_status) { values[inst] = navAnalyzeVal(zcu, et_nav, NULL); return; } } uint32_t et = ipEnumTagFromFieldName(ip, obj, fl_field_ip); if (et == IP_INDEX_NONE) UNIMPLEMENTED("field_ptr_load: enum type field not found"); values[inst] = et; return; } if (semaRefIsInterned(obj) && obj >= ZIR_REF_START_INDEX && ip->item_tags[obj] == IP_TAG_TYPE_OPAQUE) UNIMPLEMENTED("field_ptr_load: opaque namespace members"); // An error set TYPE's member is its `.err` value. if (semaRefIsInterned(obj) && obj >= ZIR_REF_START_INDEX && ztIsZigTypeTag(ip, obj, ZIG_TYPE_TAG_ERROR_SET)) { uint32_t es_val; semaComptimeNamespaceFieldVal( zcu, zir, obj, fl_field_ip, &es_val, NULL, NULL, NULL); values[inst] = es_val; return; } // (d) obj is an aggregate struct value (`cpu.arch` // /`cpu.features`): the field element by field index // (Sema.structFieldVal comptime). if (obj < ip->items_len && valIsAggregate(ip, obj)) { uint32_t agg_ty = ctAggType(ip, obj); // `.len` of a comptime tuple/array value is the field/element // Count (Sema.fieldVal's `len` special-case), not a member; // stock types it `usize` (`pt.intRef(.usize, ...)`). if (strcmp(field, "len") == 0 && (ztIsTuple(ip, agg_ty) || ip->item_tags[agg_ty] == IP_TAG_TYPE_ARRAY_SMALL)) { values[inst] = ipGetIntUsize(ip, ctAggTypeLen(ip, agg_ty)); return; } uint32_t fidx; if (ipTagIsAnyStruct(ip->item_tags[agg_ty])) { fidx = ipStructFieldIndex(ip, agg_ty, fl_field_ip); if (fidx == UINT32_MAX) UNIMPLEMENTED("field_ptr_load: aggregate field not found"); } else if (ztIsTuple(ip, agg_ty)) { // Tuple element access `kv.@"0"` (initSortedKVs). fidx = tupleFieldIndex(ip, agg_ty, field); } else { UNIMPLEMENTED("field_ptr_load: on non-struct aggregate"); } if (ztIsTuple(ip, agg_ty)) { values[inst] = ctTupleFieldValue(ip, obj, fidx); return; } // structFieldVal: a comptime field is the type's, then "if // (try field_ty.onePossibleValue(pt)) |field_val| return // ...", both ahead of the stored element. uint32_t fl_ct = semaStructFieldComptimeValue(zcu, agg_ty, fidx); if (fl_ct == IP_INDEX_NONE) fl_ct = ztOnePossibleValue(ip, ipStructFieldType(ip, agg_ty, fidx)); values[inst] = fl_ct != IP_INDEX_NONE ? fl_ct : valElemValue(ip, obj, fidx, "Value.elemValue: comptime"); return; } if (obj < ip->items_len && ip->item_tags[obj] == IP_TAG_BITPACK) { uint32_t bp_ty = ipBitpackType(ip, obj); uint32_t bp_idx = semaBitpackFieldIndex(ip, bp_ty, fl_field_ip); if (bp_idx == UINT32_MAX) UNIMPLEMENTED("zirFieldPtrLoad: packed field not found"); values[inst] = semaReadBitpackFieldValue(zcu, obj, bp_idx); return; } // (e) obj is a slice value (`self.bytes.len` / `.ptr`): the // stored length / many-pointer word (Sema.zirSliceLen / .ptr; // PtrSlice = { ty, ptr, len }), via the shared // `semaPtrValueFieldVal` helper arm (f) below and the runtime // evaluator's PTR_NAV dispatch already use, so the stock rejection // of a slice field that is neither `.len` nor `.ptr` is reported // identically. `value_ty` is `IP_INDEX_NONE`: the PTR_SLICE prong // keys on `value`'s tag alone, so the // array_ptr_msg/array_field_msg placeholders below are unreachable // for this call (the PTR_SLICE branch always returns or aborts // before the pointer-to-array branch runs). field_ptr_load_slice: if (semaRefIsInterned(obj) && obj >= ZIR_REF_START_INDEX && ip->item_tags[obj] == IP_TAG_PTR_SLICE) { uint32_t pv_val; uint32_t pv_ty; if (semaPtrValueFieldVal(ip, obj, IP_INDEX_NONE, field, &pv_val, &pv_ty, "unsupported: slice field", "field_ptr_load: .ptr of a slice-obj " "pointer-to-array", "field_ptr_load: slice-obj " "pointer-to-array field neither .len nor .ptr")) { values[inst] = pv_val; return; } } // (f) obj is a comptime pointer: Sema.fieldVal's pointer-shaped // prongs on the pointer itself (`.len` of a `*const [N]T`), else // `fieldPtrLoad`'s `pointerDeref` and `fieldVal` on the loaded // value (`is_pointer_to` re-enters here for a loaded single // pointer). IpPtrInfo p_info; if (obj >= ZIR_REF_START_INDEX && obj < ip->items_len && ipPtrInfo(ip, ipTypeOf(ip, obj), &p_info) == IP_PTRI_OK) { uint32_t pv_val; uint32_t pv_ty; if (semaPtrValueFieldVal(ip, obj, ipTypeOf(ip, obj), field, &pv_val, &pv_ty, "unsupported: slice field on a pointer", "field_ptr_load: .ptr of a " "pointer-to-array", "field_ptr_load: pointer-to-array field " "neither .len nor .ptr")) { values[inst] = pv_val; return; } if (p_info.size != IP_PTR_SIZE_ONE) UNIMPLEMENTED("zirFieldPtrLoad: field of a non-single pointer"); obj = pointerDeref(zcu, obj, NULL); if (obj == IP_INDEX_NONE) UNIMPLEMENTED("field_ptr_load: runtime load"); // A loaded slice is a value, never a cell-slice view. if (ip->item_tags[obj] == IP_TAG_PTR_SLICE) goto field_ptr_load_slice; goto field_ptr_load_object; } UNIMPLEMENTED("zirFieldPtrLoad: field access on unsupported value"); } // (part of) Sema.zig:zirOptionalPayloadPtr // Comptime `if (opt) |*p|` / `opt.?` in ref position: form a // pointer to the optional's payload (Sema.zirOptionalPayloadPtr -> // analyzeOptionalPayloadPtr with initializing=false — the ZIR // dispatch always passes initializing=false; the safe/unsafe split // only affects a runtime is_non_null_ptr safety check, which has // no comptime effect). The operand is a cell pointer to an // optional cell; the payload pointer descends one more path // component via CTPK_OPT (ctCellPtrLoad/Store already unwrap the // OPT_PAYLOAD), mirroring Value.ptrOptPayload. Per // analyzeOptionalPayloadPtr's pointerDeref/isNull, the optional // must be non-null here (the is_non_null gate folds the null arm // away before this runs); a null value is an "unable to unwrap // null" comptime error in stock. static void ctEvalOptionalPayloadPtr( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; values[inst] = ctOptionalPayloadPtr(zcu, ip, semaResolveRef(zir->inst_datas[inst].un_node.operand, values)); } // (part of) Sema.zig:zirCall // Comptime namespace-member call `ns.f(args)` inside a comptime // body. // Mirrors the runtime FIELD_CALL `.type` comptime branch: the // object resolves to a `ptr_nav` whose pointee is a container // Type (a namespace); look the member fn up, evaluate each // (comptime-known) argument body, and fold the call via // ctAnalyzeComptimeCallTyped. A method call on a comptime value // receiver is not yet modeled — aborts loudly. ZIR pl_node -> // FieldCall { flags, obj_ptr, field_name_start } + trailing arg // data (same layout as Call, abase = pi+3). static void ctEvalFieldCall(SemaCtx* sema, uint32_t inst, uint32_t* ct_ret_val, uint32_t* ct_break_block, uint32_t* ct_break_val) { Zcu* zcu = sema->zcu; const Zir* zir = sema->zir; uint32_t* values = sema->inst_values; InternPool* ip = &zcu->intern_pool; ZirInstFieldCall fc; uint32_t fc_abase = zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &fc); uint32_t fc_flags = fc.flags; uint32_t fc_obj_ref = fc.obj_ptr; const char* fc_name = zirNullTerminatedString(zir, fc.field_name_start); uint32_t fc_args_len = fc_flags >> ZIR_CALL_FLAGS_ARGS_LEN_SHIFT; if ((fc_flags & ZIR_CALL_FLAGS_PACKED_MODIFIER_MASK) != CALL_MODIFIER_AUTO) UNIMPLEMENTED("field_call: non-auto modifier"); uint32_t fc_obj = semaResolveRef(fc_obj_ref, values); uint32_t fc_ns; bool fc_has_self = false; uint32_t fc_self = 0; bool fc_self_is_nav_ptr = false; uint32_t fc_self_nav_ptr = 0; uint32_t fc_self_ty = IP_INDEX_NONE; bool fc_double_ptr = false; if (ctIsCellPointerClassValue(ip, fc_obj)) { // Method call on a comptime mutable cell receiver // (`x.addFeature(...)` with `x: Set`, method `addFeature(self: // *Set, ...)`): bind the cell POINTER as `self`. The method's // namespace comes from the pointee (Set) type. // fieldCallBind's `found_nav` arm reads any container's // namespace (`concrete_ty.getNamespaceIndex(zcu)`). uint32_t rty = ctCellPtrType(zcu, ip, fc_obj); if (!ipTagIsAnyStruct(ip->item_tags[rty]) && !ipTagIsAnyUnion(ip->item_tags[rty]) && !ipIsEnumType(ip, rty)) UNIMPLEMENTED("field_call: cell not a container"); // fieldCallBind's `.@"union"` arm takes a field of that name // before the namespace. if (ipTagIsAnyUnion(ip->item_tags[rty]) && ipUnionFieldIndex(ip, rty, ipGetOrPutString(ip, fc_name, (uint32_t)strlen(fc_name))) != UINT32_MAX) UNIMPLEMENTED("field_call: through a union cell's field"); fc_ns = ipContainerNamespaceOrNone(ip, rty); fc_has_self = true; fc_self = fc_obj; // *Set cell pointer bound by value } else if (semaRefIsInterned(fc_obj) && fc_obj >= ZIR_REF_START_INDEX && ip->item_tags[fc_obj] == IP_TAG_PTR_NAV) { IpPtrNav fc_nav_ptr; if (ipDecodePtrNav(ip, fc_obj, &fc_nav_ptr) != IP_PN_OK) UNREACHABLE("field_call: malformed Nav pointer"); const IpNav* fc_nav = ipGetNav(ip, fc_nav_ptr.nav_index); if (!fc_nav->is_const) { // Calling a method on a mutable global does not load its // initializer. The Nav pointer itself is the runtime // identity passed to a by-reference `self` parameter; // only its child type is needed for member lookup. This // is the comptime construction used by // `testing.allocator_instance.allocator()`. fc_self_ty = fc_nav_ptr.child_type; if (!ipTagIsAnyStruct(ip->item_tags[fc_self_ty])) UNIMPLEMENTED("field_call: mutable Nav receiver not a struct"); fc_ns = ipStructNamespaceOrNone(ip, fc_self_ty); fc_has_self = true; fc_self = IP_INDEX_NONE; fc_self_is_nav_ptr = true; fc_self_nav_ptr = fc_obj; } else { // Dereference the const Nav pointer at its stored byte // offset. The result is either a container type used as a // namespace, or a projected comptime value used as the // method receiver // (`sizes.big_align.toByteUnits()`). uint32_t fc_value = pointerDeref(zcu, fc_obj, &fc_self_ty); if (fc_value == IP_INDEX_NONE) UNIMPLEMENTED("field_call: runtime load"); // `fc_self_ty` is the receiver pointer's child type, i.e. // upstream's `inner_ty` (src/Sema.zig), and // `fc_value` is the comptime result of upstream's // `analyzeLoad(raw_ptr)`. Upstream's // is_double_ptr test is applied to that type, // never to the loaded value; the branch below is the only // one that needs it, so it is computed here and read once. uint32_t fc_inner_child = ctSinglePointerChild(ip, fc_self_ty); if (ipTagIsAnyStruct(ip->item_tags[fc_value])) { fc_ns = ipStructNamespaceOrNone(ip, fc_value); fc_self_ty = IP_INDEX_NONE; } else if (ipTagIsAnyUnion(ip->item_tags[fc_value])) { fc_ns = ipUnionNamespaceOrNone(ip, fc_value); fc_self_ty = IP_INDEX_NONE; } else if (ipIsEnumType(ip, fc_value)) { // `EnumType.f(args)` used as a type-namespace call // (`mem.Alignment.fromByteUnits(...)` — Alignment is // `enum(math.Log2Int(usize))`): the member lives in // the enum's namespace; no self. Union type namespaces // use the same no-self path above. fc_ns = ipEnumNamespaceOrNone(ip, fc_value); fc_self_ty = IP_INDEX_NONE; } else if (ip->item_tags[fc_value] == IP_TAG_ENUM_TAG) { fc_ns = ipEnumNamespaceOrNone(ip, ipEnumTagType(ip, fc_value)); fc_has_self = true; fc_self = fc_value; fc_self_is_nav_ptr = true; fc_self_nav_ptr = fc_obj; } else if (valIsAggregate(ip, fc_value)) { uint32_t rty = ctAggType(ip, fc_value); if (!ipTagIsAnyStruct(ip->item_tags[rty])) UNIMPLEMENTED("field_call: Nav value not a struct"); fc_ns = ipStructNamespaceOrNone(ip, rty); fc_has_self = true; fc_self = fc_value; fc_self_is_nav_ptr = true; fc_self_nav_ptr = fc_obj; } else if (fc_inner_child != IP_INDEX_NONE) { // Upstream's double-pointer receiver // (`Sema.fieldCallBind`, src/Sema.zig): // `raw_ptr_ty` = `*const *Io.Threaded`, so // `inner_ty` = `*Io.Threaded` is itself a single // pointer, `is_double_ptr` is true and // upstream sets `concrete_ty = inner_ty.childType`, // `ptr_ty = inner_ty` and // `object_ptr = analyzeLoad(raw_ptr)` — // the auto-deref whose comptime half the previous // slice ported and which `pointerDeref` // has just performed into `fc_value`. // // The classification is upstream's own: it branches on // `inner_ty` — a type — not on the loaded value's // shape. zig1's sibling arms above happen to // classify the loaded value because for every one of // them the value's tag determines its type's kind (a // type value, an enum_tag, an aggregate); a loaded // Pointer value has no such correspondence, which is // why this branch reads `fc_self_ty` instead. // Equivalently: those arms are upstream's // `is_double_ptr == false` path with `concrete_ty == // inner_ty` and `object_ptr == raw_ptr`, and this one // is the `true` path. if (!ipTagIsAnyStruct(ip->item_tags[fc_inner_child])) UNIMPLEMENTED("field_call: double-pointer receiver " "pointee not a struct"); fc_ns = ipStructNamespaceOrNone(ip, fc_inner_child); fc_has_self = true; // No by-VALUE form exists without a second comptime // deref of `object_ptr` (upstream's // `analyzeLoad`), which is not modeled — the guard on // the by-value binding arm below keeps this sentinel // from ever being bound. fc_self = IP_INDEX_NONE; fc_self_is_nav_ptr = true; // upstream `object_ptr`: the loaded inner pointer. fc_self_nav_ptr = fc_value; // upstream `concrete_ty`: what the member search and // the self-parameter shape test both run against //. fc_self_ty = fc_inner_child; fc_double_ptr = true; } else { UNIMPLEMENTED( "field_call: receiver not a namespace/container"); } } } else if (fc_obj >= ZIR_REF_START_INDEX && fc_obj < ip->items_len && (ipIsPtrUav(ip, fc_obj) || ip->item_tags[fc_obj] == IP_TAG_PTR_COMPTIME_FIELD)) { // `analyzeLoad` -> `pointerDeref` of the receiver pointer. uint32_t fc_child = ctSinglePointerChild(ip, ipTypeOf(ip, fc_obj)); ComptimeLoadResult fc_r = loadComptimePtr(zcu, fc_obj); uint32_t fc_loaded = fc_r.tag == ZPT_LOAD_SUCCESS ? mutableValueIntern(zcu, &fc_r.success) : IP_INDEX_NONE; if (fc_loaded != IP_INDEX_NONE && ipTagIsAnyStruct(ip->item_tags[fc_loaded])) { fc_ns = ipStructNamespaceOrNone(ip, fc_loaded); fc_self_ty = IP_INDEX_NONE; } else if (fc_loaded != IP_INDEX_NONE && ipTagIsAnyUnion(ip->item_tags[fc_loaded])) { fc_ns = ipUnionNamespaceOrNone(ip, fc_loaded); fc_self_ty = IP_INDEX_NONE; } else if (fc_loaded != IP_INDEX_NONE && ipIsEnumType(ip, fc_loaded)) { fc_ns = ipEnumNamespaceOrNone(ip, fc_loaded); fc_self_ty = IP_INDEX_NONE; } else { if (fc_child != IP_INDEX_NONE && ipIsEnumType(ip, fc_child)) { if (fc_loaded == IP_INDEX_NONE || ip->item_tags[fc_loaded] != IP_TAG_ENUM_TAG || ipEnumTagType(ip, fc_loaded) != fc_child) UNIMPLEMENTED( "field_call: uav enum receiver cannot load value"); fc_ns = ipEnumNamespaceOrNone(ip, fc_child); fc_has_self = true; fc_self = fc_loaded; fc_self_is_nav_ptr = true; fc_self_nav_ptr = fc_obj; fc_self_ty = fc_child; } else if (fc_child != IP_INDEX_NONE && fc_child < ip->items_len && ip->item_tags[fc_child] == IP_TAG_TYPE_POINTER && IP_PTR_FLAGS_SIZE(ip->extra[ip->item_datas[fc_child] + 2]) == IP_PTR_SIZE_ONE) { uint32_t fc_pointee = ip->extra[ip->item_datas[fc_child]]; if (!ipTagIsAnyStruct(ip->item_tags[fc_pointee])) UNIMPLEMENTED( "field_call: uav double-pointer pointee not a struct"); if (fc_loaded == IP_INDEX_NONE || ipTypeOf(ip, fc_loaded) != fc_child) UNIMPLEMENTED( "field_call: uav double-pointer cannot load receiver"); fc_ns = ipStructNamespaceOrNone(ip, fc_pointee); fc_has_self = true; fc_self = IP_INDEX_NONE; fc_self_is_nav_ptr = true; fc_self_nav_ptr = fc_loaded; fc_self_ty = fc_pointee; fc_double_ptr = true; } else { if (fc_child == IP_INDEX_NONE || !ipTagIsAnyStruct(ip->item_tags[fc_child])) UNIMPLEMENTED( "field_call: uav receiver pointee not a struct"); fc_ns = ipStructNamespaceOrNone(ip, fc_child); fc_has_self = true; fc_self = IP_INDEX_NONE; if (fc_loaded != IP_INDEX_NONE) { if (ipTypeOf(ip, fc_loaded) == fc_child) fc_self = fc_loaded; } fc_self_is_nav_ptr = true; fc_self_nav_ptr = fc_obj; fc_self_ty = fc_child; } } } else if (semaRefIsInterned(fc_obj) && fc_obj >= ZIR_REF_START_INDEX && ip->item_tags[fc_obj] == IP_TAG_ENUM_TAG) { // Method call on an enum value receiver (`cpu.arch.isX86()`): // bind the enum_tag as `self`. Namespace = the enum's // namespace. uint32_t ety = ipEnumTagType(ip, fc_obj); fc_ns = ipEnumNamespaceOrNone(ip, ety); fc_has_self = true; fc_self = fc_obj; } else if (semaRefIsInterned(fc_obj) && fc_obj >= ZIR_REF_START_INDEX && valIsAggregate(ip, fc_obj)) { // Method call on a struct value receiver (`cpu.has(...)`): // bind the aggregate as `self`. Namespace = the struct's // namespace. uint32_t rty = ctAggType(ip, fc_obj); if (!ipTagIsAnyStruct(ip->item_tags[rty])) UNIMPLEMENTED("field_call: value not a struct"); fc_ns = ipStructNamespaceOrNone(ip, rty); fc_has_self = true; fc_self = fc_obj; } else if (semaRefIsInterned(fc_obj) && fc_obj >= ZIR_REF_START_INDEX && ip->item_tags[fc_obj] == IP_TAG_UNION_VALUE) { // fieldCallBind's `.@"union"` namespace lookup on a union // Value receiver, bound as `self`. fc_ns = ipUnionNamespaceOrNone(ip, ipTypeOf(ip, fc_obj)); fc_has_self = true; fc_self = fc_obj; } else if (semaRefIsInterned(fc_obj) && fc_obj >= ZIR_REF_START_INDEX && ipTagIsAnyStruct(ip->item_tags[fc_obj])) { // `T.f(args)` where the object resolves directly to a struct // Type used as a namespace (a generic-call result type, not a // `ptr_nav`). No self. fc_ns = ipStructNamespaceOrNone(ip, fc_obj); } else if (semaRefIsInterned(fc_obj) && fc_obj >= ZIR_REF_START_INDEX && ipTagIsAnyUnion(ip->item_tags[fc_obj])) { // Same direct type-namespace call as above, for union types. fc_ns = ipUnionNamespaceOrNone(ip, fc_obj); } else { UNIMPLEMENTED("field_call: unsupported receiver kind"); } if (fc_ns == IP_NAMESPACE_NONE) UNIMPLEMENTED("field_call: namespace has no ns"); uint32_t fc_name_ip = ipGetOrPutString(ip, fc_name, (uint32_t)strlen(fc_name)); // fieldCallBind's `find_field` struct arm before its decl lookup: a // function or function-pointer field of the concrete struct is called // directly, without self (finishFieldCallBind's analyzeLoad of the // field). A cell or double-pointer receiver is `analyzeLoad`ed first. uint32_t fc_field_fn = IP_INDEX_NONE; uint32_t fc_agg = fc_double_ptr ? IP_INDEX_NONE : fc_self; uint32_t fc_load_ptr = fc_double_ptr ? fc_self_nav_ptr : fc_has_self && ctIsCellPointerClassValue(ip, fc_self) ? fc_self : IP_INDEX_NONE; if (fc_load_ptr != IP_INDEX_NONE) { uint32_t fc_pty = fc_double_ptr ? fc_self_ty : ctCellPtrType(zcu, ip, fc_self); if (ipTagIsAnyStruct(ip->item_tags[fc_pty]) && ipStructFieldIndex(ip, fc_pty, fc_name_ip) != UINT32_MAX) { fc_agg = ctLoadPtr(zcu, ip, fc_load_ptr); } } if (fc_has_self && fc_agg != IP_INDEX_NONE && fc_agg < ip->items_len && valIsAggregate(ip, fc_agg) && ipTagIsAnyStruct(ip->item_tags[ctAggType(ip, fc_agg)])) { uint32_t fc_rty = ctAggType(ip, fc_agg); uint32_t fc_fi = ipStructFieldIndex(ip, fc_rty, fc_name_ip); if (fc_fi != UINT32_MAX) { fc_field_fn = valElemValue(ip, fc_agg, fc_fi, "Value.elemValue: comptime"); if (ipItemTagIs( ip, ipTypeOf(ip, fc_field_fn), IP_TAG_TYPE_POINTER)) fc_field_fn = pointerDeref(zcu, fc_field_fn, NULL); if (fc_field_fn == IP_INDEX_NONE) UNIMPLEMENTED("field_call: field is runtime memory"); fc_has_self = false; fc_self_is_nav_ptr = false; } } uint32_t fc_cnav = fc_field_fn != IP_INDEX_NONE ? IP_INDEX_NONE : ipNamespaceLookup(ip, fc_ns, fc_name_ip); if (fc_field_fn == IP_INDEX_NONE && fc_cnav == IP_INDEX_NONE) { // Only the aggregate, cell and double-pointer receivers run // find_field's struct arm above; any other receiver naming a struct // field is a callee this evaluator cannot bind yet. uint32_t fc_concrete = fc_self_ty; if (fc_has_self && fc_concrete == IP_INDEX_NONE) fc_concrete = ctIsCellPointerClassValue(ip, fc_self) ? ctCellPtrType(zcu, ip, fc_self) : ipTypeOf(ip, fc_self); if (!fc_has_self) failWithBadMemberAccess( zcu, ipGetNamespace(ip, fc_ns)->owner_type, fc_name_ip); if ((ipTagIsAnyStruct(ip->item_tags[fc_concrete]) && ipStructFieldIndex(ip, fc_concrete, fc_name_ip) != UINT32_MAX) || (ipTagIsAnyUnion(ip->item_tags[fc_concrete]) && ipUnionFieldIndex(ip, fc_concrete, fc_name_ip) != UINT32_MAX) || ztIsTuple(ip, fc_concrete)) UNIMPLEMENTED("fieldCallBind: field callee of this comptime " "receiver"); semaFailWithNoFieldOrMemberFn(zcu, fc_concrete, fc_name_ip); } // Resolve the member to its value, which is a `func_decl` — a // direct `pub fn` or an aliased fn const (`pub const f = // Generic(T).f;`, whose body folds a comptime call + field access // to a func_decl). The func's owner // nav (which ctAnalyzeComptimeCallTyped uses for its namespace // scope) is the fn's own nav, not the alias nav — recover it from // the decl. uint32_t fc_fty = IP_INDEX_NONE; uint32_t fc_fval; if (fc_field_fn != IP_INDEX_NONE) { fc_fval = fc_field_fn; fc_fty = ipTypeOf(ip, fc_fval); } else if (!fc_has_self) { // Sema.fieldVal's type-namespace member path obtains the Nav // reference before analyzeLoad. Preserve that observable // pointer-type/ptr_nav materialization even though this // comptime evaluator immediately consumes the function value. uint32_t fc_fptr = navDeclRefResolved(zcu, fc_cnav, NULL); fc_fval = pointerDeref(zcu, fc_fptr, &fc_fty); } else { fc_fval = zptEnsureNavValUpToDate(zcu, fc_cnav, &fc_fty); } if (!ipItemTagIs(ip, fc_fval, IP_TAG_FUNC_DECL)) UNIMPLEMENTED("field_call: member not a function"); fc_cnav = ipFuncDeclOwnerNav(ip, fc_fval); if (fc_self_is_nav_ptr) { if (fc_fty == IP_INDEX_NONE || fc_fty == IP_INDEX_GENERIC_POISON_TYPE || ipFuncTypeParamsLen(ip, fc_fty) == 0) UNIMPLEMENTED("field_call: Nav method has no self type"); uint32_t self_param_ty = ipFuncTypeParamType(ip, fc_fty, 0); // The two arms below are upstream's first-parameter dispatch // in `fieldCallBind`'s `found_nav` block: a pointer whose // child is `concrete_ty` takes `object_ptr` as arg0 // (src/Sema.zig), a first parameter equal to // `concrete_ty` takes `analyzeLoad(object_ptr)` //. if (self_param_ty == fc_self_ty) { // The projected initializer value is the by-value self. // upstream loads through `object_ptr`, on the // double-pointer path the loaded inner pointer. if (fc_double_ptr) fc_self = ctLoadPtr(zcu, ip, fc_self_nav_ptr); if (fc_self == IP_INDEX_NONE) UNIMPLEMENTED( "field_call: by-value self cannot load receiver"); } else if (ip->item_tags[self_param_ty] == IP_TAG_TYPE_POINTER && ip->extra[ip->item_datas[self_param_ty]] == fc_self_ty) { // A by-reference method receives the original projected // Nav pointer, retaining allocation identity and offset. // On the double-pointer path that pointer is the loaded // inner pointer (upstream's `object_ptr`), so `self` is // `*Io.Threaded` itself, not `*const *Io.Threaded`. fc_self = fc_self_nav_ptr; } else { UNIMPLEMENTED("field_call: unsupported Nav self type"); } } // Evaluate each argument body (comptime ops only) and resolve the // terminating break_inline operand. A method receiver is bound as // param 0 (`self`); explicit args follow. uint32_t self_off = fc_has_self ? 1u : 0u; uint32_t* fc_args = ARR_INIT(uint32_t, fc_args_len + 1u); if (fc_has_self) fc_args[0] = fc_self; // self bound by value (cell ptr / value) CtCallParamCtx fc_ctx; ctCallParamCtxInit(zcu, fc_cnav, fc_fty, &fc_ctx); if (fc_has_self) ctCallParamCtxBind(&fc_ctx, 0, fc_args[0]); for (uint32_t ai = 0; ai < fc_args_len; ai++) { uint32_t param_i = ai + self_off; const uint32_t* abody; uint32_t alen = semaCallArgBody(zir, fc_abase, fc_args_len, ai, &abody); // The arg's result-location type is threaded through the // field_call instruction itself (AstGen's ref-strategy). Seed // this call inst's slot with the callee's resolved param type // so // `&.{...}` -> `[N]Feature` resolves. Overwritten after the // loop. uint32_t provided_ty = ctCallProvidedParamType(zcu, fc_fty, param_i, &fc_ctx); values[inst] = provided_ty; uint32_t alast = abody[alen - 1]; if (alen > 1) { CtEvalStatus ast = ctEvalBody(sema, abody, alen - 1, ct_ret_val, ct_break_block, ct_break_val); if (ast != CT_EVAL_NORMAL) UNIMPLEMENTED("field_call: arg body control flow"); } uint32_t av = semaResolveRef( zir->inst_datas[alast].break_data.operand, values); // Coerce the arg to the callee's param type (`&.{...}` uav // pointer -> `[]const F` slice; enum literals -> enum tags). if (provided_ty != IP_INDEX_GENERIC_POISON_TYPE) av = ctCoerceToType(zcu, av, provided_ty); ctCallParamCtxBind(&fc_ctx, param_i, av); // An enum-literal arg for a generic param (`cpu.has(.wasm, // .simd128)` — `feature: @field(...).Feature`) is bound raw // and coerced inside the callee (its instantiated param type // is evaluated there). It is a real interned value, so it // needs no exemption from this guard — it has no sentinel // bits at all. Any other unresolved sentinel is // still a bug. if (!semaRefIsInterned(av)) UNIMPLEMENTED("field_call: argument not interned"); fc_args[param_i] = av; } ctCallParamCtxFree(&fc_ctx); values[inst] = ctAnalyzeComptimeCallTyped(zcu, fc_fval, fc_cnav, IP_INDEX_NONE, fc_args, fc_args_len + self_off, NULL); free(fc_args); } // (part of) Sema.zig:zirDeclLiteral // Sema.zirDeclLiteral: `.name` with a known result type (the // `.unsigned` argument of `@Int`, whose coerced type is // std.lang.Signedness). ZIR pl_node -> Field { lhs = // dest_type_ref, field_name_start }. Resolve the dest type, then: // an enum type yields the named field's enum_tag (upstream falls // back to fieldVal when the name is not a member decl); a struct // namespace yields the member decl's value (`.empty` == // `Set.empty`). Mirrors the runtime DECL_LITERAL handler, // generalized to a struct-namespace dest. static uint32_t ctEvalDeclLiteralUncoerced( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstField dl; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &dl); uint32_t dl_dest = semaResolveRef(dl.lhs, values); const char* dl_field = zirNullTerminatedString(zir, dl.field_name_start); uint32_t dl_field_str = ipGetOrPutString(ip, dl_field, (uint32_t)strlen(dl_field)); if (dl_dest == IP_INDEX_GENERIC_POISON_TYPE) { // A `.name` with a generic result type (`cpu.has(.wasm, // .simd128)` — `feature: @field(...).Feature`): defer as an // enum literal, coerced at the instantiated use // (`@intFromEnum`). Same interned value, same helper, as the // runtime evaluator's generic-poison decl-literal arm. values[inst] = ipGetEnumLiteral(ip, dl_field_str); return IP_INDEX_NONE; } if (!semaRefIsInterned(dl_dest) || (dl_dest < ZIR_REF_START_INDEX && dl_dest != ZIR_REF_ENUM_LITERAL_TYPE)) UNIMPLEMENTED("decl_literal: dest not a type"); const uint32_t dl_orig_dest = dl_dest; // analyzeDeclLiteral's peel loop. for (;;) { IpPtrInfo dl_ptr; if (ztIsZigTypeTag(ip, dl_dest, ZIG_TYPE_TAG_OPTIONAL)) dl_dest = ipOptionalChild(ip, dl_dest); else if (ipItemTagIs(ip, dl_dest, IP_TAG_TYPE_ERROR_UNION) || ipItemTagIs(ip, dl_dest, IP_TAG_TYPE_ANYERROR_UNION)) dl_dest = ipErrorUnionPayload(ip, dl_dest); else if (ipPtrInfo(ip, dl_dest, &dl_ptr) == IP_PTRI_OK && dl_ptr.size == IP_PTR_SIZE_ONE) dl_dest = dl_ptr.child; else break; } if (dl_dest == ZIR_REF_ENUM_LITERAL_TYPE) { // The peel loop's `.enum_literal` arm: a plain enum literal, coerced // back into `orig_ty` as the runtime evaluator does. values[inst] = ipGetEnumLiteral(ip, dl_field_str); return dl_orig_dest; } if (ipIsEnumType(ip, dl_dest)) { // Prefer a member decl of the enum's namespace (true decl // literal) over a field (Sema.zirDeclLiteral order). uint32_t dl_ns = ipEnumNamespaceOrNone(ip, dl_dest); if (dl_ns != IP_NAMESPACE_NONE) { uint32_t dnav = ipNamespaceLookup(ip, dl_ns, dl_field_str); if (dnav != IP_INDEX_NONE) { uint32_t dl_decl = navAnalyzeVal(zcu, dnav, NULL); values[inst] = dl_decl; return dl_orig_dest; } } uint32_t dl_tag = ipEnumTagFromFieldName(ip, dl_dest, dl_field_str); if (dl_tag == IP_INDEX_NONE) UNIMPLEMENTED("decl_literal: field not found"); values[inst] = dl_tag; return dl_orig_dest; } if (ipTagIsAnyStruct(ip->item_tags[dl_dest])) { uint32_t dl_ns = ipStructNamespaceOrNone(ip, dl_dest); if (dl_ns == IP_NAMESPACE_NONE) UNIMPLEMENTED("decl_literal: struct has no namespace"); uint32_t dnav = ipNamespaceLookup(ip, dl_ns, dl_field_str); if (dnav == IP_INDEX_NONE) failWithBadMemberAccess(zcu, dl_dest, dl_field_str); values[inst] = navAnalyzeVal(zcu, dnav, NULL); return dl_orig_dest; } if (ip->item_tags[dl_dest] == IP_TAG_TYPE_UNION) { // Sema.fieldVal's `.type` -> `.@"union"` arm (src/Sema.zig // ~25964): a member decl of the union's namespace wins // (namespaceLookupVal comes first; a decl and a field cannot // share a name — AstGen rejects "duplicate union member // name" — so the order is unobservable, mirrored anyway). uint32_t dl_ns = ipUnionNamespaceOrNone(ip, dl_dest); if (dl_ns != IP_NAMESPACE_NONE) { uint32_t dnav = ipNamespaceLookup(ip, dl_ns, dl_field_str); if (dnav != IP_INDEX_NONE) { values[inst] = navAnalyzeVal(zcu, dnav, NULL); return dl_orig_dest; } } // Else the tag-enum field: fieldVal yields the bare enum tag // (`pt.enumValueFieldIndex` on `unionTagType`, non-null only // for `.tagged` usage — Type.zig; an untagged union's // field name is failWithBadMemberAccess, a compile error). if (ipUnionTagUsage(ip, dl_dest) != IP_UNION_TAG_USAGE_TAGGED) failWithBadMemberAccess(zcu, dl_dest, dl_field_str); uint32_t dl_tag_enum = ztUnionTagTypeHypothetical(zcu, dl_dest); uint32_t dl_tag = ipEnumTagFromFieldName(ip, dl_tag_enum, dl_field_str); if (dl_tag == IP_INDEX_NONE) failWithBadMemberAccess(zcu, dl_dest, dl_field_str); if (zir->inst_tags[inst] == ZIR_INST_DECL_LITERAL_NO_COERCE) { // analyzeDeclLiteral without do_coerce returns the // uncoerced fieldVal result (the bare tag; the `.name()` // callee path). values[inst] = dl_tag; return dl_orig_dest; } // do_coerce: Sema.coerce -> coerceEnumToUnion (src/Sema.zig // ~29831). A one-possible-value payload type wraps as // `pt.unionValue(union_ty, tag, opv)`; only the void OPV is // modeled (`Mode.no_color`'s payload). A runtime-sized // payload without an init is a stock compile error // ("coercion from enum ... to union ... must initialize ... // field ..."). uint32_t dl_ufidx = ipUnionFieldIndex(ip, dl_dest, dl_field_str); if (dl_ufidx == UINT32_MAX) UNIMPLEMENTED("decl_literal: union field index"); // Force the deferred payload types before the shared rule // reads them, then state coerceEnumToUnion once. semaResolveUnionFieldTypeLazy(zcu, dl_dest, dl_ufidx); values[inst] = coerceEnumToUnion(ip, dl_dest, dl_tag); return dl_orig_dest; } UNIMPLEMENTED("decl_literal: unsupported dest type"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // Sema.zig:analyzeDeclLiteral // `"if (do_coerce) return sema.coerce(block, orig_ty, uncoerced_result, // src)"` (IP_INDEX_NONE: an enum literal, coerced by its consumer). static void ctEvalDeclLiteral( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { uint32_t orig_ty = ctEvalDeclLiteralUncoerced(zcu, zir, inst, values); if (orig_ty != IP_INDEX_NONE && zir->inst_tags[inst] == ZIR_INST_DECL_LITERAL && ipTypeOf(&zcu->intern_pool, values[inst]) != orig_ty) values[inst] = ctCoerceToType(zcu, values[inst], orig_ty); } // (part of) Sema.zig:zirStructInit // Sema.zirStructInit (comptime path): a struct delegates to // finishStructInit (gather/coerce fields, fill defaults, intern // the aggregate); a union resolves its one field and interns a // Key.Union value with the hypothetical tag. // ZIR pl_node -> StructInit { abs_node, src_line, src_column, // fields_len } + // trailing Item { field_type: Index, init: Ref } per field. The // struct type comes from the first field's // FieldType.container_type. // STRUCT_INIT_REF (`&.{...}`, e.g. `self.kvs = &.{ .keys = ..., ... }`) // is the same fold with is_ref=true: the // comptime aggregate is wrapped in a `*const T` uav pointer // (Sema.addConstantMaybeRef -> uavRef), mirroring the runtime // path's comptime arm and STRUCT_INIT_EMPTY_REF_RESULT's wrap. static void ctEvalStructInit(SemaCtx* sema, uint32_t inst) { Zcu* zcu = sema->zcu; const Zir* zir = sema->zir; uint32_t* values = sema->inst_values; InternPool* ip = &zcu->intern_pool; ZirInstTag tag = zir->inst_tags[inst]; ZirInstStructInit extra; uint32_t items_off = zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &extra); uint32_t fields_len = extra.fields_len; if (fields_len == 0) UNIMPLEMENTED("struct_init: empty struct_init"); ZirInstStructInitItem first; zirExtraData(zir, items_off, &first); ZirInstFieldType first_ft; zirExtraData(zir, zir->inst_datas[first.field_type].pl_node.payload_index, &first_ft); uint32_t result_ty = semaResolveRef(first_ft.container_type, values); if (!semaRefIsInterned(result_ty)) UNIMPLEMENTED("struct_init: result type not interned"); uint32_t struct_ty = ipOptEuBaseType(ip, result_ty); if (struct_ty == IP_INDEX_GENERIC_POISON_TYPE) { // `"orelse { // The type wasn't actually known, so treat // this as an anon struct init. return // sema.structInitAnon(block, src, inst, .typed_init, ...); // }"` if (tag == ZIR_INST_STRUCT_INIT_REF) UNIMPLEMENTED("struct_init_ref: of a generic poison type"); values[inst] = ctStructInitAnon(sema, inst, fields_len, items_off, true); return; } if (ipTagIsAnyUnion(ip->item_tags[struct_ty])) { if (struct_ty != result_ty) UNIMPLEMENTED("zirStructInit: wrapped union init"); // Sema.zirStructInit's union arm: a union initializer has one // field. Its field-type instruction has already demanded the // union layout, but resolve all deferred payload aliases here // as well before selecting the field, matching // ensureLayoutResolved(.init). A comptime union value always // carries the hypothetical tag, including `union {}` whose // runtime representation is intentionally untagged. if (fields_len != 1) UNIMPLEMENTED( "zirStructInit: union init expects exactly one field"); semaResolveAllUnionDeferred(zcu, struct_ty); ZirInstFieldType ft; zirExtraData( zir, zir->inst_datas[first.field_type].pl_node.payload_index, &ft); uint32_t init_ref = first.init; const char* field_name = zirNullTerminatedString(zir, ft.name_start); uint32_t field_name_ip = ipGetOrPutString(ip, field_name, (uint32_t)strlen(field_name)); uint32_t field_index = ipUnionFieldIndex(ip, struct_ty, field_name_ip); if (field_index == UINT32_MAX) UNIMPLEMENTED("zirStructInit: union init unknown field"); if (ipUnionIsPacked(ip, struct_ty)) { // zirStructInit's packed arm: `sema.bitCast(block, // resolved_ty, init_inst, src)` of the coerced init, which // at comptime is `bitCastVal`. uint32_t pu_fty = semaResolveUnionFieldTypeLazy(zcu, struct_ty, field_index); uint32_t pu_init = ctCoerceToType( zcu, semaResolveRef(init_ref, values), pu_fty); uint32_t pu_val = (!semaRefIsInterned(pu_init)) ? IP_INDEX_NONE : bitCastVal(zcu, pu_init, struct_ty); if (pu_val == IP_INDEX_NONE) UNIMPLEMENTED("zirStructInit: packed union init"); if (tag == ZIR_INST_STRUCT_INIT_REF) { uint32_t ptr_ty = ipGetPtrType(ip, result_ty, IP_PTR_FLAGS_CONST_ONE); pu_val = ipGetPtrUav(ip, ptr_ty, pu_val); } values[inst] = pu_val; return; } // zirStructInit creates the enum tag before coercing the // payload. Preserve that InternPool ordering. uint32_t tag_ty = ipUnionEnumTagType(ip, struct_ty); uint32_t tag_val = ipEnumTagFromFieldName(ip, tag_ty, field_name_ip); if (tag_val == IP_INDEX_NONE) UNIMPLEMENTED("zirStructInit: union init missing tag value"); uint32_t field_ty = semaResolveUnionFieldTypeLazy(zcu, struct_ty, field_index); if (field_ty == IP_INDEX_NONE) UNIMPLEMENTED("zirStructInit: union init unresolved field type"); uint32_t init_val = semaResolveRef(init_ref, values); uint32_t coerced = ctCoerceToType(zcu, init_val, field_ty); uint32_t union_val = ipGetUnionValue(ip, struct_ty, tag_val, coerced); union_val = ctCoerceToType(zcu, union_val, result_ty); if (tag == ZIR_INST_STRUCT_INIT_REF) { uint32_t ptr_ty = ipGetPtrType(ip, result_ty, IP_PTR_FLAGS_CONST_ONE); union_val = ipGetPtrUav(ip, ptr_ty, union_val); } values[inst] = union_val; return; } if (!ipTagIsAnyStruct(ip->item_tags[struct_ty])) UNIMPLEMENTED("struct_init: non-struct result type"); uint32_t nfields = ipStructFieldsLen(ip, struct_ty); uint32_t* elems = zig1Calloc(nfields ? nfields : 1, sizeof(uint32_t)); bool* seen = zig1Calloc(nfields ? nfields : 1, sizeof(bool)); for (uint32_t f = 0; f < fields_len; f++) { ZirInstStructInitItem item; zirExtraData(zir, items_off + f * 2, &item); uint32_t init_ref = item.init; ZirInstFieldType ft; zirExtraData( zir, zir->inst_datas[item.field_type].pl_node.payload_index, &ft); const char* fnm = zirNullTerminatedString(zir, ft.name_start); uint32_t fnm_ip = ipGetOrPutString(ip, fnm, (uint32_t)strlen(fnm)); uint32_t fidx = ipStructFieldIndex(ip, struct_ty, fnm_ip); if (fidx == UINT32_MAX) { free(elems); free(seen); UNIMPLEMENTED("struct_init: unknown field"); } uint32_t init_v = semaResolveRef(init_ref, values); uint32_t fty = ipStructFieldType(ip, struct_ty, fidx); elems[fidx] = ctCoerceToType(zcu, init_v, fty); seen[fidx] = true; } for (uint32_t f = 0; f < nfields; f++) { if (!seen[f]) { // Fill an omitted field from its declared default // (Sema.finishStructInit) — e.g. the packed hash-map // `Metadata{ .fingerprint = free }` leaves `used: u1 = 0` // to its default. The default body was recorded by // semaBodyStructType[Packed]; semaFieldDefaultValue folds // + field-type-coerces it in its declaring namespace. A // field with no recorded default is a genuine "missing // field". uint32_t dv = semaFieldDefaultValue(zcu, struct_ty, f); if (dv == IP_INDEX_NONE) { free(elems); free(seen); UNIMPLEMENTED("struct_init: missing field (no default)"); } elems[f] = dv; seen[f] = true; } } if (ipStructIsPacked(ip, struct_ty)) { // Packed struct: fold the backing integer scalar from the // per-field values at their LSB-first bit offsets, intern a // `.bitpack` (the HashMap `Metadata{...}` slot idiom). uint32_t bres = semaPackStructFields(zcu, struct_ty, elems, nfields); bres = ctCoerceToType(zcu, bres, result_ty); if (tag == ZIR_INST_STRUCT_INIT_REF) { uint32_t bpty = ipGetPtrType(ip, result_ty, IP_PTR_FLAGS_CONST_ONE); bres = ipGetPtrUav(ip, bpty, bres); } values[inst] = bres; free(elems); free(seen); return; } uint32_t si_res = ipGetAggregate(ip, struct_ty, elems, nfields); si_res = ctCoerceToType(zcu, si_res, result_ty); if (tag == ZIR_INST_STRUCT_INIT_REF) { uint32_t si_pty = ipGetPtrType(ip, result_ty, IP_PTR_FLAGS_CONST_ONE); si_res = ipGetPtrUav(ip, si_pty, si_res); } values[inst] = si_res; free(elems); free(seen); } // (part of) Sema.zig:zirArrayInitElemType // Sema.zirArrayInitElemType: the element type of the array/slice // being initialized. ZIR `.bin { lhs = indexable type, rhs = elem // index (struct field index for a tuple) }`. static void ctEvalArrayInitElemType( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; uint32_t cty = semaResolveRef(zir->inst_datas[inst].bin.lhs, values); uint32_t aiet_rhs = zir->inst_datas[inst].bin.rhs; if (!semaRefIsInterned(cty)) UNIMPLEMENTED("array_init_elem_type: operand not interned"); uint32_t elem_ty; IpArrayInfo aiet_seq; // `"else => indexable_ty.indexableElem(zcu),"` covers every // sequence with one arm, vectors included. if (ipArrayInfo(ip, cty, &aiet_seq) == IP_ARR_OK) elem_ty = aiet_seq.child; else if (ztIsSlice(ip, cty)) // slice.data = many-ptr type; many-ptr.data offset 0 = child. elem_ty = ip->extra[ip->item_datas[ip->item_datas[cty]]]; else if (ipTagIsAnyStruct(ip->item_tags[cty])) elem_ty = ipStructFieldType(ip, cty, aiet_rhs); else if (ztIsTuple(ip, cty)) // Tuple layout: [fields_len, types[fields_len], // values[fields_len]]; the rhs is the tuple field index. elem_ty = ip->extra[ip->item_datas[cty] + 1 + aiet_rhs]; else if (cty == IP_INDEX_GENERIC_POISON_TYPE) // Stock resolveTypeOrPoison returns null for a poison lhs, so // the whole instruction yields `.generic_poison_type` (an // `anytype`/inferred result location: element type unknown). elem_ty = IP_INDEX_GENERIC_POISON_TYPE; else UNIMPLEMENTED("array_init_elem_type: non-array container"); values[inst] = elem_ty; } // (part of) Sema.zig:zirArrayCat // Sema.zirArrayCat comptime path: gather both comptime-known // indexables, peer-resolve to the non-tuple element type, coerce // each element, intern `[lhs.len + rhs.len]T`, and return it by // reference when either operand was pointer-shaped // (addConstantMaybeRef). Modeled shapes are exactly the // compiler's accumulator family: default const slices / array // pointers, direct array aggregates, and tuple literals whose peer // operand supplies the element type. Runtime operands, sentinel // results, and non-default pointer attrs remain loud aborts. static void ctEvalArrayCat( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstBin cat; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &cat); uint32_t lhs_ref = cat.lhs; uint32_t rhs_ref = cat.rhs; uint32_t lhs = semaResolveRef(lhs_ref, values); uint32_t rhs = semaResolveRef(rhs_ref, values); if (!semaRefIsInterned(lhs) || !semaRefIsInterned(rhs)) UNIMPLEMENTED("array_cat: operand not interned"); uint32_t lhs_ty = ipTypeOf(ip, lhs); uint32_t rhs_ty = ipTypeOf(ip, rhs); // `"if (lhs_ty.isTuple(zcu) and rhs_ty.isTuple(zcu)) return // sema.analyzeTupleCat(...)"` if (ztIsTuple(ip, lhs_ty) && ztIsTuple(ip, rhs_ty)) { values[inst] = semaTupleCatComptime(ip, lhs, rhs); return; } CtArrayCatInfo lhs_info; CtArrayCatInfo rhs_info; if (!ctArrayCatOperandInfo(zcu, lhs, rhs_ty, &lhs_info) || !ctArrayCatOperandInfo(zcu, rhs, lhs_ty, &rhs_info)) UNIMPLEMENTED("array_cat: non-indexable operand"); // A tuple operand's elem_ty is its peer's (getArrayCatInfo). uint32_t cat_elem_ty[2] = { lhs_info.elem_ty, rhs_info.elem_ty }; uint32_t elem_ty = semaArrayCatResolvedElemType(zcu, cat_elem_ty); if (lhs_info.len > UINT32_MAX - rhs_info.len) UNIMPLEMENTED("array_cat: result too long"); uint32_t result_len = lhs_info.len + rhs_info.len; uint32_t cat_sentinel[2] = { lhs_info.sentinel, rhs_info.sentinel }; uint32_t result_sentinel = semaArrayCatResultSentinel(zcu, cat_sentinel, elem_ty); uint32_t arr_ty = ipGetArrayType(ip, result_len, elem_ty, result_sentinel); uint64_t storage_len64 = (uint64_t)result_len + (result_sentinel != IP_INDEX_NONE ? 1u : 0u); if (storage_len64 > UINT32_MAX) UNIMPLEMENTED("array_cat: result storage too long"); uint32_t storage_len = (uint32_t)storage_len64; uint32_t* elems = zig1Calloc(storage_len ? storage_len : 1, sizeof(uint32_t)); const CtArrayCatInfo* cat_info[2] = { &lhs_info, &rhs_info }; const uint32_t cat_val[2] = { lhs, rhs }; uint32_t out_i = 0; for (int side = 0; side < 2; side++) { const CtArrayCatInfo* ci = cat_info[side]; for (uint32_t k = 0; k < ci->len; k++, out_i++) { uint32_t src_elem = ci->is_tuple ? ctTupleFieldValue(ip, cat_val[side], k) : semaAggregateElemValueOrUndef(ip, ci->backing, ci->elem_ty, k, "array_cat: operand backing storage"); uint32_t elem = ctCoerceToType(zcu, src_elem, elem_ty); if (!ctValueConformsToType(ip, elem, elem_ty)) { free(elems); UNIMPLEMENTED("array_cat: element type mismatch"); } elems[out_i] = elem; } } if (result_sentinel != IP_INDEX_NONE) elems[out_i++] = result_sentinel; uint32_t agg = ipGetAggregate(ip, arr_ty, elems, out_i); free(elems); if (lhs_info.is_pointer || rhs_info.is_pointer) { uint32_t ptr_ty = ipGetPtrType(ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); values[inst] = ipGetPtrUav(ip, ptr_ty, agg); } else { values[inst] = agg; } } // (part of) Sema.zig:zirElemPtrLoad // Read an array element by index. ZIR pl_node -> Bin { lhs, rhs = index }. static void ctEvalElemPtrLoad( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstBin el; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &el); uint32_t el_obj = semaResolveRef(el.lhs, values); uint32_t el_idx = semaResolveRef(el.rhs, values); if (ipIsUndef(ip, el_idx)) { uint32_t elem_ty = ctIndexableElemType(zcu, ip, el_obj); if (elem_ty == IP_INDEX_NONE) UNIMPLEMENTED("elem_ptr_load: undef index type"); values[inst] = ipGetUndef(ip, elem_ty); return; } int64_t liv; bool lict; uint32_t lib; if (!ctReadInt(ip, el_idx, &liv, &lict, &lib) || liv < 0) UNIMPLEMENTED("elem_ptr_load: non-comptime index"); IpPtrInfo el_info; if (ctIsCellPointerClassValue(ip, el_obj)) { // A cell-slice view offsets the index by its window start; the // path itself addresses the backing array either way. CtCellPtr eld_v = ctCellPtrResolveDescriptor(zcu, ip, el_obj); const CtCellPtr* eld = &eld_v; // Mirror the ELEM_PTR arm's window bound: reading past // win_len would silently walk into the backing array // (stock compile-errors on such an index). if (eld->is_slice) { if ((uint64_t)liv >= ctCellPtrViewExtent(ip, eld)) UNIMPLEMENTED("elem_ptr_load: slice index out of window"); } uint32_t eoff = eld->is_slice ? eld->win_start : 0; uint32_t agg = ctCellPtrLoad(zcu, ip, el_obj); if ((uint64_t)eoff + (uint64_t)liv > UINT32_MAX) UNIMPLEMENTED("elem_ptr_load: index overflow"); uint32_t absolute = eoff + (uint32_t)liv; // A retyped (bit or reinterpreted) pointer's pointee is its // own type's child, not the location its path addresses. uint32_t backing_ty = eld->bit_ptr_ty != 0 ? ip->extra[ip->item_datas[eld->bit_ptr_ty]] : ctCellPtrLevelType(zcu, ip, eld, eld->depth); IpArrayInfo backing; if (ipArrayInfo(ip, backing_ty, &backing) == IP_ARR_OK && !backing.is_vector && absolute == backing.len && backing.sentinel != IP_INDEX_NONE) { values[inst] = backing.sentinel; } else { values[inst] = ctIndexAnyValue(zcu, ip, agg, absolute); } } else if (el_obj < ip->items_len && (valIsAggregate(ip, el_obj) || ip->item_tags[el_obj] == IP_TAG_PTR_SLICE || ipPtrInfo(ip, ipTypeOf(ip, el_obj), &el_info) == IP_PTRI_OK)) { // zirElemPtrLoad's `pointerDeref` then `elemVal` on a // one-pointer; a runtime pointee is `elemPtr` + // `analyzeLoad` of the pointer itself, as is a slice. IpPtrInfo el_ref; uint32_t el_ty; if (ipPtrInfo(ip, ipTypeOf(ip, el_obj), &el_ref) == IP_PTRI_OK && el_ref.size == IP_PTR_SIZE_ONE) { uint32_t inner = semaAnalyzeLoadComptime(zcu, el_obj, &el_ty); if (inner != IP_INDEX_NONE) { values[inst] = ipItemTagIs(ip, el_ref.child, IP_TAG_TYPE_POINTER) ? semaElemValComptime(zcu, inner, (uint64_t)liv, &el_ty) : ctIndexAnyValue(zcu, ip, inner, (uint32_t)liv); if (values[inst] == IP_INDEX_NONE) UNIMPLEMENTED("elem_ptr_load: runtime load"); return; } } values[inst] = ctIndexAnyValue(zcu, ip, el_obj, (uint32_t)liv); } else { UNIMPLEMENTED("elem_ptr_load: unsupported object"); } } // (part of) Sema.zig:zirElemPtr // Address of an array element of a comptime cell (`set.ints[i]`): // extend the cell pointer's access path by the (comptime) element // index (Sema.elemPtr comptime). ZIR pl_node -> Bin { lhs = ptr, // rhs = index }. static void ctEvalElemPtr( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstBin e; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &e); uint32_t e_ptr = semaResolveRef(e.lhs, values); uint32_t e_idx = semaResolveRef(e.rhs, values); int64_t ei_v; bool ei_ct; uint32_t ei_b; if (!ctReadInt(ip, e_idx, &ei_v, &ei_ct, &ei_b) || ei_v < 0 || (uint64_t)ei_v > UINT32_MAX) UNIMPLEMENTED("elem_ptr: non-comptime index"); // Sema.elemPtr on an interned pointer: a one-possible-value // pointee, a slice, or a single pointer to a pointer. uint32_t ep_ty; IpPtrInfo ep_info; if (semaOpvPtrInfo(ip, e_ptr, NULL, NULL) || (e_ptr < ip->items_len && (ip->item_tags[e_ptr] == IP_TAG_PTR_SLICE || (ipPtrInfo(ip, ipTypeOf(ip, e_ptr), &ep_info) == IP_PTRI_OK && ep_info.size == IP_PTR_SIZE_ONE && (ipItemTagIs(ip, ep_info.child, IP_TAG_TYPE_POINTER) || ipItemTagIs( ip, ep_info.child, IP_TAG_TYPE_SLICE)))))) { values[inst] = semaElemPtrComptime(zcu, e_ptr, (uint64_t)ei_v, false, &ep_ty); if (values[inst] == IP_INDEX_NONE) UNIMPLEMENTED("elem_ptr: runtime load"); return; } if (semaRefIsInterned(e_ptr) && e_ptr >= ZIR_REF_START_INDEX && valIsAggregate(ip, e_ptr)) { // Element "pointer" into an immutable comptime value // (`self.kvs.keys[i]` — keys is a uav many-ptr into the // interned final_keys array): fold-only, the element value // stands for its pointer, mirroring the ELEM_PTR_LOAD // immutable arms. Mutation attempts on it would hit the // store-consumer's loud non-cell abort. values[inst] = ctIndexAnyValue(zcu, ip, e_ptr, (uint32_t)ei_v); return; } if (!ctIsCellPointerClassValue(ip, e_ptr)) { // Sema.elemPtr on any other interned pointer (a Nav's // address, an element or field pointer into one). if (e_ptr >= ip->items_len || ipPtrInfo(ip, ipTypeOf(ip, e_ptr), &ep_info) != IP_PTRI_OK) UNIMPLEMENTED("elem_ptr: on non-cell pointer"); values[inst] = semaElemPtrComptime(zcu, e_ptr, (uint64_t)ei_v, false, &ep_ty); if (values[inst] == IP_INDEX_NONE) UNIMPLEMENTED("elem_ptr: runtime load"); return; } values[inst] = semaComptimeCellTupleElemPtr(zcu, e_ptr, (uint64_t)ei_v); if (values[inst] != IP_INDEX_NONE) return; CtCellPtr d = ctCellPtrResolveDescriptor(zcu, ip, e_ptr); ep_ty = ctCellPtrDescType(zcu, ip, &d); // The write-through-pointer `for` capture `for (caps[0..N]) // |*item|` reaches this arm: `item` is elem_ptr(cell-slice, i). uint32_t ep_depth_before = d.depth; ctCellPtrAppendElem(ip, &d, (uint32_t)ei_v); // ctCellPtrAppendElem's `is_slice` unwrap normalized `d` to // a plain (non-windowed) depth-0 element access when it fired, // so a cell-slice-view base (`buf[0..N][i]`) is real-mint // eligible exactly like a bare array root (`buf[i]`). values[inst] = ctCellPtrMintAfterElemHop( zcu, ip, &d, ep_depth_before, (uint32_t)ei_v); } // (part of) Sema.zig:zirForLen // Sema.zirForLen: the loop length from the input operands. ZIR // pl_node -> MultiOp { operands_len, [indexable, range_end] pairs // }. Comptime slice/array/tuple input: the length is // comptime-known. static void ctEvalForLen( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstMultiOp mop; const uint32_t* args = zirBodySlice(zir, zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &mop)); uint32_t npairs = mop.operands_len / 2; uint32_t arg0 = ZIR_REF_NONE; uint32_t arg1 = ZIR_REF_NONE; for (uint32_t pi = 0; pi < npairs; pi++) { uint32_t c0 = args[pi * 2]; if (c0 != ZIR_REF_NONE) { arg0 = c0; arg1 = args[pi * 2 + 1]; break; } } if (arg0 == ZIR_REF_NONE) UNIMPLEMENTED("for_len: unbounded"); for (uint32_t pi = 0; pi < npairs; pi++) { uint32_t c0 = args[pi * 2]; uint32_t c0v = c0 != ZIR_REF_NONE ? semaResolveRef(c0, values) : INST_VALUE_UNSET; if (c0 != ZIR_REF_NONE && args[pi * 2 + 1] == ZIR_REF_NONE) semaForLenInternLen( ip, c0v < ip->items_len ? ipTypeOf(ip, c0v) : IP_INDEX_NONE); } if (arg1 != ZIR_REF_NONE) { // A range input `for (start..end) |k|` (pdqsort's `for // (0..left_split)`): the pair carries both refs. // Sema.zirForLen range arm: len = end - start (start==0 fast // path returns end, numerically identical). Comptime int // operands only. uint32_t sv_ref = semaResolveRef(arg0, values); uint32_t ev_ref = semaResolveRef(arg1, values); if (!semaRefIsInterned(sv_ref) || !semaRefIsInterned(ev_ref)) UNIMPLEMENTED("for_len: range operand not interned"); int64_t sv, ev; bool sct, ect; uint32_t sb, eb; if (!ctReadInt(ip, sv_ref, &sv, &sct, &sb) || !ctReadInt(ip, ev_ref, &ev, &ect, &eb)) UNIMPLEMENTED("for_len: range operand not an integer"); if (ev < sv) UNIMPLEMENTED("for_len: range end < start"); values[inst] = ipGetIntUsize(ip, (uint32_t)(ev - sv)); return; } uint32_t obj = semaResolveRef(arg0, values); uint32_t flen; if (ctIsCellPointerClassValue(ip, obj) && ctCellPtrResolveDescriptor(zcu, ip, obj).is_slice) { // `for (caps[0..N], 0..)` over a comptime cell-slice view: the // loop bound is the window length. values[inst] = ipGetIntUsize( ip, ctCellPtrResolveDescriptor(zcu, ip, obj).win_len); return; } if (ctIsCellPointerClassValue(ip, obj)) { // `for (&out, 0..)` over a comptime `var [N]T` or `var` tuple: the // loop bound is the pointee's length, as Sema.zirForLen's `.len` reads // it. if (!ctIndexableTypeLen(ip, ctCellPtrType(zcu, ip, obj), &flen)) UNIMPLEMENTED( "for_len: cell pointer not to an array, vector or tuple"); values[inst] = ipGetIntUsize(ip, flen); return; } if (!semaRefIsInterned(obj)) UNIMPLEMENTED("for_len: operand not interned"); uint32_t opv_child; if (semaOpvRootPtrInfo(ip, obj, &opv_child, NULL)) { if (!ctIndexableTypeLen(ip, opv_child, &flen)) UNIMPLEMENTED( "for_len: OPV pointer not to an array, vector or tuple"); } else if (ip->item_tags[obj] == IP_TAG_PTR_SLICE) { uint32_t len_val = ip->extra[ip->item_datas[obj] + 2]; flen = ip->item_datas[len_val]; // int_usize value } else if (ipTypeOf(ip, obj) < ip->items_len && ip->item_tags[ipTypeOf(ip, obj)] == IP_TAG_TYPE_POINTER) { // A comptime pointer-to-array value at any base address. // Sema.zirForLen asks fieldVal for `.len` on the pointer // indexable, which reads the child array type's length; the // backing value/allocation contents are irrelevant to `.len`. IpPtrInfo ptr_info; if (ipPtrInfo(ip, ipTypeOf(ip, obj), &ptr_info) != IP_PTRI_OK) UNIMPLEMENTED("for_len: pointer bad type"); if (ptr_info.size != IP_PTR_SIZE_ONE) UNIMPLEMENTED("for_len: pointer without array length"); if (!ctIndexableTypeLen(ip, ptr_info.child, &flen)) UNIMPLEMENTED("for_len: pointer non-array child"); } else if (ip->item_tags[obj] == IP_TAG_ONLY_POSSIBLE_VALUE) { if (!ctIndexableTypeLen(ip, ip->item_datas[obj], &flen)) UNIMPLEMENTED("for_len: unsupported opv indexable"); } else if (valIsAggregate(ip, obj)) { flen = ctAggTypeLen(ip, ctAggType(ip, obj)); } else { UNIMPLEMENTED("for_len: unsupported indexable"); } values[inst] = ipGetIntUsize(ip, flen); } // (part of) Sema.zig:zirSliceSentinel static void ctEvalSliceSentinel( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstSliceSentinel sl; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &sl); uint32_t lhs = ctSliceOperandLoadCellPtr(zcu, ip, semaResolveRef(sl.lhs, values)); if (ctSliceOperandIsInternedPtr(ip, lhs)) { values[inst] = ctAnalyzeSliceRefs( zcu, lhs, sl.start, sl.end, sl.sentinel, false, values); return; } if (!ctIsCellPointerClassValue(ip, lhs) || !ctCellPtrIsSliceableView(zcu, ip, lhs)) UNIMPLEMENTED("slice_sentinel: non-cell lhs"); int64_t start_v; bool start_ct; uint32_t start_bits; uint32_t start = semaResolveRef(sl.start, values); if (!ctReadInt(ip, start, &start_v, &start_ct, &start_bits) || start_v < 0 || (uint64_t)start_v > UINT32_MAX) UNIMPLEMENTED("slice_sentinel: invalid start"); CtCellPtr d = ctCellPtrResolveDescriptor(zcu, ip, lhs); uint32_t bound; if (d.is_slice) { bound = d.win_len; } else { uint32_t arr_ty = ctCellPtrLevelType(zcu, ip, &d, d.depth); IpArrayInfo array; if (ipArrayInfo(ip, arr_ty, &array) != IP_ARR_OK || array.is_vector) UNIMPLEMENTED("slice_sentinel: non-array lhs"); bound = array.len; } uint32_t end; if (sl.end == ZIR_REF_NONE) { end = bound; } else { int64_t end_v; bool end_ct; uint32_t end_bits; uint32_t end_ip = semaResolveRef(sl.end, values); if (!ctReadInt(ip, end_ip, &end_v, &end_ct, &end_bits) || end_v < 0 || (uint64_t)end_v > UINT32_MAX) UNIMPLEMENTED("slice_sentinel: invalid end"); end = (uint32_t)end_v; } if ((uint32_t)start_v > end) COMPILE_ERROR_UPSTREAM("slice: sentinel start exceeds end"); uint32_t sentinel = semaResolveRef(sl.sentinel, values); values[inst] = ctCellSliceMakeExplicit(zcu, ip, lhs, (uint32_t)start_v, end - (uint32_t)start_v, IP_INDEX_NONE, sentinel); } // (part of) Sema.zig:zirSliceStart // Sema.zirSliceStart -> analyzeSlice, comptime `slice[start..]` // (open end): a subslice from `start` to the slice length. ZIR // pl_node -> SliceStart { lhs, start }. Only a comptime slice // value sliced at the head (start == 0, `slice[0..]` == the whole // slice) is modeled for existing slice values. Direct comptime // UAV one-pointers to arrays use upstream's pointer-to-array path: // retype the backing pointer to the `[len - start]T` tail and // advance its byte offset by start*@sizeOf(T). static void ctEvalSliceStart( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstSliceStart ss; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &ss); uint32_t ss_lhs = ctSliceOperandLoadCellPtr(zcu, ip, semaResolveRef(ss.lhs, values)); if (ctSliceOperandIsInternedPtr(ip, ss_lhs)) { values[inst] = ctAnalyzeSliceRefs( zcu, ss_lhs, ss.start, ZIR_REF_NONE, ZIR_REF_NONE, false, values); return; } uint32_t ss_start = semaResolveRef(ss.start, values); int64_t ss_sv; bool ss_sc; uint32_t ss_sb; if (!ctReadInt(ip, ss_start, &ss_sv, &ss_sc, &ss_sb)) UNIMPLEMENTED("slice_start: non-comptime start"); if (ss_sv < 0) UNIMPLEMENTED("slice_start: negative start"); // A comptime var's slice (or an inferred-alloc placeholder) is // sub-sliced as a cell-slice view aliasing the var. // A comptime one-pointer at any base address (a comptime var's // too) is analyzeSlice's pointer arm below. bool ss_one = ss_lhs < ip->items_len && ip->item_tags[ss_lhs] != IP_TAG_PTR_SLICE; if (!ss_one && ctIsCellPointerClassValue(ip, ss_lhs) && ctCellPtrIsSliceableView(zcu, ip, ss_lhs)) { CtCellPtr ssd = ctCellPtrResolveDescriptor(zcu, ip, ss_lhs); uint32_t ss_bound = ssd.is_slice ? ssd.win_len : ctAggTypeLen(ip, ctCellPtrLevelType(zcu, ip, &ssd, ssd.depth)); if ((uint32_t)ss_sv > ss_bound) UNIMPLEMENTED("slice_start: start out of range"); values[inst] = ctCellSliceMake(zcu, ip, ss_lhs, (uint32_t)ss_sv, ss_bound - (uint32_t)ss_sv, IP_INDEX_NONE); return; } if (!ss_one && ctIsCellPointerClassValue(ip, ss_lhs)) ss_lhs = ctCellPtrLoad(zcu, ip, ss_lhs); slice_start_reprocess_value: // Post-LOAD second gate: the load above can yield a real // `ptr_slice` directly (a cell that holds a slice loads to // one). The gate is // class-based via ctCellPtrIsSliceableView, so it admits any // loaded cell-pointer-class value whose chain reaches an array // (a slice window or a plain array), wider than "a loaded cell // that holds a slice"; sub-slice it exactly like the gate above, // mirroring SLICE_END's own post-load second gate (upstream // funnels both instructions through analyzeSlice; `start..` and // `start..end` differ only in how the end is derived). if (ctIsCellPointerClassValue(ip, ss_lhs) && (!semaRefIsInterned(ss_lhs) || ip->item_tags[ss_lhs] == IP_TAG_PTR_SLICE) && ctCellPtrIsSliceableView(zcu, ip, ss_lhs)) { CtCellPtr lsd = ctCellPtrResolveDescriptor(zcu, ip, ss_lhs); uint32_t ls_bound = lsd.is_slice ? lsd.win_len : ctAggTypeLen(ip, ctCellPtrLevelType(zcu, ip, &lsd, lsd.depth)); if ((uint32_t)ss_sv > ls_bound) UNIMPLEMENTED("slice_start: start out of range"); values[inst] = ctCellSliceMake(zcu, ip, ss_lhs, (uint32_t)ss_sv, ls_bound - (uint32_t)ss_sv, IP_INDEX_NONE); return; } if (ss_lhs < ip->items_len && ip->item_tags[ss_lhs] != IP_TAG_PTR_SLICE) { // analyzeSlice on a comptime pointer at any base address. IpPtrInfo ptr_info; if (ipPtrInfo(ip, ipTypeOf(ip, ss_lhs), &ptr_info) != IP_PTRI_OK) UNIMPLEMENTED("slice_start: operand not a pointer"); if (ptr_info.size != IP_PTR_SIZE_ONE) UNIMPLEMENTED("slice_start: ptr_uav not one pointer"); if (ip->item_tags[ptr_info.child] == IP_TAG_TYPE_POINTER || ztIsSlice(ip, ptr_info.child)) { // analyzeSlice's `ptr_ptr`: load the sliced operand. uint32_t ss_loaded_ty; ss_lhs = semaAnalyzeLoadComptime(zcu, ss_lhs, &ss_loaded_ty); if (ss_lhs == IP_INDEX_NONE) UNIMPLEMENTED("slice_start: runtime load"); goto slice_start_reprocess_value; } if (ptr_info.packed_offset != 0) UNIMPLEMENTED("slice_start: ptr_uav packed pointer"); uint32_t arr_len, arr_child, arr_sentinel; if (!ztArrayTypeInfo( ip, ptr_info.child, &arr_len, &arr_child, &arr_sentinel)) UNIMPLEMENTED("slice_start: ptr_uav child not array"); if ((uint64_t)ss_sv > arr_len) UNIMPLEMENTED("slice_start: ptr_uav start out of range"); uint32_t ss_arr_ty = ipGetArrayType( ip, arr_len - (uint32_t)ss_sv, arr_child, arr_sentinel); values[inst] = semaPtrElemCoerced(zcu, ss_lhs, (uint32_t)ss_sv, ipGetPtrType(ip, ss_arr_ty, ptr_info.flags)); return; } if (!semaRefIsInterned(ss_lhs) || ip->item_tags[ss_lhs] != IP_TAG_PTR_SLICE) UNIMPLEMENTED("slice_start: non-slice operand"); uint32_t ss_old_len_val = ipPtrSliceLen(ip, ss_lhs); int64_t ss_old_len; bool ss_old_len_ct; uint32_t ss_old_len_bits; if (!ctReadInt( ip, ss_old_len_val, &ss_old_len, &ss_old_len_ct, &ss_old_len_bits) || ss_old_len < 0 || (uint64_t)ss_old_len > UINT32_MAX) UNIMPLEMENTED("slice_start: unreadable slice length"); if (ss_sv > ss_old_len) UNIMPLEMENTED("slice_start: start out of slice range"); values[inst] = ctSliceLengthDirectView(zcu, ip, ss_lhs, (uint32_t)ss_sv, (uint32_t)(ss_old_len - ss_sv), IP_INDEX_NONE); } // (part of) Sema.zig:zirSliceEnd // Sema.zirSliceEnd -> analyzeSlice, comptime bounded // `slice[start..end]`. ZIR pl_node -> SliceEnd { lhs, start, end // }. For comptime UAV-backed arrays, mirror analyzeSlice's // comptime-new-len arm: pointer arithmetic advances the backing // UAV byte offset by start*@sizeOf(elem), and the result type is a // pointer to `[end-start]T` rather than a slice. static void ctEvalSliceEnd( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstSliceEnd se; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &se); uint32_t se_lhs = ctSliceOperandLoadCellPtr(zcu, ip, semaResolveRef(se.lhs, values)); if (ctSliceOperandIsInternedPtr(ip, se_lhs)) { values[inst] = ctAnalyzeSliceRefs( zcu, se_lhs, se.start, se.end, ZIR_REF_NONE, false, values); return; } uint32_t se_start = semaResolveRef(se.start, values); uint32_t se_end = semaResolveRef(se.end, values); int64_t sv, ev; bool sc, ec; uint32_t sb, eb; if (!ctReadInt(ip, se_start, &sv, &sc, &sb)) UNIMPLEMENTED("slice_end: non-comptime start"); if (!ctReadInt(ip, se_end, &ev, &ec, &eb) || ev < 0) UNIMPLEMENTED("slice_end: non-comptime end"); if (sv < 0) UNIMPLEMENTED("slice_end: negative start"); // A cell pointer whose location holds an array (or is already a // cell-slice) -> a comptime cell-slice view `[start, end)` that // aliases the mutable cell (write-through-pointer `for` captures). Element // writes rebuild the cell. Distinct from the immutable UAV PTR_SLICE path // below. Both gates are class-based: the load above can yield a // real `ptr_slice` directly (a cell that holds a slice loads to // one), and via ctCellPtrIsSliceableView they admit any // cell-pointer-class value whose chain reaches an array, wider // than only one that holds a slice. if (ctIsCellPointerClassValue(ip, se_lhs) && ctCellPtrIsSliceableView(zcu, ip, se_lhs)) { if (ev < sv) UNIMPLEMENTED("slice_end: end < start"); uint32_t se_len = (uint32_t)(ev - sv); uint32_t se_view = ctCellSliceMake( zcu, ip, se_lhs, (uint32_t)sv, se_len, IP_INDEX_NONE); values[inst] = ctSliceLengthRemintView(ip, se_view, se_len); return; } if (ctIsCellPointerClassValue(ip, se_lhs)) se_lhs = ctCellPtrLoad(zcu, ip, se_lhs); if (ctIsCellPointerClassValue(ip, se_lhs) && ctCellPtrIsSliceableView(zcu, ip, se_lhs)) { if (ev < sv) UNIMPLEMENTED("slice_end: end < start"); uint32_t se_len = (uint32_t)(ev - sv); uint32_t se_view = ctCellSliceMake( zcu, ip, se_lhs, (uint32_t)sv, se_len, IP_INDEX_NONE); values[inst] = ctSliceLengthRemintView(ip, se_view, se_len); return; } slice_end_reprocess_value: if (ctIsCellPointerClassValue(ip, se_lhs) && ctCellPtrIsSliceableView(zcu, ip, se_lhs)) { if (ev < sv) UNIMPLEMENTED("slice_end: end < start"); uint32_t se_len = (uint32_t)(ev - sv); uint32_t se_view = ctCellSliceMake( zcu, ip, se_lhs, (uint32_t)sv, se_len, IP_INDEX_NONE); values[inst] = ctSliceLengthRemintView(ip, se_view, se_len); return; } if (se_lhs < ip->items_len && ip->item_tags[se_lhs] != IP_TAG_PTR_SLICE && !ctIsCellPointerClassValue(ip, se_lhs)) { // analyzeSlice on a comptime pointer at any base address. uint32_t se_ptr_ty = ipTypeOf(ip, se_lhs); if (se_ptr_ty >= ip->items_len || ip->item_tags[se_ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("slice_end: operand not a pointer"); bool se_uav = ipIsPtrUav(ip, se_lhs); uint64_t se_base_offset = se_uav ? ipPtrUavByteOffset(ip, se_lhs) : 0; uint32_t backing = se_uav ? ipPtrUavValue(ip, se_lhs) : IP_INDEX_NONE; uint32_t ptd = ip->item_datas[se_ptr_ty]; uint32_t ptr_flags = ip->extra[ptd + IP_TYPE_POINTER_FLAGS]; uint32_t ptr_size = IP_PTR_FLAGS_SIZE(ptr_flags); if (ip->extra[ptd + IP_TYPE_POINTER_PACKED_OFFSET] != 0) UNIMPLEMENTED("slice_end: ptr_uav packed pointer"); if (ptr_size == IP_PTR_SIZE_ONE && (ip->item_tags[ip->extra[ptd + IP_TYPE_POINTER_CHILD]] == IP_TAG_TYPE_POINTER || ztIsSlice(ip, ip->extra[ptd + IP_TYPE_POINTER_CHILD]))) { // analyzeSlice's `ptr_ptr`: load the sliced operand. uint32_t se_loaded_ty; se_lhs = semaAnalyzeLoadComptime(zcu, se_lhs, &se_loaded_ty); if (se_lhs == IP_INDEX_NONE) UNIMPLEMENTED("slice_end: runtime load"); goto slice_end_reprocess_value; } uint32_t arr_len, arr_child, arr_sentinel; uint32_t se_bound; uint32_t se_result_sentinel = IP_INDEX_NONE; uint32_t se_many_ty; if (ptr_size == IP_PTR_SIZE_MANY) { if (!se_uav || se_base_offset != 0) UNIMPLEMENTED("slice_end: offset ptr_uav operand"); if (!ztArrayTypeInfo(ip, ipTypeOf(ip, backing), &arr_len, &arr_child, &arr_sentinel)) UNIMPLEMENTED("slice_end: ptr_uav backing not array"); if (arr_child != ip->extra[ptd + IP_TYPE_POINTER_CHILD]) UNIMPLEMENTED("slice_end: ptr_uav child mismatch"); uint32_t ptr_sentinel = ip->extra[ptd + IP_TYPE_POINTER_SENTINEL]; if (ptr_sentinel != IP_INDEX_NONE && ptr_sentinel != arr_sentinel) UNIMPLEMENTED("slice_end: ptr_uav sentinel mismatch"); se_many_ty = se_ptr_ty; se_bound = arr_len; } else if (ptr_size == IP_PTR_SIZE_ONE) { if (!ztArrayTypeInfo(ip, ip->extra[ptd + IP_TYPE_POINTER_CHILD], &arr_len, &arr_child, &arr_sentinel)) UNIMPLEMENTED("slice_end: ptr_uav child not array"); se_bound = arr_len; if (arr_sentinel != IP_INDEX_NONE) { if (ev == (int64_t)arr_len) se_result_sentinel = arr_sentinel; if (arr_len == UINT32_MAX) UNIMPLEMENTED("slice_end: ptr_uav wide sentinel bound"); se_bound = arr_len + 1u; } se_many_ty = ipGetPtrType(ip, arr_child, (ptr_flags & ~3u) | 1u); } else { UNIMPLEMENTED("slice_end: ptr_uav unsupported pointer size"); } if (ev > (int64_t)se_bound) UNIMPLEMENTED("slice_end: ptr_uav end out of range"); if (ev < sv) UNIMPLEMENTED("slice_end: ptr_uav end < start"); uint64_t se_new_len64 = (uint64_t)(ev - sv); if (se_new_len64 > UINT32_MAX) UNIMPLEMENTED("slice_end: ptr_uav wide result"); uint32_t se_elem_ty = ip->extra[ip->item_datas[se_many_ty]]; uint32_t se_arr_ty = ipGetArrayType( ip, (uint32_t)se_new_len64, se_elem_ty, se_result_sentinel); uint32_t se_many_flags = ip->extra[ip->item_datas[se_many_ty] + 2]; uint32_t se_ret_ty = ipGetPtrType(ip, se_arr_ty, se_many_flags & ~3u); values[inst] = semaPtrElemCoerced(zcu, se_lhs, (uint32_t)sv, se_ret_ty); return; } if (ctIsCellPointerClassValue(ip, se_lhs) && ctCellPtrIsSliceableView(zcu, ip, se_lhs)) { if (ev < sv) UNIMPLEMENTED("slice_end: end < start"); uint32_t se_len = (uint32_t)(ev - sv); uint32_t se_view = ctCellSliceMake( zcu, ip, se_lhs, (uint32_t)sv, se_len, IP_INDEX_NONE); values[inst] = ctSliceLengthRemintView(ip, se_view, se_len); return; } if (!semaRefIsInterned(se_lhs) || ip->item_tags[se_lhs] != IP_TAG_PTR_SLICE) UNIMPLEMENTED("slice_end: non-slice operand"); uint32_t se_ty = ip->extra[ip->item_datas[se_lhs]]; uint32_t se_old_len_val = ip->extra[ip->item_datas[se_lhs] + 2]; int64_t se_old_len; bool se_old_len_ct; uint32_t se_old_len_bits; if (!ctReadInt( ip, se_old_len_val, &se_old_len, &se_old_len_ct, &se_old_len_bits) || se_old_len < 0) UNIMPLEMENTED("slice_end: unreadable slice length"); if (ev < sv) UNIMPLEMENTED("slice_end: end < start"); if (ev > (int64_t)se_old_len) UNIMPLEMENTED("slice_end: end out of slice range"); uint64_t se_new_len64 = (uint64_t)(ev - sv); if (se_new_len64 > UINT32_MAX) UNIMPLEMENTED("slice_end: wide slice end"); uint32_t se_many_ty = ip->item_datas[se_ty]; if (se_many_ty >= ip->items_len || ip->item_tags[se_many_ty] != IP_TAG_TYPE_POINTER) UNREACHABLE("slice_end: malformed slice type"); if (ip->extra[ip->item_datas[se_many_ty] + 1] != IP_INDEX_NONE) UNIMPLEMENTED("slice_end: sentinel slice operand"); uint32_t se_many_val = ip->extra[ip->item_datas[se_lhs] + 1]; if (ctIsCellPointerClassValue(ip, se_many_val)) { if (ipTypeOf(ip, se_many_val) != se_many_ty) UNIMPLEMENTED("slice_end: cell slice ptr type mismatch"); se_lhs = se_many_val; goto slice_end_reprocess_value; } uint32_t se_arr_ty = ipGetArrayType(ip, (uint32_t)se_new_len64, ip->extra[ip->item_datas[se_many_ty]], IP_INDEX_NONE); uint32_t se_many_flags = ip->extra[ip->item_datas[se_many_ty] + 2]; uint32_t se_ret_ty = ipGetPtrType(ip, se_arr_ty, se_many_flags & ~3u); values[inst] = semaPtrElemCoerced(zcu, se_many_val, (uint32_t)sv, se_ret_ty); } // (part of) Sema.zig:zirSliceLength static void ctEvalSliceLength( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstSliceLength sl; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &sl); uint32_t lhs_ref = sl.lhs; uint32_t start_ref = sl.start; uint32_t len_ref = sl.len; uint32_t sent_ref = sl.sentinel; uint32_t lhs = ctSliceOperandLoadCellPtr(zcu, ip, semaResolveRef(lhs_ref, values)); if (ctSliceOperandIsInternedPtr(ip, lhs)) { values[inst] = ctAnalyzeSliceRefs( zcu, lhs, start_ref, len_ref, sent_ref, true, values); return; } uint32_t start = semaResolveRef(start_ref, values); uint32_t len = semaResolveRef(len_ref, values); int64_t sv, lv; bool sc, lc; uint32_t sb, lb; if (!ctReadInt(ip, start, &sv, &sc, &sb) || sv < 0) UNIMPLEMENTED("slice_length: non-comptime start"); if (!ctReadInt(ip, len, &lv, &lc, &lb) || lv < 0) UNIMPLEMENTED("slice_length: non-comptime len"); if (sv > (int64_t)UINT32_MAX || lv > (int64_t)UINT32_MAX) UNIMPLEMENTED("slice_length: wide bounds"); uint32_t explicit_sentinel = sent_ref == UINT32_MAX ? IP_INDEX_NONE : semaResolveRef(sent_ref, values); if (ctIsCellPointerClassValue(ip, lhs) && ctCellPtrType(zcu, ip, lhs) < ip->items_len && ztIsSlice(ip, ctCellPtrType(zcu, ip, lhs))) lhs = ctCellPtrLoad(zcu, ip, lhs); // Post-load gate: the load above yields a real `ptr_slice` // when the cell holds a slice, so the gate resolves either // representation; via ctCellPtrIsSliceableView it admits any // cell-pointer-class value whose chain reaches an array. if (ctIsCellPointerClassValue(ip, lhs) && ctCellPtrIsSliceableView(zcu, ip, lhs)) { // `analyzeSlice` returns a one-pointer-to-[len]T when its // length is comptime-known. Reuse the cell-slice minter for // the window/bounds/attribute validation, then retag its // exact many-pointer carrier without changing its backing // identity or byte offset. uint32_t sl_view = ctCellSliceMakeExplicit(zcu, ip, lhs, (uint32_t)sv, (uint32_t)lv, IP_INDEX_NONE, explicit_sentinel); values[inst] = ctSliceLengthRemintView(ip, sl_view, (uint32_t)lv); return; } if (semaRefIsInterned(lhs)) { // analyzeSlice's `ptr_ptr`: a one-pointer to a pointer is // analyzeLoad'ed first. IpPtrInfo pp_info; uint32_t direct_lhs = lhs; if (ipPtrInfo(ip, ipTypeOf(ip, lhs), &pp_info) == IP_PTRI_OK && pp_info.size == IP_PTR_SIZE_ONE && ipItemTagIs(ip, pp_info.child, IP_TAG_TYPE_POINTER)) { uint32_t pp_ty; direct_lhs = semaAnalyzeLoadComptime(zcu, lhs, &pp_ty); if (direct_lhs == IP_INDEX_NONE) UNIMPLEMENTED("slice_length: runtime load"); } uint32_t sl_direct = ctSliceLengthDirectManyOrCPtr(zcu, ip, direct_lhs, (uint32_t)sv, (uint32_t)lv, explicit_sentinel); if (sl_direct != IP_INDEX_NONE) { values[inst] = sl_direct; return; } } if (ipIsPtrSlice(ip, lhs) || (lhs < ip->items_len && (ipIsPtrUav(ip, lhs) || valIsAggregate(ip, lhs)))) { uint32_t sl_view = ctSliceLengthDirectView( zcu, ip, lhs, (uint32_t)sv, (uint32_t)lv, explicit_sentinel); values[inst] = ctSliceLengthRemintView(ip, sl_view, (uint32_t)lv); return; } UNIMPLEMENTED("slice_length"); } // (part of) Sema.zig:zirFieldPtrNamedLoad // `@field(container, name)` where `name` is a comptime string // (`@field(Target, @tagName(family))`). ZIR pl_node -> FieldNamed // { lhs, field_name: Ref }. Resolve the container to a namespace // Type, read the string, and look the member up — yielding a // ptr_nav (for _named ptr) or the member value (for _named val). // Mirrors Sema.fieldPtr/fieldVal named-field `.type` path. static void ctEvalFieldPtrNamedLoad( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstTag tag = zir->inst_tags[inst]; ZirInstFieldNamed fn; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &fn); uint32_t fn_obj = semaResolveRef(fn.lhs, values); uint32_t fn_name_v = semaResolveRef(fn.field_name, values); char fn_buf[64]; uint32_t fn_len; uint32_t fn_name_ip = ctFieldNamedNameString( ip, fn_name_v, fn_buf, sizeof(fn_buf), &fn_len); // Resolve the container's value (direct container type, or a // ptr_nav naming either a type-valued decl or a comptime // aggregate decl). uint32_t fn_cty; if (semaRefIsInterned(fn_obj) && fn_obj >= ZIR_REF_START_INDEX && (ipTagIsAnyStruct(ip->item_tags[fn_obj]) || ipTagIsAnyUnion(ip->item_tags[fn_obj]) || ipIsEnumType(ip, fn_obj) || valIsAggregate(ip, fn_obj) || ip->item_tags[fn_obj] == IP_TAG_BITPACK || ip->item_tags[fn_obj] == IP_TAG_UNION_VALUE)) fn_cty = fn_obj; else if (ctIsCellPointerClassValue(ip, fn_obj)) { uint32_t cell_ty = ctCellPtrType(zcu, ip, fn_obj); if (cell_ty < ip->items_len && (ipTagIsAnyStruct(ip->item_tags[cell_ty]) || ztIsTuple(ip, cell_ty))) { CtCellPtr cell_d = ctCellPtrResolveDescriptor(zcu, ip, fn_obj); uint32_t cell_field_ptr; if (ztIsTuple(ip, cell_ty)) { cell_field_ptr = ctCellPtrFieldPtr( zcu, ip, cell_d, tupleFieldIndex(ip, cell_ty, fn_buf)); } else { uint32_t cell_fidx = ipStructFieldIndex(ip, cell_ty, fn_name_ip); if (cell_fidx == UINT32_MAX) UNIMPLEMENTED( "zirFieldPtrNamedLoad: field_named cell field"); cell_field_ptr = ctCellStructFieldPtrByIndex( zcu, ip, cell_d, cell_ty, cell_fidx); } values[inst] = (tag == ZIR_INST_FIELD_PTR_NAMED_LOAD) ? ctCellPtrLoad(zcu, ip, cell_field_ptr) : cell_field_ptr; return; } else if (cell_ty < ip->items_len && (ipTagIsAnyStruct(ip->item_tags[cell_ty]) || ipTagIsAnyUnion(ip->item_tags[cell_ty]) || ipIsEnumType(ip, cell_ty))) fn_cty = cell_ty; else UNIMPLEMENTED( "zirFieldPtrNamedLoad: field_named cell container not a type"); } else if (fn_obj >= ZIR_REF_START_INDEX && fn_obj < ip->items_len && ipItemTagIs(ip, ipTypeOf(ip, fn_obj), IP_TAG_TYPE_POINTER)) { // `fieldPtrLoad`'s `pointerDeref` of a Nav or uav pointer. fn_cty = pointerDeref(zcu, fn_obj, NULL); if (fn_cty == IP_INDEX_NONE) UNIMPLEMENTED("zirFieldPtrNamedLoad: field_named runtime load"); } else UNIMPLEMENTED( "zirFieldPtrNamedLoad: field_named container not a type"); if (ip->item_tags[fn_cty] == IP_TAG_BITPACK && tag == ZIR_INST_FIELD_PTR_NAMED_LOAD) { // `fieldVal` of a packed struct value: the field read out of // its backing integer, as the field_ptr_load arm does. uint32_t fn_bp_idx = semaBitpackFieldIndex(ip, ipBitpackType(ip, fn_cty), fn_name_ip); if (fn_bp_idx == UINT32_MAX) UNIMPLEMENTED("zirFieldPtrNamedLoad: field_named packed field"); values[inst] = semaReadBitpackFieldValue(zcu, fn_cty, fn_bp_idx); return; } if (ip->item_tags[fn_cty] == IP_TAG_UNION_VALUE && tag == ZIR_INST_FIELD_PTR_NAMED_LOAD) { // `unionFieldVal` of a comptime union value. uint32_t fn_un_idx = ipUnionFieldIndex( ip, ip->extra[ip->item_datas[fn_cty]], fn_name_ip); if (fn_un_idx == UINT32_MAX) COMPILE_ERROR_UPSTREAM("field_named: no union field"); values[inst] = semaUnionActivePayload(zcu, fn_cty, fn_un_idx); return; } if (valIsAggregate(ip, fn_cty)) { uint32_t fn_agg_ty = ctAggType(ip, fn_cty); uint32_t fn_fidx; if (ipTagIsAnyStruct(ip->item_tags[fn_agg_ty])) { fn_fidx = ipStructFieldIndex(ip, fn_agg_ty, fn_name_ip); if (fn_fidx == UINT32_MAX) UNIMPLEMENTED("zirFieldPtrNamedLoad: field_named aggregate " "field not found"); } else if (ztIsTuple(ip, fn_agg_ty)) { fn_fidx = tupleFieldIndex(ip, fn_agg_ty, fn_buf); } else { UNIMPLEMENTED( "zirFieldPtrNamedLoad: field_named aggregate not a struct"); } values[inst] = valElemValue(ip, fn_cty, fn_fidx, "Value.elemValue: comptime"); return; } if (ipTagIsAnyStruct(ip->item_tags[fn_cty]) || ipTagIsAnyUnion(ip->item_tags[fn_cty])) { uint32_t fn_ns = ipContainerNamespaceOrNone(ip, fn_cty); uint32_t fn_mnav = IP_INDEX_NONE; bool fn_status = false; if (fn_ns != IP_NAMESPACE_NONE) { fn_status = semaNamespaceLookupAccessible( zcu, zir, fn_ns, fn_name_ip, &fn_mnav); } else if (!ipTagIsAnyUnion(ip->item_tags[fn_cty])) { UNIMPLEMENTED("zirFieldPtrNamedLoad: field_named container has no " "namespace"); } if (!fn_status && ipTagIsAnyUnion(ip->item_tags[fn_cty]) && ipUnionTagUsage(ip, fn_cty) == IP_UNION_TAG_USAGE_TAGGED) { uint32_t fn_tag_enum = ztUnionTagTypeHypothetical(zcu, fn_cty); uint32_t fn_tag = ipEnumTagFromFieldName(ip, fn_tag_enum, fn_name_ip); if (fn_tag != IP_INDEX_NONE) { if (tag == ZIR_INST_FIELD_PTR_NAMED_LOAD) { values[inst] = fn_tag; } else { uint32_t fn_ptr_ty = ipGetPtrType( ip, fn_tag_enum, IP_PTR_FLAGS_CONST_ONE); values[inst] = ipGetPtrUav(ip, fn_ptr_ty, fn_tag); } return; } } if (!fn_status) UNIMPLEMENTED("zirFieldPtrNamedLoad: field_named container member " "not found"); if (tag == ZIR_INST_FIELD_PTR_NAMED_LOAD) { values[inst] = navAnalyzeVal(zcu, fn_mnav, NULL); } else { values[inst] = navDeclRefAddressResolved(zcu, fn_mnav, NULL); } return; } if (ipIsEnumType(ip, fn_cty)) { // Namespace decls win over enum fields, matching // Sema.fieldPtr/fieldVal's `.enum` order. Enum fields fall // back to the tag value; the pointer form is a const UAV // reference to that tag (Sema.fieldPtr's uavRef). uint32_t fn_ns = ipEnumNamespaceOrNone(ip, fn_cty); if (fn_ns != IP_NAMESPACE_NONE) { uint32_t fn_mnav = IP_INDEX_NONE; bool fn_status = semaNamespaceLookupAccessible( zcu, zir, fn_ns, fn_name_ip, &fn_mnav); if (fn_status) { if (tag == ZIR_INST_FIELD_PTR_NAMED_LOAD) { values[inst] = navAnalyzeVal(zcu, fn_mnav, NULL); } else { values[inst] = navDeclRefAddressResolved(zcu, fn_mnav, NULL); } return; } } uint32_t fn_tag = ipEnumTagFromFieldName(ip, fn_cty, fn_name_ip); if (fn_tag == IP_INDEX_NONE) UNIMPLEMENTED( "zirFieldPtrNamedLoad: field_named enum field not found"); if (tag == ZIR_INST_FIELD_PTR_NAMED_LOAD) { values[inst] = fn_tag; } else { uint32_t fn_ptr_ty = ipGetPtrType(ip, fn_cty, IP_PTR_FLAGS_CONST_ONE); values[inst] = ipGetPtrUav(ip, fn_ptr_ty, fn_tag); } return; } UNIMPLEMENTED( "zirFieldPtrNamedLoad: field_named container not a struct/enum"); } // (part of) Sema.zig:zirPtrType // `*T`/`[]T`/`[*c]T` inside a comptime body. When the elem ref is // an instruction already evaluated in this frame (`*const // ptr.child` in Type.Pointer.sentinel — the elem is a // block_comptime over a bound param), compose the pointer type // from the frame value (decode mirrors // semaResolveGenericComposedPtrType / ctResolveParamTypeRef's // ptr_type branch). A named/preinterned elem takes the // declared-type resolver path below. static void ctEvalPtrType(SemaCtx* sema, uint32_t inst) { Zcu* zcu = sema->zcu; const Zir* zir = sema->zir; uint32_t* values = sema->inst_values; InternPool* ip = &zcu->intern_pool; CtPtrTypeRefs pt = ctPtrTypeRefs(zir, inst); uint32_t pt_elem_ref = pt.elem_type; // Take the frame path when the elem is an instruction already // evaluated in this frame, or when a preinterned element type // carries an `align` body or bound sentinel expression which only // resolves in this frame (e.g. // `[*]align(alignment.toByteUnits()) u8` or `[:sentinel]u8` in a // generic return type; the delegated resolver cannot evaluate // those). uint32_t pt_frame_elem_value = IP_INDEX_NONE; bool pt_frame_elem = ctFrameRefValue(ip, zir, pt_elem_ref, values, &pt_frame_elem_value); bool pt_pre_elem = pt_elem_ref < ZIR_REF_START_INDEX; bool pt_frame_sentinel = pt.sentinel != ZIR_REF_NONE && ctFrameRefValue(ip, zir, pt.sentinel, values, NULL); if (pt_frame_elem || (pt_pre_elem && (pt.align != ZIR_REF_NONE || pt_frame_sentinel))) { uint32_t pt_elem = pt_frame_elem ? pt_frame_elem_value : pt_elem_ref; if (!semaRefIsInterned(pt_elem)) UNIMPLEMENTED("ptr_type: sentinel elem"); uint8_t pt_flags = zir->inst_datas[inst].ptr_type.flags; uint8_t pt_size = zir->inst_datas[inst].ptr_type.size; uint32_t pt_sentinel = IP_INDEX_NONE; if (pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_SENTINEL) { // `[:s]const T` in a comptime type body after `T` was // bound in this frame. The sentinel may be a standalone // declared-type expression (literal/as_node/named const) // or a value bound in this same frame, and is coerced to // the already-resolved element type before interning. pt_sentinel = ctResolveSentinelRef( ip, zcu, zir, pt.sentinel, pt_elem, values); } if (pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_ADDRSPACE) UNIMPLEMENTED("ptr_type: with addrspace"); uint32_t ip_flags = semaPtrTypeFlags(ip, pt_flags, pt_size, pt_elem); if (pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_ALIGN) { // `align(N)` where the align expression is a comptime // value already evaluated in this frame (the // `[]align(if (alignment) |a| ... else @alignOf(T)) T` // generic return type in Allocator.allocAdvancedWith- // RetAddr). Pack log2(N) into flags bits 2..7 and intern // the `@as(u29, N)` side-effect int, mirroring // ctResolveParamTypeRef's align arm (u29 is static IP // index 6). uint32_t pta_v = semaResolveRef(pt.align, values); int64_t pta_n; bool pta_ct; uint32_t pta_b; if (!ctReadInt(ip, pta_v, &pta_n, &pta_ct, &pta_b) || pta_n <= 0 || (pta_n & (pta_n - 1)) != 0) UNIMPLEMENTED("ptr_type: align not a comptime power of two"); ipGetIntSmall(ip, 6u, (uint32_t)pta_n); uint32_t pta_log2 = 0; while ((pta_n >> pta_log2) > 1) pta_log2++; ip_flags = IP_PTR_FLAGS_WITH_ALIGN(ip_flags, pta_log2); } uint32_t pt_packed = 0; if (pt_flags & ZIR_PTR_TYPE_FLAGS_HAS_BIT_RANGE) { const uint32_t ptb_refs[2] = { pt.bit_start, pt.host_size }; int64_t ptb_v[2]; for (uint32_t k = 0; k < 2; k++) { uint32_t v = semaResolveRef(ptb_refs[k], values); bool ct; uint32_t b; if (!ctReadInt(ip, v, &ptb_v[k], &ct, &b) || ptb_v[k] < 0) UNIMPLEMENTED( "ptr_type: bit range not a comptime integer"); } pt_packed = semaPtrTypeBitRange( zcu, pt_elem, (uint64_t)ptb_v[0], (uint64_t)ptb_v[1]); } values[inst] = semaPtrTypeIntern( ip, pt_elem, ip_flags, pt_size, pt_sentinel, pt_packed); return; } values[inst] = ctFrameFreeTypeRef(sema, inst); } // (part of) Sema.zig:zirElemVal // Sema.zirElemVal: element `index` of a comptime indexable. ZIR // pl_node -> Bin { lhs = indexable, rhs = index }. static void ctEvalElemVal( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstBin e; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &e); uint32_t e_obj = semaResolveRef(e.lhs, values); uint32_t e_idx = semaResolveRef(e.rhs, values); int64_t iv; bool ict; uint32_t ib; if (!ctReadInt(ip, e_idx, &iv, &ict, &ib) || iv < 0) UNIMPLEMENTED("elem_val: non-comptime index"); // Sema.elemVal: a pointer or slice is `elemPtr` + `analyzeLoad` // (relative to its own address), an aggregate its element. values[inst] = ctIndexAnyValue(zcu, ip, e_obj, (uint32_t)iv); } // (part of) Sema.zig:zirStructInitFieldPtr // Sema.zirStructInitFieldPtr -> fieldPtr (comptime): the address // of a named field of a result-location struct/union cell pointer // (result-location struct/union init `.{ .x = a }`). ZIR pl_node // -> Field { lhs = object_ptr, field_name_start }. Extend the cell // pointer's access path by the field index (struct field == // Indexed, union field == union). static void ctEvalStructInitFieldPtr( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstField sf; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &sf); uint32_t sf_obj = semaResolveRef(sf.lhs, values); const char* sf_name = zirNullTerminatedString(zir, sf.field_name_start); uint32_t sf_name_ip = ipGetOrPutString(ip, sf_name, (uint32_t)strlen(sf_name)); if (!ctIsCellPointerClassValue(ip, sf_obj)) { // Sema.structFieldPtr on any other interned pointer (an OPV // allocation's). uint32_t sf_ty; IpPtrInfo sf_info; bool sf_interned = sf_obj < ip->items_len; // unionFieldPtr (initializing): Value.ptrField of the union // pointer. uint32_t sf_ufidx = sf_interned && ipPtrInfo(ip, ipTypeOf(ip, sf_obj), &sf_info) == IP_PTRI_OK && sf_info.size == IP_PTR_SIZE_ONE && ipTagIsAnyUnion(ip->item_tags[sf_info.child]) ? ipUnionFieldIndex(ip, sf_info.child, sf_name_ip) : UINT32_MAX; // An `.auto` union's arm first stores the field's tag through // the pointer (or is a runtime op); only a one-possible-value // union makes that store a no-op. if (sf_ufidx != UINT32_MAX && !ipUnionIsPacked(ip, sf_info.child) && !ipUnionIsExtern(ip, sf_info.child)) { semaResolveAllUnionDeferred(zcu, sf_info.child); if (ztOnePossibleValue(ip, sf_info.child) == IP_INDEX_NONE) UNIMPLEMENTED("struct_init_field_ptr: union tag store"); } values[inst] = !sf_interned ? IP_INDEX_NONE : sf_ufidx != UINT32_MAX ? valPtrField(zcu, sf_obj, sf_ufidx) : semaStructFieldPtrComptime(zcu, sf_obj, sf_name, sf_name_ip, &sf_ty); if (values[inst] == IP_INDEX_NONE) UNIMPLEMENTED("struct_init_field_ptr: non-cell object"); return; } values[inst] = ctCellStructInitFieldPtr(zcu, ip, sf_obj, sf_name_ip); } // (part of) Sema.zig:zirArrayInit, Sema.zig:zirArrayInitAnon // Sema.zirArrayInit (comptime path): intern the array aggregate // from its element inits. ZIR pl_node -> MultiOp { operands_len, // refs... }; for `array_init`, refs[0] is the result type and // refs[1..] are the element inits (coerced to the element type). // `array_init_ref` additionally takes the address of the array (a // `*const [N]T` uav pointer), for `&.{...}`. static void ctEvalArrayInit( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstTag tag = zir->inst_tags[inst]; ZirInstMultiOp mop; const uint32_t* operands = zirBodySlice(zir, zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &mop)); uint32_t operands_len = mop.operands_len; bool is_ref = (tag == ZIR_INST_ARRAY_INIT_REF); bool anon = (tag == ZIR_INST_ARRAY_INIT_ANON); uint32_t base = 0; uint32_t nelems; uint32_t arr_ty = 0; uint32_t elem_ty = 0; bool treat_anon = anon; if (!anon) { uint32_t res_ty = semaResolveRef(operands[base], values); nelems = operands_len - 1; base += 1; if (semaRefIsInterned(res_ty) && res_ty == IP_INDEX_GENERIC_POISON_TYPE) { // Result type unknown (an `anytype` result location, e.g. // the `.{ ... }` literal passed to `initComptime(comptime // kvs_list: anytype)`): stock resolveTypeOrPoison returns // null and delegates to arrayInitAnon. treat_anon = true; } else if (!semaRefIsInterned(res_ty)) { UNIMPLEMENTED("array_init: result-type sentinel"); } else { arr_ty = res_ty; // The same `Sema.validateArrayInitTy` count rule the // runtime array-init arms apply, stated once for both // frames: array, vector and tuple result types alike. validateArrayInitTy(ip, nelems, arr_ty); IpArrayInfo ai_seq; // Sema.zirArrayInit coerces each element to // `array_ty.indexableElem(zcu)`, one arm for every // sequence: `type_array_small`, `type_array_big` and // `type_vector` all answer through ipArrayInfo. if (ipArrayInfo(ip, arr_ty, &ai_seq) == IP_ARR_OK) elem_ty = ai_seq.child; else if (ztIsTuple(ip, arr_ty)) { // A typed tuple result (`const r: Result = .{ null, // 7 };` where Result is a tuple type): Sema.zirArray- // Init coerces each element to its field type (the // per-index array_init_elem_type), not one uniform // element type — elem_ty stays 0 and the loop below // reads the tuple's per-field types. Upstream allows // fewer inits than fields (the rest take their // defaults); zig1 does not model those defaults, so // a short list declines instead of interning a tuple // whose stored count exceeds `elems[]`. if (nelems != ip->extra[ip->item_datas[arr_ty]]) UNIMPLEMENTED("array_init: tuple defaults not modelled"); } else UNIMPLEMENTED("array_init: non-array result ty"); } } else { nelems = operands_len; } uint32_t* elems = zig1Calloc(nelems ? nelems : 1, sizeof(uint32_t)); if (treat_anon) { // Sema.arrayInitAnon comptime path: build a tuple type from // each element's own type + comptime value, then the tuple // aggregate. Every element must be comptime-known here (a // runtime element would need an AIR aggregate_init — aborts). uint32_t* types = zig1Calloc(nelems ? nelems : 1, sizeof(uint32_t)); for (uint32_t e = 0; e < nelems; e++) { uint32_t ev = semaResolveRef(operands[base + e], values); if (!semaRefIsInterned(ev)) UNIMPLEMENTED("array_init_anon: runtime element"); elems[e] = ev; types[e] = ipTypeOf(ip, ev); if (types[e] == IP_INDEX_NONE) UNIMPLEMENTED("array_init_anon: element type unmodeled"); } // Stock passes `values_no_comptime` (== values here — no // element mutates comptime-var state) as the tuple type's // comptime-field slots. arr_ty = ipGetTupleType(ip, nelems, types, elems); free(types); } else { bool tuple_ty = ztIsTuple(ip, arr_ty); for (uint32_t e = 0; e < nelems; e++) { uint32_t ev = semaResolveRef(operands[base + e], values); // Tuple layout: [fields_len, types[fields_len], // values[fields_len]] — per-field element type. uint32_t e_ty = tuple_ty ? ip->extra[ip->item_datas[arr_ty] + 1 + e] : elem_ty; elems[e] = ctCoerceToType(zcu, ev, e_ty); } } uint32_t agg = ipGetAggregate(ip, arr_ty, elems, nelems); free(elems); if (is_ref) { // `&.{...}`: a `*const [N]T` anonymous-uav pointer to the // array. uint32_t pty = ipGetPtrType(ip, arr_ty, IP_PTR_FLAGS_CONST_ONE); values[inst] = ipGetPtrUav(ip, pty, agg); } else { values[inst] = agg; } } // (part of) Sema.zig:zirAsNode // Sema.zirAsNode -> coerce(operand, dest_type), comptime path. ZIR // pl_node -> As { dest_type_ref, operand_ref }. Only the coercions // the comptime std-gating bodies exercise are modeled: coercing a // value to an optional type (`null` / a comptime value -> `?T`, as // in `return null`/`return N` of a `?comptime_int` fn) and the // identity (operand already the dest type). Anything else aborts // loudly. static void ctEvalAsNode( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; ZirInstAs as; zirExtraData(zir, zir->inst_datas[inst].pl_node.payload_index, &as); uint32_t as_dest = semaResolveRef(as.dest_type, values); uint32_t as_op = semaResolveRef(as.operand, values); if (!semaRefIsInterned(as_dest)) UNIMPLEMENTED("as_node: dest not a type"); if (ztIsZigTypeTag(ip, as_dest, ZIG_TYPE_TAG_OPTIONAL)) { values[inst] = ctCoerceToType(zcu, as_op, as_dest); return; } // Identity coercion to `type` (a type value coerced to `type`): // pass through. if (as_dest == ZIR_REF_TYPE_TYPE) { values[inst] = as_op; return; } // Enum-literal -> enum coercion (`@as(Signedness, .unsigned)` — // the @Int signedness argument): resolve the literal's field in // the destination enum (Sema.coerceEnumLiteral). if (as_dest >= ZIR_REF_START_INDEX && ipIsEnumType(ip, as_dest) && ctIsEnumLiteral(ip, as_op)) { uint32_t el_tag = ipEnumTagFromFieldName( ip, as_dest, ctEnumLiteralName(ip, as_op)); if (el_tag == IP_INDEX_NONE) UNIMPLEMENTED("as_node: enum literal not a field"); values[inst] = el_tag; return; } // Integer coercion (`const bits: u16 = @typeInfo(T).int.bits` / // the `@Int` u16 bit-count argument): re-intern the comptime-known // non-negative magnitude at the destination int type (Sema.coerce // -> intCast comptime path). The destination may be a preinterned // int type ref (u16) or a dynamically interned one; // semaCoerceComptimeIntLiteral handles both and returns none for a // non-int destination (falls through to the loud abort). // Any int value qualifies, preinterned ones included: upstream // preinterns `zero_u1`/`one_u1`/`zero_usize`/`four_u8` and the // rest (src/InternPool.zig's `Index` tail), so // `@as(u16, @intFromBool(...))` hands this arm index 114 -- below // ZIR_REF_START_INDEX; skipping the coercion for it would leave // `@as(u16, @intFromBool(s == .signed)) + <7>` peered at u1, where the // fold would have to answer a 7 that u1 cannot hold. if (as_op < ip->items_len) { int64_t iv; bool ict; uint32_t ib; if (ctReadInt(ip, as_op, &iv, &ict, &ib) && iv >= 0) { // `Sema.coerceExtra` asks `intFitsInType` before // `pt.getCoerced`; a misfit is a compile error, and // `semaCoerceComptimeIntLiteral` would truncate instead. if (!valIntFitsInType(ip, as_op, as_dest)) COMPILE_ERROR_UPSTREAM("as_node: type cannot " "represent integer value"); uint32_t r = semaCoerceComptimeIntLiteral(ip, (uint64_t)iv, as_dest); if (r != IP_INDEX_NONE) { values[inst] = r; return; } } } // General coercion fallback (aggregate identity, array-ptr -> // slice, enum literal -> enum): mirrors Sema.coerce for the // remaining comptime-known shapes. values[inst] = ctCoerceToType(zcu, as_op, as_dest); } // (part of) Sema.zig:zirMakePtrConst // Sema.makePtrConst, without zirMakePtrConst's `already_ct` promotion to a // uav. ZIR un_node -> operand = ptr ref. static void ctEvalMakePtrConst( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; uint32_t operand = semaResolveRef(zir->inst_datas[inst].un_node.operand, values); if (operand < ip->items_len) { uint32_t operand_ty = ipTypeOf(ip, operand); if (operand_ty < ip->items_len && ip->item_tags[operand_ty] == IP_TAG_TYPE_POINTER) { uint32_t const_ty = makePtrTyConst(ip, operand_ty); uint32_t const_ptr = ipRetypePtrValue(ip, operand, const_ty); if (const_ptr == IP_INDEX_NONE) UNIMPLEMENTED("make_ptr_const: carrier"); operand = const_ptr; } } values[inst] = operand; } // (part of) Sema.zig:zirRef // `&expr` on a comptime value: form the same `*const T` UAV // pointer Sema.analyzeRef interns. static void ctEvalRef( Zcu* zcu, const Zir* zir, uint32_t inst, uint32_t* values) { InternPool* ip = &zcu->intern_pool; uint32_t r_op = zir->inst_datas[inst].un_tok.operand; uint32_t r_val = semaResolveRef(r_op, values); // A runtime parameter of a generic body (unset here) has no comptime // address: the ref stays unset, for `@TypeOf` to analyze // (`@TypeOf(s.method(a))`). if (!semaRefIsInterned(r_val)) { values[inst] = r_val; return; } // Sema.analyzeRef: `".func => |f| return sema.analyzeNavRef(block, // src, f.owner_nav),"` -- `&function` is the function's Nav // pointer, the one `&S.f` forms, not an anonymous uav. if (ipItemTagIs(ip, r_val, IP_TAG_FUNC_DECL) || ipItemTagIs(ip, r_val, IP_TAG_FUNC_INSTANCE)) { uint32_t r_nav = ip->item_tags[r_val] == IP_TAG_FUNC_DECL ? ipFuncDeclOwnerNav(ip, r_val) : ipFuncInstanceOwnerNav(ip, r_val); uint32_t r_nav_ty = IP_INDEX_NONE; values[inst] = navDeclRefAddressResolved(zcu, r_nav, &r_nav_ty); return; } uint32_t r_ty = ipTypeOf(ip, r_val); if (r_ty == IP_INDEX_NONE) UNIMPLEMENTED("ref: operand has no type"); uint32_t r_ptr_ty = ipGetPtrType(ip, r_ty, IP_PTR_FLAGS_CONST_ONE); values[inst] = ipGetPtrUav(ip, r_ptr_ty, r_val); } // The tail of a block's body: a break to this block is its result, as // Sema.resolveAnalyzedBlock's comptime break; any other status propagates. static bool ctBlockBreakResult(CtEvalStatus st, uint32_t inst, uint32_t* values, const uint32_t* ct_break_block, const uint32_t* ct_break_val) { if (st != CT_EVAL_BREAK || *ct_break_block != inst) return false; values[inst] = *ct_break_val; return true; } // Sema.zirSwitchBlock's comptime path: find the prong the comptime-known // operand selects and run only its body. static CtEvalStatus ctEvalSwitchBlock(SemaCtx* sema, uint32_t inst, uint32_t* ct_ret_val, uint32_t* ct_break_block, uint32_t* ct_break_val) { Zcu* zcu = sema->zcu; const Zir* zir = sema->zir; uint32_t* values = sema->inst_values; InternPool* ip = &zcu->intern_pool; ZirInstTag tag = zir->inst_tags[inst]; // Comptime `switch` over a comptime-known enum scrutinee // (Sema.zirSwitchBlock's comptime path: resolve the operand, find // the matching prong by comparing each item to the scrutinee, and // evaluate only that prong's body — no AIR, no other prong). // Four shapes are handled: an INT or bool scrutinee (scalar + // multi cases, with `x...y` ranges on ints; a bool scrutinee matches // its `false`/`true` items by interned identity), a // `@typeInfo` reflection scrutinee (with a by-val payload capture // `.int => |info|`), and an enum scrutinee (scalar + multi // single-value enum-literal cases). All take an optional `else`. // Inline prongs, labeled `continue`, by-ref captures, and non-int // by-val captures still abort loudly. Each path's trailing-data // walk mirrors the runtime SWITCH_BLOCK decode // (Zir.getSwitchBlock) but only locates the taken prong body + // resolves item values. ZirSwitchBlock zs = zirGetSwitchBlock(zir, inst); bool sw_any_ranges = zs.multi_case_ranges_lens != NULL; bool sw_has_else = zs.has_else; bool sw_tc = zs.tag_capture_placeholder != UINT32_MAX; uint32_t sw_scalar_len = zs.scalar_cases_len; uint32_t sw_total_cases = sw_scalar_len + zs.multi_cases_len; // A prong's `|x|` capture resolves to the placeholder, else the // switch instruction itself (Sema: `orelse switch_inst`). uint32_t sw_cap_inst = zs.payload_capture_placeholder != UINT32_MAX ? zs.payload_capture_placeholder : inst; // Resolve the comptime scrutinee. analyzeSwitchBlock: `"const // val, const ref = if (operand_is_ref) .{ try sema.analyzeLoad( // block, src, raw_operand, operand_src), raw_operand } else .{ // raw_operand, .none };"` -- a switch_block_ref switches on the // loaded value and keeps the pointer for by-ref captures. uint32_t scrut = semaResolveRef(zs.main_operand, values); uint32_t sw_operand_ptr = IP_INDEX_NONE; uint32_t sw_raw_operand_ty = zs.has_continue ? ipTypeOf(ip, scrut) : IP_INDEX_NONE; sw_restart: if (tag == ZIR_INST_SWITCH_BLOCK_REF) { sw_operand_ptr = scrut; scrut = pointerDeref(zcu, sw_operand_ptr, NULL); if (scrut == IP_INDEX_NONE) UNIMPLEMENTED("switch_block_ref: runtime load"); } // Sema.zirSwitchBlock's comptime loop: a `switch_continue` // targeting this switch "change[s] the operand and start[s] // over", coerced to `raw_operand_ty`, which the prongs see as // this instruction's value (`"if (zir_switch.has_continue) // sema.inst_map.putAssumeCapacity(switch_inst, .fromType( // raw_operand_ty));"`) -- the continue operand's result type, a // pointer for a by-ref switch, which then switches on its load. if (zs.has_continue) values[inst] = sw_raw_operand_ty; // resolveSwitchProng's input: the taken body and the by-value // capture it binds (IP_INDEX_NONE: no capture). const uint32_t* sw_body = NULL; uint32_t sw_body_len = 0; uint32_t sw_cap_val = IP_INDEX_NONE; uint32_t sw_tag_cap_val = IP_INDEX_NONE; uint32_t sw_tag_inst = sw_tc ? zs.tag_capture_placeholder : inst; // A `switch (@typeInfo(T))` reflection switch: prongs match by // union-tag name and may capture the active payload by value // (`.array => |info| ...`, `.int => |info| ...`). Scrutinee // carriers: `std.lang.Type` union values and a // `std.lang.Signedness` enum_tag (`switch (value_info. // signedness)` in Writer.printInt). Mirrors // Sema.zirSwitchBlock's comptime path: pick the prong whose item // names the active tag (or `else`), bind the payload capture, // and evaluate only that body. if (ctIsTypeInfoValue(zcu, scrut) || ctIsSignednessValue(zcu, scrut)) { // The active tag name decodes off the value itself: a union // value through its interned enum tag, a Signedness enum_tag // through its field name (ctEnumTagName). // `switch (@typeInfo(Elem)) { .@"struct" => Elem, ... }` in // MultiArrayList's `const Elem = ...` folds through here. const char* ti_active; if (ctIsSignednessValue(zcu, scrut)) ti_active = ctEnumTagName(ip, scrut); else ti_active = semaTypeInfoActiveTagName(zcu, scrut); if (zs.else_is_inline) UNIMPLEMENTED("zirSwitchBlock: switch inline else"); uint32_t ti_total_items = zs.item_infos_len; const uint32_t* ti_item_infos = zs.item_infos; uint32_t ti_cursor = zs.end; // per-case prong bodies uint32_t ti_item_idx = 0; // Sema.validateSwitchBlock runs over every prong before any // is selected, and its `.enum_literal` key is // `seen_sparse_values`, i.e. the interned item Index. This // arm matched by name and validated nothing, so a duplicate // prong just lost to the first one instead of being the // compile error it is. semaSwitchValidateItems is the one gate // both evaluators already answer for every other item type; // there are no ranges on an enum literal, so it needs no item // type. uint32_t* ti_seen = ARR_INIT(uint32_t, ti_total_items + 1u); for (uint32_t vi = 0; vi < ti_total_items; vi++) { uint32_t vinfo = ti_item_infos[vi]; if ((vinfo & ZIR_ITEM_INFO_KIND_MASK) != ZIR_ITEM_INFO_KIND_ENUM_LITERAL) UNIMPLEMENTED("zirSwitchBlock: switch non-enum-literal item"); const char* vname = (const char*)&zir ->string_bytes[vinfo >> ZIR_ITEM_INFO_DATA_SHIFT]; ti_seen[vi] = ipGetEnumLiteral( ip, ipGetOrPutString(ip, vname, (uint32_t)strlen(vname))); } semaSwitchValidateItems( zcu, IP_INDEX_NONE, ti_seen, ti_total_items, NULL, 0); // The missing-prong pass keys a tagged union's items by its // tag enum (`item_ty`), so the literals are coerced to it. if (scrut < ip->items_len) { uint32_t ti_operand_ty = ipTypeOf(ip, scrut); uint32_t ti_item_ty = ipTagIsAnyUnion(ip->item_tags[ti_operand_ty]) ? ipEnsureGeneratedUnionTag(ip, ti_operand_ty) : ti_operand_ty; for (uint32_t vi = 0; vi < ti_total_items; vi++) ti_seen[vi] = ctSwitchItemBodyValue( sema, inst, ti_item_infos[vi], NULL, ti_item_ty, false); semaSwitchValidateSpecialProngs(ip, ti_item_ty, ti_operand_ty, ti_seen, ti_total_items, NULL, 0u, zs.has_else, zs.has_under, (zs.else_info & ZIR_PRONG_ELSE_IS_SIMPLE_NORETURN) != 0); } const uint32_t* ti_taken = NULL; uint32_t ti_taken_len = 0; uint32_t ti_taken_cap = 0; uint32_t ti_taken_item_off = 0; uint32_t ti_taken_items_count = 0; bool ti_matched = false; for (uint32_t ci = 0; ci < sw_total_cases; ci++) { uint32_t ti_items_count = (ci < sw_scalar_len) ? 1u : zs.multi_case_items_lens[ci - sw_scalar_len]; uint32_t pinfo = zs.prong_infos[ci]; // An inline prong (bit 29) over @typeInfo — e.g. // `inline .@"struct", .@"union" => |info| ...`. Since the comptime // fold selects the single active tag and binds `|info|` to the // bound value below (per the payload ternary); an inline prong is // observably identical to a normal prong here (the per-item // specialization collapses to the one live item), so it is handled // by the same matching/eval path. A per-item tag capture (bit 30) // is still unmodeled. if (pinfo & ZIR_PRONG_INFO_HAS_TAG_CAPTURE) UNIMPLEMENTED("zirSwitchBlock: switch tag-capture prong"); uint32_t ti_cap = (pinfo >> ZIR_PRONG_INFO_CAPTURE_SHIFT) & ZIR_PRONG_INFO_CAPTURE_MASK; uint32_t plen = pinfo & ZIR_PRONG_INFO_BODY_LEN_MASK; const uint32_t* pbody = zirBodySlice(zir, ti_cursor); ti_cursor += plen; bool this_match = false; uint32_t ti_item_off = ti_item_idx; for (uint32_t it = 0; it < ti_items_count; it++) { uint32_t item_info = ti_item_infos[ti_item_idx++]; if ((item_info & ZIR_ITEM_INFO_KIND_MASK) != ZIR_ITEM_INFO_KIND_ENUM_LITERAL) UNIMPLEMENTED( "zirSwitchBlock: switch non-enum-literal item"); const char* iname = (const char*)&zir->string_bytes[item_info >> ZIR_ITEM_INFO_DATA_SHIFT]; if (strcmp(iname, ti_active) == 0) this_match = true; } if (this_match && !ti_matched) { ti_matched = true; ti_taken = pbody; ti_taken_len = plen; ti_taken_cap = ti_cap; ti_taken_item_off = ti_item_off; ti_taken_items_count = ti_items_count; } } uint32_t ti_body_cap = 0; if (ti_matched) { sw_body = ti_taken; sw_body_len = ti_taken_len; ti_body_cap = ti_taken_cap; } else if (sw_has_else) { sw_body = zs.else_body; sw_body_len = zs.else_body_len; // resolveSwitchProng's `.special` capture: the whole operand, // by value or through the operand pointer. if (zs.else_capture == ZIR_PRONG_CAPTURE_BY_VAL) sw_cap_val = scrut; else if (zs.else_capture == ZIR_PRONG_CAPTURE_BY_REF) sw_cap_val = sw_operand_ptr; } else { UNIMPLEMENTED("zirSwitchBlock: typeInfo switch no prong, no else"); } // Bind the payload capture (by value) to the active payload. // For a real `std.lang.Type` union value the payload is the // union's own payload word — the real `Type.Int` aggregate // for `.int` (`|info|` then reads `.bits`/`.signedness` // through the aggregate arm) or the interned void value for // the payload-less tags, faithful to upstream's // payload extraction. A Signedness scrutinee is its own // payload. if (ti_body_cap == ZIR_PRONG_CAPTURE_BY_REF) { // analyzeSwitchPayloadCaptureTaggedUnion's by-ref arm on the // comptime operand pointer. if (!ctIsTypeInfoValue(zcu, scrut) || sw_operand_ptr == IP_INDEX_NONE) UNIMPLEMENTED("zirSwitchBlock: switch by-ref prong capture"); uint32_t ti_cap_ty; sw_cap_val = semaSwitchByRefCapturePtr(zcu, sw_operand_ptr, ipTypeOf(ip, sw_operand_ptr), ipTypeOf(ip, scrut), &ti_seen[ti_taken_item_off], ti_taken_items_count, &ti_cap_ty); } else if (ti_body_cap != 0) sw_cap_val = ctIsTypeInfoValue(zcu, scrut) ? semaTypeInfoPayload(zcu, scrut, ti_active, NULL) : scrut; free(ti_seen); goto sw_eval; } // An integer or bool scrutinee with scalar single-value items // and/or multi-item cases with ranges (`switch (byte) { '0'...'9' => ..., // else => ... }`). Decode the SwitchBlock trailing data // (Zir.getSwitchBlock; mirrors the runtime integer switch in sema.c), // match the comptime scrutinee against each case's items/ranges // (inclusive), and evaluate only the taken prong (or `else`). Ranges are // only valid on integer operands (a range item on a bool switch is a stock // compile error, so no valid ZIR carries one; the range loop below assumes // an integer scrutinee). { // The scrutinee may be a preinterned int value (< 128): // `IntFittingRange(0, 1)`'s `switch (to)` sees comptime_int 1 // (preintern 116) via meta.FieldEnum on a 2-field struct. // A bool scrutinee (preinterned bool_true/bool_false — the // `switch (builtin.link_libc)` shape) takes this path too: // its `false`/`true` items lower as body items whose // break_inline operand is the well-known bool ref (AstGen // switchExpr item lowering, lib/std/zig/AstGen.zig — a // bool literal is an `.identifier` node, not an enum // literal), and Sema matches `cond_val.eql(item_val, ...)` = // interned-index identity (Sema.resolveSwitchBlock -> // Value.eql). ctReadInt only reads INT_* tags, so void/undef/ // OPV preinterns still fall through to the enum path below // (where non-enum scrutinees abort). bool sc_is_bool = scrut == ZIR_REF_BOOL_TRUE || scrut == ZIR_REF_BOOL_FALSE; // resolveSwitchBlock matches `"cond_val.eql(item_val, // item_ty, zcu)"`, which for a bool, a packed container's // `.bitpack`, an error value, a type and an interned pointer // is interned identity once each item is coerced to // `item_ty`: the same comparison. bool sc_is_err = ipItemTagIs(ip, scrut, IP_TAG_ERROR_SET_ERROR); bool sc_is_ident = sc_is_bool || sc_is_err || ipItemTagIs(ip, scrut, IP_TAG_BITPACK) || ipIndexIsType(ip, scrut) || (!ctIsCellPointerClassValue(ip, scrut) && ipItemTagIs(ip, ipTypeOf(ip, scrut), IP_TAG_TYPE_POINTER)); // Any integer, at any width: resolveSwitchBlock compares by // `Value.eql` / `compareAll`, i.e. `Value.order`, which reads // every carrier (`semaCmpComptime`). uint64_t sc_space[2]; BigIntConst sc_big; if (sc_is_ident || valToBigInt(ip, scrut, sc_space, &sc_big)) { uint32_t isw_item_ty = sc_is_bool ? ZIR_REF_BOOL_TYPE : ipTypeOf(ip, scrut); if (sw_tc) UNIMPLEMENTED("zirSwitchBlock: int switch tag capture"); uint32_t total_items = zs.item_infos_len; const uint32_t* item_infos = zs.item_infos; uint32_t cursor = zs.end; uint32_t item_info_idx = 0; // validateSwitchBlock's duplicate pass runs over every // resolved item and range before any prong body // (semaSwitchValidateItems, sema.c) -- one port, // shared with the runtime evaluator's switch_block. uint32_t* isw_dup_items = zig1Malloc( (total_items ? total_items : 1u) * sizeof(uint32_t)); uint32_t* isw_dup_ranges = zig1Malloc( (total_items ? total_items : 1u) * sizeof(uint32_t)); uint32_t isw_dup_items_len = 0; uint32_t isw_dup_bounds_len = 0; const uint32_t* taken = NULL; uint32_t taken_len = 0; uint32_t taken_cap = 0; uint32_t taken_items = 0, taken_items_len = 0; bool taken_inline = zs.else_is_inline; bool matched = false; for (uint32_t ci = 0; ci < sw_total_cases; ci++) { uint32_t items_count = (ci < sw_scalar_len) ? 1u : zs.multi_case_items_lens[ci - sw_scalar_len]; uint32_t ranges_count = (ci < sw_scalar_len || !sw_any_ranges) ? 0u : zs.multi_case_ranges_lens[ci - sw_scalar_len]; uint32_t pinfo = zs.prong_infos[ci]; if (pinfo & ZIR_PRONG_INFO_HAS_TAG_CAPTURE) UNIMPLEMENTED( "zirSwitchBlock: int switch inline/tag prong"); uint32_t isw_prong_cap = (pinfo >> ZIR_PRONG_INFO_CAPTURE_SHIFT) & ZIR_PRONG_INFO_CAPTURE_MASK; uint32_t plen = pinfo & ZIR_PRONG_INFO_BODY_LEN_MASK; const uint32_t* pbody = zirBodySlice(zir, cursor); cursor += plen; bool this_match = false; uint32_t prong_items = isw_dup_items_len; for (uint32_t it = 0; it < items_count; it++) { uint32_t iv_ip = ctSwitchItemBodyValue(sema, inst, item_infos[item_info_idx++], &cursor, isw_item_ty, (pinfo & ZIR_PRONG_INFO_IS_COMPTIME_UNREACH) != 0); isw_dup_items[isw_dup_items_len++] = iv_ip; if (sc_is_ident) { // Bool item: both sides are interned // (preinterned bool_true/bool_false), so // Value.eql (src/Value.zig, `a.toIntern() // == b.toIntern()`) is IP-index identity. A // non-bool item on a bool scrutinee would be // a compile error upstream (resolveSwitchItem // coerces each item to the operand type) — // abort loudly. if (sc_is_bool && iv_ip != ZIR_REF_BOOL_TRUE && iv_ip != ZIR_REF_BOOL_FALSE) UNIMPLEMENTED( "zirSwitchBlock: bool switch item not a bool"); if (iv_ip == scrut) this_match = true; continue; } // `"if (cond_val.eql(item_val, item_ty, zcu))"` if (semaCmpComptime(zcu, ZIR_INST_CMP_EQ, scrut, iv_ip) == ZIR_REF_BOOL_TRUE) this_match = true; } for (uint32_t r = 0; r < ranges_count; r++) { uint32_t lo_ip = ctSwitchItemBodyValue(sema, inst, item_infos[item_info_idx++], &cursor, isw_item_ty, (pinfo & ZIR_PRONG_INFO_IS_COMPTIME_UNREACH) != 0); uint32_t hi_ip = ctSwitchItemBodyValue(sema, inst, item_infos[item_info_idx++], &cursor, isw_item_ty, (pinfo & ZIR_PRONG_INFO_IS_COMPTIME_UNREACH) != 0); isw_dup_ranges[isw_dup_bounds_len++] = lo_ip; isw_dup_ranges[isw_dup_bounds_len++] = hi_ip; // A range on a bool item type is a stock compile // error ("ranges not allowed when switching on // type"), and semaSwitchValidateItems below is the // one place that says so -- for both evaluators. // Its bounds are not ints and there is nothing to // match, so collect them and let the gate speak // instead of aborting on the read, which would // answer a stock-invalid program with // UNIMPLEMENTED. if (sc_is_ident) continue; // `"(try sema.compareAll(cond_val, .gte, // first_val, item_ty)) and (try // sema.compareAll(cond_val, .lte, last_val, // item_ty))"` if (semaCmpComptime(zcu, ZIR_INST_CMP_GTE, scrut, lo_ip) == ZIR_REF_BOOL_TRUE && semaCmpComptime(zcu, ZIR_INST_CMP_LTE, scrut, hi_ip) == ZIR_REF_BOOL_TRUE) this_match = true; } if (this_match && !matched) { matched = true; taken = pbody; taken_len = plen; taken_cap = isw_prong_cap; taken_items = prong_items; taken_items_len = items_count; taken_inline = (pinfo & ZIR_PRONG_INFO_IS_INLINE) != 0; } } semaSwitchValidateItems(zcu, isw_item_ty, isw_dup_items, isw_dup_items_len, isw_dup_ranges, isw_dup_bounds_len / 2u); uint32_t isw_cap_ty = semaSwitchValidateSpecialProngs(ip, isw_item_ty, isw_item_ty, isw_dup_items, isw_dup_items_len, isw_dup_ranges, isw_dup_bounds_len / 2u, zs.has_else, zs.has_under, (zs.else_info & ZIR_PRONG_ELSE_IS_SIMPLE_NORETURN) != 0); free(isw_dup_ranges); uint32_t isw_bind_cap = zs.else_capture; if (matched) { sw_body = taken; sw_body_len = taken_len; isw_bind_cap = taken_cap; } else if (sw_has_else) { sw_body = zs.else_body; sw_body_len = zs.else_body_len; } else { UNIMPLEMENTED("zirSwitchBlock: int switch no prong, no else " "(exhaustive)"); } // analyzeSwitchCaptures' by-ref capture: an `inline` // prong's item is `uavRef`'d, any other prong is the // operand pointer itself. if (isw_bind_cap == 2 && sc_is_err) COMPILE_ERROR_UPSTREAM("switch: error set cannot be " "captured by reference"); if (isw_bind_cap == 2 && taken_inline) sw_cap_val = ipGetPtrUav(ip, ipGetPtrType(ip, isw_item_ty, IP_PTR_FLAGS_CONST_ONE), scrut); else if (isw_bind_cap == 2) sw_cap_val = sw_operand_ptr; // A by-value capture is the scrutinee itself (an // `.@"inline"` prong's item is the scrutinee too), with // an error set narrowed to the prong. if (isw_bind_cap == 1 && !sc_is_err) sw_cap_val = scrut; else if (isw_bind_cap == 1) { if (taken_inline) isw_cap_ty = semaSwitchErrCaptureTy(ip, &scrut, 1u); else if (matched) isw_cap_ty = semaSwitchErrCaptureTy( ip, &isw_dup_items[taken_items], taken_items_len); sw_cap_val = ipGetErrorSetError( ip, isw_cap_ty, ip->extra[ip->item_datas[scrut] + 1]); } free(isw_dup_items); goto sw_eval; } } { // Existing enum-scrutinee path, widened to a comptime // tagged-union value by comparing items against its active tag // and binding a by-value payload capture for the taken prong. // Sema.validateSwitchBlock's item-type gate // (src/Sema.zig): ranges are admitted for `.int` // and // `.comptime_int` item types only, so an enum or tagged-union // scrutinee carrying any range is an upstream compile error, // not an unmodelled shape. semaSwitchValidateItems refuses the // same program on the runtime evaluator, with this same // message. if (sw_any_ranges) COMPILE_ERROR_UPSTREAM("switch_block: ranges not allowed when " "switching on type"); if (scrut >= ip->items_len) UNIMPLEMENTED( "zirSwitchBlock: switch scrutinee not an enum value"); uint32_t sw_match_tag = scrut; uint32_t sw_payload = IP_INDEX_NONE; bool sw_is_union_value = ip->item_tags[scrut] == IP_TAG_UNION_VALUE; if (sw_is_union_value) { uint32_t uv = ip->item_datas[scrut]; sw_match_tag = ip->extra[uv + 1]; // {ty, tag, val} sw_payload = ip->extra[uv + 2]; if (sw_match_tag >= ip->items_len || ip->item_tags[sw_match_tag] != IP_TAG_ENUM_TAG) UNIMPLEMENTED( "zirSwitchBlock: switch union tag is not enum value"); } else if (ip->item_tags[scrut] != IP_TAG_ENUM_TAG) { UNIMPLEMENTED( "zirSwitchBlock: switch scrutinee not an enum value"); } uint32_t sw_item_ty = ipEnumTagType(ip, sw_match_tag); // scalar_bodies then multi_bodies begin at `sw_cursor`: per // case, a prong_body followed by one item_body per item. uint32_t sw_total_items = zs.item_infos_len; const uint32_t* sw_item_infos = zs.item_infos; uint32_t sw_cursor = zs.end; uint32_t sw_item_idx = 0; uint32_t* sw_dup_items = zig1Malloc( (sw_total_items ? sw_total_items : 1u) * sizeof(uint32_t)); uint32_t sw_dup_items_len = 0; const uint32_t* sw_taken_body = NULL; uint32_t sw_taken_body_len = 0; uint32_t sw_taken_cap = 0; const uint32_t* sw_under_body = NULL; uint32_t sw_under_body_len = 0; uint32_t sw_under_cap = 0; bool sw_taken_tagcap = false; bool sw_under_tagcap = false; bool sw_matched = false; bool sw_taken_inline = false; // The taken prong's item count, for a by-ref capture. uint32_t sw_taken_items_len = 0; uint32_t sw_taken_items_off = 0; for (uint32_t ci = 0; ci < sw_total_cases; ci++) { uint32_t sw_items_count = (ci < sw_scalar_len) ? 1u : zs.multi_case_items_lens[ci - sw_scalar_len]; uint32_t sw_prong_info = zs.prong_infos[ci]; uint32_t sw_prong_cap = (sw_prong_info >> ZIR_PRONG_INFO_CAPTURE_SHIFT) & ZIR_PRONG_INFO_CAPTURE_MASK; bool sw_prong_inline = (sw_prong_info & ZIR_PRONG_INFO_IS_INLINE) != 0; bool sw_prong_tagcap = (sw_prong_info & ZIR_PRONG_INFO_HAS_TAG_CAPTURE) != 0; uint32_t sw_prong_len = sw_prong_info & ZIR_PRONG_INFO_BODY_LEN_MASK; const uint32_t* sw_prong = zirBodySlice(zir, sw_cursor); sw_cursor += sw_prong_len; bool sw_this = false; bool sw_under = false; uint32_t sw_prong_items = sw_dup_items_len; for (uint32_t it = 0; it < sw_items_count; it++) { uint32_t item_info = sw_item_infos[sw_item_idx++]; if ((item_info & ZIR_ITEM_INFO_KIND_MASK) == ZIR_ITEM_INFO_KIND_UNDER) { sw_under = true; continue; } uint32_t itag = ctSwitchItemBodyValue(sema, inst, item_info, &sw_cursor, sw_item_ty, (sw_prong_info & ZIR_PRONG_INFO_IS_COMPTIME_UNREACH) != 0); sw_dup_items[sw_dup_items_len++] = itag; if (itag == sw_match_tag) sw_this = true; } if (sw_this && !sw_matched) { // A comptime operand selects one item of an inline // prong: resolveSwitchProng analyzes it once, with // that item. sw_taken_inline = sw_prong_inline; sw_matched = true; sw_taken_items_len = sw_dup_items_len - sw_prong_items; sw_taken_items_off = sw_prong_items; sw_taken_body = sw_prong; sw_taken_body_len = sw_prong_len; sw_taken_cap = sw_prong_cap; sw_taken_tagcap = sw_prong_tagcap; } else if (sw_under && sw_under_body == NULL) { sw_under_body = sw_prong; sw_under_body_len = sw_prong_len; sw_under_cap = sw_prong_cap; sw_under_tagcap = sw_prong_tagcap; } } semaSwitchValidateItems( zcu, sw_item_ty, sw_dup_items, sw_dup_items_len, NULL, 0u); semaSwitchValidateSpecialProngs(ip, sw_item_ty, ipTypeOf(ip, scrut), sw_dup_items, sw_dup_items_len, NULL, 0u, zs.has_else, zs.has_under, (zs.else_info & ZIR_PRONG_ELSE_IS_SIMPLE_NORETURN) != 0); uint32_t sw_body_cap = zs.else_capture; // resolveSwitchBlock's tail: `"const else_is_named_only = // zir_switch.else_case != null and under_prong != null; ... if // (else_is_named_only and item_ty.enumTagFieldIndex(cond_val, // zcu) != null)"` takes `else`; otherwise the catch-all is // `"if (under_prong) |under| ... else ... else_case"` -- an // unnamed non-exhaustive value goes to `_`, not `else`. bool sw_else_named = sw_has_else && sw_under_body != NULL && ipEnumTagFieldIndex(ip, sw_match_tag) != UINT32_MAX; bool sw_body_tagcap = (zs.else_info & ZIR_PRONG_ELSE_HAS_TAG_CAPTURE) != 0; if (sw_matched) { sw_body = sw_taken_body; sw_body_len = sw_taken_body_len; sw_body_cap = sw_taken_cap; sw_body_tagcap = sw_taken_tagcap; } else if (sw_else_named || (sw_has_else && sw_under_body == NULL)) { sw_body = zs.else_body; sw_body_len = zs.else_body_len; } else if (sw_under_body != NULL) { sw_body = sw_under_body; sw_body_len = sw_under_body_len; sw_body_cap = sw_under_cap; sw_body_tagcap = sw_under_tagcap; } else { UNIMPLEMENTED("zirSwitchBlock: switch no matching prong, no else"); } // analyzeSwitchCaptures: a union item prong (or an `inline // else`, analyzed per item) captures the payload; a plain // `else` is the // `.special` arm's whole `loaded_operand`. if (sw_body_cap == 1 && sw_is_union_value && sw_matched && !sw_taken_inline) { uint32_t sw_cap_ty; sw_cap_val = semaSwitchByValCapture(zcu, ipTypeOf(ip, scrut), &sw_dup_items[sw_taken_items_off], sw_taken_items_len, sw_payload, &sw_cap_ty); } else if (sw_body_cap == 1) sw_cap_val = !sw_is_union_value ? sw_match_tag : (sw_matched || zs.else_is_inline) ? sw_payload : scrut; if (sw_body_cap == 2) { // analyzeSwitchCaptures' by-ref tagged-union capture: // `".special => return loaded_operand"` (else / `_`), and // for an item prong // analyzeSwitchPayloadCaptureTaggedUnion's // `"const field_ptr_val = try op_ptr_val.ptrField( // first_field_index, pt);"`. if (!sw_is_union_value) UNIMPLEMENTED( "zirSwitchBlock: switch by-ref non-union capture"); if (sw_matched ? sw_taken_inline : sw_body == zs.else_body && zs.else_is_inline) { // An `inline` prong is analyzed per item: // resolveSwitchPayloadCaptureTaggedUnion's field // pointer. sw_cap_val = valPtrField(zcu, sw_operand_ptr, semaUnionFieldIndexFromTag( ip, ipTypeOf(ip, scrut), sw_match_tag)); } else if (!sw_matched) { sw_cap_val = sw_operand_ptr; } else { uint32_t cap_ptr_ty; sw_cap_val = semaSwitchByRefCapturePtr(zcu, sw_operand_ptr, ipTypeOf(ip, sw_operand_ptr), ipTypeOf(ip, scrut), &sw_dup_items[sw_taken_items_off], sw_taken_items_len, &cap_ptr_ty); } } free(sw_dup_items); // resolveSwitchBlock: `"const tag_inst = zir_switch // .tag_capture_placeholder.unwrap() orelse switch_inst; if // (!tagged_union_originally) { ... return // sema.failWithInvalidSwitchTagCapture(...); } // sema.inst_map.putAssumeCapacity(tag_inst, .fromValue( // item_opv));"` -- the active tag. if (sw_body_tagcap) { if (!sw_is_union_value) COMPILE_ERROR_UPSTREAM("cannot: capture " "tag of non-union type"); sw_tag_cap_val = sw_match_tag; } } sw_eval: { // resolveSwitchProng: bind the taken prong's by-value capture and // analyze only its body, which yields the switch value through a // `break` targeting this instruction. uint32_t sw_saved_cap = values[sw_cap_inst]; uint32_t sw_saved_tag = values[sw_tag_inst]; if (sw_cap_val != IP_INDEX_NONE) values[sw_cap_inst] = sw_cap_val; if (sw_tag_cap_val != IP_INDEX_NONE) values[sw_tag_inst] = sw_tag_cap_val; CtEvalStatus sw_st = ctEvalBody( sema, sw_body, sw_body_len, ct_ret_val, ct_break_block, ct_break_val); if (sw_tag_cap_val != IP_INDEX_NONE) values[sw_tag_inst] = sw_saved_tag; if (sw_cap_val != IP_INDEX_NONE) values[sw_cap_inst] = sw_saved_cap; if (sw_st == CT_EVAL_RETURN) return sw_st; if (sw_st != CT_EVAL_BREAK) UNIMPLEMENTED( "zirSwitchBlock: switch prong fell through without break"); if (*ct_break_block != inst) return sw_st; // break targets an outer block; propagate // Sema.zirSwitchBlock's `error.ComptimeBreak` arm: `"if // (break_inst.tag != .switch_continue) return // error.ComptimeBreak; ... const new_operand = try sema.coerce( // child_block, raw_operand_ty, new_operand_uncoerced, ...); try // sema.emitBackwardBranch(child_block, src);"`. if (zir->inst_tags[zcu->comptime_break_inst] == ZIR_INST_SWITCH_CONTINUE) { scrut = ctCoerceToType(zcu, *ct_break_val, sw_raw_operand_ty); semaChargeBranches(zcu, 1); goto sw_restart; } values[inst] = *ct_break_val; return CT_EVAL_NORMAL; } } // Sema.zirSwitchBlockErrUnion's comptime path. static CtEvalStatus ctEvalSwitchBlockErrUnion(SemaCtx* sema, uint32_t inst, uint32_t* ct_ret_val, uint32_t* ct_break_block, uint32_t* ct_break_val) { Zcu* zcu = sema->zcu; const Zir* zir = sema->zir; uint32_t* values = sema->inst_values; InternPool* ip = &zcu->intern_pool; // Comptime-known `catch |err| switch (err)` / // `if (eu) |payload| ... else |err| switch (err)`. // Sema.zirSwitchBlockErrUnion first tests whether the error union // is a success; if so it evaluates only the non-error body and // does not validate the dead error switch prongs. Mirror that for an // interned `error_union_payload` value. Known-error operands stay loud. ZirSwitchBlock zs = zirGetSwitchBlock(zir, inst); if (zs.multi_case_ranges_lens != NULL) UNIMPLEMENTED("zirSwitchBlockErrUnion: switch_err_union ranges"); if (zs.has_under) UNIMPLEMENTED("zirSwitchBlockErrUnion: switch_err_union '_' prong"); if (zs.has_continue) UNIMPLEMENTED("zirSwitchBlockErrUnion: switch_err_union continue"); if (zs.tag_capture_placeholder != UINT32_MAX) UNIMPLEMENTED( "zirSwitchBlockErrUnion: switch_err_union tag placeholder"); uint32_t non_err_info = zs.non_err_info; uint32_t non_err_body_len = non_err_info & ZIR_PRONG_NON_ERR_BODY_LEN_MASK; uint32_t non_err_capture = (non_err_info >> ZIR_PRONG_NON_ERR_CAPTURE_SHIFT) & ZIR_PRONG_NON_ERR_CAPTURE_MASK; bool non_err_operand_is_ref = (non_err_info & ZIR_PRONG_NON_ERR_OPERAND_IS_REF) != 0; if (non_err_capture > 2u) UNIMPLEMENTED( "zirSwitchBlockErrUnion: switch_err_union non-error capture"); if (zs.else_capture == ZIR_PRONG_CAPTURE_BY_REF) COMPILE_ERROR_UPSTREAM("switch_err_union: error " "set cannot be captured by reference"); if (zs.else_info & ZIR_PRONG_ELSE_HAS_TAG_CAPTURE) UNIMPLEMENTED( "zirSwitchBlockErrUnion: switch_err_union else tag capture"); const uint32_t* non_err_body = zs.non_err_body; uint32_t op = semaResolveRef(zs.main_operand, values); if (op >= ip->items_len) UNIMPLEMENTED( "zirSwitchBlockErrUnion: switch_err_union tagged operand"); // A ref operand (`if (&eu)`): analyzePtrIsNonErr and the // `...Ptr` unwraps read through the pointer. uint32_t eu_ptr = IP_INDEX_NONE; if (non_err_operand_is_ref) { eu_ptr = op; op = pointerDeref(zcu, eu_ptr, NULL); if (op == IP_INDEX_NONE) UNIMPLEMENTED( "zirSwitchBlockErrUnion: switch_err_union runtime load"); } if (ip->item_tags[op] != IP_TAG_ERROR_UNION_PAYLOAD) { // `non_err_cond == .bool_false`: analyzeSwitchBlock over the // comptime error code (analyzeErrUnionCode), whose // resolve_at_comptime arm runs only the matching prong. if (!ipItemTagIs(ip, op, IP_TAG_ERROR_UNION_ERROR) && !ipItemTagIs(ip, op, IP_TAG_ERROR_SET_ERROR)) UNIMPLEMENTED("zirSwitchBlockErrUnion: switch_err_union comptime " "error operand"); uint32_t eu_err = semaErrUnionCodeComptime(ip, op); uint32_t eu_err_ty = ipTypeOf(ip, eu_err); uint32_t eu_err_name = ip->extra[ip->item_datas[eu_err] + 1]; uint32_t eu_scalar = zs.scalar_cases_len; uint32_t eu_total = eu_scalar + zs.multi_cases_len; uint32_t* eu_items = ARR_INIT(uint32_t, zs.item_infos_len + 1u); uint32_t eu_n = 0, eu_item_idx = 0, eu_cursor = zs.end; const uint32_t* eu_body = NULL; uint32_t eu_body_len = 0, eu_cap = 0, eu_off = 0, eu_cnt = 0; bool eu_matched = false, eu_inline = false; for (uint32_t ci = 0; ci < eu_total; ci++) { uint32_t cnt = ci < eu_scalar ? 1u : zs.multi_case_items_lens[ci - eu_scalar]; uint32_t pinfo = zs.prong_infos[ci]; uint32_t plen = pinfo & ZIR_PRONG_INFO_BODY_LEN_MASK; const uint32_t* prong = zirBodySlice(zir, eu_cursor); eu_cursor += plen; uint32_t off = eu_n; bool this_prong = false; for (uint32_t it = 0; it < cnt; it++) { uint32_t v = ctSwitchItemBodyValue(sema, inst, zs.item_infos[eu_item_idx++], &eu_cursor, eu_err_ty, (pinfo & ZIR_PRONG_INFO_IS_COMPTIME_UNREACH) != 0); eu_items[eu_n++] = v; if (ip->extra[ip->item_datas[v] + 1] == eu_err_name) this_prong = true; } if (this_prong && !eu_matched) { eu_matched = true; eu_body = prong; eu_body_len = plen; eu_cap = (pinfo >> ZIR_PRONG_INFO_CAPTURE_SHIFT) & ZIR_PRONG_INFO_CAPTURE_MASK; eu_inline = (pinfo & ZIR_PRONG_INFO_IS_INLINE) != 0; eu_off = off; eu_cnt = cnt; } } semaSwitchValidateItems(zcu, eu_err_ty, eu_items, eu_n, NULL, 0u); uint32_t eu_else_err_ty = semaSwitchValidateSpecialProngs(ip, eu_err_ty, eu_err_ty, eu_items, eu_n, NULL, 0u, zs.has_else, false, (zs.else_info & ZIR_PRONG_ELSE_IS_SIMPLE_NORETURN) != 0); // analyzeSwitchCaptures' error-set arm: the capture's type is // narrowed to the prong's items (`.@"inline"`: the one // value), or to `else_err_ty` for the `.special` prong. uint32_t eu_cap_ty = IP_INDEX_NONE; if (eu_matched) { if (eu_cap == 2u) COMPILE_ERROR_UPSTREAM("switch_err_union: " "error set cannot be captured " "by reference"); if (eu_cap == 1u) eu_cap_ty = eu_inline ? semaSwitchErrCaptureTy(ip, &eu_err, 1u) : semaSwitchErrCaptureTy(ip, &eu_items[eu_off], eu_cnt); } else if (zs.has_else) { eu_body = zs.else_body; eu_body_len = zs.else_body_len; if (zs.else_capture == ZIR_PRONG_CAPTURE_BY_VAL && zs.else_is_inline) { eu_cap_ty = semaSwitchErrCaptureTy(ip, &eu_err, 1u); } else if (zs.else_capture == ZIR_PRONG_CAPTURE_BY_VAL) { if (eu_else_err_ty == IP_INDEX_NONE) UNREACHABLE("switch_err_union: " "unreachable else capture"); eu_cap_ty = eu_else_err_ty; } } else { UNIMPLEMENTED( "zirSwitchBlockErrUnion: switch_err_union no matching prong"); } free(eu_items); // The switch inst is the err capture / switch operand; a // prong capture lives in the payload placeholder. uint32_t eu_cap_inst = zs.payload_capture_placeholder; if (eu_cap_ty != IP_INDEX_NONE && eu_cap_inst == UINT32_MAX) UNIMPLEMENTED("zirSwitchBlockErrUnion: switch_err_union capture " "without placeholder"); uint32_t eu_saved_sw = values[inst]; uint32_t eu_saved_cap = eu_cap_ty != IP_INDEX_NONE ? values[eu_cap_inst] : INST_VALUE_UNSET; values[inst] = eu_err; if (eu_cap_ty != IP_INDEX_NONE) values[eu_cap_inst] = ipGetErrorSetError(ip, eu_cap_ty, eu_err_name); CtEvalStatus eu_st = ctEvalBody(sema, eu_body, eu_body_len, ct_ret_val, ct_break_block, ct_break_val); values[inst] = eu_saved_sw; if (eu_cap_ty != IP_INDEX_NONE) values[eu_cap_inst] = eu_saved_cap; if (ctBlockBreakResult( eu_st, inst, values, ct_break_block, ct_break_val)) return CT_EVAL_NORMAL; if (eu_st != CT_EVAL_NORMAL) return eu_st; UNIMPLEMENTED("zirSwitchBlockErrUnion: switch_err_union error prong " "fell through"); } uint32_t eu_payload = ip->extra[ip->item_datas[op] + 1]; uint32_t saved_payload = values[inst]; bool bound_payload = false; if (non_err_capture == ZIR_PRONG_CAPTURE_BY_VAL) { values[inst] = eu_payload; bound_payload = true; } else if (non_err_capture == ZIR_PRONG_CAPTURE_BY_REF) { uint32_t eu_payload_ptr = IP_INDEX_NONE; if (eu_ptr == IP_INDEX_NONE || !semaErrUnionPayloadPtrComptime( zcu, ip, eu_ptr, &eu_payload_ptr) || eu_payload_ptr == IP_INDEX_NONE) UNIMPLEMENTED("zirSwitchBlockErrUnion: switch_err_union by-ref " "payload capture"); values[inst] = eu_payload_ptr; bound_payload = true; } CtEvalStatus sw_st = ctEvalBody(sema, non_err_body, non_err_body_len, ct_ret_val, ct_break_block, ct_break_val); if (bound_payload) values[inst] = saved_payload; if (ctBlockBreakResult(sw_st, inst, values, ct_break_block, ct_break_val)) return CT_EVAL_NORMAL; if (sw_st != CT_EVAL_NORMAL) return sw_st; UNIMPLEMENTED( "zirSwitchBlockErrUnion: switch_err_union success body fell through"); ZIG1_TCC_RETURN(CT_EVAL_NORMAL); } // Sema.analyzeBodyInner on a comptime Block, for bodies run outside an AIR // frame (declaration values, signatures, field types and defaults, // comptime and inline calls; CONSOLIDATION 5.4 lists the entries). `sema` // is the frame (ctFrameInit). Each instruction is first offered to // ctAnalyzeInst, which runs it through analyzeBodyInner; the switch runs // the rest, inline or as the ctEvalXxx arms above. A pointer arm decodes a // pointer into a CtCellPtr (allocation, access path, view) to mint the // next. CONSOLIDATION 5.4 lists what folds next. CtEvalStatus ctEvalBody(SemaCtx* sema, const uint32_t* body, uint32_t body_len, uint32_t* ct_ret_val, uint32_t* ct_break_block, uint32_t* ct_break_val) { Zcu* zcu = sema->zcu; const Zir* zir = sema->zir; uint32_t* values = sema->inst_values; InternPool* ip = &zcu->intern_pool; for (uint32_t i = 0; i < body_len; i++) { uint32_t inst = body[i]; ZirInstTag tag = zir->inst_tags[inst]; if (ctAnalyzeInst(sema, inst)) continue; switch (tag) { case ZIR_INST_EXTENDED: ctEvalExtended(sema, inst); break; case ZIR_INST_PARAM: case ZIR_INST_PARAM_COMPTIME: case ZIR_INST_PARAM_ANYTYPE: case ZIR_INST_PARAM_ANYTYPE_COMPTIME: // A generic return-type body can include the callee's // parameter instructions. The call evaluator has already bound // them in values[]; executing the parameter instruction itself // has no additional comptime effect. Outside a bound call (a // fn-proto TYPE's block re-run for its callconv) zirParam only // records the parameter for the `func` that follows; it yields // no value. break; case ZIR_INST_FUNC: case ZIR_INST_FUNC_INFERRED: case ZIR_INST_FUNC_FANCY: // Sema.zirFunc, zirFuncFancy: a declaration's function, owned by // the Nav analyzed (a fn proto is block_inline's arm below). values[inst] = navFuncDecl(zcu, sema->owner_nav); break; case ZIR_INST_FIELD_PTR: ctEvalFieldPtr(zcu, zir, inst, values); break; case ZIR_INST_BOOL_BR_AND: case ZIR_INST_BOOL_BR_OR: { // Short-circuiting `and`/`or` (Sema.zirBoolBr comptime path). // ZIR pl_node -> payload { lhs: Ref, body_len } + rhs body // (the rhs is lowered into an inline sub-block terminated by a // `break_inline` targeting this instruction). Resolve the // comptime-known lhs; for `and` a false lhs short-circuits to // false (rhs body not evaluated), a true lhs yields the rhs // value; `or` is dual. ZirInstBoolBr bb; uint32_t rhs_body = zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &bb); uint32_t lhs = semaResolveRef(bb.lhs, values); if (lhs != ZIR_REF_BOOL_TRUE && lhs != ZIR_REF_BOOL_FALSE) UNIMPLEMENTED("analyzeBodyInner: bool_br non-bool lhs"); bool is_and = (tag == ZIR_INST_BOOL_BR_AND); bool short_circuit = is_and ? (lhs == ZIR_REF_BOOL_FALSE) : (lhs == ZIR_REF_BOOL_TRUE); if (short_circuit) { values[inst] = lhs; // `false and _` -> false; `true or _` -> true break; } // Evaluate the rhs body; its terminating break_inline targets // this instruction and supplies the result. CtEvalStatus st = ctEvalBody(sema, zirBodySlice(zir, rhs_body), bb.body_len, ct_ret_val, ct_break_block, ct_break_val); if (ctBlockBreakResult( st, inst, values, ct_break_block, ct_break_val)) break; if (st != CT_EVAL_NORMAL) return st; UNIMPLEMENTED("analyzeBodyInner: bool_br rhs did not break to it"); } case ZIR_INST_BLOCK_INLINE: { // `block_inline` serves two roles: a forced-comptime block // (e.g. the result-type wrapper of a typed struct literal) and // the loop block of an `inline while`/`inline for` (astgen's // loop_tag). As a loop block its body terminates with // `repeat_inline` (CT_EVAL_REPEAT) to restart the iteration; a // forced-comptime block (and the loop's inner cond block) // never emits repeat_inline, so looping on repeat is correct // for every shape. Mirrors Sema.zirBlockInline: a block_inline // that a `repeat` targets is re-analyzed; otherwise a // `break_inline` to it supplies its result. Same Block payload // { body_len, body... }. ZirInstBlock blk; const uint32_t* bbody = zirBodySlice(zir, zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &blk)); uint32_t blen = blk.body_len; if (ctBlockInlineBreaksFuncType(zir, bbody, blen)) { values[inst] = ctResolveFuncTypeFromBody( ip, zcu, zir, bbody, blen, values, sema); break; } for (;;) { CtEvalStatus st = ctEvalBody(sema, bbody, blen, ct_ret_val, ct_break_block, ct_break_val); if (st == CT_EVAL_REPEAT) continue; // repeat_inline restarts this loop block if (ctBlockBreakResult( st, inst, values, ct_break_block, ct_break_val)) break; if (st != CT_EVAL_NORMAL) return st; UNIMPLEMENTED("analyzeBodyInner: inline block fell through " "without break"); } break; } case ZIR_INST_BLOCK: { // A comptime `block` runs its body; a `break`/`break_inline` // targeting this block supplies its result value. Same Block // payload layout { body_len, body... }. Mirrors // Sema.zirBlock's comptime path. (A non-inline block never // contains repeat — a `repeat` targets a ZIR_INST_LOOP, // handled separately.) ZirInstBlock blk; uint32_t blk_body = zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &blk); CtEvalStatus st = ctEvalBody(sema, zirBodySlice(zir, blk_body), blk.body_len, ct_ret_val, ct_break_block, ct_break_val); if (ctBlockBreakResult( st, inst, values, ct_break_block, ct_break_val)) break; if (st != CT_EVAL_NORMAL) return st; UNIMPLEMENTED( "analyzeBodyInner: block fell through without break"); } case ZIR_INST_CONDBR: case ZIR_INST_CONDBR_INLINE: { // Comptime `condbr`: resolve the (comptime-known) condition // and run only the taken branch. `condbr` is a body // terminator, so its branch's status propagates as this body's // status. `condbr_inline` (Zir.zig: "condition is coerced to a // comptime value, and only the taken branch is analyzed") // shares the exact CondBr payload and semantics — a comptime // inline `if`; its taken branch terminates with an inline // noreturn (break_inline / ret), both already handled below. // Identical decode. ZirInstCondBr cb; const uint32_t* then_body = zirBodySlice(zir, zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &cb)); uint32_t cond = semaResolveRef(cb.condition, values); const uint32_t* taken; uint32_t taken_len; if (cond == ZIR_REF_BOOL_TRUE) { taken = then_body; taken_len = cb.then_body_len; } else if (cond == ZIR_REF_BOOL_FALSE) { taken = then_body + cb.then_body_len; taken_len = cb.else_body_len; } else { UNIMPLEMENTED("condbr: non-bool condition"); } // Sema.zig:maybeErrorUnwrapCondbr: `x catch unreachable` on an // error reports "caught unexpected error" with its name, which // this evaluator does not reproduce. uint32_t cond_ref = cb.condition; if (cond == ZIR_REF_BOOL_FALSE && cond_ref >= ZIR_REF_START_INDEX && zir->inst_tags[cond_ref - ZIR_REF_START_INDEX] == ZIR_INST_IS_NON_ERR) { for (uint32_t k = 0; k < taken_len; k++) { ZirInstTag tk = zir->inst_tags[taken[k]]; if (tk == ZIR_INST_DBG_STMT || tk == ZIR_INST_SAVE_ERR_RET_INDEX) continue; if (tk == ZIR_INST_UNREACHABLE) UNIMPLEMENTED("analyzeBodyInner: comptime error " "caught by unreachable"); break; } } return ctEvalBody(sema, taken, taken_len, ct_ret_val, ct_break_block, ct_break_val); } case ZIR_INST_DEFER: { // `Sema.analyzeBodyInner`'s `.@"defer"`: run the deferred body // in place. It ends in its own `break_inline` (AstGen's // marker, which targets no block); a body that ends noreturn // ends this one too, and any other break propagates outward. uint32_t d_index = zir->inst_datas[inst].defer_data.index; uint32_t d_len = zir->inst_datas[inst].defer_data.len; const uint32_t* d_body = zirBodySlice(zir, d_index); CtEvalStatus st = ctEvalBody( sema, d_body, d_len, ct_ret_val, ct_break_block, ct_break_val); if (st != CT_EVAL_BREAK || d_len == 0) return st; uint32_t d_last = d_body[d_len - 1]; if (zir->inst_tags[d_last] != ZIR_INST_BREAK_INLINE) return st; ZirInstBreak d_brk; zirExtraData( zir, zir->inst_datas[d_last].break_data.payload_index, &d_brk); if (*ct_break_block != d_brk.block_inst) return st; values[inst] = ZIR_REF_VOID_VALUE; break; } case ZIR_INST_BREAK: case ZIR_INST_BREAK_INLINE: case ZIR_INST_SWITCH_CONTINUE: { // Zir.Inst.Break payload: { operand_src_node, block_inst }. // The target block index is the second payload word. A // comptime `switch_continue` is the same ComptimeBreak (Sema. // analyzeBodyInner: `".switch_continue => if // (block.isComptime()) { sema.comptime_break_inst = inst; // return error.ComptimeBreak; // }"`); the targeted switch re-dispatches on it. ZirInstBreak brk; zirExtraData( zir, zir->inst_datas[inst].break_data.payload_index, &brk); *ct_break_block = brk.block_inst; *ct_break_val = semaResolveRef( zir->inst_datas[inst].break_data.operand, values); zcu->comptime_break_inst = inst; return CT_EVAL_BREAK; } case ZIR_INST_UNREACHABLE: // Sema.zig:zirUnreachable's comptime arm. COMPILE_ERROR_UPSTREAM("reached unreachable code"); case ZIR_INST_RET_IMPLICIT: case ZIR_INST_RET_NODE: { // Both carry the returned operand in the second data word. uint32_t rv = semaResolveRef( zir->inst_datas[inst].un_node.operand, values); // Sema's `ret` coerces the operand to the declared return // type. This is not always explicit in ZIR: `return sp.*` may rely // on an optional wrap, and `inline fn zero(comptime T: type) T { // return 0; }` relies on the call instantiation's concrete `T` to // type the returned zero. A cell pointer // (ctIsCellPointerClassValue: a ptr_comptime_alloc chain) skips // ctCoerceToType/ctValueConformsToType too, which do not carry // the sentinel-array (`[N:0]T`) -> sentinel-slice // (`[:0]T`) coercion shape faithfully (`return &result;` from a // `[3:0]u8` local needs this). // analyzeRet's coercion into an error union or optional is // wrapErrorUnionPayload / wrapOptional of the operand coerced // to the payload type, which a cell pointer takes too. uint32_t rv_ret_ty = sema->fn_ret_ty; if (ctIsCellPointerClassValue(ip, rv) && rv_ret_ty != IP_INDEX_NONE && rv_ret_ty != IP_INDEX_GENERIC_POISON_TYPE) { bool rv_eu = ipItemTagIs(ip, rv_ret_ty, IP_TAG_TYPE_ERROR_UNION) || ipItemTagIs(ip, rv_ret_ty, IP_TAG_TYPE_ANYERROR_UNION); if (rv_eu || ztIsZigTypeTag(ip, rv_ret_ty, ZIG_TYPE_TAG_OPTIONAL)) { uint32_t pl = ctCoerceToType(zcu, rv, rv_eu ? ipErrorUnionPayload(ip, rv_ret_ty) : ipOptionalChild(ip, rv_ret_ty)); if (!semaRefIsInterned(pl)) UNIMPLEMENTED( "analyzeBodyInner: return wraps a sentinel"); rv = rv_eu ? ipGetErrorUnionPayload(ip, rv_ret_ty, pl) : ipGetOptPayload(ip, rv_ret_ty, pl); } } bool rv_can_coerce = semaRefIsInterned(rv) && !ctIsCellPointerClassValue(ip, rv); if (rv_can_coerce && sema->fn_ret_ty != IP_INDEX_NONE && sema->fn_ret_ty != IP_INDEX_GENERIC_POISON_TYPE) { rv = ctCoerceToType(zcu, rv, sema->fn_ret_ty); if (!ctValueConformsToType(ip, rv, sema->fn_ret_ty)) UNIMPLEMENTED( "analyzeBodyInner: return value type mismatch"); } *ct_ret_val = rv; return CT_EVAL_RETURN; } case ZIR_INST_RET_ERR_VALUE: { // Comptime `return error.Name` (Sema.zirRetErrValue) — e.g. // `math.sub`'s overflow branch, genuinely taken at comptime by // pdq.zig `math.sub(usize, top, 1) catch break` when top==0. // Mirror the runtime handler's interning (single-name set + // the fn set's err), but return the bare error value: // ctEvalBody's comptime-call convention carries errors as // error_set_error directly (see IS_NON_ERR / // ERR_UNION_PAYLOAD_UNSAFE above), not as an EU wrapper. ZIR // str_tok { start = name offset }. uint32_t rev_off = zir->inst_datas[inst].str_tok.start; const char* rev_name = (const char*)&zir->string_bytes[rev_off]; uint32_t rev_str = ipGetOrPutString(ip, rev_name, (uint32_t)strlen(rev_name)); uint32_t rev_single = ipGetErrorSetType(ip, &rev_str, 1); uint32_t rev_err = ipGetErrorSetError(ip, rev_single, rev_str); if (sema->fn_ret_ty != IP_INDEX_NONE && (ztIsZigTypeTag( ip, sema->fn_ret_ty, ZIG_TYPE_TAG_ERROR_UNION))) { // Coerce into the fn's declared error set (wrapErrorUnionSet's // set-level intern; dedups when it is the single set). rev_err = semaCoerceComptime(zcu, ip, rev_err, ipErrorUnionErrorSet(ip, sema->fn_ret_ty)); } *ct_ret_val = rev_err; return CT_EVAL_RETURN; } case ZIR_INST_CALL: ctEvalCall(sema, inst, ct_ret_val, ct_break_block, ct_break_val); break; case ZIR_INST_BLOCK_COMPTIME: { // Sema.zirBlockComptime: run the body in a comptime scope; the // terminating `break_inline` supplies the block's result. // Unlike a plain `block`, the BlockComptime payload is { // reason, body_len // }. (A comptime-returning helper wraps its result expression in // such a block.) ZirInstBlockComptime bc; uint32_t blk_body = zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &bc); CtEvalStatus st = ctEvalBody(sema, zirBodySlice(zir, blk_body), bc.body_len, ct_ret_val, ct_break_block, ct_break_val); if (ctBlockBreakResult( st, inst, values, ct_break_block, ct_break_val)) break; if (st != CT_EVAL_NORMAL) return st; UNIMPLEMENTED("block_comptime: fell through without break"); } case ZIR_INST_TYPEOF_BUILTIN: ctEvalTypeofBuiltin(sema, inst); break; case ZIR_INST_FIELD_PTR_LOAD: ctEvalFieldPtrLoad(zcu, zir, inst, values); break; case ZIR_INST_OPTIONAL_PAYLOAD_SAFE_PTR: case ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE_PTR: ctEvalOptionalPayloadPtr(zcu, zir, inst, values); break; case ZIR_INST_SWITCH_BLOCK: case ZIR_INST_SWITCH_BLOCK_REF: { CtEvalStatus sw_st = ctEvalSwitchBlock( sema, inst, ct_ret_val, ct_break_block, ct_break_val); if (sw_st != CT_EVAL_NORMAL) return sw_st; break; } case ZIR_INST_FIELD_CALL: ctEvalFieldCall( sema, inst, ct_ret_val, ct_break_block, ct_break_val); break; case ZIR_INST_DECL_LITERAL: case ZIR_INST_DECL_LITERAL_NO_COERCE: ctEvalDeclLiteral(zcu, zir, inst, values); break; case ZIR_INST_STRUCT_INIT_ANON: { // Sema.zirStructInitAnon -> structInitAnon(.anon_init). ZirInstStructInitAnon extra; uint32_t items_off = zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &extra); values[inst] = ctStructInitAnon( sema, inst, extra.fields_len, items_off, false); break; } case ZIR_INST_STRUCT_INIT: ctEvalStructInit(sema, inst); break; case ZIR_INST_ARRAY_INIT_ELEM_TYPE: ctEvalArrayInitElemType(zcu, zir, inst, values); break; case ZIR_INST_ARRAY_CAT: ctEvalArrayCat(zcu, zir, inst, values); break; case ZIR_INST_VALIDATE_PTR_STRUCT_INIT: { // Sema.zirValidatePtrStructInit verifies a result-location struct // init and stores declared defaults into omitted fields. Direct // comptime struct-init paths already fill defaults while building // aggregates; by-ref/result-location init writes fields through // comptime cells one at a time, so validation must complete the // partially initialized cell here. ZirInstBlock vp; const uint32_t* vp_body = zirBodySlice(zir, zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &vp)); uint32_t vp_body_len = vp.body_len; if (vp_body_len == 0) break; uint32_t first_fp = vp_body[0]; if (zir->inst_tags[first_fp] != ZIR_INST_STRUCT_INIT_FIELD_PTR) UNIMPLEMENTED("validate_ptr_struct_init: non-field body"); ZirInstField first_field; zirExtraData(zir, zir->inst_datas[first_fp].pl_node.payload_index, &first_field); uint32_t obj_ptr = semaResolveRef(first_field.lhs, values); if (!ctIsCellPointerClassValue(ip, obj_ptr)) { // Any other interned pointer: an OPV aggregate has nothing // left to store, every omitted field being OPV too. IpPtrInfo vp_info; if (obj_ptr >= ip->items_len || ipPtrInfo(ip, ipTypeOf(ip, obj_ptr), &vp_info) != IP_PTRI_OK || ztOnePossibleValue(ip, vp_info.child) == IP_INDEX_NONE) UNIMPLEMENTED("validate_ptr_struct_init: non-cell object"); break; } CtCellPtr base = ctCellPtrResolveDescriptor(zcu, ip, obj_ptr); uint32_t struct_ty = ctCellPtrType(zcu, ip, obj_ptr); if (ipTagIsAnyUnion(ip->item_tags[struct_ty])) { if (vp_body_len != 1) UNIMPLEMENTED("validate_ptr_struct_init: union fields"); break; } if (!ipTagIsAnyStruct(ip->item_tags[struct_ty])) UNIMPLEMENTED("validate_ptr_struct_init: non-struct pointee"); uint32_t fields_len = ipStructFieldsLen(ip, struct_ty); if (vp_body_len == fields_len) break; if (vp_body_len > fields_len) UNIMPLEMENTED("validate_ptr_struct_init: field count"); bool* seen = zig1Calloc(fields_len ? fields_len : 1, sizeof(bool)); for (uint32_t j = 0; j < vp_body_len; j++) { uint32_t fp_inst = vp_body[j]; if (zir->inst_tags[fp_inst] != ZIR_INST_STRUCT_INIT_FIELD_PTR) UNIMPLEMENTED("validate_ptr_struct_init: body tag"); ZirInstField field; zirExtraData(zir, zir->inst_datas[fp_inst].pl_node.payload_index, &field); uint32_t fp_obj = semaResolveRef(field.lhs, values); if (fp_obj != obj_ptr) UNIMPLEMENTED("validate_ptr_struct_init: mixed object"); const char* name = zirNullTerminatedString(zir, field.field_name_start); uint32_t name_ip = ipGetOrPutString(ip, name, (uint32_t)strlen(name)); uint32_t fidx = ipStructFieldIndex(ip, struct_ty, name_ip); if (fidx == UINT32_MAX) { free(seen); UNIMPLEMENTED("validate_ptr_struct_init: bad field"); } seen[fidx] = true; } for (uint32_t f = 0; f < fields_len; f++) { if (seen[f]) continue; if (ipStructFieldIsComptime(ip, struct_ty, f)) continue; uint32_t dv = semaFieldDefaultValue(zcu, struct_ty, f); if (dv == IP_INDEX_NONE) { free(seen); UNIMPLEMENTED("validate_ptr_struct_init: missing default"); } // ctCellPtrStore accepts either representation -- safe // either way. ctCellPtrStore( zcu, ip, ctCellPtrFieldPtr(zcu, ip, base, f), dv); } free(seen); break; } case ZIR_INST_VALIDATE_PTR_ARRAY_INIT: // Compile-time validation instructions (Sema.zirValidate*): assert // the result type is a struct/array of the right shape. At // comptime these emit no value and never fail for the well-formed // std literals reached here — a no-op. break; case ZIR_INST_ELEM_VAL: ctEvalElemVal(zcu, zir, inst, values); break; case ZIR_INST_STRUCT_INIT_FIELD_PTR: ctEvalStructInitFieldPtr(zcu, zir, inst, values); break; case ZIR_INST_ARRAY_INIT: case ZIR_INST_ARRAY_INIT_ANON: case ZIR_INST_ARRAY_INIT_REF: ctEvalArrayInit(zcu, zir, inst, values); break; case ZIR_INST_AS_NODE: ctEvalAsNode(zcu, zir, inst, values); break; case ZIR_INST_MAKE_PTR_CONST: ctEvalMakePtrConst(zcu, zir, inst, values); break; case ZIR_INST_REF: ctEvalRef(zcu, zir, inst, values); break; case ZIR_INST_ALLOC: case ZIR_INST_ALLOC_MUT: ctEvalAlloc(zcu, zir, inst, values); break; case ZIR_INST_ELEM_PTR_LOAD: ctEvalElemPtrLoad(zcu, zir, inst, values); break; case ZIR_INST_ELEM_PTR: case ZIR_INST_ELEM_PTR_NODE: ctEvalElemPtr(zcu, zir, inst, values); break; case ZIR_INST_RET_PTR: { // Sema.zirRetPtr: the result-location pointer for a by-ref // return. Model it as a fresh comptime cell of the function's // declared return type; the body stores the return value // through it and ret_load reads it back. if (sema->fn_ret_ty == IP_INDEX_NONE) UNIMPLEMENTED("ret_ptr: with no return type"); // Real ptr_comptime_alloc root: sema->fn_ret_ty is // guaranteed concrete by the guard just above. values[inst] = analyzeComptimeAlloc( zcu, ip, sema->fn_ret_ty, IP_PTR_FLAGS_MUT_ONE, NULL); break; } case ZIR_INST_RET_LOAD: { // Sema.zirRetLoad: load the return result-location pointer and // return its value. ZIR un_node -> operand = the ret_ptr. uint32_t rl_ptr = semaResolveRef( zir->inst_datas[inst].un_node.operand, values); // In a comptime/inlined block `Sema.zirRetLoad` calls // `analyzeLoad`; its `.one_possible_value => return // .fromValue(...)` arm wins before pointer dereference. Reuse // LOAD's OPV pointer reader before requiring a comptime cell. uint32_t opv_value; if (semaOpvPtrInfo(ip, rl_ptr, NULL, &opv_value)) { *ct_ret_val = opv_value; return CT_EVAL_RETURN; } if (!ctIsCellPointerClassValue(ip, rl_ptr)) UNIMPLEMENTED("ret_load: from non-cell pointer"); *ct_ret_val = ctCellPtrLoad(zcu, ip, rl_ptr); return CT_EVAL_RETURN; } case ZIR_INST_FOR_LEN: ctEvalForLen(zcu, zir, inst, values); break; case ZIR_INST_LOOP: { // A comptime loop (Sema.zirLoop comptime path): re-evaluate // the loop body until a `break` targeting this loop exits it. // A `repeat` restarts the body. ZIR pl_node -> Block { // body_len, body... }. ZirInstBlock loop; const uint32_t* lbody = zirBodySlice(zir, zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &loop)); uint32_t lblen = loop.body_len; for (;;) { // Each restart was charged at its `repeat`. CtEvalStatus lst = ctEvalBody(sema, lbody, lblen, ct_ret_val, ct_break_block, ct_break_val); if (lst == CT_EVAL_REPEAT) continue; if (ctBlockBreakResult( lst, inst, values, ct_break_block, ct_break_val)) break; if (lst != CT_EVAL_NORMAL) return lst; // Normal fall-through of a loop body == implicit repeat // (Sema appends a `repeat` when the body doesn't // terminate). } break; } case ZIR_INST_REPEAT: case ZIR_INST_REPEAT_INLINE: // `repeat`/`repeat_inline`: restart the enclosing comptime // loop body, first charging `"try // sema.emitBackwardBranch(block, src);"`. semaChargeBranches(zcu, 1); return CT_EVAL_REPEAT; case ZIR_INST_SLICE_SENTINEL: ctEvalSliceSentinel(zcu, zir, inst, values); break; case ZIR_INST_SLICE_START: ctEvalSliceStart(zcu, zir, inst, values); break; case ZIR_INST_SLICE_END: ctEvalSliceEnd(zcu, zir, inst, values); break; case ZIR_INST_SLICE_LENGTH: ctEvalSliceLength(zcu, zir, inst, values); break; case ZIR_INST_FIELD_PTR_NAMED_LOAD: case ZIR_INST_FIELD_PTR_NAMED: ctEvalFieldPtrNamedLoad(zcu, zir, inst, values); break; case ZIR_INST_PTR_TYPE: ctEvalPtrType(sema, inst); break; case ZIR_INST_TRY: { // Comptime `try`: a known-success error union unwraps the // payload and skips the error body. Runtime operands are // handled by analyzeBodyInner. ZirInstTry try_extra; uint32_t err_body = zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &try_extra); uint32_t op = semaResolveRef(try_extra.operand, values); if (op < ip->items_len && ip->item_tags[op] == IP_TAG_ERROR_UNION_PAYLOAD) { uint32_t d = ip->item_datas[op]; values[inst] = ip->extra[d + 1]; // {err_union_ty, payload} break; } // zirTry's comptime-error arm: the noreturn error body runs in // the parent block. if (ipItemTagIs(ip, op, IP_TAG_ERROR_UNION_ERROR) || ipItemTagIs(ip, op, IP_TAG_ERROR_SET_ERROR)) return ctEvalBody(sema, zirBodySlice(zir, err_body), try_extra.body_len, ct_ret_val, ct_break_block, ct_break_val); UNIMPLEMENTED("try"); } case ZIR_INST_TRY_PTR: { // Sema.zirTryPtr on a comptime-known error union: a success is // analyzeErrUnionPayloadPtr; an error analyzes the noreturn // error body in place. Zir.Inst.Try: { operand, body_len }, // then body. ZirInstTry try_extra; uint32_t err_body = zirExtraData( zir, zir->inst_datas[inst].pl_node.payload_index, &try_extra); uint32_t tp_ptr; if (!semaErrUnionPayloadPtrComptime(zcu, ip, semaResolveRef(try_extra.operand, values), &tp_ptr)) UNIMPLEMENTED("try_ptr: operand"); if (tp_ptr != IP_INDEX_NONE) { values[inst] = tp_ptr; break; } return ctEvalBody(sema, zirBodySlice(zir, err_body), try_extra.body_len, ct_ret_val, ct_break_block, ct_break_val); } case ZIR_INST_SWITCH_BLOCK_ERR_UNION: { CtEvalStatus sw_st = ctEvalSwitchBlockErrUnion( sema, inst, ct_ret_val, ct_break_block, ct_break_val); if (sw_st != CT_EVAL_NORMAL) return sw_st; break; } default: zirAbortUnhandled(tag, false); } } return CT_EVAL_NORMAL; } // ── Comptime calls and inline calls // ───────────────────────────────────────── CtEvalStatus ctEvalBodyIn(Zcu* zcu, CtBlockScope scope, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t* values, uint32_t* ret_val, uint32_t* br_val) { uint32_t br_block = UINT32_MAX; ZcuSemaBranch sb; ZcuSemaBranch* saved_sb = semaBranchEnter(zcu, &sb); SemaDetachedFrame f; ctFrameInit(&f, zcu, zir, values, IP_INDEX_NONE, scope); CtEvalStatus st = ctEvalBody(&f.sema, body, body_len, ret_val, &br_block, br_val); ctFrameDeinit(&f); semaBranchLeave(zcu, saved_sb); return st; } // The value a lexically-comptime body's terminating break or return yields, // or IP_INDEX_NONE if it fell through. static uint32_t ctBodyResult( CtEvalStatus st, uint32_t br_val, uint32_t ret_val) { if (st == CT_EVAL_BREAK) return br_val; if (st == CT_EVAL_RETURN) return ret_val; return IP_INDEX_NONE; } // A body of a live frame (a fn proto's types and the rest of its block), in // its Block and inst map, as upstream's sema.resolveInlineBody on that block; // such a body is lexically comptime. static uint32_t ctEvalBodyInFrame( SemaCtx* frame, const uint32_t* body, uint32_t body_len) { Zcu* zcu = frame->zcu; uint32_t ret_val = ZIR_REF_VOID_VALUE, br_val = 0, br_block = UINT32_MAX; const CtEvaluator saved_ct = zcu->ct; zcu->ct.type_resolve_ns = frame->ns; bool saved_lexical = frame->ictx->ct_lexical; frame->ictx->ct_lexical = true; CtEvalStatus st = ctEvalBody(frame, body, body_len, &ret_val, &br_block, &br_val); frame->ictx->ct_lexical = saved_lexical; zcu->ct = saved_ct; return ctBodyResult(st, br_val, ret_val); } // A lexically-comptime body of its own Block in the current Sema (a generic // call's parameter and return types, with the call's arguments in `values`). // `type_name_ctx` is that Block's: the callee Nav's, as analyzeCall's. static uint32_t ctEvalBodyInBlock(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t* values, uint32_t ns, TypeNameCtx type_name_ctx) { uint32_t ret_val = ZIR_REF_VOID_VALUE, br_val = 0, br_block = UINT32_MAX; const CtEvaluator saved_ct = zcu->ct; zcu->ct.type_resolve_ns = ns; SemaDetachedFrame f; ctFrameInit(&f, zcu, zir, values, IP_INDEX_NONE, (CtBlockScope) { ns, type_name_ctx, NULL, IP_INDEX_NONE, true }); CtEvalStatus st = ctEvalBody(&f.sema, body, body_len, &ret_val, &br_block, &br_val); ctFrameDeinit(&f); zcu->ct = saved_ct; return ctBodyResult(st, br_val, ret_val); } // A declaration-level comptime body in a Sema of its own: a type body, enum // and field-default value bodies, a `func_fancy` callconv body. Upstream's // block there has the owner's name: `".type_name_ctx = struct_obj.name"` // (type_resolution.zig; union_obj, enum_obj) or `old_nav.name`. uint32_t ctEvalBodyScoped(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t* values, uint32_t ns, TypeNameCtx type_name_ctx) { ZcuSemaBranch sb; ZcuSemaBranch* saved_sb = semaBranchEnter(zcu, &sb); uint32_t rv = ctEvalBodyInBlock( zcu, zir, body, body_len, values, ns, type_name_ctx); semaBranchLeave(zcu, saved_sb); return rv; } // A container type's name, the type_name_ctx of upstream's block for its field // type, default, alignment, backing and tag bodies (`".type_name_ctx = // struct_obj.name"`, union_obj, enum_obj; type_resolution.zig). TypeNameCtx semaContainerTypeNameCtx(const InternPool* ip, uint32_t ty) { uint32_t name = ipContainerTypeName(ip, ty); if (name == IP_INDEX_NONE) return TYPE_NAME_CTX_NONE; return (TypeNameCtx) { name, ipContainerTypeFqn(ip, ty) }; } static uint32_t ctBoundTypeRef( const InternPool* ip, uint32_t ref, const uint32_t* values) { uint32_t ty = semaResolveRef(ref, values); if (ty < ZIR_REF_START_INDEX) return (ty < ZIR_REF_UNDEF) ? ty : IP_INDEX_NONE; return ipIndexIsType(ip, ty) ? ty : IP_INDEX_NONE; } static uint32_t ctWrapBoundInferredReturn(Zcu* zcu, const Zir* zir, uint32_t func_inst, uint32_t fallback_ty, uint32_t payload_ty) { InternPool* ip = &zcu->intern_pool; if (!zirFuncInstIsInferred(zir, func_inst)) return payload_ty; if (payload_ty == IP_INDEX_NONE) return fallback_ty; if (fallback_ty >= ip->items_len || ip->item_tags[fallback_ty] != IP_TAG_TYPE_ERROR_UNION) return payload_ty; uint32_t err_set = ipErrorUnionErrorSet(ip, fallback_ty); if (err_set >= ip->items_len || ip->item_tags[err_set] != IP_TAG_TYPE_INFERRED_ERROR_SET) return payload_ty; return ipGetErrorUnionType(ip, err_set, payload_ty); } static bool ctInferredErrorSetSlot( const InternPool* ip, uint32_t error_set, uint32_t* out_slot) { if (error_set >= ip->items_len || ip->item_tags[error_set] != IP_TAG_TYPE_INFERRED_ERROR_SET) return false; uint32_t func_index = ip->item_datas[error_set]; uint32_t slot = ipFuncIesResolvedSlot(ip, func_index); if (slot == UINT32_MAX || slot >= ip->extra_len) UNREACHABLE("ctInferredErrorSetSlot: missing resolved set slot"); *out_slot = slot; return true; } static bool ctReturnInferredErrorSetSlot( const InternPool* ip, uint32_t ret_ty, uint32_t* out_slot) { if (ret_ty >= ip->items_len || ip->item_tags[ret_ty] != IP_TAG_TYPE_ERROR_UNION) return false; return ctInferredErrorSetSlot( ip, ipErrorUnionErrorSet(ip, ret_ty), out_slot); } static uint32_t ctConcreteInferredReturnType(InternPool* ip, uint32_t ret_ty) { if (ret_ty >= ip->items_len || ip->item_tags[ret_ty] != IP_TAG_TYPE_ERROR_UNION) return ret_ty; uint32_t err_set = ipErrorUnionErrorSet(ip, ret_ty); if (err_set >= ip->items_len || ip->item_tags[err_set] != IP_TAG_TYPE_INFERRED_ERROR_SET) return ret_ty; uint32_t resolved = ipResolvedInferredErrorSet(ip, err_set); if (resolved == IP_INDEX_NONE) resolved = ipGetErrorSetType(ip, NULL, 0); uint32_t payload_ty = ipErrorUnionPayload(ip, ret_ty); if (resolved == ZIR_REF_ANYERROR_TYPE) return ipGetAnyerrorUnionType(ip, payload_ty); return ipGetErrorUnionType(ip, resolved, payload_ty); } static uint32_t ctFinalizeComptimeReturn( Zcu* zcu, uint32_t ret_val, uint32_t ret_ty, uint32_t* out_ret_ty) { InternPool* ip = &zcu->intern_pool; uint32_t final_ty = ctConcreteInferredReturnType(ip, ret_ty); if (out_ret_ty) *out_ret_ty = final_ty; if (final_ty == ret_ty || !semaRefIsInterned(ret_val)) return ret_val; // The same error union under its resolved set's name: a retype of the // value (`getCoerced`), not a coercion that would ask the set rule of // a set against its own resolution. if (ipItemTagIs(ip, ret_val, IP_TAG_ERROR_UNION_PAYLOAD) && ipTypeOf(ip, ret_val) == ret_ty) return ipGetErrorUnionPayload(ip, final_ty, ipPayloadVal(ip, ret_val)); if (ipItemTagIs(ip, ret_val, IP_TAG_ERROR_UNION_ERROR) && ipTypeOf(ip, ret_val) == ret_ty) return ipGetErrorUnionError(ip, final_ty, ipErrorName(ip, ret_val)); return ctCoerceToType(zcu, ret_val, final_ty); } static uint32_t ctBoundFuncReturnType(Zcu* zcu, const Zir* zir, uint32_t func_inst, uint32_t* values, uint32_t ns, TypeNameCtx type_name_ctx, uint32_t fallback_ty) { const InternPool* ip = &zcu->intern_pool; if (!zirIsFuncInst(zir->inst_tags[func_inst])) return fallback_ty; ZirFnInfo fn_info = zirGetFnInfo(zir, func_inst); if (!fn_info.ret_ty_is_generic) return fallback_ty; if (fn_info.ret_ty_body_len != 0) { uint32_t payload_rv = ctEvalBodyInBlock(zcu, zir, fn_info.ret_ty_body, fn_info.ret_ty_body_len, values, ns, type_name_ctx); if (payload_rv == IP_INDEX_NONE) UNIMPLEMENTED("analyzeCall: generic ret body"); uint32_t payload_ty = ctValidatedTypeResult( ip, payload_rv, "analyzeCall: generic ret body"); return ctWrapBoundInferredReturn( zcu, zir, func_inst, fallback_ty, payload_ty); } if (fn_info.ret_ty_ref == ZIR_REF_VOID_TYPE) return fallback_ty; uint32_t ty = ctBoundTypeRef(ip, fn_info.ret_ty_ref, values); if (ty == IP_INDEX_NONE) return fallback_ty; return ctWrapBoundInferredReturn(zcu, zir, func_inst, fallback_ty, ty); } // Fold a comptime type-expression body to its interned type // (forward-declared above semaBodyEnumType). Mirrors zptAnalyzeNavVal's // ctEvalBody drive: run the body in a fresh value map with decl lookups // scoped to `ns`, then read the terminating break's operand as the // resulting type. Used when a tag/param type is a generic type-returning // call (`math.Log2Int(usize)`) that ctResolveParamTypeRef cannot resolve // from a single ref. // A declaration's own type body, in analyzeNavVal's block (`".type_name_ctx = // old_nav.name"`). uint32_t ctFoldNavTypeBody(Zcu* zcu, uint32_t nav_idx, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t ns) { uint32_t* values = semaInstMapAlloc(zir); uint32_t rv = ctEvalBodyScoped(zcu, zir, body, body_len, values, ns, TYPE_NAME_CTX_NAV(ipGetNav(&zcu->intern_pool, nav_idx))); free(values); if (rv == IP_INDEX_NONE) UNIMPLEMENTED("analyzeNavVal: body produced no result"); return ctValidatedTypeResult( &zcu->intern_pool, rv, "analyzeNavVal: result is not a type"); } uint32_t ctFoldComptimeTypeBody(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t ns, TypeNameCtx type_name_ctx) { const InternPool* ip = &zcu->intern_pool; uint32_t rv = ctFoldComptimeBodyValue( zcu, zir, body, body_len, ns, type_name_ctx, UINT32_MAX, 0); // The result must be a type: either a preinterned type ref (< the // dynamic start and below the first non-type static ref) or a dynamic // IP item with a type tag (ctFoldComptimeBodyValue already rejects a // sentinel). if (rv >= ZIR_REF_START_INDEX) { if (!ipIndexIsType(ip, rv)) UNIMPLEMENTED("resolveInlineBody: result is not a type"); } return rv; } // (part of) Sema/type_resolution.zig:resolveStructLayout // A container field's type body, `"sema.resolveInlineBody(&block, // zir_field.type_body, zir_index)"` then analyzeAsType, in namespace `ns`. uint32_t semaResolveFieldTypeBody(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t ns, TypeNameCtx type_name_ctx) { uint32_t ty_ref = zir->inst_datas[body[body_len - 1]].break_data.operand; return ctTypeBodyNeedsComptimeFold(zir, ty_ref) ? ctFoldComptimeTypeBody(zcu, zir, body, body_len, ns, type_name_ctx) : ctResolveFieldTypeRefInNs( &zcu->intern_pool, zcu, zir, ty_ref, ns, type_name_ctx); } // Fold a general comptime body in namespace `ns` to its break or return value, // whose type the caller interprets. `seed_inst`/`seed_val` (UINT32_MAX for // none) pre-map one instruction, as Sema.resolveStructDefaults maps the // struct_decl to the field's type: AstGen gives each default the result type // `decl_inst.toRef()`. uint32_t ctFoldComptimeBodyValue(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t ns, TypeNameCtx type_name_ctx, uint32_t seed_inst, uint32_t seed_val) { uint32_t* values = semaInstMapAlloc(zir); if (seed_inst != UINT32_MAX && seed_inst < zir->inst_len) values[seed_inst] = seed_val; uint32_t rv = ctEvalBodyScoped( zcu, zir, body, body_len, values, ns, type_name_ctx); free(values); if (rv == IP_INDEX_NONE) UNIMPLEMENTED("resolveInlineBody: body produced no result"); // An enum-literal result resolves against an enum seed: the // `sema.coerce(block, field_ty, uncoerced, ...)` of resolveStructDefaults. if (ctIsEnumLiteral(&zcu->intern_pool, rv) && seed_inst != UINT32_MAX && ipIsEnumType(&zcu->intern_pool, seed_val)) { uint32_t t = ipEnumTagFromFieldName(&zcu->intern_pool, seed_val, ctEnumLiteralName(&zcu->intern_pool, rv)); if (t == IP_INDEX_NONE) UNIMPLEMENTED("resolveInlineBody: enum literal is not a field of " "the result enum type"); rv = t; } if (!semaRefIsInterned(rv)) UNIMPLEMENTED("resolveInlineBody: result is a comptime sentinel"); return rv; } static bool ctParamInstConcreteType(Zcu* zcu, const Zir* zir, uint32_t pinst, uint32_t* values, uint32_t ns, TypeNameCtx type_name_ctx, uint32_t* out_ty) { const InternPool* ip = &zcu->intern_pool; ZirInstTag pt = zir->inst_tags[pinst]; if (pt != ZIR_INST_PARAM && pt != ZIR_INST_PARAM_COMPTIME) return false; // anytype: bind raw ZirInstParam param; const uint32_t* pbody = zirBodySlice(zir, zirExtraData( zir, zir->inst_datas[pinst].pl_tok.payload_index, ¶m)); uint32_t plen = param.type & ZIR_PARAM_TYPE_BODY_LEN_MASK; if (plen == 0) return false; uint32_t plast = pbody[plen - 1]; if (zir->inst_tags[plast] != ZIR_INST_BREAK_INLINE) return false; for (uint32_t i = 0; i < plen; i++) if (zirIsFuncInst(zir->inst_tags[pbody[i]])) return false; uint32_t pty = IP_INDEX_NONE; if (plen == 1) { pty = ctBoundTypeRef( ip, zir->inst_datas[plast].break_data.operand, values); } else { // A parameter type-expression body: lexically comptime upstream. uint32_t pt_rv = ctEvalBodyInBlock( zcu, zir, pbody, plen, values, ns, type_name_ctx); if (pt_rv != IP_INDEX_NONE) pty = ctValidatedTypeResult( ip, pt_rv, "funcCommon: param type body"); } if (pty == IP_INDEX_NONE || pty == ZIR_REF_TYPE_TYPE || pty == IP_INDEX_GENERIC_POISON_TYPE) return false; // type/anytype param: bind raw if (!ipIndexIsType(ip, pty)) return false; *out_ty = pty; return true; } // The parameter instructions of a function declaration's value body, in // signature order, up to its `func` instruction (UINT32_MAX when it has none); // `param_insts` NULL only counts them. static uint32_t ctFuncBodyParams(const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t* param_insts, uint32_t param_insts_cap, uint32_t* out_nparams) { uint32_t nparams = 0; for (uint32_t b = 0; b < body_len; b++) { uint32_t inst = body[b]; ZirInstTag t = zir->inst_tags[inst]; if (t == ZIR_INST_PARAM || t == ZIR_INST_PARAM_COMPTIME || t == ZIR_INST_PARAM_ANYTYPE || t == ZIR_INST_PARAM_ANYTYPE_COMPTIME) { if (param_insts != NULL) { if (nparams >= param_insts_cap) UNIMPLEMENTED("funcCommon: too many parameters"); param_insts[nparams] = inst; } nparams++; } else if (zirIsFuncInst(t)) { *out_nparams = nparams; return inst; } } *out_nparams = nparams; return UINT32_MAX; } void ctCallParamCtxInit( Zcu* zcu, uint32_t nav_idx, uint32_t func_ty, CtCallParamCtx* ctx) { memset(ctx, 0, sizeof(*ctx)); ctx->nav_ns = IP_NAMESPACE_NONE; if (func_ty == IP_INDEX_NONE || func_ty == IP_INDEX_GENERIC_POISON_TYPE || ipFuncTypeParamsLen(&zcu->intern_pool, func_ty) == 0) return; InternPool* ip = &zcu->intern_pool; uint32_t params_len = ipFuncTypeParamsLen(ip, func_ty); bool needs_param_body_eval = false; for (uint32_t i = 0; i < params_len; i++) { if (ipFuncTypeParamType(ip, func_ty, i) == IP_INDEX_GENERIC_POISON_TYPE) { needs_param_body_eval = true; break; } } if (!needs_param_body_eval) return; const IpNav* nav = ipGetNav(ip, nav_idx); ctx->nav_ns = nav->analysis_namespace; ctx->type_name_ctx = TYPE_NAME_CTX_NAV(nav); uint32_t file_idx = ipGetNamespace(ip, ctx->nav_ns)->file_scope; ctx->zir = zcu->files[file_idx].zir; DeclInfo decl; if (!zirDecodeDeclaration(ctx->zir, nav->analysis_inst, &decl) || !decl.has_value_body) return; ctx->param_insts = ARR_INIT(uint32_t, decl.value_body_len); ctFuncBodyParams(ctx->zir, zirBodySlice(ctx->zir, decl.value_body_off), decl.value_body_len, ctx->param_insts, decl.value_body_len, &ctx->nparams); if (ctx->nparams == 0) return; ctx->values = semaInstMapAlloc(ctx->zir); } void ctCallParamCtxFree(CtCallParamCtx* ctx) { free(ctx->param_insts); free(ctx->values); memset(ctx, 0, sizeof(*ctx)); ctx->nav_ns = IP_NAMESPACE_NONE; } uint32_t ctCallProvidedParamType( Zcu* zcu, uint32_t func_ty, uint32_t param_i, CtCallParamCtx* ctx) { const InternPool* ip = &zcu->intern_pool; uint32_t provided_ty = (func_ty != IP_INDEX_NONE && func_ty != IP_INDEX_GENERIC_POISON_TYPE && param_i < ipFuncTypeParamsLen(ip, func_ty)) ? ipFuncTypeParamType(ip, func_ty, param_i) : IP_INDEX_GENERIC_POISON_TYPE; if (provided_ty == IP_INDEX_GENERIC_POISON_TYPE && param_i < ctx->nparams && ctx->values != NULL) { uint32_t concrete_ty; if (ctParamInstConcreteType(zcu, ctx->zir, ctx->param_insts[param_i], ctx->values, ctx->nav_ns, ctx->type_name_ctx, &concrete_ty)) provided_ty = concrete_ty; } return provided_ty; } void ctCallParamCtxBind(CtCallParamCtx* ctx, uint32_t param_i, uint32_t val) { if (param_i >= ctx->nparams || ctx->values == NULL) return; ctx->values[ctx->param_insts[param_i]] = val; } static void ctCoerceBoundParamValues(Zcu* zcu, const Zir* zir, const uint32_t* param_insts, uint32_t nparams, uint32_t* values, uint32_t ns, TypeNameCtx type_name_ctx, uint32_t call_fty) { InternPool* ip = &zcu->intern_pool; for (uint32_t k = 0; k < nparams; k++) { uint32_t pinst = param_insts[k]; uint32_t av = values[pinst]; if (!semaRefIsInterned(av)) continue; // Sema.analyzeCall: `"const raw = func_ty_info.param_types.get(ip) // [arg_idx]; if (raw != .generic_poison_type) break :ty // .fromInterned(raw);"` -- a concrete parameter of the called type // binds at that type; only a generic one is re-derived below. uint32_t call_pty = call_fty == IP_INDEX_NONE ? IP_INDEX_GENERIC_POISON_TYPE : ipFuncTypeParamType(ip, call_fty, k); if (call_pty != IP_INDEX_GENERIC_POISON_TYPE) { values[pinst] = ctCoerceToType(zcu, av, call_pty); continue; } // An enum literal is a real interned item, so it joins the // untyped/uncoerced-value tag list below instead of needing its // own sentinel test in both conditions. The untyped `null` and // `undefined` literals, like the untyped values below, are typed // only by `analyzeArg`'s coercion. bool needs_dest = av == ZIR_REF_NULL_VALUE || av == ZIR_REF_UNDEF; if (!needs_dest && av < ip->items_len) { IpTag tag = (IpTag)ip->item_tags[av]; needs_dest = tag == IP_TAG_ENUM_LITERAL || tag == IP_TAG_ERROR_SET_ERROR || ipTypeOf(ip, av) == ZIR_REF_COMPTIME_INT_TYPE; } if (!needs_dest) { // Typed comptime arguments normally bind unchanged. The // pointer conversions below still need the concrete parameter // type; upstream `CallArgsInfo.analyzeArg` coerces every typed // argument before it binds the callee's generic-inst map. if (av >= ip->items_len) continue; IpTag tag = (IpTag)ip->item_tags[av]; bool is_array_ptr = ipIsPtrUav(ip, av) || tag == IP_TAG_PTR_NAV || tag == IP_TAG_PTR_COMPTIME_ALLOC; if (tag != IP_TAG_PTR_SLICE && !is_array_ptr) continue; uint32_t pty; if (!ctParamInstConcreteType( zcu, zir, pinst, values, ns, type_name_ctx, &pty)) continue; // `CallArgsInfo.analyzeArg` always calls `Sema.coerceExtra` // for a typed argument. Its comptime slice coercion reaches // `InternPool.getCoerced`'s `.slice` arm, which preserves // `.len` while retyping the contained many pointer. A real // cell-slice needs the same destination remint before the // callee observes it; preserve the non-slice typed-argument // behavior below. if (tag == IP_TAG_PTR_SLICE && ztIsSlice(ip, pty)) { values[pinst] = ctCoerceToType(zcu, av, pty); continue; } if (is_array_ptr) { uint32_t coerced = ctCoerceSentinelArrayPtrToPlain(ip, av, pty); if (coerced != IP_INDEX_NONE) values[pinst] = coerced; } continue; } uint32_t pty; if (!ctParamInstConcreteType( zcu, zir, pinst, values, ns, type_name_ctx, &pty)) continue; values[pinst] = ctCoerceToType(zcu, av, pty); } } // A comptime-mutable-cell pointer-class value: an InternPool // ptr_comptime_alloc, or a ptr_field / ptr_elem / ptr_opt_payload / // ptr_eu_payload / ptr_slice chain rooted at one. `ptr_comptime_alloc` // also crosses this boundary from sema.c's `comptime var` pointers // (arriving via ctCoerceBoundParamValues below). // // Mirrors upstream's isComptimeMutablePtr / canMutateComptimeVarState // (src/Sema.zig, src/Value.zig) in spirit — recognize a // comptime-mutable-pointer-shaped value — not algorithm: those are // recursive over a value's base_addr chain / aggregate elements; this is a // flat, top-level tag test (an aggregate value nesting a comptime-mutable // pointer is invisible to `can_mutate`). Recursing into // aggregates/eu_payload/opt_payload the way upstream does is not done. // // The two upstream names are not interchangeable synonyms: // isComptimeMutablePtr is the ptr-shape companion — "is this value shaped // like a pointer into comptime-mutable state" — which is exactly what this // flat predicate answers, so it is this function's direct parallel. // canMutateComptimeVarState is the narrower memo-valve parallel: it is // what ctAnalyzeComptimeCallTyped's `can_mutate` below is actually // re-deriving (a memoization-safety decision over an argument list), not a // general shape test, and is cited by name at that call site rather than // here. static bool ctIsCellPointerClassValue(const InternPool* ip, uint32_t v) { if (v >= ip->items_len) return false; switch (ip->item_tags[v]) { case IP_TAG_PTR_COMPTIME_ALLOC: return true; case IP_TAG_PTR_FIELD: case IP_TAG_PTR_ELEM: case IP_TAG_PTR_OPT_PAYLOAD: case IP_TAG_PTR_EU_PAYLOAD: // Only a chain rooted at a comptime allocation addresses a cell. return ipIsPtrComptimeAlloc(ip, ipPtrDerivedRoot(ip, v)); case IP_TAG_PTR_SLICE: { // A `ptr_slice` is cell-pointer-class iff its own `.ptr` // is itself a cell pointer chain -- an ordinary UAV-backed slice // (string literals, enum-name arrays, `ipGetNullTerminatedString // Slice`'s own callers, ...) must not be swept into the cell- // pointer normalizer, or the immutable-slice arms // (ELEM_PTR/ELEM_PTR_LOAD's own `IP_TAG_PTR_SLICE` branches, // sema_comptime.c's `ctIndexAnyValue` callers) would stop firing. // One level of peek, not a general recursive walk: a `ptr_slice`'s // `.ptr` is never itself another `ptr_slice`. uint32_t d = ip->item_datas[v]; if (d > ip->extra_len || ip->extra_len - d < 3u) return false; return ctIsCellPointerClassValue(ip, ip->extra[d + 1]); } default: return false; } } // Sema.analyzeCall's `"if (val.canMutateComptimeVarState(zcu)) break :m // false;"`, on an argument or the result; a non-interned word is never // memoized either. static bool ctCanMutateComptimeVarState(InternPool* ip, uint32_t v) { return !semaRefIsInterned(v) || valCanMutateComptimeVarState(ip, v); } // Sema.analyzeCall's memoized-call hit: `"sema.branch_count += // memoized_call.branch_count; sema.branch_quota = @max(sema.branch_quota, // memoized_call.branch_quota); sema.quota_request = @max(...)"`, then // `Air.internedToRef(memoized_call.result)` at the result's own type. // IP_INDEX_NONE on a miss. static uint32_t ctMemoizedCallHit(Zcu* zcu, uint32_t func, const uint32_t* args, uint32_t nargs, uint32_t* out_ret_ty) { const InternPool* ip = &zcu->intern_pool; uint32_t memo = ipLookupMemoizedCall(ip, func, args, nargs); if (memo == IP_INDEX_NONE) return IP_INDEX_NONE; semaChargeBranches(zcu, ipMemoizedCallBranchCount(ip, memo)); semaSetEvalBranchQuota(zcu, ipMemoizedCallBranchQuota(ip, memo)); if (out_ret_ty) *out_ret_ty = ipTypeOf(ip, ipMemoizedCallResult(ip, memo)); return ipMemoizedCallResult(ip, memo); } // Sema.analyzeCall's memoization of one comptime call: its key (the // coerced argument values), `want_memoize`, and the Sema state the call's // analysis saves and restores. typedef struct { uint32_t* args; uint32_t nargs; bool want; uint32_t old_branch_count; uint32_t old_quota_request; bool old_allow_memoize; } CtMemoizedCall; // `"for (args) |a| { const val = sema.resolveValue(a).?; if // (val.canMutateComptimeVarState(zcu)) break :m false; }"`. static void ctMemoizeArgs(InternPool* ip, CtMemoizedCall* m, const uint32_t* values, const uint32_t* param_insts, uint32_t nparams) { m->nargs = nparams; m->args = ARR_INIT(uint32_t, nparams + 1u); m->want = true; for (uint32_t k = 0; k < nparams; k++) { m->args[k] = values[param_insts[k]]; if (ctCanMutateComptimeVarState(ip, m->args[k])) m->want = false; } } // Before the body: `"sema.allow_memoize = true;"`, `"sema.quota_request = // 0;"` and `"const old_branch_count = sema.branch_count;"`. static void ctMemoizeEnter(Zcu* zcu, CtMemoizedCall* m) { ZcuSemaBranch* b = zcu->sema_branch; m->old_allow_memoize = b->allow_memoize; b->allow_memoize = true; m->old_quota_request = b->quota_request; b->quota_request = 0; m->old_branch_count = b->count; } // After the body: the deferred restores, then `"if (want_memoize and // sema.allow_memoize and !result_val.canMutateComptimeVarState(zcu))"` the // memoized_call (the lookup before the body missed on this key). static void ctMemoizeLeave( Zcu* zcu, CtMemoizedCall* m, uint32_t func, uint32_t ret_val) { InternPool* ip = &zcu->intern_pool; ZcuSemaBranch* b = zcu->sema_branch; uint32_t branch_count = b->count - m->old_branch_count; uint32_t quota_request = b->quota_request; if (m->old_quota_request > b->quota_request) b->quota_request = m->old_quota_request; bool allow_memoize = b->allow_memoize; b->allow_memoize = m->old_allow_memoize && allow_memoize; if (m->want && allow_memoize && ret_val != IP_INDEX_NONE && !ctCanMutateComptimeVarState(ip, ret_val)) ipGetMemoizedCall( ip, func, m->args, m->nargs, ret_val, branch_count, quota_request); free(m->args); } // (part of) Sema.zig:analyzeCall // Comptime-evaluate a call on Sema.analyzeCall's `is_comptime_call` path // (Sema.zig..7354): either the enclosing scope forces comptime, or the // callee returns a comptime-only value (for example `type`, even with no // comptime parameters), including a `comptime T: type` parameter bound to // a type value. `arg_vals[k]` is the bound value for parameter `k` (a // `type` InternPool index, or an interned comptime constant). Every // supplied argument must be comptime-known (each `arg` is bound directly; // no AIR `arg` is emitted). Interns a `memoized_call` recording the // result, then returns it. uint32_t ctAnalyzeComptimeCallTyped(Zcu* zcu, uint32_t func_decl_idx, uint32_t callee_nav, uint32_t call_fty, const uint32_t* arg_vals, uint32_t nargs, uint32_t* out_ret_ty) { InternPool* ip = &zcu->intern_pool; // The passed callee_nav may be an alias / re-export whose value body // has no `func` instruction (`pub const A = b.A;`). The // walk below needs the nav that actually defines the function. // func_decl_idx is the authoritative resolved function; its owner nav // points at the real `func` ZIR (correct file + namespace + // analysis_inst). If the given callee_nav does not itself contain a // function definition, canonicalize to the owner nav. A plain direct // call already passes the owner nav (the check finds a func inst and // leaves callee_nav unchanged), so an alias callee reaches its func // instruction. { uint32_t owner = ipFuncDeclOwnerNav(ip, func_decl_idx); if (owner != callee_nav && owner != IP_INDEX_NONE) { const IpNav* cn = ipGetNav(ip, callee_nav); bool has_func = false; if (cn->has_analysis) { uint32_t cf = ipGetNamespace(ip, cn->analysis_namespace)->file_scope; const Zir* czir = zcu->files[cf].zir; DeclInfo cd; if (zirDecodeDeclaration(czir, cn->analysis_inst, &cd) && cd.has_value_body && navValueBodyHasFunc(czir, &cd)) has_func = true; } if (!has_func) callee_nav = owner; } } const IpNav* nav = ipGetNav(ip, callee_nav); // Captured by value: the ret-ty resolution below can create navs // (realloc), which would dangle the `nav` pointer. uint32_t nav_ns = nav->analysis_namespace; uint32_t file_idx = ipGetNamespace(ip, nav_ns)->file_scope; const Zir* zir = zcu->files[file_idx].zir; DeclInfo decl; if (!zirDecodeDeclaration(zir, nav->analysis_inst, &decl) || !decl.has_value_body) UNREACHABLE("comptime call: callee has no value body"); // An `anytype` param binds its comptime-known argument like a `comptime` // one. An `inline fn`'s `func_fancy` has its own body layout, which // ctAnalyzeInlineCall decodes. uint32_t* param_insts = ARR_INIT(uint32_t, decl.value_body_len); uint32_t nparams = 0; uint32_t func_inst = ctFuncBodyParams(zir, zirBodySlice(zir, decl.value_body_off), decl.value_body_len, param_insts, decl.value_body_len, &nparams); if (func_inst != UINT32_MAX && zir->inst_tags[func_inst] == ZIR_INST_FUNC_FANCY) { free(param_insts); return ctAnalyzeInlineCall( zcu, func_decl_idx, arg_vals, nargs, out_ret_ty); } if (func_inst == UINT32_MAX) UNREACHABLE("comptime call: missing func instruction"); if (nparams != nargs) UNIMPLEMENTED( "analyzeCall: comptime call - parameter/argument count mismatch"); // Sema.analyzeCall: `"try sema.emitBackwardBranch(block, call_src);"`. semaChargeBranches(zcu, 1); ZirFnInfo fn_info = zirGetFnInfo(zir, func_inst); uint32_t body_len = fn_info.body_len; const uint32_t* fn_body = fn_info.body; // Value map indexed by instruction index; bind each comptime parameter // to its argument value (Sema binds generic comptime params to their // instantiation args before evaluating the inline body). uint32_t* values = semaInstMapAlloc(zir); for (uint32_t k = 0; k < nparams; k++) values[param_insts[k]] = arg_vals[k]; // Track this callee's declared return type for the `ret_type` handler // (used to coerce `return null`/`return N` to a `?comptime_int` // result), save/restore-scoped so a nested comptime call restores the // caller's. Decoded straight from the Func ret-body (not via // navFuncDecl, which would also resolve every parameter type which folding // the body does not need since the argument is bound by value). Mirrors // navFuncDecl's ret-ty decode. uint32_t ct_ret_ty; // Scope type-ref resolution to this callee's namespace so a return // type that is a container capture reads // the right captures (keyed by namespace). uint32_t rt_saved_type_ns = ctSwapNs(&zcu->ct.type_resolve_ns, nav_ns); if (fn_info.ret_ty_is_generic) ct_ret_ty = IP_INDEX_GENERIC_POISON_TYPE; else if (fn_info.ret_ty_body_len == 0) { // A generic callee whose return type is a pointer with a // comptime-computed alignment // (`Error![*]align(alignment.toByteUnits()) u8` / `[]align(...) T` // in the Allocator's generic alloc helpers): the align expression // (a FIELD_CALL on a bound comptime param) is a separate ZIR inst // not yet evaluated in this frame, so ctResolveParamTypeRef can't // fold it. Stock treats such a return type as generic — the // concrete type flows from the actual `return` value's type (the // `ptr[0..n]` slice) via the poison-lenient coercion. Use // generic_poison here (same as the explicitly-`[generic]` arm // above). if (ctRetTypeHasComputedAlign(zir, fn_info.ret_ty_ref)) ct_ret_ty = IP_INDEX_GENERIC_POISON_TYPE; else ct_ret_ty = ctResolveParamTypeRef(ip, zcu, zir, fn_info.ret_ty_ref); } else { uint32_t rlast = fn_info.ret_ty_body[fn_info.ret_ty_body_len - 1]; if (zir->inst_tags[rlast] != ZIR_INST_BREAK_INLINE) UNREACHABLE("comptime call: ret body last not break_inline"); uint32_t rlast_ref = zir->inst_datas[rlast].break_data.operand; if (ctRetTypeHasComputedAlign(zir, rlast_ref)) ct_ret_ty = IP_INDEX_GENERIC_POISON_TYPE; else ct_ret_ty = ctResolveRetTypeBody(zcu, zir, fn_info.ret_ty_body, fn_info.ret_ty_body_len, rlast_ref, TYPE_NAME_CTX_NAV(&ip->navs[callee_nav])); } zcu->ct.type_resolve_ns = rt_saved_type_ns; // `"if (!any_generic_types) break :ret_ty // .fromInterned(func_ty_info.return_type);"` and its non-poison bare // arm: a coerced function returns at the called type's return type. if (call_fty == ipFuncDeclType(ip, func_decl_idx)) call_fty = IP_INDEX_NONE; if (call_fty != IP_INDEX_NONE) { uint32_t call_ret = ipFuncTypeReturnType(ip, call_fty); if (!semaFnRetTyIsGeneric(ip, call_ret)) ct_ret_ty = call_ret; } const CtEvaluator saved_ct = zcu->ct; // The generic-ret-body param-type table is indexed by the caller // callee's ZIR — meaningless inside this (nested) callee's body; clear // it. zcu->ct.param_tys = NULL; // Coerce each runtime (non-type) argument to its parameter's // instantiated type (Sema coerces call args to param types). A generic // param // (`value: T`) has a type body referencing an earlier comptime param; // evaluate it in this frame (earlier params bound) and coerce — so // `ceilPowerOfTwo(u16, 8)` binds `value` as u16, not comptime_int, and // `id(?u8, null)` binds a typed optional-null value. ctCoerceBoundParamValues(zcu, zir, param_insts, nparams, values, nav_ns, TYPE_NAME_CTX_NAV(&ip->navs[callee_nav]), call_fty); // Sema's memoized_call key contains the values after argument coercion // (Sema.zig anchor: "for (vals, args) |*v, a| v.* = // sema.resolveValue(a).?.toIntern();"). Keep a compact copy before the // instruction-indexed value map is freed below. // This matters for pointer IMC too: the body and the memo key must // both see `*const [N]T`, not evaluate one type while keying `*const // [N:s]T`. CtMemoizedCall memo; ctMemoizeArgs(ip, &memo, values, param_insts, nparams); { uint32_t ct_func_ty_for_ret = ipFuncDeclType(ip, func_decl_idx); if (ct_func_ty_for_ret != IP_INDEX_NONE && (ipFuncTypeIsInline(ip, ct_func_ty_for_ret) || zirFuncInstIsInferred(zir, func_inst) || ct_ret_ty == IP_INDEX_GENERIC_POISON_TYPE)) { uint32_t ct_func_decl_ret = ipFuncTypeReturnType(ip, ct_func_ty_for_ret); uint32_t ct_fallback_ret = ct_func_decl_ret != IP_INDEX_NONE ? ct_func_decl_ret : ct_ret_ty; ct_ret_ty = ctBoundFuncReturnType(zcu, zir, func_inst, values, nav_ns, TYPE_NAME_CTX_NAV(&ip->navs[callee_nav]), ct_fallback_ret); } } // analyzeCall's `"try sema.ensureLayoutResolved(.fromInterned( // param_ty_ip), arg_src, .init);"` for each parameter, whose instantiated // type is its coerced argument's, then `"try sema.ensureLayoutResolved( // .fromInterned(func_ty_info.return_type), func_ret_ty_src, // .return_type);"` for (uint32_t k = 0; k < nparams; k++) ensureLayoutResolved(zcu, ipTypeOf(ip, values[param_insts[k]])); ensureLayoutResolved(zcu, ct_ret_ty); uint32_t memo_hit = memo.want ? ctMemoizedCallHit(zcu, func_decl_idx, memo.args, nargs, out_ret_ty) : IP_INDEX_NONE; if (memo_hit != IP_INDEX_NONE) { zcu->ct = saved_ct; free(memo.args); free(param_insts); free(values); return memo_hit; } uint32_t ies_slot = UINT32_MAX; uint32_t ies_saved = IP_INDEX_NONE; bool scoped_ies = ctReturnInferredErrorSetSlot(ip, ct_ret_ty, &ies_slot); if (scoped_ies) { ies_saved = ip->extra[ies_slot]; ip->extra[ies_slot] = IP_INDEX_NONE; } uint32_t ret_val = ZIR_REF_VOID_VALUE; uint32_t br_block = UINT32_MAX, br_val = 0; // `fn_body` is the CALLEE's own function body: never lexically // comptime upstream, regardless of the caller. analyzeCall's child // block: `".type_name_ctx = fn_nav.name, .type_fqn_ctx = fn_nav.fqn"`, // with the callee's parameters bound in `sema.inst_map`. TypeNameFuncArgs tn_func = { param_insts, nparams, values }; SemaDetachedFrame f; ctFrameInit(&f, zcu, zir, values, ct_ret_ty, (CtBlockScope) { nav_ns, TYPE_NAME_CTX_NAV(&ip->navs[callee_nav]), &tn_func, IP_INDEX_NONE, false }); ctMemoizeEnter(zcu, &memo); CtEvalStatus st = ctEvalBody(&f.sema, fn_body, body_len, &ret_val, &br_block, &br_val); ctFrameDeinit(&f); zcu->ct = saved_ct; if (st != CT_EVAL_RETURN) { if (scoped_ies) ip->extra[ies_slot] = ies_saved; free(values); UNIMPLEMENTED( "analyzeCall: comptime call - body did not return a value"); } uint32_t final_ret_ty = ct_ret_ty; ret_val = ctFinalizeComptimeReturn(zcu, ret_val, ct_ret_ty, &final_ret_ty); if (scoped_ies) ip->extra[ies_slot] = ies_saved; free(param_insts); free(values); ctMemoizeLeave(zcu, &memo, func_decl_idx, ret_val); if (out_ret_ty) *out_ret_ty = final_ret_ty; return ret_val; } uint32_t ctAnalyzeInlineCall(Zcu* zcu, uint32_t func_decl_idx, const uint32_t* arg_vals, uint32_t nargs, uint32_t* out_ret_ty) { InternPool* ip = &zcu->intern_pool; // Sema.analyzeCall: `"try sema.emitBackwardBranch(block, call_src);"`. semaChargeBranches(zcu, 1); uint32_t callee_nav = ipFuncDeclOwnerNav(ip, func_decl_idx); const IpNav* nav = ipGetNav(ip, callee_nav); // Captured by value: zptEnsureNavValUpToDate below can create navs // (realloc), which would dangle the `nav` pointer. uint32_t nav_ns = nav->analysis_namespace; uint32_t file_idx = ipGetNamespace(ip, nav_ns)->file_scope; const Zir* zir = zcu->files[file_idx].zir; DeclInfo decl; if (!zirDecodeDeclaration(zir, nav->analysis_inst, &decl) || !decl.has_value_body) UNREACHABLE("analyzeCall: inline, callee has no value body"); // Collect parameter instructions (signature order) + the func-def // instruction (mirrors navFuncDecl / ctAnalyzeComptimeCallTyped's walk). // An `inline fn` is a `func_fancy`, whose body location differs from a // plain `func`, so decode both shapes. uint32_t* param_insts = ARR_INIT(uint32_t, decl.value_body_len); uint32_t nparams = 0; uint32_t func_inst = ctFuncBodyParams(zir, zirBodySlice(zir, decl.value_body_off), decl.value_body_len, param_insts, decl.value_body_len, &nparams); if (func_inst == UINT32_MAX) UNREACHABLE("analyzeCall: inline, missing func instruction"); if (nparams != nargs) UNIMPLEMENTED( "analyzeCall: inline call - parameter/argument count mismatch"); ZirFnInfo fn_info = zirGetFnInfo(zir, func_inst); const uint32_t* fn_body = fn_info.body; uint32_t body_len = fn_info.body_len; // Bind each comptime-known argument to its parameter, then run the // body. // @inComptime() inside evaluates to `true` (this is a comptime // evaluation of the inline body — its result is being folded into a // comptime gate); any genuinely runtime instruction aborts loudly in // ctEvalBody. uint32_t* values = semaInstMapAlloc(zir); for (uint32_t k = 0; k < nparams; k++) values[param_insts[k]] = arg_vals[k]; ctCoerceBoundParamValues(zcu, zir, param_insts, nparams, values, nav_ns, TYPE_NAME_CTX_NAV(&ip->navs[callee_nav]), IP_INDEX_NONE); uint32_t ic_func_ty = IP_INDEX_NONE; zptEnsureNavValUpToDate(zcu, callee_nav, &ic_func_ty); uint32_t ic_ret_ty = ipFuncTypeReturnType(ip, ic_func_ty); const CtEvaluator saved_ct = zcu->ct; ic_ret_ty = ctBoundFuncReturnType(zcu, zir, func_inst, values, nav_ns, TYPE_NAME_CTX_NAV(&ip->navs[callee_nav]), ic_ret_ty); zcu->ct.param_tys = NULL; // caller-ZIR-indexed; meaningless in this body // Every call here is in a comptime block, so it may be memoized. CtMemoizedCall memo; ctMemoizeArgs(ip, &memo, values, param_insts, nparams); uint32_t memo_hit = memo.want ? ctMemoizedCallHit(zcu, func_decl_idx, memo.args, nargs, out_ret_ty) : IP_INDEX_NONE; if (memo_hit != IP_INDEX_NONE) { zcu->ct = saved_ct; free(memo.args); free(param_insts); free(values); return memo_hit; } uint32_t ic_ies_slot = UINT32_MAX; uint32_t ic_ies_saved = IP_INDEX_NONE; bool ic_scoped_ies = ctReturnInferredErrorSetSlot(ip, ic_ret_ty, &ic_ies_slot); if (ic_scoped_ies) { ic_ies_saved = ip->extra[ic_ies_slot]; ip->extra[ic_ies_slot] = IP_INDEX_NONE; } uint32_t ret_val = ZIR_REF_VOID_VALUE; uint32_t br_block = UINT32_MAX, br_val = 0; // `fn_body` is the CALLEE's own function body: never lexically // comptime upstream, regardless of the caller. The inline child block: // `".type_name_ctx = fn_nav.name, .type_fqn_ctx = fn_nav.fqn"`, the // callee's parameters bound in `sema.inst_map`. TypeNameFuncArgs ic_tn_func = { param_insts, nparams, values }; SemaDetachedFrame f; ctFrameInit(&f, zcu, zir, values, ic_ret_ty, (CtBlockScope) { nav_ns, TYPE_NAME_CTX_NAV(&ip->navs[callee_nav]), &ic_tn_func, IP_INDEX_NONE, false }); ctMemoizeEnter(zcu, &memo); CtEvalStatus st = ctEvalBody(&f.sema, fn_body, body_len, &ret_val, &br_block, &br_val); ctFrameDeinit(&f); zcu->ct = saved_ct; if (st != CT_EVAL_RETURN) { if (ic_scoped_ies) ip->extra[ic_ies_slot] = ic_ies_saved; free(values); UNIMPLEMENTED( "analyzeCall: inline call - body did not return a value"); } uint32_t ic_final_ret_ty = ic_ret_ty; ret_val = ctFinalizeComptimeReturn(zcu, ret_val, ic_ret_ty, &ic_final_ret_ty); if (ic_scoped_ies) ip->extra[ic_ies_slot] = ic_ies_saved; free(param_insts); free(values); ctMemoizeLeave(zcu, &memo, func_decl_idx, ret_val); if (out_ret_ty) *out_ret_ty = ic_final_ret_ty; return ret_val; } // ── Nav ZIR lookup // ────────────────────────────────────────────────────────── bool semaNavZir( Zcu* zcu, uint32_t nav_idx, const Zir** out_zir, uint32_t* out_decl_inst) { InternPool* ip = &zcu->intern_pool; if (nav_idx >= ip->navs_len) return false; const IpNav* nav = ipGetNav(ip, nav_idx); if (!nav->has_analysis || nav->analysis_namespace >= ip->namespaces_len) return false; uint32_t file_idx = ipGetNamespace(ip, nav->analysis_namespace)->file_scope; if (file_idx >= zcu->num_files || zcu->files[file_idx].zir == NULL) return false; const Zir* zir = zcu->files[file_idx].zir; *out_zir = zir; *out_decl_inst = nav->analysis_inst; return true; } static bool ctDeclValueBody(const Zir* zir, uint32_t decl_inst, uint32_t* out_body_off, uint32_t* out_body_len) { DeclInfo decl; if (!zirDecodeFunctionDeclaration(zir, decl_inst, &decl) || !decl.has_value_body) return false; *out_body_off = decl.value_body_off; *out_body_len = decl.value_body_len; return true; } static bool ctDeclFuncZir(const Zir* zir, uint32_t decl_inst, uint32_t* param_insts, uint32_t param_insts_cap, uint32_t* out_nparams, uint32_t* out_func_inst) { uint32_t body_off = 0, body_len = 0; if (!ctDeclValueBody(zir, decl_inst, &body_off, &body_len)) return false; uint32_t func_inst = ctFuncBodyParams(zir, zirBodySlice(zir, body_off), body_len, param_insts, param_insts_cap, out_nparams); *out_func_inst = func_inst; return func_inst != UINT32_MAX; } static bool ctNavFuncZir(Zcu* zcu, uint32_t nav_idx, const Zir** out_zir, uint32_t* param_insts, uint32_t param_insts_cap, uint32_t* out_nparams, uint32_t* out_func_inst) { uint32_t decl_inst; if (!semaNavZir(zcu, nav_idx, out_zir, &decl_inst)) return false; return ctDeclFuncZir(*out_zir, decl_inst, param_insts, param_insts_cap, out_nparams, out_func_inst); } // For a generic callee (a `func_decl` InternPool index), resolve its owner // Nav then reuse the common Nav/declaration-to-function decoder above. bool semaCalleeFuncZir(Zcu* zcu, uint32_t func_decl, const Zir** out_zir, uint32_t* param_insts, uint32_t param_insts_cap, uint32_t* out_nparams, uint32_t* out_func_inst) { const InternPool* ip = &zcu->intern_pool; uint32_t owner_nav_idx = ipFuncDeclOwnerNav(ip, func_decl); return ctNavFuncZir(zcu, owner_nav_idx, out_zir, param_insts, param_insts_cap, out_nparams, out_func_inst); } // (part of) Sema.zig:analyzeCall // Comptime-evaluate a generic callee's return-type body with the comptime // arguments bound in `values` (`param_tys` gives each bound param's type), // decl lookups in `lookup_ns` and the result named by `type_name_ctx`. uint32_t ctEvalTypeBody(Zcu* zcu, const Zir* zir, const uint32_t* body, uint32_t body_len, uint32_t* values, const uint32_t* param_tys, uint32_t lookup_ns, TypeNameCtx type_name_ctx) { const uint32_t* saved_param_tys = zcu->ct.param_tys; zcu->ct.param_tys = param_tys; uint32_t br_val = ctEvalBodyInBlock( zcu, zir, body, body_len, values, lookup_ns, type_name_ctx); zcu->ct.param_tys = saved_param_tys; if (br_val == IP_INDEX_NONE) UNIMPLEMENTED( "analyzeCall: generic ret type body, did not break with a result"); // `u0` is IP index 0, a real result: Sema.resolveGenericBody coerces every // result to `.type`, with no zero special case. return ctValidatedTypeResult(&zcu->intern_pool, br_val, "analyzeCall: generic ret type body, result not an interned type"); } // This file's macros end with it (zig1.c is one translation unit). #undef CTPK_INDEXED #undef CTPK_UNION #undef CTPK_OPT #undef CTPK_EU #undef CT_REIFY_NAME_MAX /* ---- end sema_comptime.c ---- */ /* ---- begin softfloat.c ---- */ // softfloat.c — see softfloat.h. Each routine keeps upstream's name, // order and variable names so a version hop diffs against lib/compiler_rt/. #ifndef ZIG1_COMMON_H #include "common.h" #endif #ifndef ZIG1_SOFTFLOAT_H #include "softfloat.h" #endif #include #include #include #include // --- the `u128`/`u80` integer arithmetic upstream's `Z` provides --- // Wrap to an unsigned integer of `bits` bits (upstream's `Z` = `u80`). static CrtU128 u128Trunc(CrtU128 a, uint32_t bits) { if (bits >= 128) return a; return u128And(a, u128LowMask(bits)); } // `@clz` over 128 bits; the differences upstream takes are width-free. static uint32_t u128Clz(CrtU128 a) { uint32_t n = 0; uint64_t w = a.hi; if (w == 0) { n = 64; w = a.lo; if (w == 0) return 128; } while ((w & (UINT64_C(1) << 63)) == 0) { w <<= 1; n++; } return n; } // --- float format constants (`std.math.floatMantissaBits` & co.) --- typedef struct { uint32_t bits; // `@typeInfo(T).float.bits` uint32_t significandBits; // `math.floatMantissaBits(T)` uint32_t fractionalBits; // `math.floatFractionalBits(T)` uint32_t exponentBits; // `math.floatExponentBits(T)` } CrtFloat; static CrtFloat crtFloat(uint32_t float_bits) { CrtFloat f; f.bits = float_bits; switch (float_bits) { case 16: f.significandBits = 10; f.exponentBits = 5; break; case 32: f.significandBits = 23; f.exponentBits = 8; break; case 64: f.significandBits = 52; f.exponentBits = 11; break; case 80: f.significandBits = 64; f.exponentBits = 15; break; default: f.significandBits = 112; f.exponentBits = 15; break; } f.fractionalBits = float_bits == 80 ? 63 : f.significandBits; return f; } // `@bitCast(math.inf(T))`: all-ones exponent, and f80's explicit integer bit. static CrtU128 crtInfRep(CrtFloat f) { CrtU128 r = u128Shl( u128Make(0, (UINT64_C(1) << f.exponentBits) - 1), f.significandBits); if (f.significandBits != f.fractionalBits) r = u128Or(r, u128Bit(f.fractionalBits)); return r; } // `@bitCast(math.nan(T))`: the infinity with the quiet bit set. static CrtU128 crtNanRep(CrtFloat f) { return u128Or(crtInfRep(f), u128Bit(f.fractionalBits - 1)); } // compiler_rt.normalize: `"const shift = @clz(significand.*) - // @clz(integerBit); significand.* <<= @intCast(shift); return @as(i32, 1) - // shift;"`. static int32_t crtNormalize(CrtFloat f, CrtU128* significand) { CrtU128 integerBit = u128Bit(f.fractionalBits); int32_t shift = (int32_t)u128Clz(*significand) - (int32_t)u128Clz(integerBit); *significand = u128Shl(*significand, (uint32_t)shift); return 1 - shift; } // lib/compiler_rt/addf3.zig `addf3`, ported from LLVM's fp_add_impl.inc. CrtU128 crtAddf3(uint32_t float_bits, CrtU128 a, CrtU128 b) { const CrtFloat f = crtFloat(float_bits); const uint32_t typeWidth = f.bits; const uint32_t significandBits = f.significandBits; const uint32_t fractionalBits = f.fractionalBits; const uint32_t exponentBits = f.exponentBits; const CrtU128 signBit = u128Bit(significandBits + exponentBits); const int32_t maxExponent = (1 << exponentBits) - 1; const CrtU128 integerBit = u128Bit(fractionalBits); const CrtU128 quietBit = u128Shr(integerBit, 1); const CrtU128 significandMask = u128Sub(u128Bit(significandBits), u128Make(0, 1)); const CrtU128 absMask = u128Sub(signBit, u128Make(0, 1)); const CrtU128 qnanRep = u128Or(crtNanRep(f), quietBit); CrtU128 aRep = a; CrtU128 bRep = b; const CrtU128 aAbs = u128And(aRep, absMask); const CrtU128 bAbs = u128And(bRep, absMask); const CrtU128 infRep = crtInfRep(f); const CrtU128 one = u128Make(0, 1); // Detect if a or b is zero, infinity, or NaN. if (u128Cmp(u128Trunc(u128Sub(aAbs, one), typeWidth), u128Sub(infRep, one)) >= 0 || u128Cmp( u128Trunc(u128Sub(bAbs, one), typeWidth), u128Sub(infRep, one)) >= 0) { // NaN + anything = qNaN if (u128Cmp(aAbs, infRep) > 0) return u128Or(a, quietBit); // anything + NaN = qNaN if (u128Cmp(bAbs, infRep) > 0) return u128Or(b, quietBit); if (u128Cmp(aAbs, infRep) == 0) { // +/-infinity + -/+infinity = qNaN if (u128Cmp(u128Xor(a, b), signBit) == 0) return qnanRep; // +/-infinity + anything remaining = +/- infinity return a; } // anything remaining + +/-infinity = +/-infinity if (u128Cmp(bAbs, infRep) == 0) return b; // zero + anything = anything if (u128IsZero(aAbs)) { // but we need to get the sign right for zero + zero if (u128IsZero(bAbs)) return u128And(a, b); return b; } // anything + zero = anything if (u128IsZero(bAbs)) return a; } // Swap a and b if necessary so that a has the larger absolute value. if (u128Cmp(bAbs, aAbs) > 0) { const CrtU128 temp = aRep; aRep = bRep; bRep = temp; } // Extract the exponent and significand from the (possibly swapped) a and // b. int32_t aExponent = (int32_t)(u128Shr(aRep, significandBits).lo & (uint64_t)maxExponent); int32_t bExponent = (int32_t)(u128Shr(bRep, significandBits).lo & (uint64_t)maxExponent); CrtU128 aSignificand = u128And(aRep, significandMask); CrtU128 bSignificand = u128And(bRep, significandMask); // Normalize any denormals, and adjust the exponent accordingly. if (aExponent == 0) aExponent = crtNormalize(f, &aSignificand); if (bExponent == 0) bExponent = crtNormalize(f, &bSignificand); // The sign of the result is the sign of the larger operand, a. If they // have opposite signs, we are performing a subtraction; otherwise // addition. const CrtU128 resultSign = u128And(aRep, signBit); const bool subtraction = !u128IsZero(u128And(u128Xor(aRep, bRep), signBit)); // Shift the significands to give us round, guard and sticky, and or in // the implicit significand bit. aSignificand = u128Trunc(u128Shl(u128Or(aSignificand, integerBit), 3), typeWidth); bSignificand = u128Trunc(u128Shl(u128Or(bSignificand, integerBit), 3), typeWidth); // Shift the significand of b by the difference in exponents, with a // sticky bottom bit to get rounding correct. const uint32_t align = (uint32_t)(aExponent - bExponent); if (align != 0) { if (align < typeWidth) { const bool sticky = !u128IsZero(u128Trunc( u128Shl(bSignificand, typeWidth - align), typeWidth)); bSignificand = u128Or( u128Shr(bSignificand, align), u128Make(0, sticky ? 1u : 0u)); } else { bSignificand = one; // sticky; b is known to be non-zero. } } if (subtraction) { aSignificand = u128Sub(aSignificand, bSignificand); // If a == -b, return +zero. if (u128IsZero(aSignificand)) return u128Make(0, 0); // If partial cancellation occurred, we need to left-shift the result // and adjust the exponent: if (u128Cmp(aSignificand, u128Shl(integerBit, 3)) < 0) { const int32_t shift = (int32_t)u128Clz(aSignificand) - (int32_t)u128Clz(u128Shl(integerBit, 3)); aSignificand = u128Shl(aSignificand, (uint32_t)shift); aExponent -= shift; } } else { // addition aSignificand = u128Trunc(u128Add(aSignificand, bSignificand), typeWidth); // If the addition carried up, we need to right-shift the result and // adjust the exponent: if (!u128IsZero(u128And(aSignificand, u128Shl(integerBit, 4)))) { const CrtU128 sticky = u128And(aSignificand, one); aSignificand = u128Or(u128Shr(aSignificand, 1), sticky); aExponent += 1; } } // If we have overflowed the type, return +/- infinity: if (aExponent >= maxExponent) return u128Or(infRep, resultSign); if (aExponent <= 0) { // Result is denormal; the exponent and round/sticky bits are zero. // All we need to do is shift the significand and apply the correct // sign. aSignificand = u128Shr(aSignificand, (uint32_t)(4 - aExponent)); return u128Or(resultSign, aSignificand); } // Low three bits are round, guard, and sticky. const uint64_t roundGuardSticky = aSignificand.lo & 0x7u; // Shift the significand into place, and mask off the integer bit, if // it's implicit. CrtU128 result = u128And(u128Shr(aSignificand, 3), significandMask); // Insert the exponent and sign. result = u128Or( result, u128Shl(u128Make(0, (uint64_t)aExponent), significandBits)); result = u128Or(result, resultSign); // Final rounding. The result may overflow to infinity, but that is the // correct result in that case. if (roundGuardSticky > 0x4) result = u128Add(result, one); if (roundGuardSticky == 0x4) result = u128Add(result, u128And(result, one)); result = u128Trunc(result, typeWidth); // Restore any explicit integer bit, if it was rounded off if (significandBits != fractionalBits) { if (!u128IsZero(u128Shr(result, significandBits))) result = u128Or(result, integerBit); } return result; } // lib/compiler_rt/subtf3.zig `sub`: `"const neg_b = @bitCast(@as(u128, // @bitCast(b)) ^ (@as(u128, 1) << 127)); return addf3(f128, a, neg_b);"`, and // subxf3.zig's `"b_rep.exp ^= 0x8000; ... return a + neg_b;"` for f80. CrtU128 crtSubf3(uint32_t float_bits, CrtU128 a, CrtU128 b) { return crtAddf3(float_bits, a, u128Xor(b, u128Bit(float_bits - 1))); } // lib/compiler_rt/comparef.zig `cmpf2(f128, RT, a, b)`: -1/0/1, with an // unordered (NaN) pair answering `RT.Unordered` -- `.Greater` for the `LE` // flavour (`__lttf2`, `__letf2`, `__eqtf2`, `__netf2`), `.Less` for `GE` // (`__getf2`, `__gttf2`). static int crtCmpf2(bool ge, CrtU128 a, CrtU128 b) { const CrtFloat f = crtFloat(128); const CrtU128 signBit = u128Bit(f.significandBits + f.exponentBits); const CrtU128 absMask = u128Sub(signBit, u128Make(0, 1)); const CrtU128 infRep = crtInfRep(f); const CrtU128 aAbs = u128And(a, absMask); const CrtU128 bAbs = u128And(b, absMask); // If either a or b is NaN, they are unordered. if (u128Cmp(aAbs, infRep) > 0 || u128Cmp(bAbs, infRep) > 0) return ge ? -1 : 1; // If a and b are both zeros, they are equal. if (u128IsZero(u128Or(aAbs, bAbs))) return 0; // `aInt`/`bInt` are the patterns as `i128`: at least one non-negative // means signed order is float order; both negative flips it. const bool aNeg = (a.hi >> 63) != 0, bNeg = (b.hi >> 63) != 0; int ord; if (aNeg != bNeg) ord = aNeg ? -1 : 1; else ord = u128Cmp(a, b); // same sign: two's-complement order is unsigned if (!(aNeg && bNeg)) return ord; return -ord; } // lib/compiler_rt/comparef.zig `cmp_f80(RT, a, b)`. static int crtCmpF80(bool ge, CrtU128 a, CrtU128 b) { const uint64_t a_fraction = a.lo, b_fraction = b.lo; const uint16_t a_exp = (uint16_t)a.hi, b_exp = (uint16_t)b.hi; const uint64_t int_bit = UINT64_C(0x8000000000000000); const uint16_t sign_bit = 0x8000; const uint16_t special_exp = 0x7FFF; // If either a or b is NaN, they are unordered. if (((a_exp & special_exp) == special_exp && (a_fraction ^ int_bit) != 0) || ((b_exp & special_exp) == special_exp && (b_fraction ^ int_bit) != 0)) return ge ? -1 : 1; // If a and b are both zeros, they are equal. if ((a_fraction | b_fraction | ((a_exp | b_exp) & special_exp)) == 0) return 0; if (a_exp == b_exp && a_fraction == b_fraction) return 0; if ((a_exp & sign_bit) != (b_exp & sign_bit)) // signs are different return (int16_t)a_exp < (int16_t)b_exp ? -1 : 1; // `a_fraction | (@as(u80, a_rep.exp) << sig_bits)`, compared as u80. const CrtU128 a_full = u128Make(a_exp, a_fraction); const CrtU128 b_full = u128Make(b_exp, b_fraction); return (u128Cmp(a_full, b_full) < 0) == ((a_exp & sign_bit) == 0) ? -1 : 1; } int crtCmp(uint32_t float_bits, bool ge, CrtU128 a, CrtU128 b) { return float_bits == 80 ? crtCmpF80(ge, a, b) : crtCmpf2(ge, a, b); } // lib/compiler_rt/float_from_int.zig `floatFromInt(T, x)` for an `x` of at // most 128 bits given as magnitude and sign. Upstream's `int_bits` tests are // implied by the `exp` ones beside them, so the width is not a parameter. CrtU128 crtFloatFromInt(uint32_t float_bits, CrtU128 abs_val, bool negative) { if (u128IsZero(abs_val)) return u128Make(0, 0); const CrtFloat f = crtFloat(float_bits); const CrtU128 one = u128Make(0, 1); const uint32_t fractional_bits = f.fractionalBits; const uint32_t exp_bias = (1u << (f.exponentBits - 1)) - 1; const CrtU128 implicit_bit = float_bits != 80 ? u128Bit(fractional_bits) : u128Make(0, 0); const uint32_t max_exp = exp_bias; // Sign const CrtU128 sign_bit = negative ? u128Bit(float_bits - 1) : u128Make(0, 0); CrtU128 result; // Compute significand const uint32_t exp = 127u - u128Clz(abs_val); if (exp <= fractional_bits) { // Shift up result to line up with the significand - no rounding // required result = u128Shl(abs_val, fractional_bits - exp); result = u128Xor(result, implicit_bit); // Remove implicit integer bit } else { const uint32_t shift_amt = exp - fractional_bits; // `@ctz(abs_val) == shift_amt - 1` const bool exact_tie = u128IsZero(u128Trunc(abs_val, shift_amt - 1)) && !u128IsZero(u128And(abs_val, u128Bit(shift_amt - 1))); // Shift down result and remove implicit integer bit result = u128Xor( u128Shr(abs_val, shift_amt - 1), u128Shl(implicit_bit, 1)); // Round result, including round-to-even for exact ties result = u128Shr(u128Add(result, one), 1); if (exact_tie) result.lo &= ~UINT64_C(1); } // Compute exponent if (exp > max_exp) // If exponent too large, overflow to infinity return u128Or(sign_bit, crtInfRep(f)); result = u128Add(result, u128Shl(u128Make(0, (uint64_t)exp + exp_bias), f.significandBits)); // If the result included a carry, we need to restore the explicit // integer bit if (float_bits == 80) result = u128Or(result, u128Bit(fractional_bits)); return u128Or(sign_bit, result); } // The one rounding of an exact result, shared by division and // multiplication: `q` holds its leading p + 3 bits, bit p + 2 weighing // 2^`exponent`, and `sticky` says whether anything below them is nonzero. // Rounded once to nearest, ties to even, at the normal or subnormal position. static CrtU128 crtRoundExact( CrtFloat f, CrtU128 q, bool sticky, int32_t exponent, CrtU128 sign) { const uint32_t p = f.fractionalBits + 1; // 64 (f80), 113 (f128) const uint32_t maxExponent = (1u << f.exponentBits) - 1; const int32_t exponentBias = (int32_t)(maxExponent >> 1); // q's top bit is 2^exponent (bit p + 2), else 2^(exponent - 1). uint32_t drop = 3; if (u128IsZero(u128Shr(q, p + 2))) { drop = 2; exponent -= 1; } const int32_t writtenExponent = exponent + exponentBias; if (writtenExponent >= (int32_t)maxExponent) return u128Or(crtInfRep(f), sign); if (writtenExponent < 1) drop += (uint32_t)(1 - writtenExponent); CrtU128 m = u128Shr(q, drop); if (drop <= p + 3) { const int c = u128Cmp(u128Trunc(q, drop), u128Bit(drop - 1)); if (c > 0 || (c == 0 && (sticky || (m.lo & 1u) != 0))) m = u128Add(m, u128Make(0, 1)); } uint64_t e = writtenExponent < 1 ? 0u : (uint64_t)writtenExponent; if (!u128IsZero(u128Shr(m, p))) { // rounded up past p bits m = u128Shr(m, 1); e += 1; if (e >= maxExponent) return u128Or(crtInfRep(f), sign); } if (e == 0 && !u128IsZero(u128Shr(m, p - 1))) // subnormal rounded to normal e = 1; if (f.significandBits == f.fractionalBits) // implicit bit: not stored m = u128Trunc(m, f.fractionalBits); return u128Or(u128Or(m, u128Shl(u128Make(0, e), f.significandBits)), sign); } // The exact 256-bit product of two 128-bit integers, as (hi, lo). static void crtMulExact(CrtU128 a, CrtU128 b, CrtU128* hi, CrtU128* lo) { const CrtU128 ll = u128Mul64(a.lo, b.lo), lh = u128Mul64(a.lo, b.hi); const CrtU128 hl = u128Mul64(a.hi, b.lo), hh = u128Mul64(a.hi, b.hi); // The middle column: ll.hi + lh.lo + hl.lo, its carry into hi. const CrtU128 mid = u128Add( u128Add(u128Make(0, ll.hi), u128Make(0, lh.lo)), u128Make(0, hl.lo)); *lo = u128Make(mid.lo, ll.lo); *hi = u128Add(hh, u128Add(u128Make(0, lh.hi), u128Add(u128Make(0, hl.hi), u128Make(0, mid.hi)))); } // lib/compiler_rt/mulf3.zig `mulf3`, ported from LLVM's fp_mul_impl.inc, up // to the product. Spec over the transcription: upstream's product rounding // misrounds (`__multf3` about one f128 product in fifty, clustered on // all-ones significand runs; a comptime f80 product with a subnormal result // came out 0) -- checked against exact rationals; see "Upstream // divergences". The significands are multiplied exactly and rounded once, // by the same crtRoundExact the divisions end in. CrtU128 crtMulf3(uint32_t float_bits, CrtU128 a, CrtU128 b) { const CrtFloat f = crtFloat(float_bits); const uint32_t significandBits = f.significandBits; const uint32_t fractionalBits = f.fractionalBits; const uint32_t exponentBits = f.exponentBits; const CrtU128 signBit = u128Bit(significandBits + exponentBits); const uint32_t maxExponent = (1u << exponentBits) - 1; const int32_t exponentBias = (int32_t)(maxExponent >> 1); const CrtU128 integerBit = u128Bit(fractionalBits); const CrtU128 quietBit = u128Shr(integerBit, 1); const CrtU128 significandMask = u128Sub(u128Bit(significandBits), u128Make(0, 1)); const CrtU128 absMask = u128Sub(signBit, u128Make(0, 1)); const CrtU128 qnanRep = u128Or(crtNanRep(f), quietBit); const CrtU128 infRep = crtInfRep(f); // `@bitCast(math.floatMin(T))`: the smallest normal, exponent field 1. const CrtU128 minNormalRep = u128Or(u128Bit(significandBits), significandBits != fractionalBits ? integerBit : u128Make(0, 0)); const uint32_t aExponent = (uint32_t)(u128Shr(a, significandBits).lo & maxExponent); const uint32_t bExponent = (uint32_t)(u128Shr(b, significandBits).lo & maxExponent); const CrtU128 productSign = u128And(u128Xor(a, b), signBit); CrtU128 aSignificand = u128And(a, significandMask); CrtU128 bSignificand = u128And(b, significandMask); int32_t scale = 0; // Detect if a or b is zero, denormal, infinity, or NaN. if (aExponent - 1u >= maxExponent - 1u || bExponent - 1u >= maxExponent - 1u) { const CrtU128 aAbs = u128And(a, absMask); const CrtU128 bAbs = u128And(b, absMask); // NaN * anything = qNaN if (u128Cmp(aAbs, infRep) > 0) return u128Or(a, quietBit); // anything * NaN = qNaN if (u128Cmp(bAbs, infRep) > 0) return u128Or(b, quietBit); if (u128Cmp(aAbs, infRep) == 0) { // infinity * non-zero = +/- infinity if (!u128IsZero(bAbs)) return u128Or(aAbs, productSign); // infinity * zero = NaN return qnanRep; } if (u128Cmp(bAbs, infRep) == 0) { //? non-zero * infinity = +/- infinity if (!u128IsZero(aAbs)) return u128Or(bAbs, productSign); // zero * infinity = NaN return qnanRep; } // zero * anything = +/- zero if (u128IsZero(aAbs)) return productSign; // anything * zero = +/- zero if (u128IsZero(bAbs)) return productSign; // one or both of a or b is denormal, the other (if applicable) is a // normal number. Renormalize one or both of a and b, and set scale // to include the necessary exponent adjustment. if (u128Cmp(aAbs, minNormalRep) < 0) scale += crtNormalize(f, &aSignificand); if (u128Cmp(bAbs, minNormalRep) < 0) scale += crtNormalize(f, &bSignificand); } // Or in the implicit significand bit. aSignificand = u128Or(aSignificand, integerBit); bSignificand = u128Or(bSignificand, integerBit); // P = aSig * bSig exactly, 2^0 at bit 2 * fractionalBits; its leading // p + 3 bits put 2^0 at bit p + 1 = fractionalBits + 2. CrtU128 productHi, productLo; crtMulExact(aSignificand, bSignificand, &productHi, &productLo); const uint32_t shift = fractionalBits - 2; return crtRoundExact(f, u128Or(u128Shl(productHi, 128 - shift), u128Shr(productLo, shift)), !u128IsZero(u128Trunc(productLo, shift)), (int32_t)(aExponent + bExponent) - 2 * exponentBias + scale + 1, productSign); } // lib/compiler_rt/divtf3.zig `div`, ported from LLVM's fp_div_impl.inc. // Spec over the transcription: upstream's reciprocal-based quotient // (`__divxf3`'s Q64 reciprocal and 64-bit residual, `__divtf3`'s subnormal // path) misrounds -- about one normal f80 quotient in five, and subnormal // f128 quotients; `__divxf3` also flushes subnormal quotients to zero // (checked against exact rationals; see "Upstream divergences"). Both // divisions end here instead: the normalised p-bit significands (integer // bit set) are divided exactly, p + 3 quotient bits and a sticky // remainder, and rounded once by crtRoundExact. `quotientExponent` is the // unbiased exponent of aSig / bSig's integer bit. static CrtU128 crtDivRound(CrtFloat f, CrtU128 aSig, CrtU128 bSig, int32_t quotientExponent, CrtU128 quotientSign) { const uint32_t p = f.fractionalBits + 1; CrtU128 rem = aSig; CrtU128 q = u128Make(0, 0); for (uint32_t i = 0; i < p + 3; i++) { q = u128Shl(q, 1); if (u128Cmp(rem, bSig) >= 0) { rem = u128Sub(rem, bSig); q.lo |= 1u; } rem = u128Shl(rem, 1); } return crtRoundExact( f, q, !u128IsZero(rem), quotientExponent, quotientSign); } CrtU128 crtDivtf3(CrtU128 a, CrtU128 b) { const CrtFloat f = crtFloat(128); const uint32_t significandBits = f.significandBits; const uint32_t exponentBits = f.exponentBits; const CrtU128 one = u128Make(0, 1); const CrtU128 signBit = u128Bit(significandBits + exponentBits); const uint32_t maxExponent = (1u << exponentBits) - 1; const CrtU128 implicitBit = u128Bit(significandBits); const CrtU128 quietBit = u128Shr(implicitBit, 1); const CrtU128 significandMask = u128Sub(implicitBit, one); const CrtU128 absMask = u128Sub(signBit, one); const CrtU128 exponentMask = u128Xor(absMask, significandMask); const CrtU128 qnanRep = u128Or(exponentMask, quietBit); const CrtU128 infRep = crtInfRep(f); const uint32_t aExponent = (uint32_t)(u128Shr(a, significandBits).lo & maxExponent); const uint32_t bExponent = (uint32_t)(u128Shr(b, significandBits).lo & maxExponent); const CrtU128 quotientSign = u128And(u128Xor(a, b), signBit); CrtU128 aSignificand = u128And(a, significandMask); CrtU128 bSignificand = u128And(b, significandMask); int32_t scale = 0; // Detect if a or b is zero, denormal, infinity, or NaN. if (aExponent - 1u >= maxExponent - 1u || bExponent - 1u >= maxExponent - 1u) { const CrtU128 aAbs = u128And(a, absMask); const CrtU128 bAbs = u128And(b, absMask); // NaN / anything = qNaN if (u128Cmp(aAbs, infRep) > 0) return u128Or(a, quietBit); // anything / NaN = qNaN if (u128Cmp(bAbs, infRep) > 0) return u128Or(b, quietBit); if (u128Cmp(aAbs, infRep) == 0) { // infinity / infinity = NaN if (u128Cmp(bAbs, infRep) == 0) return qnanRep; // infinity / anything else = +/- infinity return u128Or(aAbs, quotientSign); } // anything else / infinity = +/- 0 if (u128Cmp(bAbs, infRep) == 0) return quotientSign; if (u128IsZero(aAbs)) { // zero / zero = NaN if (u128IsZero(bAbs)) return qnanRep; // zero / anything else = +/- zero return quotientSign; } // anything else / zero = +/- infinity if (u128IsZero(bAbs)) return u128Or(infRep, quotientSign); // one or both of a or b is denormal, the other (if applicable) is a // normal number. Renormalize one or both of a and b, and set scale // to include the necessary exponent adjustment. if (u128Cmp(aAbs, implicitBit) < 0) scale += crtNormalize(f, &aSignificand); if (u128Cmp(bAbs, implicitBit) < 0) scale -= crtNormalize(f, &bSignificand); } // Set the implicit significand bit. aSignificand = u128Or(aSignificand, implicitBit); bSignificand = u128Or(bSignificand, implicitBit); return crtDivRound(f, aSignificand, bSignificand, (int32_t)(aExponent - bExponent) + scale, quotientSign); } // lib/compiler_rt/divxf3.zig `__divxf3`. CrtU128 crtDivxf3(CrtU128 a, CrtU128 b) { const CrtFloat f = crtFloat(80); const uint32_t significandBits = f.significandBits; const uint32_t fractionalBits = f.fractionalBits; const uint32_t exponentBits = f.exponentBits; const CrtU128 one = u128Make(0, 1); const CrtU128 signBit = u128Bit(significandBits + exponentBits); const uint32_t maxExponent = (1u << exponentBits) - 1; const CrtU128 integerBit = u128Bit(fractionalBits); const CrtU128 quietBit = u128Shr(integerBit, 1); const CrtU128 significandMask = u128Sub(u128Bit(significandBits), one); const CrtU128 absMask = u128Sub(signBit, one); const CrtU128 qnanRep = u128Or(crtNanRep(f), quietBit); const CrtU128 infRep = crtInfRep(f); const uint32_t aExponent = (uint32_t)(u128Shr(a, significandBits).lo & maxExponent); const uint32_t bExponent = (uint32_t)(u128Shr(b, significandBits).lo & maxExponent); const CrtU128 quotientSign = u128And(u128Xor(a, b), signBit); CrtU128 aSignificand = u128And(a, significandMask); CrtU128 bSignificand = u128And(b, significandMask); int32_t scale = 0; // Detect if a or b is zero, denormal, infinity, or NaN. if (aExponent - 1u >= maxExponent - 1u || bExponent - 1u >= maxExponent - 1u) { const CrtU128 aAbs = u128And(a, absMask); const CrtU128 bAbs = u128And(b, absMask); // NaN / anything = qNaN if (u128Cmp(aAbs, infRep) > 0) return u128Or(a, quietBit); // anything / NaN = qNaN if (u128Cmp(bAbs, infRep) > 0) return u128Or(b, quietBit); if (u128Cmp(aAbs, infRep) == 0) { // infinity / infinity = NaN if (u128Cmp(bAbs, infRep) == 0) return qnanRep; // infinity / anything else = +/- infinity return u128Or(aAbs, quotientSign); } // anything else / infinity = +/- 0 if (u128Cmp(bAbs, infRep) == 0) return quotientSign; if (u128IsZero(aAbs)) { // zero / zero = NaN if (u128IsZero(bAbs)) return qnanRep; // zero / anything else = +/- zero return quotientSign; } // anything else / zero = +/- infinity if (u128IsZero(bAbs)) return u128Or(infRep, quotientSign); // one or both of a or b is denormal, the other (if applicable) is a // normal number. Renormalize one or both of a and b, and set scale // to include the necessary exponent adjustment. if (u128Cmp(aAbs, integerBit) < 0) scale += crtNormalize(f, &aSignificand); if (u128Cmp(bAbs, integerBit) < 0) scale -= crtNormalize(f, &bSignificand); } return crtDivRound(f, aSignificand, bSignificand, (int32_t)(aExponent - bExponent) + scale, quotientSign); } // lib/compiler_rt/extendf.zig `extendf(dst_t, src_t, a)` for the implicit-bit // formats (f16, f32, f64, f128): exact widening, NaN payload kept. CrtU128 crtExtendf(uint32_t dst_bits, uint32_t src_bits, CrtU128 a) { const uint32_t srcSigBits = crtFloat(src_bits).significandBits; const uint32_t dstSigBits = crtFloat(dst_bits).significandBits; const CrtU128 one = u128Make(0, 1); const uint32_t srcBits = src_bits; const uint32_t srcExpBits = srcBits - srcSigBits - 1; const uint32_t srcInfExp = (1u << srcExpBits) - 1; const uint32_t srcExpBias = srcInfExp >> 1; const CrtU128 srcMinNormal = u128Bit(srcSigBits); const CrtU128 srcInfinity = u128Shl(u128Make(0, srcInfExp), srcSigBits); const CrtU128 srcSignMask = u128Bit(srcSigBits + srcExpBits); const CrtU128 srcAbsMask = u128Sub(srcSignMask, one); const CrtU128 srcQNaN = u128Bit(srcSigBits - 1); const CrtU128 srcNaNCode = u128Sub(srcQNaN, one); const uint32_t dstBits = dst_bits; const uint32_t dstExpBits = dstBits - dstSigBits - 1; const uint32_t dstInfExp = (1u << dstExpBits) - 1; const uint32_t dstExpBias = dstInfExp >> 1; const CrtU128 dstMinNormal = u128Bit(dstSigBits); // Break a into a sign and representation of the absolute value const CrtU128 aRep = a; const CrtU128 aAbs = u128And(aRep, srcAbsMask); const CrtU128 sign = u128And(aRep, srcSignMask); CrtU128 absResult; if (u128Cmp(u128Trunc(u128Sub(aAbs, srcMinNormal), srcBits), u128Sub(srcInfinity, srcMinNormal)) < 0) { // a is a normal number. absResult = u128Shl(aAbs, dstSigBits - srcSigBits); absResult = u128Add(absResult, u128Shl(u128Make(0, dstExpBias - srcExpBias), dstSigBits)); } else if (u128Cmp(aAbs, srcInfinity) >= 0) { // a is NaN or infinity. absResult = u128Shl(u128Make(0, dstInfExp), dstSigBits); absResult = u128Or(absResult, u128Shl(u128And(aAbs, srcQNaN), dstSigBits - srcSigBits)); absResult = u128Or(absResult, u128Shl(u128And(aAbs, srcNaNCode), dstSigBits - srcSigBits)); } else if (!u128IsZero(aAbs)) { // a is denormal: renormalize the significand and clear the leading // bit, then insert the correct adjusted exponent. const uint32_t scale = u128Clz(aAbs) - u128Clz(srcMinNormal); absResult = u128Shl(aAbs, dstSigBits - srcSigBits + scale); absResult = u128Xor(absResult, dstMinNormal); const uint32_t resultExponent = dstExpBias - srcExpBias - scale + 1; absResult = u128Or( absResult, u128Shl(u128Make(0, resultExponent), dstSigBits)); } else { // a is zero. absResult = u128Make(0, 0); } // Apply the signbit to (dst_t)abs(a). return u128Or(absResult, u128Shl(sign, dstBits - srcBits)); } // lib/compiler_rt/truncf.zig `truncf(dst_t, src_t, a)` for the implicit-bit // formats: round to nearest, ties to even, with subnormal results. CrtU128 crtTruncf(uint32_t dst_bits, uint32_t src_bits, CrtU128 a) { const uint32_t srcSigBits = crtFloat(src_bits).significandBits; const uint32_t dstSigBits = crtFloat(dst_bits).significandBits; const CrtU128 one = u128Make(0, 1); const uint32_t srcBits = src_bits; const uint32_t srcExpBits = srcBits - srcSigBits - 1; const uint32_t srcInfExp = (1u << srcExpBits) - 1; const uint32_t srcExpBias = srcInfExp >> 1; const CrtU128 srcMinNormal = u128Bit(srcSigBits); const CrtU128 srcSignificandMask = u128Sub(srcMinNormal, one); const CrtU128 srcInfinity = u128Shl(u128Make(0, srcInfExp), srcSigBits); const CrtU128 srcSignMask = u128Bit(srcSigBits + srcExpBits); const CrtU128 srcAbsMask = u128Sub(srcSignMask, one); const CrtU128 roundMask = u128Sub(u128Bit(srcSigBits - dstSigBits), one); const CrtU128 halfway = u128Bit(srcSigBits - dstSigBits - 1); const CrtU128 srcQNaN = u128Bit(srcSigBits - 1); const CrtU128 srcNaNCode = u128Sub(srcQNaN, one); const uint32_t dstBits = dst_bits; const uint32_t dstExpBits = dstBits - dstSigBits - 1; const uint32_t dstInfExp = (1u << dstExpBits) - 1; const uint32_t dstExpBias = dstInfExp >> 1; const uint32_t underflowExponent = srcExpBias + 1 - dstExpBias; const uint32_t overflowExponent = srcExpBias + dstInfExp - dstExpBias; const CrtU128 underflow = u128Shl(u128Make(0, underflowExponent), srcSigBits); const CrtU128 overflow = u128Shl(u128Make(0, overflowExponent), srcSigBits); const CrtU128 dstQNaN = u128Bit(dstSigBits - 1); const CrtU128 dstNaNCode = u128Sub(dstQNaN, one); // Break a into a sign and representation of the absolute value const CrtU128 aRep = a; const CrtU128 aAbs = u128And(aRep, srcAbsMask); const CrtU128 sign = u128And(aRep, srcSignMask); CrtU128 absResult; if (u128Cmp(u128Trunc(u128Sub(aAbs, underflow), srcBits), u128Trunc(u128Sub(aAbs, overflow), srcBits)) < 0) { // The exponent of a is within the range of normal numbers in the // destination format: right-shift with rounding and rebias. absResult = u128Trunc(u128Shr(aAbs, srcSigBits - dstSigBits), dstBits); absResult = u128Trunc( u128Sub(absResult, u128Shl(u128Make(0, srcExpBias - dstExpBias), dstSigBits)), dstBits); const CrtU128 roundBits = u128And(aAbs, roundMask); if (u128Cmp(roundBits, halfway) > 0) absResult = u128Add(absResult, one); // Round to nearest else if (u128Cmp(roundBits, halfway) == 0) absResult = u128Add(absResult, u128And(absResult, one)); // Ties } else if (u128Cmp(aAbs, srcInfinity) > 0) { // a is NaN: infinity, the qNaN bit and the (truncated) payload. absResult = u128Shl(u128Make(0, dstInfExp), dstSigBits); absResult = u128Or(absResult, dstQNaN); absResult = u128Or(absResult, u128And( u128Shr(u128And(aAbs, srcNaNCode), srcSigBits - dstSigBits), dstNaNCode)); } else if (u128Cmp(aAbs, overflow) >= 0) { // a overflows to infinity. absResult = u128Shl(u128Make(0, dstInfExp), dstSigBits); } else { // a underflows on conversion to the destination type or is an exact // zero. The result may be a denormal or zero. const uint32_t aExp = (uint32_t)u128Shr(aAbs, srcSigBits).lo; const uint32_t shift = srcExpBias - dstExpBias - aExp + 1; const CrtU128 significand = u128Or(u128And(aRep, srcSignificandMask), srcMinNormal); // Right shift by the denormalization amount with sticky. if (shift > srcSigBits) { absResult = u128Make(0, 0); } else { const bool sticky = !u128IsZero( u128Trunc(u128Shl(significand, srcBits - shift), srcBits)); const CrtU128 denormalizedSignificand = u128Or( u128Shr(significand, shift), u128Make(0, sticky ? 1u : 0u)); absResult = u128Shr(denormalizedSignificand, srcSigBits - dstSigBits); const CrtU128 roundBits = u128And(denormalizedSignificand, roundMask); if (u128Cmp(roundBits, halfway) > 0) absResult = u128Add(absResult, one); else if (u128Cmp(roundBits, halfway) == 0) absResult = u128Add(absResult, u128And(absResult, one)); } } return u128Or( absResult, u128Trunc(u128Shr(sign, srcBits - dstBits), dstBits)); } // lib/compiler_rt/extendf.zig `extend_f80(src_t, a)` for f16, f32, f64 // (`__extendhfxf2`, `__extendsfxf2`, `__extenddfxf2`). The result's low 64 // bits are the fraction (with its explicit integer bit), `hi` the sign and // exponent. CrtU128 crtExtendF80(uint32_t src_bits, CrtU128 a) { assert(src_bits <= 64); // `src_t` is f16, f32 or f64 const uint64_t dst_int_bit = UINT64_C(0x8000000000000000); const uint32_t dst_sig_bits = 63; // -1 for the integer bit const uint32_t dst_exp_bias = 16383; const uint32_t src_sig_bits = crtFloat(src_bits).significandBits; const uint32_t src_exp_bits = src_bits - src_sig_bits - 1; assert(src_exp_bits < 32); const uint32_t src_inf_exp = (1u << src_exp_bits) - 1; const uint32_t src_exp_bias = src_inf_exp >> 1; const uint64_t src_min_normal = UINT64_C(1) << src_sig_bits; const uint64_t src_inf = (uint64_t)src_inf_exp << src_sig_bits; const uint64_t src_sign_mask = UINT64_C(1) << (src_sig_bits + src_exp_bits); const uint64_t src_abs_mask = src_sign_mask - 1; const uint64_t src_qnan = UINT64_C(1) << (src_sig_bits - 1); const uint64_t src_nan_code = src_qnan - 1; uint16_t exp; uint64_t fraction; // Break a into a sign and representation of the absolute value const uint64_t a_abs = a.lo & src_abs_mask; const uint16_t sign = (a.lo & src_sign_mask) != 0 ? 0x8000 : 0; if (a_abs - src_min_normal < src_inf - src_min_normal) { // a is a normal number. exp = (uint16_t)(a_abs >> src_sig_bits); exp = (uint16_t)(exp + (dst_exp_bias - src_exp_bias)); fraction = a_abs << (dst_sig_bits - src_sig_bits); fraction |= dst_int_bit; // bit 64 is always set for normal numbers } else if (a_abs >= src_inf) { // a is NaN or infinity. exp = 0x7fff; fraction = dst_int_bit; fraction |= (a_abs & src_qnan) << (dst_sig_bits - src_sig_bits); fraction |= (a_abs & src_nan_code) << (dst_sig_bits - src_sig_bits); } else if (a_abs != 0) { // a is denormal: renormalize the significand and clear the leading // bit, then insert the correct adjusted exponent. const uint32_t scale = u128Clz(u128Make(0, a_abs)) - u128Clz(u128Make(0, src_min_normal)); fraction = a_abs << (dst_sig_bits - src_sig_bits + scale); fraction |= dst_int_bit; exp = (uint16_t)(a_abs >> (src_sig_bits - scale)); exp ^= 1; exp = (uint16_t)(exp | (dst_exp_bias - src_exp_bias - scale + 1)); } else { // a is zero. exp = 0; fraction = 0; } exp = (uint16_t)(exp | sign); return u128Make(exp, fraction); } // lib/compiler_rt/extendxftf2.zig `__extendxftf2`: f80 -> f128, exact. CrtU128 crtExtendxftf2(CrtU128 a) { const uint64_t src_int_bit = UINT64_C(0x8000000000000000); const uint64_t src_sig_mask = ~src_int_bit; const uint32_t src_sig_bits = 64 - 1; // -1 for the integer bit const uint32_t dst_sig_bits = 112; const uint32_t dst_bits = 128; // Break a into a sign and representation of the absolute value uint16_t a_rep_exp = (uint16_t)a.hi; const uint64_t a_rep_fraction = a.lo; const uint16_t sign = a_rep_exp & 0x8000; a_rep_exp &= 0x7FFF; CrtU128 abs_result; if (a_rep_exp == 0 && a_rep_fraction == 0) { // zero abs_result = u128Make(0, 0); } else if (a_rep_exp == 0x7FFF) { // a is nan or infinite abs_result = u128Shl( u128Make(0, a_rep_fraction), dst_sig_bits - src_sig_bits); abs_result = u128Or( abs_result, u128Shl(u128Make(0, a_rep_exp), dst_sig_bits)); } else if ((a_rep_fraction & src_int_bit) != 0) { // a is a normal value abs_result = u128Shl(u128Make(0, a_rep_fraction & src_sig_mask), dst_sig_bits - src_sig_bits); abs_result = u128Or( abs_result, u128Shl(u128Make(0, a_rep_exp), dst_sig_bits)); } else { // a is denormal abs_result = u128Shl( u128Make(0, a_rep_fraction), dst_sig_bits - src_sig_bits); } // Apply the signbit to (dst_t)abs(a). return u128Or(abs_result, u128Shl(u128Make(0, sign), dst_bits - 16)); } // lib/compiler_rt/truncf.zig `trunc_f80(dst_t, a)` for f16, f32, f64 // (`__truncxfhf2`, `__truncxfsf2`, `__truncxfdf2`); `a` holds the f80 as // crtExtendF80 returns it. CrtU128 crtTruncF80(uint32_t dst_bits, CrtU128 a) { assert(dst_bits == 16 || dst_bits == 32 || dst_bits == 64); const CrtFloat dst = crtFloat(dst_bits); const uint32_t src_sig_bits = 63; const uint32_t dst_sig_bits = dst.significandBits; const uint64_t dst_mask = dst_bits == 64 ? UINT64_MAX : (UINT64_C(1) << dst_bits) - 1; const uint16_t src_exp_bias = 16383; const uint64_t round_mask = (UINT64_C(1) << (src_sig_bits - dst_sig_bits)) - 1; const uint64_t halfway = UINT64_C(1) << (src_sig_bits - dst_sig_bits - 1); const uint16_t dst_inf_exp = (uint16_t)((1u << dst.exponentBits) - 1); const uint16_t dst_exp_bias = dst_inf_exp >> 1; const uint16_t underflow = (uint16_t)(src_exp_bias + 1 - dst_exp_bias); const uint16_t overflow = (uint16_t)(src_exp_bias + dst_inf_exp - dst_exp_bias); const uint64_t dst_qnan = UINT64_C(1) << (dst_sig_bits - 1); const uint64_t dst_nan_mask = dst_qnan - 1; const uint16_t sign = (uint16_t)(a.hi & 0x8000); const uint16_t exp = (uint16_t)(a.hi & 0x7FFF); const uint64_t fraction = a.lo & UINT64_C(0x7FFFFFFFFFFFFFFF); uint64_t abs_result; if ((uint16_t)(exp - underflow) < (uint16_t)(exp - overflow)) { abs_result = (uint64_t)exp << dst_sig_bits; abs_result |= fraction >> (src_sig_bits - dst_sig_bits); abs_result -= (uint64_t)(src_exp_bias - dst_exp_bias) << dst_sig_bits; abs_result &= dst_mask; const uint64_t round_bits = fraction & round_mask; if (round_bits > halfway) abs_result += 1; else if (round_bits == halfway) abs_result += abs_result & 1; } else if (exp == 0x7FFF && fraction != 0) { abs_result = (uint64_t)dst_inf_exp << dst_sig_bits; abs_result |= dst_qnan; abs_result |= (fraction >> (src_sig_bits - dst_sig_bits)) & dst_nan_mask; } else if (exp >= overflow) { abs_result = (uint64_t)dst_inf_exp << dst_sig_bits; } else { const uint32_t shift = (uint32_t)(src_exp_bias - dst_exp_bias - exp); if (shift > src_sig_bits) { abs_result = 0; } else { const uint64_t sticky = (fraction << shift) != 0; const uint64_t denormalized = fraction >> shift | sticky; abs_result = denormalized >> (src_sig_bits - dst_sig_bits); const uint64_t round_bits = denormalized & round_mask; if (round_bits > halfway) abs_result += 1; else if (round_bits == halfway) abs_result += abs_result & 1; } } return u128Make( 0, (abs_result | (uint64_t)sign << (dst_bits - 16)) & dst_mask); } // lib/compiler_rt/trunctfxf2.zig `__trunctfxf2`: f128 -> f80, rounded to // nearest even; the exponent is copied, not range-checked, as upstream's is. CrtU128 crtTrunctfxf2(CrtU128 a) { const uint32_t src_sig_bits = 112; const uint32_t dst_sig_bits = 64 - 1; // -1 for the integer bit // Various constants whose values follow from the type parameters. const uint32_t src_bits = 128; const uint32_t src_exp_bits = src_bits - src_sig_bits - 1; const uint64_t src_inf_exp = 0x7FFF; const CrtU128 src_inf = u128Shl(u128Make(0, src_inf_exp), src_sig_bits); const CrtU128 src_sign_mask = u128Bit(src_sig_bits + src_exp_bits); const CrtU128 src_abs_mask = u128Sub(src_sign_mask, u128Make(0, 1)); const CrtU128 round_mask = u128Sub(u128Bit(src_sig_bits - dst_sig_bits), u128Make(0, 1)); const CrtU128 halfway = u128Bit(src_sig_bits - dst_sig_bits - 1); // Break a into a sign and representation of the absolute value const CrtU128 a_abs = u128And(a, src_abs_mask); const uint16_t sign = !u128IsZero(u128And(a, src_sign_mask)) ? 0x8000 : 0; const uint64_t integer_bit = UINT64_C(1) << 63; uint16_t res_exp; uint64_t res_fraction; if (u128Cmp(a_abs, src_inf) > 0) { // a is NaN. // Conjure the result by beginning with infinity, setting the qNaN // bit and inserting the (truncated) trailing NaN field. // Spec over the transcription: upstream sets only the integer bit // (`"res_fraction = 0x8000000000000000"`), so a signalling NaN whose // payload sits below the kept bits comes out as an infinity. The // quiet bit keeps every NaN a NaN (see "Upstream divergences"). res_exp = 0x7fff; res_fraction = UINT64_C(0xC000000000000000); res_fraction |= u128Shr(a_abs, src_sig_bits - dst_sig_bits).lo; } else { // The exponent of a is within the range of normal numbers in the // destination format. We can convert by simply right-shifting with // rounding, adding the explicit integer bit, and adjusting the // exponent res_fraction = u128Shr(a_abs, src_sig_bits - dst_sig_bits).lo | integer_bit; res_exp = (uint16_t)u128Shr(a_abs, src_sig_bits).lo; const CrtU128 round_bits = u128And(a_abs, round_mask); if (u128Cmp(round_bits, halfway) > 0) { // Round to nearest // `@addWithOverflow(res_fraction, 1)`: the carry out of an // all-ones fraction. const uint16_t ov1 = res_fraction == UINT64_MAX ? 1 : 0; res_fraction += 1; res_exp = (uint16_t)(res_exp + ov1); res_fraction |= (uint64_t)ov1 << 63; // Restore integer bit } else if (u128Cmp(round_bits, halfway) == 0) { // Ties to even const uint64_t ov0 = res_fraction + (res_fraction & 1); const uint16_t ov1 = ov0 < res_fraction ? 1 : 0; res_fraction = ov0; res_exp = (uint16_t)(res_exp + ov1); res_fraction |= (uint64_t)ov1 << 63; // Restore integer bit } if (res_exp == 0) res_fraction &= ~integer_bit; // Remove integer bit for de-normals } res_exp |= sign; return u128Make(res_exp, res_fraction); } // `@as(T, 0x1p)` for a normal power of two, f80's integer bit included. static CrtU128 crtPow2(CrtFloat f, int32_t e) { CrtU128 r = u128Shl(u128Make(0, (uint64_t)(e + 16383)), f.significandBits); if (f.significandBits != f.fractionalBits) r = u128Or(r, u128Bit(f.fractionalBits)); return r; } // lib/compiler_rt/floor_ceil.zig `impl(T, op, x)`'s `f64, f80, f128` arm // for T = f80 (`__floorx`, `__ceilx`) and f128 (`floorq`, `ceilq`), its // float operators on this file's addf3 and comparef. CrtU128 crtFloorCeil(uint32_t float_bits, bool ceil, CrtU128 x) { const CrtFloat f = crtFloat(float_bits); const uint32_t bits = float_bits; const CrtU128 C = crtPow2(f, (int32_t)f.fractionalBits); const uint32_t mantissa = f.significandBits; const uint64_t mask = 0x7FFF; const uint64_t bias = 16383; const CrtU128 zero = u128Make(0, 0); const CrtU128 one = crtPow2(f, 0); const CrtU128 sign_bit = u128Bit(bits - 1); const CrtU128 u = x; const uint64_t e = u128Shr(u, mantissa).lo & mask; if (e >= bias + f.fractionalBits || crtCmp(bits, false, x, zero) == 0) return x; const bool positive = u128IsZero(u128Shr(u, bits - 1)); const CrtU128 y = positive ? crtSubf3(bits, crtSubf3(bits, crtAddf3(bits, x, C), C), x) : crtSubf3(bits, crtAddf3(bits, crtSubf3(bits, x, C), C), x); if (e <= bias - 1) { if (!ceil) return positive ? zero : u128Xor(one, sign_bit); return positive ? one : u128Xor(zero, sign_bit); } if (!ceil) { if (crtCmp(bits, true, y, zero) > 0) return crtSubf3(bits, crtAddf3(bits, x, y), one); } else { if (crtCmp(bits, false, y, zero) < 0) return crtAddf3(bits, crtAddf3(bits, x, y), one); } return crtAddf3(bits, x, y); } // lib/compiler_rt/trunc.zig `truncq`: the fraction bits below the binary // point masked off. CrtU128 crtTruncq(CrtU128 x) { const CrtU128 u = x; int32_t e = (int32_t)(u128Shr(u, 112).lo & 0x7FFF) - 0x3FFF + 16; CrtU128 m; if (e >= 112 + 16) { return x; } if (e < 16) { e = 1; } m = u128Shr(u128Make(UINT64_MAX, UINT64_MAX), (uint32_t)e); if (u128IsZero(u128And(u, m))) { return x; } else { return u128And(u, u128Xor(m, u128Make(UINT64_MAX, UINT64_MAX))); } } // lib/compiler_rt/trunc.zig `__truncx`: `"return @floatCast(truncq(x));"`. CrtU128 crtTruncx(CrtU128 x) { return crtTrunctfxf2(crtTruncq(crtExtendxftf2(x))); } // lib/compiler_rt/round.zig `roundq`: to nearest, halfway cases away from // zero, through the `x + toint - toint - x` trick in f128 arithmetic. CrtU128 crtRoundq(CrtU128 x_) { const CrtFloat f = crtFloat(128); const CrtU128 f128_toint = crtPow2(f, 112); // 1.0 / math.floatEps(f128) const CrtU128 sign_bit = u128Bit(127); const CrtU128 one = crtPow2(f, 0); const CrtU128 half = crtPow2(f, -1); CrtU128 x = x_; const CrtU128 u = x; const uint64_t e = u128Shr(u, 112).lo & 0x7FFF; CrtU128 y; if (e >= 0x3FFF + 112) { return x; } if (!u128IsZero(u128Shr(u, 127))) { x = u128Xor(x, sign_bit); } if (e < 0x3FFF - 1) { return crtMulf3(128, u128Make(0, 0), u); } y = crtSubf3( 128, crtSubf3(128, crtAddf3(128, x, f128_toint), f128_toint), x); if (crtCmp(128, true, y, half) > 0) { y = crtSubf3(128, crtAddf3(128, y, x), one); } else if (crtCmp(128, false, y, u128Xor(half, sign_bit)) <= 0) { y = crtAddf3(128, crtAddf3(128, y, x), one); } else { y = crtAddf3(128, y, x); } if (!u128IsZero(u128Shr(u, 127))) { return u128Xor(y, sign_bit); } else { return y; } } // lib/compiler_rt/round.zig `__roundx`: `"return @floatCast(roundq(x));"`. CrtU128 crtRoundx(CrtU128 x) { return crtTrunctfxf2(crtRoundq(crtExtendxftf2(x))); } // `math.isNan` on a raw f80/f128 pattern. static bool crtIsNan(CrtFloat f, CrtU128 x) { CrtU128 abs = u128Trunc(x, f.bits - 1); return u128Cmp(abs, crtInfRep(f)) > 0; } // lib/compiler_rt/fmod.zig `__fmodx`: the remainder of a / b with the sign // of a, by shift-and-subtract over the significands ("heavily inspired by // MUSL fmodl"). CrtU128 crtFmodx(CrtU128 a, CrtU128 b) { const CrtFloat T = crtFloat(80); const uint32_t significandBits = T.significandBits; const uint32_t fractionalBits = T.fractionalBits; const uint32_t exponentBits = T.exponentBits; const CrtU128 signBit = u128Bit(significandBits + exponentBits); const int32_t maxExponent = (1 << exponentBits) - 1; CrtU128 aRep = a; CrtU128 bRep = b; const CrtU128 signA = u128And(aRep, signBit); int32_t expA = (int32_t)(u128Shr(a, significandBits).lo & (uint64_t)maxExponent); int32_t expB = (int32_t)(u128Shr(b, significandBits).lo & (uint64_t)maxExponent); // There are 3 cases where the answer is undefined, check for: // - fmodx(val, 0) // - fmodx(val, NaN) // - fmodx(inf, val) // The sign on checked values does not matter. // Doing (a * b) / (a * b) produces undefined results // because the three cases always produce undefined calculations: // - 0 / 0 // - val * NaN // - inf / inf if (crtCmp(80, false, b, u128Make(0, 0)) == 0 || crtIsNan(T, b) || expA == maxExponent) { const CrtU128 ab = crtMulf3(80, a, b); return crtDivxf3(ab, ab); } // Remove the sign from both aRep = u128And(aRep, u128Xor(signBit, u128Make(UINT64_MAX, UINT64_MAX))); bRep = u128And(bRep, u128Xor(signBit, u128Make(UINT64_MAX, UINT64_MAX))); if (u128Cmp(aRep, bRep) <= 0) { if (u128Cmp(aRep, bRep) == 0) { return crtMulf3(80, u128Make(0, 0), a); } return a; } if (expA == 0) expA = crtNormalize(T, &aRep); if (expB == 0) expB = crtNormalize(T, &bRep); uint64_t highA = 0; const uint64_t highB = 0; uint64_t lowA = aRep.lo; const uint64_t lowB = bRep.lo; while (expA > expB) { uint64_t high = highA - highB; const uint64_t low = lowA - lowB; if (lowA < lowB) { high -= 1; } if (high >> 63 == 0) { if ((high | low) == 0) { return crtMulf3(80, u128Make(0, 0), a); } highA = 2 * high + (low >> 63); lowA = 2 * low; } else { highA = 2 * highA + (lowA >> 63); lowA = 2 * lowA; } expA -= 1; } uint64_t high = highA - highB; const uint64_t low = lowA - lowB; if (lowA < lowB) { high -= 1; } if (high >> 63 == 0) { if ((high | low) == 0) { return crtMulf3(80, u128Make(0, 0), a); } // (Upstream fmodx's `highA = high;` here is never read again.) lowA = low; } while ((lowA >> fractionalBits) == 0) { lowA = 2 * lowA; expA = expA - 1; } // Combine the exponent with the sign and significand, normalize if // happened to be denormalized if (expA < -(int32_t)fractionalBits) { return signA; } else if (expA <= 0) { return u128Or(u128Make(0, lowA >> (uint32_t)(1 - expA)), signA); } else { return u128Or( u128Or(u128Make(0, lowA), u128Shl(u128Make(0, (uint16_t)expA), significandBits)), signA); } } // lib/compiler_rt/fmod.zig `fmodq`, its `[*]u16` / `[*]u64` views of the // operands spelled on the pair (`hi >> 48` is the sign-and-exponent half // word, `hi` and `lo` the two u64 words). CrtU128 crtFmodq(CrtU128 a, CrtU128 b) { const CrtFloat T = crtFloat(128); CrtU128 amod = a; CrtU128 bmod = b; const uint64_t low48 = UINT64_MAX >> 16; const uint16_t signA = (uint16_t)((amod.hi >> 48) & 0x8000); int32_t expA = (int32_t)((amod.hi >> 48) & 0x7fff); int32_t expB = (int32_t)((bmod.hi >> 48) & 0x7fff); // There are 3 cases where the answer is undefined, check for: // - fmodq(val, 0) // - fmodq(val, NaN) // - fmodq(inf, val) // The sign on checked values does not matter. // Doing (a * b) / (a * b) produces undefined results // because the three cases always produce undefined calculations: // - 0 / 0 // - val * NaN // - inf / inf if (crtCmp(128, false, b, u128Make(0, 0)) == 0 || crtIsNan(T, b) || expA == 0x7fff) { const CrtU128 ab = crtMulf3(128, a, b); return crtDivtf3(ab, ab); } // Remove the sign from both amod.hi = (amod.hi & low48) | ((uint64_t)(uint16_t)expA << 48); bmod.hi = (bmod.hi & low48) | ((uint64_t)(uint16_t)expB << 48); if (crtCmp(128, false, amod, bmod) <= 0) { if (crtCmp(128, false, amod, bmod) == 0) { return crtMulf3(128, u128Make(0, 0), a); } return a; } if (expA == 0) { amod = crtMulf3(128, amod, crtPow2(T, 120)); expA = (int32_t)(amod.hi >> 48) - 120; } if (expB == 0) { bmod = crtMulf3(128, bmod, crtPow2(T, 120)); expB = (int32_t)(bmod.hi >> 48) - 120; } // Or in extra non-stored mantissa digit uint64_t highA = (amod.hi & low48) | UINT64_C(1) << 48; const uint64_t highB = (bmod.hi & low48) | UINT64_C(1) << 48; uint64_t lowA = amod.lo; const uint64_t lowB = bmod.lo; while (expA > expB) { uint64_t high = highA - highB; const uint64_t low = lowA - lowB; if (lowA < lowB) { high -= 1; } if (high >> 63 == 0) { if ((high | low) == 0) { return crtMulf3(128, u128Make(0, 0), a); } highA = 2 * high + (low >> 63); lowA = 2 * low; } else { highA = 2 * highA + (lowA >> 63); lowA = 2 * lowA; } expA -= 1; } uint64_t high = highA - highB; const uint64_t low = lowA - lowB; if (lowA < lowB) { high -= 1; } if (high >> 63 == 0) { if ((high | low) == 0) { return crtMulf3(128, u128Make(0, 0), a); } highA = high; lowA = low; } while (highA >> 48 == 0) { highA = 2 * highA + (lowA >> 63); lowA = 2 * lowA; expA = expA - 1; } // Overwrite the current amod with the values in highA and lowA amod.hi = highA; amod.lo = lowA; // Combine the exponent with the sign, normalize if happened to be // denormalized if (expA <= 0) { amod.hi = (amod.hi & low48) | (uint64_t)(uint16_t)((uint16_t)(uint32_t)(expA + 120) | signA) << 48; amod = crtMulf3(128, amod, crtPow2(T, -120)); } else { amod.hi = (amod.hi & low48) | (uint64_t)(uint16_t)((uint16_t)(uint32_t)expA | signA) << 48; } return amod; } // lib/compiler_rt/fmin.zig / fmax.zig `generic_fmin` / `generic_fmax` for // T = f80 (`__fminx`, `__fmaxx`) and f128 (`fminq`, `fmaxq`). CrtU128 crtFminFmax(uint32_t float_bits, bool max, CrtU128 x, CrtU128 y) { const CrtFloat T = crtFloat(float_bits); if (crtIsNan(T, x)) return y; if (crtIsNan(T, y)) return x; const bool sx = !u128IsZero(u128Shr(x, T.bits - 1)); const bool sy = !u128IsZero(u128Shr(y, T.bits - 1)); if (sx != sy) return (sx == max) ? y : x; const bool lt = crtCmp(float_bits, false, x, y) < 0; if (max) return lt ? y : x; return lt ? x : y; } // lib/compiler_rt/sqrt.zig's `rsqrt_tab` and fixed-point multiplies, for // `__sqrtx` and `sqrtq` (musl's sqrtl). `u80` values live in the low 80 // bits of a CrtU128. static const uint16_t crt_rsqrt_tab[128] = { 0xB451, 0xB2F0, 0xB196, 0xB044, 0xAEF9, 0xADB6, 0xAC79, 0xAB43, 0xAA14, 0xA8EB, 0xA7C8, 0xA6AA, 0xA592, 0xA480, 0xA373, 0xA26B, 0xA168, 0xA06A, 0x9F70, 0x9E7B, 0x9D8A, 0x9C9D, 0x9BB5, 0x9AD1, 0x99F0, 0x9913, 0x983A, 0x9765, 0x9693, 0x95C4, 0x94F8, 0x9430, 0x936B, 0x92A9, 0x91EA, 0x912E, 0x9075, 0x8FBE, 0x8F0A, 0x8E59, 0x8DAA, 0x8CFE, 0x8C54, 0x8BAC, 0x8B07, 0x8A64, 0x89C4, 0x8925, 0x8889, 0x87EE, 0x8756, 0x86C0, 0x862B, 0x8599, 0x8508, 0x8479, 0x83EC, 0x8361, 0x82D8, 0x8250, 0x81C9, 0x8145, 0x80C2, 0x8040, 0xFF02, 0xFD0E, 0xFB25, 0xF947, 0xF773, 0xF5AA, 0xF3EA, 0xF234, 0xF087, 0xEEE3, 0xED47, 0xEBB3, 0xEA27, 0xE8A3, 0xE727, 0xE5B2, 0xE443, 0xE2DC, 0xE17A, 0xE020, 0xDECB, 0xDD7D, 0xDC34, 0xDAF1, 0xD9B3, 0xD87B, 0xD748, 0xD61A, 0xD4F1, 0xD3CD, 0xD2AD, 0xD192, 0xD07B, 0xCF69, 0xCE5B, 0xCD51, 0xCC4A, 0xCB48, 0xCA4A, 0xC94F, 0xC858, 0xC764, 0xC674, 0xC587, 0xC49D, 0xC3B7, 0xC2D4, 0xC1F4, 0xC116, 0xC03C, 0xBF65, 0xBE90, 0xBDBE, 0xBCEF, 0xBC23, 0xBB59, 0xBA91, 0xB9CC, 0xB90A, 0xB84A, 0xB78C, 0xB6D0, 0xB617, 0xB560, }; static uint32_t crtSqrtMul32(uint32_t a, uint32_t b) { return (uint32_t)(((uint64_t)a * b) >> 32); } static uint64_t crtSqrtMul64(uint64_t a, uint64_t b) { return u128Mul64(a, b).hi; } // `mul80` / `mul128`: `"ahi * bhi + (ahi * blo >> H) + (alo * bhi >> H)"` // with H = 40 / 64. static CrtU128 crtSqrtMulHi(uint32_t bits, CrtU128 a, CrtU128 b) { const uint32_t h = bits / 2; const CrtU128 lo_mask = u128Sub(u128Bit(h), u128Make(0, 1)); const CrtU128 ahi = u128Shr(a, h), alo = u128And(a, lo_mask); const CrtU128 bhi = u128Shr(b, h), blo = u128And(b, lo_mask); // every half fits in 64 bits, so each product is u128Mul64 const CrtU128 hh = u128Mul64(ahi.lo, bhi.lo); const CrtU128 hl = u128Shr(u128Mul64(ahi.lo, blo.lo), h); const CrtU128 lh = u128Shr(u128Mul64(alo.lo, bhi.lo), h); return u128Trunc(u128Add(u128Add(hh, hl), lh), bits); } // `mul80_tail`: `"alo * blo +% ((ahi * blo) << 40) +% ((alo * bhi) << // 40)"`, wrapping at 80 bits. static CrtU128 crtSqrtMul80Tail(CrtU128 a, CrtU128 b) { const CrtU128 lo_mask = u128Sub(u128Bit(40), u128Make(0, 1)); const CrtU128 ahi = u128Shr(a, 40), alo = u128And(a, lo_mask); const CrtU128 bhi = u128Shr(b, 40), blo = u128And(b, lo_mask); const CrtU128 ll = u128Mul64(alo.lo, blo.lo); const CrtU128 hl = u128Shl(u128Mul64(ahi.lo, blo.lo), 40); const CrtU128 lh = u128Shl(u128Mul64(alo.lo, bhi.lo), 40); return u128Trunc(u128Add(u128Add(ll, hl), lh), 80); } // The Goldschmidt iterations both `__sqrtx` and `sqrtq` share, from the // table estimate through the 32- and 64-bit rounds to `u[2]` / `s[2]` at // the full width (`bits` 80 or 128); `m` is the fixed-point [1, 4) operand. static void crtSqrtIterate(uint32_t bits, CrtU128 m, uint32_t i, uint32_t m_top32_shift, uint32_t m_top64_shift, CrtU128* s2_out, CrtU128* u2_out) { const uint32_t three0 = 0xC0000000u; const uint64_t three1 = UINT64_C(0xC000000000000000); const CrtU128 three2 = u128Shl(u128Make(0, 3), bits - 2); uint32_t r0 = crt_rsqrt_tab[i]; r0 <<= 16; // |r sqrt(m) - 1| < 0x1p-8 uint32_t s0 = crtSqrtMul32((uint32_t)u128Shr(m, m_top32_shift).lo, r0); uint32_t d0 = crtSqrtMul32(s0, r0); uint32_t u0 = three0 - d0; r0 = crtSqrtMul32(u0, r0) << 1; // |r sqrt(m) - 1| < 0x1.7bp-16, switch to 64bit uint64_t r1 = (uint64_t)r0 << 32; uint64_t s1 = crtSqrtMul64(u128Shr(m, m_top64_shift).lo, r1); uint64_t d1 = crtSqrtMul64(s1, r1); uint64_t u1 = three1 - d1; r1 = crtSqrtMul64(u1, r1) << 1; // |r sqrt(m) - 1| < 0x1.a5p-31 s1 = crtSqrtMul64(u1, s1) << 1; d1 = crtSqrtMul64(s1, r1); u1 = three1 - d1; r1 = crtSqrtMul64(u1, r1) << 1; // |r sqrt(m) - 1| < 0x1.c001p-59, switch to the full width const CrtU128 r2 = u128Shl(u128Make(0, r1), bits - 64); const CrtU128 s2 = crtSqrtMulHi(bits, m, r2); const CrtU128 d2 = crtSqrtMulHi(bits, s2, r2); const CrtU128 u2 = u128Trunc(u128Sub(three2, d2), bits); *s2_out = s2; *u2_out = u2; } // lib/compiler_rt/sqrt.zig `__sqrtx`. The invalid-operation NaN is the // wasm program's `math.nan(f80)` (`want_float_exceptions` off). CrtU128 crtSqrtx(CrtU128 x) { CrtU128 ix = u128Trunc(x, 80); uint64_t top = u128Shr(ix, 64).lo; // special case handling. if (top - 0x0001 >= 0x7FFF - 0x0001) { // x < 0x1p-16382 or inf or nan. if (u128IsZero(u128Trunc(ix, 79))) return x; const CrtU128 inf = u128Make(0x7FFF, UINT64_C(0x8000000000000000)); if (u128Cmp(ix, inf) == 0) return x; if (u128Cmp(ix, inf) > 0) return u128Make(0x7FFF, UINT64_C(0xC000000000000000)); // x is subnormal, normalize it. ix = crtMulf3(80, x, crtPow2(crtFloat(80), 63)); top = u128Shr(ix, 64).lo - 63; } // argument reduction: // x = 4^e m; with integer e, and m in [1, 4) // m: fixed point representation [2.78] // 2^e is the exponent part of the result. const bool even = (top & 1) != 0; const CrtU128 m = even ? u128Trunc(u128Shl(ix, 15), 79) : u128Trunc(u128Shl(ix, 16), 80); top = (top + 0x3FFF) >> 1; uint32_t i = (uint32_t)(u128Shr(ix, 57).lo & 0x3F); if (even) i += 64; CrtU128 s2, u2; crtSqrtIterate(80, m, i, 48, 16, &s2, &u2); s2 = crtSqrtMulHi(80, u2, s2); // repr: 3.77 s2 = u128Shr(u128Sub(s2, u128Make(0, 4)), 14); // repr: 17.63 // s < sqrt(m) < s + 1 ULP + tiny // compute nearest rounded result: // the nearest result to 63 bits is either s or s+0x1p-63, // we can decide by comparing (2^63 s + 0.5)^2 to 2^126 m const CrtU128 d0 = u128Trunc(u128Sub(u128Shl(m, 48), crtSqrtMul80Tail(s2, s2)), 80); const CrtU128 d1 = u128Trunc(u128Sub(s2, d0), 80); const CrtU128 d2 = u128Trunc(u128Add(u128Add(d1, s2), u128Make(0, 1)), 80); s2 = u128Trunc(u128Add(s2, u128Shr(d1, 79)), 80); s2 = u128And(s2, u128Make(0, UINT64_C(0x7FFFFFFFFFFFFFFF))); s2 = u128Or(s2, u128Make(0, UINT64_C(0x8000000000000000))); s2 = u128Or(s2, u128Make(top, 0)); const CrtU128 y = s2; // handle rounding modes and inexact exception: // only (s+1)^2 == 2^48 m case is exact otherwise // add a tiny value to cause the fenv effects. if (!u128IsZero(d2)) { CrtU128 tiny = u128Make(0x0001, UINT64_C(0x8000000000000000)); tiny = u128Or(tiny, u128And(u128Xor(d1, d2), u128Bit(79))); return crtAddf3(80, y, tiny); } return y; } // lib/compiler_rt/sqrt.zig `sqrtq`. CrtU128 crtSqrtq(CrtU128 x) { CrtU128 ix = x; uint64_t top = u128Shr(ix, 112).lo; // special case handling. if (top - 0x0001 >= 0x7FFF - 0x0001) { // x < 0x1p-16382 or inf or nan. if (u128IsZero(u128Trunc(ix, 127))) return x; const CrtU128 inf = u128Make(UINT64_C(0x7FFF000000000000), 0); if (u128Cmp(ix, inf) == 0) return x; if (u128Cmp(ix, inf) > 0) return u128Make(UINT64_C(0x7FFF800000000000), 0); // x is subnormal, normalize it. ix = crtMulf3(128, x, crtPow2(crtFloat(128), 112)); top = u128Shr(ix, 112).lo - 112; } // argument reduction: // x = 4^e m; with integer e, and m in [1, 4) // m: fixed point representation [2.126] // 2^e is the exponent part of the result. const bool even = (top & 1) != 0; const CrtU128 m = even ? u128Trunc(u128Shl(ix, 14), 127) : u128Or(u128Shl(ix, 15), u128Bit(127)); top = (top + 0x3FFF) >> 1; const uint32_t i = (uint32_t)(u128Shr(ix, 106).lo & 0x7F); CrtU128 s2, u2; crtSqrtIterate(128, m, i, 96, 64, &s2, &u2); s2 = crtSqrtMulHi(128, u2, s2); // repr: 3.125 // -0x1p-116 < s - sqrt(m) < 0x3.8001p-125 s2 = u128Shr(u128Sub(s2, u128Make(0, 4)), 13); // repr: 16.122 // s < sqrt(m) < s + 1 ULP + tiny // compute nearest rounded result: // the nearest result to 122 bits is either s or s+0x1p-122, // we can decide by comparing (2^122 s + 0.5)^2 to 2^244 m CrtU128 sq_hi, sq_lo; crtMulExact(s2, s2, &sq_hi, &sq_lo); // `s[2] *% s[2]` is sq_lo const CrtU128 d0 = u128Sub(u128Shl(m, 98), sq_lo); const CrtU128 d1 = u128Sub(s2, d0); const CrtU128 d2 = u128Add(u128Add(d1, s2), u128Make(0, 1)); s2 = u128Add(s2, u128Shr(d1, 127)); s2 = u128And(s2, u128Make(UINT64_C(0x0000FFFFFFFFFFFF), UINT64_MAX)); s2 = u128Or(s2, u128Shl(u128Make(0, top), 112)); const CrtU128 y = s2; // handle rounding modes and inexact exception: // only (s+1)^2 == 2^98 m case is exact otherwise // add a tiny value to cause the fenv effects. if (!u128IsZero(d2)) { CrtU128 tiny = u128Make(UINT64_C(0x0001000000000000), 0); tiny = u128Or(tiny, u128And(u128Xor(d1, d2), u128Bit(127))); return crtAddf3(128, y, tiny); } return y; } // --- the f32/f64 elementary functions (musl ports in lib/compiler_rt/) --- // // Every expression keeps upstream's operand types: an f32 routine computes // in `float` wherever upstream's operands are f32 and in `double` wherever // they are f64, one IEEE rounding per operation, never contracted into an // FMA (common.h turns contraction off). `want_float_exceptions` branches // that only raise a flag (`mem.doNotOptimizeAway`) are omitted: they change // no value. static float crtF32(uint32_t bits) { float f; memcpy(&f, &bits, sizeof f); return f; } static uint32_t crtF32Bits(float f) { uint32_t bits; memcpy(&bits, &f, sizeof bits); return bits; } static double crtF64(uint64_t bits) { double d; memcpy(&d, &bits, sizeof d); return d; } static uint64_t crtF64Bits(double d) { uint64_t bits; memcpy(&bits, &d, sizeof bits); return bits; } // `math.nan(f32)` / `math.nan(f64)` and `math.inf`. #define CRT_NAN_F32 crtF32(UINT32_C(0x7fc00000)) #define CRT_INF_F32 crtF32(UINT32_C(0x7f800000)) #define CRT_NAN_F64 crtF64(UINT64_C(0x7ff8000000000000)) #define CRT_INF_F64 crtF64(UINT64_C(0x7ff0000000000000)) static uint32_t crtCtz128(CrtU128 v, uint32_t width) { uint32_t n = 0; while (n < width && u128IsZero(u128And(v, u128Bit(n)))) n++; return n; } // lib/std/math/ldexp.zig `ldexp` (`math.scalbn`) over the bit pattern of an // f32, f64 or f128. static CrtU128 crtLdexpBits(CrtU128 repr, int32_t n, CrtFloat f) { const uint32_t width = f.bits; const uint32_t mantissa_bits = f.significandBits; const int32_t max_biased_exponent = 2 * (int32_t)((UINT32_C(1) << (f.exponentBits - 1)) - 1u); const CrtU128 mantissa_mask = u128Trunc(u128Make(UINT64_MAX, UINT64_MAX), mantissa_bits); const CrtU128 exp_field = crtInfRep(f); const CrtU128 sign_bit = u128And(repr, u128Bit(width - 1u)); // `if (math.isNan(x) or !math.isFinite(x)) return x;` if (u128Cmp(u128And(repr, exp_field), exp_field) == 0) return repr; int32_t exponent = (int32_t)u128Shr(u128Trunc(repr, width - 1u), mantissa_bits).lo; if (exponent == 0) exponent += (int32_t)f.exponentBits - (int32_t)(u128Clz(u128Trunc(u128Shl(repr, 1), width)) - (128u - width)); if (n >= 0) { if (n > max_biased_exponent - exponent) { // Overflow. Return +/- inf return u128Or(exp_field, sign_bit); } else if (exponent + n <= 0) { // Result is subnormal return u128Or( u128Trunc(u128Shl(repr, (uint32_t)n), width), sign_bit); } else if (exponent <= 0) { // Result is normal, but needs shifting CrtU128 result = u128Shl( u128Make(0, (uint64_t)(n + exponent)), mantissa_bits); result = u128Or(result, u128And( u128Shl(repr, (uint32_t)(1 - exponent)), mantissa_mask)); return u128Or(result, sign_bit); } // Result needs no shifting return u128Trunc( u128Add(repr, u128Shl(u128Make(0, (uint64_t)n), mantissa_bits)), width); } if (n <= -exponent) { if (n < -((int32_t)mantissa_bits + exponent)) return sign_bit; // Severe underflow. Return +/- 0 // Result underflowed, we need to shift and round int32_t shift_i = -(exponent + n) + 1; uint32_t shift = (uint32_t)(-n < shift_i ? -n : shift_i); bool exact_tie = crtCtz128(repr, width) == shift - 1u; CrtU128 result = u128And(repr, mantissa_mask); if (exponent > 0) result = u128Or(result, u128Bit(mantissa_bits)); result = u128Shr(result, shift - 1u); // Round result, including round-to-even for exact ties result = u128Shr(u128Add(result, u128Make(0, 1)), 1); if (exact_tie) result.lo &= ~UINT64_C(1); return u128Or(result, sign_bit); } // Result is exact, and needs no shifting return u128Sub(repr, u128Shl(u128Make(0, (uint64_t)(-n)), mantissa_bits)); } static float crtScalbnF32(float x, int32_t n) { return crtF32( (uint32_t)crtLdexpBits(u128Make(0, crtF32Bits(x)), n, crtFloat(32)) .lo); } static double crtScalbnF64(double x, int32_t n) { return crtF64( crtLdexpBits(u128Make(0, crtF64Bits(x)), n, crtFloat(64)).lo); } // @divFloor for the i32 operands rem_pio2_large divides. static int32_t crtDivFloor(int32_t a, int32_t b) { int32_t q = a / b; return (a % b != 0 && ((a < 0) != (b < 0))) ? q - 1 : q; } // --- tables, copied verbatim --- // rem_pio2_large.zig `ipio2` (2/pi in 24-bit chunks) and `PIo2`; exp2.zig // `exp2ft` / `exp2dt`; log.zig `log`'s `tab` / `tab2`. // clang-format off static const int32_t crt_ipio2[690] = { 0xA2F983, 0x6E4E44, 0x1529FC, 0x2757D1, 0xF534DD, 0xC0DB62, 0x95993C, 0x439041, 0xFE5163, 0xABDEBB, 0xC561B7, 0x246E3A, 0x424DD2, 0xE00649, 0x2EEA09, 0xD1921C, 0xFE1DEB, 0x1CB129, 0xA73EE8, 0x8235F5, 0x2EBB44, 0x84E99C, 0x7026B4, 0x5F7E41, 0x3991D6, 0x398353, 0x39F49C, 0x845F8B, 0xBDF928, 0x3B1FF8, 0x97FFDE, 0x05980F, 0xEF2F11, 0x8B5A0A, 0x6D1F6D, 0x367ECF, 0x27CB09, 0xB74F46, 0x3F669E, 0x5FEA2D, 0x7527BA, 0xC7EBE5, 0xF17B3D, 0x0739F7, 0x8A5292, 0xEA6BFB, 0x5FB11F, 0x8D5D08, 0x560330, 0x46FC7B, 0x6BABF0, 0xCFBC20, 0x9AF436, 0x1DA9E3, 0x91615E, 0xE61B08, 0x659985, 0x5F14A0, 0x68408D, 0xFFD880, 0x4D7327, 0x310606, 0x1556CA, 0x73A8C9, 0x60E27B, 0xC08C6B, 0x47C419, 0xC367CD, 0xDCE809, 0x2A8359, 0xC4768B, 0x961CA6, 0xDDAF44, 0xD15719, 0x053EA5, 0xFF0705, 0x3F7E33, 0xE832C2, 0xDE4F98, 0x327DBB, 0xC33D26, 0xEF6B1E, 0x5EF89F, 0x3A1F35, 0xCAF27F, 0x1D87F1, 0x21907C, 0x7C246A, 0xFA6ED5, 0x772D30, 0x433B15, 0xC614B5, 0x9D19C3, 0xC2C4AD, 0x414D2C, 0x5D000C, 0x467D86, 0x2D71E3, 0x9AC69B, 0x006233, 0x7CD2B4, 0x97A7B4, 0xD55537, 0xF63ED7, 0x1810A3, 0xFC764D, 0x2A9D64, 0xABD770, 0xF87C63, 0x57B07A, 0xE71517, 0x5649C0, 0xD9D63B, 0x3884A7, 0xCB2324, 0x778AD6, 0x23545A, 0xB91F00, 0x1B0AF1, 0xDFCE19, 0xFF319F, 0x6A1E66, 0x615799, 0x47FBAC, 0xD87F7E, 0xB76522, 0x89E832, 0x60BFE6, 0xCDC4EF, 0x09366C, 0xD43F5D, 0xD7DE16, 0xDE3B58, 0x929BDE, 0x2822D2, 0xE88628, 0x4D58E2, 0x32CAC6, 0x16E308, 0xCB7DE0, 0x50C017, 0xA71DF3, 0x5BE018, 0x34132E, 0x621283, 0x014883, 0x5B8EF5, 0x7FB0AD, 0xF2E91E, 0x434A48, 0xD36710, 0xD8DDAA, 0x425FAE, 0xCE616A, 0xA4280A, 0xB499D3, 0xF2A606, 0x7F775C, 0x83C2A3, 0x883C61, 0x78738A, 0x5A8CAF, 0xBDD76F, 0x63A62D, 0xCBBFF4, 0xEF818D, 0x67C126, 0x45CA55, 0x36D9CA, 0xD2A828, 0x8D61C2, 0x77C912, 0x142604, 0x9B4612, 0xC459C4, 0x44C5C8, 0x91B24D, 0xF31700, 0xAD43D4, 0xE54929, 0x10D5FD, 0xFCBE00, 0xCC941E, 0xEECE70, 0xF53E13, 0x80F1EC, 0xC3E7B3, 0x28F8C7, 0x940593, 0x3E71C1, 0xB3092E, 0xF3450B, 0x9C1288, 0x7B20AB, 0x9FB52E, 0xC29247, 0x2F327B, 0x6D550C, 0x90A772, 0x1FE76B, 0x96CB31, 0x4A1679, 0xE27941, 0x89DFF4, 0x9794E8, 0x84E6E2, 0x973199, 0x6BED88, 0x365F5F, 0x0EFDBB, 0xB49A48, 0x6CA467, 0x427271, 0x325D8D, 0xB8159F, 0x09E5BC, 0x25318D, 0x3974F7, 0x1C0530, 0x010C0D, 0x68084B, 0x58EE2C, 0x90AA47, 0x02E774, 0x24D6BD, 0xA67DF7, 0x72486E, 0xEF169F, 0xA6948E, 0xF691B4, 0x5153D1, 0xF20ACF, 0x339820, 0x7E4BF5, 0x6863B2, 0x5F3EDD, 0x035D40, 0x7F8985, 0x295255, 0xC06437, 0x10D86D, 0x324832, 0x754C5B, 0xD4714E, 0x6E5445, 0xC1090B, 0x69F52A, 0xD56614, 0x9D0727, 0x50045D, 0xDB3BB4, 0xC576EA, 0x17F987, 0x7D6B49, 0xBA271D, 0x296996, 0xACCCC6, 0x5414AD, 0x6AE290, 0x89D988, 0x50722C, 0xBEA404, 0x940777, 0x7030F3, 0x27FC00, 0xA871EA, 0x49C266, 0x3DE064, 0x83DD97, 0x973FA3, 0xFD9443, 0x8C860D, 0xDE4131, 0x9D3992, 0x8C70DD, 0xE7B717, 0x3BDF08, 0x2B3715, 0xA0805C, 0x93805A, 0x921110, 0xD8E80F, 0xAF806C, 0x4BFFDB, 0x0F9038, 0x761859, 0x15A562, 0xBBCB61, 0xB989C7, 0xBD4010, 0x04F2D2, 0x277549, 0xF6B6EB, 0xBB22DB, 0xAA140A, 0x2F2689, 0x768364, 0x333B09, 0x1A940E, 0xAA3A51, 0xC2A31D, 0xAEEDAF, 0x12265C, 0x4DC26D, 0x9C7A2D, 0x9756C0, 0x833F03, 0xF6F009, 0x8C402B, 0x99316D, 0x07B439, 0x15200C, 0x5BC3D8, 0xC492F5, 0x4BADC6, 0xA5CA4E, 0xCD37A7, 0x36A9E6, 0x9492AB, 0x6842DD, 0xDE6319, 0xEF8C76, 0x528B68, 0x37DBFC, 0xABA1AE, 0x3115DF, 0xA1AE00, 0xDAFB0C, 0x664D64, 0xB705ED, 0x306529, 0xBF5657, 0x3AFF47, 0xB9F96A, 0xF3BE75, 0xDF9328, 0x3080AB, 0xF68C66, 0x15CB04, 0x0622FA, 0x1DE4D9, 0xA4B33D, 0x8F1B57, 0x09CD36, 0xE9424E, 0xA4BE13, 0xB52333, 0x1AAAF0, 0xA8654F, 0xA5C1D2, 0x0F3F0B, 0xCD785B, 0x76F923, 0x048B7B, 0x721789, 0x53A6C6, 0xE26E6F, 0x00EBEF, 0x584A9B, 0xB7DAC4, 0xBA66AA, 0xCFCF76, 0x1D02D1, 0x2DF1B1, 0xC1998C, 0x77ADC3, 0xDA4886, 0xA05DF7, 0xF480C6, 0x2FF0AC, 0x9AECDD, 0xBC5C3F, 0x6DDED0, 0x1FC790, 0xB6DB2A, 0x3A25A3, 0x9AAF00, 0x9353AD, 0x0457B6, 0xB42D29, 0x7E804B, 0xA707DA, 0x0EAA76, 0xA1597B, 0x2A1216, 0x2DB7DC, 0xFDE5FA, 0xFEDB89, 0xFDBE89, 0x6C76E4, 0xFCA906, 0x70803E, 0x156E85, 0xFF87FD, 0x073E28, 0x336761, 0x86182A, 0xEABD4D, 0xAFE7B3, 0x6E6D8F, 0x396795, 0x5BBF31, 0x48D784, 0x16DF30, 0x432DC7, 0x356125, 0xCE70C9, 0xB8CB30, 0xFD6CBF, 0xA200A4, 0xE46C05, 0xA0DD5A, 0x476F21, 0xD21262, 0x845CB9, 0x496170, 0xE0566B, 0x015299, 0x375550, 0xB7D51E, 0xC4F133, 0x5F6E13, 0xE4305D, 0xA92E85, 0xC3B21D, 0x3632A1, 0xA4B708, 0xD4B1EA, 0x21F716, 0xE4698F, 0x77FF27, 0x80030C, 0x2D408D, 0xA0CD4F, 0x99A520, 0xD3A2B3, 0x0A5D2F, 0x42F9B4, 0xCBDA11, 0xD0BE7D, 0xC1DB9B, 0xBD17AB, 0x81A2CA, 0x5C6A08, 0x17552E, 0x550027, 0xF0147F, 0x8607E1, 0x640B14, 0x8D4196, 0xDEBE87, 0x2AFDDA, 0xB6256B, 0x34897B, 0xFEF305, 0x9EBFB9, 0x4F6A68, 0xA82A4A, 0x5AC44F, 0xBCF82D, 0x985AD7, 0x95C7F4, 0x8D4D0D, 0xA63A20, 0x5F57A4, 0xB13F14, 0x953880, 0x0120CC, 0x86DD71, 0xB6DEC9, 0xF560BF, 0x11654D, 0x6B0701, 0xACB08C, 0xD0C0B2, 0x485551, 0x0EFB1E, 0xC37295, 0x3B06A3, 0x3540C0, 0x7BDC06, 0xCC45E0, 0xFA294E, 0xC8CAD6, 0x41F3E8, 0xDE647C, 0xD8649B, 0x31BED9, 0xC397A4, 0xD45877, 0xC5E369, 0x13DAF0, 0x3C3ABA, 0x461846, 0x5F7555, 0xF5BDD2, 0xC6926E, 0x5D2EAC, 0xED440E, 0x423E1C, 0x87C461, 0xE9FD29, 0xF3D6E7, 0xCA7C22, 0x35916F, 0xC5E008, 0x8DD7FF, 0xE26A6E, 0xC6FDB0, 0xC10893, 0x745D7C, 0xB2AD6B, 0x9D6ECD, 0x7B723E, 0x6A11C6, 0xA9CFF7, 0xDF7329, 0xBAC9B5, 0x5100B7, 0x0DB2E2, 0x24BA74, 0x607DE5, 0x8AD874, 0x2C150D, 0x0C1881, 0x94667E, 0x162901, 0x767A9F, 0xBEFDFD, 0xEF4556, 0x367ED9, 0x13D9EC, 0xB9BA8B, 0xFC97C4, 0x27A831, 0xC36EF1, 0x36C594, 0x56A8D8, 0xB5A8B4, 0x0ECCCF, 0x2D8912, 0x34576F, 0x89562C, 0xE3CE99, 0xB920D6, 0xAA5E6B, 0x9C2A3E, 0xCC5F11, 0x4A0BFD, 0xFBF4E1, 0x6D3B8E, 0x2C86E2, 0x84D4E9, 0xA9B4FC, 0xD1EEEF, 0xC9352E, 0x61392F, 0x442138, 0xC8D91B, 0x0AFC81, 0x6A4AFB, 0xD81C2F, 0x84B453, 0x8C994E, 0xCC2254, 0xDC552A, 0xD6C6C0, 0x96190B, 0xB8701A, 0x649569, 0x605A26, 0xEE523F, 0x0F117F, 0x11B5F4, 0xF5CBFC, 0x2DBC34, 0xEEBC34, 0xCC5DE8, 0x605EDD, 0x9B8E67, 0xEF3392, 0xB817C9, 0x9B5861, 0xBC57E1, 0xC68351, 0x103ED8, 0x4871DD, 0xDD1C2D, 0xA118AF, 0x462C21, 0xD7F359, 0x987AD9, 0xC0549E, 0xFA864F, 0xFC0656, 0xAE79E5, 0x362289, 0x22AD38, 0xDC9367, 0xAAE855, 0x382682, 0x9BE7CA, 0xA40D51, 0xB13399, 0x0ED7A9, 0x480569, 0xF0B265, 0xA7887F, 0x974C88, 0x36D1F9, 0xB39221, 0x4A827B, 0x21CF98, 0xDC9F40, 0x5547DC, 0x3A74E1, 0x42EB67, 0xDF9DFE, 0x5FD45E, 0xA4677B, 0x7AACBA, 0xA2F655, 0x23882B, 0x55BA41, 0x086E59, 0x862A21, 0x834739, 0xE6E389, 0xD49EE5, 0x40FB49, 0xE956FF, 0xCA0F1C, 0x8A59C5, 0x2BFA94, 0xC5C1D3, 0xCFC50F, 0xAE5ADB, 0x86C547, 0x624385, 0x3B8621, 0x94792C, 0x876110, 0x7B4C2A, 0x1A2C80, 0x12BF43, 0x902688, 0x893C78, 0xE4C4A8, 0x7BDBE5, 0xC23AC4, 0xEAF426, 0x8A67F7, 0xBF920D, 0x2BA365, 0xB1933D, 0x0B7CBD, 0xDC51A4, 0x63DD27, 0xDDE169, 0x19949A, 0x9529A8, 0x28CE68, 0xB4ED09, 0x209F44, 0xCA984E, 0x638270, 0x237C7E, 0x32B90F, 0x8EF5A7, 0xE75614, 0x08F121, 0x2A9DB5, 0x4D7E6F, 0x5119A5, 0xABF9B5, 0xD6DF82, 0x61DD96, 0x023616, 0x9F3AC4, 0xA1A283, 0x6DED72, 0x7A8D39, 0xA9B882, 0x5C326B, 0x5B2746, 0xED3400, 0x7700D2, 0x55F4FC, 0x4D5901, 0x8071E0, }; static const double crt_PIo2[8] = { 1.57079625129699707031e+00, 7.54978941586159635335e-08, 5.39030252995776476554e-15, 3.28200341580791294123e-22, 1.27065575308067607349e-29, 1.22933308981111328932e-36, 2.73370053816464559624e-44, 2.16741683877804819444e-51, }; static const double crt_exp2ft[16] = { 0x1.6a09e667f3bcdp-1, 0x1.7a11473eb0187p-1, 0x1.8ace5422aa0dbp-1, 0x1.9c49182a3f090p-1, 0x1.ae89f995ad3adp-1, 0x1.c199bdd85529cp-1, 0x1.d5818dcfba487p-1, 0x1.ea4afa2a490dap-1, 0x1.0000000000000p+0, 0x1.0b5586cf9890fp+0, 0x1.172b83c7d517bp+0, 0x1.2387a6e756238p+0, 0x1.306fe0a31b715p+0, 0x1.3dea64c123422p+0, 0x1.4bfdad5362a27p+0, 0x1.5ab07dd485429p+0, }; static const double crt_exp2dt[512] = { 0x1.6a09e667f3d5dp-1, 0x1.9880p-44, 0x1.6b052fa751744p-1, 0x1.8000p-50, 0x1.6c012750bd9fep-1, -0x1.8780p-45, 0x1.6cfdcddd476bfp-1, 0x1.ec00p-46, 0x1.6dfb23c651a29p-1, -0x1.8000p-50, 0x1.6ef9298593ae3p-1, -0x1.c000p-52, 0x1.6ff7df9519386p-1, -0x1.fd80p-45, 0x1.70f7466f42da3p-1, -0x1.c880p-45, 0x1.71f75e8ec5fc3p-1, 0x1.3c00p-46, 0x1.72f8286eacf05p-1, -0x1.8300p-44, 0x1.73f9a48a58152p-1, -0x1.0c00p-47, 0x1.74fbd35d7ccfcp-1, 0x1.f880p-45, 0x1.75feb564267f1p-1, 0x1.3e00p-47, 0x1.77024b1ab6d48p-1, -0x1.7d00p-45, 0x1.780694fde5d38p-1, -0x1.d000p-50, 0x1.790b938ac1d00p-1, 0x1.3000p-49, 0x1.7a11473eb0178p-1, -0x1.d000p-49, 0x1.7b17b0976d060p-1, 0x1.0400p-45, 0x1.7c1ed0130c133p-1, 0x1.0000p-53, 0x1.7d26a62ff8636p-1, -0x1.6900p-45, 0x1.7e2f336cf4e3bp-1, -0x1.2e00p-47, 0x1.7f3878491c3e8p-1, -0x1.4580p-45, 0x1.80427543e1b4ep-1, 0x1.3000p-44, 0x1.814d2add1071ap-1, 0x1.f000p-47, 0x1.82589994ccd7ep-1, -0x1.1c00p-45, 0x1.8364c1eb942d0p-1, 0x1.9d00p-45, 0x1.8471a4623cab5p-1, 0x1.7100p-43, 0x1.857f4179f5bbcp-1, 0x1.2600p-45, 0x1.868d99b4491afp-1, -0x1.2c40p-44, 0x1.879cad931a395p-1, -0x1.3000p-45, 0x1.88ac7d98a65b8p-1, -0x1.a800p-45, 0x1.89bd0a4785800p-1, -0x1.d000p-49, 0x1.8ace5422aa223p-1, 0x1.3280p-44, 0x1.8be05bad619fap-1, 0x1.2b40p-43, 0x1.8cf3216b54383p-1, -0x1.ed00p-45, 0x1.8e06a5e08664cp-1, -0x1.0500p-45, 0x1.8f1ae99157807p-1, 0x1.8280p-45, 0x1.902fed0282c0ep-1, -0x1.cb00p-46, 0x1.9145b0b91ff96p-1, -0x1.5e00p-47, 0x1.925c353aa2ff9p-1, 0x1.5400p-48, 0x1.93737b0cdc64ap-1, 0x1.7200p-46, 0x1.948b82b5f98aep-1, -0x1.9000p-47, 0x1.95a44cbc852cbp-1, 0x1.5680p-45, 0x1.96bdd9a766f21p-1, -0x1.6d00p-44, 0x1.97d829fde4e2ap-1, -0x1.1000p-47, 0x1.98f33e47a23a3p-1, 0x1.d000p-45, 0x1.9a0f170ca0604p-1, -0x1.8a40p-44, 0x1.9b2bb4d53ff89p-1, 0x1.55c0p-44, 0x1.9c49182a3f15bp-1, 0x1.6b80p-45, 0x1.9d674194bb8c5p-1, -0x1.c000p-49, 0x1.9e86319e3238ep-1, 0x1.7d00p-46, 0x1.9fa5e8d07f302p-1, 0x1.6400p-46, 0x1.a0c667b5de54dp-1, -0x1.5000p-48, 0x1.a1e7aed8eb8f6p-1, 0x1.9e00p-47, 0x1.a309bec4a2e27p-1, 0x1.ad80p-45, 0x1.a42c980460a5dp-1, -0x1.af00p-46, 0x1.a5503b23e259bp-1, 0x1.b600p-47, 0x1.a674a8af46213p-1, 0x1.8880p-44, 0x1.a799e1330b3a7p-1, 0x1.1200p-46, 0x1.a8bfe53c12e8dp-1, 0x1.6c00p-47, 0x1.a9e6b5579fcd2p-1, -0x1.9b80p-45, 0x1.ab0e521356fb8p-1, 0x1.b700p-45, 0x1.ac36bbfd3f381p-1, 0x1.9000p-50, 0x1.ad5ff3a3c2780p-1, 0x1.4000p-49, 0x1.ae89f995ad2a3p-1, -0x1.c900p-45, 0x1.afb4ce622f367p-1, 0x1.6500p-46, 0x1.b0e07298db790p-1, 0x1.fd40p-45, 0x1.b20ce6c9a89a9p-1, 0x1.2700p-46, 0x1.b33a2b84f1a4bp-1, 0x1.d470p-43, 0x1.b468415b747e7p-1, -0x1.8380p-44, 0x1.b59728de5593ap-1, 0x1.8000p-54, 0x1.b6c6e29f1c56ap-1, 0x1.ad00p-47, 0x1.b7f76f2fb5e50p-1, 0x1.e800p-50, 0x1.b928cf22749b2p-1, -0x1.4c00p-47, 0x1.ba5b030a10603p-1, -0x1.d700p-47, 0x1.bb8e0b79a6f66p-1, 0x1.d900p-47, 0x1.bcc1e904bc1ffp-1, 0x1.2a00p-47, 0x1.bdf69c3f3a16fp-1, -0x1.f780p-46, 0x1.bf2c25bd71db8p-1, -0x1.0a00p-46, 0x1.c06286141b2e9p-1, -0x1.1400p-46, 0x1.c199bdd8552e0p-1, 0x1.be00p-47, 0x1.c2d1cd9fa64eep-1, -0x1.9400p-47, 0x1.c40ab5fffd02fp-1, -0x1.ed00p-47, 0x1.c544778fafd15p-1, 0x1.9660p-44, 0x1.c67f12e57d0cbp-1, -0x1.a100p-46, 0x1.c7ba88988c1b6p-1, -0x1.8458p-42, 0x1.c8f6d9406e733p-1, -0x1.a480p-46, 0x1.ca3405751c4dfp-1, 0x1.b000p-51, 0x1.cb720dcef9094p-1, 0x1.1400p-47, 0x1.ccb0f2e6d1689p-1, 0x1.0200p-48, 0x1.cdf0b555dc412p-1, 0x1.3600p-48, 0x1.cf3155b5bab3bp-1, -0x1.6900p-47, 0x1.d072d4a0789bcp-1, 0x1.9a00p-47, 0x1.d1b532b08c8fap-1, -0x1.5e00p-46, 0x1.d2f87080d8a85p-1, 0x1.d280p-46, 0x1.d43c8eacaa203p-1, 0x1.1a00p-47, 0x1.d5818dcfba491p-1, 0x1.f000p-50, 0x1.d6c76e862e6a1p-1, -0x1.3a00p-47, 0x1.d80e316c9834ep-1, -0x1.cd80p-47, 0x1.d955d71ff6090p-1, 0x1.4c00p-48, 0x1.da9e603db32aep-1, 0x1.f900p-48, 0x1.dbe7cd63a8325p-1, 0x1.9800p-49, 0x1.dd321f301b445p-1, -0x1.5200p-48, 0x1.de7d5641c05bfp-1, -0x1.d700p-46, 0x1.dfc97337b9aecp-1, -0x1.6140p-46, 0x1.e11676b197d5ep-1, 0x1.b480p-47, 0x1.e264614f5a3e7p-1, 0x1.0ce0p-43, 0x1.e3b333b16ee5cp-1, 0x1.c680p-47, 0x1.e502ee78b3fb4p-1, -0x1.9300p-47, 0x1.e653924676d68p-1, -0x1.5000p-49, 0x1.e7a51fbc74c44p-1, -0x1.7f80p-47, 0x1.e8f7977cdb726p-1, -0x1.3700p-48, 0x1.ea4afa2a490e8p-1, 0x1.5d00p-49, 0x1.eb9f4867ccae4p-1, 0x1.61a0p-46, 0x1.ecf482d8e680dp-1, 0x1.5500p-48, 0x1.ee4aaa2188514p-1, 0x1.6400p-51, 0x1.efa1bee615a13p-1, -0x1.e800p-49, 0x1.f0f9c1cb64106p-1, -0x1.a880p-48, 0x1.f252b376bb963p-1, -0x1.c900p-45, 0x1.f3ac948dd7275p-1, 0x1.a000p-53, 0x1.f50765b6e4524p-1, -0x1.4f00p-48, 0x1.f6632798844fdp-1, 0x1.a800p-51, 0x1.f7bfdad9cbe38p-1, 0x1.abc0p-48, 0x1.f91d802243c82p-1, -0x1.4600p-50, 0x1.fa7c1819e908ep-1, -0x1.b0c0p-47, 0x1.fbdba3692d511p-1, -0x1.0e00p-51, 0x1.fd3c22b8f7194p-1, -0x1.0de8p-46, 0x1.fe9d96b2a23eep-1, 0x1.e430p-49, 0x1.0000000000000p+0, 0x0.0000p+0, 0x1.00b1afa5abcbep+0, -0x1.3400p-52, 0x1.0163da9fb3303p+0, -0x1.2170p-46, 0x1.02168143b0282p+0, 0x1.a400p-52, 0x1.02c9a3e77806cp+0, 0x1.f980p-49, 0x1.037d42e11bbcap+0, -0x1.7400p-51, 0x1.04315e86e7f89p+0, 0x1.8300p-50, 0x1.04e5f72f65467p+0, -0x1.a3f0p-46, 0x1.059b0d315855ap+0, -0x1.2840p-47, 0x1.0650a0e3c1f95p+0, 0x1.1600p-48, 0x1.0706b29ddf71ap+0, 0x1.5240p-46, 0x1.07bd42b72a82dp+0, -0x1.9a00p-49, 0x1.0874518759bd0p+0, 0x1.6400p-49, 0x1.092bdf66607c8p+0, -0x1.0780p-47, 0x1.09e3ecac6f383p+0, -0x1.8000p-54, 0x1.0a9c79b1f3930p+0, 0x1.fa00p-48, 0x1.0b5586cf988fcp+0, -0x1.ac80p-48, 0x1.0c0f145e46c8ap+0, 0x1.9c00p-50, 0x1.0cc922b724816p+0, 0x1.5200p-47, 0x1.0d83b23395dd8p+0, -0x1.ad00p-48, 0x1.0e3ec32d3d1f3p+0, 0x1.bac0p-46, 0x1.0efa55fdfa9a6p+0, -0x1.4e80p-47, 0x1.0fb66affed2f0p+0, -0x1.d300p-47, 0x1.1073028d7234bp+0, 0x1.1500p-48, 0x1.11301d0125b5bp+0, 0x1.c000p-49, 0x1.11edbab5e2af9p+0, 0x1.6bc0p-46, 0x1.12abdc06c31d5p+0, 0x1.8400p-49, 0x1.136a814f2047dp+0, -0x1.ed00p-47, 0x1.1429aaea92de9p+0, 0x1.8e00p-49, 0x1.14e95934f3138p+0, 0x1.b400p-49, 0x1.15a98c8a58e71p+0, 0x1.5300p-47, 0x1.166a45471c3dfp+0, 0x1.3380p-47, 0x1.172b83c7d5211p+0, 0x1.8d40p-45, 0x1.17ed48695bb9fp+0, -0x1.5d00p-47, 0x1.18af9388c8d93p+0, -0x1.c880p-46, 0x1.1972658375d66p+0, 0x1.1f00p-46, 0x1.1a35beb6fcba7p+0, 0x1.0480p-46, 0x1.1af99f81387e3p+0, -0x1.7390p-43, 0x1.1bbe084045d54p+0, 0x1.4e40p-45, 0x1.1c82f95281c43p+0, -0x1.a200p-47, 0x1.1d4873168b9b2p+0, 0x1.3800p-49, 0x1.1e0e75eb44031p+0, 0x1.ac00p-49, 0x1.1ed5022fcd938p+0, 0x1.1900p-47, 0x1.1f9c18438cdf7p+0, -0x1.b780p-46, 0x1.2063b88628d8fp+0, 0x1.d940p-45, 0x1.212be3578a81ep+0, 0x1.8000p-50, 0x1.21f49917ddd41p+0, 0x1.b340p-45, 0x1.22bdda2791323p+0, 0x1.9f80p-46, 0x1.2387a6e7561e7p+0, -0x1.9c80p-46, 0x1.2451ffb821427p+0, 0x1.2300p-47, 0x1.251ce4fb2a602p+0, -0x1.3480p-46, 0x1.25e85711eceb0p+0, 0x1.2700p-46, 0x1.26b4565e27d16p+0, 0x1.1d00p-46, 0x1.2780e341de00fp+0, 0x1.1ee0p-44, 0x1.284dfe1f5633ep+0, -0x1.4c00p-46, 0x1.291ba7591bb30p+0, -0x1.3d80p-46, 0x1.29e9df51fdf09p+0, 0x1.8b00p-47, 0x1.2ab8a66d10e9bp+0, -0x1.27c0p-45, 0x1.2b87fd0dada3ap+0, 0x1.a340p-45, 0x1.2c57e39771af9p+0, -0x1.0800p-46, 0x1.2d285a6e402d9p+0, -0x1.ed00p-47, 0x1.2df961f641579p+0, -0x1.4200p-48, 0x1.2ecafa93e2ecfp+0, -0x1.4980p-45, 0x1.2f9d24abd8822p+0, -0x1.6300p-46, 0x1.306fe0a31b625p+0, -0x1.2360p-44, 0x1.31432edeea50bp+0, -0x1.0df8p-40, 0x1.32170fc4cd7b8p+0, -0x1.2480p-45, 0x1.32eb83ba8e9a2p+0, -0x1.5980p-45, 0x1.33c08b2641766p+0, 0x1.ed00p-46, 0x1.3496266e3fa27p+0, -0x1.c000p-50, 0x1.356c55f929f0fp+0, -0x1.0d80p-44, 0x1.36431a2de88b9p+0, 0x1.2c80p-45, 0x1.371a7373aaa39p+0, 0x1.0600p-45, 0x1.37f26231e74fep+0, -0x1.6600p-46, 0x1.38cae6d05d838p+0, -0x1.ae00p-47, 0x1.39a401b713ec3p+0, -0x1.4720p-43, 0x1.3a7db34e5a020p+0, 0x1.8200p-47, 0x1.3b57fbfec6e95p+0, 0x1.e800p-44, 0x1.3c32dc313a8f2p+0, 0x1.f800p-49, 0x1.3d0e544ede122p+0, -0x1.7a00p-46, 0x1.3dea64c1234bbp+0, 0x1.6300p-45, 0x1.3ec70df1c4eccp+0, -0x1.8a60p-43, 0x1.3fa4504ac7e8cp+0, -0x1.cdc0p-44, 0x1.40822c367a0bbp+0, 0x1.5b80p-45, 0x1.4160a21f72e95p+0, 0x1.ec00p-46, 0x1.423fb27094646p+0, -0x1.3600p-46, 0x1.431f5d950a920p+0, 0x1.3980p-45, 0x1.43ffa3f84b9ebp+0, 0x1.a000p-48, 0x1.44e0860618919p+0, -0x1.6c00p-48, 0x1.45c2042a7d201p+0, -0x1.bc00p-47, 0x1.46a41ed1d0016p+0, -0x1.2800p-46, 0x1.4786d668b3326p+0, 0x1.0e00p-44, 0x1.486a2b5c13c00p+0, -0x1.d400p-45, 0x1.494e1e192af04p+0, 0x1.c200p-47, 0x1.4a32af0d7d372p+0, -0x1.e500p-46, 0x1.4b17dea6db801p+0, 0x1.7800p-47, 0x1.4bfdad53629e1p+0, -0x1.3800p-46, 0x1.4ce41b817c132p+0, 0x1.0800p-47, 0x1.4dcb299fddddbp+0, 0x1.c700p-45, 0x1.4eb2d81d8ab96p+0, -0x1.ce00p-46, 0x1.4f9b2769d2d02p+0, 0x1.9200p-46, 0x1.508417f4531c1p+0, -0x1.8c00p-47, 0x1.516daa2cf662ap+0, -0x1.a000p-48, 0x1.5257de83f51eap+0, 0x1.a080p-43, 0x1.5342b569d4edap+0, -0x1.6d80p-45, 0x1.542e2f4f6ac1ap+0, -0x1.2440p-44, 0x1.551a4ca5d94dbp+0, 0x1.83c0p-43, 0x1.56070dde9116bp+0, 0x1.4b00p-45, 0x1.56f4736b529dep+0, 0x1.15a0p-43, 0x1.57e27dbe2c40ep+0, -0x1.9e00p-45, 0x1.58d12d497c76fp+0, -0x1.3080p-45, 0x1.59c0827ff0b4cp+0, 0x1.dec0p-43, 0x1.5ab07dd485427p+0, -0x1.4000p-51, 0x1.5ba11fba87af4p+0, 0x1.0080p-44, 0x1.5c9268a59460bp+0, -0x1.6c80p-45, 0x1.5d84590998e3fp+0, 0x1.69a0p-43, 0x1.5e76f15ad20e1p+0, -0x1.b400p-46, 0x1.5f6a320dcebcap+0, 0x1.7700p-46, 0x1.605e1b976dcb8p+0, 0x1.6f80p-45, 0x1.6152ae6cdf715p+0, 0x1.1000p-47, 0x1.6247eb03a5531p+0, -0x1.5d00p-46, 0x1.633dd1d1929b5p+0, -0x1.2d00p-46, 0x1.6434634ccc313p+0, -0x1.a800p-49, 0x1.652b9febc8efap+0, -0x1.8600p-45, 0x1.6623882553397p+0, 0x1.1fe0p-40, 0x1.671c1c708328ep+0, -0x1.7200p-44, 0x1.68155d44ca97ep+0, 0x1.6800p-49, 0x1.690f4b19e9471p+0, -0x1.9780p-45, }; static const struct { double invc, logc; } crt_log_tab[128] = { { 0x1.734f0c3e0de9fp+0, -0x1.7cc7f79e69000p-2 }, { 0x1.713786a2ce91fp+0, -0x1.76feec20d0000p-2 }, { 0x1.6f26008fab5a0p+0, -0x1.713e31351e000p-2 }, { 0x1.6d1a61f138c7dp+0, -0x1.6b85b38287800p-2 }, { 0x1.6b1490bc5b4d1p+0, -0x1.65d5590807800p-2 }, { 0x1.69147332f0cbap+0, -0x1.602d076180000p-2 }, { 0x1.6719f18224223p+0, -0x1.5a8ca86909000p-2 }, { 0x1.6524f99a51ed9p+0, -0x1.54f4356035000p-2 }, { 0x1.63356aa8f24c4p+0, -0x1.4f637c36b4000p-2 }, { 0x1.614b36b9ddc14p+0, -0x1.49da7fda85000p-2 }, { 0x1.5f66452c65c4cp+0, -0x1.445923989a800p-2 }, { 0x1.5d867b5912c4fp+0, -0x1.3edf439b0b800p-2 }, { 0x1.5babccb5b90dep+0, -0x1.396ce448f7000p-2 }, { 0x1.59d61f2d91a78p+0, -0x1.3401e17bda000p-2 }, { 0x1.5805612465687p+0, -0x1.2e9e2ef468000p-2 }, { 0x1.56397cee76bd3p+0, -0x1.2941b3830e000p-2 }, { 0x1.54725e2a77f93p+0, -0x1.23ec58cda8800p-2 }, { 0x1.52aff42064583p+0, -0x1.1e9e129279000p-2 }, { 0x1.50f22dbb2bddfp+0, -0x1.1956d2b48f800p-2 }, { 0x1.4f38f4734ded7p+0, -0x1.141679ab9f800p-2 }, { 0x1.4d843cfde2840p+0, -0x1.0edd094ef9800p-2 }, { 0x1.4bd3ec078a3c8p+0, -0x1.09aa518db1000p-2 }, { 0x1.4a27fc3e0258ap+0, -0x1.047e65263b800p-2 }, { 0x1.4880524d48434p+0, -0x1.feb224586f000p-3 }, { 0x1.46dce1b192d0bp+0, -0x1.f474a7517b000p-3 }, { 0x1.453d9d3391854p+0, -0x1.ea4443d103000p-3 }, { 0x1.43a2744b4845ap+0, -0x1.e020d44e9b000p-3 }, { 0x1.420b54115f8fbp+0, -0x1.d60a22977f000p-3 }, { 0x1.40782da3ef4b1p+0, -0x1.cc00104959000p-3 }, { 0x1.3ee8f5d57fe8fp+0, -0x1.c202956891000p-3 }, { 0x1.3d5d9a00b4ce9p+0, -0x1.b81178d811000p-3 }, { 0x1.3bd60c010c12bp+0, -0x1.ae2c9ccd3d000p-3 }, { 0x1.3a5242b75dab8p+0, -0x1.a45402e129000p-3 }, { 0x1.38d22cd9fd002p+0, -0x1.9a877681df000p-3 }, { 0x1.3755bc5847a1cp+0, -0x1.90c6d69483000p-3 }, { 0x1.35dce49ad36e2p+0, -0x1.87120a645c000p-3 }, { 0x1.34679984dd440p+0, -0x1.7d68fb4143000p-3 }, { 0x1.32f5cceffcb24p+0, -0x1.73cb83c627000p-3 }, { 0x1.3187775a10d49p+0, -0x1.6a39a9b376000p-3 }, { 0x1.301c8373e3990p+0, -0x1.60b3154b7a000p-3 }, { 0x1.2eb4ebb95f841p+0, -0x1.5737d76243000p-3 }, { 0x1.2d50a0219a9d1p+0, -0x1.4dc7b8fc23000p-3 }, { 0x1.2bef9a8b7fd2ap+0, -0x1.4462c51d20000p-3 }, { 0x1.2a91c7a0c1babp+0, -0x1.3b08abc830000p-3 }, { 0x1.293726014b530p+0, -0x1.31b996b490000p-3 }, { 0x1.27dfa5757a1f5p+0, -0x1.2875490a44000p-3 }, { 0x1.268b39b1d3bbfp+0, -0x1.1f3b9f879a000p-3 }, { 0x1.2539d838ff5bdp+0, -0x1.160c8252ca000p-3 }, { 0x1.23eb7aac9083bp+0, -0x1.0ce7f57f72000p-3 }, { 0x1.22a012ba940b6p+0, -0x1.03cdc49fea000p-3 }, { 0x1.2157996cc4132p+0, -0x1.f57bdbc4b8000p-4 }, { 0x1.201201dd2fc9bp+0, -0x1.e370896404000p-4 }, { 0x1.1ecf4494d480bp+0, -0x1.d17983ef94000p-4 }, { 0x1.1d8f5528f6569p+0, -0x1.bf9674ed8a000p-4 }, { 0x1.1c52311577e7cp+0, -0x1.adc79202f6000p-4 }, { 0x1.1b17c74cb26e9p+0, -0x1.9c0c3e7288000p-4 }, { 0x1.19e010c2c1ab6p+0, -0x1.8a646b372c000p-4 }, { 0x1.18ab07bb670bdp+0, -0x1.78d01b3ac0000p-4 }, { 0x1.1778a25efbcb6p+0, -0x1.674f145380000p-4 }, { 0x1.1648d354c31dap+0, -0x1.55e0e6d878000p-4 }, { 0x1.151b990275fddp+0, -0x1.4485cdea1e000p-4 }, { 0x1.13f0ea432d24cp+0, -0x1.333d94d6aa000p-4 }, { 0x1.12c8b7210f9dap+0, -0x1.22079f8c56000p-4 }, { 0x1.11a3028ecb531p+0, -0x1.10e4698622000p-4 }, { 0x1.107fbda8434afp+0, -0x1.ffa6c6ad20000p-5 }, { 0x1.0f5ee0f4e6bb3p+0, -0x1.dda8d4a774000p-5 }, { 0x1.0e4065d2a9fcep+0, -0x1.bbcece4850000p-5 }, { 0x1.0d244632ca521p+0, -0x1.9a1894012c000p-5 }, { 0x1.0c0a77ce2981ap+0, -0x1.788583302c000p-5 }, { 0x1.0af2f83c636d1p+0, -0x1.5715e67d68000p-5 }, { 0x1.09ddb98a01339p+0, -0x1.35c8a49658000p-5 }, { 0x1.08cabaf52e7dfp+0, -0x1.149e364154000p-5 }, { 0x1.07b9f2f4e28fbp+0, -0x1.e72c082eb8000p-6 }, { 0x1.06ab58c358f19p+0, -0x1.a55f152528000p-6 }, { 0x1.059eea5ecf92cp+0, -0x1.63d62cf818000p-6 }, { 0x1.04949cdd12c90p+0, -0x1.228fb8caa0000p-6 }, { 0x1.038c6c6f0ada9p+0, -0x1.c317b20f90000p-7 }, { 0x1.02865137932a9p+0, -0x1.419355daa0000p-7 }, { 0x1.0182427ea7348p+0, -0x1.81203c2ec0000p-8 }, { 0x1.008040614b195p+0, -0x1.0040979240000p-9 }, { 0x1.fe01ff726fa1ap-1, 0x1.feff384900000p-9 }, { 0x1.fa11cc261ea74p-1, 0x1.7dc41353d0000p-7 }, { 0x1.f6310b081992ep-1, 0x1.3cea3c4c28000p-6 }, { 0x1.f25f63ceeadcdp-1, 0x1.b9fc114890000p-6 }, { 0x1.ee9c8039113e7p-1, 0x1.1b0d8ce110000p-5 }, { 0x1.eae8078cbb1abp-1, 0x1.58a5bd001c000p-5 }, { 0x1.e741aa29d0c9bp-1, 0x1.95c8340d88000p-5 }, { 0x1.e3a91830a99b5p-1, 0x1.d276aef578000p-5 }, { 0x1.e01e009609a56p-1, 0x1.07598e598c000p-4 }, { 0x1.dca01e577bb98p-1, 0x1.253f5e30d2000p-4 }, { 0x1.d92f20b7c9103p-1, 0x1.42edd8b380000p-4 }, { 0x1.d5cac66fb5ccep-1, 0x1.606598757c000p-4 }, { 0x1.d272caa5ede9dp-1, 0x1.7da76356a0000p-4 }, { 0x1.cf26e3e6b2ccdp-1, 0x1.9ab434e1c6000p-4 }, { 0x1.cbe6da2a77902p-1, 0x1.b78c7bb0d6000p-4 }, { 0x1.c8b266d37086dp-1, 0x1.d431332e72000p-4 }, { 0x1.c5894bd5d5804p-1, 0x1.f0a3171de6000p-4 }, { 0x1.c26b533bb9f8cp-1, 0x1.067152b914000p-3 }, { 0x1.bf583eeece73fp-1, 0x1.147858292b000p-3 }, { 0x1.bc4fd75db96c1p-1, 0x1.2266ecdca3000p-3 }, { 0x1.b951e0c864a28p-1, 0x1.303d7a6c55000p-3 }, { 0x1.b65e2c5ef3e2cp-1, 0x1.3dfc33c331000p-3 }, { 0x1.b374867c9888bp-1, 0x1.4ba366b7a8000p-3 }, { 0x1.b094b211d304ap-1, 0x1.5933928d1f000p-3 }, { 0x1.adbe885f2ef7ep-1, 0x1.66acd2418f000p-3 }, { 0x1.aaf1d31603da2p-1, 0x1.740f8ec669000p-3 }, { 0x1.a82e63fd358a7p-1, 0x1.815c0f51af000p-3 }, { 0x1.a5740ef09738bp-1, 0x1.8e92954f68000p-3 }, { 0x1.a2c2a90ab4b27p-1, 0x1.9bb3602f84000p-3 }, { 0x1.a01a01393f2d1p-1, 0x1.a8bed1c2c0000p-3 }, { 0x1.9d79f24db3c1bp-1, 0x1.b5b515c01d000p-3 }, { 0x1.9ae2505c7b190p-1, 0x1.c2967ccbcc000p-3 }, { 0x1.9852ef297ce2fp-1, 0x1.cf635d5486000p-3 }, { 0x1.95cbaeea44b75p-1, 0x1.dc1bd3446c000p-3 }, { 0x1.934c69de74838p-1, 0x1.e8c01b8cfe000p-3 }, { 0x1.90d4f2f6752e6p-1, 0x1.f5509c0179000p-3 }, { 0x1.8e6528effd79dp-1, 0x1.00e6c121fb800p-2 }, { 0x1.8bfce9fcc007cp-1, 0x1.071b80e93d000p-2 }, { 0x1.899c0dabec30ep-1, 0x1.0d46b9e867000p-2 }, { 0x1.87427aa2317fbp-1, 0x1.13687334bd000p-2 }, { 0x1.84f00acb39a08p-1, 0x1.1980d67234800p-2 }, { 0x1.82a49e8653e55p-1, 0x1.1f8ffe0cc8000p-2 }, { 0x1.8060195f40260p-1, 0x1.2595fd7636800p-2 }, { 0x1.7e22563e0a329p-1, 0x1.2b9300914a800p-2 }, { 0x1.7beb377dcb5adp-1, 0x1.3187210436000p-2 }, { 0x1.79baa679725c2p-1, 0x1.377266dec1800p-2 }, { 0x1.77907f2170657p-1, 0x1.3d54ffbaf3000p-2 }, { 0x1.756cadbd6130cp-1, 0x1.432eee32fe000p-2 }, }; static const struct { double chi, clo; } crt_log_tab2[128] = { { 0x1.61000014fb66bp-1, 0x1.e026c91425b3cp-56 }, { 0x1.63000034db495p-1, 0x1.dbfea48005d41p-55 }, { 0x1.650000d94d478p-1, 0x1.e7fa786d6a5b7p-55 }, { 0x1.67000074e6fadp-1, 0x1.1fcea6b54254cp-57 }, { 0x1.68ffffedf0faep-1, -0x1.c7e274c590efdp-56 }, { 0x1.6b0000763c5bcp-1, -0x1.ac16848dcda01p-55 }, { 0x1.6d0001e5cc1f6p-1, 0x1.33f1c9d499311p-55 }, { 0x1.6efffeb05f63ep-1, -0x1.e80041ae22d53p-56 }, { 0x1.710000e86978p-1, 0x1.bff6671097952p-56 }, { 0x1.72ffffc67e912p-1, 0x1.c00e226bd8724p-55 }, { 0x1.74fffdf81116ap-1, -0x1.e02916ef101d2p-57 }, { 0x1.770000f679c9p-1, -0x1.7fc71cd549c74p-57 }, { 0x1.78ffffa7ec835p-1, 0x1.1bec19ef50483p-55 }, { 0x1.7affffe20c2e6p-1, -0x1.07e1729cc6465p-56 }, { 0x1.7cfffed3fc9p-1, -0x1.08072087b8b1cp-55 }, { 0x1.7efffe9261a76p-1, 0x1.dc0286d9df9aep-55 }, { 0x1.81000049ca3e8p-1, 0x1.97fd251e54c33p-55 }, { 0x1.8300017932c8fp-1, -0x1.afee9b630f381p-55 }, { 0x1.850000633739cp-1, 0x1.9bfbf6b6535bcp-55 }, { 0x1.87000204289c6p-1, -0x1.bbf65f3117b75p-55 }, { 0x1.88fffebf57904p-1, -0x1.9006ea23dcb57p-55 }, { 0x1.8b00022bc04dfp-1, -0x1.d00df38e04b0ap-56 }, { 0x1.8cfffe50c1b8ap-1, -0x1.8007146ff9f05p-55 }, { 0x1.8effffc918e43p-1, 0x1.3817bd07a7038p-55 }, { 0x1.910001efa5fc7p-1, 0x1.93e9176dfb403p-55 }, { 0x1.9300013467bb9p-1, 0x1.f804e4b980276p-56 }, { 0x1.94fffe6ee076fp-1, -0x1.f7ef0d9ff622ep-55 }, { 0x1.96fffde3c12d1p-1, -0x1.082aa962638bap-56 }, { 0x1.98ffff4458a0dp-1, -0x1.7801b9164a8efp-55 }, { 0x1.9afffdd982e3ep-1, -0x1.740e08a5a9337p-55 }, { 0x1.9cfffed49fb66p-1, 0x1.fce08c19bep-60 }, { 0x1.9f00020f19c51p-1, -0x1.a3faa27885b0ap-55 }, { 0x1.a10001145b006p-1, 0x1.4ff489958da56p-56 }, { 0x1.a300007bbf6fap-1, 0x1.cbeab8a2b6d18p-55 }, { 0x1.a500010971d79p-1, 0x1.8fecadd78793p-55 }, { 0x1.a70001df52e48p-1, -0x1.f41763dd8abdbp-55 }, { 0x1.a90001c593352p-1, -0x1.ebf0284c27612p-55 }, { 0x1.ab0002a4f3e4bp-1, -0x1.9fd043cff3f5fp-57 }, { 0x1.acfffd7ae1ed1p-1, -0x1.23ee7129070b4p-55 }, { 0x1.aefffee510478p-1, 0x1.a063ee00edea3p-57 }, { 0x1.b0fffdb650d5bp-1, 0x1.a06c8381f0ab9p-58 }, { 0x1.b2ffffeaaca57p-1, -0x1.9011e74233c1dp-56 }, { 0x1.b4fffd995badcp-1, -0x1.9ff1068862a9fp-56 }, { 0x1.b7000249e659cp-1, 0x1.aff45d0864f3ep-55 }, { 0x1.b8ffff987164p-1, 0x1.cfe7796c2c3f9p-56 }, { 0x1.bafffd204cb4fp-1, -0x1.3ff27eef22bc4p-57 }, { 0x1.bcfffd2415c45p-1, -0x1.cffb7ee3bea21p-57 }, { 0x1.beffff86309dfp-1, -0x1.14103972e0b5cp-55 }, { 0x1.c0fffe1b57653p-1, 0x1.bc16494b76a19p-55 }, { 0x1.c2ffff1fa57e3p-1, -0x1.4feef8d30c6edp-57 }, { 0x1.c4fffdcbfe424p-1, -0x1.43f68bcec4775p-55 }, { 0x1.c6fffed54b9f7p-1, 0x1.47ea3f053e0ecp-55 }, { 0x1.c8fffeb998fd5p-1, 0x1.383068df992f1p-56 }, { 0x1.cb0002125219ap-1, -0x1.8fd8e64180e04p-57 }, { 0x1.ccfffdd94469cp-1, 0x1.e7ebe1cc7ea72p-55 }, { 0x1.cefffeafdc476p-1, 0x1.ebe39ad9f88fep-55 }, { 0x1.d1000169af82bp-1, 0x1.57d91a8b95a71p-56 }, { 0x1.d30000d0ff71dp-1, 0x1.9c1906970c7dap-55 }, { 0x1.d4fffea790fc4p-1, -0x1.80e37c558fe0cp-58 }, { 0x1.d70002edc87e5p-1, -0x1.f80d64dc10f44p-56 }, { 0x1.d900021dc82aap-1, -0x1.47c8f94fd5c5cp-56 }, { 0x1.dafffd86b0283p-1, 0x1.c7f1dc521617ep-55 }, { 0x1.dd000296c4739p-1, 0x1.8019eb2ffb153p-55 }, { 0x1.defffe54490f5p-1, 0x1.e00d2c652cc89p-57 }, { 0x1.e0fffcdabf694p-1, -0x1.f8340202d69d2p-56 }, { 0x1.e2fffdb52c8ddp-1, 0x1.b00c1ca1b0864p-56 }, { 0x1.e4ffff24216efp-1, 0x1.2ffa8b094ab51p-56 }, { 0x1.e6fffe88a5e11p-1, -0x1.7f673b1efbe59p-58 }, { 0x1.e9000119eff0dp-1, -0x1.4808d5e0bc801p-55 }, { 0x1.eafffdfa51744p-1, 0x1.80006d54320b5p-56 }, { 0x1.ed0001a127fa1p-1, -0x1.002f860565c92p-58 }, { 0x1.ef00007babcc4p-1, -0x1.540445d35e611p-55 }, { 0x1.f0ffff57a8d02p-1, -0x1.ffb3139ef9105p-59 }, { 0x1.f30001ee58ac7p-1, 0x1.a81acf2731155p-55 }, { 0x1.f4ffff5823494p-1, 0x1.a3f41d4d7c743p-55 }, { 0x1.f6ffffca94c6bp-1, -0x1.202f41c987875p-57 }, { 0x1.f8fffe1f9c441p-1, 0x1.77dd1f477e74bp-56 }, { 0x1.fafffd2e0e37ep-1, -0x1.f01199a7ca331p-57 }, { 0x1.fd0001c77e49ep-1, 0x1.181ee4bceacb1p-56 }, { 0x1.feffff7e0c331p-1, -0x1.e05370170875ap-57 }, { 0x1.00ffff465606ep+0, -0x1.a7ead491c0adap-55 }, { 0x1.02ffff3867a58p+0, -0x1.77f69c3fcb2ep-54 }, { 0x1.04ffffdfc0d17p+0, 0x1.7bffe34cb945bp-54 }, { 0x1.0700003cd4d82p+0, 0x1.20083c0e456cbp-55 }, { 0x1.08ffff9f2cbe8p+0, -0x1.dffdfbe37751ap-57 }, { 0x1.0b000010cda65p+0, -0x1.13f7faee626ebp-54 }, { 0x1.0d00001a4d338p+0, 0x1.07dfa79489ff7p-55 }, { 0x1.0effffadafdfdp+0, -0x1.7040570d66bcp-56 }, { 0x1.110000bbafd96p+0, 0x1.e80d4846d0b62p-55 }, { 0x1.12ffffae5f45dp+0, 0x1.dbffa64fd36efp-54 }, { 0x1.150000dd59ad9p+0, 0x1.a0077701250aep-54 }, { 0x1.170000f21559ap+0, 0x1.dfdf9e2e3deeep-55 }, { 0x1.18ffffc275426p+0, 0x1.10030dc3b7273p-54 }, { 0x1.1b000123d3c59p+0, 0x1.97f7980030188p-54 }, { 0x1.1cffff8299eb7p+0, -0x1.5f932ab9f8c67p-57 }, { 0x1.1effff48ad4p+0, 0x1.37fbf9da75bebp-54 }, { 0x1.210000c8b86a4p+0, 0x1.f806b91fd5b22p-54 }, { 0x1.2300003854303p+0, 0x1.3ffc2eb9fbf33p-54 }, { 0x1.24fffffbcf684p+0, 0x1.601e77e2e2e72p-56 }, { 0x1.26ffff52921d9p+0, 0x1.ffcbb767f0c61p-56 }, { 0x1.2900014933a3cp+0, -0x1.202ca3c02412bp-56 }, { 0x1.2b00014556313p+0, -0x1.2808233f21f02p-54 }, { 0x1.2cfffebfe523bp+0, -0x1.8ff7e384fdcf2p-55 }, { 0x1.2f0000bb8ad96p+0, -0x1.5ff51503041c5p-55 }, { 0x1.30ffffb7ae2afp+0, -0x1.10071885e289dp-55 }, { 0x1.32ffffeac5f7fp+0, -0x1.1ff5d3fb7b715p-54 }, { 0x1.350000ca66756p+0, 0x1.57f82228b82bdp-54 }, { 0x1.3700011fbf721p+0, 0x1.000bac40dd5ccp-55 }, { 0x1.38ffff9592fb9p+0, -0x1.43f9d2db2a751p-54 }, { 0x1.3b00004ddd242p+0, 0x1.57f6b707638e1p-55 }, { 0x1.3cffff5b2c957p+0, 0x1.a023a10bf1231p-56 }, { 0x1.3efffeab0b418p+0, 0x1.87f6d66b152bp-54 }, { 0x1.410001532aff4p+0, 0x1.7f8375f198524p-57 }, { 0x1.4300017478b29p+0, 0x1.301e672dc5143p-55 }, { 0x1.44fffe795b463p+0, 0x1.9ff69b8b2895ap-55 }, { 0x1.46fffe80475ep+0, -0x1.5c0b19bc2f254p-54 }, { 0x1.48fffef6fc1e7p+0, 0x1.b4009f23a2a72p-54 }, { 0x1.4afffe5bea704p+0, -0x1.4ffb7bf0d7d45p-54 }, { 0x1.4d000171027dep+0, -0x1.9c06471dc6a3dp-54 }, { 0x1.4f0000ff03ee2p+0, 0x1.77f890b85531cp-54 }, { 0x1.5100012dc4bd1p+0, 0x1.004657166a436p-57 }, { 0x1.530001605277ap+0, -0x1.6bfcece233209p-54 }, { 0x1.54fffecdb704cp+0, -0x1.902720505a1d7p-55 }, { 0x1.56fffef5f54a9p+0, 0x1.bbfe60ec96412p-54 }, { 0x1.5900017e61012p+0, 0x1.87ec581afef9p-55 }, { 0x1.5b00003c93e92p+0, -0x1.f41080abf0ccp-54 }, { 0x1.5d0001d4919bcp+0, -0x1.8812afb254729p-54 }, { 0x1.5efffe7b87a89p+0, -0x1.47eb780ed6904p-54 }, }; // clang-format on // lib/compiler_rt/rem_pio2_large.zig `rem_pio2_large`. static const int32_t crt_init_jk[4] = { 3, 4, 4, 6 }; static int32_t crtRemPio2Large( const double* x, double* y, int32_t e0, int32_t nx, uint32_t prec) { int32_t jz, jx, jv, jp, jk, carry, n, i, j, k, m, q0, ih; int32_t iq[20]; double z, fw; double f[20], fq[20], q[20]; // initialize jk jk = crt_init_jk[prec]; assert(jk >= 3); // as every `init_jk` entry jp = jk; // determine jx,jv,q0, note that 3>q0 jx = nx - 1; jv = crtDivFloor(e0 - 3, 24); if (jv < 0) jv = 0; q0 = e0 - 24 * (jv + 1); // set up f[0] to f[jx+jk] where f[jx+jk] = ipio2[jv+jk] j = jv - jx; m = jx + jk; for (i = 0; i <= m; i++, j++) f[i] = j < 0 ? 0.0 : (double)crt_ipio2[j]; // compute q[0],q[1],...q[jk] for (i = 0; i <= jk; i++) { for (j = 0, fw = 0; j <= jx; j++) fw += x[j] * f[jx + i - j]; q[i] = fw; } jz = jk; recompute: // distill q[] into iq[] reversingly for (i = 0, j = jz, z = q[jz]; j > 0; i++, j--) { fw = (double)(int32_t)(0x1p-24 * z); iq[i] = (int32_t)(z - 0x1p24 * fw); z = q[j - 1] + fw; } // compute n z = crtScalbnF64(z, q0); // actual value of z z -= 8.0 * floor(z * 0.125); // trim off integer >= 8 n = (int32_t)z; z -= (double)n; ih = 0; if (q0 > 0) { // need iq[jz-1] to determine n i = iq[jz - 1] >> (24 - q0); n += i; iq[jz - 1] -= i << (24 - q0); ih = iq[jz - 1] >> (23 - q0); } else if (q0 == 0) { ih = iq[jz - 1] >> 23; } else if (z >= 0.5) { ih = 2; } if (ih > 0) { // q > 0.5 n += 1; carry = 0; for (i = 0; i < jz; i++) { // compute 1-q j = iq[i]; if (carry == 0) { if (j != 0) { carry = 1; iq[i] = 0x1000000 - j; } } else { iq[i] = 0xffffff - j; } } if (q0 > 0) { // rare case: chance is 1 in 12 switch (q0) { case 1: iq[jz - 1] &= 0x7fffff; break; default: // 2 iq[jz - 1] &= 0x3fffff; break; } } if (ih == 2) { z = 1.0 - z; if (carry != 0) z -= crtScalbnF64(1.0, q0); } } // check if recomputation is needed if (f64IsZero(z)) { // z == 0 j = 0; for (i = jz - 1; i >= jk; i--) j |= iq[i]; if (j == 0) { // need recomputation for (k = 1; iq[jk - k] == 0; k++) { } for (i = jz + 1; i <= jz + k; i++) { // add q[jz+1] to q[jz+k] f[jx + i] = (double)crt_ipio2[jv + i]; for (j = 0, fw = 0; j <= jx; j++) fw += x[j] * f[jx + i - j]; q[i] = fw; } jz += k; goto recompute; } } // chop off zero terms // cppcheck-suppress duplicateCondition ; two z == 0 blocks as upstream if (f64IsZero(z)) { // z == 0 jz -= 1; q0 -= 24; while (iq[jz] == 0) { jz -= 1; q0 -= 24; } } else { // break z into 24-bit if necessary z = crtScalbnF64(z, -q0); if (z >= 0x1p24) { fw = (double)(int32_t)(0x1p-24 * z); iq[jz] = (int32_t)(z - 0x1p24 * fw); jz += 1; q0 += 24; iq[jz] = (int32_t)fw; } else { iq[jz] = (int32_t)z; } } // A nonzero x leaves a nonzero chunk, so fq[0..jz] is filled below. assert(jz >= 0); // convert integer "bit" chunk to floating-point value fw = crtScalbnF64(1.0, q0); for (i = jz; i >= 0; i--) { q[i] = fw * (double)iq[i]; fw *= 0x1p-24; } // compute PIo2[0,...,jp]*q[jz,...,0] for (i = jz; i >= 0; i--) { for (fw = 0, k = 0; k <= jp && k <= jz - i; k++) fw += crt_PIo2[k] * q[i + k]; fq[jz - i] = fw; } // compress fq[] into y[] switch (prec) { case 0: fw = 0.0; for (i = jz; i >= 0; i--) fw += fq[i]; y[0] = ih == 0 ? fw : -fw; break; case 1: case 2: fw = 0.0; for (i = jz; i >= 0; i--) fw += fq[i]; y[0] = ih == 0 ? fw : -fw; fw = fq[0] - fw; for (i = 1; i <= jz; i++) fw += fq[i]; y[1] = ih == 0 ? fw : -fw; break; default: // 3, painful for (i = jz; i > 0; i--) { fw = fq[i - 1] + fq[i]; fq[i] += fq[i - 1] - fw; fq[i - 1] = fw; } for (i = jz; i > 1; i--) { fw = fq[i - 1] + fq[i]; fq[i] += fq[i - 1] - fw; fq[i - 1] = fw; } for (fw = 0, i = jz; i >= 2; i--) fw += fq[i]; if (ih == 0) { y[0] = fq[0]; y[1] = fq[1]; y[2] = fw; } else { y[0] = -fq[0]; y[1] = -fq[1]; y[2] = -fw; } break; } return n & 7; } // lib/compiler_rt/rem_pio2f.zig `rem_pio2f`. static int32_t crtRemPio2f(float x, double* y) { const double toint = 0x1.8p52; // 1.5 / math.floatEps(f64) const double pio4 = 0x1.921fb6p-1; const double invpio2 = 6.36619772367581382433e-01; // 0x3FE45F30, 0x6DC9C883 const double pio2_1 = 1.57079631090164184570e+00; // 0x3FF921FB, 0x50000000 const double pio2_1t = 1.58932547735281966916e-08; // 0x3E5110b4, 0x611A6263 double tx[1], ty[1]; uint32_t ix, e0, ui; int32_t n; bool sign; ui = crtF32Bits(x); ix = ui & 0x7fffffff; if (ix < 0x4dc90fdb) { // |x| ~< 2^28*(pi/2), medium size double fn = (double)x * invpio2 + toint - toint; n = (int32_t)fn; *y = (double)x - fn * pio2_1 - fn * pio2_1t; if (*y < -pio4) { n -= 1; fn -= 1; *y = (double)x - fn * pio2_1 - fn * pio2_1t; } else if (*y > pio4) { n += 1; fn += 1; *y = (double)x - fn * pio2_1 - fn * pio2_1t; } return n; } if (ix >= 0x7f800000) { // x is inf or NaN *y = (double)(x - x); return 0; } // scale x into [2^23, 2^24-1] sign = ui >> 31 != 0; e0 = (ix >> 23) - (0x7f + 23); // e0 = ilogb(|x|)-23, positive ui = ix - (e0 << 23); tx[0] = (double)crtF32(ui); n = crtRemPio2Large(tx, ty, (int32_t)e0, 1, 0); if (sign) { *y = -ty[0]; return -n; } *y = ty[0]; return n; } // lib/compiler_rt/rem_pio2.zig `medium` and `rem_pio2`. static const double crt_toint = 0x1.8p52; // 1.5 / math.floatEps(f64) static const double crt_pio4 = 0x1.921fb54442d18p-1; static const double crt_invpio2 = 6.36619772367581382433e-01; // 0x3FE45F30, 0x6DC9C883 static const double crt_pio2_1 = 1.57079632673412561417e+00; // 0x3FF921FB, 0x54400000 static const double crt_pio2_1t = 6.07710050650619224932e-11; // 0x3DD0B461, 0x1A626331 static const double crt_pio2_2 = 6.07710050630396597660e-11; // 0x3DD0B461, 0x1A600000 static const double crt_pio2_2t = 2.02226624879595063154e-21; // 0x3BA3198A, 0x2E037073 static const double crt_pio2_3 = 2.02226624871116645580e-21; // 0x3BA3198A, 0x2E000000 static const double crt_pio2_3t = 8.47842766036889956997e-32; // 0x397B839A, 0x252049C1 static int32_t crtRemPio2Medium(uint32_t ix, double x, double* y) { double w, r, fn; int32_t n, ex, ey; uint64_t ui; // rint(x/(pi/2)) fn = x * crt_invpio2 + crt_toint - crt_toint; n = (int32_t)fn; r = x - fn * crt_pio2_1; w = fn * crt_pio2_1t; // 1st round, good to 85 bits // Matters with directed rounding. if (r - w < -crt_pio4) { n -= 1; fn -= 1; r = x - fn * crt_pio2_1; w = fn * crt_pio2_1t; } else if (r - w > crt_pio4) { n += 1; fn += 1; r = x - fn * crt_pio2_1; w = fn * crt_pio2_1t; } y[0] = r - w; ui = crtF64Bits(y[0]); ey = (int32_t)((ui >> 52) & 0x7ff); ex = (int32_t)(ix >> 20); if (ex - ey > 16) { // 2nd round, good to 118 bits double t = r; w = fn * crt_pio2_2; r = t - w; w = fn * crt_pio2_2t - ((t - r) - w); y[0] = r - w; ui = crtF64Bits(y[0]); ey = (int32_t)((ui >> 52) & 0x7ff); if (ex - ey > 49) { // 3rd round, good to 151 bits, covers all cases t = r; w = fn * crt_pio2_3; r = t - w; w = fn * crt_pio2_3t - ((t - r) - w); y[0] = r - w; } } y[1] = (r - y[0]) - w; return n; } static int32_t crtRemPio2(double x, double* y) { double z; double tx[3], ty[2]; int32_t n, i; uint32_t ix; bool sign; uint64_t ui; ui = crtF64Bits(x); sign = ui >> 63 != 0; ix = (uint32_t)((ui >> 32) & 0x7fffffff); if (ix <= 0x400f6a7a) { // |x| ~<= 5pi/4 if ((ix & 0xfffff) == 0x921fb) // |x| ~= pi/2 or 2pi/2 return crtRemPio2Medium( ix, x, y); // cancellation -- use medium case if (ix <= 0x4002d97c) { // |x| ~<= 3pi/4 if (!sign) { z = x - crt_pio2_1; // one round good to 85 bits y[0] = z - crt_pio2_1t; y[1] = (z - y[0]) - crt_pio2_1t; return 1; } else { z = x + crt_pio2_1; y[0] = z + crt_pio2_1t; y[1] = (z - y[0]) + crt_pio2_1t; return -1; } } else { if (!sign) { z = x - 2 * crt_pio2_1; y[0] = z - 2 * crt_pio2_1t; y[1] = (z - y[0]) - 2 * crt_pio2_1t; return 2; } else { z = x + 2 * crt_pio2_1; y[0] = z + 2 * crt_pio2_1t; y[1] = (z - y[0]) + 2 * crt_pio2_1t; return -2; } } } if (ix <= 0x401c463b) { // |x| ~<= 9pi/4 if (ix <= 0x4015fdbc) { // |x| ~<= 7pi/4 if (ix == 0x4012d97c) // |x| ~= 3pi/2 return crtRemPio2Medium(ix, x, y); if (!sign) { z = x - 3 * crt_pio2_1; y[0] = z - 3 * crt_pio2_1t; y[1] = (z - y[0]) - 3 * crt_pio2_1t; return 3; } else { z = x + 3 * crt_pio2_1; y[0] = z + 3 * crt_pio2_1t; y[1] = (z - y[0]) + 3 * crt_pio2_1t; return -3; } } else { if (ix == 0x401921fb) // |x| ~= 4pi/2 return crtRemPio2Medium(ix, x, y); if (!sign) { z = x - 4 * crt_pio2_1; y[0] = z - 4 * crt_pio2_1t; y[1] = (z - y[0]) - 4 * crt_pio2_1t; return 4; } else { z = x + 4 * crt_pio2_1; y[0] = z + 4 * crt_pio2_1t; y[1] = (z - y[0]) + 4 * crt_pio2_1t; return -4; } } } if (ix < 0x413921fb) // |x| ~< 2^20*(pi/2), medium size return crtRemPio2Medium(ix, x, y); // all other (large) arguments if (ix >= 0x7ff00000) { // x is inf or NaN y[0] = x - x; y[1] = y[0]; return 0; } // set z = scalbn(|x|,-ilogb(x)+23) ui = crtF64Bits(x); ui &= UINT64_MAX >> 12; ui |= (uint64_t)(0x3ff + 23) << 52; z = crtF64(ui); for (i = 0; i < 2; i++) { tx[i] = (double)(int32_t)z; z = (z - tx[i]) * 0x1p24; } tx[i] = z; // skip zero terms, first term is non-zero while (f64IsZero(tx[i])) // tx[i] == 0 i -= 1; n = crtRemPio2Large(tx, ty, (int32_t)(ix >> 20) - (0x3ff + 23), i + 1, 1); if (sign) { y[0] = -ty[0]; y[1] = -ty[1]; return -n; } y[0] = ty[0]; y[1] = ty[1]; return n; } // lib/compiler_rt/trig.zig `cos`, `cosdf`, `sin`, `sindf`, `tan`, `tandf`. static double crtTrigCos(double x, double y) { const double C1 = 4.16666666666666019037e-02; // 0x3FA55555, 0x5555554C const double C2 = -1.38888888888741095749e-03; // 0xBF56C16C, 0x16C15177 const double C3 = 2.48015872894767294178e-05; // 0x3EFA01A0, 0x19CB1590 const double C4 = -2.75573143513906633035e-07; // 0xBE927E4F, 0x809C52AD const double C5 = 2.08757232129817482790e-09; // 0x3E21EE9E, 0xBDB4B1C4 const double C6 = -1.13596475577881948265e-11; // 0xBDA8FAE9, 0xBE8838D4 const double z = x * x; const double zs = z * z; const double r = z * (C1 + z * (C2 + z * C3)) + zs * zs * (C4 + z * (C5 + z * C6)); const double hz = 0.5 * z; const double w = 1.0 - hz; return w + (((1.0 - w) - hz) + (z * r - x * y)); } static float crtTrigCosdf(double x) { const double C0 = -0x1ffffffd0c5e81.0p-54; // -0.499999997251031003120 const double C1 = 0x155553e1053a42.0p-57; // 0.0416666233237390631894 const double C2 = -0x16c087e80f1e27.0p-62; // -0.00138867637746099294692 const double C3 = 0x199342e0ee5069.0p-68; // 0.0000243904487962774090654 const double z = x * x; const double w = z * z; const double r = C2 + z * C3; return (float)(((1.0 + z * C0) + w * C1) + (w * z) * r); } static double crtTrigSin(double x, double y, int32_t iy) { const double S1 = -1.66666666666666324348e-01; // 0xBFC55555, 0x55555549 const double S2 = 8.33333333332248946124e-03; // 0x3F811111, 0x1110F8A6 const double S3 = -1.98412698298579493134e-04; // 0xBF2A01A0, 0x19C161D5 const double S4 = 2.75573137070700676789e-06; // 0x3EC71DE3, 0x57B1FE7D const double S5 = -2.50507602534068634195e-08; // 0xBE5AE5E6, 0x8A2B9CEB const double S6 = 1.58969099521155010221e-10; // 0x3DE5D93A, 0x5ACFD57C const double z = x * x; const double w = z * z; const double r = S2 + z * (S3 + z * S4) + z * w * (S5 + z * S6); const double v = z * x; if (iy == 0) return x + v * (S1 + z * r); return x - ((z * (0.5 * y - v * r) - y) - v * S1); } static float crtTrigSindf(double x) { const double S1 = -0x15555554cbac77.0p-55; // -0.166666666416265235595 const double S2 = 0x111110896efbb2.0p-59; // 0.0083333293858894631756 const double S3 = -0x1a00f9e2cae774.0p-65; // -0.000198393348360966317347 const double S4 = 0x16cd878c3b46a7.0p-71; // 0.0000027183114939898219064 const double z = x * x; const double w = z * z; const double r = S3 + z * S4; const double s = z * x; return (float)((x + s * (S1 + z * S2)) + s * w * r); } static double crtTrigTan(double x_, double y_, bool odd) { static const double T[13] = { 3.33333333333334091986e-01, // 3FD55555, 55555563 1.33333333333201242699e-01, // 3FC11111, 1110FE7A 5.39682539762260521377e-02, // 3FABA1BA, 1BB341FE 2.18694882948595424599e-02, // 3F9664F4, 8406D637 8.86323982359930005737e-03, // 3F8226E3, E96E8493 3.59207910759131235356e-03, // 3F6D6D22, C9560328 1.45620945432529025516e-03, // 3F57DBC8, FEE08315 5.88041240820264096874e-04, // 3F4344D8, F2F26501 2.46463134818469906812e-04, // 3F3026F7, 1A8D1068 7.81794442939557092300e-05, // 3F147E88, A03792A6 7.14072491382608190305e-05, // 3F12B80F, 32F0A7E9 -1.85586374855275456654e-05, // BEF375CB, DB605373 2.59073051863633712884e-05, // 3EFB2A70, 74BF7AD4 }; const double pio4 = 7.85398163397448278999e-01; // 3FE921FB, 54442D18 const double pio4lo = 3.06161699786838301793e-17; // 3C81A626, 33145C07 double x = x_, y = y_; double z, r, v, w, s, a, w0, a0; uint32_t hx; bool sign = false; hx = (uint32_t)(crtF64Bits(x) >> 32); const bool big = (hx & 0x7fffffff) >= 0x3FE59428; // |x| >= 0.6744 if (big) { sign = hx >> 31 != 0; if (sign) { x = -x; y = -y; } x = (pio4 - x) + (pio4lo - y); y = 0.0; } z = x * x; w = z * z; r = T[1] + w * (T[3] + w * (T[5] + w * (T[7] + w * (T[9] + w * T[11])))); v = z * (T[2] + w * (T[4] + w * (T[6] + w * (T[8] + w * (T[10] + w * T[12]))))); s = z * x; r = y + z * (s * (r + v) + y) + s * T[0]; w = x + r; if (big) { s = odd ? -1.0 : 1.0; v = s - 2.0 * (x + (r - w * w / (w + s))); return sign ? -v : v; } if (!odd) return w; // -1.0/(x+r) has up to 2ulp error, so compute it accurately w0 = w; w0 = crtF64(crtF64Bits(w0) & UINT64_C(0xffffffff00000000)); v = r - (w0 - x); // w0+v = r+x a = -1.0 / w; a0 = a; a0 = crtF64(crtF64Bits(a0) & UINT64_C(0xffffffff00000000)); return a0 + a * (1.0 + a0 * w0 + a0 * v); } static float crtTrigTandf(double x, bool odd) { static const double T[6] = { 0x15554d3418c99f.0p-54, // 0.333331395030791399758 0x1112fd38999f72.0p-55, // 0.133392002712976742718 0x1b54c91d865afe.0p-57, // 0.0533812378445670393523 0x191df3908c33ce.0p-58, // 0.0245283181166547278873 0x185dadfcecf44e.0p-61, // 0.00297435743359967304927 0x1362b9bf971bcd.0p-59, // 0.00946564784943673166728 }; const double z = x * x; const double r = T[4] + z * T[5]; const double t = T[2] + z * T[3]; const double w = z * z; const double s = z * x; const double u = T[0] + z * T[1]; const double r0 = (x + s * u) + (s * w) * (t + w * r); return (float)(odd ? -1.0 / r0 : r0); } // `1.0 * math.pi / 2.0` ... `4.0 * math.pi / 2.0` as f64 (sin.zig, cos.zig, // tan.zig). static const double crt_1pio2 = 0x1.921fb54442d18p+0; // 0x3FF921FB, 0x54442D18 static const double crt_2pio2 = 0x1.921fb54442d18p+1; // 0x400921FB, 0x54442D18 static const double crt_3pio2 = 0x1.2d97c7f3321d2p+2; // 0x4012D97C, 0x7F3321D2 static const double crt_4pio2 = 0x1.921fb54442d18p+2; // 0x401921FB, 0x54442D18 // lib/compiler_rt/sin.zig `sinf`. float crtSinf(float x) { uint32_t ix = crtF32Bits(x); const bool sign = ix >> 31 != 0; ix &= 0x7fffffff; if (ix <= 0x3f490fda) { // |x| ~<= pi/4 if (ix < 0x39800000) // |x| < 2**-12 return x; return crtTrigSindf((double)x); } if (ix <= 0x407b53d1) { // |x| ~<= 5*pi/4 if (ix <= 0x4016cbe3) { // |x| ~<= 3pi/4 if (sign) return -crtTrigCosdf((double)x + crt_1pio2); return crtTrigCosdf((double)x - crt_1pio2); } return crtTrigSindf( sign ? -((double)x + crt_2pio2) : -((double)x - crt_2pio2)); } if (ix <= 0x40e231d5) { // |x| ~<= 9*pi/4 if (ix <= 0x40afeddf) { // |x| ~<= 7*pi/4 if (sign) return crtTrigCosdf((double)x + crt_3pio2); return -crtTrigCosdf((double)x - crt_3pio2); } return crtTrigSindf( sign ? (double)x + crt_4pio2 : (double)x - crt_4pio2); } // sin(Inf or NaN) is NaN if (ix >= 0x7f800000) return x - x; double y; const int32_t n = crtRemPio2f(x, &y); switch (n & 3) { case 0: return crtTrigSindf(y); case 1: return crtTrigCosdf(y); case 2: return crtTrigSindf(-y); default: return -crtTrigCosdf(y); } } // lib/compiler_rt/sin.zig `sin`. double crtSin(double x) { uint64_t ix = crtF64Bits(x) >> 32; ix &= 0x7fffffff; // |x| ~< pi/4 if (ix <= 0x3fe921fb) { if (ix < 0x3e500000) // |x| < 2**-26 return x; return crtTrigSin(x, 0.0, 0); } // sin(Inf or NaN) is NaN if (ix >= 0x7ff00000) return x - x; double y[2]; const int32_t n = crtRemPio2(x, y); switch (n & 3) { case 0: return crtTrigSin(y[0], y[1], 1); case 1: return crtTrigCos(y[0], y[1]); case 2: return -crtTrigSin(y[0], y[1], 1); default: return -crtTrigCos(y[0], y[1]); } } // lib/compiler_rt/cos.zig `cosf`. float crtCosf(float x) { uint32_t ix = crtF32Bits(x); const bool sign = ix >> 31 != 0; ix &= 0x7fffffff; if (ix <= 0x3f490fda) { // |x| ~<= pi/4 if (ix < 0x39800000) // |x| < 2**-12 return 1.0f; return crtTrigCosdf((double)x); } if (ix <= 0x407b53d1) { // |x| ~<= 5*pi/4 if (ix > 0x4016cbe3) // |x| ~> 3*pi/4 return -crtTrigCosdf( sign ? (double)x + crt_2pio2 : (double)x - crt_2pio2); if (sign) return crtTrigSindf((double)x + crt_1pio2); return crtTrigSindf(crt_1pio2 - (double)x); } if (ix <= 0x40e231d5) { // |x| ~<= 9*pi/4 if (ix > 0x40afeddf) // |x| ~> 7*pi/4 return crtTrigCosdf( sign ? (double)x + crt_4pio2 : (double)x - crt_4pio2); if (sign) return crtTrigSindf((double)-x - crt_3pio2); return crtTrigSindf((double)x - crt_3pio2); } // cos(Inf or NaN) is NaN if (ix >= 0x7f800000) return x - x; double y; const int32_t n = crtRemPio2f(x, &y); switch (n & 3) { case 0: return crtTrigCosdf(y); case 1: return crtTrigSindf(-y); case 2: return -crtTrigCosdf(y); default: return crtTrigSindf(y); } } // lib/compiler_rt/cos.zig `cos`. double crtCos(double x) { uint64_t ix = crtF64Bits(x) >> 32; ix &= 0x7fffffff; // |x| ~< pi/4 if (ix <= 0x3fe921fb) { if (ix < 0x3e46a09e) // |x| < 2**-27 * sqrt(2) return 1.0; return crtTrigCos(x, 0); } // cos(Inf or NaN) is NaN if (ix >= 0x7ff00000) return x - x; double y[2]; const int32_t n = crtRemPio2(x, y); switch (n & 3) { case 0: return crtTrigCos(y[0], y[1]); case 1: return -crtTrigSin(y[0], y[1], 1); case 2: return -crtTrigCos(y[0], y[1]); default: return crtTrigSin(y[0], y[1], 1); } } // lib/compiler_rt/tan.zig `tanf`. float crtTanf(float x) { uint32_t ix = crtF32Bits(x); const bool sign = ix >> 31 != 0; ix &= 0x7fffffff; if (ix <= 0x3f490fda) { // |x| ~<= pi/4 if (ix < 0x39800000) // |x| < 2**-12 return x; return crtTrigTandf((double)x, false); } if (ix <= 0x407b53d1) { // |x| ~<= 5*pi/4 if (ix <= 0x4016cbe3) // |x| ~<= 3pi/4 return crtTrigTandf( sign ? (double)x + crt_1pio2 : (double)x - crt_1pio2, true); return crtTrigTandf( sign ? (double)x + crt_2pio2 : (double)x - crt_2pio2, false); } if (ix <= 0x40e231d5) { // |x| ~<= 9*pi/4 if (ix <= 0x40afeddf) // |x| ~<= 7*pi/4 return crtTrigTandf( sign ? (double)x + crt_3pio2 : (double)x - crt_3pio2, true); return crtTrigTandf( sign ? (double)x + crt_4pio2 : (double)x - crt_4pio2, false); } // tan(Inf or NaN) is NaN if (ix >= 0x7f800000) return x - x; double y; const int32_t n = crtRemPio2f(x, &y); return crtTrigTandf(y, (n & 1) != 0); } // lib/compiler_rt/tan.zig `tan`. double crtTan(double x) { uint64_t ix = crtF64Bits(x) >> 32; ix &= 0x7fffffff; // |x| ~< pi/4 if (ix <= 0x3fe921fb) { if (ix < 0x3e400000) // |x| < 2**-27 return x; return crtTrigTan(x, 0.0, false); } // tan(Inf or NaN) is NaN if (ix >= 0x7ff00000) return x - x; double y[2]; const int32_t n = crtRemPio2(x, y); return crtTrigTan(y[0], y[1], (n & 1) != 0); } // lib/compiler_rt/exp.zig `expf`. float crtExpf(float x_) { const float half[2] = { 0.5f, -0.5f }; const float ln2hi = 6.9314575195e-1f; const float ln2lo = 1.4286067653e-6f; const float invln2 = 1.4426950216e+0f; const float P1 = 1.6666625440e-1f; const float P2 = -2.7667332906e-3f; float x = x_; uint32_t hx = crtF32Bits(x); const int32_t sign = (int32_t)(hx >> 31); hx &= 0x7FFFFFFF; if (isnan(x)) // math.isNan(x) return x; // |x| >= -87.33655 or nan if (hx >= 0x42AEAC50) { // nan if (hx > 0x7F800000) return x; // x >= 88.722839 if (hx >= 0x42b17218 && sign == 0) return x * 0x1.0p127f; if (sign != 0) { // x <= -103.972084 if (hx >= 0x42CFF1B5) return 0; } } int32_t k; float hi, lo; // |x| > 0.5 * ln2 if (hx > 0x3EB17218) { // |x| > 1.5 * ln2 if (hx > 0x3F851592) k = (int32_t)(invln2 * x + half[sign]); else k = 1 - sign - sign; const float fk = (float)k; hi = x - fk * ln2hi; lo = fk * ln2lo; x = hi - lo; } // |x| > 2^(-14) else if (hx > 0x39000000) { k = 0; hi = x; lo = 0; } else { return 1 + x; } const float xx = x * x; const float c = x - xx * (P1 + xx * P2); const float y = 1 + (x * c / (2 - c) - lo + hi); if (k == 0) return y; return crtScalbnF32(y, k); } // lib/compiler_rt/exp.zig `exp`. double crtExp(double x_) { const double half[2] = { 0.5, -0.5 }; const double ln2hi = 6.93147180369123816490e-01; const double ln2lo = 1.90821492927058770002e-10; const double invln2 = 1.44269504088896338700e+00; const double P1 = 1.66666666666666019037e-01; const double P2 = -2.77777777770155933842e-03; const double P3 = 6.61375632143793436117e-05; const double P4 = -1.65339022054652515390e-06; const double P5 = 4.13813679705723846039e-08; double x = x_; const uint64_t ux = crtF64Bits(x); uint64_t hx = ux >> 32; const int32_t sign = (int32_t)(hx >> 31); hx &= 0x7FFFFFFF; if (isnan(x)) // math.isNan(x) return x; // |x| >= 708.39 or nan if (hx >= 0x4086232B) { // nan if (hx > 0x7FF00000) return x; if (x > 709.782712893383973096) { // overflow if x != inf; `x * 0x1p1023` and `math.inf` agree return x * 0x1p1023; } if (x < -708.39641853226410622) { if (x < -745.13321910194110842) return 0; } } // argument reduction int32_t k; double hi, lo; // |x| > 0.5 * ln2 if (hx > 0x3FD62E42) { // |x| >= 1.5 * ln2 if (hx > 0x3FF0A2B2) k = (int32_t)(invln2 * x + half[sign]); else k = 1 - sign - sign; const double dk = (double)k; hi = x - dk * ln2hi; lo = dk * ln2lo; x = hi - lo; } // |x| > 2^(-28) else if (hx > 0x3E300000) { k = 0; hi = x; lo = 0; } else { return 1 + x; } const double xx = x * x; const double c = x - xx * (P1 + xx * (P2 + xx * (P3 + xx * (P4 + xx * P5)))); const double y = 1 + (x * c / (2 - c) - lo + hi); if (k == 0) return y; return crtScalbnF64(y, k); } // lib/compiler_rt/exp2.zig `exp2f`. float crtExp2f(float x) { const uint32_t tblsiz = 16; // exp2ft.len const float redux = 0x1.8p23f / (float)tblsiz; const float P1 = 0x1.62e430p-1f; const float P2 = 0x1.ebfbe0p-3f; const float P3 = 0x1.c6b348p-5f; const float P4 = 0x1.3b2c9cp-7f; const uint32_t u = crtF32Bits(x); const uint32_t ix = u & 0x7FFFFFFF; // |x| > 126 if (ix > 0x42FC0000) { // nan if (ix > 0x7F800000) return x; // x >= 128 if (u >= 0x43000000 && u < 0x80000000) return x * 0x1.0p127f; // x < -126 if (u >= 0x80000000) { if (u >= 0xC3160000) return 0; } } // |x| <= 0x1p-25 else if (ix <= 0x33000000) { return 1.0f + x; } // NOTE: musl relies on unsafe behaviours which are replicated below // (addition overflow, division truncation, casting). Appears that this // produces the intended result but should confirm how GCC/Clang handle // this to ensure. float uf = x + redux; uint32_t i_0 = crtF32Bits(uf); i_0 += tblsiz / 2; const uint32_t k = i_0 / tblsiz; const double uk = crtF64((uint64_t)(0x3FF + k) << 52); i_0 &= tblsiz - 1; uf -= redux; const double z = (double)(x - uf); double r = crt_exp2ft[i_0]; const double t = r * z; r = r + t * ((double)P1 + z * (double)P2) + t * (z * z) * ((double)P3 + z * (double)P4); return (float)(r * uk); } // lib/compiler_rt/exp2.zig `exp2`. double crtExp2(double x) { const uint32_t tblsiz = 256; // exp2dt.len / 2 const double redux = 0x1.8p52 / (double)tblsiz; const double P1 = 0x1.62e42fefa39efp-1; const double P2 = 0x1.ebfbdff82c575p-3; const double P3 = 0x1.c6b08d704a0a6p-5; const double P4 = 0x1.3b2ab88f70400p-7; const double P5 = 0x1.5d88003875c74p-10; const uint64_t ux = crtF64Bits(x); const uint32_t ix = (uint32_t)(ux >> 32) & 0x7FFFFFFF; if (isnan(x)) // math.isNan(x) return CRT_NAN_F64; // |x| >= 1022 or nan if (ix >= 0x408FF000) { // x >= 1024 or nan; `x * 0x1p1023` and `math.inf` agree if (ix >= 0x40900000 && ux >> 63 == 0) return x * 0x1p1023; // -inf or -nan if (ix >= 0x7FF00000) return -1 / x; // x <= -1022 if (ux >> 63 != 0) { if (x <= -1075) return 0; } } // |x| < 0x1p-54 else if (ix < 0x3C900000) { return 1.0 + x; } // reduce x double uf = x + redux; // NOTE: musl performs an implicit 64-bit to 32-bit u32 truncation here uint32_t i_0 = (uint32_t)crtF64Bits(uf); i_0 += tblsiz / 2; const uint32_t k = i_0 / tblsiz * tblsiz; const int32_t ik = (int32_t)k / (int32_t)tblsiz; i_0 %= tblsiz; uf -= redux; // r = exp2(y) = exp2t[i_0] * p(z - eps[i]) double z = x - uf; const double t = crt_exp2dt[2 * i_0]; z -= crt_exp2dt[2 * i_0 + 1]; const double r = t + t * z * (P1 + z * (P2 + z * (P3 + z * (P4 + z * P5)))); return crtScalbnF64(r, ik); } // lib/compiler_rt/log.zig `logf`. `want_float_exceptions` selects upstream's // `(x - x) / 0.0` (the host's NaN) over `math.nan(f64)`. float crtLogf(float x_, bool want_float_exceptions) { const float ln2_hi = 6.9313812256e-01f; const float ln2_lo = 9.0580006145e-06f; const float Lg1 = 0xaaaaaa.0p-24f; const float Lg2 = 0xccce13.0p-25f; const float Lg3 = 0x91e9ee.0p-25f; const float Lg4 = 0xf89e26.0p-26f; float x = x_; uint32_t ix = crtF32Bits(x); int32_t k = 0; // x < 2^(-126) if (ix < 0x00800000 || ix >> 31 != 0) { // log(+-0) = -inf if (ix << 1 == 0) return want_float_exceptions ? -1 / (x * x) : -CRT_INF_F32; // log(-#) = nan if (ix >> 31 != 0) return want_float_exceptions ? (x - x) / 0.0f : CRT_NAN_F32; // subnormal, scale x k -= 25; x *= 0x1.0p25f; ix = crtF32Bits(x); } else if (ix >= 0x7F800000) { return x; } else if (ix == 0x3F800000) { return 0; } // x into [sqrt(2) / 2, sqrt(2)] ix += 0x3F800000 - 0x3F3504F3; k += (int32_t)(ix >> 23) - 0x7F; ix = (ix & 0x007FFFFF) + 0x3F3504F3; x = crtF32(ix); const float f = x - 1.0f; const float s = f / (2.0f + f); const float z = s * s; const float w = z * z; const float t1 = w * (Lg2 + w * Lg4); const float t2 = z * (Lg1 + w * Lg3); const float R = t2 + t1; const float hfsq = 0.5f * f * f; const float dk = (float)k; return s * (hfsq + R) + dk * ln2_lo - hfsq + f + dk * ln2_hi; } // lib/compiler_rt/log.zig `log` (the table-driven routine). double crtLog(double x, bool want_float_exceptions) { static const double poly1[11] = { -0x1p-1, 0x1.5555555555577p-2, -0x1.ffffffffffdcbp-3, 0x1.999999995dd0cp-3, -0x1.55555556745a7p-3, 0x1.24924a344de3p-3, -0x1.fffffa4423d65p-4, 0x1.c7184282ad6cap-4, -0x1.999eb43b068ffp-4, 0x1.78182f7afd085p-4, -0x1.5521375d145cdp-4, }; static const double poly[5] = { -0x1.0000000000001p-1, 0x1.555555551305bp-2, -0x1.fffffffeb459p-3, 0x1.999b324f10111p-3, -0x1.55575e506c89fp-3, }; // `ix` is upstream's i64; unsigned here, which orders every value the // `LO <= ix and ix < HI` window can hold the same way. uint64_t ix = crtF64Bits(x); const uint64_t LO = crtF64Bits(1.0 - 0x1p-4); const uint64_t HI = crtF64Bits(1.0 + 0x1.09p-4); if (LO <= ix && ix < HI) { // Handle close to 1.0 inputs separately. // Fix sign of zero with downward rounding when x==1. if (ix == crtF64Bits(1.0)) return 0; const double r = x - 1; const double r2 = r * r; const double r3 = r * r2; const double y = r3 * (poly1[1] + r * poly1[2] + r2 * poly1[3] + r3 * (poly1[4] + r * poly1[5] + r2 * poly1[6] + r3 * (poly1[7] + r * poly1[8] + r2 * poly1[9] + r3 * poly1[10]))); // Worst-case error is around 0.507 ULP. double w = r * 0x1p27; const double rhi = r + w - w; const double rlo = r - rhi; w = rhi * rhi * poly1[0]; // poly1[0] == -0.5. const double hi = r + w; const double lo = r - hi + w + poly1[0] * rlo * (rhi + r); return y + lo + hi; } const uint64_t top = ix >> 48; if (top < 0x0010 || 0x7ff0 <= top) { // x < 0x1p-1022 or inf or nan. if (ix << 1 == 0) return want_float_exceptions ? -1 / (x * x) : -CRT_INF_F64; if (ix == crtF64Bits(CRT_INF_F64)) // log(inf) == inf. return x; if ((top & 0x8000) != 0 || (top & 0x7ff0) == 0x7ff0) return want_float_exceptions ? (x - x) / 0.0 : CRT_NAN_F64; // x is subnormal, normalize it. ix = crtF64Bits(x * 0x1p52) - (UINT64_C(52) << 52); } // x = 2^k z; where z is in range [off,2*off) and exact. // The range is split into N subintervals. // The ith subinterval contains z and c is near its center. // `packed struct(i64) { unused: u45, i: u7, k: i12 }` const uint64_t tmp = ix - UINT64_C(0x3fe6000000000000); const uint32_t i = (uint32_t)((tmp >> 45) & 0x7f); const uint32_t k_bits = (uint32_t)(tmp >> 52); const int32_t k = (int32_t)k_bits - (k_bits >= 0x800 ? 0x1000 : 0); const uint64_t iz = ix - (tmp & (UINT64_C(0xfff) << 52)); const double invc = crt_log_tab[i].invc; const double logc = crt_log_tab[i].logc; const double z = crtF64(iz); // log(x) = log1p(z/c-1) + log(c) + k*Ln2. // r ~= z/c - 1, |r| < 1/(2*N). // rounding error: 0x1p-55/N + 0x1p-66. const double r = (z - crt_log_tab2[i].chi - crt_log_tab2[i].clo) * invc; const double kd = (double)k; // hi + lo = r + log(c) + k*Ln2. const double w = kd * 0x1.62e42fefa3800p-1 + logc; const double hi = w + r; const double lo = w - hi + r + kd * 0x1.ef35793c76730p-45; // log(x) = lo + (log1p(r) - r) + hi. const double r2 = r * r; // rounding error: 0x1p-54/N^2. // Worst case error if |y| > 0x1p-5: // 0.5 + 4.13/N + abs-poly-error*2^57 ULP (+ 0.002 ULP without fma) // Worst case error if |y| > 0x1p-4: // 0.5 + 2.06/N + abs-poly-error*2^56 ULP (+ 0.001 ULP without fma). return lo + r2 * poly[0] + r * r2 * (poly[1] + r * poly[2] + r2 * (poly[3] + r * poly[4])) + hi; } // lib/compiler_rt/log2.zig `log2f`. float crtLog2f(float x_, bool want_float_exceptions) { const float ivln2hi = 1.4428710938e+00f; const float ivln2lo = -1.7605285393e-04f; const float Lg1 = 0xaaaaaa.0p-24f; const float Lg2 = 0xccce13.0p-25f; const float Lg3 = 0x91e9ee.0p-25f; const float Lg4 = 0xf89e26.0p-26f; float x = x_; uint32_t u = crtF32Bits(x); uint32_t ix = u; int32_t k = 0; // x < 2^(-126) if (ix < 0x00800000 || ix >> 31 != 0) { // log(+-0) = -inf if (ix << 1 == 0) return want_float_exceptions ? -1 / (x * x) : -CRT_INF_F32; // log(-#) = nan if (ix >> 31 != 0) return want_float_exceptions ? (x - x) / 0.0f : CRT_NAN_F32; k -= 25; x *= 0x1.0p25f; ix = crtF32Bits(x); } else if (ix >= 0x7F800000) { return x; } else if (ix == 0x3F800000) { return 0; } // x into [sqrt(2) / 2, sqrt(2)] ix += 0x3F800000 - 0x3F3504F3; k += (int32_t)(ix >> 23) - 0x7F; ix = (ix & 0x007FFFFF) + 0x3F3504F3; x = crtF32(ix); const float f = x - 1.0f; const float s = f / (2.0f + f); const float z = s * s; const float w = z * z; const float t1 = w * (Lg2 + w * Lg4); const float t2 = z * (Lg1 + w * Lg3); const float R = t2 + t1; const float hfsq = 0.5f * f * f; float hi = f - hfsq; u = crtF32Bits(hi); u &= 0xFFFFF000; hi = crtF32(u); const float lo = f - hi - hfsq + s * (hfsq + R); return (lo + hi) * ivln2lo + lo * ivln2hi + hi * ivln2hi + (float)k; } // lib/compiler_rt/log2.zig `log2`. double crtLog2(double x_, bool want_float_exceptions) { const double ivln2hi = 1.44269504072144627571e+00; const double ivln2lo = 1.67517131648865118353e-10; const double Lg1 = 6.666666666666735130e-01; const double Lg2 = 3.999999999940941908e-01; const double Lg3 = 2.857142874366239149e-01; const double Lg4 = 2.222219843214978396e-01; const double Lg5 = 1.818357216161805012e-01; const double Lg6 = 1.531383769920937332e-01; const double Lg7 = 1.479819860511658591e-01; double x = x_; uint64_t ix = crtF64Bits(x); uint32_t hx = (uint32_t)(ix >> 32); int32_t k = 0; if (hx < 0x00100000 || hx >> 31 != 0) { // log(+-0) = -inf if (ix << 1 == 0) return want_float_exceptions ? -1 / (x * x) : -CRT_INF_F64; // log(-#) = nan if (hx >> 31 != 0) return want_float_exceptions ? (x - x) / 0.0 : CRT_NAN_F64; // subnormal, scale x k -= 54; x *= 0x1.0p54; hx = (uint32_t)(crtF64Bits(x) >> 32); } else if (hx >= 0x7FF00000) { return x; } else if (hx == 0x3FF00000 && ix << 32 == 0) { return 0; } // x into [sqrt(2) / 2, sqrt(2)] hx += 0x3FF00000 - 0x3FE6A09E; k += (int32_t)(hx >> 20) - 0x3FF; hx = (hx & 0x000FFFFF) + 0x3FE6A09E; ix = ((uint64_t)hx << 32) | (ix & 0xFFFFFFFF); x = crtF64(ix); const double f = x - 1.0; const double hfsq = 0.5 * f * f; const double s = f / (2.0 + f); const double z = s * s; const double w = z * z; const double t1 = w * (Lg2 + w * (Lg4 + w * Lg6)); const double t2 = z * (Lg1 + w * (Lg3 + w * (Lg5 + w * Lg7))); const double R = t2 + t1; // hi + lo = f - hfsq + s * (hfsq + R) ~ log(1 + f) double hi = f - hfsq; uint64_t hii = crtF64Bits(hi); hii &= UINT64_MAX << 32; hi = crtF64(hii); const double lo = f - hi - hfsq + s * (hfsq + R); double val_hi = hi * ivln2hi; double val_lo = (lo + hi) * ivln2lo + lo * ivln2hi; // spadd(val_hi, val_lo, y) const double y = (double)k; const double ww = y + val_hi; val_lo += (y - ww) + val_hi; val_hi = ww; return val_lo + val_hi; } // lib/compiler_rt/log10.zig `log10f`. float crtLog10f(float x_, bool want_float_exceptions) { const float ivln10hi = 4.3432617188e-01f; const float ivln10lo = -3.1689971365e-05f; const float log10_2hi = 3.0102920532e-01f; const float log10_2lo = 7.9034151668e-07f; const float Lg1 = 0xaaaaaa.0p-24f; const float Lg2 = 0xccce13.0p-25f; const float Lg3 = 0x91e9ee.0p-25f; const float Lg4 = 0xf89e26.0p-26f; float x = x_; uint32_t u = crtF32Bits(x); uint32_t ix = u; int32_t k = 0; // x < 2^(-126) if (ix < 0x00800000 || ix >> 31 != 0) { // log(+-0) = -inf if (ix << 1 == 0) return want_float_exceptions ? -1 / (x * x) : -CRT_INF_F32; // log(-#) = nan if (ix >> 31 != 0) return want_float_exceptions ? (x - x) / 0.0f : CRT_NAN_F32; k -= 25; x *= 0x1.0p25f; ix = crtF32Bits(x); } else if (ix >= 0x7F800000) { return x; } else if (ix == 0x3F800000) { return 0; } // x into [sqrt(2) / 2, sqrt(2)] ix += 0x3F800000 - 0x3F3504F3; k += (int32_t)(ix >> 23) - 0x7F; ix = (ix & 0x007FFFFF) + 0x3F3504F3; x = crtF32(ix); const float f = x - 1.0f; const float s = f / (2.0f + f); const float z = s * s; const float w = z * z; const float t1 = w * (Lg2 + w * Lg4); const float t2 = z * (Lg1 + w * Lg3); const float R = t2 + t1; const float hfsq = 0.5f * f * f; float hi = f - hfsq; u = crtF32Bits(hi); u &= 0xFFFFF000; hi = crtF32(u); const float lo = f - hi - hfsq + s * (hfsq + R); const float dk = (float)k; return dk * log10_2lo + (lo + hi) * ivln10lo + lo * ivln10hi + hi * ivln10hi + dk * log10_2hi; } // lib/compiler_rt/log10.zig `log10`. double crtLog10(double x_, bool want_float_exceptions) { const double ivln10hi = 4.34294481878168880939e-01; const double ivln10lo = 2.50829467116452752298e-11; const double log10_2hi = 3.01029995663611771306e-01; const double log10_2lo = 3.69423907715893078616e-13; const double Lg1 = 6.666666666666735130e-01; const double Lg2 = 3.999999999940941908e-01; const double Lg3 = 2.857142874366239149e-01; const double Lg4 = 2.222219843214978396e-01; const double Lg5 = 1.818357216161805012e-01; const double Lg6 = 1.531383769920937332e-01; const double Lg7 = 1.479819860511658591e-01; double x = x_; uint64_t ix = crtF64Bits(x); uint32_t hx = (uint32_t)(ix >> 32); int32_t k = 0; if (hx < 0x00100000 || hx >> 31 != 0) { // log(+-0) = -inf if (ix << 1 == 0) return want_float_exceptions ? -1 / (x * x) : -CRT_INF_F64; // log(-#) = nan if (hx >> 31 != 0) return want_float_exceptions ? (x - x) / 0.0 : CRT_NAN_F64; // subnormal, scale x k -= 54; x *= 0x1.0p54; hx = (uint32_t)(crtF64Bits(x) >> 32); } else if (hx >= 0x7FF00000) { return x; } else if (hx == 0x3FF00000 && ix << 32 == 0) { return 0; } // x into [sqrt(2) / 2, sqrt(2)] hx += 0x3FF00000 - 0x3FE6A09E; k += (int32_t)(hx >> 20) - 0x3FF; hx = (hx & 0x000FFFFF) + 0x3FE6A09E; ix = ((uint64_t)hx << 32) | (ix & 0xFFFFFFFF); x = crtF64(ix); const double f = x - 1.0; const double hfsq = 0.5 * f * f; const double s = f / (2.0 + f); const double z = s * s; const double w = z * z; const double t1 = w * (Lg2 + w * (Lg4 + w * Lg6)); const double t2 = z * (Lg1 + w * (Lg3 + w * (Lg5 + w * Lg7))); const double R = t2 + t1; // hi + lo = f - hfsq + s * (hfsq + R) ~ log(1 + f) double hi = f - hfsq; uint64_t hii = crtF64Bits(hi); hii &= UINT64_MAX << 32; hi = crtF64(hii); const double lo = f - hi - hfsq + s * (hfsq + R); // val_hi + val_lo ~ log10(1 + f) + k * log10(2) double val_hi = hi * ivln10hi; const double dk = (double)k; const double y = dk * log10_2hi; double val_lo = dk * log10_2lo + (lo + hi) * ivln10lo + lo * ivln10hi; // Extra precision multiplication const double ww = y + val_hi; val_lo += (y - ww) + val_hi; val_hi = ww; return val_lo + val_hi; } // ── log_f128.zig: logq, log2q, log10q ────────────────────────────────────── // Tang's table-driven logarithm as compiler_rt adapts it to f128 (bases e, 2 // and 10). The f128 arithmetic is this file's soft-float; the `f64` terms // (`a9`, `a11`, `b15`..`b19`) are plain doubles, one rounding per operation. #define LOGQ_LOG2SIZE 7 #define LOGQ_SIZE (1 << LOGQ_LOG2SIZE) #define CRT_F128_EXP_SHIFT 48 // exponent position in the high word #define CRT_F128_EXP_MASK UINT64_C(0x7fff) #define CRT_F128_BIAS 0x3fff #define CRT_F128_MANT_BITS 112 typedef struct { CrtU128 a1, a3, a5, a7; uint64_t a9, a11; // f64 CrtU128 b1_hi, b1_lo, b3, b5, b7, b9, b11, b13; uint64_t b15, b17, b19; // f64 } CrtLogqPoly; // `log2q`'s Proc1 table (lib/compiler_rt/log2.zig `tab`), hi/lo pairs. static const CrtU128 crt_log2q_tab[LOGQ_SIZE + 1][2] = { { { UINT64_C(0x0000000000000000), UINT64_C(0x0000000000000000) }, { UINT64_C(0x0000000000000000), UINT64_C(0x0000000000000000) } }, { { UINT64_C(0x17f8e37b00000000), UINT64_C(0x3ff86fe50b6ef085) }, { UINT64_C(0x648f265a41e57d75), UINT64_C(0x3f9c794f4441ccdf) } }, { { UINT64_C(0x98a6e27e21200000), UINT64_C(0x3ff96e79685c2d22) }, { UINT64_C(0x434912ad3fe21cfb), UINT64_C(0xbf9bfbd41ae7d5a2) } }, { { UINT64_C(0x2ed814504fb00000), UINT64_C(0x3ffa11cd1d513341) }, { UINT64_C(0x8aeb4167a3359577), UINT64_C(0xbf9cb2e2b4325400) } }, { { UINT64_C(0x7313606f09700000), UINT64_C(0x3ffa6bad3758efd8) }, { UINT64_C(0x6d28e4be331fce17), UINT64_C(0xbf9c20fbdb7ce4c8) } }, { { UINT64_C(0xef4f8f869e600000), UINT64_C(0x3ffac4dfab90aab5) }, { UINT64_C(0x2fdaac375356fc29), UINT64_C(0x3f9bdc4142bd2b18) } }, { { UINT64_C(0xab3cf74bab980000), UINT64_C(0x3ffb0eb389fa29f9) }, { UINT64_C(0xf3c7ddd223517be7), UINT64_C(0x3f9b9217066b9150) } }, { { UINT64_C(0x2d804d1121b80000), UINT64_C(0x3ffb3aa2fdd27f1c) }, { UINT64_C(0xa0e1d121d0222ba5), UINT64_C(0xbf9cacec4ac95b97) } }, { { UINT64_C(0x67ccc53826180000), UINT64_C(0x3ffb663f6fac9131) }, { UINT64_C(0x182a716c033db6de), UINT64_C(0xbf9cdd4529e1ad7c) } }, { { UINT64_C(0xaae7232494d80000), UINT64_C(0x3ffb918a16e46335) }, { UINT64_C(0x7370f7cb86bd9e3e), UINT64_C(0x3f9c9d1d19046b22) } }, { { UINT64_C(0xa63b2c5a6e500000), UINT64_C(0x3ffbbc84240adabb) }, { UINT64_C(0x0810e3b820ca9aa0), UINT64_C(0x3f9b97ab879641c5) } }, { { UINT64_C(0x21cbdb5d9dc00000), UINT64_C(0x3ffbe72ec117fa5b) }, { UINT64_C(0x5b384b68c4f1e669), UINT64_C(0x3f9c4f902752a1dc) } }, { { UINT64_C(0x39627bc6fd0c0000), UINT64_C(0x3ffc08c588cda79e) }, { UINT64_C(0x15f8fdff17d7ea79), UINT64_C(0xbf9caad7542b0c40) } }, { { UINT64_C(0xb5ea454307840000), UINT64_C(0x3ffc1dcd197552b7) }, { UINT64_C(0xd52f96d8aaf3b8ed), UINT64_C(0xbf9aa7aa295e08ad) } }, { { UINT64_C(0xa1f51f2c075c0000), UINT64_C(0x3ffc32ae9e278ae1) }, { UINT64_C(0xd3b39116e44d50c8), UINT64_C(0xbf9c8b459b26ac0e) } }, { { UINT64_C(0xd3138e9416440000), UINT64_C(0x3ffc476a9f983f74) }, { UINT64_C(0x762eeaffdcde52bd), UINT64_C(0xbf9727d822a49870) } }, { { UINT64_C(0x79fa00b120a00000), UINT64_C(0x3ffc5c01a39fbd68) }, { UINT64_C(0xa8e8966c101ef964), UINT64_C(0x3f9aa2eb74493cf9) } }, { { UINT64_C(0xf29c01cfad780000), UINT64_C(0x3ffc70742d4ef027) }, { UINT64_C(0xcc7941a5df4c8dec), UINT64_C(0xbf988495d6ca3b2d) } }, { { UINT64_C(0x2fdd2248ee780000), UINT64_C(0x3ffc84c2bd02f03b) }, { UINT64_C(0x4b50240232cdb09e), UINT64_C(0xbf9cc56a8f082934) } }, { { UINT64_C(0xf02face8ca900000), UINT64_C(0x3ffc98edd077e70d) }, { UINT64_C(0x899d29e3feaf9c8f), UINT64_C(0x3f9c72b7f2fcb409) } }, { { UINT64_C(0x3efe11ecbc000000), UINT64_C(0x3ffcacf5e2db4ec9) }, { UINT64_C(0xeea143f1178aaf62), UINT64_C(0x3f9c83634b52082b) } }, { { UINT64_C(0xe40e26d9899c0000), UINT64_C(0x3ffcc0db6cdd94de) }, { UINT64_C(0xb9b4a45aa674cc1b), UINT64_C(0x3f9bfde86bd82482) } }, { { UINT64_C(0xfc8f4b5650240000), UINT64_C(0x3ffcd49ee4c32596) }, { UINT64_C(0x2d54011ca95ecca1), UINT64_C(0xbf9a7eaef901f52d) } }, { { UINT64_C(0xcc7c9f3d51e00000), UINT64_C(0x3ffce840be74e6a4) }, { UINT64_C(0x2c17f944fa1f93cf), UINT64_C(0xbf9c03c7c0d8554c) } }, { { UINT64_C(0xa23a8d998a780000), UINT64_C(0x3ffcfbc16b902680) }, { UINT64_C(0xb6b52a41ad567d8e), UINT64_C(0x3f9a3bcd7c933d5b) } }, { { UINT64_C(0x96f031a699d60000), UINT64_C(0x3ffd0790adbb0300) }, { UINT64_C(0xadf04bd0ff35f885), UINT64_C(0xbf9c748a4ccf5e3d) } }, { { UINT64_C(0x5cb09705a7960000), UINT64_C(0x3ffd11307dad30b7) }, { UINT64_C(0xf00bd800ed9b9d24), UINT64_C(0xbf9b6c580f988c64) } }, { { UINT64_C(0x0528c7386df80000), UINT64_C(0x3ffd1ac05b291f07) }, { UINT64_C(0x4e5a59fded67fcb7), UINT64_C(0x3f9c94340c3b639a) } }, { { UINT64_C(0x98245b94ba440000), UINT64_C(0x3ffd24407ab0e073) }, { UINT64_C(0x53b19301384099da), UINT64_C(0x3f9c8077f77a0f43) } }, { { UINT64_C(0xf6f137a3d8c60000), UINT64_C(0x3ffd2db10fc4d9aa) }, { UINT64_C(0x0b8be44e090558d7), UINT64_C(0x3f9ce79b8ee38b38) } }, { { UINT64_C(0xcd991336c9600000), UINT64_C(0x3ffd37124cea4cde) }, { UINT64_C(0xf4612d4b0b9a507d), UINT64_C(0x3f9bfb9186e41376) } }, { { UINT64_C(0x975b2f3442520000), UINT64_C(0x3ffd406463b1b044) }, { UINT64_C(0x5151cb25af73b143), UINT64_C(0x3f9a9a92f79c803c) } }, { { UINT64_C(0xafe492bf6ff40000), UINT64_C(0x3ffd49a784bcd1b8) }, { UINT64_C(0xbfc6f675a9d236d6), UINT64_C(0x3f9cb5fb699b2d8a) } }, { { UINT64_C(0x37dcf60e61fc0000), UINT64_C(0x3ffd52dbdfc4c96b) }, { UINT64_C(0x792b6a99ccb74cd5), UINT64_C(0x3f9c36a5977bbf78) } }, { { UINT64_C(0x79fa00b120a00000), UINT64_C(0x3ffd5c01a39fbd68) }, { UINT64_C(0xa8e8966c101ef964), UINT64_C(0x3f9ba2eb74493cf9) } }, { { UINT64_C(0x6e52278edc8a0000), UINT64_C(0x3ffd6518fe4677ba) }, { UINT64_C(0x8ce60972502c7262), UINT64_C(0xbf996778b4ba074a) } }, { { UINT64_C(0xe578d520b4500000), UINT64_C(0x3ffd6e221cd9d0cd) }, { UINT64_C(0xd7e6bd0493f2e147), UINT64_C(0xbf9c1f87779b03b7) } }, { { UINT64_C(0xbe46fecd51220000), UINT64_C(0x3ffd771d2ba7efb3) }, { UINT64_C(0xa53c236eed64d17e), UINT64_C(0x3f9b29ff1c3c9184) } }, { { UINT64_C(0x432aeb609f4e0000), UINT64_C(0x3ffd800a563161c5) }, { UINT64_C(0x36db2b8f6963d1c4), UINT64_C(0xbf980a6ee4f42720) } }, { { UINT64_C(0x5a4b664a4c8e0000), UINT64_C(0x3ffd88e9c72e0b22) }, { UINT64_C(0x380cd03989062763), UINT64_C(0xbf9b59153bc13892) } }, { { UINT64_C(0x8b4b2b5056b80000), UINT64_C(0x3ffd91bba891f170) }, { UINT64_C(0xbeba3cb180b37bbb), UINT64_C(0x3f9ba7156185dba8) } }, { { UINT64_C(0xf34bb6d0e43a0000), UINT64_C(0x3ffd9a802391e232) }, { UINT64_C(0xc2d6ff9893868aa6), UINT64_C(0xbf9c67caee1e30b7) } }, { { UINT64_C(0x09d7c40d797a0000), UINT64_C(0x3ffda33760a7f605) }, { UINT64_C(0xc31bd7b2cb5c0041), UINT64_C(0xbf993a4e0233ff8a) } }, { { UINT64_C(0x8e1a4725558c0000), UINT64_C(0x3ffdabe18797f1f4) }, { UINT64_C(0xafcee056debabb79), UINT64_C(0x3f9b6f5c786e3d2a) } }, { { UINT64_C(0x0a4146ef8fd80000), UINT64_C(0x3ffdb47ebf73882a) }, { UINT64_C(0x2f72138cbfe19e49), UINT64_C(0x3f9c44f1d5b8a540) } }, { { UINT64_C(0x0ab442ce32020000), UINT64_C(0x3ffdbd0f2e9e7903) }, { UINT64_C(0x924cf83def704a7b), UINT64_C(0xbf9cd27aeb2ddd02) } }, { { UINT64_C(0x67e7ee54aef00000), UINT64_C(0x3ffdc592fad295b5) }, { UINT64_C(0x677177c285c32088), UINT64_C(0xbf9c99e7587ea99e) } }, { { UINT64_C(0xcc95d0a2ee7a0000), UINT64_C(0x3ffdce0a4923a587) }, { UINT64_C(0x39bf0ed23f498b8a), UINT64_C(0x3f976482ae042955) } }, { { UINT64_C(0xefe3ebe199060000), UINT64_C(0x3ffdd6753e032ea0) }, { UINT64_C(0xa245f78bb523ad43), UINT64_C(0xbf9c55594a060c67) } }, { { UINT64_C(0xc4ebb19611600000), UINT64_C(0x3ffdded3fd442364) }, { UINT64_C(0x48b0b7818878492c), UINT64_C(0xbf9cdf9689b34ee8) } }, { { UINT64_C(0x20c519e12f480000), UINT64_C(0x3ffde726aa1e754d) }, { UINT64_C(0x0ac5754344a16fae), UINT64_C(0xbf9cdd99950bae45) } }, { { UINT64_C(0x07d1e01a83900000), UINT64_C(0x3ffdef6d67328e22) }, { UINT64_C(0x53ed88c47c7d794c), UINT64_C(0x3f9b04907f9ccddb) } }, { { UINT64_C(0xece1931800460000), UINT64_C(0x3ffdf7a8568cb06c) }, { UINT64_C(0xd5fee3ef52260ad1), UINT64_C(0xbf9ce149b9528336) } }, { { UINT64_C(0xab9cc7f342f80000), UINT64_C(0x3ffdffd799a83ff9) }, { UINT64_C(0x08c8c8668867736b), UINT64_C(0x3f9c70c458bda55b) } }, { { UINT64_C(0xf339434640560000), UINT64_C(0x3ffe03fda8b97997) }, { UINT64_C(0x48d48bbef492995e), UINT64_C(0x3f9ceb47cb2aadd9) } }, { { UINT64_C(0xd525b3c1d1580000), UINT64_C(0x3ffe0809cf27f703) }, { UINT64_C(0x7761e5ff9b93854e), UINT64_C(0x3f9bf51e170ccb0f) } }, { { UINT64_C(0x6588257529b60000), UINT64_C(0x3ffe0c10500d63aa) }, { UINT64_C(0x9dd6be1d1cc2dc47), UINT64_C(0x3f9b2ef0aa83f286) } }, { { UINT64_C(0xa7b1cddae6fb0000), UINT64_C(0x3ffe10113b153c8e) }, { UINT64_C(0xa32f8b327d463390), UINT64_C(0xbf9b8d4296259492) } }, { { UINT64_C(0x39e15a52a3160000), UINT64_C(0x3ffe140c9faa1e54) }, { UINT64_C(0x3b07655c599a50e7), UINT64_C(0x3f9829611295daec) } }, { { UINT64_C(0xa4eb8e97d1450000), UINT64_C(0x3ffe18028cf72976) }, { UINT64_C(0x8b1f2e108f3d37be), UINT64_C(0x3f9b9ac318308c38) } }, { { UINT64_C(0xde3b513a9dcd0000), UINT64_C(0x3ffe1bf311e95d00) }, { UINT64_C(0x54d1f10759819ad0), UINT64_C(0xbf9b3c8ff1c15395) } }, { { UINT64_C(0x642415d473840000), UINT64_C(0x3ffe1fde3d30e812) }, { UINT64_C(0xc99743f3c4617c37), UINT64_C(0x3f9c3c458dd53d12) } }, { { UINT64_C(0x8080ecc61a990000), UINT64_C(0x3ffe23c41d42727c) }, { UINT64_C(0x528bbef9ead829c7), UINT64_C(0xbf9cfb113740031e) } }, { { UINT64_C(0x805784ee0e3b0000), UINT64_C(0x3ffe27a4c0585cbf) }, { UINT64_C(0x772f673312a17ee0), UINT64_C(0xbf9a60c515c0f4b1) } }, { { UINT64_C(0x0f3f401624140000), UINT64_C(0x3ffe2b803473f7ad) }, { UINT64_C(0xa8e8966c101ef964), UINT64_C(0x3f99a2eb74493cf9) } }, { { UINT64_C(0x614278cd16990000), UINT64_C(0x3ffe2f56875eb3f2) }, { UINT64_C(0x129c318704371e3d), UINT64_C(0x3f9b88c5d320344f) } }, { { UINT64_C(0x6c86b9205fa40000), UINT64_C(0x3ffe3327c6ab49ca) }, { UINT64_C(0x060bf80459dd880d), UINT64_C(0x3f9b1010939e6edc) } }, { { UINT64_C(0x2404772a151d0000), UINT64_C(0x3ffe36f3ffb6d916) }, { UINT64_C(0xd90eed123bda5ea2), UINT64_C(0xbf9b93193dd58663) } }, { { UINT64_C(0x6cccab240e900000), UINT64_C(0x3ffe3abb3faa0216) }, { UINT64_C(0x548017167cabbd62), UINT64_C(0x3f9c3e5a84738c6a) } }, { { UINT64_C(0x66ae2a7ada550000), UINT64_C(0x3ffe3e7d9379f701) }, { UINT64_C(0xfeddee4a96320fd3), UINT64_C(0x3f9b6369ad81817b) } }, { { UINT64_C(0x9670761d14ab0000), UINT64_C(0x3ffe423b07e986aa) }, { UINT64_C(0x7017a3e16237ddd4), UINT64_C(0xbf9bd93cd9e77a52) } }, { { UINT64_C(0x8a4d7ffe02670000), UINT64_C(0x3ffe45f3a98a2073) }, { UINT64_C(0x9f0841ae8379c6ad), UINT64_C(0x3f9b75541c775be8) } }, { { UINT64_C(0xafe492bf6ff50000), UINT64_C(0x3ffe49a784bcd1b8) }, { UINT64_C(0x00e4262958b724aa), UINT64_C(0xbf9b2812599349d5) } }, { { UINT64_C(0x44a6deff99870000), UINT64_C(0x3ffe4d56a5b33cec) }, { UINT64_C(0x5e02036d27593a40), UINT64_C(0x3f9bfad1e37de08f) } }, { { UINT64_C(0x8dde949378b20000), UINT64_C(0x3ffe510118708a8f) }, { UINT64_C(0x39a60252b34c6c66), UINT64_C(0x3f9b348f1454e8c9) } }, { { UINT64_C(0xdb1b0ca63aac0000), UINT64_C(0x3ffe54a6e8ca5438) }, { UINT64_C(0x724e7f04c9712103), UINT64_C(0xbf9c2f1784ce0b08) } }, { { UINT64_C(0x3c38d8bd28d70000), UINT64_C(0x3ffe5848226989d3) }, { UINT64_C(0xe30d5e9e8139d7b1), UINT64_C(0xbf9ca8e7bb5885dc) } }, { { UINT64_C(0x4aaeb3f012220000), UINT64_C(0x3ffe5be4d0cb5143) }, { UINT64_C(0xcfc072e22bbeab1d), UINT64_C(0x3f9b2ce7e40053a5) } }, { { UINT64_C(0xeb8cb1ac05cd0000), UINT64_C(0x3ffe5f7cff41e09a) }, { UINT64_C(0x7b2e29701f76805b), UINT64_C(0xbf9b4fb2c4a0dc9f) } }, { { UINT64_C(0x8406a5a171de0000), UINT64_C(0x3ffe6310b8f55304) }, { UINT64_C(0xb92584a992692c7d), UINT64_C(0x3f9c003332544881) } }, { { UINT64_C(0xbcd2edb4390e0000), UINT64_C(0x3ffe66a008e4788c) }, { UINT64_C(0x1e948033b63cbaad), UINT64_C(0x3f9c4c1a88f0e7a4) } }, { { UINT64_C(0xa08099572f210000), UINT64_C(0x3ffe6a2af9e5a0f0) }, { UINT64_C(0xb498acfb49ce0de0), UINT64_C(0xbf9c0665eae13d10) } }, { { UINT64_C(0x9d97df07e3570000), UINT64_C(0x3ffe6db196a76194) }, { UINT64_C(0xe0dbeb3195d40b4d), UINT64_C(0xbf9c7679e5a1e4a0) } }, { { UINT64_C(0xbe5167fbdc810000), UINT64_C(0x3ffe7133e9b156c7) }, { UINT64_C(0x214a0b718fc30587), UINT64_C(0x3f9bb935e716c7cb) } }, { { UINT64_C(0x3c6e5783fd150000), UINT64_C(0x3ffe74b1fd64e075) }, { UINT64_C(0xf9baffdf4ec86923), UINT64_C(0x3f99923a7aefc37e) } }, { { UINT64_C(0x77bc87e125eb0000), UINT64_C(0x3ffe782bdbfdda65) }, { UINT64_C(0x9e967e496249719b), UINT64_C(0xbf9c56e82c9d846f) } }, { { UINT64_C(0x409eab77f21a0000), UINT64_C(0x3ffe7ba18f93502e) }, { UINT64_C(0xca9ea124a56a0c75), UINT64_C(0xbf9bc097ea4900b7) } }, { { UINT64_C(0x5d12ecd77fe70000), UINT64_C(0x3ffe7f1322182cf1) }, { UINT64_C(0x10c6f5fd964c2ae2), UINT64_C(0xbf9c0512c0ac2d35) } }, { { UINT64_C(0x2dbdc0426c3c0000), UINT64_C(0x3ffe82809d5be707) }, { UINT64_C(0x3eae70d10c173b0b), UINT64_C(0x3f9b3a309736edbb) } }, { { UINT64_C(0x61086fce864a0000), UINT64_C(0x3ffe85ea0b0b27b2) }, { UINT64_C(0xe34dbc4e07ae924c), UINT64_C(0x3f9af5979367f112) } }, { { UINT64_C(0xb406ea2c7d920000), UINT64_C(0x3ffe894f74b06ef8) }, { UINT64_C(0xa21469a1f11911ae), UINT64_C(0xbf9c54ede83a0018) } }, { { UINT64_C(0xdb3688a85fb20000), UINT64_C(0x3ffe8cb0e3b4b3bb) }, { UINT64_C(0x16331134b0c9d172), UINT64_C(0xbf9c910d207f0195) } }, { { UINT64_C(0xcfe43f50847d0000), UINT64_C(0x3ffe900e6160002c) }, { UINT64_C(0x7603fba7d2ba264a), UINT64_C(0x3f9a82887e54848c) } }, { { UINT64_C(0xe9cc865b3dd10000), UINT64_C(0x3ffe9367f6da0ab2) }, { UINT64_C(0xf32be2709a01f861), UINT64_C(0xbf9b225541e18baf) } }, { { UINT64_C(0x5efec49153140000), UINT64_C(0x3ffe96bdad2acb5f) }, { UINT64_C(0x23be56be998e8e8f), UINT64_C(0x3f9cb7c0b9db2fcb) } }, { { UINT64_C(0xfde95734abd30000), UINT64_C(0x3ffe9a0f8d3b0e04) }, { UINT64_C(0xa3979bbb0ca9365f), UINT64_C(0xbf979f19ce27d2dc) } }, { { UINT64_C(0x3666559207480000), UINT64_C(0x3ffe9d5d9fd5010b) }, { UINT64_C(0x548017167cabbd62), UINT64_C(0x3f9b3e5a84738c6a) } }, { { UINT64_C(0xce6312ebb81d0000), UINT64_C(0x3ffea0a7eda4c112) }, { UINT64_C(0xb1bf6bcd429e9fdf), UINT64_C(0xbf995a727dbaad60) } }, { { UINT64_C(0xed0798d1aa210000), UINT64_C(0x3ffea3ee7f38e181) }, { UINT64_C(0x27ac1ab989c42834), UINT64_C(0x3f9ca51584c9dc56) } }, { { UINT64_C(0x7bd560a3fee10000), UINT64_C(0x3ffea7315d02f20c) }, { UINT64_C(0x034400340f2319dd), UINT64_C(0xbf9cdacbac02cace) } }, { { UINT64_C(0x37cde37c86b20000), UINT64_C(0x3ffeaa708f58014d) }, { UINT64_C(0x4594babbdab2fd20), UINT64_C(0x3f9b06a19b5bedec) } }, { { UINT64_C(0x2d1562d61af70000), UINT64_C(0x3ffeadac1e711c83) }, { UINT64_C(0xfab077c80ac91e28), UINT64_C(0x3f9bfbfa94970bb9) } }, { { UINT64_C(0xbd7a6ed4e1b10000), UINT64_C(0x3ffeb0e4126bcc86) }, { UINT64_C(0xf14daa7bc7cc5b07), UINT64_C(0xbf9cb28c4122d931) } }, { { UINT64_C(0xbd978b98f7df0000), UINT64_C(0x3ffeb418734a9008) }, { UINT64_C(0x85d1b265e993decb), UINT64_C(0xbf9b039d32863d26) } }, { { UINT64_C(0xa4b4b71433640000), UINT64_C(0x3ffeb74948f5532d) }, { UINT64_C(0x9c8e8a9007c6ffb4), UINT64_C(0xbf9cce44c707d13d) } }, { { UINT64_C(0x40ef87ede14b0000), UINT64_C(0x3ffeba769b39e496) }, { UINT64_C(0x4add58e787eb935e), UINT64_C(0xbf9a735f3571b2e4) } }, { { UINT64_C(0xdb516de081360000), UINT64_C(0x3ffebda071cc67e6) }, { UINT64_C(0x88cea5ceba447906), UINT64_C(0x3f9cb4d5660336e2) } }, { { UINT64_C(0x362d9a9a55c70000), UINT64_C(0x3ffec0c6d447c5dd) }, { UINT64_C(0x155dfdf1fd11696e), UINT64_C(0x3f9c17b370ba83c0) } }, { { UINT64_C(0x533698b4e49b0000), UINT64_C(0x3ffec3e9ca2e1a05) }, { UINT64_C(0x823235b0f583d7a7), UINT64_C(0x3f9cec0c07c38978) } }, { { UINT64_C(0x760bc9b188c40000), UINT64_C(0x3ffec7095ae91e1c) }, { UINT64_C(0xaf4da97307d1076b), UINT64_C(0x3f9c322631fb315a) } }, { { UINT64_C(0x635fee390c0b0000), UINT64_C(0x3ffeca258dca9331) }, { UINT64_C(0xedea65e0c4e7d82d), UINT64_C(0xbf9c3e17e7a7e746) } }, { { UINT64_C(0x6c243749114c0000), UINT64_C(0x3ffecd3e6a0ca890) }, { UINT64_C(0xa670e214b298ab12), UINT64_C(0x3f9c2bbae931e8da) } }, { { UINT64_C(0x67318975dc0e0000), UINT64_C(0x3ffed053f6d26089) }, { UINT64_C(0xf4e409432bd61602), UINT64_C(0x3f9cea58d8245529) } }, { { UINT64_C(0x5297837adb4b0000), UINT64_C(0x3ffed3663b27f31d) }, { UINT64_C(0x54842668a312854d), UINT64_C(0xbf9b38daa2d672ec) } }, { { UINT64_C(0xefe3ebe199050000), UINT64_C(0x3ffed6753e032ea0) }, { UINT64_C(0xbb7410e895b8a57a), UINT64_C(0x3f9c554d6bf3e730) } }, { { UINT64_C(0x4c3c406eb4640000), UINT64_C(0x3ffed9810643d661) }, { UINT64_C(0x09915e038a135bdf), UINT64_C(0x3f9c05d328adc61c) } }, { { UINT64_C(0xc5e673abad440000), UINT64_C(0x3ffedc899ab3ff56) }, { UINT64_C(0x0d27c064978bc6f5), UINT64_C(0x3f9b8c6339fa7bd1) } }, { { UINT64_C(0xc4bef483c68b0000), UINT64_C(0x3ffedf8f02086af2) }, { UINT64_C(0xebb8f273d9820bc8), UINT64_C(0xbf9c0baa11f1460a) } }, { { UINT64_C(0x01fbaaa67e3c0000), UINT64_C(0x3ffee29142e0e014) }, { UINT64_C(0x4593546e23de0435), UINT64_C(0xbf9bd6541223b831) } }, { { UINT64_C(0xe561911a9bac0000), UINT64_C(0x3ffee59063c8822c) }, { UINT64_C(0x0c41cac421925a11), UINT64_C(0x3f9c9b0de815b6fb) } }, { { UINT64_C(0x2aa21a3c7f020000), UINT64_C(0x3ffee88c6b3626a7) }, { UINT64_C(0x8b64e3bde351d0f1), UINT64_C(0xbf990e3aeafe4f83) } }, { { UINT64_C(0xb0d4b5c673bb0000), UINT64_C(0x3ffeeb855f8ca88f) }, { UINT64_C(0x8d7b00a45b6d1082), UINT64_C(0x3f991db401cf2969) } }, { { UINT64_C(0x07d6dc1f27ef0000), UINT64_C(0x3ffeee7b471b3a95) }, { UINT64_C(0x3f54e360fd76a481), UINT64_C(0x3f9c21f23cd188a0) } }, { { UINT64_C(0x03b21928c2160000), UINT64_C(0x3ffef16e281db763) }, { UINT64_C(0x3461c5e7233e545f), UINT64_C(0xbf992854f795db44) } }, { { UINT64_C(0x54e4d5be4f700000), UINT64_C(0x3ffef45e08bcf065) }, { UINT64_C(0x947a3126425d9d0a), UINT64_C(0x3f9c07e81f4c1573) } }, { { UINT64_C(0xdd7a1b65f6390000), UINT64_C(0x3ffef74aef0efafa) }, { UINT64_C(0xa6b530fa4d847e3f), UINT64_C(0xbf9b7bc1e9882648) } }, { { UINT64_C(0x362928b9ed750000), UINT64_C(0x3ffefa34e1177c23) }, { UINT64_C(0x698bfda1e41b89bd), UINT64_C(0xbf9a856dc2b529ad) } }, { { UINT64_C(0x942b221ce0d10000), UINT64_C(0x3ffefd1be4c7f2af) }, { UINT64_C(0x9732fae5130be48b), UINT64_C(0x3f97a275c854f5bb) } }, { { UINT64_C(0x0000000000000000), UINT64_C(0x3fff000000000000) }, { UINT64_C(0x0000000000000000), UINT64_C(0x0000000000000000) } }, }; static const CrtLogqPoly crt_log2q_poly = { .a1 = { UINT64_C(0xe1777d0ffda0d23a), UINT64_C(0x3fff71547652b82f) }, .a3 = { UINT64_C(0xd749fc15522c79f0), UINT64_C(0x3ffbec709dc3a03f) }, .a5 = { UINT64_C(0xe792ca4ad4524b19), UINT64_C(0x3ff92776c50ef9bf) }, .a7 = { UINT64_C(0x99bec232e9034cd9), UINT64_C(0x3ff6a61762a7aded) }, .a9 = UINT64_C(0x3f4484b13d791e16), .a11 = UINT64_C(0x3f20c9b2cc2ebb7c), .b1_hi = { UINT64_C(0xe000000000000000), UINT64_C(0x3fff71547652b82f) }, .b1_lo = { UINT64_C(0x3a7d11d6aef551bb), UINT64_C(0x3fc7777d0ffda0d2) }, .b3 = { UINT64_C(0xd749fc15522bc421), UINT64_C(0x3ffbec709dc3a03f) }, .b5 = { UINT64_C(0xe792ca7330935465), UINT64_C(0x3ff92776c50ef9bf) }, .b7 = { UINT64_C(0x93f6461984387d2f), UINT64_C(0x3ff6a61762a7aded) }, .b9 = { UINT64_C(0x8f7e259f771bd47f), UINT64_C(0x3ff4484b13d7c02a) }, .b11 = { UINT64_C(0x54f5f3ffc0a6e691), UINT64_C(0x3ff20c9a84994023) }, .b13 = { UINT64_C(0x345bde1a31d395ee), UINT64_C(0x3fefc68f568d2807) }, .b15 = UINT64_C(0x3ed89f3b19f14170), .b17 = UINT64_C(0x3eb5b9a1109d51cf), .b19 = UINT64_C(0x3e93861fe818e18e), }; // `logq`'s Proc1 table (lib/compiler_rt/log.zig `tab`), hi/lo pairs. static const CrtU128 crt_logq_tab[LOGQ_SIZE + 1][2] = { { { UINT64_C(0x0000000000000000), UINT64_C(0x0000000000000000) }, { UINT64_C(0x0000000000000000), UINT64_C(0x0000000000000000) } }, { { UINT64_C(0x8fc3769039000000), UINT64_C(0x3ff7fe02a6b10678) }, { UINT64_C(0xc3ef9a073a876702), UINT64_C(0x3f9bdc282d2b3db2) } }, { { UINT64_C(0x3cf9eda74d400000), UINT64_C(0x3ff8fc0a8b0fc03e) }, { UINT64_C(0x16fc223acca2ebf0), UINT64_C(0xbf9b0a8552414fc4) } }, { { UINT64_C(0xaa927f54c7200000), UINT64_C(0x3ff97b91b07d5b11) }, { UINT64_C(0xab5bc5cceecb4882), UINT64_C(0xbf9c287fc46561df) } }, { { UINT64_C(0x04cf8fc13c800000), UINT64_C(0x3ff9f829b0e78330) }, { UINT64_C(0xeb3220ca52e20939), UINT64_C(0xbf9b0dd605151051) } }, { { UINT64_C(0xfa9015b202b00000), UINT64_C(0x3ffa39e87b9febd5) }, { UINT64_C(0x3dc859cfe2e178a8), UINT64_C(0xbf9c1bac6e550a3c) } }, { { UINT64_C(0xc4634f2a1ee00000), UINT64_C(0x3ffa77458f632dcf) }, { UINT64_C(0x0d9544ec6583eb3a), UINT64_C(0x3f9c2960b1e4dfb8) } }, { { UINT64_C(0xec28d14c7da00000), UINT64_C(0x3ffab42dd711971b) }, { UINT64_C(0x2fc0eb08d862221d), UINT64_C(0xbf992645ad50c767) } }, { { UINT64_C(0x6617cc9716f00000), UINT64_C(0x3ffaf0a30c01162a) }, { UINT64_C(0x5991495b4d31cf5c), UINT64_C(0xbf9a4cd0ece59716) } }, { { UINT64_C(0x0e8625c173e00000), UINT64_C(0x3ffb16536eea37ae) }, { UINT64_C(0xd2ccbd2caa9640ca), UINT64_C(0xbf9c66d0dc92deb7) } }, { { UINT64_C(0x0929983761080000), UINT64_C(0x3ffb341d7961bd1d) }, { UINT64_C(0xad243dcc3578cf7e), UINT64_C(0xbf9c976457ef2f89) } }, { { UINT64_C(0xf69278e686a00000), UINT64_C(0x3ffb51b073f06183) }, { UINT64_C(0x04de1f086f5cb4b1), UINT64_C(0x3f9c7c8ac25e4e3f) } }, { { UINT64_C(0xb9be499b9ed00000), UINT64_C(0x3ffb6f0d28ae56b4) }, { UINT64_C(0xf65087fdf23812f4), UINT64_C(0x3f9b9b640ce50c1e) } }, { { UINT64_C(0x0f5acb42a6600000), UINT64_C(0x3ffb8c345d6319b2) }, { UINT64_C(0x1bf283b3b4b8662d), UINT64_C(0xbf9b254bca8fd9fc) } }, { { UINT64_C(0x3650bd22a9c00000), UINT64_C(0x3ffba926d3a4ad56) }, { UINT64_C(0x11769cc680ef5589), UINT64_C(0x3f9cd5263cd4fb3f) } }, { { UINT64_C(0x315d617ef8180000), UINT64_C(0x3ffbc5e548f5bc74) }, { UINT64_C(0x723c88d353ee9c18), UINT64_C(0xbf9be4e896269950) } }, { { UINT64_C(0x5e9ea87ffe200000), UINT64_C(0x3ffbe27076e2af2e) }, { UINT64_C(0xbfe80906a822f768), UINT64_C(0xbf9761eaa246b143) } }, { { UINT64_C(0xaaa2e5199f900000), UINT64_C(0x3ffbfec9131dbeab) }, { UINT64_C(0xd409017630a93931), UINT64_C(0x3f9c9271dff48f15) } }, { { UINT64_C(0x96697717a40c0000), UINT64_C(0x3ffc0d77e7cd08e5) }, { UINT64_C(0x6340e183be2031c6), UINT64_C(0x3f99574712132d3f) } }, { { UINT64_C(0x029060468e580000), UINT64_C(0x3ffc1b72ad52f67a) }, { UINT64_C(0x4ca997609536a037), UINT64_C(0x3f9cae73f3bc7ec8) } }, { { UINT64_C(0x34c05dc710200000), UINT64_C(0x3ffc29552f81ff52) }, { UINT64_C(0x8f081d60452c9fc1), UINT64_C(0xbf9b20b2ef60436f) } }, { { UINT64_C(0x43bcd96c55e40000), UINT64_C(0x3ffc371fc201e8f7) }, { UINT64_C(0x558ad6fcd608bb1b), UINT64_C(0xbf9bd80d17e0cd92) } }, { { UINT64_C(0xe67d3d950f880000), UINT64_C(0x3ffc44d2b6ccb7d1) }, { UINT64_C(0x4584faad83fa4fec), UINT64_C(0xbf98e1f3be9a8337) } }, { { UINT64_C(0x7a2e401d6e3c0000), UINT64_C(0x3ffc526e5e3a1b43) }, { UINT64_C(0xa888aa87317b14f8), UINT64_C(0x3f9b6334db798c76) } }, { { UINT64_C(0x3c873e20a0740000), UINT64_C(0x3ffc5ff3070a793d) }, { UINT64_C(0x65501060ce71fa98), UINT64_C(0xbf9cedc45019551c) } }, { { UINT64_C(0xc8d54765c4cc0000), UINT64_C(0x3ffc6d60fe719d21) }, { UINT64_C(0xd18e4a7c22362e49), UINT64_C(0xbf9a790e412e6d3e) } }, { { UINT64_C(0x91be36b2d6a00000), UINT64_C(0x3ffc7ab890210d90) }, { UINT64_C(0x362042cad5d8c597), UINT64_C(0x3f9a820191ff8525) } }, { { UINT64_C(0x0902009017dc0000), UINT64_C(0x3ffc87fa06520c91) }, { UINT64_C(0x6b7eb4ebea28a6cd), UINT64_C(0x3f9b32ef5a55704b) } }, { { UINT64_C(0x47641307538c0000), UINT64_C(0x3ffc9525a9cf456b) }, { UINT64_C(0x8611d132d71d84ac), UINT64_C(0xbf9ada62766be825) } }, { { UINT64_C(0x3193711b07a80000), UINT64_C(0x3ffca23bc1fe2b56) }, { UINT64_C(0xd8b32de61c04cf95), UINT64_C(0x3f9c98c27e3f1b66) } }, { { UINT64_C(0x2d8ce601937c0000), UINT64_C(0x3ffcaf3c94e80bff) }, { UINT64_C(0x9a5d50ca14a7622f), UINT64_C(0x3f9b9eb976769b8b) } }, { { UINT64_C(0x629f9ce890e00000), UINT64_C(0x3ffcbc286742d8cd) }, { UINT64_C(0x46c83cd6d19e5e9b), UINT64_C(0x3f9cea9e1e2c3dca) } }, { { UINT64_C(0x1ac25d81ef300000), UINT64_C(0x3ffcc8ff7c79a9a2) }, { UINT64_C(0x17dca47c9d1e1d98), UINT64_C(0xbf961976d471342b) } }, { { UINT64_C(0x15b910d65f940000), UINT64_C(0x3ffcd5c216b4fbb9) }, { UINT64_C(0xd4063968ad2332f8), UINT64_C(0xbf9b5ff1e1c98c2e) } }, { { UINT64_C(0x5e9ea87ffe200000), UINT64_C(0x3ffce27076e2af2e) }, { UINT64_C(0xbfe80906a822f768), UINT64_C(0xbf9861eaa246b143) } }, { { UINT64_C(0x65218de543700000), UINT64_C(0x3ffcef0adcbdc593) }, { UINT64_C(0x683a47c3a3ca5835), UINT64_C(0x3f9b06429f5a5098) } }, { { UINT64_C(0xa8d55466c3780000), UINT64_C(0x3ffcfb9186d5e3e2) }, { UINT64_C(0x22ad91640bd8b2fc), UINT64_C(0x3f9c4d2ca09202c2) } }, { { UINT64_C(0x0dae27bd0b600000), UINT64_C(0x3ffd0402594b4d04) }, { UINT64_C(0x43f105ee37cafa25), UINT64_C(0xbf9b16a1bbb899f3) } }, { { UINT64_C(0x35f73f7a01880000), UINT64_C(0x3ffd0a324e27390e) }, { UINT64_C(0x820fb4690e4910e3), UINT64_C(0xbf9cfe78eb90fe52) } }, { { UINT64_C(0x5189fa0ab4cc0000), UINT64_C(0x3ffd1058bf9ae4ad) }, { UINT64_C(0x2076376a5960e735), UINT64_C(0xbf9c9c60f598d3a3) } }, { { UINT64_C(0xe039cc7d57100000), UINT64_C(0x3ffd1675cababa60) }, { UINT64_C(0x4a43c19f534c3c8e), UINT64_C(0x3f9bb8b823f067d0) } }, { { UINT64_C(0xafbc68e754040000), UINT64_C(0x3ffd1c898c16999f) }, { UINT64_C(0x4a1350a26c9b5535), UINT64_C(0xbf9bc443cc477d11) } }, { { UINT64_C(0x5a242853da760000), UINT64_C(0x3ffd22941fbcf796) }, { UINT64_C(0x0b0f13a31703b136), UINT64_C(0xbf9a5e685a2caa59) } }, { { UINT64_C(0x35eb49304fc20000), UINT64_C(0x3ffd2895a13de86a) }, { UINT64_C(0x834f50a903b31253), UINT64_C(0xbf9cf8d3a52b8aa6) } }, { { UINT64_C(0x09c2cc91a8500000), UINT64_C(0x3ffd2e8e2bae11d3) }, { UINT64_C(0x7d91378a909a6793), UINT64_C(0x3f9c03679bdbbd6b) } }, { { UINT64_C(0x4d645674fedc0000), UINT64_C(0x3ffd347dd9a987d5) }, { UINT64_C(0xb3b30ef57f83ca28), UINT64_C(0x3f9c821ee510a580) } }, { { UINT64_C(0x9c72f35cd7400000), UINT64_C(0x3ffd3a64c556945e) }, { UINT64_C(0x7e029e46e1334dfe), UINT64_C(0x3f9ca11beb7a3cee) } }, { { UINT64_C(0x3bd0c127df4c0000), UINT64_C(0x3ffd404308686a7e) }, { UINT64_C(0x9d91a2da0d4f2207), UINT64_C(0x3f9b92985641827d) } }, { { UINT64_C(0x27d0b7b37b340000), UINT64_C(0x3ffd4618bc21c5ec) }, { UINT64_C(0x67fe9778d8694229), UINT64_C(0xbf9ac65df511a65b) } }, { { UINT64_C(0x0db4c9949f700000), UINT64_C(0x3ffd4be5f957778a) }, { UINT64_C(0x3185a1f581529353), UINT64_C(0xbf9a3cdc28d5974f) } }, { { UINT64_C(0xd09c93d60cfa0000), UINT64_C(0x3ffd51aad872df82) }, { UINT64_C(0x7cbfae095f10fcd3), UINT64_C(0x3f9c5e311d4f4f35) } }, { { UINT64_C(0xc549ab6ca0da0000), UINT64_C(0x3ffd5767717455a6) }, { UINT64_C(0xce6c0841fd7ceb87), UINT64_C(0xbf9bf42ff0747cbc) } }, { { UINT64_C(0xa508d8e0f7200000), UINT64_C(0x3ffd5d1bdbf5809c) }, { UINT64_C(0x4bd65b44f6b20634), UINT64_C(0xbf97eea60c7f4b59) } }, { { UINT64_C(0x52f6f5f22a600000), UINT64_C(0x3ffd62c82f2b9c79) }, { UINT64_C(0xd257f44b5002c8cd), UINT64_C(0x3f99ca2e7226c55d) } }, { { UINT64_C(0xec442be1014e0000), UINT64_C(0x3ffd686c81e9b14a) }, { UINT64_C(0x8672b32ca47f535d), UINT64_C(0x3f9ac34b25bdbda3) } }, { { UINT64_C(0x38c139318d720000), UINT64_C(0x3ffd6e08eaa2ba1e) }, { UINT64_C(0x8cd3f047b933d485), UINT64_C(0xbf9c07a1f9d2a305) } }, { { UINT64_C(0x69ce24c53fae0000), UINT64_C(0x3ffd739d7f6bbd00) }, { UINT64_C(0x918ad34542221b6c), UINT64_C(0xbf9c8210d2a910f7) } }, { { UINT64_C(0x27c15da47fa80000), UINT64_C(0x3ffd792a55fdd47a) }, { UINT64_C(0xe7c702842a1590ae), UINT64_C(0xbf9b297ea603cd10) } }, { { UINT64_C(0x364b3a5e7b4a0000), UINT64_C(0x3ffd7eaf83b82afc) }, { UINT64_C(0xbf71ee8d4b3cd067), UINT64_C(0x3f9b50437160cbfc) } }, { { UINT64_C(0x7493b1465e120000), UINT64_C(0x3ffd842d1da1e8b1) }, { UINT64_C(0xe0c7f06a12cd88c3), UINT64_C(0xbf9b899770fb9eb8) } }, { { UINT64_C(0xab5a718811040000), UINT64_C(0x3ffd89a3386c1425) }, { UINT64_C(0xc97f5d4b9c15fc12), UINT64_C(0xbf9af549800739af) } }, { { UINT64_C(0x77e1769d56980000), UINT64_C(0x3ffd8f11e873662c) }, { UINT64_C(0x0e45410ee8ca0d17), UINT64_C(0x3f9ba29979cbfcbc) } }, { { UINT64_C(0xaba4cdd147d20000), UINT64_C(0x3ffd947941c2116f) }, { UINT64_C(0x11af82f2c0e6730e), UINT64_C(0xbf9cf22a2b6b19ed) } }, { { UINT64_C(0xacba92eec4780000), UINT64_C(0x3ffd99d958117e08) }, { UINT64_C(0x5f44a3225b5d8f8f), UINT64_C(0x3f9a8dfba4bb71c9) } }, { { UINT64_C(0xbf2b68d766f40000), UINT64_C(0x3ffd9f323ecbf984) }, { UINT64_C(0xb34547d7c2b38ad8), UINT64_C(0x3f974886067d20ff) } }, { { UINT64_C(0xa2bfca6b70ec0000), UINT64_C(0x3ffda484090e5bb0) }, { UINT64_C(0x3949ca12eb97e6bb), UINT64_C(0xbf9c62da6c6290af) } }, { { UINT64_C(0x98d484ff52f20000), UINT64_C(0x3ffda9cec9a9a084) }, { UINT64_C(0xe26768c44e9f35aa), UINT64_C(0x3f9b50d7be11fc8e) } }, { { UINT64_C(0xb1133844a15e0000), UINT64_C(0x3ffdaf1293247786) }, { UINT64_C(0x378f9b7aa0ef6685), UINT64_C(0xbf9aebf9e3b2fb68) } }, { { UINT64_C(0x28cbeedaae980000), UINT64_C(0x3ffdb44f77bcc8f6) }, { UINT64_C(0x684301e7888d5449), UINT64_C(0x3f9cc3c779029d5a) } }, { { UINT64_C(0xb598fb14f88e0000), UINT64_C(0x3ffdb9858969310f) }, { UINT64_C(0x31010282d8264afa), UINT64_C(0x3f9ce1cf79039d5a) } }, { { UINT64_C(0xbf7861d37abc0000), UINT64_C(0x3ffdbeb4d9da71b7) }, { UINT64_C(0xdffb9899ad6da116), UINT64_C(0xbf9af29b495a7c83) } }, { { UINT64_C(0xd73b3c14b7aa0000), UINT64_C(0x3ffdc3dd7a7cdad4) }, { UINT64_C(0x27ebf6601862d429), UINT64_C(0xbf9b649f44ae71a8) } }, { { UINT64_C(0x1ac25d81ef300000), UINT64_C(0x3ffdc8ff7c79a9a2) }, { UINT64_C(0x17dca47c9d1e1d98), UINT64_C(0xbf971976d471342b) } }, { { UINT64_C(0xb7b7d2bcaad00000), UINT64_C(0x3ffdce1af0b85f3e) }, { UINT64_C(0x0b2f6f2b23998791), UINT64_C(0x3f9c33a4e218c8c0) } }, { { UINT64_C(0x561dec8cbebe0000), UINT64_C(0x3ffdd32fe7e00ebd) }, { UINT64_C(0xaf7eb72c1ff893cd), UINT64_C(0x3f9c1b7e8c55a6ff) } }, { { UINT64_C(0xe50515ba2eca0000), UINT64_C(0x3ffdd83e7258a2f3) }, { UINT64_C(0x3edc40a13fe57c00), UINT64_C(0xbf9c7778ad7b599f) } }, { { UINT64_C(0x0bee626a49d20000), UINT64_C(0x3ffddd46a04c1c4a) }, { UINT64_C(0x6328a06054db5e01), UINT64_C(0xbf9a23c02c15f2f6) } }, { { UINT64_C(0x61cbd8f459540000), UINT64_C(0x3ffde24881a7c6c2) }, { UINT64_C(0x764e1ed219643a85), UINT64_C(0x3f9c48c6c5403df1) } }, { { UINT64_C(0x7e37da36f3cc0000), UINT64_C(0x3ffde744261d6878) }, { UINT64_C(0x55a11feaedf56a94), UINT64_C(0xbf9b2e44bfb5e71d) } }, { { UINT64_C(0x0175cee53f360000), UINT64_C(0x3ffdec399d2468cc) }, { UINT64_C(0xaf8a138d77633327), UINT64_C(0xbf9c8d2027bb4681) } }, { { UINT64_C(0xcb35c83b11320000), UINT64_C(0x3ffdf128f5faf06e) }, { UINT64_C(0x17ac271709c2b464), UINT64_C(0xbf9a851461536ddd) } }, { { UINT64_C(0xc61456c3cb6c0000), UINT64_C(0x3ffdf6123fa7028a) }, { UINT64_C(0xc0b049c0fb73b4de), UINT64_C(0x3f9ca0a432a2414c) } }, { { UINT64_C(0xed9afb3e4ea80000), UINT64_C(0x3ffdfaf588f78f31) }, { UINT64_C(0xf184dc7b6e634ba1), UINT64_C(0x3f9bafec6d4cde2e) } }, { { UINT64_C(0x85597d0364c80000), UINT64_C(0x3ffdffd2e0857f49) }, { UINT64_C(0xea68e29866b3b38b), UINT64_C(0x3f9baf246e3379d3) } }, { { UINT64_C(0xec69c695d7ee0000), UINT64_C(0x3ffe02552a5a5d0f) }, { UINT64_C(0x6261a75dc4f512ad), UINT64_C(0x3f9cfff1a3725c5c) } }, { { UINT64_C(0x265fcc1008b20000), UINT64_C(0x3ffe04bdf9da926d) }, { UINT64_C(0x0f10c7053f04f1e0), UINT64_C(0x3f9cdf6a6d08e447) } }, { { UINT64_C(0x04e5796389110000), UINT64_C(0x3ffe0723e5c1cdf4) }, { UINT64_C(0x9089087bae740509), UINT64_C(0x3f9cbaad95bd2eba) } }, { { UINT64_C(0x0b91fda94ff40000), UINT64_C(0x3ffe0986f4f57352) }, { UINT64_C(0xa26389f13c4bde3a), UINT64_C(0x3f9bfe038113f135) } }, { { UINT64_C(0x2b69897bb33e0000), UINT64_C(0x3ffe0be72e4252a8) }, { UINT64_C(0xca2c12ee56f6c90b), UINT64_C(0xbf939649bc990440) } }, { { UINT64_C(0xbf6eae5de9690000), UINT64_C(0x3ffe0e44985d1cc8) }, { UINT64_C(0xa2da185e84859e36), UINT64_C(0x3f9c8f8d1fbacf70) } }, { { UINT64_C(0x6fde3ec9b0b80000), UINT64_C(0x3ffe109f39e2d4c9) }, { UINT64_C(0x337c0da8f63aefc7), UINT64_C(0xbf9cb9d06df4e0c8) } }, { { UINT64_C(0xc53012319c7d0000), UINT64_C(0x3ffe12f719593efb) }, { UINT64_C(0x50123379aecd147c), UINT64_C(0x3f99a96893b2757f) } }, { { UINT64_C(0x9a98f33a39660000), UINT64_C(0x3ffe154c3d2f4d5e) }, { UINT64_C(0x197c61c0a23b2403), UINT64_C(0xbf9ad7f56258b99e) } }, { { UINT64_C(0x0bfc60e6fa080000), UINT64_C(0x3ffe179eabbd899a) }, { UINT64_C(0xb89eb8392b7a41e0), UINT64_C(0xbf9b68f2a71c8279) } }, { { UINT64_C(0xecc5cbdd77820000), UINT64_C(0x3ffe19ee6b467c96) }, { UINT64_C(0x93d3c8e2c9016713), UINT64_C(0xbf9b0c2a8ef8715f) } }, { { UINT64_C(0x4bc94f8ecdfc0000), UINT64_C(0x3ffe1c3b81f713c2) }, { UINT64_C(0x64d107231c87eaa7), UINT64_C(0xbf9bd103880b3dc8) } }, { { UINT64_C(0x03dec59a5f3e0000), UINT64_C(0x3ffe1e85f5e7040d) }, { UINT64_C(0x817696ad39f9a4b2), UINT64_C(0x3f9be35f2e7ca4f4) } }, { { UINT64_C(0xd93503d0f75c0000), UINT64_C(0x3ffe20cdcd192ab6) }, { UINT64_C(0x6be256c1a2a08a80), UINT64_C(0xbf983db0bb5bf2b7) } }, { { UINT64_C(0x282de368722d0000), UINT64_C(0x3ffe23130d7bebf4) }, { UINT64_C(0x9f05279c9cfd2b08), UINT64_C(0xbf9c6dfadc721901) } }, { { UINT64_C(0xb3c043adad100000), UINT64_C(0x3ffe2555bce98f7c) }, { UINT64_C(0xeb301ae8308feca1), UINT64_C(0x3f9be71084750a06) } }, { { UINT64_C(0xaeb783f3db970000), UINT64_C(0x3ffe2795e1289b11) }, { UINT64_C(0x67b5e622105c5e41), UINT64_C(0xbf9ce3801fe56c14) } }, { { UINT64_C(0xac85cd6cba5d0000), UINT64_C(0x3ffe29d37fec2b08) }, { UINT64_C(0xa6995ef5598cc5b7), UINT64_C(0xbf9b824a2f303e0a) } }, { { UINT64_C(0xbb97a9c31ba30000), UINT64_C(0x3ffe2c0e9ed448e8) }, { UINT64_C(0x3dea45d7349693e3), UINT64_C(0xbf9c8676adfad5c8) } }, { { UINT64_C(0x8405421869220000), UINT64_C(0x3ffe2e47436e4026) }, { UINT64_C(0x2c88ebc5ce2f36f2), UINT64_C(0x3f9aab1252cf2945) } }, { { UINT64_C(0xe1fb590a1f560000), UINT64_C(0x3ffe307d7334f10b) }, { UINT64_C(0x999c08cdd48a0210), UINT64_C(0x3f9cb66f70c05b80) } }, { { UINT64_C(0x1320556b67b20000), UINT64_C(0x3ffe32b1339121d7) }, { UINT64_C(0x462ddf94bde6b3a5), UINT64_C(0x3f9b6b06715ba133) } }, { { UINT64_C(0x316eb92d885d0000), UINT64_C(0x3ffe34e289d9ce1d) }, { UINT64_C(0x8fa92837dec71351), UINT64_C(0xbf9ab151b59c4405) } }, { { UINT64_C(0x5c5dd024844e0000), UINT64_C(0x3ffe37117b54747b) }, { UINT64_C(0x3d7e93c9b2f3eef6), UINT64_C(0xbf9b037076a72679) } }, { { UINT64_C(0x9a9c4426036e0000), UINT64_C(0x3ffe393e0d3562a1) }, { UINT64_C(0x863fefb367ef8616), UINT64_C(0xbf99cf1c277a3a0d) } }, { { UINT64_C(0x2af8edec18590000), UINT64_C(0x3ffe3b68449fffc2) }, { UINT64_C(0x9591a54790d29adc), UINT64_C(0x3f9bb341d6de6d9b) } }, { { UINT64_C(0xaa404263d0ad0000), UINT64_C(0x3ffe3d9026a7156f) }, { UINT64_C(0x54adf1ff9360bd04), UINT64_C(0x3f9b3cf6b809d969) } }, { { UINT64_C(0x25b4e9d505cb0000), UINT64_C(0x3ffe3fb5b84d16f4) }, { UINT64_C(0x29a5a46a5834774c), UINT64_C(0xbf9c110c9bd89866) } }, { { UINT64_C(0xe6464bcc2f460000), UINT64_C(0x3ffe41d8fe84672a) }, { UINT64_C(0x4eadeb32e848f817), UINT64_C(0x3f96779538890dd4) } }, { { UINT64_C(0x77a727b9b60f0000), UINT64_C(0x3ffe43f9fe2f9ce6) }, { UINT64_C(0xa0306333dca3b8a4), UINT64_C(0xbf9ae6927c891167) } }, { { UINT64_C(0x27d0b7b37b340000), UINT64_C(0x3ffe4618bc21c5ec) }, { UINT64_C(0x67fe9778d8694229), UINT64_C(0xbf9bc65df511a65b) } }, { { UINT64_C(0xf73d5e8bb3020000), UINT64_C(0x3ffe48353d1ea88d) }, { UINT64_C(0xcc8aca358d9263f9), UINT64_C(0xbf977b4f3e104187) } }, { { UINT64_C(0xb0227bf47a6f0000), UINT64_C(0x3ffe4a4f85db03eb) }, { UINT64_C(0xe2750b818bba40e6), UINT64_C(0xbf9be49ce91908e6) } }, { { UINT64_C(0x9b168ecdd3180000), UINT64_C(0x3ffe4c679afccee3) }, { UINT64_C(0xbbe38f559618dbd3), UINT64_C(0x3f9cbf619db0d889) } }, { { UINT64_C(0x09cb098564580000), UINT64_C(0x3ffe4e7d811b75bb) }, { UINT64_C(0x2047bba2c9ee4f52), UINT64_C(0x3f9c5770d0c5ebca) } }, { { UINT64_C(0xb4bcb3a5b0b50000), UINT64_C(0x3ffe50913cc01686) }, { UINT64_C(0x54c4e5d96f1d584f), UINT64_C(0x3f9cb0f69791cf6c) } }, { { UINT64_C(0xaee77c8af15b0000), UINT64_C(0x3ffe52a2d265bc5a) }, { UINT64_C(0xf6a4c39b31ea388b), UINT64_C(0x3f9b7aea7bed0920) } }, { { UINT64_C(0x7a915428b43e0000), UINT64_C(0x3ffe54b246799949) }, { UINT64_C(0x84758a2a5033748c), UINT64_C(0xbf9cf434bb5d154a) } }, { { UINT64_C(0x9411c62173640000), UINT64_C(0x3ffe56bf9d5b3f39) }, { UINT64_C(0xa3caf6baebad2c64), UINT64_C(0xbf97a6323ea9ce40) } }, { { UINT64_C(0x93092f25d9310000), UINT64_C(0x3ffe58cadb5cd798) }, { UINT64_C(0x6a51ddc9c241881c), UINT64_C(0xbf9cd3d5761cff2a) } }, { { UINT64_C(0xcfb29aaa5f020000), UINT64_C(0x3ffe5ad404c359f2) }, { UINT64_C(0x4578161ccf77753b), UINT64_C(0x3f9bcd40845839e0) } }, { { UINT64_C(0x48cf6e3c5d710000), UINT64_C(0x3ffe5cdb1dc6c176) }, { UINT64_C(0x6b8a7923783c6bf7), UINT64_C(0xbf9cf3a84fed7a8e) } }, { { UINT64_C(0x570d6095fd240000), UINT64_C(0x3ffe5ee02a924167) }, { UINT64_C(0xae4e7a29309e6688), UINT64_C(0x3f9b03ef4c629f04) } }, { { UINT64_C(0x8ca7c895a0b50000), UINT64_C(0x3ffe60e32f44788d) }, { UINT64_C(0x14a66aa81ead3fdb), UINT64_C(0xbf9a3557995d0639) } }, { { UINT64_C(0xf35793c767300000), UINT64_C(0x3ffe62e42fefa39e) }, { UINT64_C(0x93394c5b16c5068c), UINT64_C(0x3f98f97b57a079a1) } }, }; static const CrtLogqPoly crt_logq_poly = { .a1 = { UINT64_C(0x0000000000000000), UINT64_C(0x3fff000000000000) }, .a3 = { UINT64_C(0x555555555555d428), UINT64_C(0x3ffb555555555555) }, .a5 = { UINT64_C(0x99999961a50beb22), UINT64_C(0x3ff8999999999999) }, .a7 = { UINT64_C(0x28947afbb870a773), UINT64_C(0x3ff6249249249249) }, .a9 = UINT64_C(0x3f3c71c71c6dc474), .a11 = UINT64_C(0x3f1745e006555523), .b1_hi = { UINT64_C(0x0000000000000000), UINT64_C(0x3fff000000000000) }, .b1_lo = { UINT64_C(0x0000000000000000), UINT64_C(0x0000000000000000) }, .b3 = { UINT64_C(0x5555555555555623), UINT64_C(0x3ffb555555555555) }, .b5 = { UINT64_C(0x99999999989792a1), UINT64_C(0x3ff8999999999999) }, .b7 = { UINT64_C(0x2492495c4a1e29d3), UINT64_C(0x3ff6249249249249) }, .b9 = { UINT64_C(0xc710a5d434d9ab1a), UINT64_C(0x3ff3c71c71c71c71) }, .b11 = { UINT64_C(0x11eb1de500fc22ff), UINT64_C(0x3ff1745d1745d175) }, .b13 = { UINT64_C(0x6ace1c146c6f5509), UINT64_C(0x3fef3b13b13b0d27) }, .b15 = UINT64_C(0x3ed1111113657afc), .b17 = UINT64_C(0x3eae1e0e1dd71957), .b19 = UINT64_C(0x3e8b10df3b43abfc), }; // `log10q`'s Proc1 table (lib/compiler_rt/log10.zig `tab`), hi/lo pairs. static const CrtU128 crt_log10q_tab[LOGQ_SIZE + 1][2] = { { { UINT64_C(0x0000000000000000), UINT64_C(0x0000000000000000) }, { UINT64_C(0x0000000000000000), UINT64_C(0x0000000000000000) } }, { { UINT64_C(0x665c1ba949000000), UINT64_C(0x3ff6bafd47221ed2) }, { UINT64_C(0x8515635f3b8a1d22), UINT64_C(0xbf97eb6f20a90ad4) } }, { { UINT64_C(0xed89b5a417000000), UINT64_C(0x3ff7b9476a4fcd10) }, { UINT64_C(0x527c3dce31e5e226), UINT64_C(0x3f9b0b153c94bfd2) } }, { { UINT64_C(0x3ce1bf0b26000000), UINT64_C(0x3ff849b085144368) }, { UINT64_C(0x9a7a84e03b9c38c2), UINT64_C(0xbf9ca16199f2b1be) } }, { { UINT64_C(0x00f974ff1b400000), UINT64_C(0x3ff8b5e908eb1379) }, { UINT64_C(0xcbc0ccc513d93932), UINT64_C(0xbf996bbc5e42b470) } }, { { UINT64_C(0x7a1180aaf6000000), UINT64_C(0x3ff910a83a8446c7) }, { UINT64_C(0x3110cf07e44c289f), UINT64_C(0xbf9a09ecab3c0cf8) } }, { { UINT64_C(0x07769e25e9600000), UINT64_C(0x3ff945f4f5acb8be) }, { UINT64_C(0x12136c57591afc6c), UINT64_C(0xbf9c0a58d59387d1) } }, { { UINT64_C(0x81b980714c600000), UINT64_C(0x3ff97adc3df3b1ff) }, { UINT64_C(0x2fbfab402e472463), UINT64_C(0xbf9ba572fb60daf7) } }, { { UINT64_C(0xfa6de0a6da800000), UINT64_C(0x3ff9af5f92b00e60) }, { UINT64_C(0x41e5f6959299c961), UINT64_C(0xbf9b8335cd731aa8) } }, { { UINT64_C(0xf0d79f59da200000), UINT64_C(0x3ff9e3806acbd058) }, { UINT64_C(0xd4ea94e47e000ae6), UINT64_C(0x3f999ab1b88f11b4) } }, { { UINT64_C(0x02be3a8a48a00000), UINT64_C(0x3ffa0ba01a817000) }, { UINT64_C(0x0083e09dc0929a70), UINT64_C(0xbf9a792eb9b5df59) } }, { { UINT64_C(0xb801dad7ded00000), UINT64_C(0x3ffa25502c0fc314) }, { UINT64_C(0x1ad18fd42fe32cb2), UINT64_C(0x3f9c197c6c848f1b) } }, { { UINT64_C(0x5037527d74800000), UINT64_C(0x3ffa3ed1199a5e42) }, { UINT64_C(0x326912f10c802895), UINT64_C(0xbf9bc78118e73744) } }, { { UINT64_C(0xa7bf5153dfb00000), UINT64_C(0x3ffa58238eeb353d) }, { UINT64_C(0x9fdf5fb0469aa7ee), UINT64_C(0xbf9c9663eec73478) } }, { { UINT64_C(0x8ce1e11006000000), UINT64_C(0x3ffa71483427d2a9) }, { UINT64_C(0x97aa1e6e70df2d95), UINT64_C(0x3f9ac4ea21ef8f4a) } }, { { UINT64_C(0x393aed3e7b600000), UINT64_C(0x3ffa8a3fadeb847f) }, { UINT64_C(0x09971ad607875b6e), UINT64_C(0x3f9c098634c8ec2a) } }, { { UINT64_C(0x9c281982d7e00000), UINT64_C(0x3ffaa30a9d609efe) }, { UINT64_C(0xf07600c430a0057d), UINT64_C(0xbf990a32db4c5564) } }, { { UINT64_C(0x41a9796c34500000), UINT64_C(0x3ffabba9a058dfd8) }, { UINT64_C(0x5f8d9f32d76bef47), UINT64_C(0xbf9cd9dd77e08e9e) } }, { { UINT64_C(0x3a0188eb21000000), UINT64_C(0x3ffad41d5164facb) }, { UINT64_C(0xfa34321a7e97187f), UINT64_C(0x3f9a0128f682aeb0) } }, { { UINT64_C(0x847d0188c2d00000), UINT64_C(0x3ffaec6647eb5880) }, { UINT64_C(0x1acf1f55342436ab), UINT64_C(0xbf9b5afb00d01e07) } }, { { UINT64_C(0x5ea6bc2bc8c00000), UINT64_C(0x3ffb02428c1f0801) }, { UINT64_C(0xeef0247b7ffb956e), UINT64_C(0x3f9c5daed7d59e78) } }, { { UINT64_C(0xe62559618f200000), UINT64_C(0x3ffb0e3d29d81165) }, { UINT64_C(0x0d3db1dc752ed073), UINT64_C(0x3f9c9e819128f90a) } }, { { UINT64_C(0x5c0cde7a7f080000), UINT64_C(0x3ffb1a2344550181) }, { UINT64_C(0x4310435ef8820def), UINT64_C(0xbf9c3b49f3e8b2ed) } }, { { UINT64_C(0x9df2a5fb46b80000), UINT64_C(0x3ffb25f5215eb594) }, { UINT64_C(0x61f26e9b3ac1bf9c), UINT64_C(0x3f9a6f0f6ab32f3e) } }, { { UINT64_C(0x801b420b9b200000), UINT64_C(0x3ffb31b3055c4711) }, { UINT64_C(0x3b127df25ba8db5b), UINT64_C(0x3f9876e9002c845c) } }, { { UINT64_C(0x8caf84eb22900000), UINT64_C(0x3ffb3d5d335c5317) }, { UINT64_C(0x7e0376c69ec1cac0), UINT64_C(0xbf9bc5c284463001) } }, { { UINT64_C(0xb5e08483b6800000), UINT64_C(0x3ffb48f3ed1df48f) }, { UINT64_C(0xe36e36265eaea7bc), UINT64_C(0xbf9a7f13d7d43d84) } }, { { UINT64_C(0x48790c13ee280000), UINT64_C(0x3ffb5477731973e8) }, { UINT64_C(0x199df31ecf871835), UINT64_C(0xbf9cebaaf88383e9) } }, { { UINT64_C(0xca9254c0c6380000), UINT64_C(0x3ffb5fe80488af4f) }, { UINT64_C(0x8500745623ee81e5), UINT64_C(0x3f9c15b76527b80b) } }, { { UINT64_C(0x9590e0d54c780000), UINT64_C(0x3ffb6b45df6f3e2c) }, { UINT64_C(0xef16e4f8bb12e196), UINT64_C(0xbf9bc495b5c030bc) } }, { { UINT64_C(0xc94ecf23d4700000), UINT64_C(0x3ffb769140a2526f) }, { UINT64_C(0x6e08b6413b02da64), UINT64_C(0x3f9af1841f017a7f) } }, { { UINT64_C(0x9827184d3fd80000), UINT64_C(0x3ffb81ca63d05a44) }, { UINT64_C(0xd4768b8aae8dcde3), UINT64_C(0xbf9c073284788f12) } }, { { UINT64_C(0xc9b28b1f97e00000), UINT64_C(0x3ffb8cf183886480) }, { UINT64_C(0x927e5baecee4f8da), UINT64_C(0xbf9b13ada343a03c) } }, { { UINT64_C(0x97207328f7680000), UINT64_C(0x3ffb9806d9414a20) }, { UINT64_C(0xbf08d830caaa52ca), UINT64_C(0xbf9b213231346152) } }, { { UINT64_C(0x9c281982d7e00000), UINT64_C(0x3ffba30a9d609efe) }, { UINT64_C(0xf07600c430a0057d), UINT64_C(0xbf9a0a32db4c5564) } }, { { UINT64_C(0x6fd76ea69ee80000), UINT64_C(0x3ffbadfd07416be0) }, { UINT64_C(0x721df510fce30125), UINT64_C(0xbf9a2f27508996b3) } }, { { UINT64_C(0x7f5dc97fa3e80000), UINT64_C(0x3ffbb8de4d3ab3d9) }, { UINT64_C(0xb616ea5d5f658c63), UINT64_C(0x3f9c94aec3c479e9) } }, { { UINT64_C(0xe9d1b9bf7b480000), UINT64_C(0x3ffbc3aea4a5c6ef) }, { UINT64_C(0xc56e8ee013db4727), UINT64_C(0xbf9b6e9cbad44d58) } }, { { UINT64_C(0x4f487cfe37b00000), UINT64_C(0x3ffbce6e41e463da) }, { UINT64_C(0x0fa19cd9b24bc4a6), UINT64_C(0x3f9c79fd0d2e2971) } }, { { UINT64_C(0xb8c5ecd854480000), UINT64_C(0x3ffbd91d5866aa99) }, { UINT64_C(0x8fb09b3e2aa21032), UINT64_C(0xbf9abcfa9ad8f74e) } }, { { UINT64_C(0xe3d562a53f080000), UINT64_C(0x3ffbe3bc1ab0e19f) }, { UINT64_C(0xf5c8d025ed120de1), UINT64_C(0xbf9a4e4f16590dfe) } }, { { UINT64_C(0x47109cc020880000), UINT64_C(0x3ffbee4aba610f20) }, { UINT64_C(0x57f0573b320efb86), UINT64_C(0xbf9cb34ada47053c) } }, { { UINT64_C(0x084e03494e800000), UINT64_C(0x3ffbf8c968346819) }, { UINT64_C(0x6a32292230bf611d), UINT64_C(0xbf9c4b71b85b5d72) } }, { { UINT64_C(0x6717a76683880000), UINT64_C(0x3ffc019c2a064b48) }, { UINT64_C(0x4bb3072c0b2e5f3f), UINT64_C(0xbf973af96cd84cdb) } }, { { UINT64_C(0x65458c50fd800000), UINT64_C(0x3ffc06cbd67a6c3b) }, { UINT64_C(0xc693c952fc0c42ba), UINT64_C(0x3f98840d79fca5a0) } }, { { UINT64_C(0xc3c2f40d06dc0000), UINT64_C(0x3ffc0bf3d0937c41) }, { UINT64_C(0x430185683c421ffa), UINT64_C(0xbf9b27324307eb41) } }, { { UINT64_C(0x6e473b0e4f780000), UINT64_C(0x3ffc11142f081135) }, { UINT64_C(0x03cb49c3cc705e75), UINT64_C(0xbf9c49b0b103367c) } }, { { UINT64_C(0xf8e71a2f29100000), UINT64_C(0x3ffc162d082ac9d0) }, { UINT64_C(0x6273b74fedb290b8), UINT64_C(0xbf9c6d975e156bc0) } }, { { UINT64_C(0xabbbb3324a100000), UINT64_C(0x3ffc1b3e71ec94f7) }, { UINT64_C(0x398c7a1e37848466), UINT64_C(0xbf9676cec8d0f75a) } }, { { UINT64_C(0x7552c136a7600000), UINT64_C(0x3ffc204881dee877) }, { UINT64_C(0xb583793d15630abc), UINT64_C(0xbf99267ee5ddb2e8) } }, { { UINT64_C(0xc1d7958ffee80000), UINT64_C(0x3ffc254b4d35e7d3) }, { UINT64_C(0x74aca4dce2431023), UINT64_C(0xbf9baa823ea433ce) } }, { { UINT64_C(0x9955cdd783900000), UINT64_C(0x3ffc2a46e8ca7ba2) }, { UINT64_C(0x799a1f9e3e37c715), UINT64_C(0xbf9c7eb9688bc0de) } }, { { UINT64_C(0x0be34bf081e80000), UINT64_C(0x3ffc2f3b691c5a00) }, { UINT64_C(0xce9bdbdfdb0cfa3a), UINT64_C(0xbf9b563a5a16b595) } }, { { UINT64_C(0xd3b633e046140000), UINT64_C(0x3ffc3428e2540096) }, { UINT64_C(0xbd4c99622577f507), UINT64_C(0x3f9cf1625e245d85) } }, { { UINT64_C(0x3029d5524ca80000), UINT64_C(0x3ffc390f6844a0b8) }, { UINT64_C(0xcb3a00ca5d7bfe39), UINT64_C(0x3f9bc6d3f5f5fe70) } }, { { UINT64_C(0x4ea10095aeac0000), UINT64_C(0x3ffc3def0e6dfdf8) }, { UINT64_C(0x4a930dc964e19d64), UINT64_C(0xbf9c7ddc01913fed) } }, { { UINT64_C(0x1c5baa84ea840000), UINT64_C(0x3ffc42c7e7fe3fc0) }, { UINT64_C(0x7c7883ba343c8c3b), UINT64_C(0xbf99b57aee43292c) } }, { { UINT64_C(0x142ca3a5b0500000), UINT64_C(0x3ffc479a07d3b641) }, { UINT64_C(0x4b61ed4831123201), UINT64_C(0x3f9ba7b00c679cb5) } }, { { UINT64_C(0x821962bd39780000), UINT64_C(0x3ffc4c65807e9333) }, { UINT64_C(0xbddb9dafc48b8763), UINT64_C(0xbf9c5b7e31a62bc6) } }, { { UINT64_C(0xcb096f4725600000), UINT64_C(0x3ffc512a644296c3) }, { UINT64_C(0x13b25a668455ed84), UINT64_C(0xbf9b8eec43b9a263) } }, { { UINT64_C(0x859bf03750480000), UINT64_C(0x3ffc55e8c518b10f) }, { UINT64_C(0xef4d78001cfd92d9), UINT64_C(0xbf9c6ec79e2221c7) } }, { { UINT64_C(0x98f4b7b3557c0000), UINT64_C(0x3ffc5aa0b4b0988f) }, { UINT64_C(0x6e03d088056210a0), UINT64_C(0xbf9ad36883ff38b1) } }, { { UINT64_C(0x24e6e69599800000), UINT64_C(0x3ffc5f52447255c9) }, { UINT64_C(0x163817e106dfbade), UINT64_C(0xbf9ac9a1067c1f62) } }, { { UINT64_C(0xaa7c0cd1066c0000), UINT64_C(0x3ffc63fd857fc49b) }, { UINT64_C(0x253a333d0447df04), UINT64_C(0x3f9c4bef4a4a1c2f) } }, { { UINT64_C(0xc2b622430e540000), UINT64_C(0x3ffc68a288b60b7f) }, { UINT64_C(0xa0c9e5e8e2451944), UINT64_C(0x3f96957d4ee20104) } }, { { UINT64_C(0xa9ea9d132f180000), UINT64_C(0x3ffc6d415eaf0906) }, { UINT64_C(0x6d0789a335fcf1f2), UINT64_C(0x3f9bc7e96850691a) } }, { { UINT64_C(0xfea4e079fe800000), UINT64_C(0x3ffc71da17c2b7e7) }, { UINT64_C(0x59b5df6c05428549), UINT64_C(0xbf9a47eea7b22001) } }, { { UINT64_C(0x4a226ff02f0c0000), UINT64_C(0x3ffc766cc40889e8) }, { UINT64_C(0x77828cfd78f80af0), UINT64_C(0x3f9b17c1270bcfda) } }, { { UINT64_C(0x3ccb5c4489100000), UINT64_C(0x3ffc7af97358b9e0) }, { UINT64_C(0xb9ab4ff4e0ea0c82), UINT64_C(0xbf9b0bc25f85f8a5) } }, { { UINT64_C(0xf92f3616977c0000), UINT64_C(0x3ffc7f80354d9529) }, { UINT64_C(0x004f63fb3e4ca71d), UINT64_C(0x3f9cc73d68ec26da) } }, { { UINT64_C(0x52c5b99300080000), UINT64_C(0x3ffc84011944bcb7) }, { UINT64_C(0x37a79007f790960a), UINT64_C(0xbf99390cb4667450) } }, { { UINT64_C(0x89c603f25d900000), UINT64_C(0x3ffc887c2e605e11) }, { UINT64_C(0x942f3b7415d59507), UINT64_C(0xbf9c3a2bdea0dd3c) } }, { { UINT64_C(0xc9b28b1f97e00000), UINT64_C(0x3ffc8cf183886480) }, { UINT64_C(0x927e5baecee4f8da), UINT64_C(0xbf9c13ada343a03c) } }, { { UINT64_C(0x83a4f607cb800000), UINT64_C(0x3ffc9161276ba297) }, { UINT64_C(0xff9044bfb591e807), UINT64_C(0xbf9c0402e057ffcc) } }, { { UINT64_C(0xa6eadb3548500000), UINT64_C(0x3ffc95cb2880f45b) }, { UINT64_C(0x6c05d3a33d86f1a8), UINT64_C(0x3f99cdd9e9ad6d4f) } }, { { UINT64_C(0xb927e60039040000), UINT64_C(0x3ffc9a2f95085a45) }, { UINT64_C(0x232ebdb4c1a0e13e), UINT64_C(0xbf9c7bfe1b2fef4f) } }, { { UINT64_C(0xe203de57e9a40000), UINT64_C(0x3ffc9e8e7b0c0d4b) }, { UINT64_C(0x1cdfa7664b87a096), UINT64_C(0xbf9b7689e2fc0aa0) } }, { { UINT64_C(0x24882a4f9de40000), UINT64_C(0x3ffca2e7e8618c2d) }, { UINT64_C(0x6cd7bc85f7f30ff5), UINT64_C(0x3f9c0ad7f222a9cb) } }, { { UINT64_C(0x38e04a37a21c0000), UINT64_C(0x3ffca73beaaaa22f) }, { UINT64_C(0x0b3a89d53c8a6096), UINT64_C(0x3f9b6fed007694a6) } }, { { UINT64_C(0xc365b0e5a07c0000), UINT64_C(0x3ffcab8a8f56677f) }, { UINT64_C(0xb677423f326e48dc), UINT64_C(0xbf9a5fd6e4c7bf48) } }, { { UINT64_C(0x00f54642bb780000), UINT64_C(0x3ffcafd3e3a23b68) }, { UINT64_C(0x35925c064d8b96fa), UINT64_C(0x3f9c3d53b5cccabc) } }, { { UINT64_C(0x6bc9543817a00000), UINT64_C(0x3ffcb417f49ab880) }, { UINT64_C(0xacaf5f6acdc7ba41), UINT64_C(0x3f9819354df84685) } }, { { UINT64_C(0x56c3f6e26b740000), UINT64_C(0x3ffcb856cf1ca310) }, { UINT64_C(0x0d62c3c1c56dffcb), UINT64_C(0xbf9bbad52e70273c) } }, { { UINT64_C(0x069339ff22e00000), UINT64_C(0x3ffcbc907fd5d1c4) }, { UINT64_C(0x8afc3078d3044017), UINT64_C(0x3f9c55d384963ee9) } }, { { UINT64_C(0x67bfa808f8480000), UINT64_C(0x3ffcc0c5134610e2) }, { UINT64_C(0x47c947b509e1719e), UINT64_C(0xbf9bbf2c67a6f8c4) } }, { { UINT64_C(0x25ee9a870fd40000), UINT64_C(0x3ffcc4f495c0002a) }, { UINT64_C(0xe1b6bcccff037f29), UINT64_C(0x3f9ade41f6ddaf5a) } }, { { UINT64_C(0x9ad8af7db3a40000), UINT64_C(0x3ffcc91f1369eb7c) }, { UINT64_C(0x51fcc1561cb35d19), UINT64_C(0xbf9a75624a957be0) } }, { { UINT64_C(0xca1ed1e0c9700000), UINT64_C(0x3ffccd44983e9e7b) }, { UINT64_C(0xf357c508dec6e106), UINT64_C(0xbf9ac657e8081710) } }, { { UINT64_C(0x69c028a3c44c0000), UINT64_C(0x3ffcd165300e333f) }, { UINT64_C(0xb8b9880e28aec4a9), UINT64_C(0xbf98af0662e02a88) } }, { { UINT64_C(0xccfa0daf23040000), UINT64_C(0x3ffcd580e67edc43) }, { UINT64_C(0xdd3f11a0b9bc0a2b), UINT64_C(0xbf9c8dcb9bd2e499) } }, { { UINT64_C(0x6857c60d08e40000), UINT64_C(0x3ffcd997c70da9b4) }, { UINT64_C(0x1bbf284b644ffe95), UINT64_C(0xbf97416b471cfcc1) } }, { { UINT64_C(0x9136847dd6940000), UINT64_C(0x3ffcdda9dd0f4a32) }, { UINT64_C(0x4440d0790cbb6a14), UINT64_C(0x3f9a1a80cb70cec1) } }, { { UINT64_C(0x1094f8c216000000), UINT64_C(0x3ffce1b733b0c738) }, { UINT64_C(0x644abf7fc0a11cfe), UINT64_C(0xbf9b1f64198a27f7) } }, { { UINT64_C(0x2043025796c80000), UINT64_C(0x3ffce5bfd5f83d34) }, { UINT64_C(0x527d82ee10fac926), UINT64_C(0x3f9aeee33c4cf5a0) } }, { { UINT64_C(0x72098058f2b40000), UINT64_C(0x3ffce9c3cec58f80) }, { UINT64_C(0x01734c132384a9d3), UINT64_C(0x3f9c6404cd11267d) } }, { { UINT64_C(0xf1cba1b7464c0000), UINT64_C(0x3ffcedc328d3184a) }, { UINT64_C(0xc2bd1b9abe8a0fc4), UINT64_C(0x3f9caee952dcd721) } }, { { UINT64_C(0x0d96cd34f7e00000), UINT64_C(0x3ffcf1bdeeb65490) }, { UINT64_C(0x670262ca8d3b731d), UINT64_C(0xbf995fbba7898678) } }, { { UINT64_C(0x70bc91804dd80000), UINT64_C(0x3ffcf5b42ae08c40) }, { UINT64_C(0x308d1bc754f8f98a), UINT64_C(0xbf9c34f3fead2ae9) } }, { { UINT64_C(0x491748bb64000000), UINT64_C(0x3ffcf9a5e79f76ac) }, { UINT64_C(0x5851dd511e9ab6fa), UINT64_C(0x3f9c46fc084fcb73) } }, { { UINT64_C(0x5f2e278b32c80000), UINT64_C(0x3ffcfd932f1ddb4d) }, { UINT64_C(0xd566ad5e27e0cd80), UINT64_C(0xbf9bf7fe463a5a2f) } }, { { UINT64_C(0x4161e1a46df20000), UINT64_C(0x3ffd00be05b21784) }, { UINT64_C(0x6344f76c943a21ab), UINT64_C(0x3f98dc4853e5049d) } }, { { UINT64_C(0x0694c1c8d1080000), UINT64_C(0x3ffd02b0432c96ff) }, { UINT64_C(0x577bb9fd14424be7), UINT64_C(0xbf9a25d66fcf4861) } }, { { UINT64_C(0x409a780a5b3a0000), UINT64_C(0x3ffd04a054e13900) }, { UINT64_C(0xb19c7c9d5bf41be9), UINT64_C(0xbf9b810c5ed46a87) } }, { { UINT64_C(0xced3324274ca0000), UINT64_C(0x3ffd068e3fa282e3) }, { UINT64_C(0x996197c7d08a5628), UINT64_C(0xbf9a65bc02e61d74) } }, { { UINT64_C(0xc4f9ab7853ea0000), UINT64_C(0x3ffd087a0832fa7a) }, { UINT64_C(0x53396213a2d34961), UINT64_C(0xbf9c2214605e23cb) } }, { { UINT64_C(0x8f3ddb757ccc0000), UINT64_C(0x3ffd0a63b3456c81) }, { UINT64_C(0x0cf7c88c75d66fc0), UINT64_C(0xbf9c10d47a850449) } }, { { UINT64_C(0xd3ffa651b8b80000), UINT64_C(0x3ffd0c4b457d3193) }, { UINT64_C(0x97839bedc777964a), UINT64_C(0x3f9c1c0d5a63400f) } }, { { UINT64_C(0xf53a9ae38e1c0000), UINT64_C(0x3ffd0e30c36e71a7) }, { UINT64_C(0x388ca5d784700f47), UINT64_C(0xbf9cf89e6fc3f1e6) } }, { { UINT64_C(0xc87f6e8c7fde0000), UINT64_C(0x3ffd1014319e661b) }, { UINT64_C(0xf0bc44437859de9b), UINT64_C(0x3f956639f4ae29cc) } }, { { UINT64_C(0xd3aec4ea7c460000), UINT64_C(0x3ffd11f594839a5b) }, { UINT64_C(0xdc0aaa4fe49395fc), UINT64_C(0x3f9b056df9f7cd47) } }, { { UINT64_C(0x167244a4e9980000), UINT64_C(0x3ffd13d4f0862b2e) }, { UINT64_C(0x5ba68f4835e8a628), UINT64_C(0xbf9bdb24b7557c46) } }, { { UINT64_C(0x24955aced3a40000), UINT64_C(0x3ffd15b24a0004a9) }, { UINT64_C(0x178b4381d107f47d), UINT64_C(0xbf9c32961e32127f) } }, { { UINT64_C(0x13c7b3e96d220000), UINT64_C(0x3ffd178da53d1ee0) }, { UINT64_C(0x6d780c7f87d2d010), UINT64_C(0xbf9b0701b8cc9034) } }, { { UINT64_C(0x7feaa558f2f20000), UINT64_C(0x3ffd1967067bb94b) }, { UINT64_C(0x1510eac0efac64ad), UINT64_C(0xbf9b03e52851cf5d) } }, { { UINT64_C(0xabbbb3324a100000), UINT64_C(0x3ffd1b3e71ec94f7) }, { UINT64_C(0x398c7a1e37848466), UINT64_C(0xbf9776cec8d0f75a) } }, { { UINT64_C(0x886517823d220000), UINT64_C(0x3ffd1d13ebb32d7f) }, { UINT64_C(0x4cd2c4cb44f767d2), UINT64_C(0xbf99e1b60cbc6aa9) } }, { { UINT64_C(0x35268e3c03840000), UINT64_C(0x3ffd1ee777e5f0dc) }, { UINT64_C(0x9a0126193b44884f), UINT64_C(0xbf9c563fb0ec2d3c) } }, { { UINT64_C(0x50d250ea2a800000), UINT64_C(0x3ffd20b91a8e7610) }, { UINT64_C(0x2614d5d1d7e782ae), UINT64_C(0xbf9c0fb80164cc71) } }, { { UINT64_C(0x4132838170240000), UINT64_C(0x3ffd2288d7a9b2b6) }, { UINT64_C(0xc89a11f502eea0c8), UINT64_C(0x3f9c9b04b90001ed) } }, { { UINT64_C(0x608173a444840000), UINT64_C(0x3ffd2456b3282f78) }, { UINT64_C(0xf94383e5734624af), UINT64_C(0x3f9c54c245cf9301) } }, { { UINT64_C(0xcee2bf4fc8ea0000), UINT64_C(0x3ffd2622b0ee3b79) }, { UINT64_C(0x0d49b5ff9aee724e), UINT64_C(0xbf9c33e1e1011916) } }, { { UINT64_C(0x752d306115160000), UINT64_C(0x3ffd27ecd4d41eb6) }, { UINT64_C(0x2224c47155d4d90b), UINT64_C(0x3f9c80530269b175) } }, { { UINT64_C(0x9745e857b1e80000), UINT64_C(0x3ffd29b522a64b60) }, { UINT64_C(0xdbbd898636577548), UINT64_C(0xbf9c9d8474e5705a) } }, { { UINT64_C(0x26bf0485fab00000), UINT64_C(0x3ffd2b7b9e258e42) }, { UINT64_C(0x060bbd811063be07), UINT64_C(0xbf9b619c76c7c7c5) } }, { { UINT64_C(0xda5069cad6ec0000), UINT64_C(0x3ffd2d404b073e27) }, { UINT64_C(0xabbc31c75eedbc4d), UINT64_C(0xbf9a34f7416aedee) } }, { { UINT64_C(0xe40baedb9a4a0000), UINT64_C(0x3ffd2f032cf56a5b) }, { UINT64_C(0xc3aa12fdfb2d1cc8), UINT64_C(0xbf99e454c75d4817) } }, { { UINT64_C(0xf6cf717bf6720000), UINT64_C(0x3ffd30c4478f0835) }, { UINT64_C(0x012dc44bd3ce1929), UINT64_C(0x3f9caeef5062051e) } }, { { UINT64_C(0x236e91f3daca0000), UINT64_C(0x3ffd32839e681fc6) }, { UINT64_C(0x57af018a8d364cb8), UINT64_C(0xbf9c451bc31fd56e) } }, { { UINT64_C(0xef311f12b3580000), UINT64_C(0x3ffd34413509f79f) }, { UINT64_C(0x18a87a3e69314bce), UINT64_C(0x3f996f922f04d5a6) } }, }; static const CrtLogqPoly crt_log10q_poly = { .a1 = { UINT64_C(0xe32a6ab7555f5a68), UINT64_C(0x3ffdbcb7b1526e50) }, .a3 = { UINT64_C(0xecc6f1cf8e3fffc3), UINT64_C(0x3ffa287a7636f435) }, .a5 = { UINT64_C(0x8288552edd595489), UINT64_C(0x3ff763c62775250d) }, .a7 = { UINT64_C(0x784bbb08d2dbfdf0), UINT64_C(0x3ff4fc3fa615105c) }, .a9 = UINT64_C(0x3f28b4df2f3b88c0), .a11 = UINT64_C(0x3f0436f1ce776a16), .b1_hi = { UINT64_C(0xe000000000000000), UINT64_C(0x3ffdbcb7b1526e50) }, .b1_lo = { UINT64_C(0x33dc323ee3460246), UINT64_C(0x3fc695355baaafad) }, .b3 = { UINT64_C(0xecc6f1cf8e3f924d), UINT64_C(0x3ffa287a7636f435) }, .b5 = { UINT64_C(0x8288555f769f5ce8), UINT64_C(0x3ff763c62775250d) }, .b7 = { UINT64_C(0x71550ca007cfb92e), UINT64_C(0x3ff4fc3fa615105c) }, .b9 = { UINT64_C(0xe65458a748456555), UINT64_C(0x3ff28b4df2f3f047) }, .b11 = { UINT64_C(0x13bb11e1461bee65), UINT64_C(0x3ff0436e526a7ec7) }, .b13 = { UINT64_C(0xb0a95b2bbf1967a8), UINT64_C(0x3fee11ac1e5a16d2) }, .b15 = UINT64_C(0x3ebda5d8a3247f74), .b17 = UINT64_C(0x3e9a28de6a296125), .b19 = UINT64_C(0x3e7782577cdf1278), }; static const CrtU128 crt_logq_proc2_lo = { UINT64_C(0x3ce5e31fe0609358), UINT64_C(0x3ffee0fabfbc702a) }; static const CrtU128 crt_logq_proc2_hi = { UINT64_C(0xd7d5b1a019e225ca), UINT64_C(0x3fff1082b577d34e) }; static CrtU128 qAdd(CrtU128 a, CrtU128 b) { return crtAddf3(128, a, b); } static CrtU128 qSub(CrtU128 a, CrtU128 b) { return crtSubf3(128, a, b); } static CrtU128 qMul(CrtU128 a, CrtU128 b) { return crtMulf3(128, a, b); } static bool qLt(CrtU128 a, CrtU128 b) { return crtCmp(128, false, a, b) < 0; } static bool qEq(CrtU128 a, CrtU128 b) { return a.lo == b.lo && a.hi == b.hi; } // The low 64 bits of `@intFromFloat` (toward zero) in two's complement, for // an integer-valued f128 below 2^63 in magnitude. static uint64_t qToU64Bits(CrtU128 n) { int32_t e = (int32_t)((n.hi >> CRT_F128_EXP_SHIFT) & CRT_F128_EXP_MASK) - CRT_F128_BIAS; if (e < 0) return 0; CrtU128 sig = { n.lo, (n.hi & UINT64_C(0xffffffffffff)) | (UINT64_C(1) << CRT_F128_EXP_SHIFT) }; uint64_t mag = u128Shr(sig, (uint32_t)(CRT_F128_MANT_BITS - e)).lo; return (n.hi >> 63) != 0 ? (uint64_t)0 - mag : mag; } // One width's arithmetic: `b` is 80 or 128. static CrtU128 tAdd(uint32_t b, CrtU128 x, CrtU128 y) { return crtAddf3(b, x, y); } static CrtU128 tSub(uint32_t b, CrtU128 x, CrtU128 y) { return crtSubf3(b, x, y); } static CrtU128 tMul(uint32_t b, CrtU128 x, CrtU128 y) { return crtMulf3(b, x, y); } static CrtU128 tDiv(uint32_t b, CrtU128 x, CrtU128 y) { return b == 80 ? crtDivxf3(x, y) : crtDivtf3(x, y); } static bool tLt(uint32_t b, CrtU128 x, CrtU128 y) { return crtCmp(b, false, x, y) < 0; } static bool tGt(uint32_t b, CrtU128 x, CrtU128 y) { return crtCmp(b, true, x, y) > 0; } static bool tGe(uint32_t b, CrtU128 x, CrtU128 y) { return crtCmp(b, true, x, y) >= 0; } static uint64_t tSignBit(uint32_t b) { return b == 80 ? UINT64_C(0x8000) : UINT64_C(1) << 63; } static CrtU128 tNeg(uint32_t b, CrtU128 x) { x.hi ^= tSignBit(b); return x; } static CrtU128 tAbs(uint32_t b, CrtU128 x) { x.hi &= ~tSignBit(b); return x; } // `ld.signExponent` and `ld.mantissaTop`. static uint32_t tSignExp(uint32_t b, CrtU128 x) { return (uint32_t)(b == 80 ? x.hi & 0xffffu : x.hi >> 48); } static uint32_t tMantTop(uint32_t b, CrtU128 x) { return (uint32_t)(b == 80 ? x.lo >> 48 : (x.hi >> 32) & 0xffffu); } static CrtU128 tFromF64(uint32_t b, double d) { CrtU128 r = u128Make(0, crtF64Bits(d)); return b == 80 ? crtExtendF80(64, r) : crtExtendf(128, 64, r); } static double tToF64(uint32_t b, CrtU128 x) { return crtF64(crtTruncf(64, 128, b == 80 ? crtExtendxftf2(x) : x).lo); } static CrtU128 tFromI32(uint32_t b, int32_t i) { CrtU128 mag = { (uint64_t)(i < 0 ? -(int64_t)i : (int64_t)i), 0 }; return crtFloatFromInt(b, mag, i < 0); } static uint64_t tToU64Bits(uint32_t b, CrtU128 x) { return qToU64Bits(b == 80 ? crtExtendxftf2(x) : x); } // `@intFromFloat` (toward zero) of a value whose magnitude fits in i32. static int32_t tToI32(uint32_t b, CrtU128 x) { return (int32_t)tToU64Bits(b, x); } // `log_f128.specialCases`: NaN, +inf, zero and negatives; false when x is // finite and positive. static bool logqSpecialCases(CrtU128 x, CrtU128* out) { uint64_t exp = (x.hi >> CRT_F128_EXP_SHIFT) & CRT_F128_EXP_MASK; bool neg = (x.hi >> 63) != 0; bool frac_zero = x.lo == 0 && (x.hi & UINT64_C(0xffffffffffff)) == 0; if (exp == CRT_F128_EXP_MASK) { if (!frac_zero) { // NaN: `math.nan(f128)` *out = crtNanRep(crtFloat(128)); return true; } if (!neg) { // +inf *out = x; return true; } } bool zero = exp == 0 && frac_zero; if (zero) { // `-math.inf(f128)` *out = (CrtU128) { 0, UINT64_C(0xffff000000000000) }; return true; } if (neg) { // negative, -inf included: `math.nan(f128)` *out = crtNanRep(crtFloat(128)); return true; } return false; } // `log_f128.frexp2`: x = f * 2^k with f in [1,2), x finite and positive. static CrtU128 logqFrexp2(CrtU128 x, int32_t* exponent) { int32_t uexp = (int32_t)((x.hi >> CRT_F128_EXP_SHIFT) & CRT_F128_EXP_MASK); if (uexp == 0) { // `@clz(bits) - 15`, then `(bits << shift) | (0x3fff << 112)`. uint32_t shift = u128Clz(x) - 15u; CrtU128 s = u128Shl(x, shift); *exponent = -(int32_t)shift - 0x3ffe; s.hi |= (uint64_t)CRT_F128_BIAS << CRT_F128_EXP_SHIFT; return s; } *exponent = uexp - CRT_F128_BIAS; x.hi = (x.hi & UINT64_C(0xffffffffffff)) | ((uint64_t)CRT_F128_BIAS << CRT_F128_EXP_SHIFT); return x; } // `log_f128.proc1`. static CrtU128 logqProc1( const CrtLogqPoly* p, const CrtU128 (*tab)[2], CrtU128 x) { int32_t m; CrtU128 y = logqFrexp2(x, &m); CrtU128 F0 = crtRoundq(crtLdexpBits(y, LOGQ_LOG2SIZE, crtFloat(128))); uint32_t j = (uint32_t)tToI32(128, F0) - LOGQ_SIZE; CrtU128 F = crtLdexpBits(F0, -LOGQ_LOG2SIZE, crtFloat(128)); CrtU128 f = qSub(y, F); CrtU128 u = crtDivtf3(qAdd(f, f), qAdd(y, F)); CrtU128 v = qMul(u, u); double v64 = tToF64(128, v); double p9m = v64 * crtF64(p->a11); double p9 = crtF64(p->a9) + p9m; CrtU128 p7 = qAdd(p->a7, qMul(v, tFromF64(128, p9))); CrtU128 p5 = qAdd(p->a5, qMul(v, p7)); CrtU128 p3 = qAdd(p->a3, qMul(v, p5)); CrtU128 q = qMul(qMul(u, v), p3); CrtU128 abs_m = { (uint64_t)(m < 0 ? -(int64_t)m : (int64_t)m), 0 }; CrtU128 xm = crtFloatFromInt(128, abs_m, m < 0); CrtU128 l_hi = qAdd(qMul(xm, tab[LOGQ_SIZE][0]), tab[j][0]); CrtU128 l_lo = qAdd(qMul(xm, tab[LOGQ_SIZE][1]), tab[j][1]); const CrtU128 one = crtPow2(crtFloat(128), 0); if (qEq(p->a1, one)) return qAdd(l_hi, qAdd(u, qAdd(q, l_lo))); return qAdd(l_hi, qAdd(qMul(u, p->a1), qAdd(q, l_lo))); } // `log_f128.proc2`, for x in (Proc2.lo, Proc2.hi). static CrtU128 logqProc2(const CrtLogqPoly* p, CrtU128 x) { const CrtU128 one = crtPow2(crtFloat(128), 0); const CrtU128 two = crtPow2(crtFloat(128), 1); CrtU128 f = qSub(x, one); CrtU128 g = crtDivtf3(one, qAdd(two, f)); CrtU128 u = qMul(qMul(two, f), g); CrtU128 v = qMul(u, u); CrtU128 uv = qMul(u, v); double v64 = tToF64(128, v); double p17m = v64 * crtF64(p->b19); double p17 = crtF64(p->b17) + p17m; double p15m = v64 * p17; double p15 = crtF64(p->b15) + p15m; CrtU128 p13 = qAdd(p->b13, qMul(v, tFromF64(128, p15))); CrtU128 p11 = qAdd(p->b11, qMul(v, p13)); CrtU128 p9 = qAdd(p->b9, qMul(v, p11)); CrtU128 p7 = qAdd(p->b7, qMul(v, p9)); CrtU128 p5 = qAdd(p->b5, qMul(v, p7)); CrtU128 q_hi = qMul(uv, p->b3); CrtU128 q_lo = qMul(qMul(uv, v), p5); CrtU128 f_hi = tFromF64(128, tToF64(128, f)); CrtU128 f_lo = qSub(f, f_hi); CrtU128 u_hi = tFromF64(128, tToF64(128, u)); // `((2 * (f - u_hi) - u_hi * f_hi) - u_hi * f_lo) * g` CrtU128 u_lo = qMul(qSub(qSub(qMul(two, qSub(f, u_hi)), qMul(u_hi, f_hi)), qMul(u_hi, f_lo)), g); const CrtU128 zero = { 0, 0 }; if (qEq(p->b1_hi, one) && qEq(p->b1_lo, zero)) return qAdd(u_hi, qAdd(u_lo, qAdd(q_hi, q_lo))); CrtU128 t_hi = qMul(u_hi, p->b1_hi); CrtU128 t_lo = qAdd(qMul(u_lo, p->b1_hi), qMul(u, p->b1_lo)); CrtU128 y_hi = qAdd(t_hi, q_hi); // `t_lo + (t_hi - y_hi + q_hi) + q_lo` CrtU128 y_lo = qAdd(qAdd(t_lo, qAdd(qSub(t_hi, y_hi), q_hi)), q_lo); return qAdd(y_hi, y_lo); } static CrtU128 logqBase( const CrtLogqPoly* p, const CrtU128 (*tab)[2], CrtU128 x) { CrtU128 special; if (logqSpecialCases(x, &special)) return special; if (qLt(crt_logq_proc2_lo, x) && qLt(x, crt_logq_proc2_hi)) return logqProc2(p, x); return logqProc1(p, tab, x); } CrtU128 crtLogq(CrtU128 x) { return logqBase(&crt_logq_poly, crt_logq_tab, x); } CrtU128 crtLog2q(CrtU128 x) { return logqBase(&crt_log2q_poly, crt_log2q_tab, x); } CrtU128 crtLog10q(CrtU128 x) { return logqBase(&crt_log10q_poly, crt_log10q_tab, x); } // ── exp_f128.zig: expq and exp2q ─────────────────────────────────────────── // Tang's table-driven exponential as compiler_rt adapts it to f128 (bases e // and 2); the degree 8-11 terms are plain doubles. #define EXPQ_SIZE 64 typedef struct { CrtU128 a1, a2, a3, a4, a5, a6, a7; // a1 is unused by base e uint64_t a8, a9, a10, a11; // f64 CrtU128 inv_log_size, log_size_hi, neg_log_size_lo; bool base2; } CrtExpqCfg; static const CrtExpqCfg crt_expq_cfg = { .a2 = { UINT64_C(0x0000000000000001), UINT64_C(0x3ffe000000000000) }, .a3 = { UINT64_C(0x5555555555555558), UINT64_C(0x3ffc555555555555) }, .a4 = { UINT64_C(0x5555555553d71c43), UINT64_C(0x3ffa555555555555) }, .a5 = { UINT64_C(0x111111110f813d2c), UINT64_C(0x3ff8111111111111) }, .a6 = { UINT64_C(0x6c17884f18399451), UINT64_C(0x3ff56c16c16c16c1) }, .a7 = { UINT64_C(0xa01b002f1c7709dc), UINT64_C(0x3ff2a01a01a01a01) }, .a8 = UINT64_C(0x3efa01a01a019b93), .a9 = UINT64_C(0x3ec71de3a556c2fb), .a10 = UINT64_C(0x3e927e5063ca5d25), .a11 = UINT64_C(0x3e5ae646667479cc), .inv_log_size = { UINT64_C(0xe1777d0ffda0d23a), UINT64_C(0x400571547652b82f) }, .log_size_hi = { UINT64_C(0xf35793c767300000), UINT64_C(0x3ff862e42fefa39e) }, .neg_log_size_lo = { UINT64_C(0x93394c5b16c5068c), UINT64_C(0xbf92f97b57a079a1) }, .base2 = false, }; static const CrtExpqCfg crt_exp2q_cfg = { .a1 = { UINT64_C(0xf35793c7673007e6), UINT64_C(0x3ffe62e42fefa39e) }, .a2 = { UINT64_C(0xea86f16b06ec9740), UINT64_C(0x3ffcebfbdff82c58) }, .a3 = { UINT64_C(0xf8b33a762bb32bde), UINT64_C(0x3ffac6b08d704a0b) }, .a4 = { UINT64_C(0x729ccbbe084b8110), UINT64_C(0x3ff83b2ab6fba4e7) }, .a5 = { UINT64_C(0x110717f696ac539b), UINT64_C(0x3ff55d87fe78a673) }, .a6 = { UINT64_C(0x76f5c0688f025e23), UINT64_C(0x3ff2430912f86c78) }, .a7 = { UINT64_C(0x686ccf95d369c582), UINT64_C(0x3feeffcbfc588b0c) }, .a8 = UINT64_C(0x3eb62c0223a5c34a), .a9 = UINT64_C(0x3e7b5253d395e1bf), .a10 = UINT64_C(0x3e3e4cf64c1dca33), .a11 = UINT64_C(0x3dfe8cadac6b61aa), .inv_log_size = { UINT64_C(0x0000000000000000), UINT64_C(0x4005000000000000) }, .log_size_hi = { UINT64_C(0x0000000000000000), UINT64_C(0x3ff9000000000000) }, .neg_log_size_lo = { UINT64_C(0x0000000000000000), UINT64_C(0x0000000000000000) }, .base2 = true, }; // `exp2_tab[j].hi + exp2_tab[j].lo ~= 2^(j / size)`. static const CrtU128 crt_expq_tab[EXPQ_SIZE][2] = { { { UINT64_C(0x0000000000000000), UINT64_C(0x3fff000000000000) }, { UINT64_C(0x0000000000000000), UINT64_C(0x0000000000000000) } }, { { UINT64_C(0x0ee6f7cac0000000), UINT64_C(0x3fff02c9a3e77806) }, { UINT64_C(0xae1459578b971374), UINT64_C(0x3faa49ef4537bd27) } }, { { UINT64_C(0x43ae7c5480000000), UINT64_C(0x3fff059b0d315857) }, { UINT64_C(0x7c692befbe975f09), UINT64_C(0x3faad6d19482ffca) } }, { { UINT64_C(0x808c35f240000000), UINT64_C(0x3fff0874518759bc) }, { UINT64_C(0xb2d6829d8993a0d0), UINT64_C(0x3fabd9427fa2b041) } }, { { UINT64_C(0xf6298b9280000000), UINT64_C(0x3fff0b5586cf9890) }, { UINT64_C(0x48a0459e75851516), UINT64_C(0x3facb8c2154c1b21) } }, { { UINT64_C(0x2020742e40000000), UINT64_C(0x3fff0e3ec32d3d1a) }, { UINT64_C(0x66b162253d3f2cd5), UINT64_C(0x3faaf15ed4aa5589) } }, { { UINT64_C(0x0a4ebbf180000000), UINT64_C(0x3fff11301d0125b5) }, { UINT64_C(0x623d58b3772ba139), UINT64_C(0x3fac76c98c67562e) } }, { { UINT64_C(0xfb34101940000000), UINT64_C(0x3fff1429aaea92dd) }, { UINT64_C(0x9c4df53d76e910ea), UINT64_C(0x3fa8d92c3680c225) } }, { { UINT64_C(0xadcdf7c8c0000000), UINT64_C(0x3fff172b83c7d517) }, { UINT64_C(0x5427fdf5d61a4fc9), UINT64_C(0x3fa943ac529e480d) } }, { { UINT64_C(0x53cb698f40000000), UINT64_C(0x3fff1a35beb6fcb7) }, { UINT64_C(0x83926ae7d718dc27), UINT64_C(0x3fac4968e41ad361) } }, { { UINT64_C(0xa7805b8000000000), UINT64_C(0x3fff1d4873168b9a) }, { UINT64_C(0x11037230b367828f), UINT64_C(0x3fac44c8783d4c5a) } }, { { UINT64_C(0x63b8eeb000000000), UINT64_C(0x3fff2063b88628cd) }, { UINT64_C(0x3e90e15c20021327), UINT64_C(0x3fac4a8494e87e24) } }, { { UINT64_C(0x866c1fad80000000), UINT64_C(0x3fff2387a6e75623) }, { UINT64_C(0x942b34816fb4f26f), UINT64_C(0x3fac8e0ae5ac9d81) } }, { { UINT64_C(0xd257a67300000000), UINT64_C(0x3fff26b4565e27cd) }, { UINT64_C(0x748c36eeaffa2736), UINT64_C(0x3fac40e9d924ee72) } }, { { UINT64_C(0x12c25d15c0000000), UINT64_C(0x3fff29e9df51fdee) }, { UINT64_C(0x856046901ff6c050), UINT64_C(0x3facad12551de544) } }, { { UINT64_C(0xb40091d500000000), UINT64_C(0x3fff2d285a6e4030) }, { UINT64_C(0x0e68d9f200c5358a), UINT64_C(0x3facb683a9c22c4e) } }, { { UINT64_C(0x52de8d5a40000000), UINT64_C(0x3fff306fe0a31b71) }, { UINT64_C(0x09cd0d8a7d40bc6c), UINT64_C(0x3fa98c17217b7b2f) } }, { { UINT64_C(0xf4c9c86100000000), UINT64_C(0x3fff33c08b26416f) }, { UINT64_C(0xb26072a73b29acc5), UINT64_C(0x3faab2cd2d7f2ba2) } }, { { UINT64_C(0xaa71455000000000), UINT64_C(0x3fff371a7373aa9c) }, { UINT64_C(0x928b5fce34cdf219), UINT64_C(0x3fac7a2a3cc3f1f0) } }, { { UINT64_C(0x6ea1bc9280000000), UINT64_C(0x3fff3a7db34e59ff) }, { UINT64_C(0xfe2f852ceeb13406), UINT64_C(0x3fab9e0a1d336163) } }, { { UINT64_C(0x235b412200000000), UINT64_C(0x3fff3dea64c12342) }, { UINT64_C(0x5c15c122133e2a22), UINT64_C(0x3facf09ebb9fdd16) } }, { { UINT64_C(0x9f84325b80000000), UINT64_C(0x3fff4160a21f72e2) }, { UINT64_C(0x0a81cd740ac68828), UINT64_C(0x3faae7b6c3f66a5e) } }, { { UINT64_C(0xd03136f400000000), UINT64_C(0x3fff44e086061892) }, { UINT64_C(0x690e136f0b23ab97), UINT64_C(0x3faa3be033f7a9e7) } }, { { UINT64_C(0x013c1a3b40000000), UINT64_C(0x3fff486a2b5c13cd) }, { UINT64_C(0xae743abfbc073765), UINT64_C(0x3fac4831781e1eeb) } }, { { UINT64_C(0x71d4397ac0000000), UINT64_C(0x3fff4bfdad5362a2) }, { UINT64_C(0xdd170ace2bcfc172), UINT64_C(0x3facf621710701b1) } }, { { UINT64_C(0x6ad33d8b40000000), UINT64_C(0x3fff4f9b2769d2ca) }, { UINT64_C(0x1424bd194d3999ee), UINT64_C(0x3fac4d5039f72af0) } }, { { UINT64_C(0x1df0a83c40000000), UINT64_C(0x3fff5342b569d4f8) }, { UINT64_C(0x547c85300b60928d), UINT64_C(0x3faa3b0d4c7e9cce) } }, { { UINT64_C(0xa66ecb0040000000), UINT64_C(0x3fff56f4736b527d) }, { UINT64_C(0x6acf60075f31c9cb), UINT64_C(0x3fa9d93acf003cbd) } }, { { UINT64_C(0x958c930140000000), UINT64_C(0x3fff5ab07dd48542) }, { UINT64_C(0x7c81280e069fbdb6), UINT64_C(0x3fab191eb345d88d) } }, { { UINT64_C(0x86e9be4c00000000), UINT64_C(0x3fff5e76f15ad214) }, { UINT64_C(0x4502c14f429ded96), UINT64_C(0x3fac01ccbb35032a) } }, { { UINT64_C(0x4b1f0fa040000000), UINT64_C(0x3fff6247eb03a558) }, { UINT64_C(0x57cfb9913adc5779), UINT64_C(0x3fac7e96d215d8e7) } }, { { UINT64_C(0x49127d9e00000000), UINT64_C(0x3fff662388255222) }, { UINT64_C(0xa6e051d6f8bc3ffc), UINT64_C(0x3fac4dc798a519bf) } }, { { UINT64_C(0xc908b2fb00000000), UINT64_C(0x3fff6a09e667f3bc) }, { UINT64_C(0xadec17512775099e), UINT64_C(0x3fab366ea957d3e3) } }, { { UINT64_C(0xef220e2c80000000), UINT64_C(0x3fff6dfb23c651a2) }, { UINT64_C(0x88abbe777df360e2), UINT64_C(0x3facf0ddd541a59f) } }, { { UINT64_C(0x3dd2370f00000000), UINT64_C(0x3fff71f75e8ec5f7) }, { UINT64_C(0x5ab16cf451056ed3), UINT64_C(0x3fac778566b65a1a) } }, { { UINT64_C(0x8bf6e9aa00000000), UINT64_C(0x3fff75feb564267c) }, { UINT64_C(0x2f30d0bdcaa516d7), UINT64_C(0x3fac9d24593838c0) } }, { { UINT64_C(0x6d7d510200000000), UINT64_C(0x3fff7a11473eb018) }, { UINT64_C(0x5b34bbcb0298f411), UINT64_C(0x3facfb66d0faf7a1) } }, { { UINT64_C(0x2105d02b80000000), UINT64_C(0x3fff7e2f336cf4e6) }, { UINT64_C(0x13fc7e6faf9c8308), UINT64_C(0x3fac0abcbf0b850a) } }, { { UINT64_C(0x28acf88ac0000000), UINT64_C(0x3fff82589994cce1) }, { UINT64_C(0xb2e5dd607a9969ce), UINT64_C(0x3facd59a50087b56) } }, { { UINT64_C(0xc80e41d900000000), UINT64_C(0x3fff868d99b4492e) }, { UINT64_C(0xe7e26df6aef2cad7), UINT64_C(0x3faa584a2e0e909a) } }, { { UINT64_C(0xb5ba7c5580000000), UINT64_C(0x3fff8ace5422aa0d) }, { UINT64_C(0xb0f939998251a36c), UINT64_C(0x3fac0c964dd9f376) } }, { { UINT64_C(0x62b9827440000000), UINT64_C(0x3fff8f1ae9915773) }, { UINT64_C(0xb4ae1458ae94609a), UINT64_C(0x3fabc72ed804efc9) } }, { { UINT64_C(0x4f4501c3c0000000), UINT64_C(0x3fff93737b0cdc5e) }, { UINT64_C(0x57ac0db1f419377f), UINT64_C(0x3fac92a0511697e2) } }, { { UINT64_C(0xf8b9e920c0000000), UINT64_C(0x3fff97d829fde4e4) }, { UINT64_C(0xb8a25b7b40c04266), UINT64_C(0x3facc8f45ebf6dce) } }, { { UINT64_C(0x01c7c46b00000000), UINT64_C(0x3fff9c49182a3f09) }, { UINT64_C(0x943085c61b242d16), UINT64_C(0x3fa9c7caf96376b7) } }, { { UINT64_C(0x4b29ada880000000), UINT64_C(0x3fffa0c667b5de56) }, { UINT64_C(0x1546d3ea28976d63), UINT64_C(0x3facc6559a4d5021) } }, { { UINT64_C(0xc8b4244900000000), UINT64_C(0x3fffa5503b23e255) }, { UINT64_C(0x405381703ef7caff), UINT64_C(0x3fabcaf87bc8050a) } }, { { UINT64_C(0xf43eb24380000000), UINT64_C(0x3fffa9e6b5579fdb) }, { UINT64_C(0x8e7a32e5783da5d0), UINT64_C(0x3faceffa6262c5ab) } }, { { UINT64_C(0xd5e8734d00000000), UINT64_C(0x3fffae89f995ad3a) }, { UINT64_C(0xae675ea440b162d7), UINT64_C(0x3fab773205a7fbc3) } }, { { UINT64_C(0xaf6bfd0e40000000), UINT64_C(0x3fffb33a2b84f15f) }, { UINT64_C(0x0edda4d891be43db), UINT64_C(0x3facdeca3e12babc) } }, { { UINT64_C(0x6eaa7b0800000000), UINT64_C(0x3fffb7f76f2fb5e4) }, { UINT64_C(0x03c356e4b625aacc), UINT64_C(0x3fabab53c5354c89) } }, { { UINT64_C(0x2247ba0f40000000), UINT64_C(0x3fffbcc1e904bc1d) }, { UINT64_C(0xa1c24af566f92b66), UINT64_C(0x3fa96cf423342c80) } }, { { UINT64_C(0xc2220cb100000000), UINT64_C(0x3fffc199bdd85529) }, { UINT64_C(0x24b251b330920022), UINT64_C(0x3fac5048dd333ca2) } }, { { UINT64_C(0xb4a2137fc0000000), UINT64_C(0x3fffc67f12e57d14) }, { UINT64_C(0x6b151a6d1f9d5d5f), UINT64_C(0x3fab20f2b301dd9e) } }, { { UINT64_C(0x91503cbd00000000), UINT64_C(0x3fffcb720dcef906) }, { UINT64_C(0x59ac0cb6dd3ed59a), UINT64_C(0x3fabe949db761d95) } }, { { UINT64_C(0xb8d0f22f00000000), UINT64_C(0x3fffd072d4a07897) }, { UINT64_C(0xe312f84fa6652044), UINT64_C(0x3fac0d0ac70c7ddf) } }, { { UINT64_C(0x725da05ac0000000), UINT64_C(0x3fffd5818dcfba48) }, { UINT64_C(0x4faace043b7f91c1), UINT64_C(0x3fac5b3706e54fac) } }, { { UINT64_C(0x5708c01a40000000), UINT64_C(0x3fffda9e603db328) }, { UINT64_C(0xf0ec614ec95c9939), UINT64_C(0x3fabb6d4c97f6246) } }, { { UINT64_C(0xeb968cac00000000), UINT64_C(0x3fffdfc97337b9b5) }, { UINT64_C(0x4a277eaddaa925c9), UINT64_C(0x3faccf6948db912d) } }, { { UINT64_C(0x6273d13000000000), UINT64_C(0x3fffe502ee78b3ff) }, { UINT64_C(0x9e1693be17ae1d30), UINT64_C(0x3fab53991e8f4965) } }, { { UINT64_C(0x9858f73a00000000), UINT64_C(0x3fffea4afa2a490d) }, { UINT64_C(0xea20610ceee13eb8), UINT64_C(0x3fab8f5db301f86d) } }, { { UINT64_C(0x7771fd2180000000), UINT64_C(0x3fffefa1bee615a2) }, { UINT64_C(0x914ffb4723793f1f), UINT64_C(0x3fac496d60fb6eae) } }, { { UINT64_C(0x0674f84b40000000), UINT64_C(0x3ffff50765b6e454) }, { UINT64_C(0x006fe21a95d14dc5), UINT64_C(0x3facb14315d7fcc8) } }, { { UINT64_C(0x82e90a7e40000000), UINT64_C(0x3ffffa7c1819e90d) }, { UINT64_C(0x61b7bb285a60791a), UINT64_C(0x3faca5931e0ee030) } }, }; static const CrtU128 crt_expq_c12000 = { UINT64_C(0x0000000000000000), UINT64_C(0x400c770000000000) }; static const CrtU128 crt_expq_c17000 = { UINT64_C(0x0000000000000000), UINT64_C(0x400d09a000000000) }; static const CrtU128 crt_expq_tiny_e = { UINT64_C(0x0000000000000000), UINT64_C(0x3f8d000000000000) }; static const CrtU128 crt_expq_tiny_2 = { UINT64_C(0xe1777d0ffda0d23a), UINT64_C(0x3f8d71547652b82f) }; // exp_f128.zig `expPoly` / `exp2Poly`: a^(r_hi + r_lo) - 1. static CrtU128 expqPoly(const CrtExpqCfg* c, CrtU128 r_hi, CrtU128 r_lo) { CrtU128 r = qAdd(r_hi, r_lo); double s = tToF64(128, r); CrtU128 rr = qMul(r, r); double ss = s * s; double m11 = s * crtF64(c->a11); double a10_11 = crtF64(c->a10) + m11; double m9 = s * crtF64(c->a9); double a8_9 = crtF64(c->a8) + m9; double m10 = ss * a10_11; double a8_11 = a8_9 + m10; CrtU128 a6_7 = qAdd(c->a6, qMul(r, c->a7)); CrtU128 a4_5 = qAdd(c->a4, qMul(r, c->a5)); CrtU128 a2_3 = qAdd(c->a2, qMul(r, c->a3)); CrtU128 inner = qAdd(a6_7, qMul(rr, tFromF64(128, a8_11))); CrtU128 a2_11 = qAdd(a2_3, qMul(rr, qAdd(a4_5, qMul(rr, inner)))); if (c->base2) return qMul(r, qAdd(c->a1, qMul(r, a2_11))); return qAdd(r_hi, qAdd(r_lo, qMul(rr, a2_11))); } // exp_f128.zig `exp` / `exp2` with `proc1` and `expFinalize`. static CrtU128 expqBase( const CrtExpqCfg* c, CrtU128 x, CrtU128 limit, CrtU128 tiny) { const CrtU128 one = crtPow2(crtFloat(128), 0); const CrtU128 zero = { 0, 0 }; bool neg = (x.hi >> 63) != 0; if (((x.hi >> CRT_F128_EXP_SHIFT) & CRT_F128_EXP_MASK) == CRT_F128_EXP_MASK) { if (x.lo != 0 || (x.hi & UINT64_C(0xffffffffffff)) != 0) return crtNanRep(crtFloat(128)); return neg ? zero : x; } if (qLt(limit, tAbs(128, x))) return neg ? zero : crtInfRep(crtFloat(128)); if (qLt(tAbs(128, x), tiny)) return qAdd(one, x); CrtU128 n = crtRoundq(qMul(x, c->inv_log_size)); int32_t ni = tToI32(128, n); int32_t n2 = ((ni % EXPQ_SIZE) + EXPQ_SIZE) % EXPQ_SIZE; // `@mod` int32_t m = (ni - n2) / EXPQ_SIZE; CrtU128 r_hi = qSub(x, qMul(n, c->log_size_hi)); CrtU128 r_lo = qMul(n, c->neg_log_size_lo); CrtU128 pr = expqPoly(c, r_hi, r_lo); CrtU128 sj_hi = crt_expq_tab[n2][0]; CrtU128 sj_lo = crt_expq_tab[n2][1]; CrtU128 sj = qAdd(sj_hi, sj_lo); return crtLdexpBits( qAdd(sj_hi, qAdd(sj_lo, qMul(sj, pr))), m, crtFloat(128)); } CrtU128 crtExpq(CrtU128 x) { return expqBase(&crt_expq_cfg, x, crt_expq_c12000, crt_expq_tiny_e); } CrtU128 crtExp2q(CrtU128 x) { return expqBase(&crt_exp2q_cfg, x, crt_expq_c17000, crt_expq_tiny_2); } // ── sin, cos, tan at f80 and f128 ────────────────────────────────────────── // sin.zig/cos.zig/tan.zig `sinx`/`sinq` and friends: rem_pio2l.zig's // argument reduction (its large path the f64 `rem_pio2_large` above), then // trig.zig's f80/f128 kernels. Every kernel constant is held at the kernel's // own width, as Zig widens the f64 coefficients before any arithmetic. static const CrtU128 crt_cosx_c[7] = { { UINT64_C(0xaaaaaaaaaaaaaa9b), UINT64_C(0x0000000000003ffa) }, // cosx.C1 { UINT64_C(0xb60b60b60b608000), UINT64_C(0x000000000000bff5) }, // cosx.C2 { UINT64_C(0xd00d00d00c711000), UINT64_C(0x0000000000003fef) }, // cosx.C3 { UINT64_C(0x93f27dbb01791000), UINT64_C(0x000000000000bfe9) }, // cosx.C4 { UINT64_C(0x8f76c65576678800), UINT64_C(0x0000000000003fe2) }, // cosx.C5 { UINT64_C(0xc9c9a095e8a94800), UINT64_C(0x000000000000bfda) }, // cosx.C6 { UINT64_C(0xd564ecd7ae21f000), UINT64_C(0x0000000000003fd2) }, // cosx.C7 }; static const CrtU128 crt_cosq_c[11] = { { UINT64_C(0x5555555555555548), UINT64_C(0x3ffa555555555555) }, // cosq.C1 { UINT64_C(0x6c16c16c16bf5c98), UINT64_C(0xbff56c16c16c16c1) }, // cosq.C2 { UINT64_C(0xa01a019fffc4b13d), UINT64_C(0x3fefa01a01a01a01) }, // cosq.C3 { UINT64_C(0xc72eef94869cac2a), UINT64_C(0xbfe927e4fb7789f5) }, // cosq.C4 { UINT64_C(0x7b51b5f62ea9599a), UINT64_C(0x3fe21eed8eff8d89) }, // cosq.C5 { UINT64_C(0xc2a38fc44bbc8df5), UINT64_C(0xbfda93974a8c07c9) }, // cosq.C6 { UINT64_C(0xdda73725a8cb76c2), UINT64_C(0x3fd2ae7f3e733b48) }, // cosq.C7 { UINT64_C(0xc000000000000000), UINT64_C(0xbfca6827863b100e) }, // cosq.C8 { UINT64_C(0x7000000000000000), UINT64_C(0x3fc1e542b8a108c0) }, // cosq.C9 { UINT64_C(0x0000000000000000), UINT64_C(0xbfb90ce20cd868a2) }, // cosq.C10 { UINT64_C(0x9000000000000000), UINT64_C(0x3fafef8127d765b0) }, // cosq.C11 }; static const CrtU128 crt_sinx_c[8] = { { UINT64_C(0xaaaaaaaaaaaaaaab), UINT64_C(0x000000000000bffc) }, // sinx.S1 { UINT64_C(0x8888888888888800), UINT64_C(0x0000000000003ff8) }, // sinx.S2 { UINT64_C(0xd00d00d00cfc0800), UINT64_C(0x000000000000bff2) }, // sinx.S3 { UINT64_C(0xb8ef1d2aab07b800), UINT64_C(0x0000000000003fec) }, // sinx.S4 { UINT64_C(0xd7322b278b656800), UINT64_C(0x000000000000bfe5) }, // sinx.S5 { UINT64_C(0xb09215c8121da800), UINT64_C(0x0000000000003fde) }, // sinx.S6 { UINT64_C(0xd72175e8d9700000), UINT64_C(0x000000000000bfd6) }, // sinx.S7 { UINT64_C(0xbc9b975059fb2000), UINT64_C(0x0000000000003fce) }, // sinx.S8 }; static const CrtU128 crt_sinq_c[12] = { { UINT64_C(0x5555555555555555), UINT64_C(0xbffc555555555555) }, // sinq.S1 { UINT64_C(0x111111111111107f), UINT64_C(0x3ff8111111111111) }, // sinq.S2 { UINT64_C(0xa01a01a019f8f785), UINT64_C(0xbff2a01a01a01a01) }, // sinq.S3 { UINT64_C(0x38faac1c557167fe), UINT64_C(0x3fec71de3a556c73) }, // sinq.S4 { UINT64_C(0x38fe73426974ae93), UINT64_C(0xbfe5ae64567f544e) }, // sinq.S5 { UINT64_C(0x97c5c00cfa3d6509), UINT64_C(0x3fde6124613a86d0) }, // sinq.S6 { UINT64_C(0xdc97972aded68d8d), UINT64_C(0xbfd6ae7f3e733b81) }, // sinq.S7 { UINT64_C(0x9d8ab423f5c47870), UINT64_C(0x3fce952c77030a96) }, // sinq.S8 { UINT64_C(0x3000000000000000), UINT64_C(0xbfc62f49b46796fe) }, // sinq.S9 { UINT64_C(0xe000000000000000), UINT64_C(0x3fbd71b8ee2094ba) }, // sinq.S10 { UINT64_C(0xd000000000000000), UINT64_C(0xbfb476190e2627fc) }, // sinq.S11 { UINT64_C(0xf000000000000000), UINT64_C(0x3fab3d19ffd7ad8b) }, // sinq.S12 }; static const CrtU128 crt_tanx_c[18] = { { UINT64_C(0xc90fdaa22168c235), UINT64_C(0x0000000000003ffe) }, // tanx.pio4 { UINT64_C(0xece675d1fc8f8cbb), UINT64_C(0x000000000000bfbc) }, // tanx.pio4lo { UINT64_C(0xaaaaaaaaaaaaaaa5), UINT64_C(0x0000000000003ffd) }, // tanx.T3 { UINT64_C(0x88888888888893c3), UINT64_C(0x0000000000003ffc) }, // tanx.T5 { UINT64_C(0xdd0dd0dd0dc13ba2), UINT64_C(0x0000000000003ffa) }, // tanx.T7 { UINT64_C(0xb327a441660e1000), UINT64_C(0x0000000000003ff9) }, // tanx.T9 { UINT64_C(0x91371aae0bb09000), UINT64_C(0x0000000000003ff8) }, // tanx.T11 { UINT64_C(0xeb69e8c2ebffc000), UINT64_C(0x0000000000003ff6) }, // tanx.T13 { UINT64_C(0xbed1aa551fcb5800), UINT64_C(0x0000000000003ff5) }, // tanx.T15 { UINT64_C(0x9aac9ac342dc1800), UINT64_C(0x0000000000003ff4) }, // tanx.T17 { UINT64_C(0xfab1210135adf000), UINT64_C(0x0000000000003ff2) }, // tanx.T19 { UINT64_C(0xcbbf7e134037a000), UINT64_C(0x0000000000003ff1) }, // tanx.T21 { UINT64_C(0xa13ad04d9e756000), UINT64_C(0x0000000000003ff0) }, // tanx.T23 { UINT64_C(0x97af2b1f2a43f000), UINT64_C(0x0000000000003fef) }, // tanx.T25 { UINT64_C(0xa2606c066344b000), UINT64_C(0x0000000000003fec) }, // tanx.T27 { UINT64_C(0x835ac8a0d3659800), UINT64_C(0x0000000000003fee) }, // tanx.T29 { UINT64_C(0xdad5f79dd25ac800), UINT64_C(0x000000000000bfec) }, // tanx.T31 { UINT64_C(0x9c1aa1b60643f800), UINT64_C(0x0000000000003fec) }, // tanx.T33 }; static const CrtU128 crt_tanq_c[30] = { { UINT64_C(0x8469898cc51701b8), UINT64_C(0x3ffe921fb54442d1) }, // tanq.pio4 { UINT64_C(0xa67cc74020bbea60), UINT64_C(0x3f8bcd129024e088) }, // tanq.pio4lo { UINT64_C(0x5555555555555553), UINT64_C(0x3ffd555555555555) }, // tanq.T3 { UINT64_C(0x1111111111111eb5), UINT64_C(0x3ffc111111111111) }, // tanq.T5 { UINT64_C(0xba1ba1ba1b694cd6), UINT64_C(0x3ffaba1ba1ba1ba1) }, // tanq.T7 { UINT64_C(0x9f32d6bbe09d8bcd), UINT64_C(0x3ff9664f4882c10f) }, // tanq.T9 { UINT64_C(0xc8f5b4f5762322ee), UINT64_C(0x3ff8226e355e6c23) }, // tanq.T11 { UINT64_C(0xfb5fed8e84e27b37), UINT64_C(0x3ff6d6d3d0e157dd) }, // tanq.T13 { UINT64_C(0x2b5fce9ee7c2c92e), UINT64_C(0x3ff57da36452b75e) }, // tanq.T15 { UINT64_C(0x477dfcf726649efe), UINT64_C(0x3ff4355824803674) }, // tanq.T17 { UINT64_C(0x6e0aceb716f614c2), UINT64_C(0x3ff2f57d7734d165) }, // tanq.T19 { UINT64_C(0x0ed942dfdc518d6c), UINT64_C(0x3ff1967e18afcb18) }, // tanq.T21 { UINT64_C(0x5bc7e2aa79b9f2cd), UINT64_C(0x3ff0497d8eea21e9) }, // tanq.T23 { UINT64_C(0xc81be49eff7afd50), UINT64_C(0x3fef0b132d39f055) }, // tanq.T25 { UINT64_C(0xbfa2fbc1059d90b6), UINT64_C(0x3fedb0f72d33eff7) }, // tanq.T27 { UINT64_C(0x13df38d0fbc00267), UINT64_C(0x3fec5ef2daf21d11) }, // tanq.T29 { UINT64_C(0x4988cdaa04c96626), UINT64_C(0x3feb1c77d6eac023) }, // tanq.T31 { UINT64_C(0x0e0bdd701057dfe3), UINT64_C(0x3fe9cd2a5a292b18) }, // tanq.T33 { UINT64_C(0xc01a31d0a6f7d518), UINT64_C(0x3fe875c7357d0298) }, // tanq.T35 { UINT64_C(0x9a520f98f50081fc), UINT64_C(0x3fe72f3190f4718a) }, // tanq.T37 { UINT64_C(0x8000000000000000), UINT64_C(0x3fe5e8a759297793) }, // tanq.T39 { UINT64_C(0x9000000000000000), UINT64_C(0x3fe49baa1b122321) }, // tanq.T41 { UINT64_C(0x9000000000000000), UINT64_C(0x3fe307385dfb2452) }, // tanq.T43 { UINT64_C(0x2000000000000000), UINT64_C(0x3fe2dc6c702a0526) }, // tanq.T45 { UINT64_C(0x6000000000000000), UINT64_C(0xbfe19ecef3569ebb) }, // tanq.T47 { UINT64_C(0xa000000000000000), UINT64_C(0x3fe294c0668da786) }, // tanq.T49 { UINT64_C(0x8000000000000000), UINT64_C(0xbfe22e763b884526) }, // tanq.T51 { UINT64_C(0x4000000000000000), UINT64_C(0x3fe1a92fc98c2955) }, // tanq.T53 { UINT64_C(0x9000000000000000), UINT64_C(0xbfe051106cbc779a) }, // tanq.T55 { UINT64_C(0xa000000000000000), UINT64_C(0x3fde47edbdba6f43) }, // tanq.T57 }; static const CrtU128 crt_trig_f80[12] = { { UINT64_C(0xc90fdaa22168c235), UINT64_C(0x0000000000003ffe) }, // remf80.pio4 { UINT64_C(0xa2f9836e4e44152a), UINT64_C(0x0000000000003ffe) }, // remf80.invpio2 { UINT64_C(0xc90fdaa222000000), UINT64_C(0x0000000000003fff) }, // remf80.pio2_1 { UINT64_C(0x973dcb3b399d747f), UINT64_C(0x000000000000bfd7) }, // remf80.pio2_1t { UINT64_C(0x973dcb3b3a000000), UINT64_C(0x000000000000bfd7) }, // remf80.pio2_2 { UINT64_C(0xc51701b839a25205), UINT64_C(0x0000000000003fae) }, // remf80.pio2_2t { UINT64_C(0xc51701b83a000000), UINT64_C(0x0000000000003fae) }, // remf80.pio2_3 { UINT64_C(0xbb5bf6c7ddd660ce), UINT64_C(0x000000000000bf85) }, // remf80.pio2_3t { UINT64_C(0xc000000000000000), UINT64_C(0x000000000000403e) }, // remf80.toint { UINT64_C(0xc90fdaa22168c235), UINT64_C(0x0000000000003ffe) }, // pi_4.f80 { UINT64_C(0xaca18bd66277c45d), UINT64_C(0x0000000000003ffe) }, // c067434.f80 { UINT64_C(0x8000000000000000), UINT64_C(0x000000000000401f) }, // p32.f80 }; static const CrtU128 crt_trig_f128[12] = { { UINT64_C(0x8469898cc51701b8), UINT64_C(0x3ffe921fb54442d1) }, // remf128.pio4 { UINT64_C(0x2a53f84eafa3ea6a), UINT64_C(0x3ffe45f306dc9c88) }, // remf128.invpio2 { UINT64_C(0x8469800000000000), UINT64_C(0x3fff921fb54442d1) }, // remf128.pio2_1 { UINT64_C(0x344a4093822299f3), UINT64_C(0x3fba3198a2e03707) }, // remf128.pio2_1t { UINT64_C(0x344a400000000000), UINT64_C(0x3fba3198a2e03707) }, // remf128.pio2_2 { UINT64_C(0x0105df531d89cd91), UINT64_C(0x3f7127044533e63a) }, // remf128.pio2_2t { UINT64_C(0x0105e00000000000), UINT64_C(0x3f7127044533e63a) }, // remf128.pio2_3 { UINT64_C(0xb5f78671cbfb2210), UINT64_C(0xbf2859c4ec64ddae) }, // remf128.pio2_3t { UINT64_C(0x0000000000000000), UINT64_C(0x406f800000000000) }, // remf128.toint { UINT64_C(0x8469898cc51701b8), UINT64_C(0x3ffe921fb54442d1) }, // pi_4.f128 { UINT64_C(0x88b97785729b280f), UINT64_C(0x3ffe594317acc4ef) }, // c067434.f128 { UINT64_C(0x0000000000000000), UINT64_C(0x401f000000000000) }, // p32.f128 }; // 2^e at width `b` (1.0, 0.5 and 2.0 in the kernels). static CrtU128 tPow2(uint32_t b, int32_t e) { return crtPow2(crtFloat(b), e); } // `math.scalbn(@abs(x), -math.ilogb(x) + e)` for a normal x. static CrtU128 tWithExp(uint32_t b, CrtU128 x, int32_t e) { int32_t ilogb = (int32_t)(tSignExp(b, x) & 0x7fffu) - 0x3fff; return crtLdexpBits(tAbs(b, x), e - ilogb, crtFloat(b)); } enum { TRIG_PIO4, TRIG_INVPIO2, TRIG_PIO2_1, TRIG_PIO2_1T, TRIG_PIO2_2, TRIG_PIO2_2T, TRIG_PIO2_3, TRIG_PIO2_3T, TRIG_TOINT, TRIG_PI_4, TRIG_TAN_BIG, TRIG_P32 }; static const CrtU128* trigConsts(uint32_t b) { return b == 80 ? crt_trig_f80 : crt_trig_f128; } // rem_pio2l.zig `rem_pio2l(T, x, y)`. static int32_t crtRemPio2l(uint32_t b, CrtU128 x, CrtU128* y) { const CrtU128* k = trigConsts(b); const int32_t round1 = b == 80 ? 22 : 51; const int32_t round2 = b == 80 ? 61 : 119; const uint32_t nx = b == 80 ? 3 : 5; const uint32_t ny = b == 80 ? 2 : 3; uint32_t x_se = tSignExp(b, x); int32_t ex = (int32_t)(x_se & 0x7fffu); uint32_t lhs = ((x_se & 0x7fffu) << 16) | tMantTop(b, x); uint32_t rhs = b == 80 ? (((0x3fffu + 25u) << 16) | (0x921fu >> 1) | 0x8000u) : (((0x3fffu + 45u) << 16) | 0x921fu); if (lhs < rhs) { CrtU128 fn = tSub(b, tAdd(b, tMul(b, x, k[TRIG_INVPIO2]), k[TRIG_TOINT]), k[TRIG_TOINT]); // `quobits`: the low 31 bits of `@intFromFloat(fn)` (i64 at f128). int32_t n = (int32_t)(tToU64Bits(b, fn) & 0x7fffffffu); CrtU128 r = tSub(b, x, tMul(b, fn, k[TRIG_PIO2_1])); CrtU128 w = tMul(b, fn, k[TRIG_PIO2_1T]); if (tLt(b, tSub(b, r, w), tNeg(b, k[TRIG_PIO4]))) { n -= 1; fn = tSub(b, fn, tPow2(b, 0)); r = tSub(b, x, tMul(b, fn, k[TRIG_PIO2_1])); w = tMul(b, fn, k[TRIG_PIO2_1T]); } else if (tGt(b, tSub(b, r, w), k[TRIG_PIO4])) { n += 1; fn = tAdd(b, fn, tPow2(b, 0)); r = tSub(b, x, tMul(b, fn, k[TRIG_PIO2_1])); w = tMul(b, fn, k[TRIG_PIO2_1T]); } y[0] = tSub(b, r, w); int32_t ey = (int32_t)(tSignExp(b, y[0]) & 0x7fffu); if (ex - ey > round1) { CrtU128 t = r; w = tMul(b, fn, k[TRIG_PIO2_2]); r = tSub(b, t, w); w = tSub( b, tMul(b, fn, k[TRIG_PIO2_2T]), tSub(b, tSub(b, t, r), w)); y[0] = tSub(b, r, w); int32_t ey2 = (int32_t)(tSignExp(b, y[0]) & 0x7fffu); if (ex - ey2 > round2) { t = r; w = tMul(b, fn, k[TRIG_PIO2_3]); r = tSub(b, t, w); w = tSub(b, tMul(b, fn, k[TRIG_PIO2_3T]), tSub(b, tSub(b, t, r), w)); y[0] = tSub(b, r, w); } } y[1] = tSub(b, tSub(b, r, y[0]), w); return n; } if (ex == 0x7fff) { // inf or NaN y[0] = tSub(b, x, x); y[1] = y[0]; return 0; } const CrtU128 two24 = tPow2(b, 24); CrtU128 z = tWithExp(b, x, 23); double tx[5], ty[3]; uint32_t i = 0; for (; i < nx - 1; i++) { tx[i] = tToI32(b, z); z = tMul(b, tSub(b, z, tFromF64(b, tx[i])), two24); } tx[i] = tToF64(b, z); while (f64IsZero(tx[i])) // tx[i] == 0 i -= 1; int32_t n = crtRemPio2Large(tx, ty, ex - 0x3fff - 23, (int32_t)i + 1, ny); double w = ty[1]; if (ny == 3) w += ty[2]; double r = ty[0] + w; double rd = r - ty[0]; w -= rd; if ((x_se >> 15) != 0) { y[0] = tNeg(b, tFromF64(b, r)); y[1] = tNeg(b, tFromF64(b, w)); return -n; } y[0] = tFromF64(b, r); y[1] = tFromF64(b, w); return n; } // trig.zig `cosx` / `cosq` kernels. static CrtU128 trigKcos(uint32_t b, CrtU128 x, CrtU128 y) { const CrtU128* c = b == 80 ? crt_cosx_c : crt_cosq_c; int last = b == 80 ? 6 : 10; CrtU128 z = tMul(b, x, x); CrtU128 acc = c[last]; for (int i = last - 1; i >= 0; i--) acc = tAdd(b, c[i], tMul(b, z, acc)); CrtU128 r = tMul(b, z, acc); CrtU128 hz = tMul(b, tPow2(b, -1), z); CrtU128 w = tSub(b, tPow2(b, 0), hz); // `w + (((1.0 - w) - hz) + (z * r - x * y))` return tAdd(b, w, tAdd(b, tSub(b, tSub(b, tPow2(b, 0), w), hz), tSub(b, tMul(b, z, r), tMul(b, x, y)))); } // trig.zig `sinx` / `sinq` kernels; `iy == 0` means y is zero. static CrtU128 trigKsin(uint32_t b, CrtU128 x, CrtU128 y, int iy) { const CrtU128* s = b == 80 ? crt_sinx_c : crt_sinq_c; int last = b == 80 ? 7 : 11; CrtU128 z = tMul(b, x, x); CrtU128 v = tMul(b, z, x); CrtU128 r = s[last]; for (int i = last - 1; i >= 1; i--) r = tAdd(b, s[i], tMul(b, z, r)); if (iy == 0) return tAdd(b, x, tMul(b, v, tAdd(b, s[0], tMul(b, z, r)))); // `x - ((z * (0.5 * y - v * r) - y) - v * S1)` CrtU128 inner = tSub(b, tMul(b, tPow2(b, -1), y), tMul(b, v, r)); return tSub( b, x, tSub(b, tSub(b, tMul(b, z, inner), y), tMul(b, v, s[0]))); } // trig.zig `tanx` / `tanq` kernels: tan (odd 0) or -1/tan (odd 1). static CrtU128 trigKtan(uint32_t b, CrtU128 x, CrtU128 y, int32_t odd) { const CrtU128* t = b == 80 ? crt_tanx_c : crt_tanq_c; const CrtU128* k = trigConsts(b); // t[0] = pio4, t[1] = pio4lo, then T3, T5, ... at t[2 + (n - 3) / 2]. const int last = b == 80 ? 17 : 29; // T33 / T57 bool big = tGe(b, tAbs(b, x), k[TRIG_TAN_BIG]); bool negative = false; if (big) { if (tLt(b, x, (CrtU128) { 0, 0 })) { negative = true; x = tNeg(b, x); y = tNeg(b, y); } x = tAdd(b, tSub(b, t[0], x), tSub(b, t[1], y)); y = (CrtU128) { 0, 0 }; } CrtU128 z = tMul(b, x, x); CrtU128 w = tMul(b, z, z); // r: T5, T9, ..., t[last]; v: T7, T11, ..., t[last - 1]. CrtU128 r = t[last]; for (int i = last - 2; i >= 3; i -= 2) r = tAdd(b, t[i], tMul(b, w, r)); CrtU128 v = t[last - 1]; for (int i = last - 3; i >= 4; i -= 2) v = tAdd(b, t[i], tMul(b, w, v)); v = tMul(b, z, v); CrtU128 s = tMul(b, z, x); // `y + z * (s * (r + v) + y) + T3 * s` r = tAdd(b, tAdd(b, y, tMul(b, z, tAdd(b, tMul(b, s, tAdd(b, r, v)), y))), tMul(b, t[2], s)); w = tAdd(b, x, r); if (big) { s = tFromI32(b, 1 - 2 * odd); // `s - 2.0 * (x + (r - w * w / (w + s)))` v = tSub(b, s, tMul(b, tPow2(b, 1), tAdd( b, x, tSub(b, r, tDiv(b, tMul(b, w, w), tAdd(b, w, s)))))); return negative ? tNeg(b, v) : v; } if (odd == 0) return w; const CrtU128 p32 = k[TRIG_P32]; z = tSub(b, tAdd(b, w, p32), p32); v = tSub(b, r, tSub(b, z, x)); CrtU128 a = tDiv(b, tNeg(b, tPow2(b, 0)), w); CrtU128 tt = tSub(b, tAdd(b, a, p32), p32); s = tAdd(b, tPow2(b, 0), tMul(b, tt, z)); return tAdd(b, tt, tMul(b, a, tAdd(b, s, tMul(b, tt, v)))); } // An f80 unnormal (nonzero exponent, integer bit clear) only comes from a // @bitCast; x87 treats it as NaN, and it would derail rem_pio2l. static bool tIsUnnormal(uint32_t b, CrtU128 x) { return b == 80 && (x.hi & 0x7fffu) != 0 && (x.lo >> 63) == 0; } CrtU128 crtSinT(uint32_t b, CrtU128 x) { uint32_t se = tSignExp(b, x) & 0x7fffu; if (se == 0x7fffu) return tSub(b, x, x); if (tIsUnnormal(b, x)) return crtNanRep(crtFloat(b)); if (tLt(b, tAbs(b, x), trigConsts(b)[TRIG_PI_4])) { if (se < 0x3fffu - crtFloat(b).significandBits / 2) return x; return trigKsin(b, x, (CrtU128) { 0, 0 }, 0); } CrtU128 y[2]; int32_t n = crtRemPio2l(b, x, y); switch (n & 3) { case 0: return trigKsin(b, y[0], y[1], 1); case 1: return trigKcos(b, y[0], y[1]); case 2: return tNeg(b, trigKsin(b, y[0], y[1], 1)); default: return tNeg(b, trigKcos(b, y[0], y[1])); } } CrtU128 crtCosT(uint32_t b, CrtU128 x) { uint32_t se = tSignExp(b, x) & 0x7fffu; if (se == 0x7fffu) return tSub(b, x, x); if (tIsUnnormal(b, x)) return crtNanRep(crtFloat(b)); if (tLt(b, tAbs(b, x), trigConsts(b)[TRIG_PI_4])) { if (se < 0x3fffu - crtFloat(b).significandBits) return tAdd(b, tPow2(b, 0), x); return trigKcos(b, x, (CrtU128) { 0, 0 }); } CrtU128 y[2]; int32_t n = crtRemPio2l(b, x, y); switch (n & 3) { case 0: return trigKcos(b, y[0], y[1]); case 1: return tNeg(b, trigKsin(b, y[0], y[1], 1)); case 2: return tNeg(b, trigKcos(b, y[0], y[1])); default: return trigKsin(b, y[0], y[1], 1); } } CrtU128 crtTanT(uint32_t b, CrtU128 x) { uint32_t se = tSignExp(b, x) & 0x7fffu; if (se == 0x7fffu) return tSub(b, x, x); if (tIsUnnormal(b, x)) return crtNanRep(crtFloat(b)); if (tLt(b, tAbs(b, x), trigConsts(b)[TRIG_PI_4])) { if (se < 0x3fffu - crtFloat(b).significandBits / 2) return x; return trigKtan(b, x, (CrtU128) { 0, 0 }, 0); } CrtU128 y[2]; int32_t n = crtRemPio2l(b, x, y); return trigKtan(b, y[0], y[1], n & 1); } // This file's macros end with it (zig1.c is one translation unit). #undef CRT_NAN_F32 #undef CRT_INF_F32 #undef CRT_NAN_F64 #undef CRT_INF_F64 #undef LOGQ_LOG2SIZE #undef LOGQ_SIZE #undef CRT_F128_EXP_SHIFT #undef CRT_F128_EXP_MASK #undef CRT_F128_BIAS #undef CRT_F128_MANT_BITS #undef EXPQ_SIZE /* ---- end softfloat.c ---- */ /* ---- begin string_literal.c ---- */ // lib/std/zig/string_literal.zig: string and character literal bodies, as // AstGen and ZonGen read them. A failure reports the byte offset upstream's // Error carries; the error kind is not modeled (an invalid literal stops zig1 // at its first compile error). #ifndef ZIG1_H #include "zig1.h" #endif // The UTF-8 encoding of `codepoint` (std.unicode.utf8Encode); 0 for a // surrogate half or a value past U+10FFFF. static uint32_t stringLiteralUtf8Encode(uint32_t codepoint, uint8_t out[4]) { if (codepoint < 0x80) { out[0] = (uint8_t)codepoint; return 1; } if (codepoint < 0x800) { out[0] = (uint8_t)(0xC0 | (codepoint >> 6)); out[1] = (uint8_t)(0x80 | (codepoint & 0x3F)); return 2; } if (codepoint < 0x10000) { if (codepoint >= 0xD800 && codepoint <= 0xDFFF) return 0; out[0] = (uint8_t)(0xE0 | (codepoint >> 12)); out[1] = (uint8_t)(0x80 | ((codepoint >> 6) & 0x3F)); out[2] = (uint8_t)(0x80 | (codepoint & 0x3F)); return 3; } if (codepoint < 0x110000) { out[0] = (uint8_t)(0xF0 | (codepoint >> 18)); out[1] = (uint8_t)(0x80 | ((codepoint >> 12) & 0x3F)); out[2] = (uint8_t)(0x80 | ((codepoint >> 6) & 0x3F)); out[3] = (uint8_t)(0x80 | (codepoint & 0x3F)); return 4; } return 0; } static bool stringLiteralHexDigit(char c, uint32_t* out) { if (c >= '0' && c <= '9') *out = (uint32_t)(c - '0'); else if (c >= 'a' && c <= 'f') *out = (uint32_t)(c - 'a' + 10); else if (c >= 'A' && c <= 'F') *out = (uint32_t)(c - 'A' + 10); else return false; return true; } // string_literal.zig:parseEscapeSequence // The escape at `slice[*offset]` (a backslash); `*offset` moves past it. bool stringLiteralParseEscapeSequence(const char* slice, uint32_t len, uint32_t* offset, uint32_t* out, uint32_t* err_offset) { if (len == *offset + 1) { *err_offset = *offset + 1; return false; } *offset += 2; switch (slice[*offset - 1]) { case 'n': *out = '\n'; return true; case 'r': *out = '\r'; return true; case '\\': *out = '\\'; return true; case 't': *out = '\t'; return true; case '\'': *out = '\''; return true; case '"': *out = '"'; return true; case 'x': { uint32_t value = 0; uint32_t i = *offset; for (; i < *offset + 2; i++) { uint32_t d; if (i == len || !stringLiteralHexDigit(slice[i], &d)) { *err_offset = i; return false; } value = value * 16 + d; } *offset = i; *out = value; return true; } case 'u': { uint32_t i = *offset; if (i >= len || slice[i] != '{') { *err_offset = i; return false; } i++; if (i >= len || slice[i] == '}') { *err_offset = i; return false; } uint32_t value = 0; for (;; i++) { if (i >= len) { *err_offset = i; return false; } if (slice[i] == '}') { i++; break; } uint32_t d; if (!stringLiteralHexDigit(slice[i], &d)) { *err_offset = i; return false; } value = value * 16 + d; if (value > 0x10ffff) { *err_offset = i; return false; } } *offset = i; *out = value; return true; } default: *err_offset = *offset - 1; return false; } } // string_literal.zig:parseWrite // Appends the bytes of the quoted literal `bytes` to `out`. bool stringLiteralParseWrite( const char* bytes, uint32_t len, U8List* out, uint32_t* err_offset) { uint32_t index = 1; while (true) { const char b = bytes[index]; switch (b) { case '\\': { const uint32_t escape_char_index = index + 1; uint32_t codepoint; if (!stringLiteralParseEscapeSequence( bytes, len, &index, &codepoint, err_offset)) return false; if (bytes[escape_char_index] == 'u') { uint8_t buf[4]; const uint32_t n = stringLiteralUtf8Encode(codepoint, buf); if (n == 0) { *err_offset = escape_char_index + 1; return false; } for (uint32_t k = 0; k < n; k++) u8ListAppend(out, buf[k]); } else { u8ListAppend(out, (uint8_t)codepoint); } break; } case '\n': *err_offset = index; return false; case '"': return true; default: u8ListAppend(out, (uint8_t)b); index++; } } } // string_literal.zig:parseCharLiteral bool stringLiteralParseCharLiteral( const char* slice, uint32_t len, uint32_t* out, uint32_t* err_offset) { if (len < 3) { *err_offset = 0; return false; } if (slice[1] == '\\') { uint32_t offset = 1; if (!stringLiteralParseEscapeSequence( slice, len, &offset, out, err_offset)) return false; if (offset + 1 != len || slice[offset] != '\'') { *err_offset = offset; return false; } return true; } if (slice[1] == 0) { *err_offset = 1; return false; } // std.unicode.utf8ByteSequenceLength, then utf8Decode2/3/4. const uint8_t* inner = (const uint8_t*)slice + 1; const uint32_t inner_len = len - 2; const uint8_t b0 = inner[0]; uint32_t n; uint32_t cp; if (b0 < 0x80) { n = 1; cp = b0; } else if ((b0 & 0xE0) == 0xC0) { n = 2; cp = b0 & 0x1F; } else if ((b0 & 0xF0) == 0xE0) { n = 3; cp = b0 & 0x0F; } else if ((b0 & 0xF8) == 0xF0) { n = 4; cp = b0 & 0x07; } else { *err_offset = 1; return false; } if (inner_len > n) { *err_offset = 1 + n; return false; } if (inner_len < n) { *err_offset = 1; return false; } for (uint32_t k = 1; k < n; k++) { if ((inner[k] & 0xC0) != 0x80) { *err_offset = 1; return false; } cp = (cp << 6) | (inner[k] & 0x3F); } // Overlong encodings, surrogate halves and values past U+10FFFF. static const uint32_t min_cp[5] = { 0, 0, 0x80, 0x800, 0x10000 }; if (cp < min_cp[n] || (cp >= 0xD800 && cp <= 0xDFFF) || cp > 0x10FFFF) { *err_offset = 1; return false; } *out = cp; return true; } /* ---- end string_literal.c ---- */ /* ---- begin tokenizer.c ---- */ // tokenizer.c — ported from lib/std/zig/tokenizer.zig. #ifndef ZIG1_H #include "zig1.h" #endif #include #include // Token.keywords, in upstream's order. static const struct { const char* bytes; TokenTag tag; } keywords[] = { { "addrspace", TOKEN_KEYWORD_ADDRSPACE }, { "align", TOKEN_KEYWORD_ALIGN }, { "allowzero", TOKEN_KEYWORD_ALLOWZERO }, { "and", TOKEN_KEYWORD_AND }, { "anyframe", TOKEN_KEYWORD_ANYFRAME }, { "anytype", TOKEN_KEYWORD_ANYTYPE }, { "asm", TOKEN_KEYWORD_ASM }, { "break", TOKEN_KEYWORD_BREAK }, { "callconv", TOKEN_KEYWORD_CALLCONV }, { "catch", TOKEN_KEYWORD_CATCH }, { "comptime", TOKEN_KEYWORD_COMPTIME }, { "const", TOKEN_KEYWORD_CONST }, { "continue", TOKEN_KEYWORD_CONTINUE }, { "defer", TOKEN_KEYWORD_DEFER }, { "else", TOKEN_KEYWORD_ELSE }, { "enum", TOKEN_KEYWORD_ENUM }, { "errdefer", TOKEN_KEYWORD_ERRDEFER }, { "error", TOKEN_KEYWORD_ERROR }, { "export", TOKEN_KEYWORD_EXPORT }, { "extern", TOKEN_KEYWORD_EXTERN }, { "fn", TOKEN_KEYWORD_FN }, { "for", TOKEN_KEYWORD_FOR }, { "if", TOKEN_KEYWORD_IF }, { "inline", TOKEN_KEYWORD_INLINE }, { "noalias", TOKEN_KEYWORD_NOALIAS }, { "noinline", TOKEN_KEYWORD_NOINLINE }, { "nosuspend", TOKEN_KEYWORD_NOSUSPEND }, { "opaque", TOKEN_KEYWORD_OPAQUE }, { "or", TOKEN_KEYWORD_OR }, { "orelse", TOKEN_KEYWORD_ORELSE }, { "packed", TOKEN_KEYWORD_PACKED }, { "pub", TOKEN_KEYWORD_PUB }, { "resume", TOKEN_KEYWORD_RESUME }, { "return", TOKEN_KEYWORD_RETURN }, { "linksection", TOKEN_KEYWORD_LINKSECTION }, { "struct", TOKEN_KEYWORD_STRUCT }, { "suspend", TOKEN_KEYWORD_SUSPEND }, { "switch", TOKEN_KEYWORD_SWITCH }, { "test", TOKEN_KEYWORD_TEST }, { "threadlocal", TOKEN_KEYWORD_THREADLOCAL }, { "try", TOKEN_KEYWORD_TRY }, { "union", TOKEN_KEYWORD_UNION }, { "unreachable", TOKEN_KEYWORD_UNREACHABLE }, { "var", TOKEN_KEYWORD_VAR }, { "volatile", TOKEN_KEYWORD_VOLATILE }, { "while", TOKEN_KEYWORD_WHILE }, }; // tokenizer.zig:Token.getKeyword // A linear scan where upstream uses a StaticStringMap; TOKEN_INVALID stands // for null. static TokenTag getKeyword(const char* bytes, uint32_t len) { for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) { if (strlen(keywords[i].bytes) == len && memcmp(bytes, keywords[i].bytes, len) == 0) return keywords[i].tag; } return TOKEN_INVALID; } // tokenizer.zig:Tokenizer.init Tokenizer tokenizerInit(const char* buffer, const uint32_t len) { return (Tokenizer) { .buffer = buffer, .buffer_len = len, .index = (len >= 3 && memcmp(buffer, "\xEF\xBB\xBF", 3) == 0) ? 3 : 0, }; } // tokenizer.zig:Tokenizer.next Token tokenizerNext(Tokenizer* self) { Token result = (Token) { .tag = TOKEN_INVALID, .loc = { .start = self->index, }, }; TokenizerState state = TOKENIZER_STATE_START; state: switch (state) { case TOKENIZER_STATE_START: switch (self->buffer[self->index]) { case 0: if (self->index == self->buffer_len) { return (Token) { .tag = TOKEN_EOF, .loc = { .start = self->index, .end = self->index, } }; } else { state = TOKENIZER_STATE_INVALID; goto state; } // clang-format off case ' ': case '\n': case '\t': case '\r': // clang-format on self->index++; result.loc.start = self->index; goto state; case '"': result.tag = TOKEN_STRING_LITERAL; state = TOKENIZER_STATE_STRING_LITERAL; goto state; case '\'': result.tag = TOKEN_CHAR_LITERAL; state = TOKENIZER_STATE_CHAR_LITERAL; goto state; // clang-format off case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': // clang-format on result.tag = TOKEN_IDENTIFIER; state = TOKENIZER_STATE_IDENTIFIER; goto state; case '@': state = TOKENIZER_STATE_SAW_AT_SIGN; goto state; case '=': state = TOKENIZER_STATE_EQUAL; goto state; case '!': state = TOKENIZER_STATE_BANG; goto state; case '|': state = TOKENIZER_STATE_PIPE; goto state; case '(': result.tag = TOKEN_L_PAREN; self->index++; break; case ')': result.tag = TOKEN_R_PAREN; self->index++; break; case '[': result.tag = TOKEN_L_BRACKET; self->index++; break; case ']': result.tag = TOKEN_R_BRACKET; self->index++; break; case ';': result.tag = TOKEN_SEMICOLON; self->index++; break; case ',': result.tag = TOKEN_COMMA; self->index++; break; case '?': result.tag = TOKEN_QUESTION_MARK; self->index++; break; case ':': result.tag = TOKEN_COLON; self->index++; break; case '%': state = TOKENIZER_STATE_PERCENT; goto state; case '*': state = TOKENIZER_STATE_ASTERISK; goto state; case '+': state = TOKENIZER_STATE_PLUS; goto state; case '<': state = TOKENIZER_STATE_ANGLE_BRACKET_LEFT; goto state; case '>': state = TOKENIZER_STATE_ANGLE_BRACKET_RIGHT; goto state; case '^': state = TOKENIZER_STATE_CARET; goto state; case '\\': result.tag = TOKEN_MULTILINE_STRING_LITERAL_LINE; state = TOKENIZER_STATE_BACKSLASH; goto state; case '{': result.tag = TOKEN_L_BRACE; self->index++; break; case '}': result.tag = TOKEN_R_BRACE; self->index++; break; case '~': result.tag = TOKEN_TILDE; self->index++; break; case '.': state = TOKENIZER_STATE_PERIOD; goto state; case '-': state = TOKENIZER_STATE_MINUS; goto state; case '/': state = TOKENIZER_STATE_SLASH; goto state; case '&': state = TOKENIZER_STATE_AMPERSAND; goto state; // clang-format off case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // clang-format on result.tag = TOKEN_NUMBER_LITERAL; self->index++; state = TOKENIZER_STATE_INT; goto state; default: state = TOKENIZER_STATE_INVALID; goto state; } break; case TOKENIZER_STATE_EXPECT_NEWLINE: self->index++; switch (self->buffer[self->index]) { case 0: if (self->index == self->buffer_len) { result.tag = TOKEN_INVALID; } else { state = TOKENIZER_STATE_INVALID; goto state; } break; case '\n': self->index++; result.loc.start = self->index; state = TOKENIZER_STATE_START; goto state; default: state = TOKENIZER_STATE_INVALID; goto state; } break; case TOKENIZER_STATE_INVALID: self->index++; switch (self->buffer[self->index]) { case 0: if (self->index == self->buffer_len) { result.tag = TOKEN_INVALID; } else { state = TOKENIZER_STATE_INVALID; goto state; } break; case '\n': result.tag = TOKEN_INVALID; break; default: state = TOKENIZER_STATE_INVALID; goto state; } break; case TOKENIZER_STATE_SAW_AT_SIGN: self->index++; switch (self->buffer[self->index]) { case 0: case '\n': result.tag = TOKEN_INVALID; break; case '"': result.tag = TOKEN_IDENTIFIER; state = TOKENIZER_STATE_STRING_LITERAL; goto state; // clang-format off case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': // clang-format on result.tag = TOKEN_BUILTIN; state = TOKENIZER_STATE_BUILTIN; goto state; default: state = TOKENIZER_STATE_INVALID; goto state; } break; case TOKENIZER_STATE_AMPERSAND: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_AMPERSAND_EQUAL; self->index++; break; default: result.tag = TOKEN_AMPERSAND; break; } break; case TOKENIZER_STATE_ASTERISK: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_ASTERISK_EQUAL; self->index++; break; case '%': state = TOKENIZER_STATE_ASTERISK_PERCENT; goto state; case '|': state = TOKENIZER_STATE_ASTERISK_PIPE; goto state; default: result.tag = TOKEN_ASTERISK; break; } break; case TOKENIZER_STATE_ASTERISK_PERCENT: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_ASTERISK_PERCENT_EQUAL; self->index++; break; default: result.tag = TOKEN_ASTERISK_PERCENT; break; } break; case TOKENIZER_STATE_ASTERISK_PIPE: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_ASTERISK_PIPE_EQUAL; self->index++; break; default: result.tag = TOKEN_ASTERISK_PIPE; break; } break; case TOKENIZER_STATE_PERCENT: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_PERCENT_EQUAL; self->index++; break; default: result.tag = TOKEN_PERCENT; break; } break; case TOKENIZER_STATE_PLUS: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_PLUS_EQUAL; self->index++; break; case '+': result.tag = TOKEN_PLUS_PLUS; self->index++; break; case '%': state = TOKENIZER_STATE_PLUS_PERCENT; goto state; case '|': state = TOKENIZER_STATE_PLUS_PIPE; goto state; default: result.tag = TOKEN_PLUS; break; } break; case TOKENIZER_STATE_PLUS_PERCENT: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_PLUS_PERCENT_EQUAL; self->index++; break; default: result.tag = TOKEN_PLUS_PERCENT; break; } break; case TOKENIZER_STATE_PLUS_PIPE: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_PLUS_PIPE_EQUAL; self->index++; break; default: result.tag = TOKEN_PLUS_PIPE; break; } break; case TOKENIZER_STATE_CARET: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_CARET_EQUAL; self->index++; break; default: result.tag = TOKEN_CARET; break; } break; case TOKENIZER_STATE_IDENTIFIER: self->index++; switch (self->buffer[self->index]) { // clang-format off case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // clang-format on state = TOKENIZER_STATE_IDENTIFIER; goto state; default:; // Once we're at C23, this semicolon can be removed. const char* start = self->buffer + result.loc.start; uint32_t len = self->index - result.loc.start; TokenTag tag = getKeyword(start, len); if (tag != TOKEN_INVALID) result.tag = tag; } break; case TOKENIZER_STATE_BUILTIN: self->index++; switch (self->buffer[self->index]) { // clang-format off case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // clang-format on state = TOKENIZER_STATE_BUILTIN; goto state; } break; case TOKENIZER_STATE_BACKSLASH: self->index++; switch (self->buffer[self->index]) { case 0: result.tag = TOKEN_INVALID; break; case '\\': state = TOKENIZER_STATE_MULTILINE_STRING_LITERAL_LINE; goto state; case '\n': result.tag = TOKEN_INVALID; break; default: state = TOKENIZER_STATE_INVALID; goto state; } break; case TOKENIZER_STATE_STRING_LITERAL: self->index++; switch (self->buffer[self->index]) { case 0: if (self->index != self->buffer_len) { state = TOKENIZER_STATE_INVALID; goto state; } else { result.tag = TOKEN_INVALID; } break; case '\n': result.tag = TOKEN_INVALID; break; case '\\': state = TOKENIZER_STATE_STRING_LITERAL_BACKSLASH; goto state; case '"': self->index++; break; // clang-format off case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x7f: // clang-format on state = TOKENIZER_STATE_INVALID; goto state; default: state = TOKENIZER_STATE_STRING_LITERAL; goto state; } break; case TOKENIZER_STATE_STRING_LITERAL_BACKSLASH: self->index++; switch (self->buffer[self->index]) { case 0: case '\n': result.tag = TOKEN_INVALID; break; // clang-format off case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x7f: // clang-format on state = TOKENIZER_STATE_INVALID; goto state; default: state = TOKENIZER_STATE_STRING_LITERAL; goto state; } break; case TOKENIZER_STATE_CHAR_LITERAL: self->index++; switch (self->buffer[self->index]) { case 0: if (self->index != self->buffer_len) { state = TOKENIZER_STATE_INVALID; goto state; } else { result.tag = TOKEN_INVALID; } break; case '\n': result.tag = TOKEN_INVALID; break; case '\\': state = TOKENIZER_STATE_CHAR_LITERAL_BACKSLASH; goto state; case '\'': self->index++; break; // clang-format off case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x7f: // clang-format on state = TOKENIZER_STATE_INVALID; goto state; default: state = TOKENIZER_STATE_CHAR_LITERAL; goto state; } break; case TOKENIZER_STATE_CHAR_LITERAL_BACKSLASH: self->index++; switch (self->buffer[self->index]) { case 0: if (self->index != self->buffer_len) { state = TOKENIZER_STATE_INVALID; goto state; } else { result.tag = TOKEN_INVALID; } break; case '\n': result.tag = TOKEN_INVALID; break; // clang-format off case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x7f: // clang-format on state = TOKENIZER_STATE_INVALID; goto state; default: state = TOKENIZER_STATE_CHAR_LITERAL; goto state; } break; case TOKENIZER_STATE_MULTILINE_STRING_LITERAL_LINE: self->index++; switch (self->buffer[self->index]) { case 0: if (self->index != self->buffer_len) { state = TOKENIZER_STATE_INVALID; goto state; } break; case '\n': break; case '\r': if (self->buffer[self->index + 1] != '\n') { state = TOKENIZER_STATE_INVALID; goto state; } break; // clang-format off case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0b: case 0x0c: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x7f: // clang-format on state = TOKENIZER_STATE_INVALID; goto state; default: state = TOKENIZER_STATE_MULTILINE_STRING_LITERAL_LINE; goto state; } break; case TOKENIZER_STATE_BANG: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_BANG_EQUAL; self->index++; break; default: result.tag = TOKEN_BANG; break; } break; case TOKENIZER_STATE_PIPE: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_PIPE_EQUAL; self->index++; break; case '|': result.tag = TOKEN_PIPE_PIPE; self->index++; break; default: result.tag = TOKEN_PIPE; break; } break; case TOKENIZER_STATE_EQUAL: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_EQUAL_EQUAL; self->index++; break; case '>': result.tag = TOKEN_EQUAL_ANGLE_BRACKET_RIGHT; self->index++; break; default: result.tag = TOKEN_EQUAL; break; } break; case TOKENIZER_STATE_MINUS: self->index++; switch (self->buffer[self->index]) { case '>': result.tag = TOKEN_ARROW; self->index++; break; case '=': result.tag = TOKEN_MINUS_EQUAL; self->index++; break; case '%': state = TOKENIZER_STATE_MINUS_PERCENT; goto state; case '|': state = TOKENIZER_STATE_MINUS_PIPE; goto state; default: result.tag = TOKEN_MINUS; break; } break; case TOKENIZER_STATE_MINUS_PERCENT: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_MINUS_PERCENT_EQUAL; self->index++; break; default: result.tag = TOKEN_MINUS_PERCENT; break; } break; case TOKENIZER_STATE_MINUS_PIPE: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_MINUS_PIPE_EQUAL; self->index++; break; default: result.tag = TOKEN_MINUS_PIPE; break; } break; case TOKENIZER_STATE_ANGLE_BRACKET_LEFT: self->index++; switch (self->buffer[self->index]) { case '<': state = TOKENIZER_STATE_ANGLE_BRACKET_ANGLE_BRACKET_LEFT; goto state; case '=': result.tag = TOKEN_ANGLE_BRACKET_LEFT_EQUAL; self->index++; break; default: result.tag = TOKEN_ANGLE_BRACKET_LEFT; break; } break; case TOKENIZER_STATE_ANGLE_BRACKET_ANGLE_BRACKET_LEFT: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_EQUAL; self->index++; break; case '|': state = TOKENIZER_STATE_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE; goto state; default: result.tag = TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT; break; } break; case TOKENIZER_STATE_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE_EQUAL; self->index++; break; default: result.tag = TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE; break; } break; case TOKENIZER_STATE_ANGLE_BRACKET_RIGHT: self->index++; switch (self->buffer[self->index]) { case '>': state = TOKENIZER_STATE_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT; goto state; case '=': result.tag = TOKEN_ANGLE_BRACKET_RIGHT_EQUAL; self->index++; break; default: result.tag = TOKEN_ANGLE_BRACKET_RIGHT; break; } break; case TOKENIZER_STATE_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT: self->index++; switch (self->buffer[self->index]) { case '=': result.tag = TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT_EQUAL; self->index++; break; default: result.tag = TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT; break; } break; case TOKENIZER_STATE_PERIOD: self->index++; switch (self->buffer[self->index]) { case '.': state = TOKENIZER_STATE_PERIOD_2; goto state; case '*': result.tag = TOKEN_PERIOD_ASTERISK; self->index++; break; default: result.tag = TOKEN_PERIOD; break; } break; case TOKENIZER_STATE_PERIOD_2: self->index++; switch (self->buffer[self->index]) { case '.': result.tag = TOKEN_ELLIPSIS3; self->index++; break; default: result.tag = TOKEN_ELLIPSIS2; break; } break; case TOKENIZER_STATE_SLASH: self->index++; switch (self->buffer[self->index]) { case '/': state = TOKENIZER_STATE_LINE_COMMENT_START; goto state; case '=': result.tag = TOKEN_SLASH_EQUAL; self->index++; break; default: result.tag = TOKEN_SLASH; break; } break; case TOKENIZER_STATE_LINE_COMMENT_START: self->index++; switch (self->buffer[self->index]) { case 0: if (self->index != self->buffer_len) { state = TOKENIZER_STATE_INVALID; goto state; } else { return (Token) { .tag = TOKEN_EOF, .loc = { .start = self->index, .end = self->index, } }; } case '!': result.tag = TOKEN_CONTAINER_DOC_COMMENT; state = TOKENIZER_STATE_DOC_COMMENT; goto state; case '\n': self->index++; result.loc.start = self->index; state = TOKENIZER_STATE_START; goto state; case '/': state = TOKENIZER_STATE_DOC_COMMENT_START; goto state; case '\r': state = TOKENIZER_STATE_EXPECT_NEWLINE; goto state; // clang-format off case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0b: case 0x0c: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x7f: // clang-format on state = TOKENIZER_STATE_INVALID; goto state; default: state = TOKENIZER_STATE_LINE_COMMENT; goto state; } case TOKENIZER_STATE_DOC_COMMENT_START: self->index++; switch (self->buffer[self->index]) { case 0: case '\n': result.tag = TOKEN_DOC_COMMENT; break; case '\r': if (self->buffer[self->index + 1] == '\n') { result.tag = TOKEN_DOC_COMMENT; } else { state = TOKENIZER_STATE_INVALID; goto state; } break; case '/': state = TOKENIZER_STATE_LINE_COMMENT; goto state; // clang-format off case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0b: case 0x0c: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x7f: // clang-format on state = TOKENIZER_STATE_INVALID; goto state; default: result.tag = TOKEN_DOC_COMMENT; state = TOKENIZER_STATE_DOC_COMMENT; goto state; } break; case TOKENIZER_STATE_LINE_COMMENT: self->index++; switch (self->buffer[self->index]) { case 0: if (self->index != self->buffer_len) { state = TOKENIZER_STATE_INVALID; goto state; } else { return (Token) { .tag = TOKEN_EOF, .loc = { .start = self->index, .end = self->index, } }; } case '\n': self->index++; result.loc.start = self->index; state = TOKENIZER_STATE_START; goto state; case '\r': state = TOKENIZER_STATE_EXPECT_NEWLINE; goto state; // clang-format off case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0b: case 0x0c: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x7f: // clang-format on state = TOKENIZER_STATE_INVALID; goto state; default: state = TOKENIZER_STATE_LINE_COMMENT; goto state; } case TOKENIZER_STATE_DOC_COMMENT: self->index++; switch (self->buffer[self->index]) { case 0: case '\n': break; case '\r': if (self->buffer[self->index + 1] != '\n') { state = TOKENIZER_STATE_INVALID; goto state; } break; // clang-format off case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0b: case 0x0c: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x7f: // clang-format on state = TOKENIZER_STATE_INVALID; goto state; default: state = TOKENIZER_STATE_DOC_COMMENT; goto state; } break; case TOKENIZER_STATE_INT: switch (self->buffer[self->index]) { case '.': state = TOKENIZER_STATE_INT_PERIOD; goto state; // clang-format off case '_': case 'a': case 'b': case 'c': case 'd': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // clang-format on self->index++; state = TOKENIZER_STATE_INT; goto state; // clang-format off case 'e': case 'E': case 'p': case 'P': // clang-format on state = TOKENIZER_STATE_INT_EXPONENT; goto state; default: break; } break; case TOKENIZER_STATE_INT_EXPONENT: self->index++; switch (self->buffer[self->index]) { case '-': case '+': self->index++; state = TOKENIZER_STATE_FLOAT; goto state; default: state = TOKENIZER_STATE_INT; goto state; } case TOKENIZER_STATE_INT_PERIOD: self->index++; switch (self->buffer[self->index]) { // clang-format off case '_': case 'a': case 'b': case 'c': case 'd': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // clang-format on self->index++; state = TOKENIZER_STATE_FLOAT; goto state; // clang-format off case 'e': case 'E': case 'p': case 'P': // clang-format on state = TOKENIZER_STATE_FLOAT_EXPONENT; goto state; default: self->index--; break; } break; case TOKENIZER_STATE_FLOAT: switch (self->buffer[self->index]) { // clang-format off case '_': case 'a': case 'b': case 'c': case 'd': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // clang-format on self->index++; state = TOKENIZER_STATE_FLOAT; goto state; // clang-format off case 'e': case 'E': case 'p': case 'P': // clang-format on state = TOKENIZER_STATE_FLOAT_EXPONENT; goto state; default: break; } break; case TOKENIZER_STATE_FLOAT_EXPONENT: self->index++; switch (self->buffer[self->index]) { case '-': case '+': self->index++; state = TOKENIZER_STATE_FLOAT; goto state; default: state = TOKENIZER_STATE_FLOAT; goto state; } } result.loc.end = self->index; return result; } // tokenizer.zig:Token.Tag.lexeme const char* tokenTagLexeme(TokenTag tag) { switch (tag) { case TOKEN_INVALID: case TOKEN_IDENTIFIER: case TOKEN_STRING_LITERAL: case TOKEN_MULTILINE_STRING_LITERAL_LINE: case TOKEN_CHAR_LITERAL: case TOKEN_EOF: case TOKEN_BUILTIN: case TOKEN_NUMBER_LITERAL: case TOKEN_DOC_COMMENT: case TOKEN_CONTAINER_DOC_COMMENT: return NULL; case TOKEN_BANG: return "!"; case TOKEN_PIPE: return "|"; case TOKEN_PIPE_PIPE: return "||"; case TOKEN_PIPE_EQUAL: return "|="; case TOKEN_EQUAL: return "="; case TOKEN_EQUAL_EQUAL: return "=="; case TOKEN_EQUAL_ANGLE_BRACKET_RIGHT: return "=>"; case TOKEN_BANG_EQUAL: return "!="; case TOKEN_L_PAREN: return "("; case TOKEN_R_PAREN: return ")"; case TOKEN_SEMICOLON: return ";"; case TOKEN_PERCENT: return "%"; case TOKEN_PERCENT_EQUAL: return "%="; case TOKEN_L_BRACE: return "{"; case TOKEN_R_BRACE: return "}"; case TOKEN_L_BRACKET: return "["; case TOKEN_R_BRACKET: return "]"; case TOKEN_PERIOD: return "."; case TOKEN_PERIOD_ASTERISK: return ".*"; case TOKEN_ELLIPSIS2: return ".."; case TOKEN_ELLIPSIS3: return "..."; case TOKEN_CARET: return "^"; case TOKEN_CARET_EQUAL: return "^="; case TOKEN_PLUS: return "+"; case TOKEN_PLUS_PLUS: return "++"; case TOKEN_PLUS_EQUAL: return "+="; case TOKEN_PLUS_PERCENT: return "+%"; case TOKEN_PLUS_PERCENT_EQUAL: return "+%="; case TOKEN_PLUS_PIPE: return "+|"; case TOKEN_PLUS_PIPE_EQUAL: return "+|="; case TOKEN_MINUS: return "-"; case TOKEN_MINUS_EQUAL: return "-="; case TOKEN_MINUS_PERCENT: return "-%"; case TOKEN_MINUS_PERCENT_EQUAL: return "-%="; case TOKEN_MINUS_PIPE: return "-|"; case TOKEN_MINUS_PIPE_EQUAL: return "-|="; case TOKEN_ASTERISK: return "*"; case TOKEN_ASTERISK_EQUAL: return "*="; case TOKEN_ASTERISK_PERCENT: return "*%"; case TOKEN_ASTERISK_PERCENT_EQUAL: return "*%="; case TOKEN_ASTERISK_PIPE: return "*|"; case TOKEN_ASTERISK_PIPE_EQUAL: return "*|="; case TOKEN_ARROW: return "->"; case TOKEN_COLON: return ":"; case TOKEN_SLASH: return "/"; case TOKEN_SLASH_EQUAL: return "/="; case TOKEN_COMMA: return ","; case TOKEN_AMPERSAND: return "&"; case TOKEN_AMPERSAND_EQUAL: return "&="; case TOKEN_QUESTION_MARK: return "?"; case TOKEN_ANGLE_BRACKET_LEFT: return "<"; case TOKEN_ANGLE_BRACKET_LEFT_EQUAL: return "<="; case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT: return "<<"; case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_EQUAL: return "<<="; case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE: return "<<|"; case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE_EQUAL: return "<<|="; case TOKEN_ANGLE_BRACKET_RIGHT: return ">"; case TOKEN_ANGLE_BRACKET_RIGHT_EQUAL: return ">="; case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT: return ">>"; case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT_EQUAL: return ">>="; case TOKEN_TILDE: return "~"; case TOKEN_KEYWORD_ADDRSPACE: return "addrspace"; case TOKEN_KEYWORD_ALIGN: return "align"; case TOKEN_KEYWORD_ALLOWZERO: return "allowzero"; case TOKEN_KEYWORD_AND: return "and"; case TOKEN_KEYWORD_ANYFRAME: return "anyframe"; case TOKEN_KEYWORD_ANYTYPE: return "anytype"; case TOKEN_KEYWORD_ASM: return "asm"; case TOKEN_KEYWORD_BREAK: return "break"; case TOKEN_KEYWORD_CALLCONV: return "callconv"; case TOKEN_KEYWORD_CATCH: return "catch"; case TOKEN_KEYWORD_COMPTIME: return "comptime"; case TOKEN_KEYWORD_CONST: return "const"; case TOKEN_KEYWORD_CONTINUE: return "continue"; case TOKEN_KEYWORD_DEFER: return "defer"; case TOKEN_KEYWORD_ELSE: return "else"; case TOKEN_KEYWORD_ENUM: return "enum"; case TOKEN_KEYWORD_ERRDEFER: return "errdefer"; case TOKEN_KEYWORD_ERROR: return "error"; case TOKEN_KEYWORD_EXPORT: return "export"; case TOKEN_KEYWORD_EXTERN: return "extern"; case TOKEN_KEYWORD_FN: return "fn"; case TOKEN_KEYWORD_FOR: return "for"; case TOKEN_KEYWORD_IF: return "if"; case TOKEN_KEYWORD_INLINE: return "inline"; case TOKEN_KEYWORD_NOALIAS: return "noalias"; case TOKEN_KEYWORD_NOINLINE: return "noinline"; case TOKEN_KEYWORD_NOSUSPEND: return "nosuspend"; case TOKEN_KEYWORD_OPAQUE: return "opaque"; case TOKEN_KEYWORD_OR: return "or"; case TOKEN_KEYWORD_ORELSE: return "orelse"; case TOKEN_KEYWORD_PACKED: return "packed"; case TOKEN_KEYWORD_PUB: return "pub"; case TOKEN_KEYWORD_RESUME: return "resume"; case TOKEN_KEYWORD_RETURN: return "return"; case TOKEN_KEYWORD_LINKSECTION: return "linksection"; case TOKEN_KEYWORD_STRUCT: return "struct"; case TOKEN_KEYWORD_SUSPEND: return "suspend"; case TOKEN_KEYWORD_SWITCH: return "switch"; case TOKEN_KEYWORD_TEST: return "test"; case TOKEN_KEYWORD_THREADLOCAL: return "threadlocal"; case TOKEN_KEYWORD_TRY: return "try"; case TOKEN_KEYWORD_UNION: return "union"; case TOKEN_KEYWORD_UNREACHABLE: return "unreachable"; case TOKEN_KEYWORD_VAR: return "var"; case TOKEN_KEYWORD_VOLATILE: return "volatile"; case TOKEN_KEYWORD_WHILE: return "while"; } return NULL; } // tokenizer.zig:Token.Tag.symbol const char* tokenTagSymbol(TokenTag tag) { const char* lexeme = tokenTagLexeme(tag); if (lexeme != NULL) return lexeme; switch (tag) { case TOKEN_INVALID: return "invalid token"; case TOKEN_IDENTIFIER: return "an identifier"; case TOKEN_STRING_LITERAL: return "a string literal"; case TOKEN_MULTILINE_STRING_LITERAL_LINE: return "a multiline string literal"; case TOKEN_CHAR_LITERAL: return "a character literal"; case TOKEN_EOF: return "EOF"; case TOKEN_BUILTIN: return "a builtin function"; case TOKEN_NUMBER_LITERAL: return "a number literal"; case TOKEN_DOC_COMMENT: case TOKEN_CONTAINER_DOC_COMMENT: return "a document comment"; default: break; } UNREACHABLE("Token.Tag.symbol: a tag with neither lexeme nor symbol"); ZIG1_TCC_RETURN(NULL); } /* ---- end tokenizer.c ---- */ /* ---- begin type.c ---- */ // type.c — src/Type.zig: the type queries Sema and the interpreter share. // Type classes (comptimeOnly), the pointer, integer and function queries // (ptrInfo, intInfo, abiAlignment), Type.print, minInt/maxInt and // onePossibleValue, the wasm32 layout (abiSize, abiAlignment, bitSize) and // fieldPtrType/elemPtrType. A Type method is zt and its name (ztAbiSize is // Type.abiSize), as are the type-class queries (ztTypeClass) and this file's // helpers. Declarations // are in zig1.h's two Type and Value sections. #ifndef ZIG1_H #include "zig1.h" #endif #include #include // ── zigTypeTag ────────────────────────────────────────────────────────────── // InternPool.zig:zigTypeTag // A `simple_type` item answers by index, any other type by its storage tag. // The switch names every IpTag, so a new one fails -Wswitch. ZigTypeTag ztZigTypeTag(const InternPool* ip, uint32_t ty) { if (ty >= ip->items_len) UNREACHABLE("zigTypeTag: index is not in the InternPool"); switch ((IpTag)ip->item_tags[ty]) { case IP_TAG_SIMPLE_TYPE: switch (ty) { case ZIR_REF_USIZE_TYPE: case ZIR_REF_ISIZE_TYPE: case ZIR_REF_C_CHAR_TYPE: case ZIR_REF_C_SHORT_TYPE: case ZIR_REF_C_USHORT_TYPE: case ZIR_REF_C_INT_TYPE: case ZIR_REF_C_UINT_TYPE: case ZIR_REF_C_LONG_TYPE: case ZIR_REF_C_ULONG_TYPE: case ZIR_REF_C_LONGLONG_TYPE: case ZIR_REF_C_ULONGLONG_TYPE: return ZIG_TYPE_TAG_INT; case ZIR_REF_C_LONGDOUBLE_TYPE: case ZIR_REF_F16_TYPE: case ZIR_REF_F32_TYPE: case ZIR_REF_F64_TYPE: case ZIR_REF_F80_TYPE: case ZIR_REF_F128_TYPE: return ZIG_TYPE_TAG_FLOAT; case ZIR_REF_ANYOPAQUE_TYPE: return ZIG_TYPE_TAG_OPAQUE; case ZIR_REF_BOOL_TYPE: return ZIG_TYPE_TAG_BOOL; case ZIR_REF_VOID_TYPE: return ZIG_TYPE_TAG_VOID; case ZIR_REF_TYPE_TYPE: return ZIG_TYPE_TAG_TYPE; case ZIR_REF_ANYERROR_TYPE: case ZIR_REF_ADHOC_INFERRED_ERROR_SET_TYPE: return ZIG_TYPE_TAG_ERROR_SET; case ZIR_REF_COMPTIME_INT_TYPE: return ZIG_TYPE_TAG_COMPTIME_INT; case ZIR_REF_COMPTIME_FLOAT_TYPE: return ZIG_TYPE_TAG_COMPTIME_FLOAT; case ZIR_REF_NORETURN_TYPE: return ZIG_TYPE_TAG_NORETURN; case ZIR_REF_NULL_TYPE: return ZIG_TYPE_TAG_NULL; case ZIR_REF_UNDEFINED_TYPE: return ZIG_TYPE_TAG_UNDEFINED; case ZIR_REF_ENUM_LITERAL_TYPE: return ZIG_TYPE_TAG_ENUM_LITERAL; default: // `.generic_poison_type => unreachable` UNREACHABLE( "zigTypeTag: generic_poison or an unknown simple type"); } case IP_TAG_TYPE_INT_SIGNED: case IP_TAG_TYPE_INT_UNSIGNED: return ZIG_TYPE_TAG_INT; case IP_TAG_TYPE_ARRAY_BIG: case IP_TAG_TYPE_ARRAY_SMALL: return ZIG_TYPE_TAG_ARRAY; case IP_TAG_TYPE_VECTOR: return ZIG_TYPE_TAG_VECTOR; case IP_TAG_TYPE_POINTER: case IP_TAG_TYPE_SLICE: return ZIG_TYPE_TAG_POINTER; case IP_TAG_TYPE_OPTIONAL: return ZIG_TYPE_TAG_OPTIONAL; case IP_TAG_TYPE_ANYFRAME: return ZIG_TYPE_TAG_ANYFRAME; case IP_TAG_TYPE_ERROR_UNION: case IP_TAG_TYPE_ANYERROR_UNION: return ZIG_TYPE_TAG_ERROR_UNION; case IP_TAG_TYPE_ERROR_SET: case IP_TAG_TYPE_INFERRED_ERROR_SET: return ZIG_TYPE_TAG_ERROR_SET; case IP_TAG_TYPE_TUPLE: case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: return ZIG_TYPE_TAG_STRUCT; case IP_TAG_TYPE_UNION: case IP_TAG_TYPE_UNION_PACKED_AUTO: case IP_TAG_TYPE_UNION_PACKED_EXPLICIT: return ZIG_TYPE_TAG_UNION; case IP_TAG_TYPE_ENUM_AUTO: case IP_TAG_TYPE_ENUM_EXPLICIT: case IP_TAG_TYPE_ENUM_NONEXHAUSTIVE: return ZIG_TYPE_TAG_ENUM; case IP_TAG_TYPE_OPAQUE: return ZIG_TYPE_TAG_OPAQUE; case IP_TAG_TYPE_SPIRV: return ZIG_TYPE_TAG_SPIRV; case IP_TAG_TYPE_FUNCTION: return ZIG_TYPE_TAG_FN; // `.removed`, and the values and memoization, not types. case IP_TAG_REMOVED: case IP_TAG_UNDEF: case IP_TAG_SIMPLE_VALUE: case IP_TAG_PTR_NAV: case IP_TAG_PTR_COMPTIME_ALLOC: case IP_TAG_PTR_UAV: case IP_TAG_PTR_UAV_ALIGNED: case IP_TAG_PTR_COMPTIME_FIELD: case IP_TAG_PTR_INT: case IP_TAG_PTR_EU_PAYLOAD: case IP_TAG_PTR_OPT_PAYLOAD: case IP_TAG_PTR_ELEM: case IP_TAG_PTR_FIELD: case IP_TAG_PTR_SLICE: case IP_TAG_OPT_PAYLOAD: case IP_TAG_OPT_NULL: case IP_TAG_INT_U8: case IP_TAG_INT_U16: case IP_TAG_INT_U32: case IP_TAG_INT_I32: case IP_TAG_INT_USIZE: case IP_TAG_INT_COMPTIME_INT_U32: case IP_TAG_INT_COMPTIME_INT_I32: case IP_TAG_INT_SMALL: case IP_TAG_INT_POSITIVE: case IP_TAG_INT_NEGATIVE: case IP_TAG_ERROR_SET_ERROR: case IP_TAG_ERROR_UNION_ERROR: case IP_TAG_ERROR_UNION_PAYLOAD: case IP_TAG_ENUM_LITERAL: case IP_TAG_ENUM_TAG: case IP_TAG_FLOAT_F16: case IP_TAG_FLOAT_F32: case IP_TAG_FLOAT_F64: case IP_TAG_FLOAT_F80: case IP_TAG_FLOAT_F128: case IP_TAG_FLOAT_C_LONGDOUBLE_F80: case IP_TAG_FLOAT_C_LONGDOUBLE_F128: case IP_TAG_FLOAT_COMPTIME_FLOAT: case IP_TAG_EXTERN: case IP_TAG_FUNC_DECL: case IP_TAG_FUNC_INSTANCE: case IP_TAG_FUNC_COERCED: case IP_TAG_ONLY_POSSIBLE_VALUE: case IP_TAG_UNION_VALUE: case IP_TAG_BYTES: case IP_TAG_AGGREGATE: case IP_TAG_REPEATED: case IP_TAG_BITPACK: case IP_TAG_MEMOIZED_CALL: break; } UNREACHABLE("zigTypeTag: index is not a type"); ZIG1_TCC_RETURN(ZIG_TYPE_TAG_TYPE); } // ── Type classes (comptimeOnly, optional representation) ──────────────────── static ZtTypeClass ztClassifyTypeInner( InternPool* ip, uint32_t ty, uint32_t* visiting, uint32_t depth) { if (ty == IP_INDEX_GENERIC_POISON_TYPE) return ZT_CLASS_RUNTIME; // Type.comptimeOnly's explicit exception if (ty >= ip->items_len || depth >= ZT_CLASSIFY_MAX_DEPTH) return ZT_CLASS_UNKNOWN; for (uint32_t i = 0; i < depth; i++) if (visiting[i] == ty) return ZT_CLASS_UNKNOWN; // illegal by-value aggregate cycle visiting[depth] = ty; // The `.struct_type` and `.union_type` `.@"packed"` cases: "cur_ty = // .fromInterned(..packed_backing_int_type); continue;". if (ipTypeIsBitpack(ip, ty)) return ztClassifyTypeInner( ip, ipBitpackBackingInt(ip, ty), visiting, depth + 1); ZtTypeClass leaf; if (ztClassifyLeaf(ip, ty, &leaf)) return leaf; IpTag tag = (IpTag)ip->item_tags[ty]; switch (tag) { case IP_TAG_TYPE_OPTIONAL: { ZtTypeClass child = ztClassifyTypeInner(ip, ip->item_datas[ty], visiting, depth + 1); switch (child) { case ZT_CLASS_NPV: return ZT_CLASS_OPV; case ZT_CLASS_OPV: case ZT_CLASS_RUNTIME: return ZT_CLASS_RUNTIME; case ZT_CLASS_PARTIAL: case ZT_CLASS_FULL: case ZT_CLASS_UNKNOWN: return child; } return ZT_CLASS_UNKNOWN; } case IP_TAG_TYPE_ERROR_UNION: case IP_TAG_TYPE_ANYERROR_UNION: { ZtTypeClass child = ztClassifyTypeInner( ip, ipErrorUnionPayload(ip, ty), visiting, depth + 1); if (child == ZT_CLASS_PARTIAL || child == ZT_CLASS_FULL || child == ZT_CLASS_UNKNOWN) return child; return ZT_CLASS_RUNTIME; // the error-set side contributes many states } case IP_TAG_TYPE_ARRAY_SMALL: { const uint32_t* w = &ip->extra[ip->item_datas[ty]]; if (w[0] == 0) return ZT_CLASS_OPV; return ztClassifyTypeInner(ip, w[1], visiting, depth + 1); } case IP_TAG_TYPE_ARRAY_BIG: { const uint32_t* w = &ip->extra[ip->item_datas[ty]]; // A zero-length sentinel array stores the sentinel element, so unlike // `[0]T` it still propagates T's class. if (w[0] == 0 && w[1] == 0 && w[3] == IP_INDEX_NONE) return ZT_CLASS_OPV; return ztClassifyTypeInner(ip, w[2], visiting, depth + 1); } case IP_TAG_TYPE_VECTOR: { const uint32_t* w = &ip->extra[ip->item_datas[ty]]; if (w[0] == 0) return ZT_CLASS_OPV; return ztClassifyTypeInner(ip, w[1], visiting, depth + 1); } case IP_TAG_TYPE_TUPLE: { uint32_t off = ip->item_datas[ty]; uint32_t fields_len = ip->extra[off]; bool has_npv = false; bool has_runtime = false; bool has_comptime = false; bool has_unknown = false; for (uint32_t i = 0; i < fields_len; i++) { if (ip->extra[off + 1 + fields_len + i] != IP_INDEX_NONE) continue; // fixed-value tuple field contributes no state ZtTypeClass field = ztClassifyTypeInner( ip, ip->extra[off + 1 + i], visiting, depth + 1); if (field == ZT_CLASS_NPV) has_npv = true; if (field == ZT_CLASS_RUNTIME || field == ZT_CLASS_PARTIAL) has_runtime = true; if (field == ZT_CLASS_FULL || field == ZT_CLASS_PARTIAL) has_comptime = true; if (field == ZT_CLASS_UNKNOWN) has_unknown = true; } if (has_unknown) return ZT_CLASS_UNKNOWN; if (has_npv) return ZT_CLASS_NPV; if (has_comptime) return has_runtime ? ZT_CLASS_PARTIAL : ZT_CLASS_FULL; return has_runtime ? ZT_CLASS_RUNTIME : ZT_CLASS_OPV; } case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: { // A resolved one is a leaf (ztClassifyLeaf). uint32_t fields_len = ipStructFieldsLen(ip, ty); bool has_npv = false; bool has_runtime = false; bool has_comptime = false; bool has_unknown = false; for (uint32_t i = 0; i < fields_len; i++) { if (ipStructFieldIsComptime(ip, ty, i)) continue; // explicit comptime/fixed field is omitted uint32_t field_ty = ipStructFieldType(ip, ty, i); if (field_ty == IP_INDEX_NONE) { has_unknown = true; continue; } ZtTypeClass field = ztClassifyTypeInner(ip, field_ty, visiting, depth + 1); if (field == ZT_CLASS_NPV) has_npv = true; if (field == ZT_CLASS_RUNTIME || field == ZT_CLASS_PARTIAL) has_runtime = true; if (field == ZT_CLASS_FULL || field == ZT_CLASS_PARTIAL) has_comptime = true; if (field == ZT_CLASS_UNKNOWN) has_unknown = true; } if (has_unknown) return ZT_CLASS_UNKNOWN; if (has_npv) return ZT_CLASS_NPV; if (has_comptime) return has_runtime ? ZT_CLASS_PARTIAL : ZT_CLASS_FULL; return has_runtime ? ZT_CLASS_RUNTIME : ZT_CLASS_OPV; } case IP_TAG_TYPE_UNION: { // A resolved one is a leaf (ztClassifyLeaf). uint32_t fields_len = ipUnionFieldsLen(ip, ty); uint32_t possible_fields = 0; bool has_runtime = false; bool has_comptime = false; bool has_unknown = false; for (uint32_t i = 0; i < fields_len; i++) { uint32_t field_ty = ipUnionFieldType(ip, ty, i); if (field_ty == IP_INDEX_NONE) { has_unknown = true; continue; } ZtTypeClass field = ztClassifyTypeInner(ip, field_ty, visiting, depth + 1); if (field == ZT_CLASS_NPV) continue; // an unavailable union field has no effect if (field == ZT_CLASS_UNKNOWN) { has_unknown = true; continue; } possible_fields++; if (field == ZT_CLASS_RUNTIME || field == ZT_CLASS_PARTIAL) has_runtime = true; if (field == ZT_CLASS_FULL || field == ZT_CLASS_PARTIAL) has_comptime = true; } if (has_unknown) return ZT_CLASS_UNKNOWN; if (has_comptime) return has_runtime ? ZT_CLASS_PARTIAL : ZT_CLASS_FULL; if (possible_fields == 0) return ZT_CLASS_NPV; if (has_runtime || (possible_fields > 1 && ipUnionTagUsage(ip, ty) != IP_UNION_TAG_USAGE_NONE)) return ZT_CLASS_RUNTIME; return ZT_CLASS_OPV; } case IP_TAG_TYPE_ENUM_AUTO: case IP_TAG_TYPE_ENUM_EXPLICIT: case IP_TAG_TYPE_ENUM_NONEXHAUSTIVE: { uint32_t tag_ty = ipEnumTypeIntTagType(ip, ty); if (tag_ty == IP_INDEX_NONE) return ZT_CLASS_UNKNOWN; return ztClassifyTypeInner(ip, tag_ty, visiting, depth + 1); } default: return ZT_CLASS_UNKNOWN; // value tag or unmodeled type representation } } static ZtComptimeOnlyStatus ztTypeComptimeOnlyStatus( InternPool* ip, uint32_t ty) { ZtTypeClass class = ztTypeClass(ip, ty); if (class == ZT_CLASS_UNKNOWN) return ZT_COMPTIME_ONLY_UNKNOWN; return (class == ZT_CLASS_PARTIAL || class == ZT_CLASS_FULL) ? ZT_COMPTIME_ONLY_YES : ZT_COMPTIME_ONLY_NO; } ZtTypeClass ztTypeClassWalk(InternPool* ip, uint32_t ty) { uint32_t visiting[ZT_CLASSIFY_MAX_DEPTH]; return ztClassifyTypeInner(ip, ty, visiting, 0); } // Type.zig:optionalReprIsPayload bool ztOptionalReprIsPayload(const InternPool* ip, uint32_t opt_ty) { if (ipItemTagIs(ip, opt_ty, IP_TAG_TYPE_POINTER)) return IP_PTR_FLAGS_SIZE( ip->extra[ip->item_datas[opt_ty] + IP_TYPE_POINTER_FLAGS]) == IP_PTR_SIZE_C; if (!ipItemTagIs(ip, opt_ty, IP_TAG_TYPE_OPTIONAL)) return false; uint32_t child = ip->item_datas[opt_ty]; if (child == ZIR_REF_ANYERROR_TYPE) return true; if (child >= ip->items_len) UNREACHABLE("optionalReprIsPayload: malformed child type"); uint8_t child_tag = ip->item_tags[child]; if (child_tag == IP_TAG_TYPE_ERROR_SET || child_tag == IP_TAG_TYPE_INFERRED_ERROR_SET) return true; if (child_tag == IP_TAG_TYPE_POINTER) { uint32_t flags = ip->extra[ip->item_datas[child] + 2]; uint32_t ptr_size = IP_PTR_FLAGS_SIZE(flags); bool allowzero = (flags & IP_PTR_FLAGS_IS_ALLOWZERO) != 0; return ptr_size != IP_PTR_SIZE_C && !allowzero; } if (child_tag == IP_TAG_TYPE_SLICE) { // zig1 serializes a slice as its many-pointer item. Upstream's // conceptual PtrType has size=.slice and shares the same allowzero // flag; a non-allowzero slice therefore uses its null pointer field // as the optional niche while retaining the full {ptr,len} payload. uint32_t many_ptr = ip->item_datas[child]; if (many_ptr >= ip->items_len || ip->item_tags[many_ptr] != IP_TAG_TYPE_POINTER) UNREACHABLE("optionalReprIsPayload: malformed slice pointer"); uint32_t flags = ip->extra[ip->item_datas[many_ptr] + 2]; return (flags & IP_PTR_FLAGS_IS_ALLOWZERO) == 0; } return false; } static void ztResolveComptimeOnlyInner( Zcu* zcu, uint32_t ty, uint32_t* visiting, uint32_t depth) { InternPool* ip = &zcu->intern_pool; if (ty >= ip->items_len || depth >= ZT_CLASSIFY_MAX_DEPTH) return; for (uint32_t i = 0; i < depth; i++) if (visiting[i] == ty) return; visiting[depth] = ty; switch ((IpTag)ip->item_tags[ty]) { case IP_TAG_TYPE_OPTIONAL: ztResolveComptimeOnlyInner( zcu, ip->item_datas[ty], visiting, depth + 1); return; case IP_TAG_TYPE_ERROR_UNION: case IP_TAG_TYPE_ANYERROR_UNION: ztResolveComptimeOnlyInner( zcu, ipErrorUnionPayload(ip, ty), visiting, depth + 1); return; case IP_TAG_TYPE_ARRAY_SMALL: { uint32_t off = ip->item_datas[ty]; ztResolveComptimeOnlyInner( zcu, ip->extra[off + 1], visiting, depth + 1); return; } case IP_TAG_TYPE_ARRAY_BIG: { uint32_t off = ip->item_datas[ty]; ztResolveComptimeOnlyInner( zcu, ip->extra[off + 2], visiting, depth + 1); return; } case IP_TAG_TYPE_VECTOR: { uint32_t off = ip->item_datas[ty]; ztResolveComptimeOnlyInner( zcu, ip->extra[off + 1], visiting, depth + 1); return; } case IP_TAG_TYPE_TUPLE: { uint32_t off = ip->item_datas[ty]; uint32_t fields_len = ip->extra[off]; for (uint32_t i = 0; i < fields_len; i++) if (ip->extra[off + 1 + fields_len + i] == IP_INDEX_NONE) ztResolveComptimeOnlyInner( zcu, ip->extra[off + 1 + i], visiting, depth + 1); return; } case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: if (ipStructIsPacked(ip, ty)) { ztResolveComptimeOnlyInner( zcu, ipStructBackingInt(ip, ty), visiting, depth + 1); return; } // Type.comptimeOnly asserts the layout; this walk may run inside a // layout resolution, so it populates the fields only. semaEnsureStructFieldsResolved(zcu, ty); for (uint32_t i = 0; i < ipStructFieldsLen(ip, ty); i++) { if (ipStructFieldIsComptime(ip, ty, i)) continue; uint32_t field_ty = ipStructFieldType(ip, ty, i); if (field_ty != IP_INDEX_NONE) ztResolveComptimeOnlyInner(zcu, field_ty, visiting, depth + 1); } return; case IP_TAG_TYPE_UNION: { bool has_unresolved = false; uint32_t fields_len = ipUnionFieldsLen(ip, ty); for (uint32_t i = 0; i < fields_len; i++) if (ipUnionFieldType(ip, ty, i) == IP_INDEX_NONE) { has_unresolved = true; break; } if (has_unresolved) semaResolveAllUnionDeferred(zcu, ty); for (uint32_t i = 0; i < fields_len; i++) { uint32_t field_ty = ipUnionFieldType(ip, ty, i); if (field_ty != IP_INDEX_NONE) ztResolveComptimeOnlyInner(zcu, field_ty, visiting, depth + 1); } return; } default: return; } } ZtComptimeOnlyStatus ztComptimeOnlyStatus(Zcu* zcu, uint32_t ty) { uint32_t visiting[ZT_CLASSIFY_MAX_DEPTH]; ztResolveComptimeOnlyInner(zcu, ty, visiting, 0); return ztTypeComptimeOnlyStatus(&zcu->intern_pool, ty); } bool ztComptimeOnly(Zcu* zcu, uint32_t ty) { ZtComptimeOnlyStatus status = ztComptimeOnlyStatus(zcu, ty); if (status == ZT_COMPTIME_ONLY_UNKNOWN) { const InternPool* ip = &zcu->intern_pool; fprintf(stderr, "zig1: Type.comptimeOnly unresolved type=%u tag=%s\n", ty, ty < ip->items_len ? ipTagName((IpTag)ip->item_tags[ty]) : "out-of-bounds"); UNIMPLEMENTED("Type.comptimeOnly: unresolved or recursive type"); } return status == ZT_COMPTIME_ONLY_YES; } // ── Pointer, integer and function queries ─────────────────────────────────── // Type.zig:ptrInfo // `Type.ptrInfo(ty).flags.size == .slice` (src/Type.zig): `ptrInfo` looks // through an optional (`.opt_type => |child| ...ptr_type`), so `?[]T` reports // `.slice` exactly as `[]T` does. In zig1's split encoding a slice is its // own tag wrapping the `[*]` many-pointer, so `.slice` is that tag. bool ztPtrInfoIsSlice(const InternPool* ip, uint32_t ty) { // `.opt_type => |child|` peels one optional. if (ipItemTagIs(ip, ty, IP_TAG_TYPE_OPTIONAL)) ty = ipOptionalChild(ip, ty); return ztIsSlice(ip, ty); } // (part of) Type.zig:getNamespace // fieldCallBind's `.type` receiver: a container type whose namespace // (`Type.getNamespace`) can hold the callee -- struct, union, enum, opaque. bool ztIsNamespaceType(const InternPool* ip, uint32_t ty) { return ty < ip->items_len && (ipTagIsAnyStruct(ip->item_tags[ty]) || ipTagIsAnyUnion(ip->item_tags[ty]) || ipIsEnumType(ip, ty) || ip->item_tags[ty] == IP_TAG_TYPE_OPAQUE); } // (part of) Type.zig:intInfo, InternPool.zig:init // `Type.intInfo` for a coercion destination, which is `ztIntInfo` -- // the one statement of that rule -- with the out-of-range guard a raw // destination index owes it. The preinterned int types (`u0`..`u256`, // `usize`, `isize`) are `int_unsigned`/`int_signed` items of // `InternPool.init` (intern_pool.c's `ipInitStatic`, indices 0..14) or ones // `ztIntInfo` answers by hand. bool ztIntInfoInRange( const InternPool* ip, uint32_t dest_ty, uint32_t* bits, bool* is_signed) { return dest_ty < ip->items_len && ztIntInfo(ip, dest_ty, bits, is_signed); } // (part of) Type.zig:intInfo // Integer bit-width + signedness for an int-like type: the fixed-width int // tags plus usize/isize. Mirrors the arms of Type.intInfo that `coerce`'s // integer-widening check needs. Returns false for non-integer types. bool ztIntInfo( const InternPool* ip, uint32_t ty, uint32_t* bits, bool* is_signed) { uint8_t tag = ip->item_tags[ty]; if (tag == IP_TAG_TYPE_INT_UNSIGNED) { *bits = ip->item_datas[ty]; *is_signed = false; return true; } if (tag == IP_TAG_TYPE_INT_SIGNED) { *bits = ip->item_datas[ty]; *is_signed = true; return true; } if (ztCIntTypeInfoFE(ty, bits, is_signed)) return true; if (ty == ZIR_REF_USIZE_TYPE) { *bits = ZT_PTR_BIT_WIDTH; *is_signed = false; return true; } if (ty == ZIR_REF_ISIZE_TYPE) { *bits = 32; *is_signed = true; return true; } return false; } // Type.zig:structFieldOffset // `.struct_type => struct_type.field_offsets.get(ip)[index]` (given a Zcu the // layout is resolved first; without one an unresolved struct is laid out on // the spot); `.tuple_type` places each runtime field at its ABI alignment in // declaration order, a comptime or zero-bit field at 0, and index len at the // padded end; `.union_type` is the payload offset. uint64_t ztStructFieldOffset( Zcu* zcu, InternPool* ip, uint32_t ty, uint32_t index) { switch (ip->item_tags[ty]) { case IP_TAG_TYPE_STRUCT: { if (zcu != NULL && !resolveStructLayout(zcu, ip, ty)) UNIMPLEMENTED("structFieldOffset: struct layout unresolved"); if (ipStructLayoutResolved(ip, ty)) return ipStructFieldOffset(ip, ty, index); uint32_t n = ipStructFieldsLen(ip, ty); uint32_t* offsets = zig1Malloc((size_t)(n != 0 ? n : 1) * sizeof(uint32_t)); uint64_t size, align; if (!semaComputeStructLayout( zcu, ip, ty, &size, &align, NULL, offsets)) UNIMPLEMENTED("structFieldOffset: struct layout unresolved"); uint64_t offset = offsets[index]; free(offsets); return offset; } case IP_TAG_TYPE_TUPLE: { uint64_t offset = 0; uint64_t big_align = 1; uint32_t n = ipTupleFieldCount(ip, ty); for (uint32_t i = 0; i < n; i++) { uint32_t field_ty = ipTupleFieldType(ip, ty, i); ZtTypeClass field_class = ZT_CLASS_OPV; if (ipTupleFieldValue(ip, ty, i) == IP_INDEX_NONE) field_class = ztTypeClass(ip, field_ty); if (field_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED( "structFieldOffset: tuple field class unresolved"); if (field_class != ZT_CLASS_RUNTIME && field_class != ZT_CLASS_PARTIAL) { // comptime field if (i == index) return 0; continue; } uint64_t size, align, bits; if (!ztLayoutInfo(zcu, ip, field_ty, &size, &align, &bits)) UNIMPLEMENTED( "structFieldOffset: tuple field layout unresolved"); if (align > big_align) big_align = align; if (!ztLayoutCheckedAlign(offset, align, &offset)) UNIMPLEMENTED("structFieldOffset: tuple layout overflow"); if (i == index) return offset; if (!ztLayoutCheckedAdd(offset, size, &offset)) UNIMPLEMENTED("structFieldOffset: tuple layout overflow"); } if (!ztLayoutCheckedAlign(offset, big_align, &offset)) UNIMPLEMENTED("structFieldOffset: tuple layout overflow"); return offset; } case IP_TAG_TYPE_UNION: { ZtUnionLayout layout; if (!ztGetUnionLayout(zcu, ip, ty, &layout)) UNIMPLEMENTED("structFieldOffset: union layout unresolved"); return ztUnionLayoutPayloadOffset(&layout); } default: UNREACHABLE("structFieldOffset: not a struct, tuple or union"); } ZIG1_TCC_RETURN(0); } // (part of) Type.zig:slicePtrFieldType // The `extra[]` offset of a slice type's many-pointer payload -- the one // decode of `Type.slicePtrFieldType` every slice-shaped predicate below // begins with. False for anything that is not a slice over a pointer, so an // IP_INDEX_NONE or non-slice argument simply declines. bool ztSliceManyPtrExtraOff( const InternPool* ip, uint32_t slice_ty, uint32_t* out_off) { if (!ztIsSlice(ip, slice_ty)) return false; uint32_t many_ty = ip->item_datas[slice_ty]; if (many_ty >= ip->items_len || ip->item_tags[many_ty] != IP_TAG_TYPE_POINTER) return false; *out_off = ip->item_datas[many_ty]; return true; } // (part of) Type.zig:abiAlignment // `Type.abiAlignment` in log2 units -- the one alignment read every pointer // predicate here shares. `zcu` is nullable: given one, the aggregate arms go // through `ztLayoutInfo`'s resolving path exactly as upstream's // `abiAlignment` does; given NULL there is no layout pass to lean on, so // tuple / union / non-packed struct decline rather than answer from a // possibly-unresolved header. The recursive arms are upstream's own: an // array defers to its element, an optional to its child, and an error union // is `@max(payload, error-set)`. bool ztAbiAlignLog2( Zcu* zcu, InternPool* ip, uint32_t ty, uint32_t* out_log2) { // `.simple_type => |t| switch (t) { .bool, .void, .noreturn, .anyopaque, // .type, .comptime_int, .comptime_float, .null, .undefined, // .enum_literal => .@"1", ...` (src/Type.zig). switch (ty) { case ZIR_REF_BOOL_TYPE: case ZIR_REF_VOID_TYPE: case ZIR_REF_NORETURN_TYPE: case ZIR_REF_ANYOPAQUE_TYPE: case ZIR_REF_TYPE_TYPE: case ZIR_REF_COMPTIME_INT_TYPE: case ZIR_REF_COMPTIME_FLOAT_TYPE: case ZIR_REF_NULL_TYPE: case ZIR_REF_UNDEFINED_TYPE: case ZIR_REF_ENUM_LITERAL_TYPE: *out_log2 = 0; return true; default: break; } if (ty >= ip->items_len) return false; switch ((IpTag)ip->item_tags[ty]) { case IP_TAG_TYPE_OPAQUE: // `.opaque_type => .@"1"` (src/Type.zig) *out_log2 = 0; return true; case IP_TAG_TYPE_ARRAY_SMALL: return ztAbiAlignLog2( zcu, ip, ip->extra[ip->item_datas[ty] + 1], out_log2); case IP_TAG_TYPE_ARRAY_BIG: return ztAbiAlignLog2( zcu, ip, ip->extra[ip->item_datas[ty] + 2], out_log2); case IP_TAG_TYPE_OPTIONAL: return ztAbiAlignLog2(zcu, ip, ipOptionalChild(ip, ty), out_log2); case IP_TAG_TYPE_ERROR_UNION: case IP_TAG_TYPE_ANYERROR_UNION: { uint32_t payload = ipErrorUnionPayload(ip, ty); uint32_t payload_log2; if (!ztAbiAlignLog2(zcu, ip, payload, &payload_log2)) return false; *out_log2 = payload_log2 > 1u ? payload_log2 : 1u; return true; } case IP_TAG_TYPE_TUPLE: case IP_TAG_TYPE_UNION: // Reached, not defensive: the comptime array-pointer coercions // ask `checkPtrAttributes` with no `Zcu`, and a // `[]align(4) SomeStruct` -> `[]SomeStruct` pair makes it resolve an // aggregate's ABI alignment here. Those pairs decline (round-23 // correctness N-v); giving those callers a `Zcu` is the fix, not // weakening this arm. if (zcu == NULL) return false; break; case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: if (zcu == NULL && !ipStructIsPacked(ip, ty)) return false; break; default: break; } uint64_t size, align, bits; if (!ztLayoutInfo(zcu, ip, ty, &size, &align, &bits) || align == 0 || (align & (align - 1u)) != 0 || align > (UINT64_C(1) << 31)) return false; uint32_t log2 = 0; while ((UINT64_C(1) << log2) < align) log2++; *out_log2 = log2; return true; } // Type.zig:ptrAllowsZero // Type.ptrAllowsZero (src/Type.zig) for a `type_pointer`: the // `isPtrLikeOptional` half belongs to the optional type, not to a pointer // type, so what is left here is the allowzero flag -- plus `.c`, which // `ipPtrInfo` reports through `size` and which upstream carries as // `is_allowzero` on every C pointer it mints. bool ztPtrInfoAllowsZero(const IpPtrInfo* info) { return info->is_allowzero || info->size == IP_PTR_SIZE_C; } // Type.zig:fnHasRuntimeBits // `Type.fnHasRuntimeBits` (src/Type.zig). bool ztFnHasRuntimeBits(InternPool* ip, uint32_t fn_ty) { if (ipFuncTypeComptimeBits(ip, fn_ty) != 0) return false; uint32_t n = ipFuncTypeParamsLen(ip, fn_ty); for (uint32_t i = 0; i < n; i++) { uint32_t pt = ipFuncTypeParamType(ip, fn_ty, i); if (pt == IP_INDEX_GENERIC_POISON_TYPE) return false; ZtTypeClass c = ztTypeClass(ip, pt); if (c == ZT_CLASS_FULL || c == ZT_CLASS_PARTIAL || c == ZT_CLASS_NPV) return false; } uint32_t ret = ipFuncTypeReturnType(ip, fn_ty); if (ret == IP_INDEX_GENERIC_POISON_TYPE || ((ipItemTagIs(ip, ret, IP_TAG_TYPE_ERROR_UNION) || ipItemTagIs(ip, ret, IP_TAG_TYPE_ANYERROR_UNION)) && ipErrorUnionPayload(ip, ret) == IP_INDEX_GENERIC_POISON_TYPE)) return false; ZtTypeClass rc = ztTypeClass(ip, ret); if (rc == ZT_CLASS_FULL || rc == ZT_CLASS_PARTIAL) return false; // `if (fn_info.cc == .@"inline") return false;` return ((ip->extra[ip->item_datas[fn_ty] + 2] >> IP_PACKED_CC_TAG_SHIFT) & IP_PACKED_CC_TAG_MASK) != CC_TAG_INLINE; } // Type.zig:unionTagTypeHypothetical // Type.unionTagTypeHypothetical (src/Type.zig): "loadUnionType(...) // .enum_tag_type", the tag enum upstream retains even for an untagged union. // Upstream's `assertHasLayout` is zig1's `semaResolveAllUnionDeferred` -- // the deferred field/tag metadata has to be resolved before the generated // tag enum item can exist -- and `ipEnsureGeneratedUnionTag` answers an // already-interned `enum_tag_type` unchanged. uint32_t ztUnionTagTypeHypothetical(Zcu* zcu, uint32_t union_ty) { semaResolveAllUnionDeferred(zcu, union_ty); return ipEnsureGeneratedUnionTag(&zcu->intern_pool, union_ty); } // (part of) Type.zig:ptrAlignment // `Type.ptrAlignment` (log2 units) of a decoded pointer: its explicit // alignment, else its child's ABI alignment. bool ztPtrInfoAlignLog2( Zcu* zcu, InternPool* ip, const IpPtrInfo* info, uint32_t* out) { *out = info->alignment; return info->alignment != IP_PTR_ALIGN_NONE || ztAbiAlignLog2(zcu, ip, info->child, out); } // (part of) Type.zig:ptrAllowsZero // `Type.ptrAllowsZero` for a maybe-optional pointer or slice type. bool ztPtrAllowsZero(const InternPool* ip, uint32_t ty) { IpPtrInfo info; if (ipItemTagIs(ip, ty, IP_TAG_TYPE_OPTIONAL)) return true; return ztPtrOrSliceInfo(ip, ty, &info) && ztPtrInfoAllowsZero(&info); } // Declared in zig1.h: the comptime `@typeInfo` evaluator // (sema_comptime.c ctEvalBody's ZIR_INST_TYPE_INFO arm) needs the same // c-ABI-int {bits, signedness} model this file's coerce paths use, so one // definition serves both. bool ztCIntTypeInfoFE(uint32_t ty, uint32_t* bits, bool* is_signed) { switch (ty) { case ZIR_REF_C_CHAR_TYPE: *bits = 8; *is_signed = true; // Target.cCharSignedness for wasm32-wasi. return true; case ZIR_REF_C_SHORT_TYPE: *bits = 16; *is_signed = true; return true; case ZIR_REF_C_USHORT_TYPE: *bits = 16; *is_signed = false; return true; case ZIR_REF_C_INT_TYPE: case ZIR_REF_C_LONG_TYPE: *bits = 32; *is_signed = true; return true; case ZIR_REF_C_UINT_TYPE: case ZIR_REF_C_ULONG_TYPE: *bits = 32; *is_signed = false; return true; case ZIR_REF_C_LONGLONG_TYPE: *bits = 64; *is_signed = true; return true; case ZIR_REF_C_ULONGLONG_TYPE: *bits = 64; *is_signed = false; return true; default: return false; } } bool ztPtrIsConst(const InternPool* ip, uint32_t ptr) { if (ptr >= ip->items_len) return false; IpPtrInfo ptr_info; return ipPtrInfo(ip, ipTypeOf(ip, ptr), &ptr_info) == IP_PTRI_OK && ptr_info.is_const; } // ── Type names (print) ────────────────────────────────────────────────────── // (part of) Type.zig:print // The `@tagName` of a `simple_type` (Type.print's simple_type branch): the // preinterned simple-type entries occupy fixed IP indices (== the ZIR_REF_* // preintern numbering). Returns NULL for the tags Type.print spells specially // (null/undefined -> "@TypeOf(...)", enum_literal -> "@EnumLiteral()"). static const char* ztSimpleTypeName(uint32_t idx) { switch (idx) { case ZIR_REF_USIZE_TYPE: return "usize"; case ZIR_REF_ISIZE_TYPE: return "isize"; case ZIR_REF_C_CHAR_TYPE: return "c_char"; case ZIR_REF_C_SHORT_TYPE: return "c_short"; case ZIR_REF_C_USHORT_TYPE: return "c_ushort"; case ZIR_REF_C_INT_TYPE: return "c_int"; case ZIR_REF_C_UINT_TYPE: return "c_uint"; case ZIR_REF_C_LONG_TYPE: return "c_long"; case ZIR_REF_C_ULONG_TYPE: return "c_ulong"; case ZIR_REF_C_LONGLONG_TYPE: return "c_longlong"; case ZIR_REF_C_ULONGLONG_TYPE: return "c_ulonglong"; case ZIR_REF_C_LONGDOUBLE_TYPE: return "c_longdouble"; case ZIR_REF_F16_TYPE: return "f16"; case ZIR_REF_F32_TYPE: return "f32"; case ZIR_REF_F64_TYPE: return "f64"; case ZIR_REF_F80_TYPE: return "f80"; case ZIR_REF_F128_TYPE: return "f128"; case ZIR_REF_ANYOPAQUE_TYPE: return "anyopaque"; case ZIR_REF_BOOL_TYPE: return "bool"; case ZIR_REF_VOID_TYPE: return "void"; case ZIR_REF_TYPE_TYPE: return "type"; case ZIR_REF_ANYERROR_TYPE: return "anyerror"; case ZIR_REF_COMPTIME_INT_TYPE: return "comptime_int"; case ZIR_REF_COMPTIME_FLOAT_TYPE: return "comptime_float"; case ZIR_REF_NORETURN_TYPE: return "noreturn"; default: return NULL; } } // Append `len` bytes to `w`, keeping it NUL-terminated. static void ztTypeNameWrite( TypeNameWriter* w, const char* bytes, uint32_t len) { SLICE_ENSURE_CAPACITY(char, &w->buf, len + 1u); memcpy(w->buf.arr + w->buf.len, bytes, len); w->buf.len += len; w->buf.arr[w->buf.len] = '\0'; } void ztTypeNameWriteStr(TypeNameWriter* w, const char* s) { ztTypeNameWrite(w, s, (uint32_t)strlen(s)); } // `pre`, the decimal `v`, then `post`. static void ztTypeNameWriteU32( TypeNameWriter* w, const char* pre, uint32_t v, const char* post) { char num[16]; int n = snprintf(num, sizeof num, "%u", v); ztTypeNameWriteStr(w, pre); ztTypeNameWrite(w, num, (uint32_t)n); ztTypeNameWriteStr(w, post); } #define TYPE_NAME_DECLINE(msg) \ do { \ if (w->soft) { \ w->declined = true; \ return; \ } \ UNIMPLEMENTED(msg); \ } while (0) // (part of) Type.zig:print // Append a type's fully-qualified name to `w`, recursing into structural // children. Ports the tractable arms of Type.print (src/Type.zig): // int/simple/pointer/array/optional and named containers. // Sentineled/aligned/addrspace pointers are not yet handled (UNIMPLEMENTED). void ztFormatTypeName(InternPool* ip, uint32_t ty, TypeNameWriter* w) { switch (ip->item_tags[ty]) { case IP_TAG_TYPE_INT_UNSIGNED: ztTypeNameWriteU32(w, "u", ip->item_datas[ty], ""); return; case IP_TAG_TYPE_INT_SIGNED: ztTypeNameWriteU32(w, "i", ip->item_datas[ty], ""); return; case IP_TAG_SIMPLE_TYPE: { // `".null, .undefined => try writer.print(\"@TypeOf({s})\", // .{@tagName(s)})"`, `".enum_literal => try // writer.writeAll(\"@EnumLiteral()\")"`. const char* s = ty == ZIR_REF_NULL_TYPE ? "@TypeOf(null)" : ty == ZIR_REF_UNDEFINED_TYPE ? "@TypeOf(undefined)" : ty == ZIR_REF_ENUM_LITERAL_TYPE ? "@EnumLiteral()" : ty == ZIR_REF_ADHOC_INFERRED_ERROR_SET_TYPE ? "adhoc_inferred_error_set" : ztSimpleTypeName(ty); if (s == NULL) TYPE_NAME_DECLINE("type_name: unsupported simple type"); ztTypeNameWriteStr(w, s); return; } case IP_TAG_TYPE_VECTOR: { IpArrayInfo vi; if (ipArrayInfo(ip, ty, &vi) != IP_ARR_OK) TYPE_NAME_DECLINE("type_name: malformed vector type"); ztTypeNameWriteU32(w, "@Vector(", vi.len, ", "); ztFormatTypeName(ip, vi.child, w); ztTypeNameWriteStr(w, ")"); return; } case IP_TAG_TYPE_ERROR_UNION: case IP_TAG_TYPE_ANYERROR_UNION: { // `print(error_set_type)`, `'!'`, then the payload or `anytype`. ztFormatTypeName(ip, ipErrorUnionErrorSet(ip, ty), w); ztTypeNameWriteStr(w, "!"); uint32_t payload = ipErrorUnionPayload(ip, ty); if (payload == IP_INDEX_GENERIC_POISON_TYPE) ztTypeNameWriteStr(w, "anytype"); else ztFormatTypeName(ip, payload, w); return; } case IP_TAG_TYPE_INFERRED_ERROR_SET: { // `"@typeInfo(@typeInfo(@TypeOf({f})).@\"fn\".return_type.?) // .error_union.error_set"` of the function's owner Nav. uint32_t nav = ipFuncValueOwnerNav(ip, ip->item_datas[ty]); if (nav == IP_INDEX_NONE) TYPE_NAME_DECLINE( "type_name: inferred error set without a function"); ztTypeNameWriteStr(w, "@typeInfo(@typeInfo(@TypeOf("); ztTypeNameWriteStr(w, ipStringBytes(ip, ipGetNav(ip, nav)->fqn)); ztTypeNameWriteStr( w, ")).@\"fn\".return_type.?).error_union.error_set"); return; } case IP_TAG_TYPE_ERROR_SET: { // `"error{"`, the names sorted by their bytes and comma-separated, // `"}"`. uint32_t n = ipErrorSetNamesLen(ip, ty); uint32_t* names = ARR_INIT(uint32_t, n + 1u); for (uint32_t i = 0; i < n; i++) names[i] = ipErrorSetName(ip, ty, i); for (uint32_t i = 1; i < n; i++) for (uint32_t j = i; j > 0 && strcmp(ipStringBytes(ip, names[j - 1]), ipStringBytes(ip, names[j])) > 0; j--) { uint32_t t = names[j]; names[j] = names[j - 1]; names[j - 1] = t; } ztTypeNameWriteStr(w, "error{"); for (uint32_t i = 0; i < n; i++) { if (i != 0) ztTypeNameWriteStr(w, ","); ztTypeNameWriteStr(w, ipStringBytes(ip, names[i])); } ztTypeNameWriteStr(w, "}"); free(names); return; } case IP_TAG_TYPE_FUNCTION: { // `.func_type`: `noinline `, `fn (`, each parameter (`comptime `, // `noalias `, its type or `anytype`), `...`, `) `, a non-`.auto` // calling convention -- `callconv(.c) ` when it equals the target's // C convention -- then the return type or `anytype`. if (ipFuncTypeIsNoinline(ip, ty)) ztTypeNameWriteStr(w, "noinline "); ztTypeNameWriteStr(w, "fn ("); uint32_t n = ipFuncTypeParamsLen(ip, ty); uint32_t comptime_bits = ipFuncTypeComptimeBits(ip, ty); uint32_t noalias_bits = ipFuncTypeNoaliasBits(ip, ty); for (uint32_t i = 0; i < n; i++) { if (i != 0) ztTypeNameWriteStr(w, ", "); if (ipFuncTypeParamBit(comptime_bits, i)) ztTypeNameWriteStr(w, "comptime "); if (ipFuncTypeParamBit(noalias_bits, i)) ztTypeNameWriteStr(w, "noalias "); uint32_t pty = ipFuncTypeParamType(ip, ty, i); if (pty == IP_INDEX_GENERIC_POISON_TYPE) ztTypeNameWriteStr(w, "anytype"); else ztFormatTypeName(ip, pty, w); } if (ipFuncTypeIsVarArgs(ip, ty)) ztTypeNameWriteStr(w, n != 0 ? ", ..." : "..."); ztTypeNameWriteStr(w, ") "); uint32_t cc = ipFuncTypeCcPacked(ip, ty); if (cc == ipTypeFunctionFlags(true)) ztTypeNameWriteStr(w, "callconv(.c) "); else if (cc != ipTypeFunctionFlags(false)) TYPE_NAME_DECLINE("type_name: function calling convention"); uint32_t ret = ipFuncTypeReturnType(ip, ty); if (ret == IP_INDEX_GENERIC_POISON_TYPE) ztTypeNameWriteStr(w, "anytype"); else ztFormatTypeName(ip, ret, w); return; } case IP_TAG_TYPE_TUPLE: { // `"@TypeOf(.{})"` for no fields, else `"struct {"` and each field // (`comptime T = v` needs the value printer, not ported). uint32_t n = ipTupleFieldCount(ip, ty); if (n == 0) { ztTypeNameWriteStr(w, "@TypeOf(.{})"); return; } ztTypeNameWriteStr(w, "struct {"); for (uint32_t i = 0; i < n; i++) { if (ipTupleFieldValue(ip, ty, i) != IP_INDEX_NONE) TYPE_NAME_DECLINE("type_name: tuple comptime field value"); ztTypeNameWriteStr(w, i == 0 ? " " : ", "); ztFormatTypeName(ip, ipTupleFieldType(ip, ty, i), w); } ztTypeNameWriteStr(w, " }"); return; } case IP_TAG_TYPE_SLICE: case IP_TAG_TYPE_POINTER: { // `.ptr_type`: a slice is `ptrInfo` of its many-pointer at size // `.slice`. bool is_slice = ztIsSlice(ip, ty); const uint32_t* pw = &ip->extra[ip->item_datas[is_slice ? ip->item_datas[ty] : ty]]; uint32_t child = pw[0]; uint32_t sentinel = pw[1]; uint32_t flags = pw[2]; uint32_t packed_offset = pw[3]; uint32_t size = is_slice ? IP_PTR_SIZE_SLICE : (IP_PTR_FLAGS_SIZE(flags)); uint32_t alignment = IP_PTR_FLAGS_ALIGN(flags); uint32_t is_const = (flags & IP_PTR_FLAGS_IS_CONST) != 0; uint32_t is_volatile = (flags & IP_PTR_FLAGS_IS_VOLATILE) != 0; uint32_t is_allowzero = (flags & IP_PTR_FLAGS_IS_ALLOWZERO) != 0; uint32_t address_space = (flags >> IP_PTR_FLAGS_ADDRSPACE_SHIFT) & IP_PTR_FLAGS_ADDRSPACE_MASK; uint32_t vector_index = (flags >> IP_PTR_FLAGS_VECTOR_INDEX_SHIFT) & IP_PTR_FLAGS_VECTOR_INDEX_MASK; if (address_space != 0) TYPE_NAME_DECLINE("type_name: ptr addrspace"); if (sentinel != IP_INDEX_NONE) { // `"[*:{f}]"` / `"[:{f}]"` of the sentinel's `fmtValue`. ztTypeNameWriteStr(w, size == IP_PTR_SIZE_SLICE ? "[:" : "[*:"); if (!ztTypeNameWriteArgValue(ip, sentinel, 3, w)) TYPE_NAME_DECLINE("type_name: pointer sentinel value"); ztTypeNameWriteStr(w, "]"); } else { static const char* const size_prefix[] = { [PTR_SIZE_ONE] = "*", [PTR_SIZE_MANY] = "[*]", [PTR_SIZE_SLICE] = "[]", [PTR_SIZE_C] = "[*c]", }; ztTypeNameWriteStr(w, size_prefix[size]); } if (is_allowzero && size != IP_PTR_SIZE_C) ztTypeNameWriteStr(w, "allowzero "); uint32_t host_size = IP_PTR_PACKED_HOST_SIZE(packed_offset); uint32_t bit_offset = (packed_offset >> IP_PTR_PACKED_OFFSET_BIT_OFFSET_SHIFT) & IP_PTR_PACKED_OFFSET_BIT_OFFSET_MASK; if (alignment != IP_PTR_ALIGN_NONE || host_size != 0 || vector_index != IP_PTR_VECTOR_INDEX_NONE) { // "else Type.fromInterned(info.child).abiAlignment(zcu)". uint32_t align_log2 = alignment; if (alignment == IP_PTR_ALIGN_NONE && !ztAbiAlignLog2(NULL, ip, child, &align_log2)) TYPE_NAME_DECLINE("type_name: ptr child alignment"); ztTypeNameWriteU32(w, "align(", 1u << align_log2, ""); if (bit_offset != 0 || host_size != 0) { ztTypeNameWriteU32(w, ":", bit_offset, ""); ztTypeNameWriteU32(w, ":", host_size, ""); } if (vector_index != IP_PTR_VECTOR_INDEX_NONE) ztTypeNameWriteU32(w, ":", vector_index, ""); ztTypeNameWriteStr(w, ") "); } if (is_const) ztTypeNameWriteStr(w, "const "); if (is_volatile) ztTypeNameWriteStr(w, "volatile "); ztFormatTypeName(ip, child, w); return; } case IP_TAG_TYPE_OPTIONAL: ztTypeNameWriteStr(w, "?"); ztFormatTypeName(ip, ip->item_datas[ty], w); return; case IP_TAG_TYPE_ARRAY_SMALL: case IP_TAG_TYPE_ARRAY_BIG: { // `"[{d}]"`, or `"[{d}:{f}]"` with the sentinel's `fmtValue`. uint32_t len, child, sentinel; if (!ztArrayTypeInfo(ip, ty, &len, &child, &sentinel)) UNREACHABLE("type_name: malformed array type"); ztTypeNameWriteU32(w, "[", len, ""); if (sentinel != IP_INDEX_NONE) { ztTypeNameWriteStr(w, ":"); if (!ztTypeNameWriteArgValue(ip, sentinel, 3, w)) TYPE_NAME_DECLINE("type_name: array sentinel value"); } ztTypeNameWriteStr(w, "]"); ztFormatTypeName(ip, child, w); return; } case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: case IP_TAG_TYPE_ENUM_AUTO: case IP_TAG_TYPE_ENUM_EXPLICIT: case IP_TAG_TYPE_ENUM_NONEXHAUSTIVE: case IP_TAG_TYPE_UNION: case IP_TAG_TYPE_UNION_PACKED_AUTO: case IP_TAG_TYPE_UNION_PACKED_EXPLICIT: case IP_TAG_TYPE_OPAQUE: { // Named container: Type.print emits the interned fully-qualified name // (`ip.loadStructType(ty.toIntern()).fqn`). uint32_t name = ipContainerTypeFqn(ip, ty); // `setTypeName` always names a container; zig1 still leaves some // strategies' names unset (the empty string or `.none`). A // compile-error message renders such a name as "", `@typeName` may // not. bool unnamed = name == 0 || name == IP_INDEX_NONE; if (unnamed && w->soft) { w->declined = true; return; } if (unnamed && !w->diagnostic) UNIMPLEMENTED( "type_name: container named by an unported strategy"); ztTypeNameWriteStr(w, unnamed ? "" : ipStringBytes(ip, name)); return; } default: TYPE_NAME_DECLINE("type_name: unsupported type"); } } // ── Integer bounds, comparability, one possible value ─────────────────────── // (part of) Type.zig:minInt, Type.zig:maxInt // `Type.minInt` then `Type.maxInt` of a fixed-width integer type, interned // in that order; a comptime_int has neither (both outputs IP_INDEX_NONE). void ztIntTypeMinMax( InternPool* ip, uint32_t ty, uint32_t* out_min, uint32_t* out_max) { *out_min = IP_INDEX_NONE; *out_max = IP_INDEX_NONE; switch (ty) { case ZIR_REF_COMPTIME_INT_TYPE: // comptime_int is unbounded, so compareIntsOnlyPossibleResult has no // finite minInt/maxInt values to intern. break; default: { // A fixed-width integer type: minInt/maxInt for its width and // signedness, each through `InternPool.get`'s `.int` arm, which picks // the per-type storage (`int_u8`, ..., `int_small`, limbs). uint32_t w; bool s; if (!ztIntInfo(ip, ty, &w, &s)) UNIMPLEMENTED("minInt: unsupported integer type"); if (w > 64) { uint32_t limbs = bigIntCalcTwosCompLimbCount(w); uint64_t* buf = zig1Calloc((size_t)limbs * 2u, sizeof(uint64_t)); BigIntMutable lo = { buf, limbs, 0, true }; BigIntMutable hi = { buf + limbs, limbs, 0, true }; if (!bigIntMutableSetTwosCompIntLimit(&lo, BIG_INT_LIMIT_MIN, s, w) || !bigIntMutableSetTwosCompIntLimit( &hi, BIG_INT_LIMIT_MAX, s, w)) UNIMPLEMENTED("minInt: minInt/maxInt: limit"); *out_min = ipGetInt(ip, ty, bigIntMutableToConst(lo)); *out_max = ipGetInt(ip, ty, bigIntMutableToConst(hi)); free(buf); break; } uint64_t mn, mx; if (w == 0) { mn = 0; mx = 0; } else if (!s) { mn = 0; mx = (w >= 64) ? UINT64_MAX : (((uint64_t)1 << w) - 1ULL); } else { mn = (w >= 64) ? (uint64_t)INT64_MIN : (~(((uint64_t)1 << (w - 1)) - 1ULL)); mx = (w >= 64) ? (uint64_t)INT64_MAX : (((uint64_t)1 << (w - 1)) - 1ULL); } *out_min = ipInternIntResult(ip, mn, ty); *out_max = ipInternIntResult(ip, mx, ty); break; } } } // Type.zig:isSelfComparable // Type.isSelfComparable (src/Type.zig) for both operator kinds: the // equality relation above, and for an ordering only `.int`, `.float`, // `comptime_int`/`comptime_float`, a vector of those, and a C pointer // (`".pointer => !ty.isSlice(zcu) and (is_equality_cmp or // ty.isCPtr(zcu))"`); every other tag answers `is_equality_cmp` or false. bool ztTypeIsSelfComparable( const InternPool* ip, uint32_t ty, bool is_equality_cmp) { if (is_equality_cmp) return ztTypeIsSelfComparableEq(ip, ty); uint32_t bits; bool is_signed; if (ty == ZIR_REF_COMPTIME_INT_TYPE || ty == ZIR_REF_COMPTIME_FLOAT_TYPE || ztSimpleFloatBits(ty, &bits) || (ty < ip->items_len && ztIntInfo(ip, ty, &bits, &is_signed))) return true; IpArrayInfo vec; if (ipArrayInfo(ip, ty, &vec) == IP_ARR_OK && vec.is_vector) return ztTypeIsSelfComparable(ip, vec.child, false); return ztIsCPtr(ip, ty); } // Type.zig:isCPtr bool ztIsCPtr(const InternPool* ip, uint32_t ty) { IpPtrInfo ptr; return ipPtrInfo(ip, ty, &ptr) == IP_PTRI_OK && ptr.size == IP_PTR_SIZE_C; } // (part of) Type.zig:onePossibleValue // Type.onePossibleValue port ("pub fn onePossibleValue(ty: Type, pt: // Zcu.PerThread) !?Value {"), returning IP_INDEX_NONE for upstream's `null`. // The leading class gate is upstream's own test — the tuple arm opens with // "if (ty.classify(zcu) != .one_possible_value) return null;", struct/union // consult struct_obj.class/union_obj.class after the packed check, and every // other arm answers null exactly where ztClassifyTypeInner answers non-OPV. // // Only the classes whose upstream answer this port can build are produced. // The rest abort naming their upstream arm rather than minting the generic // IP_TAG_ONLY_POSSIBLE_VALUE marker: upstream reserves that item for // aggregates (indexToKey's ".only_possible_value => {" arm accepts only // array/vector/tuple/struct types), so a marker at any other type is a // structurally illegal item, not a cheap stand-in. uint32_t ztPtrFromIntDestInfo(InternPool* ip, uint32_t result_ty, bool* out_optional, uint32_t* out_flags) { uint32_t ptr_ty = result_ty; bool optional = false; if (ptr_ty < ip->items_len && ip->item_tags[ptr_ty] == IP_TAG_TYPE_OPTIONAL) { optional = true; ptr_ty = ip->item_datas[ptr_ty]; } if (ptr_ty >= ip->items_len || ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) COMPILE_ERROR_UPSTREAM("ptr_from_int: destination is not a pointer"); uint32_t ptd = ip->item_datas[ptr_ty]; uint32_t flags = ip->extra[ptd + IP_TYPE_POINTER_FLAGS]; if (IP_PTR_FLAGS_SIZE(flags) == IP_PTR_SIZE_SLICE) COMPILE_ERROR_UPSTREAM("ptr_from_int: integer cannot become a slice"); *out_optional = optional; *out_flags = flags; return ptr_ty; } // Type.zig:onePossibleValue uint32_t ztOnePossibleValue(InternPool* ip, uint32_t ty) { if (ztTypeClass(ip, ty) != ZT_CLASS_OPV) return IP_INDEX_NONE; if (ipTypeIsBitpack(ip, ty)) { // The struct_type and union_type arms' `.@"packed"` case: "const // backing_val = try backing_ty.onePossibleValue(pt) orelse return // null;" then "return try pt.bitpackValue(ty, backing_val);". uint32_t backing_val = ztOnePossibleValue(ip, ipBitpackBackingInt(ip, ty)); return backing_val == IP_INDEX_NONE ? IP_INDEX_NONE : ipGetBitpack(ip, ty, backing_val); } switch ((IpTag)ip->item_tags[ty]) { case IP_TAG_SIMPLE_TYPE: // ".void => .void," — the classifier's only OPV simple type. if (ty == ZIR_REF_VOID_TYPE) return ZIR_REF_VOID_VALUE; return IP_INDEX_NONE; case IP_TAG_TYPE_INT_SIGNED: case IP_TAG_TYPE_INT_UNSIGNED: // Type.onePossibleValue's int_type arm: "0 => try pt.intValue(ty, // 0),". IntSmall is the existing typed-int interner, including u0 // and i0, so no zero-byte storage is invented for the OPV. return ipGetIntSmall(ip, ty, 0); case IP_TAG_TYPE_ARRAY_SMALL: case IP_TAG_TYPE_ARRAY_BIG: case IP_TAG_TYPE_VECTOR: { // Type.onePossibleValue's shared array/vector arm, anchors // `seq_type.len + @intFromBool(has_sentinel) == 0` and // `return try pt.aggregateSplatValue(ty, opv)`. The splat interner is // O(1), like upstream; do not materialize a len-sized temporary. IpArrayInfo seq; IpArrayStatus seq_status = ipArrayInfo(ip, ty, &seq); if (seq_status == IP_ARR_LEN_TOO_BIG || (seq_status == IP_ARR_OK && seq.storage_len_overflowed)) // A perfectly legal type that zig1 cannot count: lengths are // u32 throughout. This shape (a `type_array_big` whose high // length word is non-zero) is classified UNIMPLEMENTED. UNIMPLEMENTED("onePossibleValue: one_possible_value - sequence " "len >= 2^32 unmodeled"); if (seq_status != IP_ARR_OK) UNREACHABLE("one_possible_value: undecodable sequence type"); if (seq.storage_len == 0) return ipGetAggregate(ip, ty, NULL, 0); uint32_t elem = ztOnePossibleValue(ip, seq.child); if (elem == IP_INDEX_NONE) UNIMPLEMENTED("onePossibleValue: one_possible_value - OPV " "sequence child has no value"); return ipGetRepeated(ip, ty, elem); } case IP_TAG_TYPE_OPTIONAL: // ".no_possible_value => try pt.nullValue(ty)," — an optional's only // OPV answer. Zcu.PerThread.nullValue is the bare `.opt`-with-`.none` // interner, "return Value.fromInterned(try pt.intern(.{ .opt = .{", // i.e. ipGetOptNull. The child class is re-tested rather than // inferred from the leading gate so this arm reads as upstream's // switch does. if (ztTypeClass(ip, ipOptionalChild(ip, ty)) != ZT_CLASS_NPV) return IP_INDEX_NONE; return ipGetOptNull(ip, ty); case IP_TAG_TYPE_TUPLE: { uint32_t off = ip->item_datas[ty]; uint32_t fields_len = ip->extra[off]; uint32_t stored = 0; for (uint32_t i = 0; i < fields_len; i++) if (ip->extra[off + 1 + fields_len + i] != IP_INDEX_NONE) stored++; if (stored == fields_len) // Every field value already lives in the type, so upstream's // "return try pt.aggregateValue(ty, field_vals);" hits // InternPool.get(.aggregate)'s `.tuple_type` "opv:" arm — its // "std.mem.eql(" test holds — and interns only_possible_value. // ipGetAggregate does not implement that canonicalization, hence // the direct mint here. return ipGetOnlyPossibleValue(ip, ty); // Type.onePossibleValue copies the stored values, recursively fills // each `.none` slot from its field type, then calls aggregateValue. // Since at least one slot was `.none`, the tuple-type `opv:` identity // shortcut cannot apply; ipGetAggregate chooses repeated/aggregate. uint32_t* field_vals = (uint32_t*)zig1Malloc((size_t)fields_len * sizeof(uint32_t)); for (uint32_t i = 0; i < fields_len; i++) { uint32_t stored_val = ip->extra[off + 1 + fields_len + i]; field_vals[i] = stored_val != IP_INDEX_NONE ? stored_val : ztOnePossibleValue(ip, ip->extra[off + 1 + i]); if (field_vals[i] == IP_INDEX_NONE) UNIMPLEMENTED("onePossibleValue: one_possible_value - OPV " "tuple field has no value"); } uint32_t result = ipGetAggregate(ip, ty, field_vals, fields_len); free(field_vals); return result; } case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: { uint32_t fields_len = ipStructFieldsLen(ip, ty); if (fields_len == 0) return ipGetAggregate(ip, ty, NULL, 0); // A comptime field's value is its `field_defaults` entry, every // other field its type's OPV; upstream's aggregateValue over those // interns a repeated/aggregate -- never only_possible_value. uint32_t* field_vals = (uint32_t*)zig1Malloc((size_t)fields_len * sizeof(uint32_t)); for (uint32_t i = 0; i < fields_len; i++) { field_vals[i] = ipStructFieldIsComptime(ip, ty, i) ? ipStructFieldDefault(ip, ty, i) : ztOnePossibleValue(ip, ipStructFieldType(ip, ty, i)); if (field_vals[i] == IP_INDEX_NONE) UNIMPLEMENTED("onePossibleValue: one_possible_value - OPV " "struct field has no value"); } uint32_t result = ipGetAggregate(ip, ty, field_vals, fields_len); free(field_vals); return result; } case IP_TAG_TYPE_UNION: { // The union_type arm: "// The OPV comes from exactly one field whose // type is OPV, while all others are NPV." — scan for that field and // "return try pt.unionValue(ty, tag_val, payload_val);". uint32_t union_fields_len = ipUnionFieldsLen(ip, ty); for (uint32_t i = 0; i < union_fields_len; i++) { uint32_t field_ty = ipUnionFieldType(ip, ty, i); if (field_ty == IP_INDEX_NONE) UNIMPLEMENTED("onePossibleValue: one_possible_value - union " "field type unresolved"); ZtTypeClass field_class = ztTypeClass(ip, field_ty); if (field_class == ZT_CLASS_NPV) continue; // ".no_possible_value => continue," if (field_class != ZT_CLASS_OPV) // "else => unreachable," — the classifier promised OPV, so a // third class here means the two disagree. UNIMPLEMENTED("onePossibleValue: one_possible_value - union " "field is neither OPV nor uninstantiable"); // "const tag_val = try pt.enumValueFieldIndex(enum_tag_ty, // @intCast(field_index));" — zig1 spells that as the shared // ipEnumTagFromFieldName (documented as mirroring // enumValueFieldIndex), addressed by the field's name because // declaration order and names correspond one-to-one; this is the // same spelling the other two union-tag sites use. uint32_t tag_enum = ipUnionEnumTagType(ip, ty); if (tag_enum == IP_INDEX_NONE) tag_enum = ipEnsureGeneratedUnionTag(ip, ty); uint32_t tag_val = ipEnumTagFromFieldName( ip, tag_enum, ipUnionFieldName(ip, ty, i)); if (tag_val == IP_INDEX_NONE) UNIMPLEMENTED("onePossibleValue: one_possible_value - union " "tag field missing"); uint32_t payload_val = ztOnePossibleValue(ip, field_ty); if (payload_val == IP_INDEX_NONE) UNIMPLEMENTED("onePossibleValue: one_possible_value - OPV " "union field has no value"); return ipGetUnionValue(ip, ty, tag_val, payload_val); } // "} else unreachable;" UNIMPLEMENTED("onePossibleValue: one_possible_value - OPV union has " "no possible field"); } case IP_TAG_TYPE_ENUM_AUTO: case IP_TAG_TYPE_ENUM_EXPLICIT: case IP_TAG_TYPE_ENUM_NONEXHAUSTIVE: { // Type.onePossibleValue's enum_type arm: "if (try // ty.intTagType(zcu).onePossibleValue(pt)) |int_tag_opv|" then // interns `.enum_tag = .{ .ty = ty.toIntern(), .int = ... }`. // The classifier has already established this enum is OPV, but keep // the recursive value construction faithful rather than assuming its // backing type is u0. uint32_t tag_ty = ipEnumTypeIntTagType(ip, ty); uint32_t tag_opv = ztOnePossibleValue(ip, tag_ty); if (tag_opv == IP_INDEX_NONE) return IP_INDEX_NONE; return ipGetEnumTag(ip, ty, tag_opv); } default: return IP_INDEX_NONE; } } static bool ztTypeIsSelfComparableEqImpl( const InternPool* ip, uint32_t ty, uint32_t depth, bool full_check) { if (ty == IP_INDEX_NONE || ty >= ip->items_len || (!full_check && depth > 32u)) return false; uint32_t w; bool s; if (ztIntInfo(ip, ty, &w, &s)) return true; if (ty == ZIR_REF_BOOL_TYPE || ty == ZIR_REF_VOID_TYPE || ty == ZIR_REF_TYPE_TYPE || ty == ZIR_REF_COMPTIME_INT_TYPE || ty == ZIR_REF_ENUM_LITERAL_TYPE || ty == ZIR_REF_ANYERROR_TYPE) return true; if (ipIsEnumType(ip, ty)) return true; if (full_check && (ty == ZIR_REF_C_LONGDOUBLE_TYPE || ty == ZIR_REF_F16_TYPE || ty == ZIR_REF_F32_TYPE || ty == ZIR_REF_F64_TYPE || ty == ZIR_REF_F80_TYPE || ty == ZIR_REF_F128_TYPE || ty == ZIR_REF_COMPTIME_FLOAT_TYPE || ty == ZIR_REF_ANYOPAQUE_TYPE || ty == ZIR_REF_ANYFRAME_TYPE)) return true; switch ((IpTag)ip->item_tags[ty]) { case IP_TAG_TYPE_VECTOR: { if (!full_check) return false; IpArrayInfo vector; return ipArrayInfo(ip, ty, &vector) == IP_ARR_OK && vector.is_vector && ztTypeIsSelfComparableEqImpl( ip, vector.child, depth + 1u, true); } case IP_TAG_TYPE_OPTIONAL: return ztTypeIsSelfComparableEqImpl( ip, ip->item_datas[ty], depth + 1u, full_check); case IP_TAG_TYPE_POINTER: { IpPtrInfo ptr; return ipPtrInfo(ip, ty, &ptr) == IP_PTRI_OK && ptr.size != IP_PTR_SIZE_SLICE; } case IP_TAG_TYPE_ERROR_SET: case IP_TAG_TYPE_INFERRED_ERROR_SET: return true; case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: return ipStructIsPacked(ip, ty); // `".@\"struct\", .@\"union\" => is_equality_cmp and // ty.containerLayout(zcu) == .@\"packed\""`. case IP_TAG_TYPE_UNION: case IP_TAG_TYPE_UNION_PACKED_AUTO: case IP_TAG_TYPE_UNION_PACKED_EXPLICIT: return ipUnionIsPacked(ip, ty); case IP_TAG_TYPE_FUNCTION: case IP_TAG_TYPE_OPAQUE: case IP_TAG_TYPE_ANYFRAME: return full_check; default: return false; } } // Existing comparison lowering only carries the represented scalar subset; // keep its admission boundary unchanged while sentinel validation asks for the // complete upstream equality relation below. static bool ztCmpOptionalChildComparableEq( const InternPool* ip, uint32_t ty, uint32_t depth) { return ztTypeIsSelfComparableEqImpl(ip, ty, depth, false); } bool ztCmpOptionalTypeComparableEq(const InternPool* ip, uint32_t opt_ty) { if (opt_ty >= ip->items_len || ip->item_tags[opt_ty] != IP_TAG_TYPE_OPTIONAL) return false; return ztCmpOptionalChildComparableEq(ip, ip->item_datas[opt_ty], 0u); } bool ztTypeIsSelfComparableEq(const InternPool* ip, uint32_t ty) { return ztTypeIsSelfComparableEqImpl(ip, ty, 0u, true); } // ── Layout (abiSize, abiAlignment, bitSize) ───────────────────────────────── // std.zig.target.intAlignment for wasm32, zig1's only target. // wasm32 falls to the generic branch: min(ceilPow2(ceil(bits/8)), // cMaxIntAlignment=16). Alignment is returned in bytes. static uint64_t ztWasmIntAlignment(uint32_t bits) { if (bits == 0) return 1; uint32_t bytes = (bits + 7) / 8; uint32_t p = 1; while (p < bytes) p <<= 1; return p < 16 ? p : 16; } // std.zig.target.intByteSize for wasm32: align the minimum byte count up to // the int alignment. static uint64_t ztWasmIntByteSize(uint32_t bits) { uint32_t previous_aligned = bits & ~7u; // alignBackward(bits, 8) uint32_t base = previous_aligned / 8 + (previous_aligned != bits ? 1 : 0); uint64_t align = ztWasmIntAlignment(bits); return (base + align - 1) / align * align; // alignForward(base, align) } // std.Target.cTypeBitSize / cTypeAlignment for the fixed wasm32-wasi target // profile used by zig1's AIR. The .wasi/.wasm32 `else` branch gives // long/ulong pointer width, longlong/double 64-bit, and longdouble 128-bit; // alignment is ceilPow2(byte_size) capped at 16. static bool ztWasmCTypeLayout( uint32_t ty, uint64_t* size, uint64_t* align, uint64_t* bits) { switch (ty) { case ZIR_REF_C_CHAR_TYPE: *bits = 8; break; case ZIR_REF_C_SHORT_TYPE: case ZIR_REF_C_USHORT_TYPE: *bits = 16; break; case ZIR_REF_C_INT_TYPE: case ZIR_REF_C_UINT_TYPE: case ZIR_REF_C_LONG_TYPE: case ZIR_REF_C_ULONG_TYPE: *bits = 32; break; case ZIR_REF_C_LONGLONG_TYPE: case ZIR_REF_C_ULONGLONG_TYPE: *bits = 64; break; case ZIR_REF_C_LONGDOUBLE_TYPE: *bits = 128; break; default: return false; } *size = (*bits + 7u) / 8u; uint64_t a = 1; while (a < *size) a <<= 1; *align = a < 16 ? a : 16; return true; } bool ztLayoutCheckedAdd(uint64_t a, uint64_t b, uint64_t* out) { if (b > UINT64_MAX - a) return false; *out = a + b; return true; } static bool ztLayoutCheckedMul(uint64_t a, uint64_t b, uint64_t* out) { if (a != 0 && b > UINT64_MAX / a) return false; *out = a * b; return true; } bool ztLayoutCheckedAlign(uint64_t value, uint64_t alignment, uint64_t* out) { if (alignment == 0 || (alignment & (alignment - 1u)) != 0) return false; uint64_t mask = alignment - 1u; if (mask > UINT64_MAX - value) return false; *out = (value + mask) & ~mask; return true; } // Type.abiAlignment's `.tuple_type` arm (the non-comptime fields' largest // alignment) and Type.abiSize's: `".no_possible_value => 0, else => // ty.structFieldOffset(tuple.types.len, zcu)"`. static bool ztComputeTupleLayout(Zcu* zcu, InternPool* ip, uint32_t tuple_ty, uint64_t* out_size, uint64_t* out_align) { uint32_t n = ipTupleFieldCount(ip, tuple_ty); *out_align = 1; for (uint32_t i = 0; i < n; i++) { if (ipTupleFieldValue(ip, tuple_ty, i) != IP_INDEX_NONE) continue; // comptime field uint32_t field_ty = ipTupleFieldType(ip, tuple_ty, i); uint64_t field_size, field_align, field_bits; if (!ztLayoutInfo( zcu, ip, field_ty, &field_size, &field_align, &field_bits) || ztTypeClass(ip, field_ty) == ZT_CLASS_UNKNOWN) return false; if (field_align > *out_align) *out_align = field_align; } ZtTypeClass tuple_class = ztTypeClass(ip, tuple_ty); if (tuple_class == ZT_CLASS_UNKNOWN) return false; *out_size = tuple_class == ZT_CLASS_NPV ? 0 : ztStructFieldOffset(zcu, ip, tuple_ty, n); return true; } // Type.zig:unpackable // True iff `ty` is not allowed in a // packed type -- everything but `void`, `bool`, floats, ints, enums with an // `.explicit` int tag, and packed structs/unions. Upstream's `.auto` int tag // mode is exactly the `.type_enum_auto` item tag. bool ztTypeUnpackable(const InternPool* ip, uint32_t ty) { if (ty >= ip->items_len) return true; switch (ip->item_tags[ty]) { case IP_TAG_TYPE_INT_SIGNED: case IP_TAG_TYPE_INT_UNSIGNED: case IP_TAG_TYPE_ENUM_EXPLICIT: case IP_TAG_TYPE_ENUM_NONEXHAUSTIVE: return false; case IP_TAG_SIMPLE_TYPE: { uint32_t fbits; switch (ty) { case ZIR_REF_VOID_TYPE: case ZIR_REF_BOOL_TYPE: case ZIR_REF_USIZE_TYPE: case ZIR_REF_ISIZE_TYPE: case ZIR_REF_C_CHAR_TYPE: case ZIR_REF_C_SHORT_TYPE: case ZIR_REF_C_USHORT_TYPE: case ZIR_REF_C_INT_TYPE: case ZIR_REF_C_UINT_TYPE: case ZIR_REF_C_LONG_TYPE: case ZIR_REF_C_ULONG_TYPE: case ZIR_REF_C_LONGLONG_TYPE: case ZIR_REF_C_ULONGLONG_TYPE: case ZIR_REF_C_LONGDOUBLE_TYPE: return false; default: return !ztSimpleFloatBits(ty, &fbits); } } default: return !ipTagIsStructPacked(ip->item_tags[ty]) && !ipTagIsUnionPacked(ip->item_tags[ty]); } } // Type.zig:abiSize, Type.zig:abiAlignment, Type.zig:bitSize (wasm32) bool ztLayoutInfo(Zcu* zcu, InternPool* ip, uint32_t ty, uint64_t* size, uint64_t* align, uint64_t* bits) { if (ty >= ip->items_len) return false; uint8_t tag = ip->item_tags[ty]; switch ((IpTag)tag) { case IP_TAG_TYPE_INT_UNSIGNED: case IP_TAG_TYPE_INT_SIGNED: { uint32_t b = ip->item_datas[ty]; *bits = b; *size = ztWasmIntByteSize(b); *align = ztWasmIntAlignment(b); return true; } case IP_TAG_SIMPLE_TYPE: // Static simple-type entries share their IP index with the ZIR ref. switch (ty) { case ZIR_REF_USIZE_TYPE: case ZIR_REF_ISIZE_TYPE: *bits = ZT_PTR_BIT_WIDTH; *size = ztWasmIntByteSize(ZT_PTR_BIT_WIDTH); *align = ztWasmIntAlignment(ZT_PTR_BIT_WIDTH); return true; case ZIR_REF_C_CHAR_TYPE: case ZIR_REF_C_SHORT_TYPE: case ZIR_REF_C_USHORT_TYPE: case ZIR_REF_C_INT_TYPE: case ZIR_REF_C_UINT_TYPE: case ZIR_REF_C_LONG_TYPE: case ZIR_REF_C_ULONG_TYPE: case ZIR_REF_C_LONGLONG_TYPE: case ZIR_REF_C_ULONGLONG_TYPE: case ZIR_REF_C_LONGDOUBLE_TYPE: return ztWasmCTypeLayout(ty, size, align, bits); case ZIR_REF_BOOL_TYPE: *bits = 1; *size = 1; *align = 1; return true; case ZIR_REF_VOID_TYPE: case ZIR_REF_NORETURN_TYPE: case ZIR_REF_TYPE_TYPE: case ZIR_REF_COMPTIME_INT_TYPE: case ZIR_REF_COMPTIME_FLOAT_TYPE: case ZIR_REF_NULL_TYPE: case ZIR_REF_UNDEFINED_TYPE: case ZIR_REF_ENUM_LITERAL_TYPE: *bits = 0; *size = 0; *align = 1; return true; case ZIR_REF_ANYERROR_TYPE: *bits = ZT_ERROR_ABI_SIZE * 8u; *size = ZT_ERROR_ABI_SIZE; *align = ZT_ERROR_ABI_ALIGN; return true; case ZIR_REF_F16_TYPE: *bits = 16; *size = 2; *align = 2; return true; case ZIR_REF_F32_TYPE: *bits = 32; *size = 4; *align = 4; return true; case ZIR_REF_F64_TYPE: *bits = 64; *size = 8; *align = 8; return true; case ZIR_REF_F80_TYPE: { // wasm32-wasi's c_longdouble is 128-bit, so compilerRtFloatAbi // (80) is `.soft`: Type.abiAlignment's `"// mantissa: u64,"` / // `"// exponent: u16,"` max, and abiSize that alignment // `.forward` of their summed byte sizes. uint64_t f80_align = ztWasmIntAlignment(SOFT_F80_MANTISSA_BITS); if (ztWasmIntAlignment(SOFT_F80_EXPONENT_BITS) > f80_align) f80_align = ztWasmIntAlignment(SOFT_F80_EXPONENT_BITS); uint64_t f80_bytes = ztWasmIntByteSize(SOFT_F80_MANTISSA_BITS) + ztWasmIntByteSize(SOFT_F80_EXPONENT_BITS); *bits = 80; *size = (f80_bytes + f80_align - 1) / f80_align * f80_align; *align = f80_align; return true; } case ZIR_REF_F128_TYPE: *bits = 128; *size = 16; *align = 16; return true; default: return false; } case IP_TAG_TYPE_ARRAY_SMALL: case IP_TAG_TYPE_ARRAY_BIG: { const uint32_t* w = &ip->extra[ip->item_datas[ty]]; uint64_t len = w[0]; if (tag == IP_TAG_TYPE_ARRAY_BIG) { len |= (uint64_t)w[1] << 32; // Type.Array.lenIncludingSentinel: the interned length excludes // the sentinel, but ABI size and bit size include its element. if (w[3] != IP_INDEX_NONE && !ztLayoutCheckedAdd(len, 1u, &len)) return false; } uint32_t child = (tag == IP_TAG_TYPE_ARRAY_SMALL) ? w[1] : w[2]; uint64_t csz, cal, cbits; if (!ztLayoutInfo(zcu, ip, child, &csz, &cal, &cbits)) return false; if (!ztLayoutCheckedMul(len, csz, size) || !ztLayoutCheckedMul(len, cbits, bits)) return false; *align = cal; return true; } case IP_TAG_TYPE_VECTOR: { // Type.bitSize shares one arm with arrays -- `".array, .vector => // ty.arrayLenIncludingSentinel(zcu) * ty.childType(zcu) // .bitSize(zcu),"` -- while abiSize and abiAlignment take this // chain's `.stage2_c, .stage2_wasm` arms: `"vec.len * elem_ty // .abiSize(zcu)"` rounded up to the vector's own alignment, which is // the element type's `abiAlignment`. A zero-length vector // short-circuits to alignment 1, size 0 // (`"if (vector_type.len == 0) return .@\"1\";"`). IpArrayInfo vec; if (ipArrayInfo(ip, ty, &vec) != IP_ARR_OK) return false; uint64_t csz, cal, cbits; if (!ztLayoutInfo(zcu, ip, vec.child, &csz, &cal, &cbits)) return false; if (vec.len == 0) { *size = 0; *align = 1; *bits = 0; return true; } uint64_t bytes; if (!ztLayoutCheckedMul(vec.len, csz, &bytes) || !ztLayoutCheckedMul(vec.len, cbits, bits) || cal == 0) return false; *align = cal; *size = (bytes + cal - 1) / cal * cal; // alignForward(bytes, cal) return true; } case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: case IP_TAG_TYPE_UNION_PACKED_AUTO: case IP_TAG_TYPE_UNION_PACKED_EXPLICIT: { // This sizing walk runs inside layout resolution, so it populates // the fields only; resolveStructLayout owns the layout itself. if (zcu != NULL && ipTagIsAnyStruct(tag)) semaEnsureStructFieldsResolved(zcu, ty); if (ipTagIsUnionPacked(tag) && zcu != NULL && !resolvePackedUnionLayout(zcu, ty)) return false; // `Type.abiSize`/`abiAlignment`/`bitSize`'s packed struct and union // arms are the backing integer's (`.none`, i.e. unresolved, declines). if (ipTypeIsBitpack(ip, ty)) return ztLayoutInfo( zcu, ip, ipBitpackBackingInt(ip, ty), size, align, bits); // `.struct_type => struct_obj.size` / `struct_obj.alignment`, stored // by resolveStructLayout (given a Zcu, resolved here first). Without // one an unresolved struct is laid out on the spot, zero-sized when // its class says so. `bits` = size * 8. if (zcu != NULL && !resolveStructLayout(zcu, ip, ty)) return false; if (ipStructLayoutResolved(ip, ty)) { *size = ipStructSize(ip, ty); *align = UINT64_C(1) << ipStructAlignmentLog2(ip, ty); } else if (!semaComputeStructLayout( zcu, ip, ty, size, align, NULL, NULL)) { return false; } if (!ztLayoutCheckedMul(*size, 8u, bits)) return false; return true; } case IP_TAG_TYPE_TUPLE: if (!ztComputeTupleLayout(zcu, ip, ty, size, align) || !ztLayoutCheckedMul(*size, 8u, bits)) return false; return true; case IP_TAG_TYPE_FUNCTION: // Type.abiSize(.func_type) is zero. wasm32 cannot observe or set // function alignment, so target.minFunctionAlignment is one. Function // fields still participate in a partial struct's resolved layout even // though they contribute no runtime bytes (std.Options.logFn). *bits = 0; *size = 0; *align = 1; return true; case IP_TAG_TYPE_POINTER: // Single/many pointer: ptrBitWidth (Type.abiSize pointer arm). *bits = ZT_PTR_BIT_WIDTH; *size = 4; *align = 4; return true; case IP_TAG_TYPE_SLICE: // Slice = { many-ptr, usize len } on wasm32: 8 bytes, ptr-aligned // (Type.abiSize slice arm: 2 * ptr size). Needed for // `field_names[i]` element loads in Writer.print (a comptime // `[]const [:0]const u8` element access). NOTE: `bits` is size*8 by // convention here — stock Type.bitSize asserts on slices (no defined // bit size); only `@bitSizeOf` on a slice could observe this, and // stock errors there. *bits = 64; *size = 8; *align = 4; return true; case IP_TAG_TYPE_ENUM_AUTO: case IP_TAG_TYPE_ENUM_EXPLICIT: case IP_TAG_TYPE_ENUM_NONEXHAUSTIVE: // An enum's layout is its integer tag type's layout (Type.abiSize enum // arm; mirrors interp.c interpBackingType). A single-field enum (e.g. // `SafetyLock.State = enum { unknown }` in ReleaseSmall) has a `u0` // tag type -> zero-bit. return ztLayoutInfo( zcu, ip, ipEnumTypeIntTagType(ip, ty), size, align, bits); case IP_TAG_TYPE_ERROR_SET: case IP_TAG_TYPE_INFERRED_ERROR_SET: *bits = ZT_ERROR_ABI_SIZE * 8u; *size = ZT_ERROR_ABI_SIZE; *align = ZT_ERROR_ABI_ALIGN; return true; case IP_TAG_TYPE_OPTIONAL: { // Type.abiSize: pointer-/error-like optionals use the payload's zero // value as their null niche. Other runtime optionals append a boolean // discriminator after the payload and pad by the payload alignment. uint32_t child = ip->item_datas[ty]; uint64_t csz, cal, cbits; if (!ztLayoutInfo(zcu, ip, child, &csz, &cal, &cbits)) return false; *align = cal; ZtTypeClass child_class = ztTypeClass(ip, child); if (child_class == ZT_CLASS_UNKNOWN) return false; if (child_class == ZT_CLASS_NPV || child_class == ZT_CLASS_FULL) { // Type.abiSize: ?noreturn is OPV and ?comptime-only remains fully // comptime, so neither wrapper dedicates runtime storage. *size = 0; *bits = 0; return true; } if (ztOptionalReprIsPayload(ip, ty)) { *size = csz; *bits = cbits; return true; } if (!ztLayoutCheckedAdd(csz, cal, size) || !ztLayoutCheckedMul(*size, 8u, bits)) return false; return true; } case IP_TAG_TYPE_ANYERROR_UNION: case IP_TAG_TYPE_ERROR_UNION: { uint64_t psz, pal, pbits; if (!ztLayoutInfo( zcu, ip, ipErrorUnionPayload(ip, ty), &psz, &pal, &pbits) || pal == 0 || (pal & (pal - 1u)) != 0) return false; *align = pal > ZT_ERROR_ABI_ALIGN ? pal : ZT_ERROR_ABI_ALIGN; ZtTypeClass payload_class = ztTypeClass(ip, ipErrorUnionPayload(ip, ty)); if (payload_class == ZT_CLASS_UNKNOWN) return false; if (payload_class == ZT_CLASS_FULL) { // No address can expose an error-set component of an error union; // a fully-comptime payload makes the wrapper zero-size. Alignment // remains max(payload, error-set), per Type.abiAlignment. *size = 0; *bits = 0; return true; } uint64_t raw; if (!ztLayoutCheckedAdd(ZT_ERROR_ABI_SIZE, psz, &raw) || !ztLayoutCheckedAlign(raw, *align, size) || !ztLayoutCheckedMul(*size, 8u, bits)) return false; return true; } case IP_TAG_TYPE_UNION: { // `.union_type => union_obj.size` / `union_obj.alignment`. Given a // Zcu the deferred fields and the tag are resolved and the layout // stored (resolveUnionLayout); a resolved union reads its stored // words and, without a Zcu, an unresolved one is laid out on the // spot. `bits` = size*8 (stock Type.bitSize asserts on unions; only // `@bitSizeOf` could observe it, and stock errors there). uint32_t enum_tag_ty = IP_INDEX_NONE; if (zcu != NULL) { semaResolveAllUnionDeferred(zcu, ty); enum_tag_ty = ipUnionEnumTagType(ip, ty); if (enum_tag_ty == IP_INDEX_NONE) enum_tag_ty = ipEnsureGeneratedUnionTag(ip, ty); if (enum_tag_ty == IP_INDEX_NONE) return false; } bool resolved = ipUnionLayoutResolved(ip, ty); ZtUnionLayout layout; if (!ztGetUnionLayout(zcu, ip, ty, &layout) || !ztLayoutCheckedMul(layout.abi_size, 8u, bits)) return false; *size = layout.abi_size; *align = layout.abi_align; if (zcu != NULL && !resolved) { ZtTypeClass type_class = ztTypeClass(ip, ty); if (type_class == ZT_CLASS_UNKNOWN || layout.abi_size > UINT32_MAX || layout.abi_align > (UINT64_C(1) << 62)) return false; ipResolveUnionLayout(ip, ty, enum_tag_ty, (uint32_t)type_class, layout.has_runtime_tag, (uint32_t)layout.abi_size, layout.padding, ztLog2PowerOfTwoU64(layout.abi_align)); } return true; } default: return false; } } // (part of) Type.zig:getUnionLayout // Zcu.UnionLayout of a non-packed union: the payload is the largest field // at the most-aligned field's alignment, and a runtime tag exists only for // a tagged or safety union with at least two possible fields and no // comptime-state payload (resolveUnionLayout). A resolved union reads its // stored words; an unresolved one walks its fields (given a Zcu, each // field's layout is resolved) and derives them. bool ztGetUnionLayout( Zcu* zcu, InternPool* ip, uint32_t ty, ZtUnionLayout* out) { if (!ipUnionLayoutResolved(ip, ty)) { uint32_t n = ipUnionFieldsLen(ip, ty); uint32_t possible_fields = 0; bool payload_has_comptime_state = false; uint64_t payload_size = 0; uint64_t payload_align = 1; for (uint32_t i = 0; i < n; i++) { uint32_t fty = ipUnionFieldType(ip, ty, i); if (fty == IP_INDEX_NONE) return false; uint64_t fsz, fal, fbits; if (!ztLayoutInfo(zcu, ip, fty, &fsz, &fal, &fbits)) return false; // `"if (union_obj.field_aligns.len != 0) { const a = // union_obj.field_aligns.get(ip)[field_idx]; if (a != .none) // break :a a; } break :a field_ty.abiAlignment(zcu);"` uint32_t explicit_align = ipUnionFieldAlignment(ip, ty, i); if (explicit_align != IP_PTR_ALIGN_NONE) fal = UINT64_C(1) << explicit_align; if (fsz > payload_size) payload_size = fsz; if (fal > payload_align) payload_align = fal; // An NPV field is no possible tag but still carries its // alignment. switch (ztTypeClass(ip, fty)) { case ZT_CLASS_NPV: break; case ZT_CLASS_OPV: case ZT_CLASS_RUNTIME: possible_fields++; break; case ZT_CLASS_PARTIAL: case ZT_CLASS_FULL: possible_fields++; payload_has_comptime_state = true; break; case ZT_CLASS_UNKNOWN: return false; } } out->has_runtime_tag = possible_fields >= 2u && ipUnionTagUsage(ip, ty) != IP_UNION_TAG_USAGE_NONE && !payload_has_comptime_state; out->tag_size = 0; out->tag_align = 1; if (out->has_runtime_tag) { uint64_t tag_bits; uint32_t enum_tag_ty = ipUnionEnumTagType(ip, ty); if (enum_tag_ty == IP_INDEX_NONE || !ztLayoutInfo(zcu, ip, enum_tag_ty, &out->tag_size, &out->tag_align, &tag_bits)) return false; } out->abi_align = out->tag_align > payload_align ? out->tag_align : payload_align; uint64_t unpadded; if (!ztLayoutCheckedAdd(out->tag_size, payload_size, &unpadded) || !ztLayoutCheckedAlign(unpadded, out->abi_align, &out->abi_size)) return false; out->padding = 0; if (out->has_runtime_tag) { if (out->abi_size - unpadded > UINT32_MAX) return false; out->padding = (uint32_t)(out->abi_size - unpadded); } ZtTypeClass type_class = ztTypeClass(ip, ty); if (type_class == ZT_CLASS_UNKNOWN) return false; if (type_class == ZT_CLASS_NPV || type_class == ZT_CLASS_OPV || type_class == ZT_CLASS_FULL) out->abi_size = 0; return true; } out->has_runtime_tag = ipUnionHasRuntimeTag(ip, ty); out->tag_size = 0; out->tag_align = 1; if (out->has_runtime_tag) { uint64_t tag_bits; if (!ztLayoutInfo(zcu, ip, ipUnionEnumTagType(ip, ty), &out->tag_size, &out->tag_align, &tag_bits)) return false; } out->abi_size = ipUnionSize(ip, ty); out->abi_align = UINT64_C(1) << ipUnionAlignmentLog2(ip, ty); out->padding = ipUnionPadding(ip, ty); return true; } uint32_t ztLog2PowerOfTwoU64(uint64_t n) { uint32_t log2 = 0; while ((UINT64_C(1) << log2) < n) log2++; return log2; } // ── Field and element pointer types (fieldPtrType, elemPtrType) ───────────── // (part of) Type.zig:ptrInfo // `Type.ptrInfo` for a type that may be a slice. Upstream reports // `.flags.size = .slice` for a `[]T`; a zig1 `[]T` is its own // IP_TAG_TYPE_SLICE whose data is the many-pointer carrying every other // attribute, and `ipPtrInfo` refuses it outright. `checkPtrAttributes`' own // `len0` clause reads that size, so the distinction is load-bearing. bool ztPtrOrSliceInfo(const InternPool* ip, uint32_t ty, IpPtrInfo* out) { if (ztIsSlice(ip, ty)) { if (ipPtrInfo(ip, ip->item_datas[ty], out) != IP_PTRI_OK) return false; out->size = IP_PTR_SIZE_SLICE; return true; } return ipPtrInfo(ip, ty, out) == IP_PTRI_OK; } // Type.zig:fieldPtrType // Type.fieldPtrType for the aggregate paths emitted below. Preserve the // complete parent PtrType key and replace only child + alignment. In // particular, const/volatile/allowzero/address-space/vector-index, sentinel, // and packed-offset information must survive a field projection. uint32_t ztFieldPtrType( Zcu* zcu, uint32_t parent_ptr_ty, uint32_t field_index) { InternPool* ip = &zcu->intern_pool; if (parent_ptr_ty >= ip->items_len || ip->item_tags[parent_ptr_ty] != IP_TAG_TYPE_POINTER) UNREACHABLE("field pointer source is not a pointer type"); const uint32_t* parent = &ip->extra[ip->item_datas[parent_ptr_ty]]; uint32_t aggregate_ty = parent[0]; uint32_t sentinel = parent[1]; uint32_t flags = parent[2]; uint32_t packed_offset = parent[3]; uint32_t ptr_size = IP_PTR_FLAGS_SIZE(flags); if (ptr_size != IP_PTR_SIZE_ONE && ptr_size != IP_PTR_SIZE_C) UNREACHABLE("field pointer source must be single-item or C pointer"); uint32_t field_ty; bool field_is_comptime = false; bool is_extern_struct = false; bool is_packed_struct = false; uint32_t explicit_field_align = IP_PTR_ALIGN_NONE; uint32_t parent_align = IP_PTR_FLAGS_ALIGN(flags); switch ((IpTag)ip->item_tags[aggregate_ty]) { case IP_TAG_TYPE_TUPLE: { uint32_t off = ip->item_datas[aggregate_ty]; uint32_t fields_len = ip->extra[off]; if (field_index >= fields_len) UNREACHABLE("tuple field pointer index out of bounds"); field_ty = ip->extra[off + 1 + field_index]; field_is_comptime = ip->extra[off + 1 + fields_len + field_index] != IP_INDEX_NONE; break; } case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: is_packed_struct = ipStructIsPacked(ip, aggregate_ty); // Type.fieldPtrType's `"assertHasLayout"`. ensureLayoutResolved(zcu, aggregate_ty); if (field_index >= ipStructFieldsLen(ip, aggregate_ty)) UNREACHABLE("struct field pointer index out of bounds"); if (!is_packed_struct) { // Stock begins Type.fieldPtrType with assertHasLayout. if (!resolveStructLayout(zcu, ip, aggregate_ty)) UNIMPLEMENTED("fieldPtrType: struct field pointer layout"); explicit_field_align = ipStructFieldAlignment(ip, aggregate_ty, field_index); } field_ty = ipStructFieldType(ip, aggregate_ty, field_index); if (field_ty == IP_INDEX_NONE) UNIMPLEMENTED( "fieldPtrType: unresolved struct field pointer type"); if (!is_packed_struct) { field_is_comptime = ipStructFieldIsComptime(ip, aggregate_ty, field_index); is_extern_struct = ipStructIsExtern(ip, aggregate_ty); } break; case IP_TAG_TYPE_UNION_PACKED_AUTO: case IP_TAG_TYPE_UNION_PACKED_EXPLICIT: { // `.@"union"` `.@"packed"`: the parent PtrType with `child` replaced // and, for a default-aligned parent, the union's ABI alignment made // explicit when the field's own differs ("if (field_ty.abiAlignment( // zcu) != resolved_align) field_ptr_info.flags.alignment = // resolved_align;"). `packed_offset` is kept as is. if (!resolvePackedUnionLayout(zcu, aggregate_ty) || field_index >= ipUnionFieldsLen(ip, aggregate_ty)) UNIMPLEMENTED("fieldPtrType: packed union field pointer"); field_ty = ipUnionFieldType(ip, aggregate_ty, field_index); uint32_t pu_flags = flags; if (parent_align == IP_PTR_ALIGN_NONE) { uint32_t union_align, field_align; if (!ztAbiAlignLog2(zcu, ip, aggregate_ty, &union_align) || !ztAbiAlignLog2(zcu, ip, field_ty, &field_align)) UNIMPLEMENTED( "fieldPtrType: packed union field pointer alignment"); if (field_align != union_align) pu_flags = IP_PTR_FLAGS_WITH_ALIGN( flags, union_align & IP_PTR_FLAGS_ALIGN_MASK); } return ipGetPtrTypeFull( ip, field_ty, pu_flags, sentinel, packed_offset); } case IP_TAG_TYPE_UNION: if (field_index >= ipUnionFieldsLen(ip, aggregate_ty)) UNREACHABLE("union field pointer index out of bounds"); field_ty = semaResolveUnionFieldTypeLazy(zcu, aggregate_ty, field_index); if (field_ty == IP_INDEX_NONE) UNIMPLEMENTED("fieldPtrType: unresolved union field pointer type"); // `.auto`: `"union_obj.field_aligns.getOrNone(ip, field_index)"`. explicit_field_align = ipUnionFieldAlignment(ip, aggregate_ty, field_index); if (ipUnionIsExtern(ip, aggregate_ty)) { uint32_t result_align = parent_align; if (result_align == IP_PTR_ALIGN_NONE) { uint64_t aggregate_size, aggregate_align_bytes, aggregate_bits; if (!ztLayoutInfo(zcu, ip, aggregate_ty, &aggregate_size, &aggregate_align_bytes, &aggregate_bits) || aggregate_align_bytes == 0 || (aggregate_align_bytes & (aggregate_align_bytes - 1u)) != 0) UNIMPLEMENTED("fieldPtrType: extern union ABI alignment"); uint64_t field_size, field_align_bytes, field_bits; if (!ztLayoutInfo(zcu, ip, field_ty, &field_size, &field_align_bytes, &field_bits) || field_align_bytes == 0 || (field_align_bytes & (field_align_bytes - 1u)) != 0) UNIMPLEMENTED( "fieldPtrType: extern union field ABI alignment"); if (field_align_bytes != aggregate_align_bytes) { result_align = 0; while ( (UINT64_C(1) << result_align) < aggregate_align_bytes) result_align++; } } uint32_t result_flags = IP_PTR_FLAGS_WITH_ALIGN( flags, result_align & IP_PTR_FLAGS_ALIGN_MASK); return ipGetPtrTypeFull( ip, field_ty, result_flags, sentinel, packed_offset); } break; case IP_TAG_TYPE_SLICE: if (field_index == 0) field_ty = ip->item_datas[aggregate_ty]; // many-item ptr field else if (field_index == 1) field_ty = ZIR_REF_USIZE_TYPE; else UNREACHABLE("slice field pointer index out of bounds"); break; default: UNIMPLEMENTED("fieldPtrType: aggregate type"); } if (is_packed_struct) { uint32_t result_flags = flags; if ((IP_PTR_FLAGS_ALIGN(result_flags)) == IP_PTR_ALIGN_NONE) { uint64_t aggregate_size, aggregate_align_bytes, aggregate_bits; if (!ztLayoutInfo(zcu, ip, aggregate_ty, &aggregate_size, &aggregate_align_bytes, &aggregate_bits) || aggregate_align_bytes == 0 || (aggregate_align_bytes & (aggregate_align_bytes - 1u)) != 0) UNIMPLEMENTED( "fieldPtrType: packed struct field pointer alignment"); uint32_t aggregate_align = 0; while ((UINT64_C(1) << aggregate_align) < aggregate_align_bytes) aggregate_align++; result_flags = IP_PTR_FLAGS_WITH_ALIGN( result_flags, aggregate_align & IP_PTR_FLAGS_ALIGN_MASK); } uint32_t host_size = IP_PTR_PACKED_HOST_SIZE(packed_offset); uint32_t bit_offset = packed_offset >> 16; uint32_t field_bit_offset = zcuStructPackedFieldBitOffset( zcu, ip, aggregate_ty, field_index); if (field_bit_offset > UINT16_MAX || bit_offset > UINT16_MAX - field_bit_offset) UNIMPLEMENTED( "fieldPtrType: packed struct field bit offset overflow"); bit_offset += field_bit_offset; if (host_size == 0) { uint64_t aggregate_size, aggregate_align, aggregate_bits; if (!ztLayoutInfo(zcu, ip, aggregate_ty, &aggregate_size, &aggregate_align, &aggregate_bits)) UNIMPLEMENTED("fieldPtrType: packed struct field host size"); // `.host_size = @intCast((aggregate_ty.bitSize(zcu) + 7) / 8)`: // zero for a zero-bit packed struct, whose field pointers are // then plain pointers. uint64_t bytes = (aggregate_bits + 7u) / 8u; if (bytes > UINT16_MAX) UNIMPLEMENTED( "fieldPtrType: packed struct field host size encoding"); host_size = (uint32_t)bytes; } uint32_t result_packed_offset = (bit_offset << 16) | (host_size & 0xFFFFu); return ipGetPtrTypeFull( ip, field_ty, result_flags, sentinel, result_packed_offset); } uint32_t result_align = IP_PTR_ALIGN_NONE; // Alignment.none if (field_is_comptime) { // Comptime fields have no runtime parent offset. Stock uses exactly // their annotation, or `.none` (semantic ABI alignment) when absent. result_align = explicit_field_align; } else if (is_extern_struct) { if (!resolveStructLayout(zcu, ip, aggregate_ty)) UNIMPLEMENTED("fieldPtrType: extern struct field pointer layout"); uint32_t actual_parent_align = parent_align; if (actual_parent_align == IP_PTR_ALIGN_NONE) { uint64_t aggregate_size, aggregate_align_bytes, aggregate_bits; if (!ztLayoutInfo(zcu, ip, aggregate_ty, &aggregate_size, &aggregate_align_bytes, &aggregate_bits) || aggregate_align_bytes == 0 || (aggregate_align_bytes & (aggregate_align_bytes - 1u)) != 0) UNIMPLEMENTED("fieldPtrType: extern struct ABI alignment"); actual_parent_align = 0; while ( (UINT64_C(1) << actual_parent_align) < aggregate_align_bytes) actual_parent_align++; } uint32_t offset = ipStructFieldOffset(ip, aggregate_ty, field_index); uint32_t actual_field_align = actual_parent_align; if (offset != 0) { uint32_t offset_align = 0; while ((offset & (1u << offset_align)) == 0) offset_align++; if (offset_align < actual_field_align) actual_field_align = offset_align; } bool can_use_none = false; if (parent_align == IP_PTR_ALIGN_NONE && explicit_field_align == IP_PTR_ALIGN_NONE) { uint64_t field_size, field_align_bytes, field_bits; if (!ztLayoutInfo(zcu, ip, field_ty, &field_size, &field_align_bytes, &field_bits) || field_align_bytes == 0 || (field_align_bytes & (field_align_bytes - 1u)) != 0) UNIMPLEMENTED("fieldPtrType: extern field ABI alignment"); uint32_t field_abi_align = 0; while ((UINT64_C(1) << field_abi_align) < field_align_bytes) field_abi_align++; can_use_none = actual_field_align == field_abi_align; } if (!can_use_none) result_align = actual_field_align; } else if (explicit_field_align != IP_PTR_ALIGN_NONE) { result_align = (parent_align != IP_PTR_ALIGN_NONE && parent_align < explicit_field_align) ? parent_align : explicit_field_align; } else if (parent_align != IP_PTR_ALIGN_NONE) { uint64_t field_size, field_align_bytes, field_bits; if (!ztLayoutInfo(zcu, ip, field_ty, &field_size, &field_align_bytes, &field_bits) || field_align_bytes == 0 || (field_align_bytes & (field_align_bytes - 1u)) != 0) UNIMPLEMENTED("fieldPtrType: ABI alignment"); uint32_t field_abi_align = 0; while ((UINT64_C(1) << field_abi_align) < field_align_bytes) field_abi_align++; if (parent_align < field_abi_align) result_align = parent_align; } uint32_t result_flags = IP_PTR_FLAGS_WITH_ALIGN( flags, result_align & IP_PTR_FLAGS_ALIGN_MASK); return ipGetPtrTypeFull( ip, field_ty, result_flags, sentinel, packed_offset); } // `has_index` is upstream's `index: ?u64` being non-null: false means the // element index is runtime-known, which changes two answers -- the `.runtime` // alignment arm takes `index orelse 1` (the minimum possible alignment) and // `is_allowzero` survives, since upstream clears it only for a known non-zero // index (`ptr_info.flags.is_allowzero and (index == null or index == 0)`, // src/Type.zig). uint32_t ztElemPtrTypeInner(Zcu* zcu, uint32_t parent_ptr_ty, uint32_t index, uint32_t* out_elem_ty, bool allow_empty_one_past, bool has_index) { InternPool* ip = &zcu->intern_pool; if (parent_ptr_ty >= ip->items_len || ip->item_tags[parent_ptr_ty] != IP_TAG_TYPE_POINTER) UNREACHABLE("elemPtrType: source is not a pointer type"); uint32_t pd = ip->item_datas[parent_ptr_ty]; uint32_t child = ip->extra[pd + IP_TYPE_POINTER_CHILD]; uint32_t flags = ip->extra[pd + IP_TYPE_POINTER_FLAGS]; uint32_t elem_ty; switch (IP_PTR_FLAGS_SIZE(flags)) { case IP_PTR_SIZE_ONE: { IpArrayInfo array; if (ipArrayInfo(ip, child, &array) != IP_ARR_OK || array.is_vector) UNREACHABLE("elemPtrType: child is not an array"); uint64_t limit = (uint64_t)array.len + (array.sentinel != IP_INDEX_NONE ? 1u : 0u); if (has_index) { if (index >= limit && !(allow_empty_one_past && index == limit)) COMPILE_ERROR_UPSTREAM("elemPtrType: index out of bounds"); } else if (limit == 0 && !allow_empty_one_past) { // "if (array_len_s == 0) return sema.fail(block, array_ptr_src, // \"cannot index into empty array\")" (Sema.elemPtrArray, // src/Sema.zig) -- upstream rejects the empty array before // it looks at the index at all, so a runtime index cannot slip // past it. COMPILE_ERROR_UPSTREAM("cannot index into empty array"); } elem_ty = array.child; break; } case IP_PTR_SIZE_MANY: case IP_PTR_SIZE_C: elem_ty = child; break; default: UNIMPLEMENTED("elemPtrType: source has unsupported size"); } uint32_t parent_align = IP_PTR_FLAGS_ALIGN(flags); uint32_t result_align = parent_align; // "elem_ty.assertHasLayout(zcu)" (src/Type.zig). zig1 spells // assertHasLayout as the lazy resolve, which is what gives a deferred // container a class at all; its `false` answers -- a comptime-only or // no-possible-value element -- are legal here, because the classify // switch below has an arm for each, so only the class decides. ensureLayoutResolved(zcu, elem_ty); ZtTypeClass elem_class = ztTypeClass(ip, elem_ty); if (elem_class == ZT_CLASS_UNKNOWN) UNIMPLEMENTED("elemPtrType: type has unresolved class"); if (parent_align != IP_PTR_ALIGN_NONE && elem_class != ZT_CLASS_NPV && elem_class != ZT_CLASS_OPV) { uint64_t size, align, bits; if (!ztLayoutInfo(zcu, ip, elem_ty, &size, &align, &bits)) UNIMPLEMENTED("elemPtrType: type has unresolved layout"); if (elem_class == ZT_CLASS_PARTIAL || elem_class == ZT_CLASS_FULL) { uint32_t natural = ztLog2PowerOfTwoU64(align); if (natural < result_align) result_align = natural; } else if (!has_index || index != 0) { // "const effective_index = index orelse 1": a runtime index gives // the minimum possible alignment. uint64_t effective_index = has_index ? (uint64_t)index : 1u; if (size != 0 && effective_index > UINT64_MAX / size) COMPILE_ERROR_UPSTREAM("elemPtrType: offset overflow"); uint64_t byte_offset = effective_index * size; uint32_t offset_align = 0; while ( byte_offset != 0 && ((byte_offset >> offset_align) & 1u) == 0u) offset_align++; if (byte_offset != 0 && offset_align < result_align) result_align = offset_align; } } flags &= ~IP_PTR_FLAGS_SIZE_MASK; flags = IP_PTR_FLAGS_WITH_VECTOR_INDEX( IP_PTR_FLAGS_WITH_ALIGN(flags, result_align), IP_PTR_VECTOR_INDEX_NONE); // "is_allowzero = ptr_info.flags.is_allowzero and (index == null or // index == 0)": null and 0 give the same answer, and // ztElemPtrTypeRuntimeIndex passes 0, so this one test states both. if (index != 0) flags &= ~IP_PTR_FLAGS_IS_ALLOWZERO; if (out_elem_ty) *out_elem_ty = elem_ty; return ipGetPtrTypeFull(ip, elem_ty, flags, IP_INDEX_NONE, 0); } // Type.zig:elemPtrType uint32_t ztElemPtrType( Zcu* zcu, uint32_t parent_ptr_ty, uint32_t index, uint32_t* out_elem_ty) { return ztElemPtrTypeInner( zcu, parent_ptr_ty, index, out_elem_ty, false, true); } // Type.zig:elemPtrType // Type.elemPtrType with a NULL `index`: the element index is runtime-known. uint32_t ztElemPtrTypeRuntimeIndex( Zcu* zcu, uint32_t parent_ptr_ty, uint32_t* out_elem_ty) { return ztElemPtrTypeInner( zcu, parent_ptr_ty, 0, out_elem_ty, false, false); } // Type.zig:indexableElem // Type.indexableElem of a pointer type: a slice's, many or C pointer's // child, or a single pointer's array or vector element. uint32_t ztIndexableElem(const InternPool* ip, uint32_t ptr_ty) { if (ztIsSlice(ip, ptr_ty)) ptr_ty = ip->item_datas[ptr_ty]; IpPtrInfo info; IpArrayInfo arr; if (ipPtrInfo(ip, ptr_ty, &info) != IP_PTRI_OK) UNREACHABLE("indexableElem: not a pointer type"); if (info.size != IP_PTR_SIZE_ONE) return info.child; if (ipArrayInfo(ip, info.child, &arr) != IP_ARR_OK) UNREACHABLE("indexableElem: single pointer to a non-array"); return arr.child; } // Type.zig:fieldType // Type.fieldType of a struct, tuple or union. uint32_t ztFieldType(const InternPool* ip, uint32_t ty, uint32_t i) { if (ztIsTuple(ip, ty)) return ipTupleFieldType(ip, ty, i); if (ztZigTypeTag(ip, ty) == ZIG_TYPE_TAG_UNION) return ipUnionFieldType(ip, ty, i); return ipStructFieldType(ip, ty, i); } // Type.zig:arrayBase, Value.zig:ptrElem // Exact Type.arrayBase and Value.ptrElem ports shared by element-pointer // chains and cell slices. Runtime-bit elements advance byte_offset; // comptime-only elements use canonical flattened arr_elem units. bool ztArrayBase( const InternPool* ip, uint32_t ty, uint32_t* out_base, uint64_t* out_len) { uint64_t len = 1; uint32_t cur = ty; IpArrayInfo info; while (ipArrayInfo(ip, cur, &info) == IP_ARR_OK && !info.is_vector) { uint64_t including_sentinel = (uint64_t)info.len + (info.sentinel != IP_INDEX_NONE ? 1u : 0u); if (including_sentinel != 0 && len > UINT64_MAX / including_sentinel) return false; len *= including_sentinel; cur = info.child; } *out_base = cur; *out_len = len; return true; } // ── Layout predicates (isPtrAtRuntime, hasWellDefinedLayout) ──────────────── // Type.zig:abiSize // src/Sema/reinterpret.zig. Every step answers ZPT_CAST_OK, ZPT_CAST_RUNTIME // (upstream's `error.ReinterpretDeclRef`, a pointer that is not an integer // address cut or rebuilt: `castMemory` returns null) or ZPT_CAST_UNMODELED // (a value or type zig1 does not lay out; the caller keeps its loud abort). uint64_t ztAbiSize(Zcu* zcu, uint32_t ty) { uint64_t size, align, bits; return ztLayoutInfo(zcu, &zcu->intern_pool, ty, &size, &align, &bits) ? size : UINT64_MAX; } // Type.zig:isPtrAtRuntime // `Type.isPtrAtRuntime`: a non-slice pointer or a pointer-like optional. bool ztIsPtrAtRuntime(const InternPool* ip, uint32_t ty) { return (ty < ip->items_len && ip->item_tags[ty] == IP_TAG_TYPE_POINTER) || ztTypeIsPtrLikeOptional(ip, ty); } // Type.zig:hasWellDefinedLayout // Type.hasWellDefinedLayout. bool ztHasWellDefinedLayout(const InternPool* ip, uint32_t ty) { uint32_t bits; bool is_signed; if (ztIntInfo(ip, ty, &bits, &is_signed) || ztSimpleFloatBits(ty, &bits) || ty == ZIR_REF_BOOL_TYPE || ty == ZIR_REF_VOID_TYPE) return true; if (ty >= ip->items_len) return false; IpArrayInfo array; if (ipArrayInfo(ip, ty, &array) == IP_ARR_OK) return !array.is_vector && ztHasWellDefinedLayout(ip, array.child); switch ((IpTag)ip->item_tags[ty]) { case IP_TAG_TYPE_POINTER: return true; case IP_TAG_TYPE_OPTIONAL: return ztTypeIsPtrLikeOptional(ip, ty); case IP_TAG_TYPE_ENUM_EXPLICIT: case IP_TAG_TYPE_ENUM_NONEXHAUSTIVE: return true; case IP_TAG_TYPE_UNION: return ipUnionIsExtern(ip, ty); default: return ipTypeIsBitpack(ip, ty) || (ipTagIsAnyStruct(ip->item_tags[ty]) && ipStructIsExtern(ip, ty)); } } bool ztTypeIsPtrLikeOptional(const InternPool* ip, uint32_t ty) { if (ty >= ip->items_len || ip->item_tags[ty] != IP_TAG_TYPE_OPTIONAL) return false; uint32_t child = ip->item_datas[ty]; if (child >= ip->items_len || ip->item_tags[child] != IP_TAG_TYPE_POINTER) return false; uint32_t flags = ip->extra[ip->item_datas[child] + 2]; uint32_t ptr_size = IP_PTR_FLAGS_SIZE(flags); bool allowzero = (flags & IP_PTR_FLAGS_IS_ALLOWZERO) != 0; return ptr_size != IP_PTR_SIZE_SLICE && ptr_size != IP_PTR_SIZE_C && !allowzero; } // ── Type names of generic arguments (print_value.zig) ─────────────────────── // std/zig.zig:stringEscape for ASCII; false at a byte >= 0x80, whose UTF-8 // rule zig1 does not port. static bool ztTypeNameWriteEscaped( TypeNameWriter* w, const char* bytes, uint32_t len) { for (uint32_t i = 0; i < len; i++) { uint8_t b = (uint8_t)bytes[i]; char esc[5]; if (b >= 0x80) return false; if (b == '\t' || b == '\n' || b == '\r' || b == '\\' || b == '"') { esc[0] = '\\'; esc[1] = b == '\t' ? 't' : b == '\n' ? 'n' : b == '\r' ? 'r' : (char)b; ztTypeNameWrite(w, esc, 2); } else if (b < 0x20 || b == 0x7f) { snprintf(esc, sizeof esc, "\\x%02x", b); ztTypeNameWrite(w, esc, 4); } else { ztTypeNameWrite(w, (const char*)&b, 1); } } return true; } // print_value.zig:printFieldValue // Element `i` of an aggregate value; a `bytes` element is read, not interned // as ipAggregateElemValue would. static bool ztTypeNamePrintFieldValue(InternPool* ip, uint32_t agg, uint32_t i, uint32_t level, TypeNameWriter* w) { uint32_t d = ip->item_datas[agg]; uint32_t elem; switch ((IpTag)ip->item_tags[agg]) { case IP_TAG_UNDEF: ztTypeNameWriteStr(w, "undefined"); return true; case IP_TAG_BYTES: ztTypeNameWriteU32( w, "", (uint8_t)ipStringBytes(ip, ip->extra[d + 1])[i], ""); return true; case IP_TAG_ONLY_POSSIBLE_VALUE: // indexToKey's `.only_possible_value` of a struct: its defaults. if (!ztIsTuple(ip, d)) return ztTypeNameWriteArgValue( ip, ipStructFieldDefault(ip, d, i), level, w); break; default: break; } return ipAggregateElemValue(ip, agg, i, &elem) == IP_AGG_ELEM_STORED && ztTypeNameWriteArgValue(ip, elem, level, w); } // print_value.zig:printAggregate static bool ztTypeNamePrintAggregate(InternPool* ip, uint32_t val, bool is_ref, uint32_t level, TypeNameWriter* w) { if (level == 0) { ztTypeNameWriteStr(w, is_ref ? "&.{ ... }" : ".{ ... }"); return true; } uint32_t ty = ipTypeOf(ip, val); IpArrayInfo ai; bool is_seq = ipArrayInfo(ip, ty, &ai) == IP_ARR_OK; bool is_tuple = ztIsTuple(ip, ty); if (!is_seq && !is_tuple) { if (!ipTagIsAnyStruct(ip->item_tags[ty])) return false; uint32_t n = ipStructFieldsLen(ip, ty); if (is_ref) ztTypeNameWriteStr(w, "&"); if (n == 0) { ztTypeNameWriteStr(w, ".{}"); return true; } ztTypeNameWriteStr(w, ".{ "); for (uint32_t i = 0; i < n && i < 100; i++) { ztTypeNameWriteStr(w, i != 0 ? ", ." : "."); ztTypeNameWriteStr( w, ipStringBytes(ip, ipStructFieldName(ip, ty, i))); ztTypeNameWriteStr(w, " = "); if (!ztTypeNamePrintFieldValue(ip, val, i, level - 1, w)) return false; } ztTypeNameWriteStr(w, " }"); return true; } uint32_t len = is_seq ? ai.len : ipTupleFieldCount(ip, ty); if (is_seq && !ai.is_vector) { bool sentineled = ai.sentinel != IP_INDEX_NONE; uint32_t len_incl = len + (sentineled ? 1u : 0u); if (ip->item_tags[val] == IP_TAG_BYTES && len_incl != 0) { // `bytes.toSlice(if (bytes.at(len - 1, ip) == 0) len - 1 else // len, ip)` over the length with the sentinel, which the arena // does not hold. const char* bytes = ipStringBytes(ip, ip->extra[ip->item_datas[val] + 1]); uint64_t last = (uint8_t)bytes[len_incl - 1]; if (sentineled && !ipTryReadIntValue(ip, ai.sentinel, &last)) return false; char sentinel_byte = (char)last; ztTypeNameWriteStr(w, "\""); if (!ztTypeNameWriteEscaped( w, bytes, !sentineled && last == 0 ? len - 1 : len)) return false; if (sentineled && last != 0 && !ztTypeNameWriteEscaped(w, &sentinel_byte, 1)) return false; ztTypeNameWriteStr(w, is_ref ? "\"" : "\".*"); return true; } if (len == 0) { ztTypeNameWriteStr(w, is_ref ? "&.{}" : ".{}"); return true; } // `one_byte_str`: a defined `[1]u8` prints as a string. uint32_t elem; uint64_t byte; if (len == 1 && ai.child == ZIR_REF_U8_TYPE && ipAggregateElemValue(ip, val, 0, &elem) == IP_AGG_ELEM_STORED && ipTryReadIntValue(ip, elem, &byte)) { char c = (char)byte; ztTypeNameWriteStr(w, "\""); if (!ztTypeNameWriteEscaped(w, &c, 1)) return false; ztTypeNameWriteStr(w, is_ref ? "\"" : "\".*"); return true; } } if (is_ref) ztTypeNameWriteStr(w, "&"); if (len == 0) { ztTypeNameWriteStr(w, ".{}"); return true; } ztTypeNameWriteStr(w, len == 1 ? ".{" : ".{ "); for (uint32_t i = 0; i < len && i < 100; i++) { if (i != 0) ztTypeNameWriteStr(w, ", "); if (!ztTypeNamePrintFieldValue(ip, val, i, level - 1, w)) return false; } ztTypeNameWriteStr(w, len == 1 ? "}" : len > 100 ? ", ... }" : " }"); return true; } // print_value.zig:printPtr // The `.uav` base whose value is an aggregate; false for the pointer // derivations zig1 does not print. static bool ztTypeNamePrintPtr( InternPool* ip, uint32_t ptr, uint32_t level, TypeNameWriter* w) { if (ipIsUndef(ip, ptr)) { ztTypeNameWriteStr(w, "undefined"); return true; } if (!ipIsPtrUav(ip, ptr)) return false; uint32_t uav = ipPtrUavValue(ip, ptr); switch ((IpTag)ip->item_tags[uav]) { case IP_TAG_AGGREGATE: case IP_TAG_BYTES: case IP_TAG_REPEATED: case IP_TAG_ONLY_POSSIBLE_VALUE: return ztTypeNamePrintAggregate(ip, uav, true, level, w); default: return false; } } // print_value.zig:print // `Value.fmtValueSemaFull(.{ .depth = level })`. False where zig1 cannot // render the value (an unnamed container, a float, a big integer, most // pointers): the type being named then stays unnamed, so only a later // `@typeName` of it stops. bool ztTypeNameWriteArgValue( InternPool* ip, uint32_t val, uint32_t level, TypeNameWriter* w) { if (ipTypeOf(ip, val) == ZIR_REF_TYPE_TYPE) { bool saved_soft = w->soft; bool saved_declined = w->declined; w->soft = true; w->declined = false; ztFormatTypeName(ip, val, w); bool named = !w->declined; w->soft = saved_soft; w->declined = saved_declined; return named; } const char* simple = val == ZIR_REF_VOID_VALUE ? "{}" : val == ZIR_REF_NULL_VALUE ? "null" : val == ZIR_REF_BOOL_TRUE ? "true" : val == ZIR_REF_BOOL_FALSE ? "false" : val == ZIR_REF_UNREACHABLE_VALUE ? "unreachable" : ipIsUndef(ip, val) ? "undefined" : ip->item_tags[val] == IP_TAG_OPT_NULL ? "null" : NULL; if (simple != NULL) { ztTypeNameWriteStr(w, simple); return true; } uint32_t d = ip->item_datas[val]; switch ((IpTag)ip->item_tags[val]) { case IP_TAG_EXTERN: ztTypeNameWriteStr(w, "(extern '"); ztTypeNameWriteStr( w, ipStringBytes(ip, ipGetNav(ip, ip->extra[d + 3])->name)); ztTypeNameWriteStr(w, "')"); return true; case IP_TAG_FUNC_DECL: case IP_TAG_FUNC_INSTANCE: case IP_TAG_FUNC_COERCED: ztTypeNameWriteStr(w, "(function '"); ztTypeNameWriteStr(w, ipStringBytes( ip, ipGetNav(ip, ipFuncValueOwnerNav(ip, val))->name)); ztTypeNameWriteStr(w, "')"); return true; case IP_TAG_ERROR_SET_ERROR: case IP_TAG_ERROR_UNION_ERROR: ztTypeNameWriteStr(w, "error."); ztTypeNameWriteStr(w, ipStringBytes(ip, ip->extra[d + 1])); return true; case IP_TAG_ERROR_UNION_PAYLOAD: case IP_TAG_OPT_PAYLOAD: return ztTypeNameWriteArgValue(ip, ip->extra[d + 1], level, w); case IP_TAG_ENUM_LITERAL: ztTypeNameWriteStr(w, "."); ztTypeNameWriteStr(w, ipStringBytes(ip, ip->item_datas[val])); return true; case IP_TAG_ENUM_TAG: { uint32_t field_idx = ipEnumTagFieldIndex(ip, val); if (field_idx == UINT32_MAX) { ztTypeNameWriteStr(w, "@fromBackingInt("); if (level == 0) { ztTypeNameWriteStr(w, "...)"); return true; } bool ok = ztTypeNameWriteArgValue( ip, ipEnumTagValue(ip, val), level - 1, w); ztTypeNameWriteStr(w, ")"); return ok; } ztTypeNameWriteStr(w, "."); ztTypeNameWriteStr(w, ipStringBytes( ip, ipEnumFieldName(ip, ipEnumTagType(ip, val), field_idx))); return true; } case IP_TAG_PTR_SLICE: if (ipIsUndef(ip, ip->extra[d + 1]) && ip->extra[d + 2] == ZIR_REF_ZERO_USIZE) { ztTypeNameWriteStr(w, "&.{}"); return true; } if (!ztTypeNamePrintPtr(ip, ip->extra[d + 1], level, w)) return false; ztTypeNameWriteStr(w, "[0.."); if (level == 0) ztTypeNameWriteStr(w, "(...)"); else if (!ztTypeNameWriteArgValue(ip, ip->extra[d + 2], level - 1, w)) return false; ztTypeNameWriteStr(w, "]"); return true; case IP_TAG_PTR_UAV: case IP_TAG_PTR_UAV_ALIGNED: return ztTypeNamePrintPtr(ip, val, level, w); case IP_TAG_AGGREGATE: case IP_TAG_BYTES: case IP_TAG_REPEATED: case IP_TAG_ONLY_POSSIBLE_VALUE: return ztTypeNamePrintAggregate(ip, val, false, level, w); case IP_TAG_UNION_VALUE: if (level == 0) { ztTypeNameWriteStr(w, ".{ ... }"); return true; } if (ip->extra[d + 1] == IP_INDEX_NONE) { ztTypeNameWriteStr(w, "@bitCast(@as("); if (!ztTypeNameWriteArgValue( ip, ipTypeOf(ip, ip->extra[d + 2]), level, w)) return false; ztTypeNameWriteStr(w, ", "); if (!ztTypeNameWriteArgValue(ip, ip->extra[d + 2], level - 1, w)) return false; ztTypeNameWriteStr(w, "))"); return true; } ztTypeNameWriteStr(w, ".{ "); if (!ztTypeNameWriteArgValue(ip, ip->extra[d + 1], level - 1, w)) return false; ztTypeNameWriteStr(w, " = "); if (!ztTypeNameWriteArgValue(ip, ip->extra[d + 2], level - 1, w)) return false; ztTypeNameWriteStr(w, " }"); return true; default: break; } uint64_t raw; if (!ipTryReadIntValue(ip, val, &raw)) return false; char buf[32]; if (ipIntValueIsNegative(ip, val, raw)) snprintf(buf, sizeof buf, "%lld", (long long)(int64_t)raw); else snprintf(buf, sizeof buf, "%llu", (unsigned long long)raw); ztTypeNameWriteStr(w, buf); return true; } // ── Pointer, float and integer queries ────────────────────────────────────── // Type.zig:ptrInfo, InternPool.zig:indexToKey // The pointer type `Type.ptrInfo` reads: `".opt_type => |child| switch // (ip.indexToKey(child)) { .ptr_type => |p| ..."` -- an optional pointer or // slice is its child's; anything else is itself. uint32_t ztPtrInfoType(const InternPool* ip, uint32_t ty) { if (!ipItemTagIs(ip, ty, IP_TAG_TYPE_OPTIONAL)) return ty; uint32_t child = ipOptionalChild(ip, ty); return ipItemTagIs(ip, child, IP_TAG_TYPE_POINTER) || ztIsSlice(ip, child) ? child : ty; } // Type.zig:floatSignificandBits, Type.zig:floatBits // Type.ztFloatSignificandBits (src/Type.zig), keyed on the width // `ztSimpleFloatBits` (== Type.floatBits) already reports. uint32_t ztFloatSignificandBits(uint32_t float_bits) { switch (float_bits) { case 16: return 11; case 32: return 24; case 64: return 53; case 80: return 64; default: // 128 return 113; } } // Type.zig:toUnsigned, PerThread.zig:intType // `Type.toUnsigned` (src/Type.zig): an integer type's unsigned peer, // or a vector's element-wise. The five C/`usize` pairs are distinct // preinterned Indexes upstream, so the table is the rule and must come // first: `pt.intType(.unsigned, 64)` is `u64`, not `usize`, and an // `@abs(isize)` answering `u64` would carry the wrong type into every later // coercion and comparison. IP_INDEX_NONE for a type that is neither an int // nor a vector of them (upstream's `unreachable`). uint32_t ztTypeToUnsigned(InternPool* ip, uint32_t ty) { switch (ty) { case ZIR_REF_USIZE_TYPE: case ZIR_REF_ISIZE_TYPE: return ZIR_REF_USIZE_TYPE; case ZIR_REF_C_USHORT_TYPE: case ZIR_REF_C_SHORT_TYPE: return ZIR_REF_C_USHORT_TYPE; case ZIR_REF_C_UINT_TYPE: case ZIR_REF_C_INT_TYPE: return ZIR_REF_C_UINT_TYPE; case ZIR_REF_C_ULONG_TYPE: case ZIR_REF_C_LONG_TYPE: return ZIR_REF_C_ULONG_TYPE; case ZIR_REF_C_ULONGLONG_TYPE: case ZIR_REF_C_LONGLONG_TYPE: return ZIR_REF_C_ULONGLONG_TYPE; default: break; } IpArrayInfo vi; if (ipArrayInfo(ip, ty, &vi) == IP_ARR_OK && vi.is_vector) { uint32_t child = ztTypeToUnsigned(ip, vi.child); if (child == IP_INDEX_NONE) return IP_INDEX_NONE; return ipGetVectorType(ip, vi.len, child); } uint32_t bits; bool is_signed; if (!ztIntInfo(ip, ty, &bits, &is_signed)) return IP_INDEX_NONE; return ipGetIntType(ip, false, bits); } // This file's macros end with it (zig1.c is one translation unit). #undef TYPE_NAME_DECLINE /* ---- end type.c ---- */ /* ---- begin type_resolution.c ---- */ // type_resolution.c — src/Sema/type_resolution.zig: ensureLayoutResolved and // the struct, packed-struct and union layouts it resolves // (resolveStructLayout, resolvePackedStructLayout, resolvePackedUnionLayout, // resolveUnionLayout and its semaUnion* helpers), and // resolveStructDefaults. Field types, // alignments and defaults that a declaration recorded are evaluated here on // the type's first layout demand. Declarations are in zig1.h. #ifndef ZIG1_H #include "zig1.h" #endif #include static void semaResolveStructComptimeDefaults(Zcu* zcu, uint32_t struct_ty); static void semaResolveStructNormalDefaults(Zcu* zcu, uint32_t struct_ty); static void semaQueueStructNormalDefaults(Zcu* zcu, uint32_t struct_ty); static void semaDrainPendingStructDefaults(Zcu* zcu); // Every path which reaches resolveStructLayout participates in the // Zcu.type_resolution.demand_depth nesting counter. Higher-level recursive // layout entrypoints add an outer level too, so a nested struct's successful // layout cannot run normal defaults while an enclosing layout is still // provisional. static void semaBeginTypeLayoutDemand(Zcu* zcu) { if (zcu->type_resolution.demand_depth == UINT32_MAX) UNIMPLEMENTED( "ensureLayoutResolved: type layout demand nesting overflow"); zcu->type_resolution.demand_depth++; } static void semaEndTypeLayoutDemand(Zcu* zcu) { if (zcu->type_resolution.demand_depth == 0) UNREACHABLE("unbalanced type layout demand"); zcu->type_resolution.demand_depth--; if (zcu->type_resolution.demand_depth == 0) semaDrainPendingStructDefaults(zcu); } // InternPool.zig:resolvePackedUnionLayout, Type.zig:unpackable // `Sema/type_resolution.zig` `resolvePackedUnionLayout`, entered the way // `resolveUnionLayout` enters it: the hypothetical tag enum first, then the // field types, then "packed union has no fields", then either the declared // `packed union(T)` backing validated against every field's bit width or the // inferred `u{first field's bitSize}` with every other field checked against // it, and `ip.resolvePackedUnionLayout(union_ty, enum_tag_ty, // backing_int_ty)`. Idempotent. Each field type is refused when opaque or // `Type.unpackable`, as upstream does before sizing it. bool resolvePackedUnionLayout(Zcu* zcu, uint32_t union_ty) { InternPool* ip = &zcu->intern_pool; if (ipUnionBackingInt(ip, union_ty) != IP_INDEX_NONE) return true; uint32_t enum_tag_ty = ipEnsureGeneratedUnionTag(ip, union_ty); semaResolveAllUnionDeferred(zcu, union_ty); uint32_t n = ipUnionFieldsLen(ip, union_ty); if (n == 0) COMPILE_ERROR_UPSTREAM("packed union has no fields"); uint64_t want_bits = 0; uint32_t explicit_ty = ipPendingPackedUnionBacking(ip, union_ty); if (explicit_ty != IP_INDEX_NONE) { uint32_t eb; bool es; if (!ztIntInfo(ip, explicit_ty, &eb, &es)) COMPILE_ERROR_UPSTREAM("expected backing integer type"); want_bits = eb; } for (uint32_t i = 0; i < n; i++) { uint32_t fty = ipUnionFieldType(ip, union_ty, i); if (ztIsZigTypeTag(ip, fty, ZIG_TYPE_TAG_OPAQUE)) COMPILE_ERROR_UPSTREAM( "cannot directly embed opaque type in union"); if (fty != IP_INDEX_NONE && ztTypeUnpackable(ip, fty)) COMPILE_ERROR_UPSTREAM( "packed unions cannot contain fields of type"); uint64_t fsz, fal, fbits; if (fty == IP_INDEX_NONE || !ztLayoutInfo(zcu, ip, fty, &fsz, &fal, &fbits)) return false; if (explicit_ty == IP_INDEX_NONE && i == 0) want_bits = fbits; else if (fbits != want_bits) COMPILE_ERROR_UPSTREAM(explicit_ty != IP_INDEX_NONE ? "field bit width does not match backing integer" : "field bit width does not match earlier field"); } uint32_t backing = explicit_ty; if (backing == IP_INDEX_NONE) { if (want_bits > 65535u) COMPILE_ERROR_UPSTREAM("packed union bit width exceeds maximum " "bit width of 65535"); backing = ipGetIntType(ip, false, (uint32_t)want_bits); } ipResolvePackedUnionLayout(ip, union_ty, enum_tag_ty, backing); return true; } // Port of Sema/type_resolution.zig `resolveStructLayout` (auto, non-packed // branch) for the field-reorder + offset math. Reads each field's ABI // alignment/size via ztLayoutInfo; sorts a runtime-order permutation by // descending alignment (stable for equal aligns, matching upstream's // insertion-sort behavior on the small arrays we handle); assigns offsets; // rounds the size up to the struct alignment. Returns false if any field type // has no computable layout. When `order_out`/`offsets_out` are non-NULL they // receive the runtime order (field indices) and per-field byte offsets // (indexed by declaration order) — used by resolveStructLayout to // backpatch the header. Body of semaComputeStructLayout over caller-provided // scratch (field_align, field_size, order, offsets — each n elements). Split // out so the allocating wrapper below can free the scratch on every return // path. static bool semaComputeStructLayoutBufs(Zcu* zcu, InternPool* ip, uint32_t struct_idx, uint64_t* out_size, uint64_t* out_align, uint32_t* order_out, uint32_t* offsets_out, uint32_t n, uint64_t* field_align, uint64_t* field_size, uint32_t* order, uint32_t* offsets) { *out_size = 0; *out_align = 1; // Zero state (offsets of comptime-omitted fields are read back as 0 by // the copy-out below). for (uint32_t i = 0; i < n; i++) { field_align[i] = 0; field_size[i] = 0; offsets[i] = 0; } for (uint32_t i = 0; i < n; i++) order[i] = UINT32_MAX - 1u; // RuntimeOrder.omitted uint32_t runtime_n = 0; for (uint32_t i = 0; i < n; i++) { // Every alignment body was folded when the field was resolved, so // this is upstream's layout loop reading the resolved array // (`"const a = struct_obj.field_aligns.get(ip)[field_idx];"`). uint32_t explicit_align = ipStructFieldAlignment(ip, struct_idx, i); if (explicit_align != IP_PTR_ALIGN_NONE && explicit_align >= 32u) return false; // not representable by the wasm32 address model if (ipStructFieldIsComptime(ip, struct_idx, i)) continue; // omitted: no size, alignment, offset, or runtime slot uint32_t fty = ipStructFieldType(ip, struct_idx, i); uint64_t fsz, fal, fbits; if (!ztLayoutInfo(zcu, ip, fty, &fsz, &fal, &fbits)) return false; if (explicit_align != IP_PTR_ALIGN_NONE) fal = UINT64_C(1) << explicit_align; if (fal == 0 || (fal & (fal - 1u)) != 0) return false; field_align[i] = fal; field_size[i] = fsz; order[runtime_n++] = i; } // Runtime order: extern layout retains declaration order. Auto layout // stable-sorts by descending alignment (insertion sort keeps equal- // alignment fields in declaration order, matching upstream's small-array // sort). if (!ipStructIsExtern(ip, struct_idx)) { for (uint32_t i = 1; i < runtime_n; i++) { uint32_t key = order[i]; uint32_t j = i; while (j > 0 && field_align[order[j - 1]] < field_align[key]) { order[j] = order[j - 1]; j--; } order[j] = key; } } uint64_t struct_align = 1; for (uint32_t k = 0; k < runtime_n; k++) { uint32_t fi = order[k]; if (field_align[fi] > struct_align) struct_align = field_align[fi]; } uint64_t cur = 0; for (uint32_t k = 0; k < runtime_n; k++) { uint32_t fi = order[k]; uint64_t al = field_align[fi]; uint64_t off; if (!ztLayoutCheckedAlign(cur, al, &off) || off > UINT32_MAX || !ztLayoutCheckedAdd(off, field_size[fi], &cur)) return false; offsets[fi] = (uint32_t)off; } uint64_t size; if (!ztLayoutCheckedAlign(cur, struct_align, &size) || size > UINT32_MAX || struct_align > (UINT64_C(1) << 31)) return false; *out_size = size; *out_align = struct_align; if (order_out != NULL) for (uint32_t i = 0; i < n; i++) order_out[i] = order[i]; if (offsets_out != NULL) for (uint32_t i = 0; i < n; i++) offsets_out[i] = offsets[i]; return true; } bool semaComputeStructLayout(Zcu* zcu, InternPool* ip, uint32_t struct_idx, uint64_t* out_size, uint64_t* out_align, uint32_t* order_out, uint32_t* offsets_out) { uint32_t n = ipStructFieldsLen(ip, struct_idx); // Heap scratch sized by the field count (std.Io.VTable has 108 fields). // Transient: freed before // both returns; an UNIMPLEMENTED inside the helper aborts the process. size_t cap = (n != 0) ? n : 1; uint64_t* scratch64 = zig1Malloc(cap * 2u * sizeof(uint64_t)); uint32_t* scratch32 = zig1Malloc(cap * 2u * sizeof(uint32_t)); bool ok = semaComputeStructLayoutBufs(zcu, ip, struct_idx, out_size, out_align, order_out, offsets_out, n, /*field_align=*/scratch64, /*field_size=*/scratch64 + cap, /*order=*/scratch32, /*offsets=*/scratch32 + cap); free(scratch64); free(scratch32); if (!ok) return false; // Type.abiSize is zero for NPV/OPV/fully-comptime aggregates even when // resolving their fields found a nontrivial alignment or provisional // offsets. Partially-comptime structs retain their runtime field bytes. ZtTypeClass type_class = ztTypeClass(ip, struct_idx); if (type_class == ZT_CLASS_UNKNOWN) return false; if (type_class == ZT_CLASS_NPV || type_class == ZT_CLASS_OPV || type_class == ZT_CLASS_FULL) *out_size = 0; return true; } // InternPool.zig:resolveStructLayout // Resolve and persist a declared struct's layout into its IP header (size, // alignment, per-field offsets, runtime order). Mirrors // InternPool.resolveStructLayout backpatching after Sema computes the layout. // Idempotent: a no-op once resolved. Returns false if the layout is not // computable (unsupported field type). static bool semaResolveStructLayoutInner( Zcu* zcu, InternPool* ip, uint32_t struct_idx) { // Sema.resolveStructLayout branches a packed struct away from this whole // body -- `"return resolvePackedStructLayout(sema, &block, struct_ty, // &struct_obj);"` -- and the writer it dispatches to patches only // `backing_int_type`; the size/alignment/class/field_offset/ // field_runtime_order writes below are `type_struct`-only // (`"assert(item.tag == .type_struct);"` in // InternPool.resolveStructLayout). zig1 computes a packed struct's backing // int at mint, so upstream's packed half is already done and this is a // no-op for it. if (ipStructIsPacked(ip, struct_idx)) { // Packed layout itself is finalized by semaBodyStructTypePacked. Its // comptime-default phase still belongs to type_layout; the wrapper // schedules normal defaults as a separate unit. semaResolveStructComptimeDefaults(zcu, struct_idx); return true; } if (ipStructLayoutResolved(ip, struct_idx)) { // A prior layout can predate default-body registration (notably the // packed/root setup paths), so make the in-layout comptime phase // idempotently complete before the wrapper queues normal work. semaResolveStructComptimeDefaults(zcu, struct_idx); return true; } // Upstream resolves comptime field defaults inside type_layout, before it // computes field classes/offsets. Normal defaults are a separate paired // unit and run only after the layout write succeeds below. semaResolveStructComptimeDefaults(zcu, struct_idx); uint32_t n = ipStructFieldsLen(ip, struct_idx); uint64_t size, align; // Heap order/offsets sized by the field count. Freed before every // return below. size_t cap = (n != 0) ? n : 1; uint32_t* order = zig1Malloc(cap * 2u * sizeof(uint32_t)); uint32_t* offsets = order + cap; bool ok = semaComputeStructLayout( zcu, ip, struct_idx, &size, &align, order, offsets); if (ok) { ZtTypeClass type_class = ztTypeClass(ip, struct_idx); if (type_class == ZT_CLASS_UNKNOWN || size > UINT32_MAX || align == 0 || align > (UINT64_C(1) << 31) || (align & (align - 1u)) != 0) { ok = false; } else { ipSetStructSize(ip, struct_idx, (uint32_t)size); ipSetStructAlignment(ip, struct_idx, ztLog2PowerOfTwoU64(align)); ipSetStructClass(ip, struct_idx, (uint32_t)type_class); for (uint32_t i = 0; i < n; i++) { ipSetStructFieldOffset(ip, struct_idx, i, offsets[i]); // field_runtime_order[k] = field index at runtime position k; // auto layout only, as upstream `resolveStructLayout`. if (!ipStructIsExtern(ip, struct_idx)) ipSetStructFieldRuntimeOrder(ip, struct_idx, i, order[i]); } } } free(order); return ok; } // (part of) PerThread.zig:ensureTypeLayoutUpToDate // A struct's layout demand: `"ip.setWantTypeLayout"`, whose first time queues // the struct_defaults unit, then resolveStructLayout's field population. static void semaStructLayoutWanted(Zcu* zcu, uint32_t struct_ty) { ipSetWantTypeLayout(&zcu->intern_pool, struct_ty); if (zcu->update_queue != NULL) compilationUpdateQueueStructLayoutWanted(zcu->update_queue, struct_ty); semaEnsureStructFieldsResolved(zcu, struct_ty); } // Upstream's first type-layout demand schedules a distinct struct_defaults // analysis unit. This wrapper is the common scheduler boundary for direct // @sizeOf/@offsetOf, field-pointer, ztLayoutInfo, and public layout calls. // Normal defaults are queued only after this struct's layout succeeds and are // drained only when the complete active layout stack reaches depth zero. bool resolveStructLayout(Zcu* zcu, InternPool* ip, uint32_t struct_idx) { // A resolved layout was wanted and populated already. if (ipStructIsPacked(ip, struct_idx) || !ipStructLayoutResolved(ip, struct_idx)) { semaStructLayoutWanted(zcu, struct_idx); ipResolveFieldNames(ip, struct_idx); } semaBeginTypeLayoutDemand(zcu); bool ok = semaResolveStructLayoutInner(zcu, ip, struct_idx); if (ok) semaQueueStructNormalDefaults(zcu, struct_idx); semaEndTypeLayoutDemand(zcu); return ok; } static bool resolveUnionLayout( Zcu* zcu, uint32_t ty, uint32_t* visiting, uint32_t depth); // Recursive half of Sema.ensureLayoutResolved. Containers persist their own // metadata after every nested type has been visited; wrappers and function // signatures recurse so an aligned aggregate cannot remain lazily unresolved // merely because the outer type itself has no ABI bytes. static bool ensureLayoutResolvedInner( Zcu* zcu, uint32_t ty, uint32_t* visiting, uint32_t depth) { InternPool* ip = &zcu->intern_pool; if (ty == IP_INDEX_GENERIC_POISON_TYPE) return true; if (ty >= ip->items_len || depth >= ZT_CLASSIFY_MAX_DEPTH) return false; for (uint32_t i = 0; i < depth; i++) if (visiting[i] == ty) return false; visiting[depth] = ty; switch ((IpTag)ip->item_tags[ty]) { case IP_TAG_TYPE_FUNCTION: { uint32_t n = ipFuncTypeParamsLen(ip, ty); for (uint32_t i = 0; i < n; i++) if (!ensureLayoutResolvedInner( zcu, ipFuncTypeParamType(ip, ty, i), visiting, depth + 1)) return false; return ensureLayoutResolvedInner( zcu, ipFuncTypeReturnType(ip, ty), visiting, depth + 1); } case IP_TAG_TYPE_ARRAY_SMALL: { uint32_t off = ip->item_datas[ty]; return ensureLayoutResolvedInner( zcu, ip->extra[off + 1], visiting, depth + 1); } case IP_TAG_TYPE_ARRAY_BIG: { uint32_t off = ip->item_datas[ty]; return ensureLayoutResolvedInner( zcu, ip->extra[off + 2], visiting, depth + 1); } case IP_TAG_TYPE_VECTOR: { uint32_t off = ip->item_datas[ty]; return ensureLayoutResolvedInner( zcu, ip->extra[off + 1], visiting, depth + 1); } case IP_TAG_TYPE_OPTIONAL: return ensureLayoutResolvedInner( zcu, ip->item_datas[ty], visiting, depth + 1); case IP_TAG_TYPE_ERROR_UNION: case IP_TAG_TYPE_ANYERROR_UNION: return ensureLayoutResolvedInner( zcu, ipErrorUnionPayload(ip, ty), visiting, depth + 1); case IP_TAG_TYPE_TUPLE: { uint32_t off = ip->item_datas[ty]; uint32_t n = ip->extra[off]; for (uint32_t i = 0; i < n; i++) if (!ensureLayoutResolvedInner( zcu, ip->extra[off + 1 + i], visiting, depth + 1)) return false; return true; } case IP_TAG_TYPE_STRUCT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT: case IP_TAG_TYPE_STRUCT_PACKED_AUTO_DEFAULTS: case IP_TAG_TYPE_STRUCT_PACKED_EXPLICIT_DEFAULTS: { // ensureTypeLayoutUpToDate returns for an up-to-date unit. if (!ipStructIsPacked(ip, ty) && ipStructLayoutResolved(ip, ty)) return true; semaStructLayoutWanted(zcu, ty); uint32_t n = ipStructFieldsLen(ip, ty); for (uint32_t i = 0; i < n; i++) { uint32_t field_ty = ipStructFieldType(ip, ty, i); if (field_ty == IP_INDEX_NONE || !ensureLayoutResolvedInner( zcu, field_ty, visiting, depth + 1)) return false; } if (ipStructIsPacked(ip, ty)) { if (!ensureLayoutResolvedInner( zcu, ipStructBackingInt(ip, ty), visiting, depth + 1)) return false; return resolveStructLayout(zcu, ip, ty); } return resolveStructLayout(zcu, ip, ty); } case IP_TAG_TYPE_UNION_PACKED_AUTO: case IP_TAG_TYPE_UNION_PACKED_EXPLICIT: ipSetWantTypeLayout(ip, ty); return resolvePackedUnionLayout(zcu, ty); case IP_TAG_TYPE_UNION: ipSetWantTypeLayout(ip, ty); return resolveUnionLayout(zcu, ty, visiting, depth); case IP_TAG_TYPE_ENUM_AUTO: case IP_TAG_TYPE_ENUM_EXPLICIT: case IP_TAG_TYPE_ENUM_NONEXHAUSTIVE: { ipSetWantTypeLayout(ip, ty); ipResolveFieldNames(ip, ty); uint32_t int_ty = ipEnumTypeIntTagType(ip, ty); return int_ty != IP_INDEX_NONE && ensureLayoutResolvedInner(zcu, int_ty, visiting, depth + 1); } default: // Primitive scalars, pointers/slices, error sets, and anyframe have // no nested layout dependency. Unknown value tags fail below. return ipIndexIsType(ip, ty); } } bool ensureLayoutResolved(Zcu* zcu, uint32_t ty) { InternPool* ip = &zcu->intern_pool; uint32_t visiting[ZT_CLASSIFY_MAX_DEPTH]; // This outer scheduler level covers the recursive walk before its final // resolveStructLayout call. Without it, a nested Child layout could // return to depth zero and run Child's defaults while its enclosing Outer // layout was still provisional. semaBeginTypeLayoutDemand(zcu); bool ok = ensureLayoutResolvedInner(zcu, ty, visiting, 0); if (ok && ip->item_tags[ty] != IP_TAG_TYPE_FUNCTION) { // Type.assertHasLayout accepts function types after recursively // checking every parameter and the return type. Their values are // fully comptime and Type.abiSize(.func_type) is zero, so the general // comptime-only class rejection must not fire for a function Nav. ZtTypeClass type_class = ztTypeClass(ip, ty); uint64_t size, alignment, bits; ok = type_class != ZT_CLASS_UNKNOWN && type_class != ZT_CLASS_NPV && ztLayoutInfo(zcu, ip, ty, &size, &alignment, &bits) && size <= UINT32_MAX && alignment != 0 && alignment <= (UINT64_C(1) << 31) && (alignment & (alignment - 1u)) == 0; } semaEndTypeLayoutDemand(zcu); return ok; } // Sema/type_resolution.zig:resolveUnionLayout // The tag values, the tag enum, the field types and the field aligns // (semaResolveAllUnionDeferred), then each field's layout and the tag enum's; // ztLayoutInfo computes the size and alignment on demand. static bool resolveUnionLayout( Zcu* zcu, uint32_t ty, uint32_t* visiting, uint32_t depth) { InternPool* ip = &zcu->intern_pool; semaResolveAllUnionDeferred(zcu, ty); uint32_t n = ipUnionFieldsLen(ip, ty); for (uint32_t i = 0; i < n; i++) { uint32_t field_ty = ipUnionFieldType(ip, ty, i); if (field_ty == IP_INDEX_NONE || !ensureLayoutResolvedInner(zcu, field_ty, visiting, depth + 1)) return false; } uint32_t enum_ty = ipUnionEnumTagType(ip, ty); if (enum_ty == IP_INDEX_NONE) enum_ty = ipEnsureGeneratedUnionTag(ip, ty); if (enum_ty == IP_INDEX_NONE || !ensureLayoutResolvedInner(zcu, enum_ty, visiting, depth + 1)) return false; uint64_t size, alignment, bits; return ztLayoutInfo(zcu, ip, ty, &size, &alignment, &bits); } // Interpreter-only side table mapping a resolved struct type + field index to // the location of that field's default-value ZIR body (not its evaluated // value). Registration interns nothing. The paired post-layout defaults unit // later folds each body and writes the result to the serialized IP slot; // struct_init consumers read that cache rather than evaluating independently. typedef struct StructFieldDefault { uint32_t struct_ty; uint32_t field_idx; uint32_t ns; // the struct's namespace scope for the default's lookups const Zir* zir; uint32_t body_off; // offset into zir->extra of the default body uint32_t body_len; // The struct_decl instruction index: AstGen gives every field default the // result type `decl_inst.toRef()` (AstGen.zig structDeclInner ~4968), and // Sema maps that instruction to the current FIELD's type while evaluating // its default body (type_resolution.zig resolveStructDefaultsInner: // `sema.inst_map.putAssumeCapacity(zir_index, field_ty)`). The fold seeds // values[struct_inst] = field type to mirror that. uint32_t struct_inst; } StructFieldDefault; // Upstream analyzes each declared struct as two paired units: type_layout, // then struct_defaults. Comptime fields are the exception: their defaults are // resolved inside type_layout because they participate in the type itself. // This single-threaded lifecycle is the same state machine without the // incremental job queue. It prevents re-entering a pending default body and // records when the post-layout normal-field pass has completed. typedef enum { SFD_UNRESOLVED, SFD_RESOLVING_COMPTIME, SFD_COMPTIME_RESOLVED, SFD_PENDING_NORMAL, SFD_RESOLVING_NORMAL, SFD_RESOLVED, } StructDefaultsState; typedef struct StructDefaultsResolution { uint32_t struct_ty; StructDefaultsState state; } StructDefaultsResolution; static StructDefaultsResolution* semaLookupStructDefaults( Zcu* zcu, uint32_t struct_ty) { for (uint32_t i = 0; i < zcu->type_resolution.struct_defaults_len; i++) if (zcu->type_resolution.struct_defaults[i].struct_ty == struct_ty) return &zcu->type_resolution.struct_defaults[i]; return NULL; } static StructDefaultsResolution* semaRegisterStructDefaults( Zcu* zcu, uint32_t struct_ty) { StructDefaultsResolution* prior = semaLookupStructDefaults(zcu, struct_ty); if (prior != NULL) return prior; if (zcu->type_resolution.struct_defaults_len == zcu->type_resolution.struct_defaults_cap) { uint32_t nc = zig1GrowCap(zcu->type_resolution.struct_defaults_cap, 16u); StructDefaultsResolution* nb = zig1Realloc(zcu->type_resolution.struct_defaults, nc * sizeof(StructDefaultsResolution)); zcu->type_resolution.struct_defaults = nb; zcu->type_resolution.struct_defaults_cap = nc; } StructDefaultsResolution* out = &zcu->type_resolution .struct_defaults[zcu->type_resolution.struct_defaults_len++]; *out = (StructDefaultsResolution) { struct_ty, SFD_UNRESOLVED }; return out; } void semaRegisterFieldDefault(Zcu* zcu, uint32_t struct_ty, uint32_t field_idx, uint32_t ns, const Zir* zir, uint32_t body_off, uint32_t body_len, uint32_t struct_inst) { if (zcu->type_resolution.field_defaults_len == zcu->type_resolution.field_defaults_cap) { uint32_t nc = zig1GrowCap(zcu->type_resolution.field_defaults_cap, 16u); StructFieldDefault* nb = zig1Realloc(zcu->type_resolution.field_defaults, nc * sizeof(StructFieldDefault)); zcu->type_resolution.field_defaults = nb; zcu->type_resolution.field_defaults_cap = nc; } uint32_t index = zcu->type_resolution.field_defaults_len++; zcu->type_resolution.field_defaults[index] = (StructFieldDefault) { struct_ty, field_idx, ns, zir, body_off, body_len, struct_inst }; // The actual default body is the durable registration signal for the // omitted/placeholder field_default group. All three producers (inline, // file-root, and packed) funnel through this function. semaRegisterStructDefaults(zcu, struct_ty)->state = SFD_UNRESOLVED; } // Fold one field's explicit `align(...)` body and store the resolved // Alignment. Upstream evaluates the body in the same resolveStructLayout // iteration that resolves the field's type // (`"struct_obj.field_aligns.get(ip)[zir_field.idx] = field_align;"`), so // both of zig1's field-resolution sites call this rather than deferring to // the first alignment query. A field with no body keeps the `.none` written // by ipSetStructFieldAlignsNone, which is upstream's `break :a .none`. static uint32_t semaFoldFieldAlignment(Zcu* zcu, uint32_t container_ty, uint32_t ns, const Zir* zir, uint32_t body_off, uint32_t body_len, uint32_t decl_inst) { InternPool* ip = &zcu->intern_pool; uint32_t value = ctFoldComptimeBodyValue(zcu, zir, &zir->extra[body_off], body_len, ns, semaContainerTypeNameCtx(ip, container_ty), decl_inst, container_ty); uint64_t bytes; if (!ipTryReadIntValue(ip, value, &bytes) || bytes == 0) UNIMPLEMENTED("resolveStructLayout: field alignment is not a " "representable power of two"); return analyzeAsAlign(ip, bytes, "resolveStructLayout: field alignment is not a representable power of " "two"); } void semaResolveStructFieldAlignment(Zcu* zcu, uint32_t struct_ty, uint32_t field_idx, uint32_t ns, const Zir* zir, uint32_t body_off, uint32_t body_len, uint32_t struct_inst) { ipSetStructFieldAlignment(&zcu->intern_pool, struct_ty, field_idx, semaFoldFieldAlignment( zcu, struct_ty, ns, zir, body_off, body_len, struct_inst)); } // Look up field `field_idx` of `struct_ty`'s default body location; NULL if // none was registered (the field has no default). Returns the most recent // registration (a re-resolved struct type reuses the same body). static const StructFieldDefault* semaLookupFieldDefault( const Zcu* zcu, uint32_t struct_ty, uint32_t field_idx) { for (uint32_t i = zcu->type_resolution.field_defaults_len; i > 0; i--) { const StructFieldDefault* fd = &zcu->type_resolution.field_defaults[i - 1]; if (fd->struct_ty == struct_ty && fd->field_idx == field_idx) return fd; } return NULL; } // Evaluate exactly one registered body: the single owner of the field-typed // seed, comptime fold, and coercion, while the lifecycle wrappers below // decide when it runs and cache the result. static uint32_t semaFoldFieldDefault(Zcu* zcu, const StructFieldDefault* fd) { // Fold the recorded default body in its declaring namespace, then coerce // to the field type (an int -> the field int type, a fn value -> the fn/ // fn-pointer field type, `null`/a value -> the optional, `&.{}` -> the // empty slice). The struct_decl instruction is seeded with this FIELD's // type first — AstGen gives every default the result type // `decl_inst.toRef()` (AstGen.zig structDeclInner ~4968) and // Sema.resolveStructDefaultsInner maps it per field (type_resolution.zig // ~684). IP_INDEX_NONE = no default registered for this field. InternPool* ip = &zcu->intern_pool; uint32_t field_ty = ipStructFieldType(ip, fd->struct_ty, fd->field_idx); uint32_t def_raw = ctFoldComptimeBodyValue(zcu, fd->zir, &fd->zir->extra[fd->body_off], fd->body_len, fd->ns, semaContainerTypeNameCtx(ip, fd->struct_ty), fd->struct_inst, field_ty); return semaCoerceComptime(zcu, ip, def_raw, field_ty); } // Mark the paired normal-default unit ready only after this struct's layout // has persisted successfully. The queue contains each struct at most once; // stale entries are harmless because the drain rechecks lifecycle state. static void semaQueueStructNormalDefaults(Zcu* zcu, uint32_t struct_ty) { StructDefaultsResolution* resolution = semaLookupStructDefaults(zcu, struct_ty); if (resolution == NULL) return; if (resolution->state == SFD_PENDING_NORMAL || resolution->state == SFD_RESOLVING_NORMAL || resolution->state == SFD_RESOLVED) return; if (resolution->state != SFD_COMPTIME_RESOLVED) UNREACHABLE("struct defaults queued before comptime layout phase"); const InternPool* ip = &zcu->intern_pool; if (!ipStructIsPacked(ip, struct_ty) && !ipStructLayoutResolved(ip, struct_ty)) UNREACHABLE("struct defaults queued before successful layout"); // Compilation.update's queue runs the unit. if (zcu->update_queue == NULL) u32ListAppend( &zcu->type_resolution.pending_struct_defaults, struct_ty); resolution = semaLookupStructDefaults(zcu, struct_ty); if (resolution == NULL) UNREACHABLE("struct default lifecycle vanished while queueing"); resolution->state = SFD_PENDING_NORMAL; } // This is the single-threaded equivalent of PerThread's `.struct_defaults` // work queue. It must never run while any type-layout entrypoint is active. // A default may demand another already-laid-out pending struct; that specific // unit is resolved synchronously by semaFieldDefaultValue, and its later queue // entry is skipped here as already resolved. static void semaDrainPendingStructDefaults(Zcu* zcu) { if (zcu->type_resolution.demand_depth != 0 || zcu->type_resolution.struct_defaults_draining) return; zcu->type_resolution.struct_defaults_draining = true; while (zcu->type_resolution.pending_struct_defaults_head < zcu->type_resolution.pending_struct_defaults.len) { uint32_t struct_ty = zcu->type_resolution.pending_struct_defaults .items[zcu->type_resolution.pending_struct_defaults_head++]; const StructDefaultsResolution* resolution = semaLookupStructDefaults(zcu, struct_ty); if (resolution != NULL && resolution->state == SFD_PENDING_NORMAL) semaResolveStructNormalDefaults(zcu, struct_ty); } zcu->type_resolution.pending_struct_defaults_head = 0; zcu->type_resolution.pending_struct_defaults.len = 0; zcu->type_resolution.struct_defaults_draining = false; } static void resolveStructDefaults( Zcu* zcu, uint32_t struct_ty, bool comptime_fields) { StructDefaultsResolution* resolution = semaLookupStructDefaults(zcu, struct_ty); if (resolution == NULL) return; if (comptime_fields) { if (resolution->state >= SFD_COMPTIME_RESOLVED) return; if (resolution->state == SFD_RESOLVING_COMPTIME) UNIMPLEMENTED("resolveStructDefaults: struct comptime field " "default dependency loop"); resolution->state = SFD_RESOLVING_COMPTIME; } else { if (resolution->state == SFD_RESOLVED) return; if (resolution->state == SFD_RESOLVING_COMPTIME || resolution->state == SFD_RESOLVING_NORMAL) UNIMPLEMENTED( "resolveStructDefaults: struct field default dependency loop"); if (resolution->state != SFD_PENDING_NORMAL) UNIMPLEMENTED("resolveStructDefaults: normal struct defaults " "before successful layout"); resolution->state = SFD_RESOLVING_NORMAL; } InternPool* ip = &zcu->intern_pool; uint32_t fields_len = ipStructFieldsLen(ip, struct_ty); for (uint32_t field_idx = 0; field_idx < fields_len; field_idx++) { if (ipStructFieldIsComptime(ip, struct_ty, field_idx) != comptime_fields) continue; const StructFieldDefault* fd = semaLookupFieldDefault(zcu, struct_ty, field_idx); uint32_t value = IP_INDEX_NONE; if (fd != NULL) { // Folding may register another struct default and realloc the // metadata table. Keep this declaration's small descriptor by // value so no borrowed pointer spans the fold. StructFieldDefault fd_copy = *fd; value = semaFoldFieldDefault(zcu, &fd_copy); } if (fd != NULL && value == IP_INDEX_NONE) UNIMPLEMENTED( "resolveStructDefaults: struct field default coercion failed"); ipSetStructFieldDefault(ip, struct_ty, field_idx, value); } resolution = semaLookupStructDefaults(zcu, struct_ty); if (resolution == NULL) UNREACHABLE("struct default lifecycle vanished during resolution"); resolution->state = comptime_fields ? SFD_COMPTIME_RESOLVED : SFD_RESOLVED; } static void semaResolveStructComptimeDefaults(Zcu* zcu, uint32_t struct_ty) { resolveStructDefaults(zcu, struct_ty, true); } static void semaResolveStructNormalDefaults(Zcu* zcu, uint32_t struct_ty) { resolveStructDefaults(zcu, struct_ty, false); } void ensureStructDefaultsResolved(Zcu* zcu, uint32_t struct_ty) { if (zcu->update_queue != NULL) compilationUpdateQueueStructDefaultsEnter( zcu->update_queue, struct_ty); if (!resolveStructLayout(zcu, &zcu->intern_pool, struct_ty)) UNIMPLEMENTED("ensureStructDefaultsResolved: struct defaults - layout " "resolution failed"); const StructDefaultsResolution* resolution = semaLookupStructDefaults(zcu, struct_ty); if (resolution != NULL && resolution->state != SFD_RESOLVED) semaResolveStructNormalDefaults(zcu, struct_ty); } uint32_t semaFieldDefaultValue( Zcu* zcu, uint32_t struct_ty, uint32_t field_idx) { const InternPool* ip = &zcu->intern_pool; uint32_t cached = ipStructFieldDefault(ip, struct_ty, field_idx); if (cached != IP_INDEX_NONE) return cached; // The default bodies are registered with the struct's fields. semaEnsureStructFieldsResolved(zcu, struct_ty); if (semaLookupFieldDefault(zcu, struct_ty, field_idx) == NULL) return IP_INDEX_NONE; const StructDefaultsResolution* resolution = semaLookupStructDefaults(zcu, struct_ty); if (resolution == NULL) UNREACHABLE("registered field default lacks struct lifecycle"); if (resolution->state == SFD_RESOLVING_COMPTIME || resolution->state == SFD_RESOLVING_NORMAL) UNIMPLEMENTED( "resolveStructDefaults: struct field default dependency loop"); // Upstream requires layout first, then schedules the paired // `.struct_defaults` unit. Every consumer funnels through this accessor, // so no read evaluates a default on its own. if (!ensureLayoutResolved(zcu, struct_ty)) UNIMPLEMENTED("resolveStructDefaults: struct field default layout " "resolution failed"); cached = ipStructFieldDefault(ip, struct_ty, field_idx); if (cached == IP_INDEX_NONE) { // An explicit default consumer is ensureStructDefaultsResolved: // upstream analyzes it even during an unrelated type_layout unit. ensureStructDefaultsResolved(zcu, struct_ty); cached = ipStructFieldDefault(ip, struct_ty, field_idx); } if (cached == IP_INDEX_NONE) UNIMPLEMENTED("resolveStructDefaults: registered struct field default " "stayed unresolved"); return cached; } // Deferred union field types. semaBodyUnionType records every non-primitive // field type without resolving it: upstream creates the generated hypothetical // enum during union layout before analyzing payload field types, so even a // simple composer such as `[]const u8` must not append pointer IP items at // declaration time. A real field/layout demand first creates the enum, then // resolves all registered fields in declaration order. This also preserves // std.builtin.Type's laziness: merely scanning its namespace does not // recursively materialize the many decl-referenced payload types. typedef struct UnionFieldDeferred { uint32_t union_ty; uint32_t field_idx; uint32_t ns; // scope for the field-type body's decl_val lookups const Zir* zir; // the union's declaring ZIR uint32_t body_off; // offset into zir->extra of the full field-type body uint32_t body_len; } UnionFieldDeferred; void semaRegisterUnionFieldDeferred(Zcu* zcu, uint32_t union_ty, uint32_t field_idx, uint32_t ns, const Zir* zir, uint32_t body_off, uint32_t body_len) { if (zcu->type_resolution.union_deferred_len == zcu->type_resolution.union_deferred_cap) { uint32_t nc = zig1GrowCap(zcu->type_resolution.union_deferred_cap, 16u); UnionFieldDeferred* nb = zig1Realloc(zcu->type_resolution.union_deferred, nc * sizeof(UnionFieldDeferred)); zcu->type_resolution.union_deferred = nb; zcu->type_resolution.union_deferred_cap = nc; } zcu->type_resolution .union_deferred[zcu->type_resolution.union_deferred_len++] = (UnionFieldDeferred) { union_ty, field_idx, ns, zir, body_off, body_len }; } void semaRegisterUnionLayoutDeferred(Zcu* zcu, UnionLayoutDeferred rec) { if (zcu->type_resolution.union_layout_len == zcu->type_resolution.union_layout_cap) { uint32_t nc = zig1GrowCap(zcu->type_resolution.union_layout_cap, 16u); UnionLayoutDeferred* nb = zig1Realloc(zcu->type_resolution.union_layout, nc * sizeof(UnionLayoutDeferred)); zcu->type_resolution.union_layout = nb; zcu->type_resolution.union_layout_cap = nc; } zcu->type_resolution.union_layout[zcu->type_resolution.union_layout_len++] = rec; } // `union_ty`'s record, or Zcu.type_resolution.union_layout_len. static uint32_t semaUnionLayoutDeferredIndex( const Zcu* zcu, uint32_t union_ty) { for (uint32_t i = zcu->type_resolution.union_layout_len; i-- > 0;) if (zcu->type_resolution.union_layout[i].union_ty == union_ty) return i; return zcu->type_resolution.union_layout_len; } void semaDeinitTypeResolution(ZcuTypeResolution* tr) { free(tr->field_defaults); free(tr->struct_defaults); free(tr->pending_struct_defaults.items); free(tr->union_deferred); for (uint32_t i = 0; i < tr->union_layout_len; i++) free(tr->union_layout[i].bodies); free(tr->union_layout); for (uint32_t i = 0; i < tr->pending_enum_layouts_len; i++) free(tr->pending_enum_layouts[i].caps); free(tr->pending_enum_layouts); free(tr->struct_fields); free(tr->struct_fields_slot); } // Resolve union payload types on first use, preserving upstream's ordering: // generated enum first, then every deferred field in declaration order. // Returns field_idx's resulting type, or IP_INDEX_NONE when no registration // exists for an unresolved field. uint32_t semaResolveUnionFieldTypeLazy( Zcu* zcu, uint32_t union_ty, uint32_t field_idx) { const InternPool* ip = &zcu->intern_pool; semaResolveAllUnionDeferred(zcu, union_ty); return ipUnionFieldType(ip, union_ty, field_idx); } // The scope a deferred field body evaluates in. static uint32_t semaUnionDeferredNs(Zcu* zcu, const UnionFieldDeferred* d) { return d->ns != IP_NAMESPACE_NONE ? d->ns : zcuNamespaceOfZir(zcu, d->zir); } // (part of) PerThread.zig:intValue, arith.zig:incrementDefinedInt // resolveEnumLayout's generated-tag arm: `"const uncoerced = try // sema.resolveInlineBody(&block, value_body, zir_index); const coerced = try // sema.coerce(&block, int_tag_ty, uncoerced, field_val_src);"`; an omitted // value is `"pt.intValue(int_tag_ty, 0)"` for the first field, else // `"arith.incrementDefinedInt(sema, int_tag_ty, prev_field_val)"`, every // explicit value first. The values are interned before the enum item rather // than after it. static void semaUnionResolveTagValues(Zcu* zcu, UnionLayoutDeferred rec) { InternPool* ip = &zcu->intern_pool; uint32_t int_ty = rec.tag_int_ty != IP_INDEX_NONE ? rec.tag_int_ty : ipGetIntType(ip, rec.tag_int_signed, rec.tag_int_bits); const uint32_t fields_len = rec.fields_len; uint32_t* values = zig1Calloc(fields_len ? fields_len : 1u, sizeof(uint32_t)); for (uint32_t i = 0; i < fields_len; i++) { const uint32_t* body = &rec.bodies[i * UNION_LAYOUT_BODY_WORDS + 2]; values[i] = body[1] == 0 ? IP_INDEX_NONE : semaCoerceComptime(zcu, ip, ctFoldComptimeBodyValue(zcu, rec.zir, &rec.zir->extra[body[0]], body[1], rec.ns, semaContainerTypeNameCtx(ip, rec.union_ty), rec.decl_inst, int_ty), int_ty); } semaEnumImplicitValues(zcu, int_ty, values, fields_len); ipSetPendingUnionTagValues(ip, rec.union_ty, values); } // resolveUnionLayout: `"const align_body = zir_field.align_body orelse break // :a .none; ... sema.analyzeAsAlign(...)"` into `union_obj.field_aligns`, // after the field types rather than interleaved with them. static void semaUnionResolveFieldAligns(Zcu* zcu, UnionLayoutDeferred rec) { for (uint32_t i = 0; i < rec.fields_len; i++) { const uint32_t* body = &rec.bodies[i * UNION_LAYOUT_BODY_WORDS]; ipSetUnionFieldAlignment(&zcu->intern_pool, rec.union_ty, i, body[1] == 0 ? IP_PTR_ALIGN_NONE : semaFoldFieldAlignment(zcu, rec.union_ty, rec.ns, rec.zir, body[0], body[1], rec.decl_inst)); } } static void semaUnionLayoutEnter( Zcu* zcu, uint32_t li, UnionLayoutState state) { if (li != zcu->type_resolution.union_layout_len) zcu->type_resolution.union_layout[li].state = state; } // resolveUnionLayout's order: the generated tag's values, the enum, all of // the union's deferred `.none` field types, then the field aligns. The // registration list is populated in declaration order. Only fields for this // union are touched, so unrelated union namespaces remain lazy. Evaluating a // field body can register another union's fields and so realloc // Zcu.type_resolution.union_deferred: each registration is read by copy. A // field type that re-enters its own union only resolves the types. void semaResolveAllUnionDeferred(Zcu* zcu, uint32_t union_ty) { InternPool* ip = &zcu->intern_pool; uint32_t li = semaUnionLayoutDeferredIndex(zcu, union_ty); UnionLayoutState state = li == zcu->type_resolution.union_layout_len ? UNION_LAYOUT_DONE : zcu->type_resolution.union_layout[li].state; if (state == UNION_LAYOUT_VALUES || state == UNION_LAYOUT_ALIGNS) COMPILE_ERROR_UPSTREAM("union layout: dependency loop"); if (state == UNION_LAYOUT_PENDING && zcu->type_resolution.union_layout[li].any_values) { semaUnionLayoutEnter(zcu, li, UNION_LAYOUT_VALUES); semaUnionResolveTagValues(zcu, zcu->type_resolution.union_layout[li]); } semaUnionLayoutEnter(zcu, li, UNION_LAYOUT_TYPES); ipEnsureGeneratedUnionTag(ip, union_ty); for (uint32_t i = 0; i < zcu->type_resolution.union_deferred_len; i++) { const UnionFieldDeferred d = zcu->type_resolution.union_deferred[i]; if (d.union_ty != union_ty) continue; if (ipUnionFieldType(ip, union_ty, d.field_idx) == IP_INDEX_NONE) { uint32_t ty = semaResolveFieldTypeBody(zcu, d.zir, &d.zir->extra[d.body_off], d.body_len, semaUnionDeferredNs(zcu, &d), semaContainerTypeNameCtx(ip, union_ty)); ipSetUnionFieldType(ip, union_ty, d.field_idx, ty); } } if (state != UNION_LAYOUT_PENDING) { semaUnionLayoutEnter(zcu, li, state); return; } if (zcu->type_resolution.union_layout[li].any_aligns) { semaUnionLayoutEnter(zcu, li, UNION_LAYOUT_ALIGNS); semaUnionResolveFieldAligns( zcu, zcu->type_resolution.union_layout[li]); } semaUnionLayoutEnter(zcu, li, UNION_LAYOUT_DONE); } // Sema/type_resolution.zig resolvePackedStructLayout, once every field type // is known: the inferred backing integer is the sum of the field bit widths; // an explicit one (`explicit_backing_ty`, else IP_INDEX_NONE) must be an // integer type (stock: "expected backing integer type") whose bit width // equals that sum (stock: "backing integer bit width does not match total // bit width of fields"), used verbatim (`usize` stays usize, and a signed // one is accepted: upstream checks only the `.int` tag). Per-field bit // offsets are not stored (no upstream group holds them); // zcuStructPackedFieldBitOffset recomputes the running sum on demand. void resolvePackedStructLayout( Zcu* zcu, uint32_t struct_ty, uint32_t explicit_backing_ty) { InternPool* ip = &zcu->intern_pool; uint32_t fields_len = ipStructFieldsLen(ip, struct_ty); uint64_t bit_sum = 0; for (uint32_t i = 0; i < fields_len; i++) { uint64_t fsz, fal, fbits; if (!ztLayoutInfo(zcu, ip, ipStructFieldType(ip, struct_ty, i), &fsz, &fal, &fbits)) UNIMPLEMENTED("resolvePackedStructLayout: packed struct - field " "type has no bit size"); bit_sum += fbits; } if (bit_sum > 0xFFFF) COMPILE_ERROR_UPSTREAM( "packed struct bit width exceeds maximum bit width of 65535"); uint32_t backing_int_ty; if (explicit_backing_ty != IP_INDEX_NONE) { uint32_t bbits; bool bsigned; if (!ztIntInfo(ip, explicit_backing_ty, &bbits, &bsigned)) COMPILE_ERROR_UPSTREAM("expected backing integer type"); if ((uint64_t)bbits != bit_sum) UNIMPLEMENTED( "resolvePackedStructLayout: packed struct - backing integer " "bit width does not match total bit width of fields"); backing_int_ty = explicit_backing_ty; } else { backing_int_ty = ipGetIntType(ip, false, (uint32_t)bit_sum); } ipSetStructPackedBacking(ip, struct_ty, backing_int_ty); } // (part of) Sema.zig:zirEnumFromInt, PerThread.zig:getCoerced // `Sema.zirEnumFromInt` for a comptime-known operand, both frames: a // non-exhaustive enum asks `"if (int_val.intFitsInType(int_tag_ty, null, // zcu)) ... return sema.fail(block, src, "int value '{f}' out of range of // non-exhaustive enum '{f}'", ...)"`, an exhaustive one `"if (!(try // sema.enumHasInt(dest_ty, int_val))) return sema.fail(block, src, "enum // '{f}' has no tag with value '{f}'", ...)"`, then `pt.getCoerced(int_val, // dest_ty)`. The fit is asked before the coercion in both cases, because // the coercion re-interns at the tag width and an out-of-range magnitude // wraps there (`@enumFromInt(20)` into `enum(u4)` became 4). // Type.zig:backingIntType // `.enum_type => int_tag_type, .struct_type / .union_type => // packed_backing_int_type`. uint32_t semaBackingIntType(InternPool* ip, uint32_t ty) { if (ipIsEnumType(ip, ty)) return ipEnumTypeIntTagType(ip, ty); if (ty < ip->items_len && ipTypeIsBitpack(ip, ty)) return ipBitpackBackingInt(ip, ty); COMPILE_ERROR_UPSTREAM("expected enum, packed union or packed struct"); ZIG1_TCC_RETURN(IP_INDEX_NONE); } // A declared struct whose fields wait for its resolveStructLayout: the // struct_decl that minted it (ZIR_MAIN_STRUCT_INST for a file's root struct). // Zcu.type_resolution.struct_fields_slot maps the struct to its entry + 1. typedef struct StructFieldsPending { uint32_t struct_ty; const Zir* zir; uint32_t struct_inst; } StructFieldsPending; void semaDeferStructFields( Zcu* zcu, uint32_t struct_ty, const Zir* zir, uint32_t struct_inst) { ZcuTypeResolution* tr = &zcu->type_resolution; if (ipStructFieldsLen(&zcu->intern_pool, struct_ty) == 0) return; if (tr->struct_fields_len == tr->struct_fields_cap) { tr->struct_fields_cap = zig1GrowCap(tr->struct_fields_cap, 16u); tr->struct_fields = zig1Realloc(tr->struct_fields, tr->struct_fields_cap * sizeof(StructFieldsPending)); } if (struct_ty >= tr->struct_fields_slot_cap) { uint32_t cap = zig1GrowCap(tr->struct_fields_slot_cap, 1024u); while (cap <= struct_ty) cap *= 2u; tr->struct_fields_slot = zig1Realloc(tr->struct_fields_slot, cap * sizeof(uint32_t)); memset(&tr->struct_fields_slot[tr->struct_fields_slot_cap], 0, (cap - tr->struct_fields_slot_cap) * sizeof(uint32_t)); tr->struct_fields_slot_cap = cap; } tr->struct_fields[tr->struct_fields_len++] = (StructFieldsPending) { struct_ty, zir, struct_inst }; tr->struct_fields_slot[struct_ty] = tr->struct_fields_len; } // (part of) Sema/type_resolution.zig:resolveStructLayout // "Declared structs do not yet have field information populated": per // field, its name, comptime bit, type body and align body, in its namespace. void semaEnsureStructFieldsResolved(Zcu* zcu, uint32_t struct_ty) { InternPool* ip = &zcu->intern_pool; ZcuTypeResolution* tr = &zcu->type_resolution; if (struct_ty >= tr->struct_fields_slot_cap || tr->struct_fields_slot[struct_ty] == 0) return; uint32_t p = tr->struct_fields_slot[struct_ty] - 1u; const StructFieldsPending pending = tr->struct_fields[p]; tr->struct_fields_slot[struct_ty] = 0; tr->struct_fields[p] = tr->struct_fields[--tr->struct_fields_len]; if (p != tr->struct_fields_len) tr->struct_fields_slot[tr->struct_fields[p].struct_ty] = p + 1u; const Zir* zir = pending.zir; ZirStructDecl zd = zirGetStructDecl(zir, pending.struct_inst); uint32_t ns = ipStructNamespaceOrNone(ip, struct_ty); ipSetStructFieldAlignsNone(ip, struct_ty); ZirStructFieldIterator it = { 0, zd.field_bodies }; ZirStructField f; while (zirStructFieldIteratorNext(zir, &zd, &it, &f)) { const char* s = (const char*)&zir->string_bytes[f.name]; ipSetStructFieldName(ip, struct_ty, f.idx, ipGetOrPutString(ip, s, (uint32_t)strlen(s))); if (f.is_comptime) ipSetStructFieldIsComptime(ip, struct_ty, f.idx, true); // Default bodies are folded by resolveStructDefaults; record where. if (f.default_body_len != 0) semaRegisterFieldDefault(zcu, struct_ty, f.idx, ns, zir, f.default_body, f.default_body_len, pending.struct_inst); ipSetStructFieldType(ip, struct_ty, f.idx, semaResolveFieldTypeBody(zcu, zir, &zir->extra[f.type_body], f.type_body_len, ns, semaContainerTypeNameCtx(ip, struct_ty))); if (f.align_body_len != 0) semaResolveStructFieldAlignment(zcu, struct_ty, f.idx, ns, zir, f.align_body, f.align_body_len, pending.struct_inst); } } /* ---- end type_resolution.c ---- */ /* ---- begin value.c ---- */ // value.c — src/Value.zig: the comptime value queries Sema asks of an // interned value (toBigInt, toFloat, floatCast, order and compareHetero, // ptrField and ptrElem, writeToMemory and readFromPackedMemory, abs, mulAdd, // numberMin/Max). A Value method is val and its name (valAbs is Value.abs). // Declarations are in zig1.h. #ifndef ZIG1_H #include "zig1.h" #endif // ── Float conversion and comptime mutability ──────────────────────────────── // Value.zig:floatCast, PerThread.zig:undefValue // `Value.floatCast` (src/Value.zig): `"if (val.isUndef(zcu)) return // pt.undefValue(dest_ty); return ... .storage = switch // (dest_ty.floatBits(target)) { 16 => .{ .f16 = val.toFloat(f16, zcu) }, ..."` // -- every source widens exactly to f128 first, so one rounding step // remains. IP_INDEX_NONE where `ipGetFloatFromF128Bits` has no storage for // the destination. uint32_t valFloatCast(InternPool* ip, uint32_t val, uint32_t dest_ty) { if (ipIsUndef(ip, val)) return ipGetUndef(ip, dest_ty); // `toFloat`'s `.int` arm is `@floatFromInt` straight to the destination // width (`@floatCast(1234)` in `zirFloatCast`): one rounding. if (val < ip->items_len && ipTagIsIntValue((IpTag)ip->item_tags[val])) return semaCoerceIntToFloat(ip, val, dest_ty); uint32_t words[4]; if (!ipReadFloatAsF128Bits(ip, val, words)) return IP_INDEX_NONE; // Whether `@floatCast` quiets a signalling NaN is the conversion the // pinned x86_64 compiler emits for it: every narrowing sets the qNaN bit // (compiler-rt truncXfYf2, cvtsd2ss, x87 stores) except f128 -> f80, // whose `__trunctfxf2` (lib/compiler_rt/trunctfxf2.zig) only sets the // explicit integer bit; the hardware widenings quiet too -- f16/f32 -> // f64 (cvtss2sd) and f32/f64 -> f80 (x87 loads). compiler-rt's // extendXfYf2 keeps it: f16 -> f32 and everything -> f128. // A comptime_float source is held as f128. uint32_t src_bits = 128u, dst_bits; uint32_t src_ty = ipTypeOf(ip, val); if (((words[3] >> 16) & 0x7FFFu) == 0x7FFFu && (words[0] | words[1] | words[2] | (words[3] & 0xFFFFu)) != 0 && ztSimpleFloatBits(dest_ty, &dst_bits) && (src_ty == ZIR_REF_COMPTIME_FLOAT_TYPE || ztSimpleFloatBits(src_ty, &src_bits)) && ((dst_bits < src_bits && !(dst_bits == 80u && src_bits == 128u)) || (dst_bits == 64u && src_bits < 64u) || (dst_bits == 80u && (src_bits == 32u || src_bits == 64u)))) words[3] |= 0x8000u; return ipGetFloatFromF128Bits(ip, dest_ty, words); } // Value.zig:canMutateComptimeVarState // Value.canMutateComptimeVarState (src/Value.zig). bool valCanMutateComptimeVarState(InternPool* ip, uint32_t v) { if (v >= ip->items_len) return false; IpPtrKey k; if (ipPtrKey(ip, v, &k)) { switch (k.tag) { case IP_TAG_PTR_NAV: // "The value of a Nav can never reference a case IP_TAG_PTR_INT: // comptime alloc." return false; case IP_TAG_PTR_COMPTIME_ALLOC: case IP_TAG_PTR_COMPTIME_FIELD: return true; default: // eu/opt payload, uav's value, arr_elem / field base return valCanMutateComptimeVarState(ip, k.a); } } uint32_t d = ip->item_datas[v]; switch ((IpTag)ip->item_tags[v]) { case IP_TAG_ERROR_UNION_PAYLOAD: // `.payload`; `.err_name` is false case IP_TAG_OPT_PAYLOAD: // `.none` is `opt_null`: false return valCanMutateComptimeVarState(ip, ip->extra[d + 1]); case IP_TAG_PTR_SLICE: return valCanMutateComptimeVarState(ip, ipPtrSlicePtr(ip, v)); case IP_TAG_UNION_VALUE: return valCanMutateComptimeVarState(ip, ip->extra[d + 2]); case IP_TAG_AGGREGATE: case IP_TAG_REPEATED: { uint32_t ty = ip->extra[d]; IpArrayInfo ai; uint32_t len = ipArrayInfo(ip, ty, &ai) == IP_ARR_OK ? ai.len : ip->item_tags[ty] == IP_TAG_TYPE_TUPLE ? ip->extra[ip->item_datas[ty]] : ipStructFieldsLen(ip, ty); for (uint32_t i = 0; i < len; i++) { uint32_t elem = IP_INDEX_NONE; if (ipAggregateElemValue(ip, v, i, &elem) == IP_AGG_ELEM_STORED && valCanMutateComptimeVarState(ip, elem)) return true; } return false; } default: // `bytes` and every scalar return false; } } // ── Comparison (order, compareHetero, isNan) and toBigInt ─────────────────── // Value.zig:isNan, InternPool.zig:indexToKey, Value.zig:toFloat // `Value.isNan` for an interned float value, across every float // representation: `"pub fn isNan(val: Value, zcu: *const Zcu) bool { const ip // = &zcu.intern_pool; return switch (ip.indexToKey(val.toIntern())) { .float // => |float| switch (float.storage) { inline else => |x| std.math.isNan(x) }, // else => false, }; }"` -- asked of the one exact widening to f128 // (`Value.toFloat(f128)`), which keeps every width's NaN a NaN. bool valIsNan(const InternPool* ip, uint32_t idx) { uint32_t w[4]; return ipReadFloatAsF128Bits(ip, idx, w) && ((w[3] >> 16) & 0x7FFFu) == 0x7FFFu && (w[0] | w[1] | w[2] | (w[3] & 0xFFFFu)) != 0; } // (part of) Value.zig:order, Value.zig:compareHetero, Sema.zig:cmpNumeric // One operand of a comptime numeric comparison whose other side is a float, // widened to the single carrier the comparison happens in. That is exactly // Value.order's float side -- `"if (lhs.isFloat(zcu) or rhs.isFloat(zcu)) { // const lhs_f128 = lhs.toFloat(f128, zcu); const rhs_f128 = rhs.toFloat(f128, // zcu); return std.math.order(lhs_f128, rhs_f128); }"` (src/Value.zig, // reached from Value.compareHetero, which Sema.cmpNumeric calls once both // operands are comptime-known): Both operands, float or int, widen to one // carrier and are ordered there. Every float width widens to f128 exactly // (`ipReadFloatAsF128Bits`); an int is carried as read and widened by // `valCmpNumApply`. CtNumOperand valCmpFloatOperand( const InternPool* ip, uint32_t idx, const char* what) { CtNumOperand out = { .is_float = true, .f = { 0, 0 }, .n = 0, .neg = false }; if (semaTagIsFloat(ip->item_tags[idx])) { uint32_t w[4]; if (!ipReadFloatAsF128Bits(ip, idx, w)) UNIMPLEMENTED("Value.compareHetero: float operand unreadable"); out.f = ipFloat128FromWords(w); return out; } if (!ipTryReadIntValue(ip, idx, &out.n)) { // Past the word: `lhs.toFloat(f128, zcu)` itself -- // `big_int.toFloat(f128, .nearest_even)` at any width. uint64_t space[2]; BigIntConst v; if (!valToBigInt(ip, idx, space, &v)) UNIMPLEMENTED(what); uint64_t lo, hi; bool exact; bigIntToFloatBits(v, 128, &lo, &hi, &exact); out.f = (CrtU128) { lo, hi }; return out; } out.is_float = false; out.neg = ipIntValueIsNegative(ip, idx, out.n); return out; } // |n| for the (sign-extended word, is-negative) form both int readers answer, // written so INT64_MIN needs no special case. static uint64_t valCmpIntMagnitude(uint64_t n, bool neg) { return neg ? ~n + 1u : n; } // (part of) Value.zig:compareHetero // The same comparison over a pair each side of which carries its own sign // (the `(sign-extended word, is-negative)` form semaCmpReadKnownInt reads): // with mixed signs the negative side is strictly smaller whatever the // magnitudes // -- an unsigned compare of the raw bits would invert it -- and within one // sign two's-complement order is unsigned order. Value.compareHetero's // ordering, written down once for the scalar fold, the vector lane fold and // the switch range set. bool valCmpApplySigned( ZirInstTag op, uint64_t a, bool a_neg, uint64_t b, bool b_neg) { if (a_neg != b_neg) return valCmpApplyU(op, a_neg ? 0u : 1u, a_neg ? 1u : 0u); return valCmpApplyU(op, a, b); } // (part of) Value.zig:toFloat // `std.math.order(lhs, rhs).compare(op)` over the pair. Two ints order in // integer arithmetic; otherwise both sides are f128 -- an int widened by // `@floatFromInt` (`Value.toFloat(f128)`, exact for 64 bits) -- and // `std.math.order`'s `a == b` / `a < b` on the host f128 are compiler_rt's // comparison. Every caller answers a NaN operand first // (`valIsNan`, `"return op == .neq"`), as compareHetero does. bool valCmpNumApply(ZirInstTag op, CtNumOperand a, CtNumOperand b) { if (!a.is_float && !b.is_float) return valCmpApplySigned(op, a.n, a.neg, b.n, b.neg); const CrtU128 fa = a.is_float ? a.f : crtFloatFromInt( 128, (CrtU128) { valCmpIntMagnitude(a.n, a.neg), 0 }, a.neg); const CrtU128 fb = b.is_float ? b.f : crtFloatFromInt( 128, (CrtU128) { valCmpIntMagnitude(b.n, b.neg), 0 }, b.neg); return valCmpApplyOrder(op, crtCmp(128, false, fa, fb)); } // Value.zig:toBigInt // `Value.toBigInt` (src/Value.zig) and the `InternPool.Key.Int.Storage // .toBigInt` it ends at: ANY interned integer value as a `BigIntConst`, // whatever carrier the pool chose for it. `space` is the caller's two-limb // scratch, which upstream spells as `var space: Value.BigIntSpace = undefined` // -- the limb-backed carriers hand back the pool's own limbs and never touch // it. Returns false for a value that is not a readable integer at all. bool valToBigInt( const InternPool* ip, uint32_t idx, uint64_t* space, BigIntConst* out) { if (idx >= ip->items_len) return false; const uint64_t* bl; uint32_t bn; bool bpos; if (ipIntBigLimbs(ip, idx, NULL, &bl, &bn, &bpos)) { out->limbs = bl; out->limbs_len = bn; out->positive = bpos; return true; } uint64_t n; if (!ipTryReadIntValue(ip, idx, &n)) return false; bool neg = ipIntValueIsNegative(ip, idx, n); space[0] = neg ? (uint64_t)0 - n : n; space[1] = 0; out->limbs = space; out->limbs_len = 1; out->positive = !neg; return true; } // ── Pointers (canonicalizeBasePtr, ptrElem, ptrField) ─────────────────────── // The `base_addr` and element index doPointersOverlap compares for a // comptime-only element type: an `arr_elem` into `elem_ty` is its base's // address at that index (false for an offset base: "this pointer is // invalid, just let the access fail"). static bool valOverlapBase(const InternPool* ip, const IpPtrKey* key, uint32_t elem_ty, IpPtrKey* out, uint64_t* out_idx) { *out = *key; *out_idx = 0; if (key->tag != IP_TAG_PTR_ELEM) return true; IpPtrInfo base_info; if (ipPtrInfo(ip, ipTypeOf(ip, key->a), &base_info) != IP_PTRI_OK || base_info.child != elem_ty) return true; IpPtrKey base; if (!ipPtrKey(ip, key->a, &base) || base.off != 0) return false; *out = base; *out_idx = key->b; return true; } // Value.zig:doPointersOverlap // Whether `elem_count` elements at `a` and at `b` overlap; `b` decodes its // `arr_elem` from `a`'s base address, as upstream's `"switch // (a_ptr.base_addr)"` does. bool valDoPointersOverlap( Zcu* zcu, uint32_t a, uint32_t b, uint64_t elem_count) { const InternPool* ip = &zcu->intern_pool; uint32_t a_elem_ty = ztIndexableElem(ip, ipTypeOf(ip, a)); IpPtrKey a_ptr, b_ptr; if (!ipPtrKey(ip, a, &a_ptr) || !ipPtrKey(ip, b, &b_ptr)) UNREACHABLE("doPointersOverlap: not a pointer value"); if (ztComptimeOnly(zcu, a_elem_ty)) { IpPtrKey a_base, b_base; uint64_t a_idx, b_idx; if (!valOverlapBase(ip, &a_ptr, a_elem_ty, &a_base, &a_idx)) return false; IpPtrKey b_key = a_ptr.tag == IP_TAG_PTR_ELEM ? a_ptr : b_ptr; if (!valOverlapBase(ip, &b_key, a_elem_ty, &b_base, &b_idx)) return false; if (a_ptr.tag != IP_TAG_PTR_ELEM) b_base = b_ptr; if (a_base.tag != b_base.tag || a_base.a != b_base.a || a_base.b != b_base.b || a_base.off != b_base.off) return false; uint64_t diff = a_idx >= b_idx ? a_idx - b_idx : b_idx - a_idx; return diff < elem_count; } if (a_ptr.tag != b_ptr.tag || a_ptr.a != b_ptr.a || a_ptr.b != b_ptr.b) return false; uint64_t bytes_diff = a_ptr.off >= b_ptr.off ? a_ptr.off - b_ptr.off : b_ptr.off - a_ptr.off; return bytes_diff < elem_count * ztAbiSize(zcu, a_elem_ty); } // Value.zig:canonicalizeBasePtr // Value.canonicalizeBasePtr: `base` retyped to the mutable, unaligned, // sentinel-free `size` pointer to `want_child` in its own address space. uint32_t valCanonicalizeBasePtr( InternPool* ip, uint32_t base, uint32_t size, uint32_t want_child) { IpPtrInfo info; if (ipPtrInfo(ip, ipTypeOf(ip, base), &info) != IP_PTRI_OK) UNREACHABLE("canonicalizeBasePtr: base is not a pointer"); uint32_t addr_mask = IP_PTR_FLAGS_ADDRSPACE_MASK << IP_PTR_FLAGS_ADDRSPACE_SHIFT; uint32_t flags = ((IP_PTR_FLAGS_MUT_ONE | size) & ~addr_mask) | (info.flags & addr_mask); uint32_t canonical = ipRetypePtrValue(ip, base, ipGetPtrType(ip, want_child, flags)); if (canonical == IP_INDEX_NONE) UNIMPLEMENTED("canonicalizeBasePtr: cannot retype base"); return canonical; } // Value.zig:ptrElem // Value.ptrElem for a comptime-known pointer. Runtime-representable elements // advance the carrier's byte offset. Comptime-only elements use arr_elem in // flattened array-base units, folding an existing arr_elem tail when its base // already indexes the same array-base type. Index zero is always a pure // reinterpretation; upstream never interns arr_elem{index = 0}. uint32_t valPtrElem(Zcu* zcu, InternPool* ip, uint32_t parent_ptr, uint32_t result_ptr_ty, uint32_t elem_ty, uint32_t index) { uint32_t parent_ty = ipTypeOf(ip, parent_ptr); if (parent_ty >= ip->items_len || ip->item_tags[parent_ty] != IP_TAG_TYPE_POINTER) UNREACHABLE("valPtrElem: parent is not a pointer"); if (ipIsUndef(ip, parent_ptr)) return ipGetUndef(ip, result_ptr_ty); ZtComptimeOnlyStatus elem_ct = ztComptimeOnlyStatus(zcu, elem_ty); if (elem_ct == ZT_COMPTIME_ONLY_UNKNOWN) UNIMPLEMENTED("ptrElem: unresolved element class"); if (elem_ct == ZT_COMPTIME_ONLY_NO) { uint64_t elem_size, elem_align, elem_bits; if (!ztLayoutInfo( zcu, ip, elem_ty, &elem_size, &elem_align, &elem_bits)) UNIMPLEMENTED("ptrElem: unresolved element layout"); if (elem_size != 0 && index > UINT64_MAX / elem_size) COMPILE_ERROR_UPSTREAM("valPtrElem: byte offset overflow"); uint64_t old_offset; if (!ipPtrValueByteOffset(ip, parent_ptr, &old_offset) || UINT64_MAX - old_offset < (uint64_t)index * elem_size) COMPILE_ERROR_UPSTREAM("valPtrElem: pointer offset overflow"); uint32_t result = ipRemintPtrValue(ip, parent_ptr, result_ptr_ty, old_offset + (uint64_t)index * elem_size); if (result == IP_INDEX_NONE) UNIMPLEMENTED("ptrElem: unsupported runtime carrier"); return result; } if (index == 0) { uint32_t result = ipRetypePtrValue(ip, parent_ptr, result_ptr_ty); if (result == IP_INDEX_NONE) UNIMPLEMENTED("ptrElem: unsupported zero-index carrier"); return result; } uint32_t arr_base_ty; uint64_t arr_base_len; if (!ztArrayBase(ip, elem_ty, &arr_base_ty, &arr_base_len) || arr_base_len > UINT32_MAX || (arr_base_len != 0 && index > UINT32_MAX / arr_base_len)) COMPILE_ERROR_UPSTREAM("valPtrElem: array-base index overflow"); uint32_t base_idx = (uint32_t)(arr_base_len * index); if (ipIsPtrElem(ip, parent_ptr)) { uint32_t prior_base = ipPtrElemBase(ip, parent_ptr); uint32_t prior_base_ty = ipTypeOf(ip, prior_base); if (prior_base_ty < ip->items_len && ip->item_tags[prior_base_ty] == IP_TAG_TYPE_POINTER && ip->extra[ip->item_datas[prior_base_ty]] == arr_base_ty) { uint32_t prior_idx = ipPtrElemIndex(ip, parent_ptr); if (prior_idx > UINT32_MAX - base_idx) COMPILE_ERROR_UPSTREAM("valPtrElem: folded index overflow"); return ipGetPtrElemOffset(ip, result_ptr_ty, prior_base, prior_idx + base_idx, ipPtrElemByteOffset(ip, parent_ptr)); } } return ipGetPtrElem(ip, result_ptr_ty, valCanonicalizeBasePtr(ip, parent_ptr, IP_PTR_SIZE_MANY, arr_base_ty), base_idx); } // Value.zig:ptrField // Value.ptrField (src/Value.zig) for a comptime-known pointer: a packed // container's or an extern union's field pointer is the parent retyped, an // extern struct's the parent advanced by the field offset, and an `.auto` // aggregate's the `.field` pointer over the canonical base. uint32_t valPtrField(Zcu* zcu, uint32_t parent_ptr, uint32_t field_idx) { InternPool* ip = &zcu->intern_pool; uint32_t parent_ptr_ty = ipTypeOf(ip, parent_ptr); uint32_t aggregate_ty = ip->extra[ip->item_datas[parent_ptr_ty]]; uint32_t field_ptr_ty = ztFieldPtrType(zcu, parent_ptr_ty, field_idx); uint32_t result = IP_INDEX_NONE; uint64_t offset; if (ipTypeIsBitpack(ip, aggregate_ty) || (ip->item_tags[aggregate_ty] == IP_TAG_TYPE_UNION && ipUnionIsExtern(ip, aggregate_ty))) result = ipRetypePtrValue(ip, parent_ptr, field_ptr_ty); else if (ipTagIsAnyStruct(ip->item_tags[aggregate_ty]) && ipStructIsExtern(ip, aggregate_ty)) { if (ipPtrValueByteOffset(ip, parent_ptr, &offset)) result = ipRemintPtrValue(ip, parent_ptr, field_ptr_ty, offset + ipStructFieldOffset(ip, aggregate_ty, field_idx)); } else if (ipIsUndef(ip, parent_ptr)) return ipGetUndef(ip, field_ptr_ty); else return ipGetPtrField(ip, field_ptr_ty, valCanonicalizeBasePtr( ip, parent_ptr, IP_PTR_SIZE_ONE, aggregate_ty), field_idx); if (result == IP_INDEX_NONE) UNIMPLEMENTED("ptrField: unsupported pointer carrier"); return result; } // (part of) Value.zig:ptrField // structFieldPtrByIndex on a comptime pointer to a struct- or tuple-typed // const Nav: a comptime field's `.comptime_field` pointer, else // Value.ptrField. IP_INDEX_NONE for any other object pointer. uint32_t valConstNavStructFieldPtr(Zcu* zcu, uint32_t optr, const char* field, uint32_t field_ip, uint32_t* out_ty) { InternPool* ip = &zcu->intern_pool; IpPtrInfo info; if (optr >= ip->items_len || ip->item_tags[optr] != IP_TAG_PTR_NAV || semaIsRuntimeMutableDataNavPtr(ip, optr) || ipPtrInfo(ip, ipTypeOf(ip, optr), &info) != IP_PTRI_OK || info.size != IP_PTR_SIZE_ONE || (!ipTagIsAnyStruct(ip->item_tags[info.child]) && ip->item_tags[info.child] != IP_TAG_TYPE_TUPLE)) return IP_INDEX_NONE; return semaStructFieldPtrComptime(zcu, optr, field, field_ip, out_ty); } // ── Integer and float operands (toBigInt, toFloat) ────────────────────────── // (part of) Value.zig:toBigInt // `semaBinReadComptimeInt`'s operand classification as `Value.toBigInt` rather // than a u64 working word: an interned integer value read at whatever width // and sign its carrier has, plus its own comptime type for the peer // resolution. `semaBinReadComptimeInt`'s second arm -- a tagless word read as // a `comptime_int` magnitude with bit 63 for a sign, for a value carried out // of a block break-elide -- is not restated here: nothing reaches it, and it // can only guess at a word whose type nobody recorded (`(1 << 63)` would read // as negative). An unreadable operand declines to the caller's loud abort. bool valBinReadComptimeBigInt(const InternPool* ip, uint32_t val, uint64_t* space, BigIntConst* out, uint32_t* out_ty) { if (semaInternedIntValueType(ip, val) == IP_INDEX_NONE || !valToBigInt(ip, val, space, out)) return false; *out_ty = ipIntValueType(ip, val); return true; } // (part of) Value.zig:toBigInt // The same fold over two interned operands: `Value.toBigInt` on each // (`valToBigInt`), then the rule above. IP_INDEX_NONE when an operand is // not a readable integer, so the caller keeps its own loud abort. uint32_t valComptimeIntBinOp( Zcu* zcu, ZirInstTag tag, uint32_t lhs, uint32_t rhs) { const InternPool* ip = &zcu->intern_pool; uint64_t lhs_space[2], rhs_space[2]; BigIntConst a, b; if (!valToBigInt(ip, lhs, lhs_space, &a) || !valToBigInt(ip, rhs, rhs_space, &b)) return IP_INDEX_NONE; return semaComptimeIntBinOpBig(zcu, tag, a, ipIntValueType(ip, lhs), lhs, b, ipIntValueType(ip, rhs), rhs); } // (part of) Value.zig:toFloat // `Value.toFloat(fN, zcu)` for this pool: the operand's bits at the width the // Type names, not at whatever width the carrier happens to be. A value still // wearing zig1's untyped `comptime_float` shape is materialized at the width // first, which is what `toFloat` does to it upstream. bool valFloatOperandBits( InternPool* ip, uint32_t val, uint32_t width, CrtU128* out) { uint32_t w32; uint64_t w64; uint32_t words[4]; out->hi = 0; if (val < ip->items_len && ip->item_tags[val] == IP_TAG_FLOAT_COMPTIME_FLOAT) { // f128 and this pool's `comptime_float` share one storage format, // so at 128 bits the value is its f128 bits (`toFloat(f128)` is // exact); any narrower width rounds it first. if (width == 128) { if (!ipReadFloatComptimeFloatAsF128Bits(ip, val, words)) return false; *out = ipFloat128FromWords(words); return true; } val = valComptimeFloatAtWidth(ip, val, width); if (val == IP_INDEX_NONE) return false; } switch (width) { case 16: if (!ipReadFloatF16Bits(ip, val, &w32)) return false; out->lo = w32; return true; case 32: if (!ipReadFloatF32Bits(ip, val, &w32)) return false; out->lo = w32; return true; case 64: if (!ipReadFloatF64Bits(ip, val, &w64)) return false; out->lo = w64; return true; case 80: if (!ipReadFloatF80Bits(ip, val, words)) return false; ipFloatF80WordsToLow80Bits(words, &out->lo, &out->hi); return true; default: if (!ipReadFloatF128Bits(ip, val, words)) return false; *out = ipFloat128FromWords(words); return true; } } // ── Memory (writeToMemory, readFromPackedMemory) ──────────────────────────── // Value.zig:writeToPackedMemory // Value.writeToPackedMemory (src/Value.zig): lay `val` out in logical bit // order at `bit_off`. Upstream's `else => unreachable` arms are shapes zig1 // cannot assert away -- its comptime values carry their own loose types (a // `comptime_float` where the static type is `f32`) -- so an unmodeled leaf // declines and the caller keeps its loud abort. bool valWriteToPackedMemory(Zcu* zcu, uint32_t val, uint8_t* buf, uint64_t buf_bits, uint64_t bit_off) { InternPool* ip = &zcu->intern_pool; if (val >= ip->items_len) return false; uint32_t ty = ipComptimeValueType(ip, val); uint64_t sz, al, bits; if (ty == IP_INDEX_NONE || !ztLayoutInfo(zcu, ip, ty, &sz, &al, &bits)) return false; if (bits > UINT32_MAX) return false; if (ipIsUndef(ip, val)) { // `"std.mem.writeVarPackedInt(buffer, bit_offset, bit_size, @as(u1, // 0), endian)"` -- an undef leaf reads back as zeroes. for (uint64_t i = 0; i < bits; i++) if (!packedMemWrite(buf, buf_bits, bit_off + i, 1, 0, 0)) return false; return true; } if (ty == ZIR_REF_VOID_TYPE) return true; // `".void => {}"` if (ty == ZIR_REF_BOOL_TYPE) return packedMemWrite( buf, buf_bits, bit_off, 1, val == ZIR_REF_BOOL_TRUE ? 1u : 0u, 0); if (ip->item_tags[val] == IP_TAG_ENUM_TAG) { // `".@\"enum\" => { const int_val = val.intFromEnum(zcu); // int_val.writeToPackedMemory(zcu, buffer, bit_offset); }"` return valWriteToPackedMemory( zcu, ipEnumTagValue(ip, val), buf, buf_bits, bit_off); } uint64_t lo, hi; uint32_t ibits; bool isigned; if (ztIntInfo(ip, ty, &ibits, &isigned)) { if (ibits > PACKED_MEM_CARRIER_BITS) { // `"bigint.writePackedTwosComplement(buffer, bit_offset, bits, // endian)"` past the {lo, hi} carrier. uint64_t space[2]; BigIntConst big; return valToBigInt(ip, val, space, &big) && bit_off <= buf_bits && buf_bits - bit_off >= ibits && bigIntConstWritePackedTwosComplement( big, buf, bit_off, ibits); } if (!ipTryReadIntLow128Bits(ip, val, &lo, &hi)) return false; bigIntMaskU128(&lo, &hi, ibits); return packedMemWrite(buf, buf_bits, bit_off, ibits, lo, hi); } uint32_t fbits; CrtU128 fv; if (ztSimpleFloatBits(ty, &fbits)) return valFloatOperandBits(ip, val, fbits, &fv) && packedMemWrite(buf, buf_bits, bit_off, fbits, fv.lo, fv.hi); if (ip->item_tags[val] == IP_TAG_BITPACK) { // `"assert(ty.containerLayout(zcu) == .@\"packed\"); const int_val: // Value = .fromInterned(...bitpack.backing_int_val);"` return valWriteToPackedMemory( zcu, ipBitpackBackingVal(ip, val), buf, buf_bits, bit_off); } IpArrayInfo ai; if (ipArrayInfo(ip, ty, &ai) == IP_ARR_OK && !ai.storage_len_overflowed) { uint8_t vt = ip->item_tags[val]; if (vt != IP_TAG_AGGREGATE && vt != IP_TAG_REPEATED && vt != IP_TAG_BYTES && vt != IP_TAG_ONLY_POSSIBLE_VALUE) return false; uint64_t esz, eal, ebits; if (!ztLayoutInfo(zcu, ip, ai.child, &esz, &eal, &ebits)) return false; // `"var elem_bit_off: usize = bit_offset;"` over `len` elements, then // the sentinel -- `storage_len` is that same count. for (uint32_t i = 0; i < ai.storage_len; i++) { uint32_t ev = valElemValue( ip, val, i, "writeToPackedMemory: aggregate element"); // Upstream's element value already carries the element type, // because `coerce` typed the aggregate before `@bitCast` ever saw // it. A zig1 aggregate can still hold an untyped `comptime_float` // lane, whose own type has no bit representation -- so it must be // materialized at the element's width here, exactly as // `bitCastVal` does for a whole value. Getting this wrong is // silent: the lane wrote nothing and read back as zero. if (ip->item_tags[ev] == IP_TAG_FLOAT_COMPTIME_FLOAT && ai.child != ZIR_REF_COMPTIME_FLOAT_TYPE) { ev = valComptimeFloatAtWidth(ip, ev, ebits); if (ev == IP_INDEX_NONE) return false; } if (!valWriteToPackedMemory(zcu, ev, buf, buf_bits, bit_off)) return false; bit_off += ebits; } return true; } return false; } // Value.zig:readFromPackedMemory // Value.readFromPackedMemory (src/Value.zig). Returns IP_INDEX_NONE for the // type shapes zig1 does not model, as its writer twin declines. uint32_t valReadFromPackedMemory(Zcu* zcu, uint32_t ty, const uint8_t* buf, uint64_t buf_bits, uint64_t bit_off) { InternPool* ip = &zcu->intern_pool; uint64_t sz, al, bits; if (ty >= ip->items_len || !ztLayoutInfo(zcu, ip, ty, &sz, &al, &bits)) return IP_INDEX_NONE; if (ty == ZIR_REF_VOID_TYPE) return ZIR_REF_VOID_VALUE; uint64_t lo, hi; if (ty == ZIR_REF_BOOL_TYPE) { if (!packedMemRead(buf, buf_bits, bit_off, 1, &lo, &hi)) return IP_INDEX_NONE; return lo != 0 ? (uint32_t)ZIR_REF_BOOL_TRUE : (uint32_t)ZIR_REF_BOOL_FALSE; } uint32_t ibits; bool isigned; if (ztIntInfo(ip, ty, &ibits, &isigned)) { if (ibits > PACKED_MEM_CARRIER_BITS) { // `"bigint.readPackedTwosComplement(buffer, bit_offset, bits, // endian, int_info.signedness); return pt.intValue_big(ty, // bigint.toConst());"` past the {lo, hi} carrier. if (bit_off > buf_bits || buf_bits - bit_off < ibits) return IP_INDEX_NONE; uint32_t cap = bigIntCalcTwosCompLimbCount(ibits); uint64_t* limbs = zig1Malloc(cap * sizeof(uint64_t)); BigIntMutable m = { limbs, cap, 0, true }; uint32_t res = bigIntMutableReadPackedTwosComplement( &m, buf, bit_off, ibits, isigned) ? ipGetInt(ip, ty, bigIntMutableToConst(m)) : IP_INDEX_NONE; free(limbs); return res; } if (!packedMemRead(buf, buf_bits, bit_off, ibits, &lo, &hi)) return IP_INDEX_NONE; if (ibits == 0) return ipInternIntResultU128(ip, 0, 0, ty); if (isigned && ibits <= 64) { // `".signed => pt.intValue(ty, readVarPackedInt(i64, ...))"`: // the interned value is the sign-extended integer, which is the // carrier every other signed intern in zig1 produces. uint64_t sign = UINT64_C(1) << (ibits - 1u); if ((lo & sign) != 0) lo |= ~((sign << 1u) - 1u); return semaCoerceComptimeIntI64Literal(ip, (int64_t)lo, ty); } if (isigned && ibits > 64u && ibits < 128u) { // Above the 64-bit fast path upstream still reads TWO'S // complement -- `"bigint.readPackedTwosComplement(buffer, // bit_offset, bits, endian, int_info.signedness)"` -- so a // negative `i65`/`i96` needs its sign filled to the top of this // frame's 128-bit carrier. Without it the value interned as a // large positive one, silently. uint64_t sign_hi = UINT64_C(1) << (ibits - 65u); if ((hi & sign_hi) != 0) hi |= ~((sign_hi << 1u) - 1u); } return ipInternIntResultU128(ip, lo, hi, ty); } uint32_t fbits; if (ztSimpleFloatBits(ty, &fbits)) { if (!packedMemRead(buf, buf_bits, bit_off, fbits, &lo, &hi)) return IP_INDEX_NONE; return semaFloatFromBits(ip, ty, fbits, (CrtU128) { lo, hi }); } if (ipIsEnumType(ip, ty)) { // `".@\"enum\" => { const int_ty = ty.intTagType(zcu); const // int_val: Value = try .readFromPackedMemory(int_ty, pt, buffer, // bit_offset); return pt.getCoerced(int_val, ty); }"` uint32_t eint_ty = ipEnumTypeIntTagType(ip, ty); uint32_t ev = valReadFromPackedMemory(zcu, eint_ty, buf, buf_bits, bit_off); uint32_t ewidth; bool esigned; uint64_t ebits_val; if (ev == IP_INDEX_NONE || !ztIntInfo(ip, eint_ty, &ewidth, &esigned) || !ipTryReadIntValue(ip, ev, &ebits_val)) return IP_INDEX_NONE; // `getCoerced` canonicalizes; here that has to be done by hand, // because zig1 has more than one carrier for the same typed integer // (`int_u8` and `int_small{u8, ...}` both hold `@as(u8, 200)`) while // an `enum_tag` item dedups on the carrier index. Every other // producer interns the tag int as `ipGetIntSmall(int_tag_ty, ...)` // -- `ipEnumTagFieldIndex`'s own doc says so -- so this one must too, // or a tag read back here would never compare equal to the same enum // field spelled any other way. Signed narrow carriers store the // masked word, which is what `ipTryReadIntValue` sign-extended from. // Upstream puts no width limit on the tag type -- a `u64`-tagged // enum reads back exactly like a `u8`-tagged one. The only bound is // `ipGetIntSmall`'s carrier, which stores a `uint32_t`: a tag whose // masked value does not fit one has no carrier here and declines. uint64_t emask = ewidth >= 64u ? UINT64_MAX : ((UINT64_C(1) << ewidth) - 1u); uint64_t etag = ebits_val & emask; if (etag > UINT32_MAX) return IP_INDEX_NONE; return ipGetEnumTag( ip, ty, ipGetIntSmall(ip, eint_ty, (uint32_t)etag)); } if (ipTypeIsBitpack(ip, ty)) { // `"const int_val: Value = try .readFromPackedMemory(ty // .bitpackBackingInt(zcu), pt, buffer, bit_offset); return pt // .bitpackValue(ty, int_val);"` -- the `.@"struct", .@"union"` arm. uint32_t bv = valReadFromPackedMemory( zcu, ipBitpackBackingInt(ip, ty), buf, buf_bits, bit_off); if (bv == IP_INDEX_NONE) return IP_INDEX_NONE; return ipGetBitpack(ip, ty, bv); } IpArrayInfo ai; if (ipArrayInfo(ip, ty, &ai) == IP_ARR_OK && !ai.storage_len_overflowed) { uint64_t esz, eal, ebits; if (!ztLayoutInfo(zcu, ip, ai.child, &esz, &eal, &ebits)) return IP_INDEX_NONE; if (ai.storage_len == 0) return ipGetAggregate(ip, ty, NULL, 0); uint32_t* elems = zig1Malloc((size_t)ai.storage_len * sizeof(uint32_t)); uint32_t res = IP_INDEX_NONE; for (uint32_t i = 0; i < ai.storage_len; i++) { elems[i] = valReadFromPackedMemory( zcu, ai.child, buf, buf_bits, bit_off + (uint64_t)i * ebits); if (elems[i] == IP_INDEX_NONE) goto agg_done; } res = ipGetAggregate(ip, ty, elems, ai.storage_len); agg_done: free(elems); return res; } return IP_INDEX_NONE; } // (part of) Value.zig:floatCast // A value still carrying zig1's untyped `comptime_float` shape, materialized // at a concrete float width (the divergence `bitCastVal` states below): // `Value.floatCast` to the fixed float type of that width. uint32_t valComptimeFloatAtWidth(InternPool* ip, uint32_t val, uint64_t bits) { return valFloatCast(ip, val, bits == 16 ? ZIR_REF_F16_TYPE : bits == 32 ? ZIR_REF_F32_TYPE : bits == 64 ? ZIR_REF_F64_TYPE : bits == 80 ? ZIR_REF_F80_TYPE : bits == 128 ? ZIR_REF_F128_TYPE : IP_INDEX_NONE); } // Value.zig:writeToMemory // `Value.writeToMemory` for the primitives castMemory lays out: undefined // bytes are 0xAA, an integer (enum, pointer address) is its two's complement // over its ABI size, a float its bits and zero padding. SemaCastStatus valWriteToMemory( Zcu* zcu, uint32_t val, uint8_t* buf, uint64_t size) { const InternPool* ip = &zcu->intern_pool; uint32_t ty = ipTypeOf(ip, val); if (ipIsUndef(ip, val)) { memset(buf, 0xAA, (size_t)size); return ZPT_CAST_OK; } if (ip->item_tags[ty] == IP_TAG_TYPE_POINTER) { if (!ipIsPtrInt(ip, val)) return ZPT_CAST_RUNTIME; uint64_t addr = ipPtrIntAddress(ip, val); for (uint64_t i = 0; i < size; i++) buf[i] = i < 8 ? (uint8_t)(addr >> (8 * i)) : 0; return ZPT_CAST_OK; } if (ztTypeIsPtrLikeOptional(ip, ty)) { if (ip->item_tags[val] == IP_TAG_OPT_PAYLOAD) return valWriteToMemory( zcu, ip->extra[ip->item_datas[val] + 1], buf, size); memset(buf, 0, (size_t)size); // null pointer return ZPT_CAST_OK; } if (ip->item_tags[val] == IP_TAG_ENUM_TAG) return valWriteToMemory(zcu, ipEnumTagValue(ip, val), buf, size); if (ip->item_tags[val] == IP_TAG_BITPACK) return valWriteToMemory(zcu, ipBitpackBackingVal(ip, val), buf, size); memset(buf, 0, (size_t)size); if (!valWriteToPackedMemory(zcu, val, buf, size * 8u, 0)) return ZPT_CAST_UNMODELED; uint32_t bits; bool is_signed; if (ztIntInfo(ip, ty, &bits, &is_signed) && is_signed && bits != 0 && (buf[(bits - 1) / 8] >> ((bits - 1) % 8)) & 1) for (uint64_t bit = bits; bit < size * 8u; bit++) buf[bit / 8] |= (uint8_t)(1u << (bit % 8)); return ZPT_CAST_OK; } // ── Arithmetic (intFitsInType, abs, mulAdd, numberMin) ────────────────────── // Value.zig:intFitsInType, Sema.zig:coerceExtra // `Value.intFitsInType` (src/Value.zig), which `Sema.coerceExtra`'s // comptime-known integer arm asks before every int -> int coercion: "if (!val // .intFitsInType(dest_ty, null, zcu)) ... return sema.fail(block, inst_src, // "type '{f}' cannot represent integer value '{f}'", ...)". Upstream's // operand switch is `.undef => return true`, `.int => big_int // .fitsInTwosComp(info.signedness, info.bits)`, and `else => unreachable` -- // so an integer whose carrier this cannot read is not a quiet "it fits" but a // shape the bound was asked about and could not judge, and it declines. The // two arms upstream carries that zig1 does not are its `.ptr/.func/.@"extern"` // rule and its `.aggregate` vector reduction; both // are still a widened acceptance here, recorded as the remaining gap. // // The destination side is still widened: upstream's `ty.intInfo(zcu)` asserts // an int-like type (walking an enum to its tag and a vector to its child), // where a destination this cannot read answers "fits" and leaves the caller's // own route to report. An out-of-range `val` is an unreadable operand like any // other. bool valIntFitsInType(InternPool* ip, uint32_t val, uint32_t dest_ty) { uint32_t bits; bool is_signed; // `ty.intInfo(zcu)`, whose `.vector` arm is the element's. if (dest_ty >= ip->items_len || !ztIntInfoInRange(ip, ipScalarType(ip, dest_ty), &bits, &is_signed)) return true; if (val >= ip->items_len) return true; if (ipIsUndef(ip, val)) return true; // `.undef => return true` // `".@\"extern\", .func, .ptr => { ... return switch (info.signedness) { // .signed => info.bits > ptr_bits, .unsigned => info.bits >= ptr_bits }; // }"` -- an address, at wasm32's 32-bit pointer width. uint32_t val_ty = ipTypeOf(ip, val); if (val_ty < ip->items_len && (ip->item_tags[val_ty] == IP_TAG_TYPE_POINTER || ip->item_tags[val_ty] == IP_TAG_TYPE_FUNCTION)) return is_signed ? bits > ZT_PTR_BIT_WIDTH : bits >= ZT_PTR_BIT_WIDTH; // `.aggregate` (`"assert(ty.zigTypeTag(zcu) == .vector)"`): every lane // at `ty.scalarType(zcu)`, the `.bytes` storage included. IpArrayInfo vi; if (ipArrayInfo(ip, dest_ty, &vi) == IP_ARR_OK && vi.is_vector && (ip->item_tags[val] == IP_TAG_AGGREGATE || ip->item_tags[val] == IP_TAG_REPEATED || ip->item_tags[val] == IP_TAG_BYTES)) { for (uint32_t i = 0; i < vi.len; i++) if (!valIntFitsInType(ip, valElemValue(ip, val, i, "intFitsInType: vector lane"), vi.child)) return false; return true; } // Upstream's switch is over the value, and only its `.int` arm reaches // the bound; `Sema.coerceExtra`'s `.int, .comptime_int` destination arm // asks the question only when the source is an integer too // (`"switch (inst_ty.zigTypeTag(zcu)) { ... .int, .comptime_int => { if // (try sema.resolveValue(inst)) |val| { if (!(try // val.intFitsInType(dest_ty, null, zcu))) break :int; ..."`), so a // non-integer operand is not this gate's question at all and passes // through to the caller's own coercion route -- which is upstream's // `break :int`, and which reports its own failure for a type, a pointer // or an aggregate. if (!ipTagIsIntValue((IpTag)ip->item_tags[val])) return true; // `Value.intFitsInType` is `big.int.Const.fitsInTwosComp` over // `Value.toBigInt`: One question at every carrier, sign and width alike. // An integer whose carrier this cannot read is the gate's own question, so // it declines rather than calling it a fit. uint64_t space[2]; BigIntConst v; if (!valToBigInt(ip, val, space, &v)) UNIMPLEMENTED( "intFitsInType: integer operand this carrier cannot read"); return valBigIntFitsInType(ip, v, ipScalarType(ip, dest_ty)); } // Value.zig:absScalar, PerThread.zig:intValue_big // `Value.absScalar` (src/Value.zig) -- the magnitude of one scalar at // `ty`. The int arms are `"var operand_bigint = try // val.toBigInt(&buffer, zcu).toManaged(arena); operand_bigint.abs(); return // pt.intValue_big(try ty.toUnsigned(pt), operand_bigint.toConst());"`, i.e. // the magnitude the carrier already holds re-interned under the unsigned // peer, so any width the limbs carry is answered; `comptime_int` keeps its // own unbounded type. IP_INDEX_NONE for a shape this pool cannot carry. static uint32_t valAbsScalar(InternPool* ip, uint32_t val, uint32_t ty) { uint64_t space[2]; BigIntConst v; uint32_t bits; bool is_signed; if (ztIntInfo(ip, ty, &bits, &is_signed)) { if (!valToBigInt(ip, val, space, &v)) return IP_INDEX_NONE; uint32_t uty = ztTypeToUnsigned(ip, ty); if (uty == IP_INDEX_NONE) return IP_INDEX_NONE; return ipGetInt(ip, uty, bigIntConstAbs(v)); } if (ty == ZIR_REF_COMPTIME_INT_TYPE) { if (!valToBigInt(ip, val, space, &v)) return IP_INDEX_NONE; return ipGetInt(ip, ZIR_REF_COMPTIME_INT_TYPE, bigIntConstAbs(v)); } if (ty == ZIR_REF_COMPTIME_FLOAT_TYPE || ztSimpleFloatBits(ty, &bits)) return valFloatSignBits(ip, val, ty, true); return IP_INDEX_NONE; } // Value.zig:abs, Sema.zig:maybeConstantUnaryMath, PerThread.zig:undefRef // `Value.abs` (src/Value.zig) plus `Sema.maybeConstantUnaryMath`'s two // obligations for it (src/Sema.zig): an UNDEF operand answers UNDEF at // the result type (`"if (operand_val.isUndef(zcu)) return try // pt.undefRef(result_ty)"`), and a `.vector` result maps `absScalar` over the // lanes at the vector's child type and re-aggregates. `result_ty` is // `zirAbs`'s, already unsigned where upstream made it so. IP_INDEX_NONE for // a shape this pool cannot carry, which is each caller's loud boundary. uint32_t valAbs(InternPool* ip, uint32_t val, uint32_t result_ty) { if (val >= ip->items_len) return IP_INDEX_NONE; if (ipIsUndef(ip, val)) return ipGetUndef(ip, result_ty); IpArrayInfo vi; if (ipArrayInfo(ip, result_ty, &vi) == IP_ARR_OK && vi.is_vector) { uint32_t* elems = zig1Malloc((vi.len ? (size_t)vi.len : 1u) * sizeof(*elems)); bool ok = true; for (uint32_t i = 0; i < vi.len; i++) { uint32_t le = valElemValue(ip, val, i, "abs: vector lane"); elems[i] = valAbsScalar(ip, le, vi.child); if (elems[i] == IP_INDEX_NONE) ok = false; } uint32_t res = ok ? ipGetAggregate(ip, result_ty, elems, vi.len) : IP_INDEX_NONE; free(elems); return res; } return valAbsScalar(ip, val, result_ty); } // Value.zig:mulAddScalar static uint32_t valMulAddScalar(InternPool* ip, uint32_t ty, uint32_t mulend1, uint32_t mulend2, uint32_t addend) { uint32_t width; CrtU128 a, b, c; uint64_t out; if (!ztSimpleFloatBits(ty, &width) || !valFloatOperandBits(ip, mulend1, width, &a) || !valFloatOperandBits(ip, mulend2, width, &b) || !valFloatOperandBits(ip, addend, width, &c) || arithFloatMulAdd(width, a.lo, b.lo, c.lo, &out) != ARITH_OK) UNIMPLEMENTED("mul_add: comptime operand has no exact carrier"); return semaFloatFromBits(ip, ty, width, (CrtU128) { out, 0 }); } // Value.zig:mulAdd uint32_t valMulAdd(Zcu* zcu, uint32_t ty, uint32_t mulend1, uint32_t mulend2, uint32_t addend) { InternPool* ip = &zcu->intern_pool; if (ipIsUndef(ip, mulend2) || ipIsUndef(ip, addend)) return ipGetUndef(ip, ty); IpArrayInfo vec; if (ipArrayInfo(ip, ty, &vec) != IP_ARR_OK || !vec.is_vector) return valMulAddScalar(ip, ty, mulend1, mulend2, addend); uint32_t* elems = zig1Malloc(((size_t)vec.len + 1u) * sizeof(*elems)); for (uint32_t i = 0; i < vec.len; i++) elems[i] = valMulAddScalar(ip, vec.child, valElemValue(ip, mulend1, i, "mul_add: lane"), valElemValue(ip, mulend2, i, "mul_add: lane"), valElemValue(ip, addend, i, "mul_add: lane")); uint32_t res = ipGetAggregate(ip, ty, elems, vec.len); free(elems); return res; } // Value.zig:compareAllWithZero, Sema.zig:zirModRem // `Value.compareAllWithZero` for the two predicates `Sema.zirModRem` asks it: // `.gte` (`eq == false`) and `.eq`. It quantifies over every lane of a vector // and over the one value of a scalar, so it answers about the whole value. bool valCompareAllWithZero(InternPool* ip, uint32_t val, bool eq) { if (val >= ip->items_len) UNIMPLEMENTED("compareAllWithZero: operand is not a comptime value"); uint32_t ty = ipTypeOf(ip, val); IpArrayInfo vec; if (ty != IP_INDEX_NONE && ipArrayInfo(ip, ty, &vec) == IP_ARR_OK && vec.is_vector) { for (uint32_t i = 0; i < vec.len; i++) if (!valCompareAllWithZero(ip, valElemValue( ip, val, i, "compareAllWithZero: vector lane"), eq)) return false; return true; } // `".float => |float| switch (float.storage) { inline else => |x| // std.math.compare(x, op, 0) }"`: a NaN answers false to both. Every // width widens exactly to f128, where comparef.zig's cmpf2 orders it // against +0 (its GE flavour answers a NaN below, its LE one above). if (semaTagIsFloat(ip->item_tags[val])) { uint32_t w[4]; if (!ipReadFloatAsF128Bits(ip, val, w)) UNIMPLEMENTED("compareAllWithZero: float operand is not readable"); const CrtU128 x = ipFloat128FromWords(w); const CrtU128 zero = { 0, 0 }; return eq ? crtCmp(128, false, x, zero) == 0 : crtCmp(128, true, x, zero) >= 0; } // `.int`: `orderAgainstZero` on the big integer, at any width. uint64_t space[2]; BigIntConst v; if (!valToBigInt(ip, val, space, &v)) UNIMPLEMENTED("compareAllWithZero: operand is not a readable integer"); bool is_zero = bigIntEqlZero(v); return eq ? is_zero : (v.positive || is_zero); } // Value.zig:numberMin, Sema.zig:compareScalar // `Value.numberMin` / `numberMax` (src/Value.zig): `"if (lhs.isNan(zcu)) // return rhs; if (rhs.isNan(zcu)) return lhs; if (compareHetero(lhs, .lt, // rhs, zcu)) { return lhs; } else { return rhs; }"`. They select one of their // two operands, so the ordering is this frame's one comparison rule // (`semaCmpVectorLane`, which is Sema.compareScalar over two values of one // type) rather than a second transcription of it. The undef and NaN arms are // upstream's own first three lines. uint32_t valNumberMinMax( const InternPool* ip, uint32_t lhs, uint32_t rhs, bool want_min) { if (ipIsUndef(ip, lhs) || ipIsUndef(ip, rhs)) return ZIR_REF_UNDEF; if (valIsNan(ip, lhs)) return rhs; if (valIsNan(ip, rhs)) return lhs; return semaCmpVectorLane( ip, want_min ? ZIR_INST_CMP_LT : ZIR_INST_CMP_GT, lhs, rhs) ? lhs : rhs; } // (part of) Value.zig:clz, Value.zig:ctz, Value.zig:popCount // `Value.clz` / `Value.ctz` / `Value.popCount`: `"val.toBigInt(...)"`, then // the `Const` op at `ty.intInfo(zcu).bits`. uint64_t valBitCount( const InternPool* ip, ZirInstTag tag, uint32_t val, uint32_t bits) { uint64_t space[2]; BigIntConst v; if (!valToBigInt(ip, val, space, &v)) UNIMPLEMENTED("zirBitCount: operand not an integer value"); return tag == ZIR_INST_CLZ ? bigIntConstClz(v, bits) : tag == ZIR_INST_CTZ ? bigIntConstCtz(v, bits) : bigIntConstPopCount(v, bits); } // (part of) Value.zig:absScalar // `floatNeg` (src/Sema/arith.zig): `"const storage: InternPool.Key.Float. // Storage = switch (ty.floatBits(target)) { 16 => .{ .f16 = -val.toFloat(f16, // zcu) }, ... };"`, re-interned as `.{ .float = .{ .ty = ty.toIntern(), // .storage = storage } }`; and `Value.absScalar`'s float arm // (src/Value.zig), `"@abs(val.toFloat(fN))"`, re-interned the same way. // At every IEEE width both are exactly the top bit -- flipped for negation // (which is the whole reason upstream routes float negation here instead of // through `0 - x`: a zero, an infinity and a NaN payload all survive it) and // cleared for `@abs` -- so there is one table of the sign positions and // `clear` picks the operation. Each width reads and writes its own carrier // rather than round-tripping through a host float it may not fit. // // As in `bitCastVal`, a zig1 comptime value can still be carrying the // untyped `comptime_float` shape where upstream's `coerce` would already have // produced the typed float; it is materialized at `ty`'s width first. uint32_t valFloatSignBits( InternPool* ip, uint32_t val, uint32_t ty, bool clear) { uint32_t bits = 128; // `comptime_float` is stored at f128 CrtU128 v; if (val >= ip->items_len || (ty != ZIR_REF_COMPTIME_FLOAT_TYPE && !ztSimpleFloatBits(ty, &bits)) || !valFloatOperandBits(ip, val, bits, &v)) return IP_INDEX_NONE; uint64_t* word = bits > 64 ? &v.hi : &v.lo; const uint64_t sign = UINT64_C(1) << ((bits - 1u) & 63u); *word = clear ? (*word & ~sign) : (*word ^ sign); return semaFloatFromBits(ip, ty, bits, v); } // A comptime slice value's `.ptr` (0) or `.len` (1). uint32_t valSliceField(const InternPool* ip, uint32_t slice, uint32_t i) { return i == 0 ? ipPtrSlicePtr(ip, slice) : ipPtrSliceLen(ip, slice); } // (part of) InternPool.zig:indexToKey // A comptime aggregate value in any storage InternPool.get picks, including // a tuple's only_possible_value (indexToKey's `"tuple.values"` elems). bool valIsAggregate(const InternPool* ip, uint32_t v) { if (v >= ip->items_len) return false; IpTag tag = (IpTag)ip->item_tags[v]; return tag == IP_TAG_AGGREGATE || tag == IP_TAG_REPEATED || tag == IP_TAG_BYTES || (tag == IP_TAG_ONLY_POSSIBLE_VALUE && ipItemTagIs(ip, ip->item_datas[v], IP_TAG_TYPE_TUPLE)); } // Value.zig:isNull // "Asserts the value is not undefined": null_value, a `.int`-based pointer // at byte offset 0, or an optional with no payload. bool valIsNull(const InternPool* ip, uint32_t val) { if (val == ZIR_REF_NULL_VALUE) return true; if (ipItemTagIs(ip, val, IP_TAG_PTR_INT)) return ipPtrIntAddress(ip, val) == 0; return ipItemTagIs(ip, val, IP_TAG_OPT_NULL); } // Value.zig:optionalValue // The payload of a comptime optional, IP_INDEX_NONE for null; a C pointer // (`.ptr`) is its own payload. uint32_t valOptionalValue(const InternPool* ip, uint32_t val) { if (ipItemTagIs(ip, val, IP_TAG_OPT_NULL)) return IP_INDEX_NONE; if (ipItemTagIs(ip, val, IP_TAG_OPT_PAYLOAD)) return ipPayloadVal(ip, val); return val; } /* ---- end value.c ---- */ /* ---- begin wasi.c ---- */ // wasi.c — the WASI host (declarations in wasi.h): the // wasi_snapshot_preview1 calls std.Io.Threaded makes on wasm32-wasi, after // upstream's stage1/wasi.c, served from the host's files and descriptors and // answered as wasmtime answers them. Constants are lib/std/os/wasi.zig's. Each // import's handler is named after it (wasiFdWrite serves fd_write). The host // calls go through the wasiHost* layer below: POSIX, or the Windows CRT's // low-level I/O (MSVC and mingw). #ifndef ZIG1_H #include "zig1.h" #endif #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #else #include #include #include #include #endif // lib/std/os/wasi.zig's constants the WASI host answers with. enum { WASI_ERRNO_SUCCESS = 0, WASI_ERRNO_ACCES = 2, WASI_ERRNO_BADF = 8, WASI_ERRNO_BUSY = 10, WASI_ERRNO_EXIST = 20, WASI_ERRNO_INVAL = 28, WASI_ERRNO_IO = 29, WASI_ERRNO_ISDIR = 31, WASI_ERRNO_LOOP = 32, WASI_ERRNO_MFILE = 33, WASI_ERRNO_MLINK = 34, WASI_ERRNO_NAMETOOLONG = 37, WASI_ERRNO_NOENT = 44, WASI_ERRNO_NOMEM = 48, WASI_ERRNO_NOSPC = 51, WASI_ERRNO_NOTDIR = 54, WASI_ERRNO_NOTEMPTY = 55, WASI_ERRNO_OVERFLOW = 61, WASI_ERRNO_PERM = 63, WASI_ERRNO_ROFS = 69, WASI_ERRNO_SPIPE = 70, WASI_ERRNO_XDEV = 75, }; enum { WASI_FILETYPE_UNKNOWN = 0, WASI_FILETYPE_BLOCK_DEVICE = 1, WASI_FILETYPE_CHARACTER_DEVICE = 2, WASI_FILETYPE_DIRECTORY = 3, WASI_FILETYPE_REGULAR_FILE = 4, WASI_FILETYPE_SYMBOLIC_LINK = 7, }; // What `filestat_t` reports of a host file, times in nanoseconds since 1970. typedef struct { uint64_t dev, ino, nlink, size, atim, mtim, ctim; uint8_t filetype; // filetype_t } WasiHostStat; // wasiHostSetTimes's times that are not a timestamp: fstflags_t's *_NOW, // and a time left unchanged. #define WASI_HOST_TIME_NOW UINT64_MAX #define WASI_HOST_TIME_OMIT (UINT64_MAX - 1u) // The host layer: stat, times, descriptors, positional and scatter I/O, // links, mkdir, rename, unlink, rmdir, directory listing, clocks, sleep and // randomness. Windows has no pread, pwrite or readv: positional I/O seeks and // restores the position, and a scatter read fills the first buffer. MSVC // declares only the CRT's underscore names in C11 mode, so the POSIX names it // lacks are defined to them; files and the standard streams are binary. #ifdef _WIN32 #ifndef O_RDONLY #define O_RDONLY _O_RDONLY #define O_WRONLY _O_WRONLY #define O_RDWR _O_RDWR #define O_CREAT _O_CREAT #define O_EXCL _O_EXCL #define O_TRUNC _O_TRUNC #define O_APPEND _O_APPEND #endif // The CRT's 64-bit offsets and its fsync. #define lseek _lseeki64 #define fsync _commit // The CRT has no symbolic links to follow. #define WASI_HOST_O_NOFOLLOW 0 // FILETIME's 100 ns ticks from 1601 to 1970. #define WASI_HOST_EPOCH_TICKS UINT64_C(116444736000000000) typedef struct { void* iov_base; size_t iov_len; } WasiHostIov; // The errno a Win32 error means. static int wasiHostErrno(DWORD err) { switch (err) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: return ENOENT; case ERROR_ACCESS_DENIED: case ERROR_SHARING_VIOLATION: return EACCES; case ERROR_ALREADY_EXISTS: return EEXIST; case ERROR_DIR_NOT_EMPTY: return ENOTEMPTY; case ERROR_NOT_SAME_DEVICE: return EXDEV; default: return EIO; } } static uint64_t wasiHostFileTimeNs(FILETIME t) { uint64_t ticks = ((uint64_t)t.dwHighDateTime << 32) | t.dwLowDateTime; return ticks > WASI_HOST_EPOCH_TICKS ? (ticks - WASI_HOST_EPOCH_TICKS) * 100u : 0u; } // A handle's file as GetFileInformationByHandle gives it, the file index as // inode; a reparse point opened without following is a symbolic link. static int wasiHostStatHandle(HANDLE h, bool no_follow, WasiHostStat* st) { memset(st, 0, sizeof *st); DWORD type = GetFileType(h); if (type == FILE_TYPE_CHAR) st->filetype = WASI_FILETYPE_CHARACTER_DEVICE; if (type != FILE_TYPE_DISK) return 0; BY_HANDLE_FILE_INFORMATION info; if (!GetFileInformationByHandle(h, &info)) { errno = wasiHostErrno(GetLastError()); return -1; } st->dev = info.dwVolumeSerialNumber; st->ino = ((uint64_t)info.nFileIndexHigh << 32) | info.nFileIndexLow; st->nlink = info.nNumberOfLinks; st->size = ((uint64_t)info.nFileSizeHigh << 32) | info.nFileSizeLow; st->atim = wasiHostFileTimeNs(info.ftLastAccessTime); st->mtim = wasiHostFileTimeNs(info.ftLastWriteTime); st->ctim = st->mtim; st->filetype = no_follow && (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ? WASI_FILETYPE_SYMBOLIC_LINK : (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? WASI_FILETYPE_DIRECTORY : WASI_FILETYPE_REGULAR_FILE; return 0; } // A handle to `path` (a directory too) with `access`, INVALID_HANDLE_VALUE // with errno set. static HANDLE wasiHostOpenHandle(const char* path, bool follow, DWORD access) { DWORD flags = FILE_FLAG_BACKUP_SEMANTICS; if (!follow) flags |= FILE_FLAG_OPEN_REPARSE_POINT; HANDLE h = CreateFileA(path, access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, flags, NULL); if (h == INVALID_HANDLE_VALUE) errno = wasiHostErrno(GetLastError()); return h; } // The file of host descriptor `fd`, else of `path`. static int wasiHostStat( int fd, const char* path, bool follow, WasiHostStat* st) { if (fd >= 0) { intptr_t handle = _get_osfhandle(fd); return wasiHostStatHandle((HANDLE)handle, false, st); } HANDLE h = wasiHostOpenHandle(path, follow, FILE_READ_ATTRIBUTES); if (h == INVALID_HANDLE_VALUE) return -1; int rc = wasiHostStatHandle(h, !follow, st); CloseHandle(h); return rc; } static FILETIME wasiHostFileTime(uint64_t ns, FILETIME now) { if (ns == WASI_HOST_TIME_NOW) return now; uint64_t ticks = ns / 100u + WASI_HOST_EPOCH_TICKS; return (FILETIME) { (DWORD)ticks, (DWORD)(ticks >> 32) }; } // SetFileTime through a handle with the access it needs: reopened from `fd`, // else opened at `path`. static int wasiHostSetTimes( int fd, const char* path, bool follow, uint64_t atim, uint64_t mtim) { intptr_t handle = fd >= 0 ? _get_osfhandle(fd) : -1; HANDLE h = fd >= 0 ? ReOpenFile((HANDLE)handle, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_FLAG_BACKUP_SEMANTICS) : wasiHostOpenHandle(path, follow, FILE_WRITE_ATTRIBUTES); if (h == INVALID_HANDLE_VALUE) { errno = wasiHostErrno(GetLastError()); return -1; } FILETIME now; GetSystemTimeAsFileTime(&now); FILETIME a = wasiHostFileTime(atim, now); FILETIME m = wasiHostFileTime(mtim, now); BOOL ok = SetFileTime(h, NULL, atim == WASI_HOST_TIME_OMIT ? NULL : &a, mtim == WASI_HOST_TIME_OMIT ? NULL : &m); if (!ok) errno = wasiHostErrno(GetLastError()); CloseHandle(h); return ok ? 0 : -1; } static int wasiHostOpen(const char* path, int flags) { return _open(path, flags | _O_BINARY, _S_IREAD | _S_IWRITE); } static void wasiHostClose(int fd) { if (fd >= 0) _close(fd); } static int64_t wasiHostWrite(int fd, const void* buf, size_t len) { return _write(fd, buf, (unsigned)len); } static int64_t wasiHostRead(int fd, void* buf, size_t len) { return _read(fd, buf, (unsigned)len); } static int64_t wasiHostReadv(int fd, const WasiHostIov* iov, int iov_len) { return iov_len > 0 ? wasiHostRead(fd, iov[0].iov_base, iov[0].iov_len) : 0; } static bool wasiHostSeekable(int fd) { return _lseeki64(fd, 0, SEEK_CUR) != -1; } // Seek `fd` to `pos` for a positional call, its position in `*cur`. static bool wasiHostSeekFrom(int fd, uint64_t pos, int64_t* cur) { *cur = _lseeki64(fd, 0, SEEK_CUR); return *cur != -1 && _lseeki64(fd, (int64_t)pos, SEEK_SET) != -1; } // Return the result `n` of a positional call after restoring position `cur`. static int64_t wasiHostSeekBack(int fd, int64_t cur, int64_t n) { int saved = errno; _lseeki64(fd, cur, SEEK_SET); errno = saved; return n; } static int64_t wasiHostPread(int fd, void* buf, size_t len, uint64_t pos) { int64_t cur; if (!wasiHostSeekFrom(fd, pos, &cur)) return -1; return wasiHostSeekBack(fd, cur, wasiHostRead(fd, buf, len)); } static int64_t wasiHostPwrite( int fd, const void* buf, size_t len, uint64_t pos) { int64_t cur; if (!wasiHostSeekFrom(fd, pos, &cur)) return -1; return wasiHostSeekBack(fd, cur, wasiHostWrite(fd, buf, len)); } static int wasiHostTruncate(int fd, uint64_t size) { errno_t err = _chsize_s(fd, (int64_t)size); errno = err; return err == 0 ? 0 : -1; } static int wasiHostMkdir(const char* path) { return _mkdir(path); } static int wasiHostUnlink(const char* path) { return _unlink(path); } static int wasiHostRmdir(const char* path) { return _rmdir(path); } // std's fileLock: an exclusive lock of byte 0, waiting for it, as its // NtLockFile takes. static int wasiHostLock(int fd) { OVERLAPPED overlapped = { 0 }; intptr_t handle = _get_osfhandle(fd); if (LockFileEx( (HANDLE)handle, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &overlapped)) return 0; errno = wasiHostErrno(GetLastError()); return -1; } // rename(2)'s atomic replace. static int wasiHostRename(const char* old_path, const char* new_path) { if (MoveFileExA(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) return 0; errno = wasiHostErrno(GetLastError()); return -1; } static int wasiHostLink(const char* old_path, const char* new_path) { if (CreateHardLinkA(new_path, old_path, NULL)) return 0; errno = wasiHostErrno(GetLastError()); return -1; } // The real clock (id 0) or the monotonic one (1), or its resolution. static uint64_t wasiHostClock(uint32_t id, bool res) { if (id == 0) { FILETIME now; GetSystemTimePreciseAsFileTime(&now); return res ? 100u : wasiHostFileTimeNs(now); } LARGE_INTEGER freq, count; QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&count); uint64_t f = (uint64_t)freq.QuadPart; uint64_t c = (uint64_t)count.QuadPart; return res ? (f >= 1000000000u ? 1u : 1000000000u / f) : c / f * 1000000000u + c % f * 1000000000u / f; } static void wasiHostSleep(uint64_t ns) { uint64_t ms = ns / 1000000u + (ns % 1000000u != 0); for (; ms >= INFINITE; ms -= INFINITE - 1u) Sleep(INFINITE - 1u); Sleep((DWORD)ms); } // The fdflags_t bits the host keeps: none on Windows. static uint64_t wasiHostFdFlags(int fd) { (void)fd; return 0; } // rand(), as upstream's wasi.c serves random_get, seeded with the time and // the process id so that zig1s started in one second sharing a cache draw // distinct temporary names. static int wasiHostRandom(uint8_t* buf, size_t len) { static bool seeded = false; if (!seeded) { srand((unsigned)time(NULL) ^ (unsigned)_getpid()); seeded = true; } for (size_t off = 0; off < len; off++) buf[off] = (uint8_t)rand(); return 0; } static void wasiHostStdioBinary(void) { for (int fd = 0; fd <= 2; fd++) _setmode(fd, _O_BINARY); } #else typedef struct iovec WasiHostIov; static uint64_t wasiHostNs(struct timespec t) { return (uint64_t)t.tv_sec * 1000000000u + (uint64_t)t.tv_nsec; } static struct timespec wasiHostNsToTs(uint64_t ns) { return (struct timespec) { (time_t)(ns / 1000000000u), (long)(ns % 1000000000u) }; } static void wasiHostStatFrom(const struct stat* s, WasiHostStat* st) { uint8_t filetype = S_ISDIR(s->st_mode) ? WASI_FILETYPE_DIRECTORY : S_ISREG(s->st_mode) ? WASI_FILETYPE_REGULAR_FILE : S_ISCHR(s->st_mode) ? WASI_FILETYPE_CHARACTER_DEVICE : S_ISBLK(s->st_mode) ? WASI_FILETYPE_BLOCK_DEVICE : S_ISLNK(s->st_mode) ? WASI_FILETYPE_SYMBOLIC_LINK : WASI_FILETYPE_UNKNOWN; *st = (WasiHostStat) { .dev = (uint64_t)s->st_dev, .ino = (uint64_t)s->st_ino, .nlink = (uint64_t)s->st_nlink, .size = (uint64_t)s->st_size, .atim = wasiHostNs(s->st_atim), .mtim = wasiHostNs(s->st_mtim), .ctim = wasiHostNs(s->st_ctim), .filetype = filetype, }; } // The file of host descriptor `fd`, else of `path`. static int wasiHostStat( int fd, const char* path, bool follow, WasiHostStat* st) { struct stat s; int rc = fd >= 0 ? fstat(fd, &s) : follow ? stat(path, &s) : lstat(path, &s); if (rc == 0) wasiHostStatFrom(&s, st); return rc; } static struct timespec wasiHostTimespec(uint64_t ns) { return ns == WASI_HOST_TIME_NOW ? (struct timespec) { 0, UTIME_NOW } : ns == WASI_HOST_TIME_OMIT ? (struct timespec) { 0, UTIME_OMIT } : wasiHostNsToTs(ns); } // futimens on `fd`, else utimensat at `path`. static int wasiHostSetTimes( int fd, const char* path, bool follow, uint64_t atim, uint64_t mtim) { const struct timespec ts[2] = { wasiHostTimespec(atim), wasiHostTimespec(mtim) }; return fd >= 0 ? futimens(fd, ts) : utimensat(AT_FDCWD, path, ts, follow ? 0 : AT_SYMLINK_NOFOLLOW); } #define WASI_HOST_O_NOFOLLOW O_NOFOLLOW static int wasiHostOpen(const char* path, int flags) { return open(path, flags, 0666); } static void wasiHostClose(int fd) { if (fd >= 0) close(fd); } static int64_t wasiHostWrite(int fd, const void* buf, size_t len) { return write(fd, buf, len); } static int64_t wasiHostReadv(int fd, const WasiHostIov* iov, int iov_len) { return readv(fd, iov, iov_len); } static bool wasiHostSeekable(int fd) { return lseek(fd, 0, SEEK_CUR) != (off_t)-1; } static int64_t wasiHostPread(int fd, void* buf, size_t len, uint64_t pos) { return pread(fd, buf, len, (off_t)pos); } static int64_t wasiHostPwrite( int fd, const void* buf, size_t len, uint64_t pos) { return pwrite(fd, buf, len, (off_t)pos); } static int wasiHostTruncate(int fd, uint64_t size) { return ftruncate(fd, (off_t)size); } static int wasiHostMkdir(const char* path) { return mkdir(path, 0777); } static int wasiHostUnlink(const char* path) { return unlink(path); } static int wasiHostRmdir(const char* path) { return rmdir(path); } static int wasiHostLock(int fd) { int rc; do rc = flock(fd, LOCK_EX); while (rc != 0 && errno == EINTR); return rc; } static int wasiHostRename(const char* old_path, const char* new_path) { return rename(old_path, new_path); } // link(2) of `old_path` itself, a symbolic link too. static int wasiHostLink(const char* old_path, const char* new_path) { return linkat(AT_FDCWD, old_path, AT_FDCWD, new_path, 0); } // The real clock (id 0) or the monotonic one (1), or its resolution; both // clocks always exist. static uint64_t wasiHostClock(uint32_t id, bool res) { clockid_t clock = id == 0 ? CLOCK_REALTIME : CLOCK_MONOTONIC; struct timespec ts = { 0, 0 }; if (res) clock_getres(clock, &ts); else clock_gettime(clock, &ts); return wasiHostNs(ts); } static void wasiHostSleep(uint64_t ns) { struct timespec ts = wasiHostNsToTs(ns); while (nanosleep(&ts, &ts) != 0 && errno == EINTR) { } } // fdflags_t's declaration order is the bit order: append, DSYNC, NONBLOCK, // RSYNC, SYNC (wasi.zig). Only the two the host status flags carry are // reported. static uint64_t wasiHostFdFlags(int fd) { uint64_t fs_flags = 0; int status = fcntl(fd, F_GETFL); if (status >= 0) { if ((status & O_APPEND) != 0) fs_flags |= UINT64_C(1) << 0; if ((status & O_NONBLOCK) != 0) fs_flags |= UINT64_C(1) << 2; } return fs_flags; } // /dev/urandom, not upstream's time-seeded rand(): the census and test // harness start many zig1s at once over one cache, whose temporary names // come from random_get. Returns 0 or the host errno. static int wasiHostRandom(uint8_t* buf, size_t len) { int rand_fd = open("/dev/urandom", O_RDONLY); if (rand_fd < 0) return errno; int err = 0; size_t off = 0; while (off < len && err == 0) { ssize_t n = read(rand_fd, buf + off, len - off); if (n > 0) off += (size_t)n; else if (n == 0) err = EIO; else if (errno != EINTR) err = errno; } close(rand_fd); return err; } static void wasiHostStdioBinary(void) { } #endif // Descriptors the host opens for the guest are WASI_FD_BASE and up. #define WASI_FD_BASE 100u // Bits of rights_t, oflags_t and fdflags_t. #define WASI_RIGHT_FD_READ (UINT64_C(1) << 1) #define WASI_RIGHT_FD_WRITE (UINT64_C(1) << 6) #define WASI_OFLAG_CREAT 1u #define WASI_OFLAG_DIRECTORY 2u #define WASI_LOOKUP_SYMLINK_FOLLOW 1u #define WASI_OFLAG_EXCL 4u #define WASI_OFLAG_TRUNC 8u #define WASI_FDFLAG_APPEND 1u // fstflags_t's bits. #define WASI_FSTFLAG_ATIM 1u #define WASI_FSTFLAG_ATIM_NOW 2u #define WASI_FSTFLAG_MTIM 4u #define WASI_FSTFLAG_MTIM_NOW 8u // Argument `i` of the call as an i32: a wasm32 address, length or // descriptor. static uint32_t wasiArg32(const WasiCall* c, uint32_t i) { return (uint32_t)c->args[i]; } static void wasiWriteLE(uint8_t* p, uint64_t bits, uint32_t size) { for (uint32_t i = 0; i < size; i++) p[i] = (uint8_t)(bits >> (i * 8u)); } static uint32_t wasiReadLE32(const uint8_t* p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8u) | ((uint32_t)p[2] << 16u) | ((uint32_t)p[3] << 24u); } // Store a guest `usize` (or an `fd_t`, which is i32) at `addr`. static void wasiStoreU32(const WasiCall* c, uint32_t addr, uint32_t v) { wasiWriteLE(c->mem(addr, 4u), v, 4u); } // A preopened directory: its guest name and the host directory behind it. typedef struct { const char* name; const char* host_root; } WasiPreopen; // The preopens are fd 3 `.`, fd 4 `/lib` and fd 5 `/cache`, when the host // has one (upstream's wasi.c orders /cache before /lib; the guest finds them // by name). static bool wasiPreopenAt(const Wasi* wasi, uint32_t fd, WasiPreopen* out) { const WasiPreopen preopens[WASI_PREOPEN_COUNT] = { { ".", "." }, { "/lib", wasi->lib_dir != NULL ? wasi->lib_dir : "lib" }, { "/cache", wasi->global_cache }, }; if (fd < 3 || fd - 3 >= WASI_PREOPEN_COUNT || preopens[fd - 3].host_root == NULL) return false; *out = preopens[fd - 3]; return true; } // The slot of a descriptor the host opened, or NULL. static WasiFd* wasiFdSlot(Wasi* wasi, uint32_t fd) { if (fd < WASI_FD_BASE || fd - WASI_FD_BASE >= WASI_FD_CAP) return NULL; WasiFd* slot = &wasi->fds[fd - WASI_FD_BASE]; return slot->host_root != NULL ? slot : NULL; } // The host descriptor of a standard stream or of a file this host opened (a // directory has none). static bool wasiFdStreamHost(Wasi* wasi, uint32_t fd, int* out_host_fd) { if (fd <= 2) { *out_host_fd = (int)fd; return true; } const WasiFd* slot = wasiFdSlot(wasi, fd); if (slot == NULL || slot->host_fd < 0) return false; *out_host_fd = slot->host_fd; return true; } // The host descriptor of a file this host opened in `*out_host_fd`; // `stdio_err` for a standard stream (wasmtime's streams), else BADF (a // directory too). static uint32_t wasiFdFile( Wasi* wasi, uint32_t fd, uint32_t stdio_err, int* out_host_fd) { if (fd <= 2) return stdio_err; const WasiFd* slot = wasiFdSlot(wasi, fd); if (slot == NULL || slot->host_fd < 0) return WASI_ERRNO_BADF; *out_host_fd = slot->host_fd; return WASI_ERRNO_SUCCESS; } // The host path of preopen or opened descriptor `fd`, or NULL. static const char* wasiDirRoot(Wasi* wasi, uint32_t fd) { WasiPreopen preopen; if (wasiPreopenAt(wasi, fd, &preopen)) return preopen.host_root; const WasiFd* slot = wasiFdSlot(wasi, fd); return slot != NULL ? slot->host_root : NULL; } static bool wasiIsPathSep(char ch) { return ch == '/' || ch == '\\'; } // upstream's wasi.c isAbsPath, on every host: a leading separator or a // drive letter. static bool wasiIsAbsPath(const char* path, uint32_t path_len) { if (path_len >= 1 && wasiIsPathSep(path[0])) return true; return path_len >= 3 && path[1] == ':' && wasiIsPathSep(path[2]) && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')); } // The call's guest path at arguments `path_arg` (address) and `path_arg` + 1 // (length, in `*out_len`), or NULL when it holds a NUL. static const char* wasiGuestPath( const WasiCall* c, uint32_t path_arg, uint32_t* out_len) { *out_len = wasiArg32(c, path_arg + 1); const char* path = (const char*)c->mem(wasiArg32(c, path_arg), *out_len); return memchr(path, 0, *out_len) != NULL ? NULL : path; } // The host path in `*out` (caller frees) of the call's guest path, at // arguments `path_arg` (address) and `path_arg` + 1 (length), relative to // the directory fd at argument `fd_arg`: BADF when that is no directory of // the guest's, INVAL for a path holding a NUL (as wasmtime answers), which a // C string cannot carry. An absolute path is opened as is, as upstream's // wasi.c does (the dir fd only has to be a directory). static uint32_t wasiJoinHostPath( const WasiCall* c, uint32_t fd_arg, uint32_t path_arg, char** out) { const char* root = wasiDirRoot(c->wasi, wasiArg32(c, fd_arg)); if (root == NULL) return WASI_ERRNO_BADF; uint32_t path_len; const char* path = wasiGuestPath(c, path_arg, &path_len); if (path == NULL) return WASI_ERRNO_INVAL; *out = wasiIsAbsPath(path, path_len) ? zig1Strndup(path, path_len) : zig1PathJoin(root, path, path_len); return WASI_ERRNO_SUCCESS; } // The WASI errno of host errno `err`, 0 being success. static uint32_t wasiErrno(int err) { switch (err) { case 0: return WASI_ERRNO_SUCCESS; case EACCES: return WASI_ERRNO_ACCES; case EPERM: return WASI_ERRNO_PERM; case EBADF: return WASI_ERRNO_BADF; case EEXIST: return WASI_ERRNO_EXIST; case EINVAL: return WASI_ERRNO_INVAL; case EISDIR: return WASI_ERRNO_ISDIR; case ELOOP: return WASI_ERRNO_LOOP; case ENOENT: return WASI_ERRNO_NOENT; case ENOMEM: return WASI_ERRNO_NOMEM; case ENOTDIR: return WASI_ERRNO_NOTDIR; case EBUSY: return WASI_ERRNO_BUSY; case ENAMETOOLONG: return WASI_ERRNO_NAMETOOLONG; case ENOSPC: return WASI_ERRNO_NOSPC; case ENOTEMPTY: return WASI_ERRNO_NOTEMPTY; case EMLINK: return WASI_ERRNO_MLINK; case EOVERFLOW: return WASI_ERRNO_OVERFLOW; case ESPIPE: return WASI_ERRNO_SPIPE; case EROFS: return WASI_ERRNO_ROFS; case EXDEV: return WASI_ERRNO_XDEV; default: return WASI_ERRNO_IO; } } // Take a host descriptor and its path (owned) into a free slot. static bool wasiFdAlloc( Wasi* wasi, int host_fd, char* host_root, uint32_t* out_fd) { for (uint32_t i = 0; i < WASI_FD_CAP; i++) { if (wasi->fds[i].host_root == NULL) { wasi->fds[i] = (WasiFd) { .host_fd = host_fd, .host_root = host_root }; *out_fd = WASI_FD_BASE + i; return true; } } return false; } // The host side of a WASI fd: the host descriptor of a standard stream or of // a file this host opened, else (`*out_host_fd` -1) the path of a preopen or // of a directory it opened; false for no fd of the guest's. static bool wasiFdHost( Wasi* wasi, uint32_t fd, int* out_host_fd, const char** out_path) { *out_path = NULL; if (wasiFdStreamHost(wasi, fd, out_host_fd)) return true; *out_host_fd = -1; *out_path = wasiDirRoot(wasi, fd); return *out_path != NULL; } // Resolve a WASI fd to a host stat; on failure `*out_errno` is the WASI // errno, and `*out_host_fd` is -1 when the answer came from a directory's // path. static bool wasiStatFd(Wasi* wasi, uint32_t fd, WasiHostStat* st, int* out_host_fd, uint32_t* out_errno) { const char* dir; if (!wasiFdHost(wasi, fd, out_host_fd, &dir)) { *out_errno = WASI_ERRNO_BADF; return false; } int rc = wasiHostStat(*out_host_fd, dir, true, st); if (rc != 0) { *out_errno = wasiErrno(errno); return false; } return true; } // `filestat_t` (dev, ino, filetype, nlink, size, atim, mtim, ctim) at `p`; a // regular file or a symbolic link (its target's length) has a size. static void wasiFilestatWrite(uint8_t* p, const WasiHostStat* st) { wasiWriteLE(p + 0, st->dev, 8u); wasiWriteLE(p + 8, st->ino, 8u); wasiWriteLE(p + 16, st->filetype, 8u); wasiWriteLE(p + 24, st->nlink, 8u); bool sized = st->filetype == WASI_FILETYPE_REGULAR_FILE || st->filetype == WASI_FILETYPE_SYMBOLIC_LINK; wasiWriteLE(p + 32, sized ? st->size : 0u, 8u); wasiWriteLE(p + 40, st->atim, 8u); wasiWriteLE(p + 48, st->mtim, 8u); wasiWriteLE(p + 56, st->ctim, 8u); } int wasiDirError(const char* path) { WasiHostStat st; return wasiHostStat(-1, path, true, &st) != 0 ? errno : st.filetype == WASI_FILETYPE_DIRECTORY ? 0 : ENOTDIR; } // wasiDirError without following a final symbolic link, which is ELOOP as // open(2)'s O_NOFOLLOW answers. static int wasiDirErrorNoFollow(const char* path) { WasiHostStat st; return wasiHostStat(-1, path, false, &st) != 0 ? errno : st.filetype == WASI_FILETYPE_SYMBOLIC_LINK ? ELOOP : st.filetype == WASI_FILETYPE_DIRECTORY ? 0 : ENOTDIR; } char* resolveGlobalCacheDir(void) { const char* value = getenv("ZIG_GLOBAL_CACHE_DIR"); if (value != NULL) return zig1Strdup(value); #ifdef _WIN32 const char* local_app_data_dir = getenv("LOCALAPPDATA"); return local_app_data_dir == NULL ? NULL : zig1PathJoin(local_app_data_dir, "zig", 3); #else const char* cache_root = getenv("XDG_CACHE_HOME"); if (cache_root != NULL && cache_root[0] != '\0') return zig1PathJoin(cache_root, "zig", 3); const char* home = getenv("HOME"); if (home != NULL && home[0] != '\0') return zig1PathJoin(home, ".cache/zig", 10); return NULL; #endif } // Dir.createDirPath: each parent of `path`, then `path`, whose mkdir alone // decides; 0 or -1 with errno. static int wasiMkdirPath(char* path) { for (char* p = path + 1; *p != '\0'; p++) { if (!wasiIsPathSep(*p)) continue; char sep = *p; *p = '\0'; wasiHostMkdir(path); *p = sep; } return wasiHostMkdir(path) == 0 || errno == EEXIST ? 0 : -1; } bool wasiHostStart(const char* global_cache) { wasiHostStdioBinary(); char* path = zig1Strdup(global_cache); int rc = wasiMkdirPath(path); free(path); if (rc != 0) return false; errno = wasiDirError(global_cache); return errno == 0; } // Write all of `buf` to `host_fd`, at its position or, when `pos` is not // NULL, at `*pos`; `*total` and `*pos` advance by the bytes written. A WASI // errno. static uint32_t wasiWriteAll(int host_fd, const uint8_t* buf, uint32_t len, uint64_t* pos, uint32_t* total) { uint32_t off = 0; while (off < len) { int64_t n = pos != NULL ? wasiHostPwrite(host_fd, buf + off, len - off, *pos) : wasiHostWrite(host_fd, buf + off, len - off); if (n < 0) { if (errno == EINTR) continue; return wasiErrno(errno); } if (n == 0) return WASI_ERRNO_IO; off += (uint32_t)n; *total += (uint32_t)n; if (pos != NULL) *pos += (uint64_t)n; } return WASI_ERRNO_SUCCESS; } // fd_read is one scatter `readv` over the whole iovec array, as the guest's // posix sibling of its `fd_read` call is: filling each iovec to fullness // would block an interactive stream on read-ahead. #define WASI_IOV_MAX 8 // = std.Io.Threaded.max_iovecs_len // Decode guest iovec `i` of the array at `iovs` (wasi_ciovec_t/iovec_t: a // 4-byte LE buf pointer followed by a 4-byte LE length). static void wasiIovecAt(const WasiCall* c, uint32_t iovs, uint32_t i, uint32_t* base, uint32_t* len) { const uint8_t* p = c->mem(iovs + i * 8u, 8u); *base = wasiReadLE32(p); *len = wasiReadLE32(p + 4); } // Resolve the guest iovec array at `iovs` into host iovecs, mirroring the // array the guest itself builds: "if (iovecs_buffer.len - i == 0) break;" // drops the tail once the fixed array is full and "if (buf.len != 0)" drops // empty buffers, so a caller passing more or emptier buffers than the host // array holds simply gets a short read -- which every `Io.Reader` caller loops // on. Returns the host iovec count, at most `WASI_IOV_MAX`. static int wasiGatherIovs(const WasiCall* c, uint32_t iovs, uint32_t iovs_len, WasiHostIov* host_iov) { int n = 0; for (uint32_t i = 0; i < iovs_len && n < WASI_IOV_MAX; i++) { uint32_t base, len; wasiIovecAt(c, iovs, i, &base, &len); if (len == 0) continue; host_iov[n].iov_base = c->mem(base, len); host_iov[n].iov_len = len; n++; } return n; } static uint32_t wasiArgsGet(const WasiCall* c) { uint32_t argv_addr = wasiArg32(c, 0); uint32_t cursor = wasiArg32(c, 1); for (uint32_t i = 0; i < c->wasi->argc; i++) { const char* s = c->wasi->argv[i]; uint32_t size = (uint32_t)strlen(s) + 1u; wasiStoreU32(c, argv_addr + i * 4u, cursor); memcpy(c->mem(cursor, size), s, size); cursor += size; } return WASI_ERRNO_SUCCESS; } static uint32_t wasiArgsSizesGet(const WasiCall* c) { uint32_t argv_buf_size = 0; for (uint32_t i = 0; i < c->wasi->argc; i++) argv_buf_size += (uint32_t)strlen(c->wasi->argv[i]) + 1u; wasiStoreU32(c, wasiArg32(c, 0), c->wasi->argc); wasiStoreU32(c, wasiArg32(c, 1), argv_buf_size); return WASI_ERRNO_SUCCESS; } // An empty environment. static uint32_t wasiEnvironSizesGet(const WasiCall* c) { wasiStoreU32(c, wasiArg32(c, 0), 0u); wasiStoreU32(c, wasiArg32(c, 1), 0u); return WASI_ERRNO_SUCCESS; } static uint32_t wasiRandomGet(const WasiCall* c) { uint32_t buf_len = wasiArg32(c, 1); uint8_t* buf = c->mem(wasiArg32(c, 0), buf_len); return wasiErrno(wasiHostRandom(buf, buf_len)); } static uint32_t wasiFdFilestatGet(const WasiCall* c) { WasiHostStat st; int host_fd; uint32_t err; if (!wasiStatFd(c->wasi, wasiArg32(c, 0), &st, &host_fd, &err)) return err; wasiFilestatWrite(c->mem(wasiArg32(c, 1), 64u), &st); return WASI_ERRNO_SUCCESS; } // `fdstat_t` (wasi.zig): fs_filetype (`filetype_t`, enum(u8)) at 0, // fs_flags (`fdflags_t`, packed struct(u16)) at 2, fs_rights_base // (`rights_t`, packed struct(u64)) at 8, fs_rights_inheriting at 16. static uint32_t wasiFdFdstatGet(const WasiCall* c) { WasiHostStat st; int host_fd; uint32_t err; if (!wasiStatFd(c->wasi, wasiArg32(c, 0), &st, &host_fd, &err)) return err; uint64_t fs_flags = host_fd >= 0 ? wasiHostFdFlags(host_fd) : 0u; // rights_t's declaration order is likewise the bit order, and its // last named right (SOCK_ACCEPT) is bit 29 with `_: u34` above it, so // "every right this shim can service" is bits 0..29. FD_SEEK (bit 2) // and FD_TELL (bit 5) are withheld from a descriptor the host cannot // seek: that pair is the wasi arm of `std.Io`'s tty test -- "a tty is // a character device that we can't seek or tell on" // (lib/std/Io/Threaded.zig). A directory has no host fd to probe and // is seekable by construction. uint64_t rights = (UINT64_C(1) << 30) - 1u; if (host_fd >= 0 && !wasiHostSeekable(host_fd)) rights &= ~((UINT64_C(1) << 2) | (UINT64_C(1) << 5)); uint8_t* p = c->mem(wasiArg32(c, 1), 24u); wasiWriteLE(p, 0u, 8u); // + padding wasiWriteLE(p, st.filetype, 1u); wasiWriteLE(p + 2, fs_flags, 2u); wasiWriteLE(p + 8, rights, 8u); wasiWriteLE(p + 16, rights, 8u); return WASI_ERRNO_SUCCESS; } static uint32_t wasiFdFilestatSetSize(const WasiCall* c) { const WasiFd* slot = wasiFdSlot(c->wasi, wasiArg32(c, 0)); if (slot == NULL) return WASI_ERRNO_BADF; if (wasiHostTruncate(slot->host_fd, c->args[1]) != 0) return wasiErrno(errno); return WASI_ERRNO_SUCCESS; } // The time fstflags_t `flags` sets with bits `set` (to `ns`) and `now`. static uint64_t wasiTimeArg( uint64_t ns, uint32_t flags, uint32_t set, uint32_t now) { return (flags & now) != 0 ? WASI_HOST_TIME_NOW : (flags & set) != 0 ? ns : WASI_HOST_TIME_OMIT; } // Set the times of `host_fd`, else of `path`, from the call's atim, mtim // and fstflags at arguments `i`, `i` + 1 and `i` + 2; a time set both to a // timestamp and to now is INVAL, as wasmtime answers. static uint32_t wasiSetTimes(const WasiCall* c, uint32_t i, int host_fd, const char* path, bool follow) { const uint32_t both_atim = WASI_FSTFLAG_ATIM | WASI_FSTFLAG_ATIM_NOW; const uint32_t both_mtim = WASI_FSTFLAG_MTIM | WASI_FSTFLAG_MTIM_NOW; uint32_t flags = wasiArg32(c, i + 2); if ((flags & both_atim) == both_atim || (flags & both_mtim) == both_mtim) return WASI_ERRNO_INVAL; uint64_t atim = wasiTimeArg( c->args[i], flags, WASI_FSTFLAG_ATIM, WASI_FSTFLAG_ATIM_NOW); uint64_t mtim = wasiTimeArg( c->args[i + 1], flags, WASI_FSTFLAG_MTIM, WASI_FSTFLAG_MTIM_NOW); if (wasiHostSetTimes(host_fd, path, follow, atim, mtim) != 0) return wasiErrno(errno); return WASI_ERRNO_SUCCESS; } // `fd_filestat_set_times(fd, atim, mtim, fst_flags)`; a directory's by its // path, a standard stream's BADF, as wasmtime answers. static uint32_t wasiFdFilestatSetTimes(const WasiCall* c) { int host_fd; const char* path; if (wasiArg32(c, 0) <= 2 || !wasiFdHost(c->wasi, wasiArg32(c, 0), &host_fd, &path)) return WASI_ERRNO_BADF; return wasiSetTimes(c, 1, host_fd, path, true); } // `path_filestat_set_times(fd, flags, path, path_len, atim, mtim, // fst_flags)`. static uint32_t wasiPathFilestatSetTimes(const WasiCall* c) { char* host_path; uint32_t err = wasiJoinHostPath(c, 0, 2, &host_path); if (err != WASI_ERRNO_SUCCESS) return err; err = wasiSetTimes(c, 4, -1, host_path, (wasiArg32(c, 1) & WASI_LOOKUP_SYMLINK_FOLLOW) != 0); free(host_path); return err; } // whence_t's values. #define WASI_WHENCE_CUR 1u // lseek(2) of the file at argument 0, the new offset stored at argument // `out_arg`; a directory has no offset (BADF) and a standard stream is SPIPE, // as wasmtime answers. static uint32_t wasiSeek( const WasiCall* c, int64_t offset, uint32_t whence, uint32_t out_arg) { int host_fd; uint32_t err = wasiFdFile(c->wasi, wasiArg32(c, 0), WASI_ERRNO_SPIPE, &host_fd); if (err != WASI_ERRNO_SUCCESS) return err; // whence_t: SET, CUR, END. const int host_whence[] = { SEEK_SET, SEEK_CUR, SEEK_END }; if (whence > 2) return WASI_ERRNO_INVAL; int64_t pos = lseek(host_fd, offset, host_whence[whence]); if (pos < 0) return wasiErrno(errno); wasiWriteLE(c->mem(wasiArg32(c, out_arg), 8u), (uint64_t)pos, 8u); return WASI_ERRNO_SUCCESS; } // `fd_seek(fd, offset, whence, newoffset)`. static uint32_t wasiFdSeek(const WasiCall* c) { return wasiSeek(c, (int64_t)c->args[1], wasiArg32(c, 2), 3); } // `fd_tell(fd, offset)`. static uint32_t wasiFdTell(const WasiCall* c) { return wasiSeek(c, 0, WASI_WHENCE_CUR, 1); } // fd_sync and fd_datasync: fsync(2) of a file; a directory or a standard // stream is BADF, as wasmtime answers. static uint32_t wasiFdSync(const WasiCall* c) { int host_fd; uint32_t err = wasiFdFile(c->wasi, wasiArg32(c, 0), WASI_ERRNO_BADF, &host_fd); if (err != WASI_ERRNO_SUCCESS) return err; return fsync(host_fd) != 0 ? wasiErrno(errno) : WASI_ERRNO_SUCCESS; } // clock_res_get and clock_time_get of the clock at argument 0, stored at // argument `out_arg`; wasmtime has no CPU-time clocks (BADF). static uint32_t wasiClock(const WasiCall* c, bool res, uint32_t out_arg) { uint32_t id = wasiArg32(c, 0); if (id > 1) return WASI_ERRNO_BADF; wasiWriteLE(c->mem(wasiArg32(c, out_arg), 8u), wasiHostClock(id, res), 8u); return WASI_ERRNO_SUCCESS; } // `clock_res_get(id, resolution)`. static uint32_t wasiClockResGet(const WasiCall* c) { return wasiClock(c, true, 1); } // `clock_time_get(id, precision, time)`. static uint32_t wasiClockTimeGet(const WasiCall* c) { return wasiClock(c, false, 2); } // `poll_oneoff` for one clock subscription (std's sleepWasi), slept through: // subscription_t and event_t keep userdata at 0, the clock its fields at 16. static uint32_t wasiPollOneoff(const WasiCall* c) { uint32_t nsubscriptions = wasiArg32(c, 2); if (nsubscriptions == 0) return WASI_ERRNO_INVAL; const uint8_t* sub = c->mem(wasiArg32(c, 0), 48u); if (nsubscriptions != 1 || sub[8] != 0) UNIMPLEMENTED("wasm32: poll_oneoff on more than one clock"); uint32_t id = wasiReadLE32(sub + 16); uint64_t timeout = wasiReadLE32(sub + 24) | ((uint64_t)wasiReadLE32(sub + 28) << 32u); if ((sub[40] & 1u) != 0) { // subclockflags_t ABSTIME if (id > 1) UNIMPLEMENTED("wasm32: poll_oneoff until a CPU-time clock"); uint64_t now = wasiHostClock(id, false); timeout = timeout > now ? timeout - now : 0u; } wasiHostSleep(timeout); uint8_t* event = c->mem(wasiArg32(c, 1), 32u); memset(event, 0, 32u); memcpy(event, sub, 8u); wasiStoreU32(c, wasiArg32(c, 3), 1u); return WASI_ERRNO_SUCCESS; } static uint32_t wasiFdWrite(const WasiCall* c) { int host_fd; if (!wasiFdStreamHost(c->wasi, wasiArg32(c, 0), &host_fd)) return WASI_ERRNO_BADF; uint32_t iovs = wasiArg32(c, 1); uint32_t iovs_len = wasiArg32(c, 2); uint32_t total = 0; for (uint32_t i = 0; i < iovs_len; i++) { uint32_t base, len; wasiIovecAt(c, iovs, i, &base, &len); uint32_t err = wasiWriteAll(host_fd, c->mem(base, len), len, NULL, &total); if (err != WASI_ERRNO_SUCCESS) return err; } wasiStoreU32(c, wasiArg32(c, 3), total); return WASI_ERRNO_SUCCESS; } // Positional, one write per iovec at the running offset; `pwrite` leaves // the fd position alone. static uint32_t wasiFdPwrite(const WasiCall* c) { int host_fd; uint32_t err = wasiFdFile(c->wasi, wasiArg32(c, 0), WASI_ERRNO_SPIPE, &host_fd); if (err != WASI_ERRNO_SUCCESS) return err; uint32_t iovs = wasiArg32(c, 1); uint32_t iovs_len = wasiArg32(c, 2); uint64_t pos = c->args[3]; uint32_t total = 0; for (uint32_t i = 0; i < iovs_len; i++) { uint32_t base, len; wasiIovecAt(c, iovs, i, &base, &len); err = wasiWriteAll(host_fd, c->mem(base, len), len, &pos, &total); if (err != WASI_ERRNO_SUCCESS) return err; } wasiStoreU32(c, wasiArg32(c, 4), total); return WASI_ERRNO_SUCCESS; } static uint32_t wasiFdRead(const WasiCall* c) { int host_fd; // Standard streams map straight through: the upstream test runner's // `--listen=-` server mode reads its client on fd 0. if (!wasiFdStreamHost(c->wasi, wasiArg32(c, 0), &host_fd)) return WASI_ERRNO_BADF; WasiHostIov host_iov[WASI_IOV_MAX]; int host_iov_len = wasiGatherIovs(c, wasiArg32(c, 1), wasiArg32(c, 2), host_iov); int64_t total = 0; if (host_iov_len != 0) { do { total = wasiHostReadv(host_fd, host_iov, host_iov_len); } while (total < 0 && errno == EINTR); if (total < 0) return wasiErrno(errno); } wasiStoreU32(c, wasiArg32(c, 3), (uint32_t)total); return WASI_ERRNO_SUCCESS; } // Positional, one read per iovec at the running offset, as upstream's // stage1/wasi.c reads them; `pread` leaves the fd position alone. A short // read ends the scatter, and an error after some bytes reports those bytes, // as a scatter read does. static uint32_t wasiFdPread(const WasiCall* c) { int host_fd; uint32_t err = wasiFdFile(c->wasi, wasiArg32(c, 0), WASI_ERRNO_SPIPE, &host_fd); if (err != WASI_ERRNO_SUCCESS) return err; uint64_t offset = c->args[3]; WasiHostIov host_iov[WASI_IOV_MAX]; int host_iov_len = wasiGatherIovs(c, wasiArg32(c, 1), wasiArg32(c, 2), host_iov); uint32_t total = 0; for (int k = 0; k < host_iov_len; k++) { int64_t n; do { n = wasiHostPread(host_fd, host_iov[k].iov_base, host_iov[k].iov_len, offset + total); } while (n < 0 && errno == EINTR); if (n < 0) { if (total != 0) break; return wasiErrno(errno); } total += (uint32_t)n; if ((size_t)n < host_iov[k].iov_len) break; } wasiStoreU32(c, wasiArg32(c, 4), total); return WASI_ERRNO_SUCCESS; } static void wasiDirEntriesFree(WasiDirEntries* entries) { if (entries == NULL) return; for (uint32_t i = 0; i < entries->len; i++) free(entries->names[i]); free(entries->names); free(entries->inos); free(entries->types); free(entries); } void wasiPreopenEntriesFree(Wasi* wasi) { for (uint32_t i = 0; i < WASI_PREOPEN_COUNT; i++) { wasiDirEntriesFree(wasi->preopen_entries[i]); wasi->preopen_entries[i] = NULL; } } static uint32_t wasiFdClose(const WasiCall* c) { WasiFd* slot = wasiFdSlot(c->wasi, wasiArg32(c, 0)); if (slot == NULL) return WASI_ERRNO_BADF; wasiHostClose(slot->host_fd); wasiDirEntriesFree(slot->entries); free(slot->host_root); *slot = (WasiFd) { .host_fd = -1 }; return WASI_ERRNO_SUCCESS; } static uint32_t wasiFdPrestatGet(const WasiCall* c) { WasiPreopen preopen; if (!wasiPreopenAt(c->wasi, wasiArg32(c, 0), &preopen)) return WASI_ERRNO_BADF; uint8_t* p = c->mem(wasiArg32(c, 1), 8u); wasiWriteLE(p, 0u, 1u); // preopentype_t.DIR wasiWriteLE(p + 4, strlen(preopen.name), 4u); return WASI_ERRNO_SUCCESS; } static uint32_t wasiFdPrestatDirName(const WasiCall* c) { WasiPreopen preopen; if (!wasiPreopenAt(c->wasi, wasiArg32(c, 0), &preopen)) return WASI_ERRNO_BADF; size_t name_len = strlen(preopen.name); memcpy( c->mem(wasiArg32(c, 1), (uint32_t)name_len), preopen.name, name_len); return WASI_ERRNO_SUCCESS; } static uint32_t wasiPathFilestatGet(const WasiCall* c) { char* host_path; uint32_t join_err = wasiJoinHostPath(c, 0, 2, &host_path); if (join_err != WASI_ERRNO_SUCCESS) return join_err; WasiHostStat st; int rc = wasiHostStat(-1, host_path, (wasiArg32(c, 1) & WASI_LOOKUP_SYMLINK_FOLLOW) != 0, &st); free(host_path); if (rc != 0) return wasiErrno(errno); wasiFilestatWrite(c->mem(wasiArg32(c, 4), 64u), &st); return WASI_ERRNO_SUCCESS; } // Whether `host_path` is a cache file: in a `.zig-cache` directory (the // local cache resolveSuitableLocalCacheDir picks), or below the guest's // /cache, which one of its parent directories then is by (dev, ino). static bool wasiIsCacheFile(const Wasi* wasi, const char* host_path) { const char* name = ".zig-cache"; const size_t name_len = strlen(name); for (const char* p = strstr(host_path, name); p != NULL; p = strstr(p + 1, name)) { if ((p == host_path || wasiIsPathSep(p[-1])) && (p[name_len] == '\0' || wasiIsPathSep(p[name_len]))) return true; } WasiHostStat cache, dir, prev = { 0 }; if (wasi->global_cache == NULL || wasiHostStat(-1, wasi->global_cache, true, &cache) != 0) return false; size_t parent_len = strlen(host_path); while (parent_len != 0 && !wasiIsPathSep(host_path[parent_len - 1])) parent_len--; char* path = parent_len != 0 ? zig1Strndup(host_path, parent_len) : zig1Strdup("."); bool found = false; // Up to the root, the first directory that is its own parent. while (!found && wasiHostStat(-1, path, true, &dir) == 0 && (dir.dev != prev.dev || dir.ino != prev.ino)) { found = dir.dev == cache.dev && dir.ino == cache.ino; prev = dir; char* up = zig1PathJoin(path, "..", 2); free(path); path = up; } free(path); return found; } // Lock cache file `host_fd` exclusively unless this process holds its lock // already, which would deadlock: 1 when it took the lock (the file's identity // in `*st`), 0 when it took none, -1 with errno set when locking failed. static int wasiLockCacheFile( const Wasi* wasi, int host_fd, const char* host_path, WasiHostStat* st) { if (!wasiIsCacheFile(wasi, host_path) || wasiHostStat(host_fd, NULL, true, st) != 0) return 0; for (uint32_t i = 0; i < WASI_FD_CAP; i++) { const WasiFd* held = &wasi->fds[i]; if (held->host_root != NULL && held->locked && held->dev == st->dev && held->ino == st->ino) return 0; } return wasiHostLock(host_fd) == 0 ? 1 : -1; } // A directory is opened by its path alone, as upstream's wasi.c does, so it // has no host fd. A cache file opened to write without truncating is locked // until closed, the `.lock` WASI cannot carry that Cache.Manifest and // Zcu.updateFile ask for. static uint32_t wasiPathOpen(const WasiCall* c) { char* host_path; uint32_t join_err = wasiJoinHostPath(c, 0, 2, &host_path); if (join_err != WASI_ERRNO_SUCCESS) return join_err; // lookupflags_t: without symlink_follow the last component is not // followed. bool follow = (wasiArg32(c, 1) & WASI_LOOKUP_SYMLINK_FOLLOW) != 0; uint32_t oflags = wasiArg32(c, 4); uint64_t rights = c->args[5]; uint32_t fdflags = wasiArg32(c, 7); int host_fd = -1; int err = 0; WasiHostStat lock_st; bool locked = false; if ((oflags & WASI_OFLAG_DIRECTORY) != 0) { err = follow ? wasiDirError(host_path) : wasiDirErrorNoFollow(host_path); } else { bool want_write = (rights & WASI_RIGHT_FD_WRITE) != 0; bool want_read = (rights & WASI_RIGHT_FD_READ) != 0; int flags = want_write ? (want_read ? O_RDWR : O_WRONLY) : O_RDONLY; if ((oflags & WASI_OFLAG_CREAT) != 0) flags |= O_CREAT; if ((oflags & WASI_OFLAG_EXCL) != 0) flags |= O_EXCL; if ((oflags & WASI_OFLAG_TRUNC) != 0) flags |= O_TRUNC; if ((fdflags & WASI_FDFLAG_APPEND) != 0) flags |= O_APPEND; if (!follow) flags |= WASI_HOST_O_NOFOLLOW; host_fd = wasiHostOpen(host_path, flags); if (host_fd < 0) { err = errno; } else if (want_write && (oflags & WASI_OFLAG_TRUNC) == 0) { int lock = wasiLockCacheFile(c->wasi, host_fd, host_path, &lock_st); locked = lock > 0; if (lock < 0) { err = errno; wasiHostClose(host_fd); } } } if (err != 0) { free(host_path); return wasiErrno(err); } uint32_t wasi_fd; if (!wasiFdAlloc(c->wasi, host_fd, host_path, &wasi_fd)) { wasiHostClose(host_fd); free(host_path); return WASI_ERRNO_MFILE; } if (locked) { WasiFd* slot = &c->wasi->fds[wasi_fd - WASI_FD_BASE]; slot->locked = true; slot->dev = lock_st.dev; slot->ino = lock_st.ino; } wasiStoreU32(c, wasiArg32(c, 8), wasi_fd); return WASI_ERRNO_SUCCESS; } static uint32_t wasiPathCreateDirectory(const WasiCall* c) { char* host_path; uint32_t join_err = wasiJoinHostPath(c, 0, 1, &host_path); if (join_err != WASI_ERRNO_SUCCESS) return join_err; int rc = wasiHostMkdir(host_path); free(host_path); return rc != 0 ? wasiErrno(errno) : WASI_ERRNO_SUCCESS; } // `op` from the call's old path (dir fd at argument 0, path at // `old_path_arg`) to its new path (dir fd at `new_fd_arg`, path after it). static uint32_t wasiPathPair(const WasiCall* c, uint32_t old_path_arg, uint32_t new_fd_arg, int (*op)(const char*, const char*)) { char* old_host = NULL; char* new_host = NULL; uint32_t err = wasiJoinHostPath(c, 0, old_path_arg, &old_host); if (err == WASI_ERRNO_SUCCESS) err = wasiJoinHostPath(c, new_fd_arg, new_fd_arg + 1, &new_host); if (err == WASI_ERRNO_SUCCESS && op(old_host, new_host) != 0) err = wasiErrno(errno); free(old_host); free(new_host); return err; } // `path_rename(old_fd, old_path, old_path_len, new_fd, new_path, // new_path_len)`: rename(2) between two directory-relative paths. static uint32_t wasiPathRename(const WasiCall* c) { return wasiPathPair(c, 1, 3, wasiHostRename); } // `path_link(old_fd, old_flags, old_path, old_path_len, new_fd, new_path, // new_path_len)`: a hard link to the old path itself; wasmtime refuses // lookupflags.symlink_follow. static uint32_t wasiPathLink(const WasiCall* c) { if ((wasiArg32(c, 1) & WASI_LOOKUP_SYMLINK_FOLLOW) != 0) return WASI_ERRNO_INVAL; return wasiPathPair(c, 2, 4, wasiHostLink); } // `path_symlink(old_path, old_path_len, fd, new_path, new_path_len)`: a // symbolic link holding the old path as given. static uint32_t wasiPathSymlink(const WasiCall* c) { #ifdef _WIN32 (void)c; UNIMPLEMENTED("wasm32: path_symlink on a Windows host"); ZIG1_TCC_RETURN(WASI_ERRNO_IO); #else uint32_t target_len; const char* target = wasiGuestPath(c, 0, &target_len); if (target == NULL) return WASI_ERRNO_INVAL; char* host_path; uint32_t err = wasiJoinHostPath(c, 2, 3, &host_path); if (err != WASI_ERRNO_SUCCESS) return err; char* host_target = zig1Strndup(target, target_len); if (symlink(host_target, host_path) != 0) err = wasiErrno(errno); free(host_target); free(host_path); return err; #endif } // `path_readlink(fd, path, path_len, buf, buf_len, bufused)`: the link's // contents truncated to `buf_len`, as wasmtime answers; an empty buffer // still checks that the path is a link. static uint32_t wasiPathReadlink(const WasiCall* c) { #ifdef _WIN32 (void)c; UNIMPLEMENTED("wasm32: path_readlink on a Windows host"); ZIG1_TCC_RETURN(WASI_ERRNO_IO); #else char* host_path; uint32_t err = wasiJoinHostPath(c, 0, 1, &host_path); if (err != WASI_ERRNO_SUCCESS) return err; uint32_t buf_len = wasiArg32(c, 4); char byte; char* buf = buf_len != 0 ? (char*)c->mem(wasiArg32(c, 3), buf_len) : &byte; ssize_t n = readlink(host_path, buf, buf_len != 0 ? buf_len : 1u); free(host_path); if (n < 0) return wasiErrno(errno); wasiStoreU32(c, wasiArg32(c, 5), buf_len != 0 ? (uint32_t)n : 0u); return WASI_ERRNO_SUCCESS; #endif } // `path_unlink_file(fd, path, path_len)`: unlink(2) of a directory-relative // path, upstream's DirEntry_unlink; a directory is EISDIR, a symbolic link is // itself unlinked. static uint32_t wasiPathUnlinkFile(const WasiCall* c) { char* host_path; uint32_t join_err = wasiJoinHostPath(c, 0, 1, &host_path); if (join_err != WASI_ERRNO_SUCCESS) return join_err; WasiHostStat st; uint32_t err = WASI_ERRNO_SUCCESS; if (wasiHostStat(-1, host_path, false, &st) != 0) err = wasiErrno(errno); else if (st.filetype == WASI_FILETYPE_DIRECTORY) err = wasiErrno(EISDIR); else if (wasiHostUnlink(host_path) != 0) err = wasiErrno(errno); free(host_path); return err; } // `path_remove_directory(fd, path, path_len)`: rmdir(2) of a // directory-relative path. static uint32_t wasiPathRemoveDirectory(const WasiCall* c) { char* host_path; uint32_t join_err = wasiJoinHostPath(c, 0, 1, &host_path); if (join_err != WASI_ERRNO_SUCCESS) return join_err; int rc = wasiHostRmdir(host_path); free(host_path); return rc != 0 ? wasiErrno(errno) : WASI_ERRNO_SUCCESS; } // The directory's entries, each with its filetype_t from lstat so that a // symbolic link is one; NULL with errno set when it cannot be read. static WasiDirEntries* wasiDirEntriesRead(const char* host_root) { #ifdef _WIN32 (void)host_root; UNIMPLEMENTED("wasm32: fd_readdir on a Windows host"); ZIG1_TCC_RETURN(NULL); #else DIR* d = opendir(host_root); if (d == NULL) return NULL; WasiDirEntries* entries = zig1Calloc(1, sizeof(WasiDirEntries)); uint32_t cap = 0; while (true) { errno = 0; const struct dirent* e = readdir(d); if (e == NULL) break; if (entries->len == cap) { cap = zig1GrowCap(cap, 16u); entries->names = zig1Realloc(entries->names, (size_t)cap * sizeof(char*)); entries->inos = zig1Realloc(entries->inos, (size_t)cap * sizeof(uint64_t)); entries->types = zig1Realloc(entries->types, cap); } const size_t namlen = strlen(e->d_name); char* entry_path = zig1PathJoin(host_root, e->d_name, namlen); WasiHostStat st; const uint8_t type = wasiHostStat(-1, entry_path, false, &st) == 0 ? st.filetype : (uint8_t)WASI_FILETYPE_UNKNOWN; free(entry_path); entries->names[entries->len] = zig1Strndup(e->d_name, namlen); entries->inos[entries->len] = (uint64_t)e->d_ino; entries->types[entries->len] = type; entries->len++; } const int err = errno; closedir(d); if (err != 0) { wasiDirEntriesFree(entries); errno = err; return NULL; } return entries; #endif } // `fd_readdir(fd, buf, buf_len, cookie, res_size)`: the directory's // entries from index `cookie` on as `dirent_t {next, ino, namlen, type}` // records, each followed by its name, the last one truncated at `buf_len`. // A preopen's entries are kept in the host, an opened directory's in its // slot. static uint32_t wasiFdReaddir(const WasiCall* c) { const uint32_t fd = wasiArg32(c, 0); WasiPreopen preopen; const char* root; WasiDirEntries** listed; WasiFd* slot = wasiFdSlot(c->wasi, fd); if (wasiPreopenAt(c->wasi, fd, &preopen)) { root = preopen.host_root; listed = &c->wasi->preopen_entries[fd - 3]; } else if (slot != NULL && slot->host_fd == -1) { root = slot->host_root; listed = &slot->entries; } else { return WASI_ERRNO_BADF; } const uint64_t cookie = c->args[3]; if (cookie == 0 || *listed == NULL) { wasiDirEntriesFree(*listed); *listed = wasiDirEntriesRead(root); if (*listed == NULL) return wasiErrno(errno); } const WasiDirEntries* entries = *listed; const uint32_t buf_len = wasiArg32(c, 2); uint8_t* out = c->mem(wasiArg32(c, 1), buf_len); uint32_t used = 0; for (uint64_t i = cookie; i < entries->len && used < buf_len; i++) { const uint32_t namlen = (uint32_t)strlen(entries->names[i]); uint8_t rec[24] = { 0 }; wasiWriteLE(rec, i + 1u, 8u); wasiWriteLE(rec + 8, entries->inos[i], 8u); wasiWriteLE(rec + 16, namlen, 4u); rec[20] = entries->types[i]; for (uint32_t k = 0; k < sizeof rec && used < buf_len; k++) out[used++] = rec[k]; for (uint32_t k = 0; k < namlen && used < buf_len; k++) out[used++] = (uint8_t)entries->names[i][k]; } wasiStoreU32(c, wasiArg32(c, 4), used); return WASI_ERRNO_SUCCESS; } static uint32_t wasiProcExit(const WasiCall* c) { return (uint32_t)(c->args[0] & 0xffu); } // Every import the host serves, with lib/std/os/wasi.zig's parameter count; // the interpreter aborts on any other, naming it. static const WasiImport wasi_imports[] = { { "args_get", 2, true, wasiArgsGet }, { "args_sizes_get", 2, true, wasiArgsSizesGet }, { "clock_res_get", 2, true, wasiClockResGet }, { "clock_time_get", 3, true, wasiClockTimeGet }, { "environ_get", 2, true, NULL }, { "environ_sizes_get", 2, true, wasiEnvironSizesGet }, { "random_get", 2, true, wasiRandomGet }, { "sched_yield", 0, true, NULL }, { "fd_close", 1, true, wasiFdClose }, { "fd_datasync", 1, true, wasiFdSync }, { "fd_fdstat_get", 2, true, wasiFdFdstatGet }, { "fd_filestat_get", 2, true, wasiFdFilestatGet }, { "fd_filestat_set_size", 2, true, wasiFdFilestatSetSize }, { "fd_filestat_set_times", 4, true, wasiFdFilestatSetTimes }, { "fd_pread", 5, true, wasiFdPread }, { "fd_prestat_dir_name", 3, true, wasiFdPrestatDirName }, { "fd_prestat_get", 2, true, wasiFdPrestatGet }, { "fd_pwrite", 5, true, wasiFdPwrite }, { "fd_read", 4, true, wasiFdRead }, { "fd_readdir", 5, true, wasiFdReaddir }, { "fd_seek", 4, true, wasiFdSeek }, { "fd_sync", 1, true, wasiFdSync }, { "fd_tell", 2, true, wasiFdTell }, { "fd_write", 4, true, wasiFdWrite }, { "path_create_directory", 3, true, wasiPathCreateDirectory }, { "path_filestat_get", 5, true, wasiPathFilestatGet }, { "path_filestat_set_times", 7, true, wasiPathFilestatSetTimes }, { "path_link", 7, true, wasiPathLink }, { "path_open", 9, true, wasiPathOpen }, { "path_readlink", 6, true, wasiPathReadlink }, { "path_remove_directory", 3, true, wasiPathRemoveDirectory }, { "path_rename", 6, true, wasiPathRename }, { "path_symlink", 5, true, wasiPathSymlink }, { "path_unlink_file", 3, true, wasiPathUnlinkFile }, { "poll_oneoff", 4, true, wasiPollOneoff }, { "proc_exit", 1, false, wasiProcExit }, }; const WasiImport* wasiImportFind(const char* name) { for (size_t i = 0; i < sizeof wasi_imports / sizeof wasi_imports[0]; i++) if (strcmp(wasi_imports[i].name, name) == 0) return &wasi_imports[i]; return NULL; } // This file's macros end with it (zig1.c is one translation unit). #undef WASI_FD_BASE #undef WASI_RIGHT_FD_READ #undef WASI_RIGHT_FD_WRITE #undef WASI_OFLAG_CREAT #undef WASI_OFLAG_DIRECTORY #undef WASI_LOOKUP_SYMLINK_FOLLOW #undef WASI_HOST_O_NOFOLLOW #undef WASI_OFLAG_EXCL #undef WASI_OFLAG_TRUNC #undef WASI_FDFLAG_APPEND #undef WASI_FSTFLAG_ATIM #undef WASI_FSTFLAG_ATIM_NOW #undef WASI_FSTFLAG_MTIM #undef WASI_FSTFLAG_MTIM_NOW #undef WASI_HOST_TIME_NOW #undef WASI_HOST_TIME_OMIT #undef WASI_HOST_EPOCH_TICKS #undef WASI_IOV_MAX #undef WASI_WHENCE_CUR #ifdef _WIN32 #undef lseek #undef fsync #endif /* ---- end wasi.c ---- */ /* ---- begin wyhash.c ---- */ // wyhash.c — ported from lib/std/hash/wyhash.zig. #ifndef ZIG1_COMMON_H #include "common.h" #endif #ifndef ZIG1_WYHASH_H #include "wyhash.h" #endif #include // Wyhash.secret, Zig's constants (not the reference C wyhash's). static const uint64_t secret[4] = { UINT64_C(0xa0761d6478bd642f), UINT64_C(0xe7037ed1a0b428db), UINT64_C(0x8ebc6af09c88c6e3), UINT64_C(0x589965cc75374cc3), }; // Wyhash.mum: the full 64x64->128 product, low half to `a`, high half to // `b`, built from 32-bit halves since C99 has no 128-bit integer. static void wyhashMum(uint64_t* a, uint64_t* b) { uint64_t ha = *a >> 32; uint64_t la = *a & UINT64_C(0xFFFFFFFF); uint64_t hb = *b >> 32; uint64_t lb = *b & UINT64_C(0xFFFFFFFF); uint64_t rh = ha * hb; uint64_t rm0 = ha * lb; uint64_t rm1 = hb * la; uint64_t rl = la * lb; uint64_t t = rl + (rm0 << 32); uint64_t c = (uint64_t)(t < rl); uint64_t lo = t + (rm1 << 32); c += (uint64_t)(lo < t); uint64_t hi = rh + (rm0 >> 32) + (rm1 >> 32) + c; *a = lo; *b = hi; } static uint64_t wyhashMix(uint64_t a, uint64_t b) { wyhashMum(&a, &b); return a ^ b; } static uint64_t wyhashRead8(const uint8_t* p) { uint64_t v; memcpy(&v, p, 8); return v; } static uint64_t wyhashRead4(const uint8_t* p) { uint32_t v; memcpy(&v, p, 4); return (uint64_t)v; } static void wyhashSmallKey(Wyhash* h, const uint8_t* input, uint64_t len) { if (len >= 4) { uint64_t end = len - 4; uint64_t quarter = (len >> 3) << 2; h->a = (wyhashRead4(input) << 32) | wyhashRead4(input + quarter); h->b = (wyhashRead4(input + end) << 32) | wyhashRead4(input + end - quarter); } else if (len > 0) { h->a = ((uint64_t)input[0] << 16) | ((uint64_t)input[len >> 1] << 8) | input[len - 1]; h->b = 0; } else { h->a = 0; h->b = 0; } } static void wyhashRound(Wyhash* h, const uint8_t* input) { int i; for (i = 0; i < 3; i++) { uint64_t a = wyhashRead8(input + (uint64_t)(8 * (2 * i))); uint64_t b = wyhashRead8(input + (uint64_t)(8 * (2 * i + 1))); h->state[i] = wyhashMix(a ^ secret[i + 1], b ^ h->state[i]); } } static void wyhashFinal0(Wyhash* h) { h->state[0] ^= h->state[1] ^ h->state[2]; } static void wyhashFinal1(Wyhash* h, const uint8_t* input_lb, uint64_t input_lb_len, uint64_t start_pos) { const uint8_t* input = input_lb + start_pos; uint64_t input_len = input_lb_len - start_pos; uint64_t i = 0; while (i + 16 < input_len) { h->state[0] = wyhashMix(wyhashRead8(input + i) ^ secret[1], wyhashRead8(input + i + 8) ^ h->state[0]); i += 16; } h->a = wyhashRead8(input_lb + input_lb_len - 16); h->b = wyhashRead8(input_lb + input_lb_len - 8); } static uint64_t wyhashFinal2(Wyhash* h) { h->a ^= secret[1]; h->b ^= h->state[0]; wyhashMum(&h->a, &h->b); return wyhashMix(h->a ^ secret[0] ^ h->total_len, h->b ^ secret[1]); } void wyhashInit(Wyhash* h, uint64_t seed) { h->total_len = 0; h->buf_len = 0; h->state[0] = seed ^ wyhashMix(seed ^ secret[0], secret[1]); h->state[1] = h->state[0]; h->state[2] = h->state[0]; } static void wyhashUpdate(Wyhash* h, const void* data, size_t len) { const uint8_t* input = (const uint8_t*)data; h->total_len += (uint64_t)len; if ((uint64_t)len <= 48 - h->buf_len) { memcpy(h->buf + h->buf_len, input, len); h->buf_len += (uint64_t)len; return; } uint64_t i = 0; if (h->buf_len > 0) { i = 48 - h->buf_len; memcpy(h->buf + h->buf_len, input, (size_t)i); wyhashRound(h, h->buf); h->buf_len = 0; } while (i + 48 < (uint64_t)len) { wyhashRound(h, input + i); i += 48; } uint64_t remaining = (uint64_t)len - i; if (remaining < 16 && i >= 48) { uint64_t rem = 16 - remaining; memcpy(h->buf + 48 - rem, input + i - rem, (size_t)rem); } memcpy(h->buf, input + i, (size_t)remaining); h->buf_len = remaining; } uint64_t wyhashFinal(const Wyhash* h) { Wyhash copy = *h; if (h->total_len <= 16) { wyhashSmallKey(©, h->buf, h->buf_len); } else { uint64_t offset = 0; const uint8_t* input = h->buf; uint64_t input_len = h->buf_len; uint8_t scratch[16]; if (h->buf_len < 16) { uint64_t rem = 16 - h->buf_len; memcpy(scratch, h->buf + 48 - rem, (size_t)rem); memcpy(scratch + rem, h->buf, (size_t)h->buf_len); input = scratch; input_len = 16; offset = rem; } wyhashFinal0(©); wyhashFinal1(©, input, input_len, offset); } return wyhashFinal2(©); } uint64_t wyhashHash(uint64_t seed, const void* data, size_t len) { const uint8_t* input = (const uint8_t*)data; Wyhash h; wyhashInit(&h, seed); if (len <= 16) { wyhashSmallKey(&h, input, (uint64_t)len); } else { uint64_t i = 0; if (len >= 48) { while (i + 48 < (uint64_t)len) { wyhashRound(&h, input + i); i += 48; } wyhashFinal0(&h); } wyhashFinal1(&h, input, (uint64_t)len, i); } h.total_len = (uint64_t)len; return wyhashFinal2(&h); } void wyhashUpdateU32(Wyhash* h, uint32_t val) { wyhashUpdate(h, &val, 4); } void wyhashUpdateU64(Wyhash* h, uint64_t val) { wyhashUpdate(h, &val, 8); } /* ---- end wyhash.c ---- */ /* ---- begin zcu.c ---- */ // zcu.c — ported from src/Zcu.zig (declarations in zig1.h). #ifndef ZIG1_H #include "zig1.h" #endif #include #include Zcu* zcuInit(Compilation* comp) { Zcu* zcu = zig1Calloc(1, sizeof(Zcu)); zcu->comp = comp; zcu->intern_pool = ipInit(); zcu->files = NULL; zcu->num_files = 0; zcu->files_cap = 0; zcu->std_file_idx = UINT32_MAX; zcu->root_file_idx = UINT32_MAX; zcu->main_file_idx = UINT32_MAX; zcu->wanted_navs = NULL; zcu->wanted_navs_len = 0; zcu->wanted_navs_cap = 0; zcu->wanted_navs_head = 0; zcu->sema_branch_root = (ZcuSemaBranch) { .quota = ZIG1_DEFAULT_BRANCH_QUOTA, .allow_memoize = true, .owner_func = IP_INDEX_NONE }; zcu->sema_branch = &zcu->sema_branch_root; zcu->sema_owner_nav = IP_INDEX_NONE; zcu->ct.type_resolve_ns = IP_NAMESPACE_NONE; zcu->navstruct_resolving = IP_INDEX_NONE; zcu->container_self_nav = IP_INDEX_NONE; zcu->exports = NULL; zcu->exports_len = 0; zcu->exports_cap = 0; zcu->embed_table = NULL; zcu->embed_table_len = 0; zcu->embed_table_cap = 0; zcu->type_resolution = (ZcuTypeResolution) { 0 }; zcu->comptime_tables = (CtTables) { 0 }; if (comp->config.modules_len != 0) { zcu->module_root_file_indices = zig1Malloc((size_t)comp->config.modules_len * sizeof(uint32_t)); for (uint32_t i = 0; i < comp->config.modules_len; i++) zcu->module_root_file_indices[i] = UINT32_MAX; } return zcu; } void zcuDeinit(Zcu* zcu) { if (!zcu) return; for (uint32_t i = 0; i < zcu->num_files; i++) { ZcuFile* f = &zcu->files[i]; if (f->owns_source) { if (f->zir_storage != NULL) zirDeinit(f->zir_storage); free(f->zir_storage); if (f->zoir != NULL) zoirDeinit(f->zoir); free(f->zoir); astDeinit(&f->ast); } free(f->sub_path); free(f->sub_file_path); } free(zcu->files); free(zcu->module_root_file_indices); free(zcu->wanted_navs); semaBranchDeinit(&zcu->sema_branch_root); free(zcu->exports); free(zcu->test_functions.items); for (uint32_t i = 0; i < zcu->embed_table_len; i++) free(zcu->embed_table[i].sub_path); free(zcu->embed_table); semaDeinitTypeResolution(&zcu->type_resolution); ctDeinitTables(&zcu->comptime_tables); ipDeinit(&zcu->intern_pool); free(zcu); } uint32_t zcuAppendExport(Zcu* zcu, uint32_t name, uint32_t section, uint8_t linkage, uint8_t visibility, bool exported_is_uav, uint32_t exported) { if (zcu->exports_len == zcu->exports_cap) { zcu->exports_cap = zig1GrowCap(zcu->exports_cap, 8u); zcu->exports = zig1Realloc( zcu->exports, (size_t)zcu->exports_cap * sizeof(ZcuExport)); } uint32_t idx = zcu->exports_len++; zcu->exports[idx] = (ZcuExport) { .name = name, .section = section, .linkage = linkage, .visibility = visibility, .exported_is_uav = exported_is_uav, .exported = exported, }; return idx; } static uint32_t zcuAppendFile(Zcu* zcu) { if (zcu->num_files == zcu->files_cap) { zcu->files_cap = zig1GrowCap(zcu->files_cap, 8u); zcu->files = zig1Realloc(zcu->files, zcu->files_cap * sizeof(ZcuFile)); } uint32_t idx = zcu->num_files++; ZcuFile* f = &zcu->files[idx]; f->sub_path = NULL; f->error_path = NULL; f->sub_file_path = NULL; f->module_name = NULL; f->module_id = UINT32_MAX; f->owns_source = false; f->zir_storage = NULL; f->zir = NULL; f->zoir = NULL; f->root_type = IP_INDEX_NONE; f->ns = UINT32_MAX; return idx; } uint32_t zcuAddFileOwned( Zcu* zcu, uint32_t module_id, char* sub_path, Ast ast, Zir zir) { uint32_t idx = zcuAppendFile(zcu); ZcuFile* f = &zcu->files[idx]; f->sub_path = sub_path; f->error_path = sub_path; f->module_id = module_id; f->owns_source = true; f->ast = ast; f->zir_storage = zig1Malloc(sizeof(Zir)); *f->zir_storage = zir; f->zir = f->zir_storage; return idx; } // Zcu.zig:modeFromPath bool zcuFileModeFromPath(const char* path, AstMode* out) { const size_t len = strlen(path); if (len >= 4 && strcmp(path + len - 4, ".zon") == 0) *out = AST_MODE_ZON; else if (len >= 4 && strcmp(path + len - 4, ".zig") == 0) *out = AST_MODE_ZIG; else return false; return true; } uint32_t zcuAddFileOwnedZon( Zcu* zcu, uint32_t module_id, char* sub_path, Ast ast, Zoir zoir) { uint32_t idx = zcuAppendFile(zcu); ZcuFile* f = &zcu->files[idx]; f->sub_path = sub_path; f->error_path = sub_path; f->module_id = module_id; f->owns_source = true; f->ast = ast; f->zoir = zig1Malloc(sizeof(Zoir)); *f->zoir = zoir; return idx; } uint32_t zcuAddFileBorrowed( Zcu* zcu, uint32_t module_id, const Zir* zir, const char* path) { uint32_t idx = zcuAppendFile(zcu); ZcuFile* f = &zcu->files[idx]; f->error_path = path; f->module_id = module_id; f->owns_source = false; f->zir = zir; return idx; } void zcuSetFileSourceIdentity(Zcu* zcu, uint32_t file_idx, const char* module_name, const char* sub_file_path) { if (file_idx >= zcu->num_files || module_name == NULL || sub_file_path == NULL) UNREACHABLE("zcuSetFileSourceIdentity: invalid file"); ZcuFile* f = &zcu->files[file_idx]; free(f->sub_file_path); f->sub_file_path = zig1Strdup(sub_file_path); f->module_name = module_name; } uint32_t zcuNamespaceOfZir(Zcu* zcu, const Zir* zir) { for (uint32_t fi = 0; fi < zcu->num_files; fi++) { if (zcu->files[fi].zir == zir) return zcu->files[fi].ns; } return zcu->files[zcu->root_file_idx].ns; } // Zcu.zig:structPackedFieldBitOffset, Type.zig:structFieldOffset // Port of Zcu.structPackedFieldBitOffset: sum the bit widths of the fields // preceding `field_index`, LSB-first (`"bit_sum += field_ty.bitSize(zcu);"`). // Upstream keeps no bit-offset array — `field_offsets` is `.empty` for packed // and `Type.structFieldOffset` asserts against it — so every reader // recomputes. uint32_t zcuStructPackedFieldBitOffset( Zcu* zcu, InternPool* ip, uint32_t struct_ty, uint32_t field_index) { uint64_t bit_sum = 0; for (uint32_t i = 0; i < field_index; i++) { uint64_t fsz, fal, fbits; if (!ztLayoutInfo(zcu, ip, ipStructFieldType(ip, struct_ty, i), &fsz, &fal, &fbits)) UNIMPLEMENTED("structPackedFieldBitOffset: packed struct - field " "type has no bit size"); bit_sum += fbits; } return (uint32_t)bit_sum; } /* ---- end zcu.c ---- */ /* ---- begin zcu_per_thread.c ---- */ // zcu_per_thread.c — src/Zcu/PerThread.zig: files and imports // (PerThread.doImport, embedFile, the builtin module), Nav analysis // (zptEnsureNavValUpToDate, zptAnalyzeNavVal, zptEnsureNavTypeUpToDate), // comptime units (analyzeComptimeUnit), namespace scanning (scanNamespace, // ScanDeclIter) and function bodies (analyzeFuncBody). A PerThread method is // zpt and its name (zptScanNamespace is PerThread.scanNamespace), as is a // helper of this file; Sema itself is in sema.c and sema_comptime.c. // Declarations are in zig1.h. #ifndef ZIG1_H #include "zig1.h" #endif // ── Files and imports (PerThread.doImport, the builtin module) ────────────── // Read a module-relative file (`sub_path` below `module_root`, or below the // current directory when that is NULL). Returns NULL when it cannot be opened. static char* zptReadModuleFile( const char* module_root, const char* sub_path, size_t* out_len) { char* path = zig1PathJoin(module_root, sub_path, strlen(sub_path)); char* buf = zig1ReadFile(path, out_len); free(path); return buf; } // Lexically normalize a module-relative sub-path in place: resolve "." // segments and "seg/.." pairs ("lib/std/heap/../std.zig" -> "lib/std/std.zig") // so the same file always dedups to the same key. Mirrors upstream // Zcu.importFile resolving each import to a canonical file path before its // import_table lookup. Purely lexical (no filesystem access; the module tree // has no symlink tricks). A ".." with no preceding segment aborts loudly. static void zptNormalizeSubPath(char* p) { if (strstr(p, "./") == NULL && strstr(p, "/.") == NULL) return; // fast path: nothing to resolve char* out = p; char* r = p; // seg_starts[k] = offset of the k-th written segment in out. uint32_t seg_starts[64]; uint32_t nsegs = 0; size_t w = 0; while (*r) { // Extract the next segment [r, e). char* e = r; while (*e && *e != '/') e++; size_t slen = (size_t)(e - r); if (slen == 1 && r[0] == '.') { // skip "." } else if (slen == 2 && r[0] == '.' && r[1] == '.') { if (nsegs == 0) UNIMPLEMENTED("doImport: '..' escapes module root"); nsegs--; w = seg_starts[nsegs]; // drop the previous segment } else if (slen > 0) { if (nsegs >= 64) UNIMPLEMENTED("doImport: too many path segments"); seg_starts[nsegs++] = (uint32_t)w; if (w != 0) out[w++] = '/'; memmove(out + w, r, slen); w += slen; } r = (*e == '/') ? e + 1 : e; } out[w] = '\0'; // A leading '/' cannot appear (module-relative paths); the rebuilt path // never writes a leading separator, matching the input shape. } const char* zptPathBelowDir( const char* path, const char* dir, size_t dir_len) { size_t path_len = strlen(path); while (dir_len > 1 && dir[dir_len - 1] == '/') dir_len--; if (dir_len == 1 && dir[0] == '/') { if (path_len <= 1 || path[0] != '/') UNIMPLEMENTED( "doImport: file path lies outside filesystem-root module"); return path + 1; } if (dir_len == 0 || (dir_len == 1 && dir[0] == '.')) return path[0] == '.' && path[1] == '/' ? path + 2 : path; if (path_len <= dir_len || strncmp(path, dir, dir_len) != 0 || path[dir_len] != '/') UNIMPLEMENTED("doImport: file path lies outside its module root"); return path + dir_len + 1; } // (part of) Sema.zig:zirBuiltinSrc // Capture File.sub_file_path and Module.fully_qualified_name when a loaded // file is registered. @src() then reads the file scope's identity directly, // matching Sema.zirBuiltinSrc, rather than guessing from paths at the use // site. static void zptSetOwnedFileSourceIdentity(Zcu* zcu, uint32_t file_idx) { const ZcuFile* file = &zcu->files[file_idx]; const CompilationConfig* config = &zcu->comp->config; const char* module_name = NULL; const char* sub_file_path = file->sub_path; if (file->module_id == ZCU_MODULE_ROOT) { module_name = config->module_name != NULL ? config->module_name : config->root_name; if (module_name == NULL) UNIMPLEMENTED( "zirBuiltinSrc: root module has no fully-qualified name"); if (config->module_root != NULL) { // Root-module file paths are stored relative to the configured // module root, including the borrowed root registered below. sub_file_path = file->sub_path; } else { const ZcuFile* root = &zcu->files[zcu->root_file_idx]; if (root->sub_path == NULL || root->sub_file_path == NULL) UNIMPLEMENTED( "zirBuiltinSrc: root file source identity unavailable"); size_t path_len = strlen(root->sub_path); size_t sub_len = strlen(root->sub_file_path); if (path_len < sub_len) UNIMPLEMENTED( "zirBuiltinSrc: root sub-file path exceeds stored path"); size_t dir_len = path_len - sub_len; if (dir_len > 0 && root->sub_path[dir_len - 1] == '/') dir_len--; sub_file_path = zptPathBelowDir(file->sub_path, root->sub_path, dir_len); } } else if (file->module_id == ZCU_MODULE_STD) { module_name = "std"; sub_file_path = zptPathBelowDir(file->sub_path, "lib/std", 7); } else if (file->module_id == ZCU_MODULE_BUILTIN) { module_name = "builtin"; sub_file_path = zig1PathBasename(file->sub_path); } else if (file->module_id == ZCU_MODULE_MAIN) { module_name = config->main_module_name; if (module_name == NULL) UNIMPLEMENTED( "zirBuiltinSrc: main module has no fully-qualified name"); if (config->main_module_root != NULL) sub_file_path = file->sub_path; } else if (file->module_id >= ZCU_MODULE_CONFIG_FIRST) { uint32_t mi = file->module_id - ZCU_MODULE_CONFIG_FIRST; // An out-of-range module ID is an internal module-graph invariant, // not a valid Zig source shape. if (mi >= config->modules_len) UNREACHABLE("file has invalid configured module identity"); const CompilationModule* module = &config->modules[mi]; module_name = module->name; if (module_name == NULL) UNIMPLEMENTED("zirBuiltinSrc: configured module has no " "fully-qualified name"); sub_file_path = zptPathBelowDir(file->sub_path, module->sub_path, zig1PathDirLen(module->sub_path)); } if (module_name == NULL) UNREACHABLE("file has unknown module identity"); zcuSetFileSourceIdentity(zcu, file_idx, module_name, sub_file_path); } // `operand` joined to the importing file's directory (`File.path.upJoin`), as // a fresh heap module-relative sub-path. A borrowed root file (NULL path) // sits at its module root. char* zptImporterRelativePath( const char* importer_path, const char* operand, size_t olen) { size_t dlen = importer_path ? zig1PathDirLen(importer_path) : 0; char* sub = zig1Malloc(dlen + 1 + olen + 1); if (dlen) { memcpy(sub, importer_path, dlen); sub[dlen] = '/'; memcpy(sub + dlen + 1, operand, olen + 1); } else { memcpy(sub, operand, olen + 1); } return sub; } // The filesystem directory a module's sub-paths are relative to (NULL: cwd). static const char* zptModuleRootDir(const Zcu* zcu, uint32_t module_id) { const CompilationConfig* config = &zcu->comp->config; if (module_id == ZCU_MODULE_ROOT) return config->module_root; if (module_id == ZCU_MODULE_MAIN) return config->main_module_root; if (module_id == ZCU_MODULE_STD) return config->std_root; if (module_id >= ZCU_MODULE_CONFIG_FIRST) return config->modules[module_id - ZCU_MODULE_CONFIG_FIRST].root_dir; return NULL; } // Demand-load a module-relative source file: read, parse, lower to ZIR and // register it as a Zcu-owned file, returning its index (the read, parse, // astGen and registerFile part of PerThread.importFile / doImport). The // `sub_path` is duplicated for the file record. uint32_t zptDemandLoadFile( Zcu* zcu, uint32_t module_id, const char* sub_path) { // Canonicalize + dedup: one file == one entry (upstream Zcu.importFile's // import_table). Without this, `@import("../std.zig")` from // lib/std/heap/FixedBufferAllocator.zig would load a second copy of // std.zig, duplicating every nav/type resolved through it (two distinct // interned `std.mem.Alignment` enums broke func_type dedup). char* norm = zig1Strdup(sub_path); zptNormalizeSubPath(norm); for (uint32_t fi = 0; fi < zcu->num_files; fi++) { if (zcu->files[fi].module_id == module_id && zcu->files[fi].sub_path != NULL && strcmp(zcu->files[fi].sub_path, norm) == 0) { free(norm); return fi; } } size_t src_len; char* src = zptReadModuleFile(zptModuleRootDir(zcu, module_id), norm, &src_len); if (!src) { char* path = zig1PathJoin( zptModuleRootDir(zcu, module_id), norm, strlen(norm)); char msg[512]; snprintf(msg, sizeof(msg), "unable to open '%s'", path); free(path); zig1CompileError(msg); } // Zcu.File.getMode: a `.zon` file is parsed as ZON and lowered by ZonGen // (PerThread.updateFile's `.zon` arm). AstMode mode; if (zcuFileModeFromPath(norm, &mode) && mode == AST_MODE_ZON) { Ast zon_ast = astParse(src, (uint32_t)strlen(src), AST_MODE_ZON); zon_ast.owned_source = src; Zoir zoir = zonGenGenerateFile( &zon_ast, zptModuleRootDir(zcu, module_id), norm); return zcuAddFileOwnedZon(zcu, module_id, norm, zon_ast, zoir); } Ast ast = astParse(src, (uint32_t)strlen(src), AST_MODE_ZIG); // The Ast references (does not copy) the source; hand ownership to the Ast // so the Zcu frees it together with the file's AST/ZIR. ast.owned_source = src; Zir zir = astGenFile(&ast, zptModuleRootDir(zcu, module_id), norm); uint32_t file_idx = zcuAddFileOwned(zcu, module_id, norm, ast, zir); zptSetOwnedFileSourceIdentity(zcu, file_idx); return file_idx; } // Sema.zig:zirEmbedFile, PerThread.zig:embedFile // Sema.zirEmbedFile -> PerThread.embedFile -> newEmbedFile/updateEmbedFile: // the file named relative to the importing file (`"cur_file.path.upJoin(gpa, // zcu.comp.dirs, import_string)"`), deduplicated by path in `embed_table`, // read whole and interned as the `*const [N:0]u8` pointer to its bytes. // Returns that pointer value. `zir` identifies the importing file; `name` is // NUL-terminated (toConstString's buffer). uint32_t zptEmbedFile( Zcu* zcu, const Zir* zir, const char* name, uint32_t name_len) { // "if (name.len == 0) return sema.fail(..., "file path name cannot be // empty", .{});" if (name_len == 0) COMPILE_ERROR_UPSTREAM("@embedFile: file path name cannot be empty"); if (memchr(name, 0, name_len) != NULL) UNIMPLEMENTED("@embedFile: path holds a NUL"); // A module name (`"std"`, `"root"`, `"builtin"` or a dependency) embeds // that module's root source file. if (strcmp(name, "std") == 0 || strcmp(name, "root") == 0 || strcmp(name, "builtin") == 0) UNIMPLEMENTED("@embedFile of a module root file"); const ZcuFile* cur = &zcu->files[ctFileIndexOfZir(zcu, zir)]; // zptDemandLoadNamedModule's dependency table: root and main only. const CompilationConfig* config = &zcu->comp->config; if (cur->module_id == ZCU_MODULE_ROOT || cur->module_id == ZCU_MODULE_MAIN) for (uint32_t i = 0; i < config->root_dependencies_len; i++) if (strcmp(config->root_dependencies[i].name, name) == 0) UNIMPLEMENTED("@embedFile of a module root file"); char* sub = zptImporterRelativePath(cur->sub_path, name, name_len); zptNormalizeSubPath(sub); for (uint32_t ei = 0; ei < zcu->embed_table_len; ei++) { const ZcuEmbedFile* ef = &zcu->embed_table[ei]; if (ef->module_id == cur->module_id && strcmp(ef->sub_path, sub) == 0) { free(sub); if (ef->val == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM("@embedFile: unable to open file"); return ef->val; } } // updateEmbedFileInner: the bytes as one string (`.maybe_embedded_nulls`, // the trailing sentinel dropped), then `"pt.arrayType(.{ .len = size, // .sentinel = .zero_u8, .child = .u8_type })"`, its `.bytes` aggregate // and the `.uav` pointer of `singleConstPtrType`: addStrLit's shape. size_t len = 0; char* bytes = zptReadModuleFile(zptModuleRootDir(zcu, cur->module_id), sub, &len); uint32_t val = IP_INDEX_NONE; if (bytes) { if (len >= UINT32_MAX) UNIMPLEMENTED("@embedFile: file too big"); InternPool* ip = &zcu->intern_pool; val = addNullTerminatedStrLit( ip, ipGetOrPutString(ip, bytes, (uint32_t)len), NULL); free(bytes); } if (zcu->embed_table_len == zcu->embed_table_cap) { zcu->embed_table_cap = zig1GrowCap(zcu->embed_table_cap, 4u); zcu->embed_table = zig1Realloc(zcu->embed_table, (size_t)zcu->embed_table_cap * sizeof(ZcuEmbedFile)); } zcu->embed_table[zcu->embed_table_len++] = (ZcuEmbedFile) { .module_id = cur->module_id, .sub_path = sub, .val = val }; // "if (result.val == .none) return sema.fail(..., "unable to open // '{s}': {s}", ...);" if (val == IP_INDEX_NONE) COMPILE_ERROR_UPSTREAM("@embedFile: unable to open file"); return val; } // Resolve a bare import name through the compilation root module's import // table. The module is loaded only on first use and then reused by file index, // matching Module.import_table + Zcu.importFile. Filesystem-backed modules go // through zptDemandLoadFile's canonicalization/dedup path. Synthesized modules // copy their borrowed source into Ast-owned storage and retain `sub_path` as // provenance for any relative imports made by that module. uint32_t zptDemandLoadNamedModule( Zcu* zcu, uint32_t importer_module_id, const char* name) { const CompilationConfig* config = &zcu->comp->config; // The config stores the executable root dependency table. In `zig test` // upstream clones the main module's dependencies onto the runner root, so // both root and main see these names; never leak them into std/builtin. if (importer_module_id != ZCU_MODULE_ROOT && importer_module_id != ZCU_MODULE_MAIN) return UINT32_MAX; for (uint32_t i = 0; i < config->root_dependencies_len; i++) { const CompilationDependency* dep = &config->root_dependencies[i]; if (strcmp(dep->name, name) != 0) continue; if (dep->module_index == COMPILATION_MODULE_ROOT) return zcu->root_file_idx; if (dep->module_index >= config->modules_len) UNREACHABLE("named dependency has invalid module index"); uint32_t module_index = dep->module_index; const CompilationModule* module = &config->modules[module_index]; if (zcu->module_root_file_indices[module_index] != UINT32_MAX) return zcu->module_root_file_indices[module_index]; if (module_index > UINT32_MAX - ZCU_MODULE_CONFIG_FIRST) UNIMPLEMENTED("doImport: too many configured modules"); uint32_t module_id = ZCU_MODULE_CONFIG_FIRST + module_index; uint32_t file_idx; if (module->source == NULL) { file_idx = zptDemandLoadFile(zcu, module_id, module->sub_path); } else { const char* module_source = module->source; char* src = zig1Strdup(module_source); size_t src_len = strlen(src); if (src_len > UINT32_MAX) UNIMPLEMENTED("doImport: named module source exceeds u32"); Ast ast = astParse(src, (uint32_t)src_len, AST_MODE_ZIG); ast.owned_source = src; Zir zir = astGenFile(&ast, NULL, module->sub_path); file_idx = zcuAddFileOwned( zcu, module_id, zig1Strdup(module->sub_path), ast, zir); zptSetOwnedFileSourceIdentity(zcu, file_idx); } zcu->module_root_file_indices[module_index] = file_idx; return file_idx; } return UINT32_MAX; } // (part of) Builtin.zig:append // Demand-load the synthesized `builtin` module. Unlike `std`'s files, the // `builtin.zig` source is not read from disk: it is generated in-memory for // the fixed bootstrap target (`builtinGenerate`, byte-identical to the // compiler's `Builtin.append`). Parsed + lowered to ZIR like any file, // registered once, and cached in `zcu->builtin_file_idx` (memoized, mirroring // the single `builtin` module instance shared by all `@import("builtin")`). uint32_t zptDemandLoadBuiltin(Zcu* zcu) { if (zcu->builtin_file_idx != 0) return zcu->builtin_file_idx; char* src = builtinGenerateForProfile(zcu->comp->config.profile); Ast ast = astParse(src, (uint32_t)strlen(src), AST_MODE_ZIG); ast.owned_source = src; Zir zir = astGenFile(&ast, NULL, "builtin.zig"); uint32_t idx = zcuAddFileOwned( zcu, ZCU_MODULE_BUILTIN, zig1Strdup("builtin.zig"), ast, zir); zptSetOwnedFileSourceIdentity(zcu, idx); zcu->builtin_file_idx = idx; return idx; } // ── Navs (ensureNavValUpToDate, analyzeNavVal, analyzeNavType) ────────────── static void zptErrorSourceNav(Zcu* zcu, const Zir* zir, uint32_t nav_idx); // analyzeNavRef: the pointer first, then its load by the consumer. uint32_t navAnalyzeRefLoad(Zcu* zcu, uint32_t nav) { navDeclRefAddressResolved(zcu, nav, NULL); return zptEnsureNavValUpToDate(zcu, nav, NULL); } // PerThread.zig:vectorType uint32_t zptVectorType(InternPool* ip, uint64_t len, uint32_t elem_ty) { // Upstream orders the length coerce before the element check, // so the coerced `u32` enters the pool either way and an // otherwise-identical program's IP is unchanged by this gate. ipGetIntU32(ip, (uint32_t)len); // resolveInt -> u32 coerce checkVectorElemType(ip, elem_ty); return ipGetVectorType(ip, (uint32_t)len, elem_ty); } // Sema.zig:zirFunc, zirFuncFancy, funcCommon // A declaration's `func`: the signature's type bodies, then getFuncType and // getFuncDecl, owned by the Nav being analyzed. Its value body holds the // parameter instructions in order, then the `func` instruction. Runs inside // zptAnalyzeNavVal, which publishes the result. uint32_t navFuncDecl(Zcu* zcu, uint32_t nav_idx) { InternPool* ip = &zcu->intern_pool; const IpNav* nav = ipGetNav(ip, nav_idx); // Type-expression resolution below may create/realloc Navs. Keep only // stable scalar indices across every re-entrant call. uint32_t nav_ns = nav->analysis_namespace; uint32_t analysis_inst = nav->analysis_inst; uint32_t file_idx = ipGetNamespace(ip, nav_ns)->file_scope; const Zir* zir = zcu->files[file_idx].zir; DeclInfo decl; if (!zirDecodeFunctionDeclaration(zir, analysis_inst, &decl)) UNREACHABLE("navFuncDecl: decl has no value body"); // Walk the declaration's value body: it holds the parameter instructions // (in order) followed by the `func` instruction. Collect each parameter's // (primitive) type and locate the func instruction. uint32_t* param_types = ARR_INIT(uint32_t, decl.value_body_len ? decl.value_body_len : 1); uint32_t nparams = 0; uint32_t comptime_bits = 0; uint32_t func_inst = UINT32_MAX; bool is_inferred_error = false; bool is_fancy = false; for (uint32_t b = 0; b < decl.value_body_len; b++) { uint32_t inst = zir->extra[decl.value_body_off + b]; ZirInstTag t = zir->inst_tags[inst]; if (t == ZIR_INST_PARAM || t == ZIR_INST_PARAM_COMPTIME) { // A `comptime` parameter sets its bit in the function type's // comptime_bits (Sema.zirFunc -> getFuncType). This marks the // function generic; the call site monomorphizes it. if (t == ZIR_INST_PARAM_COMPTIME) comptime_bits |= ctFuncTypeComptimeBit(nparams); // Zir.Inst.Param payload: { name, type=body_len:u31|is_generic:b31 // }, followed by the type body. uint32_t pp = zir->inst_datas[inst].pl_tok.payload_index; uint32_t body_word = zir->extra[pp + 1]; uint32_t param_ty; if (body_word & ZIR_PARAM_TYPE_IS_GENERIC) { // `[generic]` parameter type (Zir.Inst.Param.type.is_generic): // it refers to a previous (comptime) parameter, so the // declared signature type is `generic_poison_type`. The call // site monomorphizes it per-instantiation (Sema.zirFunc -> // getFuncType interns generic param types as generic_poison). param_ty = IP_INDEX_GENERIC_POISON_TYPE; } else { uint32_t plen = body_word & ZIR_PARAM_TYPE_BODY_LEN_MASK; if (plen == 0) UNIMPLEMENTED("analyzeNavVal: empty parameter type body"); uint32_t last = zir->extra[pp + 2 + plen - 1]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) UNREACHABLE("navFuncDecl: param body not break_inline"); uint32_t ref = zir->inst_datas[last].break_data.operand; if (zirRefIsThis(zir, ref)) { // `self: @This()`: the parameter type is the enclosing // container type, i.e. the owner of this method Nav's // namespace (Sema.zirThis). param_ty = ipGetNamespace(ip, nav_ns)->owner_type; } else if (ctRetTypeHasComputedAlign(zir, ref)) { // A parameter whose pointer type alignment depends on an // earlier comptime parameter (`data: // []align(alignment.toByteUnits()) u8`) is generic in // the owner signature. The call-site instantiation binds // the concrete alignment; mirror compilationDeclFuncInfo // so publishing the generic Nav does not try to fold an // unbound align expression. param_ty = IP_INDEX_GENERIC_POISON_TYPE; } else if (ctTypeBodyNeedsComptimeFold(zir, ref)) { // A concrete generic type call in a parameter type // (`resolved: *Io.Queue(HostName.LookupResult)` in // Threaded.netLookup) cannot be resolved from the final // ptr_type ref alone: the pointee is the earlier // FIELD_CALL. Fold the whole param type body, matching // ctResolveFuncTypeFromBody's fn-proto path. param_ty = ctFoldNavTypeBody( zcu, nav_idx, zir, &zir->extra[pp + 2], plen, nav_ns); } else if (ref < ZIR_REF_START_INDEX) { // Preinterned type: ZIR ref == IP index. param_ty = ref; } else { // Non-preinterned type body: resolve via helper that // handles int_type, ptr_type, etc. param_ty = ctResolveParamTypeRef(ip, zcu, zir, ref); } } param_types[nparams++] = param_ty; } else if (t == ZIR_INST_PARAM_ANYTYPE || t == ZIR_INST_PARAM_ANYTYPE_COMPTIME) { // `anytype` parameter (Sema.zirFunc: generic). Its declared // signature type is `generic_poison_type`; the call site // monomorphizes it (here: comptime-evaluated when the argument is // a comptime-only value, e.g. // `std.math.isPowerOfTwo(comptime_int)`). A `comptime anytype` // additionally sets its comptime bit. if (t == ZIR_INST_PARAM_ANYTYPE_COMPTIME) comptime_bits |= ctFuncTypeComptimeBit(nparams); param_types[nparams++] = IP_INDEX_GENERIC_POISON_TYPE; } else if (t == ZIR_INST_FUNC || t == ZIR_INST_FUNC_INFERRED) { func_inst = inst; // `func_inferred` (`fn f() !T`): the function's error set is // inferred; its return type is the error union `!`. is_inferred_error = (t == ZIR_INST_FUNC_INFERRED); break; } else if (t == ZIR_INST_FUNC_FANCY) { // `func_fancy`: an extended prototype (noinline / callconv / // var-args / noalias). func_inst = inst; is_fancy = true; break; } } if (func_inst == UINT32_MAX) UNIMPLEMENTED("analyzeNavVal: value body has no func instruction"); ZirFnInfo fn_info = zirGetFnInfo(zir, func_inst); uint32_t srcloc_off = (uint32_t)(fn_info.body - zir->extra) + fn_info.body_len; uint32_t noalias_bits = 0; bool is_noinline = false; FuncCallconv explicit_cc = { 0, 0, 0 }; bool has_explicit_cc = false; if (is_fancy) { // Sema.zirFuncFancy: the explicit calling convention and the // extended attributes. ZirInstFuncFancy extra; zirExtraData( zir, zir->inst_datas[func_inst].pl_node.payload_index, &extra); if ((extra.bits & ZIR_FUNC_FANCY_BITS_IS_VAR_ARGS) != 0) UNIMPLEMENTED("func_fancy: var-args"); if ((extra.bits & (ZIR_FUNC_FANCY_BITS_HAS_CC_REF | ZIR_FUNC_FANCY_BITS_HAS_CC_BODY)) != 0) { explicit_cc = ccResolveFuncFancy(zcu, zir, func_inst, &zir->extra[decl.value_body_off], decl.value_body_len, nav_ns, TYPE_NAME_CTX_NAV(ipGetNav(ip, nav_idx)), NULL); has_explicit_cc = true; } if (fn_info.body_len == 0) UNIMPLEMENTED("func_fancy: without body"); is_inferred_error = fn_info.inferred_error_set; is_noinline = (extra.bits & ZIR_FUNC_FANCY_BITS_IS_NOINLINE) != 0; if ((extra.bits & ZIR_FUNC_FANCY_BITS_HAS_ANY_NOALIAS) != 0) noalias_bits = fn_info.body[-1]; // the word before the body } else if (fn_info.ret_ty_is_generic) { // zirFunc's `extra_index` skips the ret-type body only when it is // not `is_generic`, so a generic return type reads src_locs that many // words early. ZirInstFunc extra; srcloc_off = zirExtraData(zir, zir->inst_datas[func_inst].pl_node.payload_index, &extra) + fn_info.body_len; } // A `[generic]` return type (Zir.Inst.Func.RetTy.is_generic) refers to // a comptime parameter: `generic_poison_type`, monomorphized per // instantiation at the call site. uint32_t ret_ty; if (fn_info.ret_ty_is_generic) { ret_ty = IP_INDEX_GENERIC_POISON_TYPE; } else if (fn_info.ret_ty_body_len != 0) { uint32_t last = fn_info.ret_ty_body[fn_info.ret_ty_body_len - 1]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) UNREACHABLE("navFuncDecl: ret body last not break_inline"); ret_ty = ctResolveRetTypeBody(zcu, zir, fn_info.ret_ty_body, fn_info.ret_ty_body_len, zir->inst_datas[last].break_data.operand, TYPE_NAME_CTX_NAV(ipGetNav(ip, nav_idx))); } else if (!is_fancy && ctRetTypeHasComputedAlign(zir, fn_info.ret_ty_ref)) { ret_ty = IP_INDEX_GENERIC_POISON_TYPE; } else { ret_ty = ctResolveParamTypeRef(ip, zcu, zir, fn_info.ret_ty_ref); } // src_locs: lbrace_line, rbrace_line, columns // (lbrace_column | rbrace_column<<16). uint32_t lbrace_line = zir->extra[srcloc_off]; uint32_t rbrace_line = zir->extra[srcloc_off + 1]; uint32_t columns = zir->extra[srcloc_off + 2]; uint32_t lbrace_column = columns & 0xFFFFu; uint32_t rbrace_column = columns >> 16; // getFuncType (entry 134) then getFuncDecl (entry 135). The func's ZIR // body instruction is tracked (a separate arena; no IP item). Exported // functions get the target C calling convention (`.wasm_mvp` on // wasm32-wasi); non-exported functions default to `.auto` (Sema.zig). // A `noinline fn` sets TypeFunction.Flags.is_noinline (bit 21) and // FuncAnalysis.is_noinline (Sema.funcCommon passing is_noinline to both // getFuncType and getFuncDecl). // funcCommon's `"try sema.ensureLayoutResolved(param_ty, param_src, // .parameter);"` per parameter, then the return type's. for (uint32_t i = 0; i < nparams; i++) ensureLayoutResolved(zcu, param_types[i]); ensureLayoutResolved(zcu, ret_ty); uint32_t base_flags; if (has_explicit_cc) { // Sema.funcCommon validates the resolved convention against the // parameters, the return type and the target before interning it. ccValidate(zcu, &explicit_cc, is_noinline, nparams, comptime_bits, param_types, ret_ty, is_inferred_error, true); base_flags = ccPack(&explicit_cc); } else { base_flags = ipTypeFunctionFlags(decl.is_export); } if (is_noinline) base_flags |= IP_FUNC_FLAGS_IS_NOINLINE; uint32_t fdecl; if (is_inferred_error) { // `fn f() !T`: intern the four-item inferred-error-set block // (func_decl + type_error_union + type_inferred_error_set + // type_function), whose function type's return type is the error union // wrapping `ret_ty` (the bare payload resolved from the ret body). // Mirrors Sema.funcCommon's `inferred_error_set` branch -> // ip.getFuncDeclIes. if (noalias_bits != 0) UNIMPLEMENTED("analyzeNavVal: noalias + inferred error set"); // Known residue: if ipGetFuncDeclIes below hits its `owner_nav` // decl_dedup match (see the rollback comment at its dedup-hit branch, // intern_pool.c), this `ipTrackZir(ip)` id is not reclaimed -- // `ipTrackZir` is a bare monotonic counter, never rolled back -- so // it is burned permanently even though the func_decl it was meant // for gets discarded. Later ipTrackZir ids (and the // zir_body_inst/zir_index words derived from them) then sit +1 vs // upstream for any duplicate-resolution program from that point on. // Accepted. uint32_t zir_body_inst = ipTrackZir(ip); fdecl = ipGetFuncDeclIes(ip, nav_idx, ret_ty, param_types, nparams, base_flags, comptime_bits, is_noinline, zir_body_inst, lbrace_line, rbrace_line, lbrace_column, rbrace_column); } else { uint32_t func_ty = ipGetFuncType(ip, ret_ty, param_types, nparams, base_flags, comptime_bits, noalias_bits); uint32_t zir_body_inst = ipTrackZir(ip); fdecl = ipGetFuncDecl(ip, nav_idx, func_ty, is_noinline, zir_body_inst, lbrace_line, rbrace_line, lbrace_column, rbrace_column); } free(param_types); return fdecl; } // cppcheck-suppress constParameterCallback ; InternPool.resolve_enum_layout // Linear in the explicit-tag enums declared but not yet demanded; it runs // only while `int_tag_type` is unset (ipResolveEnumAutoTagType), at most // once per enum. static void zptResolvePendingEnumLayout(void* ctx, uint32_t enum_ty) { Zcu* zcu = ctx; for (uint32_t p = 0; p < zcu->type_resolution.pending_enum_layouts_len; p++) { PendingEnumLayout pending = zcu->type_resolution.pending_enum_layouts[p]; if (pending.enum_ty != enum_ty) continue; zcu->type_resolution.pending_enum_layouts[p] = zcu->type_resolution.pending_enum_layouts[--zcu->type_resolution .pending_enum_layouts_len]; semaEnumDeclResolveLayout(zcu, pending.zir, pending.enum_inst, enum_ty, pending.eval_ns, pending.caps, pending.caps_len); free(pending.caps); return; } } void zptDeferEnumLayout(Zcu* zcu, const Zir* zir, uint32_t enum_inst, uint32_t enum_ty, uint32_t eval_ns, const CtCap* caps, uint32_t caps_len) { if (zcu->intern_pool.item_tags[enum_ty] == IP_TAG_TYPE_ENUM_AUTO) return; if (zcu->type_resolution.pending_enum_layouts_len == zcu->type_resolution.pending_enum_layouts_cap) { zcu->type_resolution.pending_enum_layouts_cap = zig1GrowCap(zcu->type_resolution.pending_enum_layouts_cap, 8u); zcu->type_resolution.pending_enum_layouts = zig1Realloc(zcu->type_resolution.pending_enum_layouts, zcu->type_resolution.pending_enum_layouts_cap * sizeof(PendingEnumLayout)); } PendingEnumLayout* pending = &zcu->type_resolution.pending_enum_layouts[zcu ->type_resolution.pending_enum_layouts_len++]; pending->enum_ty = enum_ty; pending->zir = zir; pending->enum_inst = enum_inst; pending->eval_ns = eval_ns; pending->caps = NULL; pending->caps_len = caps_len; if (caps_len != 0) { pending->caps = zig1Malloc(caps_len * sizeof(CtCap)); memcpy(pending->caps, caps, caps_len * sizeof(CtCap)); } zcu->intern_pool.resolve_enum_layout = zptResolvePendingEnumLayout; zcu->intern_pool.resolve_enum_layout_ctx = zcu; } // True if the Nav's decl value body contains a function-definition // instruction (`func` / `func_inferred` / `func_fancy`) — i.e. this is a // function decl rather than a plain const value. Mirrors navFuncDecl's walk. bool navValueBodyHasFunc(const Zir* zir, const DeclInfo* decl) { for (uint32_t b = 0; b < decl->value_body_len; b++) { uint32_t inst = zir->extra[decl->value_body_off + b]; if (zirIsFuncInst(zir->inst_tags[inst])) return true; } return false; } // What analyzeNavVal, analyzeNavType and analyzeComptimeUnit do alike: a Sema // of its own (`.func_index = .none`, an empty inst_map) and a comptime Block // over `ns` named `name`, with nothing of the analysis that reached this unit // carried over (Zcu.ct's type-ref namespace is `ns` too). `owner_nav` is // `sema.owner`'s Nav, or IP_INDEX_NONE for a comptime unit. analyzeNavVal's // value body has `result_ty` as `inst_map[decl_inst]` and coerces its result // to it; IP_INDEX_NONE means neither. The result is the body's break or return // value, else IP_INDEX_NONE. static uint32_t zptAnalyzeDeclBody(Zcu* zcu, const Zir* zir, uint32_t body_off, uint32_t body_len, uint32_t ns, TypeNameCtx name, uint32_t owner_nav, uint32_t decl_inst, uint32_t result_ty, CtEvalStatus* out_status) { CtBlockScope block = { ns, name, NULL, owner_nav, true }; const CtEvaluator saved_ct = zcu->ct; zcu->ct = (CtEvaluator) { .type_resolve_ns = ns }; uint32_t* values = semaInstMapAlloc(zir); if (result_ty != IP_INDEX_NONE) values[decl_inst] = result_ty; uint32_t ret_val = ZIR_REF_VOID_VALUE; uint32_t br_val = IP_INDEX_NONE; CtEvalStatus st = ctEvalBodyIn(zcu, block, zir, &zir->extra[body_off], body_len, values, &ret_val, &br_val); free(values); if (out_status != NULL) *out_status = st; uint32_t result = st == CT_EVAL_BREAK ? br_val : st == CT_EVAL_RETURN ? ret_val : IP_INDEX_NONE; // `"const result_ref = try sema.coerce(&block, ty, uncoerced_result_ref, // init_src);"`, in the same Block. if (result_ty != IP_INDEX_NONE && result != IP_INDEX_NONE && !(semaRefIsInterned(result) && ipTypeOf(&zcu->intern_pool, result) == result_ty)) result = ctCoerceToType(zcu, result, result_ty); zcu->ct = saved_ct; return result; } // Evaluate one declaration modifier body (analyzeNavType's Sema) and return // its terminating value; it may name container and file declarations. static uint32_t navModifierBodyValue(Zcu* zcu, const Zir* zir, uint32_t nav_idx, uint32_t body_off, uint32_t body_len) { const IpNav* nav = ipGetNav(&zcu->intern_pool, nav_idx); uint32_t value = zptAnalyzeDeclBody(zcu, zir, body_off, body_len, nav->analysis_namespace, TYPE_NAME_CTX_NAV(nav), nav_idx, 0, IP_INDEX_NONE, NULL); if (value == IP_INDEX_NONE) UNIMPLEMENTED("analyzeNavVal: modifier body produced no value"); return value; } // Publish the type half of InternPool.Nav.Resolved, with // resolveNavPtrModifiers' alignment, linksection and addrspace. // `resolved_value` is left none: a later value-analysis fixpoint fills it. static void navPublishResolvedTypeInner(Zcu* zcu, uint32_t nav_idx, const Zir* zir, const DeclInfo* decl, uint32_t resolved_type) { InternPool* ip = &zcu->intern_pool; if (!ipIndexIsType(ip, resolved_type)) UNIMPLEMENTED( "analyzeNavVal: Nav declared type did not resolve to a type"); // Upstream ensureNavTypeUpToDate resolves the complete storage ABI before // publishing a runtime Nav. This is what makes an aligned aggregate global // correct even when no field projection ever happens to demand layout. if (!ensureLayoutResolved(zcu, resolved_type)) UNIMPLEMENTED("ensureLayoutResolved: Nav type has no supported " "complete storage layout"); // Zcu/PerThread.zig: a `var` declaration validates its type via // Sema.validateVarType, whose comptime-only arm is the stock compile // error "variable of type '{f}' must be const or comptime". Mutable // runtime storage for comptime-only state must never be fabricated. if (!ipGetNav(ip, nav_idx)->is_const && ztComptimeOnly(zcu, resolved_type)) { TypeNameWriter w = { 0 }; semaFormatTypeNameDiagnostic(ip, resolved_type, &w); char msg[512]; snprintf(msg, sizeof(msg), "variable of type '%s' must be const or comptime", w.buf.arr); zig1CompileError(msg); } // A normal type-only publication has already evaluated its modifier // bodies. Reusing it must be side-effect free: evaluating an align body // twice can append IP items and perturb deterministic ordering. A // self-referential container may have prepublished only the type; its // `modifiers_resolved=false` marker deliberately falls through once. IpNav* nav = &ip->navs[nav_idx]; if (nav->has_resolved) { if (nav->resolved_type != resolved_type) UNREACHABLE("Nav type resolved inconsistently"); if (nav->modifiers_resolved) return; } uint8_t alignment = IP_ALIGNMENT_NONE; // natural ABI align if (decl->has_align_body) { uint32_t av = navModifierBodyValue( zcu, zir, nav_idx, decl->align_body_off, decl->align_body_len); int64_t bytes; bool is_comptime; uint32_t bits; if (!ctReadInt(ip, av, &bytes, &is_comptime, &bits) || bytes <= 0) UNIMPLEMENTED("analyzeNavVal: Nav alignment is not a positive " "comptime integer"); alignment = (uint8_t)ipAlignmentFromByteUnits((uint64_t)bytes, "analyzeNavVal: Nav alignment is not a representable power of " "two"); } // Sema.resolveNavPtrModifiers' linksection: toConstString, interned. The // Nav does not keep it; zig1 has no codegen to read it. if (decl->has_linksection_body) { uint32_t sv = navModifierBodyValue(zcu, zir, nav_idx, decl->linksection_body_off, decl->linksection_body_len); char buf[256]; uint32_t len; ctFieldNamedNameString(ip, sv, buf, sizeof(buf), &len); if (len == 0 || memchr(buf, 0, len) != NULL) COMPILE_ERROR_UPSTREAM( "linksection cannot be empty or contain null bytes"); } // Its addrspace. if (decl->has_addrspace_body) resolveAddressSpace(zcu, navModifierBodyValue(zcu, zir, nav_idx, decl->addrspace_body_off, decl->addrspace_body_len)); // Modifier evaluation is ordinary comptime evaluation and may import a // file or otherwise create Navs, reallocating `ip->navs`. Never retain a // pointer into that arena across navModifierBodyValue. nav = &ip->navs[nav_idx]; if (nav->has_resolved) { nav->resolved_alignment = alignment; nav->modifiers_resolved = true; return; } nav->has_resolved = true; nav->resolved_type = resolved_type; nav->resolved_alignment = alignment; nav->modifiers_resolved = true; } static void navPublishResolvedType(Zcu* zcu, uint32_t nav_idx, const Zir* zir, const DeclInfo* decl, uint32_t resolved_type) { const Zig1ErrorSource saved_error_source = zig1_error_source; zptErrorSourceNav(zcu, NULL, nav_idx); navPublishResolvedTypeInner(zcu, nav_idx, zir, decl, resolved_type); zig1_error_source = saved_error_source; } // Publish the complete Nav cache after its initializer has been analyzed. // Centralizing this transition prevents a type-only publication from being // mistaken for a usable initializer and makes every manual fast path validate // the same declaration modifiers and exact resolved type. static void navPublishResolvedValueInner(Zcu* zcu, uint32_t nav_idx, uint32_t resolved_type, uint32_t resolved_value) { InternPool* ip = &zcu->intern_pool; if (nav_idx >= ip->navs_len) UNREACHABLE("publishing invalid Nav value"); const IpNav* before = ipGetNav(ip, nav_idx); if (!before->has_analysis) UNREACHABLE("publishing value for Nav without analysis"); uint32_t nav_ns = before->analysis_namespace; uint32_t analysis_inst = before->analysis_inst; uint32_t file_idx = ipGetNamespace(ip, nav_ns)->file_scope; const Zir* zir = zcu->files[file_idx].zir; DeclInfo decl; if (!zirDecodeFunctionDeclaration(zir, analysis_inst, &decl)) UNREACHABLE("publishing value for undecodable Nav"); navPublishResolvedType(zcu, nav_idx, zir, &decl, resolved_type); uint32_t value_type = ipTypeOf(ip, resolved_value); if (value_type != resolved_type) { fprintf(stderr, "Nav value type mismatch: nav=%u name=%s value=%u tag=%s " "expected=%u actual=%u\n", nav_idx, ipStringBytes(ip, ip->navs[nav_idx].fqn), resolved_value, resolved_value < ip->items_len ? ipTagName((IpTag)ip->item_tags[resolved_value]) : "invalid", resolved_type, value_type); UNIMPLEMENTED( "analyzeNavVal: Nav initializer type differs from resolved type"); } if (zcu->update_queue != NULL) compilationUpdateQueueNavValuePrePublish(zcu->update_queue, nav_idx); IpNav* nav = &ip->navs[nav_idx]; if (nav->has_value_resolved) { if (nav->resolved_type != resolved_type || nav->resolved_value != resolved_value) UNREACHABLE("Nav value resolved inconsistently"); } else { nav->has_value_resolved = true; nav->resolved_value = resolved_value; } if (zcu->update_queue != NULL) compilationUpdateQueueNavValuePublished( zcu->update_queue, nav_idx, resolved_value); switch (semaNavValueModifierStatus(zcu, nav_idx, decl.has_align_body)) { case SEMA_NAV_MODIFIERS_OK: return; case SEMA_NAV_MODIFIERS_ALIGN_FUNCTION_ALIAS: COMPILE_ERROR_UPSTREAM("cannot specify alignment of function alias"); case SEMA_NAV_MODIFIERS_ALIGN_COMPTIME_ONLY: COMPILE_ERROR_UPSTREAM( "cannot specify alignment of comptime-only type"); case SEMA_NAV_MODIFIERS_ALIGN_FUNCTION_TARGET_UNSUPPORTED: COMPILE_ERROR_UPSTREAM( "target does not support function alignment greater than 1"); case SEMA_NAV_MODIFIERS_MALFORMED: UNREACHABLE("malformed Nav during modifier validation"); } UNREACHABLE("unknown Nav modifier validation status"); } static void navPublishResolvedValue(Zcu* zcu, uint32_t nav_idx, uint32_t resolved_type, uint32_t resolved_value) { const Zig1ErrorSource saved_error_source = zig1_error_source; zptErrorSourceNav(zcu, NULL, nav_idx); navPublishResolvedValueInner(zcu, nav_idx, resolved_type, resolved_value); zig1_error_source = saved_error_source; } // PerThread.zig:ensureNavTypeUpToDate, PerThread.zig:analyzeNavType // The cached type when it is up to date, else resolve a decl's declared type // from its type-annotation body without evaluating its value body — for // forming `&decl` (Sema.analyzeNavRef needs the pointee type, not the value). // Returns IP_INDEX_NONE if the decl has no explicit type annotation (caller // falls back to full value resolution). It publishes the type and modifiers on // the Nav before any initializer can recursively refer to its address. static uint32_t zptEnsureNavTypeUpToDateInner(Zcu* zcu, uint32_t nav_idx) { InternPool* ip = &zcu->intern_pool; const IpNav* nav = ipGetNav(ip, nav_idx); if (!nav->has_analysis) return nav->has_resolved ? nav->resolved_type : IP_INDEX_NONE; uint32_t nav_ns = nav->analysis_namespace; uint32_t file_idx = ipGetNamespace(ip, nav_ns)->file_scope; const Zir* zir = zcu->files[file_idx].zir; DeclInfo decl; if (!zirDecodeDeclaration(zir, nav->analysis_inst, &decl)) return IP_INDEX_NONE; if (zcu->update_queue != NULL) compilationUpdateQueueNavTypeEnter(zcu->update_queue, nav_idx); nav = ipGetNav(ip, nav_idx); if (nav->has_resolved && nav->modifiers_resolved) return nav->resolved_type; if (nav->has_resolved) { uint32_t ty = nav->resolved_type; navPublishResolvedType(zcu, nav_idx, zir, &decl, ty); return ty; } if (!decl.has_type_body) return IP_INDEX_NONE; uint32_t tref = UINT32_MAX; for (uint32_t b = 0; b < decl.type_body_len; b++) { uint32_t tinst = zir->extra[decl.type_body_off + b]; if (zir->inst_tags[tinst] == ZIR_INST_BREAK_INLINE) { tref = zir->inst_datas[tinst].break_data.operand; break; } } if (tref == UINT32_MAX) return IP_INDEX_NONE; // Upstream analyzes a Nav in a Sema of its own: nothing of the frame that // reached it applies. const CtEvaluator saved_ct = zcu->ct; zcu->ct = (CtEvaluator) { .type_resolve_ns = nav_ns }; uint32_t ty = ctTypeBodyNeedsComptimeFold(zir, tref) ? ctFoldNavTypeBody(zcu, nav_idx, zir, &zir->extra[decl.type_body_off], decl.type_body_len, nav_ns) : ctResolveParamTypeRef(ip, zcu, zir, tref); zcu->ct = saved_ct; navPublishResolvedType(zcu, nav_idx, zir, &decl, ty); return ty; } uint32_t zptEnsureNavTypeUpToDate(Zcu* zcu, uint32_t nav_idx) { const Zig1ErrorSource saved_error_source = zig1_error_source; zptErrorSourceNav(zcu, NULL, nav_idx); uint32_t r = zptEnsureNavTypeUpToDateInner(zcu, nav_idx); zig1_error_source = saved_error_source; return r; } // Queue a Nav value exactly once, preserving the order of the first address // reference. Upstream's outdated_ready.other is insertion ordered, and that // ordering is observable in the InternPool when initializers intern distinct // values. An index is stored rather than an IpNav pointer because resolving a // later item may grow and relocate the Nav arena. bool navQueueValueAnalysis(Zcu* zcu, uint32_t nav_idx) { InternPool* ip = &zcu->intern_pool; if (nav_idx >= ip->navs_len) UNREACHABLE("queueing invalid Nav value"); IpNav* nav = &ip->navs[nav_idx]; if (nav->has_value_resolved || nav->analysis_wanted) return false; if (zcu->wanted_navs_len == zcu->wanted_navs_cap) { uint32_t next = zig1GrowCap(zcu->wanted_navs_cap, 16u); if (next < zcu->wanted_navs_cap) UNIMPLEMENTED("analyzeNavVal: Nav value queue capacity overflow"); #if SIZE_MAX < UINT64_MAX if ((uint64_t)next > (uint64_t)SIZE_MAX / sizeof(*zcu->wanted_navs)) UNIMPLEMENTED( "analyzeNavVal: Nav value queue allocation size overflow"); #endif uint32_t* grown = zig1Realloc(zcu->wanted_navs, (size_t)next * sizeof(*grown)); zcu->wanted_navs = grown; zcu->wanted_navs_cap = next; } nav->analysis_wanted = true; zcu->wanted_navs[zcu->wanted_navs_len++] = nav_idx; if (zcu->update_queue != NULL) compilationUpdateQueueNavFirstWant(zcu->update_queue, nav_idx); return true; } // A Nav whose resolved value is an extern function (not another extern). static bool navIsExternFn(const InternPool* ip, const IpNav* nav) { return nav->has_value_resolved && nav->resolved_value < ip->items_len && ip->item_tags[nav->resolved_value] == IP_TAG_EXTERN; } // (part of) InternPool.zig:getNav // analyzeNavRefInner's Nav canonicalization, the half analyzeNavVal needs. // `zirDeclVal` is `"const ref = try sema.analyzeNavRefInner(block, src, // nav_index, false); return sema.analyzeLoad(block, src, ref, src);"` -- a // Pointer, then a load -- and analyzeNavRefInner retargets a function-typed // Nav to the function's owner (`".func => |f| break :nav f.owner_nav,"`), // then builds the pointer from `"const nav_resolved = ip.getNav(nav_index) // .resolved.?;"`. So the loaded value and its type are the owner's: an alias // that coerced the function (`const a: fn (u8) anyerror!u8 = f`) loads `f` // itself, at `f`'s own type, exactly as `&a` already yields a pointer to `f`. // The original Nav still drives lookup and queueing, as upstream's // `orig_nav_index` does. uint32_t navAnalyzeVal(Zcu* zcu, uint32_t nav_idx, uint32_t* out_type) { const InternPool* ip = &zcu->intern_pool; uint32_t ty = IP_INDEX_NONE; uint32_t value = zptEnsureNavValUpToDate(zcu, nav_idx, &ty); // The pointer itself is interned before the load. A threadlocal or // non-function extern Nav gets a runtime or unported pointer instead; // their callers handle (or refuse) those loads. const IpNav* nav = ipGetNav(ip, nav_idx); bool extern_fn = navIsExternFn(ip, nav); if (!nav->is_threadlocal && (!nav->is_extern || extern_fn)) navDeclRefResolved(zcu, nav_idx, NULL); if (ty >= ip->items_len || ip->item_tags[ty] != IP_TAG_TYPE_FUNCTION) { if (out_type) *out_type = ty; return value; } uint32_t owner = ipFuncValueOwnerNav(ip, value); if (owner == IP_INDEX_NONE || owner == nav_idx) { if (out_type) *out_type = ty; return value; } if (owner >= ip->navs_len) UNREACHABLE("decl_val: function value has invalid owner Nav"); return zptEnsureNavValUpToDate(zcu, owner, out_type); } uint32_t navDeclRefResolved( Zcu* zcu, uint32_t nav_idx, uint32_t* out_ptr_type) { InternPool* ip = &zcu->intern_pool; if (nav_idx >= ip->navs_len) UNREACHABLE("decl_ref names invalid Nav"); // ensureNavTypeUpToDate queues the referenced Nav // before declared-type analysis. Type bodies may themselves reference and // queue Navs, so moving this later changes first-want/IP order. Function // aliases may canonicalize only the eventual PtrNav owner, never this // queue entry. navQueueValueAnalysis(zcu, nav_idx); uint32_t decl_ty = zptEnsureNavTypeUpToDate(zcu, nav_idx); if (decl_ty == IP_INDEX_NONE) zptEnsureNavValUpToDate(zcu, nav_idx, &decl_ty); const IpNav* nav = ipGetNav(ip, nav_idx); if (!nav->has_resolved || nav->resolved_type != decl_ty || !ipIndexIsType(ip, decl_ty)) UNIMPLEMENTED("decl_ref Nav type was not published"); // A function-typed Nav can be a function alias. Its owner cannot be known // from the published type alone, so mirror analyzeNavRefInner and force // full value resolution before choosing the Nav encoded by PtrNav. This // is essential for explicitly typed aliases, whose type body otherwise // allows address formation before the initializer is analyzed. if (ip->item_tags[decl_ty] == IP_TAG_TYPE_FUNCTION && !nav->has_value_resolved) { uint32_t value_ty = IP_INDEX_NONE; zptEnsureNavValUpToDate(zcu, nav_idx, &value_ty); nav = ipGetNav(ip, nav_idx); if (!nav->has_value_resolved || value_ty != decl_ty || nav->resolved_type != decl_ty) UNIMPLEMENTED("decl_ref: function Nav did not fully resolve"); } // Function aliases have no independent storage/address. Upstream // analyzeNavRefInner canonicalizes their resolved function value to the // function's owner Nav, so `&f == &alias` and both intern one PtrNav. uint32_t owner = nav->has_value_resolved ? ipFuncValueOwnerNav(ip, nav->resolved_value) : IP_INDEX_NONE; if (owner != IP_INDEX_NONE) { if (owner >= ip->navs_len) UNREACHABLE("decl_ref: function value has invalid owner Nav"); if (owner != nav_idx) { const IpNav* owner_nav = ipGetNav(ip, owner); if (!owner_nav->has_resolved || !owner_nav->has_value_resolved || !owner_nav->modifiers_resolved) UNIMPLEMENTED("decl_ref: function alias owner unresolved"); nav_idx = owner; nav = owner_nav; // Upstream reads `nav_resolved` from the owner Nav and builds the // pointer from its type (src/Sema.zig), so an alias // that coerced the function -- `const a: fn (u8) anyerror!u8 = // f` -- still yields a pointer to `f` at `f`'s own type. The // alias's declared type is not the pointee type and must not be // required to equal the owner's. decl_ty = nav->resolved_type; } } // analyzeNavRefInner's `.@"extern" => |e| e.owner_nav`: an extern // function's address is a ptr_nav of its owner Nav. Other externs have // no storage here. bool extern_fn = navIsExternFn(ip, nav); if (extern_fn) nav_idx = ipExternFunctionOwnerNav(ip, nav->resolved_value); else if (nav->is_extern) UNIMPLEMENTED("decl_ref: extern Nav pointer"); if (nav->is_threadlocal) UNIMPLEMENTED("decl_ref: threadlocal Nav pointer"); uint32_t ptr_ty = ipGetPtrType(ip, decl_ty, semaNavDirectPtrFlags(nav)); if (out_ptr_type) *out_ptr_type = ptr_ty; return ipGetPtrNav(ip, ptr_ty, nav_idx); } uint32_t navDeclRefTypeOnlyResolved(Zcu* zcu, uint32_t nav_idx) { const InternPool* ip = &zcu->intern_pool; if (nav_idx >= ip->navs_len) UNREACHABLE("decl_ref type-only names invalid Nav"); navQueueValueAnalysis(zcu, nav_idx); uint32_t decl_ty = zptEnsureNavTypeUpToDate(zcu, nav_idx); if (decl_ty == IP_INDEX_NONE) zptEnsureNavValUpToDate(zcu, nav_idx, &decl_ty); const IpNav* nav = ipGetNav(ip, nav_idx); if (!nav->has_resolved || nav->resolved_type != decl_ty || !ipIndexIsType(ip, decl_ty)) UNIMPLEMENTED("decl_ref type-only Nav type was not published"); return decl_ty; } uint32_t navDeclRefAddressResolved( Zcu* zcu, uint32_t nav_idx, uint32_t* out_ptr_type) { InternPool* ip = &zcu->intern_pool; uint32_t ptr_ty = IP_INDEX_NONE; uint32_t ptr = navDeclRefResolved(zcu, nav_idx, &ptr_ty); if (ip->item_tags[ptr_ty] != IP_TAG_TYPE_POINTER) UNIMPLEMENTED("decl_ref address: result is not a pointer type"); uint32_t child = ipPtrChild(ip, ptr_ty); if (ip->item_tags[child] == IP_TAG_TYPE_FUNCTION) { if (ip->item_tags[ptr] != IP_TAG_PTR_NAV) UNIMPLEMENTED("decl_ref address: function pointer is not a Nav"); uint32_t owner = ip->extra[ip->item_datas[ptr] + 1]; // PtrNav already contains navDeclRefResolved's alias-canonical owner. semaQueueFnPtrPending(ip, child, owner); } if (out_ptr_type) *out_ptr_type = ptr_ty; return ptr; } static bool navHasTypeBody(Zcu* zcu, uint32_t nav_idx) { InternPool* ip = &zcu->intern_pool; const IpNav* nav = ipGetNav(ip, nav_idx); if (!nav->has_analysis) return false; const Zir* zir = zcu->files[ipGetNamespace(ip, nav->analysis_namespace)->file_scope] .zir; DeclInfo decl; return zirDecodeDeclaration(zir, nav->analysis_inst, &decl) && decl.has_type_body; } // The Nav whose whole value body is a container declaration (`const S = // struct { ... }`) is `S` itself: zirStructDecl and its twins publish that // type as soon as it exists, so a field's self-reference finds it. static bool navValueIsContainerDecl(const Zir* zir, const DeclInfo* decl) { if (decl->value_body_len != 2) return false; uint32_t inst = zir->extra[decl->value_body_off]; if (zir->inst_tags[inst] != ZIR_INST_EXTENDED) return false; uint16_t op = zir->inst_datas[inst].extended.opcode; return op == ZIR_EXT_STRUCT_DECL || op == ZIR_EXT_UNION_DECL || op == ZIR_EXT_ENUM_DECL; } // PerThread.zig:analyzeNavVal // Analyze the value body in one Sema whatever it declares, coerce the result // to the declared type and resolve the Nav. An extern has no value body: its // value is getExtern of the declared type. static void zptAnalyzeNavVal(Zcu* zcu, uint32_t nav_idx) { const Zig1ErrorSource saved_error_source = zig1_error_source; zptErrorSourceNav(zcu, NULL, nav_idx); InternPool* ip = &zcu->intern_pool; const IpNav* nav = ipGetNav(ip, nav_idx); if (!nav->has_analysis) UNREACHABLE("analyzeNavVal: Nav has no analysis"); if (nav->value_resolving) UNIMPLEMENTED("analyzeNavVal: declaration value dependency cycle"); ip->navs[nav_idx].value_resolving = true; // Nested analysis can create Navs and move `ip->navs`. uint32_t nav_ns = nav->analysis_namespace; uint32_t analysis_inst = nav->analysis_inst; const Zir* zir = zcu->files[ipGetNamespace(ip, nav_ns)->file_scope].zir; DeclInfo decl; if (!zirDecodeFunctionDeclaration(zir, analysis_inst, &decl)) UNREACHABLE("analyzeNavVal: undecodable declaration"); // `".type = ... ensureNavResolved(..., .type)"` ran in the caller. uint32_t maybe_ty = decl.has_type_body ? nav->resolved_type : IP_INDEX_NONE; uint32_t final_val; if (!decl.has_value_body) { // `".@\"extern\" => val: { ... pt.getExtern(.{ ... })"`: linkage // .strong, source .syntax, the rest default. if (!nav->is_extern || maybe_ty == IP_INDEX_NONE) UNREACHABLE("analyzeNavVal: decl has no value body"); uint32_t library_name = IP_INDEX_NONE; if (decl.has_lib_name) { const char* library = (const char*)&zir->string_bytes[decl.lib_name]; library_name = ipGetOrPutString(ip, library, (uint32_t)strlen(library)); } nav = ipGetNav(ip, nav_idx); final_val = ipGetExtern(ip, nav->name, maybe_ty, library_name, ((uint32_t)GLOBAL_LINKAGE_STRONG << IP_EXTERN_FLAGS_LINKAGE_SHIFT) | IP_EXTERN_FLAGS_SOURCE, nav->analysis_zir_index, nav->is_const, nav->resolved_alignment, nav_idx); } else { uint32_t saved_self = zcu->container_self_nav; uint32_t saved_resolving = zcu->navstruct_resolving; bool is_container = navValueIsContainerDecl(zir, &decl); zcu->container_self_nav = is_container ? nav_idx : IP_INDEX_NONE; if (is_container) zcu->navstruct_resolving = nav_idx; final_val = zptAnalyzeDeclBody(zcu, zir, decl.value_body_off, decl.value_body_len, nav_ns, TYPE_NAME_CTX_NAV(ipGetNav(ip, nav_idx)), nav_idx, analysis_inst, maybe_ty, NULL); zcu->navstruct_resolving = saved_resolving; zcu->container_self_nav = saved_self; if (final_val == IP_INDEX_NONE) UNIMPLEMENTED("analyzeNavVal: value body produced no value"); if (!semaRefIsInterned(final_val)) UNIMPLEMENTED("analyzeNavVal: comptime cell as a Nav value"); } ip->navs[nav_idx].value_resolving = false; // `"const nav_ty: Type = maybe_ty orelse final_val.?.typeOf(zcu);"`. uint32_t nav_ty = maybe_ty != IP_INDEX_NONE ? maybe_ty : ipTypeOf(ip, final_val); if (!ipIndexIsType(ip, nav_ty)) UNIMPLEMENTED("analyzeNavVal: value of a type ipTypeOf cannot name"); navPublishResolvedValue(zcu, nav_idx, nav_ty, final_val); zig1_error_source = saved_error_source; } // PerThread.zig:ensureNavValUpToDate uint32_t zptEnsureNavValUpToDate( Zcu* zcu, uint32_t nav_idx, uint32_t* out_type) { const InternPool* ip = &zcu->intern_pool; if (nav_idx >= ip->navs_len) UNREACHABLE("ensureNavValUpToDate: invalid Nav index"); if (zcu->update_queue != NULL) compilationUpdateQueueNavValueEnter(zcu->update_queue, nav_idx); const IpNav* initial = ipGetNav(ip, nav_idx); if (initial->has_value_resolved) { if (!initial->has_resolved || !ipIndexIsType(ip, initial->resolved_type) || ipTypeOf(ip, initial->resolved_value) != initial->resolved_type) UNREACHABLE("ensureNavValUpToDate: malformed cached Nav"); if (!initial->modifiers_resolved) { uint32_t cached_type = initial->resolved_type; uint32_t cached_value = initial->resolved_value; navPublishResolvedValue(zcu, nav_idx, cached_type, cached_value); } } else { // Publish an explicit declared type before marking value resolution // in progress. This is the key cycle break for `var a: *T = &b`: // DECL_REF needs only the already-known pointer child, never the // initializer. analyzeNavVal resolves the type unit first only for a // typed decl (" otherwise we already resolved it with // `Sema.ensureNavResolved`"); an untyped decl's nav_ty is cleared when // its value is published. if (navHasTypeBody(zcu, nav_idx)) zptEnsureNavTypeUpToDate(zcu, nav_idx); if (!ip->navs[nav_idx].has_value_resolved) zptAnalyzeNavVal(zcu, nav_idx); } initial = ipGetNav(ip, nav_idx); if (out_type) *out_type = initial->resolved_type; return initial->resolved_value; } // ── Comptime units (analyzeComptimeUnit) ──────────────────────────────────── // PerThread.zig:analyzeComptimeUnit // Analyze a top-level `comptime` block in a Sema of its own (named // `{parent}.comptime`): its body runs through zptAnalyzeDeclBody, as a Nav's // value body does, and emits no AIR. void zptAnalyzeComptimeUnit(Zcu* zcu, const Zir* zir, uint32_t lookup_ns, uint32_t body_off, uint32_t body_len) { // `".type_name_ctx = try ip.getOrPutStringFmt(gpa, io, pt.tid, // \"{f}.comptime\", .{ parent_ns.name.fmt(ip) })"`, then // `.type_fqn_ctx` of `parent_ns.fqn`, before the body runs. InternPool* ip = &zcu->intern_pool; uint32_t owner = ipGetNamespace(ip, lookup_ns)->owner_type; uint32_t owner_name = ipContainerTypeName(ip, owner); TypeNameCtx name = TYPE_NAME_CTX_NONE; if (owner_name != IP_INDEX_NONE) { name.name = ipGetOrPutStringFmt( ip, "%s.comptime", ipStringBytes(ip, owner_name)); name.fqn = ipGetOrPutStringFmt(ip, "%s.comptime", ipStringBytes(ip, ipContainerTypeFqn(ip, owner))); } CtEvalStatus status; zptAnalyzeDeclBody(zcu, zir, body_off, body_len, lookup_ns, name, IP_INDEX_NONE, 0, IP_INDEX_NONE, &status); if (status == CT_EVAL_RETURN || status == CT_EVAL_REPEAT) UNREACHABLE("comptime unit: invalid top-level control flow"); } // ── Values and types PerThread interns ────────────────────────────────────── // Zcu/PerThread.zig:sliceToArrayPtr // `PerThread.sliceToArrayPtr`: a comptime slice's pointer `getCoerced` to // `*[len:s]T`, the slice's own pointer attributes at size `.one`, keeping its // sentinel on the array (`".sentinel = slice_info.sentinel,"`). uint32_t zptSliceToArrayPtr(InternPool* ip, uint32_t slice_val) { IpPtrInfo many; if (ipPtrInfo(ip, ip->item_datas[ipTypeOf(ip, slice_val)], &many) != IP_PTRI_OK) UNREACHABLE("sliceToArrayPtr: malformed slice type"); uint64_t len = ipReadIntValue(ip, ipPtrSliceLen(ip, slice_val)); if (len > UINT32_MAX) UNIMPLEMENTED("sliceToArrayPtr: wide slice length"); uint32_t array_ty = ipGetArrayType(ip, (uint32_t)len, many.child, many.sentinel); uint32_t ptr_ty = ipGetPtrTypeFull(ip, array_ty, (many.flags & ~IP_PTR_FLAGS_SIZE_MASK) | IP_PTR_SIZE_ONE, IP_INDEX_NONE, many.packed_offset); uint32_t casted = ipRetypePtrValue(ip, ipPtrSlicePtr(ip, slice_val), ptr_ty); if (casted == IP_INDEX_NONE) UNIMPLEMENTED("sliceToArrayPtr: slice pointer carrier"); return casted; } // PerThread.zig:getCoerced // `pt.getCoerced(val, ty)` for in-memory-coercible types: a function body // at a coerced function type, an undefined value, a slice or pointer // retyped, an integer re-interned at `ty`, else the coercion (a payload // wrapped in an optional or error union). uint32_t zptGetCoerced(Zcu* zcu, uint32_t val, uint32_t ty) { InternPool* ip = &zcu->intern_pool; if (ipTypeOf(ip, val) == ty) return val; uint32_t coerced = semaCoerceFuncValue(ip, val, ty); if (coerced == IP_INDEX_NONE) coerced = ipRetypePtrValue(ip, val, ty); if (coerced == IP_INDEX_NONE) coerced = ctCoerceIntAnyWidth(ip, val, ty); return coerced != IP_INDEX_NONE ? coerced : semaCoerceExtraOpts(zcu, ip, val, ty, false); } // PerThread.zig:aggregateValue // pt.aggregateValue (src/Zcu/PerThread.zig): a non-empty aggregate whose // every element is undefined is the undefined value of `ty`. uint32_t zptAggregateValue( InternPool* ip, uint32_t ty, const uint32_t* elems, uint32_t len) { for (uint32_t i = 0; i < len; i++) { if (!ipItemTagIs(ip, elems[i], IP_TAG_UNDEF)) return ipGetAggregate(ip, ty, elems, len); } return len > 0 ? ipGetUndef(ip, ty) : ipGetAggregate(ip, ty, elems, len); } // PerThread.zig:errorSetFromUnsortedNames // PerThread.errorSetFromUnsortedNames: sorts `names` in place // (NullTerminatedString.indexLessThan) and interns the set. uint32_t zptErrorSetFromUnsortedNames( InternPool* ip, uint32_t* names, uint32_t names_len) { for (uint32_t a = 1; a < names_len; a++) { uint32_t key = names[a], b = a; for (; b > 0 && names[b - 1] > key; b--) names[b] = names[b - 1]; names[b] = key; } return ipGetErrorSetType(ip, names, names_len); } // PerThread.zig:overflowArithmeticTupleType, PerThread.zig:vectorType // `Zcu.PerThread.overflowArithmeticTupleType`: `"const ov_ty: Type = if // (ty.zigTypeTag(zcu) == .vector) try pt.vectorType(.{ .len = // ty.vectorLen(zcu), .child = .u1_type }) else .u1;"` and then the two-field // tuple `{ ty, ov_ty }` with no defaults. The type `@addWithOverflow` and its // siblings answer with, in both frames: for a vector destination the overflow // half is `@Vector(len, u1)`, and a frame that spells a scalar `u1` there // types its own result wrong. uint32_t zptOverflowArithmeticTupleType( InternPool* ip, uint32_t ty, uint32_t* out_ov_ty) { uint32_t ov_ty = semaU1ShapedLike(ip, ty); if (out_ov_ty != NULL) *out_ov_ty = ov_ty; const uint32_t types[2] = { ty, ov_ty }; const uint32_t defaults[2] = { IP_INDEX_NONE, IP_INDEX_NONE }; return ipGetTupleType(ip, 2, types, defaults); } // PerThread.zig:intBitsForValue // `Zcu.PerThread.intBitsForValue`. static uint32_t zptIntBitsForValue(BigIntConst v, bool sign) { if (v.positive || bigIntEqlZero(v)) return (uint32_t)bigIntBitCountAbs(v) + (sign ? 1u : 0u); return (uint32_t)bigIntBitCountTwosComp(v); } // PerThread.zig:intFittingRange // `Zcu.PerThread.intFittingRange`: the signedness from `min < 0`, the width // from both bounds. uint32_t zptIntFittingRange(InternPool* ip, uint32_t min, uint32_t max) { uint64_t mn_space[2], mx_space[2]; BigIntConst mn, mx; if (!valToBigInt(ip, min, mn_space, &mn) || !valToBigInt(ip, max, mx_space, &mx)) UNIMPLEMENTED("intFittingRange: bound not an integer"); bool sign = !mn.positive && !bigIntEqlZero(mn); uint32_t a = zptIntBitsForValue(mn, sign); uint32_t b = zptIntBitsForValue(mx, sign); return ipGetIntType(ip, sign, a > b ? a : b); } // ── Namespaces and files (scanNamespace, ensureFilePopulated) ─────────────── static void zptNsListAppend( uint32_t** arr, uint32_t* len, uint32_t* cap, uint32_t v) { U32List list = { *arr, *len, *cap }; u32ListAppend(&list, v); *arr = list.items; *len = list.len; *cap = list.cap; } // Port of Zcu.File.renderFullyQualifiedName over File.sub_file_path: convert // separators to dots and strip the extension. Registration has already made // this path module-relative, so nested/child.zig becomes nested.child while // std's repo path remains the module-local debug.zig, etc. static uint32_t zptInternFileFqn(InternPool* ip, const char* sub_path) { size_t len = strlen(sub_path); size_t ext = len; size_t component = 0; for (size_t i = len; i-- > 0;) { if (sub_path[i] == '.') { ext = i; break; } if (sub_path[i] == '/' || sub_path[i] == '\\') { component = i + 1; break; } } if (ext < component) ext = len; char* buf = ARR_INIT(char, ext + 1); for (size_t i = 0; i < ext; i++) buf[i] = (sub_path[i] == '/' || sub_path[i] == '\\') ? '.' : sub_path[i]; buf[ext] = '\0'; uint32_t result = ipGetOrPutString(ip, buf, (uint32_t)ext); free(buf); return result; } // `std.fs.path.stem(file.sub_file_path)` (posix `basename`): the last // component without its final extension, unless that `.` leads it. static uint32_t zptInternFileStem(InternPool* ip, const char* sub_path) { size_t end = strlen(sub_path); while (end > 1 && sub_path[end - 1] == '/') end--; size_t start = end; while (start > 0 && sub_path[start - 1] != '/') start--; size_t stem_end = end; for (size_t i = end; i-- > start;) { if (sub_path[i] == '.') { if (i != start) stem_end = i; break; } } return ipGetOrPutString( ip, sub_path + start, (uint32_t)(stem_end - start)); } static bool zptNamespaceHasDeclName( InternPool* ip, uint32_t ns_idx, uint32_t name) { const IpNamespace* ns = ipGetNamespace(ip, ns_idx); for (uint32_t i = 0; i < ns->pub_navs_len; i++) if (ipGetNav(ip, ns->pub_navs[i])->name == name) return true; for (uint32_t i = 0; i < ns->priv_navs_len; i++) if (ipGetNav(ip, ns->priv_navs[i])->name == name) return true; for (uint32_t i = 0; i < ns->test_navs_len; i++) if (ipGetNav(ip, ns->test_navs[i])->name == name) return true; return false; } // ScanDeclIter.avoidNameConflict interns the unsuffixed candidate first, then // repeatedly appends _0, _1, ... to the previous candidate until it is new. static uint32_t zptInternUniqueTestName( InternPool* ip, uint32_t ns_idx, const char* candidate) { uint32_t name = ipGetOrPutString(ip, candidate, (uint32_t)strlen(candidate)); for (uint32_t suffix = 0; zptNamespaceHasDeclName(ip, ns_idx, name); suffix++) { name = ipGetOrPutStringFmt( ip, "%s_%u", ipStringBytes(ip, name), suffix); } return name; } // PerThread.zig:scanNamespace // Port of PerThread.scanNamespace / ScanDeclIter.scanDecl, restricted to what // file population needs: create a real Nav (or ComptimeUnit) per declaration // and file it into the namespace's decl lists. Two passes (named, then // unnamed) mirror upstream's conflict-avoidance ordering. Navs and // ComptimeUnits live in separate arenas, so this creates no InternPool items. void zptScanNamespace(PerThread* pt, uint32_t ns_idx, const uint32_t* decls, uint32_t decls_len, const Zir* zir) { InternPool* ip = &pt->zcu->intern_pool; // Pass 1: named declarations (const/var). for (uint32_t i = 0; i < decls_len; i++) { uint32_t inst = decls[i]; uint32_t id = zirDeclarationId(zir, inst); ZirDeclKind kind = zirDeclIdKind(id); if (kind != ZIR_DECL_KIND_CONST && kind != ZIR_DECL_KIND_VAR) continue; uint32_t name_z = zirDeclIdName(zir, inst); const char* name_str = (const char*)&zir->string_bytes[name_z]; uint32_t name = ipGetOrPutString(ip, name_str, (uint32_t)strlen(name_str)); uint32_t tracked = ipTrackZir(ip); // FQN: mirror namespace.internFullyQualifiedName — "." // where ns_name is the container type name of the namespace owner. // Interned immediately after the unqualified name (scanDecl order), so // the string arena matches upstream byte-for-byte. uint32_t fqn = ipInternNamespacedName(ip, ns_idx, name); uint32_t nav = ipCreateDeclNav(ip, name, fqn, tracked, inst, ns_idx, kind == ZIR_DECL_KIND_CONST, zirDeclIdIsThreadlocal(id), zirDeclIdIsExtern(id)); IpNamespace* ns = ipGetNamespace(ip, ns_idx); if (zirDeclIdIsPub(id)) zptNsListAppend( &ns->pub_navs, &ns->pub_navs_len, &ns->pub_navs_cap, nav); else zptNsListAppend( &ns->priv_navs, &ns->priv_navs_len, &ns->priv_navs_cap, nav); } // Pass 2: unnamed declarations (comptime blocks, tests). uint32_t unnamed_test_index = 0; for (uint32_t i = 0; i < decls_len; i++) { uint32_t inst = decls[i]; uint32_t id = zirDeclarationId(zir, inst); ZirDeclKind kind = zirDeclIdKind(id); IpNamespace* ns = ipGetNamespace(ip, ns_idx); switch (kind) { case ZIR_DECL_KIND_CONST: case ZIR_DECL_KIND_VAR: continue; case ZIR_DECL_KIND_COMPTIME: { uint32_t tracked = ipTrackZir(ip); uint32_t cu = ipCreateComptimeUnit(ip, tracked, ns_idx); zptNsListAppend(&ns->comptime_units, &ns->comptime_units_len, &ns->comptime_units_cap, cu); break; } case ZIR_DECL_KIND_TEST: case ZIR_DECL_KIND_DECLTEST: case ZIR_DECL_KIND_UNNAMED_TEST: { // Tests are collected as Navs in `test_navs`. Upstream generates // the name via avoidNameConflict: unnamed_test -> "test_{index}"; // test/decltest -> "{kind}.{declname}" (e.g. "decltest.foo"). Then // the fqn is "{ns_name}.{name}". Name-conflict suffixing is not // reproduced. char* namebuf; uint32_t name; if (kind == ZIR_DECL_KIND_UNNAMED_TEST) { namebuf = ARR_INIT(char, 24); snprintf(namebuf, 24, "test_%u", unnamed_test_index++); } else { uint32_t name_z = zirDeclIdName(zir, inst); const char* s = (const char*)&zir->string_bytes[name_z]; const char* prefix = (kind == ZIR_DECL_KIND_DECLTEST) ? "decltest" : "test"; size_t cap = strlen(prefix) + 1 + strlen(s) + 1; namebuf = ARR_INIT(char, cap); snprintf(namebuf, cap, "%s.%s", prefix, s); } name = zptInternUniqueTestName(ip, ns_idx, namebuf); free(namebuf); uint32_t fqn = ipInternNamespacedName(ip, ns_idx, name); uint32_t tracked = ipTrackZir(ip); uint32_t nav = ipCreateDeclNav( ip, name, fqn, tracked, inst, ns_idx, true, false, false); zptNsListAppend( &ns->test_navs, &ns->test_navs_len, &ns->test_navs_cap, nav); break; } } } // The observer sees a completed two-pass namespace: all test Navs are // stable indices and declaration order is still available from `decls`. // It is installed by Compilation.update's queue and must not retain // `decls`. if (pt->zcu->namespace_scan_observer != NULL) pt->zcu->namespace_scan_observer(pt->zcu->namespace_scan_observer_ctx, ns_idx, zir, decls, decls_len); } // PerThread.zig:ensureFilePopulated uint32_t zptEnsureFilePopulated(PerThread* pt, uint32_t file_idx) { Zcu* zcu = pt->zcu; InternPool* ip = &zcu->intern_pool; ZcuFile* file = &zcu->files[file_idx]; if (file->root_type != IP_INDEX_NONE) return file->root_type; // already good const Zir* zir = file->zir; // A Zig source file's implicit root struct is `.auto` layout. It may hold // top-level container fields (a "type file", e.g. `value: u32,` at file // scope) as well as declarations. Decode fields_len, the layout flags, and // the declaration list. Mirrors upstream `ensureFilePopulated`: the field // names and types are not resolved here — `getDeclaredStructType` writes // placeholders (field_name=0, field_type=.none) that resolveStructLayout // fills in (semaDeferStructFields). So this is a populate-only step: // fields_len + flags + namespace scan. ZirStructDecl zd = zirGetStructDecl(zir, ZIR_MAIN_STRUCT_INST); StructDeclSmall sd = semaDecodeStructDeclSmall( zir->inst_datas[ZIR_MAIN_STRUCT_INST].extended.small); uint32_t fields_len = zd.fields_len; if (sd.layout != CONTAINER_LAYOUT_AUTO) UNIMPLEMENTED("ensureFilePopulated: non-auto root struct layout"); uint32_t tracked_inst = ipTrackZir(ip); uint32_t struct_type = ipGetDeclaredStructType(ip, tracked_inst, /*captures_len=*/0, fields_len, IP_STRUCT_LAYOUT_AUTO, sd.any_comptime_fields, sd.any_field_defaults, sd.any_field_aligns); // `wip.setName(ip, ip.getOrPutString(std.fs.path.stem( // file.sub_file_path)), file.internFullyQualifiedName(pt), .none)` — // interns the file's stem, then its fully-qualified name (e.g. "std"). // Ordered before scanNamespace so the string arena matches upstream. // The borrowed root/test file (sub_path == NULL) has no on-disk // module-relative path; upstream its `sub_file_path` is the single-file // module's own name, so its FQN is the compilation root_name (e.g. // "main"). Interning it here, before the namespace scan, makes the file's // decl FQNs render "main.f" rather than ".f", as upstream's do. if (file->sub_path != NULL) { const char* sub_file_path = file->sub_file_path != NULL ? file->sub_file_path : file->sub_path; uint32_t file_stem = zptInternFileStem(ip, sub_file_path); uint32_t file_fqn = zptInternFileFqn(ip, sub_file_path); ipSetTypeName(ip, struct_type, file_stem, file_fqn, IP_INDEX_NONE); } else if (zcu->comp != NULL && zcu->comp->config.root_name != NULL) { const char* rn = zcu->comp->config.root_name; uint32_t fqn = ipGetOrPutString(ip, rn, (uint32_t)strlen(rn)); ipSetTypeName(ip, struct_type, fqn, fqn, IP_INDEX_NONE); } uint32_t ns_idx = ipCreateNamespace(ip, IP_NAMESPACE_NONE, struct_type, file_idx); // Backpatch the root struct's namespace word (mirrors WipContainerType. // finish, as the inline struct_decl path does), so a namespace-member // call `imported.member(...)` finds the member via // ipStructNamespaceOrNone(root_type). ipSetStructNamespace(ip, struct_type, ns_idx); semaDeferStructFields(zcu, struct_type, zir, ZIR_MAIN_STRUCT_INST); zptScanNamespace(pt, ns_idx, &zir->extra[zd.decls], zd.decls_len, zir); file->root_type = struct_type; file->ns = ns_idx; return struct_type; } uint32_t zptCreateNamespace(Zcu* zcu, const Zir* zir, uint32_t container_ty, uint32_t parent_ns_opt, const uint32_t* decls_ptr, uint32_t decls_len) { InternPool* ip = &zcu->intern_pool; uint32_t file_idx = semaZcuFileIdxOfZir(zcu, zir); uint32_t parent_ns = (parent_ns_opt != IP_NAMESPACE_NONE) ? parent_ns_opt : zcu->files[file_idx].ns; uint32_t ns_idx = ipCreateNamespace(ip, parent_ns, container_ty, file_idx); if (ipTagIsAnyUnion(ip->item_tags[container_ty])) ipSetUnionNamespace(ip, container_ty, ns_idx); else ipSetStructNamespace(ip, container_ty, ns_idx); if (decls_len != 0) { PerThread pt = { .zcu = zcu, .zir = zir }; zptScanNamespace(&pt, ns_idx, decls_ptr, decls_len, zir); } return ns_idx; } // ── Function bodies (analyzeFuncBody) ─────────────────────────────────────── // A Sema error while `nav` is analyzed is reported at its declaration: the // declaration's file and the line and column it starts at. `zir` names the // file when `nav` has no declaration to point at. static void zptErrorSourceNav(Zcu* zcu, const Zir* zir, uint32_t nav_idx) { zig1ErrorSourceClear(); InternPool* ip = &zcu->intern_pool; if (nav_idx == IP_INDEX_NONE || nav_idx >= ip->navs_len || ipGetNav(ip, nav_idx)->analysis_namespace >= ip->namespaces_len) { for (uint32_t fi = 0; fi < zcu->num_files; fi++) if (zcu->files[fi].zir == zir) zig1_error_source.path = zcu->files[fi].error_path; return; } const ZcuFile* file = &zcu->files[ipGetNamespace( ip, ipGetNav(ip, nav_idx)->analysis_namespace) ->file_scope]; zig1_error_source.path = file->error_path; zir = file->zir; uint32_t inst = ipGetNav(ip, nav_idx)->analysis_inst; if (inst >= zir->inst_len || zir->inst_tags[inst] != ZIR_INST_DECLARATION) return; uint64_t flags = zirDeclarationFlags(zir, inst); zig1_error_source.line = (uint32_t)((flags >> ZIR_DECL_FLAGS_SRC_LINE_SHIFT) & ZIR_DECL_FLAGS_SRC_LINE_MASK) + 1u; zig1_error_source.column = (uint32_t)((flags >> ZIR_DECL_FLAGS_SRC_COLUMN_SHIFT) & ZIR_DECL_FLAGS_SRC_COLUMN_MASK) + 1u; } // PerThread.zig:analyzeFuncBody Air zptAnalyzeFuncBody(const PerThread* pt, uint32_t func_index, uint32_t func_decl_inst, const uint32_t* param_insts, const uint32_t* param_types, uint32_t nparams, const uint32_t* comptime_args, uint32_t override_ret_ty, uint32_t fn_name_ctx, TypeNameCtx type_name_ctx, uint32_t owner_nav, uint32_t branch_quota, U32List* pending_navs) { const Zir* zir = pt->zir; ZirInstTag ftag = zir->inst_tags[func_decl_inst]; if (!zirIsFuncInst(ftag)) UNREACHABLE("zptAnalyzeFuncBody: not a function instruction"); ZirFnInfo fn_info = zirGetFnInfo(zir, func_decl_inst); uint32_t body_len = fn_info.body_len; const uint32_t* body = fn_info.body; // The function's return type (Sema.resolveRetTy). uint32_t fn_ret_ty; if (override_ret_ty != IP_INDEX_NONE) { // A generic func_instance: the owner's `[generic]` return type // resolves to `generic_poison`, so the instance's monomorphized // return type stands in. fn_ret_ty = override_ret_ty; } else if (ftag == ZIR_INST_FUNC_FANCY && fn_info.ret_ty_is_generic) { UNIMPLEMENTED("analyzeFuncBody: fancy generic ret w/o override"); } else if (fn_info.ret_ty_body_len != 0) { uint32_t last = fn_info.ret_ty_body[fn_info.ret_ty_body_len - 1]; if (zir->inst_tags[last] != ZIR_INST_BREAK_INLINE) UNREACHABLE("zptAnalyzeFuncBody: ret body last not break_inline"); fn_ret_ty = ctResolveRetTypeBody(pt->zcu, zir, fn_info.ret_ty_body, fn_info.ret_ty_body_len, zir->inst_datas[last].break_data.operand, type_name_ctx); } else { fn_ret_ty = ctResolveParamTypeRef( &pt->zcu->intern_pool, pt->zcu, zir, fn_info.ret_ty_ref); } AirInstList insts = { 0 }; U32List main_block = { 0 }; // inst -> resolved value map (indexed by ZIR instruction index), so a // `ret` can reference the value produced by a prior instruction (e.g. // `int` or a parameter's `arg`). `inst_types` holds each such value's // InternPool type (needed to coerce runtime AIR-ref operands). uint32_t* inst_values = semaInstMapAlloc(zir); uint32_t* inst_types = semaInstMapAlloc(zir); // "Runtime semantic analysis" prologue (PerThread.analyzeFuncBody): map // each parameter's ZIR instruction to an AIR `arg` instruction, emitted as // the first instructions of the function body. The `arg` data is // { ty = param type Ref, zir_param_index }. Each param's ZIR inst is bound // to its `arg` AIR Ref so the body (e.g. `return x`) resolves to it. // // For a generic instance, a `comptime` parameter (bit set in // `comptime_args`) emits no `arg`: its ZIR inst is bound directly to the // bound comptime value (Sema binds generic comptime params to their // instantiation args). A runtime parameter's `arg` keeps its original // signature position `i` as src_index (matching upstream, which numbers // args by the generic owner's parameter order even after comptime params // are dropped from the instance signature). for (uint32_t i = 0; i < nparams; i++) { if (comptime_args != NULL && comptime_args[i] != IP_INDEX_NONE) { // Comptime parameter: bind its ZIR inst to the interned comptime // value; no AIR `arg` instruction. (An IP index < // AIR_REF_INST_BASE is its own AIR Ref, so a later use resolves to // a constant.) inst_values[param_insts[i]] = comptime_args[i]; inst_types[param_insts[i]] = param_types[i]; continue; } // Zcu.PerThread.analyzeFnBody binds a runtime-position parameter // whose type has one possible value directly to that value and emits // no AIR `arg`: "if (try param_ty.onePossibleValue(pt)) |opv| {" // whose body is "gop.value_ptr.* = .fromValue(opv);" then // "continue;". This // matters even though the parameter is not declared `comptime`: a // single-field enum, for example, is runtime-position but its value // is known from its type. The interpreter's AIR arg consumer already // mirrors the backend's zero-runtime-bit argument skip, so later // runtime parameters remain positionally aligned. uint32_t opv = ztOnePossibleValue(&pt->zcu->intern_pool, param_types[i]); if (opv != IP_INDEX_NONE) { inst_values[param_insts[i]] = opv; inst_types[param_insts[i]] = param_types[i]; continue; } AirInstData data; memset(&data, 0, sizeof(data)); data.pair.a = param_types[i]; // ty Ref (== static IP index) data.pair.b = i; // zir_param_index (original signature position) uint32_t air_idx = semaAirInstAppend(&insts, AIR_INST_ARG, data); u32ListAppend(&main_block, air_idx); inst_values[param_insts[i]] = AIR_REF_INST_BASE + air_idx; inst_types[param_insts[i]] = param_types[i]; } // Build the AIR `extra` array incrementally (mirrors PerThread. // analyzeFuncBodyInner). Slot 0 is ExtraIndex.main_block (patched at the // end). Nested blocks/condbrs append their payloads during analysis. U32List extra = { 0 }; u32ListAppend(&extra, 0); // ExtraIndex.main_block placeholder // Fresh per-body comptime state (comptime-mutable cells + inline break // signal) for inline (comptime-unrolled) control flow. InlineCtx if_ctx; memset(&if_ctx, 0, sizeof(if_ctx)); if_ctx.fn_name_ctx = fn_name_ctx; InlineCtx* ictx = &if_ctx; uint32_t saved_owner_nav = pt->zcu->sema_owner_nav; pt->zcu->sema_owner_nav = owner_nav; const Zig1ErrorSource saved_error_source = zig1_error_source; zptErrorSourceNav(pt->zcu, zir, owner_nav); ZcuSemaBranch body_branch; ZcuSemaBranch* saved_branch = semaBranchEnter(pt->zcu, &body_branch); body_branch.owner_func = func_index; // PerThread.analyzeFnBody: `".branch_quota = // @max(func.branchQuotaUnordered(ip), Sema.default_branch_quota),"`. if (branch_quota > body_branch.quota) body_branch.quota = branch_quota; SemaCtx sema = { .ip = &pt->zcu->intern_pool, .zir = zir, .fn_ret_ty = fn_ret_ty, .insts = &insts, .inst_values = inst_values, .inst_types = inst_types, .air_extra = &extra, .zcu = pt->zcu, .pending_navs = pending_navs, .type_name_ctx = type_name_ctx, .ictx = ictx }; analyzeBodyInner(&sema, &main_block, body, body_len); // `"The lack of a resolve_inferred_alloc means that this instruction is // unused so it just has to be a no-op."`: `.alloc` of // `.ptr_const_comptime_int`. An unresolved placeholder still has type 0 // (semaEmitInferredAllocPlaceholder). for (uint32_t i = 0; i < insts.len; i++) if (insts.tags[i] == AIR_INST_ALLOC && insts.datas[i].pair.a == 0) { insts.datas[i].pair.a = ZIR_REF_PTR_CONST_COMPTIME_INT_TYPE; insts.datas[i].pair.b = 0; } semaBranchLeave(pt->zcu, saved_branch); pt->zcu->sema_owner_nav = saved_owner_nav; zig1_error_source = saved_error_source; free(if_ctx.merges); // Block.Merges storage for this body free(inst_values); free(inst_types); // Append the main block payload (Air.Block{body_len} + body insts) and // patch extra[0] = main_block_index (mirrors PerThread after analysis). uint32_t main_block_index = extra.len; u32ListAppend(&extra, main_block.len); // Air.Block.body_len for (uint32_t i = 0; i < main_block.len; i++) u32ListAppend(&extra, main_block.items[i]); extra.items[0] = main_block_index; free(main_block.items); // Analysis-time only: the emitted `Air` below takes `tags`/`datas`, never // this list. Air air; air.inst_len = insts.len; air.inst_tags = insts.tags; air.inst_datas = insts.datas; air.extra_len = extra.len; air.extra = extra.items; return air; } // ── Test functions (populateTestFunctions) ────────────────────────────────── // PerThread.zig:populateTestFunctions sets builtin.test_functions' value to a // slice of every Zcu.test_functions entry, {fqn, &test}; each test's body is // in `func_airs` for the interpreter. void zptPopulateTestFunctions(Zcu* zcu, const SemaFuncAirList* func_airs) { InternPool* ip = &zcu->intern_pool; uint32_t builtin_file_idx = zptDemandLoadBuiltin(zcu); PerThread pt = { .zcu = zcu, .zir = zcu->files[builtin_file_idx].zir }; zptEnsureFilePopulated(&pt, builtin_file_idx); uint32_t test_fns_nav_index = ipNamespaceLookup(ip, zcu->files[builtin_file_idx].ns, ipGetOrPutString(ip, "test_functions", 14)); if (test_fns_nav_index == IP_INDEX_NONE) UNREACHABLE("populateTestFunctions: builtin has no test_functions"); zptEnsureNavValUpToDate(zcu, test_fns_nav_index, NULL); const uint32_t slice_ty = ip->navs[test_fns_nav_index].resolved_type; const uint32_t many_ptr_ty = ip->item_datas[slice_ty]; IpPtrInfo ptr_info; if (ip->item_tags[slice_ty] != IP_TAG_TYPE_SLICE || ipPtrInfo(ip, many_ptr_ty, &ptr_info) != IP_PTRI_OK) UNREACHABLE("populateTestFunctions: test_functions is not a slice"); const uint32_t test_fn_ty = ptr_info.child; const uint32_t func_ptr_ty = ipStructFieldType(ip, test_fn_ty, 1); const U32List* tests = &zcu->test_functions; uint32_t* test_fn_vals = zig1Malloc((size_t)(tests->len + 1) * sizeof(*test_fn_vals)); for (uint32_t i = 0; i < tests->len; i++) { const uint32_t test_nav_index = tests->items[i]; const IpNav* test_nav = ipGetNav(ip, test_nav_index); if (!test_nav->has_value_resolved || semaFuncAirListFind(func_airs, test_nav->resolved_value) == NULL) UNREACHABLE("populateTestFunctions: test has no analyzed body"); const uint32_t name_len = (uint32_t)strlen(ipStringBytes(ip, test_nav->fqn)); const uint32_t name_ty = ipGetArrayType(ip, name_len, ZIR_REF_U8_TYPE, IP_INDEX_NONE); const uint32_t name_val = ipInternBytesAggregate(ip, name_ty, test_nav->fqn, name_len); const uint32_t fields[2] = { ipGetPtrSlice(ip, ZIR_REF_SLICE_CONST_U8_TYPE, ipGetPtrUav(ip, ZIR_REF_MANYPTR_CONST_U8_TYPE, name_val), ipGetIntUsize(ip, name_len)), ipGetPtrNav(ip, func_ptr_ty, test_nav_index), }; test_fn_vals[i] = ipGetAggregate(ip, test_fn_ty, fields, 2); } const uint32_t array_ty = ipGetArrayType(ip, tests->len, test_fn_ty, IP_INDEX_NONE); const uint32_t array_val = ipGetAggregate(ip, array_ty, test_fn_vals, tests->len); free(test_fn_vals); const uint32_t new_init = ipGetPtrSlice(ip, slice_ty, ipGetPtrUav(ip, many_ptr_ty, array_val), ipGetIntUsize(ip, tests->len)); IpNav* test_fns_nav = &ip->navs[test_fns_nav_index]; test_fns_nav->resolved_value = new_init; test_fns_nav->has_value_resolved = true; } /* ---- end zcu_per_thread.c ---- */ /* ---- begin zir.c ---- */ // zir.c — ZIR data structure helpers, ported from lib/std/zig/Zir.zig. #ifndef ZIG1_H #include "zig1.h" #endif // Aborts naming the Zir.Inst.Tag or Zir.Inst.Extended opcode with no arm. void zirAbortUnhandled(uint32_t tag, bool extended) { #define ZIR_TAG_NAME_CASE(T) \ case T: \ name = #T; \ break; const char* name = "out of range"; if (extended) { switch (tag) { ZIR_EXT_FOREACH_TAG(ZIR_TAG_NAME_CASE) } } else { switch (tag) { ZIR_INST_FOREACH_TAG(ZIR_TAG_NAME_CASE) } } #undef ZIR_TAG_NAME_CASE char msg[96]; snprintf(msg, sizeof msg, "analyzeBodyInner: unhandled %s", name); UNIMPLEMENTED(msg); } #include // Zir.zig:deinit void zirDeinit(Zir* zir) { free(zir->inst_tags); zir->inst_tags = NULL; free(zir->inst_datas); zir->inst_datas = NULL; free(zir->extra); zir->extra = NULL; free(zir->string_bytes); zir->string_bytes = NULL; zir->inst_len = zir->inst_cap = 0; zir->extra_len = zir->extra_cap = 0; zir->string_bytes_len = zir->string_bytes_cap = 0; } // (part of) Zir.zig:getStructDecl ZirContainerTrailing zirContainerTrailingWords(const Zir* zir, uint32_t inst) { uint16_t small = zir->inst_datas[inst].extended.small; uint32_t ci = zir->inst_datas[inst].extended.operand; bool has_fields = false, has_arg = false; switch ((ZirInstExtended)zir->inst_datas[inst].extended.opcode) { case ZIR_EXT_STRUCT_DECL: ci += (uint32_t)(sizeof(ZirInstStructDecl) / sizeof(uint32_t)); has_fields = (small & ZIR_STRUCT_SMALL_HAS_FIELDS_LEN) != 0; has_arg = (small & ZIR_STRUCT_SMALL_HAS_BACKING_INT_TYPE) != 0; break; case ZIR_EXT_ENUM_DECL: ci += (uint32_t)(sizeof(ZirInstEnumDecl) / sizeof(uint32_t)); has_fields = (small & ZIR_ENUM_SMALL_HAS_FIELDS_LEN) != 0; has_arg = (small & ZIR_ENUM_SMALL_HAS_TAG_TYPE) != 0; break; case ZIR_EXT_UNION_DECL: ci += (uint32_t)(sizeof(ZirInstUnionDecl) / sizeof(uint32_t)); has_fields = (small & ZIR_UNION_SMALL_HAS_FIELDS_LEN) != 0; has_arg = zirUnionKindHasArgType( (small >> ZIR_UNION_SMALL_KIND_SHIFT) & ZIR_UNION_SMALL_KIND_MASK); break; case ZIR_EXT_OPAQUE_DECL: ci += (uint32_t)(sizeof(ZirInstOpaqueDecl) / sizeof(uint32_t)); break; default: UNREACHABLE("zirContainerTrailingWords: not a container decl"); } // has_captures_len and has_decls_len are bits 0 and 1 of every Small. ZirContainerTrailing t = { 0 }; if ((small & ZIR_STRUCT_SMALL_HAS_CAPTURES_LEN) != 0) t.captures_len = zir->extra[ci++]; if ((small & ZIR_STRUCT_SMALL_HAS_DECLS_LEN) != 0) t.decls_len = zir->extra[ci++]; if (has_fields) t.fields_len = zir->extra[ci++]; if (has_arg) ci++; // arg_baseline_src_node if (has_fields) ci++; // fields_baseline_src_node if (has_arg) t.arg_body_len = zir->extra[ci++]; t.captures = ci; return t; } // Zir.zig:getStructDecl // The struct_decl's trailing arrays as extra indices; an absent optional // array (`any_field_aligns` and the like unset) is UINT32_MAX. ZirStructDecl zirGetStructDecl(const Zir* zir, uint32_t struct_decl) { uint16_t small = zir->inst_datas[struct_decl].extended.small; ZirContainerTrailing t = zirContainerTrailingWords(zir, struct_decl); ZirStructDecl d; d.captures_len = t.captures_len; d.fields_len = t.fields_len; d.decls_len = t.decls_len; uint32_t ei = t.captures + 2u * t.captures_len; // captures, capture_names d.decls = ei; ei += t.decls_len; d.field_names = ei; ei += t.fields_len; d.field_type_body_lens = ei; ei += t.fields_len; d.field_align_body_lens = UINT32_MAX; if ((small & ZIR_STRUCT_SMALL_ANY_FIELD_ALIGNS) != 0) { d.field_align_body_lens = ei; ei += t.fields_len; } d.field_default_body_lens = UINT32_MAX; if ((small & ZIR_STRUCT_SMALL_ANY_FIELD_DEFAULTS) != 0) { d.field_default_body_lens = ei; ei += t.fields_len; } d.field_comptime_bits = UINT32_MAX; if ((small & ZIR_STRUCT_SMALL_ANY_COMPTIME_FIELDS) != 0) { d.field_comptime_bits = ei; ei += (t.fields_len + 31u) / 32u; } d.backing_int_type_body = ei; d.backing_int_type_body_len = t.arg_body_len; d.field_bodies = ei + t.arg_body_len; return d; } // Zir.zig:UnwrappedStructDecl.FieldIterator.next // The next field's name, comptime bit and bodies (extra index and length; // an absent body has length 0). False past the last field. bool zirStructFieldIteratorNext(const Zir* zir, const ZirStructDecl* d, ZirStructFieldIterator* it, ZirStructField* out) { uint32_t idx = it->next_idx; if (idx == d->fields_len) return false; it->next_idx++; out->idx = idx; out->name = zir->extra[d->field_names + idx]; out->type_body = it->bodies; out->type_body_len = zir->extra[d->field_type_body_lens + idx]; it->bodies += out->type_body_len; out->align_body = it->bodies; out->align_body_len = d->field_align_body_lens == UINT32_MAX ? 0 : zir->extra[d->field_align_body_lens + idx]; it->bodies += out->align_body_len; out->default_body = it->bodies; out->default_body_len = d->field_default_body_lens == UINT32_MAX ? 0 : zir->extra[d->field_default_body_lens + idx]; it->bodies += out->default_body_len; out->is_comptime = d->field_comptime_bits != UINT32_MAX && ((zir->extra[d->field_comptime_bits + idx / 32u] >> (idx % 32u)) & 1u) != 0; return true; } // Zir.zig:getSwitchBlock // Zir.getSwitchBlock. ZirSwitchBlock zirGetSwitchBlock(const Zir* zir, uint32_t switch_inst) { bool has_non_err = zir->inst_tags[switch_inst] == ZIR_INST_SWITCH_BLOCK_ERR_UNION; uint32_t ei = zir->inst_datas[switch_inst].pl_node.payload_index; ZirSwitchBlock s = { 0 }; s.main_operand = zir->extra[ei]; uint32_t bits = zir->extra[ei + 1]; ei += 2; s.has_else = (bits & ZIR_SWITCH_BITS_HAS_ELSE) != 0; s.has_under = (bits & ZIR_SWITCH_BITS_HAS_UNDER) != 0; s.has_continue = (bits & ZIR_SWITCH_BITS_HAS_CONTINUE) != 0; s.any_maybe_runtime_capture = (bits & ZIR_SWITCH_BITS_ANY_MAYBE_RUNTIME_CAPTURE) != 0; s.scalar_cases_len = bits >> ZIR_SWITCH_BITS_SCALAR_CASES_LEN_SHIFT; s.multi_cases_len = (bits & ZIR_SWITCH_BITS_HAS_MULTI_CASES) ? zir->extra[ei++] : 0; s.payload_capture_placeholder = (bits & ZIR_SWITCH_BITS_PAYLOAD_CAPTURE_INST_IS_PLACEHOLDER) ? zir->extra[ei++] : UINT32_MAX; s.tag_capture_placeholder = (bits & ZIR_SWITCH_BITS_TAG_CAPTURE_INST_IS_PLACEHOLDER) ? zir->extra[ei++] : UINT32_MAX; if (has_non_err) { ei++; // catch_or_if_src_node_offset s.non_err_info = zir->extra[ei++]; } if (s.has_else) s.else_info = zir->extra[ei++]; s.prong_infos = &zir->extra[ei]; ei += s.scalar_cases_len + s.multi_cases_len; s.multi_case_items_lens = &zir->extra[ei]; ei += s.multi_cases_len; s.item_infos_len = s.scalar_cases_len; for (uint32_t m = 0; m < s.multi_cases_len; m++) s.item_infos_len += s.multi_case_items_lens[m]; if (bits & ZIR_SWITCH_BITS_ANY_RANGES) { s.multi_case_ranges_lens = &zir->extra[ei]; ei += s.multi_cases_len; for (uint32_t m = 0; m < s.multi_cases_len; m++) s.item_infos_len += 2 * s.multi_case_ranges_lens[m]; } s.item_infos = &zir->extra[ei]; ei += s.item_infos_len; if (has_non_err) { s.non_err_body = &zir->extra[ei]; ei += s.non_err_info & ZIR_PRONG_NON_ERR_BODY_LEN_MASK; } s.else_body = &zir->extra[ei]; s.else_body_len = s.else_info & ZIR_PRONG_ELSE_BODY_LEN_MASK; s.else_capture = (s.else_info >> ZIR_PRONG_ELSE_CAPTURE_SHIFT) & ZIR_PRONG_ELSE_CAPTURE_MASK; s.else_is_inline = (s.else_info & ZIR_PRONG_ELSE_IS_INLINE) != 0; s.end = ei + s.else_body_len; return s; } // ── Declarations, function instructions and refs ──────────────────────────── // Does the function-definition instruction `func_inst` declare an inferred // error set (`fn f() !T`)? True for `func_inferred`, and for a `func_fancy` // whose is_inferred_error bit is set (e.g. `noinline fn f() !T`). Used to // decide whether the body must be lowered against the interned error-union // return type rather than the bare ZIR ret-body payload. bool zirFuncInstIsInferred(const Zir* zir, uint32_t func_inst) { ZirInstTag t = zir->inst_tags[func_inst]; if (t == ZIR_INST_FUNC_INFERRED) return true; if (t == ZIR_INST_FUNC_FANCY) { ZirInstFuncFancy extra; zirExtraData( zir, zir->inst_datas[func_inst].pl_node.payload_index, &extra); return (extra.bits & ZIR_FUNC_FANCY_BITS_IS_INFERRED_ERROR) != 0; } return false; } // --- Zir.Inst.Declaration decoding --- // Mirrors the payload layout written by AstGen.setDeclaration (astgen.c). The // `Declaration.Flags.Id` (enum(u5)) discriminates which trailing fields/body // lengths are present. We only decode what the AIR harness needs so far: the // declaration name and the value body. // True iff the declaration has `export` linkage (i.e. `export fn` or // `pub export fn`). Only exported functions appear in the reference AIR // output; non-exported (pub/private) functions are skipped unless called // from an exported root. Mirrors air_gen's collect-only-for-used // behavior: Sema only analyzes functions that are reachable from roots. static bool zirDeclIdIsExport(uint32_t id) { return id == DECL_ID_EXPORT_CONST || id == DECL_ID_PUB_EXPORT_CONST; } ZirDeclKind zirDeclIdKind(uint32_t id) { switch (id) { case DECL_ID_UNNAMED_TEST: return ZIR_DECL_KIND_UNNAMED_TEST; case DECL_ID_TEST: return ZIR_DECL_KIND_TEST; case DECL_ID_DECLTEST: return ZIR_DECL_KIND_DECLTEST; case DECL_ID_COMPTIME: return ZIR_DECL_KIND_COMPTIME; default: if (id >= DECL_ID_VAR_SIMPLE) return ZIR_DECL_KIND_VAR; return ZIR_DECL_KIND_CONST; } } // Is this decl Id `pub`? (Declaration.Unwrapped.Id.isPub.) bool zirDeclIdIsPub(uint32_t id) { switch (id) { case DECL_ID_PUB_CONST_SIMPLE: case DECL_ID_PUB_CONST_TYPED: case DECL_ID_PUB_CONST: case DECL_ID_PUB_EXTERN_CONST_SIMPLE: case DECL_ID_PUB_EXTERN_CONST: case DECL_ID_PUB_EXPORT_CONST: case DECL_ID_PUB_VAR_SIMPLE: case DECL_ID_PUB_VAR: case DECL_ID_PUB_VAR_THREADLOCAL: case DECL_ID_PUB_EXTERN_VAR: case DECL_ID_PUB_EXTERN_VAR_THREADLOCAL: case DECL_ID_PUB_EXPORT_VAR: case DECL_ID_PUB_EXPORT_VAR_THREADLOCAL: return true; default: return false; } } // Declaration.Unwrapped.Id.isThreadlocal / linkage == .extern. // Preserve these bits at scan time so runtime Nav pointers can reject the // unsupported TLS/extern storage classes rather than treating them as // ordinary process globals. bool zirDeclIdIsThreadlocal(uint32_t id) { switch (id) { case DECL_ID_VAR_THREADLOCAL: case DECL_ID_PUB_VAR_THREADLOCAL: case DECL_ID_EXTERN_VAR_THREADLOCAL: case DECL_ID_PUB_EXTERN_VAR_THREADLOCAL: case DECL_ID_EXPORT_VAR_THREADLOCAL: case DECL_ID_PUB_EXPORT_VAR_THREADLOCAL: return true; default: return false; } } bool zirDeclIdIsExtern(uint32_t id) { switch (id) { case DECL_ID_EXTERN_CONST_SIMPLE: case DECL_ID_EXTERN_CONST: case DECL_ID_PUB_EXTERN_CONST_SIMPLE: case DECL_ID_PUB_EXTERN_CONST: case DECL_ID_EXTERN_VAR: case DECL_ID_EXTERN_VAR_THREADLOCAL: case DECL_ID_PUB_EXTERN_VAR: case DECL_ID_PUB_EXTERN_VAR_THREADLOCAL: return true; default: return false; } } // A named `declaration` instruction's name index. uint32_t zirDeclIdName(const Zir* zir, uint32_t inst) { // name (NullTerminatedString) is the first trailing word (past // src_hash[4], flags_0, flags_1), present iff the decl hasName. uint32_t p = zir->inst_datas[inst].declaration.payload_index; return zir->extra[p + 6]; } // Decode `declaration` instruction `inst`. Returns false if it has no name // (unnamed test / comptime block), which cannot be a named function decl. bool zirDecodeDeclaration(const Zir* zir, uint32_t inst, DeclInfo* out) { uint32_t p = zir->inst_datas[inst].declaration.payload_index; uint32_t id = zirDeclarationId(zir, inst); if (!zirDeclIdHasName(id)) return false; uint32_t idx = p + 6; out->id = id; out->name = zir->extra[idx++]; // name (present: hasName) out->has_lib_name = zirDeclIdHasLibName(id); out->lib_name = IP_INDEX_NONE; if (out->has_lib_name) out->lib_name = zir->extra[idx++]; uint32_t type_len = 0, align_len = 0, linksection_len = 0, addrspace_len = 0, value_len = 0; if (zirDeclIdHasTypeBody(id)) type_len = zir->extra[idx++]; if (zirDeclIdHasSpecialBodies(id)) { align_len = zir->extra[idx++]; linksection_len = zir->extra[idx++]; addrspace_len = zir->extra[idx++]; } if (zirDeclIdHasValueBody(id)) value_len = zir->extra[idx++]; // Bodies follow in order: type, align, linksection, addrspace, value. out->has_type_body = zirDeclIdHasTypeBody(id) && type_len > 0; out->type_body_off = idx; // type body is first out->type_body_len = type_len; idx += type_len; out->has_align_body = align_len > 0; out->align_body_off = idx; out->align_body_len = align_len; idx += align_len; out->has_linksection_body = linksection_len > 0; out->linksection_body_off = idx; out->linksection_body_len = linksection_len; idx += linksection_len; out->has_addrspace_body = addrspace_len > 0; out->addrspace_body_off = idx; out->addrspace_body_len = addrspace_len; idx += addrspace_len; out->has_value_body = zirDeclIdHasValueBody(id); out->is_export = zirDeclIdIsExport(id); out->value_body_off = idx; out->value_body_len = value_len; return true; } // Unnamed tests are function declarations with the same value-body shape as // named tests, but intentionally have no declaration name. Their Nav supplies // the generated name/FQN; semantic function decoding still needs the body. static bool zirDecodeUnnamedTestDeclaration( const Zir* zir, uint32_t inst, DeclInfo* out) { if (inst >= zir->inst_len || zir->inst_tags[inst] != ZIR_INST_DECLARATION) return false; uint32_t p = zir->inst_datas[inst].declaration.payload_index; uint32_t id = zirDeclarationId(zir, inst); if (id != DECL_ID_UNNAMED_TEST) return false; memset(out, 0, sizeof(*out)); out->name = IP_INDEX_NONE; out->lib_name = IP_INDEX_NONE; out->id = id; out->has_value_body = true; out->value_body_len = zir->extra[p + 6]; out->value_body_off = p + 7; return true; } bool zirDecodeFunctionDeclaration( const Zir* zir, uint32_t inst, DeclInfo* out) { return zirDecodeDeclaration(zir, inst, out) || zirDecodeUnnamedTestDeclaration(zir, inst, out); } // (part of) Sema.zig:zirThis // True if `ref` is a ZIR `extended(this())` instruction — `@This()`, which // resolves to the innermost enclosing container type. The container type is // supplied by the caller (from the method Nav's namespace owner_type), since a // bare ZIR ref cannot name it. Mirrors Sema.zirThis. bool zirRefIsThis(const Zir* zir, uint32_t ref) { if (ref < ZIR_REF_START_INDEX) return false; uint32_t pi = ref - ZIR_REF_START_INDEX; return zir->inst_tags[pi] == ZIR_INST_EXTENDED && zir->inst_datas[pi].extended.opcode == (uint16_t)ZIR_EXT_THIS; } // Zir.zig:getParamBody // The body of `fn_inst`'s param block: a block's body, or a declaration's // value body. Its length is `*len`. const uint32_t* zirGetParamBody( const Zir* zir, uint32_t fn_inst, uint32_t* len) { uint32_t payload = zir->inst_datas[fn_inst].pl_node.payload_index; uint32_t param_block; if (zir->inst_tags[fn_inst] == ZIR_INST_FUNC_FANCY) { ZirInstFuncFancy extra; zirExtraData(zir, payload, &extra); param_block = extra.param_block; } else { ZirInstFunc extra; zirExtraData(zir, payload, &extra); param_block = extra.param_block; } switch (zir->inst_tags[param_block]) { case ZIR_INST_BLOCK: case ZIR_INST_BLOCK_COMPTIME: case ZIR_INST_BLOCK_INLINE: { ZirInstBlock block; uint32_t end = zirExtraData( zir, zir->inst_datas[param_block].pl_node.payload_index, &block); *len = block.body_len; return zirBodySlice(zir, end); } case ZIR_INST_DECLARATION: { DeclInfo decl; if (!zirDecodeFunctionDeclaration(zir, param_block, &decl) || !decl.has_value_body) UNREACHABLE("getParamBody: declaration without a value body"); *len = decl.value_body_len; return zirBodySlice(zir, decl.value_body_off); } default: UNREACHABLE("getParamBody: param block is not a block"); } ZIG1_TCC_RETURN(NULL); } // Zir.zig:getFnInfo ZirFnInfo zirGetFnInfo(const Zir* zir, uint32_t fn_inst) { ZirFnInfo info = { 0 }; uint32_t payload = zir->inst_datas[fn_inst].pl_node.payload_index; info.ret_ty_ref = ZIR_REF_NONE; if (zir->inst_tags[fn_inst] == ZIR_INST_FUNC_FANCY) { ZirInstFuncFancy extra; uint32_t extra_index = zirExtraData(zir, payload, &extra); if ((extra.bits & ZIR_FUNC_FANCY_BITS_HAS_CC_BODY) != 0) extra_index += zir->extra[extra_index] + 1; else if ((extra.bits & ZIR_FUNC_FANCY_BITS_HAS_CC_REF) != 0) extra_index += 1; if ((extra.bits & ZIR_FUNC_FANCY_BITS_HAS_RET_TY_BODY) != 0) { info.ret_ty_body_len = zir->extra[extra_index]; extra_index += 1; info.ret_ty_body = zirBodySlice(zir, extra_index); extra_index += info.ret_ty_body_len; } else if ((extra.bits & ZIR_FUNC_FANCY_BITS_HAS_RET_TY_REF) != 0) { info.ret_ty_ref = zir->extra[extra_index]; extra_index += 1; } else { info.ret_ty_ref = ZIR_REF_VOID_TYPE; } if ((extra.bits & ZIR_FUNC_FANCY_BITS_HAS_ANY_NOALIAS) != 0) extra_index += 1; info.body = zirBodySlice(zir, extra_index); info.body_len = extra.body_len; info.param_body_inst = extra.param_block; info.ret_ty_is_generic = (extra.bits & ZIR_FUNC_FANCY_BITS_RET_TY_IS_GENERIC) != 0; info.inferred_error_set = (extra.bits & ZIR_FUNC_FANCY_BITS_IS_INFERRED_ERROR) != 0; } else { ZirInstFunc extra; uint32_t extra_index = zirExtraData(zir, payload, &extra); uint32_t ret_ty_body_len = extra.ret_ty & ZIR_FUNC_RET_TY_BODY_LEN_MASK; if (ret_ty_body_len == 0) { info.ret_ty_ref = ZIR_REF_VOID_TYPE; } else if (ret_ty_body_len == 1) { info.ret_ty_ref = zir->extra[extra_index]; extra_index += 1; } else { info.ret_ty_body = zirBodySlice(zir, extra_index); info.ret_ty_body_len = ret_ty_body_len; extra_index += ret_ty_body_len; } info.body = zirBodySlice(zir, extra_index); info.body_len = extra.body_len; info.param_body_inst = extra.param_block; info.ret_ty_is_generic = (extra.ret_ty & ZIR_FUNC_RET_TY_IS_GENERIC) != 0; info.inferred_error_set = zir->inst_tags[fn_inst] == ZIR_INST_FUNC_INFERRED; } info.param_body = zirGetParamBody(zir, fn_inst, &info.param_body_len); for (uint32_t i = 0; i < info.param_body_len; i++) { switch (zir->inst_tags[info.param_body[i]]) { case ZIR_INST_PARAM: case ZIR_INST_PARAM_COMPTIME: case ZIR_INST_PARAM_ANYTYPE: case ZIR_INST_PARAM_ANYTYPE_COMPTIME: info.total_params_len += 1; break; default: break; } } return info; } /* ---- end zir.c ---- */ /* ---- begin zon_gen.c ---- */ // lib/std/zig/ZonGen.zig: a ZON Ast to Zoir, and lib/std/zig/Zoir.zig's // Node.Index.get. zig1 stops at the first ZonGen error, reporting it as a // compile error where upstream collects them into the Zoir. #ifndef ZIG1_H #include "zig1.h" #endif typedef struct { const Ast* tree; // nodes: std.MultiArrayList(Zoir.Node.Repr). uint32_t nodes_len; uint32_t nodes_cap; uint8_t* node_tags; uint32_t* node_datas; AstNodeIndex* node_ast_nodes; U32List extra; uint64_t* limbs; uint32_t limbs_len; uint32_t limbs_cap; U8List string_bytes; Zig1StringTable string_table; } ZonGen; // ZonGen.zig:addErrorInner // The error `byte_offset` bytes into `token`; zig1 reports the first one. ZIG1_ATTRIBUTE((noreturn, format(printf, 4, 5))) static void zonGenFailTokOff(const ZonGen* zg, AstTokenIndex token, uint32_t byte_offset, const char* fmt, ...) { zig1ErrorSourceAt( zg->tree->source, astTokenStart(zg->tree, token) + byte_offset); va_list args; va_start(args, fmt); const char* msg = zig1CompileErrorMessageV(fmt, args); va_end(args); zig1CompileError(msg); } // ZonGen.zig:addErrorNode, at the node's main token. #define zonGenFailNode(zg, node, ...) \ zonGenFailTokOff( \ (zg), astNodeMainToken((zg)->tree, (node)), 0, __VA_ARGS__) // `zg.nodes.resize(gpa, zg.nodes.len + n)`: the new nodes are undefined // until setNode fills them. static uint32_t zonGenAddNodes(ZonGen* zg, uint32_t n) { const uint32_t first = zg->nodes_len; if (first + n > zg->nodes_cap) { uint32_t cap = zig1GrowCap(zg->nodes_cap, 64u); while (cap < first + n) cap *= 2; zg->node_tags = zig1Realloc(zg->node_tags, cap); zg->node_datas = zig1Realloc(zg->node_datas, (size_t)cap * sizeof(uint32_t)); zg->node_ast_nodes = zig1Realloc( zg->node_ast_nodes, (size_t)cap * sizeof(AstNodeIndex)); zg->nodes_cap = cap; } zg->nodes_len = first + n; return first; } // ZonGen.zig:setNode static void zonGenSetNode(ZonGen* zg, uint32_t dest, ZoirNodeReprTag tag, uint32_t data, AstNodeIndex ast_node) { zg->node_tags[dest] = (uint8_t)tag; zg->node_datas[dest] = data; zg->node_ast_nodes[dest] = ast_node; } // The string_table half of identAsString and strLitAsString // (getOrPutContextAdapted on `string_bytes[str_index..]`): an existing equal // string, or `str_index` with a NUL appended. static uint32_t zonGenInternString(ZonGen* zg, uint32_t str_index) { // The NUL's room first, so even an empty string's bytes exist. const uint32_t key_len = zg->string_bytes.len - str_index; u8ListAppend(&zg->string_bytes, 0); zg->string_bytes.len--; const char* key = (const char*)&zg->string_bytes.items[str_index]; const uint32_t found = zig1StringTableFind( &zg->string_table, zg->string_bytes.items, key, key_len); if (found != UINT32_MAX) { zg->string_bytes.len = str_index; return found; } zig1StringTableInsert(&zg->string_table, str_index, key, key_len); u8ListAppend(&zg->string_bytes, 0); return str_index; } // ZonGen.zig:appendIdentStr static uint32_t zonGenAppendIdentStr(ZonGen* zg, AstTokenIndex ident_token) { const char* ident_name; uint32_t ident_len; astTokenSlice(zg->tree, ident_token, &ident_name, &ident_len); const uint32_t start = zg->string_bytes.len; if (ident_name[0] != '@') { for (uint32_t i = 0; i < ident_len; i++) u8ListAppend(&zg->string_bytes, (uint8_t)ident_name[i]); return start; } const uint32_t offset = 1; uint32_t err_offset; if (!stringLiteralParseWrite(ident_name + offset, ident_len - offset, &zg->string_bytes, &err_offset)) zonGenFailTokOff( zg, ident_token, offset + err_offset, "invalid string literal"); const uint32_t len = zg->string_bytes.len - start; if (memchr(&zg->string_bytes.items[start], 0, len) != NULL) zonGenFailTokOff( zg, ident_token, 0, "identifier cannot contain null bytes"); if (len == 0) zonGenFailTokOff(zg, ident_token, 0, "identifier cannot be empty"); return start; } // ZonGen.zig:identAsString static uint32_t zonGenIdentAsString(ZonGen* zg, AstTokenIndex ident_token) { return zonGenInternString(zg, zonGenAppendIdentStr(zg, ident_token)); } // ZonGen.zig:parseStrLit static void zonGenParseStrLit(ZonGen* zg, AstNodeIndex node) { const Ast* tree = zg->tree; switch (astNodeTag(tree, node)) { case AST_NODE_STRING_LITERAL: { const AstTokenIndex token = astNodeMainToken(tree, node); const char* raw; uint32_t raw_len; astTokenSlice(tree, token, &raw, &raw_len); uint32_t err_offset; if (!stringLiteralParseWrite( raw, raw_len, &zg->string_bytes, &err_offset)) zonGenFailTokOff(zg, token, err_offset, "invalid string literal"); return; } case AST_NODE_MULTILINE_STRING_LITERAL: { const AstData data = astNodeData(tree, node); for (AstTokenIndex tok = data.lhs; tok <= data.rhs; tok++) { const char* line; uint32_t line_len; astTokenSlice(tree, tok, &line, &line_len); // Each line after the first prepends a newline. if (tok != data.lhs) u8ListAppend(&zg->string_bytes, '\n'); for (uint32_t i = 2; i < line_len; i++) u8ListAppend(&zg->string_bytes, (uint8_t)line[i]); } return; } default: UNREACHABLE("ZonGen.parseStrLit: node must represent a string"); } } // ZonGen.zig:strLitAsString // A string with no NUL is interned (*is_nts, the offset); one with a NUL is // the slice at the offset, `*out_len` bytes long. static uint32_t zonGenStrLitAsString( ZonGen* zg, AstNodeIndex str_node, bool* is_nts, uint32_t* out_len) { const uint32_t str_index = zg->string_bytes.len; zonGenParseStrLit(zg, str_node); const uint32_t len = zg->string_bytes.len - str_index; if (len != 0 && memchr(&zg->string_bytes.items[str_index], 0, len)) { *is_nts = false; *out_len = len; return str_index; } *is_nts = true; *out_len = len; return zonGenInternString(zg, str_index); } static void zonGenSetBigIntLiteralNode(ZonGen* zg, uint32_t dest_node, AstNodeIndex src_node, const uint64_t* limbs, uint32_t limbs_len, bool positive); typedef enum { ZON_GEN_POSITIVE, ZON_GEN_NEGATIVE } ZonGenSign; // ZonGen.zig:numberLiteral static void zonGenNumberLiteral(ZonGen* zg, AstNodeIndex num_node, AstNodeIndex src_node, uint32_t dest_node, ZonGenSign sign) { const Ast* tree = zg->tree; const AstTokenIndex num_token = astNodeMainToken(tree, num_node); const char* num_bytes; uint32_t num_len; astTokenSlice(tree, num_token, &num_bytes, &num_len); const NumberLiteralResult lit = parseNumberLiteral(num_bytes, num_len); switch (lit.tag) { case NUMBER_LITERAL_INT: { const uint64_t unsigned_num = lit.int_val; if (unsigned_num == 0 && sign == ZON_GEN_NEGATIVE) zonGenFailTokOff( zg, num_token, 0, "integer literal '-0' is ambiguous"); // `std.math.cast(i32, num)` of the i65 `num`. const bool fits_i32 = sign == ZON_GEN_POSITIVE ? unsigned_num <= 0x7FFFFFFFu : unsigned_num <= 0x80000000u; if (fits_i32) { const uint32_t magnitude = (uint32_t)unsigned_num; const uint32_t bits = sign == ZON_GEN_POSITIVE ? magnitude : 0u - magnitude; zonGenSetNode(zg, dest_node, ZOIR_NODE_REPR_INT_LITERAL_SMALL, bits, src_node); return; } // Mutable.init of the i65 value: one limb of magnitude. zonGenSetBigIntLiteralNode(zg, dest_node, src_node, &unsigned_num, 1, sign == ZON_GEN_POSITIVE); return; } case NUMBER_LITERAL_BIG_INT: UNIMPLEMENTED("ZonGen.numberLiteral: an integer literal past 64 bits " "(Managed.setString)"); case NUMBER_LITERAL_FLOAT: { CrtU128 num = parseFloatF128(num_bytes, num_len); if (sign == ZON_GEN_NEGATIVE) num = u128Xor(num, u128Bit(127)); // If the value fits into an f32 without losing any precision, store // it that way. const CrtU128 smaller_float = crtTruncf(32, 128, num); const CrtU128 bigger_again = crtExtendf(128, 32, smaller_float); if (u128Cmp(bigger_again, num) == 0) { zonGenSetNode(zg, dest_node, ZOIR_NODE_REPR_FLOAT_LITERAL_SMALL, (uint32_t)smaller_float.lo, src_node); return; } const uint32_t extra_index = zg->extra.len; u32ListAppend(&zg->extra, (uint32_t)num.lo); u32ListAppend(&zg->extra, (uint32_t)(num.lo >> 32)); u32ListAppend(&zg->extra, (uint32_t)num.hi); u32ListAppend(&zg->extra, (uint32_t)(num.hi >> 32)); zonGenSetNode(zg, dest_node, ZOIR_NODE_REPR_FLOAT_LITERAL, extra_index, src_node); return; } case NUMBER_LITERAL_FAILURE: zonGenFailTokOff( zg, num_token, lit.failure_index, "invalid number literal"); } UNREACHABLE("ZonGen.numberLiteral: number literal result tag"); } // ZonGen.zig:setBigIntLiteralNode static void zonGenSetBigIntLiteralNode(ZonGen* zg, uint32_t dest_node, AstNodeIndex src_node, const uint64_t* limbs, uint32_t limbs_len, bool positive) { if (zg->limbs_len + limbs_len > zg->limbs_cap) { uint32_t cap = zig1GrowCap(zg->limbs_cap, 8u); while (cap < zg->limbs_len + limbs_len) cap *= 2; zg->limbs = zig1Realloc(zg->limbs, (size_t)cap * sizeof(uint64_t)); zg->limbs_cap = cap; } const uint32_t limbs_idx = zg->limbs_len; memcpy(&zg->limbs[limbs_idx], limbs, limbs_len * sizeof(uint64_t)); zg->limbs_len += limbs_len; const uint32_t extra_idx = zg->extra.len; u32ListAppend(&zg->extra, limbs_len); u32ListAppend(&zg->extra, limbs_idx); zonGenSetNode(zg, dest_node, positive ? ZOIR_NODE_REPR_INT_LITERAL_POS : ZOIR_NODE_REPR_INT_LITERAL_NEG, extra_idx, src_node); } // ZonGen.zig:charLiteral static void zonGenCharLiteral( ZonGen* zg, AstNodeIndex node, uint32_t dest_node) { const AstTokenIndex main_token = astNodeMainToken(zg->tree, node); const char* slice; uint32_t slice_len; astTokenSlice(zg->tree, main_token, &slice, &slice_len); uint32_t codepoint; uint32_t err_offset; if (!stringLiteralParseCharLiteral( slice, slice_len, &codepoint, &err_offset)) zonGenFailTokOff( zg, main_token, err_offset, "invalid character literal"); zonGenSetNode(zg, dest_node, ZOIR_NODE_REPR_CHAR_LITERAL, codepoint, node); } // ZonGen.zig:identifier static void zonGenIdentifier( ZonGen* zg, AstNodeIndex node, uint32_t dest_node) { const char* ident; uint32_t len; astTokenSlice(zg->tree, astNodeMainToken(zg->tree, node), &ident, &len); static const struct { const char* name; ZoirNodeReprTag tag; } words[] = { { "true", ZOIR_NODE_REPR_TRUE }, { "false", ZOIR_NODE_REPR_FALSE }, { "null", ZOIR_NODE_REPR_NULL }, { "inf", ZOIR_NODE_REPR_POS_INF }, { "nan", ZOIR_NODE_REPR_NAN }, }; for (uint32_t i = 0; i < sizeof(words) / sizeof(words[0]); i++) { if (strlen(words[i].name) == len && memcmp(words[i].name, ident, len) == 0) { zonGenSetNode(zg, dest_node, words[i].tag, 0, node); return; } } zonGenFailNode(zg, node, "invalid expression"); } // ZonGen.zig:expr // Every node tag without an arm here is a compile error in ZON (operators, // types, control flow, builtin calls and the rest of expr's error arms). static void zonGenExpr(ZonGen* zg, AstNodeIndex node, uint32_t dest_node) { const Ast* tree = zg->tree; switch (astNodeTag(tree, node)) { case AST_NODE_GROUPED_EXPRESSION: zonGenFailNode(zg, node, "expression grouping is not allowed in ZON"); case AST_NODE_NEGATION: { const AstNodeIndex child_node = astNodeData(tree, node).lhs; switch (astNodeTag(tree, child_node)) { case AST_NODE_NUMBER_LITERAL: zonGenNumberLiteral( zg, child_node, node, dest_node, ZON_GEN_NEGATIVE); return; case AST_NODE_IDENTIFIER: { const char* child_ident; uint32_t child_len; astTokenSlice(tree, astNodeMainToken(tree, child_node), &child_ident, &child_len); if (child_len == 3 && memcmp(child_ident, "inf", 3) == 0) { zonGenSetNode(zg, dest_node, ZOIR_NODE_REPR_NEG_INF, 0, node); return; } break; } default: break; } zonGenFailNode(zg, node, "expected number or 'inf' after '-'"); } case AST_NODE_NUMBER_LITERAL: zonGenNumberLiteral(zg, node, node, dest_node, ZON_GEN_POSITIVE); return; case AST_NODE_CHAR_LITERAL: zonGenCharLiteral(zg, node, dest_node); return; case AST_NODE_IDENTIFIER: zonGenIdentifier(zg, node, dest_node); return; case AST_NODE_ENUM_LITERAL: { const uint32_t str_index = zonGenIdentAsString(zg, astNodeMainToken(tree, node)); zonGenSetNode( zg, dest_node, ZOIR_NODE_REPR_ENUM_LITERAL, str_index, node); return; } case AST_NODE_STRING_LITERAL: case AST_NODE_MULTILINE_STRING_LITERAL: { bool is_nts; uint32_t len; const uint32_t start = zonGenStrLitAsString(zg, node, &is_nts, &len); if (is_nts) { zonGenSetNode(zg, dest_node, ZOIR_NODE_REPR_STRING_LITERAL_NULL, start, node); return; } const uint32_t extra_index = zg->extra.len; u32ListAppend(&zg->extra, start); u32ListAppend(&zg->extra, len); zonGenSetNode( zg, dest_node, ZOIR_NODE_REPR_STRING_LITERAL, extra_index, node); return; } case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: case AST_NODE_ARRAY_INIT_DOT: case AST_NODE_ARRAY_INIT_DOT_COMMA: { AstNodeIndex buf[2]; AstFullArrayInit full; if (!astFullArrayInit(tree, buf, node, &full)) UNREACHABLE("ZonGen.expr: array_init_dot is not an array init"); const uint32_t elems_len = full.ast.elements.len; // The elements may live in `buf`, which the recursion below reuses. AstNodeIndex* elems = zig1Malloc((elems_len ? elems_len : 1) * sizeof(AstNodeIndex)); memcpy(elems, full.ast.elements.ptr, elems_len * sizeof(AstNodeIndex)); const uint32_t first_elem = zonGenAddNodes(zg, elems_len); const uint32_t extra_index = zg->extra.len; u32ListAppend(&zg->extra, elems_len); u32ListAppend(&zg->extra, first_elem); zonGenSetNode( zg, dest_node, ZOIR_NODE_REPR_ARRAY_LITERAL, extra_index, node); for (uint32_t i = 0; i < elems_len; i++) zonGenExpr(zg, elems[i], first_elem + i); free(elems); return; } case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: case AST_NODE_STRUCT_INIT_DOT: case AST_NODE_STRUCT_INIT_DOT_COMMA: { AstNodeIndex buf[2]; AstFullStructInit full; if (!astFullStructInit(tree, buf, node, &full)) UNREACHABLE("ZonGen.expr: struct_init_dot is not a struct init"); const uint32_t fields_len = full.ast.fields.len; if (fields_len == 0) { zonGenSetNode( zg, dest_node, ZOIR_NODE_REPR_EMPTY_LITERAL, 0, node); return; } AstNodeIndex* fields = zig1Malloc((size_t)fields_len * sizeof(AstNodeIndex)); memcpy(fields, full.ast.fields.ptr, fields_len * sizeof(AstNodeIndex)); const uint32_t first_elem = zonGenAddNodes(zg, fields_len); const uint32_t extra_index = zg->extra.len; u32ListAppend(&zg->extra, fields_len); u32ListAppend(&zg->extra, first_elem); const uint32_t names_start = extra_index + 2; for (uint32_t i = 0; i < fields_len; i++) u32ListAppend(&zg->extra, 0); zonGenSetNode( zg, dest_node, ZOIR_NODE_REPR_STRUCT_LITERAL, extra_index, node); for (uint32_t i = 0; i < fields_len; i++) { const AstTokenIndex name_token = astFirstToken(tree, fields[i]) - 2; const uint32_t name_str = zonGenIdentAsString(zg, name_token); // The first duplicate name is an error (field_names' getOrPut). for (uint32_t j = 0; j < i; j++) if (zg->extra.items[names_start + j] == name_str) zonGenFailTokOff( zg, name_token, 0, "duplicate struct field name"); zg->extra.items[names_start + i] = name_str; zonGenExpr(zg, fields[i], first_elem + i); } free(fields); return; } case AST_NODE_ARRAY_INIT_ONE: case AST_NODE_ARRAY_INIT_ONE_COMMA: case AST_NODE_ARRAY_INIT: case AST_NODE_ARRAY_INIT_COMMA: case AST_NODE_STRUCT_INIT_ONE: case AST_NODE_STRUCT_INIT_ONE_COMMA: case AST_NODE_STRUCT_INIT: case AST_NODE_STRUCT_INIT_COMMA: zonGenFailNode(zg, node, "types are not available in ZON"); default: zonGenFailNode(zg, node, "expression is not allowed in ZON"); } } // ZonGen.zig:generate static Zoir zonGenGenerate(const Ast* tree) { ZonGen zg = { .tree = tree }; const AstNodeSlice root_decls = astRootDecls(tree); const AstNodeIndex root_ast_node = root_decls.ptr[0]; zonGenAddNodes(&zg, 1); // index 0; root node zonGenExpr(&zg, root_ast_node, 0); free(zg.string_table.slots); return (Zoir) { .nodes_len = zg.nodes_len, .node_tags = zg.node_tags, .node_datas = zg.node_datas, .node_ast_nodes = zg.node_ast_nodes, .extra = zg.extra.items, .extra_len = zg.extra.len, .limbs = zg.limbs, .limbs_len = zg.limbs_len, .string_bytes = zg.string_bytes.items, .string_bytes_len = zg.string_bytes.len, }; } Zoir zonGenGenerateFile(const Ast* tree, const char* dir, const char* path) { const Zig1ErrorSource saved = zig1_error_source; zig1_error_source = (Zig1ErrorSource) { dir, path, 0, 0 }; if (tree->has_error) { // Ast.errorOffset: the end of the token when token_is_prev. const char* tok_text; uint32_t tok_len = 0; if (tree->err_tok_is_prev) astTokenSlice(tree, tree->err_tok, &tok_text, &tok_len); zig1ErrorSourceAt( tree->source, astTokenStart(tree, tree->err_tok) + tok_len); zig1CompileError(tree->err_msg ? tree->err_msg : "invalid syntax"); } Zoir zoir = zonGenGenerate(tree); zig1_error_source = saved; return zoir; } void zoirDeinit(Zoir* zoir) { free(zoir->node_tags); free(zoir->node_datas); free(zoir->node_ast_nodes); free(zoir->extra); free(zoir->limbs); free(zoir->string_bytes); *zoir = (Zoir) { 0 }; } // Zoir.zig:get ZoirNode zoirNodeGet(const Zoir* zoir, uint32_t idx) { const uint32_t data = zoir->node_datas[idx]; ZoirNode n; memset(&n, 0, sizeof(n)); switch ((ZoirNodeReprTag)zoir->node_tags[idx]) { case ZOIR_NODE_REPR_TRUE: n.tag = ZOIR_NODE_TRUE; return n; case ZOIR_NODE_REPR_FALSE: n.tag = ZOIR_NODE_FALSE; return n; case ZOIR_NODE_REPR_NULL: n.tag = ZOIR_NODE_NULL; return n; case ZOIR_NODE_REPR_POS_INF: n.tag = ZOIR_NODE_POS_INF; return n; case ZOIR_NODE_REPR_NEG_INF: n.tag = ZOIR_NODE_NEG_INF; return n; case ZOIR_NODE_REPR_NAN: n.tag = ZOIR_NODE_NAN; return n; case ZOIR_NODE_REPR_INT_LITERAL_SMALL: n.tag = ZOIR_NODE_INT_LITERAL; n.data.int_literal.small = (int32_t)data; return n; case ZOIR_NODE_REPR_INT_LITERAL_POS: case ZOIR_NODE_REPR_INT_LITERAL_NEG: { n.tag = ZOIR_NODE_INT_LITERAL; n.data.int_literal.is_big = true; const uint32_t limb_count = zoir->extra[data]; const uint32_t limbs_idx = zoir->extra[data + 1]; n.data.int_literal.big = (BigIntConst) { &zoir->limbs[limbs_idx], limb_count, zoir->node_tags[idx] == ZOIR_NODE_REPR_INT_LITERAL_POS }; return n; } case ZOIR_NODE_REPR_FLOAT_LITERAL_SMALL: { n.tag = ZOIR_NODE_FLOAT_LITERAL; // `@as(f32, @bitCast(repr.data))` widened to the node's f128. ipFloat128ToWords( crtExtendf(128, 32, u128Make(0, data)), n.data.float_literal); return n; } case ZOIR_NODE_REPR_FLOAT_LITERAL: n.tag = ZOIR_NODE_FLOAT_LITERAL; memcpy(n.data.float_literal, &zoir->extra[data], 4 * sizeof(uint32_t)); return n; case ZOIR_NODE_REPR_CHAR_LITERAL: n.tag = ZOIR_NODE_CHAR_LITERAL; n.data.char_literal = data; return n; case ZOIR_NODE_REPR_ENUM_LITERAL: n.tag = ZOIR_NODE_ENUM_LITERAL; n.data.enum_literal = data; return n; case ZOIR_NODE_REPR_STRING_LITERAL: n.tag = ZOIR_NODE_STRING_LITERAL; n.data.string_literal.ptr = &zoir->string_bytes[zoir->extra[data]]; n.data.string_literal.len = zoir->extra[data + 1]; return n; case ZOIR_NODE_REPR_STRING_LITERAL_NULL: n.tag = ZOIR_NODE_STRING_LITERAL; n.data.string_literal.ptr = &zoir->string_bytes[data]; n.data.string_literal.len = (uint32_t)strlen((const char*)&zoir->string_bytes[data]); return n; case ZOIR_NODE_REPR_EMPTY_LITERAL: n.tag = ZOIR_NODE_EMPTY_LITERAL; return n; case ZOIR_NODE_REPR_ARRAY_LITERAL: n.tag = ZOIR_NODE_ARRAY_LITERAL; n.data.array_literal = (ZoirNodeRange) { .start = zoir->extra[data + 1], .len = zoir->extra[data] }; return n; case ZOIR_NODE_REPR_STRUCT_LITERAL: n.tag = ZOIR_NODE_STRUCT_LITERAL; n.data.struct_literal.names = &zoir->extra[data + 2]; n.data.struct_literal.vals = (ZoirNodeRange) { .start = zoir->extra[data + 1], .len = zoir->extra[data] }; return n; } UNREACHABLE("Zoir.Node.Index.get: node repr tag"); ZIG1_TCC_RETURN(n); } #undef zonGenFailNode /* ---- end zon_gen.c ---- */