Skip to content

Commit

Permalink
stage2: comptime function calls
Browse files Browse the repository at this point in the history
 * Function calls that happen in a comptime scope get called at
   compile-time. We do this by putting the parameters in place as
   constant values and then running regular function analysis on the
   body.
 * Added `Scope.Block.dump()` for debugging purposes.
 * Fixed some code to call `identifierTokenString` rather than
   `tokenSlice`, making it work for `@""` syntax.
 * Implemented `Value.copy` for big integers.

Follow-up issues to tackle:
 * Adding compile errors to the callsite instead of the callee Decl.
 * Proper error notes for "called from here".
   - Related: ziglang#7555
 * Branch quotas.
 * ZIR support?
  • Loading branch information
andrewrk committed Jan 3, 2021
1 parent fb37c1b commit fea8659
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 46 deletions.
42 changes: 35 additions & 7 deletions src/Module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,11 @@ pub const Decl = struct {
}
}

/// Asserts that the `Decl` is part of AST and not ZIRModule.
pub fn getFileScope(self: *Decl) *Scope.File {
return self.scope.cast(Scope.Container).?.file_scope;
}

fn removeDependant(self: *Decl, other: *Decl) void {
self.dependants.removeAssertDiscard(other);
}
Expand Down Expand Up @@ -776,6 +781,11 @@ pub const Scope = struct {
results: ArrayListUnmanaged(*Inst),
block_inst: *Inst.Block,
};

/// For debugging purposes.
pub fn dump(self: *Block, mod: Module) void {
zir.dumpBlock(mod, self);
}
};

/// This is a temporary structure, references to it are valid only
Expand Down Expand Up @@ -992,11 +1002,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
defer tracy.end();

const container_scope = decl.scope.cast(Scope.Container).?;
const tree = try self.getAstTree(container_scope);
const tree = try self.getAstTree(container_scope.file_scope);
const ast_node = tree.root_node.decls()[decl.src_index];
switch (ast_node.tag) {
.FnProto => {
const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", ast_node);
const fn_proto = ast_node.castTag(.FnProto).?;

decl.analysis = .in_progress;

Expand Down Expand Up @@ -1131,7 +1141,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
for (fn_proto.params()) |param, i| {
const name_token = param.name_token.?;
const src = tree.token_locs[name_token].start;
const param_name = tree.tokenSlice(name_token); // TODO: call identifierTokenString
const param_name = try self.identifierTokenString(&gen_scope.base, name_token);
const arg = try gen_scope_arena.allocator.create(zir.Inst.Arg);
arg.* = .{
.base = .{
Expand Down Expand Up @@ -1496,12 +1506,10 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module {
}
}

fn getAstTree(self: *Module, container_scope: *Scope.Container) !*ast.Tree {
pub fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree {
const tracy = trace(@src());
defer tracy.end();

const root_scope = container_scope.file_scope;

switch (root_scope.status) {
.never_loaded, .unloaded_success => {
try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1);
Expand Down Expand Up @@ -1549,7 +1557,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void

// We may be analyzing it for the first time, or this may be
// an incremental update. This code handles both cases.
const tree = try self.getAstTree(container_scope);
const tree = try self.getAstTree(container_scope.file_scope);
const decls = tree.root_node.decls();

try self.comp.work_queue.ensureUnusedCapacity(decls.len);
Expand Down Expand Up @@ -3427,3 +3435,23 @@ pub fn validateVarType(mod: *Module, scope: *Scope, src: usize, ty: Type) !void
return mod.fail(scope, src, "variable of type '{}' must be const or comptime", .{ty});
}
}

/// Identifier token -> String (allocated in scope.arena())
pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 {
const tree = scope.tree();

const ident_name = tree.tokenSlice(token);
if (mem.startsWith(u8, ident_name, "@")) {
const raw_string = ident_name[1..];
var bad_index: usize = undefined;
return std.zig.parseStringLiteral(scope.arena(), raw_string, &bad_index) catch |err| switch (err) {
error.InvalidCharacter => {
const bad_byte = raw_string[bad_index];
const src = tree.token_locs[token].start;
return mod.fail(scope, src + 1 + bad_index, "invalid string literal character: '{c}'\n", .{bad_byte});
},
else => |e| return e,
};
}
return ident_name;
}
38 changes: 9 additions & 29 deletions src/astgen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpr
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
else => if (node.getLabel()) |break_label| {
const label_name = try identifierTokenString(mod, parent_scope, break_label);
const label_name = try mod.identifierTokenString(parent_scope, break_label);
return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name});
} else {
return mod.failTok(parent_scope, src, "break expression outside loop", .{});
Expand Down Expand Up @@ -426,7 +426,7 @@ fn continueExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowE
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
else => if (node.getLabel()) |break_label| {
const label_name = try identifierTokenString(mod, parent_scope, break_label);
const label_name = try mod.identifierTokenString(parent_scope, break_label);
return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name});
} else {
return mod.failTok(parent_scope, src, "continue expression outside loop", .{});
Expand Down Expand Up @@ -551,7 +551,7 @@ fn varDecl(
}
const tree = scope.tree();
const name_src = tree.token_locs[node.name_token].start;
const ident_name = try identifierTokenString(mod, scope, node.name_token);
const ident_name = try mod.identifierTokenString(scope, node.name_token);

// Local variables shadowing detection, including function parameters.
{
Expand Down Expand Up @@ -843,7 +843,7 @@ fn typeInixOp(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp, op_ins
fn enumLiteral(mod: *Module, scope: *Scope, node: *ast.Node.EnumLiteral) !*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[node.name].start;
const name = try identifierTokenString(mod, scope, node.name);
const name = try mod.identifierTokenString(scope, node.name);

return addZIRInst(mod, scope, src, zir.Inst.EnumLiteral, .{ .name = name }, .{});
}
Expand All @@ -864,7 +864,7 @@ fn errorSetDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Erro

for (decls) |decl, i| {
const tag = decl.castTag(.ErrorTag).?;
fields[i] = try identifierTokenString(mod, scope, tag.name_token);
fields[i] = try mod.identifierTokenString(scope, tag.name_token);
}

// analyzing the error set results in a decl ref, so we might need to dereference it
Expand Down Expand Up @@ -988,36 +988,16 @@ fn orelseCatchExpr(
/// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating.
/// OK in theory it could do it without allocating. This implementation allocates when the @"" form is used.
fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool {
const ident_name_1 = try identifierTokenString(mod, scope, token1);
const ident_name_2 = try identifierTokenString(mod, scope, token2);
const ident_name_1 = try mod.identifierTokenString(scope, token1);
const ident_name_2 = try mod.identifierTokenString(scope, token2);
return mem.eql(u8, ident_name_1, ident_name_2);
}

/// Identifier token -> String (allocated in scope.arena())
fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 {
const tree = scope.tree();

const ident_name = tree.tokenSlice(token);
if (mem.startsWith(u8, ident_name, "@")) {
const raw_string = ident_name[1..];
var bad_index: usize = undefined;
return std.zig.parseStringLiteral(scope.arena(), raw_string, &bad_index) catch |err| switch (err) {
error.InvalidCharacter => {
const bad_byte = raw_string[bad_index];
const src = tree.token_locs[token].start;
return mod.fail(scope, src + 1 + bad_index, "invalid string literal character: '{c}'\n", .{bad_byte});
},
else => |e| return e,
};
}
return ident_name;
}

pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[node.token].start;

const ident_name = try identifierTokenString(mod, scope, node.token);
const ident_name = try mod.identifierTokenString(scope, node.token);

return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{});
}
Expand Down Expand Up @@ -1936,7 +1916,7 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo
defer tracy.end();

const tree = scope.tree();
const ident_name = try identifierTokenString(mod, scope, ident.token);
const ident_name = try mod.identifierTokenString(scope, ident.token);
const src = tree.token_locs[ident.token].start;
if (mem.eql(u8, ident_name, "_")) {
return mod.failNode(scope, &ident.base, "TODO implement '_' identifier", .{});
Expand Down
13 changes: 8 additions & 5 deletions src/value.zig
Original file line number Diff line number Diff line change
Expand Up @@ -330,11 +330,14 @@ pub const Value = extern union {
.int_type => return self.copyPayloadShallow(allocator, Payload.IntType),
.int_u64 => return self.copyPayloadShallow(allocator, Payload.U64),
.int_i64 => return self.copyPayloadShallow(allocator, Payload.I64),
.int_big_positive => {
@panic("TODO implement copying of big ints");
},
.int_big_negative => {
@panic("TODO implement copying of big ints");
.int_big_positive, .int_big_negative => {
const old_payload = self.cast(Payload.BigInt).?;
const new_payload = try allocator.create(Payload.BigInt);
new_payload.* = .{
.base = .{ .tag = self.ptr_otherwise.tag },
.data = try allocator.dupe(std.math.big.Limb, old_payload.data),
};
return Value{ .ptr_otherwise = &new_payload.base };
},
.function => return self.copyPayloadShallow(allocator, Payload.Function),
.extern_fn => return self.copyPayloadShallow(allocator, Payload.Decl),
Expand Down
70 changes: 70 additions & 0 deletions src/zir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1885,6 +1885,46 @@ pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void {
module.dump();
}

/// For debugging purposes, prints a function representation to stderr.
pub fn dumpBlock(old_module: IrModule, module_block: *IrModule.Scope.Block) void {
const allocator = old_module.gpa;
var ctx: EmitZIR = .{
.allocator = allocator,
.decls = .{},
.arena = std.heap.ArenaAllocator.init(allocator),
.old_module = &old_module,
.next_auto_name = 0,
.names = std.StringArrayHashMap(void).init(allocator),
.primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator),
.indent = 0,
.block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator),
.loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator),
.metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator),
.body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator),
};
defer ctx.metadata.deinit();
defer ctx.body_metadata.deinit();
defer ctx.block_table.deinit();
defer ctx.loop_table.deinit();
defer ctx.decls.deinit(allocator);
defer ctx.names.deinit();
defer ctx.primitive_table.deinit();
defer ctx.arena.deinit();

_ = ctx.emitBlock(module_block, 0) catch |err| {
std.debug.print("unable to dump function: {}\n", .{err});
return;
};
var module = Module{
.decls = ctx.decls.items,
.arena = ctx.arena,
.metadata = ctx.metadata,
.body_metadata = ctx.body_metadata,
};

module.dump();
}

const EmitZIR = struct {
allocator: *Allocator,
arena: std.heap.ArenaAllocator,
Expand Down Expand Up @@ -2065,6 +2105,36 @@ const EmitZIR = struct {
return &declref_inst.base;
}

fn emitBlock(self: *EmitZIR, module_block: *IrModule.Scope.Block, src: usize) Allocator.Error!*Decl {
var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator);
defer inst_table.deinit();

var instructions = std.ArrayList(*Inst).init(self.allocator);
defer instructions.deinit();

const body: ir.Body = .{ .instructions = module_block.instructions.items };
try self.emitBody(body, &inst_table, &instructions);

const fn_type = try self.emitType(src, Type.initTag(.void));

const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len);
mem.copy(*Inst, arena_instrs, instructions.items);

const fn_inst = try self.arena.allocator.create(Inst.Fn);
fn_inst.* = .{
.base = .{
.src = src,
.tag = Inst.Fn.base_tag,
},
.positionals = .{
.fn_type = fn_type.inst,
.body = .{ .instructions = arena_instrs },
},
.kw_args = .{},
};
return self.emitUnnamedDecl(&fn_inst.base);
}

fn emitFn(self: *EmitZIR, module_fn: *IrModule.Fn, src: usize, ty: Type) Allocator.Error!*Decl {
var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator);
defer inst_table.deinit();
Expand Down
Loading

0 comments on commit fea8659

Please sign in to comment.