aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2021-10-26 13:46:27 -0700
committerAndrew Kelley <andrew@ziglang.org>2021-10-26 13:46:27 -0700
commitc6b3d06535f4227541c13fe75da347a485abdb4f (patch)
tree8f0a730e1dc7619168a81d2cd4521db1e7a1b4b6 /src
parent6df26a37d13d21be061a1cccd39dd17e46a81322 (diff)
downloadzig-c6b3d06535f4227541c13fe75da347a485abdb4f.tar.gz
zig-c6b3d06535f4227541c13fe75da347a485abdb4f.zip
Sema: improved C pointers and casting
* C pointer types always have allowzero set to true but they omit the word allowzero when printed. * Implement coercion from C pointers to other pointers. * Implement in-memory coercion for slices and pointer-like optionals. * Make slicing a C pointer drop the allowzero bit. * Value representation for pointer-like optionals is now allowed to use pointer tag values in addition to the `opt_payload` tag.
Diffstat (limited to 'src')
-rw-r--r--src/Sema.zig169
-rw-r--r--src/codegen/llvm.zig2
-rw-r--r--src/type.zig87
-rw-r--r--src/value.zig3
4 files changed, 180 insertions, 81 deletions
diff --git a/src/Sema.zig b/src/Sema.zig
index dfe42e38ea..d61aa52be4 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -9125,7 +9125,7 @@ fn zirPtrTypeSimple(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
.pointee_type = elem_type,
.@"addrspace" = .generic,
.mutable = inst_data.is_mutable,
- .@"allowzero" = inst_data.is_allowzero,
+ .@"allowzero" = inst_data.is_allowzero or inst_data.size == .C,
.@"volatile" = inst_data.is_volatile,
.size = inst_data.size,
});
@@ -9185,7 +9185,7 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
.bit_offset = bit_start,
.host_size = bit_end,
.mutable = inst_data.flags.is_mutable,
- .@"allowzero" = inst_data.flags.is_allowzero,
+ .@"allowzero" = inst_data.flags.is_allowzero or inst_data.size == .C,
.@"volatile" = inst_data.flags.is_volatile,
.size = inst_data.size,
});
@@ -12102,6 +12102,21 @@ fn coerce(
}
}
+ // coercion from C pointer
+ if (inst_ty.isCPtr()) src_c_ptr: {
+ // In this case we must add a safety check because the C pointer
+ // could be null.
+ const src_elem_ty = inst_ty.childType();
+ const dest_is_mut = dest_info.mutable;
+ const dst_elem_type = dest_info.pointee_type;
+ switch (coerceInMemoryAllowed(dst_elem_type, src_elem_ty, dest_is_mut, target)) {
+ .ok => {},
+ .no_match => break :src_c_ptr,
+ }
+ // TODO add safety check for null pointer
+ return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src);
+ }
+
// coercion to C pointer
if (dest_info.size == .C) {
switch (inst_ty.zigTypeTag()) {
@@ -12262,84 +12277,107 @@ const InMemoryCoercionResult = enum {
/// * sentinel-terminated pointers can coerce into `[*]`
/// TODO improve this function to report recursive compile errors like it does in stage1.
/// look at the function types_match_const_cast_only
-fn coerceInMemoryAllowed(dest_ty: Type, src_type: Type, dest_is_mut: bool, target: std.Target) InMemoryCoercionResult {
- if (dest_ty.eql(src_type))
+fn coerceInMemoryAllowed(dest_ty: Type, src_ty: Type, dest_is_mut: bool, target: std.Target) InMemoryCoercionResult {
+ if (dest_ty.eql(src_ty))
return .ok;
- if (dest_ty.zigTypeTag() == .Pointer and
- src_type.zigTypeTag() == .Pointer)
- {
- const dest_info = dest_ty.ptrInfo().data;
- const src_info = src_type.ptrInfo().data;
-
- const child = coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable, target);
- if (child == .no_match) {
- return child;
+ // Pointers / Pointer-like Optionals
+ var dest_buf: Type.Payload.ElemType = undefined;
+ var src_buf: Type.Payload.ElemType = undefined;
+ if (dest_ty.ptrOrOptionalPtrTy(&dest_buf)) |dest_ptr_ty| {
+ if (src_ty.ptrOrOptionalPtrTy(&src_buf)) |src_ptr_ty| {
+ return coerceInMemoryAllowedPtrs(dest_ty, src_ty, dest_ptr_ty, src_ptr_ty, dest_is_mut, target);
}
+ }
- if (dest_info.@"addrspace" != src_info.@"addrspace") {
- return .no_match;
- }
+ // Slices
+ if (dest_ty.isSlice() and src_ty.isSlice()) {
+ return coerceInMemoryAllowedPtrs(dest_ty, src_ty, dest_ty, src_ty, dest_is_mut, target);
+ }
- const ok_sent = dest_info.sentinel == null or src_info.size == .C or
- (src_info.sentinel != null and
- dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.pointee_type));
- if (!ok_sent) {
- return .no_match;
- }
+ // TODO: arrays
+ // TODO: non-pointer-like optionals
+ // TODO: error unions
+ // TODO: error sets
+ // TODO: functions
+ // TODO: vectors
- 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 .no_match;
- }
+ return .no_match;
+}
- const ok_cv_qualifiers =
- (src_info.mutable or !dest_info.mutable) and
- (!src_info.@"volatile" or dest_info.@"volatile");
+fn coerceInMemoryAllowedPtrs(
+ dest_ty: Type,
+ src_ty: Type,
+ dest_ptr_ty: Type,
+ src_ptr_ty: Type,
+ dest_is_mut: bool,
+ target: std.Target,
+) InMemoryCoercionResult {
+ const dest_info = dest_ptr_ty.ptrInfo().data;
+ const src_info = src_ptr_ty.ptrInfo().data;
- if (!ok_cv_qualifiers) {
- return .no_match;
- }
+ const child = coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable, target);
+ if (child == .no_match) {
+ return child;
+ }
- const ok_allows_zero = (dest_info.@"allowzero" and
- (src_info.@"allowzero" or !dest_is_mut)) or
- (!dest_info.@"allowzero" and !src_info.@"allowzero");
- if (!ok_allows_zero) {
- return .no_match;
- }
+ if (dest_info.@"addrspace" != src_info.@"addrspace") {
+ return .no_match;
+ }
- if (dest_ty.hasCodeGenBits() != src_type.hasCodeGenBits()) {
- return .no_match;
- }
+ const ok_sent = dest_info.sentinel == null or src_info.size == .C or
+ (src_info.sentinel != null and
+ dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.pointee_type));
+ if (!ok_sent) {
+ return .no_match;
+ }
- if (src_info.host_size != dest_info.host_size or
- src_info.bit_offset != dest_info.bit_offset)
- {
- return .no_match;
- }
+ 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 .no_match;
+ }
- // 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.
- if (src_info.@"align" != 0 or dest_info.@"align" != 0 or
- !dest_info.pointee_type.eql(src_info.pointee_type))
- {
- const src_align = src_type.ptrAlignment(target);
- const dest_align = dest_ty.ptrAlignment(target);
+ const ok_cv_qualifiers =
+ (src_info.mutable or !dest_info.mutable) and
+ (!src_info.@"volatile" or dest_info.@"volatile");
- if (dest_align > src_align) {
- return .no_match;
- }
- }
+ if (!ok_cv_qualifiers) {
+ return .no_match;
+ }
- return .ok;
+ const dest_allow_zero = dest_ty.ptrAllowsZero();
+ const src_allow_zero = src_ty.ptrAllowsZero();
+
+ const ok_allows_zero = (dest_allow_zero and
+ (src_allow_zero or !dest_is_mut)) or
+ (!dest_allow_zero and !src_allow_zero);
+ if (!ok_allows_zero) {
+ return .no_match;
}
- // TODO: implement more of this function
+ if (src_info.host_size != dest_info.host_size or
+ src_info.bit_offset != dest_info.bit_offset)
+ {
+ return .no_match;
+ }
- return .no_match;
+ // 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.
+ if (src_info.@"align" != 0 or dest_info.@"align" != 0 or
+ !dest_info.pointee_type.eql(src_info.pointee_type))
+ {
+ const src_align = src_info.@"align";
+ const dest_align = dest_info.@"align";
+
+ if (dest_align > src_align) {
+ return .no_match;
+ }
+ }
+
+ return .ok;
}
fn coerceNum(
@@ -13297,6 +13335,7 @@ fn analyzeSlice(
const opt_new_len_val = try sema.resolveDefinedValue(block, src, new_len);
const new_ptr_ty_info = sema.typeOf(new_ptr).ptrInfo().data;
+ const new_allowzero = new_ptr_ty_info.@"allowzero" and sema.typeOf(ptr).ptrSize() != .C;
if (opt_new_len_val) |new_len_val| {
const new_len_int = new_len_val.toUnsignedInt();
@@ -13312,7 +13351,7 @@ fn analyzeSlice(
.@"align" = new_ptr_ty_info.@"align",
.@"addrspace" = new_ptr_ty_info.@"addrspace",
.mutable = new_ptr_ty_info.mutable,
- .@"allowzero" = new_ptr_ty_info.@"allowzero",
+ .@"allowzero" = new_allowzero,
.@"volatile" = new_ptr_ty_info.@"volatile",
.size = .One,
});
@@ -13340,7 +13379,7 @@ fn analyzeSlice(
.@"align" = new_ptr_ty_info.@"align",
.@"addrspace" = new_ptr_ty_info.@"addrspace",
.mutable = new_ptr_ty_info.mutable,
- .@"allowzero" = new_ptr_ty_info.@"allowzero",
+ .@"allowzero" = new_allowzero,
.@"volatile" = new_ptr_ty_info.@"volatile",
.size = .Slice,
});
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index ca9e973354..fd92dec413 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1184,6 +1184,8 @@ pub const DeclGen = struct {
if (tv.ty.isPtrLikeOptional()) {
if (tv.val.castTag(.opt_payload)) |payload| {
return self.genTypedValue(.{ .ty = payload_ty, .val = payload.data });
+ } else if (is_pl) {
+ return self.genTypedValue(.{ .ty = payload_ty, .val = tv.val });
} else {
const llvm_ty = try self.llvmType(tv.ty);
return llvm_ty.constNull();
diff --git a/src/type.zig b/src/type.zig
index 589639ab7f..e1a407011b 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -390,7 +390,7 @@ pub const Type = extern union {
.@"addrspace" = .generic,
.bit_offset = 0,
.host_size = 0,
- .@"allowzero" = false,
+ .@"allowzero" = true,
.mutable = false,
.@"volatile" = false,
.size = .C,
@@ -402,7 +402,7 @@ pub const Type = extern union {
.@"addrspace" = .generic,
.bit_offset = 0,
.host_size = 0,
- .@"allowzero" = false,
+ .@"allowzero" = true,
.mutable = true,
.@"volatile" = false,
.size = .C,
@@ -1153,7 +1153,7 @@ pub const Type = extern union {
}
if (!payload.mutable) try writer.writeAll("const ");
if (payload.@"volatile") try writer.writeAll("volatile ");
- if (payload.@"allowzero") try writer.writeAll("allowzero ");
+ if (payload.@"allowzero" and payload.size != .C) try writer.writeAll("allowzero ");
ty = payload.pointee_type;
continue;
@@ -2347,7 +2347,48 @@ pub const Type = extern union {
}
}
- /// Asserts that the type is an optional or a pointer that can be null.
+ /// For pointer-like optionals, returns true, otherwise returns the allowzero property
+ /// of pointers.
+ pub fn ptrAllowsZero(ty: Type) bool {
+ if (ty.isPtrLikeOptional()) {
+ return true;
+ }
+ return ty.ptrInfo().data.@"allowzero";
+ }
+
+ /// For pointer-like optionals, it returns the pointer type. For pointers,
+ /// the type is returned unmodified.
+ pub fn ptrOrOptionalPtrTy(ty: Type, buf: *Payload.ElemType) ?Type {
+ if (isPtrLikeOptional(ty)) return ty.optionalChild(buf);
+ switch (ty.tag()) {
+ .c_const_pointer,
+ .c_mut_pointer,
+ .single_const_pointer_to_comptime_int,
+ .single_const_pointer,
+ .single_mut_pointer,
+ .many_const_pointer,
+ .many_mut_pointer,
+ .manyptr_u8,
+ .manyptr_const_u8,
+ => return ty,
+
+ .pointer => {
+ if (ty.ptrSize() == .Slice) {
+ return null;
+ } else {
+ return ty;
+ }
+ },
+
+ .inferred_alloc_const => unreachable,
+ .inferred_alloc_mut => unreachable,
+
+ else => return null,
+ }
+ }
+
+ /// 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(self: Type) bool {
switch (self.tag()) {
.optional_single_const_pointer,
@@ -2371,7 +2412,8 @@ pub const Type = extern union {
},
.pointer => return self.castTag(.pointer).?.data.size == .C,
- else => unreachable,
+
+ else => return false,
}
}
@@ -2532,38 +2574,50 @@ pub const Type = extern union {
/// Asserts that the type is an optional.
/// Resulting `Type` will have inner memory referencing `buf`.
- pub fn optionalChild(self: Type, buf: *Payload.ElemType) Type {
- return switch (self.tag()) {
- .optional => self.castTag(.optional).?.data,
+ /// Note that for C pointers this returns the type unmodified.
+ pub fn optionalChild(ty: Type, buf: *Payload.ElemType) Type {
+ return switch (ty.tag()) {
+ .optional => ty.castTag(.optional).?.data,
.optional_single_mut_pointer => {
buf.* = .{
.base = .{ .tag = .single_mut_pointer },
- .data = self.castPointer().?.data,
+ .data = ty.castPointer().?.data,
};
return Type.initPayload(&buf.base);
},
.optional_single_const_pointer => {
buf.* = .{
.base = .{ .tag = .single_const_pointer },
- .data = self.castPointer().?.data,
+ .data = ty.castPointer().?.data,
};
return Type.initPayload(&buf.base);
},
+
+ .pointer, // here we assume it is a C pointer
+ .c_const_pointer,
+ .c_mut_pointer,
+ => return ty,
+
else => unreachable,
};
}
/// Asserts that the type is an optional.
/// Same as `optionalChild` but allocates the buffer if needed.
- pub fn optionalChildAlloc(self: Type, allocator: *Allocator) !Type {
- switch (self.tag()) {
- .optional => return self.castTag(.optional).?.data,
+ pub fn optionalChildAlloc(ty: Type, allocator: *Allocator) !Type {
+ switch (ty.tag()) {
+ .optional => return ty.castTag(.optional).?.data,
.optional_single_mut_pointer => {
- return Tag.single_mut_pointer.create(allocator, self.castPointer().?.data);
+ return Tag.single_mut_pointer.create(allocator, ty.castPointer().?.data);
},
.optional_single_const_pointer => {
- return Tag.single_const_pointer.create(allocator, self.castPointer().?.data);
+ return Tag.single_const_pointer.create(allocator, ty.castPointer().?.data);
},
+ .pointer, // here we assume it is a C pointer
+ .c_const_pointer,
+ .c_mut_pointer,
+ => return ty,
+
else => unreachable,
}
}
@@ -4050,6 +4104,9 @@ pub const Type = extern union {
if (d.sentinel != null or d.@"align" != 0 or d.@"addrspace" != .generic or
d.bit_offset != 0 or d.host_size != 0 or d.@"allowzero" or d.@"volatile")
{
+ if (d.size == .C) {
+ assert(d.@"allowzero"); // All C pointers must set allowzero to true.
+ }
return Type.Tag.pointer.create(arena, d);
}
diff --git a/src/value.zig b/src/value.zig
index 19c2a73666..382aeeedf7 100644
--- a/src/value.zig
+++ b/src/value.zig
@@ -1819,7 +1819,8 @@ pub const Value = extern union {
.unreachable_value => unreachable,
.inferred_alloc => unreachable,
.inferred_alloc_comptime => unreachable,
- else => unreachable,
+
+ else => false,
};
}