authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-02 22:42:07-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-02 22:42:07-07:00
log654832253a7857e78aab85e28ed09fb16b632dd2
tree7769abc2acc91382900bfb2042db3ce84a3fab4e
parent50a530196ca4e91b387f9937475dd8891edb3f4f

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.

5 files changed, 244 insertions(+), 399 deletions(-)

src/Module.zig+55-8
......@@ -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
3532pub 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+71-6
......@@ -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.
2831pub 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,
src/zir_sema.zig+71-68
......@@ -159,16 +159,11 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
159159 }
160160}
161161
162pub fn analyzeBody(mod: *Module, scope: *Scope, body: zir.Module.Body) !void {
163 for (body.instructions) |src_inst, i| {
164 const analyzed_inst = try analyzeInst(mod, scope, src_inst);
165 src_inst.analyzed_inst = analyzed_inst;
162pub fn analyzeBody(mod: *Module, block: *Scope.Block, body: zir.Module.Body) !void {
163 for (body.instructions) |src_inst| {
164 const analyzed_inst = try analyzeInst(mod, &block.base, src_inst);
165 try block.inst_table.putNoClobber(src_inst, analyzed_inst);
166166 if (analyzed_inst.ty.zigTypeTag() == .NoReturn) {
167 for (body.instructions[i..]) |unreachable_inst| {
168 if (unreachable_inst.castTag(.dbg_stmt)) |dbg_stmt| {
169 return mod.fail(scope, dbg_stmt.base.src, "unreachable code", .{});
170 }
171 }
172167 break;
173168 }
174169 }
......@@ -180,8 +175,8 @@ pub fn analyzeBodyValueAsType(
180175 zir_result_inst: *zir.Inst,
181176 body: zir.Module.Body,
182177) !Type {
183 try analyzeBody(mod, &block_scope.base, body);
184 const result_inst = zir_result_inst.analyzed_inst.?;
178 try analyzeBody(mod, block_scope, body);
179 const result_inst = block_scope.inst_table.get(zir_result_inst).?;
185180 const val = try mod.resolveConstValue(&block_scope.base, result_inst);
186181 return val.toType(block_scope.base.arena());
187182}
......@@ -264,30 +259,9 @@ fn resolveCompleteZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) Inne
264259 return decl;
265260}
266261
267/// TODO Look into removing this function. The body is only needed for .zir files, not .zig files.
268pub fn resolveInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst {
269 if (old_inst.analyzed_inst) |inst| return inst;
270
271 // If this assert trips, the instruction that was referenced did not get properly
272 // analyzed before it was referenced.
273 const zir_module = scope.namespace().cast(Scope.ZIRModule).?;
274 const entry = if (old_inst.cast(zir.Inst.DeclVal)) |declval| blk: {
275 const decl_name = declval.positionals.name;
276 const entry = zir_module.contents.module.findDecl(decl_name) orelse
277 return mod.fail(scope, old_inst.src, "decl '{s}' not found", .{decl_name});
278 break :blk entry;
279 } else blk: {
280 // If this assert trips, the instruction that was referenced did not get
281 // properly analyzed by a previous instruction analysis before it was
282 // referenced by the current one.
283 break :blk zir_module.contents.module.findInstDecl(old_inst).?;
284 };
285 const decl = try resolveCompleteZirDecl(mod, scope, entry.decl);
286 const decl_ref = try mod.analyzeDeclRef(scope, old_inst.src, decl);
287 // Note: it would be tempting here to store the result into old_inst.analyzed_inst field,
288 // but this would prevent the analyzeDeclRef from happening, which is needed to properly
289 // detect Decl dependencies and dependency failures on updates.
290 return mod.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src);
262pub fn resolveInst(mod: *Module, scope: *Scope, zir_inst: *zir.Inst) InnerError!*Inst {
263 const block = scope.cast(Scope.Block).?;
264 return block.inst_table.get(zir_inst).?; // Instruction does not dominate all uses!
291265}
292266
293267fn 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
576550
577551fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*Inst {
578552 const b = try mod.requireFunctionBlock(scope, inst.base.src);
579 if (b.inlining) |*inlining| {
553 if (b.inlining) |inlining| {
580554 const param_index = inlining.param_index;
581555 inlining.param_index += 1;
582556 return inlining.casted_args[param_index];
......@@ -613,6 +587,7 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError
613587
614588 var child_block: Scope.Block = .{
615589 .parent = parent_block,
590 .inst_table = parent_block.inst_table,
616591 .func = parent_block.func,
617592 .decl = parent_block.decl,
618593 .instructions = .{},
......@@ -622,7 +597,7 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError
622597 };
623598 defer child_block.instructions.deinit(mod.gpa);
624599
625 try analyzeBody(mod, &child_block.base, inst.positionals.body);
600 try analyzeBody(mod, &child_block, inst.positionals.body);
626601
627602 // Loop repetition is implied so the last instruction may or may not be a noreturn instruction.
628603
......@@ -636,6 +611,7 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c
636611
637612 var child_block: Scope.Block = .{
638613 .parent = parent_block,
614 .inst_table = parent_block.inst_table,
639615 .func = parent_block.func,
640616 .decl = parent_block.decl,
641617 .instructions = .{},
......@@ -646,7 +622,7 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c
646622 };
647623 defer child_block.instructions.deinit(mod.gpa);
648624
649 try analyzeBody(mod, &child_block.base, inst.positionals.body);
625 try analyzeBody(mod, &child_block, inst.positionals.body);
650626
651627 try parent_block.instructions.appendSlice(mod.gpa, child_block.instructions.items);
652628
......@@ -675,6 +651,7 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_compt
675651
676652 var child_block: Scope.Block = .{
677653 .parent = parent_block,
654 .inst_table = parent_block.inst_table,
678655 .func = parent_block.func,
679656 .decl = parent_block.decl,
680657 .instructions = .{},
......@@ -695,7 +672,7 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_compt
695672 defer child_block.instructions.deinit(mod.gpa);
696673 defer merges.results.deinit(mod.gpa);
697674
698 try analyzeBody(mod, &child_block.base, inst.positionals.body);
675 try analyzeBody(mod, &child_block, inst.positionals.body);
699676
700677 return analyzeBlockBody(mod, scope, &child_block, merges);
701678}
......@@ -886,8 +863,30 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError
886863 },
887864 .body = undefined,
888865 };
866 // If this is the top of the inline/comptime call stack, we use this data.
867 // Otherwise we pass on the shared data from the parent scope.
868 var shared_inlining = Scope.Block.Inlining.Shared{
869 .branch_count = 0,
870 .branch_quota = 1000,
871 .caller = b.func,
872 };
873 // This one is shared among sub-blocks within the same callee, but not
874 // shared among the entire inline/comptime call stack.
875 var inlining = Scope.Block.Inlining{
876 .shared = if (b.inlining) |inlining| inlining.shared else &shared_inlining,
877 .param_index = 0,
878 .casted_args = casted_args,
879 .merges = .{
880 .results = .{},
881 .block_inst = block_inst,
882 },
883 };
884 var inst_table = Scope.Block.InstTable.init(mod.gpa);
885 defer inst_table.deinit();
886
889887 var child_block: Scope.Block = .{
890888 .parent = null,
889 .inst_table = &inst_table,
891890 .func = module_fn,
892891 // Note that we pass the caller's Decl, not the callee. This causes
893892 // 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
895894 .instructions = .{},
896895 .arena = scope.arena(),
897896 .label = null,
898 // TODO @as here is working around a stage1 miscompilation bug :(
899 .inlining = @as(?Scope.Block.Inlining, Scope.Block.Inlining{
900 .caller = b.func,
901 .param_index = 0,
902 .casted_args = casted_args,
903 .merges = .{
904 .results = .{},
905 .block_inst = block_inst,
906 },
907 }),
897 .inlining = &inlining,
908898 .is_comptime = is_comptime_call,
909899 };
910900 const merges = &child_block.inlining.?.merges;
......@@ -912,11 +902,19 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError
912902 defer child_block.instructions.deinit(mod.gpa);
913903 defer merges.results.deinit(mod.gpa);
914904
905 try mod.emitBackwardBranch(&child_block, inst.base.src);
906
915907 // This will have return instructions analyzed as break instructions to
916908 // the block_inst above.
917 try analyzeBody(mod, &child_block.base, module_fn.zir);
909 try analyzeBody(mod, &child_block, module_fn.zir);
918910
919 return analyzeBlockBody(mod, scope, &child_block, merges);
911 const result = try analyzeBlockBody(mod, scope, &child_block, merges);
912 if (result.castTag(.constant)) |constant| {
913 log.debug("inline call resulted in {}", .{constant.val});
914 } else {
915 log.debug("inline call resulted in {}", .{result});
916 }
917 return result;
920918 }
921919
922920 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
13931391 const item = try mod.resolveConstValue(scope, casted);
13941392
13951393 if (target_val.eql(item)) {
1396 try analyzeBody(mod, scope, case.body);
1394 try analyzeBody(mod, scope.cast(Scope.Block).?, case.body);
13971395 return mod.constNoReturn(scope, inst.base.src);
13981396 }
13991397 }
1400 try analyzeBody(mod, scope, inst.positionals.else_body);
1398 try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body);
14011399 return mod.constNoReturn(scope, inst.base.src);
14021400 }
14031401
14041402 if (inst.positionals.cases.len == 0) {
14051403 // no cases just analyze else_branch
1406 try analyzeBody(mod, scope, inst.positionals.else_body);
1404 try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body);
14071405 return mod.constNoReturn(scope, inst.base.src);
14081406 }
14091407
......@@ -1412,6 +1410,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In
14121410
14131411 var case_block: Scope.Block = .{
14141412 .parent = parent_block,
1413 .inst_table = parent_block.inst_table,
14151414 .func = parent_block.func,
14161415 .decl = parent_block.decl,
14171416 .instructions = .{},
......@@ -1429,7 +1428,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In
14291428 const casted = try mod.coerce(scope, target.ty, resolved);
14301429 const item = try mod.resolveConstValue(scope, casted);
14311430
1432 try analyzeBody(mod, &case_block.base, case.body);
1431 try analyzeBody(mod, &case_block, case.body);
14331432
14341433 cases[i] = .{
14351434 .item = item,
......@@ -1438,7 +1437,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In
14381437 }
14391438
14401439 case_block.instructions.items.len = 0;
1441 try analyzeBody(mod, &case_block.base, inst.positionals.else_body);
1440 try analyzeBody(mod, &case_block, inst.positionals.else_body);
14421441
14431442 const else_body: ir.Body = .{
14441443 .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
17561755 }
17571756 const is_int = res_type.isInt() or res_type.zigTypeTag() == .ComptimeInt;
17581757
1759 const value = try switch (inst.base.tag) {
1758 const value = switch (inst.base.tag) {
17601759 .add => blk: {
17611760 const val = if (is_int)
1762 Module.intAdd(scope.arena(), lhs_val, rhs_val)
1761 try Module.intAdd(scope.arena(), lhs_val, rhs_val)
17631762 else
1764 mod.floatAdd(scope, res_type, inst.base.src, lhs_val, rhs_val);
1763 try mod.floatAdd(scope, res_type, inst.base.src, lhs_val, rhs_val);
17651764 break :blk val;
17661765 },
17671766 .sub => blk: {
17681767 const val = if (is_int)
1769 Module.intSub(scope.arena(), lhs_val, rhs_val)
1768 try Module.intSub(scope.arena(), lhs_val, rhs_val)
17701769 else
1771 mod.floatSub(scope, res_type, inst.base.src, lhs_val, rhs_val);
1770 try mod.floatSub(scope, res_type, inst.base.src, lhs_val, rhs_val);
17721771 break :blk val;
17731772 },
17741773 else => return mod.fail(scope, inst.base.src, "TODO Implement arithmetic operand '{s}'", .{@tagName(inst.base.tag)}),
17751774 };
17761775
1776 log.debug("{s}({}, {}) result: {}", .{ @tagName(inst.base.tag), lhs_val, rhs_val, value });
1777
17771778 return mod.constInst(scope, inst.base.src, .{
17781779 .ty = res_type,
17791780 .val = value,
......@@ -1942,16 +1943,17 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE
19421943 const uncasted_cond = try resolveInst(mod, scope, inst.positionals.condition);
19431944 const cond = try mod.coerce(scope, Type.initTag(.bool), uncasted_cond);
19441945
1946 const parent_block = scope.cast(Scope.Block).?;
1947
19451948 if (try mod.resolveDefinedValue(scope, cond)) |cond_val| {
19461949 const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body;
1947 try analyzeBody(mod, scope, body.*);
1950 try analyzeBody(mod, parent_block, body.*);
19481951 return mod.constNoReturn(scope, inst.base.src);
19491952 }
19501953
1951 const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src);
1952
19531954 var true_block: Scope.Block = .{
19541955 .parent = parent_block,
1956 .inst_table = parent_block.inst_table,
19551957 .func = parent_block.func,
19561958 .decl = parent_block.decl,
19571959 .instructions = .{},
......@@ -1960,10 +1962,11 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE
19601962 .is_comptime = parent_block.is_comptime,
19611963 };
19621964 defer true_block.instructions.deinit(mod.gpa);
1963 try analyzeBody(mod, &true_block.base, inst.positionals.then_body);
1965 try analyzeBody(mod, &true_block, inst.positionals.then_body);
19641966
19651967 var false_block: Scope.Block = .{
19661968 .parent = parent_block,
1969 .inst_table = parent_block.inst_table,
19671970 .func = parent_block.func,
19681971 .decl = parent_block.decl,
19691972 .instructions = .{},
......@@ -1972,7 +1975,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE
19721975 .is_comptime = parent_block.is_comptime,
19731976 };
19741977 defer false_block.instructions.deinit(mod.gpa);
1975 try analyzeBody(mod, &false_block.base, inst.positionals.else_body);
1978 try analyzeBody(mod, &false_block, inst.positionals.else_body);
19761979
19771980 const then_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) };
19781981 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!
19982001 const operand = try resolveInst(mod, scope, inst.positionals.operand);
19992002 const b = try mod.requireFunctionBlock(scope, inst.base.src);
20002003
2001 if (b.inlining) |*inlining| {
2004 if (b.inlining) |inlining| {
20022005 // We are inlining a function call; rewrite the `ret` as a `break`.
20032006 try inlining.merges.results.append(mod.gpa, operand);
20042007 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!
20092012
20102013fn analyzeInstRetVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
20112014 const b = try mod.requireFunctionBlock(scope, inst.base.src);
2012 if (b.inlining) |*inlining| {
2015 if (b.inlining) |inlining| {
20132016 // We are inlining a function call; rewrite the `retvoid` as a `breakvoid`.
20142017 const void_inst = try mod.constVoid(scope, inst.base.src);
20152018 try inlining.merges.results.append(mod.gpa, void_inst);
test/stage2/test.zig+47-1
......@@ -27,7 +27,6 @@ const wasi = std.zig.CrossTarget{
2727};
2828
2929pub fn addCases(ctx: *TestContext) !void {
30 try @import("zir.zig").addCases(ctx);
3130 try @import("cbe.zig").addCases(ctx);
3231 try @import("spu-ii.zig").addCases(ctx);
3332 try @import("arm.zig").addCases(ctx);
......@@ -1430,4 +1429,51 @@ pub fn addCases(ctx: *TestContext) !void {
14301429 "",
14311430 );
14321431 }
1432 {
1433 var case = ctx.exe("recursive inline function", linux_x64);
1434 case.addCompareOutput(
1435 \\export fn _start() noreturn {
1436 \\ const y = fibonacci(7);
1437 \\ exit(y - 21);
1438 \\}
1439 \\
1440 \\inline fn fibonacci(n: usize) usize {
1441 \\ if (n <= 2) return n;
1442 \\ return fibonacci(n - 2) + fibonacci(n - 1);
1443 \\}
1444 \\
1445 \\fn exit(code: usize) noreturn {
1446 \\ asm volatile ("syscall"
1447 \\ :
1448 \\ : [number] "{rax}" (231),
1449 \\ [arg1] "{rdi}" (code)
1450 \\ : "rcx", "r11", "memory"
1451 \\ );
1452 \\ unreachable;
1453 \\}
1454 ,
1455 "",
1456 );
1457 case.addError(
1458 \\export fn _start() noreturn {
1459 \\ const y = fibonacci(999);
1460 \\ exit(y - 21);
1461 \\}
1462 \\
1463 \\inline fn fibonacci(n: usize) usize {
1464 \\ if (n <= 2) return n;
1465 \\ return fibonacci(n - 2) + fibonacci(n - 1);
1466 \\}
1467 \\
1468 \\fn exit(code: usize) noreturn {
1469 \\ asm volatile ("syscall"
1470 \\ :
1471 \\ : [number] "{rax}" (231),
1472 \\ [arg1] "{rdi}" (code)
1473 \\ : "rcx", "r11", "memory"
1474 \\ );
1475 \\ unreachable;
1476 \\}
1477 , &[_][]const u8{":8:10: error: evaluation exceeded 1000 backwards branches"});
1478 }
14331479}
test/stage2/zir.zig deleted-316
......@@ -1,316 +0,0 @@
1const std = @import("std");
2const TestContext = @import("../../src/test.zig").TestContext;
3// self-hosted does not yet support PE executable files / COFF object files
4// or mach-o files. So we do the ZIR transform test cases cross compiling for
5// x86_64-linux.
6const linux_x64 = std.zig.CrossTarget{
7 .cpu_arch = .x86_64,
8 .os_tag = .linux,
9};
10
11pub fn addCases(ctx: *TestContext) !void {
12 ctx.transformZIR("referencing decls which appear later in the file", linux_x64,
13 \\@void = primitive(void)
14 \\@fnty = fntype([], @void, cc=C)
15 \\
16 \\@9 = str("entry")
17 \\@11 = export(@9, "entry")
18 \\
19 \\@entry = fn(@fnty, {
20 \\ %11 = returnvoid()
21 \\})
22 ,
23 \\@void = primitive(void)
24 \\@fnty = fntype([], @void, cc=C)
25 \\@9 = declref("9__anon_0")
26 \\@9__anon_0 = str("entry")
27 \\@unnamed$4 = str("entry")
28 \\@unnamed$5 = export(@unnamed$4, "entry")
29 \\@11 = primitive(void_value)
30 \\@unnamed$7 = fntype([], @void, cc=C)
31 \\@entry = fn(@unnamed$7, {
32 \\ %0 = returnvoid() ; deaths=0b1000000000000000
33 \\}, is_inline=0)
34 \\
35 );
36 ctx.transformZIR("elemptr, add, cmp, condbr, return, breakpoint", linux_x64,
37 \\@void = primitive(void)
38 \\@usize = primitive(usize)
39 \\@fnty = fntype([], @void, cc=C)
40 \\@0 = int(0)
41 \\@1 = int(1)
42 \\@2 = int(2)
43 \\@3 = int(3)
44 \\
45 \\@entry = fn(@fnty, {
46 \\ %a = str("\x32\x08\x01\x0a")
47 \\ %a_ref = ref(%a)
48 \\ %eptr0 = elemptr(%a_ref, @0)
49 \\ %eptr1 = elemptr(%a_ref, @1)
50 \\ %eptr2 = elemptr(%a_ref, @2)
51 \\ %eptr3 = elemptr(%a_ref, @3)
52 \\ %v0 = deref(%eptr0)
53 \\ %v1 = deref(%eptr1)
54 \\ %v2 = deref(%eptr2)
55 \\ %v3 = deref(%eptr3)
56 \\ %x0 = add(%v0, %v1)
57 \\ %x1 = add(%v2, %v3)
58 \\ %result = add(%x0, %x1)
59 \\
60 \\ %expected = int(69)
61 \\ %ok = cmp_eq(%result, %expected)
62 \\ %10 = condbr(%ok, {
63 \\ %11 = returnvoid()
64 \\ }, {
65 \\ %12 = breakpoint()
66 \\ })
67 \\})
68 \\
69 \\@9 = str("entry")
70 \\@11 = export(@9, "entry")
71 ,
72 \\@void = primitive(void)
73 \\@fnty = fntype([], @void, cc=C)
74 \\@0 = int(0)
75 \\@1 = int(1)
76 \\@2 = int(2)
77 \\@3 = int(3)
78 \\@unnamed$6 = fntype([], @void, cc=C)
79 \\@entry = fn(@unnamed$6, {
80 \\ %0 = returnvoid() ; deaths=0b1000000000000000
81 \\}, is_inline=0)
82 \\@entry__anon_1 = str("2\x08\x01\n")
83 \\@9 = declref("9__anon_0")
84 \\@9__anon_0 = str("entry")
85 \\@unnamed$11 = str("entry")
86 \\@unnamed$12 = export(@unnamed$11, "entry")
87 \\@11 = primitive(void_value)
88 \\
89 );
90
91 {
92 var case = ctx.objZIR("reference cycle with compile error in the cycle", linux_x64);
93 case.addTransform(
94 \\@void = primitive(void)
95 \\@fnty = fntype([], @void, cc=C)
96 \\
97 \\@9 = str("entry")
98 \\@11 = export(@9, "entry")
99 \\
100 \\@entry = fn(@fnty, {
101 \\ %0 = call(@a, [])
102 \\ %1 = returnvoid()
103 \\})
104 \\
105 \\@a = fn(@fnty, {
106 \\ %0 = call(@b, [])
107 \\ %1 = returnvoid()
108 \\})
109 \\
110 \\@b = fn(@fnty, {
111 \\ %0 = call(@a, [])
112 \\ %1 = returnvoid()
113 \\})
114 ,
115 \\@void = primitive(void)
116 \\@fnty = fntype([], @void, cc=C)
117 \\@9 = declref("9__anon_0")
118 \\@9__anon_0 = str("entry")
119 \\@unnamed$4 = str("entry")
120 \\@unnamed$5 = export(@unnamed$4, "entry")
121 \\@11 = primitive(void_value)
122 \\@unnamed$7 = fntype([], @void, cc=C)
123 \\@entry = fn(@unnamed$7, {
124 \\ %0 = call(@a, [], modifier=auto) ; deaths=0b1000000000000001
125 \\ %1 = returnvoid() ; deaths=0b1000000000000000
126 \\}, is_inline=0)
127 \\@unnamed$9 = fntype([], @void, cc=C)
128 \\@a = fn(@unnamed$9, {
129 \\ %0 = call(@b, [], modifier=auto) ; deaths=0b1000000000000001
130 \\ %1 = returnvoid() ; deaths=0b1000000000000000
131 \\}, is_inline=0)
132 \\@unnamed$11 = fntype([], @void, cc=C)
133 \\@b = fn(@unnamed$11, {
134 \\ %0 = call(@a, [], modifier=auto) ; deaths=0b1000000000000001
135 \\ %1 = returnvoid() ; deaths=0b1000000000000000
136 \\}, is_inline=0)
137 \\
138 );
139 // Now we introduce a compile error
140 case.addError(
141 \\@void = primitive(void)
142 \\@fnty = fntype([], @void, cc=C)
143 \\
144 \\@9 = str("entry")
145 \\@11 = export(@9, "entry")
146 \\
147 \\@entry = fn(@fnty, {
148 \\ %0 = call(@a, [])
149 \\ %1 = returnvoid()
150 \\})
151 \\
152 \\@a = fn(@fnty, {
153 \\ %0 = call(@c, [])
154 \\ %1 = returnvoid()
155 \\})
156 \\
157 \\@b = str("message")
158 \\
159 \\@c = fn(@fnty, {
160 \\ %9 = compileerror(@b)
161 \\ %0 = call(@a, [])
162 \\ %1 = returnvoid()
163 \\})
164 ,
165 &[_][]const u8{
166 ":20:21: error: message",
167 },
168 );
169 // Now we remove the call to `a`. `a` and `b` form a cycle, but no entry points are
170 // referencing either of them. This tests that the cycle is detected, and the error
171 // goes away.
172 case.addTransform(
173 \\@void = primitive(void)
174 \\@fnty = fntype([], @void, cc=C)
175 \\
176 \\@9 = str("entry")
177 \\@11 = export(@9, "entry")
178 \\
179 \\@entry = fn(@fnty, {
180 \\ %0 = returnvoid()
181 \\})
182 \\
183 \\@a = fn(@fnty, {
184 \\ %0 = call(@c, [])
185 \\ %1 = returnvoid()
186 \\})
187 \\
188 \\@b = str("message")
189 \\
190 \\@c = fn(@fnty, {
191 \\ %9 = compileerror(@b)
192 \\ %0 = call(@a, [])
193 \\ %1 = returnvoid()
194 \\})
195 ,
196 \\@void = primitive(void)
197 \\@fnty = fntype([], @void, cc=C)
198 \\@9 = declref("9__anon_3")
199 \\@9__anon_3 = str("entry")
200 \\@unnamed$4 = str("entry")
201 \\@unnamed$5 = export(@unnamed$4, "entry")
202 \\@11 = primitive(void_value)
203 \\@unnamed$7 = fntype([], @void, cc=C)
204 \\@entry = fn(@unnamed$7, {
205 \\ %0 = returnvoid() ; deaths=0b1000000000000000
206 \\}, is_inline=0)
207 \\
208 );
209 }
210
211 if (std.Target.current.os.tag != .linux or
212 std.Target.current.cpu.arch != .x86_64)
213 {
214 // TODO implement self-hosted PE (.exe file) linking
215 // TODO implement more ZIR so we don't depend on x86_64-linux
216 return;
217 }
218
219 ctx.compareOutputZIR("hello world ZIR",
220 \\@noreturn = primitive(noreturn)
221 \\@void = primitive(void)
222 \\@usize = primitive(usize)
223 \\@0 = int(0)
224 \\@1 = int(1)
225 \\@2 = int(2)
226 \\@3 = int(3)
227 \\
228 \\@msg = str("Hello, world!\n")
229 \\
230 \\@start_fnty = fntype([], @noreturn, cc=Naked)
231 \\@start = fn(@start_fnty, {
232 \\ %SYS_exit_group = int(231)
233 \\ %exit_code = as(@usize, @0)
234 \\
235 \\ %syscall = str("syscall")
236 \\ %sysoutreg = str("={rax}")
237 \\ %rax = str("{rax}")
238 \\ %rdi = str("{rdi}")
239 \\ %rcx = str("rcx")
240 \\ %rdx = str("{rdx}")
241 \\ %rsi = str("{rsi}")
242 \\ %r11 = str("r11")
243 \\ %memory = str("memory")
244 \\
245 \\ %SYS_write = as(@usize, @1)
246 \\ %STDOUT_FILENO = as(@usize, @1)
247 \\
248 \\ %msg_addr = ptrtoint(@msg)
249 \\
250 \\ %len_name = str("len")
251 \\ %msg_len_ptr = fieldptr(@msg, %len_name)
252 \\ %msg_len = deref(%msg_len_ptr)
253 \\ %rc_write = asm(%syscall, @usize,
254 \\ volatile=1,
255 \\ output=%sysoutreg,
256 \\ inputs=[%rax, %rdi, %rsi, %rdx],
257 \\ clobbers=[%rcx, %r11, %memory],
258 \\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len])
259 \\
260 \\ %rc_exit = asm(%syscall, @usize,
261 \\ volatile=1,
262 \\ output=%sysoutreg,
263 \\ inputs=[%rax, %rdi],
264 \\ clobbers=[%rcx, %r11, %memory],
265 \\ args=[%SYS_exit_group, %exit_code])
266 \\
267 \\ %99 = unreachable()
268 \\});
269 \\
270 \\@9 = str("_start")
271 \\@11 = export(@9, "start")
272 ,
273 \\Hello, world!
274 \\
275 );
276
277 ctx.compareOutputZIR("function call with no args no return value",
278 \\@noreturn = primitive(noreturn)
279 \\@void = primitive(void)
280 \\@usize = primitive(usize)
281 \\@0 = int(0)
282 \\@1 = int(1)
283 \\@2 = int(2)
284 \\@3 = int(3)
285 \\
286 \\@exit0_fnty = fntype([], @noreturn)
287 \\@exit0 = fn(@exit0_fnty, {
288 \\ %SYS_exit_group = int(231)
289 \\ %exit_code = as(@usize, @0)
290 \\
291 \\ %syscall = str("syscall")
292 \\ %sysoutreg = str("={rax}")
293 \\ %rax = str("{rax}")
294 \\ %rdi = str("{rdi}")
295 \\ %rcx = str("rcx")
296 \\ %r11 = str("r11")
297 \\ %memory = str("memory")
298 \\
299 \\ %rc = asm(%syscall, @usize,
300 \\ volatile=1,
301 \\ output=%sysoutreg,
302 \\ inputs=[%rax, %rdi],
303 \\ clobbers=[%rcx, %r11, %memory],
304 \\ args=[%SYS_exit_group, %exit_code])
305 \\
306 \\ %99 = unreachable()
307 \\});
308 \\
309 \\@start_fnty = fntype([], @noreturn, cc=Naked)
310 \\@start = fn(@start_fnty, {
311 \\ %0 = call(@exit0, [])
312 \\})
313 \\@9 = str("_start")
314 \\@11 = export(@9, "start")
315 , "");
316}