diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 426b28488f..864cd66d32 100644 --- a/src-self-hosted/Module.zig +++ b/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, + }); + } +} diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 51a59596c4..40fb6c5407 100644 --- a/src-self-hosted/codegen.zig +++ b/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); diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 3965a2ea93..deb0a91cec 100644 --- a/src-self-hosted/ir.zig +++ b/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; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index cde91cdc01..7cd6876cb2 100644 --- a/src-self-hosted/link.zig +++ b/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, diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 729292f6ab..457a69ac6d 100644 --- a/src-self-hosted/type.zig +++ b/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, - .int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0, - .int_signed => return ty.cast(Payload.IntSigned).?.bits == 0, + .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 => { + 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; }, diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 881602d76a..eff7c95be7 100644 --- a/src-self-hosted/value.zig +++ b/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, }; } diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 38751e22a3..318c4bdc8e 100644 --- a/src-self-hosted/zir.zig +++ b/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(); diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index 7db2811384..6bd4159e36 100644 --- a/src-self-hosted/zir_sema.zig +++ b/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);