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.
This commit is contained in:
Andrew Kelley 2020-09-01 12:39:47 -07:00
parent 717b0e8275
commit 4c13d020db
4 changed files with 101 additions and 92 deletions

View File

@ -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,10 +1491,53 @@ 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;
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 = .{
.decl = decl,
.arena = &gen_scope_arena.allocator,
.parent = decl.scope,
};
defer gen_scope.instructions.deinit(self.gpa);
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,
.func = null,
.decl = decl,
.instructions = .{},
.arena = &gen_scope_arena.allocator,
.is_comptime = true,
};
defer inner_block.instructions.deinit(self.gpa);
try zir_sema.analyzeBody(self, &inner_block.base, .{ .instructions = gen_scope.instructions.items });
// 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 (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();
@ -1512,71 +1554,24 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
.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, .{
const ty = try zir_sema.analyzeBodyValueAsType(self, &block_scope, var_type, .{
.instructions = type_scope.instructions.items,
});
};
var var_type: Type = undefined;
const value: ?Value = if (var_decl.getInitNode()) |init_node| blk: {
var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa);
defer gen_scope_arena.deinit();
var gen_scope: Scope.GenZIR = .{
.decl = decl,
.arena = &gen_scope_arena.allocator,
.parent = decl.scope,
break :vi .{
.ty = ty,
.val = null,
};
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);
var inner_block: Scope.Block = .{
.parent = null,
.func = null,
.decl = decl,
.instructions = .{},
.arena = &gen_scope_arena.allocator,
.is_comptime = true,
};
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;
} 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 {
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", .{});

View File

@ -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;
}
},

View File

@ -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);

View File

@ -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"});
}
}