Sema: optimize typeHasOnePossibleValue

This commit is contained in:
Jacob Young 2023-06-03 20:22:05 -04:00
parent 3c4b58cea7
commit ad3e0e4eb4
4 changed files with 332 additions and 219 deletions

View File

@ -850,6 +850,8 @@ pub const Inst = struct {
pub const Index = u32;
pub const Ref = enum(u32) {
u0_type = @intFromEnum(InternPool.Index.u0_type),
i0_type = @intFromEnum(InternPool.Index.i0_type),
u1_type = @intFromEnum(InternPool.Index.u1_type),
u8_type = @intFromEnum(InternPool.Index.u8_type),
i8_type = @intFromEnum(InternPool.Index.i8_type),
@ -909,6 +911,7 @@ pub const Inst = struct {
single_const_pointer_to_comptime_int_type = @intFromEnum(InternPool.Index.single_const_pointer_to_comptime_int_type),
slice_const_u8_type = @intFromEnum(InternPool.Index.slice_const_u8_type),
slice_const_u8_sentinel_0_type = @intFromEnum(InternPool.Index.slice_const_u8_sentinel_0_type),
optional_noreturn_type = @intFromEnum(InternPool.Index.optional_noreturn_type),
anyerror_void_error_union_type = @intFromEnum(InternPool.Index.anyerror_void_error_union_type),
generic_poison_type = @intFromEnum(InternPool.Index.generic_poison_type),
empty_struct_type = @intFromEnum(InternPool.Index.empty_struct_type),

View File

@ -1243,11 +1243,13 @@ pub const Item = struct {
/// When adding a tag to this enum, consider adding a corresponding entry to
/// `primitives` in AstGen.zig.
pub const Index = enum(u32) {
pub const first_type: Index = .u1_type;
pub const first_type: Index = .u0_type;
pub const last_type: Index = .empty_struct_type;
pub const first_value: Index = .undef;
pub const last_value: Index = .empty_struct;
u0_type,
i0_type,
u1_type,
u8_type,
i8_type,
@ -1307,6 +1309,7 @@ pub const Index = enum(u32) {
single_const_pointer_to_comptime_int_type,
slice_const_u8_type,
slice_const_u8_sentinel_0_type,
optional_noreturn_type,
anyerror_void_error_union_type,
generic_poison_type,
/// `@TypeOf(.{})`
@ -1533,6 +1536,16 @@ pub const Index = enum(u32) {
};
pub const static_keys = [_]Key{
.{ .int_type = .{
.signedness = .unsigned,
.bits = 0,
} },
.{ .int_type = .{
.signedness = .signed,
.bits = 0,
} },
.{ .int_type = .{
.signedness = .unsigned,
.bits = 1,
@ -1639,6 +1652,7 @@ pub const static_keys = [_]Key{
.{ .simple_type = .extern_options },
.{ .simple_type = .type_info },
// [*]u8
.{ .ptr_type = .{
.child = .u8_type,
.flags = .{
@ -1646,7 +1660,7 @@ pub const static_keys = [_]Key{
},
} },
// manyptr_const_u8_type
// [*]const u8
.{ .ptr_type = .{
.child = .u8_type,
.flags = .{
@ -1655,7 +1669,7 @@ pub const static_keys = [_]Key{
},
} },
// manyptr_const_u8_sentinel_0_type
// [*:0]const u8
.{ .ptr_type = .{
.child = .u8_type,
.sentinel = .zero_u8,
@ -1665,6 +1679,7 @@ pub const static_keys = [_]Key{
},
} },
// comptime_int
.{ .ptr_type = .{
.child = .comptime_int_type,
.flags = .{
@ -1673,7 +1688,7 @@ pub const static_keys = [_]Key{
},
} },
// slice_const_u8_type
// []const u8
.{ .ptr_type = .{
.child = .u8_type,
.flags = .{
@ -1682,7 +1697,7 @@ pub const static_keys = [_]Key{
},
} },
// slice_const_u8_sentinel_0_type
// [:0]const u8
.{ .ptr_type = .{
.child = .u8_type,
.sentinel = .zero_u8,
@ -1692,7 +1707,10 @@ pub const static_keys = [_]Key{
},
} },
// anyerror_void_error_union_type
// ?noreturn
.{ .opt_type = .noreturn_type },
// anyerror!void
.{ .error_union_type = .{
.error_set_type = .anyerror_type,
.payload_type = .void_type,
@ -5465,6 +5483,8 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index {
// An alternative would be to topological sort the static keys, but this would
// mean that the range of type indices would not be dense.
return switch (index) {
.u0_type,
.i0_type,
.u1_type,
.u8_type,
.i8_type,
@ -5524,6 +5544,7 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index {
.single_const_pointer_to_comptime_int_type,
.slice_const_u8_type,
.slice_const_u8_sentinel_0_type,
.optional_noreturn_type,
.anyerror_void_error_union_type,
.generic_poison_type,
.empty_struct_type,
@ -5685,6 +5706,8 @@ pub fn isNoReturn(ip: *const InternPool, ty: Index) bool {
/// rather than the more straightforward implementation of calling `indexToKey`.
pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPoison}!std.builtin.TypeId {
return switch (index) {
.u0_type,
.i0_type,
.u1_type,
.u8_type,
.i8_type,
@ -5756,6 +5779,7 @@ pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPois
.slice_const_u8_sentinel_0_type,
=> .Pointer,
.optional_noreturn_type => .Optional,
.anyerror_void_error_union_type => .ErrorUnion,
.empty_struct_type => .Struct,

View File

@ -33749,6 +33749,8 @@ pub fn resolveTypeFields(sema: *Sema, ty: Type) CompileError!Type {
.none => unreachable,
.u0_type,
.i0_type,
.u1_type,
.u8_type,
.i8_type,
@ -33797,6 +33799,7 @@ pub fn resolveTypeFields(sema: *Sema, ty: Type) CompileError!Type {
.single_const_pointer_to_comptime_int_type,
.slice_const_u8_type,
.slice_const_u8_sentinel_0_type,
.optional_noreturn_type,
.anyerror_void_error_union_type,
.generic_poison_type,
.empty_struct_type,
@ -34935,231 +34938,311 @@ fn getBuiltinType(sema: *Sema, name: []const u8) CompileError!Type {
pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
const mod = sema.mod;
return switch (ty.toIntern()) {
.u0_type,
.i0_type,
=> try mod.intValue(ty, 0),
.u1_type,
.u8_type,
.i8_type,
.u16_type,
.i16_type,
.u29_type,
.u32_type,
.i32_type,
.u64_type,
.i64_type,
.u80_type,
.u128_type,
.i128_type,
.usize_type,
.isize_type,
.c_char_type,
.c_short_type,
.c_ushort_type,
.c_int_type,
.c_uint_type,
.c_long_type,
.c_ulong_type,
.c_longlong_type,
.c_ulonglong_type,
.c_longdouble_type,
.f16_type,
.f32_type,
.f64_type,
.f80_type,
.f128_type,
.anyopaque_type,
.bool_type,
.type_type,
.anyerror_type,
.comptime_int_type,
.comptime_float_type,
.enum_literal_type,
.atomic_order_type,
.atomic_rmw_op_type,
.calling_convention_type,
.address_space_type,
.float_mode_type,
.reduce_op_type,
.call_modifier_type,
.prefetch_options_type,
.export_options_type,
.extern_options_type,
.type_info_type,
.manyptr_u8_type,
.manyptr_const_u8_type,
.manyptr_const_u8_sentinel_0_type,
.single_const_pointer_to_comptime_int_type,
.slice_const_u8_type,
.slice_const_u8_sentinel_0_type,
.anyerror_void_error_union_type,
=> null,
.void_type => Value.void,
.noreturn_type => Value.@"unreachable",
.anyframe_type => unreachable,
.null_type => Value.null,
.undefined_type => Value.undef,
.optional_noreturn_type => try mod.nullValue(ty),
.generic_poison_type => error.GenericPoison,
.empty_struct_type => Value.empty_struct,
else => switch (mod.intern_pool.indexToKey(ty.toIntern())) {
.int_type => |int_type| {
if (int_type.bits == 0) {
return try mod.intValue(ty, 0);
} else {
return null;
}
},
.ptr_type,
.error_union_type,
.func_type,
.anyframe_type,
.error_set_type,
.inferred_error_set_type,
// values, not types
.undef,
.zero,
.zero_usize,
.zero_u8,
.one,
.one_usize,
.one_u8,
.four_u8,
.negative_one,
.calling_convention_c,
.calling_convention_inline,
.void_value,
.unreachable_value,
.null_value,
.bool_true,
.bool_false,
.empty_struct,
.generic_poison,
// invalid
.var_args_param_type,
.none,
=> unreachable,
_ => switch (mod.intern_pool.items.items(.tag)[@intFromEnum(ty.toIntern())]) {
.type_int_signed, // i0 handled above
.type_int_unsigned, // u0 handled above
.type_pointer,
.type_slice,
.type_optional, // ?noreturn handled above
.type_anyframe,
.type_error_union,
.type_error_set,
.type_inferred_error_set,
.type_opaque,
.type_function,
=> null,
inline .array_type, .vector_type => |seq_type, seq_tag| {
const has_sentinel = seq_tag == .array_type and seq_type.sentinel != .none;
if (seq_type.len + @intFromBool(has_sentinel) == 0) return (try mod.intern(.{ .aggregate = .{
.ty = ty.toIntern(),
.storage = .{ .elems = &.{} },
} })).toValue();
if (try sema.typeHasOnePossibleValue(seq_type.child.toType())) |opv| {
return (try mod.intern(.{ .aggregate = .{
.ty = ty.toIntern(),
.storage = .{ .repeated_elem = opv.toIntern() },
} })).toValue();
}
return null;
},
.opt_type => |child| {
if (child == .noreturn_type) {
return try mod.nullValue(ty);
} else {
return null;
}
},
.simple_type => |t| switch (t) {
.f16,
.f32,
.f64,
.f80,
.f128,
.usize,
.isize,
.c_char,
.c_short,
.c_ushort,
.c_int,
.c_uint,
.c_long,
.c_ulong,
.c_longlong,
.c_ulonglong,
.c_longdouble,
.anyopaque,
.bool,
.type,
.anyerror,
.comptime_int,
.comptime_float,
.enum_literal,
.atomic_order,
.atomic_rmw_op,
.calling_convention,
.address_space,
.float_mode,
.reduce_op,
.call_modifier,
.prefetch_options,
.export_options,
.extern_options,
.type_info,
=> null,
.void => Value.void,
.noreturn => Value.@"unreachable",
.null => Value.null,
.undefined => Value.undef,
.generic_poison => return error.GenericPoison,
},
.struct_type => |struct_type| {
const resolved_ty = try sema.resolveTypeFields(ty);
if (mod.structPtrUnwrap(struct_type.index)) |s| {
const field_vals = try sema.arena.alloc(InternPool.Index, s.fields.count());
for (field_vals, s.fields.values(), 0..) |*field_val, field, i| {
if (field.is_comptime) {
field_val.* = field.default_val;
continue;
}
if (field.ty.eql(resolved_ty, sema.mod)) {
const msg = try Module.ErrorMsg.create(
sema.gpa,
s.srcLoc(sema.mod),
"struct '{}' depends on itself",
.{ty.fmt(sema.mod)},
);
try sema.addFieldErrNote(resolved_ty, i, msg, "while checking this field", .{});
return sema.failWithOwnedErrorMsg(msg);
}
if (try sema.typeHasOnePossibleValue(field.ty)) |field_opv| {
field_val.* = try field_opv.intern(field.ty, mod);
} else return null;
}
// In this case the struct has no runtime-known fields and
// therefore has one possible value.
return (try mod.intern(.{ .aggregate = .{
.ty = ty.toIntern(),
.storage = .{ .elems = field_vals },
} })).toValue();
}
// In this case the struct has no fields at all and
// therefore has one possible value.
return (try mod.intern(.{ .aggregate = .{
.ty = ty.toIntern(),
.storage = .{ .elems = &.{} },
} })).toValue();
},
.anon_struct_type => |tuple| {
for (tuple.values) |val| {
if (val == .none) return null;
}
// In this case the struct has all comptime-known fields and
// therefore has one possible value.
// TODO: write something like getCoercedInts to avoid needing to dupe
return (try mod.intern(.{ .aggregate = .{
.ty = ty.toIntern(),
.storage = .{ .elems = try sema.arena.dupe(InternPool.Index, tuple.values) },
} })).toValue();
},
.union_type => |union_type| {
const resolved_ty = try sema.resolveTypeFields(ty);
const union_obj = mod.unionPtr(union_type.index);
const tag_val = (try sema.typeHasOnePossibleValue(union_obj.tag_ty)) orelse
return null;
const fields = union_obj.fields.values();
if (fields.len == 0) {
const only = try mod.intern(.{ .empty_enum_value = ty.toIntern() });
return only.toValue();
}
const only_field = fields[0];
if (only_field.ty.eql(resolved_ty, sema.mod)) {
const msg = try Module.ErrorMsg.create(
sema.gpa,
union_obj.srcLoc(sema.mod),
"union '{}' depends on itself",
.{ty.fmt(sema.mod)},
);
try sema.addFieldErrNote(resolved_ty, 0, msg, "while checking this field", .{});
return sema.failWithOwnedErrorMsg(msg);
}
const val_val = (try sema.typeHasOnePossibleValue(only_field.ty)) orelse
return null;
const only = try mod.intern(.{ .un = .{
.ty = resolved_ty.toIntern(),
.tag = tag_val.toIntern(),
.val = val_val.toIntern(),
} });
return only.toValue();
},
.opaque_type => null,
.enum_type => |enum_type| switch (enum_type.tag_mode) {
.nonexhaustive => {
if (enum_type.tag_ty == .comptime_int_type) return null;
if (try sema.typeHasOnePossibleValue(enum_type.tag_ty.toType())) |int_opv| {
const only = try mod.intern(.{ .enum_tag = .{
.ty = ty.toIntern(),
.int = int_opv.toIntern(),
} });
return only.toValue();
}
return null;
},
.auto, .explicit => {
if (enum_type.tag_ty.toType().hasRuntimeBits(mod)) return null;
switch (enum_type.names.len) {
0 => {
const only = try mod.intern(.{ .empty_enum_value = ty.toIntern() });
return only.toValue();
},
1 => return try mod.getCoerced((if (enum_type.values.len == 0)
try mod.intern(.{ .int = .{
.ty = enum_type.tag_ty,
.storage = .{ .u64 = 0 },
} })
else
enum_type.values[0]).toValue(), ty),
else => return null,
}
},
},
.simple_type, // handled above
// values, not types
.undef,
.runtime_value,
.simple_value,
.ptr_decl,
.ptr_mut_decl,
.ptr_comptime_field,
.ptr_int,
.ptr_eu_payload,
.ptr_opt_payload,
.ptr_elem,
.ptr_field,
.ptr_slice,
.opt_payload,
.opt_null,
.int_u8,
.int_u16,
.int_u32,
.int_i32,
.int_usize,
.int_comptime_int_u32,
.int_comptime_int_i32,
.int_small,
.int_positive,
.int_negative,
.int_lazy_align,
.int_lazy_size,
.error_set_error,
.error_union_error,
.error_union_payload,
.enum_literal,
.enum_tag,
.float_f16,
.float_f32,
.float_f64,
.float_f80,
.float_f128,
.float_c_longdouble_f80,
.float_c_longdouble_f128,
.float_comptime_float,
.variable,
.extern_func,
.func,
.int,
.err,
.error_union,
.enum_literal,
.enum_tag,
.empty_enum_value,
.float,
.ptr,
.opt,
.only_possible_value,
.union_value,
.bytes,
.aggregate,
.un,
// memoization, not types
.repeated,
// memoized value, not types
.memoized_call,
=> unreachable,
.type_array_big,
.type_array_small,
.type_vector,
.type_enum_auto,
.type_enum_explicit,
.type_enum_nonexhaustive,
.type_struct,
.type_struct_ns,
.type_struct_anon,
.type_tuple_anon,
.type_union_tagged,
.type_union_untagged,
.type_union_safety,
=> switch (mod.intern_pool.indexToKey(ty.toIntern())) {
inline .array_type, .vector_type => |seq_type, seq_tag| {
const has_sentinel = seq_tag == .array_type and seq_type.sentinel != .none;
if (seq_type.len + @intFromBool(has_sentinel) == 0) return (try mod.intern(.{ .aggregate = .{
.ty = ty.toIntern(),
.storage = .{ .elems = &.{} },
} })).toValue();
if (try sema.typeHasOnePossibleValue(seq_type.child.toType())) |opv| {
return (try mod.intern(.{ .aggregate = .{
.ty = ty.toIntern(),
.storage = .{ .repeated_elem = opv.toIntern() },
} })).toValue();
}
return null;
},
.struct_type => |struct_type| {
const resolved_ty = try sema.resolveTypeFields(ty);
if (mod.structPtrUnwrap(struct_type.index)) |s| {
const field_vals = try sema.arena.alloc(InternPool.Index, s.fields.count());
for (field_vals, s.fields.values(), 0..) |*field_val, field, i| {
if (field.is_comptime) {
field_val.* = field.default_val;
continue;
}
if (field.ty.eql(resolved_ty, sema.mod)) {
const msg = try Module.ErrorMsg.create(
sema.gpa,
s.srcLoc(sema.mod),
"struct '{}' depends on itself",
.{ty.fmt(sema.mod)},
);
try sema.addFieldErrNote(resolved_ty, i, msg, "while checking this field", .{});
return sema.failWithOwnedErrorMsg(msg);
}
if (try sema.typeHasOnePossibleValue(field.ty)) |field_opv| {
field_val.* = try field_opv.intern(field.ty, mod);
} else return null;
}
// In this case the struct has no runtime-known fields and
// therefore has one possible value.
return (try mod.intern(.{ .aggregate = .{
.ty = ty.toIntern(),
.storage = .{ .elems = field_vals },
} })).toValue();
}
// In this case the struct has no fields at all and
// therefore has one possible value.
return (try mod.intern(.{ .aggregate = .{
.ty = ty.toIntern(),
.storage = .{ .elems = &.{} },
} })).toValue();
},
.anon_struct_type => |tuple| {
for (tuple.values) |val| {
if (val == .none) return null;
}
// In this case the struct has all comptime-known fields and
// therefore has one possible value.
// TODO: write something like getCoercedInts to avoid needing to dupe
return (try mod.intern(.{ .aggregate = .{
.ty = ty.toIntern(),
.storage = .{ .elems = try sema.arena.dupe(InternPool.Index, tuple.values) },
} })).toValue();
},
.union_type => |union_type| {
const resolved_ty = try sema.resolveTypeFields(ty);
const union_obj = mod.unionPtr(union_type.index);
const tag_val = (try sema.typeHasOnePossibleValue(union_obj.tag_ty)) orelse
return null;
const fields = union_obj.fields.values();
if (fields.len == 0) {
const only = try mod.intern(.{ .empty_enum_value = ty.toIntern() });
return only.toValue();
}
const only_field = fields[0];
if (only_field.ty.eql(resolved_ty, sema.mod)) {
const msg = try Module.ErrorMsg.create(
sema.gpa,
union_obj.srcLoc(sema.mod),
"union '{}' depends on itself",
.{ty.fmt(sema.mod)},
);
try sema.addFieldErrNote(resolved_ty, 0, msg, "while checking this field", .{});
return sema.failWithOwnedErrorMsg(msg);
}
const val_val = (try sema.typeHasOnePossibleValue(only_field.ty)) orelse
return null;
const only = try mod.intern(.{ .un = .{
.ty = resolved_ty.toIntern(),
.tag = tag_val.toIntern(),
.val = val_val.toIntern(),
} });
return only.toValue();
},
.enum_type => |enum_type| switch (enum_type.tag_mode) {
.nonexhaustive => {
if (enum_type.tag_ty == .comptime_int_type) return null;
if (try sema.typeHasOnePossibleValue(enum_type.tag_ty.toType())) |int_opv| {
const only = try mod.intern(.{ .enum_tag = .{
.ty = ty.toIntern(),
.int = int_opv.toIntern(),
} });
return only.toValue();
}
return null;
},
.auto, .explicit => {
if (enum_type.tag_ty.toType().hasRuntimeBits(mod)) return null;
switch (enum_type.names.len) {
0 => {
const only = try mod.intern(.{ .empty_enum_value = ty.toIntern() });
return only.toValue();
},
1 => return try mod.getCoerced((if (enum_type.values.len == 0)
try mod.intern(.{ .int = .{
.ty = enum_type.tag_ty,
.storage = .{ .u64 = 0 },
} })
else
enum_type.values[0]).toValue(), ty),
else => return null,
}
},
},
else => unreachable,
},
},
};
}

View File

@ -2005,6 +2005,8 @@ pub const Inst = struct {
/// The tag type is specified so that it is safe to bitcast between `[]u32`
/// and `[]Ref`.
pub const Ref = enum(u32) {
u0_type = @intFromEnum(InternPool.Index.u0_type),
i0_type = @intFromEnum(InternPool.Index.i0_type),
u1_type = @intFromEnum(InternPool.Index.u1_type),
u8_type = @intFromEnum(InternPool.Index.u8_type),
i8_type = @intFromEnum(InternPool.Index.i8_type),
@ -2064,6 +2066,7 @@ pub const Inst = struct {
single_const_pointer_to_comptime_int_type = @intFromEnum(InternPool.Index.single_const_pointer_to_comptime_int_type),
slice_const_u8_type = @intFromEnum(InternPool.Index.slice_const_u8_type),
slice_const_u8_sentinel_0_type = @intFromEnum(InternPool.Index.slice_const_u8_sentinel_0_type),
optional_noreturn_type = @intFromEnum(InternPool.Index.optional_noreturn_type),
anyerror_void_error_union_type = @intFromEnum(InternPool.Index.anyerror_void_error_union_type),
generic_poison_type = @intFromEnum(InternPool.Index.generic_poison_type),
empty_struct_type = @intFromEnum(InternPool.Index.empty_struct_type),