Commit 687bd92f9c

Andrew Kelley <superjoe30@gmail.com>
2018-07-12 21:08:40
self-hosted: generate zig IR for simple function
no tests for this yet. I think the quickest path to testing will be creating the .o files and linking with libc, executing, and then comparing output.
1 parent ce11d6d
src-self-hosted/decl.zig
@@ -0,0 +1,96 @@
+const std = @import("std");
+const Allocator = mem.Allocator;
+const mem = std.mem;
+const ast = std.zig.ast;
+const Visib = @import("visib.zig").Visib;
+const ParsedFile = @import("parsed_file.zig").ParsedFile;
+const event = std.event;
+const Value = @import("value.zig").Value;
+const Token = std.zig.Token;
+const errmsg = @import("errmsg.zig");
+const Scope = @import("scope.zig").Scope;
+const Module = @import("module.zig").Module;
+
+pub const Decl = struct {
+    id: Id,
+    name: []const u8,
+    visib: Visib,
+    resolution: event.Future(Module.BuildError!void),
+    resolution_in_progress: u8,
+    parsed_file: *ParsedFile,
+    parent_scope: *Scope,
+
+    pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8);
+
+    pub fn isExported(base: *const Decl, tree: *ast.Tree) bool {
+        switch (base.id) {
+            Id.Fn => {
+                const fn_decl = @fieldParentPtr(Fn, "base", base);
+                return fn_decl.isExported(tree);
+            },
+            else => return false,
+        }
+    }
+
+    pub fn getSpan(base: *const Decl) errmsg.Span {
+        switch (base.id) {
+            Id.Fn => {
+                const fn_decl = @fieldParentPtr(Fn, "base", base);
+                const fn_proto = fn_decl.fn_proto;
+                const start = fn_proto.fn_token;
+                const end = fn_proto.name_token orelse start;
+                return errmsg.Span{
+                    .first = start,
+                    .last = end + 1,
+                };
+            },
+            else => @panic("TODO"),
+        }
+    }
+
+    pub const Id = enum {
+        Var,
+        Fn,
+        CompTime,
+    };
+
+    pub const Var = struct {
+        base: Decl,
+    };
+
+    pub const Fn = struct {
+        base: Decl,
+        value: Val,
+        fn_proto: *const ast.Node.FnProto,
+
+        // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous
+        pub const Val = union {
+            Unresolved: void,
+            Ok: *Value.Fn,
+        };
+
+        pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 {
+            return if (self.fn_proto.extern_export_inline_token) |tok_index| x: {
+                const token = tree.tokens.at(tok_index);
+                break :x switch (token.id) {
+                    Token.Id.Extern => tree.tokenSlicePtr(token),
+                    else => null,
+                };
+            } else null;
+        }
+
+        pub fn isExported(self: Fn, tree: *ast.Tree) bool {
+            if (self.fn_proto.extern_export_inline_token) |tok_index| {
+                const token = tree.tokens.at(tok_index);
+                return token.id == Token.Id.Keyword_export;
+            } else {
+                return false;
+            }
+        }
+    };
+
+    pub const CompTime = struct {
+        base: Decl,
+    };
+};
+
src-self-hosted/ir.zig
@@ -1,111 +1,656 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const Module = @import("module.zig").Module;
 const Scope = @import("scope.zig").Scope;
+const ast = std.zig.ast;
+const Allocator = std.mem.Allocator;
+const Value = @import("value.zig").Value;
+const Type = Value.Type;
+const assert = std.debug.assert;
+const Token = std.zig.Token;
+const ParsedFile = @import("parsed_file.zig").ParsedFile;
+
+pub const LVal = enum {
+    None,
+    Ptr,
+};
+
+pub const Mut = enum {
+    Mut,
+    Const,
+};
+
+pub const Volatility = enum {
+    NonVolatile,
+    Volatile,
+};
+
+pub const IrVal = union(enum) {
+    Unknown,
+    Known: *Value,
+
+    pub fn dump(self: IrVal) void {
+        switch (self) {
+            IrVal.Unknown => std.debug.warn("Unknown"),
+            IrVal.Known => |value| {
+                std.debug.warn("Known(");
+                value.dump();
+                std.debug.warn(")");
+            },
+        }
+    }
+};
 
 pub const Instruction = struct {
     id: Id,
     scope: *Scope,
+    debug_id: usize,
+    val: IrVal,
+
+    /// true if this instruction was generated by zig and not from user code
+    is_generated: bool,
+
+    pub fn cast(base: *Instruction, comptime T: type) ?*T {
+        if (base.id == comptime typeToId(T)) {
+            return @fieldParentPtr(T, "base", base);
+        }
+        return null;
+    }
+
+    pub fn typeToId(comptime T: type) Id {
+        comptime var i = 0;
+        inline while (i < @memberCount(Id)) : (i += 1) {
+            if (T == @field(Instruction, @memberName(Id, i))) {
+                return @field(Id, @memberName(Id, i));
+            }
+        }
+        unreachable;
+    }
+
+    pub fn dump(base: *const Instruction) void {
+        comptime var i = 0;
+        inline while (i < @memberCount(Id)) : (i += 1) {
+            if (base.id == @field(Id, @memberName(Id, i))) {
+                const T = @field(Instruction, @memberName(Id, i));
+                std.debug.warn("#{} = {}(", base.debug_id, @tagName(base.id));
+                @fieldParentPtr(T, "base", base).dump();
+                std.debug.warn(")");
+                return;
+            }
+        }
+        unreachable;
+    }
+
+    pub fn setGenerated(base: *Instruction) void {
+        base.is_generated = true;
+    }
+
+    pub fn isNoReturn(base: *const Instruction) bool {
+        switch (base.val) {
+            IrVal.Unknown => return false,
+            IrVal.Known => |x| return x.typeof.id == Type.Id.NoReturn,
+        }
+    }
 
     pub const Id = enum {
-        Br,
-        CondBr,
-        SwitchBr,
-        SwitchVar,
-        SwitchTarget,
-        Phi,
-        UnOp,
-        BinOp,
-        DeclVar,
-        LoadPtr,
-        StorePtr,
-        FieldPtr,
-        StructFieldPtr,
-        UnionFieldPtr,
-        ElemPtr,
-        VarPtr,
-        Call,
-        Const,
         Return,
-        Cast,
-        ContainerInitList,
-        ContainerInitFields,
-        StructInit,
-        UnionInit,
-        Unreachable,
-        TypeOf,
-        ToPtrType,
-        PtrTypeChild,
-        SetRuntimeSafety,
-        SetFloatMode,
-        ArrayType,
-        SliceType,
-        Asm,
-        SizeOf,
-        TestNonNull,
-        UnwrapMaybe,
-        MaybeWrap,
-        UnionTag,
-        Clz,
-        Ctz,
-        Import,
-        CImport,
-        CInclude,
-        CDefine,
-        CUndef,
-        ArrayLen,
+        Const,
         Ref,
-        MinValue,
-        MaxValue,
-        CompileErr,
-        CompileLog,
-        ErrName,
-        EmbedFile,
-        Cmpxchg,
-        Fence,
-        Truncate,
-        IntType,
-        BoolNot,
-        Memset,
-        Memcpy,
-        Slice,
-        MemberCount,
-        MemberType,
-        MemberName,
-        Breakpoint,
-        ReturnAddress,
-        FrameAddress,
-        AlignOf,
-        OverflowOp,
-        TestErr,
-        UnwrapErrCode,
-        UnwrapErrPayload,
-        ErrWrapCode,
-        ErrWrapPayload,
-        FnProto,
-        TestComptime,
-        PtrCast,
-        BitCast,
-        WidenOrShorten,
-        IntToPtr,
-        PtrToInt,
-        IntToEnum,
-        IntToErr,
-        ErrToInt,
-        CheckSwitchProngs,
-        CheckStatementIsVoid,
-        TypeName,
-        CanImplicitCast,
-        DeclRef,
-        Panic,
-        TagName,
-        TagType,
-        FieldParentPtr,
-        OffsetOf,
-        TypeId,
-        SetEvalBranchQuota,
-        PtrTypeOf,
-        AlignCast,
-        OpaqueType,
-        SetAlignStack,
-        ArgType,
-        Export,
+        DeclVar,
+        CheckVoidStmt,
+        Phi,
+        Br,
+    };
+
+    pub const Const = struct {
+        base: Instruction,
+
+        pub fn buildBool(irb: *Builder, scope: *Scope, val: bool) !*Instruction {
+            const inst = try irb.arena().create(Const{
+                .base = Instruction{
+                    .id = Instruction.Id.Const,
+                    .is_generated = false,
+                    .scope = scope,
+                    .debug_id = irb.next_debug_id,
+                    .val = IrVal{ .Known = &Value.Bool.get(irb.module, val).base },
+                },
+            });
+            irb.next_debug_id += 1;
+            try irb.current_basic_block.instruction_list.append(&inst.base);
+            return &inst.base;
+        }
+
+        pub fn buildVoid(irb: *Builder, scope: *Scope, is_generated: bool) !*Instruction {
+            const inst = try irb.arena().create(Const{
+                .base = Instruction{
+                    .id = Instruction.Id.Const,
+                    .is_generated = is_generated,
+                    .scope = scope,
+                    .debug_id = irb.next_debug_id,
+                    .val = IrVal{ .Known = &Value.Void.get(irb.module).base },
+                },
+            });
+            irb.next_debug_id += 1;
+            try irb.current_basic_block.instruction_list.append(&inst.base);
+            return &inst.base;
+        }
+
+        pub fn dump(inst: *const Const) void {
+            inst.base.val.Known.dump();
+        }
+    };
+
+    pub const Return = struct {
+        base: Instruction,
+        return_value: *Instruction,
+
+        pub fn build(irb: *Builder, scope: *Scope, return_value: *Instruction) !*Instruction {
+            const inst = try irb.arena().create(Return{
+                .base = Instruction{
+                    .id = Instruction.Id.Return,
+                    .is_generated = false,
+                    .scope = scope,
+                    .debug_id = irb.next_debug_id,
+                    .val = IrVal{ .Known = &Value.Void.get(irb.module).base },
+                },
+                .return_value = return_value,
+            });
+            irb.next_debug_id += 1;
+            try irb.current_basic_block.instruction_list.append(&inst.base);
+            return &inst.base;
+        }
+
+        pub fn dump(inst: *const Return) void {
+            std.debug.warn("#{}", inst.return_value.debug_id);
+        }
+    };
+
+    pub const Ref = struct {
+        base: Instruction,
+        target: *Instruction,
+        mut: Mut,
+        volatility: Volatility,
+
+        pub fn build(
+            irb: *Builder,
+            scope: *Scope,
+            target: *Instruction,
+            mut: Mut,
+            volatility: Volatility,
+        ) !*Instruction {
+            const inst = try irb.arena().create(Ref{
+                .base = Instruction{
+                    .id = Instruction.Id.Ref,
+                    .is_generated = false,
+                    .scope = scope,
+                    .debug_id = irb.next_debug_id,
+                    .val = IrVal.Unknown,
+                },
+                .target = target,
+                .mut = mut,
+                .volatility = volatility,
+            });
+            irb.next_debug_id += 1;
+            try irb.current_basic_block.instruction_list.append(&inst.base);
+            return &inst.base;
+        }
+
+        pub fn dump(inst: *const Ref) void {}
+    };
+
+    pub const DeclVar = struct {
+        base: Instruction,
+        variable: *Variable,
+
+        pub fn dump(inst: *const DeclVar) void {}
+    };
+
+    pub const CheckVoidStmt = struct {
+        base: Instruction,
+        target: *Instruction,
+
+        pub fn build(
+            irb: *Builder,
+            scope: *Scope,
+            target: *Instruction,
+        ) !*Instruction {
+            const inst = try irb.arena().create(CheckVoidStmt{
+                .base = Instruction{
+                    .id = Instruction.Id.CheckVoidStmt,
+                    .is_generated = true,
+                    .scope = scope,
+                    .debug_id = irb.next_debug_id,
+                    .val = IrVal{ .Known = &Value.Void.get(irb.module).base },
+                },
+                .target = target,
+            });
+            irb.next_debug_id += 1;
+            try irb.current_basic_block.instruction_list.append(&inst.base);
+            return &inst.base;
+        }
+
+        pub fn dump(inst: *const CheckVoidStmt) void {}
+    };
+
+    pub const Phi = struct {
+        base: Instruction,
+        incoming_blocks: []*BasicBlock,
+        incoming_values: []*Instruction,
+
+        pub fn build(
+            irb: *Builder,
+            scope: *Scope,
+            incoming_blocks: []*BasicBlock,
+            incoming_values: []*Instruction,
+        ) !*Instruction {
+            const inst = try irb.arena().create(Phi{
+                .base = Instruction{
+                    .id = Instruction.Id.Phi,
+                    .is_generated = false,
+                    .scope = scope,
+                    .debug_id = irb.next_debug_id,
+                    .val = IrVal.Unknown,
+                },
+                .incoming_blocks = incoming_blocks,
+                .incoming_values = incoming_values,
+            });
+            irb.next_debug_id += 1;
+            try irb.current_basic_block.instruction_list.append(&inst.base);
+            return &inst.base;
+        }
+
+        pub fn dump(inst: *const Phi) void {}
+    };
+
+    pub const Br = struct {
+        base: Instruction,
+        dest_block: *BasicBlock,
+        is_comptime: *Instruction,
+
+        pub fn build(
+            irb: *Builder,
+            scope: *Scope,
+            dest_block: *BasicBlock,
+            is_comptime: *Instruction,
+        ) !*Instruction {
+            const inst = try irb.arena().create(Br{
+                .base = Instruction{
+                    .id = Instruction.Id.Br,
+                    .is_generated = false,
+                    .scope = scope,
+                    .debug_id = irb.next_debug_id,
+                    .val = IrVal{ .Known = &Value.NoReturn.get(irb.module).base },
+                },
+                .dest_block = dest_block,
+                .is_comptime = is_comptime,
+            });
+            irb.next_debug_id += 1;
+            try irb.current_basic_block.instruction_list.append(&inst.base);
+            return &inst.base;
+        }
+
+        pub fn dump(inst: *const Br) void {}
     };
 };
+
+pub const Variable = struct {
+    child_scope: *Scope,
+};
+
+pub const BasicBlock = struct {
+    ref_count: usize,
+    name_hint: []const u8,
+    debug_id: usize,
+    scope: *Scope,
+    instruction_list: std.ArrayList(*Instruction),
+
+    pub fn ref(self: *BasicBlock) void {
+        self.ref_count += 1;
+    }
+};
+
+/// Stuff that survives longer than Builder
+pub const Code = struct {
+    basic_block_list: std.ArrayList(*BasicBlock),
+    arena: std.heap.ArenaAllocator,
+
+    /// allocator is module.a()
+    pub fn destroy(self: *Code, allocator: *Allocator) void {
+        self.arena.deinit();
+        allocator.destroy(self);
+    }
+
+    pub fn dump(self: *Code) void {
+        var bb_i: usize = 0;
+        for (self.basic_block_list.toSliceConst()) |bb| {
+            std.debug.warn("{}_{}:\n", bb.name_hint, bb.debug_id);
+            for (bb.instruction_list.toSliceConst()) |instr| {
+                std.debug.warn("  ");
+                instr.dump();
+                std.debug.warn("\n");
+            }
+        }
+    }
+};
+
+pub const Builder = struct {
+    module: *Module,
+    code: *Code,
+    current_basic_block: *BasicBlock,
+    next_debug_id: usize,
+    parsed_file: *ParsedFile,
+    is_comptime: bool,
+
+    pub const Error = error{
+        OutOfMemory,
+        Unimplemented,
+    };
+
+    pub fn init(module: *Module, parsed_file: *ParsedFile) !Builder {
+        const code = try module.a().create(Code{
+            .basic_block_list = undefined,
+            .arena = std.heap.ArenaAllocator.init(module.a()),
+        });
+        code.basic_block_list = std.ArrayList(*BasicBlock).init(&code.arena.allocator);
+        errdefer code.destroy(module.a());
+
+        return Builder{
+            .module = module,
+            .parsed_file = parsed_file,
+            .current_basic_block = undefined,
+            .code = code,
+            .next_debug_id = 0,
+            .is_comptime = false,
+        };
+    }
+
+    pub fn abort(self: *Builder) void {
+        self.code.destroy(self.module.a());
+    }
+
+    /// Call code.destroy() when done
+    pub fn finish(self: *Builder) *Code {
+        return self.code;
+    }
+
+    /// No need to clean up resources thanks to the arena allocator.
+    pub fn createBasicBlock(self: *Builder, scope: *Scope, name_hint: []const u8) !*BasicBlock {
+        const basic_block = try self.arena().create(BasicBlock{
+            .ref_count = 0,
+            .name_hint = name_hint,
+            .debug_id = self.next_debug_id,
+            .scope = scope,
+            .instruction_list = std.ArrayList(*Instruction).init(self.arena()),
+        });
+        self.next_debug_id += 1;
+        return basic_block;
+    }
+
+    pub fn setCursorAtEndAndAppendBlock(self: *Builder, basic_block: *BasicBlock) !void {
+        try self.code.basic_block_list.append(basic_block);
+        self.setCursorAtEnd(basic_block);
+    }
+
+    pub fn setCursorAtEnd(self: *Builder, basic_block: *BasicBlock) void {
+        self.current_basic_block = basic_block;
+    }
+
+    pub fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Instruction {
+        switch (node.id) {
+            ast.Node.Id.Root => unreachable,
+            ast.Node.Id.Use => unreachable,
+            ast.Node.Id.TestDecl => unreachable,
+            ast.Node.Id.VarDecl => @panic("TODO"),
+            ast.Node.Id.Defer => @panic("TODO"),
+            ast.Node.Id.InfixOp => @panic("TODO"),
+            ast.Node.Id.PrefixOp => @panic("TODO"),
+            ast.Node.Id.SuffixOp => @panic("TODO"),
+            ast.Node.Id.Switch => @panic("TODO"),
+            ast.Node.Id.While => @panic("TODO"),
+            ast.Node.Id.For => @panic("TODO"),
+            ast.Node.Id.If => @panic("TODO"),
+            ast.Node.Id.ControlFlowExpression => return error.Unimplemented,
+            ast.Node.Id.Suspend => @panic("TODO"),
+            ast.Node.Id.VarType => @panic("TODO"),
+            ast.Node.Id.ErrorType => @panic("TODO"),
+            ast.Node.Id.FnProto => @panic("TODO"),
+            ast.Node.Id.PromiseType => @panic("TODO"),
+            ast.Node.Id.IntegerLiteral => @panic("TODO"),
+            ast.Node.Id.FloatLiteral => @panic("TODO"),
+            ast.Node.Id.StringLiteral => @panic("TODO"),
+            ast.Node.Id.MultilineStringLiteral => @panic("TODO"),
+            ast.Node.Id.CharLiteral => @panic("TODO"),
+            ast.Node.Id.BoolLiteral => @panic("TODO"),
+            ast.Node.Id.NullLiteral => @panic("TODO"),
+            ast.Node.Id.UndefinedLiteral => @panic("TODO"),
+            ast.Node.Id.ThisLiteral => @panic("TODO"),
+            ast.Node.Id.Unreachable => @panic("TODO"),
+            ast.Node.Id.Identifier => @panic("TODO"),
+            ast.Node.Id.GroupedExpression => {
+                const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", node);
+                return irb.genNode(grouped_expr.expr, scope, lval);
+            },
+            ast.Node.Id.BuiltinCall => @panic("TODO"),
+            ast.Node.Id.ErrorSetDecl => @panic("TODO"),
+            ast.Node.Id.ContainerDecl => @panic("TODO"),
+            ast.Node.Id.Asm => @panic("TODO"),
+            ast.Node.Id.Comptime => @panic("TODO"),
+            ast.Node.Id.Block => {
+                const block = @fieldParentPtr(ast.Node.Block, "base", node);
+                return irb.lvalWrap(scope, try irb.genBlock(block, scope), lval);
+            },
+            ast.Node.Id.DocComment => @panic("TODO"),
+            ast.Node.Id.SwitchCase => @panic("TODO"),
+            ast.Node.Id.SwitchElse => @panic("TODO"),
+            ast.Node.Id.Else => @panic("TODO"),
+            ast.Node.Id.Payload => @panic("TODO"),
+            ast.Node.Id.PointerPayload => @panic("TODO"),
+            ast.Node.Id.PointerIndexPayload => @panic("TODO"),
+            ast.Node.Id.StructField => @panic("TODO"),
+            ast.Node.Id.UnionTag => @panic("TODO"),
+            ast.Node.Id.EnumTag => @panic("TODO"),
+            ast.Node.Id.ErrorTag => @panic("TODO"),
+            ast.Node.Id.AsmInput => @panic("TODO"),
+            ast.Node.Id.AsmOutput => @panic("TODO"),
+            ast.Node.Id.AsyncAttribute => @panic("TODO"),
+            ast.Node.Id.ParamDecl => @panic("TODO"),
+            ast.Node.Id.FieldInitializer => @panic("TODO"),
+        }
+    }
+
+    fn isCompTime(irb: *Builder, target_scope: *Scope) bool {
+        if (irb.is_comptime)
+            return true;
+
+        var scope = target_scope;
+        while (true) {
+            switch (scope.id) {
+                Scope.Id.CompTime => return true,
+                Scope.Id.FnDef => return false,
+                Scope.Id.Decls => unreachable,
+                Scope.Id.Block,
+                Scope.Id.Defer,
+                Scope.Id.DeferExpr,
+                => scope = scope.parent orelse return false,
+            }
+        }
+    }
+
+    pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Instruction {
+        const block_scope = try Scope.Block.create(irb.module, parent_scope);
+
+        const outer_block_scope = &block_scope.base;
+        var child_scope = outer_block_scope;
+
+        if (parent_scope.findFnDef()) |fndef_scope| {
+            if (fndef_scope.fn_val.child_scope == parent_scope) {
+                fndef_scope.fn_val.block_scope = block_scope;
+            }
+        }
+
+        if (block.statements.len == 0) {
+            // {}
+            return Instruction.Const.buildVoid(irb, child_scope, false);
+        }
+
+        if (block.label) |label| {
+            block_scope.incoming_values = std.ArrayList(*Instruction).init(irb.arena());
+            block_scope.incoming_blocks = std.ArrayList(*BasicBlock).init(irb.arena());
+            block_scope.end_block = try irb.createBasicBlock(parent_scope, "BlockEnd");
+            block_scope.is_comptime = try Instruction.Const.buildBool(irb, parent_scope, irb.isCompTime(parent_scope));
+        }
+
+        var is_continuation_unreachable = false;
+        var noreturn_return_value: ?*Instruction = null;
+
+        var stmt_it = block.statements.iterator(0);
+        while (stmt_it.next()) |statement_node_ptr| {
+            const statement_node = statement_node_ptr.*;
+
+            if (statement_node.cast(ast.Node.Defer)) |defer_node| {
+                // defer starts a new scope
+                const defer_token = irb.parsed_file.tree.tokens.at(defer_node.defer_token);
+                const kind = switch (defer_token.id) {
+                    Token.Id.Keyword_defer => Scope.Defer.Kind.ScopeExit,
+                    Token.Id.Keyword_errdefer => Scope.Defer.Kind.ErrorExit,
+                    else => unreachable,
+                };
+                const defer_expr_scope = try Scope.DeferExpr.create(irb.module, parent_scope, defer_node.expr);
+                const defer_child_scope = try Scope.Defer.create(irb.module, parent_scope, kind, defer_expr_scope);
+                child_scope = &defer_child_scope.base;
+                continue;
+            }
+            const statement_value = try irb.genNode(statement_node, child_scope, LVal.None);
+
+            is_continuation_unreachable = statement_value.isNoReturn();
+            if (is_continuation_unreachable) {
+                // keep the last noreturn statement value around in case we need to return it
+                noreturn_return_value = statement_value;
+            }
+
+            if (statement_value.cast(Instruction.DeclVar)) |decl_var| {
+                // variable declarations start a new scope
+                child_scope = decl_var.variable.child_scope;
+            } else if (!is_continuation_unreachable) {
+                // this statement's value must be void
+                _ = Instruction.CheckVoidStmt.build(irb, child_scope, statement_value);
+            }
+        }
+
+        if (is_continuation_unreachable) {
+            assert(noreturn_return_value != null);
+            if (block.label == null or block_scope.incoming_blocks.len == 0) {
+                return noreturn_return_value.?;
+            }
+
+            try irb.setCursorAtEndAndAppendBlock(block_scope.end_block);
+            return Instruction.Phi.build(
+                irb,
+                parent_scope,
+                block_scope.incoming_blocks.toOwnedSlice(),
+                block_scope.incoming_values.toOwnedSlice(),
+            );
+        }
+
+        if (block.label) |label| {
+            try block_scope.incoming_blocks.append(irb.current_basic_block);
+            try block_scope.incoming_values.append(
+                try Instruction.Const.buildVoid(irb, parent_scope, true),
+            );
+            _ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit);
+            (try Instruction.Br.build(
+                irb,
+                parent_scope,
+                block_scope.end_block,
+                block_scope.is_comptime,
+            )).setGenerated();
+            try irb.setCursorAtEndAndAppendBlock(block_scope.end_block);
+            return Instruction.Phi.build(
+                irb,
+                parent_scope,
+                block_scope.incoming_blocks.toOwnedSlice(),
+                block_scope.incoming_values.toOwnedSlice(),
+            );
+        }
+
+        _ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit);
+        const result = try Instruction.Const.buildVoid(irb, child_scope, false);
+        result.setGenerated();
+        return result;
+    }
+
+    fn genDefersForBlock(
+        irb: *Builder,
+        inner_scope: *Scope,
+        outer_scope: *Scope,
+        gen_kind: Scope.Defer.Kind,
+    ) !bool {
+        var scope = inner_scope;
+        var is_noreturn = false;
+        while (true) {
+            switch (scope.id) {
+                Scope.Id.Defer => {
+                    const defer_scope = @fieldParentPtr(Scope.Defer, "base", scope);
+                    const generate = switch (defer_scope.kind) {
+                        Scope.Defer.Kind.ScopeExit => true,
+                        Scope.Defer.Kind.ErrorExit => gen_kind == Scope.Defer.Kind.ErrorExit,
+                    };
+                    if (generate) {
+                        const defer_expr_scope = defer_scope.defer_expr_scope;
+                        const instruction = try irb.genNode(
+                            defer_expr_scope.expr_node,
+                            &defer_expr_scope.base,
+                            LVal.None,
+                        );
+                        if (instruction.isNoReturn()) {
+                            is_noreturn = true;
+                        } else {
+                            _ = Instruction.CheckVoidStmt.build(irb, &defer_expr_scope.base, instruction);
+                        }
+                    }
+                },
+                Scope.Id.FnDef,
+                Scope.Id.Decls,
+                => return is_noreturn,
+
+                Scope.Id.CompTime,
+                Scope.Id.Block,
+                => scope = scope.parent orelse return is_noreturn,
+
+                Scope.Id.DeferExpr => unreachable,
+            }
+        }
+    }
+
+    pub fn lvalWrap(irb: *Builder, scope: *Scope, instruction: *Instruction, lval: LVal) !*Instruction {
+        switch (lval) {
+            LVal.None => return instruction,
+            LVal.Ptr => {
+                // We needed a pointer to a value, but we got a value. So we create
+                // an instruction which just makes a const pointer of it.
+                return Instruction.Ref.build(irb, scope, instruction, Mut.Const, Volatility.NonVolatile);
+            },
+        }
+    }
+
+    fn arena(self: *Builder) *Allocator {
+        return &self.code.arena.allocator;
+    }
+};
+
+pub async fn gen(module: *Module, body_node: *ast.Node, scope: *Scope, parsed_file: *ParsedFile) !*Code {
+    var irb = try Builder.init(module, parsed_file);
+    errdefer irb.abort();
+
+    const entry_block = try irb.createBasicBlock(scope, "Entry");
+    entry_block.ref(); // Entry block gets a reference because we enter it to begin.
+    try irb.setCursorAtEndAndAppendBlock(entry_block);
+
+    const result = try irb.genNode(body_node, scope, LVal.None);
+    if (!result.isNoReturn()) {
+        const void_inst = try Instruction.Const.buildVoid(&irb, scope, false);
+        (try Instruction.Return.build(&irb, scope, void_inst)).setGenerated();
+    }
+
+    return irb.finish();
+}
src-self-hosted/module.zig
@@ -15,12 +15,21 @@ const errmsg = @import("errmsg.zig");
 const ast = std.zig.ast;
 const event = std.event;
 const assert = std.debug.assert;
+const AtomicRmwOp = builtin.AtomicRmwOp;
+const AtomicOrder = builtin.AtomicOrder;
+const Scope = @import("scope.zig").Scope;
+const Decl = @import("decl.zig").Decl;
+const ir = @import("ir.zig");
+const Visib = @import("visib.zig").Visib;
+const ParsedFile = @import("parsed_file.zig").ParsedFile;
+const Value = @import("value.zig").Value;
+const Type = Value.Type;
 
 pub const Module = struct {
     loop: *event.Loop,
     name: Buffer,
     root_src_path: ?[]const u8,
-    module: llvm.ModuleRef,
+    llvm_module: llvm.ModuleRef,
     context: llvm.ContextRef,
     builder: llvm.BuilderRef,
     target: Target,
@@ -91,6 +100,16 @@ pub const Module = struct {
 
     compile_errors: event.Locked(CompileErrList),
 
+    meta_type: *Type.MetaType,
+    void_type: *Type.Void,
+    bool_type: *Type.Bool,
+    noreturn_type: *Type.NoReturn,
+
+    void_value: *Value.Void,
+    true_value: *Value.Bool,
+    false_value: *Value.Bool,
+    noreturn_value: *Value.NoReturn,
+
     const CompileErrList = std.ArrayList(*errmsg.Msg);
 
     // TODO handle some of these earlier and report them in a way other than error codes
@@ -129,6 +148,7 @@ pub const Module = struct {
         Overflow,
         NotSupported,
         BufferTooSmall,
+        Unimplemented,
     };
 
     pub const Event = union(enum) {
@@ -180,8 +200,8 @@ pub const Module = struct {
         const context = c.LLVMContextCreate() orelse return error.OutOfMemory;
         errdefer c.LLVMContextDispose(context);
 
-        const module = c.LLVMModuleCreateWithNameInContext(name_buffer.ptr(), context) orelse return error.OutOfMemory;
-        errdefer c.LLVMDisposeModule(module);
+        const llvm_module = c.LLVMModuleCreateWithNameInContext(name_buffer.ptr(), context) orelse return error.OutOfMemory;
+        errdefer c.LLVMDisposeModule(llvm_module);
 
         const builder = c.LLVMCreateBuilderInContext(context) orelse return error.OutOfMemory;
         errdefer c.LLVMDisposeBuilder(builder);
@@ -189,12 +209,12 @@ pub const Module = struct {
         const events = try event.Channel(Event).create(loop, 0);
         errdefer events.destroy();
 
-        return loop.allocator.create(Module{
+        const module = try loop.allocator.create(Module{
             .loop = loop,
             .events = events,
             .name = name_buffer,
             .root_src_path = root_src_path,
-            .module = module,
+            .llvm_module = llvm_module,
             .context = context,
             .builder = builder,
             .target = target.*,
@@ -248,7 +268,109 @@ pub const Module = struct {
             .exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)),
             .build_group = event.Group(BuildError!void).init(loop),
             .compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)),
+
+            .meta_type = undefined,
+            .void_type = undefined,
+            .void_value = undefined,
+            .bool_type = undefined,
+            .true_value = undefined,
+            .false_value = undefined,
+            .noreturn_type = undefined,
+            .noreturn_value = undefined,
+        });
+        try module.initTypes();
+        return module;
+    }
+
+    fn initTypes(module: *Module) !void {
+        module.meta_type = try module.a().create(Type.MetaType{
+            .base = Type{
+                .base = Value{
+                    .id = Value.Id.Type,
+                    .typeof = undefined,
+                    .ref_count = 3, // 3 because it references itself twice
+                },
+                .id = builtin.TypeId.Type,
+            },
+            .value = undefined,
+        });
+        module.meta_type.value = &module.meta_type.base;
+        module.meta_type.base.base.typeof = &module.meta_type.base;
+        errdefer module.a().destroy(module.meta_type);
+
+        module.void_type = try module.a().create(Type.Void{
+            .base = Type{
+                .base = Value{
+                    .id = Value.Id.Type,
+                    .typeof = &Type.MetaType.get(module).base,
+                    .ref_count = 1,
+                },
+                .id = builtin.TypeId.Void,
+            },
+        });
+        errdefer module.a().destroy(module.void_type);
+
+        module.noreturn_type = try module.a().create(Type.NoReturn{
+            .base = Type{
+                .base = Value{
+                    .id = Value.Id.Type,
+                    .typeof = &Type.MetaType.get(module).base,
+                    .ref_count = 1,
+                },
+                .id = builtin.TypeId.NoReturn,
+            },
+        });
+        errdefer module.a().destroy(module.noreturn_type);
+
+        module.bool_type = try module.a().create(Type.Bool{
+            .base = Type{
+                .base = Value{
+                    .id = Value.Id.Type,
+                    .typeof = &Type.MetaType.get(module).base,
+                    .ref_count = 1,
+                },
+                .id = builtin.TypeId.Bool,
+            },
+        });
+        errdefer module.a().destroy(module.bool_type);
+
+        module.void_value = try module.a().create(Value.Void{
+            .base = Value{
+                .id = Value.Id.Void,
+                .typeof = &Type.Void.get(module).base,
+                .ref_count = 1,
+            },
+        });
+        errdefer module.a().destroy(module.void_value);
+
+        module.true_value = try module.a().create(Value.Bool{
+            .base = Value{
+                .id = Value.Id.Bool,
+                .typeof = &Type.Bool.get(module).base,
+                .ref_count = 1,
+            },
+            .x = true,
+        });
+        errdefer module.a().destroy(module.true_value);
+
+        module.false_value = try module.a().create(Value.Bool{
+            .base = Value{
+                .id = Value.Id.Bool,
+                .typeof = &Type.Bool.get(module).base,
+                .ref_count = 1,
+            },
+            .x = false,
         });
+        errdefer module.a().destroy(module.false_value);
+
+        module.noreturn_value = try module.a().create(Value.NoReturn{
+            .base = Value{
+                .id = Value.Id.NoReturn,
+                .typeof = &Type.NoReturn.get(module).base,
+                .ref_count = 1,
+            },
+        });
+        errdefer module.a().destroy(module.noreturn_value);
     }
 
     fn dump(self: *Module) void {
@@ -256,9 +378,17 @@ pub const Module = struct {
     }
 
     pub fn destroy(self: *Module) void {
+        self.noreturn_value.base.deref(self);
+        self.void_value.base.deref(self);
+        self.false_value.base.deref(self);
+        self.true_value.base.deref(self);
+        self.noreturn_type.base.base.deref(self);
+        self.void_type.base.base.deref(self);
+        self.meta_type.base.base.deref(self);
+
         self.events.destroy();
         c.LLVMDisposeBuilder(self.builder);
-        c.LLVMDisposeModule(self.module);
+        c.LLVMDisposeModule(self.llvm_module);
         c.LLVMContextDispose(self.context);
         self.name.deinit();
 
@@ -331,8 +461,8 @@ pub const Module = struct {
         const tree = &parsed_file.tree;
 
         // create empty struct for it
-        const decls = try Scope.Decls.create(self.a(), null);
-        errdefer decls.destroy();
+        const decls = try Scope.Decls.create(self, null);
+        defer decls.base.deref(self);
 
         var decl_group = event.Group(BuildError!void).init(self.loop);
         errdefer decl_group.cancelAll();
@@ -359,14 +489,17 @@ pub const Module = struct {
                             .id = Decl.Id.Fn,
                             .name = name,
                             .visib = parseVisibToken(tree, fn_proto.visib_token),
-                            .resolution = Decl.Resolution.Unresolved,
+                            .resolution = event.Future(BuildError!void).init(self.loop),
+                            .resolution_in_progress = 0,
+                            .parsed_file = parsed_file,
+                            .parent_scope = &decls.base,
                         },
                         .value = Decl.Fn.Val{ .Unresolved = {} },
                         .fn_proto = fn_proto,
                     });
                     errdefer self.a().destroy(fn_decl);
 
-                    try decl_group.call(addTopLevelDecl, self, parsed_file, &fn_decl.base);
+                    try decl_group.call(addTopLevelDecl, self, &fn_decl.base);
                 },
                 ast.Node.Id.TestDecl => @panic("TODO"),
                 else => unreachable,
@@ -376,12 +509,12 @@ pub const Module = struct {
         try await (async self.build_group.wait() catch unreachable);
     }
 
-    async fn addTopLevelDecl(self: *Module, parsed_file: *ParsedFile, decl: *Decl) !void {
-        const is_export = decl.isExported(&parsed_file.tree);
+    async fn addTopLevelDecl(self: *Module, decl: *Decl) !void {
+        const is_export = decl.isExported(&decl.parsed_file.tree);
 
         if (is_export) {
-            try self.build_group.call(verifyUniqueSymbol, self, parsed_file, decl);
-            try self.build_group.call(generateDecl, self, parsed_file, decl);
+            try self.build_group.call(verifyUniqueSymbol, self, decl);
+            try self.build_group.call(resolveDecl, self, decl);
         }
     }
 
@@ -416,36 +549,21 @@ pub const Module = struct {
         try compile_errors.value.append(msg);
     }
 
-    async fn verifyUniqueSymbol(self: *Module, parsed_file: *ParsedFile, decl: *Decl) !void {
+    async fn verifyUniqueSymbol(self: *Module, decl: *Decl) !void {
         const exported_symbol_names = await (async self.exported_symbol_names.acquire() catch unreachable);
         defer exported_symbol_names.release();
 
         if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| {
             try self.addCompileError(
-                parsed_file,
+                decl.parsed_file,
                 decl.getSpan(),
                 "exported symbol collision: '{}'",
                 decl.name,
             );
+            // TODO add error note showing location of other symbol
         }
     }
 
-    /// This declaration has been blessed as going into the final code generation.
-    async fn generateDecl(self: *Module, parsed_file: *ParsedFile, decl: *Decl) void {
-        switch (decl.id) {
-            Decl.Id.Var => @panic("TODO"),
-            Decl.Id.Fn => {
-                const fn_decl = @fieldParentPtr(Decl.Fn, "base", decl);
-                return await (async self.generateDeclFn(parsed_file, fn_decl) catch unreachable);
-            },
-            Decl.Id.CompTime => @panic("TODO"),
-        }
-    }
-
-    async fn generateDeclFn(self: *Module, parsed_file: *ParsedFile, fn_decl: *Decl.Fn) void {
-        fn_decl.value = Decl.Fn.Val{ .Ok = Value.Fn{} };
-    }
-
     pub fn link(self: *Module, out_file: ?[]const u8) !void {
         warn("TODO link");
         return error.Todo;
@@ -501,177 +619,48 @@ fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib
     }
 }
 
-pub const Scope = struct {
-    id: Id,
-    parent: ?*Scope,
-
-    pub const Id = enum {
-        Decls,
-        Block,
-    };
-
-    pub const Decls = struct {
-        base: Scope,
-        table: Decl.Table,
-
-        pub fn create(a: *Allocator, parent: ?*Scope) !*Decls {
-            const self = try a.create(Decls{
-                .base = Scope{
-                    .id = Id.Decls,
-                    .parent = parent,
-                },
-                .table = undefined,
-            });
-            errdefer a.destroy(self);
-
-            self.table = Decl.Table.init(a);
-            errdefer self.table.deinit();
-
-            return self;
-        }
-
-        pub fn destroy(self: *Decls) void {
-            self.table.deinit();
-            self.table.allocator.destroy(self);
-            self.* = undefined;
-        }
-    };
-
-    pub const Block = struct {
-        base: Scope,
-    };
-};
-
-pub const Visib = enum {
-    Private,
-    Pub,
-};
-
-pub const Decl = struct {
-    id: Id,
-    name: []const u8,
-    visib: Visib,
-    resolution: Resolution,
-
-    pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8);
-
-    pub fn isExported(base: *const Decl, tree: *ast.Tree) bool {
-        switch (base.id) {
-            Id.Fn => {
-                const fn_decl = @fieldParentPtr(Fn, "base", base);
-                return fn_decl.isExported(tree);
-            },
-            else => return false,
-        }
+/// This declaration has been blessed as going into the final code generation.
+pub async fn resolveDecl(module: *Module, decl: *Decl) !void {
+    if (@atomicRmw(u8, &decl.resolution_in_progress, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) == 0) {
+        decl.resolution.data = await (async generateDecl(module, decl) catch unreachable);
+        decl.resolution.resolve();
+    } else {
+        return (await (async decl.resolution.get() catch unreachable)).*;
     }
+}
 
-    pub fn getSpan(base: *const Decl) errmsg.Span {
-        switch (base.id) {
-            Id.Fn => {
-                const fn_decl = @fieldParentPtr(Fn, "base", base);
-                const fn_proto = fn_decl.fn_proto;
-                const start = fn_proto.fn_token;
-                const end = fn_proto.name_token orelse start;
-                return errmsg.Span{
-                    .first = start,
-                    .last = end + 1,
-                };
-            },
-            else => @panic("TODO"),
-        }
+/// The function that actually does the generation.
+async fn generateDecl(module: *Module, decl: *Decl) !void {
+    switch (decl.id) {
+        Decl.Id.Var => @panic("TODO"),
+        Decl.Id.Fn => {
+            const fn_decl = @fieldParentPtr(Decl.Fn, "base", decl);
+            return await (async generateDeclFn(module, fn_decl) catch unreachable);
+        },
+        Decl.Id.CompTime => @panic("TODO"),
     }
+}
 
-    pub const Resolution = enum {
-        Unresolved,
-        InProgress,
-        Invalid,
-        Ok,
-    };
-
-    pub const Id = enum {
-        Var,
-        Fn,
-        CompTime,
-    };
-
-    pub const Var = struct {
-        base: Decl,
-    };
+async fn generateDeclFn(module: *Module, fn_decl: *Decl.Fn) !void {
+    const body_node = fn_decl.fn_proto.body_node orelse @panic("TODO extern fn proto decl");
 
-    pub const Fn = struct {
-        base: Decl,
-        value: Val,
-        fn_proto: *const ast.Node.FnProto,
+    const fndef_scope = try Scope.FnDef.create(module, fn_decl.base.parent_scope);
+    defer fndef_scope.base.deref(module);
 
-        // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous
-        pub const Val = union {
-            Unresolved: void,
-            Ok: Value.Fn,
-        };
+    const fn_type = try Type.Fn.create(module);
+    defer fn_type.base.base.deref(module);
 
-        pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 {
-            return if (self.fn_proto.extern_export_inline_token) |tok_index| x: {
-                const token = tree.tokens.at(tok_index);
-                break :x switch (token.id) {
-                    Token.Id.Extern => tree.tokenSlicePtr(token),
-                    else => null,
-                };
-            } else null;
-        }
+    const fn_val = try Value.Fn.create(module, fn_type, fndef_scope);
+    defer fn_val.base.deref(module);
 
-        pub fn isExported(self: Fn, tree: *ast.Tree) bool {
-            if (self.fn_proto.extern_export_inline_token) |tok_index| {
-                const token = tree.tokens.at(tok_index);
-                return token.id == Token.Id.Keyword_export;
-            } else {
-                return false;
-            }
-        }
-    };
+    fn_decl.value = Decl.Fn.Val{ .Ok = fn_val };
 
-    pub const CompTime = struct {
-        base: Decl,
-    };
-};
-
-pub const Value = struct {
-    pub const Fn = struct {};
-};
-
-pub const Type = struct {
-    id: Id,
-
-    pub const Id = enum {
-        Type,
-        Void,
-        Bool,
-        NoReturn,
-        Int,
-        Float,
-        Pointer,
-        Array,
-        Struct,
-        ComptimeFloat,
-        ComptimeInt,
-        Undefined,
-        Null,
-        Optional,
-        ErrorUnion,
-        ErrorSet,
-        Enum,
-        Union,
-        Fn,
-        Opaque,
-        Promise,
-    };
-
-    pub const Struct = struct {
-        base: Type,
-        decls: *Scope.Decls,
-    };
-};
-
-pub const ParsedFile = struct {
-    tree: ast.Tree,
-    realpath: []const u8,
-};
+    const code = try await (async ir.gen(
+        module,
+        body_node,
+        &fndef_scope.base,
+        fn_decl.base.parsed_file,
+    ) catch unreachable);
+    //code.dump();
+    //try await (async irAnalyze(module, func) catch unreachable);
+}
src-self-hosted/parsed_file.zig
@@ -0,0 +1,6 @@
+const ast = @import("std").zig.ast;
+
+pub const ParsedFile = struct {
+    tree: ast.Tree,
+    realpath: []const u8,
+};
src-self-hosted/scope.zig
@@ -1,16 +1,234 @@
+const std = @import("std");
+const Allocator = mem.Allocator;
+const Decl = @import("decl.zig").Decl;
+const Module = @import("module.zig").Module;
+const mem = std.mem;
+const ast = std.zig.ast;
+const Value = @import("value.zig").Value;
+const ir = @import("ir.zig");
+
 pub const Scope = struct {
     id: Id,
-    parent: *Scope,
+    parent: ?*Scope,
+    ref_count: usize,
+
+    pub fn ref(base: *Scope) void {
+        base.ref_count += 1;
+    }
+
+    pub fn deref(base: *Scope, module: *Module) void {
+        base.ref_count -= 1;
+        if (base.ref_count == 0) {
+            if (base.parent) |parent| parent.deref(module);
+            switch (base.id) {
+                Id.Decls => @fieldParentPtr(Decls, "base", base).destroy(),
+                Id.Block => @fieldParentPtr(Block, "base", base).destroy(module),
+                Id.FnDef => @fieldParentPtr(FnDef, "base", base).destroy(module),
+                Id.CompTime => @fieldParentPtr(CompTime, "base", base).destroy(module),
+                Id.Defer => @fieldParentPtr(Defer, "base", base).destroy(module),
+                Id.DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(module),
+            }
+        }
+    }
+
+    pub fn findFnDef(base: *Scope) ?*FnDef {
+        var scope = base;
+        while (true) {
+            switch (scope.id) {
+                Id.FnDef => return @fieldParentPtr(FnDef, "base", base),
+                Id.Decls => return null,
+
+                Id.Block,
+                Id.Defer,
+                Id.DeferExpr,
+                Id.CompTime,
+                => scope = scope.parent orelse return null,
+            }
+        }
+    }
 
     pub const Id = enum {
         Decls,
         Block,
-        Defer,
-        DeferExpr,
-        VarDecl,
-        CImport,
-        Loop,
         FnDef,
         CompTime,
+        Defer,
+        DeferExpr,
+    };
+
+    pub const Decls = struct {
+        base: Scope,
+        table: Decl.Table,
+
+        /// Creates a Decls scope with 1 reference
+        pub fn create(module: *Module, parent: ?*Scope) !*Decls {
+            const self = try module.a().create(Decls{
+                .base = Scope{
+                    .id = Id.Decls,
+                    .parent = parent,
+                    .ref_count = 1,
+                },
+                .table = undefined,
+            });
+            errdefer module.a().destroy(self);
+
+            self.table = Decl.Table.init(module.a());
+            errdefer self.table.deinit();
+
+            if (parent) |p| p.ref();
+
+            return self;
+        }
+
+        pub fn destroy(self: *Decls) void {
+            self.table.deinit();
+            self.table.allocator.destroy(self);
+        }
+    };
+
+    pub const Block = struct {
+        base: Scope,
+        incoming_values: std.ArrayList(*ir.Instruction),
+        incoming_blocks: std.ArrayList(*ir.BasicBlock),
+        end_block: *ir.BasicBlock,
+        is_comptime: *ir.Instruction,
+
+        /// Creates a Block scope with 1 reference
+        pub fn create(module: *Module, parent: ?*Scope) !*Block {
+            const self = try module.a().create(Block{
+                .base = Scope{
+                    .id = Id.Block,
+                    .parent = parent,
+                    .ref_count = 1,
+                },
+                .incoming_values = undefined,
+                .incoming_blocks = undefined,
+                .end_block = undefined,
+                .is_comptime = undefined,
+            });
+            errdefer module.a().destroy(self);
+
+            if (parent) |p| p.ref();
+            return self;
+        }
+
+        pub fn destroy(self: *Block, module: *Module) void {
+            module.a().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(module: *Module, parent: ?*Scope) !*FnDef {
+            const self = try module.a().create(FnDef{
+                .base = Scope{
+                    .id = Id.FnDef,
+                    .parent = parent,
+                    .ref_count = 1,
+                },
+                .fn_val = undefined,
+            });
+
+            if (parent) |p| p.ref();
+
+            return self;
+        }
+
+        pub fn destroy(self: *FnDef, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const CompTime = struct {
+        base: Scope,
+
+        /// Creates a CompTime scope with 1 reference
+        pub fn create(module: *Module, parent: ?*Scope) !*CompTime {
+            const self = try module.a().create(CompTime{
+                .base = Scope{
+                    .id = Id.CompTime,
+                    .parent = parent,
+                    .ref_count = 1,
+                },
+            });
+
+            if (parent) |p| p.ref();
+            return self;
+        }
+
+        pub fn destroy(self: *CompTime, module: *Module) void {
+            module.a().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(
+            module: *Module,
+            parent: ?*Scope,
+            kind: Kind,
+            defer_expr_scope: *DeferExpr,
+        ) !*Defer {
+            const self = try module.a().create(Defer{
+                .base = Scope{
+                    .id = Id.Defer,
+                    .parent = parent,
+                    .ref_count = 1,
+                },
+                .defer_expr_scope = defer_expr_scope,
+                .kind = kind,
+            });
+            errdefer module.a().destroy(self);
+
+            defer_expr_scope.base.ref();
+
+            if (parent) |p| p.ref();
+            return self;
+        }
+
+        pub fn destroy(self: *Defer, module: *Module) void {
+            self.defer_expr_scope.base.deref(module);
+            module.a().destroy(self);
+        }
+    };
+
+    pub const DeferExpr = struct {
+        base: Scope,
+        expr_node: *ast.Node,
+
+        /// Creates a DeferExpr scope with 1 reference
+        pub fn create(module: *Module, parent: ?*Scope, expr_node: *ast.Node) !*DeferExpr {
+            const self = try module.a().create(DeferExpr{
+                .base = Scope{
+                    .id = Id.DeferExpr,
+                    .parent = parent,
+                    .ref_count = 1,
+                },
+                .expr_node = expr_node,
+            });
+            errdefer module.a().destroy(self);
+
+            if (parent) |p| p.ref();
+            return self;
+        }
+
+        pub fn destroy(self: *DeferExpr, module: *Module) void {
+            module.a().destroy(self);
+        }
     };
 };
src-self-hosted/type.zig
@@ -0,0 +1,268 @@
+const builtin = @import("builtin");
+const Scope = @import("scope.zig").Scope;
+const Module = @import("module.zig").Module;
+const Value = @import("value.zig").Value;
+
+pub const Type = struct {
+    base: Value,
+    id: Id,
+
+    pub const Id = builtin.TypeId;
+
+    pub fn destroy(base: *Type, module: *Module) void {
+        switch (base.id) {
+            Id.Struct => @fieldParentPtr(Struct, "base", base).destroy(module),
+            Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(module),
+            Id.Type => @fieldParentPtr(MetaType, "base", base).destroy(module),
+            Id.Void => @fieldParentPtr(Void, "base", base).destroy(module),
+            Id.Bool => @fieldParentPtr(Bool, "base", base).destroy(module),
+            Id.NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(module),
+            Id.Int => @fieldParentPtr(Int, "base", base).destroy(module),
+            Id.Float => @fieldParentPtr(Float, "base", base).destroy(module),
+            Id.Pointer => @fieldParentPtr(Pointer, "base", base).destroy(module),
+            Id.Array => @fieldParentPtr(Array, "base", base).destroy(module),
+            Id.ComptimeFloat => @fieldParentPtr(ComptimeFloat, "base", base).destroy(module),
+            Id.ComptimeInt => @fieldParentPtr(ComptimeInt, "base", base).destroy(module),
+            Id.Undefined => @fieldParentPtr(Undefined, "base", base).destroy(module),
+            Id.Null => @fieldParentPtr(Null, "base", base).destroy(module),
+            Id.Optional => @fieldParentPtr(Optional, "base", base).destroy(module),
+            Id.ErrorUnion => @fieldParentPtr(ErrorUnion, "base", base).destroy(module),
+            Id.ErrorSet => @fieldParentPtr(ErrorSet, "base", base).destroy(module),
+            Id.Enum => @fieldParentPtr(Enum, "base", base).destroy(module),
+            Id.Union => @fieldParentPtr(Union, "base", base).destroy(module),
+            Id.Namespace => @fieldParentPtr(Namespace, "base", base).destroy(module),
+            Id.Block => @fieldParentPtr(Block, "base", base).destroy(module),
+            Id.BoundFn => @fieldParentPtr(BoundFn, "base", base).destroy(module),
+            Id.ArgTuple => @fieldParentPtr(ArgTuple, "base", base).destroy(module),
+            Id.Opaque => @fieldParentPtr(Opaque, "base", base).destroy(module),
+            Id.Promise => @fieldParentPtr(Promise, "base", base).destroy(module),
+        }
+    }
+
+    pub const Struct = struct {
+        base: Type,
+        decls: *Scope.Decls,
+
+        pub fn destroy(self: *Struct, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const Fn = struct {
+        base: Type,
+
+        pub fn create(module: *Module) !*Fn {
+            return module.a().create(Fn{
+                .base = Type{
+                    .base = Value{
+                        .id = Value.Id.Type,
+                        .typeof = &MetaType.get(module).base,
+                        .ref_count = 1,
+                    },
+                    .id = builtin.TypeId.Fn,
+                },
+            });
+        }
+
+        pub fn destroy(self: *Fn, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const MetaType = struct {
+        base: Type,
+        value: *Type,
+
+        /// Adds 1 reference to the resulting type
+        pub fn get(module: *Module) *MetaType {
+            module.meta_type.base.base.ref();
+            return module.meta_type;
+        }
+
+        pub fn destroy(self: *MetaType, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const Void = struct {
+        base: Type,
+
+        /// Adds 1 reference to the resulting type
+        pub fn get(module: *Module) *Void {
+            module.void_type.base.base.ref();
+            return module.void_type;
+        }
+
+        pub fn destroy(self: *Void, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const Bool = struct {
+        base: Type,
+
+        /// Adds 1 reference to the resulting type
+        pub fn get(module: *Module) *Bool {
+            module.bool_type.base.base.ref();
+            return module.bool_type;
+        }
+
+        pub fn destroy(self: *Bool, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const NoReturn = struct {
+        base: Type,
+
+        /// Adds 1 reference to the resulting type
+        pub fn get(module: *Module) *NoReturn {
+            module.noreturn_type.base.base.ref();
+            return module.noreturn_type;
+        }
+
+        pub fn destroy(self: *NoReturn, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const Int = struct {
+        base: Type,
+
+        pub fn destroy(self: *Int, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const Float = struct {
+        base: Type,
+
+        pub fn destroy(self: *Float, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+    pub const Pointer = struct {
+        base: Type,
+
+        pub fn destroy(self: *Pointer, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+    pub const Array = struct {
+        base: Type,
+
+        pub fn destroy(self: *Array, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+    pub const ComptimeFloat = struct {
+        base: Type,
+
+        pub fn destroy(self: *ComptimeFloat, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+    pub const ComptimeInt = struct {
+        base: Type,
+
+        pub fn destroy(self: *ComptimeInt, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+    pub const Undefined = struct {
+        base: Type,
+
+        pub fn destroy(self: *Undefined, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+    pub const Null = struct {
+        base: Type,
+
+        pub fn destroy(self: *Null, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+    pub const Optional = struct {
+        base: Type,
+
+        pub fn destroy(self: *Optional, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+    pub const ErrorUnion = struct {
+        base: Type,
+
+        pub fn destroy(self: *ErrorUnion, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+    pub const ErrorSet = struct {
+        base: Type,
+
+        pub fn destroy(self: *ErrorSet, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+    pub const Enum = struct {
+        base: Type,
+
+        pub fn destroy(self: *Enum, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+    pub const Union = struct {
+        base: Type,
+
+        pub fn destroy(self: *Union, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+    pub const Namespace = struct {
+        base: Type,
+
+        pub fn destroy(self: *Namespace, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const Block = struct {
+        base: Type,
+
+        pub fn destroy(self: *Block, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const BoundFn = struct {
+        base: Type,
+
+        pub fn destroy(self: *BoundFn, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const ArgTuple = struct {
+        base: Type,
+
+        pub fn destroy(self: *ArgTuple, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const Opaque = struct {
+        base: Type,
+
+        pub fn destroy(self: *Opaque, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const Promise = struct {
+        base: Type,
+
+        pub fn destroy(self: *Promise, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+};
src-self-hosted/value.zig
@@ -0,0 +1,125 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const Scope = @import("scope.zig").Scope;
+const Module = @import("module.zig").Module;
+
+/// Values are ref-counted, heap-allocated, and copy-on-write
+/// If there is only 1 ref then write need not copy
+pub const Value = struct {
+    id: Id,
+    typeof: *Type,
+    ref_count: usize,
+
+    pub fn ref(base: *Value) void {
+        base.ref_count += 1;
+    }
+
+    pub fn deref(base: *Value, module: *Module) void {
+        base.ref_count -= 1;
+        if (base.ref_count == 0) {
+            base.typeof.base.deref(module);
+            switch (base.id) {
+                Id.Type => @fieldParentPtr(Type, "base", base).destroy(module),
+                Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(module),
+                Id.Void => @fieldParentPtr(Void, "base", base).destroy(module),
+                Id.Bool => @fieldParentPtr(Bool, "base", base).destroy(module),
+                Id.NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(module),
+            }
+        }
+    }
+
+    pub fn dump(base: *const Value) void {
+        std.debug.warn("{}", @tagName(base.id));
+    }
+
+    pub const Id = enum {
+        Type,
+        Fn,
+        Void,
+        Bool,
+        NoReturn,
+    };
+
+    pub const Type = @import("type.zig").Type;
+
+    pub const Fn = struct {
+        base: Value,
+
+        /// parent should be the top level decls or container decls
+        fndef_scope: *Scope.FnDef,
+
+        /// parent is scope for last parameter
+        child_scope: *Scope,
+
+        /// parent is child_scope
+        block_scope: *Scope.Block,
+
+        /// Creates a Fn value with 1 ref
+        pub fn create(module: *Module, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef) !*Fn {
+            const self = try module.a().create(Fn{
+                .base = Value{
+                    .id = Value.Id.Fn,
+                    .typeof = &fn_type.base,
+                    .ref_count = 1,
+                },
+                .fndef_scope = fndef_scope,
+                .child_scope = &fndef_scope.base,
+                .block_scope = undefined,
+            });
+            fn_type.base.base.ref();
+            fndef_scope.fn_val = self;
+            fndef_scope.base.ref();
+            return self;
+        }
+
+        pub fn destroy(self: *Fn, module: *Module) void {
+            self.fndef_scope.base.deref(module);
+            module.a().destroy(self);
+        }
+    };
+
+    pub const Void = struct {
+        base: Value,
+
+        pub fn get(module: *Module) *Void {
+            module.void_value.base.ref();
+            return module.void_value;
+        }
+
+        pub fn destroy(self: *Void, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const Bool = struct {
+        base: Value,
+        x: bool,
+
+        pub fn get(module: *Module, x: bool) *Bool {
+            if (x) {
+                module.true_value.base.ref();
+                return module.true_value;
+            } else {
+                module.false_value.base.ref();
+                return module.false_value;
+            }
+        }
+
+        pub fn destroy(self: *Bool, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+
+    pub const NoReturn = struct {
+        base: Value,
+
+        pub fn get(module: *Module) *NoReturn {
+            module.noreturn_value.base.ref();
+            return module.noreturn_value;
+        }
+
+        pub fn destroy(self: *NoReturn, module: *Module) void {
+            module.a().destroy(self);
+        }
+    };
+};
src-self-hosted/visib.zig
@@ -0,0 +1,4 @@
+pub const Visib = enum {
+    Private,
+    Pub,
+};
std/event/future.zig
@@ -57,7 +57,7 @@ test "std.event.Future" {
     const allocator = &da.allocator;
 
     var loop: Loop = undefined;
-    try loop.initSingleThreaded(allocator);
+    try loop.initMultiThreaded(allocator);
     defer loop.deinit();
 
     const handle = try async<allocator> testFuture(&loop);
std/zig/ast.zig
@@ -970,14 +970,8 @@ pub const Node = struct {
     pub const Defer = struct {
         base: Node,
         defer_token: TokenIndex,
-        kind: Kind,
         expr: *Node,
 
-        const Kind = enum {
-            Error,
-            Unconditional,
-        };
-
         pub fn iterate(self: *Defer, index: usize) ?*Node {
             var i = index;
 
std/zig/parse.zig
@@ -1041,11 +1041,6 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree {
                         const node = try arena.create(ast.Node.Defer{
                             .base = ast.Node{ .id = ast.Node.Id.Defer },
                             .defer_token = token_index,
-                            .kind = switch (token_ptr.id) {
-                                Token.Id.Keyword_defer => ast.Node.Defer.Kind.Unconditional,
-                                Token.Id.Keyword_errdefer => ast.Node.Defer.Kind.Error,
-                                else => unreachable,
-                            },
                             .expr = undefined,
                         });
                         const node_ptr = try block.statements.addOne();