diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-10-26 13:46:27 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-10-26 13:46:27 -0700 |
| commit | c6b3d06535f4227541c13fe75da347a485abdb4f (patch) | |
| tree | 8f0a730e1dc7619168a81d2cd4521db1e7a1b4b6 /src | |
| parent | 6df26a37d13d21be061a1cccd39dd17e46a81322 (diff) | |
| download | zig-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.zig | 169 | ||||
| -rw-r--r-- | src/codegen/llvm.zig | 2 | ||||
| -rw-r--r-- | src/type.zig | 87 | ||||
| -rw-r--r-- | src/value.zig | 3 |
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, }; } |
