Commit 4c13d020db

Andrew Kelley <andrew@ziglang.org>
2020-09-01 21:39:47
stage2: proper split of requireRuntimeBlock and requireFunctionBlock
* improve the ZIR generated of variable decls - utilize the same ZIR for the type and init value when possible - init value gets a result location with the variable type. no manual coercion is required. * no longer use return instructions to extract values out of comptime blocks. Instead run the analysis and then look at the corresponding analyzed instruction, relying on the comptime mechanism to report errors when something could not be comptime evaluated.
1 parent 717b0e8
Changed files (4)
src-self-hosted
test
stage2
src-self-hosted/Module.zig
@@ -1308,7 +1308,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
                 .return_type = return_type_inst,
                 .param_types = param_types,
             }, .{});
-            _ = try astgen.addZIRUnOp(self, &fn_type_scope.base, fn_src, .@"return", fn_type_inst);
 
             // We need the memory for the Type to go into the arena for the Decl
             var decl_arena = std.heap.ArenaAllocator.init(self.gpa);
@@ -1325,7 +1324,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
             };
             defer block_scope.instructions.deinit(self.gpa);
 
-            const fn_type = try zir_sema.analyzeBodyValueAsType(self, &block_scope, .{
+            const fn_type = try zir_sema.analyzeBodyValueAsType(self, &block_scope, fn_type_inst, .{
                 .instructions = fn_type_scope.instructions.items,
             });
             const new_func = try decl_arena.allocator.create(Fn);
@@ -1492,35 +1491,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
                 return self.failNode(&block_scope.base, sect_expr, "TODO implement function section expression", .{});
             }
 
-            const explicit_type = blk: {
-                const type_node = var_decl.getTypeNode() orelse
-                    break :blk null;
-
-                // Temporary arena for the zir instructions.
-                var type_scope_arena = std.heap.ArenaAllocator.init(self.gpa);
-                defer type_scope_arena.deinit();
-                var type_scope: Scope.GenZIR = .{
-                    .decl = decl,
-                    .arena = &type_scope_arena.allocator,
-                    .parent = decl.scope,
-                };
-                defer type_scope.instructions.deinit(self.gpa);
-
-                const src = tree.token_locs[type_node.firstToken()].start;
-                const type_type = try astgen.addZIRInstConst(self, &type_scope.base, src, .{
-                    .ty = Type.initTag(.type),
-                    .val = Value.initTag(.type_type),
-                });
-                const var_type = try astgen.expr(self, &type_scope.base, .{ .ty = type_type }, type_node);
-                _ = try astgen.addZIRUnOp(self, &type_scope.base, src, .@"return", var_type);
-
-                break :blk try zir_sema.analyzeBodyValueAsType(self, &block_scope, .{
-                    .instructions = type_scope.instructions.items,
-                });
-            };
-
-            var var_type: Type = undefined;
-            const value: ?Value = if (var_decl.getInitNode()) |init_node| blk: {
+            const var_info: struct { ty: Type, val: ?Value } = if (var_decl.getInitNode()) |init_node| vi: {
                 var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa);
                 defer gen_scope_arena.deinit();
                 var gen_scope: Scope.GenZIR = .{
@@ -1529,10 +1500,19 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
                     .parent = decl.scope,
                 };
                 defer gen_scope.instructions.deinit(self.gpa);
-                const src = tree.token_locs[init_node.firstToken()].start;
 
-                const init_inst = try astgen.expr(self, &gen_scope.base, .none, init_node);
-                _ = try astgen.addZIRUnOp(self, &gen_scope.base, src, .@"return", init_inst);
+                const init_result_loc: astgen.ResultLoc = if (var_decl.getTypeNode()) |type_node| rl: {
+                    const src = tree.token_locs[type_node.firstToken()].start;
+                    const type_type = try astgen.addZIRInstConst(self, &gen_scope.base, src, .{
+                        .ty = Type.initTag(.type),
+                        .val = Value.initTag(.type_type),
+                    });
+                    const var_type = try astgen.expr(self, &gen_scope.base, .{ .ty = type_type }, type_node);
+                    break :rl .{ .ty = var_type };
+                } else .none;
+
+                const src = tree.token_locs[init_node.firstToken()].start;
+                const init_inst = try astgen.expr(self, &gen_scope.base, init_result_loc, init_node);
 
                 var inner_block: Scope.Block = .{
                     .parent = null,
@@ -1545,38 +1525,53 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
                 defer inner_block.instructions.deinit(self.gpa);
                 try zir_sema.analyzeBody(self, &inner_block.base, .{ .instructions = gen_scope.instructions.items });
 
-                for (inner_block.instructions.items) |inst| {
-                    if (inst.castTag(.ret)) |ret| {
-                        const coerced = if (explicit_type) |some|
-                            try self.coerce(&inner_block.base, some, ret.operand)
-                        else
-                            ret.operand;
-                        const val = coerced.value() orelse
-                            return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{});
-
-                        var_type = explicit_type orelse try ret.operand.ty.copy(block_scope.arena);
-                        break :blk try val.copy(block_scope.arena);
-                    } else {
-                        return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{});
-                    }
-                }
-                unreachable;
+                // The result location guarantees the type coercion.
+                const analyzed_init_inst = init_inst.analyzed_inst.?;
+                // The is_comptime in the Scope.Block guarantees the result is comptime-known.
+                const val = analyzed_init_inst.value().?;
+
+                const ty = try analyzed_init_inst.ty.copy(block_scope.arena);
+                break :vi .{
+                    .ty = ty,
+                    .val = try val.copy(block_scope.arena),
+                };
             } else if (!is_extern) {
                 return self.failTok(&block_scope.base, var_decl.firstToken(), "variables must be initialized", .{});
-            } else if (explicit_type) |some| blk: {
-                var_type = some;
-                break :blk null;
+            } else if (var_decl.getTypeNode()) |type_node| vi: {
+                // Temporary arena for the zir instructions.
+                var type_scope_arena = std.heap.ArenaAllocator.init(self.gpa);
+                defer type_scope_arena.deinit();
+                var type_scope: Scope.GenZIR = .{
+                    .decl = decl,
+                    .arena = &type_scope_arena.allocator,
+                    .parent = decl.scope,
+                };
+                defer type_scope.instructions.deinit(self.gpa);
+
+                const src = tree.token_locs[type_node.firstToken()].start;
+                const type_type = try astgen.addZIRInstConst(self, &type_scope.base, src, .{
+                    .ty = Type.initTag(.type),
+                    .val = Value.initTag(.type_type),
+                });
+                const var_type = try astgen.expr(self, &type_scope.base, .{ .ty = type_type }, type_node);
+                const ty = try zir_sema.analyzeBodyValueAsType(self, &block_scope, var_type, .{
+                    .instructions = type_scope.instructions.items,
+                });
+                break :vi .{
+                    .ty = ty,
+                    .val = null,
+                };
             } else {
                 return self.failTok(&block_scope.base, var_decl.firstToken(), "unable to infer variable type", .{});
             };
 
-            if (is_mutable and !var_type.isValidVarType(is_extern)) {
-                return self.failTok(&block_scope.base, var_decl.firstToken(), "variable of type '{}' must be const", .{var_type});
+            if (is_mutable and !var_info.ty.isValidVarType(is_extern)) {
+                return self.failTok(&block_scope.base, var_decl.firstToken(), "variable of type '{}' must be const", .{var_info.ty});
             }
 
             var type_changed = true;
             if (decl.typedValueManaged()) |tvm| {
-                type_changed = !tvm.typed_value.ty.eql(var_type);
+                type_changed = !tvm.typed_value.ty.eql(var_info.ty);
 
                 tvm.deinit(self.gpa);
             }
@@ -1585,7 +1580,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
             const var_payload = try decl_arena.allocator.create(Value.Payload.Variable);
             new_variable.* = .{
                 .owner_decl = decl,
-                .init = value orelse undefined,
+                .init = var_info.val orelse undefined,
                 .is_extern = is_extern,
                 .is_mutable = is_mutable,
                 .is_threadlocal = is_threadlocal,
@@ -1596,7 +1591,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
             decl.typed_value = .{
                 .most_recent = .{
                     .typed_value = .{
-                        .ty = var_type,
+                        .ty = var_info.ty,
                         .val = Value.initPayload(&var_payload.base),
                     },
                     .arena = decl_arena_state,
@@ -2096,12 +2091,19 @@ pub fn getErrorValue(self: *Module, name: []const u8) !std.StringHashMapUnmanage
     return gop.entry.*;
 }
 
-/// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites.
-pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
+pub fn requireFunctionBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
     return scope.cast(Scope.Block) orelse
         return self.fail(scope, src, "instruction illegal outside function body", .{});
 }
 
+pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
+    const block = try self.requireFunctionBlock(scope, src);
+    if (block.is_comptime) {
+        return self.fail(scope, src, "unable to resolve comptime value", .{});
+    }
+    return block;
+}
+
 pub fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value {
     return (try self.resolveDefinedValue(scope, base)) orelse
         return self.fail(scope, base.src, "unable to resolve comptime value", .{});
src-self-hosted/test.zig
@@ -474,15 +474,15 @@ pub const TestContext = struct {
                 var all_errors = try module.getAllErrorsAlloc();
                 defer all_errors.deinit(allocator);
                 if (all_errors.list.len != 0) {
-                    std.debug.warn("\nErrors occurred updating the module:\n================\n", .{});
+                    std.debug.print("\nErrors occurred updating the module:\n================\n", .{});
                     for (all_errors.list) |err| {
-                        std.debug.warn(":{}:{}: error: {}\n================\n", .{ err.line + 1, err.column + 1, err.msg });
+                        std.debug.print(":{}:{}: error: {}\n================\n", .{ err.line + 1, err.column + 1, err.msg });
                     }
                     if (case.cbe) {
                         const C = module.bin_file.cast(link.File.C).?;
-                        std.debug.warn("Generated C: \n===============\n{}\n\n===========\n\n", .{C.main.items});
+                        std.debug.print("Generated C: \n===============\n{}\n\n===========\n\n", .{C.main.items});
                     }
-                    std.debug.warn("Test failed.\n", .{});
+                    std.debug.print("Test failed.\n", .{});
                     std.process.exit(1);
                 }
             }
@@ -497,12 +497,12 @@ pub const TestContext = struct {
                         var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read C output!");
 
                         if (expected_output.len != out.len) {
-                            std.debug.warn("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
+                            std.debug.print("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
                             std.process.exit(1);
                         }
                         for (expected_output) |e, i| {
                             if (out[i] != e) {
-                                std.debug.warn("\nTransformed C differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
+                                std.debug.print("\nTransformed C differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
                                 std.process.exit(1);
                             }
                         }
@@ -526,12 +526,12 @@ pub const TestContext = struct {
                         defer test_node.end();
 
                         if (expected_output.len != out_zir.items.len) {
-                            std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
+                            std.debug.print("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
                             std.process.exit(1);
                         }
                         for (expected_output) |e, i| {
                             if (out_zir.items[i] != e) {
-                                std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
+                                std.debug.print("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
                                 std.process.exit(1);
                             }
                         }
@@ -554,7 +554,7 @@ pub const TestContext = struct {
                                 break;
                             }
                         } else {
-                            std.debug.warn("{}\nUnexpected error:\n================\n:{}:{}: error: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg });
+                            std.debug.print("{}\nUnexpected error:\n================\n:{}:{}: error: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg });
                             std.process.exit(1);
                         }
                     }
@@ -562,7 +562,7 @@ pub const TestContext = struct {
                     for (handled_errors) |h, i| {
                         if (!h) {
                             const er = e[i];
-                            std.debug.warn("{}\nDid not receive error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, er.line, er.column, er.msg });
+                            std.debug.print("{}\nDid not receive error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, er.line, er.column, er.msg });
                             std.process.exit(1);
                         }
                     }
@@ -643,7 +643,7 @@ pub const TestContext = struct {
                     switch (exec_result.term) {
                         .Exited => |code| {
                             if (code != 0) {
-                                std.debug.warn("elf file exited with code {}\n", .{code});
+                                std.debug.print("elf file exited with code {}\n", .{code});
                                 return error.BinaryBadExitCode;
                             }
                         },
src-self-hosted/zir_sema.zig
@@ -150,18 +150,16 @@ pub fn analyzeBody(mod: *Module, scope: *Scope, body: zir.Module.Body) !void {
     }
 }
 
-/// TODO improve this to use .block_comptime_flat
-pub fn analyzeBodyValueAsType(mod: *Module, block_scope: *Scope.Block, body: zir.Module.Body) !Type {
+pub fn analyzeBodyValueAsType(
+    mod: *Module,
+    block_scope: *Scope.Block,
+    zir_result_inst: *zir.Inst,
+    body: zir.Module.Body,
+) !Type {
     try analyzeBody(mod, &block_scope.base, body);
-    for (block_scope.instructions.items) |inst| {
-        if (inst.castTag(.ret)) |ret| {
-            const val = try mod.resolveConstValue(&block_scope.base, ret.operand);
-            return val.toType(block_scope.base.arena());
-        } else {
-            return mod.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{});
-        }
-    }
-    unreachable;
+    const result_inst = zir_result_inst.analyzed_inst.?;
+    const val = try mod.resolveConstValue(&block_scope.base, result_inst);
+    return val.toType(block_scope.base.arena());
 }
 
 pub fn analyzeZirDecl(mod: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool {
@@ -366,7 +364,7 @@ fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!
 }
 
 fn analyzeInstRetType(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
-    const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+    const b = try mod.requireFunctionBlock(scope, inst.base.src);
     const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
     const ret_type = fn_ty.fnReturnType();
     return mod.constType(scope, inst.base.src, ret_type);
test/stage2/test.zig
@@ -967,10 +967,19 @@ pub fn addCases(ctx: *TestContext) !void {
         \\fn entry() void {}
     , &[_][]const u8{":2:4: error: redefinition of 'entry'"});
 
-    ctx.compileError("extern variable has no type", linux_x64,
-        \\comptime {
-        \\    _ = foo;
-        \\}
-        \\extern var foo;
-    , &[_][]const u8{":4:1: error: unable to infer variable type"});
+    {
+        var case = ctx.obj("extern variable has no type", linux_x64);
+        case.addError(
+            \\comptime {
+            \\    _ = foo;
+            \\}
+            \\extern var foo;
+        , &[_][]const u8{":2:5: error: unable to resolve comptime value"});
+        case.addError(
+            \\export fn entry() void {
+            \\    _ = foo;
+            \\}
+            \\extern var foo;
+        , &[_][]const u8{":4:1: error: unable to infer variable type"});
+    }
 }