BRANCH_TODO
* nested function decl: how to refer to params?
* look for cached zir code
* save zir code to cache
* keep track of file dependencies/dependants
* unload files from memory when a dependency is dropped
* implement zir error notes
* implement the new AstGen compile errors
* get rid of failed_root_src_file
* get rid of Scope.DeclRef
* get rid of optional_type_from_ptr_elem
* handle decl collision with usingnamespace
* the decl doing the looking up needs to create a decl dependency
on each usingnamespace decl
* handle usingnamespace cycles
* compile error for return inside defer expression
* when block has noreturn statement
- avoid emitting defers
- compile error for unreachable code
* detect `return error.Foo` and emit ZIR that unconditionally generates errdefers
* `return`: check return operand and generate errdefers if necessary
* have failed_trees and just put the file in there
- this way we can emit all the parse errors not just the first one
- but maybe we want just the first one?
* need a var decl zir instruction which includes the name because we need to do the
compile error for a local shadowing a decl with Sema looking up the decl name.
- this means LocalVal and LocalPtr should use the string table
* sort compile errors before presenting them to eliminate nondeterministic error reporting
* when handling decls, catch the error and continue, so that
AstGen can report more than one compile error.
* AstGen: inside a nosuspend block, emit function calls as nosuspend calls
* AstGen: add result location pointers to function calls
const container_name_hash: Scope.NameHash = if (found_pkg) |pkg|
pkg.namespace_hash
else
std.zig.hashName(cur_pkg.namespace_hash, "/", resolved_path);
file_scope.* = .{
.root_container = .{
.parent = null,
.file_scope = file_scope,
.decls = .{},
.ty = struct_ty,
.parent_name_hash = container_name_hash,
},
};
mod.analyzeContainer(&file_scope.root_container) catch |err| switch (err) {
error.AnalysisFail => {
assert(mod.comp.totalErrorCount() != 0);
},
else => |e| return e,
};
return file_scope;
// Until then we simulate a full cache miss. Source files could have been loaded
// for any reason; to force a refresh we unload now.
module.unloadFile(module.root_scope);
module.failed_root_src_file = null;
module.analyzeNamespace(&module.root_scope.root_container) catch |err| switch (err) {
error.AnalysisFail => {
assert(self.totalErrorCount() != 0);
},
error.OutOfMemory => return error.OutOfMemory,
else => |e| {
module.failed_root_src_file = e;
},
};
// TODO only analyze imports if they are still referenced
for (module.import_table.items()) |entry| {
module.unloadFile(entry.value);
module.analyzeNamespace(&entry.value.root_container) catch |err| switch (err) {
error.AnalysisFail => {
assert(self.totalErrorCount() != 0);
},
else => |e| return e,
};
}
pub fn createContainerDecl(
mod: *Module,
scope: *Scope,
base_token: std.zig.ast.TokenIndex,
decl_arena: *std.heap.ArenaAllocator,
typed_value: TypedValue,
) !*Decl {
const scope_decl = scope.ownerDecl().?;
const name = try mod.getAnonTypeName(scope, base_token);
defer mod.gpa.free(name);
const name_hash = scope.namespace().fullyQualifiedNameHash(name);
const src_hash: std.zig.SrcHash = undefined;
const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_node, name_hash, src_hash);
const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
decl_arena_state.* = decl_arena.state;
new_decl.typed_value = .{
.most_recent = .{
.typed_value = typed_value,
.arena = decl_arena_state,
},
};
new_decl.analysis = .complete;
new_decl.generation = mod.generation;
return new_decl;
}
fn getAnonTypeName(mod: *Module, scope: *Scope, base_token: std.zig.ast.TokenIndex) ![]u8 {
// TODO add namespaces, generic function signatrues
const tree = scope.tree();
const token_tags = tree.tokens.items(.tag);
const base_name = switch (token_tags[base_token]) {
.keyword_struct => "struct",
.keyword_enum => "enum",
.keyword_union => "union",
.keyword_opaque => "opaque",
else => unreachable,
};
const loc = tree.tokenLocation(0, base_token);
return std.fmt.allocPrint(mod.gpa, "{s}:{d}:{d}", .{ base_name, loc.line, loc.column });
}
const parent_name_hash: Scope.NameHash = if (found_pkg) |pkg|
pkg.namespace_hash
else
std.zig.hashName(cur_pkg.namespace_hash, "/", resolved_path);
// We need a Decl to pass to AstGen and collect dependencies. But ultimately we
// want to pass them on to the Decl for the struct that represents the file.
var tmp_namespace: Scope.Namespace = .{
.parent = null,
.file_scope = new_file,
.parent_name_hash = parent_name_hash,
.ty = Type.initTag(.type),
};
const tree = try mod.getAstTree(new_file);
const top_decl = try mod.createNewDecl(
&tmp_namespace,
resolved_path,
0,
parent_name_hash,
std.zig.hashSrc(tree.source),
);
defer {
mod.decl_table.removeAssertDiscard(parent_name_hash);
top_decl.destroy(mod);
}
var gen_scope_arena = std.heap.ArenaAllocator.init(gpa);
defer gen_scope_arena.deinit();
var astgen = try AstGen.init(mod, top_decl, &gen_scope_arena.allocator);
defer astgen.deinit();
var gen_scope: Scope.GenZir = .{
.force_comptime = true,
.parent = &new_file.base,
.astgen = &astgen,
};
defer gen_scope.instructions.deinit(gpa);
const container_decl: ast.full.ContainerDecl = .{
.layout_token = null,
.ast = .{
.main_token = undefined,
.enum_token = null,
.members = tree.rootDecls(),
.arg = 0,
},
};
const struct_decl_ref = try AstGen.structDeclInner(
&gen_scope,
&gen_scope.base,
0,
container_decl,
.struct_decl,
);
_ = try gen_scope.addBreak(.break_inline, 0, struct_decl_ref);
var code = try gen_scope.finish();
defer code.deinit(gpa);
if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
code.dump(gpa, "import", &gen_scope.base, 0) catch {};
}
var sema: Sema = .{
.mod = mod,
.gpa = gpa,
.arena = &gen_scope_arena.allocator,
.code = code,
.inst_map = try gen_scope_arena.allocator.alloc(*ir.Inst, code.instructions.len),
.owner_decl = top_decl,
.namespace = top_decl.namespace,
.func = null,
.owner_func = null,
.param_inst_list = &.{},
};
var block_scope: Scope.Block = .{
.parent = null,
.sema = &sema,
.src_decl = top_decl,
.instructions = .{},
.inlining = null,
.is_comptime = true,
};
defer block_scope.instructions.deinit(gpa);
const init_inst_zir_ref = try sema.rootAsRef(&block_scope);
const analyzed_struct_inst = try sema.resolveInst(init_inst_zir_ref);
assert(analyzed_struct_inst.ty.zigTypeTag() == .Type);
const val = analyzed_struct_inst.value().?;
const struct_ty = try val.toType(&gen_scope_arena.allocator);
const struct_decl = struct_ty.getOwnerDecl();
struct_decl.contents_hash = top_decl.contents_hash;
new_file.namespace = struct_ty.getNamespace().?;
new_file.namespace.parent = null;
//new_file.namespace.parent_name_hash = tmp_namespace.parent_name_hash;
// Transfer the dependencies to `owner_decl`.
assert(top_decl.dependants.count() == 0);
for (top_decl.dependencies.items()) |entry| {
const dep = entry.key;
dep.removeDependant(top_decl);
if (dep == struct_decl) continue;
_ = try mod.declareDeclDependency(struct_decl, dep);
}
return new_file;
pub fn analyzeFile(mod: *Module, file: *Scope.File) !void {
// We call `getAstTree` here so that `analyzeFile` has the error set that includes
// file system operations, but `analyzeNamespace` does not.
const tree = try mod.getAstTree(file.namespace.file_scope);
const decls = tree.rootDecls();
return mod.analyzeNamespace(file.namespace, decls);
}
/// Returns `true` if the Decl type changed.
/// Returns `true` if this is the first time analyzing the Decl.
/// Returns `false` otherwise.
fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool {
const tracy = trace(@src());
defer tracy.end();
const tree = try mod.getAstTree(decl.namespace.file_scope);
const node_tags = tree.nodes.items(.tag);
const node_datas = tree.nodes.items(.data);
const decl_node = decl.src_node;
switch (node_tags[decl_node]) {
.fn_decl => {
const fn_proto = node_datas[decl_node].lhs;
const body = node_datas[decl_node].rhs;
switch (node_tags[fn_proto]) {
.fn_proto_simple => {
var params: [1]ast.Node.Index = undefined;
return mod.astgenAndSemaFn(decl, tree.*, body, tree.fnProtoSimple(¶ms, fn_proto));
},
.fn_proto_multi => return mod.astgenAndSemaFn(decl, tree.*, body, tree.fnProtoMulti(fn_proto)),
.fn_proto_one => {
var params: [1]ast.Node.Index = undefined;
return mod.astgenAndSemaFn(decl, tree.*, body, tree.fnProtoOne(¶ms, fn_proto));
},
.fn_proto => return mod.astgenAndSemaFn(decl, tree.*, body, tree.fnProto(fn_proto)),
else => unreachable,
}
},
.fn_proto_simple => {
var params: [1]ast.Node.Index = undefined;
return mod.astgenAndSemaFn(decl, tree.*, 0, tree.fnProtoSimple(¶ms, decl_node));
},
.fn_proto_multi => return mod.astgenAndSemaFn(decl, tree.*, 0, tree.fnProtoMulti(decl_node)),
.fn_proto_one => {
var params: [1]ast.Node.Index = undefined;
return mod.astgenAndSemaFn(decl, tree.*, 0, tree.fnProtoOne(¶ms, decl_node));
},
.fn_proto => return mod.astgenAndSemaFn(decl, tree.*, 0, tree.fnProto(decl_node)),
.global_var_decl => return mod.astgenAndSemaVarDecl(decl, tree.*, tree.globalVarDecl(decl_node)),
.local_var_decl => return mod.astgenAndSemaVarDecl(decl, tree.*, tree.localVarDecl(decl_node)),
.simple_var_decl => return mod.astgenAndSemaVarDecl(decl, tree.*, tree.simpleVarDecl(decl_node)),
.aligned_var_decl => return mod.astgenAndSemaVarDecl(decl, tree.*, tree.alignedVarDecl(decl_node)),
.@"comptime" => {
decl.analysis = .in_progress;
// A comptime decl does not store any value so we can just deinit this arena after analysis is done.
var analysis_arena = std.heap.ArenaAllocator.init(mod.gpa);
defer analysis_arena.deinit();
var code: Zir = blk: {
var astgen = try AstGen.init(mod, decl, &analysis_arena.allocator);
defer astgen.deinit();
var gen_scope: Scope.GenZir = .{
.force_comptime = true,
.parent = &decl.namespace.base,
.astgen = &astgen,
};
defer gen_scope.instructions.deinit(mod.gpa);
const block_expr = node_datas[decl_node].lhs;
_ = try AstGen.comptimeExpr(&gen_scope, &gen_scope.base, .none, block_expr);
_ = try gen_scope.addBreak(.break_inline, 0, .void_value);
const code = try gen_scope.finish();
if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
code.dump(mod.gpa, "comptime_block", &gen_scope.base, 0) catch {};
}
break :blk code;
};
defer code.deinit(mod.gpa);
var sema: Sema = .{
.mod = mod,
.gpa = mod.gpa,
.arena = &analysis_arena.allocator,
.code = code,
.inst_map = try analysis_arena.allocator.alloc(*ir.Inst, code.instructions.len),
.owner_decl = decl,
.namespace = decl.namespace,
.func = null,
.owner_func = null,
.param_inst_list = &.{},
};
var block_scope: Scope.Block = .{
.parent = null,
.sema = &sema,
.src_decl = decl,
.instructions = .{},
.inlining = null,
.is_comptime = true,
};
defer block_scope.instructions.deinit(mod.gpa);
_ = try sema.root(&block_scope);
decl.analysis = .complete;
decl.generation = mod.generation;
return true;
},
.@"usingnamespace" => {
decl.analysis = .in_progress;
var code: Zir = blk: {
var astgen = try AstGen.init(mod, decl, &analysis_arena.allocator);
defer astgen.deinit();
var gen_scope: Scope.GenZir = .{
.force_comptime = true,
.parent = &decl.namespace.base,
.astgen = &astgen,
};
defer gen_scope.instructions.deinit(mod.gpa);
const ns_type = try AstGen.typeExpr(&gen_scope, &gen_scope.base, type_expr);
};
try decl.namespace.usingnamespace_set.put(mod.gpa, ty.getNamespace().?, is_pub);
decl.analysis = .complete;
decl.generation = mod.generation;
return true;
},
else => unreachable,
}
}
fn astgenAndSemaFn(
mod: *Module,
decl: *Decl,
tree: ast.Tree,
body_node: ast.Node.Index,
fn_proto: ast.full.FnProto,
) !bool {
const is_inline = fn_type.fnCallingConvention() == .Inline;
const anal_state: Fn.Analysis = if (is_inline) .inline_only else .queued;
new_func.* = .{
.state = anal_state,
.zir = fn_zir,
.body = undefined,
.owner_decl = decl,
};
fn_payload.* = .{
.base = .{ .tag = .function },
.data = new_func,
};
var prev_type_has_bits = false;
var prev_is_inline = false;
var type_changed = true;
if (decl.typedValueManaged()) |tvm| {
prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits();
type_changed = !tvm.typed_value.ty.eql(fn_type);
if (tvm.typed_value.val.castTag(.function)) |payload| {
const prev_func = payload.data;
prev_is_inline = prev_func.state == .inline_only;
prev_func.deinit(mod.gpa);
}
tvm.deinit(mod.gpa);
}
decl_arena_state.* = decl_arena.state;
decl.typed_value = .{
.most_recent = .{
.typed_value = .{
.ty = fn_type,
.val = Value.initPayload(&fn_payload.base),
},
.arena = decl_arena_state,
},
};
decl.analysis = .complete;
decl.generation = mod.generation;
if (!is_inline and fn_type.hasCodeGenBits()) {
// We don't fully codegen the decl until later, but we do need to reserve a global
// offset table index for it. This allows us to codegen decls out of dependency order,
// increasing how many computations can be done in parallel.
try mod.comp.bin_file.allocateDeclIndexes(decl);
try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl });
if (type_changed and mod.emit_h != null) {
try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
}
} else if (!prev_is_inline and prev_type_has_bits) {
mod.comp.bin_file.freeDecl(decl);
}
if (fn_proto.extern_export_token) |maybe_export_token| {
if (token_tags[maybe_export_token] == .keyword_export) {
if (is_inline) {
return mod.failTok(
&block_scope.base,
maybe_export_token,
"export of inline function",
.{},
);
}
const export_src = decl.tokSrcLoc(maybe_export_token);
const name = tree.tokenSlice(fn_proto.name_token.?); // TODO identifierTokenString
// The scope needs to have the decl in it.
try mod.analyzeExport(&block_scope.base, export_src, name, decl);
}
}
return type_changed or is_inline != prev_is_inline;
}
fn astgenAndSemaVarDecl(
mod: *Module,
decl: *Decl,
tree: ast.Tree,
var_decl: ast.full.VarDecl,
) !bool {
const token_tags = tree.tokens.items(.tag);
}
/// Asserts the scope is a child of a File and has an AST tree and returns the tree.
pub fn tree(scope: *Scope) *const ast.Tree {
switch (scope.tag) {
.file => return &scope.cast(File).?.tree,
.block => return &scope.cast(Block).?.src_decl.namespace.file_scope.tree,
.gen_zir => return scope.cast(GenZir).?.tree(),
.local_val => return &scope.cast(LocalVal).?.gen_zir.astgen.decl.namespace.file_scope.tree,
.local_ptr => return &scope.cast(LocalPtr).?.gen_zir.astgen.decl.namespace.file_scope.tree,
.namespace => return &scope.cast(Namespace).?.file_scope.tree,
.decl_ref => return &scope.cast(DeclRef).?.decl.namespace.file_scope.tree,
}
}
error.FileNotFound => {
return mod.fail(&block.base, src, "unable to find '{s}'", .{operand});
},
log.debug("extern fn symbol expected in lib '{s}'", .{lib_name_str});
mod.comp.stage1AddLinkLib(lib_name_str) catch |err| {
return mod.failTok(
&fn_type_scope.base,
lib_name_token,
"unable to add link lib '{s}': {s}",
.{ lib_name_str, @errorName(err) },
);
};
const target = mod.comp.getTarget();
if (target_util.is_libc_lib_name(target, lib_name_str)) {
if (!mod.comp.bin_file.options.link_libc) {
return mod.failTok(
&fn_type_scope.base,
lib_name_token,
"dependency on libc must be explicitly specified in the build command",
.{},
);
}
break :blk;
}
if (target_util.is_libcpp_lib_name(target, lib_name_str)) {
if (!mod.comp.bin_file.options.link_libcpp) {
return mod.failTok(
&fn_type_scope.base,
lib_name_token,
"dependency on libc++ must be explicitly specified in the build command",
.{},
);
}
break :blk;
}
if (!target.isWasm() and !mod.comp.bin_file.options.pic) {
return mod.failTok(
&fn_type_scope.base,
lib_name_token,
"dependency on dynamic library '{s}' requires enabling Position Independent Code. Fixed by `-l{s}` or `-fPIC`.",
.{ lib_name_str, lib_name_str },
);
}
if (counts.values == 0 and counts.decls == 0 and arg_inst == .none) {
// No explicitly provided tag values and no top level declarations! In this case,
// we can construct the enum type in AstGen and it will be correctly shared by all
// generic function instantiations and comptime function calls.
var new_decl_arena = std.heap.ArenaAllocator.init(gpa);
errdefer new_decl_arena.deinit();
const arena = &new_decl_arena.allocator;
var fields_map: std.StringArrayHashMapUnmanaged(void) = .{};
try fields_map.ensureCapacity(arena, counts.total_fields);
for (container_decl.ast.members) |member_node| {
if (member_node == counts.nonexhaustive_node)
continue;
const member = switch (node_tags[member_node]) {
.container_field_init => tree.containerFieldInit(member_node),
.container_field_align => tree.containerFieldAlign(member_node),
.container_field => tree.containerField(member_node),
else => unreachable, // We checked earlier.
};
const name_token = member.ast.name_token;
const tag_name = try mod.identifierTokenStringTreeArena(
scope,
name_token,
tree,
arena,
);
const gop = fields_map.getOrPutAssumeCapacity(tag_name);
if (gop.found_existing) {
const msg = msg: {
const msg = try mod.errMsg(
scope,
gz.tokSrcLoc(name_token),
"duplicate enum tag",
.{},
);
errdefer msg.destroy(gpa);
// Iterate to find the other tag. We don't eagerly store it in a hash
// map because in the hot path there will be no compile error and we
// don't need to waste time with a hash map.
const bad_node = for (container_decl.ast.members) |other_member_node| {
const other_member = switch (node_tags[other_member_node]) {
.container_field_init => tree.containerFieldInit(other_member_node),
.container_field_align => tree.containerFieldAlign(other_member_node),
.container_field => tree.containerField(other_member_node),
else => unreachable, // We checked earlier.
};
const other_tag_name = try mod.identifierTokenStringTreeArena(
scope,
other_member.ast.name_token,
tree,
arena,
);
if (mem.eql(u8, tag_name, other_tag_name))
break other_member_node;
} else unreachable;
const other_src = gz.nodeSrcLoc(bad_node);
try mod.errNote(scope, other_src, msg, "other tag here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(scope, msg);
}
}
const enum_simple = try arena.create(Module.EnumSimple);
enum_simple.* = .{
.owner_decl = astgen.decl,
.node_offset = astgen.decl.nodeIndexToRelative(node),
.fields = fields_map,
};
const enum_ty = try Type.Tag.enum_simple.create(arena, enum_simple);
const enum_val = try Value.Tag.ty.create(arena, enum_ty);
const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{
.ty = Type.initTag(.type),
.val = enum_val,
});
const decl_index = try mod.declareDeclDependency(astgen.decl, new_decl);
const result = try gz.addDecl(.decl_val, decl_index, node);
return rvalue(gz, scope, rl, result, node);
}
if (mod.lookupIdentifier(scope, ident_name)) |decl| {
const msg = msg: {
const msg = try mod.errMsg(
scope,
name_src,
"redeclaration of '{s}'",
.{ident_name},
);
errdefer msg.destroy(gpa);
try mod.errNoteNonLazy(decl.srcLoc(), msg, "previously declared here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(scope, msg);
}
var type_changed = true;
if (decl.typedValueManaged()) |tvm| {
type_changed = !tvm.typed_value.ty.eql(var_info.ty);
tvm.deinit(mod.gpa);
}
const new_variable = try decl_arena.allocator.create(Var);
new_variable.* = .{
.owner_decl = decl,
.init = var_info.val orelse undefined,
.is_extern = is_extern,
.is_mutable = is_mutable,
.is_threadlocal = is_threadlocal,
};
const var_val = try Value.Tag.variable.create(&decl_arena.allocator, new_variable);
decl_arena_state.* = decl_arena.state;
decl.typed_value = .{
.most_recent = .{
.typed_value = .{
.ty = var_info.ty,
.val = var_val,
},
.arena = decl_arena_state,
},
};
decl.analysis = .complete;
decl.generation = mod.generation;
if (is_mutable and !var_info.ty.isValidVarType(is_extern)) {
return mod.failTok(
&decl_scope.base,
var_decl.ast.mut_token,
"variable of type '{}' must be const",
.{var_info.ty},
);
}
if (var_decl.extern_export_token) |maybe_export_token| {
if (token_tags[maybe_export_token] == .keyword_export) {
const export_src = decl.tokSrcLoc(maybe_export_token);
const name_token = var_decl.ast.mut_token + 1;
const name = tree.tokenSlice(name_token); // TODO identifierTokenString
// The scope needs to have the decl in it.
try mod.analyzeExport(&decl_scope.base, export_src, name, decl);
}
}
fn writeFuncExtra(
self: *Writer,
stream: anytype,
inst: Inst.Index,
var_args: bool,
) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
const extra = self.code.extraData(Inst.FuncExtra, inst_data.payload_index);
const param_types = self.code.refSlice(extra.end, extra.data.param_types_len);
const cc = extra.data.cc;
const body = self.code.extra[extra.end + param_types.len ..][0..extra.data.body_len];
return self.writeFuncCommon(
stream,
param_types,
extra.data.return_type,
var_args,
cc,
body,
src,
);
}
const error_set = try arena.create(Module.ErrorSet);
error_set.* = .{
.owner_decl = astgen.decl,
.node_offset = astgen.decl.nodeIndexToRelative(node),
.names_ptr = fields.ptr,
.names_len = @intCast(u32, fields.len),
};
const error_set_ty = try Type.Tag.error_set.create(arena, error_set);
const error_set_val = try Value.Tag.ty.create(arena, error_set_ty);
const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{
.ty = Type.initTag(.type),
.val = error_set_val,
});
const decl_index = try mod.declareDeclDependency(astgen.decl, new_decl);
const result = try gz.addDecl(.decl_val, decl_index, node);
return rvalue(gz, scope, rl, result, node);
// when implementing this be sure to add test coverage for the asm return type
// not resolving into a type (the node_offset_asm_ret_ty field of LazySrcLoc)