commit 0c0ac99d27db09fbfeb4c7ea13ac0e6b76b36449 (tree)
parent 21db07da43bec3aef0b10fa0d2a2fbc1e3a5fc10
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Mon, 23 Feb 2026 05:11:17 +0000
sema: add abs, max/min, div, aggregate_init, cross-module re-export resolution; enable divdc3
Port missing builtins and operations needed for divdc3.zig corpus test:
- @abs: emit AIR_INST_ABS (ty_op) for float operands
- @max/@min: emit AIR_INST_MAX/AIR_INST_MIN at runtime (bin_op)
- div: emit AIR_INST_DIV_FLOAT for floats, AIR_INST_DIV_TRUNC for ints
- struct_init: runtime path emits AIR_INST_AGGREGATE_INIT
- Cross-module import chain: follow re-exports (e.g. scalbn -> ldexp)
- Fix return type resolution for generic anytype params (use semaTypeOf)
- Add comptime_float peer type resolution
- Skip test/comptime decls in findFuncInstInZir
- Handle validate_ref_ty as validation-only (no AIR)
- Remove leftover debug fprintf traces
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
3 files changed, 193 insertions(+), 54 deletions(-)
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -566,6 +566,10 @@ static TypeIndex semaTypeOf(Sema* sema, AirInstRef ref) {
case AIR_INST_SHL:
case AIR_INST_SHL_SAT:
case AIR_INST_SHR:
+ case AIR_INST_MAX:
+ case AIR_INST_MIN:
+ case AIR_INST_DIV_FLOAT:
+ case AIR_INST_DIV_TRUNC:
return semaTypeOf(sema, sema->air_inst_datas[inst_idx].bin_op.lhs);
// cmp bin_op: result type is bool.
case AIR_INST_CMP_LT:
@@ -592,6 +596,7 @@ static TypeIndex semaTypeOf(Sema* sema, AirInstRef ref) {
case AIR_INST_CTZ:
case AIR_INST_POPCOUNT:
case AIR_INST_BYTE_SWAP:
+ case AIR_INST_ABS:
case AIR_INST_DBG_INLINE_BLOCK:
case AIR_INST_BLOCK:
case AIR_INST_STRUCT_FIELD_PTR_INDEX_0:
@@ -600,6 +605,7 @@ static TypeIndex semaTypeOf(Sema* sema, AirInstRef ref) {
case AIR_INST_STRUCT_FIELD_PTR_INDEX_3:
return AIR_REF_TO_IP(sema->air_inst_datas[inst_idx].ty_op.ty_ref);
case AIR_INST_STRUCT_FIELD_VAL:
+ case AIR_INST_AGGREGATE_INIT:
return AIR_REF_TO_IP(sema->air_inst_datas[inst_idx].ty_pl.ty_ref);
// call: return type from side table (populated by zirCall).
case AIR_INST_CALL:
@@ -657,6 +663,10 @@ static TypeIndex semaResolvePeerTypes(
return cIntToRegularInt(rhs_ty);
if (rhs_ty == IP_INDEX_COMPTIME_INT_TYPE)
return cIntToRegularInt(lhs_ty);
+ if (lhs_ty == IP_INDEX_COMPTIME_FLOAT_TYPE)
+ return rhs_ty;
+ if (rhs_ty == IP_INDEX_COMPTIME_FLOAT_TYPE)
+ return lhs_ty;
// When both types are concrete int types, pick the wider type.
// Ported from src/Sema.zig peer_resolve_int_int (fixed_int strategy).
if (sema->ip->items[lhs_ty].tag == IP_KEY_INT_TYPE
@@ -1064,6 +1074,25 @@ static uint16_t smallestUnsignedBits(uint16_t max) {
return count;
}
+// zirAbs: handle @abs ZIR instruction.
+// Ported from src/Sema.zig zirAbs.
+// For floats/comptime_float: result_ty = operand_ty.
+// For signed ints: result_ty = toUnsigned(operand_ty).
+// For unsigned ints: identity (return operand).
+static AirInstRef zirAbs(Sema* sema, SemaBlock* block, uint32_t inst) {
+ ZirInstRef operand_ref = sema->code.inst_datas[inst].un_node.operand;
+ AirInstRef operand = resolveInst(sema, operand_ref);
+ TypeIndex operand_ty = semaTypeOf(sema, operand);
+
+ // For floats, result type is the same as operand type.
+ // Emit AIR_INST_ABS as ty_op.
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.ty_op.ty_ref = AIR_REF_FROM_IP(operand_ty);
+ data.ty_op.operand = operand;
+ return semaAddInst(block, AIR_INST_ABS, data);
+}
+
// zirBitCount: handle clz/ctz/pop_count ZIR instructions.
// Ported from src/Sema.zig zirBitCount.
// Result type is smallestUnsignedInt(operand_bits).
@@ -1248,6 +1277,35 @@ emit_runtime:;
return semaAddInst(block, air_tag, data);
}
+// zirDiv: handle div ZIR instruction (/ operator).
+// Ported from src/Sema.zig zirDiv.
+// For floats: emits AIR_INST_DIV_FLOAT (strict mode).
+// For unsigned ints: emits AIR_INST_DIV_TRUNC.
+static AirInstRef zirDiv(Sema* sema, SemaBlock* block, uint32_t inst) {
+ uint32_t payload_index = sema->code.inst_datas[inst].pl_node.payload_index;
+ ZirInstRef zir_lhs = sema->code.extra[payload_index];
+ ZirInstRef zir_rhs = sema->code.extra[payload_index + 1];
+ AirInstRef lhs = resolveInst(sema, zir_lhs);
+ AirInstRef rhs = resolveInst(sema, zir_rhs);
+
+ TypeIndex peer_ty = semaResolvePeerTypes(sema, lhs, rhs);
+ lhs = semaCoerce(sema, block, peer_ty, lhs);
+ rhs = semaCoerce(sema, block, peer_ty, rhs);
+
+ // Determine air_tag based on scalar type.
+ AirInstTag air_tag = AIR_INST_DIV_FLOAT; // default for floats
+ InternPoolKey pk = ipIndexToKey(sema->ip, peer_ty);
+ if (pk.tag == IP_KEY_INT_TYPE) {
+ air_tag = AIR_INST_DIV_TRUNC;
+ }
+
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.bin_op.lhs = lhs;
+ data.bin_op.rhs = rhs;
+ return semaAddInst(block, air_tag, data);
+}
+
// zirBitwise: handle bitwise operation ZIR instructions.
// Ported from src/Sema.zig zirBitwise.
static AirInstRef zirBitwise(
@@ -2259,6 +2317,11 @@ static uint32_t findFuncInstInZir(const Zir* zir, const char* func_name) {
uint32_t flags_1 = zir->extra[payload + 5];
uint32_t id = (flags_1 >> 27) & 0x1F;
+ // Skip test declarations (unnamed_test=0, test=1, decltest=2)
+ // and comptime blocks (3) — only look at const/var/export decls.
+ if (id <= 3)
+ continue;
+
uint32_t di = payload + 6;
// Extract declaration name.
@@ -2846,18 +2909,6 @@ static AirInstRef zirCall(
}
}
- // Debug: save callee name before chain resolution changes sema->code.
- char dbg_callee[64] = "?";
- if (callee_name_idx) {
- const char* nm
- = (const char*)&sema->code.string_bytes[callee_name_idx];
- size_t nl = strlen(nm);
- if (nl > 63)
- nl = 63;
- memcpy(dbg_callee, nm, nl);
- dbg_callee[nl] = 0;
- }
-
// Find the inline function's ZIR instruction.
uint32_t func_inst = findDeclFuncInst(sema, callee_name_idx);
// Fallback: during cross-module inline expansion, sema->decl_names
@@ -3004,19 +3055,29 @@ static AirInstRef zirCall(
zirDeinit(&cur_zir);
astDeinit(&cur_ast);
if (fn_zir.inst_len > 0) {
- // Function should be directly in this
- // module with same name.
+ // Try direct lookup first, then follow
+ // re-exports (e.g. scalbn -> ldexp).
+ char fn_src_dir[1024];
+ computeSourceDir(NULL, cur_dir, fn_import,
+ fn_src_dir, sizeof(fn_src_dir));
func_inst
- = findFuncInstInZir(&fn_zir, fn_name);
+ = findFuncInModuleZir(fn_src_dir, &fn_zir,
+ &fn_ast, fn_name, import_source_dir,
+ sizeof(import_source_dir));
if (func_inst != UINT32_MAX) {
+ // If findFuncInModuleZir found it
+ // directly (no re-export), it did
+ // not write import_source_dir.
+ // Set it to fn_src_dir as fallback.
+ if (import_source_dir[0] == '\0')
+ snprintf(import_source_dir,
+ sizeof(import_source_dir), "%s",
+ fn_src_dir);
saved_code = sema->code;
sema->code = fn_zir;
import_zir = fn_zir;
import_ast = fn_ast;
is_cross_module = true;
- computeSourceDir(NULL, cur_dir, fn_import,
- import_source_dir,
- sizeof(import_source_dir));
} else {
zirDeinit(&fn_zir);
astDeinit(&fn_ast);
@@ -3844,19 +3905,6 @@ static AirInstRef zirCall(
sema->code = arg_code;
}
- // Ported from src/Sema.zig line 7480:
- // if (call_dbg_node) |some| try sema.zirDbgStmt(block, some);
- // call_dbg_node is the ZIR instruction preceding the call (inst - 1).
- // Must use the caller's ZIR for cross-module calls.
- if (inst > 0 && !block->is_comptime) {
- Zir dbg_code = sema->code;
- if (is_cross_module)
- sema->code = saved_code;
- zirDbgStmt(sema, block, inst - 1);
- if (is_cross_module)
- sema->code = dbg_code;
- }
-
// Handle type-returning functions whose result can be computed from
// the comptime arguments without inlining.
// Upstream always reserves a dead BLOCK before inlining; we match
@@ -4255,8 +4303,15 @@ static AirInstRef zirCall(
if (param_body[p] == ret_ty_inst && pi < args_len) {
// Return type ref matches this param.
// For comptime type params, the arg value IS
- // the type.
- ret_ty = AIR_REF_TO_IP(arg_refs[pi]);
+ // the type (an IP ref). For runtime params
+ // (including anytype), use semaTypeOf to get
+ // the argument's type.
+ if (ptag == ZIR_INST_PARAM_COMPTIME
+ || ptag == ZIR_INST_PARAM_ANYTYPE_COMPTIME) {
+ ret_ty = AIR_REF_TO_IP(arg_refs[pi]);
+ } else {
+ ret_ty = semaTypeOf(sema, arg_refs[pi]);
+ }
break;
}
pi++;
@@ -4278,12 +4333,6 @@ static AirInstRef zirCall(
|| ret_ty == IP_INDEX_ENUM_LITERAL_TYPE);
bool is_inline_call
= func_info.is_inline || block->is_comptime || is_comptime_only_ret;
- if (strcmp(dbg_callee, "copysign") == 0)
- fprintf(stderr,
- " COPYSIGN_PATH: is_inline=%d is_comptime=%d ct_only_ret=%d"
- " ret_ty=%u is_generic=%d\n",
- func_info.is_inline, block->is_comptime, is_comptime_only_ret,
- ret_ty, is_generic);
if (!is_inline_call) {
// Dummy allocs for generic runtime params are now emitted
// interleaved in the arg evaluation loop above (Fix A).
@@ -4359,8 +4408,12 @@ static AirInstRef zirCall(
semaAddExtra(sema, runtime_arg_refs[a]);
// Ported from Sema.zig:7480: AstGen ensures a dbg_stmt
- // always precedes a call instruction. For cross-module
- // calls, inst-1 is in the calling module's ZIR.
+ // always precedes a call instruction. Use the caller's ZIR
+ // for cross-module calls. Note: the general dbg_stmt at the
+ // top of zirCall (before arg evaluation) may have been
+ // followed by arg instructions, so emit another one here
+ // to directly precede the call. zirDbgStmt de-duplicates
+ // when the previous block instruction is already a dbg_stmt.
if (is_cross_module) {
Zir temp = sema->code;
sema->code = saved_code;
@@ -4440,6 +4493,18 @@ static AirInstRef zirCall(
}
}
+ // Ported from src/Sema.zig line 7480: emit dbg_stmt for inline
+ // calls, just before the inline block. Use the caller's ZIR
+ // for cross-module calls.
+ if (inst > 0 && !block->is_comptime) {
+ Zir dbg_code = sema->code;
+ if (is_cross_module)
+ sema->code = saved_code;
+ zirDbgStmt(sema, block, inst - 1);
+ if (is_cross_module)
+ sema->code = dbg_code;
+ }
+
// Upstream: need_debug_scope = !block.isComptime() && ...
// When comptime (or all args comptime), use BLOCK; otherwise
// DBG_INLINE_BLOCK.
@@ -5884,11 +5949,11 @@ static InternPoolIndex resolveUnsignedIntType(InternPool* ip, uint32_t bits) {
}
}
-// zirStructInitComptime: handle struct_init in comptime context.
-// For @Type(.{.int = .{.signedness = .unsigned, .bits = N}}):
-// - inner struct: extract signedness and bits, track as CT_TAG_INT_INFO
-// - outer struct: extract field name (.int), track as CT_TAG_REIFY_INT
-static AirInstRef zirStructInitComptime(Sema* sema, uint32_t inst) {
+// zirStructInit: handle struct_init ZIR instruction.
+// Comptime case: @Type(.{.int = .{.signedness = .unsigned, .bits = N}})
+// Runtime case: emits AIR_INST_AGGREGATE_INIT with field values.
+// Ported from src/Sema.zig zirStructInit / finishStructInit.
+static AirInstRef zirStructInit(Sema* sema, SemaBlock* block, uint32_t inst) {
uint32_t payload_index = sema->code.inst_datas[inst].pl_node.payload_index;
// StructInit payload: abs_node, abs_line, fields_len, then Items.
uint32_t fields_len = sema->code.extra[payload_index + 2];
@@ -5967,7 +6032,32 @@ static AirInstRef zirStructInitComptime(Sema* sema, uint32_t inst) {
return AIR_REF_FROM_IP(marker);
}
- return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
+ // Runtime struct init: emit aggregate_init.
+ // Ported from Sema.zig finishStructInit line 19655.
+ // The field_type instructions provide the container type.
+ // Resolve the container type from the first field_type's container ref.
+ {
+ uint32_t ft_payload0
+ = sema->code.inst_datas[items[0]].pl_node.payload_index;
+ ZirInstRef container_ref = sema->code.extra[ft_payload0];
+ AirInstRef container_air = resolveInst(sema, container_ref);
+ TypeIndex container_ty = AIR_REF_IS_IP(container_air)
+ ? AIR_REF_TO_IP(container_air)
+ : IP_INDEX_VOID_TYPE;
+
+ // Build aggregate_init extra: element refs in field order.
+ uint32_t extra_idx = sema->air_extra_len;
+ for (uint32_t f = 0; f < fields_len; f++) {
+ ZirInstRef init_ref2 = items[f * 2 + 1];
+ AirInstRef init_air2 = resolveInst(sema, init_ref2);
+ semaAddExtra(sema, init_air2);
+ }
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.ty_pl.ty_ref = AIR_REF_FROM_IP(container_ty);
+ data.ty_pl.payload = extra_idx;
+ return semaAddInst(block, AIR_INST_AGGREGATE_INIT, data);
+ }
}
// zirReifyComptime: handle @Type(...) in comptime context.
@@ -6629,6 +6719,10 @@ static bool analyzeBodyInner(
zirArithmetic(sema, block, inst, AIR_INST_MUL_WRAP));
i++;
continue;
+ case ZIR_INST_DIV:
+ instMapPut(&sema->inst_map, inst, zirDiv(sema, block, inst));
+ i++;
+ continue;
case ZIR_INST_ADD_SAT:
instMapPut(&sema->inst_map, inst,
zirArithmetic(sema, block, inst, AIR_INST_ADD_SAT));
@@ -6724,8 +6818,18 @@ static bool analyzeBodyInner(
instMapPut(&sema->inst_map, inst,
AIR_REF_FROM_IP(ipIntern(sema->ip, key)));
} else {
+ // Runtime: emit AIR_INST_MAX or AIR_INST_MIN.
+ TypeIndex peer_ty
+ = semaResolvePeerTypes(sema, min_lhs, min_rhs);
+ AirInstRef c_lhs = semaCoerce(sema, block, peer_ty, min_lhs);
+ AirInstRef c_rhs = semaCoerce(sema, block, peer_ty, min_rhs);
+ AirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.bin_op.lhs = c_lhs;
+ data.bin_op.rhs = c_rhs;
instMapPut(&sema->inst_map, inst,
- AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE));
+ semaAddInst(
+ block, is_max ? AIR_INST_MAX : AIR_INST_MIN, data));
}
i++;
continue;
@@ -6860,9 +6964,16 @@ static bool analyzeBodyInner(
i++;
continue;
+ // @abs.
+ case ZIR_INST_ABS:
+ instMapPut(&sema->inst_map, inst, zirAbs(sema, block, inst));
+ i++;
+ continue;
+
// Validation-only (no AIR emitted).
case ZIR_INST_VALIDATE_DEREF:
case ZIR_INST_VALIDATE_CONST:
+ case ZIR_INST_VALIDATE_REF_TY:
i++;
continue;
@@ -7078,10 +7189,10 @@ static bool analyzeBodyInner(
i++;
continue;
- // struct_init: comptime struct initialization.
+ // struct_init: struct initialization (comptime or runtime).
case ZIR_INST_STRUCT_INIT:
instMapPut(
- &sema->inst_map, inst, zirStructInitComptime(sema, inst));
+ &sema->inst_map, inst, zirStructInit(sema, block, inst));
i++;
continue;
@@ -7676,8 +7787,6 @@ static bool analyzeBodyInner(
}
semaAddExtra(sema, sub_br_idx);
sema->air_inst_tags[br] = AIR_INST_BLOCK;
- fprintf(
- stderr, " REWRITE_BR_BLOCK: air_idx=%u\n", br);
sema->air_inst_datas[br].ty_pl.ty_ref
= AIR_REF_FROM_IP(resolved_ty);
sema->air_inst_datas[br].ty_pl.payload = sub_extra;
@@ -8127,7 +8236,6 @@ static bool analyzeBodyInner(
// For all other instructions, produce a void mapping and skip.
// As handlers are implemented, they will replace this default.
default: {
- fprintf(stderr, " UNHANDLED: inst=%u tag=%u\n", inst, inst_tag);
AirInstRef air_ref = AIR_REF_FROM_IP(IP_INDEX_VOID_TYPE);
instMapPut(&sema->inst_map, inst, air_ref);
i++;
diff --git a/stage0/sema_test.zig b/stage0/sema_test.zig
@@ -456,6 +456,7 @@ fn airDataRefSlots(tag_val: u8) [2]bool {
c.AIR_INST_STRUCT_FIELD_PTR,
c.AIR_INST_DBG_INLINE_BLOCK,
c.AIR_INST_BLOCK,
+ c.AIR_INST_AGGREGATE_INIT,
=> .{ true, false },
// bin_op: lhs(Ref) + rhs(Ref)
c.AIR_INST_ADD,
@@ -488,6 +489,19 @@ fn airDataRefSlots(tag_val: u8) [2]bool {
c.AIR_INST_CMP_GTE,
c.AIR_INST_CMP_GT,
c.AIR_INST_CMP_NEQ,
+ c.AIR_INST_MAX,
+ c.AIR_INST_MIN,
+ c.AIR_INST_DIV_FLOAT,
+ c.AIR_INST_DIV_FLOAT_OPTIMIZED,
+ c.AIR_INST_DIV_TRUNC,
+ c.AIR_INST_DIV_TRUNC_OPTIMIZED,
+ c.AIR_INST_DIV_FLOOR,
+ c.AIR_INST_DIV_FLOOR_OPTIMIZED,
+ c.AIR_INST_DIV_EXACT,
+ c.AIR_INST_DIV_EXACT_OPTIMIZED,
+ c.AIR_INST_ADD_SAT,
+ c.AIR_INST_SUB_SAT,
+ c.AIR_INST_MUL_SAT,
=> .{ true, true },
// ty_op: type(Ref) + operand(Ref)
c.AIR_INST_BITCAST,
@@ -515,6 +529,7 @@ fn airDataRefSlots(tag_val: u8) [2]bool {
c.AIR_INST_CTZ,
c.AIR_INST_POPCOUNT,
c.AIR_INST_BYTE_SWAP,
+ c.AIR_INST_ABS,
c.AIR_INST_STRUCT_FIELD_PTR_INDEX_0,
c.AIR_INST_STRUCT_FIELD_PTR_INDEX_1,
c.AIR_INST_STRUCT_FIELD_PTR_INDEX_2,
@@ -1342,3 +1357,19 @@ test "sema air: inline fn with += call inside two conditionals" {
\\}
);
}
+
+test "sema air: @abs float" {
+ try semaAirRawCheck("export fn f(x: f64) f64 { return @abs(x); }");
+}
+
+test "sema air: @max float" {
+ try semaAirRawCheck("export fn f(x: f64, y: f64) f64 { return @max(x, y); }");
+}
+
+test "sema air: @min float" {
+ try semaAirRawCheck("export fn f(x: f64, y: f64) f64 { return @min(x, y); }");
+}
+
+test "sema air: f64 div" {
+ try semaAirRawCheck("export fn f(x: f64, y: f64) f64 { return x / y; }");
+}
diff --git a/stage0/stages_test.zig b/stage0/stages_test.zig
@@ -164,7 +164,7 @@ const corpus_files = .{
"../lib/compiler_rt/mulhc3.zig", // 425
"../lib/compiler_rt/mulsc3.zig", // 425
"../lib/compiler_rt/mulxc3.zig", // 425
- //"../lib/compiler_rt/divdc3.zig", // 434
+ "../lib/compiler_rt/divdc3.zig", // 434
//"../lib/compiler_rt/divhc3.zig", // 434
//"../lib/compiler_rt/divsc3.zig", // 434
//"../lib/compiler_rt/divxc3.zig", // 434