Commit 654832253a
Changed files (5)
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,
+ });
+ }
+}
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,
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);
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"});
+ }
}
test/stage2/zir.zig
@@ -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")
- , "");
-}