Commit adefd1a52b

Andrew Kelley <superjoe30@gmail.com>
2018-07-25 02:24:05
self-hosted: function calling another function
1 parent 2ea0856
src-self-hosted/codegen.zig
@@ -6,6 +6,7 @@ const c = @import("c.zig");
 const ir = @import("ir.zig");
 const Value = @import("value.zig").Value;
 const Type = @import("type.zig").Type;
+const Scope = @import("scope.zig").Scope;
 const event = std.event;
 const assert = std.debug.assert;
 const DW = std.dwarf;
@@ -156,7 +157,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code)
         llvm_fn_type,
     ) orelse return error.OutOfMemory;
 
-    const want_fn_safety = fn_val.block_scope.safety.get(ofile.comp);
+    const want_fn_safety = fn_val.block_scope.?.safety.get(ofile.comp);
     if (want_fn_safety and ofile.comp.haveLibC()) {
         try addLLVMFnAttr(ofile, llvm_fn, "sspstrong");
         try addLLVMFnAttrStr(ofile, llvm_fn, "stack-protector-buffer-size", "4");
@@ -227,9 +228,86 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code)
 
     // TODO set up error return tracing
     // TODO allocate temporary stack values
-    // TODO create debug variable declarations for variables and allocate all local variables
+
+    const var_list = fn_type.non_key.Normal.variable_list.toSliceConst();
+    // create debug variable declarations for variables and allocate all local variables
+    for (var_list) |var_scope, i| {
+        const var_type = switch (var_scope.data) {
+            Scope.Var.Data.Const => unreachable,
+            Scope.Var.Data.Param => |param| param.typ,
+        };
+        //    if (!type_has_bits(var->value->type)) {
+        //        continue;
+        //    }
+        //    if (ir_get_var_is_comptime(var))
+        //        continue;
+        //    if (type_requires_comptime(var->value->type))
+        //        continue;
+        //    if (var->src_arg_index == SIZE_MAX) {
+        //        var->value_ref = build_alloca(g, var->value->type, buf_ptr(&var->name), var->align_bytes);
+
+        //        var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope),
+        //                buf_ptr(&var->name), import->di_file, (unsigned)(var->decl_node->line + 1),
+        //                var->value->type->di_type, !g->strip_debug_symbols, 0);
+
+        //    } else {
+        // it's a parameter
+        //        assert(var->gen_arg_index != SIZE_MAX);
+        //        TypeTableEntry *gen_type;
+        //        FnGenParamInfo *gen_info = &fn_table_entry->type_entry->data.fn.gen_param_info[var->src_arg_index];
+
+        if (var_type.handleIsPtr()) {
+            //            if (gen_info->is_byval) {
+            //                gen_type = var->value->type;
+            //            } else {
+            //                gen_type = gen_info->type;
+            //            }
+            var_scope.data.Param.llvm_value = llvm.GetParam(llvm_fn, @intCast(c_uint, i));
+        } else {
+            //            gen_type = var->value->type;
+            var_scope.data.Param.llvm_value = try renderAlloca(ofile, var_type, var_scope.name, Type.Pointer.Align.Abi);
+        }
+        //        if (var->decl_node) {
+        //            var->di_loc_var = ZigLLVMCreateParameterVariable(g->dbuilder, get_di_scope(g, var->parent_scope),
+        //                    buf_ptr(&var->name), import->di_file,
+        //                    (unsigned)(var->decl_node->line + 1),
+        //                    gen_type->di_type, !g->strip_debug_symbols, 0, (unsigned)(var->gen_arg_index + 1));
+        //        }
+
+        //    }
+    }
+
     // TODO finishing error return trace setup. we have to do this after all the allocas.
-    // TODO create debug variable declarations for parameters
+
+    // create debug variable declarations for parameters
+    // rely on the first variables in the variable_list being parameters.
+    //size_t next_var_i = 0;
+    for (fn_type.key.data.Normal.params) |param, i| {
+        //FnGenParamInfo *info = &fn_table_entry->type_entry->data.fn.gen_param_info[param_i];
+        //if (info->gen_index == SIZE_MAX)
+        //    continue;
+        const scope_var = var_list[i];
+        //assert(variable->src_arg_index != SIZE_MAX);
+        //next_var_i += 1;
+        //assert(variable);
+        //assert(variable->value_ref);
+
+        if (!param.typ.handleIsPtr()) {
+            //clear_debug_source_node(g);
+            const llvm_param = llvm.GetParam(llvm_fn, @intCast(c_uint, i));
+            _ = renderStoreUntyped(
+                ofile,
+                llvm_param,
+                scope_var.data.Param.llvm_value,
+                Type.Pointer.Align.Abi,
+                Type.Pointer.Vol.Non,
+            );
+        }
+
+        //if (variable->decl_node) {
+        //    gen_var_debug_decl(g, variable);
+        //}
+    }
 
     for (code.basic_block_list.toSlice()) |current_block| {
         llvm.PositionBuilderAtEnd(ofile.builder, current_block.llvm_block);
@@ -294,3 +372,79 @@ fn addLLVMFnAttrStr(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []cons
 fn addLLVMFnAttrInt(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8, attr_val: u64) !void {
     return addLLVMAttrInt(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name, attr_val);
 }
+
+fn renderLoadUntyped(
+    ofile: *ObjectFile,
+    ptr: llvm.ValueRef,
+    alignment: Type.Pointer.Align,
+    vol: Type.Pointer.Vol,
+    name: [*]const u8,
+) !llvm.ValueRef {
+    const result = llvm.BuildLoad(ofile.builder, ptr, name) orelse return error.OutOfMemory;
+    switch (vol) {
+        Type.Pointer.Vol.Non => {},
+        Type.Pointer.Vol.Volatile => llvm.SetVolatile(result, 1),
+    }
+    llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm.GetElementType(llvm.TypeOf(ptr))));
+    return result;
+}
+
+fn renderLoad(ofile: *ObjectFile, ptr: llvm.ValueRef, ptr_type: *Type.Pointer, name: [*]const u8) !llvm.ValueRef {
+    return renderLoadUntyped(ofile, ptr, ptr_type.key.alignment, ptr_type.key.vol, name);
+}
+
+pub fn getHandleValue(ofile: *ObjectFile, ptr: llvm.ValueRef, ptr_type: *Type.Pointer) !?llvm.ValueRef {
+    const child_type = ptr_type.key.child_type;
+    if (!child_type.hasBits()) {
+        return null;
+    }
+    if (child_type.handleIsPtr()) {
+        return ptr;
+    }
+    return try renderLoad(ofile, ptr, ptr_type, c"");
+}
+
+pub fn renderStoreUntyped(
+    ofile: *ObjectFile,
+    value: llvm.ValueRef,
+    ptr: llvm.ValueRef,
+    alignment: Type.Pointer.Align,
+    vol: Type.Pointer.Vol,
+) !llvm.ValueRef {
+    const result = llvm.BuildStore(ofile.builder, value, ptr) orelse return error.OutOfMemory;
+    switch (vol) {
+        Type.Pointer.Vol.Non => {},
+        Type.Pointer.Vol.Volatile => llvm.SetVolatile(result, 1),
+    }
+    llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm.TypeOf(value)));
+    return result;
+}
+
+pub fn renderStore(
+    ofile: *ObjectFile,
+    value: llvm.ValueRef,
+    ptr: llvm.ValueRef,
+    ptr_type: *Type.Pointer,
+) !llvm.ValueRef {
+    return renderStoreUntyped(ofile, value, ptr, ptr_type.key.alignment, ptr_type.key.vol);
+}
+
+pub fn renderAlloca(
+    ofile: *ObjectFile,
+    var_type: *Type,
+    name: []const u8,
+    alignment: Type.Pointer.Align,
+) !llvm.ValueRef {
+    const llvm_var_type = try var_type.getLlvmType(ofile.arena, ofile.context);
+    const name_with_null = try std.cstr.addNullByte(ofile.arena, name);
+    const result = llvm.BuildAlloca(ofile.builder, llvm_var_type, name_with_null.ptr) orelse return error.OutOfMemory;
+    llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm_var_type));
+    return result;
+}
+
+pub fn resolveAlign(ofile: *ObjectFile, alignment: Type.Pointer.Align, llvm_type: llvm.TypeRef) u32 {
+    return switch (alignment) {
+        Type.Pointer.Align.Abi => return llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, llvm_type),
+        Type.Pointer.Align.Override => |a| a,
+    };
+}
src-self-hosted/compilation.zig
@@ -1165,49 +1165,47 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void {
     symbol_name_consumed = true;
 
     // Define local parameter variables
-    //for (size_t i = 0; i < fn_type_id->param_count; i += 1) {
-    //    FnTypeParamInfo *param_info = &fn_type_id->param_info[i];
-    //    AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i);
-    //    Buf *param_name;
-    //    bool is_var_args = param_decl_node && param_decl_node->data.param_decl.is_var_args;
-    //    if (param_decl_node && !is_var_args) {
-    //        param_name = param_decl_node->data.param_decl.name;
-    //    } else {
-    //        param_name = buf_sprintf("arg%" ZIG_PRI_usize "", i);
-    //    }
-    //    if (param_name == nullptr) {
-    //        continue;
-    //    }
-
-    //    TypeTableEntry *param_type = param_info->type;
-    //    bool is_noalias = param_info->is_noalias;
-
-    //    if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) {
-    //        add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter"));
-    //    }
-
-    //    VariableTableEntry *var = add_variable(g, param_decl_node, fn_table_entry->child_scope,
-    //            param_name, true, create_const_runtime(param_type), nullptr);
-    //    var->src_arg_index = i;
-    //    fn_table_entry->child_scope = var->child_scope;
-    //    var->shadowable = var->shadowable || is_var_args;
-
-    //    if (type_has_bits(param_type)) {
-    //        fn_table_entry->variable_list.append(var);
-    //    }
-
-    //    if (fn_type->data.fn.gen_param_info) {
-    //        var->gen_arg_index = fn_type->data.fn.gen_param_info[i].gen_index;
-    //    }
-    //}
+    const root_scope = fn_decl.base.findRootScope();
+    for (fn_type.key.data.Normal.params) |param, i| {
+        //AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i);
+        const param_decl = @fieldParentPtr(ast.Node.ParamDecl, "base", fn_decl.fn_proto.params.at(i).*);
+        const name_token = param_decl.name_token orelse {
+            try comp.addCompileError(root_scope, Span{
+                .first = param_decl.firstToken(),
+                .last = param_decl.type_node.firstToken(),
+            }, "missing parameter name");
+            return error.SemanticAnalysisFailed;
+        };
+        const param_name = root_scope.tree.tokenSlice(name_token);
+
+        // if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) {
+        //     add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter"));
+        // }
+
+        // TODO check for shadowing
+
+        const var_scope = try Scope.Var.createParam(
+            comp,
+            fn_val.child_scope,
+            param_name,
+            &param_decl.base,
+            i,
+            param.typ,
+        );
+        fn_val.child_scope = &var_scope.base;
+
+        try fn_type.non_key.Normal.variable_list.append(var_scope);
+    }
 
     const analyzed_code = try await (async comp.genAndAnalyzeCode(
-        &fndef_scope.base,
+        fn_val.child_scope,
         body_node,
         fn_type.key.data.Normal.return_type,
     ) catch unreachable);
     errdefer analyzed_code.destroy(comp.gpa());
 
+    assert(fn_val.block_scope != null);
+
     // Kick off rendering to LLVM module, but it doesn't block the fn decl
     // analysis from being complete.
     try comp.prelink_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code);
@@ -1263,7 +1261,7 @@ async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.Fn
     const key = Type.Fn.Key{
         .alignment = null,
         .data = Type.Fn.Key.Data{
-            .Normal = Type.Fn.Normal{
+            .Normal = Type.Fn.Key.Normal{
                 .return_type = return_type,
                 .params = params.toOwnedSlice(),
                 .is_var_args = false, // TODO
src-self-hosted/ir.zig
@@ -10,8 +10,10 @@ const assert = std.debug.assert;
 const Token = std.zig.Token;
 const Span = @import("errmsg.zig").Span;
 const llvm = @import("llvm.zig");
-const ObjectFile = @import("codegen.zig").ObjectFile;
+const codegen = @import("codegen.zig");
+const ObjectFile = codegen.ObjectFile;
 const Decl = @import("decl.zig").Decl;
+const mem = std.mem;
 
 pub const LVal = enum {
     None,
@@ -122,6 +124,8 @@ pub const Inst = struct {
             Id.Br => return @fieldParentPtr(Br, "base", base).analyze(ira),
             Id.AddImplicitReturnType => return @fieldParentPtr(AddImplicitReturnType, "base", base).analyze(ira),
             Id.PtrType => return await (async @fieldParentPtr(PtrType, "base", base).analyze(ira) catch unreachable),
+            Id.VarPtr => return await (async @fieldParentPtr(VarPtr, "base", base).analyze(ira) catch unreachable),
+            Id.LoadPtr => return await (async @fieldParentPtr(LoadPtr, "base", base).analyze(ira) catch unreachable),
         }
     }
 
@@ -130,6 +134,8 @@ pub const Inst = struct {
             Id.Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val),
             Id.Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val),
             Id.Call => return @fieldParentPtr(Call, "base", base).render(ofile, fn_val),
+            Id.VarPtr => return @fieldParentPtr(VarPtr, "base", base).render(ofile, fn_val),
+            Id.LoadPtr => return @fieldParentPtr(LoadPtr, "base", base).render(ofile, fn_val),
             Id.DeclRef => unreachable,
             Id.PtrType => unreachable,
             Id.Ref => @panic("TODO"),
@@ -248,6 +254,8 @@ pub const Inst = struct {
         Call,
         DeclRef,
         PtrType,
+        VarPtr,
+        LoadPtr,
     };
 
     pub const Call = struct {
@@ -491,6 +499,133 @@ pub const Inst = struct {
         }
     };
 
+    pub const VarPtr = struct {
+        base: Inst,
+        params: Params,
+
+        const Params = struct {
+            var_scope: *Scope.Var,
+        };
+
+        const ir_val_init = IrVal.Init.Unknown;
+
+        pub fn dump(inst: *const VarPtr) void {
+            std.debug.warn("{}", inst.params.var_scope.name);
+        }
+
+        pub fn hasSideEffects(inst: *const VarPtr) bool {
+            return false;
+        }
+
+        pub async fn analyze(self: *const VarPtr, ira: *Analyze) !*Inst {
+            switch (self.params.var_scope.data) {
+                Scope.Var.Data.Const => @panic("TODO"),
+                Scope.Var.Data.Param => |param| {
+                    const new_inst = try ira.irb.build(
+                        Inst.VarPtr,
+                        self.base.scope,
+                        self.base.span,
+                        Inst.VarPtr.Params{ .var_scope = self.params.var_scope },
+                    );
+                    const ptr_type = try await (async Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{
+                        .child_type = param.typ,
+                        .mut = Type.Pointer.Mut.Const,
+                        .vol = Type.Pointer.Vol.Non,
+                        .size = Type.Pointer.Size.One,
+                        .alignment = Type.Pointer.Align.Abi,
+                    }) catch unreachable);
+                    new_inst.val = IrVal{ .KnownType = &ptr_type.base };
+                    return new_inst;
+                },
+            }
+        }
+
+        pub fn render(self: *VarPtr, ofile: *ObjectFile, fn_val: *Value.Fn) llvm.ValueRef {
+            switch (self.params.var_scope.data) {
+                Scope.Var.Data.Const => unreachable, // turned into Inst.Const in analyze pass
+                Scope.Var.Data.Param => |param| return param.llvm_value,
+            }
+        }
+    };
+
+    pub const LoadPtr = struct {
+        base: Inst,
+        params: Params,
+
+        const Params = struct {
+            target: *Inst,
+        };
+
+        const ir_val_init = IrVal.Init.Unknown;
+
+        pub fn dump(inst: *const LoadPtr) void {}
+
+        pub fn hasSideEffects(inst: *const LoadPtr) bool {
+            return false;
+        }
+
+        pub async fn analyze(self: *const LoadPtr, ira: *Analyze) !*Inst {
+            const target = try self.params.target.getAsParam();
+            const target_type = target.getKnownType();
+            if (target_type.id != Type.Id.Pointer) {
+                try ira.addCompileError(self.base.span, "dereference of non pointer type '{}'", target_type.name);
+                return error.SemanticAnalysisFailed;
+            }
+            const ptr_type = @fieldParentPtr(Type.Pointer, "base", target_type);
+            //    if (instr_is_comptime(ptr)) {
+            //        if (ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst ||
+            //            ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar)
+            //        {
+            //            ConstExprValue *pointee = const_ptr_pointee(ira->codegen, &ptr->value);
+            //            if (pointee->special != ConstValSpecialRuntime) {
+            //                IrInstruction *result = ir_create_const(&ira->new_irb, source_instruction->scope,
+            //                    source_instruction->source_node, child_type);
+            //                copy_const_val(&result->value, pointee, ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst);
+            //                result->value.type = child_type;
+            //                return result;
+            //            }
+            //        }
+            //    }
+            const new_inst = try ira.irb.build(
+                Inst.LoadPtr,
+                self.base.scope,
+                self.base.span,
+                Inst.LoadPtr.Params{ .target = target },
+            );
+            new_inst.val = IrVal{ .KnownType = ptr_type.key.child_type };
+            return new_inst;
+        }
+
+        pub fn render(self: *LoadPtr, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef {
+            const child_type = self.base.getKnownType();
+            if (!child_type.hasBits()) {
+                return null;
+            }
+            const ptr = self.params.target.llvm_value.?;
+            const ptr_type = self.params.target.getKnownType().cast(Type.Pointer).?;
+
+            return try codegen.getHandleValue(ofile, ptr, ptr_type);
+
+            //uint32_t unaligned_bit_count = ptr_type->data.pointer.unaligned_bit_count;
+            //if (unaligned_bit_count == 0)
+            //    return get_handle_value(g, ptr, child_type, ptr_type);
+
+            //bool big_endian = g->is_big_endian;
+
+            //assert(!handle_is_ptr(child_type));
+            //LLVMValueRef containing_int = gen_load(g, ptr, ptr_type, "");
+
+            //uint32_t bit_offset = ptr_type->data.pointer.bit_offset;
+            //uint32_t host_bit_count = LLVMGetIntTypeWidth(LLVMTypeOf(containing_int));
+            //uint32_t shift_amt = big_endian ? host_bit_count - bit_offset - unaligned_bit_count : bit_offset;
+
+            //LLVMValueRef shift_amt_val = LLVMConstInt(LLVMTypeOf(containing_int), shift_amt, false);
+            //LLVMValueRef shifted_value = LLVMBuildLShr(g->builder, containing_int, shift_amt_val, "");
+
+            //return LLVMBuildTrunc(g->builder, shifted_value, child_type->type_ref, "");
+        }
+    };
+
     pub const PtrType = struct {
         base: Inst,
         params: Params,
@@ -1160,6 +1295,7 @@ pub const Builder = struct {
                 Scope.Id.Block,
                 Scope.Id.Defer,
                 Scope.Id.DeferExpr,
+                Scope.Id.Var,
                 => scope = scope.parent.?,
             }
         }
@@ -1261,8 +1397,8 @@ pub const Builder = struct {
         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 (fndef_scope.fn_val.?.block_scope == null) {
+                fndef_scope.fn_val.?.block_scope = block_scope;
             }
         }
 
@@ -1492,20 +1628,23 @@ pub const Builder = struct {
             error.OutOfMemory => return error.OutOfMemory,
         }
 
-        //VariableTableEntry *var = find_variable(irb->codegen, scope, variable_name);
-        //if (var) {
-        //    IrInstruction *var_ptr = ir_build_var_ptr(irb, scope, node, var);
-        //    if (lval == LValPtr)
-        //        return var_ptr;
-        //    else
-        //        return ir_build_load_ptr(irb, scope, node, var_ptr);
-        //}
-
-        if (await (async irb.findDecl(scope, name) catch unreachable)) |decl| {
-            return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{
-                .decl = decl,
-                .lval = lval,
-            });
+        switch (await (async irb.findIdent(scope, name) catch unreachable)) {
+            Ident.Decl => |decl| {
+                return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{
+                    .decl = decl,
+                    .lval = lval,
+                });
+            },
+            Ident.VarScope => |var_scope| {
+                const var_ptr = try irb.build(Inst.VarPtr, scope, src_span, Inst.VarPtr.Params{ .var_scope = var_scope });
+                switch (lval) {
+                    LVal.Ptr => return var_ptr,
+                    LVal.None => {
+                        return irb.build(Inst.LoadPtr, scope, src_span, Inst.LoadPtr.Params{ .target = var_ptr });
+                    },
+                }
+            },
+            Ident.NotFound => {},
         }
 
         //if (node->owner->any_imports_failed) {
@@ -1546,6 +1685,7 @@ pub const Builder = struct {
                 Scope.Id.Block,
                 Scope.Id.Decls,
                 Scope.Id.Root,
+                Scope.Id.Var,
                 => scope = scope.parent orelse break,
 
                 Scope.Id.DeferExpr => unreachable,
@@ -1596,6 +1736,7 @@ pub const Builder = struct {
 
                 Scope.Id.CompTime,
                 Scope.Id.Block,
+                Scope.Id.Var,
                 => scope = scope.parent orelse return is_noreturn,
 
                 Scope.Id.DeferExpr => unreachable,
@@ -1674,8 +1815,10 @@ pub const Builder = struct {
                 Type.Pointer.Size,
                 LVal,
                 *Decl,
+                *Scope.Var,
                 => {},
-                // it's ok to add more types here, just make sure any instructions are ref'd appropriately
+                // it's ok to add more types here, just make sure that
+                // any instructions and basic blocks are ref'd appropriately
                 else => @compileError("unrecognized type in Params: " ++ @typeName(FieldType)),
             }
         }
@@ -1773,18 +1916,30 @@ pub const Builder = struct {
         //// the above blocks are rendered by ir_gen after the rest of codegen
     }
 
-    async fn findDecl(irb: *Builder, scope: *Scope, name: []const u8) ?*Decl {
+    const Ident = union(enum) {
+        NotFound,
+        Decl: *Decl,
+        VarScope: *Scope.Var,
+    };
+
+    async fn findIdent(irb: *Builder, scope: *Scope, name: []const u8) Ident {
         var s = scope;
         while (true) {
             switch (s.id) {
+                Scope.Id.Root => return Ident.NotFound,
                 Scope.Id.Decls => {
                     const decls = @fieldParentPtr(Scope.Decls, "base", s);
                     const table = await (async decls.getTableReadOnly() catch unreachable);
                     if (table.get(name)) |entry| {
-                        return entry.value;
+                        return Ident{ .Decl = entry.value };
+                    }
+                },
+                Scope.Id.Var => {
+                    const var_scope = @fieldParentPtr(Scope.Var, "base", s);
+                    if (mem.eql(u8, var_scope.name, name)) {
+                        return Ident{ .VarScope = var_scope };
                     }
                 },
-                Scope.Id.Root => return null,
                 else => {},
             }
             s = s.parent.?;
src-self-hosted/llvm.zig
@@ -30,6 +30,7 @@ pub const AddGlobal = c.LLVMAddGlobal;
 pub const AddModuleCodeViewFlag = c.ZigLLVMAddModuleCodeViewFlag;
 pub const AddModuleDebugInfoFlag = c.ZigLLVMAddModuleDebugInfoFlag;
 pub const ArrayType = c.LLVMArrayType;
+pub const BuildLoad = c.LLVMBuildLoad;
 pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation;
 pub const ConstAllOnes = c.LLVMConstAllOnes;
 pub const ConstArray = c.LLVMConstArray;
@@ -95,13 +96,25 @@ pub const SetInitializer = c.LLVMSetInitializer;
 pub const SetLinkage = c.LLVMSetLinkage;
 pub const SetTarget = c.LLVMSetTarget;
 pub const SetUnnamedAddr = c.LLVMSetUnnamedAddr;
+pub const SetVolatile = c.LLVMSetVolatile;
 pub const StructTypeInContext = c.LLVMStructTypeInContext;
 pub const TokenTypeInContext = c.LLVMTokenTypeInContext;
-pub const TypeOf = c.LLVMTypeOf;
 pub const VoidTypeInContext = c.LLVMVoidTypeInContext;
 pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext;
 pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext;
 
+pub const GetElementType = LLVMGetElementType;
+extern fn LLVMGetElementType(Ty: TypeRef) TypeRef;
+
+pub const TypeOf = LLVMTypeOf;
+extern fn LLVMTypeOf(Val: ValueRef) TypeRef;
+
+pub const BuildStore = LLVMBuildStore;
+extern fn LLVMBuildStore(arg0: BuilderRef, Val: ValueRef, Ptr: ValueRef) ?ValueRef;
+
+pub const BuildAlloca = LLVMBuildAlloca;
+extern fn LLVMBuildAlloca(arg0: BuilderRef, Ty: TypeRef, Name: ?[*]const u8) ?ValueRef;
+
 pub const ConstInBoundsGEP = LLVMConstInBoundsGEP;
 pub extern fn LLVMConstInBoundsGEP(ConstantVal: ValueRef, ConstantIndices: [*]ValueRef, NumIndices: c_uint) ?ValueRef;
 
src-self-hosted/scope.zig
@@ -6,23 +6,26 @@ 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: usize,
+    ref_count: std.atomic.Int(usize),
 
+    /// Thread-safe
     pub fn ref(base: *Scope) void {
-        base.ref_count += 1;
+        _ = base.ref_count.incr();
     }
 
+    /// Thread-safe
     pub fn deref(base: *Scope, comp: *Compilation) void {
-        base.ref_count -= 1;
-        if (base.ref_count == 0) {
+        if (base.ref_count.decr() == 1) {
             if (base.parent) |parent| parent.deref(comp);
             switch (base.id) {
                 Id.Root => @fieldParentPtr(Root, "base", base).destroy(comp),
@@ -32,6 +35,7 @@ pub const Scope = struct {
                 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),
             }
         }
     }
@@ -49,15 +53,15 @@ pub const Scope = struct {
         var scope = base;
         while (true) {
             switch (scope.id) {
-                Id.FnDef => return @fieldParentPtr(FnDef, "base", base),
-                Id.Decls => return null,
+                Id.FnDef => return @fieldParentPtr(FnDef, "base", scope),
+                Id.Root, Id.Decls => return null,
 
                 Id.Block,
                 Id.Defer,
                 Id.DeferExpr,
                 Id.CompTime,
-                Id.Root,
-                => scope = scope.parent orelse return null,
+                Id.Var,
+                => scope = scope.parent.?,
             }
         }
     }
@@ -66,7 +70,7 @@ pub const Scope = struct {
         var scope = base;
         while (true) {
             switch (scope.id) {
-                Id.DeferExpr => return @fieldParentPtr(DeferExpr, "base", base),
+                Id.DeferExpr => return @fieldParentPtr(DeferExpr, "base", scope),
 
                 Id.FnDef,
                 Id.Decls,
@@ -76,11 +80,21 @@ pub const Scope = struct {
                 Id.Defer,
                 Id.CompTime,
                 Id.Root,
+                Id.Var,
                 => scope = scope.parent orelse return null,
             }
         }
     }
 
+    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,
         Decls,
@@ -89,6 +103,7 @@ pub const Scope = struct {
         CompTime,
         Defer,
         DeferExpr,
+        Var,
     };
 
     pub const Root = struct {
@@ -100,16 +115,16 @@ pub const Scope = struct {
         /// Takes ownership of realpath
         /// Takes ownership of tree, will deinit and destroy when done.
         pub fn create(comp: *Compilation, tree: *ast.Tree, realpath: []u8) !*Root {
-            const self = try comp.gpa().create(Root{
+            const self = try comp.gpa().createOne(Root);
+            self.* = Root{
                 .base = Scope{
                     .id = Id.Root,
                     .parent = null,
-                    .ref_count = 1,
+                    .ref_count = std.atomic.Int(usize).init(1),
                 },
                 .tree = tree,
                 .realpath = realpath,
-            });
-            errdefer comp.gpa().destroy(self);
+            };
 
             return self;
         }
@@ -137,16 +152,13 @@ pub const Scope = struct {
 
         /// Creates a Decls scope with 1 reference
         pub fn create(comp: *Compilation, parent: *Scope) !*Decls {
-            const self = try comp.gpa().create(Decls{
-                .base = Scope{
-                    .id = Id.Decls,
-                    .parent = parent,
-                    .ref_count = 1,
-                },
+            const self = try comp.gpa().createOne(Decls);
+            self.* = Decls{
+                .base = undefined,
                 .table = event.Locked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())),
                 .name_future = event.Future(void).init(comp.loop),
-            });
-            parent.ref();
+            };
+            self.base.init(Id.Decls, parent);
             return self;
         }
 
@@ -199,21 +211,16 @@ pub const Scope = struct {
 
         /// Creates a Block scope with 1 reference
         pub fn create(comp: *Compilation, parent: *Scope) !*Block {
-            const self = try comp.gpa().create(Block{
-                .base = Scope{
-                    .id = Id.Block,
-                    .parent = parent,
-                    .ref_count = 1,
-                },
+            const self = try comp.gpa().createOne(Block);
+            self.* = Block{
+                .base = undefined,
                 .incoming_values = undefined,
                 .incoming_blocks = undefined,
                 .end_block = undefined,
                 .is_comptime = undefined,
                 .safety = Safety.Auto,
-            });
-            errdefer comp.gpa().destroy(self);
-
-            parent.ref();
+            };
+            self.base.init(Id.Block, parent);
             return self;
         }
 
@@ -226,22 +233,17 @@ pub const Scope = struct {
         base: Scope,
 
         /// This reference is not counted so that the scope can get destroyed with the function
-        fn_val: *Value.Fn,
+        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{
-                .base = Scope{
-                    .id = Id.FnDef,
-                    .parent = parent,
-                    .ref_count = 1,
-                },
-                .fn_val = undefined,
-            });
-
-            parent.ref();
-
+            const self = try comp.gpa().createOne(FnDef);
+            self.* = FnDef{
+                .base = undefined,
+                .fn_val = null,
+            };
+            self.base.init(Id.FnDef, parent);
             return self;
         }
 
@@ -255,15 +257,9 @@ pub const Scope = struct {
 
         /// Creates a CompTime scope with 1 reference
         pub fn create(comp: *Compilation, parent: *Scope) !*CompTime {
-            const self = try comp.gpa().create(CompTime{
-                .base = Scope{
-                    .id = Id.CompTime,
-                    .parent = parent,
-                    .ref_count = 1,
-                },
-            });
-
-            parent.ref();
+            const self = try comp.gpa().createOne(CompTime);
+            self.* = CompTime{ .base = undefined };
+            self.base.init(Id.CompTime, parent);
             return self;
         }
 
@@ -289,20 +285,14 @@ pub const Scope = struct {
             kind: Kind,
             defer_expr_scope: *DeferExpr,
         ) !*Defer {
-            const self = try comp.gpa().create(Defer{
-                .base = Scope{
-                    .id = Id.Defer,
-                    .parent = parent,
-                    .ref_count = 1,
-                },
+            const self = try comp.gpa().createOne(Defer);
+            self.* = Defer{
+                .base = undefined,
                 .defer_expr_scope = defer_expr_scope,
                 .kind = kind,
-            });
-            errdefer comp.gpa().destroy(self);
-
+            };
+            self.base.init(Id.Defer, parent);
             defer_expr_scope.base.ref();
-
-            parent.ref();
             return self;
         }
 
@@ -319,18 +309,13 @@ pub const Scope = struct {
 
         /// 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{
-                .base = Scope{
-                    .id = Id.DeferExpr,
-                    .parent = parent,
-                    .ref_count = 1,
-                },
+            const self = try comp.gpa().createOne(DeferExpr);
+            self.* = DeferExpr{
+                .base = undefined,
                 .expr_node = expr_node,
                 .reported_err = false,
-            });
-            errdefer comp.gpa().destroy(self);
-
-            parent.ref();
+            };
+            self.base.init(Id.DeferExpr, parent);
             return self;
         }
 
@@ -338,4 +323,74 @@ pub const Scope = struct {
             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().createOne(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);
+        }
+    };
 };
src-self-hosted/type.zig
@@ -141,9 +141,13 @@ pub const Type = struct {
             Id.Promise,
             => return true,
 
+            Id.Pointer => {
+                const ptr_type = @fieldParentPtr(Pointer, "base", base);
+                return ptr_type.key.child_type.hasBits();
+            },
+
             Id.ErrorSet => @panic("TODO"),
             Id.Enum => @panic("TODO"),
-            Id.Pointer => @panic("TODO"),
             Id.Struct => @panic("TODO"),
             Id.Array => @panic("TODO"),
             Id.Optional => @panic("TODO"),
@@ -222,29 +226,65 @@ pub const Type = struct {
     pub const Fn = struct {
         base: Type,
         key: Key,
+        non_key: NonKey,
         garbage_node: std.atomic.Stack(*Fn).Node,
 
+        pub const Kind = enum {
+            Normal,
+            Generic,
+        };
+
+        pub const NonKey = union {
+            Normal: Normal,
+            Generic: void,
+
+            pub const Normal = struct {
+                variable_list: std.ArrayList(*Scope.Var),
+            };
+        };
+
         pub const Key = struct {
             data: Data,
             alignment: ?u32,
 
-            pub const Data = union(enum) {
+            pub const Data = union(Kind) {
                 Generic: Generic,
                 Normal: Normal,
             };
 
+            pub const Normal = struct {
+                params: []Param,
+                return_type: *Type,
+                is_var_args: bool,
+                cc: CallingConvention,
+            };
+
+            pub const Generic = struct {
+                param_count: usize,
+                cc: CC,
+
+                pub const CC = union(CallingConvention) {
+                    Auto,
+                    C,
+                    Cold,
+                    Naked,
+                    Stdcall,
+                    Async: *Type, // allocator type
+                };
+            };
+
             pub fn hash(self: *const Key) u32 {
                 var result: u32 = 0;
                 result +%= hashAny(self.alignment, 0);
                 switch (self.data) {
-                    Data.Generic => |generic| {
+                    Kind.Generic => |generic| {
                         result +%= hashAny(generic.param_count, 1);
                         switch (generic.cc) {
                             CallingConvention.Async => |allocator_type| result +%= hashAny(allocator_type, 2),
                             else => result +%= hashAny(CallingConvention(generic.cc), 3),
                         }
                     },
-                    Data.Normal => |normal| {
+                    Kind.Normal => |normal| {
                         result +%= hashAny(normal.return_type, 4);
                         result +%= hashAny(normal.is_var_args, 5);
                         result +%= hashAny(normal.cc, 6);
@@ -264,7 +304,7 @@ pub const Type = struct {
                 }
                 if (@TagType(Data)(self.data) != @TagType(Data)(other.data)) return false;
                 switch (self.data) {
-                    Data.Generic => |*self_generic| {
+                    Kind.Generic => |*self_generic| {
                         const other_generic = &other.data.Generic;
                         if (self_generic.param_count != other_generic.param_count) return false;
                         if (CallingConvention(self_generic.cc) != CallingConvention(other_generic.cc)) return false;
@@ -276,7 +316,7 @@ pub const Type = struct {
                             else => {},
                         }
                     },
-                    Data.Normal => |*self_normal| {
+                    Kind.Normal => |*self_normal| {
                         const other_normal = &other.data.Normal;
                         if (self_normal.cc != other_normal.cc) return false;
                         if (self_normal.is_var_args != other_normal.is_var_args) return false;
@@ -293,13 +333,13 @@ pub const Type = struct {
 
             pub fn deref(key: Key, comp: *Compilation) void {
                 switch (key.data) {
-                    Key.Data.Generic => |generic| {
+                    Kind.Generic => |generic| {
                         switch (generic.cc) {
                             CallingConvention.Async => |allocator_type| allocator_type.base.deref(comp),
                             else => {},
                         }
                     },
-                    Key.Data.Normal => |normal| {
+                    Kind.Normal => |normal| {
                         normal.return_type.base.deref(comp);
                         for (normal.params) |param| {
                             param.typ.base.deref(comp);
@@ -310,13 +350,13 @@ pub const Type = struct {
 
             pub fn ref(key: Key) void {
                 switch (key.data) {
-                    Key.Data.Generic => |generic| {
+                    Kind.Generic => |generic| {
                         switch (generic.cc) {
                             CallingConvention.Async => |allocator_type| allocator_type.base.ref(),
                             else => {},
                         }
                     },
-                    Key.Data.Normal => |normal| {
+                    Kind.Normal => |normal| {
                         normal.return_type.base.ref();
                         for (normal.params) |param| {
                             param.typ.base.ref();
@@ -326,27 +366,6 @@ pub const Type = struct {
             }
         };
 
-        pub const Normal = struct {
-            params: []Param,
-            return_type: *Type,
-            is_var_args: bool,
-            cc: CallingConvention,
-        };
-
-        pub const Generic = struct {
-            param_count: usize,
-            cc: CC,
-
-            pub const CC = union(CallingConvention) {
-                Auto,
-                C,
-                Cold,
-                Naked,
-                Stdcall,
-                Async: *Type, // allocator type
-            };
-        };
-
         pub const CallingConvention = enum {
             Auto,
             C,
@@ -374,8 +393,8 @@ pub const Type = struct {
 
         pub fn paramCount(self: *Fn) usize {
             return switch (self.key.data) {
-                Key.Data.Generic => |generic| generic.param_count,
-                Key.Data.Normal => |normal| normal.params.len,
+                Kind.Generic => |generic| generic.param_count,
+                Kind.Normal => |normal| normal.params.len,
             };
         }
 
@@ -394,11 +413,13 @@ pub const Type = struct {
             key.ref();
             errdefer key.deref(comp);
 
-            const self = try comp.gpa().create(Fn{
+            const self = try comp.gpa().createOne(Fn);
+            self.* = Fn{
                 .base = undefined,
                 .key = key,
+                .non_key = undefined,
                 .garbage_node = undefined,
-            });
+            };
             errdefer comp.gpa().destroy(self);
 
             var name_buf = try std.Buffer.initSize(comp.gpa(), 0);
@@ -407,7 +428,8 @@ pub const Type = struct {
             const name_stream = &std.io.BufferOutStream.init(&name_buf).stream;
 
             switch (key.data) {
-                Key.Data.Generic => |generic| {
+                Kind.Generic => |generic| {
+                    self.non_key = NonKey{ .Generic = {} };
                     switch (generic.cc) {
                         CallingConvention.Async => |async_allocator_type| {
                             try name_stream.print("async<{}> ", async_allocator_type.name);
@@ -429,7 +451,10 @@ pub const Type = struct {
                     }
                     try name_stream.write(" var");
                 },
-                Key.Data.Normal => |normal| {
+                Kind.Normal => |normal| {
+                    self.non_key = NonKey{
+                        .Normal = NonKey.Normal{ .variable_list = std.ArrayList(*Scope.Var).init(comp.gpa()) },
+                    };
                     const cc_str = ccFnTypeStr(normal.cc);
                     try name_stream.print("{}fn(", cc_str);
                     for (normal.params) |param, i| {
@@ -462,6 +487,12 @@ pub const Type = struct {
 
         pub fn destroy(self: *Fn, comp: *Compilation) void {
             self.key.deref(comp);
+            switch (self.key.data) {
+                Kind.Generic => {},
+                Kind.Normal => {
+                    self.non_key.Normal.variable_list.deinit();
+                },
+            }
             comp.gpa().destroy(self);
         }
 
src-self-hosted/value.zig
@@ -60,7 +60,7 @@ pub const Value = struct {
     pub fn getLlvmConst(base: *Value, ofile: *ObjectFile) (error{OutOfMemory}!?llvm.ValueRef) {
         switch (base.id) {
             Id.Type => unreachable,
-            Id.Fn => @panic("TODO"),
+            Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmConst(ofile),
             Id.FnProto => return @fieldParentPtr(FnProto, "base", base).getLlvmConst(ofile),
             Id.Void => return null,
             Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile),
@@ -180,7 +180,7 @@ pub const Value = struct {
         child_scope: *Scope,
 
         /// parent is child_scope
-        block_scope: *Scope.Block,
+        block_scope: ?*Scope.Block,
 
         /// Path to the object file that contains this function
         containing_object: Buffer,
@@ -205,7 +205,7 @@ pub const Value = struct {
                 },
                 .fndef_scope = fndef_scope,
                 .child_scope = &fndef_scope.base,
-                .block_scope = undefined,
+                .block_scope = null,
                 .symbol_name = symbol_name,
                 .containing_object = Buffer.initNull(comp.gpa()),
                 .link_set_node = link_set_node,
@@ -231,6 +231,22 @@ pub const Value = struct {
             self.symbol_name.deinit();
             comp.gpa().destroy(self);
         }
+
+        /// We know that the function definition will end up in an .o file somewhere.
+        /// Here, all we have to do is generate a global prototype.
+        /// TODO cache the prototype per ObjectFile
+        pub fn getLlvmConst(self: *Fn, ofile: *ObjectFile) !?llvm.ValueRef {
+            const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
+            const llvm_fn = llvm.AddFunction(
+                ofile.module,
+                self.symbol_name.ptr(),
+                llvm_fn_type,
+            ) orelse return error.OutOfMemory;
+
+            // TODO port more logic from codegen.cpp:fn_llvm_value
+
+            return llvm_fn;
+        }
     };
 
     pub const Void = struct {