From 8fb1f2060492e7cd4ba63f646b17dffda4dbdcba Mon Sep 17 00:00:00 2001 From: Motiejus Date: Mon, 23 Feb 2026 03:46:37 +0000 Subject: [PATCH] sema: resolve cross-module inline return types, memoize type functions; enable mul*c3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- stage0/intern_pool.c | 16 ++ stage0/intern_pool.h | 5 +- stage0/sema.c | 556 ++++++++++++++++++++++++++++++++++++++--- stage0/sema_test.zig | 2 + stage0/stages_test.zig | 8 +- stage0/verbose_air.c | 2 +- 6 files changed, 545 insertions(+), 44 deletions(-) diff --git a/stage0/intern_pool.c b/stage0/intern_pool.c index bb6496ebd5..8425d230f7 100644 --- 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 index b298718036..8cb30bc28d 100644 --- 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 index 8fff7702ed..4370ef17dc 100644 --- a/stage0/sema.c +++ b/stage0/sema.c @@ -4,6 +4,16 @@ #include #include #include +// 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 /lib/. 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") → /lib/ + } 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"): + // /lib/ 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 index bf47e878cd..396749f42c 100644 --- 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 index a498a4265e..394b8325ad 100644 --- 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 index c917b2acb5..96f392c17c 100644 --- 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);