From b7452fe35f514d4c04aae4582bc8071bc9e70f1b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 20 Jan 2021 20:37:44 -0700 Subject: stage2: rework astgen result locations Motivating test case: ```zig export fn _start() noreturn { var x: u64 = 1; var y: u32 = 2; var thing: u32 = 1; const result = if (thing == 1) x else y; exit(); } ``` The main idea here is for astgen to output ideal ZIR depending on whether or not the sub-expressions of a block consume the result location. Here, neither `x` nor `y` consume the result location of the conditional expression block, and so the ZIR should communicate the result of the condbr using break instructions, not with the result location pointer. With this commit, this is accomplished: ``` %22 = alloc_inferred() %23 = block({ %24 = const(TypedValue{ .ty = type, .val = bool}) %25 = deref(%18) %26 = const(TypedValue{ .ty = comptime_int, .val = 1}) %27 = cmp_eq(%25, %26) %28 = as(%24, %27) %29 = condbr(%28, { %30 = deref(%4) < there is no longer a store instruction here > %31 = break("label_23", %30) }, { %32 = deref(%11) < there is no longer a store instruction here > %33 = break("label_23", %32) }) }) %34 = store_to_inferred_ptr(%22, %23) <-- the store is only here %35 = resolve_inferred_alloc(%22) ``` However if the result location gets consumed, the break instructions change to break_void, and the result value is communicated only by the stores, not by the break instructions. Implementation: * The GenZIR scope that conditional branches uses now has an optional result location pointer field and a count of how many times the result location ended up being an rvalue (not consumed). * When rvalue() is called on a result location for a block, it increments this counter. After generating the branches of a block, astgen for the conditional branch checks this count and if it is 2 then the store_to_block_ptr instructions are elided and it calls rvalue() using the block result (which will account for peer type resolution on the break operands). astgen has many functions disabled until they can be reworked with these new semantics. That will be done before merging the branch. There are some new rules for astgen to follow regarding result locations and what you are allowed/required to do depending on which one is passed to expr(). See the updated doc comments of ResultLoc for details. I also changed naming conventions of stuff in this commit, sorry about that. --- src/Module.zig | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index fa9722814e..2dc84a93a9 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -697,6 +697,13 @@ pub const Scope = struct { continue_block: ?*zir.Inst.Block = null, /// only valid if label != null or (continue_block and break_block) != null break_result_loc: astgen.ResultLoc = undefined, + /// When a block has a pointer result location, here it is. + rl_ptr: ?*zir.Inst = null, + /// Keeps track of how many branches of a block did not actually + /// consume the result location. astgen uses this to figure out + /// whether to rely on break instructions or writing to the result + /// pointer for the result instruction. + rvalue_rl_count: usize = 0, pub const Label = struct { token: ast.TokenIndex, @@ -1171,7 +1178,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn()) { const src = tree.token_locs[body_block.rbrace].start; - _ = try astgen.addZIRNoOp(self, &gen_scope.base, src, .returnvoid); + _ = try astgen.addZIRNoOp(self, &gen_scope.base, src, .return_void); } if (std.builtin.mode == .Debug and self.comp.verbose_ir) { -- cgit v1.2.3 From 588171c30b34426fbb07645aa2625e989f369eec Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 22 Jan 2021 16:45:09 -0700 Subject: sema: after block gets peer type resolved, insert type coercions on the break instruction operands. This involves a new TZIR instruction, br_block_flat, which represents a break instruction where the operand is the result of a flat block. See the doc comments on the instructions for more details. How it works: when adding break instructions in semantic analysis, the underlying allocation is slightly padded so that it is the size of a br_block_flat instruction, which allows the break instruction to later be converted without removing instructions inside the parent body. The extra type coercion instructions go into the body of the br_block_flat, and backends are responsible for dispatching the instruction correctly (it should map to the same function calls for related instructions). --- src/Module.zig | 28 +++++++++++++++-- src/codegen.zig | 31 ++++++++++++------- src/ir.zig | 32 +++++++++++++++++++- src/zir.zig | 28 +++++++++++++++-- src/zir_sema.zig | 91 ++++++++++++++++++++++++++++++++++++++++++-------------- 5 files changed, 171 insertions(+), 39 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index 2dc84a93a9..b7967aacc5 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -671,14 +671,36 @@ pub const Scope = struct { }; pub const Merges = struct { - results: ArrayListUnmanaged(*Inst), block_inst: *Inst.Block, + /// Separate array list from break_inst_list so that it can be passed directly + /// to resolvePeerTypes. + results: ArrayListUnmanaged(*Inst), + /// Keeps track of the break instructions so that the operand can be replaced + /// if we need to add type coercion at the end of block analysis. + /// Same indexes, capacity, length as `results`. + br_list: ArrayListUnmanaged(*Inst.Br), }; /// For debugging purposes. pub fn dump(self: *Block, mod: Module) void { zir.dumpBlock(mod, self); } + + pub fn makeSubBlock(parent: *Block) Block { + return .{ + .parent = parent, + .inst_table = parent.inst_table, + .func = parent.func, + .owner_decl = parent.owner_decl, + .src_decl = parent.src_decl, + .instructions = .{}, + .arena = parent.arena, + .label = null, + .inlining = parent.inlining, + .is_comptime = parent.is_comptime, + .branch_quota = parent.branch_quota, + }; + } }; /// This is a temporary structure, references to it are valid only @@ -2107,7 +2129,7 @@ pub fn addBr( src: usize, target_block: *Inst.Block, operand: *Inst, -) !*Inst { +) !*Inst.Br { const inst = try scope_block.arena.create(Inst.Br); inst.* = .{ .base = .{ @@ -2119,7 +2141,7 @@ pub fn addBr( .block = target_block, }; try scope_block.instructions.append(self.gpa, &inst.base); - return &inst.base; + return inst; } pub fn addCondBr( diff --git a/src/codegen.zig b/src/codegen.zig index a7b067f7e1..1f5aad8ab8 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -844,6 +844,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .bit_or => return self.genBitOr(inst.castTag(.bit_or).?), .block => return self.genBlock(inst.castTag(.block).?), .br => return self.genBr(inst.castTag(.br).?), + .br_block_flat => return self.genBrBlockFlat(inst.castTag(.br_block_flat).?), .breakpoint => return self.genBreakpoint(inst.src), .brvoid => return self.genBrVoid(inst.castTag(.brvoid).?), .bool_and => return self.genBoolOp(inst.castTag(.bool_and).?), @@ -2441,17 +2442,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } + fn genBrBlockFlat(self: *Self, parent_inst: *ir.Inst.BrBlockFlat) !MCValue { + try self.genBody(parent_inst.body); + const last = parent_inst.body.instructions[parent_inst.body.instructions.len - 1]; + return self.br(parent_inst.base.src, parent_inst.block, last); + } + fn genBr(self: *Self, inst: *ir.Inst.Br) !MCValue { - if (inst.operand.ty.hasCodeGenBits()) { - const operand = try self.resolveInst(inst.operand); - const block_mcv = @bitCast(MCValue, inst.block.codegen.mcv); - if (block_mcv == .none) { - inst.block.codegen.mcv = @bitCast(AnyMCValue, operand); - } else { - try self.setRegOrMem(inst.base.src, inst.block.base.ty, block_mcv, operand); - } - } - return self.brVoid(inst.base.src, inst.block); + return self.br(inst.base.src, inst.block, inst.operand); } fn genBrVoid(self: *Self, inst: *ir.Inst.BrVoid) !MCValue { @@ -2478,6 +2476,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } + fn br(self: *Self, src: usize, block: *ir.Inst.Block, operand: *ir.Inst) !MCValue { + if (operand.ty.hasCodeGenBits()) { + const operand_mcv = try self.resolveInst(operand); + const block_mcv = @bitCast(MCValue, block.codegen.mcv); + if (block_mcv == .none) { + block.codegen.mcv = @bitCast(AnyMCValue, operand_mcv); + } else { + try self.setRegOrMem(src, block.base.ty, block_mcv, operand_mcv); + } + } + return self.brVoid(src, block); + } + fn brVoid(self: *Self, src: usize, block: *ir.Inst.Block) !MCValue { // Emit a jump with a relocation. It will be patched up after the block ends. try block.codegen.relocs.ensureCapacity(self.gpa, block.codegen.relocs.items.len + 1); diff --git a/src/ir.zig b/src/ir.zig index b1147871f4..4d421dda4c 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -61,6 +61,13 @@ pub const Inst = struct { bit_or, block, br, + /// Same as `br` except the operand is a list of instructions to be treated as + /// a flat block; that is there is only 1 break instruction from the block, and + /// it is implied to be after the last instruction, and the last instruction is + /// the break operand. + /// This instruction exists for late-stage semantic analysis patch ups, to + /// replace one br operand with multiple instructions, without moving anything else around. + br_block_flat, breakpoint, brvoid, call, @@ -158,6 +165,7 @@ pub const Inst = struct { .assembly => Assembly, .block => Block, .br => Br, + .br_block_flat => BrBlockFlat, .brvoid => BrVoid, .call => Call, .condbr => CondBr, @@ -252,6 +260,7 @@ pub const Inst = struct { return switch (base.tag) { .br => base.castTag(.br).?.block, .brvoid => base.castTag(.brvoid).?.block, + .br_block_flat => base.castTag(.br_block_flat).?.block, else => null, }; } @@ -355,6 +364,27 @@ pub const Inst = struct { } }; + pub const convertable_br_size = std.math.max(@sizeOf(BrBlockFlat), @sizeOf(Br)); + pub const convertable_br_align = std.math.max(@alignOf(BrBlockFlat), @alignOf(Br)); + comptime { + assert(@byteOffsetOf(BrBlockFlat, "base") == @byteOffsetOf(Br, "base")); + } + + pub const BrBlockFlat = struct { + pub const base_tag = Tag.br_block_flat; + + base: Inst, + block: *Block, + body: Body, + + pub fn operandCount(self: *const BrBlockFlat) usize { + return 0; + } + pub fn getOperand(self: *const BrBlockFlat, index: usize) ?*Inst { + return null; + } + }; + pub const Br = struct { pub const base_tag = Tag.br; @@ -363,7 +393,7 @@ pub const Inst = struct { operand: *Inst, pub fn operandCount(self: *const Br) usize { - return 0; + return 1; } pub fn getOperand(self: *const Br, index: usize) ?*Inst { if (index == 0) diff --git a/src/zir.zig b/src/zir.zig index 651e5ee3dc..301b52efc0 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -1634,6 +1634,12 @@ const DumpTzir = struct { try dtz.findConst(br.operand); }, + .br_block_flat => { + const br_block_flat = inst.castTag(.br_block_flat).?; + try dtz.findConst(&br_block_flat.block.base); + try dtz.fetchInstsAndResolveConsts(br_block_flat.body); + }, + .brvoid => { const brvoid = inst.castTag(.brvoid).?; try dtz.findConst(&brvoid.block.base); @@ -1779,6 +1785,24 @@ const DumpTzir = struct { } }, + .br_block_flat => { + const br_block_flat = inst.castTag(.br_block_flat).?; + const block_kinky = try dtz.writeInst(writer, &br_block_flat.block.base); + if (block_kinky != null) { + try writer.writeAll(", { // Instruction does not dominate all uses!\n"); + } else { + try writer.writeAll(", {\n"); + } + + const old_indent = dtz.indent; + dtz.indent += 2; + try dtz.dumpBody(br_block_flat.body, writer); + dtz.indent = old_indent; + + try writer.writeByteNTimes(' ', dtz.indent); + try writer.writeAll("})\n"); + }, + .brvoid => { const brvoid = inst.castTag(.brvoid).?; const kinky = try dtz.writeInst(writer, &brvoid.block.base); @@ -1792,7 +1816,7 @@ const DumpTzir = struct { .block => { const block = inst.castTag(.block).?; - try writer.writeAll("\n"); + try writer.writeAll("{\n"); const old_indent = dtz.indent; dtz.indent += 2; @@ -1800,7 +1824,7 @@ const DumpTzir = struct { dtz.indent = old_indent; try writer.writeByteNTimes(' ', dtz.indent); - try writer.writeAll(")\n"); + try writer.writeAll("})\n"); }, .condbr => { diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 40ea563bb6..0297b9873a 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -664,20 +664,9 @@ fn zirBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime: defer tracy.end(); const parent_block = scope.cast(Scope.Block).?; - var child_block: Scope.Block = .{ - .parent = parent_block, - .inst_table = parent_block.inst_table, - .func = parent_block.func, - .owner_decl = parent_block.owner_decl, - .src_decl = parent_block.src_decl, - .instructions = .{}, - .arena = parent_block.arena, - .label = null, - .inlining = parent_block.inlining, - .is_comptime = parent_block.is_comptime or is_comptime, - .branch_quota = parent_block.branch_quota, - }; + var child_block = parent_block.makeSubBlock(); defer child_block.instructions.deinit(mod.gpa); + child_block.is_comptime = child_block.is_comptime or is_comptime; try analyzeBody(mod, &child_block, inst.positionals.body); @@ -728,6 +717,7 @@ fn zirBlock( .zir_block = inst, .merges = .{ .results = .{}, + .br_list = .{}, .block_inst = block_inst, }, }), @@ -739,6 +729,7 @@ fn zirBlock( defer child_block.instructions.deinit(mod.gpa); defer merges.results.deinit(mod.gpa); + defer merges.br_list.deinit(mod.gpa); try analyzeBody(mod, &child_block, inst.positionals.body); @@ -772,22 +763,53 @@ fn analyzeBlockBody( const last_inst = child_block.instructions.items[last_inst_index]; if (last_inst.breakBlock()) |br_block| { if (br_block == merges.block_inst) { - // No need for a block instruction. We can put the new instructions directly into the parent block. - // Here we omit the break instruction. + // No need for a block instruction. We can put the new instructions directly + // into the parent block. Here we omit the break instruction. const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items[0..last_inst_index]); try parent_block.instructions.appendSlice(mod.gpa, copied_instructions); return merges.results.items[0]; } } } - // It should be impossible to have the number of results be > 1 in a comptime scope. - assert(!child_block.is_comptime); // We should have already got a compile error in the condbr condition. + // It is impossible to have the number of results be > 1 in a comptime scope. + assert(!child_block.is_comptime); // Should already got a compile error in the condbr condition. // Need to set the type and emit the Block instruction. This allows machine code generation // to emit a jump instruction to after the block when it encounters the break. try parent_block.instructions.append(mod.gpa, &merges.block_inst.base); - merges.block_inst.base.ty = try mod.resolvePeerTypes(scope, merges.results.items); - merges.block_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) }; + const resolved_ty = try mod.resolvePeerTypes(scope, merges.results.items); + merges.block_inst.base.ty = resolved_ty; + merges.block_inst.body = .{ + .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items), + }; + // Now that the block has its type resolved, we need to go back into all the break + // instructions, and insert type coercion on the operands. + for (merges.br_list.items) |br| { + if (br.operand.ty.eql(resolved_ty)) { + // No type coercion needed. + continue; + } + var coerce_block = parent_block.makeSubBlock(); + defer coerce_block.instructions.deinit(mod.gpa); + const coerced_operand = try mod.coerce(&coerce_block.base, resolved_ty, br.operand); + assert(coerce_block.instructions.items[coerce_block.instructions.items.len - 1] == coerced_operand); + // Here we depend on the br instruction having been over-allocated (if necessary) + // inide analyzeBreak so that it can be converted into a br_block_flat instruction. + const br_src = br.base.src; + const br_ty = br.base.ty; + const br_block_flat = @ptrCast(*Inst.BrBlockFlat, br); + br_block_flat.* = .{ + .base = .{ + .src = br_src, + .ty = br_ty, + .tag = .br_block_flat, + }, + .block = merges.block_inst, + .body = .{ + .instructions = try parent_block.arena.dupe(*Inst, coerce_block.instructions.items), + }, + }; + } return &merges.block_inst.base; } @@ -827,9 +849,28 @@ fn analyzeBreak( while (opt_block) |block| { if (block.label) |*label| { if (label.zir_block == zir_block) { - try label.merges.results.append(mod.gpa, operand); const b = try mod.requireFunctionBlock(scope, src); - return mod.addBr(b, src, label.merges.block_inst, operand); + // Here we add a br instruction, but we over-allocate a little bit + // (if necessary) to make it possible to convert the instruction into + // a br_block_flat instruction later. + const br = @ptrCast(*Inst.Br, try b.arena.alignedAlloc( + u8, + Inst.convertable_br_align, + Inst.convertable_br_size, + )); + br.* = .{ + .base = .{ + .tag = .br, + .ty = Type.initTag(.noreturn), + .src = src, + }, + .operand = operand, + .block = label.merges.block_inst, + }; + try b.instructions.append(mod.gpa, &br.base); + try label.merges.results.append(mod.gpa, operand); + try label.merges.br_list.append(mod.gpa, br); + return &br.base; } } opt_block = block.parent; @@ -980,6 +1021,7 @@ fn zirCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst { .casted_args = casted_args, .merges = .{ .results = .{}, + .br_list = .{}, .block_inst = block_inst, }, }; @@ -1004,6 +1046,7 @@ fn zirCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst { defer child_block.instructions.deinit(mod.gpa); defer merges.results.deinit(mod.gpa); + defer merges.br_list.deinit(mod.gpa); try mod.emitBackwardBranch(&child_block, inst.base.src); @@ -2194,7 +2237,8 @@ fn zirReturn(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst if (b.inlining) |inlining| { // We are inlining a function call; rewrite the `ret` as a `break`. try inlining.merges.results.append(mod.gpa, operand); - return mod.addBr(b, inst.base.src, inlining.merges.block_inst, operand); + const br = try mod.addBr(b, inst.base.src, inlining.merges.block_inst, operand); + return &br.base; } return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand); @@ -2208,7 +2252,8 @@ fn zirReturnVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!* // We are inlining a function call; rewrite the `retvoid` as a `breakvoid`. const void_inst = try mod.constVoid(scope, inst.base.src); try inlining.merges.results.append(mod.gpa, void_inst); - return mod.addBr(b, inst.base.src, inlining.merges.block_inst, void_inst); + const br = try mod.addBr(b, inst.base.src, inlining.merges.block_inst, void_inst); + return &br.base; } if (b.func) |func| { -- cgit v1.2.3 From 6c8985fceeeb6314143691570cf0c7a42521e590 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 24 Jan 2021 20:23:37 -0700 Subject: astgen: rework labeled blocks --- src/Module.zig | 23 +++-- src/astgen.zig | 249 +++++++++++++++++++++++++++++++++++-------------------- src/codegen.zig | 10 +-- src/ir.zig | 8 +- src/zir.zig | 19 +++-- src/zir_sema.zig | 3 + 6 files changed, 200 insertions(+), 112 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index b7967aacc5..0bafc72e6b 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -717,7 +717,7 @@ pub const Scope = struct { label: ?Label = null, break_block: ?*zir.Inst.Block = null, continue_block: ?*zir.Inst.Block = null, - /// only valid if label != null or (continue_block and break_block) != null + /// Only valid when setBlockResultLoc is called. break_result_loc: astgen.ResultLoc = undefined, /// When a block has a pointer result location, here it is. rl_ptr: ?*zir.Inst = null, @@ -726,6 +726,17 @@ pub const Scope = struct { /// whether to rely on break instructions or writing to the result /// pointer for the result instruction. rvalue_rl_count: usize = 0, + /// Keeps track of how many break instructions there are. When astgen is finished + /// with a block, it can check this against rvalue_rl_count to find out whether + /// the break instructions should be downgraded to break_void. + break_count: usize = 0, + /// Tracks `break :foo bar` instructions so they can possibly be elided later if + /// the labeled block ends up not needing a result location pointer. + labeled_breaks: std.ArrayListUnmanaged(*zir.Inst.Break) = .{}, + /// Tracks `store_to_block_ptr` instructions that correspond to break instructions + /// so they can possibly be elided later if the labeled block ends up not needing + /// a result location pointer. + labeled_store_to_block_ptr_list: std.ArrayListUnmanaged(*zir.Inst.BinOp) = .{}, pub const Label = struct { token: ast.TokenIndex, @@ -3495,18 +3506,18 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic }; const ok_body: ir.Body = .{ - .instructions = try parent_block.arena.alloc(*Inst, 1), // Only need space for the brvoid. + .instructions = try parent_block.arena.alloc(*Inst, 1), // Only need space for the br_void. }; - const brvoid = try parent_block.arena.create(Inst.BrVoid); - brvoid.* = .{ + const br_void = try parent_block.arena.create(Inst.BrVoid); + br_void.* = .{ .base = .{ - .tag = .brvoid, + .tag = .br_void, .ty = Type.initTag(.noreturn), .src = ok.src, }, .block = block_inst, }; - ok_body.instructions[0] = &brvoid.base; + ok_body.instructions[0] = &br_void.base; var fail_block: Scope.Block = .{ .parent = parent_block, diff --git a/src/astgen.zig b/src/astgen.zig index 49f60aa6ba..994205c3a7 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -38,6 +38,21 @@ pub const ResultLoc = union(enum) { /// is inferred based on peer type resolution for a `zir.Inst.Block`. /// The result instruction from the expression must be ignored. block_ptr: *Module.Scope.GenZIR, + + pub const Strategy = struct { + elide_store_to_block_ptr_instructions: bool, + tag: Tag, + + pub const Tag = enum { + /// Both branches will use break_void; result location is used to communicate the + /// result instruction. + break_void, + /// Use break statements to pass the block result value, and call rvalue() at + /// the end depending on rl. Also elide the store_to_block_ptr instructions + /// depending on rl. + break_operand, + }; + }; }; pub fn typeExpr(mod: *Module, scope: *Scope, type_node: *ast.Node) InnerError!*zir.Inst { @@ -348,10 +363,11 @@ pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *as return &block.base; } -fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { - if (true) { - @panic("TODO reimplement this"); - } +fn breakExpr( + mod: *Module, + parent_scope: *Scope, + node: *ast.Node.ControlFlowExpression, +) InnerError!*zir.Inst { const tree = parent_scope.tree(); const src = tree.token_locs[node.ltoken].start; @@ -377,25 +393,31 @@ fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpr continue; }; - if (node.getRHS()) |rhs| { - // Most result location types can be forwarded directly; however - // if we need to write to a pointer which has an inferred type, - // proper type inference requires peer type resolution on the block's - // break operand expressions. - const branch_rl: ResultLoc = switch (gen_zir.break_result_loc) { - .discard, .none, .ty, .ptr, .ref => gen_zir.break_result_loc, - .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block_inst }, - }; - const operand = try expr(mod, parent_scope, branch_rl, rhs); - return try addZIRInst(mod, parent_scope, src, zir.Inst.Break, .{ + const rhs = node.getRHS() orelse { + return addZirInstTag(mod, parent_scope, src, .break_void, .{ .block = block_inst, - .operand = operand, - }, .{}); - } else { - return try addZIRInst(mod, parent_scope, src, zir.Inst.BreakVoid, .{ - .block = block_inst, - }, .{}); + }); + }; + gen_zir.break_count += 1; + const prev_rvalue_rl_count = gen_zir.rvalue_rl_count; + const operand = try expr(mod, parent_scope, gen_zir.break_result_loc, rhs); + const have_store_to_block = gen_zir.rvalue_rl_count != prev_rvalue_rl_count; + const br = try addZirInstTag(mod, parent_scope, src, .@"break", .{ + .block = block_inst, + .operand = operand, + }); + if (gen_zir.break_result_loc == .block_ptr) { + try gen_zir.labeled_breaks.append(mod.gpa, br.castTag(.@"break").?); + + if (have_store_to_block) { + const inst_list = parent_scope.cast(Scope.GenZIR).?.instructions.items; + const last_inst = inst_list[inst_list.len - 2]; + const store_inst = last_inst.castTag(.store_to_block_ptr).?; + assert(store_inst.positionals.lhs == gen_zir.rl_ptr.?); + try gen_zir.labeled_store_to_block_ptr_list.append(mod.gpa, store_inst); + } } + return br; }, .local_val => scope = scope.cast(Scope.LocalVal).?.parent, .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, @@ -538,7 +560,6 @@ fn labeledBlockExpr( .decl = parent_scope.ownerDecl().?, .arena = gen_zir.arena, .instructions = .{}, - .break_result_loc = rl, // TODO @as here is working around a stage1 miscompilation bug :( .label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{ .token = block_node.label, @@ -546,19 +567,57 @@ fn labeledBlockExpr( }), }; defer block_scope.instructions.deinit(mod.gpa); + defer block_scope.labeled_breaks.deinit(mod.gpa); + defer block_scope.labeled_store_to_block_ptr_list.deinit(mod.gpa); + + setBlockResultLoc(&block_scope, rl); try blockExprStmts(mod, &block_scope.base, &block_node.base, block_node.statements()); + if (!block_scope.label.?.used) { return mod.fail(parent_scope, tree.token_locs[block_node.label].start, "unused block label", .{}); } - block_inst.positionals.body.instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items); try gen_zir.instructions.append(mod.gpa, &block_inst.base); - return &block_inst.base; + const strat = rlStrategy(rl, &block_scope); + switch (strat.tag) { + .break_void => { + // The code took advantage of the result location as a pointer. + // Turn the break instructions into break_void instructions. + for (block_scope.labeled_breaks.items) |br| { + br.base.tag = .break_void; + } + // TODO technically not needed since we changed the tag to break_void but + // would be better still to elide the ones that are in this list. + try copyBodyNoEliding(&block_inst.positionals.body, block_scope); + + return &block_inst.base; + }, + .break_operand => { + // All break operands are values that did not use the result location pointer. + if (strat.elide_store_to_block_ptr_instructions) { + for (block_scope.labeled_store_to_block_ptr_list.items) |inst| { + inst.base.tag = .void_value; + } + // TODO technically not needed since we changed the tag to void_value but + // would be better still to elide the ones that are in this list. + } + try copyBodyNoEliding(&block_inst.positionals.body, block_scope); + switch (rl) { + .ref => return &block_inst.base, + else => return rvalue(mod, parent_scope, rl, &block_inst.base), + } + }, + } } -fn blockExprStmts(mod: *Module, parent_scope: *Scope, node: *ast.Node, statements: []*ast.Node) !void { +fn blockExprStmts( + mod: *Module, + parent_scope: *Scope, + node: *ast.Node, + statements: []*ast.Node, +) !void { const tree = parent_scope.tree(); var block_arena = std.heap.ArenaAllocator.init(mod.gpa); @@ -1659,7 +1718,6 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn cond_kind = .{ .err_union = null }; } } - const block_branch_count = 2; // then and else var block_scope: Scope.GenZIR = .{ .parent = scope, .decl = scope.ownerDecl().?, @@ -1668,6 +1726,8 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn }; defer block_scope.instructions.deinit(mod.gpa); + setBlockResultLoc(&block_scope, rl); + const tree = scope.tree(); const if_src = tree.token_locs[if_node.if_token].start; const cond = try cond_kind.cond(mod, &block_scope, if_src, if_node.condition); @@ -1682,33 +1742,6 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), }); - // 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 - // pointer to communicate the result, and use breakvoid; in the latter case - // the block break instructions will have the result values. - // One more complication: when the result location is a pointer, we detect - // the scenario where the result location is not consumed. In this case - // we emit ZIR for the block break instructions to have the result values, - // and then rvalue() on that to pass the value to the result location. - const branch_rl: ResultLoc = switch (rl) { - .discard, .none, .ty, .ptr, .ref => rl, - - .inferred_ptr => |ptr| blk: { - block_scope.rl_ptr = &ptr.base; - break :blk .{ .block_ptr = &block_scope }; - }, - - .bitcasted_ptr => |ptr| blk: { - block_scope.rl_ptr = &ptr.base; - break :blk .{ .block_ptr = &block_scope }; - }, - - .block_ptr => |parent_block_scope| blk: { - block_scope.rl_ptr = parent_block_scope.rl_ptr.?; - break :blk .{ .block_ptr = &block_scope }; - }, - }; - const then_src = tree.token_locs[if_node.body.lastToken()].start; var then_scope: Scope.GenZIR = .{ .parent = scope, @@ -1721,7 +1754,8 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn // declare payload to the then_scope const then_sub_scope = try cond_kind.thenSubScope(mod, &then_scope, then_src, if_node.payload); - const then_result = try expr(mod, then_sub_scope, branch_rl, if_node.body); + block_scope.break_count += 1; + const then_result = try expr(mod, then_sub_scope, block_scope.break_result_loc, if_node.body); // We hold off on the break instructions as well as copying the then/else // instructions into place until we know whether to keep store_to_block_ptr // instructions or not. @@ -1741,47 +1775,18 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn // declare payload to the then_scope else_sub_scope = try cond_kind.elseSubScope(mod, &else_scope, else_src, else_node.payload); - break :blk try expr(mod, else_sub_scope, branch_rl, else_node.body); + block_scope.break_count += 1; + break :blk try expr(mod, else_sub_scope, block_scope.break_result_loc, else_node.body); } else blk: { else_src = tree.token_locs[if_node.lastToken()].start; else_sub_scope = &else_scope.base; - block_scope.rvalue_rl_count += 1; break :blk null; }; // We now have enough information to decide whether the result instruction should // be communicated via result location pointer or break instructions. - const Strategy = enum { - /// Both branches will use break_void; result location is used to communicate the - /// result instruction. - break_void, - /// Use break statements to pass the block result value, and call rvalue() at - /// the end depending on rl. Also elide the store_to_block_ptr instructions - /// depending on rl. - break_operand, - }; - var elide_store_to_block_ptr_instructions = false; - const strategy: Strategy = switch (rl) { - // In this branch there will not be any store_to_block_ptr instructions. - .discard, .none, .ty, .ref => .break_operand, - // The pointer got passed through to the sub-expressions, so we will use - // break_void here. - // In this branch there will not be any store_to_block_ptr instructions. - .ptr => .break_void, - .inferred_ptr, .bitcasted_ptr, .block_ptr => blk: { - if (block_scope.rvalue_rl_count == 2) { - // Neither prong of the if consumed the result location, so we can - // use break instructions to create an rvalue. - elide_store_to_block_ptr_instructions = true; - break :blk Strategy.break_operand; - } else { - // Allow the store_to_block_ptr instructions to remain so that - // semantic analysis can turn them into bitcasts. - break :blk Strategy.break_void; - } - }, - }; - switch (strategy) { + const strat = rlStrategy(rl, &block_scope); + switch (strat.tag) { .break_void => { if (!then_result.tag.isNoReturn()) { _ = try addZirInstTag(mod, then_sub_scope, then_src, .break_void, .{ @@ -1799,7 +1804,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn .block = block, }); } - assert(!elide_store_to_block_ptr_instructions); + assert(!strat.elide_store_to_block_ptr_instructions); try copyBodyNoEliding(&condbr.positionals.then_body, then_scope); try copyBodyNoEliding(&condbr.positionals.else_body, else_scope); return &block.base; @@ -1823,7 +1828,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn .block = block, }); } - if (elide_store_to_block_ptr_instructions) { + if (strat.elide_store_to_block_ptr_instructions) { try copyBodyWithElidedStoreBlockPtr(&condbr.positionals.then_body, then_scope); try copyBodyWithElidedStoreBlockPtr(&condbr.positionals.else_body, else_scope); } else { @@ -3376,6 +3381,72 @@ fn rvalueVoid(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node, resul return rvalue(mod, scope, rl, void_inst); } +fn rlStrategy(rl: ResultLoc, block_scope: *Scope.GenZIR) ResultLoc.Strategy { + var elide_store_to_block_ptr_instructions = false; + switch (rl) { + // In this branch there will not be any store_to_block_ptr instructions. + .discard, .none, .ty, .ref => return .{ + .tag = .break_operand, + .elide_store_to_block_ptr_instructions = false, + }, + // The pointer got passed through to the sub-expressions, so we will use + // break_void here. + // In this branch there will not be any store_to_block_ptr instructions. + .ptr => return .{ + .tag = .break_void, + .elide_store_to_block_ptr_instructions = false, + }, + .inferred_ptr, .bitcasted_ptr, .block_ptr => { + if (block_scope.rvalue_rl_count == block_scope.break_count) { + // Neither prong of the if consumed the result location, so we can + // use break instructions to create an rvalue. + return .{ + .tag = .break_operand, + .elide_store_to_block_ptr_instructions = true, + }; + } else { + // Allow the store_to_block_ptr instructions to remain so that + // semantic analysis can turn them into bitcasts. + return .{ + .tag = .break_void, + .elide_store_to_block_ptr_instructions = false, + }; + } + }, + } +} + +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 + // pointer to communicate the result, and use breakvoid; in the latter case + // the block break instructions will have the result values. + // One more complication: when the result location is a pointer, we detect + // the scenario where the result location is not consumed. In this case + // we emit ZIR for the block break instructions to have the result values, + // and then rvalue() on that to pass the value to the result location. + switch (parent_rl) { + .discard, .none, .ty, .ptr, .ref => { + block_scope.break_result_loc = parent_rl; + }, + + .inferred_ptr => |ptr| { + block_scope.rl_ptr = &ptr.base; + block_scope.break_result_loc = .{ .block_ptr = block_scope }; + }, + + .bitcasted_ptr => |ptr| { + block_scope.rl_ptr = &ptr.base; + block_scope.break_result_loc = .{ .block_ptr = block_scope }; + }, + + .block_ptr => |parent_block_scope| { + block_scope.rl_ptr = parent_block_scope.rl_ptr.?; + block_scope.break_result_loc = .{ .block_ptr = block_scope }; + }, + } +} + pub fn addZirInstTag( mod: *Module, scope: *Scope, diff --git a/src/codegen.zig b/src/codegen.zig index 1f5aad8ab8..362b04ab26 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -846,7 +846,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .br => return self.genBr(inst.castTag(.br).?), .br_block_flat => return self.genBrBlockFlat(inst.castTag(.br_block_flat).?), .breakpoint => return self.genBreakpoint(inst.src), - .brvoid => return self.genBrVoid(inst.castTag(.brvoid).?), + .br_void => return self.genBrVoid(inst.castTag(.br_void).?), .bool_and => return self.genBoolOp(inst.castTag(.bool_and).?), .bool_or => return self.genBoolOp(inst.castTag(.bool_or).?), .call => return self.genCall(inst.castTag(.call).?), @@ -2442,10 +2442,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - fn genBrBlockFlat(self: *Self, parent_inst: *ir.Inst.BrBlockFlat) !MCValue { - try self.genBody(parent_inst.body); - const last = parent_inst.body.instructions[parent_inst.body.instructions.len - 1]; - return self.br(parent_inst.base.src, parent_inst.block, last); + fn genBrBlockFlat(self: *Self, inst: *ir.Inst.BrBlockFlat) !MCValue { + try self.genBody(inst.body); + const last = inst.body.instructions[inst.body.instructions.len - 1]; + return self.br(inst.base.src, inst.block, last); } fn genBr(self: *Self, inst: *ir.Inst.Br) !MCValue { diff --git a/src/ir.zig b/src/ir.zig index 4d421dda4c..408efc3bba 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -69,7 +69,7 @@ pub const Inst = struct { /// replace one br operand with multiple instructions, without moving anything else around. br_block_flat, breakpoint, - brvoid, + br_void, call, cmp_lt, cmp_lte, @@ -166,7 +166,7 @@ pub const Inst = struct { .block => Block, .br => Br, .br_block_flat => BrBlockFlat, - .brvoid => BrVoid, + .br_void => BrVoid, .call => Call, .condbr => CondBr, .constant => Constant, @@ -259,7 +259,7 @@ pub const Inst = struct { pub fn breakBlock(base: *Inst) ?*Block { return switch (base.tag) { .br => base.castTag(.br).?.block, - .brvoid => base.castTag(.brvoid).?.block, + .br_void => base.castTag(.br_void).?.block, .br_block_flat => base.castTag(.br_block_flat).?.block, else => null, }; @@ -403,7 +403,7 @@ pub const Inst = struct { }; pub const BrVoid = struct { - pub const base_tag = Tag.brvoid; + pub const base_tag = Tag.br_void; base: Inst, block: *Block, diff --git a/src/zir.zig b/src/zir.zig index 301b52efc0..d372cfbf00 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -264,8 +264,7 @@ pub const Inst = struct { /// Write a value to a pointer. For loading, see `deref`. store, /// Same as `store` but the type of the value being stored will be used to infer - /// the block type. The LHS is a block instruction, whose result location is - /// being stored to. + /// the block type. The LHS is the pointer to store to. store_to_block_ptr, /// Same as `store` but the type of the value being stored will be used to infer /// the pointer type. @@ -343,6 +342,8 @@ pub const Inst = struct { /// Only checks that `lhs >= rhs` if they are ints, everything else is /// validated by the .switch instruction. switch_range, + /// Does nothing; returns a void value. + void_value, pub fn Type(tag: Tag) type { return switch (tag) { @@ -355,6 +356,7 @@ pub const Inst = struct { .ret_type, .unreachable_unsafe, .unreachable_safe, + .void_value, => NoOp, .alloc, @@ -611,6 +613,7 @@ pub const Inst = struct { .enum_type, .union_type, .struct_type, + .void_value, => false, .@"break", @@ -1640,9 +1643,9 @@ const DumpTzir = struct { try dtz.fetchInstsAndResolveConsts(br_block_flat.body); }, - .brvoid => { - const brvoid = inst.castTag(.brvoid).?; - try dtz.findConst(&brvoid.block.base); + .br_void => { + const br_void = inst.castTag(.br_void).?; + try dtz.findConst(&br_void.block.base); }, .block => { @@ -1803,9 +1806,9 @@ const DumpTzir = struct { try writer.writeAll("})\n"); }, - .brvoid => { - const brvoid = inst.castTag(.brvoid).?; - const kinky = try dtz.writeInst(writer, &brvoid.block.base); + .br_void => { + const br_void = inst.castTag(.br_void).?; + const kinky = try dtz.writeInst(writer, &br_void.block.base); if (kinky) |_| { try writer.writeAll(") // Instruction does not dominate all uses!\n"); } else { diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 0297b9873a..773c782746 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -155,6 +155,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .switch_range => return zirSwitchRange(mod, scope, old_inst.castTag(.switch_range).?), .bool_and => return zirBoolOp(mod, scope, old_inst.castTag(.bool_and).?), .bool_or => return zirBoolOp(mod, scope, old_inst.castTag(.bool_or).?), + .void_value => return mod.constVoid(scope, old_inst.src), .container_field_named, .container_field_typed, @@ -447,6 +448,8 @@ fn zirStoreToBlockPtr( const ptr = try resolveInst(mod, scope, inst.positionals.lhs); const value = try resolveInst(mod, scope, inst.positionals.rhs); const ptr_ty = try mod.simplePtrType(scope, inst.base.src, value.ty, true, .One); + // TODO detect when this store should be done at compile-time. For example, + // if expressions should force it when the condition is compile-time known. const b = try mod.requireRuntimeBlock(scope, inst.base.src); const bitcasted_ptr = try mod.addUnOp(b, inst.base.src, ptr_ty, .bitcast, ptr); return mod.storePtr(scope, inst.base.src, bitcasted_ptr, value); -- cgit v1.2.3 From 0f5eda973e0c17b3f792cdb06674bf8d2863c8fb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 31 Jan 2021 20:58:11 -0700 Subject: stage2: delete astgen for switch expressions The astgen for switch expressions did not respect the ZIR rules of only referencing instructions that are in scope: %14 = block_comptime_flat({ %15 = block_comptime_flat({ %16 = const(TypedValue{ .ty = comptime_int, .val = 1}) }) %17 = block_comptime_flat({ %18 = const(TypedValue{ .ty = comptime_int, .val = 2}) }) }) %19 = block({ %20 = ref(%5) %21 = deref(%20) %22 = switchbr(%20, [%15, %17], { %15 => { %23 = const(TypedValue{ .ty = comptime_int, .val = 1}) %24 = store(%10, %23) %25 = const(TypedValue{ .ty = void, .val = {}}) %26 = break("label_19", %25) }, %17 => { %27 = const(TypedValue{ .ty = comptime_int, .val = 2}) %28 = store(%10, %27) %29 = const(TypedValue{ .ty = void, .val = {}}) %30 = break("label_19", %29) } }, { %31 = unreachable_safe() }, special_prong=else) }) In this snippet you can see that the comptime expr referenced %15 and %17 which are not in scope. There also was no test coverage for runtime switch expressions. Switch expressions will have to be re-introduced to follow these rules and with some test coverage. There is some usable code being deleted in this commit; it will be useful to reference when re-implementing switch later. A few more improvements to do while we're at it: * only use .ref result loc on switch target if any prongs obtain the payload with |*syntax| - this improvement should be done to if, while, and for as well. - this will remove the needless ref/deref instructions above * remove switchbr and add switch_block, which is both a block and a switch branch. - similarly we should remove loop and add loop_block. This commit introduces a "force_comptime" flag into the GenZIR scope. The main purpose of this will be to choose the "comptime" variants of certain key zir instructions, such as function calls and branches. We will be moving away from using the block_comptime_flat ZIR instruction, and eventually deleting it. This commit also contains miscellaneous fixes to this branch that bring it to the state of passing all the tests. --- src/Module.zig | 13 +- src/astgen.zig | 342 ++++++++------------------------------------------- src/zir.zig | 56 --------- src/zir_sema.zig | 236 +---------------------------------- test/stage2/test.zig | 37 ------ 5 files changed, 72 insertions(+), 612 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index 0bafc72e6b..b495afb336 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -375,6 +375,10 @@ pub const Scope = struct { } } + pub fn isComptime(self: *Scope) bool { + return self.getGenZIR().force_comptime; + } + pub fn ownerDecl(self: *Scope) ?*Decl { return switch (self.tag) { .block => self.cast(Block).?.owner_decl, @@ -712,6 +716,7 @@ pub const Scope = struct { parent: *Scope, decl: *Decl, arena: *Allocator, + force_comptime: bool, /// The first N instructions in a function body ZIR are arg instructions. instructions: std.ArrayListUnmanaged(*zir.Inst) = .{}, label: ?Label = null, @@ -1008,6 +1013,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .decl = decl, .arena = &fn_type_scope_arena.allocator, .parent = &decl.container.base, + .force_comptime = true, }; defer fn_type_scope.instructions.deinit(self.gpa); @@ -1171,6 +1177,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .decl = decl, .arena = &decl_arena.allocator, .parent = &decl.container.base, + .force_comptime = false, }; defer gen_scope.instructions.deinit(self.gpa); @@ -1369,6 +1376,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .decl = decl, .arena = &gen_scope_arena.allocator, .parent = &decl.container.base, + .force_comptime = false, }; defer gen_scope.instructions.deinit(self.gpa); @@ -1428,6 +1436,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .decl = decl, .arena = &type_scope_arena.allocator, .parent = &decl.container.base, + .force_comptime = true, }; defer type_scope.instructions.deinit(self.gpa); @@ -1497,13 +1506,15 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { decl.analysis = .in_progress; - // A comptime decl does not store any value so we can just deinit this arena after analysis is done. + // A comptime decl does not store any value so we can just deinit + // this arena after analysis is done. var analysis_arena = std.heap.ArenaAllocator.init(self.gpa); defer analysis_arena.deinit(); var gen_scope: Scope.GenZIR = .{ .decl = decl, .arena = &analysis_arena.allocator, .parent = &decl.container.base, + .force_comptime = true, }; defer gen_scope.instructions.deinit(self.gpa); diff --git a/src/astgen.zig b/src/astgen.zig index 446ee98eb4..dfc5f06ddc 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -309,7 +309,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?), .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?), .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?), - .Switch => return switchExpr(mod, scope, rl, node.castTag(.Switch).?), + .Switch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Switch", .{}), .ContainerDecl => return containerDecl(mod, scope, rl, node.castTag(.ContainerDecl).?), .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), @@ -334,11 +334,19 @@ fn comptimeKeyword(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.C return comptimeExpr(mod, scope, rl, node.expr); } -pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerError!*zir.Inst { - const tree = parent_scope.tree(); - const src = tree.token_locs[node.firstToken()].start; +pub fn comptimeExpr( + mod: *Module, + parent_scope: *Scope, + rl: ResultLoc, + node: *ast.Node, +) InnerError!*zir.Inst { + // If we are already in a comptime scope, no need to make another one. + if (parent_scope.isComptime()) { + return expr(mod, parent_scope, rl, node); + } - // Optimization for labeled blocks: don't need to have 2 layers of blocks, we can reuse the existing one. + // Optimization for labeled blocks: don't need to have 2 layers of blocks, + // we can reuse the existing one. if (node.castTag(.LabeledBlock)) |block_node| { return labeledBlockExpr(mod, parent_scope, rl, block_node, .block_comptime); } @@ -348,6 +356,7 @@ pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *as .parent = parent_scope, .decl = parent_scope.ownerDecl().?, .arena = parent_scope.arena(), + .force_comptime = true, .instructions = .{}, }; defer block_scope.instructions.deinit(mod.gpa); @@ -356,6 +365,9 @@ pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *as // instruction is the block's result value. _ = try expr(mod, &block_scope.base, rl, node); + const tree = parent_scope.tree(); + const src = tree.token_locs[node.firstToken()].start; + const block = try addZIRInstBlock(mod, parent_scope, src, .block_comptime_flat, .{ .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), }); @@ -410,7 +422,7 @@ fn breakExpr( try gen_zir.labeled_breaks.append(mod.gpa, br.castTag(.@"break").?); if (have_store_to_block) { - const inst_list = parent_scope.cast(Scope.GenZIR).?.instructions.items; + const inst_list = parent_scope.getGenZIR().instructions.items; const last_inst = inst_list[inst_list.len - 2]; const store_inst = last_inst.castTag(.store_to_block_ptr).?; assert(store_inst.positionals.lhs == gen_zir.rl_ptr.?); @@ -559,6 +571,7 @@ fn labeledBlockExpr( .parent = parent_scope, .decl = parent_scope.ownerDecl().?, .arena = gen_zir.arena, + .force_comptime = parent_scope.isComptime(), .instructions = .{}, // TODO @as here is working around a stage1 miscompilation bug :( .label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{ @@ -746,6 +759,7 @@ fn varDecl( .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; defer init_scope.instructions.deinit(mod.gpa); @@ -1107,6 +1121,7 @@ fn containerDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Con .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; defer gen_scope.instructions.deinit(mod.gpa); @@ -1343,6 +1358,7 @@ fn orelseCatchExpr( .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; setBlockResultLoc(&block_scope, rl); @@ -1367,6 +1383,7 @@ fn orelseCatchExpr( .parent = &block_scope.base, .decl = block_scope.decl, .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, .instructions = .{}, }; defer then_scope.instructions.deinit(mod.gpa); @@ -1395,6 +1412,7 @@ fn orelseCatchExpr( .parent = &block_scope.base, .decl = block_scope.decl, .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, .instructions = .{}, }; defer else_scope.instructions.deinit(mod.gpa); @@ -1416,6 +1434,7 @@ fn orelseCatchExpr( then_result, unwrapped_payload, block, + block, ); } @@ -1432,7 +1451,8 @@ fn finishThenElseBlock( else_src: usize, then_result: *zir.Inst, else_result: ?*zir.Inst, - block: *zir.Inst.Block, + main_block: *zir.Inst.Block, + then_break_block: *zir.Inst.Block, ) InnerError!*zir.Inst { // We now have enough information to decide whether the result instruction should // be communicated via result location pointer or break instructions. @@ -1441,42 +1461,42 @@ fn finishThenElseBlock( .break_void => { if (!then_result.tag.isNoReturn()) { _ = try addZirInstTag(mod, &then_scope.base, then_src, .break_void, .{ - .block = block, + .block = then_break_block, }); } if (else_result) |inst| { if (!inst.tag.isNoReturn()) { _ = try addZirInstTag(mod, &else_scope.base, else_src, .break_void, .{ - .block = block, + .block = main_block, }); } } else { _ = try addZirInstTag(mod, &else_scope.base, else_src, .break_void, .{ - .block = block, + .block = main_block, }); } assert(!strat.elide_store_to_block_ptr_instructions); try copyBodyNoEliding(then_body, then_scope.*); try copyBodyNoEliding(else_body, else_scope.*); - return &block.base; + return &main_block.base; }, .break_operand => { if (!then_result.tag.isNoReturn()) { _ = try addZirInstTag(mod, &then_scope.base, then_src, .@"break", .{ - .block = block, + .block = then_break_block, .operand = then_result, }); } if (else_result) |inst| { if (!inst.tag.isNoReturn()) { _ = try addZirInstTag(mod, &else_scope.base, else_src, .@"break", .{ - .block = block, + .block = main_block, .operand = inst, }); } } else { _ = try addZirInstTag(mod, &else_scope.base, else_src, .break_void, .{ - .block = block, + .block = main_block, }); } if (strat.elide_store_to_block_ptr_instructions) { @@ -1487,8 +1507,8 @@ fn finishThenElseBlock( try copyBodyNoEliding(else_body, else_scope.*); } switch (rl) { - .ref => return &block.base, - else => return rvalue(mod, parent_scope, rl, &block.base), + .ref => return &main_block.base, + else => return rvalue(mod, parent_scope, rl, &main_block.base), } }, } @@ -1643,6 +1663,7 @@ fn boolBinOp( .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; defer block_scope.instructions.deinit(mod.gpa); @@ -1662,6 +1683,7 @@ fn boolBinOp( .parent = scope, .decl = block_scope.decl, .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, .instructions = .{}, }; defer rhs_scope.instructions.deinit(mod.gpa); @@ -1676,6 +1698,7 @@ fn boolBinOp( .parent = scope, .decl = block_scope.decl, .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, .instructions = .{}, }; defer const_scope.instructions.deinit(mod.gpa); @@ -1789,6 +1812,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; setBlockResultLoc(&block_scope, rl); @@ -1813,6 +1837,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn .parent = scope, .decl = block_scope.decl, .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, .instructions = .{}, }; defer then_scope.instructions.deinit(mod.gpa); @@ -1830,6 +1855,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn .parent = scope, .decl = block_scope.decl, .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, .instructions = .{}, }; defer else_scope.instructions.deinit(mod.gpa); @@ -1863,6 +1889,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn then_result, else_result, block, + block, ); } @@ -1912,6 +1939,7 @@ fn whileExpr( .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; setBlockResultLoc(&loop_scope, rl); @@ -1921,6 +1949,7 @@ fn whileExpr( .parent = &loop_scope.base, .decl = loop_scope.decl, .arena = loop_scope.arena, + .force_comptime = loop_scope.force_comptime, .instructions = .{}, }; defer continue_scope.instructions.deinit(mod.gpa); @@ -1978,6 +2007,7 @@ fn whileExpr( .parent = &continue_scope.base, .decl = continue_scope.decl, .arena = continue_scope.arena, + .force_comptime = continue_scope.force_comptime, .instructions = .{}, }; defer then_scope.instructions.deinit(mod.gpa); @@ -1992,6 +2022,7 @@ fn whileExpr( .parent = &continue_scope.base, .decl = continue_scope.decl, .arena = continue_scope.arena, + .force_comptime = continue_scope.force_comptime, .instructions = .{}, }; defer else_scope.instructions.deinit(mod.gpa); @@ -2027,6 +2058,7 @@ fn whileExpr( then_result, else_result, while_block, + cond_block, ); } @@ -2068,6 +2100,7 @@ fn forExpr( .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; setBlockResultLoc(&loop_scope, rl); @@ -2077,6 +2110,7 @@ fn forExpr( .parent = &loop_scope.base, .decl = loop_scope.decl, .arena = loop_scope.arena, + .force_comptime = loop_scope.force_comptime, .instructions = .{}, }; defer cond_scope.instructions.deinit(mod.gpa); @@ -2134,6 +2168,7 @@ fn forExpr( .parent = &cond_scope.base, .decl = cond_scope.decl, .arena = cond_scope.arena, + .force_comptime = cond_scope.force_comptime, .instructions = .{}, }; defer then_scope.instructions.deinit(mod.gpa); @@ -2174,6 +2209,7 @@ fn forExpr( .parent = &cond_scope.base, .decl = cond_scope.decl, .arena = cond_scope.arena, + .force_comptime = cond_scope.force_comptime, .instructions = .{}, }; defer else_scope.instructions.deinit(mod.gpa); @@ -2206,281 +2242,10 @@ fn forExpr( then_result, else_result, for_block, + cond_block, ); } -fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp { - var cur = node; - while (true) { - switch (cur.tag) { - .Range => return @fieldParentPtr(ast.Node.SimpleInfixOp, "base", cur), - .GroupedExpression => cur = @fieldParentPtr(ast.Node.GroupedExpression, "base", cur).expr, - else => return null, - } - } -} - -fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst { - if (true) { - @panic("TODO reimplement this"); - } - var block_scope: Scope.GenZIR = .{ - .parent = scope, - .decl = scope.ownerDecl().?, - .arena = scope.arena(), - .instructions = .{}, - }; - defer block_scope.instructions.deinit(mod.gpa); - - const tree = scope.tree(); - const switch_src = tree.token_locs[switch_node.switch_token].start; - const target_ptr = try expr(mod, &block_scope.base, .ref, switch_node.expr); - const target = try addZIRUnOp(mod, &block_scope.base, target_ptr.src, .deref, target_ptr); - // Add the switch instruction here so that it comes before any range checks. - const switch_inst = (try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{ - .target_ptr = target_ptr, - .cases = undefined, // populated below - .items = &[_]*zir.Inst{}, // populated below - .else_body = undefined, // populated below - }, .{})).castTag(.switchbr).?; - - var items = std.ArrayList(*zir.Inst).init(mod.gpa); - defer items.deinit(); - var cases = std.ArrayList(zir.Inst.SwitchBr.Case).init(mod.gpa); - defer cases.deinit(); - - // Add comptime block containing all prong items first, - const item_block = try addZIRInstBlock(mod, scope, switch_src, .block_comptime_flat, .{ - .instructions = undefined, // populated below - }); - // then add block containing the switch. - const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ - .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), - }); - - // Most result location types can be forwarded directly; however - // if we need to write to a pointer which has an inferred type, - // proper type inference requires peer type resolution on the switch case. - const case_rl: ResultLoc = switch (rl) { - .discard, .none, .ty, .ptr, .ref => rl, - .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, - }; - - var item_scope: Scope.GenZIR = .{ - .parent = scope, - .decl = scope.ownerDecl().?, - .arena = scope.arena(), - .instructions = .{}, - }; - defer item_scope.instructions.deinit(mod.gpa); - - var case_scope: Scope.GenZIR = .{ - .parent = scope, - .decl = block_scope.decl, - .arena = block_scope.arena, - .instructions = .{}, - }; - defer case_scope.instructions.deinit(mod.gpa); - - var else_scope: Scope.GenZIR = .{ - .parent = scope, - .decl = block_scope.decl, - .arena = block_scope.arena, - .instructions = .{}, - }; - defer else_scope.instructions.deinit(mod.gpa); - - // first we gather all the switch items and check else/'_' prongs - var else_src: ?usize = null; - var underscore_src: ?usize = null; - var first_range: ?*zir.Inst = null; - var special_case: ?*ast.Node.SwitchCase = null; - for (switch_node.cases()) |uncasted_case| { - const case = uncasted_case.castTag(.SwitchCase).?; - const case_src = tree.token_locs[case.firstToken()].start; - // reset without freeing to reduce allocations. - case_scope.instructions.items.len = 0; - assert(case.items_len != 0); - - // Check for else/_ prong, those are handled last. - if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { - if (else_src) |src| { - const msg = msg: { - const msg = try mod.errMsg( - scope, - case_src, - "multiple else prongs in switch expression", - .{}, - ); - errdefer msg.destroy(mod.gpa); - try mod.errNote(scope, src, msg, "previous else prong is here", .{}); - break :msg msg; - }; - return mod.failWithOwnedErrorMsg(scope, msg); - } - else_src = case_src; - special_case = case; - continue; - } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and - mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) - { - if (underscore_src) |src| { - const msg = msg: { - const msg = try mod.errMsg( - scope, - case_src, - "multiple '_' prongs in switch expression", - .{}, - ); - errdefer msg.destroy(mod.gpa); - try mod.errNote(scope, src, msg, "previous '_' prong is here", .{}); - break :msg msg; - }; - return mod.failWithOwnedErrorMsg(scope, msg); - } - underscore_src = case_src; - special_case = case; - continue; - } - - if (else_src) |some_else| { - if (underscore_src) |some_underscore| { - const msg = msg: { - const msg = try mod.errMsg( - scope, - switch_src, - "else and '_' prong in switch expression", - .{}, - ); - errdefer msg.destroy(mod.gpa); - try mod.errNote(scope, some_else, msg, "else prong is here", .{}); - try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{}); - break :msg msg; - }; - return mod.failWithOwnedErrorMsg(scope, msg); - } - } - - // If this is a simple one item prong then it is handled by the switchbr. - if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) { - const item = try expr(mod, &item_scope.base, .none, case.items()[0]); - try items.append(item); - try switchCaseExpr(mod, &case_scope.base, case_rl, block, case); - - try cases.append(.{ - .item = item, - .body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items) }, - }); - continue; - } - - // TODO if the case has few items and no ranges it might be better - // to just handle them as switch prongs. - - // Check if the target matches any of the items. - // 1, 2, 3..6 will result in - // target == 1 or target == 2 or (target >= 3 and target <= 6) - var any_ok: ?*zir.Inst = null; - for (case.items()) |item| { - if (getRangeNode(item)) |range| { - const start = try expr(mod, &item_scope.base, .none, range.lhs); - const end = try expr(mod, &item_scope.base, .none, range.rhs); - const range_src = tree.token_locs[range.op_token].start; - const range_inst = try addZIRBinOp(mod, &item_scope.base, range_src, .switch_range, start, end); - try items.append(range_inst); - if (first_range == null) first_range = range_inst; - - // target >= start and target <= end - const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, start); - const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, end); - const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_and, range_start_ok, range_end_ok); - - if (any_ok) |some| { - any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_or, some, range_ok); - } else { - any_ok = range_ok; - } - continue; - } - - const item_inst = try expr(mod, &item_scope.base, .none, item); - try items.append(item_inst); - const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst); - - if (any_ok) |some| { - any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .bool_or, some, cpm_ok); - } else { - any_ok = cpm_ok; - } - } - - const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{ - .condition = any_ok.?, - .then_body = undefined, // populated below - .else_body = undefined, // populated below - }, .{}); - const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{ - .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), - }); - - // reset cond_scope for then_body - case_scope.instructions.items.len = 0; - try switchCaseExpr(mod, &case_scope.base, case_rl, block, case); - condbr.positionals.then_body = .{ - .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), - }; - - // reset cond_scope for else_body - case_scope.instructions.items.len = 0; - _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{ - .block = cond_block, - }, .{}); - condbr.positionals.else_body = .{ - .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), - }; - } - - // Generate else block or a break last to finish the block. - if (special_case) |case| { - try switchCaseExpr(mod, &else_scope.base, case_rl, block, case); - } else { - // Not handling all possible cases is a compile error. - _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreachable_unsafe); - } - - // All items have been generated, add the instructions to the comptime block. - item_block.positionals.body = .{ - .instructions = try block_scope.arena.dupe(*zir.Inst, item_scope.instructions.items), - }; - - // Actually populate switch instruction values. - if (else_src != null) switch_inst.kw_args.special_prong = .@"else"; - if (underscore_src != null) switch_inst.kw_args.special_prong = .underscore; - switch_inst.positionals.cases = try block_scope.arena.dupe(zir.Inst.SwitchBr.Case, cases.items); - switch_inst.positionals.items = try block_scope.arena.dupe(*zir.Inst, items.items); - switch_inst.kw_args.range = first_range; - switch_inst.positionals.else_body = .{ - .instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items), - }; - return &block.base; -} - -fn switchCaseExpr(mod: *Module, scope: *Scope, rl: ResultLoc, block: *zir.Inst.Block, case: *ast.Node.SwitchCase) !void { - const tree = scope.tree(); - const case_src = tree.token_locs[case.firstToken()].start; - if (case.payload != null) { - return mod.fail(scope, case_src, "TODO switch case payload capture", .{}); - } - - const case_body = try expr(mod, scope, rl, case.expr); - if (!case_body.tag.isNoReturn()) { - _ = try addZIRInst(mod, scope, case_src, zir.Inst.Break, .{ - .block = block, - .operand = case_body, - }, .{}); - } -} - fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[cfe.ltoken].start; @@ -2859,6 +2624,7 @@ fn asRlPtr( .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; defer as_scope.instructions.deinit(mod.gpa); diff --git a/src/zir.zig b/src/zir.zig index 83eee71f87..2559fcdc8e 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -336,12 +336,6 @@ pub const Inst = struct { enum_literal, /// Create an enum type. enum_type, - /// A switch expression. - switchbr, - /// A range in a switch case, `lhs...rhs`. - /// Only checks that `lhs >= rhs` if they are ints, everything else is - /// validated by the .switch instruction. - switch_range, /// Does nothing; returns a void value. void_value, @@ -441,7 +435,6 @@ pub const Inst = struct { .error_union_type, .merge_error_sets, .slice_start, - .switch_range, => BinOp, .block, @@ -478,7 +471,6 @@ pub const Inst = struct { .enum_literal => EnumLiteral, .error_set => ErrorSet, .slice => Slice, - .switchbr => SwitchBr, .typeof_peer => TypeOfPeer, .container_field_named => ContainerFieldNamed, .container_field_typed => ContainerFieldTyped, @@ -605,7 +597,6 @@ pub const Inst = struct { .slice, .slice_start, .import, - .switch_range, .typeof_peer, .resolve_inferred_alloc, .set_eval_branch_quota, @@ -625,7 +616,6 @@ pub const Inst = struct { .unreachable_unsafe, .unreachable_safe, .loop, - .switchbr, .container_field_named, .container_field_typed, .container_field, @@ -1091,32 +1081,6 @@ pub const Inst = struct { }, }; - pub const SwitchBr = struct { - pub const base_tag = Tag.switchbr; - base: Inst, - - positionals: struct { - target_ptr: *Inst, - /// List of all individual items and ranges - items: []*Inst, - cases: []Case, - else_body: Body, - }, - kw_args: struct { - /// Pointer to first range if such exists. - range: ?*Inst = null, - special_prong: enum { - none, - @"else", - underscore, - } = .none, - }, - - pub const Case = struct { - item: *Inst, - body: Body, - }; - }; pub const TypeOfPeer = struct { pub const base_tag = .typeof_peer; base: Inst, @@ -1467,26 +1431,6 @@ const Writer = struct { } try stream.writeByte(']'); }, - []Inst.SwitchBr.Case => { - if (param.len == 0) { - return stream.writeAll("{}"); - } - try stream.writeAll("{\n"); - for (param) |*case, i| { - if (i != 0) { - try stream.writeAll(",\n"); - } - try stream.writeByteNTimes(' ', self.indent); - self.indent += 2; - try self.writeParamToStream(stream, &case.item); - try stream.writeAll(" => "); - try self.writeParamToStream(stream, &case.body); - self.indent -= 2; - } - try stream.writeByte('\n'); - try stream.writeByteNTimes(' ', self.indent - 2); - try stream.writeByte('}'); - }, else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), } } diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 773c782746..301b95ad97 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -151,8 +151,6 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .slice => return zirSlice(mod, scope, old_inst.castTag(.slice).?), .slice_start => return zirSliceStart(mod, scope, old_inst.castTag(.slice_start).?), .import => return zirImport(mod, scope, old_inst.castTag(.import).?), - .switchbr => return zirSwitchbr(mod, scope, old_inst.castTag(.switchbr).?), - .switch_range => return zirSwitchRange(mod, scope, old_inst.castTag(.switch_range).?), .bool_and => return zirBoolOp(mod, scope, old_inst.castTag(.bool_and).?), .bool_or => return zirBoolOp(mod, scope, old_inst.castTag(.bool_or).?), .void_value => return mod.constVoid(scope, old_inst.src), @@ -795,6 +793,12 @@ fn analyzeBlockBody( var coerce_block = parent_block.makeSubBlock(); defer coerce_block.instructions.deinit(mod.gpa); const coerced_operand = try mod.coerce(&coerce_block.base, resolved_ty, br.operand); + // If no instructions were produced, such as in the case of a coercion of a + // constant value to a new type, we can simply point the br operand to it. + if (coerce_block.instructions.items.len == 0) { + br.operand = coerced_operand; + continue; + } assert(coerce_block.instructions.items[coerce_block.instructions.items.len - 1] == coerced_operand); // Here we depend on the br instruction having been over-allocated (if necessary) // inide analyzeBreak so that it can be converted into a br_block_flat instruction. @@ -1531,234 +1535,6 @@ fn zirSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError! return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null); } -fn zirSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { - const tracy = trace(@src()); - defer tracy.end(); - const start = try resolveInst(mod, scope, inst.positionals.lhs); - const end = try resolveInst(mod, scope, inst.positionals.rhs); - - switch (start.ty.zigTypeTag()) { - .Int, .ComptimeInt => {}, - else => return mod.constVoid(scope, inst.base.src), - } - switch (end.ty.zigTypeTag()) { - .Int, .ComptimeInt => {}, - else => return mod.constVoid(scope, inst.base.src), - } - if (start.value()) |start_val| { - if (end.value()) |end_val| { - if (start_val.compare(.gte, end_val)) { - return mod.fail(scope, inst.base.src, "range start value must be smaller than the end value", .{}); - } - } - } - return mod.constVoid(scope, inst.base.src); -} - -fn zirSwitchbr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) InnerError!*Inst { - const tracy = trace(@src()); - defer tracy.end(); - const target_ptr = try resolveInst(mod, scope, inst.positionals.target_ptr); - const target = try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target_ptr.src); - try validateSwitch(mod, scope, target, inst); - - if (try mod.resolveDefinedValue(scope, target)) |target_val| { - for (inst.positionals.cases) |case| { - const resolved = try resolveInst(mod, scope, case.item); - const casted = try mod.coerce(scope, target.ty, resolved); - const item = try mod.resolveConstValue(scope, casted); - - if (target_val.eql(item)) { - try analyzeBody(mod, scope.cast(Scope.Block).?, case.body); - return mod.constNoReturn(scope, inst.base.src); - } - } - try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body); - return mod.constNoReturn(scope, inst.base.src); - } - - if (inst.positionals.cases.len == 0) { - // no cases just analyze else_branch - try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body); - return mod.constNoReturn(scope, inst.base.src); - } - - const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); - const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len); - - var case_block: Scope.Block = .{ - .parent = parent_block, - .inst_table = parent_block.inst_table, - .func = parent_block.func, - .owner_decl = parent_block.owner_decl, - .src_decl = parent_block.src_decl, - .instructions = .{}, - .arena = parent_block.arena, - .inlining = parent_block.inlining, - .is_comptime = parent_block.is_comptime, - .branch_quota = parent_block.branch_quota, - }; - defer case_block.instructions.deinit(mod.gpa); - - for (inst.positionals.cases) |case, i| { - // Reset without freeing. - case_block.instructions.items.len = 0; - - const resolved = try resolveInst(mod, scope, case.item); - const casted = try mod.coerce(scope, target.ty, resolved); - const item = try mod.resolveConstValue(scope, casted); - - try analyzeBody(mod, &case_block, case.body); - - cases[i] = .{ - .item = item, - .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) }, - }; - } - - case_block.instructions.items.len = 0; - try analyzeBody(mod, &case_block, inst.positionals.else_body); - - const else_body: ir.Body = .{ - .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items), - }; - - return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases, else_body); -} - -fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void { - // validate usage of '_' prongs - if (inst.kw_args.special_prong == .underscore and target.ty.zigTypeTag() != .Enum) { - return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{}); - // TODO notes "'_' prong here" inst.positionals.cases[last].src - } - - // check that target type supports ranges - if (inst.kw_args.range) |range_inst| { - switch (target.ty.zigTypeTag()) { - .Int, .ComptimeInt => {}, - else => { - return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty}); - // TODO notes "range used here" range_inst.src - }, - } - } - - // validate for duplicate items/missing else prong - switch (target.ty.zigTypeTag()) { - .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}), - .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}), - .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}), - .Int, .ComptimeInt => { - var range_set = @import("RangeSet.zig").init(mod.gpa); - defer range_set.deinit(); - - for (inst.positionals.items) |item| { - const maybe_src = if (item.castTag(.switch_range)) |range| blk: { - const start_resolved = try resolveInst(mod, scope, range.positionals.lhs); - const start_casted = try mod.coerce(scope, target.ty, start_resolved); - const end_resolved = try resolveInst(mod, scope, range.positionals.rhs); - const end_casted = try mod.coerce(scope, target.ty, end_resolved); - - break :blk try range_set.add( - try mod.resolveConstValue(scope, start_casted), - try mod.resolveConstValue(scope, end_casted), - item.src, - ); - } else blk: { - const resolved = try resolveInst(mod, scope, item); - const casted = try mod.coerce(scope, target.ty, resolved); - const value = try mod.resolveConstValue(scope, casted); - break :blk try range_set.add(value, value, item.src); - }; - - if (maybe_src) |previous_src| { - return mod.fail(scope, item.src, "duplicate switch value", .{}); - // TODO notes "previous value is here" previous_src - } - } - - if (target.ty.zigTypeTag() == .Int) { - var arena = std.heap.ArenaAllocator.init(mod.gpa); - defer arena.deinit(); - - const start = try target.ty.minInt(&arena, mod.getTarget()); - const end = try target.ty.maxInt(&arena, mod.getTarget()); - if (try range_set.spans(start, end)) { - if (inst.kw_args.special_prong == .@"else") { - return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); - } - return; - } - } - - if (inst.kw_args.special_prong != .@"else") { - return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{}); - } - }, - .Bool => { - var true_count: u8 = 0; - var false_count: u8 = 0; - for (inst.positionals.items) |item| { - const resolved = try resolveInst(mod, scope, item); - const casted = try mod.coerce(scope, Type.initTag(.bool), resolved); - if ((try mod.resolveConstValue(scope, casted)).toBool()) { - true_count += 1; - } else { - false_count += 1; - } - - if (true_count + false_count > 2) { - return mod.fail(scope, item.src, "duplicate switch value", .{}); - } - } - if ((true_count + false_count < 2) and inst.kw_args.special_prong != .@"else") { - return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{}); - } - if ((true_count + false_count == 2) and inst.kw_args.special_prong == .@"else") { - return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); - } - }, - .EnumLiteral, .Void, .Fn, .Pointer, .Type => { - if (inst.kw_args.special_prong != .@"else") { - return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty}); - } - - var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa); - defer seen_values.deinit(); - - for (inst.positionals.items) |item| { - const resolved = try resolveInst(mod, scope, item); - const casted = try mod.coerce(scope, target.ty, resolved); - const val = try mod.resolveConstValue(scope, casted); - - if (try seen_values.fetchPut(val, item.src)) |prev| { - return mod.fail(scope, item.src, "duplicate switch value", .{}); - // TODO notes "previous value here" prev.value - } - } - }, - - .ErrorUnion, - .NoReturn, - .Array, - .Struct, - .Undefined, - .Null, - .Optional, - .BoundFn, - .Opaque, - .Vector, - .Frame, - .AnyFrame, - .ComptimeFloat, - .Float, - => { - return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty}); - }, - } -} - fn zirImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 0d5a52980b..78d7eba262 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -962,43 +962,6 @@ pub fn addCases(ctx: *TestContext) !void { , "hello\nhello\nhello\nhello\nhello\n", ); - - // comptime switch - - // Basic for loop - case.addCompareOutput( - \\pub export fn _start() noreturn { - \\ assert(foo() == 1); - \\ exit(); - \\} - \\ - \\fn foo() u32 { - \\ const a: comptime_int = 1; - \\ var b: u32 = 0; - \\ switch (a) { - \\ 1 => b = 1, - \\ 2 => b = 2, - \\ else => unreachable, - \\ } - \\ return b; - \\} - \\ - \\pub fn assert(ok: bool) void { - \\ if (!ok) unreachable; // assertion failure - \\} - \\ - \\fn exit() noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (0) - \\ : "rcx", "r11", "memory" - \\ ); - \\ unreachable; - \\} - , - "", - ); } { -- cgit v1.2.3