From d4ec0279d3ec59cf33b1b2fbf74f9ae82af753cc Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Sat, 9 Jan 2021 16:22:43 +0100 Subject: [PATCH 1/4] stage2: add support for optionals in the LLVM backend We can now codegen optionals! This includes the following instructions: - is_null - is_null_ptr - is_non_null - is_non_null_ptr - optional_payload - optional_payload_ptr - br_void Also includes a test for optionals. --- src/astgen.zig | 29 ++++++--- src/codegen/llvm.zig | 113 +++++++++++++++++++++++++++++++--- src/codegen/llvm/bindings.zig | 9 +++ test/stage2/llvm.zig | 40 ++++++++++++ 4 files changed, 176 insertions(+), 15 deletions(-) diff --git a/src/astgen.zig b/src/astgen.zig index 63184e641b..2c2f41c3f6 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -453,13 +453,23 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) In return rvalue(mod, scope, rl, result); }, .unwrap_optional => { - const operand = try expr(mod, scope, rl, node_datas[node].lhs); - const op: zir.Inst.Tag = switch (rl) { - .ref => .optional_payload_safe_ptr, - else => .optional_payload_safe, - }; const src = token_starts[main_tokens[node]]; - return addZIRUnOp(mod, scope, src, op, operand); + switch (rl) { + .ref => return addZIRUnOp( + mod, + scope, + src, + .optional_payload_safe_ptr, + try expr(mod, scope, .ref, node_datas[node].lhs), + ), + else => return rvalue(mod, scope, rl, try addZIRUnOp( + mod, + scope, + src, + .optional_payload_safe, + try expr(mod, scope, .none, node_datas[node].lhs), + )), + } }, .block_two, .block_two_semicolon => { const statements = [2]ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs }; @@ -1701,7 +1711,12 @@ fn orelseCatchExpr( // This could be a pointer or value depending on the `rl` parameter. block_scope.break_count += 1; - const operand = try expr(mod, &block_scope.base, block_scope.break_result_loc, lhs); + const operand = try expr( + mod, + &block_scope.base, + if (block_scope.break_result_loc == .ref) .ref else .none, + lhs, + ); const cond = try addZIRUnOp(mod, &block_scope.base, src, cond_op, operand); const condbr = try addZIRInstSpecial(mod, &block_scope.base, src, zir.Inst.CondBr, .{ diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index df6a58b1e2..f087957f1d 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -397,6 +397,7 @@ pub const LLVMIRModule = struct { .block => try self.genBlock(inst.castTag(.block).?), .br => try self.genBr(inst.castTag(.br).?), .breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?), + .br_void => try self.genBrVoid(inst.castTag(.br_void).?), .call => try self.genCall(inst.castTag(.call).?), .cmp_eq => try self.genCmp(inst.castTag(.cmp_eq).?, .eq), .cmp_gt => try self.genCmp(inst.castTag(.cmp_gt).?, .gt), @@ -406,6 +407,10 @@ pub const LLVMIRModule = struct { .cmp_neq => try self.genCmp(inst.castTag(.cmp_neq).?, .neq), .condbr => try self.genCondBr(inst.castTag(.condbr).?), .intcast => try self.genIntCast(inst.castTag(.intcast).?), + .is_non_null => try self.genIsNonNull(inst.castTag(.is_non_null).?, false), + .is_non_null_ptr => try self.genIsNonNull(inst.castTag(.is_non_null_ptr).?, true), + .is_null => try self.genIsNull(inst.castTag(.is_null).?, false), + .is_null_ptr => try self.genIsNull(inst.castTag(.is_null_ptr).?, true), .load => try self.genLoad(inst.castTag(.load).?), .loop => try self.genLoop(inst.castTag(.loop).?), .not => try self.genNot(inst.castTag(.not).?), @@ -414,6 +419,8 @@ pub const LLVMIRModule = struct { .store => try self.genStore(inst.castTag(.store).?), .sub => try self.genSub(inst.castTag(.sub).?), .unreach => self.genUnreach(inst.castTag(.unreach).?), + .optional_payload => try self.genOptionalPayload(inst.castTag(.optional_payload).?, false), + .optional_payload_ptr => try self.genOptionalPayload(inst.castTag(.optional_payload_ptr).?, true), .dbg_stmt => blk: { // TODO: implement debug info break :blk null; @@ -534,21 +541,29 @@ pub const LLVMIRModule = struct { } fn genBr(self: *LLVMIRModule, inst: *Inst.Br) !?*const llvm.Value { - // Get the block that we want to break to. var block = self.blocks.get(inst.block).?; - _ = self.builder.buildBr(block.parent_bb); // If the break doesn't break a value, then we don't have to add // the values to the lists. - if (!inst.operand.ty.hasCodeGenBits()) return null; + if (!inst.operand.ty.hasCodeGenBits()) { + // TODO: in astgen these instructions should turn into `br_void` instructions. + _ = self.builder.buildBr(block.parent_bb); + } else { + const val = try self.resolveInst(inst.operand); - // For the phi node, we need the basic blocks and the values of the - // break instructions. - try block.break_bbs.append(self.gpa, self.builder.getInsertBlock()); + // For the phi node, we need the basic blocks and the values of the + // break instructions. + try block.break_bbs.append(self.gpa, self.builder.getInsertBlock()); + try block.break_vals.append(self.gpa, val); - const val = try self.resolveInst(inst.operand); - try block.break_vals.append(self.gpa, val); + _ = self.builder.buildBr(block.parent_bb); + } + return null; + } + fn genBrVoid(self: *LLVMIRModule, inst: *Inst.BrVoid) !?*const llvm.Value { + var block = self.blocks.get(inst.block).?; + _ = self.builder.buildBr(block.parent_bb); return null; } @@ -591,6 +606,44 @@ pub const LLVMIRModule = struct { return null; } + fn genIsNonNull(self: *LLVMIRModule, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value { + const operand = try self.resolveInst(inst.operand); + + if (operand_is_ptr) { + const index_type = self.context.intType(32); + + var indices: [2]*const llvm.Value = .{ + index_type.constNull(), + index_type.constInt(1, false), + }; + + return self.builder.buildLoad(self.builder.buildInBoundsGEP(operand, &indices, 2, ""), ""); + } else { + return self.builder.buildExtractValue(operand, 1, ""); + } + } + + fn genIsNull(self: *LLVMIRModule, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value { + return self.builder.buildNot((try self.genIsNonNull(inst, operand_is_ptr)).?, ""); + } + + fn genOptionalPayload(self: *LLVMIRModule, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value { + const operand = try self.resolveInst(inst.operand); + + if (operand_is_ptr) { + const index_type = self.context.intType(32); + + var indices: [2]*const llvm.Value = .{ + index_type.constNull(), + index_type.constNull(), + }; + + return self.builder.buildInBoundsGEP(operand, &indices, 2, ""); + } else { + return self.builder.buildExtractValue(operand, 0, ""); + } + } + fn genAdd(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { const lhs = try self.resolveInst(inst.lhs); const rhs = try self.resolveInst(inst.rhs); @@ -751,6 +804,13 @@ pub const LLVMIRModule = struct { // TODO: consider using buildInBoundsGEP2 for opaque pointers return self.builder.buildInBoundsGEP(val, &indices, 2, ""); }, + .ref_val => { + const elem_value = tv.val.castTag(.ref_val).?.data; + const elem_type = tv.ty.castPointer().?.data; + const alloca = self.buildAlloca(try self.getLLVMType(elem_type, src)); + _ = self.builder.buildStore(try self.genTypedValue(src, .{ .ty = elem_type, .val = elem_value }), alloca); + return alloca; + }, else => return self.fail(src, "TODO implement const of pointer type '{}'", .{tv.ty}), }, .Array => { @@ -765,6 +825,29 @@ pub const LLVMIRModule = struct { return self.fail(src, "TODO handle more array values", .{}); } }, + .Optional => { + if (!tv.ty.isPtrLikeOptional()) { + var buf: Type.Payload.ElemType = undefined; + const child_type = tv.ty.optionalChild(&buf); + const llvm_child_type = try self.getLLVMType(child_type, src); + + if (tv.val.tag() == .null_value) { + var optional_values: [2]*const llvm.Value = .{ + llvm_child_type.constNull(), + self.context.intType(1).constNull(), + }; + return self.context.constStruct(&optional_values, 2, false); + } else { + var optional_values: [2]*const llvm.Value = .{ + try self.genTypedValue(src, .{ .ty = child_type, .val = tv.val }), + self.context.intType(1).constAllOnes(), + }; + return self.context.constStruct(&optional_values, 2, false); + } + } else { + return self.fail(src, "TODO implement const of optional pointer", .{}); + } + }, else => return self.fail(src, "TODO implement const of type '{}'", .{tv.ty}), } } @@ -790,6 +873,20 @@ pub const LLVMIRModule = struct { const elem_type = try self.getLLVMType(t.elemType(), src); return elem_type.arrayType(@intCast(c_uint, t.abiSize(self.module.getTarget()))); }, + .Optional => { + if (!t.isPtrLikeOptional()) { + var buf: Type.Payload.ElemType = undefined; + const child_type = t.optionalChild(&buf); + + var optional_types: [2]*const llvm.Type = .{ + try self.getLLVMType(child_type, src), + self.context.intType(1), + }; + return self.context.structType(&optional_types, 2, false); + } else { + return self.fail(src, "TODO implement optional pointers as actual pointers", .{}); + } + }, else => return self.fail(src, "TODO implement getLLVMType for type '{}'", .{t}), } } diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 6474957b1c..ccba3d9973 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -21,9 +21,15 @@ pub const Context = opaque { pub const voidType = LLVMVoidTypeInContext; extern fn LLVMVoidTypeInContext(C: *const Context) *const Type; + pub const structType = LLVMStructTypeInContext; + extern fn LLVMStructTypeInContext(C: *const Context, ElementTypes: [*]*const Type, ElementCount: c_uint, Packed: LLVMBool) *const Type; + pub const constString = LLVMConstStringInContext; extern fn LLVMConstStringInContext(C: *const Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: LLVMBool) *const Value; + pub const constStruct = LLVMConstStructInContext; + extern fn LLVMConstStructInContext(C: *const Context, ConstantVals: [*]*const Value, Count: c_uint, Packed: LLVMBool) *const Value; + pub const createBasicBlock = LLVMCreateBasicBlockInContext; extern fn LLVMCreateBasicBlockInContext(C: *const Context, Name: [*:0]const u8) *const BasicBlock; @@ -204,6 +210,9 @@ pub const Builder = opaque { pub const buildPhi = LLVMBuildPhi; extern fn LLVMBuildPhi(*const Builder, Ty: *const Type, Name: [*:0]const u8) *const Value; + + pub const buildExtractValue = LLVMBuildExtractValue; + extern fn LLVMBuildExtractValue(*const Builder, AggVal: *const Value, Index: c_uint, Name: [*:0]const u8) *const Value; }; pub const IntPredicate = extern enum { diff --git a/test/stage2/llvm.zig b/test/stage2/llvm.zig index f52ccecb68..0888903beb 100644 --- a/test/stage2/llvm.zig +++ b/test/stage2/llvm.zig @@ -132,4 +132,44 @@ pub fn addCases(ctx: *TestContext) !void { \\} , ""); } + + { + var case = ctx.exeUsingLlvmBackend("optionals", linux_x64); + + case.addCompareOutput( + \\fn assert(ok: bool) void { + \\ if (!ok) unreachable; + \\} + \\ + \\export fn main() c_int { + \\ var opt_val: ?i32 = 10; + \\ var null_val: ?i32 = null; + \\ + \\ var val1: i32 = opt_val.?; + \\ const val1_1: i32 = opt_val.?; + \\ var ptr_val1 = &(opt_val.?); + \\ const ptr_val1_1 = &(opt_val.?); + \\ + \\ var val2: i32 = null_val orelse 20; + \\ const val2_2: i32 = null_val orelse 20; + \\ + \\ var value: i32 = 20; + \\ var ptr_val2 = &(null_val orelse value); + \\ + \\ const val3 = opt_val orelse 30; + \\ + \\ assert(val1 == 10); + \\ assert(val1_1 == 10); + \\ assert(ptr_val1.* == 10); + \\ assert(ptr_val1_1.* == 10); + \\ + \\ assert(val2 == 20); + \\ assert(val2_2 == 20); + \\ assert(ptr_val2.* == 20); + \\ + \\ assert(val3 == 10); + \\ return 0; + \\} + , ""); + } } From ed6757ece6228f8bf43f9d8e0481f4160b9a884b Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Tue, 19 Jan 2021 10:27:16 +0100 Subject: [PATCH 2/4] stage2: add a test for `for` loops in LLVM backend --- test/stage2/llvm.zig | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/stage2/llvm.zig b/test/stage2/llvm.zig index 0888903beb..7e672d8a20 100644 --- a/test/stage2/llvm.zig +++ b/test/stage2/llvm.zig @@ -172,4 +172,23 @@ pub fn addCases(ctx: *TestContext) !void { \\} , ""); } + + { + var case = ctx.exeUsingLlvmBackend("for loop", linux_x64); + + case.addCompareOutput( + \\fn assert(ok: bool) void { + \\ if (!ok) unreachable; + \\} + \\ + \\export fn main() c_int { + \\ var x: u32 = 0; + \\ for ("hello") |_| { + \\ x += 1; + \\ } + \\ assert("hello".len == x); + \\ return 0; + \\} + , ""); + } } From 6aa1ea9c59340a1f5b7560ecd97e45928bd4cf56 Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Tue, 2 Mar 2021 11:44:33 +0100 Subject: [PATCH 3/4] stage2: fixup some formatting errors ({x} -> {s}) These were missed in cd7c870bd81391dd97c5c75eb3910382ba7280a1 --- src/link.zig | 4 ++-- src/link/Coff.zig | 6 +++--- src/link/Elf.zig | 6 +++--- src/link/MachO.zig | 6 +++--- src/link/Wasm.zig | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/link.zig b/src/link.zig index 0a4cde0284..db3e973f84 100644 --- a/src/link.zig +++ b/src/link.zig @@ -550,11 +550,11 @@ pub const File = struct { id_symlink_basename, &prev_digest_buf, ) catch |err| b: { - log.debug("archive new_digest={x} readFile error: {s}", .{ digest, @errorName(err) }); + log.debug("archive new_digest={s} readFile error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); break :b prev_digest_buf[0..0]; }; if (mem.eql(u8, prev_digest, &digest)) { - log.debug("archive digest={x} match - skipping invocation", .{digest}); + log.debug("archive digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); base.lock = man.toOwnedLock(); return; } diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 2eee19b4f6..a73b8aaf9c 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -892,17 +892,17 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { id_symlink_basename, &prev_digest_buf, ) catch |err| blk: { - log.debug("COFF LLD new_digest={x} error: {s}", .{ digest, @errorName(err) }); + log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; if (mem.eql(u8, prev_digest, &digest)) { - log.debug("COFF LLD digest={x} match - skipping invocation", .{digest}); + log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); // Hot diggity dog! The output binary is already there. self.base.lock = man.toOwnedLock(); return; } - log.debug("COFF LLD prev_digest={x} new_digest={x}", .{ prev_digest, digest }); + log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); // We are about to change the output file to be different, so we invalidate the build hash now. directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 1b6fbb0f0f..bfef2cd12c 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1365,17 +1365,17 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { id_symlink_basename, &prev_digest_buf, ) catch |err| blk: { - log.debug("ELF LLD new_digest={x} error: {s}", .{ digest, @errorName(err) }); + log.debug("ELF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; if (mem.eql(u8, prev_digest, &digest)) { - log.debug("ELF LLD digest={x} match - skipping invocation", .{digest}); + log.debug("ELF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); // Hot diggity dog! The output binary is already there. self.base.lock = man.toOwnedLock(); return; } - log.debug("ELF LLD prev_digest={x} new_digest={x}", .{ prev_digest, digest }); + log.debug("ELF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); // We are about to change the output file to be different, so we invalidate the build hash now. directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 139a9b8940..0f76925618 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -556,17 +556,17 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { id_symlink_basename, &prev_digest_buf, ) catch |err| blk: { - log.debug("MachO LLD new_digest={x} error: {s}", .{ digest, @errorName(err) }); + log.debug("MachO LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; if (mem.eql(u8, prev_digest, &digest)) { - log.debug("MachO LLD digest={x} match - skipping invocation", .{digest}); + log.debug("MachO LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); // Hot diggity dog! The output binary is already there. self.base.lock = man.toOwnedLock(); return; } - log.debug("MachO LLD prev_digest={x} new_digest={x}", .{ prev_digest, digest }); + log.debug("MachO LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); // We are about to change the output file to be different, so we invalidate the build hash now. directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e0e10ad88d..71cb171d98 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -391,17 +391,17 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { id_symlink_basename, &prev_digest_buf, ) catch |err| blk: { - log.debug("WASM LLD new_digest={x} error: {s}", .{ digest, @errorName(err) }); + log.debug("WASM LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; if (mem.eql(u8, prev_digest, &digest)) { - log.debug("WASM LLD digest={x} match - skipping invocation", .{digest}); + log.debug("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); // Hot diggity dog! The output binary is already there. self.base.lock = man.toOwnedLock(); return; } - log.debug("WASM LLD prev_digest={x} new_digest={x}", .{ prev_digest, digest }); + log.debug("WASM LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); // We are about to change the output file to be different, so we invalidate the build hash now. directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { From 713f1138222dc40355c34c70d83b0a0805bd46c6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 2 Mar 2021 21:59:23 -0700 Subject: [PATCH 4/4] stage2: improve orelse implementation * Now it supports being an lvalue (see additional lines in the test case). * Properly handles a pointer result location (see additional lines in the test case that assign the result of the orelse to a variable rather than a const). * Properly sets the result location type when possible, so that type inference of an `orelse` operand expression knows its result type. --- src/astgen.zig | 38 ++++++++++++++++++++++++++++++-------- src/zir.zig | 5 +++++ src/zir_sema.zig | 11 +++++++++++ test/stage2/llvm.zig | 9 +++++++++ 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/astgen.zig b/src/astgen.zig index 2c2f41c3f6..9f112531ee 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -1709,14 +1709,13 @@ fn orelseCatchExpr( setBlockResultLoc(&block_scope, rl); defer block_scope.instructions.deinit(mod.gpa); - // This could be a pointer or value depending on the `rl` parameter. + // This could be a pointer or value depending on the `operand_rl` parameter. + // We cannot use `block_scope.break_result_loc` because that has the bare + // type, whereas this expression has the optional type. Later we make + // up for this fact by calling rvalue on the else branch. block_scope.break_count += 1; - const operand = try expr( - mod, - &block_scope.base, - if (block_scope.break_result_loc == .ref) .ref else .none, - lhs, - ); + const operand_rl = try makeOptionalTypeResultLoc(mod, &block_scope.base, src, block_scope.break_result_loc); + const operand = try expr(mod, &block_scope.base, operand_rl, lhs); const cond = try addZIRUnOp(mod, &block_scope.base, src, cond_op, operand); const condbr = try addZIRInstSpecial(mod, &block_scope.base, src, zir.Inst.CondBr, .{ @@ -1768,6 +1767,10 @@ fn orelseCatchExpr( // This could be a pointer or value depending on `unwrap_op`. const unwrapped_payload = try addZIRUnOp(mod, &else_scope.base, src, unwrap_op, operand); + const else_result = switch (rl) { + .ref => unwrapped_payload, + else => try rvalue(mod, &else_scope.base, block_scope.break_result_loc, unwrapped_payload), + }; return finishThenElseBlock( mod, @@ -1781,7 +1784,7 @@ fn orelseCatchExpr( src, src, then_result, - unwrapped_payload, + else_result, block, block, ); @@ -3970,6 +3973,25 @@ fn rlStrategy(rl: ResultLoc, block_scope: *Scope.GenZIR) ResultLoc.Strategy { } } +/// If the input ResultLoc is ref, returns ResultLoc.ref. Otherwise: +/// Returns ResultLoc.ty, where the type is determined by the input +/// ResultLoc type, wrapped in an optional type. If the input ResultLoc +/// has no type, .none is returned. +fn makeOptionalTypeResultLoc(mod: *Module, scope: *Scope, src: usize, rl: ResultLoc) !ResultLoc { + switch (rl) { + .ref => return ResultLoc.ref, + .discard, .none, .block_ptr, .inferred_ptr, .bitcasted_ptr => return ResultLoc.none, + .ty => |elem_ty| { + const wrapped_ty = try addZIRUnOp(mod, scope, src, .optional_type, elem_ty); + return ResultLoc{ .ty = wrapped_ty }; + }, + .ptr => |ptr_ty| { + const wrapped_ty = try addZIRUnOp(mod, scope, src, .optional_type_from_ptr_elem, ptr_ty); + return ResultLoc{ .ty = wrapped_ty }; + }, + } +} + fn setBlockResultLoc(block_scope: *Scope.GenZIR, parent_rl: ResultLoc) void { // Depending on whether the result location is a pointer or value, different // ZIR needs to be generated. In the former case we rely on storing to the diff --git a/src/zir.zig b/src/zir.zig index 7a3a1e2684..1331f26dc7 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -299,6 +299,9 @@ pub const Inst = struct { xor, /// Create an optional type '?T' optional_type, + /// Create an optional type '?T'. The operand is a pointer value. The optional type will + /// be the type of the pointer element, wrapped in an optional. + optional_type_from_ptr_elem, /// Create a union type. union_type, /// ?T => T with safety. @@ -397,6 +400,7 @@ pub const Inst = struct { .mut_slice_type, .const_slice_type, .optional_type, + .optional_type_from_ptr_elem, .optional_payload_safe, .optional_payload_unsafe, .optional_payload_safe_ptr, @@ -597,6 +601,7 @@ pub const Inst = struct { .typeof, .xor, .optional_type, + .optional_type_from_ptr_elem, .optional_payload_safe, .optional_payload_unsafe, .optional_payload_safe_ptr, diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 1cf550852f..cfc1ea3280 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -131,6 +131,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .typeof => return zirTypeof(mod, scope, old_inst.castTag(.typeof).?), .typeof_peer => return zirTypeofPeer(mod, scope, old_inst.castTag(.typeof_peer).?), .optional_type => return zirOptionalType(mod, scope, old_inst.castTag(.optional_type).?), + .optional_type_from_ptr_elem => return zirOptionalTypeFromPtrElem(mod, scope, old_inst.castTag(.optional_type_from_ptr_elem).?), .optional_payload_safe => return zirOptionalPayload(mod, scope, old_inst.castTag(.optional_payload_safe).?, true), .optional_payload_unsafe => return zirOptionalPayload(mod, scope, old_inst.castTag(.optional_payload_unsafe).?, false), .optional_payload_safe_ptr => return zirOptionalPayloadPtr(mod, scope, old_inst.castTag(.optional_payload_safe_ptr).?, true), @@ -1093,6 +1094,16 @@ fn zirOptionalType(mod: *Module, scope: *Scope, optional: *zir.Inst.UnOp) InnerE return mod.constType(scope, optional.base.src, try mod.optionalType(scope, child_type)); } +fn zirOptionalTypeFromPtrElem(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + const tracy = trace(@src()); + defer tracy.end(); + + const ptr = try resolveInst(mod, scope, inst.positionals.operand); + const elem_ty = ptr.ty.elemType(); + + return mod.constType(scope, inst.base.src, try mod.optionalType(scope, elem_ty)); +} + fn zirArrayType(mod: *Module, scope: *Scope, array: *zir.Inst.BinOp) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); diff --git a/test/stage2/llvm.zig b/test/stage2/llvm.zig index 7e672d8a20..4b00ed124c 100644 --- a/test/stage2/llvm.zig +++ b/test/stage2/llvm.zig @@ -157,6 +157,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var ptr_val2 = &(null_val orelse value); \\ \\ const val3 = opt_val orelse 30; + \\ var val3_var = opt_val orelse 30; \\ \\ assert(val1 == 10); \\ assert(val1_1 == 10); @@ -168,6 +169,14 @@ pub fn addCases(ctx: *TestContext) !void { \\ assert(ptr_val2.* == 20); \\ \\ assert(val3 == 10); + \\ assert(val3_var == 10); + \\ + \\ (null_val orelse val2) = 1234; + \\ assert(val2 == 1234); + \\ + \\ (opt_val orelse val2) = 5678; + \\ assert(opt_val.? == 5678); + \\ \\ return 0; \\} , "");