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 {