diff --git a/astgen.c b/astgen.c index 9308910f67..d764f577cb 100644 --- a/astgen.c +++ b/astgen.c @@ -7116,6 +7116,292 @@ static void globalVarDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, (void)gz; } +// --- nodeImpliesMoreThanOnePossibleValue (AstGen.zig:10548) --- +// Check if an identifier is a primitive type with more than one value. +static bool identImpliesMoreThanOnePossibleValue( + const Ast* tree, uint32_t main_token) { + uint32_t start = tree->tokens.starts[main_token]; + const char* src = tree->source + start; + // Match known primitive types that have more than one possible value. + // (AstGen.zig:10729-10766) + if (src[0] == 'u' || src[0] == 'i') { + // u8, u16, u32, u64, u128, u1, u29, usize, i8, i16, i32, i64, i128, + // isize + char c1 = src[1]; + if (c1 >= '0' && c1 <= '9') + return true; + if (c1 == 's') // usize, isize + return (src[2] == 'i' && src[3] == 'z' && src[4] == 'e'); + } + if (src[0] == 'f') { + // f16, f32, f64, f80, f128 + char c1 = src[1]; + if (c1 >= '0' && c1 <= '9') + return true; + } + if (src[0] == 'b' && src[1] == 'o' && src[2] == 'o' && src[3] == 'l' + && !(src[4] >= 'a' && src[4] <= 'z') + && !(src[4] >= 'A' && src[4] <= 'Z') + && !(src[4] >= '0' && src[4] <= '9') && src[4] != '_') + return true; + if (src[0] == 'c' && src[1] == '_') + return true; // c_int, c_long, etc. + if (src[0] == 'a' && src[1] == 'n' && src[2] == 'y') { + // anyerror, anyframe, anyopaque + return true; + } + if (src[0] == 'c' && src[1] == 'o' && src[2] == 'm' && src[3] == 'p' + && src[4] == 't' && src[5] == 'i' && src[6] == 'm' && src[7] == 'e') + return true; // comptime_float, comptime_int + if (src[0] == 't' && src[1] == 'y' && src[2] == 'p' && src[3] == 'e' + && !(src[4] >= 'a' && src[4] <= 'z') + && !(src[4] >= 'A' && src[4] <= 'Z') + && !(src[4] >= '0' && src[4] <= '9') && src[4] != '_') + return true; + return false; +} + +static bool nodeImpliesMoreThanOnePossibleValue( + const Ast* tree, uint32_t node) { + uint32_t cur = node; + while (1) { + AstNodeTag tag = tree->nodes.tags[cur]; + switch (tag) { + // Pointer/optional/array/anyframe types → true + // (AstGen.zig:10718-10725) + case AST_NODE_PTR_TYPE_ALIGNED: + case AST_NODE_PTR_TYPE_SENTINEL: + case AST_NODE_PTR_TYPE: + case AST_NODE_PTR_TYPE_BIT_RANGE: + case AST_NODE_OPTIONAL_TYPE: + case AST_NODE_ANYFRAME_TYPE: + case AST_NODE_ARRAY_TYPE_SENTINEL: + return true; + // Forward to LHS: try, comptime, nosuspend + // (AstGen.zig:10710-10713) + case AST_NODE_TRY: + case AST_NODE_COMPTIME: + case AST_NODE_NOSUSPEND: + cur = tree->nodes.datas[cur].lhs; + continue; + // Forward to LHS: grouped_expression, unwrap_optional + // (AstGen.zig:10714-10716) + case AST_NODE_GROUPED_EXPRESSION: + case AST_NODE_UNWRAP_OPTIONAL: + cur = tree->nodes.datas[cur].lhs; + continue; + // Identifier: check primitives (AstGen.zig:10727-10780) + case AST_NODE_IDENTIFIER: + return identImpliesMoreThanOnePossibleValue( + tree, tree->nodes.main_tokens[cur]); + default: + return false; + } + } +} + +// --- nodeImpliesComptimeOnly (AstGen.zig:10787) --- + +static bool identImpliesComptimeOnly( + const Ast* tree, uint32_t main_token) { + uint32_t start = tree->tokens.starts[main_token]; + const char* src = tree->source + start; + // Only comptime_float, comptime_int, type → true + // (AstGen.zig:11010-11013) + if (src[0] == 'c' && src[1] == 'o' && src[2] == 'm' && src[3] == 'p' + && src[4] == 't' && src[5] == 'i' && src[6] == 'm' && src[7] == 'e') + return true; // comptime_float, comptime_int + if (src[0] == 't' && src[1] == 'y' && src[2] == 'p' && src[3] == 'e' + && !(src[4] >= 'a' && src[4] <= 'z') + && !(src[4] >= 'A' && src[4] <= 'Z') + && !(src[4] >= '0' && src[4] <= '9') && src[4] != '_') + return true; + return false; +} + +static bool nodeImpliesComptimeOnly(const Ast* tree, uint32_t node) { + uint32_t cur = node; + while (1) { + AstNodeTag tag = tree->nodes.tags[cur]; + switch (tag) { + // Function prototypes → true (AstGen.zig:10950-10955) + case AST_NODE_FN_PROTO_SIMPLE: + case AST_NODE_FN_PROTO_MULTI: + case AST_NODE_FN_PROTO_ONE: + case AST_NODE_FN_PROTO: + return true; + // Forward to LHS: try, comptime, nosuspend + case AST_NODE_TRY: + case AST_NODE_COMPTIME: + case AST_NODE_NOSUSPEND: + cur = tree->nodes.datas[cur].lhs; + continue; + case AST_NODE_GROUPED_EXPRESSION: + case AST_NODE_UNWRAP_OPTIONAL: + cur = tree->nodes.datas[cur].lhs; + continue; + // Identifier: check primitives + case AST_NODE_IDENTIFIER: + return identImpliesComptimeOnly( + tree, tree->nodes.main_tokens[cur]); + default: + return false; + } + } +} + +// --- WipMembers (AstGen.zig:3989) --- +// Tracks decl indices, field bit-flags, and per-field data during container +// processing. All data lives in a single malloc'd array laid out as: +// [decls (decl_count)] [field_bits (ceil)] [fields (up to field_count*max)] +// Bodies are tracked separately in a dynamic array. + +typedef struct { + uint32_t* payload; // malloc'd array + uint32_t payload_top; // always 0 (start of decls region) + uint32_t field_bits_start; + uint32_t fields_start; + uint32_t fields_end; + uint32_t decl_index; + uint32_t field_index; + // Bodies scratch: dynamically grown array for field type/align/init bodies. + uint32_t* bodies; + uint32_t bodies_len; + uint32_t bodies_cap; +} WipMembers; + +static WipMembers wipMembersInit( + uint32_t decl_count, uint32_t field_count) { + // bits_per_field = 4, max_field_size = 5 + uint32_t fields_per_u32 = 8; // 32 / 4 + uint32_t field_bits_start = decl_count; + uint32_t bit_words + = field_count > 0 ? (field_count + fields_per_u32 - 1) / fields_per_u32 + : 0; + uint32_t fields_start = field_bits_start + bit_words; + uint32_t payload_end = fields_start + field_count * 5; + uint32_t alloc_size = payload_end > 0 ? payload_end : 1; + uint32_t* payload = calloc(alloc_size, sizeof(uint32_t)); + if (!payload) + exit(1); + WipMembers wm; + memset(&wm, 0, sizeof(wm)); + wm.payload = payload; + wm.payload_top = 0; + wm.field_bits_start = field_bits_start; + wm.fields_start = fields_start; + wm.fields_end = fields_start; + wm.decl_index = 0; + wm.field_index = 0; + wm.bodies = NULL; + wm.bodies_len = 0; + wm.bodies_cap = 0; + return wm; +} + +static void wipMembersDeinit(WipMembers* wm) { + free(wm->payload); + free(wm->bodies); +} + +static void wipMembersNextDecl(WipMembers* wm, uint32_t decl_inst) { + wm->payload[wm->payload_top + wm->decl_index] = decl_inst; + wm->decl_index++; +} + +// bits_per_field = 4: bits[0]=have_align, bits[1]=have_value, +// bits[2]=is_comptime, bits[3]=have_type_body +static void wipMembersNextField(WipMembers* wm, bool bits[4]) { + uint32_t fields_per_u32 = 8; // 32 / 4 + uint32_t index = wm->field_bits_start + wm->field_index / fields_per_u32; + uint32_t bit_bag + = (wm->field_index % fields_per_u32 == 0) ? 0 : wm->payload[index]; + bit_bag >>= 4; + for (int i = 0; i < 4; i++) { + bit_bag |= ((uint32_t)(bits[i] ? 1 : 0)) << (32 - 4 + i); + } + wm->payload[index] = bit_bag; + wm->field_index++; +} + +static void wipMembersAppendToField(WipMembers* wm, uint32_t data) { + wm->payload[wm->fields_end] = data; + wm->fields_end++; +} + +static void wipMembersFinishBits(WipMembers* wm) { + uint32_t fields_per_u32 = 8; // 32 / 4 + uint32_t empty_field_slots = fields_per_u32 - (wm->field_index % fields_per_u32); + if (wm->field_index > 0 && empty_field_slots < fields_per_u32) { + uint32_t index + = wm->field_bits_start + wm->field_index / fields_per_u32; + wm->payload[index] >>= (empty_field_slots * 4); + } +} + +// Returns pointer to decls region and its length. +static const uint32_t* wipMembersDeclsSlice( + const WipMembers* wm, uint32_t* out_len) { + *out_len = wm->decl_index; + return wm->payload + wm->payload_top; +} + +// Returns pointer to fields region (field_bits + field_data) and its length. +static const uint32_t* wipMembersFieldsSlice( + const WipMembers* wm, uint32_t* out_len) { + *out_len = wm->fields_end - wm->field_bits_start; + return wm->payload + wm->field_bits_start; +} + +// Append body instructions to the WipMembers bodies scratch. +static void wipMembersBodiesAppend( + WipMembers* wm, const uint32_t* data, uint32_t len) { + if (wm->bodies_len + len > wm->bodies_cap) { + uint32_t new_cap + = wm->bodies_cap == 0 ? 64 : wm->bodies_cap * 2; + while (new_cap < wm->bodies_len + len) + new_cap *= 2; + wm->bodies = realloc(wm->bodies, new_cap * sizeof(uint32_t)); + if (!wm->bodies) + exit(1); + wm->bodies_cap = new_cap; + } + memcpy(wm->bodies + wm->bodies_len, data, len * sizeof(uint32_t)); + wm->bodies_len += len; +} + +// Append body instructions with ref_table fixups to wm->bodies. +static void wipMembersBodiesAppendWithFixups( + WipMembers* wm, AstGenCtx* ag, const uint32_t* body, uint32_t body_len) { + for (uint32_t i = 0; i < body_len; i++) { + uint32_t inst = body[i]; + // Grow if needed. + if (wm->bodies_len + 1 > wm->bodies_cap) { + uint32_t new_cap + = wm->bodies_cap == 0 ? 64 : wm->bodies_cap * 2; + wm->bodies = realloc(wm->bodies, new_cap * sizeof(uint32_t)); + if (!wm->bodies) + exit(1); + wm->bodies_cap = new_cap; + } + wm->bodies[wm->bodies_len++] = inst; + // Check for ref fixup. + uint32_t ref_inst; + while (refTableFetchRemove(ag, inst, &ref_inst)) { + if (wm->bodies_len + 1 > wm->bodies_cap) { + uint32_t new_cap = wm->bodies_cap * 2; + wm->bodies + = realloc(wm->bodies, new_cap * sizeof(uint32_t)); + if (!wm->bodies) + exit(1); + wm->bodies_cap = new_cap; + } + wm->bodies[wm->bodies_len++] = ref_inst; + inst = ref_inst; + } + } +} + // --- containerDecl (AstGen.zig:5468) --- // Handles container declarations as expressions (struct{}, enum{}, etc.). @@ -7186,7 +7472,9 @@ static uint32_t containerDecl(GenZir* gz, Scope* scope, uint32_t node) { static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, const uint32_t* members, uint32_t members_len) { + const Ast* tree = ag->tree; uint32_t decl_inst = reserveInstructionIndex(ag); + gzAppendInstruction(gz, decl_inst); // Fast path: no members, no backing int (AstGen.zig:4954-4970). if (members_len == 0) { @@ -7201,63 +7489,224 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, uint32_t decl_count = scanContainer(ag, members, members_len); uint32_t field_count = members_len - decl_count; - (void)field_count; // TODO: handle struct fields - // WipMembers: simplified to a plain array of declaration indices. - // (AstGen.zig:5031 — WipMembers.init) - uint32_t alloc_count = decl_count > 0 ? decl_count : 1; - uint32_t* wip_decl_insts = calloc(alloc_count, sizeof(uint32_t)); - if (!wip_decl_insts) - exit(1); - uint32_t decl_idx = 0; + WipMembers wm = wipMembersInit(decl_count, field_count); + + // Set up block_scope for field type/align/init expressions. + // (AstGen.zig:4983-4992) + GenZir block_scope; + memset(&block_scope, 0, sizeof(block_scope)); + block_scope.base.tag = SCOPE_GEN_ZIR; + block_scope.parent = NULL; + block_scope.astgen = ag; + block_scope.decl_node_index = node; + block_scope.decl_line = ag->source_line; + block_scope.is_comptime = true; + block_scope.instructions_top = ag->scratch_inst_len; + + bool known_non_opv = false; + bool known_comptime_only = false; + bool any_comptime_fields = false; + bool any_aligned_fields = false; + bool any_default_inits = false; // Process each member (AstGen.zig:5060-5147). for (uint32_t i = 0; i < members_len; i++) { uint32_t member_node = members[i]; - AstNodeTag tag = ag->tree->nodes.tags[member_node]; - switch (tag) { + AstNodeTag mtag = tree->nodes.tags[member_node]; + switch (mtag) { case AST_NODE_COMPTIME: - comptimeDecl(ag, gz, wip_decl_insts, &decl_idx, member_node); + comptimeDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_SIMPLE_VAR_DECL: - globalVarDecl(ag, gz, wip_decl_insts, &decl_idx, member_node); + globalVarDecl( + ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_TEST_DECL: - testDecl(ag, gz, wip_decl_insts, &decl_idx, member_node); + testDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_FN_DECL: - fnDecl(ag, gz, wip_decl_insts, &decl_idx, member_node); + fnDecl(ag, gz, 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, wip_decl_insts, &decl_idx, member_node); + globalVarDecl( + ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_CONTAINER_FIELD_INIT: case AST_NODE_CONTAINER_FIELD_ALIGN: - case AST_NODE_CONTAINER_FIELD: - // Struct fields — skip for now (counted but not emitted). + case AST_NODE_CONTAINER_FIELD: { + // Extract field info from AST node (Ast.zig:1413-1454). + 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; + bool has_comptime_token = false; + + switch (mtag) { + case AST_NODE_CONTAINER_FIELD_INIT: + // lhs = type_expr, rhs = value_expr (optional, 0=none) + value_node = nd.rhs; + break; + case AST_NODE_CONTAINER_FIELD_ALIGN: + // lhs = type_expr, rhs = align_expr + align_node = nd.rhs; + break; + case AST_NODE_CONTAINER_FIELD: + // lhs = type_expr, rhs = extra index to {align, value} + if (nd.rhs != 0) { + align_node = tree->extra_data.arr[nd.rhs]; + value_node = tree->extra_data.arr[nd.rhs + 1]; + } + break; + default: + break; + } + + // Check for comptime token preceding main_token + // (Ast.zig:2071-2082). + if (main_token > 0 + && tree->tokens.tags[main_token - 1] + == TOKEN_KEYWORD_COMPTIME) { + has_comptime_token = true; + } + + // Field name (AstGen.zig:5080). + uint32_t field_name = identAsString(ag, main_token); + wipMembersAppendToField(&wm, field_name); + + // Type expression (AstGen.zig:5089-5109). + bool have_type_body = false; + uint32_t field_type = 0; + if (type_node != 0) { + field_type = typeExpr( + &block_scope, &block_scope.base, type_node); + have_type_body = (gzInstructionsLen(&block_scope) > 0); + } + + bool have_align = (align_node != 0); + bool have_value = (value_node != 0); + bool is_comptime = has_comptime_token; + + if (is_comptime) { + any_comptime_fields = true; + } else { + // (AstGen.zig:5106-5109) + if (type_node != 0) { + known_non_opv = known_non_opv + || nodeImpliesMoreThanOnePossibleValue( + tree, type_node); + known_comptime_only = known_comptime_only + || nodeImpliesComptimeOnly(tree, type_node); + } + } + + bool field_bits[4] + = { have_align, have_value, is_comptime, have_type_body }; + wipMembersNextField(&wm, field_bits); + + if (have_type_body) { + // Emit break_inline to carry the type value + // (AstGen.zig:5097-5099). + if (!endsWithNoReturn(&block_scope)) { + makeBreakInline(&block_scope, decl_inst, field_type, + AST_NODE_OFFSET_NONE); + } + uint32_t raw_len = gzInstructionsLen(&block_scope); + const uint32_t* body = gzInstructionsSlice(&block_scope); + uint32_t body_len + = countBodyLenAfterFixups(ag, body, raw_len); + uint32_t bodies_before = wm.bodies_len; + wipMembersBodiesAppendWithFixups( + &wm, ag, body, raw_len); + (void)bodies_before; + wipMembersAppendToField(&wm, body_len); + // Reset block_scope. + ag->scratch_inst_len = block_scope.instructions_top; + } else { + wipMembersAppendToField(&wm, field_type); + } + + if (have_align) { + any_aligned_fields = true; + uint32_t align_ref = expr( + &block_scope, &block_scope.base, align_node); + if (!endsWithNoReturn(&block_scope)) { + makeBreakInline(&block_scope, decl_inst, align_ref, + AST_NODE_OFFSET_NONE); + } + uint32_t raw_len = gzInstructionsLen(&block_scope); + const uint32_t* body = gzInstructionsSlice(&block_scope); + uint32_t body_len + = countBodyLenAfterFixups(ag, body, raw_len); + wipMembersBodiesAppendWithFixups( + &wm, ag, body, raw_len); + wipMembersAppendToField(&wm, body_len); + ag->scratch_inst_len = block_scope.instructions_top; + } + + if (have_value) { + any_default_inits = true; + uint32_t default_ref = expr( + &block_scope, &block_scope.base, value_node); + if (!endsWithNoReturn(&block_scope)) { + makeBreakInline(&block_scope, decl_inst, + default_ref, AST_NODE_OFFSET_NONE); + } + uint32_t raw_len = gzInstructionsLen(&block_scope); + const uint32_t* body = gzInstructionsSlice(&block_scope); + uint32_t body_len + = countBodyLenAfterFixups(ag, body, raw_len); + wipMembersBodiesAppendWithFixups( + &wm, ag, body, raw_len); + wipMembersAppendToField(&wm, body_len); + ag->scratch_inst_len = block_scope.instructions_top; + } break; + } default: SET_ERROR(ag); break; } } + wipMembersFinishBits(&wm); + // setStruct (AstGen.zig:5152-5166). StructDeclSmall small; memset(&small, 0, sizeof(small)); small.has_decls_len = (decl_count > 0); - setStruct(ag, decl_inst, node, small, 0, 0, decl_count); + small.has_fields_len = (field_count > 0); + small.known_non_opv = known_non_opv; + small.known_comptime_only = known_comptime_only; + small.any_comptime_fields = any_comptime_fields; + small.any_default_inits = any_default_inits; + small.any_aligned_fields = any_aligned_fields; + setStruct( + ag, decl_inst, node, small, 0, field_count, decl_count); - // Append declarations list after StructDecl payload (AstGen.zig:5184). - ensureExtraCapacity(ag, decl_count); - for (uint32_t i = 0; i < decl_count; i++) { - ag->extra[ag->extra_len++] = wip_decl_insts[i]; - } + // Append: captures (none), backing_int (none), decls, fields, bodies + // (AstGen.zig:5176-5189). + 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); - free(wip_decl_insts); + ensureExtraCapacity( + ag, decls_len + fields_len + wm.bodies_len); + for (uint32_t i = 0; i < decls_len; i++) + ag->extra[ag->extra_len++] = decls_slice[i]; + for (uint32_t i = 0; i < fields_len; i++) + ag->extra[ag->extra_len++] = fields_slice[i]; + for (uint32_t i = 0; i < wm.bodies_len; i++) + ag->extra[ag->extra_len++] = wm.bodies[i]; + + gzUnstack(&block_scope); + wipMembersDeinit(&wm); return decl_inst; } diff --git a/astgen_test.zig b/astgen_test.zig index 18ed026436..d162142233 100644 --- a/astgen_test.zig +++ b/astgen_test.zig @@ -848,6 +848,66 @@ fn corpusCheck(gpa: Allocator, name: []const u8, source: [:0]const u8) !void { } } +test "astgen: struct single field" { + const gpa = std.testing.allocator; + const source: [:0]const u8 = "const T = struct { x: u32 };"; + var ref_zir = try refZir(gpa, source); + defer ref_zir.deinit(gpa); + var c_ast = c.astParse(source.ptr, @intCast(source.len)); + defer c.astDeinit(&c_ast); + var c_zir = c.astGen(&c_ast); + defer c.zirDeinit(&c_zir); + try expectEqualZir(gpa, ref_zir, c_zir); +} + +test "astgen: struct multiple fields" { + const gpa = std.testing.allocator; + const source: [:0]const u8 = "const T = struct { x: u32, y: bool };"; + var ref_zir = try refZir(gpa, source); + defer ref_zir.deinit(gpa); + var c_ast = c.astParse(source.ptr, @intCast(source.len)); + defer c.astDeinit(&c_ast); + var c_zir = c.astGen(&c_ast); + defer c.zirDeinit(&c_zir); + try expectEqualZir(gpa, ref_zir, c_zir); +} + +test "astgen: struct field with default" { + const gpa = std.testing.allocator; + const source: [:0]const u8 = "const T = struct { x: u32 = 0 };"; + var ref_zir = try refZir(gpa, source); + defer ref_zir.deinit(gpa); + var c_ast = c.astParse(source.ptr, @intCast(source.len)); + defer c.astDeinit(&c_ast); + var c_zir = c.astGen(&c_ast); + defer c.zirDeinit(&c_zir); + try expectEqualZir(gpa, ref_zir, c_zir); +} + +test "astgen: struct field with align" { + const gpa = std.testing.allocator; + const source: [:0]const u8 = "const T = struct { x: u32 align(4) };"; + var ref_zir = try refZir(gpa, source); + defer ref_zir.deinit(gpa); + var c_ast = c.astParse(source.ptr, @intCast(source.len)); + defer c.astDeinit(&c_ast); + var c_zir = c.astGen(&c_ast); + defer c.zirDeinit(&c_zir); + try expectEqualZir(gpa, ref_zir, c_zir); +} + +test "astgen: struct comptime field" { + const gpa = std.testing.allocator; + const source: [:0]const u8 = "const T = struct { comptime x: u32 = 0 };"; + var ref_zir = try refZir(gpa, source); + defer ref_zir.deinit(gpa); + var c_ast = c.astParse(source.ptr, @intCast(source.len)); + defer c.astDeinit(&c_ast); + var c_zir = c.astGen(&c_ast); + defer c.zirDeinit(&c_zir); + try expectEqualZir(gpa, ref_zir, c_zir); +} + test "astgen: corpus test_all.zig" { const gpa = std.testing.allocator; try corpusCheck(gpa, "test_all.zig", @embedFile("test_all.zig"));