diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-03-25 00:37:52 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-03-25 00:55:36 -0700 |
| commit | 31023de6c4b3957ef356be01b5454426844955a9 (patch) | |
| tree | 7951b8c7265ace392bde49165b505762f1cc33e8 | |
| parent | 12d18a36e5eed4b0898e93df2fd13b56061049b3 (diff) | |
| download | zig-31023de6c4b3957ef356be01b5454426844955a9.tar.gz zig-31023de6c4b3957ef356be01b5454426844955a9.zip | |
stage2: implement inline while
Introduce "inline" variants of ZIR tags:
* block => block_inline
* repeat => repeat_inline
* break => break_inline
* condbr => condbr_inline
The inline variants perform control flow at compile-time, and they
utilize the return value of `Sema.analyzeBody`.
`analyzeBody` now returns an Index, not a Ref, which is the ZIR index of
a break instruction. This effectively communicates both the intended
break target block as well as the operand, allowing parent blocks to
find out whether they, in turn, should return the break instruction up the
call stack, or accept the operand as the block's result and continue
analyzing instructions in the block.
Additionally:
* removed the deprecated ZIR tag `block_comptime`.
* removed `break_void_node` so that all break instructions use the same Data.
* zir.Code: remove the `root_start` and `root_len` fields. There is now
implied to be a block at index 0 for the root body. This is so that
`break_inline` has something to point at and we no longer need the
special instruction `break_flat`.
* implement source location byteOffset() for .node_offset_if_cond
.node_offset_for_cond is probably redundant and can be deleted.
We don't have `comptime var` supported yet, so this commit adds a test
that at least makes sure the condition is required to be comptime known
for `inline while`.
| -rw-r--r-- | lib/std/zig/parse.zig | 5 | ||||
| -rw-r--r-- | src/Module.zig | 116 | ||||
| -rw-r--r-- | src/Sema.zig | 117 | ||||
| -rw-r--r-- | src/astgen.zig | 47 | ||||
| -rw-r--r-- | src/zir.zig | 81 | ||||
| -rw-r--r-- | test/stage2/test.zig | 37 |
6 files changed, 227 insertions, 176 deletions
diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 874a210375..029d8ede50 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -59,10 +59,7 @@ pub fn parse(gpa: *Allocator, source: []const u8) Allocator.Error!Tree { parser.nodes.appendAssumeCapacity(.{ .tag = .root, .main_token = 0, - .data = .{ - .lhs = undefined, - .rhs = undefined, - }, + .data = undefined, }); const root_members = try parser.parseContainerMembers(); const root_decls = try root_members.toSpan(&parser); diff --git a/src/Module.zig b/src/Module.zig index 0258e703cf..9f57bdb93d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -952,15 +952,11 @@ pub const Scope = struct { /// initialized, but empty, state. pub fn finish(gz: *GenZir) !zir.Code { const gpa = gz.zir_code.gpa; - const root_start = @intCast(u32, gz.zir_code.extra.items.len); - const root_len = @intCast(u32, gz.instructions.items.len); - try gz.zir_code.extra.appendSlice(gpa, gz.instructions.items); + try gz.setBlockBody(0); return zir.Code{ .instructions = gz.zir_code.instructions.toOwnedSlice(), .string_bytes = gz.zir_code.string_bytes.toOwnedSlice(gpa), .extra = gz.zir_code.extra.toOwnedSlice(gpa), - .root_start = root_start, - .root_len = root_len, }; } @@ -1224,11 +1220,12 @@ pub const Scope = struct { pub fn addBreak( gz: *GenZir, + tag: zir.Inst.Tag, break_block: zir.Inst.Index, operand: zir.Inst.Ref, ) !zir.Inst.Index { return gz.addAsIndex(.{ - .tag = .@"break", + .tag = tag, .data = .{ .@"break" = .{ .block_inst = break_block, .operand = operand, @@ -1236,20 +1233,6 @@ pub const Scope = struct { }); } - pub fn addBreakVoid( - gz: *GenZir, - break_block: zir.Inst.Index, - node_index: ast.Node.Index, - ) !zir.Inst.Index { - return gz.addAsIndex(.{ - .tag = .break_void_node, - .data = .{ .break_void_node = .{ - .src_node = gz.zir_code.decl.nodeIndexToRelative(node_index), - .block_inst = break_block, - } }, - }); - } - pub fn addBin( gz: *GenZir, tag: zir.Inst.Tag, @@ -1323,11 +1306,11 @@ pub const Scope = struct { /// Note that this returns a `zir.Inst.Index` not a ref. /// Leaves the `payload_index` field undefined. - pub fn addCondBr(gz: *GenZir, node: ast.Node.Index) !zir.Inst.Index { + pub fn addCondBr(gz: *GenZir, tag: zir.Inst.Tag, node: ast.Node.Index) !zir.Inst.Index { try gz.instructions.ensureCapacity(gz.zir_code.gpa, gz.instructions.items.len + 1); const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len); try gz.zir_code.instructions.append(gz.zir_code.gpa, .{ - .tag = .condbr, + .tag = tag, .data = .{ .pl_node = .{ .src_node = gz.zir_code.decl.nodeIndexToRelative(node), .payload_index = undefined, @@ -1462,6 +1445,24 @@ pub const WipZirCode = struct { } }; +/// Call `deinit` on the result. +fn initAstGen(mod: *Module, decl: *Decl, arena: *Allocator) !WipZirCode { + var wzc: WipZirCode = .{ + .decl = decl, + .arena = arena, + .gpa = mod.gpa, + }; + // Must be a block instruction at index 0 with the root body. + try wzc.instructions.append(mod.gpa, .{ + .tag = .block, + .data = .{ .pl_node = .{ + .src_node = 0, + .payload_index = undefined, + } }, + }); + return wzc; +} + /// This struct holds data necessary to construct API-facing `AllErrors.Message`. /// Its memory is managed with the general purpose allocator so that they /// can be created and destroyed in response to incremental updates. @@ -1572,10 +1573,10 @@ pub const SrcLoc = struct { const token_starts = tree.tokens.items(.start); return token_starts[tok_index]; }, - .node_abs => |node_index| { + .node_abs => |node| { const tree = src_loc.container.file_scope.base.tree(); const token_starts = tree.tokens.items(.start); - const tok_index = tree.firstToken(node_index); + const tok_index = tree.firstToken(node); return token_starts[tok_index]; }, .byte_offset => |byte_off| { @@ -1591,15 +1592,14 @@ pub const SrcLoc = struct { }, .node_offset => |node_off| { const decl = src_loc.container.decl; - const node_index = decl.relativeToNodeIndex(node_off); + const node = decl.relativeToNodeIndex(node_off); const tree = decl.container.file_scope.base.tree(); const main_tokens = tree.nodes.items(.main_token); - const tok_index = main_tokens[node_index]; + const tok_index = main_tokens[node]; const token_starts = tree.tokens.items(.start); return token_starts[tok_index]; }, .node_offset_var_decl_ty => @panic("TODO"), - .node_offset_for_cond => @panic("TODO"), .node_offset_builtin_call_arg0 => @panic("TODO"), .node_offset_builtin_call_arg1 => @panic("TODO"), .node_offset_builtin_call_argn => unreachable, // Handled specially in `Sema`. @@ -1610,7 +1610,27 @@ pub const SrcLoc = struct { .node_offset_deref_ptr => @panic("TODO"), .node_offset_asm_source => @panic("TODO"), .node_offset_asm_ret_ty => @panic("TODO"), - .node_offset_if_cond => @panic("TODO"), + + .node_offset_for_cond, .node_offset_if_cond => |node_off| { + const decl = src_loc.container.decl; + const node = decl.relativeToNodeIndex(node_off); + const tree = decl.container.file_scope.base.tree(); + const node_tags = tree.nodes.items(.tag); + const cond_expr = switch (node_tags[node]) { + .if_simple => tree.ifSimple(node).ast.cond_expr, + .@"if" => tree.ifFull(node).ast.cond_expr, + .while_simple => tree.whileSimple(node).ast.cond_expr, + .while_cont => tree.whileCont(node).ast.cond_expr, + .@"while" => tree.whileFull(node).ast.cond_expr, + .for_simple => tree.forSimple(node).ast.cond_expr, + .@"for" => tree.forFull(node).ast.cond_expr, + else => unreachable, + }; + const main_tokens = tree.nodes.items(.main_token); + const tok_index = main_tokens[cond_expr]; + const token_starts = tree.tokens.items(.start); + return token_starts[tok_index]; + }, .node_offset_bin_op => @panic("TODO"), .node_offset_bin_lhs => @panic("TODO"), .node_offset_bin_rhs => @panic("TODO"), @@ -2034,11 +2054,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool { defer analysis_arena.deinit(); var code: zir.Code = blk: { - var wip_zir_code: WipZirCode = .{ - .decl = decl, - .arena = &analysis_arena.allocator, - .gpa = mod.gpa, - }; + var wip_zir_code = try mod.initAstGen(decl, &analysis_arena.allocator); defer wip_zir_code.deinit(); var gen_scope: Scope.GenZir = .{ @@ -2111,11 +2127,7 @@ fn astgenAndSemaFn( var fn_type_scope_arena = std.heap.ArenaAllocator.init(mod.gpa); defer fn_type_scope_arena.deinit(); - var fn_type_wip_zir_code: WipZirCode = .{ - .decl = decl, - .arena = &fn_type_scope_arena.allocator, - .gpa = mod.gpa, - }; + var fn_type_wip_zir_code = try mod.initAstGen(decl, &fn_type_scope_arena.allocator); defer fn_type_wip_zir_code.deinit(); var fn_type_scope: Scope.GenZir = .{ @@ -2270,7 +2282,7 @@ fn astgenAndSemaFn( const tag: zir.Inst.Tag = if (is_var_args) .fn_type_var_args else .fn_type; break :fn_type try fn_type_scope.addFnType(tag, return_type_inst, param_types); }; - _ = try fn_type_scope.addUnNode(.break_flat, fn_type_inst, 0); + _ = try fn_type_scope.addBreak(.break_inline, 0, fn_type_inst); // We need the memory for the Type to go into the arena for the Decl var decl_arena = std.heap.ArenaAllocator.init(mod.gpa); @@ -2348,12 +2360,8 @@ fn astgenAndSemaFn( const fn_zir: zir.Code = blk: { // We put the ZIR inside the Decl arena. - var wip_zir_code: WipZirCode = .{ - .decl = decl, - .arena = &decl_arena.allocator, - .gpa = mod.gpa, - .ref_start_index = @intCast(u32, zir.Inst.Ref.typed_value_map.len + param_count), - }; + var wip_zir_code = try mod.initAstGen(decl, &decl_arena.allocator); + wip_zir_code.ref_start_index = @intCast(u32, zir.Inst.Ref.typed_value_map.len + param_count); defer wip_zir_code.deinit(); var gen_scope: Scope.GenZir = .{ @@ -2559,11 +2567,7 @@ fn astgenAndSemaVarDecl( var gen_scope_arena = std.heap.ArenaAllocator.init(mod.gpa); defer gen_scope_arena.deinit(); - var wip_zir_code: WipZirCode = .{ - .decl = decl, - .arena = &gen_scope_arena.allocator, - .gpa = mod.gpa, - }; + var wip_zir_code = try mod.initAstGen(decl, &gen_scope_arena.allocator); defer wip_zir_code.deinit(); var gen_scope: Scope.GenZir = .{ @@ -2583,7 +2587,7 @@ fn astgenAndSemaVarDecl( init_result_loc, var_decl.ast.init_node, ); - _ = try gen_scope.addUnNode(.break_flat, init_inst, var_decl.ast.init_node); + _ = try gen_scope.addBreak(.break_inline, 0, init_inst); var code = try gen_scope.finish(); defer code.deinit(mod.gpa); if (std.builtin.mode == .Debug and mod.comp.verbose_ir) { @@ -2611,7 +2615,7 @@ fn astgenAndSemaVarDecl( }; defer block_scope.instructions.deinit(mod.gpa); - const init_inst_zir_ref = try sema.root(&block_scope); + const init_inst_zir_ref = try sema.rootAsRef(&block_scope); // The result location guarantees the type coercion. const analyzed_init_inst = try sema.resolveInst(init_inst_zir_ref); // The is_comptime in the Scope.Block guarantees the result is comptime-known. @@ -2632,11 +2636,7 @@ fn astgenAndSemaVarDecl( var type_scope_arena = std.heap.ArenaAllocator.init(mod.gpa); defer type_scope_arena.deinit(); - var wip_zir_code: WipZirCode = .{ - .decl = decl, - .arena = &type_scope_arena.allocator, - .gpa = mod.gpa, - }; + var wip_zir_code = try mod.initAstGen(decl, &type_scope_arena.allocator); defer wip_zir_code.deinit(); var type_scope: Scope.GenZir = .{ @@ -2647,7 +2647,7 @@ fn astgenAndSemaVarDecl( defer type_scope.instructions.deinit(mod.gpa); const var_type = try astgen.typeExpr(mod, &type_scope.base, var_decl.ast.type_node); - _ = try type_scope.addUnNode(.break_flat, var_type, 0); + _ = try type_scope.addBreak(.break_inline, 0, var_type); var code = try type_scope.finish(); defer code.deinit(mod.gpa); diff --git a/src/Sema.zig b/src/Sema.zig index 543e3528cf..9370b54e3c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -60,14 +60,21 @@ const InnerError = Module.InnerError; const Decl = Module.Decl; const LazySrcLoc = Module.LazySrcLoc; -pub fn root(sema: *Sema, root_block: *Scope.Block) !zir.Inst.Ref { - const root_body = sema.code.extra[sema.code.root_start..][0..sema.code.root_len]; +pub fn root(sema: *Sema, root_block: *Scope.Block) !zir.Inst.Index { + const inst_data = sema.code.instructions.items(.data)[0].pl_node; + const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index); + const root_body = sema.code.extra[extra.end..][0..extra.data.body_len]; return sema.analyzeBody(root_block, root_body); } -/// Assumes that `root_block` ends with `break_flat`. +pub fn rootAsRef(sema: *Sema, root_block: *Scope.Block) !zir.Inst.Ref { + const break_inst = try sema.root(root_block); + return sema.code.instructions.items(.data)[break_inst].@"break".operand; +} + +/// Assumes that `root_block` ends with `break_inline`. pub fn rootAsType(sema: *Sema, root_block: *Scope.Block) !Type { - const zir_inst_ref = try sema.root(root_block); + const zir_inst_ref = try sema.rootAsRef(root_block); // Source location is unneeded because resolveConstValue must have already // been successfully called when coercing the value to a type, from the // result location. @@ -78,17 +85,22 @@ pub fn rootAsType(sema: *Sema, root_block: *Scope.Block) !Type { /// return type of `analyzeBody` so that we can tail call them. /// Only appropriate to return when the instruction is known to be NoReturn /// solely based on the ZIR tag. -const always_noreturn: InnerError!zir.Inst.Ref = .none; +const always_noreturn: InnerError!zir.Inst.Index = @as(zir.Inst.Index, undefined); /// This function is the main loop of `Sema` and it can be used in two different ways: /// * The traditional way where there are N breaks out of the block and peer type /// resolution is done on the break operands. In this case, the `zir.Inst.Index` /// part of the return value will be `undefined`, and callsites should ignore it, /// finding the block result value via the block scope. -/// * The "flat" way. There is only 1 break out of the block, and it is with a `break_flat` +/// * The "flat" way. There is only 1 break out of the block, and it is with a `break_inline` /// instruction. In this case, the `zir.Inst.Index` part of the return value will be -/// the block result value. No block scope needs to be created for this strategy. -pub fn analyzeBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Index) !zir.Inst.Ref { +/// the break instruction. This communicates both which block the break applies to, as +/// well as the operand. No block scope needs to be created for this strategy. +pub fn analyzeBody( + sema: *Sema, + block: *Scope.Block, + body: []const zir.Inst.Index, +) InnerError!zir.Inst.Index { // No tracy calls here, to avoid interfering with the tail call mechanism. const map = block.sema.inst_map; @@ -127,8 +139,7 @@ pub fn analyzeBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Inde .bitcast => try sema.zirBitcast(block, inst), .bitcast_ref => try sema.zirBitcastRef(block, inst), .bitcast_result_ptr => try sema.zirBitcastResultPtr(block, inst), - .block => try sema.zirBlock(block, inst, false), - .block_comptime => try sema.zirBlock(block, inst, true), + .block => try sema.zirBlock(block, inst), .bool_not => try sema.zirBoolNot(block, inst), .bool_and => try sema.zirBoolOp(block, inst, false), .bool_or => try sema.zirBoolOp(block, inst, true), @@ -227,8 +238,7 @@ pub fn analyzeBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Inde // tail call them here. .condbr => return sema.zirCondbr(block, inst), .@"break" => return sema.zirBreak(block, inst), - .break_void_node => return sema.zirBreakVoidNode(block, inst), - .break_flat => return sema.code.instructions.items(.data)[inst].un_node.operand, + .break_inline => return inst, .compile_error => return sema.zirCompileError(block, inst), .ret_coerce => return sema.zirRetTok(block, inst, true), .ret_node => return sema.zirRetNode(block, inst), @@ -286,13 +296,43 @@ pub fn analyzeBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Inde continue; }, - // Special case: send comptime control flow back to the beginning of this block. + // Special case instructions to handle comptime control flow. .repeat_inline => { + // Send comptime control flow back to the beginning of this block. const src: LazySrcLoc = .{ .node_offset = datas[inst].node }; try sema.emitBackwardBranch(block, src); i = 0; continue; }, + .block_inline => blk: { + // Directly analyze the block body without introducing a new block. + const inst_data = datas[inst].pl_node; + const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index); + const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len]; + const break_inst = try sema.analyzeBody(block, inline_body); + const break_data = datas[break_inst].@"break"; + if (inst == break_data.block_inst) { + break :blk try sema.resolveInst(break_data.operand); + } else { + return break_inst; + } + }, + .condbr_inline => blk: { + const inst_data = datas[inst].pl_node; + const cond_src: LazySrcLoc = .{ .node_offset_if_cond = inst_data.src_node }; + const extra = sema.code.extraData(zir.Inst.CondBr, inst_data.payload_index); + const then_body = sema.code.extra[extra.end..][0..extra.data.then_body_len]; + const else_body = sema.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; + const cond = try sema.resolveInstConst(block, cond_src, extra.data.condition); + const inline_body = if (cond.val.toBool()) then_body else else_body; + const break_inst = try sema.analyzeBody(block, inline_body); + const break_data = datas[break_inst].@"break"; + if (inst == break_data.block_inst) { + break :blk try sema.resolveInst(break_data.operand); + } else { + return break_inst; + } + }, }; if (map[inst].ty.isNoReturn()) return always_noreturn; @@ -745,7 +785,7 @@ fn zirInt(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*In return sema.mod.constIntUnsigned(sema.arena, .unneeded, Type.initTag(.comptime_int), int); } -fn zirCompileError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Ref { +fn zirCompileError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index { const tracy = trace(@src()); defer tracy.end(); @@ -783,7 +823,7 @@ fn zirCompileLog(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerEr } } -fn zirRepeat(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Ref { +fn zirRepeat(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index { const tracy = trace(@src()); defer tracy.end(); @@ -854,12 +894,7 @@ fn zirLoop(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) InnerE return sema.analyzeBlockBody(parent_block, &child_block, merges); } -fn zirBlock( - sema: *Sema, - parent_block: *Scope.Block, - inst: zir.Inst.Index, - is_comptime: bool, -) InnerError!*Inst { +fn zirBlock(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); @@ -896,7 +931,7 @@ fn zirBlock( }, }), .inlining = parent_block.inlining, - .is_comptime = is_comptime or parent_block.is_comptime, + .is_comptime = parent_block.is_comptime, }; const merges = &child_block.label.?.merges; @@ -1000,7 +1035,7 @@ fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerEr _ = try block.addNoOp(src, Type.initTag(.void), .breakpoint); } -fn zirBreak(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Ref { +fn zirBreak(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index { const tracy = trace(@src()); defer tracy.end(); @@ -1009,22 +1044,13 @@ fn zirBreak(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!z return sema.analyzeBreak(block, sema.src, inst_data.block_inst, operand); } -fn zirBreakVoidNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Ref { - const tracy = trace(@src()); - defer tracy.end(); - - const inst_data = sema.code.instructions.items(.data)[inst].break_void_node; - const void_inst = try sema.mod.constVoid(sema.arena, .unneeded); - return sema.analyzeBreak(block, inst_data.src(), inst_data.block_inst, void_inst); -} - fn analyzeBreak( sema: *Sema, start_block: *Scope.Block, src: LazySrcLoc, zir_block: zir.Inst.Index, operand: *Inst, -) InnerError!zir.Inst.Ref { +) InnerError!zir.Inst.Index { var block = start_block; while (true) { if (block.label) |*label| { @@ -2844,7 +2870,8 @@ fn zirBoolBr( const tracy = trace(@src()); defer tracy.end(); - const inst_data = sema.code.instructions.items(.data)[inst].bool_br; + const datas = sema.code.instructions.items(.data); + const inst_data = datas[inst].bool_br; const src: LazySrcLoc = .unneeded; const lhs = try sema.resolveInst(inst_data.lhs); const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index); @@ -2856,9 +2883,9 @@ fn zirBoolBr( } // comptime-known left-hand side. No need for a block here; the result // is simply the rhs expression. Here we rely on there only being 1 - // break instruction (`break_flat`). - const zir_inst_ref = try sema.analyzeBody(parent_block, body); - return sema.resolveInst(zir_inst_ref); + // break instruction (`break_inline`). + const break_inst = try sema.analyzeBody(parent_block, body); + return sema.resolveInst(datas[break_inst].@"break".operand); } const block_inst = try sema.arena.create(Inst.Block); @@ -2889,8 +2916,8 @@ fn zirBoolBr( }); _ = try lhs_block.addBr(src, block_inst, lhs_result); - const rhs_result_zir_ref = try sema.analyzeBody(rhs_block, body); - const rhs_result = try sema.resolveInst(rhs_result_zir_ref); + const rhs_break_inst = try sema.analyzeBody(rhs_block, body); + const rhs_result = try sema.resolveInst(datas[rhs_break_inst].@"break".operand); _ = try rhs_block.addBr(src, block_inst, rhs_result); const tzir_then_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, then_block.instructions.items) }; @@ -2959,7 +2986,7 @@ fn zirCondbr( sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index, -) InnerError!zir.Inst.Ref { +) InnerError!zir.Inst.Index { const tracy = trace(@src()); defer tracy.end(); @@ -3008,7 +3035,7 @@ fn zirCondbr( return always_noreturn; } -fn zirUnreachable(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Ref { +fn zirUnreachable(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index { const tracy = trace(@src()); defer tracy.end(); @@ -3030,7 +3057,7 @@ fn zirRetTok( block: *Scope.Block, inst: zir.Inst.Index, need_coercion: bool, -) InnerError!zir.Inst.Ref { +) InnerError!zir.Inst.Index { const tracy = trace(@src()); defer tracy.end(); @@ -3041,7 +3068,7 @@ fn zirRetTok( return sema.analyzeRet(block, operand, src, need_coercion); } -fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Ref { +fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index { const tracy = trace(@src()); defer tracy.end(); @@ -3058,7 +3085,7 @@ fn analyzeRet( operand: *Inst, src: LazySrcLoc, need_coercion: bool, -) InnerError!zir.Inst.Ref { +) InnerError!zir.Inst.Index { if (block.inlining) |inlining| { // We are inlining a function call; rewrite the `ret` as a `break`. try inlining.merges.results.append(sema.gpa, operand); @@ -3244,7 +3271,7 @@ fn addSafetyCheck(sema: *Sema, parent_block: *Scope.Block, ok: *Inst, panic_id: try parent_block.instructions.append(sema.gpa, &block_inst.base); } -fn safetyPanic(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, panic_id: PanicId) !zir.Inst.Ref { +fn safetyPanic(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, panic_id: PanicId) !zir.Inst.Index { // TODO Once we have a panic function to call, call it here instead of breakpoint. _ = try block.addNoOp(src, Type.initTag(.void), .breakpoint); _ = try block.addNoOp(src, Type.initTag(.noreturn), .unreach); diff --git a/src/astgen.zig b/src/astgen.zig index ed9c4afd7e..7245e09b66 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -699,7 +699,7 @@ fn breakExpr(mod: *Module, parent_scope: *Scope, node: ast.Node.Index) InnerErro }; if (rhs == 0) { - _ = try parent_gz.addBreakVoid(block_inst, node); + _ = try parent_gz.addBreak(.@"break", block_inst, .void_value); return zir.Inst.Ref.unreachable_value; } block_gz.break_count += 1; @@ -707,7 +707,7 @@ fn breakExpr(mod: *Module, parent_scope: *Scope, node: ast.Node.Index) InnerErro const operand = try expr(mod, parent_scope, block_gz.break_result_loc, rhs); const have_store_to_block = block_gz.rvalue_rl_count != prev_rvalue_rl_count; - const br = try parent_gz.addBreak(block_inst, operand); + const br = try parent_gz.addBreak(.@"break", block_inst, operand); if (block_gz.break_result_loc == .block_ptr) { try block_gz.labeled_breaks.append(mod.gpa, br); @@ -860,7 +860,7 @@ fn labeledBlockExpr( const tracy = trace(@src()); defer tracy.end(); - assert(zir_tag == .block or zir_tag == .block_comptime); + assert(zir_tag == .block); const tree = parent_scope.tree(); const main_tokens = tree.nodes.items(.main_token); @@ -911,8 +911,6 @@ fn labeledBlockExpr( for (block_scope.labeled_breaks.items) |br| { zir_datas[br].@"break".operand = .void_value; } - // 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 block_scope.setBlockBody(block_inst); return gz.zir_code.indexToRef(block_inst); @@ -1027,7 +1025,7 @@ fn blockExprStmts( .bitcast_result_ptr, .bit_or, .block, - .block_comptime, + .block_inline, .loop, .bool_br_and, .bool_br_or, @@ -1122,9 +1120,9 @@ fn blockExprStmts( .compile_log, .ensure_err_payload_void, .@"break", - .break_void_node, - .break_flat, + .break_inline, .condbr, + .condbr_inline, .compile_error, .ret_node, .ret_tok, @@ -1663,7 +1661,7 @@ fn orelseCatchExpr( }; const operand = try expr(mod, &block_scope.base, operand_rl, lhs); const cond = try block_scope.addUnNode(cond_op, operand, node); - const condbr = try block_scope.addCondBr(node); + const condbr = try block_scope.addCondBr(.condbr, node); const block = try parent_gz.addBlock(.block, node); try parent_gz.instructions.append(mod.gpa, block); @@ -1731,6 +1729,7 @@ fn orelseCatchExpr( else_result, block, block, + .@"break", ); } @@ -1750,6 +1749,7 @@ fn finishThenElseBlock( else_result: zir.Inst.Ref, main_block: zir.Inst.Index, then_break_block: zir.Inst.Index, + break_tag: zir.Inst.Tag, ) InnerError!zir.Inst.Ref { // We now have enough information to decide whether the result instruction should // be communicated via result location pointer or break instructions. @@ -1758,11 +1758,11 @@ fn finishThenElseBlock( switch (strat.tag) { .break_void => { if (!wzc.refIsNoReturn(then_result)) { - _ = try then_scope.addBreakVoid(then_break_block, then_src); + _ = try then_scope.addBreak(break_tag, then_break_block, .void_value); } const elide_else = if (else_result != .none) wzc.refIsNoReturn(else_result) else false; if (!elide_else) { - _ = try else_scope.addBreakVoid(main_block, else_src); + _ = try else_scope.addBreak(break_tag, main_block, .void_value); } assert(!strat.elide_store_to_block_ptr_instructions); try setCondBrPayload(condbr, cond, then_scope, else_scope); @@ -1770,14 +1770,14 @@ fn finishThenElseBlock( }, .break_operand => { if (!wzc.refIsNoReturn(then_result)) { - _ = try then_scope.addBreak(then_break_block, then_result); + _ = try then_scope.addBreak(break_tag, then_break_block, then_result); } if (else_result != .none) { if (!wzc.refIsNoReturn(else_result)) { - _ = try else_scope.addBreak(main_block, else_result); + _ = try else_scope.addBreak(break_tag, main_block, else_result); } } else { - _ = try else_scope.addBreakVoid(main_block, else_src); + _ = try else_scope.addBreak(break_tag, main_block, .void_value); } if (strat.elide_store_to_block_ptr_instructions) { try setCondBrPayloadElideBlockStorePtr(condbr, cond, then_scope, else_scope); @@ -1944,7 +1944,7 @@ fn boolBinOp( }; defer rhs_scope.instructions.deinit(mod.gpa); const rhs = try expr(mod, &rhs_scope.base, .{ .ty = .bool_type }, node_datas[node].rhs); - _ = try rhs_scope.addUnNode(.break_flat, rhs, node); + _ = try rhs_scope.addBreak(.break_inline, bool_br, rhs); try rhs_scope.setBoolBrBody(bool_br); const block_ref = gz.zir_code.indexToRef(bool_br); @@ -1979,7 +1979,7 @@ fn ifExpr( } }; - const condbr = try block_scope.addCondBr(node); + const condbr = try block_scope.addCondBr(.condbr, node); const block = try parent_gz.addBlock(.block, node); try parent_gz.instructions.append(mod.gpa, block); @@ -2042,6 +2042,7 @@ fn ifExpr( else_info.result, block, block, + .@"break", ); } @@ -2108,7 +2109,9 @@ fn whileExpr( try checkLabelRedefinition(mod, scope, label_token); } const parent_gz = scope.getGenZir(); - const loop_block = try parent_gz.addBlock(.loop, node); + const is_inline = while_full.inline_token != null; + const loop_tag: zir.Inst.Tag = if (is_inline) .block_inline else .loop; + const loop_block = try parent_gz.addBlock(loop_tag, node); try parent_gz.instructions.append(mod.gpa, loop_block); var loop_scope: Scope.GenZir = .{ @@ -2140,8 +2143,10 @@ fn whileExpr( } }; - const condbr = try continue_scope.addCondBr(node); - const cond_block = try loop_scope.addBlock(.block, node); + const condbr_tag: zir.Inst.Tag = if (is_inline) .condbr_inline else .condbr; + const condbr = try continue_scope.addCondBr(condbr_tag, node); + const block_tag: zir.Inst.Tag = if (is_inline) .block_inline else .block; + const cond_block = try loop_scope.addBlock(block_tag, node); try loop_scope.instructions.append(mod.gpa, cond_block); try continue_scope.setBlockBody(cond_block); @@ -2152,7 +2157,6 @@ fn whileExpr( if (while_full.ast.cont_expr != 0) { _ = try expr(mod, &loop_scope.base, .{ .ty = .void_type }, while_full.ast.cont_expr); } - const is_inline = while_full.inline_token != null; const repeat_tag: zir.Inst.Tag = if (is_inline) .repeat_inline else .repeat; _ = try loop_scope.addNode(repeat_tag, node); @@ -2208,6 +2212,7 @@ fn whileExpr( return mod.failTok(scope, some.token, "unused while loop label", .{}); } } + const break_tag: zir.Inst.Tag = if (is_inline) .break_inline else .@"break"; return finishThenElseBlock( mod, scope, @@ -2224,6 +2229,7 @@ fn whileExpr( else_info.result, loop_block, cond_block, + break_tag, ); } @@ -2424,6 +2430,7 @@ fn forExpr( else_info.result, for_block, cond_block, + .@"break", ); } diff --git a/src/zir.zig b/src/zir.zig index 8a64ce19c5..549ba69c47 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -26,6 +26,8 @@ const LazySrcLoc = Module.LazySrcLoc; /// handled by the codegen backend, and errors reported there. However for now, /// inline assembly is not an exception. pub const Code = struct { + /// There is always implicitly a `block` instruction at index 0. + /// This is so that `break_inline` can break from the root block. instructions: std.MultiArrayList(Inst).Slice, /// In order to store references to strings in fewer bytes, we copy all /// string bytes into here. String bytes can be null. It is up to whomever @@ -35,11 +37,6 @@ pub const Code = struct { string_bytes: []u8, /// The meaning of this data is determined by `Inst.Tag` value. extra: []u32, - /// First ZIR instruction in this `Code`. - /// `extra` at this index contains a `Ref` for every root member. - root_start: u32, - /// Number of ZIR instructions in the implicit root block of the `Code`. - root_len: u32, /// Returns the requested data, as well as the new index which is at the start of the /// trailers for the object. @@ -98,17 +95,14 @@ pub const Code = struct { .arena = &arena.allocator, .scope = scope, .code = code, - .indent = 2, + .indent = 0, .param_count = param_count, }; const decl_name = scope.srcDecl().?.name; const stderr = std.io.getStdErr().writer(); - try stderr.print("ZIR {s} {s} {{\n", .{ kind, decl_name }); - - const root_body = code.extra[code.root_start..][0..code.root_len]; - try writer.writeBody(stderr, root_body); - + try stderr.print("ZIR {s} {s} %0 ", .{ kind, decl_name }); + try writer.writeInstToStream(stderr, 0); try stderr.print("}} // ZIR {s} {s}\n\n", .{ kind, decl_name }); } }; @@ -189,8 +183,11 @@ pub const Inst = struct { /// A labeled block of code, which can return a value. /// Uses the `pl_node` union field. Payload is `Block`. block, - /// Same as `block` but additionally makes the inner instructions execute at comptime. - block_comptime, + /// A list of instructions which are analyzed in the parent context, without + /// generating a runtime block. Must terminate with an "inline" variant of + /// a noreturn instruction. + /// Uses the `pl_node` union field. Payload is `Block`. + block_inline, /// Boolean AND. See also `bit_and`. /// Uses the `pl_node` union field. Payload is `Bin`. bool_and, @@ -212,16 +209,12 @@ pub const Inst = struct { /// Uses the `break` union field. /// Uses the source information from previous instruction. @"break", - /// Same as `break` but has source information in the form of an AST node, and - /// the operand is assumed to be the void value. - /// Uses the `break_void_node` union field. - break_void_node, - /// Return a value from a block. This is a special form that is only valid - /// when there is exactly 1 break from a block (this one). This instruction - /// allows using the return value from `Sema.analyzeBody`. The block is - /// assumed to be the direct parent of this instruction. - /// Uses the `un_node` union field. The AST node is unused. - break_flat, + /// Return a value from a block. This instruction is used as the terminator + /// of a `block_inline`. It allows using the return value from `Sema.analyzeBody`. + /// This instruction may also be used when it is known that there is only one + /// break instruction in a block, and the target block is the parent. + /// Uses the `break` union field. + break_inline, /// Uses the `node` union field. breakpoint, /// Function call with modifier `.auto`. @@ -270,7 +263,11 @@ pub const Inst = struct { /// Uses the `pl_node` union field. AST node is an if, while, for, etc. /// Payload is `CondBr`. condbr, - /// Special case, has no textual representation. + /// Same as `condbr`, except the condition is coerced to a comptime value, and + /// only the taken branch is analyzed. The then block and else block must + /// terminate with an "inline" variant of a noreturn instruction. + condbr_inline, + /// A comptime known value. /// Uses the `const` union field. @"const", /// Declares the beginning of a statement. Used for debug info. @@ -640,7 +637,7 @@ pub const Inst = struct { .bitcast_result_ptr, .bit_or, .block, - .block_comptime, + .block_inline, .loop, .bool_br_and, .bool_br_or, @@ -744,9 +741,9 @@ pub const Inst = struct { => false, .@"break", - .break_void_node, - .break_flat, + .break_inline, .condbr, + .condbr_inline, .compile_error, .ret_node, .ret_tok, @@ -1194,16 +1191,6 @@ pub const Inst = struct { return .{ .node_offset = self.src_node }; } }, - break_void_node: struct { - /// Offset from Decl AST node index. - /// `Tag` determines which kind of AST node this points to. - src_node: i32, - block_inst: Index, - - pub fn src(self: @This()) LazySrcLoc { - return .{ .node_offset = self.src_node }; - } - }, @"break": struct { block_inst: Index, operand: Ref, @@ -1410,7 +1397,6 @@ const Writer = struct { .err_union_payload_unsafe_ptr, .err_union_code, .err_union_code_ptr, - .break_flat, .is_non_null, .is_null, .is_non_null_ptr, @@ -1438,9 +1424,11 @@ const Writer = struct { .int => try self.writeInt(stream, inst), .str => try self.writeStr(stream, inst), .elided => try stream.writeAll(")"), - .break_void_node => try self.writeBreakVoidNode(stream, inst), .int_type => try self.writeIntType(stream, inst), - .@"break" => try self.writeBreak(stream, inst), + + .@"break", + .break_inline, + => try self.writeBreak(stream, inst), .@"asm", .asm_volatile, @@ -1487,11 +1475,13 @@ const Writer = struct { => try self.writePlNodeCall(stream, inst), .block, - .block_comptime, + .block_inline, .loop, => try self.writePlNodeBlock(stream, inst), - .condbr => try self.writePlNodeCondBr(stream, inst), + .condbr, + .condbr_inline, + => try self.writePlNodeCondBr(stream, inst), .as_node => try self.writeAs(stream, inst), @@ -1771,13 +1761,6 @@ const Writer = struct { return self.writeFnTypeCommon(stream, param_types, inst_data.return_type, var_args, cc); } - fn writeBreakVoidNode(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].break_void_node; - try self.writeInstIndex(stream, inst_data.block_inst); - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - fn writeIntType(self: *Writer, stream: anytype, inst: Inst.Index) !void { const int_type = self.code.instructions.items(.data)[inst].int_type; const prefix: u8 = switch (int_type.signedness) { diff --git a/test/stage2/test.zig b/test/stage2/test.zig index a7ef7d98b6..4ced83a951 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -621,6 +621,43 @@ pub fn addCases(ctx: *TestContext) !void { "hello\nhello\nhello\nhello\n", ); + // inline while requires the condition to be comptime known. + case.addError( + \\export fn _start() noreturn { + \\ var i: u32 = 0; + \\ inline while (i < 4) : (i += 1) print(); + \\ assert(i == 4); + \\ + \\ exit(); + \\} + \\ + \\fn print() void { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (1), + \\ [arg1] "{rdi}" (1), + \\ [arg2] "{rsi}" (@ptrToInt("hello\n")), + \\ [arg3] "{rdx}" (6) + \\ : "rcx", "r11", "memory" + \\ ); + \\ return; + \\} + \\ + \\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; + \\} + , &[_][]const u8{":3:21: error: unable to resolve comptime value"}); + // Labeled blocks (no conditional branch) case.addCompareOutput( \\export fn _start() noreturn { |
