commit 0f68ac18ed55888abae3e455ee82e62811a737aa (tree)
parent edf1ec738ef0b6601a4ad55fb58e8a72fe5d27b4
Author: Motiejus <motiejus@jakstys.lt>
Date: Fri, 27 Feb 2026 16:25:15 +0000
sema: extract CPU model features from ZIR decl_literals
Add IP_KEY_AGGREGATE and IP_KEY_UNION_VALUE hash/equality support in
intern_pool.c. These key types were falling through to the default
memcmp, which could produce incorrect results.
In triggerArchModuleCascade, extract the CPU model's features from ZIR
by scanning for ZIR_INST_DECL_LITERAL instructions between the CALL
and STRUCT_INIT instructions in the model's value body. This creates
IP entries [707-720]: feature array type, enum_tags for each feature,
aggregate, and a pointer/slice chain.
The key insight is that AstGen encodes `.bulk_memory_opt` etc. as
decl_literal (tag 142), not enum_literal (tag 141) — they resolve
against the expected type's declarations in a typed context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
2 files changed, 172 insertions(+), 1 deletion(-)
diff --git a/stage0/intern_pool.c b/stage0/intern_pool.c
@@ -156,6 +156,12 @@ static uint32_t ipHashKey(const InternPoolKey* key) {
case IP_KEY_SLICE:
h = ipHashCombine(h, key->data.slice);
break;
+ case IP_KEY_AGGREGATE:
+ h = ipHashCombine(h, key->data.aggregate);
+ break;
+ case IP_KEY_UNION_VALUE:
+ h = ipHashCombine(h, key->data.union_value);
+ break;
default:
/* For other tag types, just use the tag hash. */
break;
@@ -275,6 +281,10 @@ static bool ipKeysEqual(const InternPoolKey* a, const InternPoolKey* b) {
== b->data.func_type.noalias_bits;
case IP_KEY_SLICE:
return a->data.slice == b->data.slice;
+ case IP_KEY_AGGREGATE:
+ return a->data.aggregate == b->data.aggregate;
+ case IP_KEY_UNION_VALUE:
+ return a->data.union_value == b->data.union_value;
default:
/* Fallback: memcmp the entire data union. */
return memcmp(&a->data, &b->data, sizeof(a->data)) == 0;
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -5753,12 +5753,13 @@ static void triggerArchModuleCascade(
// cCallingConvention cascade (demand-driven from loadComptimePtrInner
// dereferencing cpu.model which is *const CpuModel).
// Find it in the cpu struct's namespace using s_target_cpu_model_name.
+ uint32_t model_nav = UINT32_MAX;
if (cpu_nav != UINT32_MAX && s_target_cpu_model_name) {
const Nav* cpu_n = ipGetNav(cpu_nav);
if (cpu_n->resolved_type != IP_INDEX_NONE) {
uint32_t cpu_ns = findNamespaceForType(cpu_n->resolved_type);
if (cpu_ns != UINT32_MAX) {
- uint32_t model_nav
+ model_nav
= findNavInNamespace(cpu_ns, s_target_cpu_model_name);
if (model_nav != UINT32_MAX)
(void)resolveNavRef(model_nav);
@@ -5914,6 +5915,166 @@ static void triggerArchModuleCascade(
}
}
+ // $707-$720: Evaluate the CPU model's feature set.
+ // The Zig compiler evaluates featureSet(&[_]Feature{...}) from the
+ // CPU model's struct literal. This creates the feature array type,
+ // enum_tag entries for each feature, an aggregate for the array value,
+ // and a pointer/slice chain.
+ // Ported from the comptime evaluation of CpuModel.features init.
+ if (model_nav != UINT32_MAX && feature != UINT32_MAX) {
+ InternPoolIndex feature_type = ipGetNav(feature)->resolved_type;
+ if (feature_type != IP_INDEX_NONE) {
+ // Get the Feature enum's ZIR instruction (for field lookup).
+ const Nav* feat_n = ipGetNav(feature);
+ uint32_t feat_file = s_namespaces[feat_n->namespace_idx].file_idx;
+ const Zir* feat_zir = &s_loaded_modules[feat_file].zir;
+ const uint32_t* feat_vb = NULL;
+ uint32_t feat_vb_len = 0;
+ getValueBodyFromZir(
+ feat_zir, feat_n->zir_index, &feat_vb, &feat_vb_len);
+ uint32_t enum_inst = UINT32_MAX;
+ for (uint32_t i = 0; i < feat_vb_len; i++) {
+ if (feat_vb[i] < feat_zir->inst_len
+ && feat_zir->inst_tags[feat_vb[i]] == ZIR_INST_EXTENDED
+ && feat_zir->inst_datas[feat_vb[i]].extended.opcode
+ == ZIR_EXT_ENUM_DECL) {
+ enum_inst = feat_vb[i];
+ break;
+ }
+ }
+
+ if (enum_inst != UINT32_MAX) {
+ // Get the model nav's ZIR value body.
+ const Nav* model_n = ipGetNav(model_nav);
+ uint32_t model_file
+ = s_namespaces[model_n->namespace_idx].file_idx;
+ const Zir* model_zir = &s_loaded_modules[model_file].zir;
+ const uint32_t* mvb = NULL;
+ uint32_t mvb_len = 0;
+ getValueBodyFromZir(
+ model_zir, model_n->zir_index, &mvb, &mvb_len);
+
+ if (mvb && mvb_len > 0) {
+ // Find the call instruction and the next
+ // top-level instruction in the value body.
+ // The enum_literal instructions for the feature
+ // array are between these two (as sub-insts
+ // not in the value body's top-level list).
+ uint32_t call_inst = UINT32_MAX;
+ uint32_t after_call = UINT32_MAX;
+ for (uint32_t i = 0; i < mvb_len; i++) {
+ uint32_t mi = mvb[i];
+ if (mi < model_zir->inst_len
+ && model_zir->inst_tags[mi] == ZIR_INST_CALL) {
+ call_inst = mi;
+ if (i + 1 < mvb_len)
+ after_call = mvb[i + 1];
+ break;
+ }
+ }
+
+ // Phase 1: Scan ZIR instructions between the
+ // call and the next top-level inst for
+ // decl_literal instructions matching Feature
+ // enum fields.
+ uint32_t feat_indices[64];
+ uint32_t feat_count = 0;
+ if (call_inst != UINT32_MAX && after_call != UINT32_MAX) {
+ for (uint32_t zi = call_inst + 1; zi < after_call;
+ zi++) {
+ if (zi >= model_zir->inst_len)
+ continue;
+ if (model_zir->inst_tags[zi]
+ != ZIR_INST_DECL_LITERAL)
+ continue;
+ // decl_literal uses pl_node; payload is
+ // Field {lhs: Ref, field_name_start: u32}.
+ uint32_t pi = model_zir->inst_datas[zi]
+ .pl_node.payload_index;
+ uint32_t nsi = model_zir->extra[pi + 1];
+ const char* fn
+ = (const char*)&model_zir->string_bytes[nsi];
+ uint32_t fidx
+ = findEnumFieldByName(feat_zir, enum_inst, fn);
+ if (fidx != UINT32_MAX && feat_count < 64)
+ feat_indices[feat_count++] = fidx;
+ }
+ }
+ if (feat_count > 0) {
+ // $707: type_array_small [N]Feature.
+ InternPoolKey ak;
+ memset(&ak, 0, sizeof(ak));
+ ak.tag = IP_KEY_ARRAY_TYPE;
+ ak.data.array_type.len = feat_count;
+ ak.data.array_type.child = feature_type;
+ ak.data.array_type.sentinel = IP_INDEX_NONE;
+ InternPoolIndex arr_type = ipIntern(s_module_ip, ak);
+
+ // $708-$714: enum_tag for each feature.
+ for (uint32_t i = 0; i < feat_count; i++) {
+ InternPoolIndex iv = getEnumFieldIntVal(
+ feat_zir, enum_inst, feat_indices[i]);
+ if (iv != IP_INDEX_NONE)
+ (void)internEnumTag(feature_type, iv);
+ }
+
+ // $715: aggregate (array value).
+ InternPoolKey agk;
+ memset(&agk, 0, sizeof(agk));
+ agk.tag = IP_KEY_AGGREGATE;
+ agk.data.aggregate = arr_type;
+ InternPoolIndex agg = ipIntern(s_module_ip, agk);
+
+ // $716: *const [N]Feature.
+ InternPoolIndex arr_ptr = internPtrConst(arr_type);
+
+ // $717: ptr_uav.
+ InternPoolKey uav;
+ memset(&uav, 0, sizeof(uav));
+ uav.tag = IP_KEY_PTR_UAV;
+ uav.data.ptr_uav.ty = arr_ptr;
+ uav.data.ptr_uav.val = agg;
+ InternPoolIndex uav_ptr = ipIntern(s_module_ip, uav);
+
+ // $718: ptr_uav_aligned.
+ InternPoolKey uaa;
+ memset(&uaa, 0, sizeof(uaa));
+ uaa.tag = IP_KEY_PTR_UAV_ALIGNED;
+ uaa.data.ptr_uav_aligned.ty = arr_ptr;
+ uaa.data.ptr_uav_aligned.val = agg;
+ uaa.data.ptr_uav_aligned.orig_ty = uav_ptr;
+ InternPoolIndex aligned = ipIntern(s_module_ip, uaa);
+
+ // $719: int_usize(N).
+ InternPoolIndex len_val
+ = internTypedInt(IP_INDEX_USIZE_TYPE, feat_count);
+
+ // $720: ptr_slice.
+ // Slice type []const Feature should already
+ // exist from FeatureSetFns evaluation ($702).
+ InternPoolKey slk;
+ memset(&slk, 0, sizeof(slk));
+ slk.tag = IP_KEY_PTR_TYPE;
+ slk.data.ptr_type.child = feature_type;
+ slk.data.ptr_type.sentinel = IP_INDEX_NONE;
+ slk.data.ptr_type.flags
+ = PTR_FLAGS_SIZE_SLICE | PTR_FLAGS_IS_CONST;
+ slk.data.ptr_type.packed_offset = 0;
+ InternPoolIndex slice_ty = ipIntern(s_module_ip, slk);
+
+ InternPoolKey psk;
+ memset(&psk, 0, sizeof(psk));
+ psk.tag = IP_KEY_PTR_SLICE;
+ psk.data.ptr_slice.ty = slice_ty;
+ psk.data.ptr_slice.ptr = aligned;
+ psk.data.ptr_slice.len = len_val;
+ (void)ipIntern(s_module_ip, psk);
+ }
+ }
+ }
+ }
+ }
+
(void)target_file_idx;
}