diff --git a/src/Module.zig b/src/Module.zig index 8acc485079..24ea48043b 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -752,17 +752,22 @@ pub const Scope = struct { /// during semantic analysis of the block. pub const Block = struct { pub const base_tag: Tag = .block; + base: Scope = Scope{ .tag = base_tag }, parent: ?*Block, + /// Maps ZIR to TZIR. Shared to sub-blocks. + inst_table: *InstTable, func: ?*Fn, decl: *Decl, instructions: ArrayListUnmanaged(*Inst), /// Points to the arena allocator of DeclAnalysis arena: *Allocator, label: ?Label = null, - inlining: ?Inlining, + inlining: ?*Inlining, is_comptime: bool, + pub const InstTable = std.AutoHashMap(*zir.Inst, *Inst); + /// This `Block` maps a block ZIR instruction to the corresponding /// TZIR instruction for break instruction analysis. pub const Label = struct { @@ -773,14 +778,23 @@ pub const Scope = struct { /// This `Block` indicates that an inline function call is happening /// and return instructions should be analyzed as a break instruction /// to this TZIR block instruction. + /// It is shared among all the blocks in an inline or comptime called + /// function. pub const Inlining = struct { - caller: ?*Fn, + /// Shared state among the entire inline/comptime call stack. + shared: *Shared, /// We use this to count from 0 so that arg instructions know /// which parameter index they are, without having to store /// a parameter index with each arg instruction. param_index: usize, casted_args: []*Inst, merges: Merges, + + pub const Shared = struct { + caller: ?*Fn, + branch_count: u64, + branch_quota: u64, + }; }; pub const Merges = struct { @@ -1087,8 +1101,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { errdefer decl_arena.deinit(); const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); + var inst_table = Scope.Block.InstTable.init(self.gpa); + defer inst_table.deinit(); + var block_scope: Scope.Block = .{ .parent = null, + .inst_table = &inst_table, .func = null, .decl = decl, .instructions = .{}, @@ -1276,8 +1294,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { errdefer decl_arena.deinit(); const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); + var decl_inst_table = Scope.Block.InstTable.init(self.gpa); + defer decl_inst_table.deinit(); + var block_scope: Scope.Block = .{ .parent = null, + .inst_table = &decl_inst_table, .func = null, .decl = decl, .instructions = .{}, @@ -1342,8 +1364,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { zir.dumpZir(self.gpa, "var_init", decl.name, gen_scope.instructions.items) catch {}; } + var var_inst_table = Scope.Block.InstTable.init(self.gpa); + defer var_inst_table.deinit(); + var inner_block: Scope.Block = .{ .parent = null, + .inst_table = &var_inst_table, .func = null, .decl = decl, .instructions = .{}, @@ -1352,10 +1378,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .is_comptime = true, }; defer inner_block.instructions.deinit(self.gpa); - try zir_sema.analyzeBody(self, &inner_block.base, .{ .instructions = gen_scope.instructions.items }); + try zir_sema.analyzeBody(self, &inner_block, .{ + .instructions = gen_scope.instructions.items, + }); // The result location guarantees the type coercion. - const analyzed_init_inst = init_inst.analyzed_inst.?; + const analyzed_init_inst = var_inst_table.get(init_inst).?; // The is_comptime in the Scope.Block guarantees the result is comptime-known. const val = analyzed_init_inst.value().?; @@ -1463,8 +1491,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { zir.dumpZir(self.gpa, "comptime_block", decl.name, gen_scope.instructions.items) catch {}; } + var inst_table = Scope.Block.InstTable.init(self.gpa); + defer inst_table.deinit(); + var block_scope: Scope.Block = .{ .parent = null, + .inst_table = &inst_table, .func = null, .decl = decl, .instructions = .{}, @@ -1474,7 +1506,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { }; defer block_scope.instructions.deinit(self.gpa); - _ = try zir_sema.analyzeBody(self, &block_scope.base, .{ + _ = try zir_sema.analyzeBody(self, &block_scope, .{ .instructions = gen_scope.instructions.items, }); @@ -1841,8 +1873,11 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { // Use the Decl's arena for function memory. var arena = decl.typed_value.most_recent.arena.?.promote(self.gpa); defer decl.typed_value.most_recent.arena.?.* = arena.state; + var inst_table = Scope.Block.InstTable.init(self.gpa); + defer inst_table.deinit(); var inner_block: Scope.Block = .{ .parent = null, + .inst_table = &inst_table, .func = func, .decl = decl, .instructions = .{}, @@ -1855,7 +1890,7 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { func.state = .in_progress; log.debug("set {s} to in_progress\n", .{decl.name}); - try zir_sema.analyzeBody(self, &inner_block.base, func.zir); + try zir_sema.analyzeBody(self, &inner_block, func.zir); const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items); func.state = .success; @@ -3055,8 +3090,8 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Com }, .block => { const block = scope.cast(Scope.Block).?; - if (block.inlining) |*inlining| { - if (inlining.caller) |func| { + if (block.inlining) |inlining| { + if (inlining.shared.caller) |func| { func.state = .sema_failure; } else { block.decl.analysis = .sema_failure; @@ -3424,6 +3459,7 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic var fail_block: Scope.Block = .{ .parent = parent_block, + .inst_table = parent_block.inst_table, .func = parent_block.func, .decl = parent_block.decl, .instructions = .{}, @@ -3492,3 +3528,14 @@ pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) } return ident_name; } + +pub fn emitBackwardBranch(mod: *Module, block: *Scope.Block, src: usize) !void { + const shared = block.inlining.?.shared; + shared.branch_count += 1; + if (shared.branch_count > shared.branch_quota) { + // TODO show the "called from here" stack + return mod.fail(&block.base, src, "evaluation exceeded {d} backwards branches", .{ + shared.branch_quota, + }); + } +} diff --git a/src/zir.zig b/src/zir.zig index 56ddee919c..3fd2ac7c80 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -25,12 +25,13 @@ pub const Decl = struct { /// These are instructions that correspond to the ZIR text format. See `ir.Inst` for /// in-memory, analyzed instructions with types and values. +/// We use a table to map these instruction to their respective semantically analyzed +/// instructions because it is possible to have multiple analyses on the same ZIR +/// happening at the same time. pub const Inst = struct { tag: Tag, /// Byte offset into the source. src: usize, - /// Pre-allocated field for mapping ZIR text instructions to post-analysis instructions. - analyzed_inst: ?*ir.Inst = null, /// These names are used directly as the instruction names in the text format. pub const Tag = enum { @@ -1947,11 +1948,20 @@ const DumpTzir = struct { .arg => {}, + .br => { + const br = inst.castTag(.br).?; + try dtz.findConst(&br.block.base); + try dtz.findConst(br.operand); + }, + + .brvoid => { + const brvoid = inst.castTag(.brvoid).?; + try dtz.findConst(&brvoid.block.base); + }, + // TODO fill out this debug printing .assembly, .block, - .br, - .brvoid, .call, .condbr, .constant, @@ -2078,11 +2088,66 @@ const DumpTzir = struct { try writer.print("{s})\n", .{arg.name}); }, + .br => { + const br = inst.castTag(.br).?; + + var lhs_kinky: ?usize = null; + var rhs_kinky: ?usize = null; + + if (dtz.partial_inst_table.get(&br.block.base)) |operand_index| { + try writer.print("%{d}, ", .{operand_index}); + } else if (dtz.const_table.get(&br.block.base)) |operand_index| { + try writer.print("@{d}, ", .{operand_index}); + } else if (dtz.inst_table.get(&br.block.base)) |operand_index| { + lhs_kinky = operand_index; + try writer.print("%{d}, ", .{operand_index}); + } else { + try writer.writeAll("!BADREF!, "); + } + + if (dtz.partial_inst_table.get(br.operand)) |operand_index| { + try writer.print("%{d}", .{operand_index}); + } else if (dtz.const_table.get(br.operand)) |operand_index| { + try writer.print("@{d}", .{operand_index}); + } else if (dtz.inst_table.get(br.operand)) |operand_index| { + rhs_kinky = operand_index; + try writer.print("%{d}", .{operand_index}); + } else { + try writer.writeAll("!BADREF!"); + } + + if (lhs_kinky != null or rhs_kinky != null) { + try writer.writeAll(") // Instruction does not dominate all uses!"); + if (lhs_kinky) |lhs| { + try writer.print(" %{d}", .{lhs}); + } + if (rhs_kinky) |rhs| { + try writer.print(" %{d}", .{rhs}); + } + try writer.writeAll("\n"); + } else { + try writer.writeAll(")\n"); + } + }, + + .brvoid => { + const brvoid = inst.castTag(.brvoid).?; + if (dtz.partial_inst_table.get(&brvoid.block.base)) |operand_index| { + try writer.print("%{d})\n", .{operand_index}); + } else if (dtz.const_table.get(&brvoid.block.base)) |operand_index| { + try writer.print("@{d})\n", .{operand_index}); + } else if (dtz.inst_table.get(&brvoid.block.base)) |operand_index| { + try writer.print("%{d}) // Instruction does not dominate all uses!\n", .{ + operand_index, + }); + } else { + try writer.writeAll("!BADREF!)\n"); + } + }, + // TODO fill out this debug printing .assembly, .block, - .br, - .brvoid, .call, .condbr, .constant, diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 62d4de10e0..a5627933e1 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -159,16 +159,11 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! } } -pub fn analyzeBody(mod: *Module, scope: *Scope, body: zir.Module.Body) !void { - for (body.instructions) |src_inst, i| { - const analyzed_inst = try analyzeInst(mod, scope, src_inst); - src_inst.analyzed_inst = analyzed_inst; +pub fn analyzeBody(mod: *Module, block: *Scope.Block, body: zir.Module.Body) !void { + for (body.instructions) |src_inst| { + const analyzed_inst = try analyzeInst(mod, &block.base, src_inst); + try block.inst_table.putNoClobber(src_inst, analyzed_inst); if (analyzed_inst.ty.zigTypeTag() == .NoReturn) { - for (body.instructions[i..]) |unreachable_inst| { - if (unreachable_inst.castTag(.dbg_stmt)) |dbg_stmt| { - return mod.fail(scope, dbg_stmt.base.src, "unreachable code", .{}); - } - } break; } } @@ -180,8 +175,8 @@ pub fn analyzeBodyValueAsType( zir_result_inst: *zir.Inst, body: zir.Module.Body, ) !Type { - try analyzeBody(mod, &block_scope.base, body); - const result_inst = zir_result_inst.analyzed_inst.?; + try analyzeBody(mod, block_scope, body); + const result_inst = block_scope.inst_table.get(zir_result_inst).?; const val = try mod.resolveConstValue(&block_scope.base, result_inst); return val.toType(block_scope.base.arena()); } @@ -264,30 +259,9 @@ fn resolveCompleteZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) Inne return decl; } -/// TODO Look into removing this function. The body is only needed for .zir files, not .zig files. -pub fn resolveInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { - if (old_inst.analyzed_inst) |inst| return inst; - - // If this assert trips, the instruction that was referenced did not get properly - // analyzed before it was referenced. - const zir_module = scope.namespace().cast(Scope.ZIRModule).?; - const entry = if (old_inst.cast(zir.Inst.DeclVal)) |declval| blk: { - const decl_name = declval.positionals.name; - const entry = zir_module.contents.module.findDecl(decl_name) orelse - return mod.fail(scope, old_inst.src, "decl '{s}' not found", .{decl_name}); - break :blk entry; - } else blk: { - // If this assert trips, the instruction that was referenced did not get - // properly analyzed by a previous instruction analysis before it was - // referenced by the current one. - break :blk zir_module.contents.module.findInstDecl(old_inst).?; - }; - const decl = try resolveCompleteZirDecl(mod, scope, entry.decl); - const decl_ref = try mod.analyzeDeclRef(scope, old_inst.src, decl); - // Note: it would be tempting here to store the result into old_inst.analyzed_inst field, - // but this would prevent the analyzeDeclRef from happening, which is needed to properly - // detect Decl dependencies and dependency failures on updates. - return mod.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); +pub fn resolveInst(mod: *Module, scope: *Scope, zir_inst: *zir.Inst) InnerError!*Inst { + const block = scope.cast(Scope.Block).?; + return block.inst_table.get(zir_inst).?; // Instruction does not dominate all uses! } fn resolveConstString(mod: *Module, scope: *Scope, old_inst: *zir.Inst) ![]u8 { @@ -576,7 +550,7 @@ fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) In fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*Inst { const b = try mod.requireFunctionBlock(scope, inst.base.src); - if (b.inlining) |*inlining| { + if (b.inlining) |inlining| { const param_index = inlining.param_index; inlining.param_index += 1; return inlining.casted_args[param_index]; @@ -613,6 +587,7 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError var child_block: Scope.Block = .{ .parent = parent_block, + .inst_table = parent_block.inst_table, .func = parent_block.func, .decl = parent_block.decl, .instructions = .{}, @@ -622,7 +597,7 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError }; defer child_block.instructions.deinit(mod.gpa); - try analyzeBody(mod, &child_block.base, inst.positionals.body); + try analyzeBody(mod, &child_block, inst.positionals.body); // Loop repetition is implied so the last instruction may or may not be a noreturn instruction. @@ -636,6 +611,7 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c var child_block: Scope.Block = .{ .parent = parent_block, + .inst_table = parent_block.inst_table, .func = parent_block.func, .decl = parent_block.decl, .instructions = .{}, @@ -646,7 +622,7 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c }; defer child_block.instructions.deinit(mod.gpa); - try analyzeBody(mod, &child_block.base, inst.positionals.body); + try analyzeBody(mod, &child_block, inst.positionals.body); try parent_block.instructions.appendSlice(mod.gpa, child_block.instructions.items); @@ -675,6 +651,7 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_compt var child_block: Scope.Block = .{ .parent = parent_block, + .inst_table = parent_block.inst_table, .func = parent_block.func, .decl = parent_block.decl, .instructions = .{}, @@ -695,7 +672,7 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_compt defer child_block.instructions.deinit(mod.gpa); defer merges.results.deinit(mod.gpa); - try analyzeBody(mod, &child_block.base, inst.positionals.body); + try analyzeBody(mod, &child_block, inst.positionals.body); return analyzeBlockBody(mod, scope, &child_block, merges); } @@ -886,8 +863,30 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError }, .body = undefined, }; + // If this is the top of the inline/comptime call stack, we use this data. + // Otherwise we pass on the shared data from the parent scope. + var shared_inlining = Scope.Block.Inlining.Shared{ + .branch_count = 0, + .branch_quota = 1000, + .caller = b.func, + }; + // This one is shared among sub-blocks within the same callee, but not + // shared among the entire inline/comptime call stack. + var inlining = Scope.Block.Inlining{ + .shared = if (b.inlining) |inlining| inlining.shared else &shared_inlining, + .param_index = 0, + .casted_args = casted_args, + .merges = .{ + .results = .{}, + .block_inst = block_inst, + }, + }; + var inst_table = Scope.Block.InstTable.init(mod.gpa); + defer inst_table.deinit(); + var child_block: Scope.Block = .{ .parent = null, + .inst_table = &inst_table, .func = module_fn, // Note that we pass the caller's Decl, not the callee. This causes // compile errors to be attached (correctly) to the caller's Decl. @@ -895,16 +894,7 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError .instructions = .{}, .arena = scope.arena(), .label = null, - // TODO @as here is working around a stage1 miscompilation bug :( - .inlining = @as(?Scope.Block.Inlining, Scope.Block.Inlining{ - .caller = b.func, - .param_index = 0, - .casted_args = casted_args, - .merges = .{ - .results = .{}, - .block_inst = block_inst, - }, - }), + .inlining = &inlining, .is_comptime = is_comptime_call, }; const merges = &child_block.inlining.?.merges; @@ -912,11 +902,19 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError defer child_block.instructions.deinit(mod.gpa); defer merges.results.deinit(mod.gpa); + try mod.emitBackwardBranch(&child_block, inst.base.src); + // This will have return instructions analyzed as break instructions to // the block_inst above. - try analyzeBody(mod, &child_block.base, module_fn.zir); + try analyzeBody(mod, &child_block, module_fn.zir); - return analyzeBlockBody(mod, scope, &child_block, merges); + const result = try analyzeBlockBody(mod, scope, &child_block, merges); + if (result.castTag(.constant)) |constant| { + log.debug("inline call resulted in {}", .{constant.val}); + } else { + log.debug("inline call resulted in {}", .{result}); + } + return result; } return mod.addCall(b, inst.base.src, ret_type, func, casted_args); @@ -1393,17 +1391,17 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In const item = try mod.resolveConstValue(scope, casted); if (target_val.eql(item)) { - try analyzeBody(mod, scope, case.body); + try analyzeBody(mod, scope.cast(Scope.Block).?, case.body); return mod.constNoReturn(scope, inst.base.src); } } - try analyzeBody(mod, scope, inst.positionals.else_body); + 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, inst.positionals.else_body); + try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body); return mod.constNoReturn(scope, inst.base.src); } @@ -1412,6 +1410,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In var case_block: Scope.Block = .{ .parent = parent_block, + .inst_table = parent_block.inst_table, .func = parent_block.func, .decl = parent_block.decl, .instructions = .{}, @@ -1429,7 +1428,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In const casted = try mod.coerce(scope, target.ty, resolved); const item = try mod.resolveConstValue(scope, casted); - try analyzeBody(mod, &case_block.base, case.body); + try analyzeBody(mod, &case_block, case.body); cases[i] = .{ .item = item, @@ -1438,7 +1437,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In } case_block.instructions.items.len = 0; - try analyzeBody(mod, &case_block.base, inst.positionals.else_body); + 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), @@ -1756,24 +1755,26 @@ fn analyzeInstComptimeOp(mod: *Module, scope: *Scope, res_type: Type, inst: *zir } const is_int = res_type.isInt() or res_type.zigTypeTag() == .ComptimeInt; - const value = try switch (inst.base.tag) { + const value = switch (inst.base.tag) { .add => blk: { const val = if (is_int) - Module.intAdd(scope.arena(), lhs_val, rhs_val) + try Module.intAdd(scope.arena(), lhs_val, rhs_val) else - mod.floatAdd(scope, res_type, inst.base.src, lhs_val, rhs_val); + try mod.floatAdd(scope, res_type, inst.base.src, lhs_val, rhs_val); break :blk val; }, .sub => blk: { const val = if (is_int) - Module.intSub(scope.arena(), lhs_val, rhs_val) + try Module.intSub(scope.arena(), lhs_val, rhs_val) else - mod.floatSub(scope, res_type, inst.base.src, lhs_val, rhs_val); + try mod.floatSub(scope, res_type, inst.base.src, lhs_val, rhs_val); break :blk val; }, else => return mod.fail(scope, inst.base.src, "TODO Implement arithmetic operand '{s}'", .{@tagName(inst.base.tag)}), }; + log.debug("{s}({}, {}) result: {}", .{ @tagName(inst.base.tag), lhs_val, rhs_val, value }); + return mod.constInst(scope, inst.base.src, .{ .ty = res_type, .val = value, @@ -1942,16 +1943,17 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE const uncasted_cond = try resolveInst(mod, scope, inst.positionals.condition); const cond = try mod.coerce(scope, Type.initTag(.bool), uncasted_cond); + const parent_block = scope.cast(Scope.Block).?; + if (try mod.resolveDefinedValue(scope, cond)) |cond_val| { const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body; - try analyzeBody(mod, scope, body.*); + try analyzeBody(mod, parent_block, body.*); return mod.constNoReturn(scope, inst.base.src); } - const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); - var true_block: Scope.Block = .{ .parent = parent_block, + .inst_table = parent_block.inst_table, .func = parent_block.func, .decl = parent_block.decl, .instructions = .{}, @@ -1960,10 +1962,11 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE .is_comptime = parent_block.is_comptime, }; defer true_block.instructions.deinit(mod.gpa); - try analyzeBody(mod, &true_block.base, inst.positionals.then_body); + try analyzeBody(mod, &true_block, inst.positionals.then_body); var false_block: Scope.Block = .{ .parent = parent_block, + .inst_table = parent_block.inst_table, .func = parent_block.func, .decl = parent_block.decl, .instructions = .{}, @@ -1972,7 +1975,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE .is_comptime = parent_block.is_comptime, }; defer false_block.instructions.deinit(mod.gpa); - try analyzeBody(mod, &false_block.base, inst.positionals.else_body); + try analyzeBody(mod, &false_block, inst.positionals.else_body); const then_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) }; const else_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) }; @@ -1998,7 +2001,7 @@ fn analyzeInstRet(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError! const operand = try resolveInst(mod, scope, inst.positionals.operand); const b = try mod.requireFunctionBlock(scope, inst.base.src); - if (b.inlining) |*inlining| { + 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); @@ -2009,7 +2012,7 @@ fn analyzeInstRet(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError! fn analyzeInstRetVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { const b = try mod.requireFunctionBlock(scope, inst.base.src); - if (b.inlining) |*inlining| { + if (b.inlining) |inlining| { // 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); diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 8d3fac4760..9a74e9ee09 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -27,7 +27,6 @@ const wasi = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - try @import("zir.zig").addCases(ctx); try @import("cbe.zig").addCases(ctx); try @import("spu-ii.zig").addCases(ctx); try @import("arm.zig").addCases(ctx); @@ -1430,4 +1429,51 @@ pub fn addCases(ctx: *TestContext) !void { "", ); } + { + var case = ctx.exe("recursive inline function", linux_x64); + case.addCompareOutput( + \\export fn _start() noreturn { + \\ const y = fibonacci(7); + \\ exit(y - 21); + \\} + \\ + \\inline fn fibonacci(n: usize) usize { + \\ if (n <= 2) return n; + \\ return fibonacci(n - 2) + fibonacci(n - 1); + \\} + \\ + \\fn exit(code: usize) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + case.addError( + \\export fn _start() noreturn { + \\ const y = fibonacci(999); + \\ exit(y - 21); + \\} + \\ + \\inline fn fibonacci(n: usize) usize { + \\ if (n <= 2) return n; + \\ return fibonacci(n - 2) + fibonacci(n - 1); + \\} + \\ + \\fn exit(code: usize) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , &[_][]const u8{":8:10: error: evaluation exceeded 1000 backwards branches"}); + } } diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig deleted file mode 100644 index c29e636cd4..0000000000 --- a/test/stage2/zir.zig +++ /dev/null @@ -1,316 +0,0 @@ -const std = @import("std"); -const TestContext = @import("../../src/test.zig").TestContext; -// self-hosted does not yet support PE executable files / COFF object files -// or mach-o files. So we do the ZIR transform test cases cross compiling for -// x86_64-linux. -const linux_x64 = std.zig.CrossTarget{ - .cpu_arch = .x86_64, - .os_tag = .linux, -}; - -pub fn addCases(ctx: *TestContext) !void { - ctx.transformZIR("referencing decls which appear later in the file", linux_x64, - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\ - \\@9 = str("entry") - \\@11 = export(@9, "entry") - \\ - \\@entry = fn(@fnty, { - \\ %11 = returnvoid() - \\}) - , - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\@9 = declref("9__anon_0") - \\@9__anon_0 = str("entry") - \\@unnamed$4 = str("entry") - \\@unnamed$5 = export(@unnamed$4, "entry") - \\@11 = primitive(void_value) - \\@unnamed$7 = fntype([], @void, cc=C) - \\@entry = fn(@unnamed$7, { - \\ %0 = returnvoid() ; deaths=0b1000000000000000 - \\}, is_inline=0) - \\ - ); - ctx.transformZIR("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, - \\@void = primitive(void) - \\@usize = primitive(usize) - \\@fnty = fntype([], @void, cc=C) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\ - \\@entry = fn(@fnty, { - \\ %a = str("\x32\x08\x01\x0a") - \\ %a_ref = ref(%a) - \\ %eptr0 = elemptr(%a_ref, @0) - \\ %eptr1 = elemptr(%a_ref, @1) - \\ %eptr2 = elemptr(%a_ref, @2) - \\ %eptr3 = elemptr(%a_ref, @3) - \\ %v0 = deref(%eptr0) - \\ %v1 = deref(%eptr1) - \\ %v2 = deref(%eptr2) - \\ %v3 = deref(%eptr3) - \\ %x0 = add(%v0, %v1) - \\ %x1 = add(%v2, %v3) - \\ %result = add(%x0, %x1) - \\ - \\ %expected = int(69) - \\ %ok = cmp_eq(%result, %expected) - \\ %10 = condbr(%ok, { - \\ %11 = returnvoid() - \\ }, { - \\ %12 = breakpoint() - \\ }) - \\}) - \\ - \\@9 = str("entry") - \\@11 = export(@9, "entry") - , - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\@unnamed$6 = fntype([], @void, cc=C) - \\@entry = fn(@unnamed$6, { - \\ %0 = returnvoid() ; deaths=0b1000000000000000 - \\}, is_inline=0) - \\@entry__anon_1 = str("2\x08\x01\n") - \\@9 = declref("9__anon_0") - \\@9__anon_0 = str("entry") - \\@unnamed$11 = str("entry") - \\@unnamed$12 = export(@unnamed$11, "entry") - \\@11 = primitive(void_value) - \\ - ); - - { - var case = ctx.objZIR("reference cycle with compile error in the cycle", linux_x64); - case.addTransform( - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\ - \\@9 = str("entry") - \\@11 = export(@9, "entry") - \\ - \\@entry = fn(@fnty, { - \\ %0 = call(@a, []) - \\ %1 = returnvoid() - \\}) - \\ - \\@a = fn(@fnty, { - \\ %0 = call(@b, []) - \\ %1 = returnvoid() - \\}) - \\ - \\@b = fn(@fnty, { - \\ %0 = call(@a, []) - \\ %1 = returnvoid() - \\}) - , - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\@9 = declref("9__anon_0") - \\@9__anon_0 = str("entry") - \\@unnamed$4 = str("entry") - \\@unnamed$5 = export(@unnamed$4, "entry") - \\@11 = primitive(void_value) - \\@unnamed$7 = fntype([], @void, cc=C) - \\@entry = fn(@unnamed$7, { - \\ %0 = call(@a, [], modifier=auto) ; deaths=0b1000000000000001 - \\ %1 = returnvoid() ; deaths=0b1000000000000000 - \\}, is_inline=0) - \\@unnamed$9 = fntype([], @void, cc=C) - \\@a = fn(@unnamed$9, { - \\ %0 = call(@b, [], modifier=auto) ; deaths=0b1000000000000001 - \\ %1 = returnvoid() ; deaths=0b1000000000000000 - \\}, is_inline=0) - \\@unnamed$11 = fntype([], @void, cc=C) - \\@b = fn(@unnamed$11, { - \\ %0 = call(@a, [], modifier=auto) ; deaths=0b1000000000000001 - \\ %1 = returnvoid() ; deaths=0b1000000000000000 - \\}, is_inline=0) - \\ - ); - // Now we introduce a compile error - case.addError( - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\ - \\@9 = str("entry") - \\@11 = export(@9, "entry") - \\ - \\@entry = fn(@fnty, { - \\ %0 = call(@a, []) - \\ %1 = returnvoid() - \\}) - \\ - \\@a = fn(@fnty, { - \\ %0 = call(@c, []) - \\ %1 = returnvoid() - \\}) - \\ - \\@b = str("message") - \\ - \\@c = fn(@fnty, { - \\ %9 = compileerror(@b) - \\ %0 = call(@a, []) - \\ %1 = returnvoid() - \\}) - , - &[_][]const u8{ - ":20:21: error: message", - }, - ); - // Now we remove the call to `a`. `a` and `b` form a cycle, but no entry points are - // referencing either of them. This tests that the cycle is detected, and the error - // goes away. - case.addTransform( - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\ - \\@9 = str("entry") - \\@11 = export(@9, "entry") - \\ - \\@entry = fn(@fnty, { - \\ %0 = returnvoid() - \\}) - \\ - \\@a = fn(@fnty, { - \\ %0 = call(@c, []) - \\ %1 = returnvoid() - \\}) - \\ - \\@b = str("message") - \\ - \\@c = fn(@fnty, { - \\ %9 = compileerror(@b) - \\ %0 = call(@a, []) - \\ %1 = returnvoid() - \\}) - , - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\@9 = declref("9__anon_3") - \\@9__anon_3 = str("entry") - \\@unnamed$4 = str("entry") - \\@unnamed$5 = export(@unnamed$4, "entry") - \\@11 = primitive(void_value) - \\@unnamed$7 = fntype([], @void, cc=C) - \\@entry = fn(@unnamed$7, { - \\ %0 = returnvoid() ; deaths=0b1000000000000000 - \\}, is_inline=0) - \\ - ); - } - - if (std.Target.current.os.tag != .linux or - std.Target.current.cpu.arch != .x86_64) - { - // TODO implement self-hosted PE (.exe file) linking - // TODO implement more ZIR so we don't depend on x86_64-linux - return; - } - - ctx.compareOutputZIR("hello world ZIR", - \\@noreturn = primitive(noreturn) - \\@void = primitive(void) - \\@usize = primitive(usize) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\ - \\@msg = str("Hello, world!\n") - \\ - \\@start_fnty = fntype([], @noreturn, cc=Naked) - \\@start = fn(@start_fnty, { - \\ %SYS_exit_group = int(231) - \\ %exit_code = as(@usize, @0) - \\ - \\ %syscall = str("syscall") - \\ %sysoutreg = str("={rax}") - \\ %rax = str("{rax}") - \\ %rdi = str("{rdi}") - \\ %rcx = str("rcx") - \\ %rdx = str("{rdx}") - \\ %rsi = str("{rsi}") - \\ %r11 = str("r11") - \\ %memory = str("memory") - \\ - \\ %SYS_write = as(@usize, @1) - \\ %STDOUT_FILENO = as(@usize, @1) - \\ - \\ %msg_addr = ptrtoint(@msg) - \\ - \\ %len_name = str("len") - \\ %msg_len_ptr = fieldptr(@msg, %len_name) - \\ %msg_len = deref(%msg_len_ptr) - \\ %rc_write = asm(%syscall, @usize, - \\ volatile=1, - \\ output=%sysoutreg, - \\ inputs=[%rax, %rdi, %rsi, %rdx], - \\ clobbers=[%rcx, %r11, %memory], - \\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len]) - \\ - \\ %rc_exit = asm(%syscall, @usize, - \\ volatile=1, - \\ output=%sysoutreg, - \\ inputs=[%rax, %rdi], - \\ clobbers=[%rcx, %r11, %memory], - \\ args=[%SYS_exit_group, %exit_code]) - \\ - \\ %99 = unreachable() - \\}); - \\ - \\@9 = str("_start") - \\@11 = export(@9, "start") - , - \\Hello, world! - \\ - ); - - ctx.compareOutputZIR("function call with no args no return value", - \\@noreturn = primitive(noreturn) - \\@void = primitive(void) - \\@usize = primitive(usize) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\ - \\@exit0_fnty = fntype([], @noreturn) - \\@exit0 = fn(@exit0_fnty, { - \\ %SYS_exit_group = int(231) - \\ %exit_code = as(@usize, @0) - \\ - \\ %syscall = str("syscall") - \\ %sysoutreg = str("={rax}") - \\ %rax = str("{rax}") - \\ %rdi = str("{rdi}") - \\ %rcx = str("rcx") - \\ %r11 = str("r11") - \\ %memory = str("memory") - \\ - \\ %rc = asm(%syscall, @usize, - \\ volatile=1, - \\ output=%sysoutreg, - \\ inputs=[%rax, %rdi], - \\ clobbers=[%rcx, %r11, %memory], - \\ args=[%SYS_exit_group, %exit_code]) - \\ - \\ %99 = unreachable() - \\}); - \\ - \\@start_fnty = fntype([], @noreturn, cc=Naked) - \\@start = fn(@start_fnty, { - \\ %0 = call(@exit0, []) - \\}) - \\@9 = str("_start") - \\@11 = export(@9, "start") - , ""); -}