From 75eaf15740bc42167592f3bdd7205236828191c7 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 12 Aug 2020 16:00:29 +0300 Subject: [PATCH 1/5] stage2: add optional types --- src-self-hosted/type.zig | 181 +++++++++++++++++++++++++++++++++------ 1 file changed, 155 insertions(+), 26 deletions(-) diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 8d643c2005..fbc34c27bc 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -70,6 +70,11 @@ pub const Type = extern union { .single_mut_pointer => return .Pointer, .single_const_pointer_to_comptime_int => return .Pointer, .const_slice_u8 => return .Pointer, + + .optional, + .optional_single_const_pointer, + .optional_single_mut_pointer, + => return .Optional, } } @@ -179,9 +184,13 @@ pub const Type = extern union { } return true; }, + .Optional => { + if (a.tag() != b.tag()) + return false; + return a.elemType().eql(b.elemType()); + }, .Float, .Struct, - .Optional, .ErrorUnion, .ErrorSet, .Enum, @@ -241,9 +250,11 @@ pub const Type = extern union { std.hash.autoHash(&hasher, self.fnParamType(i).hash()); } }, + .Optional => { + std.hash.autoHash(&hasher, self.elemType().hash()); + }, .Float, .Struct, - .Optional, .ErrorUnion, .ErrorSet, .Enum, @@ -317,24 +328,8 @@ pub const Type = extern union { }; return Type{ .ptr_otherwise = &new_payload.base }; }, - .single_const_pointer => { - const payload = @fieldParentPtr(Payload.SingleConstPointer, "base", self.ptr_otherwise); - const new_payload = try allocator.create(Payload.SingleConstPointer); - new_payload.* = .{ - .base = payload.base, - .pointee_type = try payload.pointee_type.copy(allocator), - }; - return Type{ .ptr_otherwise = &new_payload.base }; - }, - .single_mut_pointer => { - const payload = @fieldParentPtr(Payload.SingleMutPointer, "base", self.ptr_otherwise); - const new_payload = try allocator.create(Payload.SingleMutPointer); - new_payload.* = .{ - .base = payload.base, - .pointee_type = try payload.pointee_type.copy(allocator), - }; - return Type{ .ptr_otherwise = &new_payload.base }; - }, + .single_const_pointer => return self.copyPayloadSingleField(allocator, Payload.SingleConstPointer, "pointee_type"), + .single_mut_pointer => return self.copyPayloadSingleField(allocator, Payload.SingleMutPointer, "pointee_type"), .int_signed => return self.copyPayloadShallow(allocator, Payload.IntSigned), .int_unsigned => return self.copyPayloadShallow(allocator, Payload.IntUnsigned), .function => { @@ -352,6 +347,9 @@ pub const Type = extern union { }; return Type{ .ptr_otherwise = &new_payload.base }; }, + .optional => return self.copyPayloadSingleField(allocator, Payload.Optional, "child_type"), + .optional_single_mut_pointer => return self.copyPayloadSingleField(allocator, Payload.OptionalSingleMutPointer, "pointee_type"), + .optional_single_const_pointer => return self.copyPayloadSingleField(allocator, Payload.OptionalSingleConstPointer, "pointee_type"), } } @@ -362,6 +360,14 @@ pub const Type = extern union { return Type{ .ptr_otherwise = &new_payload.base }; } + fn copyPayloadSingleField(self: Type, allocator: *Allocator, comptime T: type, comptime field_name: []const u8) error{OutOfMemory}!Type { + const payload = @fieldParentPtr(T, "base", self.ptr_otherwise); + const new_payload = try allocator.create(T); + new_payload.base = payload.base; + @field(new_payload, field_name) = try @field(payload, field_name).copy(allocator); + return Type{ .ptr_otherwise = &new_payload.base }; + } + pub fn format( self: Type, comptime fmt: []const u8, @@ -456,6 +462,24 @@ pub const Type = extern union { const payload = @fieldParentPtr(Payload.IntUnsigned, "base", ty.ptr_otherwise); return out_stream.print("u{}", .{payload.bits}); }, + .optional => { + const payload = @fieldParentPtr(Payload.Optional, "base", ty.ptr_otherwise); + try out_stream.writeByte('?'); + ty = payload.child_type; + continue; + }, + .optional_single_const_pointer => { + const payload = @fieldParentPtr(Payload.OptionalSingleConstPointer, "base", ty.ptr_otherwise); + try out_stream.writeAll("?*const "); + ty = payload.pointee_type; + continue; + }, + .optional_single_mut_pointer => { + const payload = @fieldParentPtr(Payload.OptionalSingleMutPointer, "base", ty.ptr_otherwise); + try out_stream.writeAll("?*"); + ty = payload.pointee_type; + continue; + }, } unreachable; } @@ -545,11 +569,15 @@ pub const Type = extern union { .single_const_pointer_to_comptime_int, .const_slice_u8, .array_u8_sentinel_0, - .array, // TODO check for zero bits - .single_const_pointer, - .single_mut_pointer, - .int_signed, // TODO check for zero bits - .int_unsigned, // TODO check for zero bits + // TODO lazy types + .array => self.elemType().hasCodeGenBits() and self.arrayLen() != 0, + .single_const_pointer => self.elemType().hasCodeGenBits(), + .single_mut_pointer => self.elemType().hasCodeGenBits(), + .int_signed => self.cast(Payload.IntSigned).?.bits == 0, + .int_unsigned => self.cast(Payload.IntUnsigned).?.bits == 0, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => true, .c_void, @@ -597,6 +625,8 @@ pub const Type = extern union { .const_slice_u8, .single_const_pointer, .single_mut_pointer, + .optional_single_const_pointer, + .optional_single_mut_pointer, => return @divExact(target.cpu.arch.ptrBitWidth(), 8), .c_short => return @divExact(CType.short.sizeInBits(target), 8), @@ -629,6 +659,12 @@ pub const Type = extern union { return std.math.ceilPowerOfTwoPromote(u16, (bits + 7) / 8); }, + .optional => { + const child_type = self.cast(Payload.Optional).?.child_type; + if (!child_type.hasCodeGenBits()) return 1; + return child_type.abiAlignment(target); + }, + .c_void, .void, .type, @@ -679,6 +715,8 @@ pub const Type = extern union { .const_slice_u8, .single_const_pointer, .single_mut_pointer, + .optional_single_const_pointer, + .optional_single_mut_pointer, => return @divExact(target.cpu.arch.ptrBitWidth(), 8), .c_short => return @divExact(CType.short.sizeInBits(target), 8), @@ -708,6 +746,16 @@ pub const Type = extern union { return std.math.ceilPowerOfTwoPromote(u16, (bits + 7) / 8); }, + + .optional => { + const child_type = self.cast(Payload.Optional).?.child_type; + if (!child_type.hasCodeGenBits()) return 1; + // Optional types are represented as a struct with the child type as the first + // field and a boolean as the second. Since the child type's abi alignment is + // guaranteed to be >= that of bool's (1 byte) the added size is exactly equal + // to the child type's ABI alignment. + return child_type.abiAlignment(target) + child_type.abiSize(target); + }, }; } @@ -756,6 +804,9 @@ pub const Type = extern union { .function, .int_unsigned, .int_signed, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => false, .single_const_pointer, @@ -812,6 +863,9 @@ pub const Type = extern union { .function, .int_unsigned, .int_signed, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => false, .const_slice_u8 => true, @@ -863,6 +917,9 @@ pub const Type = extern union { .int_unsigned, .int_signed, .single_mut_pointer, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => false, .single_const_pointer, @@ -920,11 +977,14 @@ pub const Type = extern union { .single_const_pointer, .single_const_pointer_to_comptime_int, .const_slice_u8, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => false, }; } - /// Asserts the type is a pointer or array type. + /// Asserts the type is a pointer, optional or array type. pub fn elemType(self: Type) Type { return switch (self.tag()) { .u8, @@ -974,6 +1034,9 @@ pub const Type = extern union { .single_mut_pointer => self.cast(Payload.SingleMutPointer).?.pointee_type, .array_u8_sentinel_0, .const_slice_u8 => Type.initTag(.u8), .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int), + .optional => self.cast(Payload.Optional).?.child_type, + .optional_single_mut_pointer => self.cast(Payload.OptionalSingleMutPointer).?.pointee_type, + .optional_single_const_pointer => self.cast(Payload.OptionalSingleConstPointer).?.pointee_type, }; } @@ -1024,6 +1087,9 @@ pub const Type = extern union { .const_slice_u8, .int_unsigned, .int_signed, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => unreachable, .array => self.cast(Payload.Array).?.len, @@ -1078,6 +1144,9 @@ pub const Type = extern union { .const_slice_u8, .int_unsigned, .int_signed, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => unreachable, .array => return null, @@ -1129,6 +1198,9 @@ pub const Type = extern union { .u16, .u32, .u64, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => false, .int_signed, @@ -1184,6 +1256,9 @@ pub const Type = extern union { .i16, .i32, .i64, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => false, .int_unsigned, @@ -1229,6 +1304,9 @@ pub const Type = extern union { .single_const_pointer_to_comptime_int, .array_u8_sentinel_0, .const_slice_u8, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => unreachable, .int_unsigned => .{ .signed = false, .bits = self.cast(Payload.IntUnsigned).?.bits }, @@ -1292,6 +1370,9 @@ pub const Type = extern union { .i32, .u64, .i64, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => false, .usize, @@ -1384,6 +1465,9 @@ pub const Type = extern union { .c_ulonglong, .int_unsigned, .int_signed, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => unreachable, }; } @@ -1442,6 +1526,9 @@ pub const Type = extern union { .c_ulonglong, .int_unsigned, .int_signed, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => unreachable, } } @@ -1499,6 +1586,9 @@ pub const Type = extern union { .c_ulonglong, .int_unsigned, .int_signed, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => unreachable, } } @@ -1556,6 +1646,9 @@ pub const Type = extern union { .c_ulonglong, .int_unsigned, .int_signed, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => unreachable, }; } @@ -1610,6 +1703,9 @@ pub const Type = extern union { .c_ulonglong, .int_unsigned, .int_signed, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => unreachable, }; } @@ -1664,6 +1760,9 @@ pub const Type = extern union { .c_ulonglong, .int_unsigned, .int_signed, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => unreachable, }; } @@ -1718,6 +1817,9 @@ pub const Type = extern union { .single_const_pointer_to_comptime_int, .array_u8_sentinel_0, .const_slice_u8, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => false, }; } @@ -1762,6 +1864,9 @@ pub const Type = extern union { .array_u8_sentinel_0, .const_slice_u8, .c_void, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => return null, .void => return Value.initTag(.void_value), @@ -1851,6 +1956,9 @@ pub const Type = extern union { .array, .single_const_pointer, .single_mut_pointer, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, => return false, }; } @@ -1911,6 +2019,9 @@ pub const Type = extern union { int_signed, int_unsigned, function, + optional, + optional_single_mut_pointer, + optional_single_const_pointer, pub const last_no_payload_tag = Tag.const_slice_u8; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -1963,6 +2074,24 @@ pub const Type = extern union { return_type: Type, cc: std.builtin.CallingConvention, }; + + pub const Optional = struct { + base: Payload = Payload{ .tag = .optional }, + + child_type: Type, + }; + + pub const OptionalSingleConstPointer = struct { + base: Payload = Payload{ .tag = .optional_single_const_pointer }, + + pointee_type: Type, + }; + + pub const OptionalSingleMutPointer = struct { + base: Payload = Payload{ .tag = .optional_single_mut_pointer }, + + pointee_type: Type, + }; }; }; From 5c1fe5861389462f309e3f0b69096a85f330dc20 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 12 Aug 2020 21:06:29 +0300 Subject: [PATCH 2/5] stage2: gen optional types --- src-self-hosted/Module.zig | 18 ++++++++++++++++++ src-self-hosted/astgen.zig | 12 ++++++++++++ src-self-hosted/type.zig | 8 ++++---- src-self-hosted/zir.zig | 18 ++++++++++++++++++ src-self-hosted/zir_sema.zig | 29 +++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 6abd4f51e1..110fe05b8e 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2476,6 +2476,24 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst } assert(inst.ty.zigTypeTag() != .Undefined); + // null to ?T + if (dest_type.zigTypeTag() == .Optional and inst.ty.zigTypeTag() == .Null) { + return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = inst.ty.onePossibleValue().? }); + } + + // T to ?T + if (dest_type.zigTypeTag() == .Optional) { + const child_type = dest_type.elemType(); + if (inst.value()) |val| { + if (child_type.eql(inst.ty)) { + return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); + } + return self.fail(scope, inst.src, "TODO optional wrap {} to {}", .{ val, inst.ty }); + } else if (child_type.eql(inst.ty)) { + return self.fail(scope, inst.src, "TODO optional wrap {}", .{inst.ty}); + } + } + // *[N]T to []T if (inst.ty.isSinglePointer() and dest_type.isSlice() and (!inst.ty.isConstPtr() or dest_type.isConstPtr())) diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 1827d53043..57f399d696 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -105,6 +105,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .UndefinedLiteral => return rlWrap(mod, scope, rl, try undefLiteral(mod, scope, node.castTag(.UndefinedLiteral).?)), .BoolLiteral => return rlWrap(mod, scope, rl, try boolLiteral(mod, scope, node.castTag(.BoolLiteral).?)), .NullLiteral => return rlWrap(mod, scope, rl, try nullLiteral(mod, scope, node.castTag(.NullLiteral).?)), + .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)), else => return mod.failNode(scope, node, "TODO implement astgen.Expr for {}", .{@tagName(node.tag)}), } } @@ -293,6 +294,17 @@ fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerErr return addZIRUnOp(mod, scope, src, .boolnot, operand); } +fn optionalType(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.op_token].start; + const meta_type = try addZIRInstConst(mod, scope, src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.type_type), + }); + const operand = try expr(mod, scope, .{ .ty = meta_type }, node.rhs); + return addZIRUnOp(mod, scope, src, .optional_type, operand); +} + /// Identifier token -> String (allocated in scope.arena()) pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 { const tree = scope.tree(); diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index fbc34c27bc..eadb91ffea 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -569,16 +569,16 @@ pub const Type = extern union { .single_const_pointer_to_comptime_int, .const_slice_u8, .array_u8_sentinel_0, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, + => true, // TODO lazy types .array => self.elemType().hasCodeGenBits() and self.arrayLen() != 0, .single_const_pointer => self.elemType().hasCodeGenBits(), .single_mut_pointer => self.elemType().hasCodeGenBits(), .int_signed => self.cast(Payload.IntSigned).?.bits == 0, .int_unsigned => self.cast(Payload.IntUnsigned).?.bits == 0, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - => true, .c_void, .void, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index ba0c05d587..c98fcf4a74 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -212,6 +212,8 @@ pub const Inst = struct { @"unreachable", /// Bitwise XOR. `^` xor, + /// Create an optional type '?T' + optional_type, pub fn Type(tag: Tag) type { return switch (tag) { @@ -240,6 +242,7 @@ pub const Inst = struct { .typeof, .single_const_ptr_type, .single_mut_ptr_type, + .optional_type, => UnOp, .add, @@ -372,6 +375,7 @@ pub const Inst = struct { .subwrap, .typeof, .xor, + .optional_type, => false, .@"break", @@ -2242,6 +2246,20 @@ const EmitZIR = struct { std.debug.panic("TODO implement emitType for {}", .{ty}); } }, + .Optional => { + const inst = try self.arena.allocator.create(Inst.UnOp); + inst.* = .{ + .base = .{ + .src = src, + .tag = .optional_type, + }, + .positionals = .{ + .operand = (try self.emitType(src, ty.elemType())).inst, + }, + .kw_args = .{}, + }; + return self.emitUnnamedDecl(&inst.base); + }, else => std.debug.panic("TODO implement emitType for {}", .{ty}), }, } diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index e36487e8d5..e241caefc9 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -106,6 +106,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .isnonnull => return analyzeInstIsNonNull(mod, scope, old_inst.castTag(.isnonnull).?, false), .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?), .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?), + .optional_type => return analyzeInstOptionalType(mod, scope, old_inst.castTag(.optional_type).?), } } @@ -620,6 +621,34 @@ fn analyzeInstIntType(mod: *Module, scope: *Scope, inttype: *zir.Inst.IntType) I return mod.fail(scope, inttype.base.src, "TODO implement inttype", .{}); } +fn analyzeInstOptionalType(mod: *Module, scope: *Scope, optional: *zir.Inst.UnOp) InnerError!*Inst { + const child_type = try resolveType(mod, scope, optional.positionals.operand); + + return mod.constType(scope, optional.base.src, Type.initPayload(switch (child_type.tag()) { + .single_const_pointer => blk: { + const payload = try scope.arena().create(Type.Payload.OptionalSingleConstPointer); + payload.* = .{ + .pointee_type = child_type.elemType(), + }; + break :blk &payload.base; + }, + .single_mut_pointer => blk: { + const payload = try scope.arena().create(Type.Payload.OptionalSingleMutPointer); + payload.* = .{ + .pointee_type = child_type.elemType(), + }; + break :blk &payload.base; + }, + else => blk: { + const payload = try scope.arena().create(Type.Payload.Optional); + payload.* = .{ + .child_type = child_type, + }; + break :blk &payload.base; + }, + })); +} + fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { const return_type = try resolveType(mod, scope, fntype.positionals.return_type); From 4a40282391f0b92a83a6a8c269c27a32be92884a Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 12 Aug 2020 22:30:14 +0300 Subject: [PATCH 3/5] stage2: implement unwrap optional --- src-self-hosted/Module.zig | 26 +++++++++++++++++++-- src-self-hosted/astgen.zig | 12 ++++++++++ src-self-hosted/codegen.zig | 10 ++++++++ src-self-hosted/ir.zig | 22 ++++++++++++++++++ src-self-hosted/zir.zig | 35 ++++++++++++++++++++++++++++ src-self-hosted/zir_sema.zig | 42 +++++++++++++++++++++++++++++++++- test/stage2/compare_output.zig | 5 ++++ 7 files changed, 149 insertions(+), 3 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 110fe05b8e..bbbcde9a71 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2016,6 +2016,28 @@ pub fn addCall( return &inst.base; } +pub fn addUnwrapOptional( + self: *Module, + block: *Scope.Block, + src: usize, + ty: Type, + operand: *Inst, + safety_check: bool, +) !*Inst { + const inst = try block.arena.create(Inst.UnwrapOptional); + inst.* = .{ + .base = .{ + .tag = .unwrap_optional, + .ty = ty, + .src = src, + }, + .operand = operand, + .safety_check = safety_check, + }; + try block.instructions.append(self.gpa, &inst.base); + return &inst.base; +} + pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst { const const_inst = try scope.arena().create(Inst.Constant); const_inst.* = .{ @@ -2488,9 +2510,9 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst if (child_type.eql(inst.ty)) { return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); } - return self.fail(scope, inst.src, "TODO optional wrap {} to {}", .{ val, inst.ty }); + return self.fail(scope, inst.src, "TODO optional wrap {} to {}", .{ val, dest_type }); } else if (child_type.eql(inst.ty)) { - return self.fail(scope, inst.src, "TODO optional wrap {}", .{inst.ty}); + return self.fail(scope, inst.src, "TODO optional wrap {}", .{dest_type}); } } diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 57f399d696..ed3f8ab1b6 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -106,6 +106,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .BoolLiteral => return rlWrap(mod, scope, rl, try boolLiteral(mod, scope, node.castTag(.BoolLiteral).?)), .NullLiteral => return rlWrap(mod, scope, rl, try nullLiteral(mod, scope, node.castTag(.NullLiteral).?)), .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)), + .UnwrapOptional => return unwrapOptional(mod, scope, rl, node.castTag(.UnwrapOptional).?), else => return mod.failNode(scope, node, "TODO implement astgen.Expr for {}", .{@tagName(node.tag)}), } } @@ -305,6 +306,17 @@ fn optionalType(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) Inn return addZIRUnOp(mod, scope, src, .optional_type, operand); } +fn unwrapOptional(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.rtoken].start; + + const operand = try expr(mod, scope, .lvalue, node.lhs); + const unwrapped_ptr = try addZIRInst(mod, scope, src, zir.Inst.UnwrapOptional, .{ .operand = operand }, .{}); + if (rl == .lvalue) return unwrapped_ptr; + + return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, unwrapped_ptr)); +} + /// Identifier token -> String (allocated in scope.arena()) pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 { const tree = scope.tree(); diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 6e8ab34478..887126ba2b 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -668,6 +668,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .store => return self.genStore(inst.castTag(.store).?), .sub => return self.genSub(inst.castTag(.sub).?), .unreach => return MCValue{ .unreach = {} }, + .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?), } } @@ -817,6 +818,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } + fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnwrapOptional) !MCValue { + // No side effects, so if it's unreferenced, do nothing. + if (inst.base.isUnused()) + return MCValue.dead; + switch (arch) { + else => return self.fail(inst.base.src, "TODO implement unwrap optional for {}", .{self.target.cpu.arch}), + } + } + fn genLoad(self: *Self, inst: *ir.Inst.UnOp) !MCValue { const elem_ty = inst.base.ty; if (!elem_ty.hasCodeGenBits()) diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index f4262592de..93952a4214 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -82,6 +82,7 @@ pub const Inst = struct { not, floatcast, intcast, + unwrap_optional, pub fn Type(tag: Tag) type { return switch (tag) { @@ -124,6 +125,7 @@ pub const Inst = struct { .condbr => CondBr, .constant => Constant, .loop => Loop, + .unwrap_optional => UnwrapOptional, }; } @@ -420,6 +422,26 @@ pub const Inst = struct { } }; + pub const UnwrapOptional = struct { + pub const base_tag = Tag.unwrap_optional; + base: Inst, + + operand: *Inst, + safety_check: bool, + + pub fn operandCount(self: *const UnwrapOptional) usize { + return 1; + } + pub fn getOperand(self: *const UnwrapOptional, index: usize) ?*Inst { + var i = index; + + if (i < 1) + return self.operand; + i -= 1; + + return null; + } + }; }; pub const Body = struct { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index c98fcf4a74..84e9731126 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -214,6 +214,8 @@ pub const Inst = struct { xor, /// Create an optional type '?T' optional_type, + /// Unwraps an optional value 'lhs.?' + unwrap_optional, pub fn Type(tag: Tag) type { return switch (tag) { @@ -301,6 +303,7 @@ pub const Inst = struct { .fntype => FnType, .elemptr => ElemPtr, .condbr => CondBr, + .unwrap_optional => UnwrapOptional, }; } @@ -376,6 +379,7 @@ pub const Inst = struct { .typeof, .xor, .optional_type, + .unwrap_optional, => false, .@"break", @@ -816,6 +820,18 @@ pub const Inst = struct { }, kw_args: struct {}, }; + + pub const UnwrapOptional = struct { + pub const base_tag = Tag.unwrap_optional; + base: Inst, + + positionals: struct { + operand: *Inst, + }, + kw_args: struct { + safety_check: bool = true, + }, + }; }; pub const ErrorMsg = struct { @@ -2141,6 +2157,25 @@ const EmitZIR = struct { }; break :blk &new_inst.base; }, + + .unwrap_optional => blk: { + const old_inst = inst.castTag(.unwrap_optional).?; + + const new_inst = try self.arena.allocator.create(Inst.UnwrapOptional); + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.UnwrapOptional.base_tag, + }, + .positionals = .{ + .operand = try self.resolveInst(new_body, old_inst.operand), + }, + .kw_args = .{ + .safety_check = old_inst.safety_check, + }, + }; + break :blk &new_inst.base; + }, }; try instructions.append(new_inst); try inst_table.put(inst, new_inst); diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index e241caefc9..39fbf9221a 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -107,6 +107,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?), .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?), .optional_type => return analyzeInstOptionalType(mod, scope, old_inst.castTag(.optional_type).?), + .unwrap_optional => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional).?), } } @@ -306,8 +307,19 @@ fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerErr fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const operand = try resolveInst(mod, scope, inst.positionals.operand); - const b = try mod.requireRuntimeBlock(scope, inst.base.src); const ptr_type = try mod.singleConstPtrType(scope, inst.base.src, operand.ty); + + if (operand.value()) |val| { + const ref_payload = try scope.arena().create(Value.Payload.RefVal); + ref_payload.* = .{ .val = val }; + + return mod.constInst(scope, inst.base.src, .{ + .ty = ptr_type, + .val = Value.initPayload(&ref_payload.base), + }); + } + + const b = try mod.requireRuntimeBlock(scope, inst.base.src); return mod.addUnOp(b, inst.base.src, ptr_type, .ref, operand); } @@ -649,6 +661,34 @@ fn analyzeInstOptionalType(mod: *Module, scope: *Scope, optional: *zir.Inst.UnOp })); } +fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnwrapOptional) InnerError!*Inst { + const operand = try resolveInst(mod, scope, unwrap.positionals.operand); + assert(operand.ty.zigTypeTag() == .Pointer); + + if (operand.ty.elemType().zigTypeTag() != .Optional) { + return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{operand.ty.elemType()}); + } + + const child_type = operand.ty.elemType().elemType(); + const child_pointer = if (operand.ty.isConstPtr()) + try mod.singleConstPtrType(scope, unwrap.base.src, child_type) + else + try mod.singleMutPtrType(scope, unwrap.base.src, child_type); + + if (operand.value()) |val| { + if (val.tag() == .null_value) { + return mod.fail(scope, unwrap.base.src, "unable to unwrap null", .{}); + } + return mod.constInst(scope, unwrap.base.src, .{ + .ty = child_pointer, + .val = val, + }); + } + + const b = try mod.requireRuntimeBlock(scope, unwrap.base.src); + return mod.addUnwrapOptional(b, unwrap.base.src, child_pointer, operand, unwrap.kw_args.safety_check); +} + fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { const return_type = try resolveType(mod, scope, fntype.positionals.return_type); diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index bb3e542f13..477b5d92b1 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -31,6 +31,11 @@ pub fn addCases(ctx: *TestContext) !void { \\export fn _start() noreturn { \\ print(); \\ + \\ const a: u32 = 2; + \\ const b: ?u32 = a; + \\ const c = b.?; + \\ if (c != 2) unreachable; + \\ \\ exit(); \\} \\ From 6b2ce9d1e99902d429d9946bcc57e8cb02f4d224 Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 13 Aug 2020 14:18:44 +0300 Subject: [PATCH 4/5] stage2: split unwrap_optional to safe and unsafe verions --- src-self-hosted/Module.zig | 24 +------------------ src-self-hosted/astgen.zig | 2 +- src-self-hosted/codegen.zig | 5 ++-- src-self-hosted/ir.zig | 27 ++++----------------- src-self-hosted/type.zig | 10 ++++++-- src-self-hosted/zir.zig | 43 +++++++--------------------------- src-self-hosted/zir_sema.zig | 10 +++++--- test/stage2/compare_output.zig | 29 +++++++++++++++++++---- 8 files changed, 57 insertions(+), 93 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index bbbcde9a71..66da42cadf 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2016,28 +2016,6 @@ pub fn addCall( return &inst.base; } -pub fn addUnwrapOptional( - self: *Module, - block: *Scope.Block, - src: usize, - ty: Type, - operand: *Inst, - safety_check: bool, -) !*Inst { - const inst = try block.arena.create(Inst.UnwrapOptional); - inst.* = .{ - .base = .{ - .tag = .unwrap_optional, - .ty = ty, - .src = src, - }, - .operand = operand, - .safety_check = safety_check, - }; - try block.instructions.append(self.gpa, &inst.base); - return &inst.base; -} - pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst { const const_inst = try scope.arena().create(Inst.Constant); const_inst.* = .{ @@ -2500,7 +2478,7 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst // null to ?T if (dest_type.zigTypeTag() == .Optional and inst.ty.zigTypeTag() == .Null) { - return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = inst.ty.onePossibleValue().? }); + return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = Value.initTag(.null_value) }); } // T to ?T diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index ed3f8ab1b6..95c4e63c92 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -311,7 +311,7 @@ fn unwrapOptional(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Si const src = tree.token_locs[node.rtoken].start; const operand = try expr(mod, scope, .lvalue, node.lhs); - const unwrapped_ptr = try addZIRInst(mod, scope, src, zir.Inst.UnwrapOptional, .{ .operand = operand }, .{}); + const unwrapped_ptr = try addZIRUnOp(mod, scope, src, .unwrap_optional_safe, operand); if (rl == .lvalue) return unwrapped_ptr; return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, unwrapped_ptr)); diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 887126ba2b..37e775b4b3 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -668,7 +668,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .store => return self.genStore(inst.castTag(.store).?), .sub => return self.genSub(inst.castTag(.sub).?), .unreach => return MCValue{ .unreach = {} }, - .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?), + .unwrap_optional_safe => return self.genUnwrapOptional(inst.castTag(.unwrap_optional_safe).?, true), + .unwrap_optional_unsafe => return self.genUnwrapOptional(inst.castTag(.unwrap_optional_unsafe).?, false), } } @@ -818,7 +819,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnwrapOptional) !MCValue { + fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnOp, safety_check: bool) !MCValue { // No side effects, so if it's unreferenced, do nothing. if (inst.base.isUnused()) return MCValue.dead; diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 93952a4214..307dcfe62e 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -82,7 +82,8 @@ pub const Inst = struct { not, floatcast, intcast, - unwrap_optional, + unwrap_optional_safe, + unwrap_optional_unsafe, pub fn Type(tag: Tag) type { return switch (tag) { @@ -103,6 +104,8 @@ pub const Inst = struct { .floatcast, .intcast, .load, + .unwrap_optional_safe, + .unwrap_optional_unsafe, => UnOp, .add, @@ -125,7 +128,6 @@ pub const Inst = struct { .condbr => CondBr, .constant => Constant, .loop => Loop, - .unwrap_optional => UnwrapOptional, }; } @@ -421,27 +423,6 @@ pub const Inst = struct { return null; } }; - - pub const UnwrapOptional = struct { - pub const base_tag = Tag.unwrap_optional; - base: Inst, - - operand: *Inst, - safety_check: bool, - - pub fn operandCount(self: *const UnwrapOptional) usize { - return 1; - } - pub fn getOperand(self: *const UnwrapOptional, index: usize) ?*Inst { - var i = index; - - if (i < 1) - return self.operand; - i -= 1; - - return null; - } - }; }; pub const Body = struct { diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index eadb91ffea..ef78b046e6 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -185,8 +185,6 @@ pub const Type = extern union { return true; }, .Optional => { - if (a.tag() != b.tag()) - return false; return a.elemType().eql(b.elemType()); }, .Float, @@ -662,6 +660,10 @@ pub const Type = extern union { .optional => { const child_type = self.cast(Payload.Optional).?.child_type; if (!child_type.hasCodeGenBits()) return 1; + + if (child_type.zigTypeTag() == .Pointer and !child_type.isCPtr()) + return @divExact(target.cpu.arch.ptrBitWidth(), 8); + return child_type.abiAlignment(target); }, @@ -750,6 +752,10 @@ pub const Type = extern union { .optional => { const child_type = self.cast(Payload.Optional).?.child_type; if (!child_type.hasCodeGenBits()) return 1; + + if (child_type.zigTypeTag() == .Pointer and !child_type.isCPtr()) + return @divExact(target.cpu.arch.ptrBitWidth(), 8); + // Optional types are represented as a struct with the child type as the first // field and a boolean as the second. Since the child type's abi alignment is // guaranteed to be >= that of bool's (1 byte) the added size is exactly equal diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 84e9731126..10fb419440 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -215,7 +215,9 @@ pub const Inst = struct { /// Create an optional type '?T' optional_type, /// Unwraps an optional value 'lhs.?' - unwrap_optional, + unwrap_optional_safe, + /// Same as previous, but without safety checks. Used for orelse, if and while + unwrap_optional_unsafe, pub fn Type(tag: Tag) type { return switch (tag) { @@ -245,6 +247,8 @@ pub const Inst = struct { .single_const_ptr_type, .single_mut_ptr_type, .optional_type, + .unwrap_optional_safe, + .unwrap_optional_unsafe, => UnOp, .add, @@ -303,7 +307,6 @@ pub const Inst = struct { .fntype => FnType, .elemptr => ElemPtr, .condbr => CondBr, - .unwrap_optional => UnwrapOptional, }; } @@ -379,7 +382,8 @@ pub const Inst = struct { .typeof, .xor, .optional_type, - .unwrap_optional, + .unwrap_optional_safe, + .unwrap_optional_unsafe, => false, .@"break", @@ -820,18 +824,6 @@ pub const Inst = struct { }, kw_args: struct {}, }; - - pub const UnwrapOptional = struct { - pub const base_tag = Tag.unwrap_optional; - base: Inst, - - positionals: struct { - operand: *Inst, - }, - kw_args: struct { - safety_check: bool = true, - }, - }; }; pub const ErrorMsg = struct { @@ -1935,6 +1927,8 @@ const EmitZIR = struct { .isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull), .load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref), .ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref), + .unwrap_optional_safe => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional_safe).?, .unwrap_optional_safe), + .unwrap_optional_unsafe => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional_unsafe).?, .unwrap_optional_unsafe), .add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add), .sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub), @@ -2157,25 +2151,6 @@ const EmitZIR = struct { }; break :blk &new_inst.base; }, - - .unwrap_optional => blk: { - const old_inst = inst.castTag(.unwrap_optional).?; - - const new_inst = try self.arena.allocator.create(Inst.UnwrapOptional); - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.UnwrapOptional.base_tag, - }, - .positionals = .{ - .operand = try self.resolveInst(new_body, old_inst.operand), - }, - .kw_args = .{ - .safety_check = old_inst.safety_check, - }, - }; - break :blk &new_inst.base; - }, }; try instructions.append(new_inst); try inst_table.put(inst, new_inst); diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index 39fbf9221a..b217752494 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -107,7 +107,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?), .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?), .optional_type => return analyzeInstOptionalType(mod, scope, old_inst.castTag(.optional_type).?), - .unwrap_optional => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional).?), + .unwrap_optional_safe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_safe).?, true), + .unwrap_optional_unsafe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_unsafe).?, false), } } @@ -661,7 +662,7 @@ fn analyzeInstOptionalType(mod: *Module, scope: *Scope, optional: *zir.Inst.UnOp })); } -fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnwrapOptional) InnerError!*Inst { +fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp, safety_check: bool) InnerError!*Inst { const operand = try resolveInst(mod, scope, unwrap.positionals.operand); assert(operand.ty.zigTypeTag() == .Pointer); @@ -686,7 +687,10 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.Unwr } const b = try mod.requireRuntimeBlock(scope, unwrap.base.src); - return mod.addUnwrapOptional(b, unwrap.base.src, child_pointer, operand, unwrap.kw_args.safety_check); + return if (safety_check) + mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional_safe, operand) + else + mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional_unsafe, operand); } fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 477b5d92b1..29312a8f53 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -31,11 +31,6 @@ pub fn addCases(ctx: *TestContext) !void { \\export fn _start() noreturn { \\ print(); \\ - \\ const a: u32 = 2; - \\ const b: ?u32 = a; - \\ const c = b.?; - \\ if (c != 2) unreachable; - \\ \\ exit(); \\} \\ @@ -446,5 +441,29 @@ pub fn addCases(ctx: *TestContext) !void { , "", ); + + // Optionals + case.addCompareOutput( + \\export fn _start() noreturn { + \\ const a: u32 = 2; + \\ const b: ?u32 = a; + \\ const c = b.?; + \\ if (c != 2) unreachable; + \\ + \\ exit(); + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); } } From ec4953504a07f3025d5f32344180dd9b6a4de8ae Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 13 Aug 2020 10:04:46 -0700 Subject: [PATCH 5/5] stage2: implement safety checks at the zir_sema level --- src-self-hosted/Module.zig | 72 +++++++++++++++++++++++++++++++++--- src-self-hosted/codegen.zig | 5 +-- src-self-hosted/ir.zig | 6 +-- src-self-hosted/zir.zig | 3 +- src-self-hosted/zir_sema.zig | 36 +++++++++--------- 5 files changed, 91 insertions(+), 31 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 66da42cadf..2e847519f3 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2219,11 +2219,6 @@ pub fn wantSafety(self: *Module, scope: *Scope) bool { }; } -pub fn analyzeUnreach(self: *Module, scope: *Scope, src: usize) InnerError!*Inst { - const b = try self.requireRuntimeBlock(scope, src); - return self.addNoOp(b, src, Type.initTag(.noreturn), .unreach); -} - pub fn analyzeIsNull( self: *Module, scope: *Scope, @@ -2902,3 +2897,70 @@ pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void { }); } } + +pub const PanicId = enum { + unreach, + unwrap_null, +}; + +pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic_id: PanicId) !void { + const block_inst = try parent_block.arena.create(Inst.Block); + block_inst.* = .{ + .base = .{ + .tag = Inst.Block.base_tag, + .ty = Type.initTag(.void), + .src = ok.src, + }, + .body = .{ + .instructions = try parent_block.arena.alloc(*Inst, 1), // Only need space for the condbr. + }, + }; + + const ok_body: ir.Body = .{ + .instructions = try parent_block.arena.alloc(*Inst, 1), // Only need space for the brvoid. + }; + const brvoid = try parent_block.arena.create(Inst.BrVoid); + brvoid.* = .{ + .base = .{ + .tag = .brvoid, + .ty = Type.initTag(.noreturn), + .src = ok.src, + }, + .block = block_inst, + }; + ok_body.instructions[0] = &brvoid.base; + + var fail_block: Scope.Block = .{ + .parent = parent_block, + .func = parent_block.func, + .decl = parent_block.decl, + .instructions = .{}, + .arena = parent_block.arena, + }; + defer fail_block.instructions.deinit(mod.gpa); + + _ = try mod.safetyPanic(&fail_block, ok.src, panic_id); + + const fail_body: ir.Body = .{ .instructions = try parent_block.arena.dupe(*Inst, fail_block.instructions.items) }; + + const condbr = try parent_block.arena.create(Inst.CondBr); + condbr.* = .{ + .base = .{ + .tag = .condbr, + .ty = Type.initTag(.noreturn), + .src = ok.src, + }, + .condition = ok, + .then_body = ok_body, + .else_body = fail_body, + }; + block_inst.body.instructions[0] = &condbr.base; + + try parent_block.instructions.append(mod.gpa, &block_inst.base); +} + +pub fn safetyPanic(mod: *Module, block: *Scope.Block, src: usize, panic_id: PanicId) !*Inst { + // TODO Once we have a panic function to call, call it here instead of breakpoint. + _ = try mod.addNoOp(block, src, Type.initTag(.void), .breakpoint); + return mod.addNoOp(block, src, Type.initTag(.noreturn), .unreach); +} diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 37e775b4b3..d25fa65f47 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -668,8 +668,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .store => return self.genStore(inst.castTag(.store).?), .sub => return self.genSub(inst.castTag(.sub).?), .unreach => return MCValue{ .unreach = {} }, - .unwrap_optional_safe => return self.genUnwrapOptional(inst.castTag(.unwrap_optional_safe).?, true), - .unwrap_optional_unsafe => return self.genUnwrapOptional(inst.castTag(.unwrap_optional_unsafe).?, false), + .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?), } } @@ -819,7 +818,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnOp, safety_check: bool) !MCValue { + fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnOp) !MCValue { // No side effects, so if it's unreferenced, do nothing. if (inst.base.isUnused()) return MCValue.dead; diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 307dcfe62e..81f99f53ba 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -82,8 +82,7 @@ pub const Inst = struct { not, floatcast, intcast, - unwrap_optional_safe, - unwrap_optional_unsafe, + unwrap_optional, pub fn Type(tag: Tag) type { return switch (tag) { @@ -104,8 +103,7 @@ pub const Inst = struct { .floatcast, .intcast, .load, - .unwrap_optional_safe, - .unwrap_optional_unsafe, + .unwrap_optional, => UnOp, .add, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 10fb419440..2e638b0755 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -1927,8 +1927,7 @@ const EmitZIR = struct { .isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull), .load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref), .ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref), - .unwrap_optional_safe => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional_safe).?, .unwrap_optional_safe), - .unwrap_optional_unsafe => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional_unsafe).?, .unwrap_optional_unsafe), + .unwrap_optional => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional).?, .unwrap_optional_unsafe), .add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add), .sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub), diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index b217752494..74e57b7735 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -68,8 +68,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .deref => return analyzeInstDeref(mod, scope, old_inst.castTag(.deref).?), .as => return analyzeInstAs(mod, scope, old_inst.castTag(.as).?), .@"asm" => return analyzeInstAsm(mod, scope, old_inst.castTag(.@"asm").?), - .@"unreachable" => return analyzeInstUnreachable(mod, scope, old_inst.castTag(.@"unreachable").?), - .unreach_nocheck => return analyzeInstUnreachNoChk(mod, scope, old_inst.castTag(.unreach_nocheck).?), + .@"unreachable" => return analyzeInstUnreachable(mod, scope, old_inst.castTag(.@"unreachable").?, true), + .unreach_nocheck => return analyzeInstUnreachable(mod, scope, old_inst.castTag(.unreach_nocheck).?, false), .@"return" => return analyzeInstRet(mod, scope, old_inst.castTag(.@"return").?), .returnvoid => return analyzeInstRetVoid(mod, scope, old_inst.castTag(.returnvoid).?), .@"fn" => return analyzeInstFn(mod, scope, old_inst.castTag(.@"fn").?), @@ -313,7 +313,7 @@ fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError! if (operand.value()) |val| { const ref_payload = try scope.arena().create(Value.Payload.RefVal); ref_payload.* = .{ .val = val }; - + return mod.constInst(scope, inst.base.src, .{ .ty = ptr_type, .val = Value.initPayload(&ref_payload.base), @@ -677,7 +677,7 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp try mod.singleMutPtrType(scope, unwrap.base.src, child_type); if (operand.value()) |val| { - if (val.tag() == .null_value) { + if (val.isNull()) { return mod.fail(scope, unwrap.base.src, "unable to unwrap null", .{}); } return mod.constInst(scope, unwrap.base.src, .{ @@ -687,10 +687,11 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp } const b = try mod.requireRuntimeBlock(scope, unwrap.base.src); - return if (safety_check) - mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional_safe, operand) - else - mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional_unsafe, operand); + if (safety_check and mod.wantSafety(scope)) { + const is_non_null = try mod.addUnOp(b, unwrap.base.src, Type.initTag(.bool), .isnonnull, operand); + try mod.addSafetyCheck(b, is_non_null, .unwrap_null); + } + return mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional, operand); } fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { @@ -1167,18 +1168,19 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE return mod.addCondBr(parent_block, inst.base.src, cond, then_body, else_body); } -fn analyzeInstUnreachNoChk(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst { - return mod.analyzeUnreach(scope, unreach.base.src); -} - -fn analyzeInstUnreachable(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst { +fn analyzeInstUnreachable( + mod: *Module, + scope: *Scope, + unreach: *zir.Inst.NoOp, + safety_check: bool, +) InnerError!*Inst { const b = try mod.requireRuntimeBlock(scope, unreach.base.src); // TODO Add compile error for @optimizeFor occurring too late in a scope. - if (mod.wantSafety(scope)) { - // TODO Once we have a panic function to call, call it here instead of this. - _ = try mod.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint); + if (safety_check and mod.wantSafety(scope)) { + return mod.safetyPanic(b, unreach.base.src, .unreach); + } else { + return mod.addNoOp(b, unreach.base.src, Type.initTag(.noreturn), .unreach); } - return mod.analyzeUnreach(scope, unreach.base.src); } fn analyzeInstRet(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {