Skip to content

Commit 6548322

Browse files
committed
stage2: support recursive inline/comptime functions
zir.Inst no longer has an `analyzed_inst` field. This is previously how we mapped ZIR to their TZIR counterparts, however with the way inline and comptime function calls work, we can potentially have the same ZIR structure being analyzed by multiple different analyses, such as during a recursive inline function call. This would cause the `analyzed_inst` field to become clobbered. So instead, we use a table to map the instructions to their semantically analyzed counterparts. This will help with multi-threaded compilation as well. Scope.Block.Inlining is split into 2 different layers of "sharedness". The first layer is shared by the whole inline/comptime function call stack. It contains the callsite where something is being inlined and the branch count/quota. The second layer is different per function call but shared by all the blocks within the function being inlined. Add support for debug dumping br and brvoid TZIR instructions. Remove the "unreachable code" error. It was happening even for this case: ```zig if (comptime_condition) return; bar(); // error: unreachable code ``` We will need smarter logic for when it is legal to emit this compile error. Remove the ZIR test cases. These are redundant with other higher level Zig source tests we have, and maintaining support for ZIRModule as a first-class top level abstraction is getting in the way of clean compiler design for the main use case. We will have ZIR/TZIR based test cases someday to help with testing optimization passes and ZIR to TZIR analysis, but as is, these test cases are not accomplishing that, and they are getting in the way.
1 parent 50a5301 commit 6548322

File tree

5 files changed

+244
-399
lines changed

5 files changed

+244
-399
lines changed

src/Module.zig

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -752,17 +752,22 @@ pub const Scope = struct {
752752
/// during semantic analysis of the block.
753753
pub const Block = struct {
754754
pub const base_tag: Tag = .block;
755+
755756
base: Scope = Scope{ .tag = base_tag },
756757
parent: ?*Block,
758+
/// Maps ZIR to TZIR. Shared to sub-blocks.
759+
inst_table: *InstTable,
757760
func: ?*Fn,
758761
decl: *Decl,
759762
instructions: ArrayListUnmanaged(*Inst),
760763
/// Points to the arena allocator of DeclAnalysis
761764
arena: *Allocator,
762765
label: ?Label = null,
763-
inlining: ?Inlining,
766+
inlining: ?*Inlining,
764767
is_comptime: bool,
765768

769+
pub const InstTable = std.AutoHashMap(*zir.Inst, *Inst);
770+
766771
/// This `Block` maps a block ZIR instruction to the corresponding
767772
/// TZIR instruction for break instruction analysis.
768773
pub const Label = struct {
@@ -773,14 +778,23 @@ pub const Scope = struct {
773778
/// This `Block` indicates that an inline function call is happening
774779
/// and return instructions should be analyzed as a break instruction
775780
/// to this TZIR block instruction.
781+
/// It is shared among all the blocks in an inline or comptime called
782+
/// function.
776783
pub const Inlining = struct {
777-
caller: ?*Fn,
784+
/// Shared state among the entire inline/comptime call stack.
785+
shared: *Shared,
778786
/// We use this to count from 0 so that arg instructions know
779787
/// which parameter index they are, without having to store
780788
/// a parameter index with each arg instruction.
781789
param_index: usize,
782790
casted_args: []*Inst,
783791
merges: Merges,
792+
793+
pub const Shared = struct {
794+
caller: ?*Fn,
795+
branch_count: u64,
796+
branch_quota: u64,
797+
};
784798
};
785799

786800
pub const Merges = struct {
@@ -1087,8 +1101,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
10871101
errdefer decl_arena.deinit();
10881102
const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
10891103

1104+
var inst_table = Scope.Block.InstTable.init(self.gpa);
1105+
defer inst_table.deinit();
1106+
10901107
var block_scope: Scope.Block = .{
10911108
.parent = null,
1109+
.inst_table = &inst_table,
10921110
.func = null,
10931111
.decl = decl,
10941112
.instructions = .{},
@@ -1276,8 +1294,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
12761294
errdefer decl_arena.deinit();
12771295
const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
12781296

1297+
var decl_inst_table = Scope.Block.InstTable.init(self.gpa);
1298+
defer decl_inst_table.deinit();
1299+
12791300
var block_scope: Scope.Block = .{
12801301
.parent = null,
1302+
.inst_table = &decl_inst_table,
12811303
.func = null,
12821304
.decl = decl,
12831305
.instructions = .{},
@@ -1342,8 +1364,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
13421364
zir.dumpZir(self.gpa, "var_init", decl.name, gen_scope.instructions.items) catch {};
13431365
}
13441366

1367+
var var_inst_table = Scope.Block.InstTable.init(self.gpa);
1368+
defer var_inst_table.deinit();
1369+
13451370
var inner_block: Scope.Block = .{
13461371
.parent = null,
1372+
.inst_table = &var_inst_table,
13471373
.func = null,
13481374
.decl = decl,
13491375
.instructions = .{},
@@ -1352,10 +1378,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
13521378
.is_comptime = true,
13531379
};
13541380
defer inner_block.instructions.deinit(self.gpa);
1355-
try zir_sema.analyzeBody(self, &inner_block.base, .{ .instructions = gen_scope.instructions.items });
1381+
try zir_sema.analyzeBody(self, &inner_block, .{
1382+
.instructions = gen_scope.instructions.items,
1383+
});
13561384

13571385
// The result location guarantees the type coercion.
1358-
const analyzed_init_inst = init_inst.analyzed_inst.?;
1386+
const analyzed_init_inst = var_inst_table.get(init_inst).?;
13591387
// The is_comptime in the Scope.Block guarantees the result is comptime-known.
13601388
const val = analyzed_init_inst.value().?;
13611389

@@ -1463,8 +1491,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
14631491
zir.dumpZir(self.gpa, "comptime_block", decl.name, gen_scope.instructions.items) catch {};
14641492
}
14651493

1494+
var inst_table = Scope.Block.InstTable.init(self.gpa);
1495+
defer inst_table.deinit();
1496+
14661497
var block_scope: Scope.Block = .{
14671498
.parent = null,
1499+
.inst_table = &inst_table,
14681500
.func = null,
14691501
.decl = decl,
14701502
.instructions = .{},
@@ -1474,7 +1506,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
14741506
};
14751507
defer block_scope.instructions.deinit(self.gpa);
14761508

1477-
_ = try zir_sema.analyzeBody(self, &block_scope.base, .{
1509+
_ = try zir_sema.analyzeBody(self, &block_scope, .{
14781510
.instructions = gen_scope.instructions.items,
14791511
});
14801512

@@ -1841,8 +1873,11 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {
18411873
// Use the Decl's arena for function memory.
18421874
var arena = decl.typed_value.most_recent.arena.?.promote(self.gpa);
18431875
defer decl.typed_value.most_recent.arena.?.* = arena.state;
1876+
var inst_table = Scope.Block.InstTable.init(self.gpa);
1877+
defer inst_table.deinit();
18441878
var inner_block: Scope.Block = .{
18451879
.parent = null,
1880+
.inst_table = &inst_table,
18461881
.func = func,
18471882
.decl = decl,
18481883
.instructions = .{},
@@ -1855,7 +1890,7 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {
18551890
func.state = .in_progress;
18561891
log.debug("set {s} to in_progress\n", .{decl.name});
18571892

1858-
try zir_sema.analyzeBody(self, &inner_block.base, func.zir);
1893+
try zir_sema.analyzeBody(self, &inner_block, func.zir);
18591894

18601895
const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items);
18611896
func.state = .success;
@@ -3055,8 +3090,8 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Com
30553090
},
30563091
.block => {
30573092
const block = scope.cast(Scope.Block).?;
3058-
if (block.inlining) |*inlining| {
3059-
if (inlining.caller) |func| {
3093+
if (block.inlining) |inlining| {
3094+
if (inlining.shared.caller) |func| {
30603095
func.state = .sema_failure;
30613096
} else {
30623097
block.decl.analysis = .sema_failure;
@@ -3424,6 +3459,7 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic
34243459

34253460
var fail_block: Scope.Block = .{
34263461
.parent = parent_block,
3462+
.inst_table = parent_block.inst_table,
34273463
.func = parent_block.func,
34283464
.decl = parent_block.decl,
34293465
.instructions = .{},
@@ -3492,3 +3528,14 @@ pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex)
34923528
}
34933529
return ident_name;
34943530
}
3531+
3532+
pub fn emitBackwardBranch(mod: *Module, block: *Scope.Block, src: usize) !void {
3533+
const shared = block.inlining.?.shared;
3534+
shared.branch_count += 1;
3535+
if (shared.branch_count > shared.branch_quota) {
3536+
// TODO show the "called from here" stack
3537+
return mod.fail(&block.base, src, "evaluation exceeded {d} backwards branches", .{
3538+
shared.branch_quota,
3539+
});
3540+
}
3541+
}

src/zir.zig

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ pub const Decl = struct {
2525

2626
/// These are instructions that correspond to the ZIR text format. See `ir.Inst` for
2727
/// in-memory, analyzed instructions with types and values.
28+
/// We use a table to map these instruction to their respective semantically analyzed
29+
/// instructions because it is possible to have multiple analyses on the same ZIR
30+
/// happening at the same time.
2831
pub const Inst = struct {
2932
tag: Tag,
3033
/// Byte offset into the source.
3134
src: usize,
32-
/// Pre-allocated field for mapping ZIR text instructions to post-analysis instructions.
33-
analyzed_inst: ?*ir.Inst = null,
3435

3536
/// These names are used directly as the instruction names in the text format.
3637
pub const Tag = enum {
@@ -1947,11 +1948,20 @@ const DumpTzir = struct {
19471948

19481949
.arg => {},
19491950

1951+
.br => {
1952+
const br = inst.castTag(.br).?;
1953+
try dtz.findConst(&br.block.base);
1954+
try dtz.findConst(br.operand);
1955+
},
1956+
1957+
.brvoid => {
1958+
const brvoid = inst.castTag(.brvoid).?;
1959+
try dtz.findConst(&brvoid.block.base);
1960+
},
1961+
19501962
// TODO fill out this debug printing
19511963
.assembly,
19521964
.block,
1953-
.br,
1954-
.brvoid,
19551965
.call,
19561966
.condbr,
19571967
.constant,
@@ -2078,11 +2088,66 @@ const DumpTzir = struct {
20782088
try writer.print("{s})\n", .{arg.name});
20792089
},
20802090

2091+
.br => {
2092+
const br = inst.castTag(.br).?;
2093+
2094+
var lhs_kinky: ?usize = null;
2095+
var rhs_kinky: ?usize = null;
2096+
2097+
if (dtz.partial_inst_table.get(&br.block.base)) |operand_index| {
2098+
try writer.print("%{d}, ", .{operand_index});
2099+
} else if (dtz.const_table.get(&br.block.base)) |operand_index| {
2100+
try writer.print("@{d}, ", .{operand_index});
2101+
} else if (dtz.inst_table.get(&br.block.base)) |operand_index| {
2102+
lhs_kinky = operand_index;
2103+
try writer.print("%{d}, ", .{operand_index});
2104+
} else {
2105+
try writer.writeAll("!BADREF!, ");
2106+
}
2107+
2108+
if (dtz.partial_inst_table.get(br.operand)) |operand_index| {
2109+
try writer.print("%{d}", .{operand_index});
2110+
} else if (dtz.const_table.get(br.operand)) |operand_index| {
2111+
try writer.print("@{d}", .{operand_index});
2112+
} else if (dtz.inst_table.get(br.operand)) |operand_index| {
2113+
rhs_kinky = operand_index;
2114+
try writer.print("%{d}", .{operand_index});
2115+
} else {
2116+
try writer.writeAll("!BADREF!");
2117+
}
2118+
2119+
if (lhs_kinky != null or rhs_kinky != null) {
2120+
try writer.writeAll(") // Instruction does not dominate all uses!");
2121+
if (lhs_kinky) |lhs| {
2122+
try writer.print(" %{d}", .{lhs});
2123+
}
2124+
if (rhs_kinky) |rhs| {
2125+
try writer.print(" %{d}", .{rhs});
2126+
}
2127+
try writer.writeAll("\n");
2128+
} else {
2129+
try writer.writeAll(")\n");
2130+
}
2131+
},
2132+
2133+
.brvoid => {
2134+
const brvoid = inst.castTag(.brvoid).?;
2135+
if (dtz.partial_inst_table.get(&brvoid.block.base)) |operand_index| {
2136+
try writer.print("%{d})\n", .{operand_index});
2137+
} else if (dtz.const_table.get(&brvoid.block.base)) |operand_index| {
2138+
try writer.print("@{d})\n", .{operand_index});
2139+
} else if (dtz.inst_table.get(&brvoid.block.base)) |operand_index| {
2140+
try writer.print("%{d}) // Instruction does not dominate all uses!\n", .{
2141+
operand_index,
2142+
});
2143+
} else {
2144+
try writer.writeAll("!BADREF!)\n");
2145+
}
2146+
},
2147+
20812148
// TODO fill out this debug printing
20822149
.assembly,
20832150
.block,
2084-
.br,
2085-
.brvoid,
20862151
.call,
20872152
.condbr,
20882153
.constant,

0 commit comments

Comments
 (0)