Skip to content

Commit 6249a24

Browse files
committed
stage2: integer-backed packed structs
This implements #10113 for the self-hosted compiler only. It removes the ability to override alignment of packed struct fields, and removes the ability to put pointers and arrays inside packed structs. After this commit, nearly all the behavior tests pass for the stage2 llvm backend that involve packed structs. I didn't implement the compile errors or compile error tests yet. I'm waiting until we have stage2 building itself and then I want to rework the compile error test harness with inspiration from Vexu's arocc test harness. At that point it should be a much nicer dev experience to work on compile errors.
1 parent 65c0475 commit 6249a24

File tree

13 files changed

+513
-643
lines changed

13 files changed

+513
-643
lines changed

lib/std/builtin.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ pub const TypeInfo = union(enum) {
356356
alignment: comptime_int,
357357
is_generic: bool,
358358
is_var_args: bool,
359+
/// TODO change the language spec to make this not optional.
359360
return_type: ?type,
360361
args: []const Param,
361362

lib/std/os/uefi/protocols/edid_override_protocol.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub const EdidOverrideProtocol = extern struct {
2323
};
2424

2525
pub const EdidOverrideProtocolAttributes = packed struct {
26-
dont_override: bool align(4),
26+
dont_override: bool,
2727
enable_hot_plug: bool,
2828
_pad: u30 = 0,
2929
};

src/AstGen.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4002,6 +4002,9 @@ fn structDeclInner(
40024002
wip_members.nextField(bits_per_field, .{ have_align, have_value, is_comptime, unused });
40034003

40044004
if (have_align) {
4005+
if (layout == .Packed) {
4006+
try astgen.appendErrorNode(member.ast.align_expr, "unable to override alignment of packed struct fields", .{});
4007+
}
40054008
const align_inst = try expr(&block_scope, &namespace.base, align_rl, member.ast.align_expr);
40064009
wip_members.appendToField(@enumToInt(align_inst));
40074010
}

src/Module.zig

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -886,15 +886,6 @@ pub const Struct = struct {
886886
offset: u32,
887887
is_comptime: bool,
888888

889-
/// Returns the field alignment, assuming the struct is packed.
890-
pub fn packedAlignment(field: Field) u32 {
891-
if (field.abi_align.tag() == .abi_align_default) {
892-
return 0;
893-
} else {
894-
return @intCast(u32, field.abi_align.toUnsignedInt());
895-
}
896-
}
897-
898889
/// Returns the field alignment, assuming the struct is not packed.
899890
pub fn normalAlignment(field: Field, target: Target) u32 {
900891
if (field.abi_align.tag() == .abi_align_default) {
@@ -985,6 +976,31 @@ pub const Struct = struct {
985976
=> true,
986977
};
987978
}
979+
980+
pub fn packedFieldBitOffset(s: Struct, target: Target, index: usize) u16 {
981+
assert(s.layout == .Packed);
982+
assert(s.haveFieldTypes());
983+
var bit_sum: u64 = 0;
984+
for (s.fields.values()) |field, i| {
985+
if (i == index) {
986+
return @intCast(u16, bit_sum);
987+
}
988+
bit_sum += field.ty.bitSize(target);
989+
}
990+
return @intCast(u16, bit_sum);
991+
}
992+
993+
pub fn packedIntegerBits(s: Struct, target: Target) u16 {
994+
return s.packedFieldBitOffset(target, s.fields.count());
995+
}
996+
997+
pub fn packedIntegerType(s: Struct, target: Target, buf: *Type.Payload.Bits) Type {
998+
buf.* = .{
999+
.base = .{ .tag = .int_unsigned },
1000+
.data = s.packedIntegerBits(target),
1001+
};
1002+
return Type.initPayload(&buf.base);
1003+
}
9881004
};
9891005

9901006
/// Represents the data that an enum declaration provides, when the fields

src/Sema.zig

Lines changed: 103 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -9701,7 +9701,8 @@ fn zirSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
97019701
fn zirBitSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
97029702
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
97039703
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
9704-
const operand_ty = try sema.resolveType(block, operand_src, inst_data.operand);
9704+
const unresolved_operand_ty = try sema.resolveType(block, operand_src, inst_data.operand);
9705+
const operand_ty = try sema.resolveTypeFields(block, operand_src, unresolved_operand_ty);
97059706
const target = sema.mod.getTarget();
97069707
const bit_size = operand_ty.bitSize(target);
97079708
return sema.addIntUnsigned(Type.initTag(.comptime_int), bit_size);
@@ -9891,6 +9892,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
98919892
.Fn => {
98929893
// TODO: look into memoizing this result.
98939894
const info = ty.fnInfo();
9895+
98949896
var params_anon_decl = try block.startAnonDecl(src);
98959897
defer params_anon_decl.deinit();
98969898

@@ -9948,19 +9950,24 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
99489950
break :v try Value.Tag.decl_ref.create(sema.arena, new_decl);
99499951
};
99509952

9951-
const field_values = try sema.arena.alloc(Value, 6);
9952-
// calling_convention: CallingConvention,
9953-
field_values[0] = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.cc));
9954-
// alignment: comptime_int,
9955-
field_values[1] = try Value.Tag.int_u64.create(sema.arena, ty.abiAlignment(target));
9956-
// is_generic: bool,
9957-
field_values[2] = Value.makeBool(info.is_generic);
9958-
// is_var_args: bool,
9959-
field_values[3] = Value.makeBool(info.is_var_args);
9960-
// return_type: ?type,
9961-
field_values[4] = try Value.Tag.ty.create(sema.arena, info.return_type);
9962-
// args: []const Fn.Param,
9963-
field_values[5] = args_val;
9953+
const field_values = try sema.arena.create([6]Value);
9954+
field_values.* = .{
9955+
// calling_convention: CallingConvention,
9956+
try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.cc)),
9957+
// alignment: comptime_int,
9958+
try Value.Tag.int_u64.create(sema.arena, ty.abiAlignment(target)),
9959+
// is_generic: bool,
9960+
Value.makeBool(info.is_generic),
9961+
// is_var_args: bool,
9962+
Value.makeBool(info.is_var_args),
9963+
// return_type: ?type,
9964+
try Value.Tag.opt_payload.create(
9965+
sema.arena,
9966+
try Value.Tag.ty.create(sema.arena, info.return_type),
9967+
),
9968+
// args: []const Fn.Param,
9969+
args_val,
9970+
};
99649971

99659972
return sema.addConstant(
99669973
type_info_ty,
@@ -10007,25 +10014,27 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
1000710014
const alignment = if (info.@"align" != 0)
1000810015
info.@"align"
1000910016
else
10010-
info.pointee_type.abiAlignment(target);
10011-
10012-
const field_values = try sema.arena.alloc(Value, 8);
10013-
// size: Size,
10014-
field_values[0] = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.size));
10015-
// is_const: bool,
10016-
field_values[1] = Value.makeBool(!info.mutable);
10017-
// is_volatile: bool,
10018-
field_values[2] = Value.makeBool(info.@"volatile");
10019-
// alignment: comptime_int,
10020-
field_values[3] = try Value.Tag.int_u64.create(sema.arena, alignment);
10021-
// address_space: AddressSpace
10022-
field_values[4] = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.@"addrspace"));
10023-
// child: type,
10024-
field_values[5] = try Value.Tag.ty.create(sema.arena, info.pointee_type);
10025-
// is_allowzero: bool,
10026-
field_values[6] = Value.makeBool(info.@"allowzero");
10027-
// sentinel: ?*const anyopaque,
10028-
field_values[7] = try sema.optRefValue(block, src, info.pointee_type, info.sentinel);
10017+
try sema.typeAbiAlignment(block, src, info.pointee_type);
10018+
10019+
const field_values = try sema.arena.create([8]Value);
10020+
field_values.* = .{
10021+
// size: Size,
10022+
try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.size)),
10023+
// is_const: bool,
10024+
Value.makeBool(!info.mutable),
10025+
// is_volatile: bool,
10026+
Value.makeBool(info.@"volatile"),
10027+
// alignment: comptime_int,
10028+
try Value.Tag.int_u64.create(sema.arena, alignment),
10029+
// address_space: AddressSpace
10030+
try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.@"addrspace")),
10031+
// child: type,
10032+
try Value.Tag.ty.create(sema.arena, info.pointee_type),
10033+
// is_allowzero: bool,
10034+
Value.makeBool(info.@"allowzero"),
10035+
// sentinel: ?*const anyopaque,
10036+
try sema.optRefValue(block, src, info.pointee_type, info.sentinel),
10037+
};
1002910038

1003010039
return sema.addConstant(
1003110040
type_info_ty,
@@ -10377,7 +10386,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
1037710386
const default_val_ptr = try sema.optRefValue(block, src, field.ty, opt_default_val);
1037810387
const alignment = switch (layout) {
1037910388
.Auto, .Extern => field.normalAlignment(target),
10380-
.Packed => field.packedAlignment(),
10389+
.Packed => 0,
1038110390
};
1038210391

1038310392
struct_field_fields.* = .{
@@ -12120,6 +12129,7 @@ fn zirBitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
1212012129

1212112130
fn zirOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
1212212131
const offset = try bitOffsetOf(sema, block, inst);
12132+
// TODO reminder to make this a compile error for packed structs
1212312133
return sema.addIntUnsigned(Type.comptime_int, offset / 8);
1212412134
}
1212512135

@@ -12143,7 +12153,8 @@ fn bitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!u6
1214312153
);
1214412154
}
1214512155

12146-
const index = ty.structFields().getIndex(field_name) orelse {
12156+
const fields = ty.structFields();
12157+
const index = fields.getIndex(field_name) orelse {
1214712158
return sema.fail(
1214812159
block,
1214912160
rhs_src,
@@ -12153,24 +12164,25 @@ fn bitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!u6
1215312164
};
1215412165

1215512166
const target = sema.mod.getTarget();
12156-
const layout = ty.containerLayout();
12157-
if (layout == .Packed) {
12158-
var it = ty.iteratePackedStructOffsets(target);
12159-
while (it.next()) |field_offset| {
12160-
if (field_offset.field == index) {
12161-
return (field_offset.offset * 8) + field_offset.running_bits;
12162-
}
12163-
}
12164-
} else {
12165-
var it = ty.iterateStructOffsets(target);
12166-
while (it.next()) |field_offset| {
12167-
if (field_offset.field == index) {
12168-
return field_offset.offset * 8;
12169-
}
12170-
}
12167+
switch (ty.containerLayout()) {
12168+
.Packed => {
12169+
var bit_sum: u64 = 0;
12170+
for (fields.values()) |field, i| {
12171+
if (i == index) {
12172+
return bit_sum;
12173+
}
12174+
bit_sum += field.ty.bitSize(target);
12175+
} else unreachable;
12176+
},
12177+
else => {
12178+
var it = ty.iterateStructOffsets(target);
12179+
while (it.next()) |field_offset| {
12180+
if (field_offset.field == index) {
12181+
return field_offset.offset * 8;
12182+
}
12183+
} else unreachable;
12184+
},
1217112185
}
12172-
12173-
unreachable;
1217412186
}
1217512187

1217612188
/// Returns `true` if the type was a comptime_int.
@@ -14199,61 +14211,44 @@ fn structFieldPtrByIndex(
1419914211
field_src: LazySrcLoc,
1420014212
) CompileError!Air.Inst.Ref {
1420114213
const field = struct_obj.fields.values()[field_index];
14202-
1420314214
const struct_ptr_ty = sema.typeOf(struct_ptr);
14215+
const struct_ptr_ty_info = struct_ptr_ty.ptrInfo().data;
14216+
1420414217
var ptr_ty_data: Type.Payload.Pointer.Data = .{
1420514218
.pointee_type = field.ty,
14206-
.mutable = struct_ptr_ty.ptrIsMutable(),
14207-
.@"addrspace" = struct_ptr_ty.ptrAddressSpace(),
14219+
.mutable = struct_ptr_ty_info.mutable,
14220+
.@"addrspace" = struct_ptr_ty_info.@"addrspace",
1420814221
};
14222+
1420914223
// TODO handle when the struct pointer is overaligned, we should return a potentially
1421014224
// over-aligned field pointer too.
14211-
if (struct_obj.layout == .Packed) p: {
14225+
if (struct_obj.layout == .Packed) {
1421214226
const target = sema.mod.getTarget();
14213-
comptime assert(Type.packed_struct_layout_version == 1);
14227+
comptime assert(Type.packed_struct_layout_version == 2);
1421414228

14215-
var offset: u64 = 0;
1421614229
var running_bits: u16 = 0;
1421714230
for (struct_obj.fields.values()) |f, i| {
1421814231
if (!(try sema.typeHasRuntimeBits(block, field_src, f.ty))) continue;
1421914232

14220-
const field_align = f.packedAlignment();
14221-
if (field_align == 0) {
14222-
if (i == field_index) {
14223-
ptr_ty_data.bit_offset = running_bits;
14224-
}
14225-
running_bits += @intCast(u16, f.ty.bitSize(target));
14226-
} else {
14227-
if (running_bits != 0) {
14228-
var int_payload: Type.Payload.Bits = .{
14229-
.base = .{ .tag = .int_unsigned },
14230-
.data = running_bits,
14231-
};
14232-
const int_ty: Type = .{ .ptr_otherwise = &int_payload.base };
14233-
if (i > field_index) {
14234-
ptr_ty_data.host_size = @intCast(u16, int_ty.abiSize(target));
14235-
break :p;
14236-
}
14237-
const int_align = int_ty.abiAlignment(target);
14238-
offset = std.mem.alignForwardGeneric(u64, offset, int_align);
14239-
offset += int_ty.abiSize(target);
14240-
running_bits = 0;
14241-
}
14242-
offset = std.mem.alignForwardGeneric(u64, offset, field_align);
14243-
if (i == field_index) {
14244-
break :p;
14245-
}
14246-
offset += f.ty.abiSize(target);
14233+
if (i == field_index) {
14234+
ptr_ty_data.bit_offset = running_bits;
1424714235
}
14236+
running_bits += @intCast(u16, f.ty.bitSize(target));
14237+
}
14238+
ptr_ty_data.host_size = (running_bits + 7) / 8;
14239+
14240+
// If this is a packed struct embedded in another one, we need to offset
14241+
// the bits against each other.
14242+
if (struct_ptr_ty_info.host_size != 0) {
14243+
ptr_ty_data.host_size = struct_ptr_ty_info.host_size;
14244+
ptr_ty_data.bit_offset += struct_ptr_ty_info.bit_offset;
14245+
}
14246+
} else {
14247+
if (field.abi_align.tag() != .abi_align_default) {
14248+
ptr_ty_data.@"align" = @intCast(u32, field.abi_align.toUnsignedInt());
1424814249
}
14249-
assert(running_bits != 0);
14250-
var int_payload: Type.Payload.Bits = .{
14251-
.base = .{ .tag = .int_unsigned },
14252-
.data = running_bits,
14253-
};
14254-
const int_ty: Type = .{ .ptr_otherwise = &int_payload.base };
14255-
ptr_ty_data.host_size = @intCast(u16, int_ty.abiSize(target));
1425614250
}
14251+
1425714252
const ptr_field_ty = try Type.ptr(sema.arena, ptr_ty_data);
1425814253

1425914254
if (try sema.resolveDefinedValue(block, src, struct_ptr)) |struct_ptr_val| {
@@ -15849,13 +15844,18 @@ fn beginComptimePtrLoad(
1584915844
fn bitCast(
1585015845
sema: *Sema,
1585115846
block: *Block,
15852-
dest_ty: Type,
15847+
dest_ty_unresolved: Type,
1585315848
inst: Air.Inst.Ref,
1585415849
inst_src: LazySrcLoc,
1585515850
) CompileError!Air.Inst.Ref {
15851+
const dest_ty = try sema.resolveTypeFields(block, inst_src, dest_ty_unresolved);
15852+
try sema.resolveTypeLayout(block, inst_src, dest_ty);
15853+
15854+
const old_ty = try sema.resolveTypeFields(block, inst_src, sema.typeOf(inst));
15855+
try sema.resolveTypeLayout(block, inst_src, old_ty);
15856+
1585615857
// TODO validate the type size and other compile errors
1585715858
if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| {
15858-
const old_ty = sema.typeOf(inst);
1585915859
const result_val = try sema.bitCastVal(block, inst_src, val, old_ty, dest_ty);
1586015860
return sema.addConstant(dest_ty, result_val);
1586115861
}
@@ -17506,6 +17506,9 @@ fn semaStructFields(
1750617506
// But only resolve the source location if we need to emit a compile error.
1750717507
try sema.resolveType(&block_scope, src, field_type_ref);
1750817508

17509+
// TODO emit compile errors for invalid field types
17510+
// such as arrays and pointers inside packed structs.
17511+
1750917512
const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
1751017513
assert(!gop.found_existing);
1751117514
gop.value_ptr.* = .{
@@ -18690,6 +18693,12 @@ pub fn typeHasRuntimeBits(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type)
1869018693
return true;
1869118694
}
1869218695

18696+
fn typeAbiAlignment(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !u32 {
18697+
try sema.resolveTypeLayout(block, src, ty);
18698+
const target = sema.mod.getTarget();
18699+
return ty.abiAlignment(target);
18700+
}
18701+
1869318702
/// Synchronize logic with `Type.isFnOrHasRuntimeBits`.
1869418703
pub fn fnHasRuntimeBits(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!bool {
1869518704
const fn_info = ty.fnInfo();

0 commit comments

Comments
 (0)