commit 21db07da43bec3aef0b10fa0d2a2fbc1e3a5fc10 (tree)
parent 06edb12fe93109dd4c3815b3943ad7d1aac10c4e
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Mon, 23 Feb 2026 03:46:37 +0000
sema: resolve cross-module inline return types, memoize type functions; enable mul*c3
- Add Complex struct lookup for multi-instruction return type bodies in
cross-module inline calls (e.g. mulc3 returning Complex(f64))
- Add memoization for comptime type function calls to avoid duplicate
block pre-allocation
- Add comptime float coercion (comptime_float → concrete float)
- Add tryResolveInst for graceful handling of unresolved references
- Classify dbg_inline_block and block as ref-bearing in airDataRefSlots
- Enable muldc3, mulhc3, mulsc3, mulxc3 corpus tests (all pass)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
6 files changed, 545 insertions(+), 44 deletions(-)
diff --git a/stage0/intern_pool.c b/stage0/intern_pool.c
@@ -70,6 +70,14 @@ static uint32_t ipHashKey(const InternPoolKey* key) {
case IP_KEY_ENUM_LITERAL:
h = ipHashCombine(h, key->data.enum_literal);
break;
+ case IP_KEY_FLOAT: {
+ uint64_t fbits;
+ memcpy(&fbits, &key->data.float_val.val, sizeof(fbits));
+ h = ipHashCombine(h, key->data.float_val.ty);
+ h = ipHashCombine(h, (uint32_t)fbits);
+ h = ipHashCombine(h, (uint32_t)(fbits >> 32));
+ break;
+ }
default:
/* For other tag types, just use the tag hash. */
break;
@@ -122,6 +130,11 @@ static bool ipKeysEqual(const InternPoolKey* a, const InternPoolKey* b) {
return a->data.tuple_type == b->data.tuple_type;
case IP_KEY_ENUM_LITERAL:
return a->data.enum_literal == b->data.enum_literal;
+ case IP_KEY_FLOAT:
+ return a->data.float_val.ty == b->data.float_val.ty
+ && memcmp(&a->data.float_val.val, &b->data.float_val.val,
+ sizeof(double))
+ == 0;
default:
/* Fallback: memcmp the entire data union. */
return memcmp(&a->data, &b->data, sizeof(a->data)) == 0;
@@ -762,6 +775,9 @@ InternPoolIndex ipTypeOf(const InternPool* ip, InternPoolIndex index) {
case IP_KEY_INT:
return key.data.int_val.ty;
+ case IP_KEY_FLOAT:
+ return key.data.float_val.ty;
+
case IP_KEY_ENUM_LITERAL:
return IP_INDEX_ENUM_LITERAL_TYPE;
diff --git a/stage0/intern_pool.h b/stage0/intern_pool.h
@@ -339,7 +339,10 @@ typedef struct {
uint32_t enum_literal; // string index
InternPoolIndex enum_tag;
InternPoolIndex empty_enum_value;
- double float_val;
+ struct {
+ InternPoolIndex ty;
+ double val;
+ } float_val;
InternPoolIndex ptr;
InternPoolIndex slice;
InternPoolIndex opt;
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -4,6 +4,16 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+// Debug assert that prints location before aborting.
+#undef assert
+#define assert(cond) \
+ do { \
+ if (!(cond)) { \
+ fprintf(stderr, "ASSERT FAIL: %s:%d: %s\n", __FILE__, __LINE__, \
+ #cond); \
+ abort(); \
+ } \
+ } while (0)
// Simple djb2 hash for enum literal names.
static uint32_t simpleStringHash(const char* s) {
@@ -347,8 +357,9 @@ static uint32_t semaAppendAirString(Sema* sema, const char* str) {
// Ported from src/Sema.zig zirDbgVar.
static void zirDbgVar(
Sema* sema, SemaBlock* block, uint32_t inst, AirInstTag air_tag) {
- if (block->is_comptime)
+ if (block->is_comptime) {
return;
+ }
uint32_t str_idx = sema->code.inst_datas[inst].str_op.str;
ZirInstRef operand_ref = sema->code.inst_datas[inst].str_op.operand;
@@ -677,8 +688,23 @@ static AirInstRef semaCoerce(
TypeIndex src_ty = semaTypeOf(sema, ref);
if (src_ty == target_ty)
return ref;
+ if (src_ty == TYPE_NONE || target_ty == TYPE_NONE)
+ return ref;
if (src_ty == IP_INDEX_COMPTIME_INT_TYPE)
return semaCoerceIntRef(sema, ref, target_ty);
+ // Comptime float → concrete float: re-intern with target type.
+ // Ported from src/Sema.zig coerce comptime_float → float.
+ if (src_ty == IP_INDEX_COMPTIME_FLOAT_TYPE && AIR_REF_IS_IP(ref)) {
+ InternPoolKey src_key = ipIndexToKey(sema->ip, AIR_REF_TO_IP(ref));
+ if (src_key.tag == IP_KEY_FLOAT) {
+ InternPoolKey key;
+ memset(&key, 0, sizeof(key));
+ key.tag = IP_KEY_FLOAT;
+ key.data.float_val.ty = target_ty;
+ key.data.float_val.val = src_key.data.float_val.val;
+ return AIR_REF_FROM_IP(ipIntern(sema->ip, key));
+ }
+ }
// Undefined coercion: re-intern undefined with the target type.
// Ported from src/Sema.zig coerceInMemory → getCoerced.
if (src_ty == IP_INDEX_UNDEFINED_TYPE) {
@@ -1474,6 +1500,26 @@ static AirInstRef zirShl(
return semaAddInst(block, air_tag, data);
}
+// tryResolveInst: like resolveInst, but returns AIR_REF_NONE without
+// setting has_compile_errors when the instruction is not in the inst_map.
+// Ported from upstream Zig's GenericPoison handling: callers that can
+// gracefully handle unresolved references (e.g. zirAs in generic arg
+// bodies) use this to avoid cascading failures.
+static AirInstRef tryResolveInst(Sema* sema, ZirInstRef zir_ref) {
+ assert(zir_ref != ZIR_REF_NONE);
+ if (zir_ref >= ZIR_REF_START_INDEX) {
+ uint32_t zir_inst = zir_ref - ZIR_REF_START_INDEX;
+ // Return AIR_REF_NONE if the instruction is not in the map range.
+ if (sema->inst_map.items_len == 0)
+ return AIR_REF_NONE;
+ if (zir_inst < sema->inst_map.start
+ || zir_inst >= sema->inst_map.start + sema->inst_map.items_len)
+ return AIR_REF_NONE;
+ return sema->inst_map.items[zir_inst - sema->inst_map.start];
+ }
+ return AIR_REF_FROM_IP(zir_ref);
+}
+
// zirAsNode: handle @as ZIR instruction.
// Ported from src/Sema.zig zirAs / zirAsNode.
static AirInstRef zirAsNode(Sema* sema, SemaBlock* block, uint32_t inst) {
@@ -1484,8 +1530,16 @@ static AirInstRef zirAsNode(Sema* sema, SemaBlock* block, uint32_t inst) {
if (dest_ty_ref < ZIR_REF_START_INDEX) {
dest_ty = dest_ty_ref;
} else {
- // Resolve through inst_map (comptime-evaluated type).
- AirInstRef resolved = resolveInst(sema, dest_ty_ref);
+ // Ported from src/Sema.zig zirAs: when the destination type
+ // references a not-yet-resolved instruction (GenericPoison),
+ // skip the coercion and pass through the operand. This
+ // happens in arg bodies of generic calls where the result
+ // type references the call instruction itself.
+ AirInstRef resolved = tryResolveInst(sema, dest_ty_ref);
+ if (resolved == AIR_REF_NONE) {
+ // GenericPoison: skip coercion.
+ return resolveInst(sema, operand_ref);
+ }
if (!AIR_REF_IS_IP(resolved)) {
return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
}
@@ -1587,6 +1641,9 @@ static FuncZirInfo parseFuncZir(Sema* sema, uint32_t inst) {
return info;
}
+// Forward declaration (defined later, used by findDeclImportPath et al).
+static uint32_t findDeclInstByNameInZir(const Zir* zir, const char* decl_name);
+
// findDeclImportPath: given a declaration name index, check if the
// declaration's value body contains a ZIR_INST_IMPORT. If so, return
// the import path string (from string_bytes). Returns NULL if not found.
@@ -1598,6 +1655,11 @@ static const char* findDeclImportPath(Sema* sema, uint32_t name_idx) {
break;
}
}
+ // Fallback: search current ZIR by name (cross-module inline).
+ if (decl_inst == UINT32_MAX && name_idx != 0) {
+ const char* nm = (const char*)&sema->code.string_bytes[name_idx];
+ decl_inst = findDeclInstByNameInZir(&sema->code, nm);
+ }
if (decl_inst == UINT32_MAX)
return NULL;
@@ -1634,6 +1696,11 @@ static bool findDeclImportFieldVal(Sema* sema, uint32_t name_idx,
break;
}
}
+ // Fallback: search current ZIR by name (cross-module inline).
+ if (decl_inst == UINT32_MAX && name_idx != 0) {
+ const char* nm = (const char*)&sema->code.string_bytes[name_idx];
+ decl_inst = findDeclInstByNameInZir(&sema->code, nm);
+ }
if (decl_inst == UINT32_MAX)
return false;
@@ -1947,18 +2014,26 @@ static bool findDeclImportFieldValInZir(const Zir* zir, const char* decl_name,
// resolves to <module_root>/lib/<name>.
static void computeSourceDir(const char* module_root, const char* source_dir,
const char* import_path, char* out_dir, size_t out_size) {
- if (import_path[0] == '.' && import_path[1] == '/') {
- // Relative import: source_dir + dirname(import_path)
- const char* rel = import_path + 2;
- const char* last_slash = strrchr(rel, '/');
- if (last_slash) {
+ const char* last_slash = strrchr(import_path, '/');
+ if (import_path[0] == '.'
+ && (import_path[1] == '/'
+ || (import_path[1] == '.' && import_path[2] == '/'))) {
+ // Relative import (./foo.zig or ../foo.zig):
+ // source_dir + dirname(import_path)
+ if (last_slash && last_slash != import_path) {
snprintf(out_dir, out_size, "%s/%.*s", source_dir,
- (int)(last_slash - rel), rel);
+ (int)(last_slash - import_path), import_path);
} else {
snprintf(out_dir, out_size, "%s", source_dir);
}
- } else if (module_root) {
- // Non-relative (e.g. "std") → <module_root>/lib/<name>
+ } else if (last_slash) {
+ // Path with subdirectory (e.g. "math/isinf.zig"):
+ // source_dir + dirname(import_path)
+ snprintf(out_dir, out_size, "%s/%.*s", source_dir,
+ (int)(last_slash - import_path), import_path);
+ } else if (module_root && import_path[0] != '.') {
+ // Non-relative bare module name (e.g. "std"):
+ // <module_root>/lib/<name>
snprintf(out_dir, out_size, "%s/lib/%s", module_root, import_path);
} else {
snprintf(out_dir, out_size, "%s", source_dir);
@@ -2054,6 +2129,74 @@ static void populateDeclTableFromZir(Sema* sema, const Zir* zir) {
}
}
+// findDeclInstByNameInZir: find a declaration instruction by name in a ZIR.
+// Scans the ZIR's struct_decl (inst 0) for a declaration with the given name.
+// Returns the declaration instruction index, or UINT32_MAX if not found.
+// Used as fallback during cross-module inline expansion where sema->decl_names
+// contains indices from a different module's string table.
+static uint32_t findDeclInstByNameInZir(
+ const Zir* zir, const char* decl_name) {
+ if (zir->inst_len == 0)
+ return UINT32_MAX;
+ if (zir->inst_tags[0] != ZIR_INST_EXTENDED)
+ return UINT32_MAX;
+ if (zir->inst_datas[0].extended.opcode != ZIR_EXT_STRUCT_DECL)
+ return UINT32_MAX;
+
+ uint16_t small = zir->inst_datas[0].extended.small;
+ uint32_t operand = zir->inst_datas[0].extended.operand;
+ uint32_t extra_index = operand + 6;
+
+ bool has_captures_len = (small & (1 << 0)) != 0;
+ bool has_fields_len = (small & (1 << 1)) != 0;
+ bool has_decls_len = (small & (1 << 2)) != 0;
+ bool has_backing_int = (small & (1 << 3)) != 0;
+
+ uint32_t captures_len = 0;
+ if (has_captures_len) {
+ captures_len = zir->extra[extra_index];
+ extra_index++;
+ }
+ if (has_fields_len)
+ extra_index++;
+
+ uint32_t decls_len = 0;
+ if (has_decls_len) {
+ decls_len = zir->extra[extra_index];
+ extra_index++;
+ }
+ extra_index += captures_len * 2;
+ if (has_backing_int) {
+ uint32_t backing_int_body_len = zir->extra[extra_index];
+ extra_index++;
+ if (backing_int_body_len == 0)
+ extra_index++;
+ else
+ extra_index += backing_int_body_len;
+ }
+
+ for (uint32_t d = 0; d < decls_len; d++) {
+ uint32_t di_inst = zir->extra[extra_index + d];
+ if (zir->inst_tags[di_inst] != ZIR_INST_DECLARATION)
+ continue;
+ uint32_t payload = zir->inst_datas[di_inst].declaration.payload_index;
+ uint32_t flags_1 = zir->extra[payload + 5];
+ uint32_t id = (flags_1 >> 27) & 0x1F;
+ uint32_t di = payload + 6;
+ uint32_t name_idx = 0;
+ if (declIdHasName(id)) {
+ name_idx = zir->extra[di];
+ di++;
+ }
+ if (name_idx == 0)
+ continue;
+ const char* name = (const char*)&zir->string_bytes[name_idx];
+ if (strcmp(name, decl_name) == 0)
+ return di_inst;
+ }
+ return UINT32_MAX;
+}
+
// findFuncInstInZir: find a func/func_fancy instruction by name in a ZIR.
// Scans the ZIR's struct_decl (inst 0) for a declaration matching the
// given name, then searches its value body for a func instruction.
@@ -2577,8 +2720,10 @@ static InternPoolIndex registerStructTypeFromZir(
// source_dir is the directory containing the module.
// On success, returns func_inst and may replace *zir/*ast with the target
// module (freeing the old ones). On failure, returns UINT32_MAX.
-static uint32_t findFuncInModuleZir(
- const char* source_dir, Zir* zir, Ast* ast, const char* func_name) {
+// If out_resolved_dir is non-NULL and a re-export was followed, writes
+// the resolved target module's source directory there.
+static uint32_t findFuncInModuleZir(const char* source_dir, Zir* zir, Ast* ast,
+ const char* func_name, char* out_resolved_dir, size_t resolved_dir_size) {
uint32_t func_inst = findFuncInstInZir(zir, func_name);
if (func_inst != UINT32_MAX)
return func_inst;
@@ -2603,6 +2748,10 @@ static uint32_t findFuncInModuleZir(
astDeinit(ast);
*zir = target_zir;
*ast = target_ast;
+ // Report the resolved directory if requested.
+ if (out_resolved_dir)
+ computeSourceDir(NULL, source_dir, reexport_import, out_resolved_dir,
+ resolved_dir_size);
return func_inst;
}
@@ -2697,8 +2846,29 @@ 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
+ // contains indices from the main module's string table, but
+ // callee_name_idx is from the inlined module's string table.
+ // Search the current ZIR directly by name.
+ if (func_inst == UINT32_MAX && callee_name_idx != 0) {
+ const char* cn
+ = (const char*)&sema->code.string_bytes[callee_name_idx];
+ func_inst = findFuncInstInZir(&sema->code, cn);
+ }
// For cross-module field_call: if local lookup fails, check if
// the callee object is an import and load the imported module.
Ast import_ast;
@@ -2706,9 +2876,11 @@ static AirInstRef zirCall(
Zir saved_code;
bool is_cross_module = false;
InternPoolIndex struct_ret_type = IP_INDEX_VOID_TYPE;
+ char import_source_dir[1024];
memset(&import_ast, 0, sizeof(import_ast));
memset(&import_zir, 0, sizeof(import_zir));
memset(&saved_code, 0, sizeof(saved_code));
+ import_source_dir[0] = 0;
// For non-field calls to imported function aliases:
// e.g. `const isNan = std.math.isNan;` then `isNan(x)`.
@@ -2726,6 +2898,12 @@ static AirInstRef zirCall(
break;
}
}
+ // Fallback: search current ZIR by name (cross-module inline).
+ if (decl_inst_val == UINT32_MAX) {
+ const char* cn
+ = (const char*)&sema->code.string_bytes[callee_name_idx];
+ decl_inst_val = findDeclInstByNameInZir(&sema->code, cn);
+ }
if (decl_inst_val != UINT32_MAX) {
const uint32_t* vb;
uint32_t vb_len;
@@ -2786,6 +2964,8 @@ static AirInstRef zirCall(
import_zir = cur_zir;
import_ast = cur_ast;
is_cross_module = true;
+ snprintf(import_source_dir,
+ sizeof(import_source_dir), "%s", cur_dir);
} else {
zirDeinit(&cur_zir);
astDeinit(&cur_ast);
@@ -2834,6 +3014,9 @@ static AirInstRef zirCall(
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);
@@ -2847,6 +3030,8 @@ static AirInstRef zirCall(
import_zir = cur_zir;
import_ast = cur_ast;
is_cross_module = true;
+ snprintf(import_source_dir,
+ sizeof(import_source_dir), "%s", cur_dir);
} else {
zirDeinit(&cur_zir);
astDeinit(&cur_ast);
@@ -2870,6 +3055,9 @@ static AirInstRef zirCall(
import_zir = base_zir;
import_ast = base_ast;
is_cross_module = true;
+ computeSourceDir(sema->module_root, sema->source_dir,
+ base_import, import_source_dir,
+ sizeof(import_source_dir));
} else {
zirDeinit(&base_zir);
astDeinit(&base_ast);
@@ -2897,8 +3085,12 @@ static AirInstRef zirCall(
import_zir = loadImportZir(
sema->source_dir, import_path, &import_ast);
if (import_zir.inst_len > 0) {
- func_inst = findFuncInModuleZir(sema->source_dir,
- &import_zir, &import_ast, field_name);
+ computeSourceDir(sema->module_root, sema->source_dir,
+ import_path, import_source_dir,
+ sizeof(import_source_dir));
+ func_inst = findFuncInModuleZir(import_source_dir,
+ &import_zir, &import_ast, field_name,
+ import_source_dir, sizeof(import_source_dir));
if (func_inst != UINT32_MAX) {
// Swap to imported module's ZIR.
saved_code = sema->code;
@@ -2948,8 +3140,13 @@ static AirInstRef zirCall(
const char* field_name
= (const char*)&sema->code
.string_bytes[callee_name_idx];
- func_inst = findFuncInModuleZir(base_dir,
- &import_zir, &import_ast, field_name);
+ computeSourceDir(NULL, base_dir, sub_import,
+ import_source_dir,
+ sizeof(import_source_dir));
+ func_inst = findFuncInModuleZir(
+ import_source_dir, &import_zir,
+ &import_ast, field_name, import_source_dir,
+ sizeof(import_source_dir));
if (func_inst != UINT32_MAX) {
saved_code = sema->code;
sema->code = import_zir;
@@ -2969,6 +3166,10 @@ static AirInstRef zirCall(
import_zir = base_zir;
import_ast = base_ast;
is_cross_module = true;
+ computeSourceDir(sema->module_root,
+ sema->source_dir, chain_import,
+ import_source_dir,
+ sizeof(import_source_dir));
} else {
zirDeinit(&base_zir);
astDeinit(&base_ast);
@@ -3032,9 +3233,14 @@ static AirInstRef zirCall(
zirDeinit(&alias_zir);
astDeinit(&alias_ast);
if (import_zir.inst_len > 0) {
+ computeSourceDir(NULL, alias_dir,
+ sub_path, import_source_dir,
+ sizeof(import_source_dir));
func_inst = findFuncInModuleZir(
- alias_dir, &import_zir,
- &import_ast, fn_field);
+ import_source_dir, &import_zir,
+ &import_ast, fn_field,
+ import_source_dir,
+ sizeof(import_source_dir));
if (func_inst != UINT32_MAX) {
saved_code = sema->code;
sema->code = import_zir;
@@ -3073,9 +3279,14 @@ static AirInstRef zirCall(
.string_bytes
[callee_name_idx]
: fn_field;
+ computeSourceDir(NULL, base_dir,
+ sub_import, import_source_dir,
+ sizeof(import_source_dir));
func_inst = findFuncInModuleZir(
- base_dir, &import_zir, &import_ast,
- fn_name);
+ import_source_dir, &import_zir,
+ &import_ast, fn_name,
+ import_source_dir,
+ sizeof(import_source_dir));
if (func_inst != UINT32_MAX) {
saved_code = sema->code;
sema->code = import_zir;
@@ -3151,6 +3362,10 @@ static AirInstRef zirCall(
saved_code = sema->code;
sema->code = import_zir;
is_cross_module = true;
+ computeSourceDir(NULL, bd,
+ sp, import_source_dir,
+ sizeof(
+ import_source_dir));
}
}
} else {
@@ -3179,8 +3394,11 @@ static AirInstRef zirCall(
import_zir
= loadImportZir(sema->source_dir, import_path, &import_ast);
if (import_zir.inst_len > 0) {
- func_inst = findFuncInModuleZir(
- sema->source_dir, &import_zir, &import_ast, field_name);
+ computeSourceDir(sema->module_root, sema->source_dir,
+ import_path, import_source_dir, sizeof(import_source_dir));
+ func_inst = findFuncInModuleZir(import_source_dir, &import_zir,
+ &import_ast, field_name, import_source_dir,
+ sizeof(import_source_dir));
if (func_inst != UINT32_MAX) {
saved_code = sema->code;
sema->code = import_zir;
@@ -3398,7 +3616,8 @@ static AirInstRef zirCall(
&& (strcmp(type_fn_name, "Int") == 0
|| strcmp(type_fn_name, "Log2Int") == 0
|| strcmp(type_fn_name, "PowerOfTwoSignificandZ") == 0
- || strcmp(type_fn_name, "F16T") == 0)) {
+ || strcmp(type_fn_name, "F16T") == 0
+ || strcmp(type_fn_name, "Complex") == 0)) {
returns_type = true;
} else {
if (is_cross_module) {
@@ -3438,6 +3657,13 @@ static AirInstRef zirCall(
if (ptag == ZIR_INST_PARAM_COMPTIME
|| ptag == ZIR_INST_PARAM_ANYTYPE_COMPTIME) {
is_ct_param[pi] = true;
+ }
+ // Ported from upstream: anytype and comptime params both
+ // make the function generic. Used for dummy alloc emission
+ // in the non-inline CALL path (Sema.zig:7394-7399).
+ if (ptag == ZIR_INST_PARAM_COMPTIME
+ || ptag == ZIR_INST_PARAM_ANYTYPE_COMPTIME
+ || ptag == ZIR_INST_PARAM_ANYTYPE) {
is_generic = true;
}
// Check if param type is generic (refers to previous params).
@@ -3639,6 +3865,42 @@ static AirInstRef zirCall(
// returns_type functions return `type` which is comptime-only.
// Upstream evaluates these in comptime context, so
// need_debug_scope is always false → BLOCK tag.
+ // Upstream (Sema.zig:7247) sets block to comptime for
+ // comptime-only ret types, enabling memoization (line 7724).
+ // Memoized calls return BEFORE block pre-allocation, so
+ // repeated calls with same args don't create dead blocks.
+ // Check memo first to match upstream behavior.
+ if (!block->is_comptime) {
+ bool all_ct = (args_len > 0);
+ for (uint32_t a = 0; a < args_len && all_ct; a++) {
+ if (!AIR_REF_IS_IP(arg_refs[a]))
+ all_ct = false;
+ }
+ if (all_ct) {
+ for (uint32_t mi = 0; mi < sema->num_memo; mi++) {
+ if (sema->memo_func_inst[mi] != func_inst)
+ continue;
+ if (sema->memo_args_len[mi] != args_len)
+ continue;
+ bool match = true;
+ for (uint32_t a = 0; a < args_len && a < 4; a++) {
+ if (sema->memo_args[mi][a] != arg_refs[a]) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ AirInstRef mr = sema->memo_result[mi];
+ if (is_cross_module) {
+ sema->code = saved_code;
+ zirDeinit(&import_zir);
+ astDeinit(&import_ast);
+ }
+ return mr;
+ }
+ }
+ }
+ }
// Check if this function's dead block was pre-emitted during
// generic param type resolution.
{
@@ -3661,7 +3923,7 @@ static AirInstRef zirCall(
(void)semaAddInstAsIndex(sema, AIR_INST_BLOCK, rt_dead);
}
// Track that this function has had its dead block created.
- if (!block->is_comptime && type_fn_name
+ if (!skip_block && !block->is_comptime && type_fn_name
&& sema->num_type_fn_created < 16)
sema->type_fn_created[sema->num_type_fn_created++]
= type_fn_name;
@@ -3840,6 +4102,53 @@ static AirInstRef zirCall(
// F16T(T): returns u16 on wasm32-wasi (test target).
// Ported from lib/compiler_rt/common.zig F16T.
result_type = IP_INDEX_U16_TYPE;
+ } else if (type_fn_name && strcmp(type_fn_name, "Complex") == 0) {
+ // Complex(T): returns extern struct { real: T, imag: T }.
+ // Ported from lib/compiler_rt/mulc3.zig Complex.
+ if (args_len >= 1 && AIR_REF_IS_IP(arg_refs[0])) {
+ InternPoolIndex elem_ty = AIR_REF_TO_IP(arg_refs[0]);
+ // Use unique struct ID based on element type.
+ uint32_t struct_id = 0xC0A100 + (uint32_t)elem_ty;
+ // Check if already registered.
+ bool found = false;
+ for (uint32_t si = 0; si < sema->num_struct_info; si++) {
+ if (sema->struct_info[si].num_fields == 2
+ && strcmp(sema->struct_info[si].fields[0].name, "real")
+ == 0
+ && sema->struct_info[si].fields[0].type == elem_ty) {
+ result_type = sema->struct_info[si].struct_type;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ InternPoolKey skey;
+ memset(&skey, 0, sizeof(skey));
+ skey.tag = IP_KEY_STRUCT_TYPE;
+ skey.data.struct_type = struct_id;
+ InternPoolIndex struct_ip = ipIntern(sema->ip, skey);
+
+ InternPoolKey pkey;
+ memset(&pkey, 0, sizeof(pkey));
+ pkey.tag = IP_KEY_PTR_TYPE;
+ pkey.data.ptr_type.child = struct_ip;
+ pkey.data.ptr_type.flags = 0;
+ InternPoolIndex ptr_ip = ipIntern(sema->ip, pkey);
+
+ if (sema->num_struct_info < 32) {
+ StructFieldInfo* info
+ = &sema->struct_info[sema->num_struct_info++];
+ info->struct_type = struct_ip;
+ info->ptr_type = ptr_ip;
+ info->num_fields = 2;
+ info->fields[0].name = "real";
+ info->fields[0].type = elem_ty;
+ info->fields[1].name = "imag";
+ info->fields[1].type = elem_ty;
+ }
+ result_type = struct_ip;
+ }
+ }
}
if (is_cross_module) {
@@ -3847,6 +4156,24 @@ static AirInstRef zirCall(
zirDeinit(&import_zir);
astDeinit(&import_ast);
}
+ // Memoize type function result for future calls with same args.
+ // Matches upstream behavior where memoized calls skip block
+ // pre-allocation (Sema.zig:7724-7744, 7891-7896).
+ if (result_type != IP_INDEX_NONE && !block->is_comptime) {
+ bool all_ct = (args_len > 0);
+ for (uint32_t a = 0; a < args_len && all_ct; a++) {
+ if (!AIR_REF_IS_IP(arg_refs[a]))
+ all_ct = false;
+ }
+ if (all_ct && sema->num_memo < 32) {
+ uint32_t mi = sema->num_memo++;
+ sema->memo_func_inst[mi] = func_inst;
+ sema->memo_args_len[mi] = args_len;
+ for (uint32_t a = 0; a < args_len && a < 4; a++)
+ sema->memo_args[mi][a] = arg_refs[a];
+ sema->memo_result[mi] = AIR_REF_FROM_IP(result_type);
+ }
+ }
if (result_type != IP_INDEX_NONE)
return AIR_REF_FROM_IP(result_type);
return AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE);
@@ -3871,10 +4198,44 @@ static AirInstRef zirCall(
&& struct_ret_type != IP_INDEX_VOID_TYPE) {
ret_ty = struct_ret_type;
}
+ // Resolve multi-instruction return type body (e.g. Complex(f64)).
+ // We cannot call analyzeBodyInner here because the inst_map
+ // doesn't cover the ret_ty body instructions. Instead, check if
+ // the return type was already resolved by zirFunc's pre_resolved
+ // pass (which populated struct_info for Complex).
+ // Look for a matching struct_info entry based on element type.
+ if (ret_ty == IP_INDEX_VOID_TYPE && func_info.ret_ty_body_len > 2
+ && args_len > 0) {
+ // Check if any registered struct matches as a return type.
+ // For Complex(T): struct with {real: T, imag: T} where T is
+ // the first arg's element type.
+ for (uint32_t si = 0; si < sema->num_struct_info; si++) {
+ if (sema->struct_info[si].num_fields == 2
+ && strcmp(sema->struct_info[si].fields[0].name, "real") == 0) {
+ // Found Complex struct — use its type as return type.
+ ret_ty = sema->struct_info[si].struct_type;
+ break;
+ }
+ }
+ }
if (ret_ty == IP_INDEX_VOID_TYPE && is_cross_module && args_len > 0
&& func_info.ret_ty_body_len > 1) {
- // Assume return type = first argument's type (covers @TypeOf(a)).
- ret_ty = semaTypeOf(sema, arg_refs[0]);
+ // Assume return type = argument's type (covers @TypeOf(a)).
+ // Try each arg until we find one with a concrete (non-comptime-only)
+ // type. This handles GenericPoison where the first arg's type
+ // might not be resolved (e.g. copysign's first arg in generic
+ // context).
+ for (uint32_t a = 0; a < args_len; a++) {
+ TypeIndex candidate = semaTypeOf(sema, arg_refs[a]);
+ if (candidate != IP_INDEX_TYPE_TYPE
+ && candidate != IP_INDEX_COMPTIME_INT_TYPE
+ && candidate != IP_INDEX_COMPTIME_FLOAT_TYPE
+ && candidate != IP_INDEX_ENUM_LITERAL_TYPE
+ && candidate != TYPE_NONE) {
+ ret_ty = candidate;
+ break;
+ }
+ }
}
// Handle case where return type is a param ref (e.g. `fn absv(comptime
// ST: type, a: ST) ST`). The ret_ty_ref points to a param instruction;
@@ -3909,13 +4270,20 @@ static AirInstRef zirCall(
// Upstream (Sema.zig:7247): if the return type is comptime-only
// (e.g. comptime_int, type), the block is set to comptime, making
// is_inline_call = true. Upstream (Sema.zig:7482):
- // is_inline_call = block.isComptime() or inline_requested;
+ // is_inline_call = block.isComptime() or func_type.isGeneric()
+ // or inline_requested;
bool is_comptime_only_ret
= (ret_ty == IP_INDEX_TYPE_TYPE || ret_ty == IP_INDEX_COMPTIME_INT_TYPE
|| ret_ty == IP_INDEX_COMPTIME_FLOAT_TYPE
|| 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).
@@ -4171,9 +4539,12 @@ static AirInstRef zirCall(
// For cross-module calls, populate decl table from imported ZIR
// so that decl_val/decl_ref can resolve the imported module's
// declarations (e.g. `const std = @import("std")`).
+ // Also set source_dir to the imported module's directory so that
+ // nested cross-module calls resolve relative imports correctly.
uint32_t saved_decl_names[64];
uint32_t saved_decl_insts[64];
uint32_t saved_num_decls = 0;
+ const char* saved_source_dir = NULL;
if (is_cross_module) {
saved_num_decls = sema->num_decls;
memcpy(saved_decl_names, sema->decl_names,
@@ -4181,6 +4552,10 @@ static AirInstRef zirCall(
memcpy(saved_decl_insts, sema->decl_insts,
saved_num_decls * sizeof(uint32_t));
populateDeclTableFromZir(sema, &import_zir);
+ if (import_source_dir[0]) {
+ saved_source_dir = sema->source_dir;
+ sema->source_dir = import_source_dir;
+ }
}
// Analyze the inline function body.
@@ -4188,13 +4563,15 @@ static AirInstRef zirCall(
(void)analyzeBodyInner(sema, &child_block, func_body, func_info.body_len);
- // Restore decl table for cross-module calls.
+ // Restore decl table and source_dir for cross-module calls.
if (is_cross_module) {
memcpy(sema->decl_names, saved_decl_names,
saved_num_decls * sizeof(uint32_t));
memcpy(sema->decl_insts, saved_decl_insts,
saved_num_decls * sizeof(uint32_t));
sema->num_decls = saved_num_decls;
+ if (saved_source_dir)
+ sema->source_dir = saved_source_dir;
}
sema->fn_ret_ty = saved_fn_ret_ty;
@@ -4580,7 +4957,6 @@ static void zirFunc(Sema* sema, SemaBlock* block, uint32_t inst) {
// Multi-instruction return type body resolved before state save.
sema->fn_ret_ty = pre_resolved_ret_ty;
}
-
// --- Set up block for function body ---
SemaBlock fn_block;
semaBlockInit(&fn_block, sema, NULL);
@@ -5846,6 +6222,67 @@ static bool analyzeBodyInner(
return false;
}
+ // ret_load: return by loading from the return pointer.
+ // Ported from src/Sema.zig zirRetLoad / analyzeRet.
+ case ZIR_INST_RET_LOAD: {
+ ZirInstRef operand_ref
+ = sema->code.inst_datas[inst].un_node.operand;
+ AirInstRef ret_ptr = resolveInst(sema, operand_ref);
+
+ if (block->is_comptime || block->inlining) {
+ // Load from the return pointer.
+ TypeIndex ptr_ty = semaTypeOf(sema, ret_ptr);
+ TypeIndex elem_ty = ptrChildType(sema->ip, ptr_ty);
+ AirInstData load_data;
+ memset(&load_data, 0, sizeof(load_data));
+ load_data.ty_op.ty_ref = AIR_REF_FROM_IP(elem_ty);
+ load_data.ty_op.operand = ret_ptr;
+ AirInstRef operand
+ = semaAddInst(block, AIR_INST_LOAD, load_data);
+
+ if (block->inlining) {
+ SemaBlockInlining* inl = block->inlining;
+ if (block->is_comptime) {
+ inl->comptime_result = operand;
+ inl->comptime_returned = true;
+ return false;
+ }
+ // Runtime inlining: rewrite ret as br.
+ AirInstData br_data;
+ memset(&br_data, 0, sizeof(br_data));
+ br_data.br.block_inst = inl->merges.block_inst;
+ br_data.br.operand = operand;
+ AirInstRef br_ref
+ = semaAddInst(block, AIR_INST_BR, br_data);
+ if (inl->merges.results_len >= inl->merges.results_cap) {
+ uint32_t new_cap = (inl->merges.results_cap == 0)
+ ? 4
+ : inl->merges.results_cap * 2;
+ inl->merges.results = realloc(
+ inl->merges.results, new_cap * sizeof(AirInstRef));
+ inl->merges.br_list = realloc(
+ inl->merges.br_list, new_cap * sizeof(uint32_t));
+ if (!inl->merges.results || !inl->merges.br_list)
+ exit(1);
+ inl->merges.results_cap = new_cap;
+ inl->merges.br_list_cap = new_cap;
+ }
+ inl->merges.results[inl->merges.results_len++] = operand;
+ inl->merges.br_list[inl->merges.br_list_len++]
+ = AIR_REF_TO_INST(br_ref);
+ return false;
+ }
+ return false;
+ }
+
+ // Non-inlining runtime: emit AIR ret_load.
+ AirInstData ret_data;
+ memset(&ret_data, 0, sizeof(ret_data));
+ ret_data.un_op.operand = ret_ptr;
+ (void)semaAddInst(block, AIR_INST_RET_LOAD, ret_data);
+ return false;
+ }
+
// func/func_inferred/func_fancy: function declaration.
// Ported from src/Sema.zig zirFunc / zirFuncFancy.
case ZIR_INST_FUNC:
@@ -5916,6 +6353,21 @@ static bool analyzeBodyInner(
i++;
continue;
+ // float: comptime float literal (fits in f64).
+ // Ported from src/Sema.zig zirFloat.
+ case ZIR_INST_FLOAT: {
+ double val = sema->code.inst_datas[inst].float_val;
+ InternPoolKey key;
+ memset(&key, 0, sizeof(key));
+ key.tag = IP_KEY_FLOAT;
+ key.data.float_val.ty = IP_INDEX_COMPTIME_FLOAT_TYPE;
+ key.data.float_val.val = val;
+ instMapPut(&sema->inst_map, inst,
+ AIR_REF_FROM_IP(ipIntern(sema->ip, key)));
+ i++;
+ continue;
+ }
+
// extended: handle extended opcodes.
case ZIR_INST_EXTENDED: {
uint16_t opcode = sema->code.inst_datas[inst].extended.opcode;
@@ -6596,8 +7048,22 @@ static bool analyzeBodyInner(
i++;
continue;
- // field_ptr: runtime struct field pointer access.
+ // opt_eu_base_ptr_init: pass-through for struct init.
+ // Ported from Sema.zig zirOptEuBasePtrInit: resolves to the
+ // alloc pointer unchanged (for non-error-union/optional types).
+ case ZIR_INST_OPT_EU_BASE_PTR_INIT: {
+ ZirInstRef operand = sema->code.inst_datas[inst].un_node.operand;
+ AirInstRef resolved_op = resolveInst(sema, operand);
+ instMapPut(&sema->inst_map, inst, resolved_op);
+ i++;
+ continue;
+ }
+
+ // field_ptr / struct_init_field_ptr: runtime struct field pointer
+ // access. struct_init_field_ptr uses the same pl_node + Field format
+ // as field_ptr. Upstream calls sema.fieldPtr for both.
case ZIR_INST_FIELD_PTR:
+ case ZIR_INST_STRUCT_INIT_FIELD_PTR:
instMapPut(&sema->inst_map, inst, zirFieldPtr(sema, block, inst));
i++;
continue;
@@ -6607,7 +7073,6 @@ static bool analyzeBodyInner(
case ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY:
case ZIR_INST_VALIDATE_PTR_STRUCT_INIT:
case ZIR_INST_STRUCT_INIT_FIELD_TYPE:
- case ZIR_INST_STRUCT_INIT_FIELD_PTR:
instMapPut(
&sema->inst_map, inst, AIR_REF_FROM_IP(IP_INDEX_VOID_TYPE));
i++;
@@ -7084,13 +7549,10 @@ static bool analyzeBodyInner(
// Ported from src/Sema.zig resolveAnalyzedBlock.
uint32_t last_inst_idx = child_block.instructions_len - 1;
uint32_t last_inst = child_block.instructions[last_inst_idx];
- bool elide = false;
- if (!need_debug_scope
+ bool elide = !need_debug_scope
&& sema->air_inst_tags[last_inst] == AIR_INST_BR
&& sema->air_inst_datas[last_inst].br.block_inst
- == block_inst_idx) {
- elide = true;
- }
+ == block_inst_idx;
if (elide) {
// Elide the block: copy instructions (excluding
// trailing br) to parent.
@@ -7214,6 +7676,8 @@ 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;
@@ -7472,7 +7936,6 @@ static bool analyzeBodyInner(
if (cond == AIR_REF_FROM_IP(IP_INDEX_BOOL_FALSE)) {
return analyzeBodyInner(sema, block, else_body, else_body_len);
}
-
// Analyze then-body in a sub-block, collecting branch hint.
// Upstream (Sema.zig line 18364): need_debug_scope = null
// because this body is emitted regardless.
@@ -7645,9 +8108,26 @@ static bool analyzeBodyInner(
continue;
}
+ // coerce_ptr_elem_ty: coerce a value so that a reference to it
+ // would be coercible to a given pointer type.
+ // Ported from src/Sema.zig zirCoercePtrElemTy.
+ // Uses pl_node with Bin payload: lhs=pointer type, rhs=value.
+ case ZIR_INST_COERCE_PTR_ELEM_TY: {
+ uint32_t payload_index
+ = sema->code.inst_datas[inst].pl_node.payload_index;
+ ZirInstRef val_ref = sema->code.extra[payload_index + 1];
+ AirInstRef val = resolveInst(sema, val_ref);
+ // For single pointers with matching types (the common case
+ // in our bootstrap), the coercion is a no-op.
+ instMapPut(&sema->inst_map, inst, val);
+ i++;
+ continue;
+ }
+
// 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
@@ -454,6 +454,8 @@ fn airDataRefSlots(tag_val: u8) [2]bool {
// ty_pl: type(Ref) + payload(u32)
c.AIR_INST_STRUCT_FIELD_VAL,
c.AIR_INST_STRUCT_FIELD_PTR,
+ c.AIR_INST_DBG_INLINE_BLOCK,
+ c.AIR_INST_BLOCK,
=> .{ true, false },
// bin_op: lhs(Ref) + rhs(Ref)
c.AIR_INST_ADD,
diff --git a/stage0/stages_test.zig b/stage0/stages_test.zig
@@ -160,10 +160,10 @@ const corpus_files = .{
"../lib/compiler_rt/subhf3.zig", // 406
"../lib/compiler_rt/negtf2.zig", // 409
"../lib/std/os/linux/bpf/btf_ext.zig", // 419
- //"../lib/compiler_rt/muldc3.zig", // 425
- //"../lib/compiler_rt/mulhc3.zig", // 425
- //"../lib/compiler_rt/mulsc3.zig", // 425
- //"../lib/compiler_rt/mulxc3.zig", // 425
+ "../lib/compiler_rt/muldc3.zig", // 425
+ "../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/divhc3.zig", // 434
//"../lib/compiler_rt/divsc3.zig", // 434
diff --git a/stage0/verbose_air.c b/stage0/verbose_air.c
@@ -356,7 +356,7 @@ static void writeValue(
fprintf(out, "(function)");
break;
case IP_KEY_FLOAT:
- fprintf(out, "%g", key.data.float_val);
+ fprintf(out, "%g", key.data.float_val.val);
break;
default:
fprintf(out, "(val@%u)", val_ip);