Commit a5c6e51f03

Andrew Kelley <andrew@ziglang.org>
2021-07-30 00:59:51
stage2: more principled approach to comptime references
* AIR no longer has a `variables` array. Instead of the `varptr` instruction, Sema emits a constant with a `decl_ref`. * AIR no longer has a `ref` instruction. There is no longer any instruction that takes a value and returns a pointer to it. If this is desired, Sema must either create an anynomous Decl and return a constant `decl_ref`, or in the case of a runtime value, emit an `alloc` instruction, `store` the value to it, and then return the `alloc`. * The `ref_val` Value Tag is eliminated. `decl_ref` should be used instead. Also added is `eu_payload_ptr` which points to the payload of an error union, given an error union pointer. In general, Sema should avoid calling `analyzeRef` if it can be helped. For example in the case of field_val and elem_val, there should never be a reason to create a temporary (alloc or decl). Recent previous commits made progress along that front. There is a new abstraction in Sema, which looks like this: var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); // here 'anon_decl.arena()` may be used const decl = try anon_decl.finish(ty, val); // decl is typically now used with `decl_ref`. This pattern is used to upgrade `ref_val` usages to `decl_ref` usages. Additional improvements: * Sema: fix source location resolution for calling convention expression. * Sema: properly report "unable to resolve comptime value" for loads of global variables. There is now a set of functions which can be called if the callee wants to obtain the Value even if the tag is `variable` (indicating comptime-known address but runtime-known value). * Sema: `coerce` resolves builtin types before checking equality. * Sema: fix `u1_type` missing from `addType`, making this type have a slightly more efficient representation in AIR. * LLVM backend: fix `genTypedValue` for tags `decl_ref` and `variable` to properly do an LLVMConstBitCast. * Remove unused parameter from `Value.toEnum`. After this commit, some test cases are no longer passing. This is due to the more principled approach to comptime references causing more anonymous decls to get sent to the linker for codegen. However, in all these cases the decls are not actually referenced by the runtime machine code. A future commit in this branch will implement garbage collection of decls so that unused decls do not get sent to the linker for codegen. This will make the tests go back to passing.
1 parent ed174b7
src/codegen/llvm/bindings.zig
@@ -112,6 +112,9 @@ pub const Value = opaque {
         ConstantIndices: [*]const *const Value,
         NumIndices: c_uint,
     ) *const Value;
+
+    pub const constBitCast = LLVMConstBitCast;
+    extern fn LLVMConstBitCast(ConstantVal: *const Value, ToType: *const Type) *const Value;
 };
 
 pub const Type = opaque {
src/codegen/c.zig
@@ -283,22 +283,7 @@ pub const DeclGen = struct {
                     },
                     else => switch (t.ptrSize()) {
                         .Slice => unreachable,
-                        .Many => {
-                            if (val.castTag(.ref_val)) |ref_val_payload| {
-                                const sub_val = ref_val_payload.data;
-                                if (sub_val.castTag(.bytes)) |bytes_payload| {
-                                    const bytes = bytes_payload.data;
-                                    try writer.writeByte('(');
-                                    try dg.renderType(writer, t);
-                                    // TODO: make our own C string escape instead of using std.zig.fmtEscapes
-                                    try writer.print(")\"{}\"", .{std.zig.fmtEscapes(bytes)});
-                                } else {
-                                    unreachable;
-                                }
-                            } else {
-                                unreachable;
-                            }
-                        },
+                        .Many => unreachable,
                         .One => {
                             var arena = std.heap.ArenaAllocator.init(dg.module.gpa);
                             defer arena.deinit();
@@ -934,10 +919,8 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM
             .br               => try airBr(o, inst),
             .switch_br        => try airSwitchBr(o, inst),
             .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"),
 
@@ -996,12 +979,6 @@ fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue
     return local;
 }
 
-fn airVarPtr(o: *Object, inst: Air.Inst.Index) !CValue {
-    const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
-    const variable = o.air.variables[ty_pl.payload];
-    return CValue{ .decl_ref = variable.owner_decl };
-}
-
 fn airAlloc(o: *Object, inst: Air.Inst.Index) !CValue {
     const writer = o.writer();
     const inst_ty = o.air.typeOfIndex(inst);
@@ -1653,22 +1630,6 @@ fn airOptionalPayload(o: *Object, inst: Air.Inst.Index) !CValue {
     return local;
 }
 
-fn airRef(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
-        return CValue.none;
-
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const writer = o.writer();
-    const operand = try o.resolveInst(ty_op.operand);
-
-    const inst_ty = o.air.typeOfIndex(inst);
-    const local = try o.allocLocal(inst_ty, .Const);
-    try writer.writeAll(" = ");
-    try o.writeCValue(writer, operand);
-    try writer.writeAll(";\n");
-    return local;
-}
-
 fn airStructFieldPtr(o: *Object, inst: Air.Inst.Index) !CValue {
     if (o.liveness.isUnused(inst))
         return CValue.none;
src/codegen/llvm.zig
@@ -699,29 +699,12 @@ pub const DeclGen = struct {
                 .decl_ref => {
                     const decl = tv.val.castTag(.decl_ref).?.data;
                     const val = try self.resolveGlobalDecl(decl);
-
-                    const usize_type = try self.llvmType(Type.initTag(.usize));
-
-                    // TODO: second index should be the index into the memory!
-                    var indices: [2]*const llvm.Value = .{
-                        usize_type.constNull(),
-                        usize_type.constNull(),
-                    };
-
-                    return val.constInBoundsGEP(&indices, indices.len);
-                },
-                .ref_val => {
-                    //const elem_value = tv.val.castTag(.ref_val).?.data;
-                    //const elem_type = tv.ty.castPointer().?.data;
-                    //const alloca = fg.?.buildAlloca(try self.llvmType(elem_type));
-                    //_ = fg.?.builder.buildStore(try self.genTypedValue(.{ .ty = elem_type, .val = elem_value }, fg), alloca);
-                    //return alloca;
-                    // TODO eliminate the ref_val Value Tag
-                    return self.todo("implement const of pointer tag ref_val", .{});
+                    return val.constBitCast(llvm_type);
                 },
                 .variable => {
                     const variable = tv.val.castTag(.variable).?.data;
-                    return self.resolveGlobalDecl(variable.owner_decl);
+                    const val = try self.resolveGlobalDecl(variable.owner_decl);
+                    return val.constBitCast(llvm_type);
                 },
                 .slice => {
                     const slice = tv.val.castTag(.slice).?.data;
@@ -977,7 +960,6 @@ pub const FuncGen = struct {
                 .ret        => try self.airRet(inst),
                 .store      => try self.airStore(inst),
                 .assembly   => try self.airAssembly(inst),
-                .varptr     => try self.airVarPtr(inst),
                 .slice_ptr  => try self.airSliceField(inst, 0),
                 .slice_len  => try self.airSliceField(inst, 1),
 
@@ -1001,7 +983,6 @@ pub const FuncGen = struct {
 
                 .constant => unreachable,
                 .const_ty => unreachable,
-                .ref => unreachable, // TODO eradicate this instruction
                 .unreach  => self.airUnreach(inst),
                 .dbg_stmt => blk: {
                     // TODO: implement debug info
@@ -1180,16 +1161,6 @@ pub const FuncGen = struct {
         return null;
     }
 
-    fn airVarPtr(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 variable = self.air.variables[ty_pl.payload];
-        const decl_llvm_value = self.dg.resolveGlobalDecl(variable.owner_decl);
-        return decl_llvm_value;
-    }
-
     fn airSliceField(self: *FuncGen, inst: Air.Inst.Index, index: c_uint) !?*const llvm.Value {
         if (self.liveness.isUnused(inst))
             return null;
src/codegen/wasm.zig
@@ -754,22 +754,21 @@ pub const Context = struct {
     }
 
     /// Generates the wasm bytecode for the declaration belonging to `Context`
-    pub fn gen(self: *Context, typed_value: TypedValue) InnerError!Result {
-        switch (typed_value.ty.zigTypeTag()) {
+    pub fn gen(self: *Context, ty: Type, val: Value) InnerError!Result {
+        switch (ty.zigTypeTag()) {
             .Fn => {
                 try self.genFunctype();
-                if (typed_value.val.castTag(.extern_fn)) |_| return Result.appended; // don't need code body for extern functions
+                if (val.tag() == .extern_fn) {
+                    return Result.appended; // don't need code body for extern functions
+                }
                 return self.fail("TODO implement wasm codegen for function pointers", .{});
             },
             .Array => {
-                if (typed_value.val.castTag(.bytes)) |payload| {
-                    if (typed_value.ty.sentinel()) |sentinel| {
+                if (val.castTag(.bytes)) |payload| {
+                    if (ty.sentinel()) |sentinel| {
                         try self.code.appendSlice(payload.data);
 
-                        switch (try self.gen(.{
-                            .ty = typed_value.ty.elemType(),
-                            .val = sentinel,
-                        })) {
+                        switch (try self.gen(ty.elemType(), sentinel)) {
                             .appended => return Result.appended,
                             .externally_managed => |data| {
                                 try self.code.appendSlice(data);
@@ -781,13 +780,17 @@ pub const Context = struct {
                 } else return self.fail("TODO implement gen for more kinds of arrays", .{});
             },
             .Int => {
-                const info = typed_value.ty.intInfo(self.target);
+                const info = ty.intInfo(self.target);
                 if (info.bits == 8 and info.signedness == .unsigned) {
-                    const int_byte = typed_value.val.toUnsignedInt();
+                    const int_byte = val.toUnsignedInt();
                     try self.code.append(@intCast(u8, int_byte));
                     return Result.appended;
                 }
-                return self.fail("TODO: Implement codegen for int type: '{}'", .{typed_value.ty});
+                return self.fail("TODO: Implement codegen for int type: '{}'", .{ty});
+            },
+            .Enum => {
+                try self.emitConstant(val, ty);
+                return Result.appended;
             },
             else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
         }
@@ -969,7 +972,7 @@ pub const Context = struct {
         return WValue{ .code_offset = offset };
     }
 
-    fn emitConstant(self: *Context, value: Value, ty: Type) InnerError!void {
+    fn emitConstant(self: *Context, val: Value, ty: Type) InnerError!void {
         const writer = self.code.writer();
         switch (ty.zigTypeTag()) {
             .Int => {
@@ -982,10 +985,10 @@ pub const Context = struct {
                 const int_info = ty.intInfo(self.target);
                 // write constant
                 switch (int_info.signedness) {
-                    .signed => try leb.writeILEB128(writer, value.toSignedInt()),
+                    .signed => try leb.writeILEB128(writer, val.toSignedInt()),
                     .unsigned => switch (int_info.bits) {
-                        0...32 => try leb.writeILEB128(writer, @bitCast(i32, @intCast(u32, value.toUnsignedInt()))),
-                        33...64 => try leb.writeILEB128(writer, @bitCast(i64, value.toUnsignedInt())),
+                        0...32 => try leb.writeILEB128(writer, @bitCast(i32, @intCast(u32, val.toUnsignedInt()))),
+                        33...64 => try leb.writeILEB128(writer, @bitCast(i64, val.toUnsignedInt())),
                         else => |bits| return self.fail("Wasm TODO: emitConstant for integer with {d} bits", .{bits}),
                     },
                 }
@@ -994,7 +997,7 @@ pub const Context = struct {
                 // write opcode
                 try writer.writeByte(wasm.opcode(.i32_const));
                 // write constant
-                try leb.writeILEB128(writer, value.toSignedInt());
+                try leb.writeILEB128(writer, val.toSignedInt());
             },
             .Float => {
                 // write opcode
@@ -1005,13 +1008,13 @@ pub const Context = struct {
                 try writer.writeByte(wasm.opcode(opcode));
                 // write constant
                 switch (ty.floatBits(self.target)) {
-                    0...32 => try writer.writeIntLittle(u32, @bitCast(u32, value.toFloat(f32))),
-                    64 => try writer.writeIntLittle(u64, @bitCast(u64, value.toFloat(f64))),
+                    0...32 => try writer.writeIntLittle(u32, @bitCast(u32, val.toFloat(f32))),
+                    64 => try writer.writeIntLittle(u64, @bitCast(u64, val.toFloat(f64))),
                     else => |bits| return self.fail("Wasm TODO: emitConstant for float with {d} bits", .{bits}),
                 }
             },
             .Pointer => {
-                if (value.castTag(.decl_ref)) |payload| {
+                if (val.castTag(.decl_ref)) |payload| {
                     const decl = payload.data;
 
                     // offset into the offset table within the 'data' section
@@ -1024,11 +1027,11 @@ pub const Context = struct {
                     try writer.writeByte(wasm.opcode(.i32_load));
                     try leb.writeULEB128(writer, @as(u32, 0));
                     try leb.writeULEB128(writer, @as(u32, 0));
-                } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{value.tag()});
+                } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});
             },
             .Void => {},
             .Enum => {
-                if (value.castTag(.enum_field_index)) |field_index| {
+                if (val.castTag(.enum_field_index)) |field_index| {
                     switch (ty.tag()) {
                         .enum_simple => {
                             try writer.writeByte(wasm.opcode(.i32_const));
@@ -1049,20 +1052,20 @@ pub const Context = struct {
                 } else {
                     var int_tag_buffer: Type.Payload.Bits = undefined;
                     const int_tag_ty = ty.intTagType(&int_tag_buffer);
-                    try self.emitConstant(value, int_tag_ty);
+                    try self.emitConstant(val, int_tag_ty);
                 }
             },
             .ErrorSet => {
-                const error_index = self.global_error_set.get(value.getError().?).?;
+                const error_index = self.global_error_set.get(val.getError().?).?;
                 try writer.writeByte(wasm.opcode(.i32_const));
                 try leb.writeULEB128(writer, error_index);
             },
             .ErrorUnion => {
-                const data = value.castTag(.error_union).?.data;
+                const data = val.castTag(.error_union).?.data;
                 const error_type = ty.errorUnionSet();
                 const payload_type = ty.errorUnionPayload();
-                if (value.getError()) |_| {
-                    // write the error value
+                if (val.getError()) |_| {
+                    // write the error val
                     try self.emitConstant(data, error_type);
 
                     // no payload, so write a '0' const
@@ -1085,7 +1088,7 @@ pub const Context = struct {
     }
 
     /// Returns a `Value` as a signed 32 bit value.
-    /// It's illegale to provide a value with a type that cannot be represented
+    /// It's illegal to provide a value with a type that cannot be represented
     /// as an integer value.
     fn valueAsI32(self: Context, val: Value, ty: Type) i32 {
         switch (ty.zigTypeTag()) {
src/link/Wasm.zig
@@ -275,7 +275,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
     defer context.deinit();
 
     // generate the 'code' section for the function declaration
-    const result = context.gen(.{ .ty = decl.ty, .val = decl.val }) catch |err| switch (err) {
+    const result = context.gen(decl.ty, decl.val) catch |err| switch (err) {
         error.CodegenFail => {
             decl.analysis = .codegen_failure;
             try module.failed_decls.put(module.gpa, decl, context.err_msg);
src/Air.zig
@@ -15,7 +15,6 @@ instructions: std.MultiArrayList(Inst).Slice,
 /// The first few indexes are reserved. See `ExtraIndex` for the values.
 extra: []const u32,
 values: []const Value,
-variables: []const *Module.Var,
 
 pub const ExtraIndex = enum(u32) {
     /// Payload index of the main `Block` in the `extra` array.
@@ -193,20 +192,10 @@ pub const Inst = struct {
         /// Result type is always `u1`.
         /// Uses the `un_op` field.
         bool_to_int,
-        /// Stores a value onto the stack and returns a pointer to it.
-        /// TODO audit where this AIR instruction is emitted, maybe it should instead be emitting
-        /// alloca instruction and storing to the alloca.
-        /// Uses the `ty_op` field.
-        ref,
         /// Return a value from a function.
         /// Result type is always noreturn; no instructions in a block follow this one.
         /// Uses the `un_op` field.
         ret,
-        /// Returns a pointer to a global variable.
-        /// Uses the `ty_pl` field. Index is into the `variables` array.
-        /// TODO this can be modeled simply as a constant with a decl ref and then
-        /// the variables array can be removed from Air.
-        varptr,
         /// Write a value to a pointer. LHS is pointer, RHS is value.
         /// Result type is always void.
         /// Uses the `bin_op` field.
@@ -454,7 +443,6 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .assembly,
         .block,
         .constant,
-        .varptr,
         .struct_field_ptr,
         .struct_field_val,
         => return air.getRefType(datas[inst].ty_pl.ty),
@@ -462,7 +450,6 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .not,
         .bitcast,
         .load,
-        .ref,
         .floatcast,
         .intcast,
         .optional_payload,
@@ -550,7 +537,6 @@ pub fn deinit(air: *Air, gpa: *std.mem.Allocator) void {
     air.instructions.deinit(gpa);
     gpa.free(air.extra);
     gpa.free(air.values);
-    gpa.free(air.variables);
     air.* = undefined;
 }
 
src/codegen.zig
@@ -848,13 +848,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .loop            => try self.airLoop(inst),
                     .not             => try self.airNot(inst),
                     .ptrtoint        => try self.airPtrToInt(inst),
-                    .ref             => try self.airRef(inst),
                     .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),
                     .slice_len       => try self.airSliceLen(inst),
 
@@ -1340,13 +1338,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
         }
 
-        fn airVarPtr(self: *Self, inst: Air.Inst.Index) !void {
-            const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
-                else => return self.fail("TODO implement varptr for {}", .{self.target.cpu.arch}),
-            };
-            return self.finishAir(inst, result, .{ .none, .none, .none });
-        }
-
         fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void {
             const ty_op = self.air.instructions.items(.data)[inst].ty_op;
             const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
@@ -2833,38 +2824,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return bt.finishAir(result);
         }
 
-        fn airRef(self: *Self, inst: Air.Inst.Index) !void {
-            const ty_op = self.air.instructions.items(.data)[inst].ty_op;
-            const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
-                const operand_ty = self.air.typeOf(ty_op.operand);
-                const operand = try self.resolveInst(ty_op.operand);
-                switch (operand) {
-                    .unreach => unreachable,
-                    .dead => unreachable,
-                    .none => break :result MCValue{ .none = {} },
-
-                    .immediate,
-                    .register,
-                    .ptr_stack_offset,
-                    .ptr_embedded_in_code,
-                    .compare_flags_unsigned,
-                    .compare_flags_signed,
-                    => {
-                        const stack_offset = try self.allocMemPtr(inst);
-                        try self.genSetStack(operand_ty, stack_offset, operand);
-                        break :result MCValue{ .ptr_stack_offset = stack_offset };
-                    },
-
-                    .stack_offset => |offset| break :result MCValue{ .ptr_stack_offset = offset },
-                    .embedded_in_code => |offset| break :result MCValue{ .ptr_embedded_in_code = offset },
-                    .memory => |vaddr| break :result MCValue{ .immediate = vaddr },
-
-                    .undef => return self.fail("TODO implement ref on an undefined value", .{}),
-                }
-            };
-            return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
-        }
-
         fn ret(self: *Self, mcv: MCValue) !void {
             const ret_ty = self.fn_type.fnReturnType();
             try self.setRegOrMem(ret_ty, self.ret_mcv, mcv);
src/Liveness.zig
@@ -256,14 +256,12 @@ fn analyzeInst(
         .const_ty,
         .breakpoint,
         .dbg_stmt,
-        .varptr,
         .unreach,
         => return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }),
 
         .not,
         .bitcast,
         .load,
-        .ref,
         .floatcast,
         .intcast,
         .optional_payload,
src/Module.zig
@@ -1324,6 +1324,42 @@ pub const Scope = struct {
             block.instructions.appendAssumeCapacity(result_index);
             return result_index;
         }
+
+        pub fn startAnonDecl(block: *Block) !WipAnonDecl {
+            return WipAnonDecl{
+                .block = block,
+                .new_decl_arena = std.heap.ArenaAllocator.init(block.sema.gpa),
+                .finished = false,
+            };
+        }
+
+        pub const WipAnonDecl = struct {
+            block: *Scope.Block,
+            new_decl_arena: std.heap.ArenaAllocator,
+            finished: bool,
+
+            pub fn arena(wad: *WipAnonDecl) *Allocator {
+                return &wad.new_decl_arena.allocator;
+            }
+
+            pub fn deinit(wad: *WipAnonDecl) void {
+                if (!wad.finished) {
+                    wad.new_decl_arena.deinit();
+                }
+                wad.* = undefined;
+            }
+
+            pub fn finish(wad: *WipAnonDecl, ty: Type, val: Value) !*Decl {
+                const new_decl = try wad.block.sema.mod.createAnonymousDecl(&wad.block.base, .{
+                    .ty = ty,
+                    .val = val,
+                });
+                errdefer wad.block.sema.mod.deleteAnonDecl(&wad.block.base, new_decl);
+                try new_decl.finalizeNewArena(&wad.new_decl_arena);
+                wad.finished = true;
+                return new_decl;
+            }
+        };
     };
 };
 
@@ -1700,6 +1736,7 @@ pub const SrcLoc = struct {
 
             .node_offset_fn_type_cc => |node_off| {
                 const tree = try src_loc.file_scope.getTree(gpa);
+                const node_datas = tree.nodes.items(.data);
                 const node_tags = tree.nodes.items(.tag);
                 const node = src_loc.declRelativeToNodeIndex(node_off);
                 var params: [1]ast.Node.Index = undefined;
@@ -1708,6 +1745,13 @@ pub const SrcLoc = struct {
                     .fn_proto_multi => tree.fnProtoMulti(node),
                     .fn_proto_one => tree.fnProtoOne(&params, node),
                     .fn_proto => tree.fnProto(node),
+                    .fn_decl => switch (node_tags[node_datas[node].lhs]) {
+                        .fn_proto_simple => tree.fnProtoSimple(&params, node_datas[node].lhs),
+                        .fn_proto_multi => tree.fnProtoMulti(node_datas[node].lhs),
+                        .fn_proto_one => tree.fnProtoOne(&params, node_datas[node].lhs),
+                        .fn_proto => tree.fnProto(node_datas[node].lhs),
+                        else => unreachable,
+                    },
                     else => unreachable,
                 };
                 const main_tokens = tree.nodes.items(.main_token);
@@ -2935,7 +2979,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
     const break_index = try sema.analyzeBody(&block_scope, body);
     const result_ref = zir_datas[break_index].@"break".operand;
     const src: LazySrcLoc = .{ .node_offset = 0 };
-    const decl_tv = try sema.resolveInstConst(&block_scope, src, result_ref);
+    const decl_tv = try sema.resolveInstValue(&block_scope, src, result_ref);
     const align_val = blk: {
         const align_ref = decl.zirAlignRef();
         if (align_ref == .none) break :blk Value.initTag(.null_value);
@@ -3603,7 +3647,6 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air {
         .instructions = sema.air_instructions.toOwnedSlice(),
         .extra = sema.air_extra.toOwnedSlice(gpa),
         .values = sema.air_values.toOwnedSlice(gpa),
-        .variables = sema.air_variables.toOwnedSlice(gpa),
     };
 }
 
src/print_air.zig
@@ -15,12 +15,11 @@ pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void {
         (@sizeOf(Air.Inst.Tag) + 8);
     const extra_bytes = air.extra.len * @sizeOf(u32);
     const values_bytes = air.values.len * @sizeOf(Value);
-    const variables_bytes = air.variables.len * @sizeOf(*Module.Var);
     const tomb_bytes = liveness.tomb_bits.len * @sizeOf(usize);
     const liveness_extra_bytes = liveness.extra.len * @sizeOf(u32);
     const liveness_special_bytes = liveness.special.count() * 8;
     const total_bytes = @sizeOf(Air) + instruction_bytes + extra_bytes +
-        values_bytes * variables_bytes + @sizeOf(Liveness) + liveness_extra_bytes +
+        values_bytes + @sizeOf(Liveness) + liveness_extra_bytes +
         liveness_special_bytes + tomb_bytes;
 
     // zig fmt: off
@@ -29,7 +28,6 @@ pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void {
         \\# AIR Instructions:         {d} ({})
         \\# AIR Extra Data:           {d} ({})
         \\# AIR Values Bytes:         {d} ({})
-        \\# AIR Variables Bytes:      {d} ({})
         \\# Liveness tomb_bits:       {}
         \\# Liveness Extra Data:      {d} ({})
         \\# Liveness special table:   {d} ({})
@@ -39,7 +37,6 @@ pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void {
         air.instructions.len, fmtIntSizeBin(instruction_bytes),
         air.extra.len, fmtIntSizeBin(extra_bytes),
         air.values.len, fmtIntSizeBin(values_bytes),
-        air.variables.len, fmtIntSizeBin(variables_bytes),
         fmtIntSizeBin(tomb_bytes),
         liveness.extra.len, fmtIntSizeBin(liveness_extra_bytes),
         liveness.special.count(), fmtIntSizeBin(liveness_special_bytes),
@@ -152,7 +149,6 @@ const Writer = struct {
             .not,
             .bitcast,
             .load,
-            .ref,
             .floatcast,
             .intcast,
             .optional_payload,
@@ -174,7 +170,6 @@ const Writer = struct {
 
             .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),
             .dbg_stmt => try w.writeDbgStmt(s, inst),
@@ -243,12 +238,6 @@ const Writer = struct {
         try s.print(", {d}", .{extra.data.field_index});
     }
 
-    fn writeVarPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
-        _ = w;
-        _ = inst;
-        try s.writeAll("TODO");
-    }
-
     fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
         const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
         const val = w.air.values[ty_pl.payload];
src/Sema.zig
@@ -14,7 +14,6 @@ code: Zir,
 air_instructions: std.MultiArrayList(Air.Inst) = .{},
 air_extra: std.ArrayListUnmanaged(u32) = .{},
 air_values: std.ArrayListUnmanaged(Value) = .{},
-air_variables: std.ArrayListUnmanaged(*Module.Var) = .{},
 /// Maps ZIR to AIR.
 inst_map: InstMap = .{},
 /// When analyzing an inline function call, owner_decl is the Decl of the caller
@@ -76,7 +75,6 @@ pub fn deinit(sema: *Sema) void {
     sema.air_instructions.deinit(gpa);
     sema.air_extra.deinit(gpa);
     sema.air_values.deinit(gpa);
-    sema.air_variables.deinit(gpa);
     sema.inst_map.deinit(gpa);
     sema.decl_val_table.deinit(gpa);
     sema.* = undefined;
@@ -639,16 +637,40 @@ fn analyzeAsType(
     return val.toType(sema.arena);
 }
 
+/// May return Value Tags: `variable`, `undef`.
+/// See `resolveConstValue` for an alternative.
+fn resolveValue(
+    sema: *Sema,
+    block: *Scope.Block,
+    src: LazySrcLoc,
+    air_ref: Air.Inst.Ref,
+) CompileError!Value {
+    if (try sema.resolveMaybeUndefValAllowVariables(block, src, air_ref)) |val| {
+        return val;
+    }
+    return sema.failWithNeededComptime(block, src);
+}
+
+/// Will not return Value Tags: `variable`, `undef`. Instead they will emit compile errors.
+/// See `resolveValue` for an alternative.
 fn resolveConstValue(
     sema: *Sema,
     block: *Scope.Block,
     src: LazySrcLoc,
     air_ref: Air.Inst.Ref,
 ) CompileError!Value {
-    return (try sema.resolveDefinedValue(block, src, air_ref)) orelse
-        return sema.failWithNeededComptime(block, src);
+    if (try sema.resolveMaybeUndefValAllowVariables(block, src, air_ref)) |val| {
+        switch (val.tag()) {
+            .undef => return sema.failWithUseOfUndef(block, src),
+            .variable => return sema.failWithNeededComptime(block, src),
+            else => return val,
+        }
+    }
+    return sema.failWithNeededComptime(block, src);
 }
 
+/// Value Tag `variable` causes this function to return `null`.
+/// Value Tag `undef` causes this function to return a compile error.
 fn resolveDefinedValue(
     sema: *Sema,
     block: *Scope.Block,
@@ -664,11 +686,27 @@ fn resolveDefinedValue(
     return null;
 }
 
+/// Value Tag `variable` causes this function to return `null`.
+/// Value Tag `undef` causes this function to return the Value.
 fn resolveMaybeUndefVal(
     sema: *Sema,
     block: *Scope.Block,
     src: LazySrcLoc,
     inst: Air.Inst.Ref,
+) CompileError!?Value {
+    const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null;
+    if (val.tag() == .variable) {
+        return sema.failWithNeededComptime(block, src);
+    }
+    return val;
+}
+
+/// Returns all Value tags including `variable` and `undef`.
+fn resolveMaybeUndefValAllowVariables(
+    sema: *Sema,
+    block: *Scope.Block,
+    src: LazySrcLoc,
+    inst: Air.Inst.Ref,
 ) CompileError!?Value {
     // First section of indexes correspond to a set number of constant values.
     var i: usize = @enumToInt(inst);
@@ -734,6 +772,8 @@ fn resolveInt(
     return val.toUnsignedInt();
 }
 
+// Returns a compile error if the value has tag `variable`. See `resolveInstValue` for
+// a function that does not.
 pub fn resolveInstConst(
     sema: *Sema,
     block: *Scope.Block,
@@ -748,6 +788,22 @@ pub fn resolveInstConst(
     };
 }
 
+// Value Tag may be `undef` or `variable`.
+// See `resolveInstConst` for an alternative.
+pub fn resolveInstValue(
+    sema: *Sema,
+    block: *Scope.Block,
+    src: LazySrcLoc,
+    zir_ref: Zir.Inst.Ref,
+) CompileError!TypedValue {
+    const air_ref = sema.resolveInst(zir_ref);
+    const val = try sema.resolveValue(block, src, air_ref);
+    return TypedValue{
+        .ty = sema.typeOf(air_ref),
+        .val = val,
+    };
+}
+
 fn zirBitcastResultPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
@@ -1707,7 +1763,7 @@ fn zirStr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
     });
     errdefer sema.mod.deleteAnonDecl(&block.base, new_decl);
     try new_decl.finalizeNewArena(&new_decl_arena);
-    return sema.analyzeDeclRef(block, .unneeded, new_decl);
+    return sema.analyzeDeclRef(new_decl);
 }
 
 fn zirInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -2090,10 +2146,7 @@ fn zirExport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro
     const linkage_index = struct_obj.fields.getIndex("linkage").?;
     const section_index = struct_obj.fields.getIndex("section").?;
     const export_name = try fields[name_index].toAllocatedBytes(sema.arena);
-    const linkage = fields[linkage_index].toEnum(
-        struct_obj.fields.values()[linkage_index].ty,
-        std.builtin.GlobalLinkage,
-    );
+    const linkage = fields[linkage_index].toEnum(std.builtin.GlobalLinkage);
 
     if (linkage != .Strong) {
         return sema.mod.fail(&block.base, src, "TODO: implement exporting with non-strong linkage", .{});
@@ -2194,7 +2247,7 @@ fn zirDeclRef(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
     const src = inst_data.src();
     const decl_name = inst_data.get(sema.code);
     const decl = try sema.lookupIdentifier(block, src, decl_name);
-    return sema.analyzeDeclRef(block, src, decl);
+    return sema.analyzeDeclRef(decl);
 }
 
 fn zirDeclVal(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -2978,14 +3031,9 @@ fn zirErrUnionPayloadPtr(
         if (val.getError()) |name| {
             return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name});
         }
-        const data = val.castTag(.error_union).?.data;
-        // The same Value represents the pointer to the error union and the payload.
         return sema.addConstant(
             operand_pointer_ty,
-            try Value.Tag.ref_val.create(
-                sema.arena,
-                data,
-            ),
+            try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val),
         );
     }
 
@@ -6296,7 +6344,7 @@ fn zirFuncExtended(
         const cc_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
         extra_index += 1;
         const cc_tv = try sema.resolveInstConst(block, cc_src, cc_ref);
-        break :blk cc_tv.val.toEnum(cc_tv.ty, std.builtin.CallingConvention);
+        break :blk cc_tv.val.toEnum(std.builtin.CallingConvention);
     } else .Unspecified;
 
     const align_val: Value = if (small.has_align) blk: {
@@ -6554,7 +6602,7 @@ fn safetyPanic(
         });
         errdefer sema.mod.deleteAnonDecl(&block.base, new_decl);
         try new_decl.finalizeNewArena(&new_decl_arena);
-        break :msg_inst try sema.analyzeDeclRef(block, .unneeded, new_decl);
+        break :msg_inst try sema.analyzeDeclRef(new_decl);
     };
 
     const casted_msg_inst = try sema.coerce(block, Type.initTag(.const_slice_u8), msg_inst, src);
@@ -6761,11 +6809,16 @@ fn fieldPtr(
     switch (object_ty.zigTypeTag()) {
         .Array => {
             if (mem.eql(u8, field_name, "len")) {
+                var anon_decl = try block.startAnonDecl();
+                defer anon_decl.deinit();
                 return sema.addConstant(
                     Type.initTag(.single_const_pointer_to_comptime_int),
-                    try Value.Tag.ref_val.create(
+                    try Value.Tag.decl_ref.create(
                         arena,
-                        try Value.Tag.int_u64.create(arena, object_ty.arrayLen()),
+                        try anon_decl.finish(
+                            Type.initTag(.comptime_int),
+                            try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()),
+                        ),
                     ),
                 );
             } else {
@@ -6780,18 +6833,25 @@ fn fieldPtr(
         .Pointer => {
             const ptr_child = object_ty.elemType();
             if (ptr_child.isSlice()) {
+                // Here for the ptr and len fields what we need to do is the situation
+                // when a temporary has its address taken, e.g. `&a[c..d].len`.
+                // This value may be known at compile-time or runtime. In the former
+                // case, it should create an anonymous Decl and return a decl_ref to it.
+                // In the latter case, it should add an `alloc` instruction, store
+                // the runtime value to it, and then return the `alloc`.
+                // In both cases the pointer should be const.
                 if (mem.eql(u8, field_name, "ptr")) {
                     return mod.fail(
                         &block.base,
                         field_name_src,
-                        "cannot obtain reference to pointer field of slice '{}'",
+                        "TODO: implement reference to 'ptr' field of slice '{}'",
                         .{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 '{}'",
+                        "TODO: implement reference to 'len' field of slice '{}'",
                         .{object_ty},
                     );
                 } else {
@@ -6805,11 +6865,16 @@ fn fieldPtr(
             } else switch (ptr_child.zigTypeTag()) {
                 .Array => {
                     if (mem.eql(u8, field_name, "len")) {
+                        var anon_decl = try block.startAnonDecl();
+                        defer anon_decl.deinit();
                         return sema.addConstant(
                             Type.initTag(.single_const_pointer_to_comptime_int),
-                            try Value.Tag.ref_val.create(
+                            try Value.Tag.decl_ref.create(
                                 arena,
-                                try Value.Tag.int_u64.create(arena, ptr_child.arrayLen()),
+                                try anon_decl.finish(
+                                    Type.initTag(.comptime_int),
+                                    try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()),
+                                ),
                             ),
                         );
                     } else {
@@ -6848,13 +6913,16 @@ fn fieldPtr(
                         });
                     } else (try mod.getErrorValue(field_name)).key;
 
+                    var anon_decl = try block.startAnonDecl();
+                    defer anon_decl.deinit();
                     return sema.addConstant(
                         try Module.simplePtrType(arena, child_type, false, .One),
-                        try Value.Tag.ref_val.create(
+                        try Value.Tag.decl_ref.create(
                             arena,
-                            try Value.Tag.@"error".create(arena, .{
-                                .name = name,
-                            }),
+                            try anon_decl.finish(
+                                child_type,
+                                try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }),
+                            ),
                         ),
                     );
                 },
@@ -6901,10 +6969,17 @@ fn fieldPtr(
                         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);
+                    var anon_decl = try block.startAnonDecl();
+                    defer anon_decl.deinit();
                     return sema.addConstant(
                         try Module.simplePtrType(arena, child_type, false, .One),
-                        try Value.Tag.ref_val.create(arena, enum_val),
+                        try Value.Tag.decl_ref.create(
+                            arena,
+                            try anon_decl.finish(
+                                child_type,
+                                try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32),
+                            ),
+                        ),
                     );
                 },
                 else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}),
@@ -6951,7 +7026,7 @@ fn namespaceLookupRef(
     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);
+    return try sema.analyzeDeclRef(decl);
 }
 
 fn structFieldPtr(
@@ -7207,13 +7282,15 @@ fn elemPtrArray(
 fn coerce(
     sema: *Sema,
     block: *Scope.Block,
-    dest_type: Type,
+    dest_type_unresolved: Type,
     inst: Air.Inst.Ref,
     inst_src: LazySrcLoc,
 ) CompileError!Air.Inst.Ref {
-    if (dest_type.tag() == .var_args_param) {
+    if (dest_type_unresolved.tag() == .var_args_param) {
         return sema.coerceVarArgParam(block, inst, inst_src);
     }
+    const dest_type_src = inst_src; // TODO better source location
+    const dest_type = try sema.resolveTypeFields(block, dest_type_src, dest_type_unresolved);
 
     const inst_ty = sema.typeOf(inst);
     // If the types are the same, we can return the operand.
@@ -7554,17 +7631,17 @@ fn analyzeDeclVal(
     if (sema.decl_val_table.get(decl)) |result| {
         return result;
     }
-    const decl_ref = try sema.analyzeDeclRef(block, src, decl);
+    const decl_ref = try sema.analyzeDeclRef(decl);
     const result = try sema.analyzeLoad(block, src, decl_ref, src);
     if (Air.refToIndex(result)) |index| {
         if (sema.air_instructions.items(.tag)[index] == .constant) {
-            sema.decl_val_table.put(sema.gpa, decl, result) catch {};
+            try sema.decl_val_table.put(sema.gpa, decl, result);
         }
     }
     return result;
 }
 
-fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl) CompileError!Air.Inst.Ref {
+fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref {
     try sema.mod.declareDeclDependency(sema.owner_decl, decl);
     sema.mod.ensureDeclAnalyzed(decl) catch |err| {
         if (sema.func) |func| {
@@ -7576,8 +7653,10 @@ fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl
     };
 
     const decl_tv = try decl.typedValue();
-    if (decl_tv.val.tag() == .variable) {
-        return sema.analyzeVarRef(block, src, decl_tv);
+    if (decl_tv.val.castTag(.variable)) |payload| {
+        const variable = payload.data;
+        const ty = try Module.simplePtrType(sema.arena, decl_tv.ty, variable.is_mutable, .One);
+        return sema.addConstant(ty, try Value.Tag.decl_ref.create(sema.arena, decl));
     }
     return sema.addConstant(
         try Module.simplePtrType(sema.arena, decl_tv.ty, false, .One),
@@ -7585,26 +7664,6 @@ fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl
     );
 }
 
-fn analyzeVarRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, tv: TypedValue) CompileError!Air.Inst.Ref {
-    const variable = tv.val.castTag(.variable).?.data;
-
-    const ty = try Module.simplePtrType(sema.arena, tv.ty, variable.is_mutable, .One);
-    if (!variable.is_mutable and !variable.is_extern) {
-        return sema.addConstant(ty, try Value.Tag.ref_val.create(sema.arena, variable.init));
-    }
-
-    const gpa = sema.gpa;
-    try sema.requireRuntimeBlock(block, src);
-    try sema.air_variables.append(gpa, variable);
-    return block.addInst(.{
-        .tag = .varptr,
-        .data = .{ .ty_pl = .{
-            .ty = try sema.addType(ty),
-            .payload = @intCast(u32, sema.air_variables.items.len - 1),
-        } },
-    });
-}
-
 fn analyzeRef(
     sema: *Sema,
     block: *Scope.Block,
@@ -7615,11 +7674,21 @@ fn analyzeRef(
     const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One);
 
     if (try sema.resolveMaybeUndefVal(block, src, operand)) |val| {
-        return sema.addConstant(ptr_type, try Value.Tag.ref_val.create(sema.arena, val));
+        var anon_decl = try block.startAnonDecl();
+        defer anon_decl.deinit();
+        return sema.addConstant(
+            ptr_type,
+            try Value.Tag.decl_ref.create(
+                sema.arena,
+                try anon_decl.finish(operand_ty, try val.copy(anon_decl.arena())),
+            ),
+        );
     }
 
     try sema.requireRuntimeBlock(block, src);
-    return block.addTyOp(.ref, ptr_type, operand);
+    const alloc = try block.addTy(.alloc, ptr_type);
+    try sema.storePtr(block, src, alloc, operand);
+    return alloc;
 }
 
 fn analyzeLoad(
@@ -8447,12 +8516,12 @@ fn getTmpAir(sema: Sema) Air {
         .instructions = sema.air_instructions.slice(),
         .extra = sema.air_extra.items,
         .values = sema.air_values.items,
-        .variables = sema.air_variables.items,
     };
 }
 
 pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref {
     switch (ty.tag()) {
+        .u1 => return .u1_type,
         .u8 => return .u8_type,
         .i8 => return .i8_type,
         .u16 => return .u16_type,
src/value.zig
@@ -100,8 +100,6 @@ pub const Value = extern union {
         function,
         extern_fn,
         variable,
-        /// Represents a pointer to another immutable value.
-        ref_val,
         /// Represents a comptime variables storage.
         comptime_alloc,
         /// Represents a pointer to a decl, not the value of the decl.
@@ -126,6 +124,8 @@ pub const Value = extern union {
         enum_field_index,
         @"error",
         error_union,
+        /// A pointer to the payload of an error union, based on a pointer to an error union.
+        eu_payload_ptr,
         /// An instance of a struct.
         @"struct",
         /// An instance of a union.
@@ -214,9 +214,9 @@ pub const Value = extern union {
                 .decl_ref,
                 => Payload.Decl,
 
-                .ref_val,
                 .repeated,
                 .error_union,
+                .eu_payload_ptr,
                 => Payload.SubValue,
 
                 .bytes,
@@ -407,15 +407,6 @@ pub const Value = extern union {
             .function => return self.copyPayloadShallow(allocator, Payload.Function),
             .extern_fn => return self.copyPayloadShallow(allocator, Payload.Decl),
             .variable => return self.copyPayloadShallow(allocator, Payload.Variable),
-            .ref_val => {
-                const payload = self.castTag(.ref_val).?;
-                const new_payload = try allocator.create(Payload.SubValue);
-                new_payload.* = .{
-                    .base = payload.base,
-                    .data = try payload.data.copy(allocator),
-                };
-                return Value{ .ptr_otherwise = &new_payload.base };
-            },
             .comptime_alloc => return self.copyPayloadShallow(allocator, Payload.ComptimeAlloc),
             .decl_ref => return self.copyPayloadShallow(allocator, Payload.Decl),
             .elem_ptr => {
@@ -443,8 +434,8 @@ pub const Value = extern union {
                 return Value{ .ptr_otherwise = &new_payload.base };
             },
             .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes),
-            .repeated => {
-                const payload = self.castTag(.repeated).?;
+            .repeated, .error_union, .eu_payload_ptr => {
+                const payload = self.cast(Payload.SubValue).?;
                 const new_payload = try allocator.create(Payload.SubValue);
                 new_payload.* = .{
                     .base = payload.base,
@@ -489,15 +480,6 @@ pub const Value = extern union {
             },
             .enum_field_index => return self.copyPayloadShallow(allocator, Payload.U32),
             .@"error" => return self.copyPayloadShallow(allocator, Payload.Error),
-            .error_union => {
-                const payload = self.castTag(.error_union).?;
-                const new_payload = try allocator.create(Payload.SubValue);
-                new_payload.* = .{
-                    .base = payload.base,
-                    .data = try payload.data.copy(allocator),
-                };
-                return Value{ .ptr_otherwise = &new_payload.base };
-            },
             .@"struct" => @panic("TODO can't copy struct value without knowing the type"),
             .@"union" => @panic("TODO can't copy union value without knowing the type"),
 
@@ -609,11 +591,6 @@ pub const Value = extern union {
             .function => return out_stream.print("(function '{s}')", .{val.castTag(.function).?.data.owner_decl.name}),
             .extern_fn => return out_stream.writeAll("(extern function)"),
             .variable => return out_stream.writeAll("(variable)"),
-            .ref_val => {
-                const ref_val = val.castTag(.ref_val).?.data;
-                try out_stream.writeAll("&const ");
-                val = ref_val;
-            },
             .comptime_alloc => {
                 const ref_val = val.castTag(.comptime_alloc).?.data.val;
                 try out_stream.writeAll("&");
@@ -648,6 +625,10 @@ pub const Value = extern union {
             // TODO to print this it should be error{ Set, Items }!T(val), but we need the type for that
             .error_union => return out_stream.print("error_union_val({})", .{val.castTag(.error_union).?.data}),
             .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"),
+            .eu_payload_ptr => {
+                try out_stream.writeAll("(eu_payload_ptr)");
+                val = val.castTag(.eu_payload_ptr).?.data;
+            },
         };
     }
 
@@ -758,7 +739,6 @@ pub const Value = extern union {
             .function,
             .extern_fn,
             .variable,
-            .ref_val,
             .comptime_alloc,
             .decl_ref,
             .elem_ptr,
@@ -780,18 +760,21 @@ pub const Value = extern union {
             .@"union",
             .inferred_alloc,
             .abi_align_default,
+            .eu_payload_ptr,
             => unreachable,
         };
     }
 
     /// Asserts the type is an enum type.
-    pub fn toEnum(val: Value, enum_ty: Type, comptime E: type) E {
-        _ = enum_ty;
-        // TODO this needs to resolve other kinds of Value tags rather than
-        // assuming the tag will be .enum_field_index.
-        const field_index = val.castTag(.enum_field_index).?.data;
-        // TODO should `@intToEnum` do this `@intCast` for you?
-        return @intToEnum(E, @intCast(@typeInfo(E).Enum.tag_type, field_index));
+    pub fn toEnum(val: Value, comptime E: type) E {
+        switch (val.tag()) {
+            .enum_field_index => {
+                const field_index = val.castTag(.enum_field_index).?.data;
+                // TODO should `@intToEnum` do this `@intCast` for you?
+                return @intToEnum(E, @intCast(@typeInfo(E).Enum.tag_type, field_index));
+            },
+            else => unreachable,
+        }
     }
 
     /// Asserts the value is an integer.
@@ -1255,6 +1238,9 @@ pub const Value = extern union {
             .slice => {
                 @panic("TODO Value.hash for slice");
             },
+            .eu_payload_ptr => {
+                @panic("TODO Value.hash for eu_payload_ptr");
+            },
             .int_u64 => {
                 const payload = self.castTag(.int_u64).?;
                 std.hash.autoHash(&hasher, payload.data);
@@ -1263,10 +1249,6 @@ pub const Value = extern union {
                 const payload = self.castTag(.int_i64).?;
                 std.hash.autoHash(&hasher, payload.data);
             },
-            .ref_val => {
-                const payload = self.castTag(.ref_val).?;
-                std.hash.autoHash(&hasher, payload.data.hash());
-            },
             .comptime_alloc => {
                 const payload = self.castTag(.comptime_alloc).?;
                 std.hash.autoHash(&hasher, payload.data.val.hash());
@@ -1367,7 +1349,6 @@ pub const Value = extern union {
     pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {
         return switch (self.tag()) {
             .comptime_alloc => self.castTag(.comptime_alloc).?.data.val,
-            .ref_val => self.castTag(.ref_val).?.data,
             .decl_ref => self.castTag(.decl_ref).?.data.value(),
             .elem_ptr => {
                 const elem_ptr = self.castTag(.elem_ptr).?.data;
@@ -1379,6 +1360,11 @@ pub const Value = extern union {
                 const container_val = try field_ptr.container_ptr.pointerDeref(allocator);
                 return container_val.fieldValue(allocator, field_ptr.field_index);
             },
+            .eu_payload_ptr => {
+                const err_union_ptr = self.castTag(.eu_payload_ptr).?.data;
+                const err_union_val = try err_union_ptr.pointerDeref(allocator);
+                return err_union_val.castTag(.error_union).?.data;
+            },
 
             else => unreachable,
         };
@@ -1390,7 +1376,6 @@ pub const Value = extern union {
             .bytes => val.castTag(.bytes).?.data.len,
             .array => val.castTag(.array).?.data.len,
             .slice => val.castTag(.slice).?.data.len.toUnsignedInt(),
-            .ref_val => sliceLen(val.castTag(.ref_val).?.data),
             .decl_ref => {
                 const decl = val.castTag(.decl_ref).?.data;
                 if (decl.ty.zigTypeTag() == .Array) {
@@ -1576,7 +1561,6 @@ pub const Value = extern union {
             .int_i64,
             .int_big_positive,
             .int_big_negative,
-            .ref_val,
             .comptime_alloc,
             .decl_ref,
             .elem_ptr,
@@ -1599,6 +1583,7 @@ pub const Value = extern union {
             .@"union",
             .null_value,
             .abi_align_default,
+            .eu_payload_ptr,
             => false,
 
             .undef => unreachable,
test/cases.zig
@@ -1182,10 +1182,11 @@ pub fn addCases(ctx: *TestContext) !void {
         var case = ctx.obj("extern variable has no type", linux_x64);
         case.addError(
             \\comptime {
-            \\    _ = foo;
+            \\    const x = foo + foo;
+            \\    _ = x;
             \\}
             \\extern var foo: i32;
-        , &[_][]const u8{":2:9: error: unable to resolve comptime value"});
+        , &[_][]const u8{":2:15: error: unable to resolve comptime value"});
         case.addError(
             \\export fn entry() void {
             \\    _ = foo;