Commit 3f7d9b5fc1

Andrew Kelley <andrew@ziglang.org>
2020-12-31 06:31:56
stage2: rework Value Payload layout
This is the same as the previous commit but for Value instead of Type. Add `Value.castTag` and note that it is preferable to call than `Value.cast`. This matches other abstractions in the codebase. Added a convenience function `Value.Tag.create` which really cleans up the callsites of creating `Value` objects. `Value` tags can now share payload types. This is in preparation for another improvement that I want to do.
1 parent 133da86
src/codegen/c.zig
@@ -138,25 +138,25 @@ fn renderValue(
             .undef, .zero => try writer.writeAll("0"),
             .one => try writer.writeAll("1"),
             .decl_ref => {
-                const decl_ref_payload = val.cast(Value.Payload.DeclRef).?;
+                const decl = val.castTag(.decl_ref).?.data;
 
                 // Determine if we must pointer cast.
-                const decl_tv = decl_ref_payload.decl.typed_value.most_recent.typed_value;
+                const decl_tv = decl.typed_value.most_recent.typed_value;
                 if (t.eql(decl_tv.ty)) {
-                    try writer.print("&{s}", .{decl_ref_payload.decl.name});
+                    try writer.print("&{s}", .{decl.name});
                 } else {
                     try writer.writeAll("(");
                     try renderType(ctx, writer, t);
-                    try writer.print(")&{s}", .{decl_ref_payload.decl.name});
+                    try writer.print(")&{s}", .{decl.name});
                 }
             },
             .function => {
-                const payload = val.cast(Value.Payload.Function).?;
-                try writer.print("{s}", .{payload.func.owner_decl.name});
+                const func = val.castTag(.function).?.data;
+                try writer.print("{s}", .{func.owner_decl.name});
             },
             .extern_fn => {
-                const payload = val.cast(Value.Payload.ExternFn).?;
-                try writer.print("{s}", .{payload.decl.name});
+                const decl = val.castTag(.extern_fn).?.data;
+                try writer.print("{s}", .{decl.name});
             },
             else => |e| return ctx.fail(
                 ctx.decl.src(),
@@ -169,7 +169,7 @@ fn renderValue(
             switch (val.tag()) {
                 .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"),
                 .bytes => {
-                    const bytes = val.cast(Value.Payload.Bytes).?.data;
+                    const bytes = val.castTag(.bytes).?.data;
                     // TODO: make our own C string escape instead of using {Z}
                     try writer.print("\"{Z}\"", .{bytes});
                 },
@@ -209,7 +209,7 @@ fn renderFunctionSignature(
         switch (tv.val.tag()) {
             .extern_fn => break :blk true,
             .function => {
-                const func = tv.val.cast(Value.Payload.Function).?.func;
+                const func = tv.val.castTag(.function).?.data;
                 break :blk ctx.module.decl_exports.contains(func.owner_decl);
             },
             else => unreachable,
@@ -268,13 +268,13 @@ pub fn generate(file: *C, module: *Module, decl: *Decl) !void {
         ctx.deinit();
     }
 
-    if (tv.val.cast(Value.Payload.Function)) |func_payload| {
+    if (tv.val.castTag(.function)) |func_payload| {
         const writer = file.main.writer();
         try renderFunctionSignature(&ctx, writer, decl);
 
         try writer.writeAll(" {");
 
-        const func: *Module.Fn = func_payload.func;
+        const func: *Module.Fn = func_payload.data;
         const instructions = func.analysis.success.instructions;
         if (instructions.len > 0) {
             try writer.writeAll("\n");
@@ -480,10 +480,10 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
     const writer = file.main.writer();
     const header = file.header.buf.writer();
     if (inst.func.castTag(.constant)) |func_inst| {
-        const fn_decl = if (func_inst.val.cast(Value.Payload.ExternFn)) |extern_fn|
-            extern_fn.decl
-        else if (func_inst.val.cast(Value.Payload.Function)) |func_val|
-            func_val.func.owner_decl
+        const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn|
+            extern_fn.data
+        else if (func_inst.val.castTag(.function)) |func_payload|
+            func_payload.data.owner_decl
         else
             unreachable;
 
@@ -513,8 +513,8 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
                 if (i > 0) {
                     try writer.writeAll(", ");
                 }
-                if (arg.cast(Inst.Constant)) |con| {
-                    try renderValue(ctx, writer, arg.ty, con.val);
+                if (arg.value()) |val| {
+                    try renderValue(ctx, writer, arg.ty, val);
                 } else {
                     const val = try ctx.resolveInst(arg);
                     try writer.print("{}", .{val});
src/codegen/wasm.zig
@@ -62,7 +62,7 @@ pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void {
     // Write instructions
     // TODO: check for and handle death of instructions
     const tv = decl.typed_value.most_recent.typed_value;
-    const mod_fn = tv.val.cast(Value.Payload.Function).?.func;
+    const mod_fn = tv.val.castTag(.function).?.data;
     for (mod_fn.analysis.success.instructions) |inst| try genInst(buf, decl, inst);
 
     // Write 'end' opcode
@@ -125,8 +125,8 @@ fn genRet(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.UnOp) !void {
 
 fn genCall(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Call) !void {
     const func_inst = inst.func.castTag(.constant).?;
-    const func_val = func_inst.val.cast(Value.Payload.Function).?;
-    const target = func_val.func.owner_decl;
+    const func = func_inst.val.castTag(.function).?.data;
+    const target = func.owner_decl;
     const target_ty = target.typed_value.most_recent.typed_value.ty;
 
     if (inst.args.len != 0) return error.TODOImplementMoreWasmCodegen;
src/link/Elf.zig
@@ -2183,7 +2183,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
             for (zir_dumps) |fn_name| {
                 if (mem.eql(u8, mem.spanZ(decl.name), fn_name)) {
                     std.debug.print("\n{}\n", .{decl.name});
-                    typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*);
+                    typed_value.val.castTag(.function).?.data.dump(module.*);
                 }
             }
         }
src/astgen.zig
@@ -1956,13 +1956,13 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo
                 32 => if (is_signed) Value.initTag(.i32_type) else Value.initTag(.u32_type),
                 64 => if (is_signed) Value.initTag(.i64_type) else Value.initTag(.u64_type),
                 else => {
-                    const int_type_payload = try scope.arena().create(Value.Payload.IntType);
-                    int_type_payload.* = .{ .signed = is_signed, .bits = bit_count };
-                    const result = try addZIRInstConst(mod, scope, src, .{
+                    return rlWrap(mod, scope, rl, try addZIRInstConst(mod, scope, src, .{
                         .ty = Type.initTag(.type),
-                        .val = Value.initPayload(&int_type_payload.base),
-                    });
-                    return rlWrap(mod, scope, rl, result);
+                        .val = try Value.Tag.int_type.create(scope.arena(), .{
+                            .signed = is_signed,
+                            .bits = bit_count,
+                        }),
+                    }));
                 },
             };
             const result = try addZIRInstConst(mod, scope, src, .{
@@ -2062,11 +2062,9 @@ fn charLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) !*zir.Inst
         },
     };
 
-    const int_payload = try scope.arena().create(Value.Payload.Int_u64);
-    int_payload.* = .{ .int = value };
     return addZIRInstConst(mod, scope, src, .{
         .ty = Type.initTag(.comptime_int),
-        .val = Value.initPayload(&int_payload.base),
+        .val = try Value.Tag.int_u64.create(scope.arena(), value),
     });
 }
 
@@ -2089,12 +2087,10 @@ fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.OneToken) Inne
         prefixed_bytes[2..];
 
     if (std.fmt.parseInt(u64, bytes, base)) |small_int| {
-        const int_payload = try arena.create(Value.Payload.Int_u64);
-        int_payload.* = .{ .int = small_int };
         const src = tree.token_locs[int_lit.token].start;
         return addZIRInstConst(mod, scope, src, .{
             .ty = Type.initTag(.comptime_int),
-            .val = Value.initPayload(&int_payload.base),
+            .val = try Value.Tag.int_u64.create(arena, small_int),
         });
     } else |err| {
         return mod.failTok(scope, int_lit.token, "TODO implement int literals that don't fit in a u64", .{});
@@ -2109,15 +2105,13 @@ fn floatLiteral(mod: *Module, scope: *Scope, float_lit: *ast.Node.OneToken) Inne
         return mod.failTok(scope, float_lit.token, "TODO hex floats", .{});
     }
 
-    const val = std.fmt.parseFloat(f128, bytes) catch |e| switch (e) {
+    const float_number = std.fmt.parseFloat(f128, bytes) catch |e| switch (e) {
         error.InvalidCharacter => unreachable, // validated by tokenizer
     };
-    const float_payload = try arena.create(Value.Payload.Float_128);
-    float_payload.* = .{ .val = val };
     const src = tree.token_locs[float_lit.token].start;
     return addZIRInstConst(mod, scope, src, .{
         .ty = Type.initTag(.comptime_float),
-        .val = Value.initPayload(&float_payload.base),
+        .val = try Value.Tag.float_128.create(arena, float_number),
     });
 }
 
src/codegen.zig
@@ -137,7 +137,7 @@ pub fn generateSymbol(
         },
         .Array => {
             // TODO populate .debug_info for the array
-            if (typed_value.val.cast(Value.Payload.Bytes)) |payload| {
+            if (typed_value.val.castTag(.bytes)) |payload| {
                 if (typed_value.ty.sentinel()) |sentinel| {
                     try code.ensureCapacity(code.items.len + payload.data.len + 1);
                     code.appendSliceAssumeCapacity(payload.data);
@@ -168,8 +168,8 @@ pub fn generateSymbol(
         },
         .Pointer => {
             // TODO populate .debug_info for the pointer
-            if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| {
-                const decl = payload.decl;
+            if (typed_value.val.castTag(.decl_ref)) |payload| {
+                const decl = payload.data;
                 if (decl.analysis != .complete) return error.AnalysisFail;
                 // TODO handle the dependency of this symbol on the decl's vaddr.
                 // If the decl changes vaddr, then this symbol needs to get regenerated.
@@ -432,7 +432,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 @panic("Attempted to compile for architecture that was disabled by build configuration");
             }
 
-            const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;
+            const module_fn = typed_value.val.castTag(.function).?.data;
 
             const fn_type = module_fn.owner_decl.typed_value.most_recent.typed_value.ty;
 
@@ -1579,9 +1579,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             }
                         }
 
-                        if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
-                            if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
-                                const func = func_val.func;
+                        if (inst.func.value()) |func_value| {
+                            if (func_value.castTag(.function)) |func_payload| {
+                                const func = func_payload.data;
 
                                 const ptr_bits = self.target.cpu.arch.ptrBitWidth();
                                 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
@@ -1607,9 +1607,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .riscv64 => {
                         if (info.args.len > 0) return self.fail(inst.base.src, "TODO implement fn args for {}", .{self.target.cpu.arch});
 
-                        if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
-                            if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
-                                const func = func_val.func;
+                        if (inst.func.value()) |func_value| {
+                            if (func_value.castTag(.function)) |func_payload| {
+                                const func = func_payload.data;
 
                                 const ptr_bits = self.target.cpu.arch.ptrBitWidth();
                                 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
@@ -1631,12 +1631,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         }
                     },
                     .spu_2 => {
-                        if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
+                        if (inst.func.value()) |func_value| {
                             if (info.args.len != 0) {
                                 return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{});
                             }
-                            if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
-                                const func = func_val.func;
+                            if (func_value.castTag(.function)) |func_payload| {
+                                const func = func_payload.data;
                                 const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
                                     const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
                                     break :blk @intCast(u16, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * 2);
@@ -1705,9 +1705,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             }
                         }
 
-                        if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
-                            if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
-                                const func = func_val.func;
+                        if (inst.func.value()) |func_value| {
+                            if (func_value.castTag(.function)) |func_payload| {
+                                const func = func_payload.data;
                                 const ptr_bits = self.target.cpu.arch.ptrBitWidth();
                                 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
                                 const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
@@ -1766,9 +1766,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             }
                         }
 
-                        if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
-                            if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
-                                const func = func_val.func;
+                        if (inst.func.value()) |func_value| {
+                            if (func_value.castTag(.function)) |func_payload| {
+                                const func = func_payload.data;
                                 const ptr_bits = self.target.cpu.arch.ptrBitWidth();
                                 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
                                 const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
@@ -1825,9 +1825,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     }
                 }
 
-                if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
-                    if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
-                        const func = func_val.func;
+                if (inst.func.value()) |func_value| {
+                    if (func_value.castTag(.function)) |func_payload| {
+                        const func = func_payload.data;
                         const text_segment = &macho_file.load_commands.items[macho_file.text_segment_cmd_index.?].Segment;
                         const got = &text_segment.sections.items[macho_file.got_section_index.?];
                         const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64);
@@ -3223,20 +3223,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             const ptr_bytes: u64 = @divExact(ptr_bits, 8);
             switch (typed_value.ty.zigTypeTag()) {
                 .Pointer => {
-                    if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| {
+                    if (typed_value.val.castTag(.decl_ref)) |payload| {
                         if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-                            const decl = payload.decl;
+                            const decl = payload.data;
                             const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
                             const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes;
                             return MCValue{ .memory = got_addr };
                         } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
-                            const decl = payload.decl;
+                            const decl = payload.data;
                             const text_segment = &macho_file.load_commands.items[macho_file.text_segment_cmd_index.?].Segment;
                             const got = &text_segment.sections.items[macho_file.got_section_index.?];
                             const got_addr = got.addr + decl.link.macho.offset_table_index * ptr_bytes;
                             return MCValue{ .memory = got_addr };
                         } else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
-                            const decl = payload.decl;
+                            const decl = payload.data;
                             const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes;
                             return MCValue{ .memory = got_addr };
                         } else {
src/Compilation.zig
@@ -1457,11 +1457,12 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
 
             .complete, .codegen_failure_retryable => {
                 const module = self.bin_file.options.module.?;
-                if (decl.typed_value.most_recent.typed_value.val.cast(Value.Payload.Function)) |payload| {
-                    switch (payload.func.analysis) {
-                        .queued => module.analyzeFnBody(decl, payload.func) catch |err| switch (err) {
+                if (decl.typed_value.most_recent.typed_value.val.castTag(.function)) |payload| {
+                    const func = payload.data;
+                    switch (func.analysis) {
+                        .queued => module.analyzeFnBody(decl, func) catch |err| switch (err) {
                             error.AnalysisFail => {
-                                assert(payload.func.analysis != .in_progress);
+                                assert(func.analysis != .in_progress);
                                 continue;
                             },
                             error.OutOfMemory => return error.OutOfMemory,
@@ -1475,7 +1476,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
                     var decl_arena = decl.typed_value.most_recent.arena.?.promote(module.gpa);
                     defer decl.typed_value.most_recent.arena.?.* = decl_arena.state;
                     log.debug("analyze liveness of {}\n", .{decl.name});
-                    try liveness.analyze(module.gpa, &decl_arena.allocator, payload.func.analysis.success);
+                    try liveness.analyze(module.gpa, &decl_arena.allocator, func.analysis.success);
                 }
 
                 assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits());
src/llvm_backend.zig
@@ -280,7 +280,7 @@ pub const LLVMIRModule = struct {
     fn gen(self: *LLVMIRModule, module: *Module, typed_value: TypedValue, src: usize) !void {
         switch (typed_value.ty.zigTypeTag()) {
             .Fn => {
-                const func = typed_value.val.cast(Value.Payload.Function).?.func;
+                const func = typed_value.val.castTag(.function).?.data;
 
                 const llvm_func = try self.resolveLLVMFunction(func);
 
@@ -314,9 +314,9 @@ pub const LLVMIRModule = struct {
     }
 
     fn genCall(self: *LLVMIRModule, inst: *Inst.Call) !void {
-        if (inst.func.cast(Inst.Constant)) |func_inst| {
-            if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
-                const func = func_val.func;
+        if (inst.func.value()) |func_value| {
+            if (func_value.castTag(.function)) |func_payload| {
+                const func = func_payload.data;
                 const zig_fn_type = func.owner_decl.typed_value.most_recent.typed_value.ty;
                 const llvm_fn = try self.resolveLLVMFunction(func);
 
src/Module.zig
@@ -1092,16 +1092,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
 
                     tvm.deinit(self.gpa);
                 }
-                const value_payload = try decl_arena.allocator.create(Value.Payload.ExternFn);
-                value_payload.* = .{ .decl = decl };
+                const fn_val = try Value.Tag.extern_fn.create(&decl_arena.allocator, decl);
 
                 decl_arena_state.* = decl_arena.state;
                 decl.typed_value = .{
                     .most_recent = .{
-                        .typed_value = .{
-                            .ty = fn_type,
-                            .val = Value.initPayload(&value_payload.base),
-                        },
+                        .typed_value = .{ .ty = fn_type, .val = fn_val },
                         .arena = decl_arena_state,
                     },
                 };
@@ -1187,7 +1183,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
                 .analysis = .{ .queued = fn_zir },
                 .owner_decl = decl,
             };
-            fn_payload.* = .{ .func = new_func };
+            fn_payload.* = .{
+                .base = .{ .tag = .function },
+                .data = new_func,
+            };
 
             var prev_type_has_bits = false;
             var type_changed = true;
@@ -1375,7 +1374,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
             }
 
             const new_variable = try decl_arena.allocator.create(Var);
-            const var_payload = try decl_arena.allocator.create(Value.Payload.Variable);
             new_variable.* = .{
                 .owner_decl = decl,
                 .init = var_info.val orelse undefined,
@@ -1383,14 +1381,14 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
                 .is_mutable = is_mutable,
                 .is_threadlocal = is_threadlocal,
             };
-            var_payload.* = .{ .variable = new_variable };
+            const var_val = try Value.Tag.variable.create(&decl_arena.allocator, new_variable);
 
             decl_arena_state.* = decl_arena.state;
             decl.typed_value = .{
                 .most_recent = .{
                     .typed_value = .{
                         .ty = var_info.ty,
-                        .val = Value.initPayload(&var_payload.base),
+                        .val = var_val,
                     },
                     .arena = decl_arena_state,
                 },
@@ -2232,52 +2230,43 @@ pub fn constBool(self: *Module, scope: *Scope, src: usize, v: bool) !*Inst {
 }
 
 pub fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64) !*Inst {
-    const int_payload = try scope.arena().create(Value.Payload.Int_u64);
-    int_payload.* = .{ .int = int };
-
     return self.constInst(scope, src, .{
         .ty = ty,
-        .val = Value.initPayload(&int_payload.base),
+        .val = try Value.Tag.int_u64.create(scope.arena(), int),
     });
 }
 
 pub fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) !*Inst {
-    const int_payload = try scope.arena().create(Value.Payload.Int_i64);
-    int_payload.* = .{ .int = int };
-
     return self.constInst(scope, src, .{
         .ty = ty,
-        .val = Value.initPayload(&int_payload.base),
+        .val = try Value.Tag.int_i64.create(scope.arena(), int),
     });
 }
 
 pub fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigIntConst) !*Inst {
-    const val_payload = if (big_int.positive) blk: {
+    if (big_int.positive) {
         if (big_int.to(u64)) |x| {
             return self.constIntUnsigned(scope, src, ty, x);
         } else |err| switch (err) {
             error.NegativeIntoUnsigned => unreachable,
             error.TargetTooSmall => {}, // handled below
         }
-        const big_int_payload = try scope.arena().create(Value.Payload.IntBigPositive);
-        big_int_payload.* = .{ .limbs = big_int.limbs };
-        break :blk &big_int_payload.base;
-    } else blk: {
+        return self.constInst(scope, src, .{
+            .ty = ty,
+            .val = try Value.Tag.int_big_positive.create(scope.arena(), big_int.limbs),
+        });
+    } else {
         if (big_int.to(i64)) |x| {
             return self.constIntSigned(scope, src, ty, x);
         } else |err| switch (err) {
             error.NegativeIntoUnsigned => unreachable,
             error.TargetTooSmall => {}, // handled below
         }
-        const big_int_payload = try scope.arena().create(Value.Payload.IntBigNegative);
-        big_int_payload.* = .{ .limbs = big_int.limbs };
-        break :blk &big_int_payload.base;
-    };
-
-    return self.constInst(scope, src, .{
-        .ty = ty,
-        .val = Value.initPayload(val_payload),
-    });
+        return self.constInst(scope, src, .{
+            .ty = ty,
+            .val = try Value.Tag.int_big_negative.create(scope.arena(), big_int.limbs),
+        });
+    }
 }
 
 pub fn createAnonymousDecl(
@@ -2346,26 +2335,20 @@ pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) Inn
     if (decl_tv.val.tag() == .variable) {
         return self.analyzeVarRef(scope, src, decl_tv);
     }
-    const ty = try self.simplePtrType(scope, src, decl_tv.ty, false, .One);
-    const val_payload = try scope.arena().create(Value.Payload.DeclRef);
-    val_payload.* = .{ .decl = decl };
-
     return self.constInst(scope, src, .{
-        .ty = ty,
-        .val = Value.initPayload(&val_payload.base),
+        .ty = try self.simplePtrType(scope, src, decl_tv.ty, false, .One),
+        .val = try Value.Tag.decl_ref.create(scope.arena(), decl),
     });
 }
 
 fn analyzeVarRef(self: *Module, scope: *Scope, src: usize, tv: TypedValue) InnerError!*Inst {
-    const variable = tv.val.cast(Value.Payload.Variable).?.variable;
+    const variable = tv.val.castTag(.variable).?.data;
 
     const ty = try self.simplePtrType(scope, src, tv.ty, variable.is_mutable, .One);
     if (!variable.is_mutable and !variable.is_extern) {
-        const val_payload = try scope.arena().create(Value.Payload.RefVal);
-        val_payload.* = .{ .val = variable.init };
         return self.constInst(scope, src, .{
             .ty = ty,
-            .val = Value.initPayload(&val_payload.base),
+            .val = try Value.Tag.ref_val.create(scope.arena(), variable.init),
         });
     }
 
@@ -3107,17 +3090,11 @@ pub fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
     result_bigint.add(lhs_bigint, rhs_bigint);
     const result_limbs = result_bigint.limbs[0..result_bigint.len];
 
-    const val_payload = if (result_bigint.positive) blk: {
-        const val_payload = try allocator.create(Value.Payload.IntBigPositive);
-        val_payload.* = .{ .limbs = result_limbs };
-        break :blk &val_payload.base;
-    } else blk: {
-        const val_payload = try allocator.create(Value.Payload.IntBigNegative);
-        val_payload.* = .{ .limbs = result_limbs };
-        break :blk &val_payload.base;
-    };
-
-    return Value.initPayload(val_payload);
+    if (result_bigint.positive) {
+        return Value.Tag.int_big_positive.create(allocator, result_limbs);
+    } else {
+        return Value.Tag.int_big_negative.create(allocator, result_limbs);
+    }
 }
 
 pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
@@ -3135,85 +3112,81 @@ pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
     result_bigint.sub(lhs_bigint, rhs_bigint);
     const result_limbs = result_bigint.limbs[0..result_bigint.len];
 
-    const val_payload = if (result_bigint.positive) blk: {
-        const val_payload = try allocator.create(Value.Payload.IntBigPositive);
-        val_payload.* = .{ .limbs = result_limbs };
-        break :blk &val_payload.base;
-    } else blk: {
-        const val_payload = try allocator.create(Value.Payload.IntBigNegative);
-        val_payload.* = .{ .limbs = result_limbs };
-        break :blk &val_payload.base;
-    };
-
-    return Value.initPayload(val_payload);
+    if (result_bigint.positive) {
+        return Value.Tag.int_big_positive.create(allocator, result_limbs);
+    } else {
+        return Value.Tag.int_big_negative.create(allocator, result_limbs);
+    }
 }
 
-pub fn floatAdd(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: Value, rhs: Value) !Value {
-    var bit_count = switch (float_type.tag()) {
-        .comptime_float => 128,
-        else => float_type.floatBits(self.getTarget()),
-    };
-
-    const allocator = scope.arena();
-    const val_payload = switch (bit_count) {
-        16 => {
-            return self.fail(scope, src, "TODO Implement addition for soft floats", .{});
+pub fn floatAdd(
+    self: *Module,
+    scope: *Scope,
+    float_type: Type,
+    src: usize,
+    lhs: Value,
+    rhs: Value,
+) !Value {
+    const arena = scope.arena();
+    switch (float_type.tag()) {
+        .f16 => {
+            @panic("TODO add __trunctfhf2 to compiler-rt");
+            //const lhs_val = lhs.toFloat(f16);
+            //const rhs_val = rhs.toFloat(f16);
+            //return Value.Tag.float_16.create(arena, lhs_val + rhs_val);
         },
-        32 => blk: {
+        .f32 => {
             const lhs_val = lhs.toFloat(f32);
             const rhs_val = rhs.toFloat(f32);
-            const val_payload = try allocator.create(Value.Payload.Float_32);
-            val_payload.* = .{ .val = lhs_val + rhs_val };
-            break :blk &val_payload.base;
+            return Value.Tag.float_32.create(arena, lhs_val + rhs_val);
         },
-        64 => blk: {
+        .f64 => {
             const lhs_val = lhs.toFloat(f64);
             const rhs_val = rhs.toFloat(f64);
-            const val_payload = try allocator.create(Value.Payload.Float_64);
-            val_payload.* = .{ .val = lhs_val + rhs_val };
-            break :blk &val_payload.base;
+            return Value.Tag.float_64.create(arena, lhs_val + rhs_val);
         },
-        128 => {
-            return self.fail(scope, src, "TODO Implement addition for big floats", .{});
+        .f128, .comptime_float, .c_longdouble => {
+            const lhs_val = lhs.toFloat(f128);
+            const rhs_val = rhs.toFloat(f128);
+            return Value.Tag.float_128.create(arena, lhs_val + rhs_val);
         },
         else => unreachable,
-    };
-
-    return Value.initPayload(val_payload);
+    }
 }
 
-pub fn floatSub(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: Value, rhs: Value) !Value {
-    var bit_count = switch (float_type.tag()) {
-        .comptime_float => 128,
-        else => float_type.floatBits(self.getTarget()),
-    };
-
-    const allocator = scope.arena();
-    const val_payload = switch (bit_count) {
-        16 => {
-            return self.fail(scope, src, "TODO Implement substraction for soft floats", .{});
+pub fn floatSub(
+    self: *Module,
+    scope: *Scope,
+    float_type: Type,
+    src: usize,
+    lhs: Value,
+    rhs: Value,
+) !Value {
+    const arena = scope.arena();
+    switch (float_type.tag()) {
+        .f16 => {
+            @panic("TODO add __trunctfhf2 to compiler-rt");
+            //const lhs_val = lhs.toFloat(f16);
+            //const rhs_val = rhs.toFloat(f16);
+            //return Value.Tag.float_16.create(arena, lhs_val - rhs_val);
         },
-        32 => blk: {
+        .f32 => {
             const lhs_val = lhs.toFloat(f32);
             const rhs_val = rhs.toFloat(f32);
-            const val_payload = try allocator.create(Value.Payload.Float_32);
-            val_payload.* = .{ .val = lhs_val - rhs_val };
-            break :blk &val_payload.base;
+            return Value.Tag.float_32.create(arena, lhs_val - rhs_val);
         },
-        64 => blk: {
+        .f64 => {
             const lhs_val = lhs.toFloat(f64);
             const rhs_val = rhs.toFloat(f64);
-            const val_payload = try allocator.create(Value.Payload.Float_64);
-            val_payload.* = .{ .val = lhs_val - rhs_val };
-            break :blk &val_payload.base;
+            return Value.Tag.float_64.create(arena, lhs_val - rhs_val);
         },
-        128 => {
-            return self.fail(scope, src, "TODO Implement substraction for big floats", .{});
+        .f128, .comptime_float, .c_longdouble => {
+            const lhs_val = lhs.toFloat(f128);
+            const rhs_val = rhs.toFloat(f128);
+            return Value.Tag.float_128.create(arena, lhs_val - rhs_val);
         },
         else => unreachable,
-    };
-
-    return Value.initPayload(val_payload);
+    }
 }
 
 pub fn simplePtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type, mutable: bool, size: std.builtin.TypeInfo.Pointer.Size) Allocator.Error!Type {
src/type.zig
@@ -733,11 +733,7 @@ pub const Type = extern union {
             .single_const_pointer_to_comptime_int => return Value.initTag(.single_const_pointer_to_comptime_int_type),
             .const_slice_u8 => return Value.initTag(.const_slice_u8_type),
             .enum_literal => return Value.initTag(.enum_literal_type),
-            else => {
-                const ty_payload = try allocator.create(Value.Payload.Ty);
-                ty_payload.* = .{ .ty = self };
-                return Value.initPayload(&ty_payload.base);
-            },
+            else => return Value.Tag.ty.create(allocator, self),
         }
     }
 
@@ -2951,11 +2947,8 @@ pub const Type = extern union {
         }
 
         if ((info.bits - 1) <= std.math.maxInt(u6)) {
-            const payload = try arena.allocator.create(Value.Payload.Int_i64);
-            payload.* = .{
-                .int = -(@as(i64, 1) << @truncate(u6, info.bits - 1)),
-            };
-            return Value.initPayload(&payload.base);
+            const n: i64 = -(@as(i64, 1) << @truncate(u6, info.bits - 1));
+            return Value.Tag.int_i64.create(&arena.allocator, n);
         }
 
         var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1);
@@ -2964,13 +2957,9 @@ pub const Type = extern union {
 
         const res_const = res.toConst();
         if (res_const.positive) {
-            const val_payload = try arena.allocator.create(Value.Payload.IntBigPositive);
-            val_payload.* = .{ .limbs = res_const.limbs };
-            return Value.initPayload(&val_payload.base);
+            return Value.Tag.int_big_positive.create(&arena.allocator, res_const.limbs);
         } else {
-            const val_payload = try arena.allocator.create(Value.Payload.IntBigNegative);
-            val_payload.* = .{ .limbs = res_const.limbs };
-            return Value.initPayload(&val_payload.base);
+            return Value.Tag.int_big_negative.create(&arena.allocator, res_const.limbs);
         }
     }
 
@@ -2980,17 +2969,11 @@ pub const Type = extern union {
         const info = self.intInfo(target);
 
         if (info.signedness == .signed and (info.bits - 1) <= std.math.maxInt(u6)) {
-            const payload = try arena.allocator.create(Value.Payload.Int_i64);
-            payload.* = .{
-                .int = (@as(i64, 1) << @truncate(u6, info.bits - 1)) - 1,
-            };
-            return Value.initPayload(&payload.base);
+            const n: i64 = (@as(i64, 1) << @truncate(u6, info.bits - 1)) - 1;
+            return Value.Tag.int_i64.create(&arena.allocator, n);
         } else if (info.signedness == .signed and info.bits <= std.math.maxInt(u6)) {
-            const payload = try arena.allocator.create(Value.Payload.Int_u64);
-            payload.* = .{
-                .int = (@as(u64, 1) << @truncate(u6, info.bits)) - 1,
-            };
-            return Value.initPayload(&payload.base);
+            const n: u64 = (@as(u64, 1) << @truncate(u6, info.bits)) - 1;
+            return Value.Tag.int_u64.create(&arena.allocator, n);
         }
 
         var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1);
@@ -3003,13 +2986,9 @@ pub const Type = extern union {
 
         const res_const = res.toConst();
         if (res_const.positive) {
-            const val_payload = try arena.allocator.create(Value.Payload.IntBigPositive);
-            val_payload.* = .{ .limbs = res_const.limbs };
-            return Value.initPayload(&val_payload.base);
+            return Value.Tag.int_big_positive.create(&arena.allocator, res_const.limbs);
         } else {
-            const val_payload = try arena.allocator.create(Value.Payload.IntBigNegative);
-            val_payload.* = .{ .limbs = res_const.limbs };
-            return Value.initPayload(&val_payload.base);
+            return Value.Tag.int_big_negative.create(&arena.allocator, res_const.limbs);
         }
     }
 
src/value.zig
@@ -84,11 +84,16 @@ pub const Value = extern union {
         function,
         extern_fn,
         variable,
+        /// Represents a pointer to another immutable value.
         ref_val,
+        /// Represents a pointer to a decl, not the value of the decl.
         decl_ref,
         elem_ptr,
+        /// A slice of u8 whose memory is managed externally.
         bytes,
-        repeated, // the value is a value repeated some number of times
+        /// This value is repeated some number of times. The amount of times to repeat
+        /// is stored externally.
+        repeated,
         float_16,
         float_32,
         float_64,
@@ -99,6 +104,106 @@ pub const Value = extern union {
 
         pub const last_no_payload_tag = Tag.bool_false;
         pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
+
+        pub fn Type(comptime t: Tag) type {
+            return switch (t) {
+                .u8_type,
+                .i8_type,
+                .u16_type,
+                .i16_type,
+                .u32_type,
+                .i32_type,
+                .u64_type,
+                .i64_type,
+                .usize_type,
+                .isize_type,
+                .c_short_type,
+                .c_ushort_type,
+                .c_int_type,
+                .c_uint_type,
+                .c_long_type,
+                .c_ulong_type,
+                .c_longlong_type,
+                .c_ulonglong_type,
+                .c_longdouble_type,
+                .f16_type,
+                .f32_type,
+                .f64_type,
+                .f128_type,
+                .c_void_type,
+                .bool_type,
+                .void_type,
+                .type_type,
+                .anyerror_type,
+                .comptime_int_type,
+                .comptime_float_type,
+                .noreturn_type,
+                .null_type,
+                .undefined_type,
+                .fn_noreturn_no_args_type,
+                .fn_void_no_args_type,
+                .fn_naked_noreturn_no_args_type,
+                .fn_ccc_void_no_args_type,
+                .single_const_pointer_to_comptime_int_type,
+                .const_slice_u8_type,
+                .enum_literal_type,
+                .anyframe_type,
+                .undef,
+                .zero,
+                .one,
+                .void_value,
+                .unreachable_value,
+                .empty_struct_value,
+                .empty_array,
+                .null_value,
+                .bool_true,
+                .bool_false,
+                => @compileError("Value Tag " ++ @tagName(t) ++ " has no payload"),
+
+                .int_big_positive,
+                .int_big_negative,
+                => Payload.BigInt,
+
+                .extern_fn,
+                .decl_ref,
+                => Payload.Decl,
+
+                .ref_val,
+                .repeated,
+                => Payload.SubValue,
+
+                .bytes,
+                .enum_literal,
+                => Payload.Bytes,
+
+                .ty => Payload.Ty,
+                .int_type => Payload.IntType,
+                .int_u64 => Payload.U64,
+                .int_i64 => Payload.I64,
+                .function => Payload.Function,
+                .variable => Payload.Variable,
+                .elem_ptr => Payload.ElemPtr,
+                .float_16 => Payload.Float_16,
+                .float_32 => Payload.Float_32,
+                .float_64 => Payload.Float_64,
+                .float_128 => Payload.Float_128,
+                .error_set => Payload.ErrorSet,
+                .@"error" => Payload.Error,
+            };
+        }
+
+        pub fn create(comptime t: Tag, ally: *Allocator, data: Data(t)) error{OutOfMemory}!Value {
+            const ptr = try ally.create(t.Type());
+            ptr.* = .{
+                .base = .{ .tag = t },
+                .data = data,
+            };
+            return Value{ .ptr_otherwise = &ptr.base };
+        }
+
+        pub fn Data(comptime t: Tag) type {
+            return std.meta.fieldInfo(t.Type(), "data").field_type;
+        }
     };
 
     pub fn initTag(small_tag: Tag) Value {
@@ -119,15 +224,36 @@ pub const Value = extern union {
         }
     }
 
+    /// Prefer `castTag` to this.
     pub fn cast(self: Value, comptime T: type) ?*T {
-        if (self.tag_if_small_enough < Tag.no_payload_count)
+        if (@hasField(T, "base_tag")) {
+            return base.castTag(T.base_tag);
+        }
+        if (self.tag_if_small_enough < Tag.no_payload_count) {
             return null;
+        }
+        inline for (@typeInfo(Tag).Enum.fields) |field| {
+            if (field.value < Tag.no_payload_count)
+                continue;
+            const t = @intToEnum(Tag, field.value);
+            if (self.ptr_otherwise.tag == t) {
+                if (T == t.Type()) {
+                    return @fieldParentPtr(T, "base", self.ptr_otherwise);
+                }
+                return null;
+            }
+        }
+        unreachable;
+    }
 
-        const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag;
-        if (self.ptr_otherwise.tag != expected_tag)
+    pub fn castTag(self: Value, comptime t: Tag) ?*t.Type() {
+        if (self.tag_if_small_enough < Tag.no_payload_count)
             return null;
 
-        return @fieldParentPtr(T, "base", self.ptr_otherwise);
+        if (self.ptr_otherwise.tag == t)
+            return @fieldParentPtr(t.Type(), "base", self.ptr_otherwise);
+
+        return null;
     }
 
     pub fn copy(self: Value, allocator: *Allocator) error{OutOfMemory}!Value {
@@ -188,17 +314,17 @@ pub const Value = extern union {
             => unreachable,
 
             .ty => {
-                const payload = @fieldParentPtr(Payload.Ty, "base", self.ptr_otherwise);
+                const payload = self.castTag(.ty).?;
                 const new_payload = try allocator.create(Payload.Ty);
                 new_payload.* = .{
                     .base = payload.base,
-                    .ty = try payload.ty.copy(allocator),
+                    .data = try payload.data.copy(allocator),
                 };
                 return Value{ .ptr_otherwise = &new_payload.base };
             },
             .int_type => return self.copyPayloadShallow(allocator, Payload.IntType),
-            .int_u64 => return self.copyPayloadShallow(allocator, Payload.Int_u64),
-            .int_i64 => return self.copyPayloadShallow(allocator, Payload.Int_i64),
+            .int_u64 => return self.copyPayloadShallow(allocator, Payload.U64),
+            .int_i64 => return self.copyPayloadShallow(allocator, Payload.I64),
             .int_big_positive => {
                 @panic("TODO implement copying of big ints");
             },
@@ -206,35 +332,37 @@ pub const Value = extern union {
                 @panic("TODO implement copying of big ints");
             },
             .function => return self.copyPayloadShallow(allocator, Payload.Function),
-            .extern_fn => return self.copyPayloadShallow(allocator, Payload.ExternFn),
+            .extern_fn => return self.copyPayloadShallow(allocator, Payload.Decl),
             .variable => return self.copyPayloadShallow(allocator, Payload.Variable),
             .ref_val => {
-                const payload = @fieldParentPtr(Payload.RefVal, "base", self.ptr_otherwise);
-                const new_payload = try allocator.create(Payload.RefVal);
+                const payload = self.castTag(.ref_val).?;
+                const new_payload = try allocator.create(Payload.SubValue);
                 new_payload.* = .{
                     .base = payload.base,
-                    .val = try payload.val.copy(allocator),
+                    .data = try payload.data.copy(allocator),
                 };
                 return Value{ .ptr_otherwise = &new_payload.base };
             },
-            .decl_ref => return self.copyPayloadShallow(allocator, Payload.DeclRef),
+            .decl_ref => return self.copyPayloadShallow(allocator, Payload.Decl),
             .elem_ptr => {
-                const payload = @fieldParentPtr(Payload.ElemPtr, "base", self.ptr_otherwise);
+                const payload = self.castTag(.elem_ptr).?;
                 const new_payload = try allocator.create(Payload.ElemPtr);
                 new_payload.* = .{
                     .base = payload.base,
-                    .array_ptr = try payload.array_ptr.copy(allocator),
-                    .index = payload.index,
+                    .data = .{
+                        .array_ptr = try payload.data.array_ptr.copy(allocator),
+                        .index = payload.data.index,
+                    },
                 };
                 return Value{ .ptr_otherwise = &new_payload.base };
             },
             .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes),
             .repeated => {
-                const payload = @fieldParentPtr(Payload.Repeated, "base", self.ptr_otherwise);
-                const new_payload = try allocator.create(Payload.Repeated);
+                const payload = self.castTag(.repeated).?;
+                const new_payload = try allocator.create(Payload.SubValue);
                 new_payload.* = .{
                     .base = payload.base,
-                    .val = try payload.val.copy(allocator),
+                    .data = try payload.data.copy(allocator),
                 };
                 return Value{ .ptr_otherwise = &new_payload.base };
             },
@@ -243,7 +371,7 @@ pub const Value = extern union {
             .float_64 => return self.copyPayloadShallow(allocator, Payload.Float_64),
             .float_128 => return self.copyPayloadShallow(allocator, Payload.Float_128),
             .enum_literal => {
-                const payload = @fieldParentPtr(Payload.Bytes, "base", self.ptr_otherwise);
+                const payload = self.castTag(.enum_literal).?;
                 const new_payload = try allocator.create(Payload.Bytes);
                 new_payload.* = .{
                     .base = payload.base,
@@ -259,7 +387,7 @@ pub const Value = extern union {
     }
 
     fn copyPayloadShallow(self: Value, allocator: *Allocator, comptime T: type) error{OutOfMemory}!Value {
-        const payload = @fieldParentPtr(T, "base", self.ptr_otherwise);
+        const payload = self.cast(T).?;
         const new_payload = try allocator.create(T);
         new_payload.* = payload.*;
         return Value{ .ptr_otherwise = &new_payload.base };
@@ -326,45 +454,45 @@ pub const Value = extern union {
             .unreachable_value => return out_stream.writeAll("unreachable"),
             .bool_true => return out_stream.writeAll("true"),
             .bool_false => return out_stream.writeAll("false"),
-            .ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream),
+            .ty => return val.castTag(.ty).?.data.format("", options, out_stream),
             .int_type => {
-                const int_type = val.cast(Payload.IntType).?;
+                const int_type = val.castTag(.int_type).?.data;
                 return out_stream.print("{}{}", .{
                     if (int_type.signed) "s" else "u",
                     int_type.bits,
                 });
             },
-            .int_u64 => return std.fmt.formatIntValue(val.cast(Payload.Int_u64).?.int, "", options, out_stream),
-            .int_i64 => return std.fmt.formatIntValue(val.cast(Payload.Int_i64).?.int, "", options, out_stream),
-            .int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}),
-            .int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}),
+            .int_u64 => return std.fmt.formatIntValue(val.castTag(.int_u64).?.data, "", options, out_stream),
+            .int_i64 => return std.fmt.formatIntValue(val.castTag(.int_i64).?.data, "", options, out_stream),
+            .int_big_positive => return out_stream.print("{}", .{val.castTag(.int_big_positive).?.asBigInt()}),
+            .int_big_negative => return out_stream.print("{}", .{val.castTag(.int_big_negative).?.asBigInt()}),
             .function => return out_stream.writeAll("(function)"),
             .extern_fn => return out_stream.writeAll("(extern function)"),
             .variable => return out_stream.writeAll("(variable)"),
             .ref_val => {
-                const ref_val = val.cast(Payload.RefVal).?;
+                const ref_val = val.castTag(.ref_val).?.data;
                 try out_stream.writeAll("&const ");
-                val = ref_val.val;
+                val = ref_val;
             },
             .decl_ref => return out_stream.writeAll("(decl ref)"),
             .elem_ptr => {
-                const elem_ptr = val.cast(Payload.ElemPtr).?;
+                const elem_ptr = val.castTag(.elem_ptr).?.data;
                 try out_stream.print("&[{}] ", .{elem_ptr.index});
                 val = elem_ptr.array_ptr;
             },
             .empty_array => return out_stream.writeAll(".{}"),
-            .enum_literal => return out_stream.print(".{z}", .{self.cast(Payload.Bytes).?.data}),
-            .bytes => return out_stream.print("\"{Z}\"", .{self.cast(Payload.Bytes).?.data}),
+            .enum_literal => return out_stream.print(".{z}", .{self.castTag(.enum_literal).?.data}),
+            .bytes => return out_stream.print("\"{Z}\"", .{self.castTag(.bytes).?.data}),
             .repeated => {
                 try out_stream.writeAll("(repeated) ");
-                val = val.cast(Payload.Repeated).?.val;
+                val = val.castTag(.repeated).?.data;
             },
-            .float_16 => return out_stream.print("{}", .{val.cast(Payload.Float_16).?.val}),
-            .float_32 => return out_stream.print("{}", .{val.cast(Payload.Float_32).?.val}),
-            .float_64 => return out_stream.print("{}", .{val.cast(Payload.Float_64).?.val}),
-            .float_128 => return out_stream.print("{}", .{val.cast(Payload.Float_128).?.val}),
+            .float_16 => return out_stream.print("{}", .{val.castTag(.float_16).?.data}),
+            .float_32 => return out_stream.print("{}", .{val.castTag(.float_32).?.data}),
+            .float_64 => return out_stream.print("{}", .{val.castTag(.float_64).?.data}),
+            .float_128 => return out_stream.print("{}", .{val.castTag(.float_128).?.data}),
             .error_set => {
-                const error_set = val.cast(Payload.ErrorSet).?;
+                const error_set = val.castTag(.error_set).?.data;
                 try out_stream.writeAll("error{");
                 var it = error_set.fields.iterator();
                 while (it.next()) |entry| {
@@ -372,21 +500,24 @@ pub const Value = extern union {
                 }
                 return out_stream.writeAll("}");
             },
-            .@"error" => return out_stream.print("error.{}", .{val.cast(Payload.Error).?.name}),
+            .@"error" => return out_stream.print("error.{}", .{val.castTag(.@"error").?.data.name}),
         };
     }
 
     /// Asserts that the value is representable as an array of bytes.
     /// Copies the value into a freshly allocated slice of memory, which is owned by the caller.
     pub fn toAllocatedBytes(self: Value, allocator: *Allocator) ![]u8 {
-        if (self.cast(Payload.Bytes)) |bytes| {
-            return std.mem.dupe(allocator, u8, bytes.data);
+        if (self.castTag(.bytes)) |payload| {
+            return std.mem.dupe(allocator, u8, payload.data);
         }
-        if (self.cast(Payload.Repeated)) |repeated| {
+        if (self.castTag(.enum_literal)) |payload| {
+            return std.mem.dupe(allocator, u8, payload.data);
+        }
+        if (self.castTag(.repeated)) |payload| {
             @panic("TODO implement toAllocatedBytes for this Value tag");
         }
-        if (self.cast(Payload.DeclRef)) |declref| {
-            const val = try declref.decl.value();
+        if (self.castTag(.decl_ref)) |payload| {
+            const val = try payload.data.value();
             return val.toAllocatedBytes(allocator);
         }
         unreachable;
@@ -395,7 +526,7 @@ pub const Value = extern union {
     /// Asserts that the value is representable as a type.
     pub fn toType(self: Value, allocator: *Allocator) !Type {
         return switch (self.tag()) {
-            .ty => self.cast(Payload.Ty).?.ty,
+            .ty => self.castTag(.ty).?.data,
             .u8_type => Type.initTag(.u8),
             .i8_type => Type.initTag(.i8),
             .u16_type => Type.initTag(.u16),
@@ -439,7 +570,7 @@ pub const Value = extern union {
             .anyframe_type => Type.initTag(.@"anyframe"),
 
             .int_type => {
-                const payload = self.cast(Payload.IntType).?;
+                const payload = self.castTag(.int_type).?.data;
                 const new = try allocator.create(Type.Payload.Bits);
                 new.* = .{
                     .base = .{
@@ -450,7 +581,7 @@ pub const Value = extern union {
                 return Type.initPayload(&new.base);
             },
             .error_set => {
-                const payload = self.cast(Payload.ErrorSet).?;
+                const payload = self.castTag(.error_set).?.data;
                 return Type.Tag.error_set.create(allocator, payload.decl);
             },
 
@@ -564,10 +695,10 @@ pub const Value = extern union {
             .bool_true,
             => return BigIntMutable.init(&space.limbs, 1).toConst(),
 
-            .int_u64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_u64).?.int).toConst(),
-            .int_i64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_i64).?.int).toConst(),
-            .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt(),
-            .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt(),
+            .int_u64 => return BigIntMutable.init(&space.limbs, self.castTag(.int_u64).?.data).toConst(),
+            .int_i64 => return BigIntMutable.init(&space.limbs, self.castTag(.int_i64).?.data).toConst(),
+            .int_big_positive => return self.castTag(.int_big_positive).?.asBigInt(),
+            .int_big_negative => return self.castTag(.int_big_negative).?.asBigInt(),
         }
     }
 
@@ -649,10 +780,10 @@ pub const Value = extern union {
             .bool_true,
             => return 1,
 
-            .int_u64 => return self.cast(Payload.Int_u64).?.int,
-            .int_i64 => return @intCast(u64, self.cast(Payload.Int_i64).?.int),
-            .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().to(u64) catch unreachable,
-            .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt().to(u64) catch unreachable,
+            .int_u64 => return self.castTag(.int_u64).?.data,
+            .int_i64 => return @intCast(u64, self.castTag(.int_i64).?.data),
+            .int_big_positive => return self.castTag(.int_big_positive).?.asBigInt().to(u64) catch unreachable,
+            .int_big_negative => return self.castTag(.int_big_negative).?.asBigInt().to(u64) catch unreachable,
         }
     }
 
@@ -734,10 +865,10 @@ pub const Value = extern union {
             .bool_true,
             => return 1,
 
-            .int_u64 => return @intCast(i64, self.cast(Payload.Int_u64).?.int),
-            .int_i64 => return self.cast(Payload.Int_i64).?.int,
-            .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().to(i64) catch unreachable,
-            .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt().to(i64) catch unreachable,
+            .int_u64 => return @intCast(i64, self.castTag(.int_u64).?.data),
+            .int_i64 => return self.castTag(.int_i64).?.data,
+            .int_big_positive => return self.castTag(.int_big_positive).?.asBigInt().to(i64) catch unreachable,
+            .int_big_negative => return self.castTag(.int_big_negative).?.asBigInt().to(i64) catch unreachable,
         }
     }
 
@@ -753,14 +884,14 @@ pub const Value = extern union {
     pub fn toFloat(self: Value, comptime T: type) T {
         return switch (self.tag()) {
             .float_16 => @panic("TODO soft float"),
-            .float_32 => @floatCast(T, self.cast(Payload.Float_32).?.val),
-            .float_64 => @floatCast(T, self.cast(Payload.Float_64).?.val),
-            .float_128 => @floatCast(T, self.cast(Payload.Float_128).?.val),
+            .float_32 => @floatCast(T, self.castTag(.float_32).?.data),
+            .float_64 => @floatCast(T, self.castTag(.float_64).?.data),
+            .float_128 => @floatCast(T, self.castTag(.float_128).?.data),
 
             .zero => 0,
             .one => 1,
-            .int_u64 => @intToFloat(T, self.cast(Payload.Int_u64).?.int),
-            .int_i64 => @intToFloat(T, self.cast(Payload.Int_i64).?.int),
+            .int_u64 => @intToFloat(T, self.castTag(.int_u64).?.data),
+            .int_i64 => @intToFloat(T, self.castTag(.int_i64).?.data),
 
             .int_big_positive, .int_big_negative => @panic("big int to f128"),
             else => unreachable,
@@ -846,15 +977,15 @@ pub const Value = extern union {
             => return 1,
 
             .int_u64 => {
-                const x = self.cast(Payload.Int_u64).?.int;
+                const x = self.castTag(.int_u64).?.data;
                 if (x == 0) return 0;
                 return @intCast(usize, std.math.log2(x) + 1);
             },
             .int_i64 => {
                 @panic("TODO implement i64 intBitCountTwosComp");
             },
-            .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().bitCountTwosComp(),
-            .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt().bitCountTwosComp(),
+            .int_big_positive => return self.castTag(.int_big_positive).?.asBigInt().bitCountTwosComp(),
+            .int_big_negative => return self.castTag(.int_big_negative).?.asBigInt().bitCountTwosComp(),
         }
     }
 
@@ -943,7 +1074,7 @@ pub const Value = extern union {
 
             .int_u64 => switch (ty.zigTypeTag()) {
                 .Int => {
-                    const x = self.cast(Payload.Int_u64).?.int;
+                    const x = self.castTag(.int_u64).?.data;
                     if (x == 0) return true;
                     const info = ty.intInfo(target);
                     const needed_bits = std.math.log2(x) + 1 + @boolToInt(info.signedness == .signed);
@@ -954,7 +1085,7 @@ pub const Value = extern union {
             },
             .int_i64 => switch (ty.zigTypeTag()) {
                 .Int => {
-                    const x = self.cast(Payload.Int_i64).?.int;
+                    const x = self.castTag(.int_i64).?.data;
                     if (x == 0) return true;
                     const info = ty.intInfo(target);
                     if (info.signedness == .unsigned and x < 0)
@@ -967,7 +1098,7 @@ pub const Value = extern union {
             .int_big_positive => switch (ty.zigTypeTag()) {
                 .Int => {
                     const info = ty.intInfo(target);
-                    return self.cast(Payload.IntBigPositive).?.asBigInt().fitsInTwosComp(info.signedness, info.bits);
+                    return self.castTag(.int_big_positive).?.asBigInt().fitsInTwosComp(info.signedness, info.bits);
                 },
                 .ComptimeInt => return true,
                 else => unreachable,
@@ -975,7 +1106,7 @@ pub const Value = extern union {
             .int_big_negative => switch (ty.zigTypeTag()) {
                 .Int => {
                     const info = ty.intInfo(target);
-                    return self.cast(Payload.IntBigNegative).?.asBigInt().fitsInTwosComp(info.signedness, info.bits);
+                    return self.castTag(.int_big_negative).?.asBigInt().fitsInTwosComp(info.signedness, info.bits);
                 },
                 .ComptimeInt => return true,
                 else => unreachable,
@@ -986,42 +1117,28 @@ pub const Value = extern union {
     /// Converts an integer or a float to a float.
     /// Returns `error.Overflow` if the value does not fit in the new type.
     pub fn floatCast(self: Value, allocator: *Allocator, ty: Type, target: Target) !Value {
-        const dest_bit_count = switch (ty.tag()) {
-            .comptime_float => 128,
-            else => ty.floatBits(target),
-        };
-        switch (dest_bit_count) {
-            16, 32, 64, 128 => {},
-            else => std.debug.panic("TODO float cast bit count {}\n", .{dest_bit_count}),
-        }
-        if (ty.isInt()) {
-            @panic("TODO int to float");
-        }
-
-        switch (dest_bit_count) {
-            16 => {
-                @panic("TODO soft float");
-                // var res_payload = Value.Payload.Float_16{.val = self.toFloat(f16)};
-                // if (!self.eql(Value.initPayload(&res_payload.base)))
-                //     return error.Overflow;
-                // return Value.initPayload(&res_payload.base).copy(allocator);
+        switch (ty.tag()) {
+            .f16 => {
+                @panic("TODO add __trunctfhf2 to compiler-rt");
+                //const res = try Value.Tag.float_16.create(allocator, self.toFloat(f16));
+                //if (!self.eql(res))
+                //    return error.Overflow;
+                //return res;
             },
-            32 => {
-                var res_payload = Value.Payload.Float_32{ .val = self.toFloat(f32) };
-                if (!self.eql(Value.initPayload(&res_payload.base)))
+            .f32 => {
+                const res = try Value.Tag.float_32.create(allocator, self.toFloat(f32));
+                if (!self.eql(res))
                     return error.Overflow;
-                return Value.initPayload(&res_payload.base).copy(allocator);
+                return res;
             },
-            64 => {
-                var res_payload = Value.Payload.Float_64{ .val = self.toFloat(f64) };
-                if (!self.eql(Value.initPayload(&res_payload.base)))
+            .f64 => {
+                const res = try Value.Tag.float_64.create(allocator, self.toFloat(f64));
+                if (!self.eql(res))
                     return error.Overflow;
-                return Value.initPayload(&res_payload.base).copy(allocator);
+                return res;
             },
-            128 => {
-                const float_payload = try allocator.create(Value.Payload.Float_128);
-                float_payload.* = .{ .val = self.toFloat(f128) };
-                return Value.initPayload(&float_payload.base);
+            .f128, .comptime_float, .c_longdouble => {
+                return Value.Tag.float_128.create(allocator, self.toFloat(f128));
             },
             else => unreachable,
         }
@@ -1102,10 +1219,10 @@ pub const Value = extern union {
             .one,
             => false,
 
-            .float_16 => @rem(self.cast(Payload.Float_16).?.val, 1) != 0,
-            .float_32 => @rem(self.cast(Payload.Float_32).?.val, 1) != 0,
-            .float_64 => @rem(self.cast(Payload.Float_64).?.val, 1) != 0,
-            // .float_128 => @rem(self.cast(Payload.Float_128).?.val, 1) != 0,
+            .float_16 => @rem(self.castTag(.float_16).?.data, 1) != 0,
+            .float_32 => @rem(self.castTag(.float_32).?.data, 1) != 0,
+            .float_64 => @rem(self.castTag(.float_64).?.data, 1) != 0,
+            // .float_128 => @rem(self.castTag(.float_128).?.data, 1) != 0,
             .float_128 => @panic("TODO lld: error: undefined symbol: fmodl"),
         };
     }
@@ -1182,15 +1299,15 @@ pub const Value = extern union {
             .bool_true,
             => .gt,
 
-            .int_u64 => std.math.order(lhs.cast(Payload.Int_u64).?.int, 0),
-            .int_i64 => std.math.order(lhs.cast(Payload.Int_i64).?.int, 0),
-            .int_big_positive => lhs.cast(Payload.IntBigPositive).?.asBigInt().orderAgainstScalar(0),
-            .int_big_negative => lhs.cast(Payload.IntBigNegative).?.asBigInt().orderAgainstScalar(0),
+            .int_u64 => std.math.order(lhs.castTag(.int_u64).?.data, 0),
+            .int_i64 => std.math.order(lhs.castTag(.int_i64).?.data, 0),
+            .int_big_positive => lhs.castTag(.int_big_positive).?.asBigInt().orderAgainstScalar(0),
+            .int_big_negative => lhs.castTag(.int_big_negative).?.asBigInt().orderAgainstScalar(0),
 
-            .float_16 => std.math.order(lhs.cast(Payload.Float_16).?.val, 0),
-            .float_32 => std.math.order(lhs.cast(Payload.Float_32).?.val, 0),
-            .float_64 => std.math.order(lhs.cast(Payload.Float_64).?.val, 0),
-            .float_128 => std.math.order(lhs.cast(Payload.Float_128).?.val, 0),
+            .float_16 => std.math.order(lhs.castTag(.float_16).?.data, 0),
+            .float_32 => std.math.order(lhs.castTag(.float_32).?.data, 0),
+            .float_64 => std.math.order(lhs.castTag(.float_64).?.data, 0),
+            .float_128 => std.math.order(lhs.castTag(.float_128).?.data, 0),
         };
     }
 
@@ -1208,10 +1325,10 @@ pub const Value = extern union {
         if (lhs_float and rhs_float) {
             if (lhs_tag == rhs_tag) {
                 return switch (lhs.tag()) {
-                    .float_16 => return std.math.order(lhs.cast(Payload.Float_16).?.val, rhs.cast(Payload.Float_16).?.val),
-                    .float_32 => return std.math.order(lhs.cast(Payload.Float_32).?.val, rhs.cast(Payload.Float_32).?.val),
-                    .float_64 => return std.math.order(lhs.cast(Payload.Float_64).?.val, rhs.cast(Payload.Float_64).?.val),
-                    .float_128 => return std.math.order(lhs.cast(Payload.Float_128).?.val, rhs.cast(Payload.Float_128).?.val),
+                    .float_16 => return std.math.order(lhs.castTag(.float_16).?.data, rhs.castTag(.float_16).?.data),
+                    .float_32 => return std.math.order(lhs.castTag(.float_32).?.data, rhs.castTag(.float_32).?.data),
+                    .float_64 => return std.math.order(lhs.castTag(.float_64).?.data, rhs.castTag(.float_64).?.data),
+                    .float_128 => return std.math.order(lhs.castTag(.float_128).?.data, rhs.castTag(.float_128).?.data),
                     else => unreachable,
                 };
             }
@@ -1244,8 +1361,8 @@ pub const Value = extern union {
             if (a.tag() == .void_value or a.tag() == .null_value) {
                 return true;
             } else if (a.tag() == .enum_literal) {
-                const a_name = @fieldParentPtr(Payload.Bytes, "base", a.ptr_otherwise).data;
-                const b_name = @fieldParentPtr(Payload.Bytes, "base", b.ptr_otherwise).data;
+                const a_name = a.castTag(.enum_literal).?.data;
+                const b_name = b.castTag(.enum_literal).?.data;
                 return std.mem.eql(u8, a_name, b_name);
             }
         }
@@ -1313,11 +1430,11 @@ pub const Value = extern union {
             },
             .error_set => {
                 // Payload.decl should be same for all instances of the type.
-                const payload = @fieldParentPtr(Payload.ErrorSet, "base", self.ptr_otherwise);
+                const payload = self.castTag(.error_set).?.data;
                 std.hash.autoHash(&hasher, payload.decl);
             },
             .int_type => {
-                const payload = self.cast(Payload.IntType).?;
+                const payload = self.castTag(.int_type).?.data;
                 var int_payload = Type.Payload.Bits{
                     .base = .{
                         .tag = if (payload.signed) .int_signed else .int_unsigned,
@@ -1341,25 +1458,29 @@ pub const Value = extern union {
             .one, .bool_true => std.hash.autoHash(&hasher, @as(u64, 1)),
 
             .float_16, .float_32, .float_64, .float_128 => {},
-            .enum_literal, .bytes => {
-                const payload = @fieldParentPtr(Payload.Bytes, "base", self.ptr_otherwise);
+            .enum_literal => {
+                const payload = self.castTag(.enum_literal).?;
+                hasher.update(payload.data);
+            },
+            .bytes => {
+                const payload = self.castTag(.bytes).?;
                 hasher.update(payload.data);
             },
             .int_u64 => {
-                const payload = @fieldParentPtr(Payload.Int_u64, "base", self.ptr_otherwise);
-                std.hash.autoHash(&hasher, payload.int);
+                const payload = self.castTag(.int_u64).?;
+                std.hash.autoHash(&hasher, payload.data);
             },
             .int_i64 => {
-                const payload = @fieldParentPtr(Payload.Int_i64, "base", self.ptr_otherwise);
-                std.hash.autoHash(&hasher, payload.int);
+                const payload = self.castTag(.int_i64).?;
+                std.hash.autoHash(&hasher, payload.data);
             },
             .repeated => {
-                const payload = @fieldParentPtr(Payload.Repeated, "base", self.ptr_otherwise);
-                std.hash.autoHash(&hasher, payload.val.hash());
+                const payload = self.castTag(.repeated).?;
+                std.hash.autoHash(&hasher, payload.data.hash());
             },
             .ref_val => {
-                const payload = @fieldParentPtr(Payload.RefVal, "base", self.ptr_otherwise);
-                std.hash.autoHash(&hasher, payload.val.hash());
+                const payload = self.castTag(.ref_val).?;
+                std.hash.autoHash(&hasher, payload.data.hash());
             },
             .int_big_positive, .int_big_negative => {
                 var space: BigIntSpace = undefined;
@@ -1379,28 +1500,28 @@ pub const Value = extern union {
                 }
             },
             .elem_ptr => {
-                const payload = @fieldParentPtr(Payload.ElemPtr, "base", self.ptr_otherwise);
+                const payload = self.castTag(.elem_ptr).?.data;
                 std.hash.autoHash(&hasher, payload.array_ptr.hash());
                 std.hash.autoHash(&hasher, payload.index);
             },
             .decl_ref => {
-                const payload = @fieldParentPtr(Payload.DeclRef, "base", self.ptr_otherwise);
-                std.hash.autoHash(&hasher, payload.decl);
+                const decl = self.castTag(.decl_ref).?.data;
+                std.hash.autoHash(&hasher, decl);
             },
             .function => {
-                const payload = @fieldParentPtr(Payload.Function, "base", self.ptr_otherwise);
-                std.hash.autoHash(&hasher, payload.func);
+                const func = self.castTag(.function).?.data;
+                std.hash.autoHash(&hasher, func);
             },
             .extern_fn => {
-                const payload = @fieldParentPtr(Payload.ExternFn, "base", self.ptr_otherwise);
-                std.hash.autoHash(&hasher, payload.decl);
+                const decl = self.castTag(.extern_fn).?.data;
+                std.hash.autoHash(&hasher, decl);
             },
             .variable => {
-                const payload = @fieldParentPtr(Payload.Variable, "base", self.ptr_otherwise);
-                std.hash.autoHash(&hasher, payload.variable);
+                const variable = self.castTag(.variable).?.data;
+                std.hash.autoHash(&hasher, variable);
             },
             .@"error" => {
-                const payload = @fieldParentPtr(Payload.Error, "base", self.ptr_otherwise);
+                const payload = self.castTag(.@"error").?.data;
                 hasher.update(payload.name);
                 std.hash.autoHash(&hasher, payload.value);
             },
@@ -1483,10 +1604,10 @@ pub const Value = extern union {
             .empty_struct_value,
             => unreachable,
 
-            .ref_val => self.cast(Payload.RefVal).?.val,
-            .decl_ref => self.cast(Payload.DeclRef).?.decl.value(),
+            .ref_val => self.castTag(.ref_val).?.data,
+            .decl_ref => self.castTag(.decl_ref).?.data.value(),
             .elem_ptr => {
-                const elem_ptr = self.cast(Payload.ElemPtr).?;
+                const elem_ptr = self.castTag(.elem_ptr).?.data;
                 const array_val = try elem_ptr.array_ptr.pointerDeref(allocator);
                 return array_val.elemValue(allocator, elem_ptr.index);
             },
@@ -1570,26 +1691,26 @@ pub const Value = extern union {
 
             .empty_array => unreachable, // out of bounds array index
 
-            .bytes => {
-                const int_payload = try allocator.create(Payload.Int_u64);
-                int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] };
-                return Value.initPayload(&int_payload.base);
-            },
+            .bytes => return Tag.int_u64.create(allocator, self.castTag(.bytes).?.data[index]),
 
             // No matter the index; all the elements are the same!
-            .repeated => return self.cast(Payload.Repeated).?.val,
+            .repeated => return self.castTag(.repeated).?.data,
         }
     }
 
     /// Returns a pointer to the element value at the index.
     pub fn elemPtr(self: Value, allocator: *Allocator, index: usize) !Value {
-        const payload = try allocator.create(Payload.ElemPtr);
-        if (self.cast(Payload.ElemPtr)) |elem_ptr| {
-            payload.* = .{ .array_ptr = elem_ptr.array_ptr, .index = elem_ptr.index + index };
-        } else {
-            payload.* = .{ .array_ptr = self, .index = index };
+        if (self.castTag(.elem_ptr)) |elem_ptr| {
+            return Tag.elem_ptr.create(allocator, .{
+                .array_ptr = elem_ptr.data.array_ptr,
+                .index = elem_ptr.data.index + index,
+            });
         }
-        return Value.initPayload(&payload.base);
+
+        return Tag.elem_ptr.create(allocator, .{
+            .array_ptr = self,
+            .index = index,
+        });
     }
 
     pub fn isUndef(self: Value) bool {
@@ -1776,131 +1897,128 @@ pub const Value = extern union {
     pub const Payload = struct {
         tag: Tag,
 
-        pub const Int_u64 = struct {
-            base: Payload = Payload{ .tag = .int_u64 },
-            int: u64,
+        pub const U64 = struct {
+            base: Payload,
+            data: u64,
         };
 
-        pub const Int_i64 = struct {
-            base: Payload = Payload{ .tag = .int_i64 },
-            int: i64,
-        };
-
-        pub const IntBigPositive = struct {
-            base: Payload = Payload{ .tag = .int_big_positive },
-            limbs: []const std.math.big.Limb,
-
-            pub fn asBigInt(self: IntBigPositive) BigIntConst {
-                return BigIntConst{ .limbs = self.limbs, .positive = true };
-            }
+        pub const I64 = struct {
+            base: Payload,
+            data: i64,
         };
 
-        pub const IntBigNegative = struct {
-            base: Payload = Payload{ .tag = .int_big_negative },
-            limbs: []const std.math.big.Limb,
+        pub const BigInt = struct {
+            base: Payload,
+            data: []const std.math.big.Limb,
 
-            pub fn asBigInt(self: IntBigNegative) BigIntConst {
-                return BigIntConst{ .limbs = self.limbs, .positive = false };
+            pub fn asBigInt(self: BigInt) BigIntConst {
+                const positive = switch (self.base.tag) {
+                    .int_big_positive => true,
+                    .int_big_negative => false,
+                    else => unreachable,
+                };
+                return BigIntConst{ .limbs = self.data, .positive = positive };
             }
         };
 
         pub const Function = struct {
-            base: Payload = Payload{ .tag = .function },
-            func: *Module.Fn,
+            base: Payload,
+            data: *Module.Fn,
         };
 
-        pub const ExternFn = struct {
-            base: Payload = Payload{ .tag = .extern_fn },
-            decl: *Module.Decl,
+        pub const Decl = struct {
+            base: Payload,
+            data: *Module.Decl,
         };
 
         pub const Variable = struct {
-            base: Payload = Payload{ .tag = .variable },
-            variable: *Module.Var,
-        };
-
-        pub const ArraySentinel0_u8_Type = struct {
-            base: Payload = Payload{ .tag = .array_sentinel_0_u8_type },
-            len: u64,
-        };
-
-        /// Represents a pointer to another immutable value.
-        pub const RefVal = struct {
-            base: Payload = Payload{ .tag = .ref_val },
-            val: Value,
+            base: Payload,
+            data: *Module.Var,
         };
 
-        /// Represents a pointer to a decl, not the value of the decl.
-        pub const DeclRef = struct {
-            base: Payload = Payload{ .tag = .decl_ref },
-            decl: *Module.Decl,
+        pub const SubValue = struct {
+            base: Payload,
+            data: Value,
         };
 
         pub const ElemPtr = struct {
-            base: Payload = Payload{ .tag = .elem_ptr },
-            array_ptr: Value,
-            index: usize,
+            pub const base_tag = Tag.elem_ptr;
+
+            base: Payload = Payload{ .tag = base_tag },
+            data: struct {
+                array_ptr: Value,
+                index: usize,
+            },
         };
 
         pub const Bytes = struct {
-            base: Payload = Payload{ .tag = .bytes },
+            base: Payload,
             data: []const u8,
         };
 
         pub const Ty = struct {
-            base: Payload = Payload{ .tag = .ty },
-            ty: Type,
+            base: Payload,
+            data: Type,
         };
 
         pub const IntType = struct {
-            base: Payload = Payload{ .tag = .int_type },
-            bits: u16,
-            signed: bool,
-        };
+            pub const base_tag = Tag.int_type;
 
-        pub const Repeated = struct {
-            base: Payload = Payload{ .tag = .ty },
-            /// This value is repeated some number of times. The amount of times to repeat
-            /// is stored externally.
-            val: Value,
+            base: Payload = Payload{ .tag = base_tag },
+            data: struct {
+                bits: u16,
+                signed: bool,
+            },
         };
 
         pub const Float_16 = struct {
-            base: Payload = .{ .tag = .float_16 },
-            val: f16,
+            pub const base_tag = Tag.float_16;
+
+            base: Payload = .{ .tag = base_tag },
+            data: f16,
         };
 
         pub const Float_32 = struct {
-            base: Payload = .{ .tag = .float_32 },
-            val: f32,
+            pub const base_tag = Tag.float_32;
+
+            base: Payload = .{ .tag = base_tag },
+            data: f32,
         };
 
         pub const Float_64 = struct {
-            base: Payload = .{ .tag = .float_64 },
-            val: f64,
+            pub const base_tag = Tag.float_64;
+
+            base: Payload = .{ .tag = base_tag },
+            data: f64,
         };
 
         pub const Float_128 = struct {
-            base: Payload = .{ .tag = .float_128 },
-            val: f128,
+            pub const base_tag = Tag.float_128;
+
+            base: Payload = .{ .tag = base_tag },
+            data: f128,
         };
 
         pub const ErrorSet = struct {
-            base: Payload = .{ .tag = .error_set },
+            pub const base_tag = Tag.error_set;
 
-            // TODO revisit this when we have the concept of the error tag type
-            fields: std.StringHashMapUnmanaged(u16),
-            decl: *Module.Decl,
+            base: Payload = .{ .tag = base_tag },
+            data: struct {
+                // TODO revisit this when we have the concept of the error tag type
+                fields: std.StringHashMapUnmanaged(u16),
+                decl: *Module.Decl,
+            },
         };
 
         pub const Error = struct {
             base: Payload = .{ .tag = .@"error" },
-
-            // TODO revisit this when we have the concept of the error tag type
-            /// `name` is owned by `Module` and will be valid for the entire
-            /// duration of the compilation.
-            name: []const u8,
-            value: u16,
+            data: struct {
+                // TODO revisit this when we have the concept of the error tag type
+                /// `name` is owned by `Module` and will be valid for the entire
+                /// duration of the compilation.
+                name: []const u8,
+                value: u16,
+            },
         };
     };
 
@@ -1914,15 +2032,24 @@ pub const Value = extern union {
 
 test "hash same value different representation" {
     const zero_1 = Value.initTag(.zero);
-    var payload_1 = Value.Payload.Int_u64{ .int = 0 };
+    var payload_1 = Value.Payload.U64{
+        .base = .{ .tag = .int_u64 },
+        .data = 0,
+    };
     const zero_2 = Value.initPayload(&payload_1.base);
     std.testing.expectEqual(zero_1.hash(), zero_2.hash());
 
-    var payload_2 = Value.Payload.Int_i64{ .int = 0 };
+    var payload_2 = Value.Payload.I64{
+        .base = .{ .tag = .int_i64 },
+        .data = 0,
+    };
     const zero_3 = Value.initPayload(&payload_2.base);
     std.testing.expectEqual(zero_2.hash(), zero_3.hash());
 
-    var payload_3 = Value.Payload.IntBigNegative{ .limbs = &[_]std.math.big.Limb{0} };
+    var payload_3 = Value.Payload.BigInt{
+        .base = .{ .tag = .int_big_negative },
+        .data = &[_]std.math.big.Limb{0},
+    };
     const zero_4 = Value.initPayload(&payload_3.base);
     std.testing.expectEqual(zero_3.hash(), zero_4.hash());
 }
src/zir.zig
@@ -1990,15 +1990,15 @@ const EmitZIR = struct {
 
     fn resolveInst(self: *EmitZIR, new_body: ZirBody, inst: *ir.Inst) !*Inst {
         if (inst.cast(ir.Inst.Constant)) |const_inst| {
-            const new_inst = if (const_inst.val.cast(Value.Payload.Function)) |func_pl| blk: {
-                const owner_decl = func_pl.func.owner_decl;
+            const new_inst = if (const_inst.val.castTag(.function)) |func_pl| blk: {
+                const owner_decl = func_pl.data.owner_decl;
                 break :blk try self.emitDeclVal(inst.src, mem.spanZ(owner_decl.name));
-            } else if (const_inst.val.cast(Value.Payload.DeclRef)) |declref| blk: {
-                const decl_ref = try self.emitDeclRef(inst.src, declref.decl);
+            } else if (const_inst.val.castTag(.decl_ref)) |declref| blk: {
+                const decl_ref = try self.emitDeclRef(inst.src, declref.data);
                 try new_body.instructions.append(decl_ref);
                 break :blk decl_ref;
-            } else if (const_inst.val.cast(Value.Payload.Variable)) |var_pl| blk: {
-                const owner_decl = var_pl.variable.owner_decl;
+            } else if (const_inst.val.castTag(.variable)) |var_pl| blk: {
+                const owner_decl = var_pl.data.owner_decl;
                 break :blk try self.emitDeclVal(inst.src, mem.spanZ(owner_decl.name));
             } else blk: {
                 break :blk (try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val })).inst;
@@ -2150,13 +2150,13 @@ const EmitZIR = struct {
 
     fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Decl {
         const allocator = &self.arena.allocator;
-        if (typed_value.val.cast(Value.Payload.DeclRef)) |decl_ref| {
-            const decl = decl_ref.decl;
+        if (typed_value.val.castTag(.decl_ref)) |decl_ref| {
+            const decl = decl_ref.data;
             return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl));
-        } else if (typed_value.val.cast(Value.Payload.Variable)) |variable| {
+        } else if (typed_value.val.castTag(.variable)) |variable| {
             return self.emitTypedValue(src, .{
                 .ty = typed_value.ty,
-                .val = variable.variable.init,
+                .val = variable.data.init,
             });
         }
         if (typed_value.val.isUndef()) {
@@ -2215,7 +2215,7 @@ const EmitZIR = struct {
                 return self.emitType(src, ty);
             },
             .Fn => {
-                const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;
+                const module_fn = typed_value.val.castTag(.function).?.data;
                 return self.emitFn(module_fn, src, typed_value.ty);
             },
             .Array => {
@@ -2248,7 +2248,7 @@ const EmitZIR = struct {
             else
                 return self.emitPrimitive(src, .@"false"),
             .EnumLiteral => {
-                const enum_literal = @fieldParentPtr(Value.Payload.Bytes, "base", typed_value.val.ptr_otherwise);
+                const enum_literal = typed_value.val.castTag(.enum_literal).?;
                 const inst = try self.arena.allocator.create(Inst.Str);
                 inst.* = .{
                     .base = .{
@@ -2748,9 +2748,8 @@ const EmitZIR = struct {
                         .signed => .@"true",
                         .unsigned => .@"false",
                     });
-                    const bits_payload = try self.arena.allocator.create(Value.Payload.Int_u64);
-                    bits_payload.* = .{ .int = info.bits };
-                    const bits = try self.emitComptimeIntVal(src, Value.initPayload(&bits_payload.base));
+                    const bits_val = try Value.Tag.int_u64.create(&self.arena.allocator, info.bits);
+                    const bits = try self.emitComptimeIntVal(src, bits_val);
                     const inttype_inst = try self.arena.allocator.create(Inst.IntType);
                     inttype_inst.* = .{
                         .base = .{
@@ -2800,7 +2799,10 @@ const EmitZIR = struct {
                     return self.emitUnnamedDecl(&inst.base);
                 },
                 .Array => {
-                    var len_pl = Value.Payload.Int_u64{ .int = ty.arrayLen() };
+                    var len_pl = Value.Payload.U64{
+                        .base = .{ .tag = .int_u64 },
+                        .data = ty.arrayLen(),
+                    };
                     const len = Value.initPayload(&len_pl.base);
 
                     const inst = if (ty.sentinel()) |sentinel| blk: {
src/zir_sema.zig
@@ -364,12 +364,9 @@ fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!
     const ptr_type = try mod.simplePtrType(scope, inst.base.src, operand.ty, false, .One);
 
     if (operand.value()) |val| {
-        const ref_payload = try scope.arena().create(Value.Payload.RefVal);
-        ref_payload.* = .{ .val = val };
-
         return mod.constInst(scope, inst.base.src, .{
             .ty = ptr_type,
-            .val = Value.initPayload(&ref_payload.base),
+            .val = try Value.Tag.ref_val.create(scope.arena(), val),
         });
     }
 
@@ -480,12 +477,9 @@ fn analyzeInstStr(mod: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerErr
     errdefer new_decl_arena.deinit();
     const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes);
 
-    const bytes_payload = try scope.arena().create(Value.Payload.Bytes);
-    bytes_payload.* = .{ .data = arena_bytes };
-
     const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{
         .ty = try Type.Tag.array_u8_sentinel_0.create(scope.arena(), arena_bytes.len),
-        .val = Value.initPayload(&bytes_payload.base),
+        .val = try Value.Tag.bytes.create(scope.arena(), arena_bytes),
     });
     return mod.analyzeDeclRef(scope, str_inst.base.src, new_decl);
 }
@@ -779,11 +773,9 @@ fn analyzeInstFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!
         .analysis = .{ .queued = fn_zir },
         .owner_decl = scope.decl().?,
     };
-    const fn_payload = try scope.arena().create(Value.Payload.Function);
-    fn_payload.* = .{ .func = new_func };
     return mod.constInst(scope, fn_inst.base.src, .{
         .ty = fn_type,
-        .val = Value.initPayload(&fn_payload.base),
+        .val = try Value.Tag.function.create(scope.arena(), new_func),
     });
 }
 
@@ -838,14 +830,17 @@ fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) In
 
     const payload = try scope.arena().create(Value.Payload.ErrorSet);
     payload.* = .{
-        .fields = .{},
-        .decl = undefined, // populated below
+        .base = .{ .tag = .error_set },
+        .data = .{
+            .fields = .{},
+            .decl = undefined, // populated below
+        },
     };
-    try payload.fields.ensureCapacity(&new_decl_arena.allocator, @intCast(u32, inst.positionals.fields.len));
+    try payload.data.fields.ensureCapacity(&new_decl_arena.allocator, @intCast(u32, inst.positionals.fields.len));
 
     for (inst.positionals.fields) |field_name| {
         const entry = try mod.getErrorValue(field_name);
-        if (payload.fields.fetchPutAssumeCapacity(entry.key, entry.value)) |prev| {
+        if (payload.data.fields.fetchPutAssumeCapacity(entry.key, entry.value)) |prev| {
             return mod.fail(scope, inst.base.src, "duplicate error: '{}'", .{field_name});
         }
     }
@@ -854,7 +849,7 @@ fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) In
         .ty = Type.initTag(.type),
         .val = Value.initPayload(&payload.base),
     });
-    payload.decl = new_decl;
+    payload.data.decl = new_decl;
     return mod.analyzeDeclRef(scope, inst.base.src, new_decl);
 }
 
@@ -863,14 +858,10 @@ fn analyzeInstMergeErrorSets(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp)
 }
 
 fn analyzeInstEnumLiteral(mod: *Module, scope: *Scope, inst: *zir.Inst.EnumLiteral) InnerError!*Inst {
-    const payload = try scope.arena().create(Value.Payload.Bytes);
-    payload.* = .{
-        .base = .{ .tag = .enum_literal },
-        .data = try scope.arena().dupe(u8, inst.positionals.name),
-    };
+    const duped_name = try scope.arena().dupe(u8, inst.positionals.name);
     return mod.constInst(scope, inst.base.src, .{
         .ty = Type.initTag(.enum_literal),
-        .val = Value.initPayload(&payload.base),
+        .val = try Value.Tag.enum_literal.create(scope.arena(), duped_name),
     });
 }
 
@@ -989,15 +980,12 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr
     switch (elem_ty.zigTypeTag()) {
         .Array => {
             if (mem.eql(u8, field_name, "len")) {
-                const len_payload = try scope.arena().create(Value.Payload.Int_u64);
-                len_payload.* = .{ .int = elem_ty.arrayLen() };
-
-                const ref_payload = try scope.arena().create(Value.Payload.RefVal);
-                ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) };
-
                 return mod.constInst(scope, fieldptr.base.src, .{
                     .ty = Type.initTag(.single_const_pointer_to_comptime_int),
-                    .val = Value.initPayload(&ref_payload.base),
+                    .val = try Value.Tag.ref_val.create(
+                        scope.arena(),
+                        try Value.Tag.int_u64.create(scope.arena(), elem_ty.arrayLen()),
+                    ),
                 });
             } else {
                 return mod.fail(
@@ -1013,15 +1001,12 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr
             switch (ptr_child.zigTypeTag()) {
                 .Array => {
                     if (mem.eql(u8, field_name, "len")) {
-                        const len_payload = try scope.arena().create(Value.Payload.Int_u64);
-                        len_payload.* = .{ .int = ptr_child.arrayLen() };
-
-                        const ref_payload = try scope.arena().create(Value.Payload.RefVal);
-                        ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) };
-
                         return mod.constInst(scope, fieldptr.base.src, .{
                             .ty = Type.initTag(.single_const_pointer_to_comptime_int),
-                            .val = Value.initPayload(&ref_payload.base),
+                            .val = try Value.Tag.ref_val.create(
+                                scope.arena(),
+                                try Value.Tag.int_u64.create(scope.arena(), ptr_child.arrayLen()),
+                            ),
                         });
                     } else {
                         return mod.fail(
@@ -1043,21 +1028,12 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr
             switch (child_type.zigTypeTag()) {
                 .ErrorSet => {
                     // TODO resolve inferred error sets
-                    const entry = if (val.cast(Value.Payload.ErrorSet)) |payload|
-                        (payload.fields.getEntry(field_name) orelse
+                    const entry = if (val.castTag(.error_set)) |payload|
+                        (payload.data.fields.getEntry(field_name) orelse
                             return mod.fail(scope, fieldptr.base.src, "no error named '{}' in '{}'", .{ field_name, child_type })).*
                     else
                         try mod.getErrorValue(field_name);
 
-                    const error_payload = try scope.arena().create(Value.Payload.Error);
-                    error_payload.* = .{
-                        .name = entry.key,
-                        .value = entry.value,
-                    };
-
-                    const ref_payload = try scope.arena().create(Value.Payload.RefVal);
-                    ref_payload.* = .{ .val = Value.initPayload(&error_payload.base) };
-
                     const result_type = if (child_type.tag() == .anyerror)
                         try Type.Tag.error_set_single.create(scope.arena(), entry.key)
                     else
@@ -1065,7 +1041,13 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr
 
                     return mod.constInst(scope, fieldptr.base.src, .{
                         .ty = try mod.simplePtrType(scope, fieldptr.base.src, result_type, false, .One),
-                        .val = Value.initPayload(&ref_payload.base),
+                        .val = try Value.Tag.ref_val.create(
+                            scope.arena(),
+                            try Value.Tag.@"error".create(scope.arena(), .{
+                                .name = entry.key,
+                                .value = entry.value,
+                            }),
+                        ),
                     });
                 },
                 .Struct => {