partially implement peer type resolution for pointer types

This commit is contained in:
Techatrix 2023-01-04 09:53:48 +01:00
parent 475da58895
commit 5598ad032b

View File

@ -31,20 +31,20 @@ pub const Int = struct {
pub const Pointer = struct { pub const Pointer = struct {
elem_type: Index, elem_type: Index,
sentinel: Index, sentinel: Index = .none,
alignment: u16, alignment: u16 = 0,
size: std.builtin.Type.Pointer.Size, size: std.builtin.Type.Pointer.Size,
is_const: bool, is_const: bool = false,
is_volatile: bool, is_volatile: bool = false,
is_allowzero: bool, is_allowzero: bool = false,
address_space: std.builtin.AddressSpace, address_space: std.builtin.AddressSpace = .generic,
}; };
pub const Array = struct { pub const Array = struct {
// TODO support big int // TODO support big int
len: u32, len: u32,
child: Index, child: Index,
sentinel: Index, sentinel: Index = .none,
}; };
pub const Struct = struct { pub const Struct = struct {
@ -181,6 +181,7 @@ pub const Key = union(enum) {
union_type: Union, union_type: Union,
tuple_type: Tuple, tuple_type: Tuple,
vector_type: Vector, vector_type: Vector,
// TODO anyframe_T
declaration: Decl, declaration: Decl,
namespace: Namespace, namespace: Namespace,
@ -560,6 +561,61 @@ pub const Key = union(enum) {
}; };
} }
/// For pointer-like optionals, returns true, otherwise returns the allowzero property
/// of pointers.
pub fn ptrAllowsZero(ty: Key, ip: *const InternPool) bool {
if (ty.isPtrLikeOptional(ip)) {
return true;
}
return ty.pointer_type.is_allowzero;
}
/// Returns true if the type is optional and would be lowered to a single pointer
/// address value, using 0 for null. Note that this returns true for C pointers.
pub fn isPtrLikeOptional(ty: Key, ip: *const InternPool) bool {
switch (ty) {
.optional_type => |optional_info| {
const child_ty = optional_info.payload_type;
const child_key = ip.indexToKey(child_ty);
if(child_key != .pointer_type) return false;
const info = child_key.pointer_type;
switch (info.size) {
.Slice, .C => return false,
.Many, .One => return !info.is_allowzero,
}
},
.pointer_type => |pointer_info| return pointer_info.size == .C,
else => return false,
}
}
pub fn elemType2(ty: Key) Index {
return switch (ty) {
.simple => |simple| switch(simple) {
.@"anyframe" => @panic("TODO: return void type"),
else => unreachable,
},
.pointer_type => |pointer_info| pointer_info.elem_type,
.array_type => |array_info| array_info.child,
.optional_type => |optional_info| optional_info.payload_type,
.vector_type => |vector_info| vector_info.child,
else => unreachable,
};
}
/// Asserts the type is an array, pointer or vector.
pub fn sentinel(ty: Key) Index {
return switch (ty) {
.pointer_type => |pointer_info| pointer_info.sentinel,
.array_type => |array_info| array_info.sentinel,
.struct_type,
.tuple_type,
.vector_type,
=> Index.none,
else => unreachable,
};
}
pub fn getNamespace(ty: Key) ?Index { pub fn getNamespace(ty: Key) ?Index {
return switch (ty) { return switch (ty) {
.struct_type => |struct_info| struct_info.namespace, .struct_type => |struct_info| struct_info.namespace,
@ -1152,6 +1208,11 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
var arena = arena_allocator.allocator(); var arena = arena_allocator.allocator();
var chosen = types[0]; var chosen = types[0];
// // If this is non-null then it does the following thing, depending on the chosen zigTypeTag().
// // * ErrorSet: this is an override
// // * ErrorUnion: this is an override of the error set only
// // * other: at the end we make an ErrorUnion with the other thing and this
// var err_set_ty: Index = Index.none;
var any_are_null = false; var any_are_null = false;
var seen_const = false; var seen_const = false;
var convert_to_slice = false; var convert_to_slice = false;
@ -1162,6 +1223,17 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
const candidate_key: Key = ip.indexToKey(candidate); const candidate_key: Key = ip.indexToKey(candidate);
const chosen_key = ip.indexToKey(chosen); const chosen_key = ip.indexToKey(chosen);
// If the candidate can coerce into our chosen type, we're done.
// If the chosen type can coerce into the candidate, use that.
if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen, candidate, true, target)) == .ok) {
continue;
}
if ((try ip.coerceInMemoryAllowed(gpa, arena, candidate, chosen, true, target)) == .ok) {
chosen = candidate;
chosen_i = candidate_i + 1;
continue;
}
switch (candidate_key) { switch (candidate_key) {
.simple => |candidate_simple| switch (candidate_simple) { .simple => |candidate_simple| switch (candidate_simple) {
.f16, .f32, .f64, .f80, .f128 => switch (chosen_key) { .f16, .f32, .f64, .f80, .f128 => switch (chosen_key) {
@ -1170,8 +1242,8 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
if (chosen_key.floatBits(target) < candidate_key.floatBits(target)) { if (chosen_key.floatBits(target) < candidate_key.floatBits(target)) {
chosen = candidate; chosen = candidate;
chosen_i = candidate_i + 1; chosen_i = candidate_i + 1;
continue;
} }
continue;
}, },
.comptime_int, .comptime_float => { .comptime_int, .comptime_float => {
chosen = candidate; chosen = candidate;
@ -1193,7 +1265,8 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
.c_ulong, .c_ulong,
.c_longlong, .c_longlong,
.c_ulonglong, .c_ulonglong,
.c_longdouble, => switch (chosen_key) { .c_longdouble,
=> switch (chosen_key) {
.simple => |chosen_simple| switch (chosen_simple) { .simple => |chosen_simple| switch (chosen_simple) {
.usize, .usize,
.isize, .isize,
@ -1205,11 +1278,12 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
.c_ulong, .c_ulong,
.c_longlong, .c_longlong,
.c_ulonglong, .c_ulonglong,
.c_longdouble, => { .c_longdouble,
=> {
const chosen_bits = chosen_key.intInfo(target, ip).bits; const chosen_bits = chosen_key.intInfo(target, ip).bits;
const candidate_bits = candidate_key.intInfo(target, ip).bits; const candidate_bits = candidate_key.intInfo(target, ip).bits;
if(chosen_bits < candidate_bits) { if (chosen_bits < candidate_bits) {
chosen = candidate; chosen = candidate;
chosen_i = candidate_i + 1; chosen_i = candidate_i + 1;
} }
@ -1253,7 +1327,8 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
.c_longlong, .c_longlong,
.c_ulonglong, .c_ulonglong,
.c_longdouble, .c_longdouble,
.comptime_float, => continue, .comptime_float,
=> continue,
.comptime_int => unreachable, .comptime_int => unreachable,
else => {}, else => {},
}, },
@ -1292,11 +1367,12 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
.c_ulong, .c_ulong,
.c_longlong, .c_longlong,
.c_ulonglong, .c_ulonglong,
.c_longdouble, => { .c_longdouble,
=> {
const chosen_bits = chosen_key.intInfo(target, ip).bits; const chosen_bits = chosen_key.intInfo(target, ip).bits;
const candidate_bits = candidate_key.intInfo(target, ip).bits; const candidate_bits = candidate_key.intInfo(target, ip).bits;
if(chosen_bits < candidate_bits) { if (chosen_bits < candidate_bits) {
chosen = candidate; chosen = candidate;
chosen_i = candidate_i + 1; chosen_i = candidate_i + 1;
} }
@ -1368,13 +1444,13 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
const chosen_elem_ty = chosen_elem_info.array_type.child; const chosen_elem_ty = chosen_elem_info.array_type.child;
const cand_elem_ty = candidate_elem_info.array_type.child; const cand_elem_ty = candidate_elem_info.array_type.child;
const chosen_ok = .ok == try ip.coerceInMemoryAllowed(gpa, chosen_elem_ty, cand_elem_ty, !chosen_info.is_const, target); const chosen_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, chosen_elem_ty, cand_elem_ty, chosen_info.is_const, target);
if (chosen_ok) { if (chosen_ok) {
convert_to_slice = true; convert_to_slice = true;
continue; continue;
} }
const cand_ok = .ok == try ip.coerceInMemoryAllowed(gpa, cand_elem_ty, chosen_elem_ty, !candidate_info.is_const, target); const cand_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, cand_elem_ty, chosen_elem_ty, candidate_info.is_const, target);
if (cand_ok) { if (cand_ok) {
convert_to_slice = true; convert_to_slice = true;
chosen = candidate; chosen = candidate;
@ -1394,8 +1470,8 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
// the one we will keep. If they're both OK then we keep the // the one we will keep. If they're both OK then we keep the
// C pointer since it matches both single and many pointers. // C pointer since it matches both single and many pointers.
if (candidate_info.size == .C or chosen_info.size == .C) { if (candidate_info.size == .C or chosen_info.size == .C) {
const cand_ok = .ok == try ip.coerceInMemoryAllowed(arena, candidate_info.elem_type, chosen_info.elem_type, !candidate_info.is_const, target); const cand_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, candidate_info.elem_type, chosen_info.elem_type, candidate_info.is_const, target);
const chosen_ok = .ok == try ip.coerceInMemoryAllowed(arena, chosen_info.elem_type, candidate_info.elem_type, !chosen_info.is_const, target); const chosen_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, chosen_info.elem_type, candidate_info.elem_type, chosen_info.is_const, target);
if (cand_ok) { if (cand_ok) {
if (!chosen_ok or chosen_info.size != .C) { if (!chosen_ok or chosen_info.size != .C) {
@ -1412,7 +1488,6 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
// "incompatible types" error below. // "incompatible types" error below.
} }
} }
// TODO
}, },
.int_type => { .int_type => {
if (candidate_info.size == .C) { if (candidate_info.size == .C) {
@ -1457,7 +1532,7 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
if (candidate_info.is_const) { if (candidate_info.is_const) {
const candidate_elem_key = ip.indexToKey(candidate_info.elem_type); const candidate_elem_key = ip.indexToKey(candidate_info.elem_type);
if (candidate_elem_key == .function_type and if (candidate_elem_key == .function_type and
.ok == try ip.coerceInMemoryAllowedFns(arena, chosen_info, candidate_elem_key.function_type, target)) .ok == try ip.coerceInMemoryAllowedFns(gpa, arena, chosen_info, candidate_elem_key.function_type, target))
{ {
chosen = candidate; chosen = candidate;
chosen_i = candidate_i + 1; chosen_i = candidate_i + 1;
@ -1472,18 +1547,13 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
else => {}, else => {},
}, },
.optional_type => |candidate_info| { .optional_type => |candidate_info| {
const is_chosen_const_ptr = switch (chosen_key) { if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen, candidate_info.payload_type, true, target)) == .ok) {
.pointer_type => |chosen_info| chosen_info.is_const,
else => false,
};
if ((try ip.coerceInMemoryAllowed(arena, chosen, candidate_info.payload_type, false, target)) == .ok) {
seen_const = seen_const or ip.indexToKey(candidate_info.payload_type).isConstPtr(); seen_const = seen_const or ip.indexToKey(candidate_info.payload_type).isConstPtr();
any_are_null = true; any_are_null = true;
continue; continue;
} }
seen_const = seen_const or is_chosen_const_ptr; seen_const = seen_const or chosen_key.isConstPtr();
any_are_null = false; any_are_null = false;
chosen = candidate; chosen = candidate;
chosen_i = candidate_i + 1; chosen_i = candidate_i + 1;
@ -1518,10 +1588,10 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
else => {}, else => {},
}, },
.optional_type => |chosen_info| { .optional_type => |chosen_info| {
if ((try ip.coerceInMemoryAllowed(arena, chosen_info.payload_type, candidate, false, target)) == .ok) { if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen_info.payload_type, candidate, true, target)) == .ok) {
continue; continue;
} }
if ((try ip.coerceInMemoryAllowed(arena, candidate, chosen_info.payload_type, false, target)) == .ok) { if ((try ip.coerceInMemoryAllowed(gpa, arena, candidate, chosen_info.payload_type, true, target)) == .ok) {
any_are_null = true; any_are_null = true;
chosen = candidate; chosen = candidate;
chosen_i = candidate_i + 1; chosen_i = candidate_i + 1;
@ -1529,7 +1599,7 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
} }
}, },
.error_union_type => |chosen_info| { .error_union_type => |chosen_info| {
if ((try ip.coerceInMemoryAllowed(arena, chosen_info.payload_type, candidate, false, target)) == .ok) { if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen_info.payload_type, candidate, true, target)) == .ok) {
continue; continue;
} }
}, },
@ -1539,6 +1609,78 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t
return Index.none; return Index.none;
} }
if(chosen == .none) return chosen;
const chosen_key = ip.indexToKey(chosen);
if (convert_to_slice) {
// turn *[N]T => []T
const chosen_elem_key = ip.indexToKey(chosen_key.pointer_type.elem_type);
var info = chosen_key.pointer_type;
info.sentinel = chosen_elem_key.sentinel();
info.size = .Slice;
info.is_const = seen_const or chosen_elem_key.isConstPtr();
info.elem_type = chosen_elem_key.elemType2();
const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info });
const opt_ptr_ty = if (any_are_null) try ip.get(gpa, .{ .optional_type = .{ .payload_type = new_ptr_ty } }) else new_ptr_ty;
return opt_ptr_ty;
// const set_ty = if(err_set_ty != .none) err_set_ty else return opt_ptr_ty;
// return try ip.get(gpa, .{ .error_union_type = .{
// .error_set_type = set_ty,
// .payload_type = opt_ptr_ty,
// } });
}
if (seen_const) {
// turn []T => []const T
switch (chosen_key) {
.error_union_type => |error_union_info| {
var info: Pointer = ip.indexToKey(error_union_info.payload_type).pointer_type;
info.is_const = true;
const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info });
const opt_ptr_ty = if (any_are_null) try ip.get(gpa, .{ .optional_type = .{ .payload_type = new_ptr_ty } }) else new_ptr_ty;
// const set_ty = if(err_set_ty != .none) err_set_ty else error_union_info.error_set_type;
const set_ty = error_union_info.error_set_type;
return try ip.get(gpa, .{ .error_union_type = .{
.error_set_type = set_ty,
.payload_type = opt_ptr_ty,
} });
},
.pointer_type => |pointer_info| {
var info = pointer_info;
info.is_const = true;
const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info });
const opt_ptr_ty = if (any_are_null) try ip.get(gpa, .{ .optional_type = .{ .payload_type = new_ptr_ty } }) else new_ptr_ty;
return opt_ptr_ty;
// const set_ty = if(err_set_ty != .none) err_set_ty else return opt_ptr_ty;
// return try ip.get(gpa, .{ .error_union_type = .{
// .error_set_type = set_ty,
// .payload_type = opt_ptr_ty,
// } });
},
else => return chosen,
}
}
if (any_are_null) {
const opt_ty = switch (chosen_key) {
.simple => |simple| switch (simple) {
.null_type => chosen,
else => try ip.get(gpa, .{ .optional_type = .{ .payload_type = chosen } }),
},
.optional_type => chosen,
else => try ip.get(gpa, .{ .optional_type = .{ .payload_type = chosen } }),
};
return opt_ty;
// const set_ty = if(err_set_ty != .none) err_set_ty else return opt_ty;
// return try ip.get(gpa, .{ .error_union_type = .{
// .error_set_type = set_ty,
// .payload_type = opt_ty,
// } });
}
return chosen; return chosen;
} }
@ -1653,22 +1795,21 @@ const InMemoryCoercionResult = union(enum) {
} }
}; };
/// If pointers have the same representation in runtime memory, a bitcast AIR instruction /// If pointers have the same representation in runtime memory
/// may be used for the coercion.
/// * `const` attribute can be gained /// * `const` attribute can be gained
/// * `volatile` attribute can be gained /// * `volatile` attribute can be gained
/// * `allowzero` attribute can be gained (whether from explicit attribute, C pointer, or optional pointer) but only if !dest_is_mut /// * `allowzero` attribute can be gained (whether from explicit attribute, C pointer, or optional pointer) but only if dest_is_const
/// * alignment can be decreased /// * alignment can be decreased
/// * bit offset attributes must match exactly /// * bit offset attributes must match exactly
/// * `*`/`[*]` must match exactly, but `[*c]` matches either one /// * `*`/`[*]` must match exactly, but `[*c]` matches either one
/// * sentinel-terminated pointers can coerce into `[*]` /// * sentinel-terminated pointers can coerce into `[*]`
fn coerceInMemoryAllowed( fn coerceInMemoryAllowed(
ip: *InternPool, ip: *InternPool,
// gpa: Allocator, gpa: Allocator,
arena: Allocator, arena: Allocator,
dest_ty: Index, dest_ty: Index,
src_ty: Index, src_ty: Index,
dest_is_mut: bool, dest_is_const: bool,
target: std.Target, target: std.Target,
) error{OutOfMemory}!InMemoryCoercionResult { ) error{OutOfMemory}!InMemoryCoercionResult {
if (dest_ty == src_ty) return .ok; if (dest_ty == src_ty) return .ok;
@ -1679,168 +1820,157 @@ fn coerceInMemoryAllowed(
const dest_tag = dest_key.zigTypeTag(); const dest_tag = dest_key.zigTypeTag();
const src_tag = src_key.zigTypeTag(); const src_tag = src_key.zigTypeTag();
// integers with the same number of bits. if (dest_tag != src_tag) {
if (dest_tag == .Int and src_tag == .Int) { return InMemoryCoercionResult{ .no_match = .{
const dest_info = dest_key.intInfo(target, ip); .actual = dest_ty,
const src_info = src_key.intInfo(target, ip); .wanted = src_ty,
} };
}
if (dest_info.signedness == src_info.signedness and dest_info.bits == src_info.bits) return .ok; switch (dest_tag) {
.Int => {
const dest_info = dest_key.intInfo(target, ip);
const src_info = src_key.intInfo(target, ip);
if (dest_info.signedness == src_info.signedness and dest_info.bits == src_info.bits) return .ok;
if ((src_info.signedness == dest_info.signedness and dest_info.bits < src_info.bits) or
// small enough unsigned ints can get casted to large enough signed ints
(dest_info.signedness == .signed and (src_info.signedness == .unsigned or dest_info.bits <= src_info.bits)) or
(dest_info.signedness == .unsigned and src_info.signedness == .signed))
{
return InMemoryCoercionResult{ .int_not_coercible = .{ return InMemoryCoercionResult{ .int_not_coercible = .{
.actual_signedness = src_info.signedness, .actual_signedness = src_info.signedness,
.wanted_signedness = dest_info.signedness, .wanted_signedness = dest_info.signedness,
.actual_bits = src_info.bits, .actual_bits = src_info.bits,
.wanted_bits = dest_info.bits, .wanted_bits = dest_info.bits,
} }; } };
} },
} .Float => {
const dest_bits = dest_key.floatBits(target);
// floats with the same number of bits. const src_bits = src_key.floatBits(target);
if (dest_tag == .Float and src_tag == .Float and if (dest_bits == src_bits) return .ok;
// this is an optimization because only a long double can have the same size as a other Float // TODO return float_not_coercible
// SAFETY: every Float is a Simple return InMemoryCoercionResult{ .no_match = .{
dest_key.simple == .c_longdouble or src_key.simple == .c_longdouble) .actual = dest_ty,
{ .wanted = src_ty,
const dest_bits = dest_key.floatBits(target);
const src_bits = src_key.floatBits(target);
if (dest_bits == src_bits) return .ok;
}
// Pointers / Pointer-like Optionals
const maybe_dest_ptr_ty = try ip.typePtrOrOptionalPtrTy(dest_ty);
const maybe_src_ptr_ty = try ip.typePtrOrOptionalPtrTy(src_ty);
if (maybe_dest_ptr_ty != Index.none and maybe_src_ptr_ty != Index.none) {
@panic("TODO: implement coerceInMemoryAllowedPtrs");
// return try ip.coerceInMemoryAllowedPtrs(dest_ty, src_ty, maybe_dest_ptr_ty, maybe_src_ptr_ty, dest_is_mut, target);
}
// Slices
if (dest_key.isSlice() and src_key.isSlice()) {
@panic("TODO: implement coerceInMemoryAllowedPtrs");
// return try ip.coerceInMemoryAllowedPtrs(dest_ty, src_ty, dest_ty, src_ty, dest_is_mut, target);
}
// Functions
if (dest_tag == .Fn and src_tag == .Fn) {
return try ip.coerceInMemoryAllowedFns(arena, dest_key.function_type, src_key.function_type, target);
}
// Error Unions
if (dest_tag == .ErrorUnion and src_tag == .ErrorUnion) {
const dest_payload = dest_key.error_union_type.payload_type;
const src_payload = src_key.error_union_type.payload_type;
const child = try ip.coerceInMemoryAllowed(arena, dest_payload, src_payload, dest_is_mut, target);
if (child != .ok) {
return InMemoryCoercionResult{ .error_union_payload = .{
.child = try child.dupe(arena),
.actual = src_payload,
.wanted = dest_payload,
} }; } };
} },
const dest_set = dest_key.error_union_type.error_set_type; .Pointer => {
const src_set = src_key.error_union_type.error_set_type; return try ip.coerceInMemoryAllowedPtrs(gpa, arena, dest_ty, src_ty, dest_key, src_key, dest_is_const, target);
return try ip.coerceInMemoryAllowed(arena, dest_set, src_set, dest_is_mut, target); },
.Optional => {
// Pointer-like Optionals
const maybe_dest_ptr_ty = try ip.optionalPtrTy(dest_key);
const maybe_src_ptr_ty = try ip.optionalPtrTy(src_key);
if (maybe_dest_ptr_ty != .none and maybe_src_ptr_ty != .none) {
const dest_ptr_info = ip.indexToKey(maybe_dest_ptr_ty);
const src_ptr_info = ip.indexToKey(maybe_src_ptr_ty);
return try ip.coerceInMemoryAllowedPtrs(gpa, arena, dest_ty, src_ty, dest_ptr_info, src_ptr_info, dest_is_const, target);
}
if (maybe_dest_ptr_ty != maybe_src_ptr_ty) {
return InMemoryCoercionResult{ .optional_shape = .{
.actual = src_ty,
.wanted = dest_ty,
} };
}
const dest_child_type = dest_key.optional_type.payload_type;
const src_child_type = src_key.optional_type.payload_type;
const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_child_type, src_child_type, dest_is_const, target);
if (child != .ok) {
return InMemoryCoercionResult{ .optional_child = .{
.child = try child.dupe(arena),
.actual = src_child_type,
.wanted = dest_child_type,
} };
}
return .ok;
},
.Fn => {
return try ip.coerceInMemoryAllowedFns(gpa, arena, dest_key.function_type, src_key.function_type, target);
},
.ErrorUnion => {
const dest_payload = dest_key.error_union_type.payload_type;
const src_payload = src_key.error_union_type.payload_type;
const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_payload, src_payload, dest_is_const, target);
if (child != .ok) {
return InMemoryCoercionResult{ .error_union_payload = .{
.child = try child.dupe(arena),
.actual = src_payload,
.wanted = dest_payload,
} };
}
const dest_set = dest_key.error_union_type.error_set_type;
const src_set = src_key.error_union_type.error_set_type;
return try ip.coerceInMemoryAllowed(gpa, arena, dest_set, src_set, dest_is_const, target);
},
.ErrorSet => {
return .ok;
// TODO: implement coerceInMemoryAllowedErrorSets
// return try ip.coerceInMemoryAllowedErrorSets(dest_ty, src_ty);
},
.Array => {
const dest_info = dest_key.array_type;
const src_info = src_key.array_type;
if (dest_info.len != src_info.len) {
return InMemoryCoercionResult{ .array_len = .{
.actual = src_info.len,
.wanted = dest_info.len,
} };
}
const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.child, src_info.child, dest_is_const, target);
if (child != .ok) {
return InMemoryCoercionResult{ .array_elem = .{
.child = try child.dupe(arena),
.actual = src_info.child,
.wanted = dest_info.child,
} };
}
const ok_sent = dest_info.sentinel == Index.none or
(src_info.sentinel != Index.none and
dest_info.sentinel == src_info.sentinel // is this enough for a value equality check?
);
if (!ok_sent) {
return InMemoryCoercionResult{ .array_sentinel = .{
.actual = src_info.sentinel,
.wanted = dest_info.sentinel,
.ty = dest_info.child,
} };
}
return .ok;
},
.Vector => {
const dest_len = dest_key.vector_type.len;
const src_len = src_key.vector_type.len;
if (dest_len != src_len) {
return InMemoryCoercionResult{ .vector_len = .{
.actual = src_len,
.wanted = dest_len,
} };
}
const dest_elem_ty = dest_key.vector_type.child;
const src_elem_ty = src_key.vector_type.child;
const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_elem_ty, src_elem_ty, dest_is_const, target);
if (child != .ok) {
return InMemoryCoercionResult{ .vector_elem = .{
.child = try child.dupe(arena),
.actual = src_elem_ty,
.wanted = dest_elem_ty,
} };
}
return .ok;
},
else => {
return InMemoryCoercionResult{ .no_match = .{
.actual = dest_ty,
.wanted = src_ty,
} };
},
} }
// Error Sets
if (dest_tag == .ErrorSet and src_tag == .ErrorSet) {
return .ok; // TODO: implement coerceInMemoryAllowedErrorSets
// return try ip.coerceInMemoryAllowedErrorSets(dest_ty, src_ty);
}
// Arrays
if (dest_tag == .Array and src_tag == .Array) {
const dest_info = dest_key.array_type;
const src_info = src_key.array_type;
if (dest_info.len != src_info.len) {
return InMemoryCoercionResult{ .array_len = .{
.actual = src_info.len,
.wanted = dest_info.len,
} };
}
const child = try ip.coerceInMemoryAllowed(arena, dest_info.child, src_info.child, dest_is_mut, target);
if (child != .ok) {
return InMemoryCoercionResult{ .array_elem = .{
.child = try child.dupe(arena),
.actual = src_info.child,
.wanted = dest_info.child,
} };
}
const ok_sent = dest_info.sentinel == Index.none or
(src_info.sentinel != Index.none and
dest_info.sentinel == src_info.sentinel // is this enough for a value equality check?
);
if (!ok_sent) {
return InMemoryCoercionResult{ .array_sentinel = .{
.actual = src_info.sentinel,
.wanted = dest_info.sentinel,
.ty = dest_info.child,
} };
}
return .ok;
}
// Vectors
if (dest_tag == .Vector and src_tag == .Vector) {
const dest_len = dest_key.vector_type.len;
const src_len = src_key.vector_type.len;
if (dest_len != src_len) {
return InMemoryCoercionResult{ .vector_len = .{
.actual = src_len,
.wanted = dest_len,
} };
}
const dest_elem_ty = dest_key.vector_type.child;
const src_elem_ty = src_key.vector_type.child;
const child = try ip.coerceInMemoryAllowed(arena, dest_elem_ty, src_elem_ty, dest_is_mut, target);
if (child != .ok) {
return InMemoryCoercionResult{ .vector_elem = .{
.child = try child.dupe(arena),
.actual = src_elem_ty,
.wanted = dest_elem_ty,
} };
}
return .ok;
}
// Optionals
if (dest_tag == .Optional and src_tag == .Optional) {
if (maybe_dest_ptr_ty != maybe_src_ptr_ty) {
return InMemoryCoercionResult{ .optional_shape = .{
.actual = src_ty,
.wanted = dest_ty,
} };
}
const dest_child_type = dest_key.optional_type.payload_type;
const src_child_type = src_key.optional_type.payload_type;
const child = try ip.coerceInMemoryAllowed(arena, dest_child_type, src_child_type, dest_is_mut, target);
if (child != .ok) {
return InMemoryCoercionResult{ .optional_child = .{
.child = try child.dupe(arena),
.actual = src_child_type,
.wanted = dest_child_type,
} };
}
return .ok;
}
return InMemoryCoercionResult{ .no_match = .{
.actual = dest_ty,
.wanted = src_ty,
} };
} }
// fn coerceInMemoryAllowedErrorSets( // fn coerceInMemoryAllowedErrorSets(
@ -1996,6 +2126,7 @@ fn coerceInMemoryAllowed(
fn coerceInMemoryAllowedFns( fn coerceInMemoryAllowedFns(
ip: *InternPool, ip: *InternPool,
gpa: std.mem.Allocator,
arena: std.mem.Allocator, arena: std.mem.Allocator,
dest_info: Fn, dest_info: Fn,
src_info: Fn, src_info: Fn,
@ -2020,7 +2151,7 @@ fn coerceInMemoryAllowedFns(
const is_noreturn = return_type_key == .simple and return_type_key.simple == .noreturn; const is_noreturn = return_type_key == .simple and return_type_key.simple == .noreturn;
if (!is_noreturn) { if (!is_noreturn) {
const rt = try ip.coerceInMemoryAllowed(arena, dest_info.return_type, src_info.return_type, false, target); const rt = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.return_type, src_info.return_type, true, target);
if (rt != .ok) { if (rt != .ok) {
return InMemoryCoercionResult{ .fn_return_type = .{ return InMemoryCoercionResult{ .fn_return_type = .{
.child = try rt.dupe(arena), .child = try rt.dupe(arena),
@ -2057,7 +2188,7 @@ fn coerceInMemoryAllowedFns(
// } // }
// // Note: Cast direction is reversed here. // // Note: Cast direction is reversed here.
// const param = try ip.coerceInMemoryAllowed(src_param_ty, dest_param_ty, false, target); // const param = try ip.coerceInMemoryAllowed(gpa, src_param_ty, dest_param_ty, true, target);
// if (param != .ok) { // if (param != .ok) {
// return InMemoryCoercionResult{ .fn_param = .{ // return InMemoryCoercionResult{ .fn_param = .{
// .child = try param.dupe(arena), // .child = try param.dupe(arena),
@ -2071,22 +2202,129 @@ fn coerceInMemoryAllowedFns(
return .ok; return .ok;
} }
/// For pointer-like optionals, it returns the pointer type. For pointers, fn coerceInMemoryAllowedPtrs(
/// the type is returned unmodified. ip: *InternPool,
/// This can return `error.AnalysisFail` because it sometimes requires resolving whether gpa: std.mem.Allocator,
/// a type has zero bits, which can cause a "foo depends on itself" compile error. arena: std.mem.Allocator,
/// This logic must be kept in sync with `Type.isPtrLikeOptional`. dest_ty: Index,
fn typePtrOrOptionalPtrTy( src_ty: Index,
ip: InternPool, dest_ptr_info: Key,
ty: Index, src_ptr_info: Key,
) !Index { dest_is_const: bool,
const key = ip.indexToKey(ty); target: std.Target,
switch (key) { ) !InMemoryCoercionResult {
.pointer_type => |pointer_info| switch (pointer_info.size) { const dest_info = dest_ptr_info.pointer_type;
.Slice => return Index.none, const src_info = src_ptr_info.pointer_type;
else => return ty,
},
const ok_ptr_size = src_info.size == dest_info.size or
src_info.size == .C or dest_info.size == .C;
if (!ok_ptr_size) {
return InMemoryCoercionResult{ .ptr_size = .{
.actual = src_info.size,
.wanted = dest_info.size,
} };
}
const ok_cv_qualifiers =
(!src_info.is_const or dest_info.is_const) and
(!src_info.is_volatile or dest_info.is_volatile);
if (!ok_cv_qualifiers) {
return InMemoryCoercionResult{ .ptr_qualifiers = .{
.actual_const = src_info.is_const,
.wanted_const = dest_info.is_const,
.actual_volatile = src_info.is_volatile,
.wanted_volatile = dest_info.is_volatile,
} };
}
if (dest_info.address_space != src_info.address_space) {
return InMemoryCoercionResult{ .ptr_addrspace = .{
.actual = src_info.address_space,
.wanted = dest_info.address_space,
} };
}
const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.elem_type, src_info.elem_type, dest_info.is_const, target);
if (child != .ok) {
return InMemoryCoercionResult{ .ptr_child = .{
.child = try child.dupe(arena),
.actual = src_info.elem_type,
.wanted = dest_info.elem_type,
} };
}
const dest_allow_zero = dest_ptr_info.ptrAllowsZero(ip);
const src_allow_zero = src_ptr_info.ptrAllowsZero(ip);
const ok_allows_zero = (dest_allow_zero and (src_allow_zero or dest_is_const)) or (!dest_allow_zero and !src_allow_zero);
if (!ok_allows_zero) {
return InMemoryCoercionResult{ .ptr_allowzero = .{
.actual = src_ty,
.wanted = dest_ty,
} };
}
// if (src_info.host_size != dest_info.host_size or
// src_info.bit_offset != dest_info.bit_offset)
// {
// return InMemoryCoercionResult{ .ptr_bit_range = .{
// .actual_host = src_info.host_size,
// .wanted_host = dest_info.host_size,
// .actual_offset = src_info.bit_offset,
// .wanted_offset = dest_info.bit_offset,
// } };
// }
const ok_sent = dest_info.sentinel == .none or src_info.size == .C or dest_info.sentinel == src_info.sentinel; // is this enough for a value equality check?
if (!ok_sent) {
return InMemoryCoercionResult{ .ptr_sentinel = .{
.actual = if(src_info.sentinel != .none) src_info.sentinel else try ip.get(gpa, .{.simple = .unreachable_value}),
.wanted = if(dest_info.sentinel != .none) dest_info.sentinel else try ip.get(gpa, .{.simple = .unreachable_value}),
.ty = dest_info.elem_type,
} };
}
// If both pointers have alignment 0, it means they both want ABI alignment.
// In this case, if they share the same child type, no need to resolve
// pointee type alignment. Otherwise both pointee types must have their alignment
// resolved and we compare the alignment numerically.
alignment: {
if (src_info.alignment == 0 and dest_info.alignment == 0 and
dest_info.elem_type == src_info.elem_type // is this enough for a value equality check?
)
{
break :alignment;
}
// const src_align = if (src_info.alignment != 0)
// src_info.alignment
// else
// src_info.elem_type.abiAlignment(target);
// const dest_align = if (dest_info.alignment != 0)
// dest_info.alignment
// else
// dest_info.elem_type.abiAlignment(target);
// if (dest_align > src_align) {
// return InMemoryCoercionResult{ .ptr_alignment = .{
// .actual = src_align,
// .wanted = dest_align,
// } };
// }
break :alignment;
}
return .ok;
}
fn optionalPtrTy(
ip: InternPool,
ty: Key,
) !Index {
switch (ty) {
.optional_type => |optional_info| { .optional_type => |optional_info| {
const child_type = optional_info.payload_type; const child_type = optional_info.payload_type;
const child_key = ip.indexToKey(child_type); const child_key = ip.indexToKey(child_type);
@ -2108,8 +2346,7 @@ fn typePtrOrOptionalPtrTy(
}, },
} }
}, },
else => unreachable,
else => return Index.none,
} }
} }
@ -2196,32 +2433,34 @@ test "pointer type" {
try std.testing.expect(i32_type_0 == i32_type_1); try std.testing.expect(i32_type_0 == i32_type_1);
try std.testing.expect(i32_type_0 != u32_type); try std.testing.expect(i32_type_0 != u32_type);
var ptr: Pointer = .{ const i32_pointer_type_0 = try ip.get(gpa, .{ .pointer_type = .{
.elem_type = undefined, .elem_type = i32_type_0,
.sentinel = Index.none, .size = .One,
.alignment = 0, // TODO } });
.size = std.builtin.Type.Pointer.Size.One, const i32_pointer_type_1 = try ip.get(gpa, .{ .pointer_type = .{
.is_const = false, .elem_type = i32_type_0,
.is_volatile = false, .size = .One,
.is_allowzero = false, } });
.address_space = std.builtin.AddressSpace.generic, const i32_pointer_type_2 = try ip.get(gpa, .{ .pointer_type = .{
}; .elem_type = i32_type_1,
.size = .One,
ptr.elem_type = i32_type_0; } });
const i32_pointer_type_0 = try ip.get(gpa, .{ .pointer_type = ptr }); const u32_pointer_type = try ip.get(gpa, .{ .pointer_type = .{
ptr.elem_type = i32_type_0; .elem_type = u32_type,
const i32_pointer_type_1 = try ip.get(gpa, .{ .pointer_type = ptr }); .size = .One,
ptr.elem_type = i32_type_1; } });
const i32_pointer_type_2 = try ip.get(gpa, .{ .pointer_type = ptr });
ptr.elem_type = u32_type;
const u32_pointer_type = try ip.get(gpa, .{ .pointer_type = ptr });
try std.testing.expect(i32_pointer_type_0 == i32_pointer_type_1); try std.testing.expect(i32_pointer_type_0 == i32_pointer_type_1);
try std.testing.expect(i32_pointer_type_1 == i32_pointer_type_2); try std.testing.expect(i32_pointer_type_1 == i32_pointer_type_2);
try std.testing.expect(i32_pointer_type_0 != u32_pointer_type); try std.testing.expect(i32_pointer_type_0 != u32_pointer_type);
ptr.is_const = true; const const_u32_pointer_type = try ip.get(gpa, .{ .pointer_type = .{
const const_u32_pointer_type = try ip.get(gpa, .{ .pointer_type = ptr }); .elem_type = u32_type,
.size = .One,
.is_const = true,
} });
try std.testing.expect(const_u32_pointer_type != u32_pointer_type);
try testExpectFmtType(&ip, i32_pointer_type_0, "*i32"); try testExpectFmtType(&ip, i32_pointer_type_0, "*i32");
try testExpectFmtType(&ip, u32_pointer_type, "*u32"); try testExpectFmtType(&ip, u32_pointer_type, "*u32");
@ -2263,12 +2502,10 @@ test "array type" {
const i32_3_array_type_0 = try ip.get(gpa, .{ .array_type = .{ const i32_3_array_type_0 = try ip.get(gpa, .{ .array_type = .{
.len = 3, .len = 3,
.child = i32_type_0, .child = i32_type_0,
.sentinel = Index.none,
} }); } });
const i32_3_array_type_1 = try ip.get(gpa, .{ .array_type = .{ const i32_3_array_type_1 = try ip.get(gpa, .{ .array_type = .{
.len = 3, .len = 3,
.child = i32_type_1, .child = i32_type_1,
.sentinel = Index.none,
} }); } });
const u32_0_0_array_type = try ip.get(gpa, .{ .array_type = .{ const u32_0_0_array_type = try ip.get(gpa, .{ .array_type = .{
.len = 3, .len = 3,
@ -2379,6 +2616,9 @@ test "resolvePeerTypes integers and floats" {
try ip.testResolvePeerTypes(comptime_float_type, comptime_int_type, comptime_float_type); try ip.testResolvePeerTypes(comptime_float_type, comptime_int_type, comptime_float_type);
try ip.testResolvePeerTypes(f16_type, f32_type, f32_type);
try ip.testResolvePeerTypes(f32_type, f64_type, f64_type);
try ip.testResolvePeerTypes(comptime_int_type, f16_type, f16_type); try ip.testResolvePeerTypes(comptime_int_type, f16_type, f16_type);
try ip.testResolvePeerTypes(comptime_int_type, f32_type, f32_type); try ip.testResolvePeerTypes(comptime_int_type, f32_type, f32_type);
try ip.testResolvePeerTypes(comptime_int_type, f64_type, f64_type); try ip.testResolvePeerTypes(comptime_int_type, f64_type, f64_type);
@ -2404,12 +2644,66 @@ test "resolvePeerTypes integers and floats" {
try ip.testResolvePeerTypes(bool_type, f32_type, Index.none); try ip.testResolvePeerTypes(bool_type, f32_type, Index.none);
} }
test "resolvePeerTypes pointers" {
const gpa = std.testing.allocator;
var ip: InternPool = .{};
defer ip.deinit(gpa);
const u32_type = try ip.get(gpa, .{ .int_type = .{ .signedness = .unsigned, .bits = 32 } });
const comptime_int_type = try ip.get(gpa, .{ .simple = .comptime_int });
const comptime_float_type = try ip.get(gpa, .{ .simple = .comptime_float });
const bool_type = try ip.get(gpa, .{ .simple = .bool });
const @"[*c]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = u32_type, .size = .C } });
const @"[1]u32" = try ip.get(gpa, .{ .array_type = .{
.len = 1,
.child = u32_type,
.sentinel = Index.none,
} });
const @"[2]u32" = try ip.get(gpa, .{ .array_type = .{
.len = 2,
.child = u32_type,
.sentinel = Index.none,
} });
const @"*[1]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[1]u32", .size = .One } });
const @"*[2]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[2]u32", .size = .One } });
const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = u32_type, .size = .One } });
const @"*const u32" = try ip.get(gpa, .{ .pointer_type = .{
.elem_type = u32_type,
.size = .One,
.is_const = true,
} });
const @"[*]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = u32_type, .size = .Many } });
const @"[]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = u32_type, .size = .Slice } });
try ip.testResolvePeerTypes(@"[*c]u32", comptime_int_type, @"[*c]u32");
try ip.testResolvePeerTypes(@"[*c]u32", u32_type, @"[*c]u32");
try ip.testResolvePeerTypes(@"[*c]u32", comptime_float_type, Index.none);
try ip.testResolvePeerTypes(@"[*c]u32", bool_type, Index.none);
try ip.testResolvePeerTypes(@"[*]u32", @"*[2]u32", @"[*]u32");
try ip.testResolvePeerTypes(@"[]u32", @"*[2]u32", @"[]u32");
try ip.testResolvePeerTypes(@"*u32", @"*u32", @"*u32");
try ip.testResolvePeerTypes(@"*u32", @"*u32", @"*u32");
try ip.testResolvePeerTypes(@"*u32", @"*const u32", @"*const u32");
try ip.testResolvePeerTypes(@"*[1]u32", @"*[2]u32", @"[]u32");
}
fn testResolvePeerTypes(ip: *InternPool, a: Index, b: Index, expected: Index) !void { fn testResolvePeerTypes(ip: *InternPool, a: Index, b: Index, expected: Index) !void {
try ip.testResolvePeerTypesInOrder(a, b, expected); try ip.testResolvePeerTypesInOrder(a, b, expected);
try ip.testResolvePeerTypesInOrder(b, a, expected); try ip.testResolvePeerTypesInOrder(b, a, expected);
} }
fn testResolvePeerTypesInOrder(ip: *InternPool, lhs: Index, rhs: Index, expected: Index) !void { fn testResolvePeerTypesInOrder(ip: *InternPool, lhs: Index, rhs: Index, expected: Index) !void {
const actual = try resolvePeerTypes(ip, std.testing.allocator, &.{lhs, rhs}, builtin.target); const actual = try resolvePeerTypes(ip, std.testing.allocator, &.{ lhs, rhs }, builtin.target);
try std.testing.expectEqual(expected, actual); try std.testing.expectEqual(expected, actual);
} }