Commit 34a6fcd88e

Cody Tapscott <topolarity@tapscott.me>
2022-03-11 22:18:23
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.
1 parent bbd750f
Changed files (3)
src/Module.zig
@@ -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`.
src/Sema.zig
@@ -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();
src/type.zig
@@ -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()) {