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>
This commit is contained in:
2026-02-14 18:11:24 +00:00
parent a8e29e4541
commit 3bcb29bee8

View File

@@ -2069,6 +2069,79 @@ static void setStruct(AstGenCtx* ag, uint32_t inst, uint32_t src_node,
ag->inst_datas[inst] = data; 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) --- // --- scanContainer (AstGen.zig:13384) ---
// Mirrors 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, static uint32_t tupleDecl(AstGenCtx* ag, GenZir* gz, Scope* scope,
uint32_t node, const uint32_t* members, uint32_t members_len, uint32_t node, const uint32_t* members, uint32_t members_len,
uint8_t layout, uint32_t backing_int_node); 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, static uint32_t enumDeclInner(AstGenCtx* ag, GenZir* gz, Scope* scope,
uint32_t node, const uint32_t* members, uint32_t members_len, uint32_t node, const uint32_t* members, uint32_t members_len,
uint32_t arg_node, uint8_t name_strategy); 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_EXPORT_OPTIONS 15
#define COMPTIME_REASON_CALL_MODIFIER 18 #define COMPTIME_REASON_CALL_MODIFIER 18
#define COMPTIME_REASON_SHUFFLE_MASK 11 #define COMPTIME_REASON_SHUFFLE_MASK 11
#define COMPTIME_REASON_DECL_NAME 41
// Mirrors comptimeExpr2 (AstGen.zig:1982). // Mirrors comptimeExpr2 (AstGen.zig:1982).
// Evaluates a node in a comptime block_comptime scope. // 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); = addPlNodeBin(gz, ZIR_INST_DIV_EXACT, node, lhs, rhs);
return rvalue(gz, rl, result, node); 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. // @bitOffsetOf — AstGen.zig:9490, 9962-9978.
if (name_len == 11 if (name_len == 11
&& memcmp(source + name_start, "bitOffsetOf", 11) == 0) { && memcmp(source + name_start, "bitOffsetOf", 11) == 0) {
@@ -4044,6 +4161,34 @@ static uint32_t builtinCall(
addInstruction(gz, ZIR_INST_EXPORT, data); addInstruction(gz, ZIR_INST_EXPORT, data);
return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); 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 // clang-format on
// TODO: handle other builtins. // TODO: handle other builtins.
@@ -14093,8 +14238,36 @@ static uint32_t containerDecl(
decl_inst = enumDeclInner(ag, gz, scope, node, members, members_len, decl_inst = enumDeclInner(ag, gz, scope, node, members, members_len,
arg_node, name_strategy); arg_node, name_strategy);
break; 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: default:
// union/opaque: fall back to struct for now. // opaque: fall back to struct for now.
decl_inst = structDeclInner( decl_inst = structDeclInner(
ag, gz, scope, node, members, members_len, 0, 0, name_strategy); ag, gz, scope, node, members, members_len, 0, 0, name_strategy);
break; break;
@@ -14545,6 +14718,267 @@ static uint32_t tupleDecl(AstGenCtx* ag, GenZir* gz, Scope* scope,
return idx; 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) --- // --- structDeclInner (AstGen.zig:4926) ---
static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, Scope* scope, static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, Scope* scope,