Commit 4beff80b2f

Andrew Kelley <andrew@ziglang.org>
2020-07-29 06:57:13
stage2: codegen handles undefined values
* `optimize_mode` is passed to `link.File` and stored there * improve the debugging function `Module.dumpInst` * get rid of `Value.the_one_possible_value` in favor of a few more specific values for different types. This is less buggy, one less footgun. * `Type.onePossibleValue` now returns a `?Value` instead of `bool`. * codegen handles undefined values. `undef` is a new `MCValue` tag. It uses 0xaa values depending on optimization mode. However optimization mode does not yet support scope overrides. * link.zig: move the `Options` field from `File.Elf` and `File.C` to the base struct. - fix the Tag enum to adhere to style conventions * ZIR now supports emitting undefined values. * Fix the logic of comptime math to properly compare against zero using the `compareWithZero` function.
1 parent 2b8e7de
src-self-hosted/codegen.zig
@@ -50,7 +50,7 @@ pub fn generateSymbol(
 
     switch (typed_value.ty.zigTypeTag()) {
         .Fn => {
-            switch (bin_file.options.target.cpu.arch) {
+            switch (bin_file.base.options.target.cpu.arch) {
                 //.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code),
                 //.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code),
                 //.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code),
@@ -143,7 +143,7 @@ pub fn generateSymbol(
                 // TODO handle the dependency of this symbol on the decl's vaddr.
                 // If the decl changes vaddr, then this symbol needs to get regenerated.
                 const vaddr = bin_file.local_symbols.items[decl.link.local_sym_index].st_value;
-                const endian = bin_file.options.target.cpu.arch.endian();
+                const endian = bin_file.base.options.target.cpu.arch.endian();
                 switch (bin_file.ptr_width) {
                     .p32 => {
                         try code.resize(4);
@@ -166,7 +166,7 @@ pub fn generateSymbol(
             };
         },
         .Int => {
-            const info = typed_value.ty.intInfo(bin_file.options.target);
+            const info = typed_value.ty.intInfo(bin_file.base.options.target);
             if (info.bits == 8 and !info.signed) {
                 const x = typed_value.val.toUnsignedInt();
                 try code.append(@intCast(u8, x));
@@ -230,6 +230,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             unreach,
             /// No more references to this value remain.
             dead,
+            /// The value is undefined.
+            undef,
             /// A pointer-sized integer that fits in a register.
             /// If the type is a pointer, this is the pointer address in virtual address space.
             immediate: u64,
@@ -282,6 +284,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .compare_flags_signed,
                     .ptr_stack_offset,
                     .ptr_embedded_in_code,
+                    .undef,
                     => false,
 
                     .register,
@@ -360,7 +363,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
             var function = Self{
                 .gpa = bin_file.allocator,
-                .target = &bin_file.options.target,
+                .target = &bin_file.base.options.target,
                 .bin_file = bin_file,
                 .mod_fn = module_fn,
                 .code = code,
@@ -656,6 +659,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             };
             switch (ptr) {
                 .none => unreachable,
+                .undef => unreachable,
                 .unreach => unreachable,
                 .dead => unreachable,
                 .compare_flags_unsigned => unreachable,
@@ -687,6 +691,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             const elem_ty = inst.rhs.ty;
             switch (ptr) {
                 .none => unreachable,
+                .undef => unreachable,
                 .unreach => unreachable,
                 .dead => unreachable,
                 .compare_flags_unsigned => unreachable,
@@ -798,6 +803,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         fn genX8664BinMathCode(self: *Self, src: usize, dst_mcv: MCValue, src_mcv: MCValue, opx: u8, mr: u8) !void {
             switch (dst_mcv) {
                 .none => unreachable,
+                .undef => unreachable,
                 .dead, .unreach, .immediate => unreachable,
                 .compare_flags_unsigned => unreachable,
                 .compare_flags_signed => unreachable,
@@ -806,6 +812,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .register => |dst_reg| {
                     switch (src_mcv) {
                         .none => unreachable,
+                        .undef => try self.genSetReg(src, dst_reg, .undef),
                         .dead, .unreach => unreachable,
                         .ptr_stack_offset => unreachable,
                         .ptr_embedded_in_code => unreachable,
@@ -905,11 +912,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
                             },
                             .ptr_stack_offset => {
-                                return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset", .{});
+                                return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
                             },
                             .ptr_embedded_in_code => {
-                                return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code", .{});
+                                return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
                             },
+                            .undef => unreachable,
                             .immediate => unreachable,
                             .unreach => unreachable,
                             .dead => unreachable,
@@ -966,6 +974,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .stack_offset => |offset| return MCValue{ .ptr_stack_offset = offset },
                 .embedded_in_code => |offset| return MCValue{ .ptr_embedded_in_code = offset },
                 .memory => |vaddr| return MCValue{ .immediate = vaddr },
+
+                .undef => return self.fail(inst.base.src, "TODO implement ref on an undefined value", .{}),
             }
         }
 
@@ -1243,6 +1253,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .ptr_stack_offset => unreachable,
                     .ptr_embedded_in_code => unreachable,
                     .unreach, .none => return, // Nothing to do.
+                    .undef => {
+                        if (!self.wantSafety())
+                            return; // The already existing value will do just fine.
+                        // TODO Upgrade this to a memset call when we have that available.
+                        return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaa });
+                    },
                     .compare_flags_unsigned => |op| {
                         return self.fail(src, "TODO implement set stack variable with compare flags value (unsigned)", .{});
                     },
@@ -1250,6 +1266,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         return self.fail(src, "TODO implement set stack variable with compare flags value (signed)", .{});
                     },
                     .immediate => |x_big| {
+                        if (ty.abiSize(self.target.*) != 4) {
+                            // TODO after fixing this, need to update the undef case above
+                            return self.fail(src, "TODO implement set non 4 abi size stack variable with immediate", .{});
+                        }
                         try self.code.ensureCapacity(self.code.items.len + 7);
                         if (x_big <= math.maxInt(u32)) {
                             const x = @intCast(u32, x_big);
@@ -1311,6 +1331,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .ptr_stack_offset => unreachable,
                     .ptr_embedded_in_code => unreachable,
                     .unreach, .none => return, // Nothing to do.
+                    .undef => {
+                        if (!self.wantSafety())
+                            return; // The already existing value will do just fine.
+                        // Write the debug undefined value.
+                        switch (reg.size()) {
+                            8 => return self.genSetReg(src, reg, .{ .immediate = 0xaa }),
+                            16 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaa }),
+                            32 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa }),
+                            64 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
+                            else => unreachable,
+                        }
+                    },
                     .compare_flags_unsigned => |op| {
                         try self.code.ensureCapacity(self.code.items.len + 3);
                         self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 });
@@ -1471,7 +1503,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 // is no way to possibly encode it. This means that RSP, RBP, R12, and R13 cannot be used with
                                 // this instruction.
                                 const id3 = @truncate(u3, reg.id());
-                                std.debug.assert(id3 != 4 and id3 != 5);
+                                assert(id3 != 4 and id3 != 5);
 
                                 // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue.
                                 try self.genSetReg(src, reg, MCValue{ .immediate = x });
@@ -1580,6 +1612,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         }
 
         fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) !MCValue {
+            if (typed_value.val.isUndef())
+                return MCValue.undef;
             const ptr_bits = self.target.cpu.arch.ptrBitWidth();
             const ptr_bytes: u64 = @divExact(ptr_bits, 8);
             switch (typed_value.ty.zigTypeTag()) {
@@ -1691,6 +1725,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return result;
         }
 
+        /// TODO support scope overrides. Also note this logic is duplicated with `Module.wantSafety`.
+        fn wantSafety(self: *Self) bool {
+            return switch (self.bin_file.base.options.optimize_mode) {
+                .Debug => true,
+                .ReleaseSafe => true,
+                .ReleaseFast => false,
+                .ReleaseSmall => false,
+            };
+        }
+
         fn fail(self: *Self, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } {
             @setCold(true);
             assert(self.err_msg == null);
src-self-hosted/ir.zig
@@ -165,8 +165,7 @@ pub const Inst = struct {
 
     /// Returns `null` if runtime-known.
     pub fn value(base: *Inst) ?Value {
-        if (base.ty.onePossibleValue())
-            return Value.initTag(.the_one_possible_value);
+        if (base.ty.onePossibleValue()) |opv| return opv;
 
         const inst = base.cast(Constant) orelse return null;
         return inst.val;
src-self-hosted/link.zig
@@ -16,6 +16,7 @@ pub const Options = struct {
     output_mode: std.builtin.OutputMode,
     link_mode: std.builtin.LinkMode,
     object_format: std.builtin.ObjectFormat,
+    optimize_mode: std.builtin.Mode,
     /// Used for calculating how much space to reserve for symbols in case the binary file
     /// does not already have a symbol table.
     symbol_count_hint: u64 = 32,
@@ -66,6 +67,7 @@ pub fn writeFilePath(
         .link_mode = module.link_mode,
         .object_format = module.object_format,
         .symbol_count_hint = module.decls.items.len,
+        .optimize_mode = module.optimize_mode,
     };
     const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(options) });
     defer af.deinit();
@@ -88,9 +90,12 @@ pub fn writeFilePath(
 
 fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C {
     return File.C{
+        .base = .{
+            .tag = .c,
+            .options = options,
+        },
         .allocator = allocator,
         .file = file,
-        .options = options,
         .main = std.ArrayList(u8).init(allocator),
         .header = std.ArrayList(u8).init(allocator),
         .constants = std.ArrayList(u8).init(allocator),
@@ -114,6 +119,8 @@ pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !File
 
 pub const File = struct {
     tag: Tag,
+    options: Options,
+
     pub fn cast(base: *File, comptime T: type) ?*T {
         if (base.tag != T.base_tag)
             return null;
@@ -123,47 +130,47 @@ pub const File = struct {
 
     pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void {
         switch (base.tag) {
-            .Elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path),
-            .C => {},
+            .elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path),
+            .c => {},
         }
     }
 
     pub fn makeExecutable(base: *File) !void {
         switch (base.tag) {
-            .Elf => return @fieldParentPtr(Elf, "base", base).makeExecutable(),
-            .C => unreachable,
+            .elf => return @fieldParentPtr(Elf, "base", base).makeExecutable(),
+            .c => unreachable,
         }
     }
 
     pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void {
         switch (base.tag) {
-            .Elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
-            .C => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
+            .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
+            .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
         }
     }
 
     pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void {
         switch (base.tag) {
-            .Elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
-            .C => {},
+            .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
+            .c => {},
         }
     }
 
     pub fn deinit(base: *File) void {
         switch (base.tag) {
-            .Elf => @fieldParentPtr(Elf, "base", base).deinit(),
-            .C => @fieldParentPtr(C, "base", base).deinit(),
+            .elf => @fieldParentPtr(Elf, "base", base).deinit(),
+            .c => @fieldParentPtr(C, "base", base).deinit(),
         }
     }
 
     pub fn destroy(base: *File) void {
         switch (base.tag) {
-            .Elf => {
+            .elf => {
                 const parent = @fieldParentPtr(Elf, "base", base);
                 parent.deinit();
                 parent.allocator.destroy(parent);
             },
-            .C => {
+            .c => {
                 const parent = @fieldParentPtr(C, "base", base);
                 parent.deinit();
                 parent.allocator.destroy(parent);
@@ -173,29 +180,22 @@ pub const File = struct {
 
     pub fn flush(base: *File) !void {
         try switch (base.tag) {
-            .Elf => @fieldParentPtr(Elf, "base", base).flush(),
-            .C => @fieldParentPtr(C, "base", base).flush(),
+            .elf => @fieldParentPtr(Elf, "base", base).flush(),
+            .c => @fieldParentPtr(C, "base", base).flush(),
         };
     }
 
     pub fn freeDecl(base: *File, decl: *Module.Decl) void {
         switch (base.tag) {
-            .Elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
-            .C => unreachable,
+            .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
+            .c => unreachable,
         }
     }
 
     pub fn errorFlags(base: *File) ErrorFlags {
         return switch (base.tag) {
-            .Elf => @fieldParentPtr(Elf, "base", base).error_flags,
-            .C => return .{ .no_entry_point_found = false },
-        };
-    }
-
-    pub fn options(base: *File) Options {
-        return switch (base.tag) {
-            .Elf => @fieldParentPtr(Elf, "base", base).options,
-            .C => @fieldParentPtr(C, "base", base).options,
+            .elf => @fieldParentPtr(Elf, "base", base).error_flags,
+            .c => return .{ .no_entry_point_found = false },
         };
     }
 
@@ -207,14 +207,14 @@ pub const File = struct {
         exports: []const *Module.Export,
     ) !void {
         switch (base.tag) {
-            .Elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
-            .C => return {},
+            .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
+            .c => return {},
         }
     }
 
     pub const Tag = enum {
-        Elf,
-        C,
+        elf,
+        c,
     };
 
     pub const ErrorFlags = struct {
@@ -222,15 +222,15 @@ pub const File = struct {
     };
 
     pub const C = struct {
-        pub const base_tag: Tag = .C;
-        base: File = File{ .tag = base_tag },
+        pub const base_tag: Tag = .c;
+
+        base: File,
 
         allocator: *Allocator,
         header: std.ArrayList(u8),
         constants: std.ArrayList(u8),
         main: std.ArrayList(u8),
         file: ?fs.File,
-        options: Options,
         called: std.StringHashMap(void),
         need_stddef: bool = false,
         need_stdint: bool = false,
@@ -294,13 +294,13 @@ pub const File = struct {
     };
 
     pub const Elf = struct {
-        pub const base_tag: Tag = .Elf;
-        base: File = File{ .tag = base_tag },
+        pub const base_tag: Tag = .elf;
+
+        base: File,
 
         allocator: *Allocator,
         file: ?fs.File,
         owns_file_handle: bool,
-        options: Options,
         ptr_width: enum { p32, p64 },
 
         /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
@@ -460,13 +460,13 @@ pub const File = struct {
             self.file = try dir.createFile(sub_path, .{
                 .truncate = false,
                 .read = true,
-                .mode = determineMode(self.options),
+                .mode = determineMode(self.base.options),
             });
         }
 
         /// Returns end pos of collision, if any.
         fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {
-            const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32;
+            const small_ptr = self.base.options.target.cpu.arch.ptrBitWidth() == 32;
             const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);
             if (start < ehdr_size)
                 return ehdr_size;
@@ -569,7 +569,7 @@ pub const File = struct {
             };
             if (self.phdr_load_re_index == null) {
                 self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len);
-                const file_size = self.options.program_code_size_hint;
+                const file_size = self.base.options.program_code_size_hint;
                 const p_align = 0x1000;
                 const off = self.findFreeSpace(file_size, p_align);
                 std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
@@ -588,7 +588,7 @@ pub const File = struct {
             }
             if (self.phdr_got_index == null) {
                 self.phdr_got_index = @intCast(u16, self.program_headers.items.len);
-                const file_size = @as(u64, ptr_size) * self.options.symbol_count_hint;
+                const file_size = @as(u64, ptr_size) * self.base.options.symbol_count_hint;
                 // We really only need ptr alignment but since we are using PROGBITS, linux requires
                 // page align.
                 const p_align = 0x1000;
@@ -671,7 +671,7 @@ pub const File = struct {
                 self.symtab_section_index = @intCast(u16, self.sections.items.len);
                 const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym);
                 const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym);
-                const file_size = self.options.symbol_count_hint * each_size;
+                const file_size = self.base.options.symbol_count_hint * each_size;
                 const off = self.findFreeSpace(file_size, min_align);
                 std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
 
@@ -726,7 +726,7 @@ pub const File = struct {
 
         /// Commit pending changes and write headers.
         pub fn flush(self: *Elf) !void {
-            const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+            const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
 
             // Unfortunately these have to be buffered and done at the end because ELF does not allow
             // mixing local and global symbols within a symbol table.
@@ -845,7 +845,7 @@ pub const File = struct {
                 }
                 self.shdr_table_dirty = false;
             }
-            if (self.entry_addr == null and self.options.output_mode == .Exe) {
+            if (self.entry_addr == null and self.base.options.output_mode == .Exe) {
                 std.log.debug(.link, "no_entry_point_found = true\n", .{});
                 self.error_flags.no_entry_point_found = true;
             } else {
@@ -875,7 +875,7 @@ pub const File = struct {
             };
             index += 1;
 
-            const endian = self.options.target.cpu.arch.endian();
+            const endian = self.base.options.target.cpu.arch.endian();
             hdr_buf[index] = switch (endian) {
                 .Little => elf.ELFDATA2LSB,
                 .Big => elf.ELFDATA2MSB,
@@ -893,10 +893,10 @@ pub const File = struct {
 
             assert(index == 16);
 
-            const elf_type = switch (self.options.output_mode) {
+            const elf_type = switch (self.base.options.output_mode) {
                 .Exe => elf.ET.EXEC,
                 .Obj => elf.ET.REL,
-                .Lib => switch (self.options.link_mode) {
+                .Lib => switch (self.base.options.link_mode) {
                     .Static => elf.ET.REL,
                     .Dynamic => elf.ET.DYN,
                 },
@@ -904,7 +904,7 @@ pub const File = struct {
             mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian);
             index += 2;
 
-            const machine = self.options.target.cpu.arch.toElfMachine();
+            const machine = self.base.options.target.cpu.arch.toElfMachine();
             mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian);
             index += 2;
 
@@ -1216,7 +1216,7 @@ pub const File = struct {
                 },
             };
 
-            const required_alignment = typed_value.ty.abiAlignment(self.options.target);
+            const required_alignment = typed_value.ty.abiAlignment(self.base.options.target);
 
             const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) {
                 .Fn => elf.STT_FUNC,
@@ -1361,9 +1361,9 @@ pub const File = struct {
         }
 
         fn writeProgHeader(self: *Elf, index: usize) !void {
-            const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+            const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
             const offset = self.program_headers.items[index].p_offset;
-            switch (self.options.target.cpu.arch.ptrBitWidth()) {
+            switch (self.base.options.target.cpu.arch.ptrBitWidth()) {
                 32 => {
                     var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])};
                     if (foreign_endian) {
@@ -1383,9 +1383,9 @@ pub const File = struct {
         }
 
         fn writeSectHeader(self: *Elf, index: usize) !void {
-            const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+            const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
             const offset = self.sections.items[index].sh_offset;
-            switch (self.options.target.cpu.arch.ptrBitWidth()) {
+            switch (self.base.options.target.cpu.arch.ptrBitWidth()) {
                 32 => {
                     var shdr: [1]elf.Elf32_Shdr = undefined;
                     shdr[0] = sectHeaderTo32(self.sections.items[index]);
@@ -1433,7 +1433,7 @@ pub const File = struct {
 
                 self.offset_table_count_dirty = false;
             }
-            const endian = self.options.target.cpu.arch.endian();
+            const endian = self.base.options.target.cpu.arch.endian();
             const off = shdr.sh_offset + @as(u64, entry_size) * index;
             switch (self.ptr_width) {
                 .p32 => {
@@ -1475,7 +1475,7 @@ pub const File = struct {
                 syms_sect.sh_size = needed_size; // anticipating adding the global symbols later
                 self.shdr_table_dirty = true; // TODO look into only writing one section
             }
-            const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+            const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
             switch (self.ptr_width) {
                 .p32 => {
                     var sym = [1]elf.Elf32_Sym{
@@ -1511,7 +1511,7 @@ pub const File = struct {
                 .p32 => @sizeOf(elf.Elf32_Sym),
                 .p64 => @sizeOf(elf.Elf64_Sym),
             };
-            const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+            const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
             const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size;
             switch (self.ptr_width) {
                 .p32 => {
@@ -1577,9 +1577,12 @@ pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !Fi
     }
 
     var self: File.Elf = .{
+        .base = .{
+            .tag = .elf,
+            .options = options,
+        },
         .allocator = allocator,
         .file = file,
-        .options = options,
         .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
             32 => .p32,
             64 => .p64,
@@ -1637,10 +1640,13 @@ fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !Fil
         .raw => return error.IncrFailed,
     }
     var self: File.Elf = .{
+        .base = .{
+            .tag = .elf,
+            .options = options,
+        },
         .allocator = allocator,
         .file = file,
         .owns_file_handle = false,
-        .options = options,
         .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
             32 => .p32,
             64 => .p64,
src-self-hosted/Module.zig
@@ -47,7 +47,6 @@ export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{},
 /// Maps fully qualified namespaced names to the Decl struct for them.
 decl_table: std.HashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{},
 
-optimize_mode: std.builtin.Mode,
 link_error_flags: link.File.ErrorFlags = .{},
 
 work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic),
@@ -385,18 +384,6 @@ pub const Scope = struct {
         };
     }
 
-    pub fn dumpInst(self: *Scope, inst: *Inst) void {
-        const zir_module = self.namespace();
-        const loc = std.zig.findLineColumn(zir_module.source.bytes, inst.src);
-        std.debug.warn("{}:{}:{}: {}: ty={}\n", .{
-            zir_module.sub_file_path,
-            loc.line + 1,
-            loc.column + 1,
-            @tagName(inst.tag),
-            inst.ty,
-        });
-    }
-
     /// Asserts the scope has a parent which is a ZIRModule or File and
     /// returns the sub_file_path field.
     pub fn subFilePath(base: *Scope) []const u8 {
@@ -802,6 +789,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
         .output_mode = options.output_mode,
         .link_mode = options.link_mode orelse .Static,
         .object_format = options.object_format orelse options.target.getObjectFormat(),
+        .optimize_mode = options.optimize_mode,
     });
     errdefer bin_file.destroy();
 
@@ -838,7 +826,6 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
         .bin_file_dir = bin_file_dir,
         .bin_file_path = options.bin_file_path,
         .bin_file = bin_file,
-        .optimize_mode = options.optimize_mode,
         .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa),
         .keep_source_files_loaded = options.keep_source_files_loaded,
     };
@@ -894,7 +881,11 @@ fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
 }
 
 pub fn target(self: Module) std.Target {
-    return self.bin_file.options().target;
+    return self.bin_file.options.target;
+}
+
+pub fn optimizeMode(self: Module) std.builtin.Mode {
+    return self.bin_file.options.optimize_mode;
 }
 
 /// Detect changes to source files, perform semantic analysis, and update the output files.
@@ -1991,14 +1982,14 @@ pub fn constType(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst {
 pub fn constVoid(self: *Module, scope: *Scope, src: usize) !*Inst {
     return self.constInst(scope, src, .{
         .ty = Type.initTag(.void),
-        .val = Value.initTag(.the_one_possible_value),
+        .val = Value.initTag(.void_value),
     });
 }
 
 pub fn constNoReturn(self: *Module, scope: *Scope, src: usize) !*Inst {
     return self.constInst(scope, src, .{
         .ty = Type.initTag(.noreturn),
-        .val = Value.initTag(.the_one_possible_value),
+        .val = Value.initTag(.unreachable_value),
     });
 }
 
@@ -2162,7 +2153,8 @@ pub fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name:
 }
 
 pub fn wantSafety(self: *Module, scope: *Scope) bool {
-    return switch (self.optimize_mode) {
+    // TODO take into account scope's safety overrides
+    return switch (self.optimizeMode()) {
         .Debug => true,
         .ReleaseSafe => true,
         .ReleaseFast => false,
@@ -2511,7 +2503,7 @@ pub fn storePtr(self: *Module, scope: *Scope, src: usize, ptr: *Inst, uncasted_v
 
     const elem_ty = ptr.ty.elemType();
     const value = try self.coerce(scope, elem_ty, uncasted_value);
-    if (elem_ty.onePossibleValue())
+    if (elem_ty.onePossibleValue() != null)
         return self.constVoid(scope, src);
 
     // TODO handle comptime pointer writes
@@ -2803,3 +2795,35 @@ pub fn singleConstPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Typ
     type_payload.* = .{ .pointee_type = elem_ty };
     return Type.initPayload(&type_payload.base);
 }
+
+pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void {
+    const zir_module = scope.namespace();
+    const source = zir_module.getSource(self) catch @panic("dumpInst failed to get source");
+    const loc = std.zig.findLineColumn(source, inst.src);
+    if (inst.tag == .constant) {
+        std.debug.warn("constant ty={} val={} src={}:{}:{}\n", .{
+            inst.ty,
+            inst.castTag(.constant).?.val,
+            zir_module.subFilePath(),
+            loc.line + 1,
+            loc.column + 1,
+        });
+    } else if (inst.deaths == 0) {
+        std.debug.warn("{} ty={} src={}:{}:{}\n", .{
+            @tagName(inst.tag),
+            inst.ty,
+            zir_module.subFilePath(),
+            loc.line + 1,
+            loc.column + 1,
+        });
+    } else {
+        std.debug.warn("{} ty={} deaths={b} src={}:{}:{}\n", .{
+            @tagName(inst.tag),
+            inst.ty,
+            inst.deaths,
+            zir_module.subFilePath(),
+            loc.line + 1,
+            loc.column + 1,
+        });
+    }
+}
src-self-hosted/type.zig
@@ -1653,7 +1653,7 @@ pub const Type = extern union {
         };
     }
 
-    pub fn onePossibleValue(self: Type) bool {
+    pub fn onePossibleValue(self: Type) ?Value {
         var ty = self;
         while (true) switch (ty.tag()) {
             .f16,
@@ -1692,21 +1692,32 @@ pub const Type = extern union {
             .single_const_pointer_to_comptime_int,
             .array_u8_sentinel_0,
             .const_slice_u8,
-            => return false,
-
             .c_void,
-            .void,
-            .noreturn,
-            .@"null",
-            .@"undefined",
-            => return true,
+            => return null,
+
+            .void => return Value.initTag(.void_value),
+            .noreturn => return Value.initTag(.unreachable_value),
+            .@"null" => return Value.initTag(.null_value),
+            .@"undefined" => return Value.initTag(.undef),
 
-            .int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0,
-            .int_signed => return ty.cast(Payload.IntSigned).?.bits == 0,
+            .int_unsigned => {
+                if (ty.cast(Payload.IntUnsigned).?.bits == 0) {
+                    return Value.initTag(.zero);
+                } else {
+                    return null;
+                }
+            },
+            .int_signed => {
+                if (ty.cast(Payload.IntSigned).?.bits == 0) {
+                    return Value.initTag(.zero);
+                } else {
+                    return null;
+                }
+            },
             .array => {
                 const array = ty.cast(Payload.Array).?;
                 if (array.len == 0)
-                    return true;
+                    return Value.initTag(.empty_array);
                 ty = array.elem_type;
                 continue;
             },
src-self-hosted/value.zig
@@ -63,7 +63,9 @@ pub const Value = extern union {
 
         undef,
         zero,
-        the_one_possible_value, // when the type only has one possible value
+        void_value,
+        unreachable_value,
+        empty_array,
         null_value,
         bool_true,
         bool_false, // See last_no_payload_tag below.
@@ -164,7 +166,9 @@ pub const Value = extern union {
             .const_slice_u8_type,
             .undef,
             .zero,
-            .the_one_possible_value,
+            .void_value,
+            .unreachable_value,
+            .empty_array,
             .null_value,
             .bool_true,
             .bool_false,
@@ -285,7 +289,8 @@ pub const Value = extern union {
             .null_value => return out_stream.writeAll("null"),
             .undef => return out_stream.writeAll("undefined"),
             .zero => return out_stream.writeAll("0"),
-            .the_one_possible_value => return out_stream.writeAll("(one possible value)"),
+            .void_value => return out_stream.writeAll("{}"),
+            .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),
@@ -312,6 +317,7 @@ pub const Value = extern union {
                 try out_stream.print("&[{}] ", .{elem_ptr.index});
                 val = elem_ptr.array_ptr;
             },
+            .empty_array => return out_stream.writeAll(".{}"),
             .bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream),
             .repeated => {
                 try out_stream.writeAll("(repeated) ");
@@ -388,7 +394,9 @@ pub const Value = extern union {
 
             .undef,
             .zero,
-            .the_one_possible_value,
+            .void_value,
+            .unreachable_value,
+            .empty_array,
             .bool_true,
             .bool_false,
             .null_value,
@@ -460,15 +468,18 @@ pub const Value = extern union {
             .decl_ref,
             .elem_ptr,
             .bytes,
-            .undef,
             .repeated,
             .float_16,
             .float_32,
             .float_64,
             .float_128,
+            .void_value,
+            .unreachable_value,
+            .empty_array,
             => unreachable,
 
-            .the_one_possible_value, // An integer with one possible value is always zero.
+            .undef => unreachable,
+
             .zero,
             .bool_false,
             => return BigIntMutable.init(&space.limbs, 0).toConst(),
@@ -532,16 +543,19 @@ pub const Value = extern union {
             .decl_ref,
             .elem_ptr,
             .bytes,
-            .undef,
             .repeated,
             .float_16,
             .float_32,
             .float_64,
             .float_128,
+            .void_value,
+            .unreachable_value,
+            .empty_array,
             => unreachable,
 
+            .undef => unreachable,
+
             .zero,
-            .the_one_possible_value, // an integer with one possible value is always zero
             .bool_false,
             => return 0,
 
@@ -570,7 +584,7 @@ pub const Value = extern union {
             .float_64 => @floatCast(T, self.cast(Payload.Float_64).?.val),
             .float_128 => @floatCast(T, self.cast(Payload.Float_128).?.val),
 
-            .zero, .the_one_possible_value => 0,
+            .zero => 0,
             .int_u64 => @intToFloat(T, self.cast(Payload.Int_u64).?.int),
             // .int_i64 => @intToFloat(f128, self.cast(Payload.Int_i64).?.int),
             .int_i64 => @panic("TODO lld: error: undefined symbol: __floatditf"),
@@ -637,9 +651,11 @@ pub const Value = extern union {
             .float_32,
             .float_64,
             .float_128,
+            .void_value,
+            .unreachable_value,
+            .empty_array,
             => unreachable,
 
-            .the_one_possible_value, // an integer with one possible value is always zero
             .zero,
             .bool_false,
             => return 0,
@@ -714,11 +730,13 @@ pub const Value = extern union {
             .float_32,
             .float_64,
             .float_128,
+            .void_value,
+            .unreachable_value,
+            .empty_array,
             => unreachable,
 
             .zero,
             .undef,
-            .the_one_possible_value, // an integer with one possible value is always zero
             .bool_false,
             => return true,
 
@@ -797,13 +815,13 @@ pub const Value = extern union {
                 // return Value.initPayload(&res_payload.base).copy(allocator);
             },
             32 => {
-                var res_payload = Value.Payload.Float_32{.val = self.toFloat(f32)};
+                var res_payload = Value.Payload.Float_32{ .val = self.toFloat(f32) };
                 if (!self.eql(Value.initPayload(&res_payload.base)))
                     return error.Overflow;
                 return Value.initPayload(&res_payload.base).copy(allocator);
             },
             64 => {
-                var res_payload = Value.Payload.Float_64{.val = self.toFloat(f64)};
+                var res_payload = Value.Payload.Float_64{ .val = self.toFloat(f64) };
                 if (!self.eql(Value.initPayload(&res_payload.base)))
                     return error.Overflow;
                 return Value.initPayload(&res_payload.base).copy(allocator);
@@ -875,7 +893,9 @@ pub const Value = extern union {
             .int_i64,
             .int_big_positive,
             .int_big_negative,
-            .the_one_possible_value,
+            .empty_array,
+            .void_value,
+            .unreachable_value,
             => unreachable,
 
             .zero => false,
@@ -939,10 +959,12 @@ pub const Value = extern union {
             .bytes,
             .repeated,
             .undef,
+            .void_value,
+            .unreachable_value,
+            .empty_array,
             => unreachable,
 
             .zero,
-            .the_one_possible_value, // an integer with one possible value is always zero
             .bool_false,
             => .eq,
 
@@ -964,8 +986,8 @@ pub const Value = extern union {
     pub fn order(lhs: Value, rhs: Value) std.math.Order {
         const lhs_tag = lhs.tag();
         const rhs_tag = rhs.tag();
-        const lhs_is_zero = lhs_tag == .zero or lhs_tag == .the_one_possible_value;
-        const rhs_is_zero = rhs_tag == .zero or rhs_tag == .the_one_possible_value;
+        const lhs_is_zero = lhs_tag == .zero;
+        const rhs_is_zero = rhs_tag == .zero;
         if (lhs_is_zero) return rhs.orderAgainstZero().invert();
         if (rhs_is_zero) return lhs.orderAgainstZero();
 
@@ -1071,9 +1093,11 @@ pub const Value = extern union {
             .float_32,
             .float_64,
             .float_128,
+            .void_value,
+            .unreachable_value,
+            .empty_array,
             => unreachable,
 
-            .the_one_possible_value => Value.initTag(.the_one_possible_value),
             .ref_val => self.cast(Payload.RefVal).?.val,
             .decl_ref => self.cast(Payload.DeclRef).?.decl.value(),
             .elem_ptr => {
@@ -1130,7 +1154,6 @@ pub const Value = extern union {
             .single_const_pointer_to_comptime_int_type,
             .const_slice_u8_type,
             .zero,
-            .the_one_possible_value,
             .bool_true,
             .bool_false,
             .null_value,
@@ -1147,8 +1170,12 @@ pub const Value = extern union {
             .float_32,
             .float_64,
             .float_128,
+            .void_value,
+            .unreachable_value,
             => unreachable,
 
+            .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] };
@@ -1175,8 +1202,7 @@ pub const Value = extern union {
         return self.tag() == .undef;
     }
 
-    /// Valid for all types. Asserts the value is not undefined.
-    /// `.the_one_possible_value` is reported as not null.
+    /// Valid for all types. Asserts the value is not undefined and not unreachable.
     pub fn isNull(self: Value) bool {
         return switch (self.tag()) {
             .ty,
@@ -1221,7 +1247,7 @@ pub const Value = extern union {
             .single_const_pointer_to_comptime_int_type,
             .const_slice_u8_type,
             .zero,
-            .the_one_possible_value,
+            .empty_array,
             .bool_true,
             .bool_false,
             .function,
@@ -1238,9 +1264,11 @@ pub const Value = extern union {
             .float_32,
             .float_64,
             .float_128,
+            .void_value,
             => false,
 
             .undef => unreachable,
+            .unreachable_value => unreachable,
             .null_value => true,
         };
     }
src-self-hosted/zir.zig
@@ -742,7 +742,7 @@ pub const Inst = struct {
                     .@"false" => .{ .ty = Type.initTag(.bool), .val = Value.initTag(.bool_false) },
                     .@"null" => .{ .ty = Type.initTag(.@"null"), .val = Value.initTag(.null_value) },
                     .@"undefined" => .{ .ty = Type.initTag(.@"undefined"), .val = Value.initTag(.undef) },
-                    .void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.the_one_possible_value) },
+                    .void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.void_value) },
                 };
             }
         };
@@ -1598,6 +1598,21 @@ const EmitZIR = struct {
             const decl = decl_ref.decl;
             return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl));
         }
+        if (typed_value.val.isUndef()) {
+            const as_inst = try self.arena.allocator.create(Inst.BinOp);
+            as_inst.* = .{
+                .base = .{
+                    .tag = .as,
+                    .src = src,
+                },
+                .positionals = .{
+                    .lhs = (try self.emitType(src, typed_value.ty)).inst,
+                    .rhs = (try self.emitPrimitive(src, .@"undefined")).inst,
+                },
+                .kw_args = .{},
+            };
+            return self.emitUnnamedDecl(&as_inst.base);
+        }
         switch (typed_value.ty.zigTypeTag()) {
             .Pointer => {
                 const ptr_elem_type = typed_value.ty.elemType();
src-self-hosted/zir_sema.zig
@@ -882,7 +882,7 @@ fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn
 fn analyzeInstComptimeOp(mod: *Module, scope: *Scope, res_type: Type, inst: *zir.Inst.BinOp, lhs_val: Value, rhs_val: Value) InnerError!*Inst {
     // incase rhs is 0, simply return lhs without doing any calculations
     // TODO Once division is implemented we should throw an error when dividing by 0.
-    if (rhs_val.tag() == .zero or rhs_val.tag() == .the_one_possible_value) {
+    if (rhs_val.compareWithZero(.eq)) {
         return mod.constInst(scope, inst.base.src, .{
             .ty = res_type,
             .val = lhs_val,
@@ -1083,6 +1083,7 @@ fn analyzeInstUnreachNoChk(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp)
 
 fn analyzeInstUnreachable(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst {
     const b = try mod.requireRuntimeBlock(scope, unreach.base.src);
+    // TODO Add compile error for @optimizeFor occurring too late in a scope.
     if (mod.wantSafety(scope)) {
         // TODO Once we have a panic function to call, call it here instead of this.
         _ = try mod.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint);