const std = @import("std"); const builtin = @import("builtin"); const Allocator = mem.Allocator; const Decl = @import("decl.zig").Decl; const Compilation = @import("compilation.zig").Compilation; const mem = std.mem; const ast = std.zig.ast; const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const ir = @import("ir.zig"); const Span = @import("errmsg.zig").Span; const assert = std.debug.assert; const event = std.event; const llvm = @import("llvm.zig"); pub const Scope = struct { id: Id, parent: ?*Scope, ref_count: std.atomic.Int(usize), /// Thread-safe pub fn ref(base: *Scope) void { _ = base.ref_count.incr(); } /// Thread-safe pub fn deref(base: *Scope, comp: *Compilation) void { if (base.ref_count.decr() == 1) { if (base.parent) |parent| parent.deref(comp); switch (base.id) { Id.Root => @fieldParentPtr(Root, "base", base).destroy(comp), Id.Decls => @fieldParentPtr(Decls, "base", base).destroy(comp), Id.Block => @fieldParentPtr(Block, "base", base).destroy(comp), Id.FnDef => @fieldParentPtr(FnDef, "base", base).destroy(comp), Id.CompTime => @fieldParentPtr(CompTime, "base", base).destroy(comp), Id.Defer => @fieldParentPtr(Defer, "base", base).destroy(comp), Id.DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(comp), Id.Var => @fieldParentPtr(Var, "base", base).destroy(comp), Id.AstTree => @fieldParentPtr(AstTree, "base", base).destroy(comp), } } } pub fn findRoot(base: *Scope) *Root { var scope = base; while (scope.parent) |parent| { scope = parent; } assert(scope.id == Id.Root); return @fieldParentPtr(Root, "base", scope); } pub fn findFnDef(base: *Scope) ?*FnDef { var scope = base; while (true) { switch (scope.id) { Id.FnDef => return @fieldParentPtr(FnDef, "base", scope), Id.Root, Id.Decls => return null, Id.Block, Id.Defer, Id.DeferExpr, Id.CompTime, Id.Var, => scope = scope.parent.?, Id.AstTree => unreachable, } } } pub fn findDeferExpr(base: *Scope) ?*DeferExpr { var scope = base; while (true) { switch (scope.id) { Id.DeferExpr => return @fieldParentPtr(DeferExpr, "base", scope), Id.FnDef, Id.Decls, => return null, Id.Block, Id.Defer, Id.CompTime, Id.Root, Id.Var, => scope = scope.parent orelse return null, Id.AstTree => unreachable, } } } fn init(base: *Scope, id: Id, parent: *Scope) void { base.* = Scope{ .id = id, .parent = parent, .ref_count = std.atomic.Int(usize).init(1), }; parent.ref(); } pub const Id = enum { Root, AstTree, Decls, Block, FnDef, CompTime, Defer, DeferExpr, Var, }; pub const Root = struct { base: Scope, realpath: []const u8, decls: *Decls, /// Creates a Root scope with 1 reference /// Takes ownership of realpath pub fn create(comp: *Compilation, realpath: []u8) !*Root { const self = try comp.gpa().create(Root); self.* = Root{ .base = Scope{ .id = Id.Root, .parent = null, .ref_count = std.atomic.Int(usize).init(1), }, .realpath = realpath, .decls = undefined, }; errdefer comp.gpa().destroy(self); self.decls = try Decls.create(comp, &self.base); return self; } pub fn destroy(self: *Root, comp: *Compilation) void { // TODO comp.fs_watch.removeFile(self.realpath); self.decls.base.deref(comp); comp.gpa().free(self.realpath); comp.gpa().destroy(self); } }; pub const AstTree = struct { base: Scope, tree: *ast.Tree, /// Creates a scope with 1 reference /// Takes ownership of tree, will deinit and destroy when done. pub fn create(comp: *Compilation, tree: *ast.Tree, root_scope: *Root) !*AstTree { const self = try comp.gpa().create(AstTree); self.* = AstTree{ .base = undefined, .tree = tree, }; self.base.init(Id.AstTree, &root_scope.base); return self; } pub fn destroy(self: *AstTree, comp: *Compilation) void { comp.gpa().free(self.tree.source); self.tree.deinit(); comp.gpa().destroy(self.tree); comp.gpa().destroy(self); } pub fn root(self: *AstTree) *Root { return self.base.findRoot(); } }; pub const Decls = struct { base: Scope, /// This table remains Write Locked when the names are incomplete or possibly outdated. /// So if a reader manages to grab a lock, it can be sure that the set of names is complete /// and correct. table: event.RwLocked(Decl.Table), /// Creates a Decls scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope) !*Decls { const self = try comp.gpa().create(Decls); self.* = Decls{ .base = undefined, .table = event.RwLocked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())), }; self.base.init(Id.Decls, parent); return self; } pub fn destroy(self: *Decls, comp: *Compilation) void { self.table.deinit(); comp.gpa().destroy(self); } }; pub const Block = struct { base: Scope, incoming_values: std.ArrayList(*ir.Inst), incoming_blocks: std.ArrayList(*ir.BasicBlock), end_block: *ir.BasicBlock, is_comptime: *ir.Inst, safety: Safety, const Safety = union(enum) { Auto, Manual: Manual, const Manual = struct { /// the source span that disabled the safety value span: Span, /// whether safety is enabled enabled: bool, }; fn get(self: Safety, comp: *Compilation) bool { return switch (self) { Safety.Auto => switch (comp.build_mode) { builtin.Mode.Debug, builtin.Mode.ReleaseSafe, => true, builtin.Mode.ReleaseFast, builtin.Mode.ReleaseSmall, => false, }, @TagType(Safety).Manual => |man| man.enabled, }; } }; /// Creates a Block scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope) !*Block { const self = try comp.gpa().create(Block); self.* = Block{ .base = undefined, .incoming_values = undefined, .incoming_blocks = undefined, .end_block = undefined, .is_comptime = undefined, .safety = Safety.Auto, }; self.base.init(Id.Block, parent); return self; } pub fn destroy(self: *Block, comp: *Compilation) void { comp.gpa().destroy(self); } }; pub const FnDef = struct { base: Scope, /// This reference is not counted so that the scope can get destroyed with the function fn_val: ?*Value.Fn, /// Creates a FnDef scope with 1 reference /// Must set the fn_val later pub fn create(comp: *Compilation, parent: *Scope) !*FnDef { const self = try comp.gpa().create(FnDef); self.* = FnDef{ .base = undefined, .fn_val = null, }; self.base.init(Id.FnDef, parent); return self; } pub fn destroy(self: *FnDef, comp: *Compilation) void { comp.gpa().destroy(self); } }; pub const CompTime = struct { base: Scope, /// Creates a CompTime scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope) !*CompTime { const self = try comp.gpa().create(CompTime); self.* = CompTime{ .base = undefined }; self.base.init(Id.CompTime, parent); return self; } pub fn destroy(self: *CompTime, comp: *Compilation) void { comp.gpa().destroy(self); } }; pub const Defer = struct { base: Scope, defer_expr_scope: *DeferExpr, kind: Kind, pub const Kind = enum { ScopeExit, ErrorExit, }; /// Creates a Defer scope with 1 reference pub fn create( comp: *Compilation, parent: *Scope, kind: Kind, defer_expr_scope: *DeferExpr, ) !*Defer { const self = try comp.gpa().create(Defer); self.* = Defer{ .base = undefined, .defer_expr_scope = defer_expr_scope, .kind = kind, }; self.base.init(Id.Defer, parent); defer_expr_scope.base.ref(); return self; } pub fn destroy(self: *Defer, comp: *Compilation) void { self.defer_expr_scope.base.deref(comp); comp.gpa().destroy(self); } }; pub const DeferExpr = struct { base: Scope, expr_node: *ast.Node, reported_err: bool, /// Creates a DeferExpr scope with 1 reference pub fn create(comp: *Compilation, parent: *Scope, expr_node: *ast.Node) !*DeferExpr { const self = try comp.gpa().create(DeferExpr); self.* = DeferExpr{ .base = undefined, .expr_node = expr_node, .reported_err = false, }; self.base.init(Id.DeferExpr, parent); return self; } pub fn destroy(self: *DeferExpr, comp: *Compilation) void { comp.gpa().destroy(self); } }; pub const Var = struct { base: Scope, name: []const u8, src_node: *ast.Node, data: Data, pub const Data = union(enum) { Param: Param, Const: *Value, }; pub const Param = struct { index: usize, typ: *Type, llvm_value: llvm.ValueRef, }; pub fn createParam( comp: *Compilation, parent: *Scope, name: []const u8, src_node: *ast.Node, param_index: usize, param_type: *Type, ) !*Var { const self = try create(comp, parent, name, src_node); self.data = Data{ .Param = Param{ .index = param_index, .typ = param_type, .llvm_value = undefined, }, }; return self; } pub fn createConst( comp: *Compilation, parent: *Scope, name: []const u8, src_node: *ast.Node, value: *Value, ) !*Var { const self = try create(comp, parent, name, src_node); self.data = Data{ .Const = value }; value.ref(); return self; } fn create(comp: *Compilation, parent: *Scope, name: []const u8, src_node: *ast.Node) !*Var { const self = try comp.gpa().create(Var); self.* = Var{ .base = undefined, .name = name, .src_node = src_node, .data = undefined, }; self.base.init(Id.Var, parent); return self; } pub fn destroy(self: *Var, comp: *Compilation) void { switch (self.data) { Data.Param => {}, Data.Const => |value| value.deref(comp), } comp.gpa().destroy(self); } }; };