Merge pull request #22997 from Rexicon226/align-0-reify

sema: compile error on reifying align(0) fields and pointers
This commit is contained in:
Matthew Lugg 2025-08-03 09:58:23 +01:00 committed by GitHub
commit c80aa9f719
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 147 additions and 129 deletions

View File

@ -708,7 +708,7 @@ pub const Arguments = blk: {
field.* = .{
.name = decl.name,
.type = @field(attributes, decl.name),
.alignment = 0,
.alignment = @alignOf(@field(attributes, decl.name)),
};
}

View File

@ -939,7 +939,7 @@ fn CreateUniqueTuple(comptime N: comptime_int, comptime types: [N]type) type {
.type = T,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
.alignment = @alignOf(T),
};
}

View File

@ -5386,6 +5386,9 @@ fn unionDeclInner(
return astgen.failNode(member_node, "union field missing type", .{});
}
if (member.ast.align_expr.unwrap()) |align_expr| {
if (layout == .@"packed") {
return astgen.failNode(align_expr, "unable to override alignment of packed union fields", .{});
}
const align_inst = try expr(&block_scope, &block_scope.base, coerced_align_ri, align_expr);
wip_members.appendToField(@intFromEnum(align_inst));
any_aligned_fields = true;

View File

@ -8533,18 +8533,19 @@ pub const Metadata = enum(u32) {
.type = []const u8,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
.alignment = @alignOf([]const u8),
};
}
fmt_str = fmt_str ++ "(";
inline for (fields[2..], names) |*field, name| {
fmt_str = fmt_str ++ "{[" ++ name ++ "]f}";
const T = std.fmt.Formatter(FormatData, format);
field.* = .{
.name = name,
.type = std.fmt.Formatter(FormatData, format),
.type = T,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
.alignment = @alignOf(T),
};
}
fmt_str = fmt_str ++ ")\n";

View File

@ -1137,13 +1137,16 @@ const Local = struct {
const elem_info = @typeInfo(Elem).@"struct";
const elem_fields = elem_info.fields;
var new_fields: [elem_fields.len]std.builtin.Type.StructField = undefined;
for (&new_fields, elem_fields) |*new_field, elem_field| new_field.* = .{
.name = elem_field.name,
.type = *[len]elem_field.type,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
};
for (&new_fields, elem_fields) |*new_field, elem_field| {
const T = *[len]elem_field.type;
new_field.* = .{
.name = elem_field.name,
.type = T,
.default_value_ptr = null,
.is_comptime = false,
.alignment = @alignOf(T),
};
}
return @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &new_fields,
@ -1158,22 +1161,25 @@ const Local = struct {
const elem_info = @typeInfo(Elem).@"struct";
const elem_fields = elem_info.fields;
var new_fields: [elem_fields.len]std.builtin.Type.StructField = undefined;
for (&new_fields, elem_fields) |*new_field, elem_field| new_field.* = .{
.name = elem_field.name,
.type = @Type(.{ .pointer = .{
for (&new_fields, elem_fields) |*new_field, elem_field| {
const T = @Type(.{ .pointer = .{
.size = opts.size,
.is_const = opts.is_const,
.is_volatile = false,
.alignment = 0,
.alignment = @alignOf(elem_field.type),
.address_space = .generic,
.child = elem_field.type,
.is_allowzero = false,
.sentinel_ptr = null,
} }),
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
};
} });
new_field.* = .{
.name = elem_field.name,
.type = T,
.default_value_ptr = null,
.is_comptime = false,
.alignment = @alignOf(T),
};
}
return @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &new_fields,

View File

@ -2649,7 +2649,13 @@ pub fn analyzeAsAlign(
src: LazySrcLoc,
air_ref: Air.Inst.Ref,
) !Alignment {
const alignment_big = try sema.analyzeAsInt(block, src, air_ref, align_ty, .{ .simple = .@"align" });
const alignment_big = try sema.analyzeAsInt(
block,
src,
air_ref,
align_ty,
.{ .simple = .@"align" },
);
return sema.validateAlign(block, src, alignment_big);
}
@ -18817,7 +18823,7 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
const abi_align: Alignment = if (inst_data.flags.has_align) blk: {
const ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_i]);
extra_i += 1;
const coerced = try sema.coerce(block, .u32, try sema.resolveInst(ref), align_src);
const coerced = try sema.coerce(block, align_ty, try sema.resolveInst(ref), align_src);
const val = try sema.resolveConstDefinedValue(block, align_src, coerced, .{ .simple = .@"align" });
// Check if this happens to be the lazy alignment of our element type, in
// which case we can make this 0 without resolving it.
@ -20335,15 +20341,11 @@ fn zirReify(
try ip.getOrPutString(gpa, pt.tid, "sentinel_ptr", .no_embedded_nulls),
).?);
if (!try sema.intFitsInType(alignment_val, .u32, null)) {
return sema.fail(block, src, "alignment must fit in 'u32'", .{});
if (!try sema.intFitsInType(alignment_val, align_ty, null)) {
return sema.fail(block, src, "alignment must fit in '{f}'", .{align_ty.fmt(pt)});
}
const alignment_val_int = try alignment_val.toUnsignedIntSema(pt);
if (alignment_val_int > 0 and !math.isPowerOfTwo(alignment_val_int)) {
return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{alignment_val_int});
}
const abi_align = Alignment.fromByteUnits(alignment_val_int);
const abi_align = try sema.validateAlign(block, src, alignment_val_int);
const elem_ty = child_val.toType();
if (abi_align != .none) {
@ -20920,8 +20922,6 @@ fn reifyUnion(
std.hash.autoHash(&hasher, opt_tag_type_val.toIntern());
std.hash.autoHash(&hasher, fields_len);
var any_aligns = false;
for (0..fields_len) |field_idx| {
const field_info = try fields_val.elemValue(pt, field_idx);
@ -20930,16 +20930,11 @@ fn reifyUnion(
const field_align_val = try sema.resolveLazyValue(try field_info.fieldValue(pt, 2));
const field_name = try sema.sliceToIpString(block, src, field_name_val, .{ .simple = .union_field_name });
std.hash.autoHash(&hasher, .{
field_name,
field_type_val.toIntern(),
field_align_val.toIntern(),
});
if (field_align_val.toUnsignedInt(zcu) != 0) {
any_aligns = true;
}
}
const tracked_inst = try block.trackZir(inst);
@ -20956,7 +20951,7 @@ fn reifyUnion(
true => .safety,
false => .none,
},
.any_aligned_fields = any_aligns,
.any_aligned_fields = layout != .@"packed",
.requires_comptime = .unknown,
.assumed_runtime_bits = false,
.assumed_pointer_aligned = false,
@ -20989,8 +20984,7 @@ fn reifyUnion(
);
wip_ty.setName(ip, type_name.name, type_name.nav);
const field_types = try sema.arena.alloc(InternPool.Index, fields_len);
const field_aligns = if (any_aligns) try sema.arena.alloc(InternPool.Alignment, fields_len) else undefined;
const loaded_union = ip.loadUnionType(wip_ty.index);
const enum_tag_ty, const has_explicit_tag = if (opt_tag_type_val.optionalValue(zcu)) |tag_type_val| tag_ty: {
switch (ip.indexToKey(tag_type_val.toIntern())) {
@ -21003,11 +20997,12 @@ fn reifyUnion(
const tag_ty_fields_len = enum_tag_ty.enumFieldCount(zcu);
var seen_tags = try std.DynamicBitSetUnmanaged.initEmpty(sema.arena, tag_ty_fields_len);
for (field_types, 0..) |*field_ty, field_idx| {
for (0..fields_len) |field_idx| {
const field_info = try fields_val.elemValue(pt, field_idx);
const field_name_val = try field_info.fieldValue(pt, 0);
const field_type_val = try field_info.fieldValue(pt, 1);
const field_alignment_val = try field_info.fieldValue(pt, 2);
// Don't pass a reason; first loop acts as an assertion that this is valid.
const field_name = try sema.sliceToIpString(block, src, field_name_val, undefined);
@ -21024,14 +21019,12 @@ fn reifyUnion(
}
seen_tags.set(enum_index);
field_ty.* = field_type_val.toIntern();
if (any_aligns) {
const byte_align = try (try field_info.fieldValue(pt, 2)).toUnsignedIntSema(pt);
if (byte_align > 0 and !math.isPowerOfTwo(byte_align)) {
// TODO: better source location
return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{byte_align});
}
field_aligns[field_idx] = Alignment.fromByteUnits(byte_align);
loaded_union.field_types.get(ip)[field_idx] = field_type_val.toIntern();
const byte_align = try field_alignment_val.toUnsignedIntSema(pt);
if (layout == .@"packed") {
if (byte_align != 0) return sema.fail(block, src, "alignment of a packed union field must be set to 0", .{});
} else {
loaded_union.field_aligns.get(ip)[field_idx] = try sema.validateAlign(block, src, byte_align);
}
}
@ -21055,11 +21048,12 @@ fn reifyUnion(
var field_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .empty;
try field_names.ensureTotalCapacity(sema.arena, fields_len);
for (field_types, 0..) |*field_ty, field_idx| {
for (0..fields_len) |field_idx| {
const field_info = try fields_val.elemValue(pt, field_idx);
const field_name_val = try field_info.fieldValue(pt, 0);
const field_type_val = try field_info.fieldValue(pt, 1);
const field_alignment_val = try field_info.fieldValue(pt, 2);
// Don't pass a reason; first loop acts as an assertion that this is valid.
const field_name = try sema.sliceToIpString(block, src, field_name_val, undefined);
@ -21069,14 +21063,12 @@ fn reifyUnion(
return sema.fail(block, src, "duplicate union field {f}", .{field_name.fmt(ip)});
}
field_ty.* = field_type_val.toIntern();
if (any_aligns) {
const byte_align = try (try field_info.fieldValue(pt, 2)).toUnsignedIntSema(pt);
if (byte_align > 0 and !math.isPowerOfTwo(byte_align)) {
// TODO: better source location
return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{byte_align});
}
field_aligns[field_idx] = Alignment.fromByteUnits(byte_align);
loaded_union.field_types.get(ip)[field_idx] = field_type_val.toIntern();
const byte_align = try field_alignment_val.toUnsignedIntSema(pt);
if (layout == .@"packed") {
if (byte_align != 0) return sema.fail(block, src, "alignment of a packed union field must be set to 0", .{});
} else {
loaded_union.field_aligns.get(ip)[field_idx] = try sema.validateAlign(block, src, byte_align);
}
}
@ -21085,7 +21077,7 @@ fn reifyUnion(
};
errdefer if (!has_explicit_tag) ip.remove(pt.tid, enum_tag_ty); // remove generated tag type on error
for (field_types) |field_ty_ip| {
for (loaded_union.field_types.get(ip)) |field_ty_ip| {
const field_ty: Type = .fromInterned(field_ty_ip);
if (field_ty.zigTypeTag(zcu) == .@"opaque") {
return sema.failWithOwnedErrorMsg(block, msg: {
@ -21119,11 +21111,6 @@ fn reifyUnion(
}
}
const loaded_union = ip.loadUnionType(wip_ty.index);
loaded_union.setFieldTypes(ip, field_types);
if (any_aligns) {
loaded_union.setFieldAligns(ip, field_aligns);
}
loaded_union.setTagType(ip, enum_tag_ty);
loaded_union.setStatus(ip, .have_field_types);
@ -21276,7 +21263,6 @@ fn reifyStruct(
var any_comptime_fields = false;
var any_default_inits = false;
var any_aligned_fields = false;
for (0..fields_len) |field_idx| {
const field_info = try fields_val.elemValue(pt, field_idx);
@ -21311,11 +21297,6 @@ fn reifyStruct(
if (field_is_comptime) any_comptime_fields = true;
if (field_default_value != .none) any_default_inits = true;
switch (try field_alignment_val.orderAgainstZeroSema(pt)) {
.eq => {},
.gt => any_aligned_fields = true,
.lt => unreachable,
}
}
const tracked_inst = try block.trackZir(inst);
@ -21327,7 +21308,7 @@ fn reifyStruct(
.requires_comptime = .unknown,
.any_comptime_fields = any_comptime_fields,
.any_default_inits = any_default_inits,
.any_aligned_fields = any_aligned_fields,
.any_aligned_fields = layout != .@"packed",
.inits_resolved = true,
.key = .{ .reified = .{
.zir_index = tracked_inst,
@ -21371,21 +21352,14 @@ fn reifyStruct(
return sema.fail(block, src, "duplicate struct field name {f}", .{field_name.fmt(ip)});
}
if (any_aligned_fields) {
if (!try sema.intFitsInType(field_alignment_val, .u32, null)) {
return sema.fail(block, src, "alignment must fit in 'u32'", .{});
}
const byte_align = try field_alignment_val.toUnsignedIntSema(pt);
if (byte_align == 0) {
if (layout != .@"packed") {
struct_type.field_aligns.get(ip)[field_idx] = .none;
}
} else {
if (layout == .@"packed") return sema.fail(block, src, "alignment in a packed struct field must be set to 0", .{});
if (!math.isPowerOfTwo(byte_align)) return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{byte_align});
struct_type.field_aligns.get(ip)[field_idx] = Alignment.fromNonzeroByteUnits(byte_align);
}
if (!try sema.intFitsInType(field_alignment_val, align_ty, null)) {
return sema.fail(block, src, "alignment must fit in '{f}'", .{align_ty.fmt(pt)});
}
const byte_align = try field_alignment_val.toUnsignedIntSema(pt);
if (layout == .@"packed") {
if (byte_align != 0) return sema.fail(block, src, "alignment of a packed struct field must be set to 0", .{});
} else {
struct_type.field_aligns.get(ip)[field_idx] = try sema.validateAlign(block, src, byte_align);
}
const field_is_comptime = field_is_comptime_val.toBool();

View File

@ -33,13 +33,16 @@ pub fn nextInstruction(as: *Assemble) !?Instruction {
var symbols: Symbols: {
const symbols = @typeInfo(@TypeOf(instruction.symbols)).@"struct".fields;
var symbol_fields: [symbols.len]std.builtin.Type.StructField = undefined;
for (&symbol_fields, symbols) |*symbol_field, symbol| symbol_field.* = .{
.name = symbol.name,
.type = zonCast(SymbolSpec, @field(instruction.symbols, symbol.name), .{}).Storage(),
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
};
for (&symbol_fields, symbols) |*symbol_field, symbol| {
const Storage = zonCast(SymbolSpec, @field(instruction.symbols, symbol.name), .{}).Storage();
symbol_field.* = .{
.name = symbol.name,
.type = Storage,
.default_value_ptr = null,
.is_comptime = false,
.alignment = @alignOf(Storage),
};
}
break :Symbols @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &symbol_fields,

View File

@ -318,6 +318,8 @@ test "tuple type with void field" {
test "zero sized struct in tuple handled correctly" {
const State = struct {
const Self = @This();
const Inner = struct {};
data: @Type(.{
.@"struct" = .{
.is_tuple = true,
@ -325,10 +327,10 @@ test "zero sized struct in tuple handled correctly" {
.decls = &.{},
.fields = &.{.{
.name = "0",
.type = struct {},
.type = Inner,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
.alignment = @alignOf(Inner),
}},
},
}),

View File

@ -433,8 +433,8 @@ test "Type.Union" {
.layout = .@"packed",
.tag_type = null,
.fields = &.{
.{ .name = "signed", .type = i32, .alignment = @alignOf(i32) },
.{ .name = "unsigned", .type = u32, .alignment = @alignOf(u32) },
.{ .name = "signed", .type = i32, .alignment = 0 },
.{ .name = "unsigned", .type = u32, .alignment = 0 },
},
.decls = &.{},
},
@ -735,7 +735,7 @@ test "struct field names sliced at comptime from larger string" {
var it = std.mem.tokenizeScalar(u8, text, '\n');
while (it.next()) |name| {
fields = fields ++ &[_]Type.StructField{.{
.alignment = 0,
.alignment = @alignOf(usize),
.name = name ++ "",
.type = usize,
.default_value_ptr = null,

View File

@ -1,52 +1,80 @@
pub var global_var: i32 align(0) = undefined;
var global_var: i32 align(0) = undefined;
pub export fn a() void {
export fn a() void {
_ = &global_var;
}
pub extern var extern_var: i32 align(0);
extern var extern_var: i32 align(0);
pub export fn b() void {
export fn b() void {
_ = &extern_var;
}
pub export fn c() align(0) void {}
export fn c() align(0) void {}
pub export fn d() void {
export fn d() void {
_ = *align(0) fn () i32;
}
pub export fn e() void {
export fn e() void {
var local_var: i32 align(0) = undefined;
_ = &local_var;
}
pub export fn f() void {
export fn f() void {
_ = *align(0) i32;
}
pub export fn g() void {
export fn g() void {
_ = []align(0) i32;
}
pub export fn h() void {
export fn h() void {
_ = struct { field: i32 align(0) };
}
pub export fn i() void {
export fn i() void {
_ = union { field: i32 align(0) };
}
export fn j() void {
_ = @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &.{.{
.name = "test",
.type = u32,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
}},
.decls = &.{},
.is_tuple = false,
} });
}
export fn k() void {
_ = @Type(.{ .pointer = .{
.size = .one,
.is_const = false,
.is_volatile = false,
.alignment = 0,
.address_space = .generic,
.child = u32,
.is_allowzero = false,
.sentinel_ptr = null,
} });
}
// error
// backend=stage2
// target=native
//
// :1:31: error: alignment must be >= 1
// :7:38: error: alignment must be >= 1
// :13:25: error: alignment must be >= 1
// :1:27: error: alignment must be >= 1
// :7:34: error: alignment must be >= 1
// :13:21: error: alignment must be >= 1
// :16:16: error: alignment must be >= 1
// :20:30: error: alignment must be >= 1
// :25:16: error: alignment must be >= 1
// :29:17: error: alignment must be >= 1
// :33:35: error: alignment must be >= 1
// :37:34: error: alignment must be >= 1
// :41:9: error: alignment must be >= 1
// :56:9: error: alignment must be >= 1

View File

@ -11,5 +11,5 @@ export fn entry2() void {
// backend=stage2
// target=native
//
// :2:22: error: expected type 'u32', found 'bool'
// :6:21: error: fractional component prevents float value '12.34' from coercion to type 'u32'
// :2:22: error: expected type 'u29', found 'bool'
// :6:21: error: fractional component prevents float value '12.34' from coercion to type 'u29'

View File

@ -1,9 +0,0 @@
export fn entry() void {
_ = @Type(.{ .@"struct" = .{ .layout = .@"packed", .fields = &.{
.{ .name = "one", .type = u4, .default_value_ptr = null, .is_comptime = false, .alignment = 2 },
}, .decls = &.{}, .is_tuple = false } });
}
// error
//
// :2:9: error: alignment in a packed struct field must be set to 0

View File

@ -0,0 +1,9 @@
const U = packed union {
x: f32,
y: u8 align(10),
z: u32,
};
// error
//
// :3:17: error: unable to override alignment of packed union fields

View File

@ -75,4 +75,5 @@ comptime {
// :16:5: error: tuple field name '3' does not match field index 0
// :30:5: error: comptime field without default initialization value
// :44:5: error: extern struct fields cannot be marked comptime
// :58:5: error: alignment in a packed struct field must be set to 0
// :58:5: error: alignment of a packed struct field must be set to 0

View File

@ -43,6 +43,6 @@ comptime {
// error
//
// :2:9: error: alignment value '3' is not a power of two or zero
// :14:9: error: alignment value '5' is not a power of two or zero
// :30:9: error: alignment value '7' is not a power of two or zero
// :2:9: error: alignment value '3' is not a power of two
// :14:9: error: alignment value '5' is not a power of two
// :30:9: error: alignment value '7' is not a power of two