When both operands are comptime-known floats (IP_KEY_FLOAT entries,
including comptime_float and concrete float types), fold the result
at comptime instead of emitting a runtime AIR instruction.
Ported from Sema.zig analyzeArithmetic: resolveValue(casted_lhs/rhs)
path which evaluates comptime values via arith.add/sub/mul.
Handles ADD, SUB, MUL variants (including wrap/sat) for both
comptime_float × comptime_float and concrete float × float. Result
type is the wider of the two float types.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
When semaCoerce encounters comptime_int → concrete float (or
comptime_float), it was incorrectly calling semaCoerceIntRef which
produces an IP_KEY_INT entry with a float type — wrong.
Fix by detecting the float target first: convert the integer value
to a double and intern an IP_KEY_FLOAT entry. This matches Sema.zig
coerce comptime_int → float path.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
When both operands are concrete float types (e.g. f32 + f64),
resolvePeerTypes returned the lhs type regardless of width.
Ported from Sema.zig peer_resolve_float strategy: return the
wider float type.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
zirNegate and zirNegateWrap: extend the float detection to include
IP_INDEX_COMPTIME_FLOAT_TYPE. Previously floatBits() returned 0 for
comptime_float, causing negate to fall through to integer path.
This fixes -(comptime_float) and -(comptime_float) wrap.
zirAbs: similarly extend float check to include comptime_float so
@abs on comptime_float values is properly folded at comptime.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The comptime equality check compared only lo/hi magnitude fields,
ignoring is_negative. This meant (-5) == 5 would return true.
Fix by also checking lhs_neg == rhs_neg. Zero is never negative
(internComptimeInt already clears neg for zero values), so this
handles the -0 == 0 edge case correctly.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The comptime folding path in zirCmp used unsigned comparison of
the magnitude fields (lo/hi), ignoring the is_negative sign flag.
This gave wrong results for mixed-sign comparisons like (-5) < 3.
Replace with a proper signed 128-bit comparison that:
- If signs differ: negative < positive
- Both non-negative: compare magnitudes in ascending order
- Both negative: compare magnitudes in descending order (larger mag = smaller value)
Also derive equality separately for LTE/GTE cases.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
zirAbs now folds @abs for comptime_int operands: absolute value is
simply clearing the is_negative flag. This matches Sema.zig which
treats comptime_int like float/comptime_float (result_ty = operand_ty)
and calls maybeConstantUnaryMath(Value.abs).
Also fold @abs for comptime-known typed signed integers: since the
result type is already unsigned, we just intern the magnitude (lo/hi)
with the unsigned result type and is_negative=false.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
zirBoolBr: after analyzing the RHS body, if the result is
comptime-known and equals the short-circuit value (true for or,
false for and), fold the whole expression to the comptime result.
This matches Sema.zig lines 18167-18173.
zirTypeofBuiltin: set want_safety=false on the typeof block, matching
Sema.zig line 17941 which explicitly disables safety in typeof blocks.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Several comptime folding paths in sema.c ignored the is_negative flag
of sign-magnitude InternPool integers:
- internComptimeInt: add `neg` parameter (was hardcoded false).
- analyzeArithmetic: implement sign-aware add/sub/mul via addSignedMag128
helper. add(a,b) handles mixed signs by subtracting smaller magnitude;
sub(a,b) = add(a,-b); mul result_neg = lhs_neg XOR rhs_neg.
- zirDiv: pass correct sign to result (lhs_neg != rhs_neg).
- zirShl/zirShr: preserve lhs sign through comptime shift.
- zirMinMax: use isComptimeIntWide (128-bit) instead of isComptimeInt
(64-bit) and implement full 128-bit signed comparison.
- semaTypeOf: add DIV_FLOOR, DIV_EXACT, DIV_FLOAT_OPTIMIZED, MOD, REM
to the bin_op type-from-lhs group.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Simplify the 128-bit case in zirByteSwap comptime folding:
- r_lo byte b ← val_hi byte (7-b)
- r_hi byte b ← val_lo byte (7-b)
Previous code had a dead `src < 8` check (always false).
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Add the float-to-integer coercion path: convert the float value to an
integer (truncating toward zero) and intern it with the target type.
Previously, comptime_float coercion to integer types was silently
ignored, returning the original float ref unchanged.
Also restructure the comptime_float case to explicitly separate
float→float from float→int paths.
Matches src/Sema.zig coerce: getCoerced(val, dst_ty) for float→int.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
For comptime-known integer operands (64-bit case), compute the quotient
at comptime instead of emitting AIR. Matches src/Sema.zig zirDiv: arith.div
for comptime-known values.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
For comptime-known float operands, compute -val at comptime instead of
emitting AIR_INST_NEG. Matches src/Sema.zig zirNegate: arith.negateFloat
for comptime values, preserving negative zero semantics.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
For comptime-known float operands, compute abs(val) at comptime
instead of emitting AIR. Matches src/Sema.zig zirAbs: maybeConstantUnaryMath
with Value.abs. Also extends to comptime_float type.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
For comptime-known integer operands with bit-aligned widths, compute
the byte-swapped value at comptime rather than emitting AIR.
Matches src/Sema.zig zirByteSwap: arith.byteSwap() comptime path.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
For float operands, create IP_KEY_FLOAT(0.0) instead of IP_KEY_INT(0).
The previous code created an int zero with a float type, which is an
invalid IP entry. Matches src/Sema.zig zirNegateWrap: pt.intValue()
returns the appropriate zero for the scalar type.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Match upstream Sema.zig zirBitwise structure: resolve peer types and
coerce operands FIRST, then attempt comptime folding on the coerced
values. This ensures typed IP entries (e.g. u16(3)) are created as
side effects of coercion, matching Zig's IP entry sequence.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Implement runtime while/for loop handling:
- Reserve block_inst (outer) + loop_inst (inner) in advance
- Analyze loop body in loop_block
- If body ends noreturn: copy instructions to child_block (no repeat)
- Otherwise: add repeat instruction, set up loop_inst body in air_extra
- Resolve outer block via label merges (handles break-from-loop)
Matches src/Sema.zig zirLoop decomposition.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Use resolveZirTypeRef instead of returning void for instruction refs
in the single-instruction return type body case. This handles ptr_type,
int_type, array_type, etc. Matches upstream's resolveType call in zirFunc.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The coerce calls create IP side effects; comparison uses captured
lhs_lo/rhs_lo values. Use (void)semaCoerce() to make intent explicit.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
These ty_op instructions were missing from the semaTypeOf dispatch,
needed for the float negation path added in zirNegate.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Add zirIntCast() and zirTruncate() as named entry points matching
src/Sema.zig's decomposition. Rename shared implementation to
analyzeTyOpCast(). Update dispatch and call sites.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Matches upstream Sema.zig which has zirBitNot → analyzeBitNot
two-function decomposition. Pure extraction, no behavioral change.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Extend skip_dedup_end to full preamble items_len (not just
preamble_memoized_end) so CC sub-type entries created by
ensureCcMemoizedStateC are in the skip range
- Set preamble_cc_start AFTER CC union creation so main analysis
creates fresh CC union via skip_dedup
- Clear all 15 CC-phase builtins (not just 5) for main shard re-resolution
- Reset cc_memoized_resolved so ensureCcMemoizedStateC re-runs in main
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Port Sema.zig analyzeArithmetic coercion: coerce both operands to
result_ty when result is not comptime_int, creating typed intermediate
IP entries that match the Zig compiler's output.
- Fix zirTypeofLog2IntType and zirShl to use ipIntern (dedup) instead
of ipForceIntern, matching Zig's pt.intType which deduplicates via
the global pool.
- Add cc_memoized_resolved / ensureCcMemoizedStateC for the CC phase of
builtin resolution (builtins 0-14), analogous to PerThread's CC stage.
- Add preamble_skip_ptr_nav flag to defer AS/CC ptr_nav creation to
main analysis, matching Zig's sharded IP where preamble and main
shards use different nav IDs.
- Extend ct_struct_vals.fields from 4 to 8 entries and store const char*
name instead of string_bytes index, enabling zirFieldValComptime to
access TypeInfo fields by name.
- Advance num_passing from 87 to 100.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two fixes for test 86 (inline_fn_with_plus_eq_call_inside_two_conditionals):
1. Skip duplicate func_type/func_decl in zirFunc when ensureNavValUpToDate
has already resolved the nav. Previously, ensureNavValUpToDate created
entries with ipForceIntern (no hash table) and zirFunc re-created them
with ipIntern (can't find existing), causing duplicates.
2. Defer non-generic body analysis from semaAnalyzeCall to match Zig's
ensureFuncBodyAnalysisQueued ordering. Callee body analysis is queued
and processed after the current function body completes, ensuring IP
entry ordering matches the Zig compiler's work queue semantics.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. `dev == .bootstrap` does not include wasm backend, no use.
2. different optimize modes _of the compiler_ (_not_ target) _should_
not emit a different AIR. Thus if we want to `zig build test-zig0
-Doptimize=ReleaseFast`, we most often mean to compile the test
executable under this mode, but not `air_gen`, which will only emit
the AIR for comparison.
zirEnumLiteral was storing simpleStringHash(name) — a djb2 hash — as the
enum_literal IP key field. Upstream Zig stores a NullTerminatedString index
(getOrPutString). All three read sites treat the field as a string table
index and dereference it into ip->string_bytes, so the hash value caused
silent out-of-bounds reads whenever enum_literal coercion, callconv, or
signedness parsing were exercised.
Replace simpleStringHash with ipGetOrPutString, matching upstream exactly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use ipIntern (not ipForceIntern) for func_instance so that multiple
calls to the same generic function with the same comptime args share
one IP entry. Fixes the two-generic-calls test case where Zig produces
one func_instance but C was producing two.
Bumps num_passing from 85 to 86.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Port IP_KEY_FUNC_INSTANCE from upstream InternPool.getFuncInstance.
When a generic non-inline function is called, create a monomorphized
func_type (runtime params only) and a func_instance entry referencing
the generic owner's func_decl. This matches the Zig compiler's IP
entry sequence for generic instantiations.
Bumps num_passing from 84 to 85.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactor to match upstream Sema.zig structure where zirCall handles
callee resolution and analyzeCall handles the actual call analysis.
The new semaAnalyzeCall function takes a CalleeResolution struct that
bundles cross-module state. No functional changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generic functions (with comptime, anytype, or anytype_comptime params)
should not be compiled as standalone function bodies — they're resolved
via monomorphization when called with concrete types. Ported from
upstream: generic functions are excluded from analysis roots.
Detect comptime params by scanning the param body in the ZIR for
ZIR_INST_PARAM_COMPTIME, ZIR_INST_PARAM_ANYTYPE_COMPTIME, or
ZIR_INST_PARAM_ANYTYPE instruction tags.
Next blocker: test 84 (inline_fn_with_generic_call_inside_conditional)
has a monomorphization naming mismatch: C generates normalize__anon_503
while Zig generates normalize__anon_507. The IP index gap of 4 needs
investigation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix non-inline function call to reuse the callee's existing func_decl
IP entry instead of creating a new one with func_inst as owner_nav.
Ported from upstream Sema.zig analyzeCall: func_val references the
resolved callee function, not a newly-created entry.
The callee is looked up by name in the file namespace. If the nav's
resolved_type already points to a func_decl (IP_KEY_FUNC), it's used
directly. Otherwise, the func_type from resolved_type is used to
construct and deduplicate the func_decl key.
Also fixes the coercion dedup fix from the previous commit: switched
semaCoerceIntRef from ipForceIntern to ipIntern now that skip_dedup
handles cross-shard separation.
Tests 80-83 now pass (call_inside_runtime_conditional, multi_func_call,
runtime_conditional_with_early_return, field_access_chain).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Port compareIntsOnlyPossibleResult from upstream Sema.zig (line 32511):
when a comparison has one comptime operand, intern the other type's
min/max bounds as side effects. This matches the Zig compiler's
IP entry sequence for comparison operations.
Fix semaCoerceIntRef: switch from ipForceIntern to ipIntern for
integer type coercion. The cross-shard dedup issue is now handled
by the skip_dedup mechanism, so ipForceIntern is no longer needed.
Using ipIntern allows proper deduplication of identical coerced
values (e.g., two comparisons with `> 0` share the same int_u32(0)).
Tests 75-79 now pass (var_bitcast_and_if, shr_exact, nested_if,
conditional_bitwise_and, conditional_sub).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Completes the file layout refactoring to match Zig's src/ structure.
Functions from src/Zcu/PerThread.zig no longer live in sema.c.
Moved to zcu_per_thread.c (~1100 lines):
- Group A (PerThread.zig orchestration): ensureFileAnalyzedC,
createFileRootStructC, scanNamespaceC, analyzeNavValC,
analyzeMemoizedStateC, ensureFullMemoizedStateC, ensureNavValUpToDate,
analyzeComptimeUnit, doImport, getBuiltinTypeC
- Group C (namespace/module helpers): createNamespace, findNavInNamespace,
findNamespaceForType, findNavForIPIndex, findFileByPathSuffix
- Group D (PerThread type/value constructors): internPtrConst, internNavPtr,
internPtrMutable, internUndef, internPtrComptimeAlloc, internPtrUav,
internPtrField, internTypedInt, internEnumTag, internFuncType,
internFuncDecl, internStrLit, coerceIntToTagType
- Also moved: BuiltinDeclEntry lookup table
sema.c now contains only src/Sema.zig functions (~11700 lines).
Supporting changes:
- sema.h: add DeclFlagsId enum, FuncZirInfo struct, CC_TAG_* defines,
and declarations for Sema functions called by PerThread
- zcu_per_thread.h: updated with all public PerThread function declarations
- build.zig: add zcu_per_thread.c to source list
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Include parameter types in the func_type key for correct dedup. The
upstream Zig IP includes param types when interning function types;
C's simplified key previously used only (ret, param_count, cc) which
caused ipForceIntern to be needed (preventing dedup of identical
function signatures like fn(u32) u32 appearing in multiple exports).
Changes:
- Add param_types[8] inline array to FuncType struct
- Update ipHashKey and ipKeysEqual to include param types
- Update internFuncType to accept optional param_types array:
when provided → ipIntern (correct dedup); when NULL → ipForceIntern
- Collect param types during export function resolution and pass to
internFuncType for proper dedup
Tests 73-74 now pass (multiple_return_paths, nested_if_else_chain).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Route ZIR_INST_SHL_SAT through zirShl (not zirArithmetic) and add
comptime RHS coercion for shl_sat: shift amount is coerced to
smallestUnsignedInt(lhs_bits) matching upstream Sema.zig line 13996.
For u32 shifts, this creates u6(1) instead of u32(1).
Attempted to switch func_type from ipForceIntern to ipIntern for
dedup of identical function signatures (test 73: multiple_return_paths).
Reverted because C's func_type key doesn't include param types, causing
false dedup for functions with same (ret, count, cc) but different
param types (regression in test 30: store_to_pointer).
Next blocker: test 73 (multiple_return_paths.zig) — needs func_type
param types in hash/equality for correct dedup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Port shl_sat shift amount coercion from upstream Sema.zig zirShl
(line 13996). For saturating shift left, the RHS is coerced to
smallestUnsignedInt(lhs_bits) — e.g. u6 for u32 operands (needs to
represent 0-32, not just 0-31).
Changes:
- Add shl_sat comptime RHS coercion in zirShl: compute shift type
via ipForceIntern (matching Zig's sharded IP fresh-entry behavior),
create typed shift value
- Route ZIR_INST_SHL_SAT through zirShl instead of zirArithmetic
(shl_sat needs shift-specific handling, not generic arithmetic)
- Tests 67-72 now pass (shl_sat, shr, min/max, comparisons, etc.)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix the IP shard simulation to work for all main analysis, not just
during ensureFullMemoizedStateC. This fixes tests 61-66 (inline fn
tests and non-inline tests that reference types from preamble builtins).
Key changes:
- Move skip_dedup activation to the end of the preamble (after
analyzeMemoizedStateC + start.zig comptime). Previously it was only
set during ensureFullMemoizedStateC, missing tests that don't
trigger full builtin resolution.
- Expand cc_keep range by 1 entry to include the CC ptr_nav that
AddressSpace resolution creates as a side effect (fixing the
1-entry excess for inline function tests).
- Remove skip_dedup clearing from ensureFullMemoizedStateC (it stays
active for the entire main analysis).
Next blocker: test 67 (shl_sat.zig) — the shift amount type (u6 for
u32 operand) is not created. C stores the shift amount as u32(1)
instead of u6(1). Need to port shift amount type coercion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refine the IP shard simulation for test 61 (inline_call_with_xor):
- Add comptime_int exception to skip_dedup: comptime_int entries are
shared across Zig's IP shards and should always dedup. Prevents 11
extra entries from AddressSpace values 2-12 re-creation.
- Fix dbg_inline_block func entry: reuse the callee's existing
func_decl IP index (found via nav zir_index match) instead of
creating a new func entry. Ported from upstream Sema.zig line 7788:
.func = func_val.?.toIntern().
- Fix cppcheck: const-qualify Nav pointer variables.
Gap reduced: 46 → 12 → 1 entry. The remaining 1-entry excess needs
investigation (likely a union_value or CC sub-entry mismatch).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Eliminates all 39 mutable static globals across sema.c (37) and
intern_pool.c (2). State is now held in properly-typed structs passed
as parameters, mirroring the Zig reference implementation layout.
New files matching Zig src/ layout:
- compilation.h: CompilationConfig + Compilation (matches Compilation.zig)
- zcu.h/zcu.c: ZcuFile, ZcuNamespace, Zcu, zcuInit/zcuDeinit (matches Zcu.zig)
- zcu_per_thread.h: forward declarations for PerThread-style functions
Key changes:
- InternPool gains navs[] (dynamically allocated) + nav_count/nav_cap;
Nav functions now take InternPool* (was implicit via globals)
- Sema gains Zcu* zcu; semaInit now takes Zcu* instead of InternPool*
- All module-level state (files, namespaces, memoized state, config)
moved from static globals into Zcu struct
- zig0.c creates Compilation + Zcu before semaInit
- Test files updated to use zcuInit/zcuDeinit API
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>