Commit 31a59c229c

Andrew Kelley <andrew@ziglang.org>
2021-07-27 04:12:34
stage2: improvements towards `zig test`
* Add AIR instruction: struct_field_val - This is part of an effort to eliminate the AIR instruction `ref`. - It's implemented for C backend and LLVM backend so far. * Rename `resolvePossiblyUndefinedValue` to `resolveMaybeUndefVal` just to save some columns on long lines. * Sema: add `fieldVal` alongside `fieldPtr` (renamed from `namedFieldPtr`). This is part of an effort to eliminate the AIR instruction `ref`. The idea is to avoid unnecessary loads, stores, stack usage, and IR instructions, by paying a DRY cost. LLVM backend improvements: * internal linkage vs exported linkage is implemented, along with aliases. There is an issue with incremental updates due to missing LLVM API for deleting aliases; see the relevant comment in this commit. - `updateDeclExports` is hooked up to the LLVM backend now. * Fix usage of `Type.tag() == .noreturn` rather than calling `isNoReturn()`. * Properly mark global variables as mutable/constant. * Fix llvm type generation of function pointers * Fix codegen for calls of function pointers * Implement llvm type generation of error unions and error sets. * Implement AIR instructions: addwrap, subwrap, mul, mulwrap, div, bit_and, bool_and, bit_or, bool_or, xor, struct_field_ptr, struct_field_val, unwrap_errunion_err, add for floats, sub for floats. After this commit, `zig test` on a file with `test "example" {}` correctly generates and executes a test binary. However the `test_functions` slice is undefined and just happens to be going into the .bss section, causing the length to be 0. The next step towards `zig test` will be replacing the `test_functions` Decl Value with the set of test function pointers, before it is sent to linker/codegen.
1 parent cdeea3b
src/codegen/llvm/bindings.zig
@@ -82,6 +82,24 @@ pub const Value = opaque {
 
     pub const setGlobalConstant = LLVMSetGlobalConstant;
     extern fn LLVMSetGlobalConstant(GlobalVar: *const Value, IsConstant: Bool) void;
+
+    pub const setLinkage = LLVMSetLinkage;
+    extern fn LLVMSetLinkage(Global: *const Value, Linkage: Linkage) void;
+
+    pub const setUnnamedAddr = LLVMSetUnnamedAddr;
+    extern fn LLVMSetUnnamedAddr(Global: *const Value, HasUnnamedAddr: Bool) void;
+
+    pub const deleteGlobal = LLVMDeleteGlobal;
+    extern fn LLVMDeleteGlobal(GlobalVar: *const Value) void;
+
+    pub const getNextGlobalAlias = LLVMGetNextGlobalAlias;
+    extern fn LLVMGetNextGlobalAlias(GA: *const Value) *const Value;
+
+    pub const getAliasee = LLVMAliasGetAliasee;
+    extern fn LLVMAliasGetAliasee(Alias: *const Value) *const Value;
+
+    pub const setAliasee = LLVMAliasSetAliasee;
+    extern fn LLVMAliasSetAliasee(Alias: *const Value, Aliasee: *const Value) void;
 };
 
 pub const Type = opaque {
@@ -145,6 +163,27 @@ pub const Module = opaque {
 
     pub const dump = LLVMDumpModule;
     extern fn LLVMDumpModule(M: *const Module) void;
+
+    pub const getFirstGlobalAlias = LLVMGetFirstGlobalAlias;
+    extern fn LLVMGetFirstGlobalAlias(M: *const Module) *const Value;
+
+    pub const getLastGlobalAlias = LLVMGetLastGlobalAlias;
+    extern fn LLVMGetLastGlobalAlias(M: *const Module) *const Value;
+
+    pub const addAlias = LLVMAddAlias;
+    extern fn LLVMAddAlias(
+        M: *const Module,
+        Ty: *const Type,
+        Aliasee: *const Value,
+        Name: [*:0]const u8,
+    ) *const Value;
+
+    pub const getNamedGlobalAlias = LLVMGetNamedGlobalAlias;
+    extern fn LLVMGetNamedGlobalAlias(
+        M: *const Module,
+        Name: [*]const u8,
+        NameLen: usize,
+    ) ?*const Value;
 };
 
 pub const lookupIntrinsicID = LLVMLookupIntrinsicID;
@@ -252,18 +291,60 @@ pub const Builder = opaque {
     pub const buildNot = LLVMBuildNot;
     extern fn LLVMBuildNot(*const Builder, V: *const Value, Name: [*:0]const u8) *const Value;
 
+    pub const buildFAdd = LLVMBuildFAdd;
+    extern fn LLVMBuildFAdd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildAdd = LLVMBuildAdd;
+    extern fn LLVMBuildAdd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
     pub const buildNSWAdd = LLVMBuildNSWAdd;
     extern fn LLVMBuildNSWAdd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
 
     pub const buildNUWAdd = LLVMBuildNUWAdd;
     extern fn LLVMBuildNUWAdd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
 
+    pub const buildFSub = LLVMBuildFSub;
+    extern fn LLVMBuildFSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildSub = LLVMBuildSub;
+    extern fn LLVMBuildSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
     pub const buildNSWSub = LLVMBuildNSWSub;
     extern fn LLVMBuildNSWSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
 
     pub const buildNUWSub = LLVMBuildNUWSub;
     extern fn LLVMBuildNUWSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
 
+    pub const buildFMul = LLVMBuildFMul;
+    extern fn LLVMBuildFMul(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildMul = LLVMBuildMul;
+    extern fn LLVMBuildMul(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildNSWMul = LLVMBuildNSWMul;
+    extern fn LLVMBuildNSWMul(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildNUWMul = LLVMBuildNUWMul;
+    extern fn LLVMBuildNUWMul(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildUDiv = LLVMBuildUDiv;
+    extern fn LLVMBuildUDiv(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildSDiv = LLVMBuildSDiv;
+    extern fn LLVMBuildSDiv(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildFDiv = LLVMBuildFDiv;
+    extern fn LLVMBuildFDiv(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildAnd = LLVMBuildAnd;
+    extern fn LLVMBuildAnd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildOr = LLVMBuildOr;
+    extern fn LLVMBuildOr(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const buildXor = LLVMBuildXor;
+    extern fn LLVMBuildXor(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
     pub const buildIntCast2 = LLVMBuildIntCast2;
     extern fn LLVMBuildIntCast2(*const Builder, Val: *const Value, DestTy: *const Type, IsSigned: Bool, Name: [*:0]const u8) *const Value;
 
@@ -279,6 +360,16 @@ pub const Builder = opaque {
         Name: [*:0]const u8,
     ) *const Value;
 
+    pub const buildInBoundsGEP2 = LLVMBuildInBoundsGEP2;
+    extern fn LLVMBuildInBoundsGEP2(
+        B: *const Builder,
+        Ty: *const Type,
+        Pointer: *const Value,
+        Indices: [*]const *const Value,
+        NumIndices: c_uint,
+        Name: [*:0]const u8,
+    ) *const Value;
+
     pub const buildICmp = LLVMBuildICmp;
     extern fn LLVMBuildICmp(*const Builder, Op: IntPredicate, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
 
@@ -292,10 +383,28 @@ pub const Builder = opaque {
     extern fn LLVMBuildPhi(*const Builder, Ty: *const Type, Name: [*:0]const u8) *const Value;
 
     pub const buildExtractValue = LLVMBuildExtractValue;
-    extern fn LLVMBuildExtractValue(*const Builder, AggVal: *const Value, Index: c_uint, Name: [*:0]const u8) *const Value;
+    extern fn LLVMBuildExtractValue(
+        *const Builder,
+        AggVal: *const Value,
+        Index: c_uint,
+        Name: [*:0]const u8,
+    ) *const Value;
 
     pub const buildPtrToInt = LLVMBuildPtrToInt;
-    extern fn LLVMBuildPtrToInt(*const Builder, Val: *const Value, DestTy: *const Type, Name: [*:0]const u8) *const Value;
+    extern fn LLVMBuildPtrToInt(
+        *const Builder,
+        Val: *const Value,
+        DestTy: *const Type,
+        Name: [*:0]const u8,
+    ) *const Value;
+
+    pub const buildStructGEP = LLVMBuildStructGEP;
+    extern fn LLVMBuildStructGEP(
+        B: *const Builder,
+        Pointer: *const Value,
+        Idx: c_uint,
+        Name: [*:0]const u8,
+    ) *const Value;
 };
 
 pub const IntPredicate = enum(c_int) {
@@ -715,3 +824,23 @@ extern fn ZigLLVMWriteImportLibrary(
     output_lib_path: [*c]const u8,
     kill_at: bool,
 ) bool;
+
+pub const Linkage = enum(c_uint) {
+    External,
+    AvailableExternally,
+    LinkOnceAny,
+    LinkOnceODR,
+    LinkOnceODRAutoHide,
+    WeakAny,
+    WeakODR,
+    Appending,
+    Internal,
+    Private,
+    DLLImport,
+    DLLExport,
+    ExternalWeak,
+    Ghost,
+    Common,
+    LinkerPrivate,
+    LinkerPrivateWeak,
+};
src/codegen/c.zig
@@ -935,6 +935,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM
             .wrap_optional    => try airWrapOptional(o, inst),
             .ref              => try airRef(o, inst),
             .struct_field_ptr => try airStructFieldPtr(o, inst),
+            .struct_field_val => try airStructFieldVal(o, inst),
             .varptr           => try airVarPtr(o, inst),
             .slice_ptr        => try airSliceField(o, inst, ".ptr;\n"),
             .slice_len        => try airSliceField(o, inst, ".len;\n"),
@@ -1660,8 +1661,8 @@ fn airStructFieldPtr(o: *Object, inst: Air.Inst.Index) !CValue {
     const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
     const extra = o.air.extraData(Air.StructField, ty_pl.payload).data;
     const writer = o.writer();
-    const struct_ptr = try o.resolveInst(extra.struct_ptr);
-    const struct_ptr_ty = o.air.typeOf(extra.struct_ptr);
+    const struct_ptr = try o.resolveInst(extra.struct_operand);
+    const struct_ptr_ty = o.air.typeOf(extra.struct_operand);
     const struct_obj = struct_ptr_ty.elemType().castTag(.@"struct").?.data;
     const field_name = struct_obj.fields.keys()[extra.field_index];
 
@@ -1680,6 +1681,26 @@ fn airStructFieldPtr(o: *Object, inst: Air.Inst.Index) !CValue {
     return local;
 }
 
+fn airStructFieldVal(o: *Object, inst: Air.Inst.Index) !CValue {
+    if (o.liveness.isUnused(inst))
+        return CValue.none;
+
+    const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
+    const extra = o.air.extraData(Air.StructField, ty_pl.payload).data;
+    const writer = o.writer();
+    const struct_byval = try o.resolveInst(extra.struct_operand);
+    const struct_ty = o.air.typeOf(extra.struct_operand);
+    const struct_obj = struct_ty.castTag(.@"struct").?.data;
+    const field_name = struct_obj.fields.keys()[extra.field_index];
+
+    const inst_ty = o.air.typeOfIndex(inst);
+    const local = try o.allocLocal(inst_ty, .Const);
+    try writer.writeAll(" = ");
+    try o.writeCValue(writer, struct_byval);
+    try writer.print(".{};\n", .{fmtIdent(field_name)});
+    return local;
+}
+
 // *(E!T) -> E NOT *E
 fn airUnwrapErrUnionErr(o: *Object, inst: Air.Inst.Index) !CValue {
     if (o.liveness.isUnused(inst))
src/codegen/llvm.zig
@@ -350,19 +350,21 @@ pub const Object = struct {
         air: Air,
         liveness: Liveness,
     ) !void {
+        const decl = func.owner_decl;
+
         var dg: DeclGen = .{
             .context = self.context,
             .object = self,
             .module = module,
-            .decl = func.owner_decl,
+            .decl = decl,
             .err_msg = null,
             .gpa = module.gpa,
         };
 
-        const llvm_func = try dg.resolveLLVMFunction(func.owner_decl);
+        const llvm_func = try dg.resolveLlvmFunction(decl);
 
         // This gets the LLVM values from the function and stores them in `dg.args`.
-        const fn_param_len = func.owner_decl.ty.fnParamLen();
+        const fn_param_len = decl.ty.fnParamLen();
         var args = try dg.gpa.alloc(*const llvm.Value, fn_param_len);
 
         for (args) |*arg, i| {
@@ -400,13 +402,16 @@ pub const Object = struct {
 
         fg.genBody(air.getMainBody()) catch |err| switch (err) {
             error.CodegenFail => {
-                func.owner_decl.analysis = .codegen_failure;
-                try module.failed_decls.put(module.gpa, func.owner_decl, dg.err_msg.?);
+                decl.analysis = .codegen_failure;
+                try module.failed_decls.put(module.gpa, decl, dg.err_msg.?);
                 dg.err_msg = null;
                 return;
             },
             else => |e| return e,
         };
+
+        const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
+        try self.updateDeclExports(module, decl, decl_exports);
     }
 
     pub fn updateDecl(self: *Object, module: *Module, decl: *Module.Decl) !void {
@@ -428,6 +433,38 @@ pub const Object = struct {
             else => |e| return e,
         };
     }
+
+    pub fn updateDeclExports(
+        self: *Object,
+        module: *const Module,
+        decl: *const Module.Decl,
+        exports: []const *Module.Export,
+    ) !void {
+        const llvm_fn = self.llvm_module.getNamedFunction(decl.name).?;
+        const is_extern = decl.val.tag() == .extern_fn;
+        if (is_extern or exports.len != 0) {
+            llvm_fn.setLinkage(.External);
+            llvm_fn.setUnnamedAddr(.False);
+        } else {
+            llvm_fn.setLinkage(.Internal);
+            llvm_fn.setUnnamedAddr(.True);
+        }
+        // TODO LLVM C API does not support deleting aliases. We need to
+        // patch it to support this or figure out how to wrap the C++ API ourselves.
+        // Until then we iterate over existing aliases and make them point
+        // to the correct decl, or otherwise add a new alias. Old aliases are leaked.
+        for (exports) |exp| {
+            if (self.llvm_module.getNamedGlobalAlias(exp.options.name.ptr, exp.options.name.len)) |alias| {
+                alias.setAliasee(llvm_fn);
+            } else {
+                const exp_name_z = try module.gpa.dupeZ(u8, exp.options.name);
+                defer module.gpa.free(exp_name_z);
+
+                const alias = self.llvm_module.addAlias(llvm_fn.typeOf(), llvm_fn, exp_name_z);
+                _ = alias;
+            }
+        }
+    }
 };
 
 pub const DeclGen = struct {
@@ -461,21 +498,19 @@ pub const DeclGen = struct {
             _ = func_payload;
             @panic("TODO llvm backend genDecl function pointer");
         } else if (decl.val.castTag(.extern_fn)) |extern_fn| {
-            _ = try self.resolveLLVMFunction(extern_fn.data);
+            _ = try self.resolveLlvmFunction(extern_fn.data);
         } else {
             _ = try self.resolveGlobalDecl(decl);
         }
     }
 
     /// If the llvm function does not exist, create it
-    fn resolveLLVMFunction(self: *DeclGen, func: *Module.Decl) !*const llvm.Value {
-        // TODO: do we want to store this in our own datastructure?
-        if (self.llvmModule().getNamedFunction(func.name)) |llvm_fn| return llvm_fn;
+    fn resolveLlvmFunction(self: *DeclGen, decl: *Module.Decl) !*const llvm.Value {
+        if (self.llvmModule().getNamedFunction(decl.name)) |llvm_fn| return llvm_fn;
 
-        assert(func.has_tv);
-        const zig_fn_type = func.ty;
+        assert(decl.has_tv);
+        const zig_fn_type = decl.ty;
         const return_type = zig_fn_type.fnReturnType();
-
         const fn_param_len = zig_fn_type.fnParamLen();
 
         const fn_param_types = try self.gpa.alloc(Type, fn_param_len);
@@ -495,9 +530,17 @@ pub const DeclGen = struct {
             @intCast(c_uint, fn_param_len),
             .False,
         );
-        const llvm_fn = self.llvmModule().addFunction(func.name, fn_type);
+        const llvm_fn = self.llvmModule().addFunction(decl.name, fn_type);
+
+        const is_extern = decl.val.tag() == .extern_fn;
+        if (!is_extern) {
+            llvm_fn.setLinkage(.Internal);
+            llvm_fn.setUnnamedAddr(.True);
+        }
+
+        // TODO: calling convention, linkage, tsan, etc. see codegen.cpp `make_fn_llvm_value`.
 
-        if (return_type.tag() == .noreturn) {
+        if (return_type.isNoReturn()) {
             self.addFnAttr(llvm_fn, "noreturn");
         }
 
@@ -505,7 +548,6 @@ pub const DeclGen = struct {
     }
 
     fn resolveGlobalDecl(self: *DeclGen, decl: *Module.Decl) error{ OutOfMemory, CodegenFail }!*const llvm.Value {
-        // TODO: do we want to store this in our own datastructure?
         if (self.llvmModule().getNamedGlobal(decl.name)) |val| return val;
 
         assert(decl.has_tv);
@@ -515,9 +557,11 @@ pub const DeclGen = struct {
         const global = self.llvmModule().addGlobal(llvm_type, decl.name);
         const init_val = if (decl.val.castTag(.variable)) |payload| init_val: {
             const variable = payload.data;
-            global.setGlobalConstant(.False);
             break :init_val variable.init;
-        } else decl.val;
+        } else init_val: {
+            global.setGlobalConstant(.True);
+            break :init_val decl.val;
+        };
 
         const llvm_init = try self.genTypedValue(.{ .ty = decl.ty, .val = init_val }, null);
         llvm.setInitializer(global, llvm_init);
@@ -602,12 +646,13 @@ pub const DeclGen = struct {
                     llvm_param.* = try self.llvmType(t.fnParamType(i));
                 }
                 const is_var_args = t.fnIsVarArgs();
-                return llvm.functionType(
+                const llvm_fn_ty = llvm.functionType(
                     ret_ty,
                     llvm_params.ptr,
                     @intCast(c_uint, llvm_params.len),
                     llvm.Bool.fromBool(is_var_args),
                 );
+                return llvm_fn_ty.pointerType(0);
             },
             .ComptimeInt => unreachable,
             .ComptimeFloat => unreachable,
@@ -717,6 +762,42 @@ pub const DeclGen = struct {
                     return self.todo("implement const of optional pointer", .{});
                 }
             },
+            .Fn => {
+                const fn_decl = if (tv.val.castTag(.extern_fn)) |extern_fn|
+                    extern_fn.data
+                else if (tv.val.castTag(.function)) |func_payload|
+                    func_payload.data.owner_decl
+                else
+                    unreachable;
+
+                return self.resolveLlvmFunction(fn_decl);
+            },
+            .ErrorSet => {
+                const llvm_ty = try self.llvmType(tv.ty);
+                switch (tv.val.tag()) {
+                    .@"error" => {
+                        const err_name = tv.val.castTag(.@"error").?.data.name;
+                        const kv = try self.module.getErrorValue(err_name);
+                        return llvm_ty.constInt(kv.value, .False);
+                    },
+                    else => {
+                        // In this case we are rendering an error union which has a 0 bits payload.
+                        return llvm_ty.constNull();
+                    },
+                }
+            },
+            .ErrorUnion => {
+                const error_type = tv.ty.errorUnionSet();
+                const payload_type = tv.ty.errorUnionPayload();
+                const sub_val = tv.val.castTag(.error_union).?.data;
+
+                if (!payload_type.hasCodeGenBits()) {
+                    // We use the error type directly as the type.
+                    return self.genTypedValue(.{ .ty = error_type, .val = sub_val }, fg);
+                }
+
+                return self.todo("implement error union const of type '{}'", .{tv.ty});
+            },
             else => return self.todo("implement const of type '{}'", .{tv.ty}),
         }
     }
@@ -801,8 +882,17 @@ pub const FuncGen = struct {
         for (body) |inst| {
             const opt_value: ?*const llvm.Value = switch (air_tags[inst]) {
                 // zig fmt: off
-                .add => try self.airAdd(inst),
-                .sub => try self.airSub(inst),
+                .add     => try self.airAdd(inst, false),
+                .addwrap => try self.airAdd(inst, true),
+                .sub     => try self.airSub(inst, false),
+                .subwrap => try self.airSub(inst, true),
+                .mul     => try self.airMul(inst, false),
+                .mulwrap => try self.airMul(inst, true),
+                .div     => try self.airDiv(inst),
+
+                .bit_and, .bool_and => try self.airAnd(inst),
+                .bit_or, .bool_or   => try self.airOr(inst),
+                .xor                => try self.airXor(inst),
 
                 .cmp_eq  => try self.airCmp(inst, .eq),
                 .cmp_gt  => try self.airCmp(inst, .gt),
@@ -825,10 +915,12 @@ pub const FuncGen = struct {
                 .bitcast    => try self.airBitCast(inst),
                 .block      => try self.airBlock(inst),
                 .br         => try self.airBr(inst),
+                .switch_br  => try self.airSwitchBr(inst),
                 .breakpoint => try self.airBreakpoint(inst),
                 .call       => try self.airCall(inst),
                 .cond_br    => try self.airCondBr(inst),
                 .intcast    => try self.airIntCast(inst),
+                .floatcast  => try self.airFloatCast(inst),
                 .ptrtoint   => try self.airPtrToInt(inst),
                 .load       => try self.airLoad(inst),
                 .loop       => try self.airLoop(inst),
@@ -840,6 +932,9 @@ pub const FuncGen = struct {
                 .slice_ptr  => try self.airSliceField(inst, 0),
                 .slice_len  => try self.airSliceField(inst, 1),
 
+                .struct_field_ptr => try self.airStructFieldPtr(inst),
+                .struct_field_val => try self.airStructFieldVal(inst),
+
                 .slice_elem_val     => try self.airSliceElemVal(inst, false),
                 .ptr_slice_elem_val => try self.airSliceElemVal(inst, true),
 
@@ -851,12 +946,18 @@ pub const FuncGen = struct {
                 .unwrap_errunion_err         => try self.airErrUnionErr(inst, false),
                 .unwrap_errunion_err_ptr     => try self.airErrUnionErr(inst, true),
 
+                .wrap_optional         => try self.airWrapOptional(inst),
+                .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
+                .wrap_errunion_err     => try self.airWrapErrUnionErr(inst),
+
+                .constant => unreachable,
+                .const_ty => unreachable,
+                .ref => unreachable, // TODO eradicate this instruction
                 .unreach  => self.airUnreach(inst),
                 .dbg_stmt => blk: {
                     // TODO: implement debug info
                     break :blk null;
                 },
-                else => |tag| return self.todo("implement AIR instruction: {}", .{tag}),
                 // zig fmt: on
             };
             if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa, inst, val);
@@ -867,47 +968,32 @@ pub const FuncGen = struct {
         const pl_op = self.air.instructions.items(.data)[inst].pl_op;
         const extra = self.air.extraData(Air.Call, pl_op.payload);
         const args = @bitCast([]const Air.Inst.Ref, self.air.extra[extra.end..][0..extra.data.args_len]);
+        const zig_fn_type = self.air.typeOf(pl_op.operand);
+        const return_type = zig_fn_type.fnReturnType();
+        const llvm_fn = try self.resolveInst(pl_op.operand);
 
-        if (self.air.value(pl_op.operand)) |func_value| {
-            const fn_decl = if (func_value.castTag(.extern_fn)) |extern_fn|
-                extern_fn.data
-            else if (func_value.castTag(.function)) |func_payload|
-                func_payload.data.owner_decl
-            else
-                unreachable;
-
-            assert(fn_decl.has_tv);
-            const zig_fn_type = fn_decl.ty;
-            const llvm_fn = try self.dg.resolveLLVMFunction(fn_decl);
+        const llvm_param_vals = try self.gpa.alloc(*const llvm.Value, args.len);
+        defer self.gpa.free(llvm_param_vals);
 
-            const llvm_param_vals = try self.gpa.alloc(*const llvm.Value, args.len);
-            defer self.gpa.free(llvm_param_vals);
+        for (args) |arg, i| {
+            llvm_param_vals[i] = try self.resolveInst(arg);
+        }
 
-            for (args) |arg, i| {
-                llvm_param_vals[i] = try self.resolveInst(arg);
-            }
+        const call = self.builder.buildCall(
+            llvm_fn,
+            llvm_param_vals.ptr,
+            @intCast(c_uint, args.len),
+            "",
+        );
 
-            // TODO: LLVMBuildCall2 handles opaque function pointers, according to llvm docs
-            //       Do we need that?
-            const call = self.builder.buildCall(
-                llvm_fn,
-                llvm_param_vals.ptr,
-                @intCast(c_uint, args.len),
-                "",
-            );
-
-            const return_type = zig_fn_type.fnReturnType();
-            if (return_type.tag() == .noreturn) {
-                _ = self.builder.buildUnreachable();
-            }
+        if (return_type.isNoReturn()) {
+            _ = self.builder.buildUnreachable();
+        }
 
-            // No need to store the LLVM value if the return type is void or noreturn
-            if (!return_type.hasCodeGenBits()) return null;
+        // No need to store the LLVM value if the return type is void or noreturn
+        if (!return_type.hasCodeGenBits()) return null;
 
-            return call;
-        } else {
-            return self.todo("implement calling runtime known function pointer", .{});
-        }
+        return call;
     }
 
     fn airRet(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
@@ -1026,6 +1112,11 @@ pub const FuncGen = struct {
         return null;
     }
 
+    fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        _ = inst;
+        return self.todo("implement llvm codegen for switch_br", .{});
+    }
+
     fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
         const loop = self.air.extraData(Air.Block, ty_pl.payload);
@@ -1082,10 +1173,32 @@ pub const FuncGen = struct {
         };
 
         const indices: [1]*const llvm.Value = .{rhs};
-        const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, 1, "");
+        const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
         return self.builder.buildLoad(ptr, "");
     }
 
+    fn airStructFieldPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+        const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
+        const struct_ptr = try self.resolveInst(struct_field.struct_operand);
+        const field_index = @intCast(c_uint, struct_field.field_index);
+        return self.builder.buildStructGEP(struct_ptr, field_index, "");
+    }
+
+    fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+        const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
+        const struct_byval = try self.resolveInst(struct_field.struct_operand);
+        const field_index = @intCast(c_uint, struct_field.field_index);
+        return self.builder.buildExtractValue(struct_byval, field_index, "");
+    }
+
     fn airNot(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         if (self.liveness.isUnused(inst))
             return null;
@@ -1321,7 +1434,7 @@ pub const FuncGen = struct {
 
         _ = operand;
         _ = operand_is_ptr;
-        return self.todo("implement 'airErrUnionPayload' for type {}", .{self.air.typeOf(ty_op.operand)});
+        return self.todo("implement llvm codegen for 'airErrUnionPayload' for type {}", .{self.air.typeOf(ty_op.operand)});
     }
 
     fn airErrUnionErr(
@@ -1332,42 +1445,123 @@ pub const FuncGen = struct {
         if (self.liveness.isUnused(inst))
             return null;
 
-        _ = operand_is_ptr;
-        return self.todo("implement 'airErrUnionErr'", .{});
+        const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+        const operand = try self.resolveInst(ty_op.operand);
+        const operand_ty = self.air.typeOf(ty_op.operand);
+
+        const payload_ty = operand_ty.errorUnionPayload();
+        if (!payload_ty.hasCodeGenBits()) {
+            if (!operand_is_ptr) return operand;
+            return self.builder.buildLoad(operand, "");
+        }
+        return self.todo("implement llvm codegen for 'airErrUnionErr'", .{});
+    }
+
+    fn airWrapOptional(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        return self.todo("implement llvm codegen for 'airWrapOptional'", .{});
+    }
+
+    fn airWrapErrUnionPayload(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        return self.todo("implement llvm codegen for 'airWrapErrUnionPayload'", .{});
+    }
+
+    fn airWrapErrUnionErr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{});
     }
 
-    fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+    fn airAdd(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value {
         if (self.liveness.isUnused(inst))
             return null;
+
         const bin_op = self.air.instructions.items(.data)[inst].bin_op;
         const lhs = try self.resolveInst(bin_op.lhs);
         const rhs = try self.resolveInst(bin_op.rhs);
         const inst_ty = self.air.typeOfIndex(inst);
 
-        if (!inst_ty.isInt())
-            return self.todo("implement 'airAdd' for type {}", .{inst_ty});
+        if (inst_ty.isFloat()) return self.builder.buildFAdd(lhs, rhs, "");
+        if (wrap) return self.builder.buildAdd(lhs, rhs, "");
+        if (inst_ty.isSignedInt()) return self.builder.buildNSWAdd(lhs, rhs, "");
+        return self.builder.buildNUWAdd(lhs, rhs, "");
+    }
 
-        return if (inst_ty.isSignedInt())
-            self.builder.buildNSWAdd(lhs, rhs, "")
-        else
-            self.builder.buildNUWAdd(lhs, rhs, "");
+    fn airSub(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const lhs = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        const inst_ty = self.air.typeOfIndex(inst);
+
+        if (inst_ty.isFloat()) return self.builder.buildFSub(lhs, rhs, "");
+        if (wrap) return self.builder.buildSub(lhs, rhs, "");
+        if (inst_ty.isSignedInt()) return self.builder.buildNSWSub(lhs, rhs, "");
+        return self.builder.buildNUWSub(lhs, rhs, "");
     }
 
-    fn airSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+    fn airMul(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value {
         if (self.liveness.isUnused(inst))
             return null;
+
         const bin_op = self.air.instructions.items(.data)[inst].bin_op;
         const lhs = try self.resolveInst(bin_op.lhs);
         const rhs = try self.resolveInst(bin_op.rhs);
         const inst_ty = self.air.typeOfIndex(inst);
 
-        if (!inst_ty.isInt())
-            return self.todo("implement 'airSub' for type {}", .{inst_ty});
+        if (inst_ty.isFloat()) return self.builder.buildFMul(lhs, rhs, "");
+        if (wrap) return self.builder.buildMul(lhs, rhs, "");
+        if (inst_ty.isSignedInt()) return self.builder.buildNSWMul(lhs, rhs, "");
+        return self.builder.buildNUWMul(lhs, rhs, "");
+    }
 
-        return if (inst_ty.isSignedInt())
-            self.builder.buildNSWSub(lhs, rhs, "")
-        else
-            self.builder.buildNUWSub(lhs, rhs, "");
+    fn airDiv(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const lhs = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        const inst_ty = self.air.typeOfIndex(inst);
+
+        if (inst_ty.isFloat()) return self.builder.buildFDiv(lhs, rhs, "");
+        if (inst_ty.isSignedInt()) return self.builder.buildSDiv(lhs, rhs, "");
+        return self.builder.buildUDiv(lhs, rhs, "");
+    }
+
+    fn airAnd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const lhs = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        return self.builder.buildAnd(lhs, rhs, "");
+    }
+
+    fn airOr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const lhs = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        return self.builder.buildOr(lhs, rhs, "");
+    }
+
+    fn airXor(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const lhs = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        return self.builder.buildXor(lhs, rhs, "");
     }
 
     fn airIntCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
@@ -1384,6 +1578,14 @@ pub const FuncGen = struct {
         return self.builder.buildIntCast2(operand, try self.dg.llvmType(inst_ty), llvm.Bool.fromBool(signed), "");
     }
 
+    fn airFloatCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        // TODO split floatcast AIR into float_widen and float_shorten
+        return self.todo("implement 'airFloatCast'", .{});
+    }
+
     fn airPtrToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         if (self.liveness.isUnused(inst))
             return null;
@@ -1474,8 +1676,8 @@ pub const FuncGen = struct {
 
     fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         _ = inst;
-        const llvn_fn = self.getIntrinsic("llvm.debugtrap");
-        _ = self.builder.buildCall(llvn_fn, undefined, 0, "");
+        const llvm_fn = self.getIntrinsic("llvm.debugtrap");
+        _ = self.builder.buildCall(llvm_fn, undefined, 0, "");
         return null;
     }
 
src/codegen/wasm.zig
@@ -1306,7 +1306,7 @@ pub const Context = struct {
     fn airStructFieldPtr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
         const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
         const extra = self.air.extraData(Air.StructField, ty_pl.payload);
-        const struct_ptr = self.resolveInst(extra.data.struct_ptr);
+        const struct_ptr = self.resolveInst(extra.data.struct_operand);
 
         return WValue{ .local = struct_ptr.multi_value.index + @intCast(u32, extra.data.field_index) };
     }
src/link/Coff.zig
@@ -777,8 +777,18 @@ pub fn freeDecl(self: *Coff, decl: *Module.Decl) void {
     self.offset_table_free_list.append(self.base.allocator, decl.link.coff.offset_table_index) catch {};
 }
 
-pub fn updateDeclExports(self: *Coff, module: *Module, decl: *Module.Decl, exports: []const *Module.Export) !void {
-    if (self.llvm_object) |_| return;
+pub fn updateDeclExports(
+    self: *Coff,
+    module: *Module,
+    decl: *Module.Decl,
+    exports: []const *Module.Export,
+) !void {
+    if (build_options.skip_non_native and builtin.object_format != .coff) {
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+    }
+    if (build_options.have_llvm) {
+        if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl, exports);
+    }
 
     for (exports) |exp| {
         if (exp.options.section) |section_name| {
src/link/Elf.zig
@@ -2716,7 +2716,12 @@ pub fn updateDeclExports(
     decl: *Module.Decl,
     exports: []const *Module.Export,
 ) !void {
-    if (self.llvm_object) |_| return;
+    if (build_options.skip_non_native and builtin.object_format != .elf) {
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+    }
+    if (build_options.have_llvm) {
+        if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl, exports);
+    }
 
     const tracy = trace(@src());
     defer tracy.end();
src/link/MachO.zig
@@ -3785,6 +3785,12 @@ pub fn updateDeclExports(
     decl: *Module.Decl,
     exports: []const *Module.Export,
 ) !void {
+    if (build_options.skip_non_native and builtin.object_format != .macho) {
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+    }
+    if (build_options.have_llvm) {
+        if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl, exports);
+    }
     const tracy = trace(@src());
     defer tracy.end();
 
src/link/Wasm.zig
@@ -330,10 +330,12 @@ pub fn updateDeclExports(
     decl: *const Module.Decl,
     exports: []const *Module.Export,
 ) !void {
-    _ = self;
-    _ = module;
-    _ = decl;
-    _ = exports;
+    if (build_options.skip_non_native and builtin.object_format != .wasm) {
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+    }
+    if (build_options.have_llvm) {
+        if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl, exports);
+    }
 }
 
 pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
src/Air.zig
@@ -247,6 +247,9 @@ pub const Inst = struct {
         /// Given a pointer to a struct and a field index, returns a pointer to the field.
         /// Uses the `ty_pl` field, payload is `StructField`.
         struct_field_ptr,
+        /// Given a byval struct and a field index, returns the field byval.
+        /// Uses the `ty_pl` field, payload is `StructField`.
+        struct_field_val,
         /// Given a slice value, return the length.
         /// Result type is always usize.
         /// Uses the `ty_op` field.
@@ -376,7 +379,8 @@ pub const SwitchBr = struct {
 };
 
 pub const StructField = struct {
-    struct_ptr: Inst.Ref,
+    /// Whether this is a pointer or byval is determined by the AIR tag.
+    struct_operand: Inst.Ref,
     field_index: u32,
 };
 
@@ -448,6 +452,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .constant,
         .varptr,
         .struct_field_ptr,
+        .struct_field_val,
         => return air.getRefType(datas[inst].ty_pl.ty),
 
         .not,
src/codegen.zig
@@ -851,6 +851,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .ret             => try self.airRet(inst),
                     .store           => try self.airStore(inst),
                     .struct_field_ptr=> try self.airStructFieldPtr(inst),
+                    .struct_field_val=> try self.airStructFieldVal(inst),
                     .switch_br       => try self.airSwitch(inst),
                     .varptr          => try self.airVarPtr(inst),
                     .slice_ptr       => try self.airSlicePtr(inst),
@@ -1501,6 +1502,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             //return self.finishAir(inst, result, .{ extra.struct_ptr, .none, .none });
         }
 
+        fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void {
+            const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+            const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
+            _ = extra;
+            return self.fail("TODO implement codegen struct_field_val", .{});
+            //return self.finishAir(inst, result, .{ extra.struct_ptr, .none, .none });
+        }
+
         fn armOperandShouldBeRegister(self: *Self, mcv: MCValue) !bool {
             return switch (mcv) {
                 .none => unreachable,
src/Liveness.zig
@@ -320,9 +320,9 @@ fn analyzeInst(
             }
             return extra_tombs.finish();
         },
-        .struct_field_ptr => {
+        .struct_field_ptr, .struct_field_val => {
             const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data;
-            return trackOperands(a, new_set, inst, main_tomb, .{ extra.struct_ptr, .none, .none });
+            return trackOperands(a, new_set, inst, main_tomb, .{ extra.struct_operand, .none, .none });
         },
         .br => {
             const br = inst_datas[inst].br;
src/Module.zig
@@ -3702,8 +3702,8 @@ pub fn analyzeExport(
         else => return mod.fail(scope, src, "unable to export type '{}'", .{exported_decl.ty}),
     }
 
-    try mod.decl_exports.ensureCapacity(mod.gpa, mod.decl_exports.count() + 1);
-    try mod.export_owners.ensureCapacity(mod.gpa, mod.export_owners.count() + 1);
+    try mod.decl_exports.ensureUnusedCapacity(mod.gpa, 1);
+    try mod.export_owners.ensureUnusedCapacity(mod.gpa, 1);
 
     const new_export = try mod.gpa.create(Export);
     errdefer mod.gpa.destroy(new_export);
src/print_air.zig
@@ -171,7 +171,8 @@ const Writer = struct {
             .loop,
             => try w.writeBlock(s, inst),
 
-            .struct_field_ptr => try w.writeStructFieldPtr(s, inst),
+            .struct_field_ptr => try w.writeStructField(s, inst),
+            .struct_field_val => try w.writeStructField(s, inst),
             .varptr => try w.writeVarPtr(s, inst),
             .constant => try w.writeConstant(s, inst),
             .assembly => try w.writeAssembly(s, inst),
@@ -233,11 +234,11 @@ const Writer = struct {
         try s.writeAll("}");
     }
 
-    fn writeStructFieldPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
+    fn writeStructField(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
         const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
         const extra = w.air.extraData(Air.StructField, ty_pl.payload);
 
-        try w.writeOperand(s, inst, 0, extra.data.struct_ptr);
+        try w.writeOperand(s, inst, 0, extra.data.struct_operand);
         try s.print(", {d}", .{extra.data.field_index});
     }
 
src/Sema.zig
@@ -655,7 +655,7 @@ fn resolveDefinedValue(
     src: LazySrcLoc,
     air_ref: Air.Inst.Ref,
 ) CompileError!?Value {
-    if (try sema.resolvePossiblyUndefinedValue(block, src, air_ref)) |val| {
+    if (try sema.resolveMaybeUndefVal(block, src, air_ref)) |val| {
         if (val.isUndef()) {
             return sema.failWithUseOfUndef(block, src);
         }
@@ -664,7 +664,7 @@ fn resolveDefinedValue(
     return null;
 }
 
-fn resolvePossiblyUndefinedValue(
+fn resolveMaybeUndefVal(
     sema: *Sema,
     block: *Scope.Block,
     src: LazySrcLoc,
@@ -1293,7 +1293,7 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co
         };
         return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
     }
-    const result_ptr = try sema.namedFieldPtr(block, src, array_ptr, "len", src);
+    const result_ptr = try sema.fieldPtr(block, src, array_ptr, "len", src);
     const result_ptr_src = array_ptr_src;
     return sema.analyzeLoad(block, src, result_ptr, result_ptr_src);
 }
@@ -1789,7 +1789,7 @@ fn zirCompileLog(
 
         const arg = sema.resolveInst(arg_ref);
         const arg_ty = sema.typeOf(arg);
-        if (try sema.resolvePossiblyUndefinedValue(block, src, arg)) |val| {
+        if (try sema.resolveMaybeUndefVal(block, src, arg)) |val| {
             try writer.print("@as({}, {})", .{ arg_ty, val });
         } else {
             try writer.print("@as({}, [runtime value])", .{arg_ty});
@@ -2579,7 +2579,7 @@ fn zirErrorToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compile
     const op_coerced = try sema.coerce(block, Type.initTag(.anyerror), op, operand_src);
     const result_ty = Type.initTag(.u16);
 
-    if (try sema.resolvePossiblyUndefinedValue(block, src, op_coerced)) |val| {
+    if (try sema.resolveMaybeUndefVal(block, src, op_coerced)) |val| {
         if (val.isUndef()) {
             return sema.addConstUndef(result_ty);
         }
@@ -2759,7 +2759,7 @@ fn zirEnumToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE
         return sema.addConstant(int_tag_ty, opv);
     }
 
-    if (try sema.resolvePossiblyUndefinedValue(block, operand_src, enum_tag)) |enum_tag_val| {
+    if (try sema.resolveMaybeUndefVal(block, operand_src, enum_tag)) |enum_tag_val| {
         if (enum_tag_val.castTag(.enum_field_index)) |enum_field_payload| {
             const field_index = enum_field_payload.data;
             switch (enum_tag_ty.tag()) {
@@ -2806,7 +2806,7 @@ fn zirIntToEnum(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE
         return mod.fail(&block.base, dest_ty_src, "expected enum, found {}", .{dest_ty});
     }
 
-    if (try sema.resolvePossiblyUndefinedValue(block, operand_src, operand)) |int_val| {
+    if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |int_val| {
         if (dest_ty.isNonexhaustiveEnum()) {
             return sema.addConstant(dest_ty, int_val);
         }
@@ -3309,16 +3309,16 @@ fn zirFieldVal(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
     const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
+    const lhs_src: LazySrcLoc = src; // TODO
     const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
     const field_name = sema.code.nullTerminatedString(extra.field_name_start);
     const object = sema.resolveInst(extra.lhs);
-    const object_ptr = if (sema.typeOf(object).zigTypeTag() == .Pointer)
-        object
-    else
-        try sema.analyzeRef(block, src, object);
-    const result_ptr = try sema.namedFieldPtr(block, src, object_ptr, field_name, field_name_src);
-    const result_ptr_src = src;
-    return sema.analyzeLoad(block, src, result_ptr, result_ptr_src);
+    if (sema.typeOf(object).isSinglePointer()) {
+        const result_ptr = try sema.fieldPtr(block, src, object, field_name, field_name_src);
+        return sema.analyzeLoad(block, src, result_ptr, lhs_src);
+    } else {
+        return sema.fieldVal(block, src, object, field_name, field_name_src);
+    }
 }
 
 fn zirFieldPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -3331,7 +3331,7 @@ fn zirFieldPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
     const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
     const field_name = sema.code.nullTerminatedString(extra.field_name_start);
     const object_ptr = sema.resolveInst(extra.lhs);
-    return sema.namedFieldPtr(block, src, object_ptr, field_name, field_name_src);
+    return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src);
 }
 
 fn zirFieldValNamed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -3344,9 +3344,7 @@ fn zirFieldValNamed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp
     const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data;
     const object = sema.resolveInst(extra.lhs);
     const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name);
-    const object_ptr = try sema.analyzeRef(block, src, object);
-    const result_ptr = try sema.namedFieldPtr(block, src, object_ptr, field_name, field_name_src);
-    return sema.analyzeLoad(block, src, result_ptr, src);
+    return sema.fieldVal(block, src, object, field_name, field_name_src);
 }
 
 fn zirFieldPtrNamed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -3359,7 +3357,7 @@ fn zirFieldPtrNamed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp
     const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data;
     const object_ptr = sema.resolveInst(extra.lhs);
     const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name);
-    return sema.namedFieldPtr(block, src, object_ptr, field_name, field_name_src);
+    return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src);
 }
 
 fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -4691,8 +4689,8 @@ fn zirBitwise(
         return sema.mod.fail(&block.base, src, "invalid operands to binary bitwise expression: '{s}' and '{s}'", .{ @tagName(lhs_ty.zigTypeTag()), @tagName(rhs_ty.zigTypeTag()) });
     }
 
-    if (try sema.resolvePossiblyUndefinedValue(block, lhs_src, casted_lhs)) |lhs_val| {
-        if (try sema.resolvePossiblyUndefinedValue(block, rhs_src, casted_rhs)) |rhs_val| {
+    if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
+        if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {
             if (lhs_val.isUndef() or rhs_val.isUndef()) {
                 return sema.addConstUndef(resolved_type);
             }
@@ -4823,8 +4821,8 @@ fn analyzeArithmetic(
         return sema.mod.fail(&block.base, src, "invalid operands to binary expression: '{s}' and '{s}'", .{ @tagName(lhs_ty.zigTypeTag()), @tagName(rhs_ty.zigTypeTag()) });
     }
 
-    if (try sema.resolvePossiblyUndefinedValue(block, lhs_src, casted_lhs)) |lhs_val| {
-        if (try sema.resolvePossiblyUndefinedValue(block, rhs_src, casted_rhs)) |rhs_val| {
+    if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
+        if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {
             if (lhs_val.isUndef() or rhs_val.isUndef()) {
                 return sema.addConstUndef(resolved_type);
             }
@@ -5038,8 +5036,8 @@ fn zirCmp(
         if (!is_equality_cmp) {
             return mod.fail(&block.base, src, "{s} operator not allowed for errors", .{@tagName(op)});
         }
-        if (try sema.resolvePossiblyUndefinedValue(block, lhs_src, lhs)) |lval| {
-            if (try sema.resolvePossiblyUndefinedValue(block, rhs_src, rhs)) |rval| {
+        if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lval| {
+            if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rval| {
                 if (lval.isUndef() or rval.isUndef()) {
                     return sema.addConstUndef(Type.initTag(.bool));
                 }
@@ -5085,8 +5083,8 @@ fn zirCmp(
     const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
     const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
 
-    if (try sema.resolvePossiblyUndefinedValue(block, lhs_src, casted_lhs)) |lhs_val| {
-        if (try sema.resolvePossiblyUndefinedValue(block, rhs_src, casted_rhs)) |rhs_val| {
+    if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
+        if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {
             if (lhs_val.isUndef() or rhs_val.isUndef()) {
                 return sema.addConstUndef(resolved_type);
             }
@@ -5759,7 +5757,7 @@ fn zirStructInit(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref:
     if (is_comptime) {
         const values = try sema.arena.alloc(Value, field_inits.len);
         for (field_inits) |field_init, i| {
-            values[i] = (sema.resolvePossiblyUndefinedValue(block, src, field_init) catch unreachable).?;
+            values[i] = (sema.resolveMaybeUndefVal(block, src, field_init) catch unreachable).?;
         }
         return sema.addConstant(struct_ty, try Value.Tag.@"struct".create(sema.arena, values.ptr));
     }
@@ -6234,7 +6232,7 @@ fn zirVarExtended(
         const init_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
         extra_index += 1;
         const init_air_inst = sema.resolveInst(init_ref);
-        break :blk (try sema.resolvePossiblyUndefinedValue(block, init_src, init_air_inst)) orelse
+        break :blk (try sema.resolveMaybeUndefVal(block, init_src, init_air_inst)) orelse
             return sema.failWithNeededComptime(block, init_src);
     } else Value.initTag(.unreachable_value);
 
@@ -6565,31 +6563,203 @@ fn emitBackwardBranch(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void {
     }
 }
 
-fn namedFieldPtr(
+fn fieldVal(
     sema: *Sema,
     block: *Scope.Block,
     src: LazySrcLoc,
-    object_ptr: Air.Inst.Ref,
+    object: Air.Inst.Ref,
     field_name: []const u8,
     field_name_src: LazySrcLoc,
 ) CompileError!Air.Inst.Ref {
+    // When editing this function, note that there is corresponding logic to be edited
+    // in `fieldPtr`. This function takes a value and returns a value.
+
     const mod = sema.mod;
     const arena = sema.arena;
+    const object_src = src; // TODO better source location
+    const object_ty = sema.typeOf(object);
+
+    switch (object_ty.zigTypeTag()) {
+        .Array => {
+            if (mem.eql(u8, field_name, "len")) {
+                return sema.addConstant(
+                    Type.initTag(.comptime_int),
+                    try Value.Tag.int_u64.create(arena, object_ty.arrayLen()),
+                );
+            } else {
+                return mod.fail(
+                    &block.base,
+                    field_name_src,
+                    "no member named '{s}' in '{}'",
+                    .{ field_name, object_ty },
+                );
+            }
+        },
+        .Pointer => switch (object_ty.ptrSize()) {
+            .Slice => {
+                if (mem.eql(u8, field_name, "ptr")) {
+                    const buf = try arena.create(Type.Payload.ElemType);
+                    const result_ty = object_ty.slicePtrFieldType(buf);
+                    if (try sema.resolveMaybeUndefVal(block, object_src, object)) |val| {
+                        if (val.isUndef()) return sema.addConstUndef(result_ty);
+                        return mod.fail(
+                            &block.base,
+                            field_name_src,
+                            "TODO implement comptime slice ptr",
+                            .{},
+                        );
+                    }
+                    try sema.requireRuntimeBlock(block, src);
+                    return block.addTyOp(.slice_ptr, result_ty, object);
+                } else if (mem.eql(u8, field_name, "len")) {
+                    const result_ty = Type.initTag(.usize);
+                    if (try sema.resolveMaybeUndefVal(block, object_src, object)) |val| {
+                        if (val.isUndef()) return sema.addConstUndef(result_ty);
+                        return sema.addConstant(
+                            result_ty,
+                            try Value.Tag.int_u64.create(arena, val.sliceLen()),
+                        );
+                    }
+                    try sema.requireRuntimeBlock(block, src);
+                    return block.addTyOp(.slice_len, result_ty, object);
+                } else {
+                    return mod.fail(
+                        &block.base,
+                        field_name_src,
+                        "no member named '{s}' in '{}'",
+                        .{ field_name, object_ty },
+                    );
+                }
+            },
+            .One => {
+                const elem_ty = object_ty.elemType();
+                if (elem_ty.zigTypeTag() == .Array) {
+                    if (mem.eql(u8, field_name, "len")) {
+                        return sema.addConstant(
+                            Type.initTag(.comptime_int),
+                            try Value.Tag.int_u64.create(arena, elem_ty.arrayLen()),
+                        );
+                    } else {
+                        return mod.fail(
+                            &block.base,
+                            field_name_src,
+                            "no member named '{s}' in '{}'",
+                            .{ field_name, object_ty },
+                        );
+                    }
+                }
+            },
+            .Many, .C => {},
+        },
+        .Type => {
+            const val = (try sema.resolveDefinedValue(block, object_src, object)).?;
+            const child_type = try val.toType(arena);
+            switch (child_type.zigTypeTag()) {
+                .ErrorSet => {
+                    // TODO resolve inferred error sets
+                    const name: []const u8 = if (child_type.castTag(.error_set)) |payload| blk: {
+                        const error_set = payload.data;
+                        // TODO this is O(N). I'm putting off solving this until we solve inferred
+                        // error sets at the same time.
+                        const names = error_set.names_ptr[0..error_set.names_len];
+                        for (names) |name| {
+                            if (mem.eql(u8, field_name, name)) {
+                                break :blk name;
+                            }
+                        }
+                        return mod.fail(&block.base, src, "no error named '{s}' in '{}'", .{
+                            field_name, child_type,
+                        });
+                    } else (try mod.getErrorValue(field_name)).key;
+
+                    return sema.addConstant(
+                        child_type,
+                        try Value.Tag.@"error".create(arena, .{ .name = name }),
+                    );
+                },
+                .Struct, .Opaque, .Union => {
+                    if (child_type.getNamespace()) |namespace| {
+                        if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| {
+                            return sema.analyzeLoad(block, src, inst, src);
+                        }
+                    }
+                    // TODO add note: declared here
+                    const kw_name = switch (child_type.zigTypeTag()) {
+                        .Struct => "struct",
+                        .Opaque => "opaque",
+                        .Union => "union",
+                        else => unreachable,
+                    };
+                    return mod.fail(&block.base, src, "{s} '{}' has no member named '{s}'", .{
+                        kw_name, child_type, field_name,
+                    });
+                },
+                .Enum => {
+                    if (child_type.getNamespace()) |namespace| {
+                        if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| {
+                            return sema.analyzeLoad(block, src, inst, src);
+                        }
+                    }
+                    const field_index = child_type.enumFieldIndex(field_name) orelse {
+                        const msg = msg: {
+                            const msg = try mod.errMsg(
+                                &block.base,
+                                src,
+                                "enum '{}' has no member named '{s}'",
+                                .{ child_type, field_name },
+                            );
+                            errdefer msg.destroy(sema.gpa);
+                            try mod.errNoteNonLazy(
+                                child_type.declSrcLoc(),
+                                msg,
+                                "enum declared here",
+                                .{},
+                            );
+                            break :msg msg;
+                        };
+                        return mod.failWithOwnedErrorMsg(&block.base, msg);
+                    };
+                    const field_index_u32 = @intCast(u32, field_index);
+                    const enum_val = try Value.Tag.enum_field_index.create(arena, field_index_u32);
+                    return sema.addConstant(child_type, enum_val);
+                },
+                else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}),
+            }
+        },
+        .Struct => return sema.structFieldVal(block, src, object, field_name, field_name_src, object_ty),
+        .Union => return sema.unionFieldVal(block, src, object, field_name, field_name_src, object_ty),
+        else => {},
+    }
+    return mod.fail(&block.base, src, "type '{}' does not support field access", .{object_ty});
+}
 
+fn fieldPtr(
+    sema: *Sema,
+    block: *Scope.Block,
+    src: LazySrcLoc,
+    object_ptr: Air.Inst.Ref,
+    field_name: []const u8,
+    field_name_src: LazySrcLoc,
+) CompileError!Air.Inst.Ref {
+    // When editing this function, note that there is corresponding logic to be edited
+    // in `fieldVal`. This function takes a pointer and returns a pointer.
+
+    const mod = sema.mod;
+    const arena = sema.arena;
     const object_ptr_src = src; // TODO better source location
     const object_ptr_ty = sema.typeOf(object_ptr);
-    const elem_ty = switch (object_ptr_ty.zigTypeTag()) {
+    const object_ty = switch (object_ptr_ty.zigTypeTag()) {
         .Pointer => object_ptr_ty.elemType(),
         else => return mod.fail(&block.base, object_ptr_src, "expected pointer, found '{}'", .{object_ptr_ty}),
     };
-    switch (elem_ty.zigTypeTag()) {
+    switch (object_ty.zigTypeTag()) {
         .Array => {
             if (mem.eql(u8, field_name, "len")) {
                 return sema.addConstant(
                     Type.initTag(.single_const_pointer_to_comptime_int),
                     try Value.Tag.ref_val.create(
                         arena,
-                        try Value.Tag.int_u64.create(arena, elem_ty.arrayLen()),
+                        try Value.Tag.int_u64.create(arena, object_ty.arrayLen()),
                     ),
                 );
             } else {
@@ -6597,33 +6767,33 @@ fn namedFieldPtr(
                     &block.base,
                     field_name_src,
                     "no member named '{s}' in '{}'",
-                    .{ field_name, elem_ty },
+                    .{ field_name, object_ty },
                 );
             }
         },
         .Pointer => {
-            const ptr_child = elem_ty.elemType();
+            const ptr_child = object_ty.elemType();
             if (ptr_child.isSlice()) {
                 if (mem.eql(u8, field_name, "ptr")) {
                     return mod.fail(
                         &block.base,
                         field_name_src,
                         "cannot obtain reference to pointer field of slice '{}'",
-                        .{elem_ty},
+                        .{object_ty},
                     );
                 } else if (mem.eql(u8, field_name, "len")) {
                     return mod.fail(
                         &block.base,
                         field_name_src,
                         "cannot obtain reference to length field of slice '{}'",
-                        .{elem_ty},
+                        .{object_ty},
                     );
                 } else {
                     return mod.fail(
                         &block.base,
                         field_name_src,
                         "no member named '{s}' in '{}'",
-                        .{ field_name, elem_ty },
+                        .{ field_name, object_ty },
                     );
                 }
             } else switch (ptr_child.zigTypeTag()) {
@@ -6641,7 +6811,7 @@ fn namedFieldPtr(
                             &block.base,
                             field_name_src,
                             "no member named '{s}' in '{}'",
-                            .{ field_name, elem_ty },
+                            .{ field_name, object_ty },
                         );
                     }
                 },
@@ -6684,7 +6854,7 @@ fn namedFieldPtr(
                 },
                 .Struct, .Opaque, .Union => {
                     if (child_type.getNamespace()) |namespace| {
-                        if (try sema.analyzeNamespaceLookup(block, src, namespace, field_name)) |inst| {
+                        if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| {
                             return inst;
                         }
                     }
@@ -6701,7 +6871,7 @@ fn namedFieldPtr(
                 },
                 .Enum => {
                     if (child_type.getNamespace()) |namespace| {
-                        if (try sema.analyzeNamespaceLookup(block, src, namespace, field_name)) |inst| {
+                        if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| {
                             return inst;
                         }
                     }
@@ -6734,20 +6904,20 @@ fn namedFieldPtr(
                 else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}),
             }
         },
-        .Struct => return sema.analyzeStructFieldPtr(block, src, object_ptr, field_name, field_name_src, elem_ty),
-        .Union => return sema.analyzeUnionFieldPtr(block, src, object_ptr, field_name, field_name_src, elem_ty),
+        .Struct => return sema.structFieldPtr(block, src, object_ptr, field_name, field_name_src, object_ty),
+        .Union => return sema.unionFieldPtr(block, src, object_ptr, field_name, field_name_src, object_ty),
         else => {},
     }
-    return mod.fail(&block.base, src, "type '{}' does not support field access", .{elem_ty});
+    return mod.fail(&block.base, src, "type '{}' does not support field access", .{object_ty});
 }
 
-fn analyzeNamespaceLookup(
+fn namespaceLookup(
     sema: *Sema,
     block: *Scope.Block,
     src: LazySrcLoc,
     namespace: *Scope.Namespace,
     decl_name: []const u8,
-) CompileError!?Air.Inst.Ref {
+) CompileError!?*Decl {
     const mod = sema.mod;
     const gpa = sema.gpa;
     if (try sema.lookupInNamespace(namespace, decl_name)) |decl| {
@@ -6762,12 +6932,23 @@ fn analyzeNamespaceLookup(
             };
             return mod.failWithOwnedErrorMsg(&block.base, msg);
         }
-        return try sema.analyzeDeclRef(block, src, decl);
+        return decl;
     }
     return null;
 }
 
-fn analyzeStructFieldPtr(
+fn namespaceLookupRef(
+    sema: *Sema,
+    block: *Scope.Block,
+    src: LazySrcLoc,
+    namespace: *Scope.Namespace,
+    decl_name: []const u8,
+) CompileError!?Air.Inst.Ref {
+    const decl = (try sema.namespaceLookup(block, src, namespace, decl_name)) orelse return null;
+    return try sema.analyzeDeclRef(block, src, decl);
+}
+
+fn structFieldPtr(
     sema: *Sema,
     block: *Scope.Block,
     src: LazySrcLoc,
@@ -6803,14 +6984,52 @@ fn analyzeStructFieldPtr(
         .data = .{ .ty_pl = .{
             .ty = try sema.addType(ptr_field_ty),
             .payload = try sema.addExtra(Air.StructField{
-                .struct_ptr = struct_ptr,
+                .struct_operand = struct_ptr,
                 .field_index = @intCast(u32, field_index),
             }),
         } },
     });
 }
 
-fn analyzeUnionFieldPtr(
+fn structFieldVal(
+    sema: *Sema,
+    block: *Scope.Block,
+    src: LazySrcLoc,
+    struct_byval: Air.Inst.Ref,
+    field_name: []const u8,
+    field_name_src: LazySrcLoc,
+    unresolved_struct_ty: Type,
+) CompileError!Air.Inst.Ref {
+    assert(unresolved_struct_ty.zigTypeTag() == .Struct);
+
+    const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_ty);
+    const struct_obj = struct_ty.castTag(.@"struct").?.data;
+
+    const field_index = struct_obj.fields.getIndex(field_name) orelse
+        return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name);
+    const field = struct_obj.fields.values()[field_index];
+
+    if (try sema.resolveMaybeUndefVal(block, src, struct_byval)) |struct_val| {
+        if (struct_val.isUndef()) return sema.addConstUndef(field.ty);
+
+        const field_values = struct_val.castTag(.@"struct").?.data;
+        return sema.addConstant(field.ty, field_values[field_index]);
+    }
+
+    try sema.requireRuntimeBlock(block, src);
+    return block.addInst(.{
+        .tag = .struct_field_val,
+        .data = .{ .ty_pl = .{
+            .ty = try sema.addType(field.ty),
+            .payload = try sema.addExtra(Air.StructField{
+                .struct_operand = struct_byval,
+                .field_index = @intCast(u32, field_index),
+            }),
+        } },
+    });
+}
+
+fn unionFieldPtr(
     sema: *Sema,
     block: *Scope.Block,
     src: LazySrcLoc,
@@ -6847,6 +7066,37 @@ fn analyzeUnionFieldPtr(
     return mod.fail(&block.base, src, "TODO implement runtime union field access", .{});
 }
 
+fn unionFieldVal(
+    sema: *Sema,
+    block: *Scope.Block,
+    src: LazySrcLoc,
+    union_byval: Air.Inst.Ref,
+    field_name: []const u8,
+    field_name_src: LazySrcLoc,
+    unresolved_union_ty: Type,
+) CompileError!Air.Inst.Ref {
+    assert(unresolved_union_ty.zigTypeTag() == .Union);
+
+    const union_ty = try sema.resolveTypeFields(block, src, unresolved_union_ty);
+    const union_obj = union_ty.cast(Type.Payload.Union).?.data;
+
+    const field_index = union_obj.fields.getIndex(field_name) orelse
+        return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name);
+
+    const field = union_obj.fields.values()[field_index];
+
+    if (try sema.resolveMaybeUndefVal(block, src, union_byval)) |union_val| {
+        if (union_val.isUndef()) return sema.addConstUndef(field.ty);
+
+        // TODO detect inactive union field and emit compile error
+        const active_val = union_val.castTag(.@"union").?.data.val;
+        return sema.addConstant(field.ty, active_val);
+    }
+
+    try sema.requireRuntimeBlock(block, src);
+    return sema.mod.fail(&block.base, src, "TODO implement runtime union field access", .{});
+}
+
 fn elemPtr(
     sema: *Sema,
     block: *Scope.Block,
@@ -6973,7 +7223,7 @@ fn coerce(
     const arena = sema.arena;
 
     // undefined to anything
-    if (try sema.resolvePossiblyUndefinedValue(block, inst_src, inst)) |val| {
+    if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| {
         if (val.isUndef() or inst_ty.zigTypeTag() == .Undefined) {
             return sema.addConstant(dest_type, val);
         }
@@ -7207,8 +7457,8 @@ fn storePtr(
     if ((try sema.typeHasOnePossibleValue(block, src, elem_ty)) != null)
         return;
 
-    if (try sema.resolvePossiblyUndefinedValue(block, src, ptr)) |ptr_val| blk: {
-        const const_val = (try sema.resolvePossiblyUndefinedValue(block, src, value)) orelse
+    if (try sema.resolveMaybeUndefVal(block, src, ptr)) |ptr_val| blk: {
+        const const_val = (try sema.resolveMaybeUndefVal(block, src, value)) orelse
             return sema.mod.fail(&block.base, src, "cannot store runtime value in compile time variable", .{});
 
         if (ptr_val.tag() == .int_u64)
@@ -7252,7 +7502,7 @@ fn bitcast(
     inst: Air.Inst.Ref,
     inst_src: LazySrcLoc,
 ) CompileError!Air.Inst.Ref {
-    if (try sema.resolvePossiblyUndefinedValue(block, inst_src, inst)) |val| {
+    if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| {
         // Keep the comptime Value representation; take the new type.
         return sema.addConstant(dest_type, val);
     }
@@ -7358,7 +7608,7 @@ fn analyzeRef(
     const operand_ty = sema.typeOf(operand);
     const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One);
 
-    if (try sema.resolvePossiblyUndefinedValue(block, src, operand)) |val| {
+    if (try sema.resolveMaybeUndefVal(block, src, operand)) |val| {
         return sema.addConstant(ptr_type, try Value.Tag.ref_val.create(sema.arena, val));
     }
 
@@ -7395,7 +7645,7 @@ fn analyzeSliceLen(
     src: LazySrcLoc,
     slice_inst: Air.Inst.Ref,
 ) CompileError!Air.Inst.Ref {
-    if (try sema.resolvePossiblyUndefinedValue(block, src, slice_inst)) |slice_val| {
+    if (try sema.resolveMaybeUndefVal(block, src, slice_inst)) |slice_val| {
         if (slice_val.isUndef()) {
             return sema.addConstUndef(Type.initTag(.usize));
         }
@@ -7413,7 +7663,7 @@ fn analyzeIsNull(
     invert_logic: bool,
 ) CompileError!Air.Inst.Ref {
     const result_ty = Type.initTag(.bool);
-    if (try sema.resolvePossiblyUndefinedValue(block, src, operand)) |opt_val| {
+    if (try sema.resolveMaybeUndefVal(block, src, operand)) |opt_val| {
         if (opt_val.isUndef()) {
             return sema.addConstUndef(result_ty);
         }
@@ -7442,7 +7692,7 @@ fn analyzeIsNonErr(
     if (ot == .ErrorSet) return Air.Inst.Ref.bool_false;
     assert(ot == .ErrorUnion);
     const result_ty = Type.initTag(.bool);
-    if (try sema.resolvePossiblyUndefinedValue(block, src, operand)) |err_union| {
+    if (try sema.resolveMaybeUndefVal(block, src, operand)) |err_union| {
         if (err_union.isUndef()) {
             return sema.addConstUndef(result_ty);
         }
@@ -7567,8 +7817,8 @@ fn cmpNumeric(
         });
     }
 
-    if (try sema.resolvePossiblyUndefinedValue(block, lhs_src, lhs)) |lhs_val| {
-        if (try sema.resolvePossiblyUndefinedValue(block, rhs_src, rhs)) |rhs_val| {
+    if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| {
+        if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| {
             if (lhs_val.isUndef() or rhs_val.isUndef()) {
                 return sema.addConstUndef(Type.initTag(.bool));
             }
@@ -7635,7 +7885,7 @@ fn cmpNumeric(
     var dest_float_type: ?Type = null;
 
     var lhs_bits: usize = undefined;
-    if (try sema.resolvePossiblyUndefinedValue(block, lhs_src, lhs)) |lhs_val| {
+    if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| {
         if (lhs_val.isUndef())
             return sema.addConstUndef(Type.initTag(.bool));
         const is_unsigned = if (lhs_is_float) x: {
@@ -7670,7 +7920,7 @@ fn cmpNumeric(
     }
 
     var rhs_bits: usize = undefined;
-    if (try sema.resolvePossiblyUndefinedValue(block, rhs_src, rhs)) |rhs_val| {
+    if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| {
         if (rhs_val.isUndef())
             return sema.addConstUndef(Type.initTag(.bool));
         const is_unsigned = if (rhs_is_float) x: {
@@ -7725,7 +7975,7 @@ fn wrapOptional(
     inst: Air.Inst.Ref,
     inst_src: LazySrcLoc,
 ) !Air.Inst.Ref {
-    if (try sema.resolvePossiblyUndefinedValue(block, inst_src, inst)) |val| {
+    if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| {
         return sema.addConstant(dest_type, val);
     }
 
@@ -7743,7 +7993,7 @@ fn wrapErrorUnion(
     const inst_ty = sema.typeOf(inst);
     const dest_err_set_ty = dest_type.errorUnionSet();
     const dest_payload_ty = dest_type.errorUnionPayload();
-    if (try sema.resolvePossiblyUndefinedValue(block, inst_src, inst)) |val| {
+    if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| {
         if (inst_ty.zigTypeTag() != .ErrorSet) {
             _ = try sema.coerce(block, dest_payload_ty, inst, inst_src);
         } else switch (dest_err_set_ty.tag()) {
@@ -7956,7 +8206,7 @@ fn getBuiltin(
     const mod = sema.mod;
     const std_pkg = mod.main_pkg.table.get("std").?;
     const std_file = (mod.importPkg(std_pkg) catch unreachable).file;
-    const opt_builtin_inst = try sema.analyzeNamespaceLookup(
+    const opt_builtin_inst = try sema.namespaceLookupRef(
         block,
         src,
         std_file.root_decl.?.namespace,
@@ -7964,7 +8214,7 @@ fn getBuiltin(
     );
     const builtin_inst = try sema.analyzeLoad(block, src, opt_builtin_inst.?, src);
     const builtin_ty = try sema.analyzeAsType(block, src, builtin_inst);
-    const opt_ty_inst = try sema.analyzeNamespaceLookup(
+    const opt_ty_inst = try sema.namespaceLookupRef(
         block,
         src,
         builtin_ty.getNamespace().?,
@@ -8320,5 +8570,5 @@ fn isComptimeKnown(
     src: LazySrcLoc,
     inst: Air.Inst.Ref,
 ) !bool {
-    return (try sema.resolvePossiblyUndefinedValue(block, src, inst)) != null;
+    return (try sema.resolveMaybeUndefVal(block, src, inst)) != null;
 }