Commit 363f4facea

Andrew Kelley <superjoe30@gmail.com>
2018-07-15 06:04:12
self-hosted: generate LLVM IR for simple function
1 parent 28c3d48
src-self-hosted/codegen.zig
@@ -8,6 +8,7 @@ const ir = @import("ir.zig");
 const Value = @import("value.zig").Value;
 const Type = @import("type.zig").Type;
 const event = std.event;
+const assert = std.debug.assert;
 
 pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) !void {
     fn_val.base.ref();
@@ -35,9 +36,23 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code)
 
     try renderToLlvmModule(&ofile, fn_val, code);
 
+    // TODO module level assembly
+    //if (buf_len(&g->global_asm) != 0) {
+    //    LLVMSetModuleInlineAsm(g->module, buf_ptr(&g->global_asm));
+    //}
+
+    // TODO
+    //ZigLLVMDIBuilderFinalize(g->dbuilder);
+
     if (comp.verbose_llvm_ir) {
         llvm.DumpModule(ofile.module);
     }
+
+    // verify the llvm module when safety is on
+    if (std.debug.runtime_safety) {
+        var error_ptr: ?[*]u8 = null;
+        _ = llvm.VerifyModule(ofile.module, llvm.AbortProcessAction, &error_ptr);
+    }
 }
 
 pub const ObjectFile = struct {
@@ -55,5 +70,146 @@ pub const ObjectFile = struct {
 pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) !void {
     // TODO audit more of codegen.cpp:fn_llvm_value and port more logic
     const llvm_fn_type = try fn_val.base.typeof.getLlvmType(ofile);
-    const llvm_fn = llvm.AddFunction(ofile.module, fn_val.symbol_name.ptr(), llvm_fn_type);
+    const llvm_fn = llvm.AddFunction(
+        ofile.module,
+        fn_val.symbol_name.ptr(),
+        llvm_fn_type,
+    ) orelse return error.OutOfMemory;
+
+    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");
+    }
+
+    // TODO
+    //if (fn_val.align_stack) |align_stack| {
+    //    try addLLVMFnAttrInt(ofile, llvm_fn, "alignstack", align_stack);
+    //}
+
+    const fn_type = fn_val.base.typeof.cast(Type.Fn).?;
+
+    try addLLVMFnAttr(ofile, llvm_fn, "nounwind");
+    //add_uwtable_attr(g, fn_table_entry->llvm_value);
+    try addLLVMFnAttr(ofile, llvm_fn, "nobuiltin");
+
+    //if (g->build_mode == BuildModeDebug && fn_table_entry->fn_inline != FnInlineAlways) {
+    //    ZigLLVMAddFunctionAttr(fn_table_entry->llvm_value, "no-frame-pointer-elim", "true");
+    //    ZigLLVMAddFunctionAttr(fn_table_entry->llvm_value, "no-frame-pointer-elim-non-leaf", nullptr);
+    //}
+
+    //if (fn_table_entry->section_name) {
+    //    LLVMSetSection(fn_table_entry->llvm_value, buf_ptr(fn_table_entry->section_name));
+    //}
+    //if (fn_table_entry->align_bytes > 0) {
+    //    LLVMSetAlignment(fn_table_entry->llvm_value, (unsigned)fn_table_entry->align_bytes);
+    //} else {
+    //    // We'd like to set the best alignment for the function here, but on Darwin LLVM gives
+    //    // "Cannot getTypeInfo() on a type that is unsized!" assertion failure when calling
+    //    // any of the functions for getting alignment. Not specifying the alignment should
+    //    // use the ABI alignment, which is fine.
+    //}
+
+    //if (!type_has_bits(return_type)) {
+    //    // nothing to do
+    //} else if (type_is_codegen_pointer(return_type)) {
+    //    addLLVMAttr(fn_table_entry->llvm_value, 0, "nonnull");
+    //} else if (handle_is_ptr(return_type) &&
+    //        calling_convention_does_first_arg_return(fn_type->data.fn.fn_type_id.cc))
+    //{
+    //    addLLVMArgAttr(fn_table_entry->llvm_value, 0, "sret");
+    //    addLLVMArgAttr(fn_table_entry->llvm_value, 0, "nonnull");
+    //}
+
+    // TODO set parameter attributes
+
+    // TODO
+    //uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(g, fn_table_entry);
+    //if (err_ret_trace_arg_index != UINT32_MAX) {
+    //    addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)err_ret_trace_arg_index, "nonnull");
+    //}
+
+    const cur_ret_ptr = if (fn_type.return_type.handleIsPtr()) llvm.GetParam(llvm_fn, 0) else null;
+
+    // build all basic blocks
+    for (code.basic_block_list.toSlice()) |bb| {
+        bb.llvm_block = llvm.AppendBasicBlockInContext(
+            ofile.context,
+            llvm_fn,
+            bb.name_hint,
+        ) orelse return error.OutOfMemory;
+    }
+    const entry_bb = code.basic_block_list.at(0);
+    llvm.PositionBuilderAtEnd(ofile.builder, entry_bb.llvm_block);
+
+    llvm.ClearCurrentDebugLocation(ofile.builder);
+
+    // TODO set up error return tracing
+    // TODO allocate temporary stack values
+    // TODO create debug variable declarations for variables and allocate all local variables
+    // TODO finishing error return trace setup. we have to do this after all the allocas.
+    // TODO create debug variable declarations for parameters
+
+    for (code.basic_block_list.toSlice()) |current_block| {
+        llvm.PositionBuilderAtEnd(ofile.builder, current_block.llvm_block);
+        for (current_block.instruction_list.toSlice()) |instruction| {
+            if (instruction.ref_count == 0 and !instruction.hasSideEffects()) continue;
+
+            instruction.llvm_value = try instruction.render(ofile, fn_val);
+        }
+        current_block.llvm_exit_block = llvm.GetInsertBlock(ofile.builder);
+    }
+}
+
+fn addLLVMAttr(
+    ofile: *ObjectFile,
+    val: llvm.ValueRef,
+    attr_index: llvm.AttributeIndex,
+    attr_name: []const u8,
+) !void {
+    const kind_id = llvm.GetEnumAttributeKindForName(attr_name.ptr, attr_name.len);
+    assert(kind_id != 0);
+    const llvm_attr = llvm.CreateEnumAttribute(ofile.context, kind_id, 0) orelse return error.OutOfMemory;
+    llvm.AddAttributeAtIndex(val, attr_index, llvm_attr);
+}
+
+fn addLLVMAttrStr(
+    ofile: *ObjectFile,
+    val: llvm.ValueRef,
+    attr_index: llvm.AttributeIndex,
+    attr_name: []const u8,
+    attr_val: []const u8,
+) !void {
+    const llvm_attr = llvm.CreateStringAttribute(
+        ofile.context,
+        attr_name.ptr,
+        @intCast(c_uint, attr_name.len),
+        attr_val.ptr,
+        @intCast(c_uint, attr_val.len),
+    ) orelse return error.OutOfMemory;
+    llvm.AddAttributeAtIndex(val, attr_index, llvm_attr);
+}
+
+fn addLLVMAttrInt(
+    val: llvm.ValueRef,
+    attr_index: llvm.AttributeIndex,
+    attr_name: []const u8,
+    attr_val: u64,
+) !void {
+    const kind_id = llvm.GetEnumAttributeKindForName(attr_name.ptr, attr_name.len);
+    assert(kind_id != 0);
+    const llvm_attr = llvm.CreateEnumAttribute(ofile.context, kind_id, attr_val) orelse return error.OutOfMemory;
+    llvm.AddAttributeAtIndex(val, attr_index, llvm_attr);
+}
+
+fn addLLVMFnAttr(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8) !void {
+    return addLLVMAttr(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name);
+}
+
+fn addLLVMFnAttrStr(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8, attr_val: []const u8) !void {
+    return addLLVMAttrStr(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name, attr_val);
+}
+
+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);
 }
src-self-hosted/compilation.zig
@@ -606,6 +606,10 @@ pub const Compilation = struct {
         return error.Todo;
     }
 
+    pub fn haveLibC(self: *Compilation) bool {
+        return self.libc_link_lib != null;
+    }
+
     pub fn addLinkLib(self: *Compilation, name: []const u8, provided_explicitly: bool) !*LinkLib {
         const is_libc = mem.eql(u8, name, "c");
 
@@ -741,7 +745,7 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void {
         analyzed_code.dump();
     }
 
-    // Kick off rendering to LLVM comp, but it doesn't block the fn decl
+    // Kick off rendering to LLVM module, but it doesn't block the fn decl
     // analysis from being complete.
     try comp.build_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code);
 }
src-self-hosted/ir.zig
@@ -10,6 +10,8 @@ const assert = std.debug.assert;
 const Token = std.zig.Token;
 const ParsedFile = @import("parsed_file.zig").ParsedFile;
 const Span = @import("errmsg.zig").Span;
+const llvm = @import("llvm.zig");
+const ObjectFile = @import("codegen.zig").ObjectFile;
 
 pub const LVal = enum {
     None,
@@ -61,6 +63,9 @@ pub const Instruction = struct {
     /// the instruction that this one derives from in analysis
     parent: ?*Instruction,
 
+    /// populated durign codegen
+    llvm_value: ?llvm.ValueRef,
+
     pub fn cast(base: *Instruction, comptime T: type) ?*T {
         if (base.id == comptime typeToId(T)) {
             return @fieldParentPtr(T, "base", base);
@@ -108,14 +113,25 @@ pub const Instruction = struct {
         inline while (i < @memberCount(Id)) : (i += 1) {
             if (base.id == @field(Id, @memberName(Id, i))) {
                 const T = @field(Instruction, @memberName(Id, i));
-                const new_inst = try @fieldParentPtr(T, "base", base).analyze(ira);
-                new_inst.linkToParent(base);
-                return new_inst;
+                return @fieldParentPtr(T, "base", base).analyze(ira);
             }
         }
         unreachable;
     }
 
+    pub fn render(base: *Instruction, ofile: *ObjectFile, fn_val: *Value.Fn) (error{OutOfMemory}!?llvm.ValueRef) {
+        switch (base.id) {
+            Id.Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val),
+            Id.Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val),
+            Id.Ref => @panic("TODO"),
+            Id.DeclVar => @panic("TODO"),
+            Id.CheckVoidStmt => @panic("TODO"),
+            Id.Phi => @panic("TODO"),
+            Id.Br => @panic("TODO"),
+            Id.AddImplicitReturnType => @panic("TODO"),
+        }
+    }
+
     fn getAsParam(param: *Instruction) !*Instruction {
         const child = param.child orelse return error.SemanticAnalysisFailed;
         switch (child.val) {
@@ -186,6 +202,10 @@ pub const Instruction = struct {
             new_inst.val = IrVal{ .KnownValue = self.base.val.KnownValue.getRef() };
             return new_inst;
         }
+
+        pub fn render(self: *Const, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef {
+            return self.base.val.KnownValue.getLlvmConst(ofile);
+        }
     };
 
     pub const Return = struct {
@@ -214,6 +234,18 @@ pub const Instruction = struct {
 
             return ira.irb.build(Return, self.base.scope, self.base.span, Params{ .return_value = casted_value });
         }
+
+        pub fn render(self: *Return, ofile: *ObjectFile, fn_val: *Value.Fn) ?llvm.ValueRef {
+            const value = self.params.return_value.llvm_value;
+            const return_type = self.params.return_value.getKnownType();
+
+            if (return_type.handleIsPtr()) {
+                @panic("TODO");
+            } else {
+                _ = llvm.BuildRet(ofile.builder, value);
+            }
+            return null;
+        }
     };
 
     pub const Ref = struct {
@@ -387,12 +419,16 @@ pub const Variable = struct {
 
 pub const BasicBlock = struct {
     ref_count: usize,
-    name_hint: []const u8,
+    name_hint: [*]const u8, // must be a C string literal
     debug_id: usize,
     scope: *Scope,
     instruction_list: std.ArrayList(*Instruction),
     ref_instruction: ?*Instruction,
 
+    /// for codegen
+    llvm_block: llvm.BasicBlockRef,
+    llvm_exit_block: llvm.BasicBlockRef,
+
     /// the basic block that is derived from this one in analysis
     child: ?*BasicBlock,
 
@@ -426,7 +462,7 @@ pub const Code = struct {
     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);
+            std.debug.warn("{s}_{}:\n", bb.name_hint, bb.debug_id);
             for (bb.instruction_list.toSliceConst()) |instr| {
                 std.debug.warn("  ");
                 instr.dump();
@@ -475,7 +511,7 @@ pub const Builder = struct {
     }
 
     /// No need to clean up resources thanks to the arena allocator.
-    pub fn createBasicBlock(self: *Builder, scope: *Scope, name_hint: []const u8) !*BasicBlock {
+    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,
@@ -485,6 +521,8 @@ pub const Builder = struct {
             .child = null,
             .parent = null,
             .ref_instruction = null,
+            .llvm_block = undefined,
+            .llvm_exit_block = undefined,
         });
         self.next_debug_id += 1;
         return basic_block;
@@ -600,7 +638,7 @@ pub const Builder = struct {
         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.end_block = try irb.createBasicBlock(parent_scope, c"BlockEnd");
             block_scope.is_comptime = try irb.buildConstBool(
                 parent_scope,
                 Span.token(block.lbrace),
@@ -777,6 +815,7 @@ pub const Builder = struct {
                 .span = span,
                 .child = null,
                 .parent = null,
+                .llvm_value = undefined,
             },
             .params = params,
         });
@@ -968,7 +1007,7 @@ pub async fn gen(
     var irb = try Builder.init(comp, parsed_file);
     errdefer irb.abort();
 
-    const entry_block = try irb.createBasicBlock(scope, "Entry");
+    const entry_block = try irb.createBasicBlock(scope, c"Entry");
     entry_block.ref(); // Entry block gets a reference because we enter it to begin.
     try irb.setCursorAtEndAndAppendBlock(entry_block);
 
@@ -1013,6 +1052,7 @@ pub async fn analyze(comp: *Compilation, parsed_file: *ParsedFile, old_code: *Co
         }
 
         const return_inst = try old_instruction.analyze(&ira);
+        return_inst.linkToParent(old_instruction);
         // Note: if we ever modify the above to handle error.CompileError by continuing analysis,
         // then here we want to check if ira.isCompTime() and return early if true
 
src-self-hosted/llvm.zig
@@ -2,29 +2,91 @@ const builtin = @import("builtin");
 const c = @import("c.zig");
 const assert = @import("std").debug.assert;
 
+pub const AttributeIndex = c_uint;
+pub const Bool = c_int;
+
 pub const BuilderRef = removeNullability(c.LLVMBuilderRef);
 pub const ContextRef = removeNullability(c.LLVMContextRef);
 pub const ModuleRef = removeNullability(c.LLVMModuleRef);
 pub const ValueRef = removeNullability(c.LLVMValueRef);
 pub const TypeRef = removeNullability(c.LLVMTypeRef);
+pub const BasicBlockRef = removeNullability(c.LLVMBasicBlockRef);
+pub const AttributeRef = removeNullability(c.LLVMAttributeRef);
 
+pub const AddAttributeAtIndex = c.LLVMAddAttributeAtIndex;
 pub const AddFunction = c.LLVMAddFunction;
+pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation;
+pub const ConstInt = c.LLVMConstInt;
+pub const ConstStringInContext = c.LLVMConstStringInContext;
+pub const ConstStructInContext = c.LLVMConstStructInContext;
 pub const CreateBuilderInContext = c.LLVMCreateBuilderInContext;
+pub const CreateEnumAttribute = c.LLVMCreateEnumAttribute;
+pub const CreateStringAttribute = c.LLVMCreateStringAttribute;
 pub const DisposeBuilder = c.LLVMDisposeBuilder;
 pub const DisposeModule = c.LLVMDisposeModule;
+pub const DoubleTypeInContext = c.LLVMDoubleTypeInContext;
 pub const DumpModule = c.LLVMDumpModule;
+pub const FP128TypeInContext = c.LLVMFP128TypeInContext;
+pub const FloatTypeInContext = c.LLVMFloatTypeInContext;
+pub const GetEnumAttributeKindForName = c.LLVMGetEnumAttributeKindForName;
+pub const GetMDKindIDInContext = c.LLVMGetMDKindIDInContext;
+pub const HalfTypeInContext = c.LLVMHalfTypeInContext;
+pub const InsertBasicBlockInContext = c.LLVMInsertBasicBlockInContext;
+pub const Int128TypeInContext = c.LLVMInt128TypeInContext;
+pub const Int16TypeInContext = c.LLVMInt16TypeInContext;
+pub const Int1TypeInContext = c.LLVMInt1TypeInContext;
+pub const Int32TypeInContext = c.LLVMInt32TypeInContext;
+pub const Int64TypeInContext = c.LLVMInt64TypeInContext;
+pub const Int8TypeInContext = c.LLVMInt8TypeInContext;
+pub const IntPtrTypeForASInContext = c.LLVMIntPtrTypeForASInContext;
+pub const IntPtrTypeInContext = c.LLVMIntPtrTypeInContext;
+pub const IntTypeInContext = c.LLVMIntTypeInContext;
+pub const LabelTypeInContext = c.LLVMLabelTypeInContext;
+pub const MDNodeInContext = c.LLVMMDNodeInContext;
+pub const MDStringInContext = c.LLVMMDStringInContext;
+pub const MetadataTypeInContext = c.LLVMMetadataTypeInContext;
 pub const ModuleCreateWithNameInContext = c.LLVMModuleCreateWithNameInContext;
+pub const PPCFP128TypeInContext = c.LLVMPPCFP128TypeInContext;
+pub const StructTypeInContext = c.LLVMStructTypeInContext;
+pub const TokenTypeInContext = c.LLVMTokenTypeInContext;
 pub const VoidTypeInContext = c.LLVMVoidTypeInContext;
+pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext;
+pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext;
+pub const ConstAllOnes = c.LLVMConstAllOnes;
+pub const ConstNull = c.LLVMConstNull;
+
+pub const VerifyModule = LLVMVerifyModule;
+extern fn LLVMVerifyModule(M: ModuleRef, Action: VerifierFailureAction, OutMessage: *?[*]u8) Bool;
+
+pub const GetInsertBlock = LLVMGetInsertBlock;
+extern fn LLVMGetInsertBlock(Builder: BuilderRef) BasicBlockRef;
 
 pub const FunctionType = LLVMFunctionType;
 extern fn LLVMFunctionType(
     ReturnType: TypeRef,
     ParamTypes: [*]TypeRef,
     ParamCount: c_uint,
-    IsVarArg: c_int,
+    IsVarArg: Bool,
 ) ?TypeRef;
 
+pub const GetParam = LLVMGetParam;
+extern fn LLVMGetParam(Fn: ValueRef, Index: c_uint) ValueRef;
+
+pub const AppendBasicBlockInContext = LLVMAppendBasicBlockInContext;
+extern fn LLVMAppendBasicBlockInContext(C: ContextRef, Fn: ValueRef, Name: [*]const u8) ?BasicBlockRef;
+
+pub const PositionBuilderAtEnd = LLVMPositionBuilderAtEnd;
+extern fn LLVMPositionBuilderAtEnd(Builder: BuilderRef, Block: BasicBlockRef) void;
+
+pub const AbortProcessAction = VerifierFailureAction.LLVMAbortProcessAction;
+pub const PrintMessageAction = VerifierFailureAction.LLVMPrintMessageAction;
+pub const ReturnStatusAction = VerifierFailureAction.LLVMReturnStatusAction;
+pub const VerifierFailureAction = c.LLVMVerifierFailureAction;
+
 fn removeNullability(comptime T: type) type {
     comptime assert(@typeId(T) == builtin.TypeId.Optional);
     return T.Child;
 }
+
+pub const BuildRet = LLVMBuildRet;
+extern fn LLVMBuildRet(arg0: BuilderRef, V: ?ValueRef) ValueRef;
src-self-hosted/scope.zig
@@ -1,4 +1,5 @@
 const std = @import("std");
+const builtin = @import("builtin");
 const Allocator = mem.Allocator;
 const Decl = @import("decl.zig").Decl;
 const Compilation = @import("compilation.zig").Compilation;
@@ -6,6 +7,7 @@ const mem = std.mem;
 const ast = std.zig.ast;
 const Value = @import("value.zig").Value;
 const ir = @import("ir.zig");
+const Span = @import("errmsg.zig").Span;
 
 pub const Scope = struct {
     id: Id,
@@ -93,6 +95,35 @@ pub const Scope = struct {
         end_block: *ir.BasicBlock,
         is_comptime: *ir.Instruction,
 
+        safety: Safety,
+
+        const Safety = union(enum) {
+            Auto,
+            Manual: Manual,
+
+            const Manual = struct {
+                /// the source span that disabled the safety value
+                span: Span,
+
+                /// whether safety is enabled
+                enabled: bool,
+            };
+
+            fn get(self: Safety, comp: *Compilation) bool {
+                return switch (self) {
+                    Safety.Auto => switch (comp.build_mode) {
+                        builtin.Mode.Debug,
+                        builtin.Mode.ReleaseSafe,
+                        => true,
+                        builtin.Mode.ReleaseFast,
+                        builtin.Mode.ReleaseSmall,
+                        => false,
+                    },
+                    @TagType(Safety).Manual => |man| man.enabled,
+                };
+            }
+        };
+
         /// Creates a Block scope with 1 reference
         pub fn create(comp: *Compilation, parent: ?*Scope) !*Block {
             const self = try comp.a().create(Block{
@@ -105,6 +136,7 @@ pub const Scope = struct {
                 .incoming_blocks = undefined,
                 .end_block = undefined,
                 .is_comptime = undefined,
+                .safety = Safety.Auto,
             });
             errdefer comp.a().destroy(self);
 
src-self-hosted/type.zig
@@ -72,6 +72,81 @@ pub const Type = struct {
         }
     }
 
+    pub fn handleIsPtr(base: *Type) bool {
+        switch (base.id) {
+            Id.Type,
+            Id.ComptimeFloat,
+            Id.ComptimeInt,
+            Id.Undefined,
+            Id.Null,
+            Id.Namespace,
+            Id.Block,
+            Id.BoundFn,
+            Id.ArgTuple,
+            Id.Opaque,
+            => unreachable,
+
+            Id.NoReturn,
+            Id.Void,
+            Id.Bool,
+            Id.Int,
+            Id.Float,
+            Id.Pointer,
+            Id.ErrorSet,
+            Id.Enum,
+            Id.Fn,
+            Id.Promise,
+            => return false,
+
+            Id.Struct => @panic("TODO"),
+            Id.Array => @panic("TODO"),
+            Id.Optional => @panic("TODO"),
+            Id.ErrorUnion => @panic("TODO"),
+            Id.Union => @panic("TODO"),
+        }
+    }
+
+    pub fn hasBits(base: *Type) bool {
+        switch (base.id) {
+            Id.Type,
+            Id.ComptimeFloat,
+            Id.ComptimeInt,
+            Id.Undefined,
+            Id.Null,
+            Id.Namespace,
+            Id.Block,
+            Id.BoundFn,
+            Id.ArgTuple,
+            Id.Opaque,
+            => unreachable,
+
+            Id.Void,
+            Id.NoReturn,
+            => return false,
+
+            Id.Bool,
+            Id.Int,
+            Id.Float,
+            Id.Fn,
+            Id.Promise,
+            => return true,
+
+            Id.ErrorSet => @panic("TODO"),
+            Id.Enum => @panic("TODO"),
+            Id.Pointer => @panic("TODO"),
+            Id.Struct => @panic("TODO"),
+            Id.Array => @panic("TODO"),
+            Id.Optional => @panic("TODO"),
+            Id.ErrorUnion => @panic("TODO"),
+            Id.Union => @panic("TODO"),
+        }
+    }
+
+    pub fn cast(base: *Type, comptime T: type) ?*T {
+        if (base.id != @field(Id, @typeName(T))) return null;
+        return @fieldParentPtr(T, "base", base);
+    }
+
     pub fn dump(base: *const Type) void {
         std.debug.warn("{}", @tagName(base.id));
     }
src-self-hosted/value.zig
@@ -2,6 +2,8 @@ const std = @import("std");
 const builtin = @import("builtin");
 const Scope = @import("scope.zig").Scope;
 const Compilation = @import("compilation.zig").Compilation;
+const ObjectFile = @import("codegen.zig").ObjectFile;
+const llvm = @import("llvm.zig");
 
 /// Values are ref-counted, heap-allocated, and copy-on-write
 /// If there is only 1 ref then write need not copy
@@ -39,6 +41,17 @@ pub const Value = struct {
         std.debug.warn("{}", @tagName(base.id));
     }
 
+    pub fn getLlvmConst(base: *Value, ofile: *ObjectFile) (error{OutOfMemory}!?llvm.ValueRef) {
+        switch (base.id) {
+            Id.Type => unreachable,
+            Id.Fn => @panic("TODO"),
+            Id.Void => return null,
+            Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile),
+            Id.NoReturn => unreachable,
+            Id.Ptr => @panic("TODO"),
+        }
+    }
+
     pub const Id = enum {
         Type,
         Fn,
@@ -123,6 +136,15 @@ pub const Value = struct {
         pub fn destroy(self: *Bool, comp: *Compilation) void {
             comp.a().destroy(self);
         }
+
+        pub fn getLlvmConst(self: *Bool, ofile: *ObjectFile) ?llvm.ValueRef {
+            const llvm_type = llvm.Int1TypeInContext(ofile.context);
+            if (self.x) {
+                return llvm.ConstAllOnes(llvm_type);
+            } else {
+                return llvm.ConstNull(llvm_type);
+            }
+        }
     };
 
     pub const NoReturn = struct {
std/event/future.zig
@@ -40,6 +40,16 @@ pub fn Future(comptime T: type) type {
             return &self.data;
         }
 
+        /// Gets the data without waiting for it. If it's available, a pointer is
+        /// returned. Otherwise, null is returned.
+        pub fn getOrNull(self: *Self) ?*T {
+            if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 1) {
+                return &self.data;
+            } else {
+                return null;
+            }
+        }
+
         /// Make the data become available. May be called only once.
         /// Before calling this, modify the `data` property.
         pub fn resolve(self: *Self) void {