zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit d9b0a07771f2c49f5bb321f013281c0663385336 (tree)
parent a8bdfd6507b0e3510d5f9a8f6760616e3216eae1
Author: Motiejus <motiejus@jakstys.lt>
Date:   Sun,  8 Mar 2026 14:00:14 +0000

sema: port null→optional coercion, refactor resolveStructFieldInitsC

Port null-to-optional coercion in semaCoerce: when source is null_type
and target is optional, create opt entry matching Zig's coerce path.

Refactor resolveStructFieldInitsC to use analyzeBodyInner for init body
evaluation instead of direct ZIR parsing. This produces the same IP
entries as the Zig compiler for struct field default values.

Add import_export_linkage test files to corpus (not yet enabled).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Diffstat:
Mstage0/corpus.zig | 1+
Mstage0/sema.c | 254+++++++++++++++++++++++++++++--------------------------------------------------
Astage0/sema_tests/import_export_linkage.zig | 9+++++++++
Astage0/sema_tests/import_export_linkage_helper.zig | 4++++
4 files changed, 106 insertions(+), 162 deletions(-)

diff --git a/stage0/corpus.zig b/stage0/corpus.zig @@ -110,6 +110,7 @@ pub const files = [_][]const u8{ "lib/std/zig/llvm.zig", "stage0/sema_tests/export_builtin.zig", "stage0/sema_tests/callconv_c.zig", + "stage0/sema_tests/import_export_linkage.zig", "lib/compiler_rt/neghf2.zig", "lib/compiler_rt/negxf2.zig", "lib/compiler_rt/absvdi2.zig", diff --git a/stage0/sema.c b/stage0/sema.c @@ -863,6 +863,16 @@ static AirInstRef semaCoerce( } } } + // null → optional coercion: create opt entry with the optional type. + // Ported from src/Sema.zig coerce null → optional path. + if (src_ty == IP_INDEX_NULL_TYPE && target_ty >= 124 + && sema->ip->items[target_ty].tag == IP_KEY_OPT_TYPE) { + InternPoolKey key; + memset(&key, 0, sizeof(key)); + key.tag = IP_KEY_OPT; + key.data.opt = target_ty; + 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) { @@ -4693,19 +4703,53 @@ static void resolveStructFieldInitsC(Sema* sema, const Zir* zir, } } - // Compute type body start positions from cumulative offsets. - uint32_t type_body_starts[32]; + // Resolve field types from ZIR (needed for coercion after body eval). + // Ported from Sema.zig structFieldInits which reads field_types from + // the already-resolved struct type. Here we re-resolve from ZIR since + // C doesn't store field types in the IP struct_type extra data. + InternPoolIndex field_types[32]; { - uint32_t pos = sei; + uint32_t type_pos = sei; for (uint32_t fi = 0; fi < s_fields_len; fi++) { - type_body_starts[fi] = pos; - pos += fi_type_body_len[fi]; - pos += fi_align_body_len[fi]; - pos += fi_init_body_len[fi]; + field_types[fi] = IP_INDEX_NONE; + if (fi_type_body_len[fi] == 0) { + field_types[fi] = resolveZirTypeRef( + sema, zir, fi_type_ref[fi], struct_ns, file_idx); + } else { + uint32_t tbl = fi_type_body_len[fi]; + uint32_t tb_last = zir->extra[type_pos + tbl - 1]; + if (tb_last < zir->inst_len + && zir->inst_tags[tb_last] == ZIR_INST_BREAK_INLINE) { + ZirInstRef type_op + = zir->inst_datas[tb_last].break_data.operand; + field_types[fi] = resolveZirTypeRef( + sema, zir, type_op, struct_ns, file_idx); + } + } + type_pos += fi_type_body_len[fi]; + type_pos += fi_align_body_len[fi]; + type_pos += fi_init_body_len[fi]; } } - // Second pass: iterate through bodies and evaluate init bodies. + // Second pass: evaluate init bodies via analyzeBodyInner. + // Ported from Sema.zig structFieldInits which calls resolveInlineBody + // + coerce + resolveConstValue for each field init body. This creates + // intermediate IP entries (enum_literal, comptime_int, etc.) that match + // the Zig compiler's output. + Zir saved_code = sema->code; + InstMap saved_map = sema->inst_map; + uint32_t saved_cbi = sema->comptime_break_inst; + TypeIndex saved_fn_ret_ty = sema->fn_ret_ty; + sema->code = *zir; + sema->fn_ret_ty = IP_INDEX_VOID_TYPE; + + // Pre-allocate inst_map to cover the full ZIR instruction range. + sema->inst_map.start = 0; + sema->inst_map.items_len = zir->inst_len; + sema->inst_map.items = malloc(zir->inst_len * sizeof(AirInstRef)); + memset(sema->inst_map.items, 0xFF, zir->inst_len * sizeof(AirInstRef)); + for (uint32_t fi = 0; fi < s_fields_len; fi++) { sei += fi_type_body_len[fi]; sei += fi_align_body_len[fi]; @@ -4716,169 +4760,55 @@ static void resolveStructFieldInitsC(Sema* sema, const Zir* zir, if (init_body_len == 0) continue; - // Find the break_inline at the end of the init body. - uint32_t last_zi = zir->extra[init_body_start + init_body_len - 1]; - if (last_zi >= zir->inst_len) - continue; - if (zir->inst_tags[last_zi] != ZIR_INST_BREAK_INLINE) - continue; + // Reset inst_map entries for fresh body evaluation. + memset(sema->inst_map.items, 0xFF, zir->inst_len * sizeof(AirInstRef)); - ZirInstRef operand = zir->inst_datas[last_zi].break_data.operand; + // Map struct_inst → field type ref so init body can reference it. + // Ported from Sema.zig structFieldInits line 35449: + // sema.inst_map.putAssumeCapacity(zir_index, type_ref); + if (field_types[fi] != IP_INDEX_NONE) + instMapPut(&sema->inst_map, struct_inst, + AIR_REF_FROM_IP(field_types[fi])); - // Null default → create opt_null(field_type). - if (operand == ZIR_REF_NULL_VALUE) { - // Resolve the field type to get the optional type IP index. - InternPoolIndex opt_ty = IP_INDEX_NONE; - if (fi_type_body_len[fi] > 0) { - uint32_t tbs = type_body_starts[fi]; - uint32_t tbl = fi_type_body_len[fi]; - uint32_t tb_last = zir->extra[tbs + tbl - 1]; - if (tb_last < zir->inst_len - && zir->inst_tags[tb_last] == ZIR_INST_BREAK_INLINE) { - ZirInstRef type_op - = zir->inst_datas[tb_last].break_data.operand; - opt_ty = resolveZirTypeRef( - sema, zir, type_op, struct_ns, file_idx); - } - } - if (opt_ty != IP_INDEX_NONE) { - InternPoolKey ok; - memset(&ok, 0, sizeof(ok)); - ok.tag = IP_KEY_OPT; - ok.data.opt = opt_ty; - (void)ipIntern(sema->ip, ok); - } - continue; - } - - // Pre-interned ref (void, true, false, etc.) — already in IP. - if (operand < ZIR_REF_START_INDEX) - continue; - uint32_t operand_inst = operand - ZIR_REF_START_INDEX; - if (operand_inst >= zir->inst_len) - continue; + // Create comptime block for body evaluation. + SemaBlock ct_block; + semaBlockInit(&ct_block, sema, NULL); + ct_block.is_comptime = true; - // Case 1: enum literal default (e.g., rw: Rw = .read). - const char* lit_name = NULL; + // Evaluate init body through analyzeBodyInner. + // Ported from Sema.zig structFieldInits → resolveInlineBody. + sema->comptime_break_inst = UINT32_MAX; + const uint32_t* body = &zir->extra[init_body_start]; + (void)analyzeBodyInner(sema, &ct_block, body, init_body_len); - if (zir->inst_tags[operand_inst] == ZIR_INST_ENUM_LITERAL) { - uint32_t str_start = zir->inst_datas[operand_inst].str_tok.start; - lit_name = (const char*)&zir->string_bytes[str_start]; - } else if (zir->inst_tags[operand_inst] == ZIR_INST_DECL_LITERAL) { - uint32_t pi = zir->inst_datas[operand_inst].pl_node.payload_index; - uint32_t name_start = zir->extra[pi + 1]; - lit_name = (const char*)&zir->string_bytes[name_start]; + // Get break result. + AirInstRef result = AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE); + if (sema->comptime_break_inst != UINT32_MAX) { + ZirInstRef operand + = sema->code.inst_datas[sema->comptime_break_inst] + .break_data.operand; + result = resolveInst(sema, operand); } - if (lit_name != NULL) { - // Find matching enum type in the struct's namespace, - // then fall back to the file root namespace. - // Ported from Sema.zig structFieldInits → resolveInlineBody - // → coerce path which resolves enum types on demand. - uint32_t ns_search[2]; - ns_search[0] = struct_ns; - ns_search[1] = sema->zcu->file_namespaces[file_idx]; - bool found = false; - for (uint32_t nk = 0; nk < 2 && !found; nk++) { - const ZcuNamespace* sns - = &sema->zcu->namespaces[ns_search[nk]]; - for (uint32_t j = 0; j < sns->pub_nav_count; j++) { - uint32_t nav_j = sns->pub_navs[j]; - const Nav* enav = ipGetNav(sema->ip, nav_j); - if (enav->resolved_type == IP_INDEX_NONE) - continue; - if (sema->ip->items[enav->resolved_type].tag - != IP_KEY_ENUM_TYPE) - continue; - - InternPoolIndex enum_ip = enav->resolved_type; - const uint32_t* ebody = NULL; - uint32_t ebody_len = 0; - getValueBodyFromZir( - zir, enav->zir_index, &ebody, &ebody_len); - uint32_t ed_inst = UINT32_MAX; - for (uint32_t ei = 0; ei < ebody_len; ei++) { - uint32_t einst = ebody[ei]; - if (einst < zir->inst_len - && zir->inst_tags[einst] == ZIR_INST_EXTENDED - && zir->inst_datas[einst].extended.opcode - == ZIR_EXT_ENUM_DECL) { - ed_inst = einst; - break; - } - } - if (ed_inst == UINT32_MAX) - continue; - uint32_t field_idx - = findEnumFieldByName(zir, ed_inst, lit_name); - if (field_idx == UINT32_MAX) - continue; - - // Find the enum's tag type from ZIR and compute - // the int value for this field. - InternPoolIndex int_val - = getEnumFieldIntVal(sema, zir, ed_inst, field_idx); - if (int_val == IP_INDEX_NONE) - break; - - InternPoolKey etk; - memset(&etk, 0, sizeof(etk)); - etk.tag = IP_KEY_ENUM_TAG; - etk.data.enum_tag.ty = enum_ip; - etk.data.enum_tag.int_val = int_val; - (void)ipIntern(sema->ip, etk); - found = true; - break; - } - } - continue; + // Coerce to field type (creates enum_tag, typed int, opt_null, etc.). + // Ported from Sema.zig structFieldInits line 35458: + // const coerced = try sema.coerce(&block_scope, field_ty, init, + // ...); + if (field_types[fi] != IP_INDEX_NONE + && result != AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE)) { + (void)semaCoerce(sema, &ct_block, field_types[fi], result); } - // Case 2: integer literal default (e.g., locality: u2 = 3). - if (zir->inst_tags[operand_inst] == ZIR_INST_INT) { - uint64_t val = zir->inst_datas[operand_inst].int_val; - InternPoolIndex field_ty = IP_INDEX_NONE; - - if (fi_type_body_len[fi] == 0 - && fi_type_ref[fi] < ZIR_REF_START_INDEX) { - // Simple pre-interned type ref. - field_ty = fi_type_ref[fi]; - } else if (fi_type_body_len[fi] > 0) { - // Resolve the field type from the type body. - // Find the break_inline at end of type body. - uint32_t tbs = type_body_starts[fi]; - uint32_t tbl = fi_type_body_len[fi]; - uint32_t tb_last = zir->extra[tbs + tbl - 1]; - if (tb_last < zir->inst_len - && zir->inst_tags[tb_last] == ZIR_INST_BREAK_INLINE) { - ZirInstRef type_op - = zir->inst_datas[tb_last].break_data.operand; - if (type_op < ZIR_REF_START_INDEX) { - field_ty = type_op; - } else { - uint32_t ti = type_op - ZIR_REF_START_INDEX; - if (ti < zir->inst_len - && zir->inst_tags[ti] == ZIR_INST_INT_TYPE) { - uint16_t bits - = zir->inst_datas[ti].int_type.bit_count; - uint8_t zsign - = zir->inst_datas[ti].int_type.signedness; - uint8_t ip_sign = (zsign == 0) ? 1 : 0; - InternPoolKey itk; - memset(&itk, 0, sizeof(itk)); - itk.tag = IP_KEY_INT_TYPE; - itk.data.int_type.bits = bits; - itk.data.int_type.signedness = ip_sign; - field_ty = ipIntern(sema->ip, itk); - } - } - } - } - - if (field_ty != IP_INDEX_NONE) - (void)internTypedInt(sema, field_ty, val); - } + // Clean up this body's block. + semaBlockDeinit(&ct_block); } + + // Restore sema state. + free(sema->inst_map.items); + sema->inst_map = saved_map; + sema->code = saved_code; + sema->comptime_break_inst = saved_cbi; + sema->fn_ret_ty = saved_fn_ret_ty; } // --- resolveStructDeclFromZir --- diff --git a/stage0/sema_tests/import_export_linkage.zig b/stage0/sema_tests/import_export_linkage.zig @@ -0,0 +1,9 @@ +const helper = @import("import_export_linkage_helper.zig"); + +comptime { + @export(&foo, .{ .name = "foo", .linkage = helper.linkage }); +} + +fn foo(a: u16) callconv(.c) u16 { + return a ^ 0x8000; +} diff --git a/stage0/sema_tests/import_export_linkage_helper.zig b/stage0/sema_tests/import_export_linkage_helper.zig @@ -0,0 +1,4 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +pub const linkage: std.builtin.GlobalLinkage = if (builtin.is_test) .internal else .strong;