stage2: Add hasWellDefinedLayout() to type.zig and Sema.zig

This follows the same strategy as sema.typeRequiresComptime() and
type.comptimeOnly(): Two versions of the function, one which performs
resolution just-in-time and another which asserts that resolution is
complete.

Thankfully, this doesn't cause very viral type resolution, since
auto-layout structs and unions are very common and are known to not have
a well-defined layout without resolving their fields.
This commit is contained in:
Cody Tapscott 2022-03-11 14:18:23 -07:00 committed by Andrew Kelley
parent bbd750ff05
commit 34a6fcd88e
3 changed files with 348 additions and 14 deletions

View File

@ -852,7 +852,7 @@ pub const ErrorSet = struct {
}
};
pub const RequiresComptime = enum { no, yes, unknown, wip };
pub const PropertyBoolean = enum { no, yes, unknown, wip };
/// Represents the data that a struct declaration provides.
pub const Struct = struct {
@ -884,7 +884,8 @@ pub const Struct = struct {
/// If false, resolving the fields is necessary to determine whether the type has only
/// one possible value.
known_non_opv: bool,
requires_comptime: RequiresComptime = .unknown,
requires_comptime: PropertyBoolean = .unknown,
has_well_defined_layout: PropertyBoolean = .unknown,
pub const Fields = std.StringArrayHashMapUnmanaged(Field);
@ -1079,6 +1080,8 @@ pub const EnumFull = struct {
/// An integer type which is used for the numerical value of the enum.
/// Whether zig chooses this type or the user specifies it, it is stored here.
tag_ty: Type,
/// true if zig inferred this tag type, false if user specified it
tag_ty_inferred: bool,
/// Set of field names in declaration order.
fields: NameMap,
/// Maps integer tag value to field index.
@ -1132,7 +1135,8 @@ pub const Union = struct {
// which `have_layout` does not ensure.
fully_resolved,
},
requires_comptime: RequiresComptime = .unknown,
requires_comptime: PropertyBoolean = .unknown,
has_well_defined_layout: PropertyBoolean = .unknown,
pub const Field = struct {
/// undefined until `status` is `have_field_types` or `have_layout`.

View File

@ -1579,6 +1579,8 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
const target = sema.mod.getTarget();
const addr_space = target_util.defaultAddressSpace(target, .local);
try sema.resolveTypeLayout(block, src, pointee_ty);
if (Air.refToIndex(ptr)) |ptr_inst| {
if (sema.air_instructions.items(.tag)[ptr_inst] == .constant) {
const air_datas = sema.air_instructions.items(.data);
@ -1885,6 +1887,7 @@ fn zirEnumDecl(
enum_obj.* = .{
.owner_decl = new_decl,
.tag_ty = Type.initTag(.@"null"),
.tag_ty_inferred = true,
.fields = .{},
.values = .{},
.node_offset = src.node_offset,
@ -1907,6 +1910,7 @@ fn zirEnumDecl(
// TODO better source location
const ty = try sema.resolveType(block, src, tag_type_ref);
enum_obj.tag_ty = try ty.copy(new_decl_arena_allocator);
enum_obj.tag_ty_inferred = false;
}
try new_decl.finalizeNewArena(&new_decl_arena);
return sema.analyzeDeclVal(block, src, new_decl);
@ -1956,16 +1960,16 @@ fn zirEnumDecl(
try wip_captures.finalize();
const tag_ty = blk: {
if (tag_type_ref != .none) {
// TODO better source location
const ty = try sema.resolveType(block, src, tag_type_ref);
break :blk try ty.copy(new_decl_arena_allocator);
}
if (tag_type_ref != .none) {
// TODO better source location
const ty = try sema.resolveType(block, src, tag_type_ref);
enum_obj.tag_ty = try ty.copy(new_decl_arena_allocator);
enum_obj.tag_ty_inferred = false;
} else {
const bits = std.math.log2_int_ceil(usize, fields_len);
break :blk try Type.Tag.int_unsigned.create(new_decl_arena_allocator, bits);
};
enum_obj.tag_ty = tag_ty;
enum_obj.tag_ty = try Type.Tag.int_unsigned.create(new_decl_arena_allocator, bits);
enum_obj.tag_ty_inferred = true;
}
}
try enum_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len);
@ -2417,13 +2421,13 @@ fn zirAllocExtended(
try sema.validateVarType(block, ty_src, var_ty, false);
}
const target = sema.mod.getTarget();
try sema.requireRuntimeBlock(block, src);
try sema.resolveTypeLayout(block, src, var_ty);
const ptr_type = try Type.ptr(sema.arena, target, .{
.pointee_type = var_ty,
.@"align" = alignment,
.@"addrspace" = target_util.defaultAddressSpace(target, .local),
});
try sema.requireRuntimeBlock(block, src);
try sema.resolveTypeLayout(block, src, var_ty);
return block.addTy(.alloc, ptr_type);
}
@ -21209,6 +21213,182 @@ fn typePtrOrOptionalPtrTy(
}
}
fn typeHasWellDefinedLayout(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!bool {
return switch (ty.tag()) {
.u1,
.u8,
.i8,
.u16,
.i16,
.u32,
.i32,
.u64,
.i64,
.u128,
.i128,
.usize,
.isize,
.c_short,
.c_ushort,
.c_int,
.c_uint,
.c_long,
.c_ulong,
.c_longlong,
.c_ulonglong,
.c_longdouble,
.f16,
.f32,
.f64,
.f80,
.f128,
.bool,
.void,
.manyptr_u8,
.manyptr_const_u8,
.manyptr_const_u8_sentinel_0,
.anyerror_void_error_union,
.empty_struct_literal,
.empty_struct,
.array_u8,
.array_u8_sentinel_0,
.int_signed,
.int_unsigned,
.pointer,
.single_const_pointer,
.single_mut_pointer,
.many_const_pointer,
.many_mut_pointer,
.c_const_pointer,
.c_mut_pointer,
.single_const_pointer_to_comptime_int,
.enum_numbered,
=> true,
.anyopaque,
.anyerror,
.noreturn,
.@"null",
.@"anyframe",
.@"undefined",
.atomic_order,
.atomic_rmw_op,
.calling_convention,
.address_space,
.float_mode,
.reduce_op,
.call_options,
.prefetch_options,
.export_options,
.extern_options,
.error_set,
.error_set_single,
.error_set_inferred,
.error_set_merged,
.@"opaque",
.generic_poison,
.type,
.comptime_int,
.comptime_float,
.enum_literal,
.type_info,
// These are function bodies, not function pointers.
.fn_noreturn_no_args,
.fn_void_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.function,
.const_slice_u8,
.const_slice_u8_sentinel_0,
.const_slice,
.mut_slice,
.enum_simple,
.error_union,
.anyframe_T,
.tuple,
.anon_struct,
=> false,
.enum_full,
.enum_nonexhaustive,
=> !ty.cast(Type.Payload.EnumFull).?.data.tag_ty_inferred,
.var_args_param => unreachable,
.inferred_alloc_mut => unreachable,
.inferred_alloc_const => unreachable,
.bound_fn => unreachable,
.array,
.array_sentinel,
.vector,
=> sema.typeHasWellDefinedLayout(block, src, ty.childType()),
.optional,
.optional_single_mut_pointer,
.optional_single_const_pointer,
=> blk: {
var buf: Type.Payload.ElemType = undefined;
break :blk sema.typeHasWellDefinedLayout(block, src, ty.optionalChild(&buf));
},
.@"struct" => {
const struct_obj = ty.castTag(.@"struct").?.data;
if (struct_obj.layout == .Auto) {
struct_obj.has_well_defined_layout = .no;
return false;
}
switch (struct_obj.has_well_defined_layout) {
.no => return false,
.yes, .wip => return true,
.unknown => {
if (struct_obj.status == .field_types_wip)
return true;
try sema.resolveTypeFieldsStruct(block, src, ty, struct_obj);
struct_obj.has_well_defined_layout = .wip;
for (struct_obj.fields.values()) |field| {
if (!(try sema.typeHasWellDefinedLayout(block, src, field.ty))) {
struct_obj.has_well_defined_layout = .no;
return false;
}
}
struct_obj.has_well_defined_layout = .yes;
return true;
},
}
},
.@"union", .union_tagged => {
const union_obj = ty.cast(Type.Payload.Union).?.data;
if (union_obj.layout == .Auto) {
union_obj.has_well_defined_layout = .no;
return false;
}
switch (union_obj.has_well_defined_layout) {
.no => return false,
.yes, .wip => return true,
.unknown => {
if (union_obj.status == .field_types_wip)
return true;
try sema.resolveTypeFieldsUnion(block, src, ty, union_obj);
union_obj.has_well_defined_layout = .wip;
for (union_obj.fields.values()) |field| {
if (!(try sema.typeHasWellDefinedLayout(block, src, field.ty))) {
union_obj.has_well_defined_layout = .no;
return false;
}
}
union_obj.has_well_defined_layout = .yes;
return true;
},
}
},
};
}
/// `generic_poison` will return false.
/// This function returns false negatives when structs and unions are having their
/// field types resolved.
@ -21412,6 +21592,12 @@ pub fn typeHasRuntimeBits(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type)
return true;
}
fn typeAbiSize(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !u64 {
try sema.resolveTypeLayout(block, src, ty);
const target = sema.mod.getTarget();
return ty.abiSize(target);
}
fn typeAbiAlignment(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !u32 {
try sema.resolveTypeLayout(block, src, ty);
const target = sema.mod.getTarget();

View File

@ -2173,6 +2173,149 @@ pub const Type = extern union {
};
}
/// true if and only if the type has a well-defined memory layout
/// readFrom/writeToMemory are supported only for types with a well-
/// defined memory layout
pub fn hasWellDefinedLayout(ty: Type) bool {
return switch (ty.tag()) {
.u1,
.u8,
.i8,
.u16,
.i16,
.u32,
.i32,
.u64,
.i64,
.u128,
.i128,
.usize,
.isize,
.c_short,
.c_ushort,
.c_int,
.c_uint,
.c_long,
.c_ulong,
.c_longlong,
.c_ulonglong,
.c_longdouble,
.f16,
.f32,
.f64,
.f80,
.f128,
.bool,
.void,
.manyptr_u8,
.manyptr_const_u8,
.manyptr_const_u8_sentinel_0,
.anyerror_void_error_union,
.empty_struct_literal,
.empty_struct,
.array_u8,
.array_u8_sentinel_0,
.int_signed,
.int_unsigned,
.pointer,
.single_const_pointer,
.single_mut_pointer,
.many_const_pointer,
.many_mut_pointer,
.c_const_pointer,
.c_mut_pointer,
.single_const_pointer_to_comptime_int,
.enum_numbered,
=> true,
.anyopaque,
.anyerror,
.noreturn,
.@"null",
.@"anyframe",
.@"undefined",
.atomic_order,
.atomic_rmw_op,
.calling_convention,
.address_space,
.float_mode,
.reduce_op,
.call_options,
.prefetch_options,
.export_options,
.extern_options,
.error_set,
.error_set_single,
.error_set_inferred,
.error_set_merged,
.@"opaque",
.generic_poison,
.type,
.comptime_int,
.comptime_float,
.enum_literal,
.type_info,
// These are function bodies, not function pointers.
.fn_noreturn_no_args,
.fn_void_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.function,
.const_slice_u8,
.const_slice_u8_sentinel_0,
.const_slice,
.mut_slice,
.enum_simple,
.error_union,
.anyframe_T,
.tuple,
.anon_struct,
=> false,
.enum_full,
.enum_nonexhaustive,
=> !ty.cast(Payload.EnumFull).?.data.tag_ty_inferred,
.var_args_param => unreachable,
.inferred_alloc_mut => unreachable,
.inferred_alloc_const => unreachable,
.bound_fn => unreachable,
.array,
.array_sentinel,
.vector,
=> ty.childType().hasWellDefinedLayout(),
.optional,
.optional_single_mut_pointer,
.optional_single_const_pointer,
=> {
var buf: Type.Payload.ElemType = undefined;
return ty.optionalChild(&buf).hasWellDefinedLayout();
},
.@"struct" => {
const struct_obj = ty.castTag(.@"struct").?.data;
if (struct_obj.layout == .Auto) return false;
switch (struct_obj.has_well_defined_layout) {
.wip, .unknown => unreachable, // This function asserts types already resolved.
.no => return false,
.yes => return true,
}
},
.@"union", .union_tagged => {
const union_obj = ty.cast(Type.Payload.Union).?.data;
if (union_obj.layout == .Auto) return false;
switch (union_obj.has_well_defined_layout) {
.wip, .unknown => unreachable, // This function asserts types already resolved.
.no => return false,
.yes => return true,
}
},
};
}
pub fn hasRuntimeBits(ty: Type) bool {
return hasRuntimeBitsAdvanced(ty, false);
}
@ -3263,6 +3406,7 @@ pub const Type = extern union {
/// For ?[*]T, returns T.
/// For *T, returns T.
/// For [*]T, returns T.
/// For [N]T, returns T.
/// For []T, returns T.
pub fn elemType2(ty: Type) Type {
return switch (ty.tag()) {