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>
This commit is contained in:
2026-02-23 03:46:37 +00:00
parent f60356780c
commit 8fb1f20604
6 changed files with 545 additions and 44 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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++;

View File

@@ -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,

View File

@@ -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

View File

@@ -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);