commit 3e8281100f49763bbd58bdcb7a3c01f6d78500a5 (tree)
parent 4987fc5aee495b229dd49dcd733352015f0beae1
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Sat, 14 Feb 2026 18:11:24 +0000
astgen: add unionDeclInner, @hasDecl, @hasField, bitBuiltins
Implement unionDeclInner (AstGen.zig:5289-5466) to properly handle
union container declarations instead of falling through to
structDeclInner. This fixes tupleDecl errors for void union fields
(A, B, Compiled, x86_64) and resolves localVarRef failures for
union field identifiers.
Add builtins: @hasDecl, @hasField (@hasDeclOrField pattern),
@clz, @ctz, @popCount, @byteSwap, @bitReverse (bitBuiltin pattern).
Add setUnion with UnionDeclSmall packing for ZIR_EXT_UNION_DECL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
| M | stage0/astgen.c | | | 436 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
1 file changed, 435 insertions(+), 1 deletion(-)
diff --git a/stage0/astgen.c b/stage0/astgen.c
@@ -2069,6 +2069,79 @@ static void setStruct(AstGenCtx* ag, uint32_t inst, uint32_t src_node,
ag->inst_datas[inst] = data;
}
+// --- setUnion (AstGen.zig:12999) ---
+
+// UnionDecl.Small bit packing (Zir.zig:3573-3590).
+typedef struct {
+ bool has_tag_type;
+ bool has_captures_len;
+ bool has_body_len;
+ bool has_fields_len;
+ bool has_decls_len;
+ uint8_t name_strategy; // 2 bits
+ uint8_t layout; // 2 bits
+ bool auto_enum_tag;
+ bool any_aligned_fields;
+} UnionDeclSmall;
+
+static uint16_t packUnionDeclSmall(UnionDeclSmall s) {
+ uint16_t r = 0;
+ if (s.has_tag_type)
+ r |= (1u << 0);
+ if (s.has_captures_len)
+ r |= (1u << 1);
+ if (s.has_body_len)
+ r |= (1u << 2);
+ if (s.has_fields_len)
+ r |= (1u << 3);
+ if (s.has_decls_len)
+ r |= (1u << 4);
+ r |= (uint16_t)(s.name_strategy & 0x3u) << 5;
+ r |= (uint16_t)(s.layout & 0x3u) << 7;
+ if (s.auto_enum_tag)
+ r |= (1u << 9);
+ if (s.any_aligned_fields)
+ r |= (1u << 10);
+ return r;
+}
+
+// Mirrors GenZir.setUnion (AstGen.zig:12999-13062).
+static void setUnion(AstGenCtx* ag, uint32_t inst, uint32_t src_node,
+ UnionDeclSmall small, uint32_t tag_type, uint32_t captures_len,
+ uint32_t body_len, uint32_t fields_len, uint32_t decls_len) {
+ ensureExtraCapacity(ag, 6 + 5);
+
+ uint32_t payload_index = ag->extra_len;
+
+ // fields_hash (4 words): zero-filled; hash comparison skipped in tests.
+ ag->extra[ag->extra_len++] = 0;
+ ag->extra[ag->extra_len++] = 0;
+ ag->extra[ag->extra_len++] = 0;
+ ag->extra[ag->extra_len++] = 0;
+
+ ag->extra[ag->extra_len++] = ag->source_line;
+ ag->extra[ag->extra_len++] = src_node;
+
+ if (small.has_tag_type)
+ ag->extra[ag->extra_len++] = tag_type;
+ if (small.has_captures_len)
+ ag->extra[ag->extra_len++] = captures_len;
+ if (small.has_body_len)
+ ag->extra[ag->extra_len++] = body_len;
+ if (small.has_fields_len)
+ ag->extra[ag->extra_len++] = fields_len;
+ if (small.has_decls_len)
+ ag->extra[ag->extra_len++] = decls_len;
+
+ ag->inst_tags[inst] = ZIR_INST_EXTENDED;
+ ZirInstData data;
+ memset(&data, 0, sizeof(data));
+ data.extended.opcode = (uint16_t)ZIR_EXT_UNION_DECL;
+ data.extended.small = packUnionDeclSmall(small);
+ data.extended.operand = payload_index;
+ ag->inst_datas[inst] = data;
+}
+
// --- scanContainer (AstGen.zig:13384) ---
// Mirrors scanContainer (AstGen.zig:13384).
@@ -2619,6 +2692,10 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, Scope* scope,
static uint32_t tupleDecl(AstGenCtx* ag, GenZir* gz, Scope* scope,
uint32_t node, const uint32_t* members, uint32_t members_len,
uint8_t layout, uint32_t backing_int_node);
+static uint32_t unionDeclInner(AstGenCtx* ag, GenZir* gz, Scope* scope,
+ uint32_t node, const uint32_t* members, uint32_t members_len,
+ uint8_t layout, uint32_t arg_node, bool auto_enum_tag,
+ uint8_t name_strategy);
static uint32_t enumDeclInner(AstGenCtx* ag, GenZir* gz, Scope* scope,
uint32_t node, const uint32_t* members, uint32_t members_len,
uint32_t arg_node, uint8_t name_strategy);
@@ -2793,6 +2870,7 @@ static uint32_t tryResolvePrimitiveIdent(GenZir* gz, uint32_t node);
#define COMPTIME_REASON_EXPORT_OPTIONS 15
#define COMPTIME_REASON_CALL_MODIFIER 18
#define COMPTIME_REASON_SHUFFLE_MASK 11
+#define COMPTIME_REASON_DECL_NAME 41
// Mirrors comptimeExpr2 (AstGen.zig:1982).
// Evaluates a node in a comptime block_comptime scope.
@@ -4007,6 +4085,45 @@ static uint32_t builtinCall(
= addPlNodeBin(gz, ZIR_INST_DIV_EXACT, node, lhs, rhs);
return rvalue(gz, rl, result, node);
}
+ // @clz — bitBuiltin (AstGen.zig:9475, 9907-9918).
+ if (name_len == 3 && memcmp(source + name_start, "clz", 3) == 0) {
+ AstData nd = tree->nodes.datas[node];
+ uint32_t operand = expr(gz, scope, nd.lhs);
+ uint32_t result = addUnNode(gz, ZIR_INST_CLZ, operand, node);
+ return rvalue(gz, rl, result, node);
+ }
+ // @ctz — bitBuiltin (AstGen.zig:9476, 9907-9918).
+ if (name_len == 3 && memcmp(source + name_start, "ctz", 3) == 0) {
+ AstData nd = tree->nodes.datas[node];
+ uint32_t operand = expr(gz, scope, nd.lhs);
+ uint32_t result = addUnNode(gz, ZIR_INST_CTZ, operand, node);
+ return rvalue(gz, rl, result, node);
+ }
+ // @popCount — bitBuiltin (AstGen.zig:9477, 9907-9918).
+ if (name_len == 8
+ && memcmp(source + name_start, "popCount", 8) == 0) {
+ AstData nd = tree->nodes.datas[node];
+ uint32_t operand = expr(gz, scope, nd.lhs);
+ uint32_t result = addUnNode(gz, ZIR_INST_POP_COUNT, operand, node);
+ return rvalue(gz, rl, result, node);
+ }
+ // @byteSwap — bitBuiltin (AstGen.zig:9478, 9907-9918).
+ if (name_len == 8
+ && memcmp(source + name_start, "byteSwap", 8) == 0) {
+ AstData nd = tree->nodes.datas[node];
+ uint32_t operand = expr(gz, scope, nd.lhs);
+ uint32_t result = addUnNode(gz, ZIR_INST_BYTE_SWAP, operand, node);
+ return rvalue(gz, rl, result, node);
+ }
+ // @bitReverse — bitBuiltin (AstGen.zig:9479, 9907-9918).
+ if (name_len == 10
+ && memcmp(source + name_start, "bitReverse", 10) == 0) {
+ AstData nd = tree->nodes.datas[node];
+ uint32_t operand = expr(gz, scope, nd.lhs);
+ uint32_t result
+ = addUnNode(gz, ZIR_INST_BIT_REVERSE, operand, node);
+ return rvalue(gz, rl, result, node);
+ }
// @bitOffsetOf — AstGen.zig:9490, 9962-9978.
if (name_len == 11
&& memcmp(source + name_start, "bitOffsetOf", 11) == 0) {
@@ -4044,6 +4161,34 @@ static uint32_t builtinCall(
addInstruction(gz, ZIR_INST_EXPORT, data);
return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node);
}
+ // @hasDecl — hasDeclOrField (AstGen.zig:9472, 9783-9805).
+ if (name_len == 7
+ && memcmp(source + name_start, "hasDecl", 7) == 0) {
+ AstData nd = tree->nodes.datas[node];
+ uint32_t container_type = typeExpr(gz, scope, nd.lhs);
+ ResultLoc name_rl = { .tag = RL_COERCED_TY,
+ .data = ZIR_REF_SLICE_CONST_U8_TYPE,
+ .src_node = 0, .ctx = RI_CTX_NONE };
+ uint32_t name_inst = comptimeExpr(
+ gz, scope, name_rl, nd.rhs, COMPTIME_REASON_DECL_NAME);
+ uint32_t result = addPlNodeBin(
+ gz, ZIR_INST_HAS_DECL, node, container_type, name_inst);
+ return rvalue(gz, rl, result, node);
+ }
+ // @hasField — hasDeclOrField (AstGen.zig:9473, 9783-9805).
+ if (name_len == 8
+ && memcmp(source + name_start, "hasField", 8) == 0) {
+ AstData nd = tree->nodes.datas[node];
+ uint32_t container_type = typeExpr(gz, scope, nd.lhs);
+ ResultLoc name_rl = { .tag = RL_COERCED_TY,
+ .data = ZIR_REF_SLICE_CONST_U8_TYPE,
+ .src_node = 0, .ctx = RI_CTX_NONE };
+ uint32_t name_inst = comptimeExpr(
+ gz, scope, name_rl, nd.rhs, COMPTIME_REASON_FIELD_NAME);
+ uint32_t result = addPlNodeBin(
+ gz, ZIR_INST_HAS_FIELD, node, container_type, name_inst);
+ return rvalue(gz, rl, result, node);
+ }
// clang-format on
// TODO: handle other builtins.
@@ -14093,8 +14238,36 @@ static uint32_t containerDecl(
decl_inst = enumDeclInner(ag, gz, scope, node, members, members_len,
arg_node, name_strategy);
break;
+ case TOKEN_KEYWORD_UNION: {
+ // Extract layout (AstGen.zig:5499-5503).
+ uint8_t layout = 0; // auto
+ if (main_token > 0) {
+ TokenizerTag prev_tag = tree->tokens.tags[main_token - 1];
+ if (prev_tag == TOKEN_KEYWORD_PACKED)
+ layout = 2;
+ else if (prev_tag == TOKEN_KEYWORD_EXTERN)
+ layout = 1;
+ }
+ // Determine auto_enum_tag from node type (AstGen.zig:5505).
+ bool auto_enum_tag = false;
+ switch (tag) {
+ case AST_NODE_TAGGED_UNION:
+ case AST_NODE_TAGGED_UNION_TRAILING:
+ case AST_NODE_TAGGED_UNION_TWO:
+ case AST_NODE_TAGGED_UNION_TWO_TRAILING:
+ case AST_NODE_TAGGED_UNION_ENUM_TAG:
+ case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING:
+ auto_enum_tag = true;
+ break;
+ default:
+ break;
+ }
+ decl_inst = unionDeclInner(ag, gz, scope, node, members, members_len,
+ layout, arg_node, auto_enum_tag, name_strategy);
+ break;
+ }
default:
- // union/opaque: fall back to struct for now.
+ // opaque: fall back to struct for now.
decl_inst = structDeclInner(
ag, gz, scope, node, members, members_len, 0, 0, name_strategy);
break;
@@ -14545,6 +14718,267 @@ static uint32_t tupleDecl(AstGenCtx* ag, GenZir* gz, Scope* scope,
return idx;
}
+// --- unionDeclInner (AstGen.zig:5289) ---
+
+static uint32_t unionDeclInner(AstGenCtx* ag, GenZir* gz, Scope* scope,
+ uint32_t node, const uint32_t* members, uint32_t members_len,
+ uint8_t layout, uint32_t arg_node, bool auto_enum_tag,
+ uint8_t name_strategy) {
+ const Ast* tree = ag->tree;
+
+ uint32_t decl_inst = reserveInstructionIndex(ag);
+ gzAppendInstruction(gz, decl_inst);
+
+ // Create namespace scope (AstGen.zig:5304-5310).
+ ScopeNamespace namespace;
+ scopeNamespaceInit(&namespace, scope, node, decl_inst, gz, ag->within_fn);
+
+ // Create block scope (AstGen.zig:5317-5325).
+ advanceSourceCursorToNode(ag, node);
+ GenZir block_scope;
+ memset(&block_scope, 0, sizeof(block_scope));
+ block_scope.base.tag = SCOPE_GEN_ZIR;
+ block_scope.parent = &namespace.base;
+ block_scope.astgen = ag;
+ block_scope.decl_node_index = node;
+ block_scope.decl_line = gz->decl_line;
+ block_scope.is_comptime = true;
+ block_scope.instructions_top = ag->scratch_inst_len;
+ block_scope.any_defer_node = UINT32_MAX;
+
+ // Scan container (AstGen.zig:5328).
+ uint32_t decl_count = scanContainer(ag, &namespace, members, members_len);
+ uint32_t field_count = members_len - decl_count;
+
+ // Layout validation (AstGen.zig:5331-5337).
+ if (layout != 0 && (auto_enum_tag || arg_node != 0)) {
+ SET_ERROR(ag);
+ }
+
+ // Process arg instruction (AstGen.zig:5339-5342).
+ uint32_t arg_inst = ZIR_REF_NONE;
+ if (arg_node != 0) {
+ arg_inst = typeExpr(&block_scope, &namespace.base, arg_node);
+ }
+
+ // WipMembers with bits_per_field=4, max_field_size=4 (AstGen.zig:5347).
+ WipMembers wm = wipMembersInitEx(decl_count, field_count, 4, 4);
+
+ bool any_aligned_fields = false;
+
+ // Process each member (AstGen.zig:5359-5428).
+ for (uint32_t i = 0; i < members_len; i++) {
+ uint32_t member_node = members[i];
+ AstNodeTag mtag = tree->nodes.tags[member_node];
+
+ switch (mtag) {
+ // Declarations (containerMember AstGen.zig:5809-5901).
+ case AST_NODE_COMPTIME:
+ comptimeDecl(ag, gz, &namespace.base, wm.payload, &wm.decl_index,
+ member_node);
+ break;
+ case AST_NODE_SIMPLE_VAR_DECL:
+ globalVarDecl(ag, gz, &namespace.base, wm.payload, &wm.decl_index,
+ member_node);
+ break;
+ case AST_NODE_TEST_DECL:
+ testDecl(ag, gz, &namespace.base, wm.payload, &wm.decl_index,
+ member_node);
+ break;
+ case AST_NODE_FN_DECL:
+ case AST_NODE_FN_PROTO_SIMPLE:
+ case AST_NODE_FN_PROTO_MULTI:
+ case AST_NODE_FN_PROTO_ONE:
+ case AST_NODE_FN_PROTO:
+ fnDecl(ag, gz, &namespace.base, wm.payload, &wm.decl_index,
+ member_node);
+ break;
+ case AST_NODE_USINGNAMESPACE:
+ case AST_NODE_GLOBAL_VAR_DECL:
+ case AST_NODE_LOCAL_VAR_DECL:
+ case AST_NODE_ALIGNED_VAR_DECL:
+ globalVarDecl(ag, gz, &namespace.base, wm.payload, &wm.decl_index,
+ member_node);
+ break;
+
+ // Fields (AstGen.zig:5359-5427).
+ case AST_NODE_CONTAINER_FIELD_INIT:
+ case AST_NODE_CONTAINER_FIELD_ALIGN:
+ case AST_NODE_CONTAINER_FIELD: {
+ uint32_t main_token = tree->nodes.main_tokens[member_node];
+ AstData nd = tree->nodes.datas[member_node];
+ uint32_t type_node = nd.lhs;
+ uint32_t align_node = 0;
+ uint32_t value_node = 0;
+
+ switch (mtag) {
+ case AST_NODE_CONTAINER_FIELD_INIT:
+ value_node = nd.rhs;
+ break;
+ case AST_NODE_CONTAINER_FIELD_ALIGN:
+ align_node = nd.rhs;
+ break;
+ case AST_NODE_CONTAINER_FIELD:
+ if (nd.rhs != 0) {
+ align_node = tree->extra_data.arr[nd.rhs];
+ value_node = tree->extra_data.arr[nd.rhs + 1];
+ }
+ break;
+ default:
+ break;
+ }
+
+ // convertToNonTupleLike (AstGen.zig:5365, Ast.zig:2635-2641).
+ bool tuple_like = tree->tokens.tags[main_token] != TOKEN_IDENTIFIER
+ || tree->tokens.tags[main_token + 1] != TOKEN_COLON;
+ if (tuple_like && type_node != 0
+ && tree->nodes.tags[type_node] == AST_NODE_IDENTIFIER) {
+ type_node = 0;
+ tuple_like = false;
+ }
+
+ // Still tuple-like after conversion: error (AstGen.zig:5366-5368).
+ if (tuple_like) {
+ SET_ERROR(ag);
+ break;
+ }
+
+ // Comptime check (AstGen.zig:5369-5371).
+ if (main_token > 0
+ && tree->tokens.tags[main_token - 1]
+ == TOKEN_KEYWORD_COMPTIME) {
+ SET_ERROR(ag); // union fields cannot be marked comptime
+ break;
+ }
+
+ // Field name (AstGen.zig:5373).
+ uint32_t field_name = identAsString(ag, main_token);
+ wipMembersAppendToField(&wm, field_name);
+
+ bool have_type = (type_node != 0);
+ bool have_align = (align_node != 0);
+ bool have_value = (value_node != 0);
+ bool field_bits[4] = { have_type, have_align, have_value, false };
+ wipMembersNextField(&wm, field_bits);
+
+ // Type expression (AstGen.zig:5382-5387).
+ if (have_type) {
+ uint32_t field_type
+ = typeExpr(&block_scope, &namespace.base, type_node);
+ wipMembersAppendToField(&wm, field_type);
+ } else if (arg_inst == ZIR_REF_NONE && !auto_enum_tag) {
+ SET_ERROR(ag); // union field missing type
+ break;
+ }
+
+ // Alignment (AstGen.zig:5388-5395).
+ if (have_align) {
+ if (layout == 2) { // packed
+ SET_ERROR(ag);
+ break;
+ }
+ ResultLoc align_rl = { .tag = RL_COERCED_TY,
+ .data = ZIR_REF_U29_TYPE,
+ .src_node = 0,
+ .ctx = RI_CTX_NONE };
+ uint32_t align_ref = exprRl(
+ &block_scope, &block_scope.base, align_rl, align_node);
+ wipMembersAppendToField(&wm, align_ref);
+ any_aligned_fields = true;
+ }
+
+ // Value expression / tag value (AstGen.zig:5396-5427).
+ if (have_value) {
+ if (arg_inst == ZIR_REF_NONE) {
+ SET_ERROR(ag);
+ break;
+ }
+ if (!auto_enum_tag) {
+ SET_ERROR(ag);
+ break;
+ }
+ ResultLoc val_rl = { .tag = RL_COERCED_TY,
+ .data = arg_inst,
+ .src_node = 0,
+ .ctx = RI_CTX_NONE };
+ uint32_t tag_value = exprRl(
+ &block_scope, &block_scope.base, val_rl, value_node);
+ wipMembersAppendToField(&wm, tag_value);
+ }
+ break;
+ }
+ default:
+ SET_ERROR(ag);
+ break;
+ }
+ }
+
+ wipMembersFinishBits(&wm);
+
+ // Handle block scope body (AstGen.zig:5433-5438).
+ uint32_t body_len = 0;
+ if (gzInstructionsLen(&block_scope) > 0) {
+ if (!endsWithNoReturn(&block_scope)) {
+ makeBreakInline(&block_scope, decl_inst, ZIR_REF_VOID_VALUE,
+ AST_NODE_OFFSET_NONE);
+ }
+ uint32_t raw_len = gzInstructionsLen(&block_scope);
+ const uint32_t* body_slice = gzInstructionsSlice(&block_scope);
+ body_len = countBodyLenAfterFixups(ag, body_slice, raw_len);
+ }
+
+ // setUnion (AstGen.zig:5440-5452).
+ UnionDeclSmall small;
+ memset(&small, 0, sizeof(small));
+ small.has_tag_type = (arg_inst != ZIR_REF_NONE);
+ small.has_captures_len = (namespace.captures_len > 0);
+ small.has_body_len = (body_len > 0);
+ small.has_fields_len = (field_count > 0);
+ small.has_decls_len = (decl_count > 0);
+ small.name_strategy = name_strategy;
+ small.layout = layout;
+ small.auto_enum_tag = auto_enum_tag;
+ small.any_aligned_fields = any_aligned_fields;
+ setUnion(ag, decl_inst, node, small, arg_inst, namespace.captures_len,
+ body_len, field_count, decl_count);
+
+ // Append trailing data (AstGen.zig:5454-5462).
+ uint32_t decls_len;
+ const uint32_t* decls_slice = wipMembersDeclsSlice(&wm, &decls_len);
+ uint32_t fields_len;
+ const uint32_t* fields_slice = wipMembersFieldsSlice(&wm, &fields_len);
+
+ ensureExtraCapacity(
+ ag, namespace.captures_len * 2 + decls_len + body_len + fields_len);
+
+ // Captures (AstGen.zig:5458-5459).
+ for (uint32_t j = 0; j < namespace.captures_len; j++)
+ ag->extra[ag->extra_len++] = namespace.capture_keys[j];
+ for (uint32_t j = 0; j < namespace.captures_len; j++)
+ ag->extra[ag->extra_len++] = namespace.capture_vals[j];
+
+ // Decls (AstGen.zig:5460).
+ for (uint32_t j = 0; j < decls_len; j++)
+ ag->extra[ag->extra_len++] = decls_slice[j];
+
+ // Body (AstGen.zig:5461).
+ if (body_len > 0) {
+ uint32_t raw_len = gzInstructionsLen(&block_scope);
+ const uint32_t* body_data = gzInstructionsSlice(&block_scope);
+ for (uint32_t j = 0; j < raw_len; j++)
+ appendPossiblyRefdBodyInst(ag, body_data[j]);
+ }
+
+ // Fields (AstGen.zig:5462).
+ for (uint32_t j = 0; j < fields_len; j++)
+ ag->extra[ag->extra_len++] = fields_slice[j];
+
+ gzUnstack(&block_scope);
+ wipMembersDeinit(&wm);
+ scopeNamespaceDeinit(&namespace);
+ return decl_inst;
+}
+
// --- structDeclInner (AstGen.zig:4926) ---
static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, Scope* scope,