| author | |
| committer | |
| log | 7634e67ba503fdbdf75daff48a13f9d35e331cd4 |
| tree | 17266638acfc3403956633c8c28a7a4ab871275c |
| parent | c829f2f7b7c5bdd13d3c39ae2960ed108393a210 |
| parent | 9ebf25d1458988b6aa75d4608062f18f802c6a38 |
| signature |
beginnings of (non-LLVM) self-hosted machine code generation and linking8 files changed, 1388 insertions(+), 930 deletions(-)
lib/std/fs.zig+4-2| ... | ... | @@ -1345,8 +1345,10 @@ pub const Dir = struct { |
| 1345 | 1345 | mode: File.Mode = File.default_mode, |
| 1346 | 1346 | }; |
| 1347 | 1347 | |
| 1348 | /// `dest_path` must remain valid for the lifetime of `AtomicFile`. | |
| 1349 | /// Call `AtomicFile.finish` to atomically replace `dest_path` with contents. | |
| 1348 | /// Directly access the `.file` field, and then call `AtomicFile.finish` | |
| 1349 | /// to atomically replace `dest_path` with contents. | |
| 1350 | /// Always call `AtomicFile.deinit` to clean up, regardless of whether `AtomicFile.finish` succeeded. | |
| 1351 | /// `dest_path` must remain valid until `AtomicFile.deinit` is called. | |
| 1350 | 1352 | pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile { |
| 1351 | 1353 | if (path.dirname(dest_path)) |dirname| { |
| 1352 | 1354 | const dir = try self.openDir(dirname, .{}); |
lib/std/fs/file.zig+1-1| ... | ... | @@ -93,7 +93,7 @@ pub const File = struct { |
| 93 | 93 | /// This means that a process that does not respect the locking API can still get access |
| 94 | 94 | /// to the file, despite the lock. |
| 95 | 95 | /// |
| 96 | /// Windows' file locks are mandatory, and any process attempting to access the file will | |
| 96 | /// Windows's file locks are mandatory, and any process attempting to access the file will | |
| 97 | 97 | /// receive an error. |
| 98 | 98 | /// |
| 99 | 99 | /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt |
lib/std/mem.zig+15-3| ... | ... | @@ -2027,7 +2027,13 @@ test "sliceAsBytes and bytesAsSlice back" { |
| 2027 | 2027 | /// Round an address up to the nearest aligned address |
| 2028 | 2028 | /// The alignment must be a power of 2 and greater than 0. |
| 2029 | 2029 | pub fn alignForward(addr: usize, alignment: usize) usize { |
| 2030 | return alignBackward(addr + (alignment - 1), alignment); | |
| 2030 | return alignForwardGeneric(usize, addr, alignment); | |
| 2031 | } | |
| 2032 | ||
| 2033 | /// Round an address up to the nearest aligned address | |
| 2034 | /// The alignment must be a power of 2 and greater than 0. | |
| 2035 | pub fn alignForwardGeneric(comptime T: type, addr: T, alignment: T) T { | |
| 2036 | return alignBackwardGeneric(T, addr + (alignment - 1), alignment); | |
| 2031 | 2037 | } |
| 2032 | 2038 | |
| 2033 | 2039 | test "alignForward" { |
| ... | ... | @@ -2048,8 +2054,14 @@ test "alignForward" { |
| 2048 | 2054 | /// Round an address up to the previous aligned address |
| 2049 | 2055 | /// The alignment must be a power of 2 and greater than 0. |
| 2050 | 2056 | pub fn alignBackward(addr: usize, alignment: usize) usize { |
| 2051 | assert(@popCount(usize, alignment) == 1); | |
| 2052 | // 000010000 // example addr | |
| 2057 | return alignBackwardGeneric(usize, addr, alignment); | |
| 2058 | } | |
| 2059 | ||
| 2060 | /// Round an address up to the previous aligned address | |
| 2061 | /// The alignment must be a power of 2 and greater than 0. | |
| 2062 | pub fn alignBackwardGeneric(comptime T: type, addr: T, alignment: T) T { | |
| 2063 | assert(@popCount(T, alignment) == 1); | |
| 2064 | // 000010000 // example alignment | |
| 2053 | 2065 | // 000001111 // subtract 1 |
| 2054 | 2066 | // 111110000 // binary not |
| 2055 | 2067 | return addr & ~(alignment - 1); |
src-self-hosted/codegen.zig+482-405| ... | ... | @@ -1,447 +1,524 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Compilation = @import("compilation.zig").Compilation; | |
| 3 | const llvm = @import("llvm.zig"); | |
| 4 | const c = @import("c.zig"); | |
| 2 | const mem = std.mem; | |
| 3 | const assert = std.debug.assert; | |
| 5 | 4 | const ir = @import("ir.zig"); |
| 6 | const Value = @import("value.zig").Value; | |
| 7 | 5 | const Type = @import("type.zig").Type; |
| 8 | const Scope = @import("scope.zig").Scope; | |
| 9 | const util = @import("util.zig"); | |
| 10 | const event = std.event; | |
| 11 | const assert = std.debug.assert; | |
| 12 | const DW = std.dwarf; | |
| 13 | const maxInt = std.math.maxInt; | |
| 14 | ||
| 15 | pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) Compilation.BuildError!void { | |
| 16 | fn_val.base.ref(); | |
| 17 | defer fn_val.base.deref(comp); | |
| 18 | defer code.destroy(comp.gpa()); | |
| 19 | ||
| 20 | var output_path = try comp.createRandomOutputPath(comp.target.oFileExt()); | |
| 21 | errdefer output_path.deinit(); | |
| 22 | ||
| 23 | const llvm_handle = try comp.zig_compiler.getAnyLlvmContext(); | |
| 24 | defer llvm_handle.release(comp.zig_compiler); | |
| 6 | const Value = @import("value.zig").Value; | |
| 7 | const Target = std.Target; | |
| 25 | 8 | |
| 26 | const context = llvm_handle.node.data; | |
| 9 | pub const ErrorMsg = struct { | |
| 10 | byte_offset: usize, | |
| 11 | msg: []const u8, | |
| 12 | }; | |
| 27 | 13 | |
| 28 | const module = llvm.ModuleCreateWithNameInContext(comp.name.span(), context) orelse return error.OutOfMemory; | |
| 29 | defer llvm.DisposeModule(module); | |
| 14 | pub const Symbol = struct { | |
| 15 | errors: []ErrorMsg, | |
| 30 | 16 | |
| 31 | llvm.SetTarget(module, comp.llvm_triple.span()); | |
| 32 | llvm.SetDataLayout(module, comp.target_layout_str); | |
| 17 | pub fn deinit(self: *Symbol, allocator: *mem.Allocator) void { | |
| 18 | for (self.errors) |err| { | |
| 19 | allocator.free(err.msg); | |
| 20 | } | |
| 21 | allocator.free(self.errors); | |
| 22 | self.* = undefined; | |
| 23 | } | |
| 24 | }; | |
| 33 | 25 | |
| 34 | if (comp.target.getObjectFormat() == .coff) { | |
| 35 | llvm.AddModuleCodeViewFlag(module); | |
| 36 | } else { | |
| 37 | llvm.AddModuleDebugInfoFlag(module); | |
| 26 | pub fn generateSymbol(typed_value: ir.TypedValue, module: ir.Module, code: *std.ArrayList(u8)) !Symbol { | |
| 27 | switch (typed_value.ty.zigTypeTag()) { | |
| 28 | .Fn => { | |
| 29 | const index = typed_value.val.cast(Value.Payload.Function).?.index; | |
| 30 | const module_fn = module.fns[index]; | |
| 31 | ||
| 32 | var function = Function{ | |
| 33 | .module = &module, | |
| 34 | .mod_fn = &module_fn, | |
| 35 | .code = code, | |
| 36 | .inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(code.allocator), | |
| 37 | .errors = std.ArrayList(ErrorMsg).init(code.allocator), | |
| 38 | }; | |
| 39 | defer function.inst_table.deinit(); | |
| 40 | defer function.errors.deinit(); | |
| 41 | ||
| 42 | for (module_fn.body) |inst| { | |
| 43 | const new_inst = function.genFuncInst(inst) catch |err| switch (err) { | |
| 44 | error.CodegenFail => { | |
| 45 | assert(function.errors.items.len != 0); | |
| 46 | break; | |
| 47 | }, | |
| 48 | else => |e| return e, | |
| 49 | }; | |
| 50 | try function.inst_table.putNoClobber(inst, new_inst); | |
| 51 | } | |
| 52 | ||
| 53 | return Symbol{ .errors = function.errors.toOwnedSlice() }; | |
| 54 | }, | |
| 55 | else => @panic("TODO implement generateSymbol for non-function types"), | |
| 38 | 56 | } |
| 57 | } | |
| 39 | 58 | |
| 40 | const builder = llvm.CreateBuilderInContext(context) orelse return error.OutOfMemory; | |
| 41 | defer llvm.DisposeBuilder(builder); | |
| 42 | ||
| 43 | const dibuilder = llvm.CreateDIBuilder(module, true) orelse return error.OutOfMemory; | |
| 44 | defer llvm.DisposeDIBuilder(dibuilder); | |
| 45 | ||
| 46 | // Don't use ZIG_VERSION_STRING here. LLVM misparses it when it includes | |
| 47 | // the git revision. | |
| 48 | const producer = try std.fmt.allocPrintZ(&code.arena.allocator, "zig {}.{}.{}", .{ | |
| 49 | @as(u32, c.ZIG_VERSION_MAJOR), | |
| 50 | @as(u32, c.ZIG_VERSION_MINOR), | |
| 51 | @as(u32, c.ZIG_VERSION_PATCH), | |
| 52 | }); | |
| 53 | const flags = ""; | |
| 54 | const runtime_version = 0; | |
| 55 | const compile_unit_file = llvm.CreateFile( | |
| 56 | dibuilder, | |
| 57 | comp.name.span(), | |
| 58 | comp.root_package.root_src_dir.span(), | |
| 59 | ) orelse return error.OutOfMemory; | |
| 60 | const is_optimized = comp.build_mode != .Debug; | |
| 61 | const compile_unit = llvm.CreateCompileUnit( | |
| 62 | dibuilder, | |
| 63 | DW.LANG_C99, | |
| 64 | compile_unit_file, | |
| 65 | producer, | |
| 66 | is_optimized, | |
| 67 | flags, | |
| 68 | runtime_version, | |
| 69 | "", | |
| 70 | 0, | |
| 71 | !comp.strip, | |
| 72 | ) orelse return error.OutOfMemory; | |
| 73 | ||
| 74 | var ofile = ObjectFile{ | |
| 75 | .comp = comp, | |
| 76 | .module = module, | |
| 77 | .builder = builder, | |
| 78 | .dibuilder = dibuilder, | |
| 79 | .context = context, | |
| 80 | .lock = event.Lock.init(), | |
| 81 | .arena = &code.arena.allocator, | |
| 59 | const Function = struct { | |
| 60 | module: *const ir.Module, | |
| 61 | mod_fn: *const ir.Module.Fn, | |
| 62 | code: *std.ArrayList(u8), | |
| 63 | inst_table: std.AutoHashMap(*ir.Inst, MCValue), | |
| 64 | errors: std.ArrayList(ErrorMsg), | |
| 65 | ||
| 66 | const MCValue = union(enum) { | |
| 67 | none, | |
| 68 | unreach, | |
| 69 | /// A pointer-sized integer that fits in a register. | |
| 70 | immediate: u64, | |
| 71 | /// The constant was emitted into the code, at this offset. | |
| 72 | embedded_in_code: usize, | |
| 73 | /// The value is in a target-specific register. The value can | |
| 74 | /// be @intToEnum casted to the respective Reg enum. | |
| 75 | register: usize, | |
| 82 | 76 | }; |
| 83 | 77 | |
| 84 | try renderToLlvmModule(&ofile, fn_val, code); | |
| 85 | ||
| 86 | // TODO module level assembly | |
| 87 | //if (buf_len(&g->global_asm) != 0) { | |
| 88 | // LLVMSetModuleInlineAsm(g->module, buf_ptr(&g->global_asm)); | |
| 89 | //} | |
| 90 | ||
| 91 | llvm.DIBuilderFinalize(dibuilder); | |
| 92 | ||
| 93 | if (comp.verbose_llvm_ir) { | |
| 94 | std.debug.warn("raw module:\n", .{}); | |
| 95 | llvm.DumpModule(ofile.module); | |
| 78 | fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue { | |
| 79 | switch (inst.tag) { | |
| 80 | .unreach => return self.genPanic(inst.src), | |
| 81 | .constant => unreachable, // excluded from function bodies | |
| 82 | .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?), | |
| 83 | .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?), | |
| 84 | .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), | |
| 85 | } | |
| 96 | 86 | } |
| 97 | 87 | |
| 98 | // verify the llvm module when safety is on | |
| 99 | if (std.debug.runtime_safety) { | |
| 100 | var error_ptr: ?[*:0]u8 = null; | |
| 101 | _ = llvm.VerifyModule(ofile.module, llvm.AbortProcessAction, &error_ptr); | |
| 88 | fn genPanic(self: *Function, src: usize) !MCValue { | |
| 89 | // TODO change this to call the panic function | |
| 90 | switch (self.module.target.cpu.arch) { | |
| 91 | .i386, .x86_64 => { | |
| 92 | try self.code.append(0xcc); // int3 | |
| 93 | }, | |
| 94 | else => return self.fail(src, "TODO implement panic for {}", .{self.module.target.cpu.arch}), | |
| 95 | } | |
| 96 | return .unreach; | |
| 102 | 97 | } |
| 103 | 98 | |
| 104 | const is_small = comp.build_mode == .ReleaseSmall; | |
| 105 | const is_debug = comp.build_mode == .Debug; | |
| 106 | ||
| 107 | var err_msg: [*:0]u8 = undefined; | |
| 108 | // TODO integrate this with evented I/O | |
| 109 | if (llvm.TargetMachineEmitToFile( | |
| 110 | comp.target_machine, | |
| 111 | module, | |
| 112 | output_path.span(), | |
| 113 | llvm.EmitBinary, | |
| 114 | &err_msg, | |
| 115 | is_debug, | |
| 116 | is_small, | |
| 117 | )) { | |
| 118 | if (std.debug.runtime_safety) { | |
| 119 | std.debug.panic("unable to write object file {}: {s}\n", .{ output_path.span(), err_msg }); | |
| 99 | fn genRet(self: *Function, src: usize) !void { | |
| 100 | // TODO change this to call the panic function | |
| 101 | switch (self.module.target.cpu.arch) { | |
| 102 | .i386, .x86_64 => { | |
| 103 | try self.code.append(0xc3); // ret | |
| 104 | }, | |
| 105 | else => return self.fail(src, "TODO implement ret for {}", .{self.module.target.cpu.arch}), | |
| 120 | 106 | } |
| 121 | return error.WritingObjectFileFailed; | |
| 122 | } | |
| 123 | //validate_inline_fns(g); TODO | |
| 124 | fn_val.containing_object = output_path; | |
| 125 | if (comp.verbose_llvm_ir) { | |
| 126 | std.debug.warn("optimized module:\n", .{}); | |
| 127 | llvm.DumpModule(ofile.module); | |
| 128 | } | |
| 129 | if (comp.verbose_link) { | |
| 130 | std.debug.warn("created {}\n", .{output_path.span()}); | |
| 131 | 107 | } |
| 132 | } | |
| 133 | 108 | |
| 134 | pub const ObjectFile = struct { | |
| 135 | comp: *Compilation, | |
| 136 | module: *llvm.Module, | |
| 137 | builder: *llvm.Builder, | |
| 138 | dibuilder: *llvm.DIBuilder, | |
| 139 | context: *llvm.Context, | |
| 140 | lock: event.Lock, | |
| 141 | arena: *std.mem.Allocator, | |
| 142 | ||
| 143 | fn gpa(self: *ObjectFile) *std.mem.Allocator { | |
| 144 | return self.comp.gpa(); | |
| 109 | fn genRelativeFwdJump(self: *Function, src: usize, amount: u32) !void { | |
| 110 | switch (self.module.target.cpu.arch) { | |
| 111 | .i386, .x86_64 => { | |
| 112 | if (amount <= std.math.maxInt(u8)) { | |
| 113 | try self.code.resize(self.code.items.len + 2); | |
| 114 | self.code.items[self.code.items.len - 2] = 0xeb; | |
| 115 | self.code.items[self.code.items.len - 1] = @intCast(u8, amount); | |
| 116 | } else { | |
| 117 | try self.code.resize(self.code.items.len + 5); | |
| 118 | self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 | |
| 119 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | |
| 120 | mem.writeIntLittle(u32, imm_ptr, amount); | |
| 121 | } | |
| 122 | }, | |
| 123 | else => return self.fail(src, "TODO implement relative forward jump for {}", .{self.module.target.cpu.arch}), | |
| 124 | } | |
| 145 | 125 | } |
| 146 | }; | |
| 147 | 126 | |
| 148 | pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) !void { | |
| 149 | // TODO audit more of codegen.cpp:fn_llvm_value and port more logic | |
| 150 | const llvm_fn_type = try fn_val.base.typ.getLlvmType(ofile.arena, ofile.context); | |
| 151 | const llvm_fn = llvm.AddFunction( | |
| 152 | ofile.module, | |
| 153 | fn_val.symbol_name.span(), | |
| 154 | llvm_fn_type, | |
| 155 | ) orelse return error.OutOfMemory; | |
| 156 | ||
| 157 | const want_fn_safety = fn_val.block_scope.?.safety.get(ofile.comp); | |
| 158 | if (want_fn_safety and ofile.comp.haveLibC()) { | |
| 159 | try addLLVMFnAttr(ofile, llvm_fn, "sspstrong"); | |
| 160 | try addLLVMFnAttrStr(ofile, llvm_fn, "stack-protector-buffer-size", "4"); | |
| 127 | fn genAsm(self: *Function, inst: *ir.Inst.Assembly) !MCValue { | |
| 128 | // TODO convert to inline function | |
| 129 | switch (self.module.target.cpu.arch) { | |
| 130 | .arm => return self.genAsmArch(.arm, inst), | |
| 131 | .armeb => return self.genAsmArch(.armeb, inst), | |
| 132 | .aarch64 => return self.genAsmArch(.aarch64, inst), | |
| 133 | .aarch64_be => return self.genAsmArch(.aarch64_be, inst), | |
| 134 | .aarch64_32 => return self.genAsmArch(.aarch64_32, inst), | |
| 135 | .arc => return self.genAsmArch(.arc, inst), | |
| 136 | .avr => return self.genAsmArch(.avr, inst), | |
| 137 | .bpfel => return self.genAsmArch(.bpfel, inst), | |
| 138 | .bpfeb => return self.genAsmArch(.bpfeb, inst), | |
| 139 | .hexagon => return self.genAsmArch(.hexagon, inst), | |
| 140 | .mips => return self.genAsmArch(.mips, inst), | |
| 141 | .mipsel => return self.genAsmArch(.mipsel, inst), | |
| 142 | .mips64 => return self.genAsmArch(.mips64, inst), | |
| 143 | .mips64el => return self.genAsmArch(.mips64el, inst), | |
| 144 | .msp430 => return self.genAsmArch(.msp430, inst), | |
| 145 | .powerpc => return self.genAsmArch(.powerpc, inst), | |
| 146 | .powerpc64 => return self.genAsmArch(.powerpc64, inst), | |
| 147 | .powerpc64le => return self.genAsmArch(.powerpc64le, inst), | |
| 148 | .r600 => return self.genAsmArch(.r600, inst), | |
| 149 | .amdgcn => return self.genAsmArch(.amdgcn, inst), | |
| 150 | .riscv32 => return self.genAsmArch(.riscv32, inst), | |
| 151 | .riscv64 => return self.genAsmArch(.riscv64, inst), | |
| 152 | .sparc => return self.genAsmArch(.sparc, inst), | |
| 153 | .sparcv9 => return self.genAsmArch(.sparcv9, inst), | |
| 154 | .sparcel => return self.genAsmArch(.sparcel, inst), | |
| 155 | .s390x => return self.genAsmArch(.s390x, inst), | |
| 156 | .tce => return self.genAsmArch(.tce, inst), | |
| 157 | .tcele => return self.genAsmArch(.tcele, inst), | |
| 158 | .thumb => return self.genAsmArch(.thumb, inst), | |
| 159 | .thumbeb => return self.genAsmArch(.thumbeb, inst), | |
| 160 | .i386 => return self.genAsmArch(.i386, inst), | |
| 161 | .x86_64 => return self.genAsmArch(.x86_64, inst), | |
| 162 | .xcore => return self.genAsmArch(.xcore, inst), | |
| 163 | .nvptx => return self.genAsmArch(.nvptx, inst), | |
| 164 | .nvptx64 => return self.genAsmArch(.nvptx64, inst), | |
| 165 | .le32 => return self.genAsmArch(.le32, inst), | |
| 166 | .le64 => return self.genAsmArch(.le64, inst), | |
| 167 | .amdil => return self.genAsmArch(.amdil, inst), | |
| 168 | .amdil64 => return self.genAsmArch(.amdil64, inst), | |
| 169 | .hsail => return self.genAsmArch(.hsail, inst), | |
| 170 | .hsail64 => return self.genAsmArch(.hsail64, inst), | |
| 171 | .spir => return self.genAsmArch(.spir, inst), | |
| 172 | .spir64 => return self.genAsmArch(.spir64, inst), | |
| 173 | .kalimba => return self.genAsmArch(.kalimba, inst), | |
| 174 | .shave => return self.genAsmArch(.shave, inst), | |
| 175 | .lanai => return self.genAsmArch(.lanai, inst), | |
| 176 | .wasm32 => return self.genAsmArch(.wasm32, inst), | |
| 177 | .wasm64 => return self.genAsmArch(.wasm64, inst), | |
| 178 | .renderscript32 => return self.genAsmArch(.renderscript32, inst), | |
| 179 | .renderscript64 => return self.genAsmArch(.renderscript64, inst), | |
| 180 | .ve => return self.genAsmArch(.ve, inst), | |
| 181 | } | |
| 161 | 182 | } |
| 162 | 183 | |
| 163 | // TODO | |
| 164 | //if (fn_val.align_stack) |align_stack| { | |
| 165 | // try addLLVMFnAttrInt(ofile, llvm_fn, "alignstack", align_stack); | |
| 166 | //} | |
| 167 | ||
| 168 | const fn_type = fn_val.base.typ.cast(Type.Fn).?; | |
| 169 | const fn_type_normal = &fn_type.key.data.Normal; | |
| 170 | ||
| 171 | try addLLVMFnAttr(ofile, llvm_fn, "nounwind"); | |
| 172 | //add_uwtable_attr(g, fn_table_entry->llvm_value); | |
| 173 | try addLLVMFnAttr(ofile, llvm_fn, "nobuiltin"); | |
| 174 | ||
| 175 | //if (g->build_mode == BuildModeDebug && fn_table_entry->fn_inline != FnInlineAlways) { | |
| 176 | // ZigLLVMAddFunctionAttr(fn_table_entry->llvm_value, "no-frame-pointer-elim", "true"); | |
| 177 | // ZigLLVMAddFunctionAttr(fn_table_entry->llvm_value, "no-frame-pointer-elim-non-leaf", nullptr); | |
| 178 | //} | |
| 179 | ||
| 180 | //if (fn_table_entry->section_name) { | |
| 181 | // LLVMSetSection(fn_table_entry->llvm_value, buf_ptr(fn_table_entry->section_name)); | |
| 182 | //} | |
| 183 | //if (fn_table_entry->align_bytes > 0) { | |
| 184 | // LLVMSetAlignment(fn_table_entry->llvm_value, (unsigned)fn_table_entry->align_bytes); | |
| 185 | //} else { | |
| 186 | // // We'd like to set the best alignment for the function here, but on Darwin LLVM gives | |
| 187 | // // "Cannot getTypeInfo() on a type that is unsized!" assertion failure when calling | |
| 188 | // // any of the functions for getting alignment. Not specifying the alignment should | |
| 189 | // // use the ABI alignment, which is fine. | |
| 190 | //} | |
| 191 | ||
| 192 | //if (!type_has_bits(return_type)) { | |
| 193 | // // nothing to do | |
| 194 | //} else if (type_is_codegen_pointer(return_type)) { | |
| 195 | // addLLVMAttr(fn_table_entry->llvm_value, 0, "nonnull"); | |
| 196 | //} else if (handle_is_ptr(return_type) && | |
| 197 | // calling_convention_does_first_arg_return(fn_type->data.fn.fn_type_id.cc)) | |
| 198 | //{ | |
| 199 | // addLLVMArgAttr(fn_table_entry->llvm_value, 0, "sret"); | |
| 200 | // addLLVMArgAttr(fn_table_entry->llvm_value, 0, "nonnull"); | |
| 201 | //} | |
| 202 | ||
| 203 | // TODO set parameter attributes | |
| 204 | ||
| 205 | // TODO | |
| 206 | //uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(g, fn_table_entry); | |
| 207 | //if (err_ret_trace_arg_index != UINT32_MAX) { | |
| 208 | // addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)err_ret_trace_arg_index, "nonnull"); | |
| 209 | //} | |
| 210 | ||
| 211 | const cur_ret_ptr = if (fn_type_normal.return_type.handleIsPtr()) llvm.GetParam(llvm_fn, 0) else null; | |
| 212 | ||
| 213 | // build all basic blocks | |
| 214 | for (code.basic_block_list.span()) |bb| { | |
| 215 | bb.llvm_block = llvm.AppendBasicBlockInContext( | |
| 216 | ofile.context, | |
| 217 | llvm_fn, | |
| 218 | bb.name_hint, | |
| 219 | ) orelse return error.OutOfMemory; | |
| 220 | } | |
| 221 | const entry_bb = code.basic_block_list.at(0); | |
| 222 | llvm.PositionBuilderAtEnd(ofile.builder, entry_bb.llvm_block); | |
| 223 | ||
| 224 | llvm.ClearCurrentDebugLocation(ofile.builder); | |
| 225 | ||
| 226 | // TODO set up error return tracing | |
| 227 | // TODO allocate temporary stack values | |
| 228 | ||
| 229 | const var_list = fn_type.non_key.Normal.variable_list.span(); | |
| 230 | // create debug variable declarations for variables and allocate all local variables | |
| 231 | for (var_list) |var_scope, i| { | |
| 232 | const var_type = switch (var_scope.data) { | |
| 233 | .Const => unreachable, | |
| 234 | .Param => |param| param.typ, | |
| 235 | }; | |
| 236 | // if (!type_has_bits(var->value->type)) { | |
| 237 | // continue; | |
| 238 | // } | |
| 239 | // if (ir_get_var_is_comptime(var)) | |
| 240 | // continue; | |
| 241 | // if (type_requires_comptime(var->value->type)) | |
| 242 | // continue; | |
| 243 | // if (var->src_arg_index == SIZE_MAX) { | |
| 244 | // var->value_ref = build_alloca(g, var->value->type, buf_ptr(&var->name), var->align_bytes); | |
| 245 | ||
| 246 | // var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope), | |
| 247 | // buf_ptr(&var->name), import->di_file, (unsigned)(var->decl_node->line + 1), | |
| 248 | // var->value->type->di_type, !g->strip_debug_symbols, 0); | |
| 249 | ||
| 250 | // } else { | |
| 251 | // it's a parameter | |
| 252 | // assert(var->gen_arg_index != SIZE_MAX); | |
| 253 | // TypeTableEntry *gen_type; | |
| 254 | // FnGenParamInfo *gen_info = &fn_table_entry->type_entry->data.fn.gen_param_info[var->src_arg_index]; | |
| 255 | ||
| 256 | if (var_type.handleIsPtr()) { | |
| 257 | // if (gen_info->is_byval) { | |
| 258 | // gen_type = var->value->type; | |
| 259 | // } else { | |
| 260 | // gen_type = gen_info->type; | |
| 261 | // } | |
| 262 | var_scope.data.Param.llvm_value = llvm.GetParam(llvm_fn, @intCast(c_uint, i)); | |
| 263 | } else { | |
| 264 | // gen_type = var->value->type; | |
| 265 | var_scope.data.Param.llvm_value = try renderAlloca(ofile, var_type, var_scope.name, .Abi); | |
| 184 | fn genAsmArch(self: *Function, comptime arch: Target.Cpu.Arch, inst: *ir.Inst.Assembly) !MCValue { | |
| 185 | if (arch != .x86_64 and arch != .i386) { | |
| 186 | return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{}); | |
| 187 | } | |
| 188 | for (inst.args.inputs) |input, i| { | |
| 189 | if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { | |
| 190 | return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); | |
| 191 | } | |
| 192 | const reg_name = input[1 .. input.len - 1]; | |
| 193 | const reg = parseRegName(arch, reg_name) orelse | |
| 194 | return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); | |
| 195 | const arg = try self.resolveInst(inst.args.args[i]); | |
| 196 | try self.genSetReg(inst.base.src, arch, reg, arg); | |
| 266 | 197 | } |
| 267 | // if (var->decl_node) { | |
| 268 | // var->di_loc_var = ZigLLVMCreateParameterVariable(g->dbuilder, get_di_scope(g, var->parent_scope), | |
| 269 | // buf_ptr(&var->name), import->di_file, | |
| 270 | // (unsigned)(var->decl_node->line + 1), | |
| 271 | // gen_type->di_type, !g->strip_debug_symbols, 0, (unsigned)(var->gen_arg_index + 1)); | |
| 272 | // } | |
| 273 | ||
| 274 | // } | |
| 275 | } | |
| 276 | 198 | |
| 277 | // TODO finishing error return trace setup. we have to do this after all the allocas. | |
| 278 | ||
| 279 | // create debug variable declarations for parameters | |
| 280 | // rely on the first variables in the variable_list being parameters. | |
| 281 | //size_t next_var_i = 0; | |
| 282 | for (fn_type.key.data.Normal.params) |param, i| { | |
| 283 | //FnGenParamInfo *info = &fn_table_entry->type_entry->data.fn.gen_param_info[param_i]; | |
| 284 | //if (info->gen_index == SIZE_MAX) | |
| 285 | // continue; | |
| 286 | const scope_var = var_list[i]; | |
| 287 | //assert(variable->src_arg_index != SIZE_MAX); | |
| 288 | //next_var_i += 1; | |
| 289 | //assert(variable); | |
| 290 | //assert(variable->value_ref); | |
| 291 | ||
| 292 | if (!param.typ.handleIsPtr()) { | |
| 293 | //clear_debug_source_node(g); | |
| 294 | const llvm_param = llvm.GetParam(llvm_fn, @intCast(c_uint, i)); | |
| 295 | _ = try renderStoreUntyped( | |
| 296 | ofile, | |
| 297 | llvm_param, | |
| 298 | scope_var.data.Param.llvm_value, | |
| 299 | .Abi, | |
| 300 | .Non, | |
| 301 | ); | |
| 199 | if (mem.eql(u8, inst.args.asm_source, "syscall")) { | |
| 200 | try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 }); | |
| 201 | } else { | |
| 202 | return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{}); | |
| 302 | 203 | } |
| 303 | 204 | |
| 304 | //if (variable->decl_node) { | |
| 305 | // gen_var_debug_decl(g, variable); | |
| 306 | //} | |
| 205 | if (inst.args.output) |output| { | |
| 206 | if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { | |
| 207 | return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); | |
| 208 | } | |
| 209 | const reg_name = output[2 .. output.len - 1]; | |
| 210 | const reg = parseRegName(arch, reg_name) orelse | |
| 211 | return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); | |
| 212 | return MCValue{ .register = @enumToInt(reg) }; | |
| 213 | } else { | |
| 214 | return MCValue.none; | |
| 215 | } | |
| 307 | 216 | } |
| 308 | 217 | |
| 309 | for (code.basic_block_list.span()) |current_block| { | |
| 310 | llvm.PositionBuilderAtEnd(ofile.builder, current_block.llvm_block); | |
| 311 | for (current_block.instruction_list.span()) |instruction| { | |
| 312 | if (instruction.ref_count == 0 and !instruction.hasSideEffects()) continue; | |
| 313 | ||
| 314 | instruction.llvm_value = try instruction.render(ofile, fn_val); | |
| 218 | fn genSetReg(self: *Function, src: usize, comptime arch: Target.Cpu.Arch, reg: Reg(arch), mcv: MCValue) !void { | |
| 219 | switch (arch) { | |
| 220 | .x86_64 => switch (reg) { | |
| 221 | .rax => switch (mcv) { | |
| 222 | .none, .unreach => unreachable, | |
| 223 | .immediate => |x| { | |
| 224 | // Setting the eax register zeroes the upper part of rax, so if the number is small | |
| 225 | // enough, that is preferable. | |
| 226 | // Best case: zero | |
| 227 | // 31 c0 xor eax,eax | |
| 228 | if (x == 0) { | |
| 229 | return self.code.appendSlice(&[_]u8{ 0x31, 0xc0 }); | |
| 230 | } | |
| 231 | // Next best case: set eax with 4 bytes | |
| 232 | // b8 04 03 02 01 mov eax,0x01020304 | |
| 233 | if (x <= std.math.maxInt(u32)) { | |
| 234 | try self.code.resize(self.code.items.len + 5); | |
| 235 | self.code.items[self.code.items.len - 5] = 0xb8; | |
| 236 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | |
| 237 | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); | |
| 238 | return; | |
| 239 | } | |
| 240 | // Worst case: set rax with 8 bytes | |
| 241 | // 48 b8 08 07 06 05 04 03 02 01 movabs rax,0x0102030405060708 | |
| 242 | try self.code.resize(self.code.items.len + 10); | |
| 243 | self.code.items[self.code.items.len - 10] = 0x48; | |
| 244 | self.code.items[self.code.items.len - 9] = 0xb8; | |
| 245 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; | |
| 246 | mem.writeIntLittle(u64, imm_ptr, x); | |
| 247 | return; | |
| 248 | }, | |
| 249 | .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rax = embedded_in_code", .{}), | |
| 250 | .register => return self.fail(src, "TODO implement x86_64 genSetReg %rax = register", .{}), | |
| 251 | }, | |
| 252 | .rdx => switch (mcv) { | |
| 253 | .none, .unreach => unreachable, | |
| 254 | .immediate => |x| { | |
| 255 | // Setting the edx register zeroes the upper part of rdx, so if the number is small | |
| 256 | // enough, that is preferable. | |
| 257 | // Best case: zero | |
| 258 | // 31 d2 xor edx,edx | |
| 259 | if (x == 0) { | |
| 260 | return self.code.appendSlice(&[_]u8{ 0x31, 0xd2 }); | |
| 261 | } | |
| 262 | // Next best case: set edx with 4 bytes | |
| 263 | // ba 04 03 02 01 mov edx,0x1020304 | |
| 264 | if (x <= std.math.maxInt(u32)) { | |
| 265 | try self.code.resize(self.code.items.len + 5); | |
| 266 | self.code.items[self.code.items.len - 5] = 0xba; | |
| 267 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | |
| 268 | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); | |
| 269 | return; | |
| 270 | } | |
| 271 | // Worst case: set rdx with 8 bytes | |
| 272 | // 48 ba 08 07 06 05 04 03 02 01 movabs rdx,0x0102030405060708 | |
| 273 | try self.code.resize(self.code.items.len + 10); | |
| 274 | self.code.items[self.code.items.len - 10] = 0x48; | |
| 275 | self.code.items[self.code.items.len - 9] = 0xba; | |
| 276 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; | |
| 277 | mem.writeIntLittle(u64, imm_ptr, x); | |
| 278 | return; | |
| 279 | }, | |
| 280 | .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rdx = embedded_in_code", .{}), | |
| 281 | .register => return self.fail(src, "TODO implement x86_64 genSetReg %rdx = register", .{}), | |
| 282 | }, | |
| 283 | .rdi => switch (mcv) { | |
| 284 | .none, .unreach => unreachable, | |
| 285 | .immediate => |x| { | |
| 286 | // Setting the edi register zeroes the upper part of rdi, so if the number is small | |
| 287 | // enough, that is preferable. | |
| 288 | // Best case: zero | |
| 289 | // 31 ff xor edi,edi | |
| 290 | if (x == 0) { | |
| 291 | return self.code.appendSlice(&[_]u8{ 0x31, 0xff }); | |
| 292 | } | |
| 293 | // Next best case: set edi with 4 bytes | |
| 294 | // bf 04 03 02 01 mov edi,0x1020304 | |
| 295 | if (x <= std.math.maxInt(u32)) { | |
| 296 | try self.code.resize(self.code.items.len + 5); | |
| 297 | self.code.items[self.code.items.len - 5] = 0xbf; | |
| 298 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | |
| 299 | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); | |
| 300 | return; | |
| 301 | } | |
| 302 | // Worst case: set rdi with 8 bytes | |
| 303 | // 48 bf 08 07 06 05 04 03 02 01 movabs rax,0x0102030405060708 | |
| 304 | try self.code.resize(self.code.items.len + 10); | |
| 305 | self.code.items[self.code.items.len - 10] = 0x48; | |
| 306 | self.code.items[self.code.items.len - 9] = 0xbf; | |
| 307 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; | |
| 308 | mem.writeIntLittle(u64, imm_ptr, x); | |
| 309 | return; | |
| 310 | }, | |
| 311 | .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rdi = embedded_in_code", .{}), | |
| 312 | .register => return self.fail(src, "TODO implement x86_64 genSetReg %rdi = register", .{}), | |
| 313 | }, | |
| 314 | .rsi => switch (mcv) { | |
| 315 | .none, .unreach => unreachable, | |
| 316 | .immediate => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = immediate", .{}), | |
| 317 | .embedded_in_code => |code_offset| { | |
| 318 | // Examples: | |
| 319 | // lea rsi, [rip + 0x01020304] | |
| 320 | // lea rsi, [rip - 7] | |
| 321 | // f: 48 8d 35 04 03 02 01 lea rsi,[rip+0x1020304] # 102031a <_start+0x102031a> | |
| 322 | // 16: 48 8d 35 f9 ff ff ff lea rsi,[rip+0xfffffffffffffff9] # 16 <_start+0x16> | |
| 323 | // | |
| 324 | // We need the offset from RIP in a signed i32 twos complement. | |
| 325 | // The instruction is 7 bytes long and RIP points to the next instruction. | |
| 326 | try self.code.resize(self.code.items.len + 7); | |
| 327 | const rip = self.code.items.len; | |
| 328 | const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); | |
| 329 | const offset = @intCast(i32, big_offset); | |
| 330 | self.code.items[self.code.items.len - 7] = 0x48; | |
| 331 | self.code.items[self.code.items.len - 6] = 0x8d; | |
| 332 | self.code.items[self.code.items.len - 5] = 0x35; | |
| 333 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | |
| 334 | mem.writeIntLittle(i32, imm_ptr, offset); | |
| 335 | return; | |
| 336 | }, | |
| 337 | .register => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = register", .{}), | |
| 338 | }, | |
| 339 | else => return self.fail(src, "TODO implement genSetReg for x86_64 '{}'", .{@tagName(reg)}), | |
| 340 | }, | |
| 341 | else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}), | |
| 315 | 342 | } |
| 316 | current_block.llvm_exit_block = llvm.GetInsertBlock(ofile.builder); | |
| 317 | 343 | } |
| 318 | } | |
| 319 | ||
| 320 | fn addLLVMAttr( | |
| 321 | ofile: *ObjectFile, | |
| 322 | val: *llvm.Value, | |
| 323 | attr_index: llvm.AttributeIndex, | |
| 324 | attr_name: []const u8, | |
| 325 | ) !void { | |
| 326 | const kind_id = llvm.GetEnumAttributeKindForName(attr_name.ptr, attr_name.len); | |
| 327 | assert(kind_id != 0); | |
| 328 | const llvm_attr = llvm.CreateEnumAttribute(ofile.context, kind_id, 0) orelse return error.OutOfMemory; | |
| 329 | llvm.AddAttributeAtIndex(val, attr_index, llvm_attr); | |
| 330 | } | |
| 331 | ||
| 332 | fn addLLVMAttrStr( | |
| 333 | ofile: *ObjectFile, | |
| 334 | val: *llvm.Value, | |
| 335 | attr_index: llvm.AttributeIndex, | |
| 336 | attr_name: []const u8, | |
| 337 | attr_val: []const u8, | |
| 338 | ) !void { | |
| 339 | const llvm_attr = llvm.CreateStringAttribute( | |
| 340 | ofile.context, | |
| 341 | attr_name.ptr, | |
| 342 | @intCast(c_uint, attr_name.len), | |
| 343 | attr_val.ptr, | |
| 344 | @intCast(c_uint, attr_val.len), | |
| 345 | ) orelse return error.OutOfMemory; | |
| 346 | llvm.AddAttributeAtIndex(val, attr_index, llvm_attr); | |
| 347 | } | |
| 348 | ||
| 349 | fn addLLVMAttrInt( | |
| 350 | val: *llvm.Value, | |
| 351 | attr_index: llvm.AttributeIndex, | |
| 352 | attr_name: []const u8, | |
| 353 | attr_val: u64, | |
| 354 | ) !void { | |
| 355 | const kind_id = llvm.GetEnumAttributeKindForName(attr_name.ptr, attr_name.len); | |
| 356 | assert(kind_id != 0); | |
| 357 | const llvm_attr = llvm.CreateEnumAttribute(ofile.context, kind_id, attr_val) orelse return error.OutOfMemory; | |
| 358 | llvm.AddAttributeAtIndex(val, attr_index, llvm_attr); | |
| 359 | } | |
| 360 | 344 | |
| 361 | fn addLLVMFnAttr(ofile: *ObjectFile, fn_val: *llvm.Value, attr_name: []const u8) !void { | |
| 362 | return addLLVMAttr(ofile, fn_val, maxInt(llvm.AttributeIndex), attr_name); | |
| 363 | } | |
| 364 | ||
| 365 | fn addLLVMFnAttrStr(ofile: *ObjectFile, fn_val: *llvm.Value, attr_name: []const u8, attr_val: []const u8) !void { | |
| 366 | return addLLVMAttrStr(ofile, fn_val, maxInt(llvm.AttributeIndex), attr_name, attr_val); | |
| 367 | } | |
| 368 | ||
| 369 | fn addLLVMFnAttrInt(ofile: *ObjectFile, fn_val: *llvm.Value, attr_name: []const u8, attr_val: u64) !void { | |
| 370 | return addLLVMAttrInt(ofile, fn_val, maxInt(llvm.AttributeIndex), attr_name, attr_val); | |
| 371 | } | |
| 372 | ||
| 373 | fn renderLoadUntyped( | |
| 374 | ofile: *ObjectFile, | |
| 375 | ptr: *llvm.Value, | |
| 376 | alignment: Type.Pointer.Align, | |
| 377 | vol: Type.Pointer.Vol, | |
| 378 | name: [*:0]const u8, | |
| 379 | ) !*llvm.Value { | |
| 380 | const result = llvm.BuildLoad(ofile.builder, ptr, name) orelse return error.OutOfMemory; | |
| 381 | switch (vol) { | |
| 382 | .Non => {}, | |
| 383 | .Volatile => llvm.SetVolatile(result, 1), | |
| 345 | fn genPtrToInt(self: *Function, inst: *ir.Inst.PtrToInt) !MCValue { | |
| 346 | // no-op | |
| 347 | return self.resolveInst(inst.args.ptr); | |
| 384 | 348 | } |
| 385 | llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm.GetElementType(llvm.TypeOf(ptr)))); | |
| 386 | return result; | |
| 387 | } | |
| 388 | 349 | |
| 389 | fn renderLoad(ofile: *ObjectFile, ptr: *llvm.Value, ptr_type: *Type.Pointer, name: [*:0]const u8) !*llvm.Value { | |
| 390 | return renderLoadUntyped(ofile, ptr, ptr_type.key.alignment, ptr_type.key.vol, name); | |
| 391 | } | |
| 392 | ||
| 393 | pub fn getHandleValue(ofile: *ObjectFile, ptr: *llvm.Value, ptr_type: *Type.Pointer) !?*llvm.Value { | |
| 394 | const child_type = ptr_type.key.child_type; | |
| 395 | if (!child_type.hasBits()) { | |
| 396 | return null; | |
| 350 | fn genBitCast(self: *Function, inst: *ir.Inst.BitCast) !MCValue { | |
| 351 | const operand = try self.resolveInst(inst.args.operand); | |
| 352 | return operand; | |
| 397 | 353 | } |
| 398 | if (child_type.handleIsPtr()) { | |
| 399 | return ptr; | |
| 354 | ||
| 355 | fn resolveInst(self: *Function, inst: *ir.Inst) !MCValue { | |
| 356 | if (self.inst_table.getValue(inst)) |mcv| { | |
| 357 | return mcv; | |
| 358 | } | |
| 359 | if (inst.cast(ir.Inst.Constant)) |const_inst| { | |
| 360 | const mcvalue = try self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val }); | |
| 361 | try self.inst_table.putNoClobber(inst, mcvalue); | |
| 362 | return mcvalue; | |
| 363 | } else { | |
| 364 | return self.inst_table.getValue(inst).?; | |
| 365 | } | |
| 400 | 366 | } |
| 401 | return try renderLoad(ofile, ptr, ptr_type, ""); | |
| 402 | } | |
| 403 | 367 | |
| 404 | pub fn renderStoreUntyped( | |
| 405 | ofile: *ObjectFile, | |
| 406 | value: *llvm.Value, | |
| 407 | ptr: *llvm.Value, | |
| 408 | alignment: Type.Pointer.Align, | |
| 409 | vol: Type.Pointer.Vol, | |
| 410 | ) !*llvm.Value { | |
| 411 | const result = llvm.BuildStore(ofile.builder, value, ptr) orelse return error.OutOfMemory; | |
| 412 | switch (vol) { | |
| 413 | .Non => {}, | |
| 414 | .Volatile => llvm.SetVolatile(result, 1), | |
| 368 | fn genTypedValue(self: *Function, src: usize, typed_value: ir.TypedValue) !MCValue { | |
| 369 | switch (typed_value.ty.zigTypeTag()) { | |
| 370 | .Pointer => { | |
| 371 | const ptr_elem_type = typed_value.ty.elemType(); | |
| 372 | switch (ptr_elem_type.zigTypeTag()) { | |
| 373 | .Array => { | |
| 374 | // TODO more checks to make sure this can be emitted as a string literal | |
| 375 | const bytes = try typed_value.val.toAllocatedBytes(self.code.allocator); | |
| 376 | defer self.code.allocator.free(bytes); | |
| 377 | const smaller_len = std.math.cast(u32, bytes.len) catch | |
| 378 | return self.fail(src, "TODO handle a larger string constant", .{}); | |
| 379 | ||
| 380 | // Emit the string literal directly into the code; jump over it. | |
| 381 | try self.genRelativeFwdJump(src, smaller_len); | |
| 382 | const offset = self.code.items.len; | |
| 383 | try self.code.appendSlice(bytes); | |
| 384 | return MCValue{ .embedded_in_code = offset }; | |
| 385 | }, | |
| 386 | else => |t| return self.fail(src, "TODO implement emitTypedValue for pointer to '{}'", .{@tagName(t)}), | |
| 387 | } | |
| 388 | }, | |
| 389 | .Int => { | |
| 390 | const info = typed_value.ty.intInfo(self.module.target); | |
| 391 | const ptr_bits = self.module.target.cpu.arch.ptrBitWidth(); | |
| 392 | if (info.bits > ptr_bits or info.signed) { | |
| 393 | return self.fail(src, "TODO const int bigger than ptr and signed int", .{}); | |
| 394 | } | |
| 395 | return MCValue{ .immediate = typed_value.val.toUnsignedInt() }; | |
| 396 | }, | |
| 397 | .ComptimeInt => unreachable, // semantic analysis prevents this | |
| 398 | .ComptimeFloat => unreachable, // semantic analysis prevents this | |
| 399 | else => return self.fail(src, "TODO implement const of type '{}'", .{typed_value.ty}), | |
| 400 | } | |
| 415 | 401 | } |
| 416 | llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm.TypeOf(value))); | |
| 417 | return result; | |
| 418 | } | |
| 419 | 402 | |
| 420 | pub fn renderStore( | |
| 421 | ofile: *ObjectFile, | |
| 422 | value: *llvm.Value, | |
| 423 | ptr: *llvm.Value, | |
| 424 | ptr_type: *Type.Pointer, | |
| 425 | ) !*llvm.Value { | |
| 426 | return renderStoreUntyped(ofile, value, ptr, ptr_type.key.alignment, ptr_type.key.vol); | |
| 427 | } | |
| 403 | fn fail(self: *Function, src: usize, comptime format: []const u8, args: var) error{ CodegenFail, OutOfMemory } { | |
| 404 | @setCold(true); | |
| 405 | const msg = try std.fmt.allocPrint(self.errors.allocator, format, args); | |
| 406 | { | |
| 407 | errdefer self.errors.allocator.free(msg); | |
| 408 | (try self.errors.addOne()).* = .{ | |
| 409 | .byte_offset = src, | |
| 410 | .msg = msg, | |
| 411 | }; | |
| 412 | } | |
| 413 | return error.CodegenFail; | |
| 414 | } | |
| 415 | }; | |
| 428 | 416 | |
| 429 | pub fn renderAlloca( | |
| 430 | ofile: *ObjectFile, | |
| 431 | var_type: *Type, | |
| 432 | name: []const u8, | |
| 433 | alignment: Type.Pointer.Align, | |
| 434 | ) !*llvm.Value { | |
| 435 | const llvm_var_type = try var_type.getLlvmType(ofile.arena, ofile.context); | |
| 436 | const name_with_null = try std.cstr.addNullByte(ofile.arena, name); | |
| 437 | const result = llvm.BuildAlloca(ofile.builder, llvm_var_type, @ptrCast([*:0]const u8, name_with_null.ptr)) orelse return error.OutOfMemory; | |
| 438 | llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm_var_type)); | |
| 439 | return result; | |
| 417 | fn Reg(comptime arch: Target.Cpu.Arch) type { | |
| 418 | return switch (arch) { | |
| 419 | .i386 => enum { | |
| 420 | eax, | |
| 421 | ebx, | |
| 422 | ecx, | |
| 423 | edx, | |
| 424 | ebp, | |
| 425 | esp, | |
| 426 | esi, | |
| 427 | edi, | |
| 428 | ||
| 429 | ax, | |
| 430 | bx, | |
| 431 | cx, | |
| 432 | dx, | |
| 433 | bp, | |
| 434 | sp, | |
| 435 | si, | |
| 436 | di, | |
| 437 | ||
| 438 | ah, | |
| 439 | bh, | |
| 440 | ch, | |
| 441 | dh, | |
| 442 | ||
| 443 | al, | |
| 444 | bl, | |
| 445 | cl, | |
| 446 | dl, | |
| 447 | }, | |
| 448 | .x86_64 => enum { | |
| 449 | rax, | |
| 450 | rbx, | |
| 451 | rcx, | |
| 452 | rdx, | |
| 453 | rbp, | |
| 454 | rsp, | |
| 455 | rsi, | |
| 456 | rdi, | |
| 457 | r8, | |
| 458 | r9, | |
| 459 | r10, | |
| 460 | r11, | |
| 461 | r12, | |
| 462 | r13, | |
| 463 | r14, | |
| 464 | r15, | |
| 465 | ||
| 466 | eax, | |
| 467 | ebx, | |
| 468 | ecx, | |
| 469 | edx, | |
| 470 | ebp, | |
| 471 | esp, | |
| 472 | esi, | |
| 473 | edi, | |
| 474 | r8d, | |
| 475 | r9d, | |
| 476 | r10d, | |
| 477 | r11d, | |
| 478 | r12d, | |
| 479 | r13d, | |
| 480 | r14d, | |
| 481 | r15d, | |
| 482 | ||
| 483 | ax, | |
| 484 | bx, | |
| 485 | cx, | |
| 486 | dx, | |
| 487 | bp, | |
| 488 | sp, | |
| 489 | si, | |
| 490 | di, | |
| 491 | r8w, | |
| 492 | r9w, | |
| 493 | r10w, | |
| 494 | r11w, | |
| 495 | r12w, | |
| 496 | r13w, | |
| 497 | r14w, | |
| 498 | r15w, | |
| 499 | ||
| 500 | ah, | |
| 501 | bh, | |
| 502 | ch, | |
| 503 | dh, | |
| 504 | ||
| 505 | al, | |
| 506 | bl, | |
| 507 | cl, | |
| 508 | dl, | |
| 509 | r8b, | |
| 510 | r9b, | |
| 511 | r10b, | |
| 512 | r11b, | |
| 513 | r12b, | |
| 514 | r13b, | |
| 515 | r14b, | |
| 516 | r15b, | |
| 517 | }, | |
| 518 | else => @compileError("TODO add more register enums"), | |
| 519 | }; | |
| 440 | 520 | } |
| 441 | 521 | |
| 442 | pub fn resolveAlign(ofile: *ObjectFile, alignment: Type.Pointer.Align, llvm_type: *llvm.Type) u32 { | |
| 443 | return switch (alignment) { | |
| 444 | .Abi => return llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, llvm_type), | |
| 445 | .Override => |a| a, | |
| 446 | }; | |
| 522 | fn parseRegName(comptime arch: Target.Cpu.Arch, name: []const u8) ?Reg(arch) { | |
| 523 | return std.meta.stringToEnum(Reg(arch), name); | |
| 447 | 524 | } |
src-self-hosted/ir.zig+61-19| ... | ... | @@ -24,6 +24,7 @@ pub const Inst = struct { |
| 24 | 24 | constant, |
| 25 | 25 | assembly, |
| 26 | 26 | ptrtoint, |
| 27 | bitcast, | |
| 27 | 28 | }; |
| 28 | 29 | |
| 29 | 30 | pub fn cast(base: *Inst, comptime T: type) ?*T { |
| ... | ... | @@ -45,6 +46,7 @@ pub const Inst = struct { |
| 45 | 46 | |
| 46 | 47 | .assembly, |
| 47 | 48 | .ptrtoint, |
| 49 | .bitcast, | |
| 48 | 50 | => null, |
| 49 | 51 | }; |
| 50 | 52 | } |
| ... | ... | @@ -84,6 +86,15 @@ pub const Inst = struct { |
| 84 | 86 | ptr: *Inst, |
| 85 | 87 | }, |
| 86 | 88 | }; |
| 89 | ||
| 90 | pub const BitCast = struct { | |
| 91 | pub const base_tag = Tag.bitcast; | |
| 92 | ||
| 93 | base: Inst, | |
| 94 | args: struct { | |
| 95 | operand: *Inst, | |
| 96 | }, | |
| 97 | }; | |
| 87 | 98 | }; |
| 88 | 99 | |
| 89 | 100 | pub const TypedValue = struct { |
| ... | ... | @@ -96,6 +107,7 @@ pub const Module = struct { |
| 96 | 107 | errors: []ErrorMsg, |
| 97 | 108 | arena: std.heap.ArenaAllocator, |
| 98 | 109 | fns: []Fn, |
| 110 | target: Target, | |
| 99 | 111 | |
| 100 | 112 | pub const Export = struct { |
| 101 | 113 | name: []const u8, |
| ... | ... | @@ -122,9 +134,7 @@ pub const ErrorMsg = struct { |
| 122 | 134 | msg: []const u8, |
| 123 | 135 | }; |
| 124 | 136 | |
| 125 | pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { | |
| 126 | const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{}); | |
| 127 | ||
| 137 | pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) !Module { | |
| 128 | 138 | var ctx = Analyze{ |
| 129 | 139 | .allocator = allocator, |
| 130 | 140 | .arena = std.heap.ArenaAllocator.init(allocator), |
| ... | ... | @@ -133,7 +143,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { |
| 133 | 143 | .decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator), |
| 134 | 144 | .exports = std.ArrayList(Module.Export).init(allocator), |
| 135 | 145 | .fns = std.ArrayList(Module.Fn).init(allocator), |
| 136 | .target = native_info.target, | |
| 146 | .target = target, | |
| 137 | 147 | }; |
| 138 | 148 | defer ctx.errors.deinit(); |
| 139 | 149 | defer ctx.decl_table.deinit(); |
| ... | ... | @@ -152,6 +162,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { |
| 152 | 162 | .errors = ctx.errors.toOwnedSlice(), |
| 153 | 163 | .fns = ctx.fns.toOwnedSlice(), |
| 154 | 164 | .arena = ctx.arena, |
| 165 | .target = target, | |
| 155 | 166 | }; |
| 156 | 167 | } |
| 157 | 168 | |
| ... | ... | @@ -234,7 +245,7 @@ const Analyze = struct { |
| 234 | 245 | fn resolveConstString(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) ![]u8 { |
| 235 | 246 | const new_inst = try self.resolveInst(func, old_inst); |
| 236 | 247 | const wanted_type = Type.initTag(.const_slice_u8); |
| 237 | const coerced_inst = try self.coerce(wanted_type, new_inst); | |
| 248 | const coerced_inst = try self.coerce(func, wanted_type, new_inst); | |
| 238 | 249 | const val = try self.resolveConstValue(coerced_inst); |
| 239 | 250 | return val.toAllocatedBytes(&self.arena.allocator); |
| 240 | 251 | } |
| ... | ... | @@ -242,7 +253,7 @@ const Analyze = struct { |
| 242 | 253 | fn resolveType(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) !Type { |
| 243 | 254 | const new_inst = try self.resolveInst(func, old_inst); |
| 244 | 255 | const wanted_type = Type.initTag(.@"type"); |
| 245 | const coerced_inst = try self.coerce(wanted_type, new_inst); | |
| 256 | const coerced_inst = try self.coerce(func, wanted_type, new_inst); | |
| 246 | 257 | const val = try self.resolveConstValue(coerced_inst); |
| 247 | 258 | return val.toType(); |
| 248 | 259 | } |
| ... | ... | @@ -409,6 +420,7 @@ const Analyze = struct { |
| 409 | 420 | .primitive => return self.analyzeInstPrimitive(func, old_inst.cast(text.Inst.Primitive).?), |
| 410 | 421 | .fntype => return self.analyzeInstFnType(func, old_inst.cast(text.Inst.FnType).?), |
| 411 | 422 | .intcast => return self.analyzeInstIntCast(func, old_inst.cast(text.Inst.IntCast).?), |
| 423 | .bitcast => return self.analyzeInstBitCast(func, old_inst.cast(text.Inst.BitCast).?), | |
| 412 | 424 | } |
| 413 | 425 | } |
| 414 | 426 | |
| ... | ... | @@ -472,7 +484,7 @@ const Analyze = struct { |
| 472 | 484 | fn analyzeInstAs(self: *Analyze, func: ?*Fn, as: *text.Inst.As) InnerError!*Inst { |
| 473 | 485 | const dest_type = try self.resolveType(func, as.positionals.dest_type); |
| 474 | 486 | const new_inst = try self.resolveInst(func, as.positionals.value); |
| 475 | return self.coerce(dest_type, new_inst); | |
| 487 | return self.coerce(func, dest_type, new_inst); | |
| 476 | 488 | } |
| 477 | 489 | |
| 478 | 490 | fn analyzeInstPtrToInt(self: *Analyze, func: ?*Fn, ptrtoint: *text.Inst.PtrToInt) InnerError!*Inst { |
| ... | ... | @@ -545,12 +557,18 @@ const Analyze = struct { |
| 545 | 557 | } |
| 546 | 558 | |
| 547 | 559 | if (dest_is_comptime_int or new_inst.value() != null) { |
| 548 | return self.coerce(dest_type, new_inst); | |
| 560 | return self.coerce(func, dest_type, new_inst); | |
| 549 | 561 | } |
| 550 | 562 | |
| 551 | 563 | return self.fail(intcast.base.src, "TODO implement analyze widen or shorten int", .{}); |
| 552 | 564 | } |
| 553 | 565 | |
| 566 | fn analyzeInstBitCast(self: *Analyze, func: ?*Fn, inst: *text.Inst.BitCast) InnerError!*Inst { | |
| 567 | const dest_type = try self.resolveType(func, inst.positionals.dest_type); | |
| 568 | const operand = try self.resolveInst(func, inst.positionals.operand); | |
| 569 | return self.bitcast(func, dest_type, operand); | |
| 570 | } | |
| 571 | ||
| 554 | 572 | fn analyzeInstDeref(self: *Analyze, func: ?*Fn, deref: *text.Inst.Deref) InnerError!*Inst { |
| 555 | 573 | const ptr = try self.resolveInst(func, deref.positionals.ptr); |
| 556 | 574 | const elem_ty = switch (ptr.ty.zigTypeTag()) { |
| ... | ... | @@ -583,7 +601,8 @@ const Analyze = struct { |
| 583 | 601 | elem.* = try self.resolveConstString(func, assembly.kw_args.clobbers[i]); |
| 584 | 602 | } |
| 585 | 603 | for (args) |*elem, i| { |
| 586 | elem.* = try self.resolveInst(func, assembly.kw_args.args[i]); | |
| 604 | const arg = try self.resolveInst(func, assembly.kw_args.args[i]); | |
| 605 | elem.* = try self.coerce(func, Type.initTag(.usize), arg); | |
| 587 | 606 | } |
| 588 | 607 | |
| 589 | 608 | const f = try self.requireFunctionBody(func, assembly.base.src); |
| ... | ... | @@ -602,10 +621,14 @@ const Analyze = struct { |
| 602 | 621 | return self.addNewInstArgs(f, unreach.base.src, Type.initTag(.noreturn), Inst.Unreach, {}); |
| 603 | 622 | } |
| 604 | 623 | |
| 605 | fn coerce(self: *Analyze, dest_type: Type, inst: *Inst) !*Inst { | |
| 624 | fn coerce(self: *Analyze, func: ?*Fn, dest_type: Type, inst: *Inst) !*Inst { | |
| 625 | // If the types are the same, we can return the operand. | |
| 626 | if (dest_type.eql(inst.ty)) | |
| 627 | return inst; | |
| 628 | ||
| 606 | 629 | const in_memory_result = coerceInMemoryAllowed(dest_type, inst.ty); |
| 607 | 630 | if (in_memory_result == .ok) { |
| 608 | return self.bitcast(dest_type, inst); | |
| 631 | return self.bitcast(func, dest_type, inst); | |
| 609 | 632 | } |
| 610 | 633 | |
| 611 | 634 | // *[N]T to []T |
| ... | ... | @@ -634,12 +657,14 @@ const Analyze = struct { |
| 634 | 657 | return self.fail(inst.src, "TODO implement type coercion", .{}); |
| 635 | 658 | } |
| 636 | 659 | |
| 637 | fn bitcast(self: *Analyze, dest_type: Type, inst: *Inst) !*Inst { | |
| 660 | fn bitcast(self: *Analyze, func: ?*Fn, dest_type: Type, inst: *Inst) !*Inst { | |
| 638 | 661 | if (inst.value()) |val| { |
| 639 | 662 | // Keep the comptime Value representation; take the new type. |
| 640 | 663 | return self.constInst(inst.src, .{ .ty = dest_type, .val = val }); |
| 641 | 664 | } |
| 642 | return self.fail(inst.src, "TODO implement runtime bitcast", .{}); | |
| 665 | // TODO validate the type size and other compile errors | |
| 666 | const f = try self.requireFunctionBody(func, inst.src); | |
| 667 | return self.addNewInstArgs(f, inst.src, dest_type, Inst.BitCast, Inst.Args(Inst.BitCast){ .operand = inst }); | |
| 643 | 668 | } |
| 644 | 669 | |
| 645 | 670 | fn coerceArrayPtrToSlice(self: *Analyze, dest_type: Type, inst: *Inst) !*Inst { |
| ... | ... | @@ -699,7 +724,9 @@ pub fn main() anyerror!void { |
| 699 | 724 | std.process.exit(1); |
| 700 | 725 | } |
| 701 | 726 | |
| 702 | var analyzed_module = try analyze(allocator, zir_module); | |
| 727 | const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{}); | |
| 728 | ||
| 729 | var analyzed_module = try analyze(allocator, zir_module, native_info.target); | |
| 703 | 730 | defer analyzed_module.deinit(allocator); |
| 704 | 731 | |
| 705 | 732 | if (analyzed_module.errors.len != 0) { |
| ... | ... | @@ -711,12 +738,27 @@ pub fn main() anyerror!void { |
| 711 | 738 | std.process.exit(1); |
| 712 | 739 | } |
| 713 | 740 | |
| 714 | var new_zir_module = try text.emit_zir(allocator, analyzed_module); | |
| 715 | defer new_zir_module.deinit(allocator); | |
| 741 | const output_zir = true; | |
| 742 | if (output_zir) { | |
| 743 | var new_zir_module = try text.emit_zir(allocator, analyzed_module); | |
| 744 | defer new_zir_module.deinit(allocator); | |
| 716 | 745 | |
| 717 | var bos = std.io.bufferedOutStream(std.io.getStdOut().outStream()); | |
| 718 | try new_zir_module.writeToStream(allocator, bos.outStream()); | |
| 719 | try bos.flush(); | |
| 746 | var bos = std.io.bufferedOutStream(std.io.getStdOut().outStream()); | |
| 747 | try new_zir_module.writeToStream(allocator, bos.outStream()); | |
| 748 | try bos.flush(); | |
| 749 | } | |
| 750 | ||
| 751 | const link = @import("link.zig"); | |
| 752 | var result = try link.updateExecutableFilePath(allocator, analyzed_module, std.fs.cwd(), "a.out"); | |
| 753 | defer result.deinit(allocator); | |
| 754 | if (result.errors.len != 0) { | |
| 755 | for (result.errors) |err_msg| { | |
| 756 | const loc = findLineColumn(source, err_msg.byte_offset); | |
| 757 | std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg }); | |
| 758 | } | |
| 759 | if (debug_error_trace) return error.ParseFailure; | |
| 760 | std.process.exit(1); | |
| 761 | } | |
| 720 | 762 | } |
| 721 | 763 | |
| 722 | 764 | fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } { |
src-self-hosted/ir/text.zig+27| ... | ... | @@ -31,6 +31,7 @@ pub const Inst = struct { |
| 31 | 31 | primitive, |
| 32 | 32 | fntype, |
| 33 | 33 | intcast, |
| 34 | bitcast, | |
| 34 | 35 | }; |
| 35 | 36 | |
| 36 | 37 | pub fn TagToType(tag: Tag) type { |
| ... | ... | @@ -48,6 +49,7 @@ pub const Inst = struct { |
| 48 | 49 | .primitive => Primitive, |
| 49 | 50 | .fntype => FnType, |
| 50 | 51 | .intcast => IntCast, |
| 52 | .bitcast => BitCast, | |
| 51 | 53 | }; |
| 52 | 54 | } |
| 53 | 55 | |
| ... | ... | @@ -258,6 +260,17 @@ pub const Inst = struct { |
| 258 | 260 | }, |
| 259 | 261 | kw_args: struct {}, |
| 260 | 262 | }; |
| 263 | ||
| 264 | pub const BitCast = struct { | |
| 265 | pub const base_tag = Tag.bitcast; | |
| 266 | base: Inst, | |
| 267 | ||
| 268 | positionals: struct { | |
| 269 | dest_type: *Inst, | |
| 270 | operand: *Inst, | |
| 271 | }, | |
| 272 | kw_args: struct {}, | |
| 273 | }; | |
| 261 | 274 | }; |
| 262 | 275 | |
| 263 | 276 | pub const ErrorMsg = struct { |
| ... | ... | @@ -331,6 +344,7 @@ pub const Module = struct { |
| 331 | 344 | .primitive => return self.writeInstToStreamGeneric(stream, .primitive, decl, inst_table), |
| 332 | 345 | .fntype => return self.writeInstToStreamGeneric(stream, .fntype, decl, inst_table), |
| 333 | 346 | .intcast => return self.writeInstToStreamGeneric(stream, .intcast, decl, inst_table), |
| 347 | .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, decl, inst_table), | |
| 334 | 348 | } |
| 335 | 349 | } |
| 336 | 350 | |
| ... | ... | @@ -957,6 +971,19 @@ const EmitZIR = struct { |
| 957 | 971 | }; |
| 958 | 972 | break :blk &new_inst.base; |
| 959 | 973 | }, |
| 974 | .bitcast => blk: { | |
| 975 | const old_inst = inst.cast(ir.Inst.BitCast).?; | |
| 976 | const new_inst = try self.arena.allocator.create(Inst.BitCast); | |
| 977 | new_inst.* = .{ | |
| 978 | .base = .{ .src = inst.src, .tag = Inst.BitCast.base_tag }, | |
| 979 | .positionals = .{ | |
| 980 | .dest_type = try self.emitType(inst.src, inst.ty), | |
| 981 | .operand = try self.resolveInst(&inst_table, old_inst.args.operand), | |
| 982 | }, | |
| 983 | .kw_args = .{}, | |
| 984 | }; | |
| 985 | break :blk &new_inst.base; | |
| 986 | }, | |
| 960 | 987 | }; |
| 961 | 988 | try instructions.append(new_inst); |
| 962 | 989 | try inst_table.putNoClobber(inst, new_inst); |
src-self-hosted/link.zig+748-500| ... | ... | @@ -1,576 +1,824 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | const mem = std.mem; |
| 3 | const c = @import("c.zig"); | |
| 4 | const Compilation = @import("compilation.zig").Compilation; | |
| 5 | const Target = std.Target; | |
| 6 | const ObjectFormat = Target.ObjectFormat; | |
| 7 | const LibCInstallation = @import("libc_installation.zig").LibCInstallation; | |
| 8 | 3 | const assert = std.debug.assert; |
| 9 | const util = @import("util.zig"); | |
| 10 | ||
| 11 | const Context = struct { | |
| 12 | comp: *Compilation, | |
| 13 | arena: std.heap.ArenaAllocator, | |
| 14 | args: std.ArrayList([*:0]const u8), | |
| 15 | link_in_crt: bool, | |
| 4 | const Allocator = std.mem.Allocator; | |
| 5 | const ir = @import("ir.zig"); | |
| 6 | const fs = std.fs; | |
| 7 | const elf = std.elf; | |
| 8 | const codegen = @import("codegen.zig"); | |
| 9 | ||
| 10 | /// On common systems with a 0o022 umask, 0o777 will still result in a file created | |
| 11 | /// with 0o755 permissions, but it works appropriately if the system is configured | |
| 12 | /// more leniently. As another data point, C's fopen seems to open files with the | |
| 13 | /// 666 mode. | |
| 14 | const executable_mode = 0o777; | |
| 15 | const default_entry_addr = 0x8000000; | |
| 16 | ||
| 17 | pub const ErrorMsg = struct { | |
| 18 | byte_offset: usize, | |
| 19 | msg: []const u8, | |
| 20 | }; | |
| 16 | 21 | |
| 17 | link_err: error{OutOfMemory}!void, | |
| 18 | link_msg: std.ArrayListSentineled(u8, 0), | |
| 22 | pub const Result = struct { | |
| 23 | errors: []ErrorMsg, | |
| 19 | 24 | |
| 20 | libc: *LibCInstallation, | |
| 21 | out_file_path: std.ArrayListSentineled(u8, 0), | |
| 25 | pub fn deinit(self: *Result, allocator: *mem.Allocator) void { | |
| 26 | for (self.errors) |err| { | |
| 27 | allocator.free(err.msg); | |
| 28 | } | |
| 29 | allocator.free(self.errors); | |
| 30 | self.* = undefined; | |
| 31 | } | |
| 22 | 32 | }; |
| 23 | 33 | |
| 24 | pub fn link(comp: *Compilation) !void { | |
| 25 | var ctx = Context{ | |
| 26 | .comp = comp, | |
| 27 | .arena = std.heap.ArenaAllocator.init(comp.gpa()), | |
| 28 | .args = undefined, | |
| 29 | .link_in_crt = comp.haveLibC() and comp.kind == .Exe, | |
| 30 | .link_err = {}, | |
| 31 | .link_msg = undefined, | |
| 32 | .libc = undefined, | |
| 33 | .out_file_path = undefined, | |
| 34 | }; | |
| 35 | defer ctx.arena.deinit(); | |
| 36 | ctx.args = std.ArrayList([*:0]const u8).init(&ctx.arena.allocator); | |
| 37 | ctx.link_msg = std.ArrayListSentineled(u8, 0).initNull(&ctx.arena.allocator); | |
| 38 | ||
| 39 | ctx.out_file_path = try std.ArrayListSentineled(u8, 0).init(&ctx.arena.allocator, comp.name.span()); | |
| 40 | switch (comp.kind) { | |
| 41 | .Exe => { | |
| 42 | try ctx.out_file_path.append(comp.target.exeFileExt()); | |
| 43 | }, | |
| 44 | .Lib => { | |
| 45 | try ctx.out_file_path.append(if (comp.is_static) comp.target.staticLibSuffix() else comp.target.dynamicLibSuffix()); | |
| 46 | }, | |
| 47 | .Obj => { | |
| 48 | try ctx.out_file_path.append(comp.target.oFileExt()); | |
| 34 | /// Attempts incremental linking, if the file already exists. | |
| 35 | /// If incremental linking fails, falls back to truncating the file and rewriting it. | |
| 36 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. | |
| 37 | /// This operation is not atomic. | |
| 38 | pub fn updateExecutableFilePath( | |
| 39 | allocator: *Allocator, | |
| 40 | module: ir.Module, | |
| 41 | dir: fs.Dir, | |
| 42 | sub_path: []const u8, | |
| 43 | ) !Result { | |
| 44 | const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = executable_mode }); | |
| 45 | defer file.close(); | |
| 46 | ||
| 47 | return updateExecutableFile(allocator, module, file); | |
| 48 | } | |
| 49 | ||
| 50 | /// Atomically overwrites the old file, if present. | |
| 51 | pub fn writeExecutableFilePath( | |
| 52 | allocator: *Allocator, | |
| 53 | module: ir.Module, | |
| 54 | dir: fs.Dir, | |
| 55 | sub_path: []const u8, | |
| 56 | ) !Result { | |
| 57 | const af = try dir.atomicFile(sub_path, .{ .mode = executable_mode }); | |
| 58 | defer af.deinit(); | |
| 59 | ||
| 60 | const result = try writeExecutableFile(allocator, module, af.file); | |
| 61 | try af.finish(); | |
| 62 | return result; | |
| 63 | } | |
| 64 | ||
| 65 | /// Attempts incremental linking, if the file already exists. | |
| 66 | /// If incremental linking fails, falls back to truncating the file and rewriting it. | |
| 67 | /// Returns an error if `file` is not already open with +read +write +seek abilities. | |
| 68 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. | |
| 69 | /// This operation is not atomic. | |
| 70 | pub fn updateExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | |
| 71 | return updateExecutableFileInner(allocator, module, file) catch |err| switch (err) { | |
| 72 | error.IncrFailed => { | |
| 73 | return writeExecutableFile(allocator, module, file); | |
| 49 | 74 | }, |
| 50 | } | |
| 75 | else => |e| return e, | |
| 76 | }; | |
| 77 | } | |
| 51 | 78 | |
| 52 | // even though we're calling LLD as a library it thinks the first | |
| 53 | // argument is its own exe name | |
| 54 | try ctx.args.append("lld"); | |
| 55 | ||
| 56 | if (comp.haveLibC()) { | |
| 57 | // TODO https://github.com/ziglang/zig/issues/3190 | |
| 58 | var libc = ctx.comp.override_libc orelse blk: { | |
| 59 | @panic("this code has bitrotted"); | |
| 60 | //switch (comp.target) { | |
| 61 | // Target.Native => { | |
| 62 | // break :blk comp.zig_compiler.getNativeLibC() catch return error.LibCRequiredButNotProvidedOrFound; | |
| 63 | // }, | |
| 64 | // else => return error.LibCRequiredButNotProvidedOrFound, | |
| 65 | //} | |
| 66 | }; | |
| 67 | ctx.libc = libc; | |
| 68 | } | |
| 79 | const Update = struct { | |
| 80 | file: fs.File, | |
| 81 | module: *const ir.Module, | |
| 69 | 82 | |
| 70 | try constructLinkerArgs(&ctx); | |
| 83 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. | |
| 84 | /// Same order as in the file. | |
| 85 | sections: std.ArrayList(elf.Elf64_Shdr), | |
| 86 | shdr_table_offset: ?u64, | |
| 71 | 87 | |
| 72 | if (comp.verbose_link) { | |
| 73 | for (ctx.args.span()) |arg, i| { | |
| 74 | const space = if (i == 0) "" else " "; | |
| 75 | std.debug.warn("{}{s}", .{ space, arg }); | |
| 76 | } | |
| 77 | std.debug.warn("\n", .{}); | |
| 88 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. | |
| 89 | /// Same order as in the file. | |
| 90 | program_headers: std.ArrayList(elf.Elf64_Phdr), | |
| 91 | phdr_table_offset: ?u64, | |
| 92 | /// The index into the program headers of a PT_LOAD program header with Read and Execute flags | |
| 93 | phdr_load_re_index: ?u16, | |
| 94 | entry_addr: ?u64, | |
| 95 | ||
| 96 | shstrtab: std.ArrayList(u8), | |
| 97 | shstrtab_index: ?u16, | |
| 98 | ||
| 99 | text_section_index: ?u16, | |
| 100 | symtab_section_index: ?u16, | |
| 101 | ||
| 102 | /// The same order as in the file | |
| 103 | symbols: std.ArrayList(elf.Elf64_Sym), | |
| 104 | ||
| 105 | errors: std.ArrayList(ErrorMsg), | |
| 106 | ||
| 107 | fn deinit(self: *Update) void { | |
| 108 | self.sections.deinit(); | |
| 109 | self.program_headers.deinit(); | |
| 110 | self.shstrtab.deinit(); | |
| 111 | self.symbols.deinit(); | |
| 112 | self.errors.deinit(); | |
| 78 | 113 | } |
| 79 | 114 | |
| 80 | const extern_ofmt = toExternObjectFormatType(comp.target.getObjectFormat()); | |
| 81 | const args_slice = ctx.args.span(); | |
| 82 | ||
| 83 | { | |
| 84 | // LLD is not thread-safe, so we grab a global lock. | |
| 85 | const held = comp.zig_compiler.lld_lock.acquire(); | |
| 86 | defer held.release(); | |
| 87 | ||
| 88 | // Not evented I/O. LLD does its own multithreading internally. | |
| 89 | if (!ZigLLDLink(extern_ofmt, args_slice.ptr, args_slice.len, linkDiagCallback, @ptrCast(*c_void, &ctx))) { | |
| 90 | if (!ctx.link_msg.isNull()) { | |
| 91 | // TODO capture these messages and pass them through the system, reporting them through the | |
| 92 | // event system instead of printing them directly here. | |
| 93 | // perhaps try to parse and understand them. | |
| 94 | std.debug.warn("{}\n", .{ctx.link_msg.span()}); | |
| 115 | // `expand_num / expand_den` is the factor of padding when allocation | |
| 116 | const alloc_num = 4; | |
| 117 | const alloc_den = 3; | |
| 118 | ||
| 119 | /// Returns end pos of collision, if any. | |
| 120 | fn detectAllocCollision(self: *Update, start: u64, size: u64) ?u64 { | |
| 121 | const small_ptr = self.module.target.cpu.arch.ptrBitWidth() == 32; | |
| 122 | const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); | |
| 123 | if (start < ehdr_size) | |
| 124 | return ehdr_size; | |
| 125 | ||
| 126 | const end = start + satMul(size, alloc_num) / alloc_den; | |
| 127 | ||
| 128 | if (self.shdr_table_offset) |off| { | |
| 129 | const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr); | |
| 130 | const tight_size = self.sections.items.len * shdr_size; | |
| 131 | const increased_size = satMul(tight_size, alloc_num) / alloc_den; | |
| 132 | const test_end = off + increased_size; | |
| 133 | if (end > off and start < test_end) { | |
| 134 | return test_end; | |
| 95 | 135 | } |
| 96 | return error.LinkFailed; | |
| 97 | 136 | } |
| 98 | } | |
| 99 | } | |
| 100 | 137 | |
| 101 | extern fn ZigLLDLink( | |
| 102 | oformat: c.ZigLLVM_ObjectFormatType, | |
| 103 | args: [*]const [*]const u8, | |
| 104 | arg_count: usize, | |
| 105 | append_diagnostic: extern fn (*c_void, [*]const u8, usize) void, | |
| 106 | context: *c_void, | |
| 107 | ) bool; | |
| 108 | ||
| 109 | fn linkDiagCallback(context: *c_void, ptr: [*]const u8, len: usize) callconv(.C) void { | |
| 110 | const ctx = @ptrCast(*Context, @alignCast(@alignOf(Context), context)); | |
| 111 | ctx.link_err = linkDiagCallbackErrorable(ctx, ptr[0..len]); | |
| 112 | } | |
| 138 | if (self.phdr_table_offset) |off| { | |
| 139 | const phdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Phdr) else @sizeOf(elf.Elf64_Phdr); | |
| 140 | const tight_size = self.sections.items.len * phdr_size; | |
| 141 | const increased_size = satMul(tight_size, alloc_num) / alloc_den; | |
| 142 | const test_end = off + increased_size; | |
| 143 | if (end > off and start < test_end) { | |
| 144 | return test_end; | |
| 145 | } | |
| 146 | } | |
| 113 | 147 | |
| 114 | fn linkDiagCallbackErrorable(ctx: *Context, msg: []const u8) !void { | |
| 115 | if (ctx.link_msg.isNull()) { | |
| 116 | try ctx.link_msg.resize(0); | |
| 148 | for (self.sections.items) |section| { | |
| 149 | const increased_size = satMul(section.sh_size, alloc_num) / alloc_den; | |
| 150 | const test_end = section.sh_offset + increased_size; | |
| 151 | if (end > section.sh_offset and start < test_end) { | |
| 152 | return test_end; | |
| 153 | } | |
| 154 | } | |
| 155 | for (self.program_headers.items) |program_header| { | |
| 156 | const increased_size = satMul(program_header.p_filesz, alloc_num) / alloc_den; | |
| 157 | const test_end = program_header.p_offset + increased_size; | |
| 158 | if (end > program_header.p_offset and start < test_end) { | |
| 159 | return test_end; | |
| 160 | } | |
| 161 | } | |
| 162 | return null; | |
| 117 | 163 | } |
| 118 | try ctx.link_msg.append(msg); | |
| 119 | } | |
| 120 | 164 | |
| 121 | fn toExternObjectFormatType(ofmt: ObjectFormat) c.ZigLLVM_ObjectFormatType { | |
| 122 | return switch (ofmt) { | |
| 123 | .unknown => .ZigLLVM_UnknownObjectFormat, | |
| 124 | .coff => .ZigLLVM_COFF, | |
| 125 | .elf => .ZigLLVM_ELF, | |
| 126 | .macho => .ZigLLVM_MachO, | |
| 127 | .wasm => .ZigLLVM_Wasm, | |
| 128 | }; | |
| 129 | } | |
| 130 | ||
| 131 | fn constructLinkerArgs(ctx: *Context) !void { | |
| 132 | switch (ctx.comp.target.getObjectFormat()) { | |
| 133 | .unknown => unreachable, | |
| 134 | .coff => return constructLinkerArgsCoff(ctx), | |
| 135 | .elf => return constructLinkerArgsElf(ctx), | |
| 136 | .macho => return constructLinkerArgsMachO(ctx), | |
| 137 | .wasm => return constructLinkerArgsWasm(ctx), | |
| 165 | fn allocatedSize(self: *Update, start: u64) u64 { | |
| 166 | var min_pos: u64 = std.math.maxInt(u64); | |
| 167 | if (self.shdr_table_offset) |off| { | |
| 168 | if (off > start and off < min_pos) min_pos = off; | |
| 169 | } | |
| 170 | if (self.phdr_table_offset) |off| { | |
| 171 | if (off > start and off < min_pos) min_pos = off; | |
| 172 | } | |
| 173 | for (self.sections.items) |section| { | |
| 174 | if (section.sh_offset <= start) continue; | |
| 175 | if (section.sh_offset < min_pos) min_pos = section.sh_offset; | |
| 176 | } | |
| 177 | for (self.program_headers.items) |program_header| { | |
| 178 | if (program_header.p_offset <= start) continue; | |
| 179 | if (program_header.p_offset < min_pos) min_pos = program_header.p_offset; | |
| 180 | } | |
| 181 | return min_pos - start; | |
| 138 | 182 | } |
| 139 | } | |
| 140 | 183 | |
| 141 | fn constructLinkerArgsElf(ctx: *Context) !void { | |
| 142 | // TODO commented out code in this function | |
| 143 | //if (g->linker_script) { | |
| 144 | // lj->args.append("-T"); | |
| 145 | // lj->args.append(g->linker_script); | |
| 146 | //} | |
| 147 | try ctx.args.append("--gc-sections"); | |
| 148 | if (ctx.comp.link_eh_frame_hdr) { | |
| 149 | try ctx.args.append("--eh-frame-hdr"); | |
| 184 | fn findFreeSpace(self: *Update, object_size: u64, min_alignment: u16) u64 { | |
| 185 | var start: u64 = 0; | |
| 186 | while (self.detectAllocCollision(start, object_size)) |item_end| { | |
| 187 | start = mem.alignForwardGeneric(u64, item_end, min_alignment); | |
| 188 | } | |
| 189 | return start; | |
| 150 | 190 | } |
| 151 | 191 | |
| 152 | //lj->args.append("-m"); | |
| 153 | //lj->args.append(getLDMOption(&g->zig_target)); | |
| 154 | ||
| 155 | //bool is_lib = g->out_type == OutTypeLib; | |
| 156 | //bool shared = !g->is_static && is_lib; | |
| 157 | //Buf *soname = nullptr; | |
| 158 | if (ctx.comp.is_static) { | |
| 159 | //if (util.isArmOrThumb(ctx.comp.target)) { | |
| 160 | // try ctx.args.append("-Bstatic"); | |
| 161 | //} else { | |
| 162 | // try ctx.args.append("-static"); | |
| 163 | //} | |
| 164 | } | |
| 165 | //} else if (shared) { | |
| 166 | // lj->args.append("-shared"); | |
| 167 | ||
| 168 | // if (buf_len(&lj->out_file) == 0) { | |
| 169 | // buf_appendf(&lj->out_file, "lib%s.so.%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".%" ZIG_PRI_usize "", | |
| 170 | // buf_ptr(g->root_out_name), g->version_major, g->version_minor, g->version_patch); | |
| 171 | // } | |
| 172 | // soname = buf_sprintf("lib%s.so.%" ZIG_PRI_usize "", buf_ptr(g->root_out_name), g->version_major); | |
| 173 | //} | |
| 174 | ||
| 175 | try ctx.args.append("-o"); | |
| 176 | try ctx.args.append(ctx.out_file_path.span()); | |
| 177 | ||
| 178 | if (ctx.link_in_crt) { | |
| 179 | const crt1o = if (ctx.comp.is_static) "crt1.o" else "Scrt1.o"; | |
| 180 | try addPathJoin(ctx, ctx.libc.crt_dir.?, crt1o); | |
| 181 | try addPathJoin(ctx, ctx.libc.crt_dir.?, "crti.o"); | |
| 192 | fn makeString(self: *Update, bytes: []const u8) !u32 { | |
| 193 | const result = self.shstrtab.items.len; | |
| 194 | try self.shstrtab.appendSlice(bytes); | |
| 195 | try self.shstrtab.append(0); | |
| 196 | return @intCast(u32, result); | |
| 182 | 197 | } |
| 183 | 198 | |
| 184 | if (ctx.comp.haveLibC()) { | |
| 185 | try ctx.args.append("-L"); | |
| 186 | // TODO addNullByte should probably return [:0]u8 | |
| 187 | try ctx.args.append(@ptrCast([*:0]const u8, (try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.crt_dir.?)).ptr)); | |
| 188 | ||
| 189 | //if (!ctx.comp.is_static) { | |
| 190 | // const dl = blk: { | |
| 191 | // //if (ctx.libc.dynamic_linker_path) |dl| break :blk dl; | |
| 192 | // //if (util.getDynamicLinkerPath(ctx.comp.target)) |dl| break :blk dl; | |
| 193 | // return error.LibCMissingDynamicLinker; | |
| 194 | // }; | |
| 195 | // try ctx.args.append("-dynamic-linker"); | |
| 196 | // try ctx.args.append(@ptrCast([*:0]const u8, (try std.cstr.addNullByte(&ctx.arena.allocator, dl)).ptr)); | |
| 197 | //} | |
| 198 | } | |
| 199 | fn perform(self: *Update) !void { | |
| 200 | const ptr_width: enum { p32, p64 } = switch (self.module.target.cpu.arch.ptrBitWidth()) { | |
| 201 | 32 => .p32, | |
| 202 | 64 => .p64, | |
| 203 | else => return error.UnsupportedArchitecture, | |
| 204 | }; | |
| 205 | const small_ptr = switch (ptr_width) { | |
| 206 | .p32 => true, | |
| 207 | .p64 => false, | |
| 208 | }; | |
| 209 | // This means the entire read-only executable program code needs to be rewritten. | |
| 210 | var phdr_load_re_dirty = false; | |
| 211 | var phdr_table_dirty = false; | |
| 212 | var shdr_table_dirty = false; | |
| 213 | var shstrtab_dirty = false; | |
| 214 | var symtab_dirty = false; | |
| 215 | ||
| 216 | if (self.phdr_load_re_index == null) { | |
| 217 | self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len); | |
| 218 | const file_size = 256 * 1024; | |
| 219 | const p_align = 0x1000; | |
| 220 | const off = self.findFreeSpace(file_size, p_align); | |
| 221 | //std.debug.warn("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | |
| 222 | try self.program_headers.append(.{ | |
| 223 | .p_type = elf.PT_LOAD, | |
| 224 | .p_offset = off, | |
| 225 | .p_filesz = file_size, | |
| 226 | .p_vaddr = default_entry_addr, | |
| 227 | .p_paddr = default_entry_addr, | |
| 228 | .p_memsz = 0, | |
| 229 | .p_align = p_align, | |
| 230 | .p_flags = elf.PF_X | elf.PF_R, | |
| 231 | }); | |
| 232 | self.entry_addr = null; | |
| 233 | phdr_load_re_dirty = true; | |
| 234 | phdr_table_dirty = true; | |
| 235 | } | |
| 236 | if (self.sections.items.len == 0) { | |
| 237 | // There must always be a null section in index 0 | |
| 238 | try self.sections.append(.{ | |
| 239 | .sh_name = 0, | |
| 240 | .sh_type = elf.SHT_NULL, | |
| 241 | .sh_flags = 0, | |
| 242 | .sh_addr = 0, | |
| 243 | .sh_offset = 0, | |
| 244 | .sh_size = 0, | |
| 245 | .sh_link = 0, | |
| 246 | .sh_info = 0, | |
| 247 | .sh_addralign = 0, | |
| 248 | .sh_entsize = 0, | |
| 249 | }); | |
| 250 | shdr_table_dirty = true; | |
| 251 | } | |
| 252 | if (self.shstrtab_index == null) { | |
| 253 | self.shstrtab_index = @intCast(u16, self.sections.items.len); | |
| 254 | assert(self.shstrtab.items.len == 0); | |
| 255 | try self.shstrtab.append(0); // need a 0 at position 0 | |
| 256 | const off = self.findFreeSpace(self.shstrtab.items.len, 1); | |
| 257 | //std.debug.warn("found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); | |
| 258 | try self.sections.append(.{ | |
| 259 | .sh_name = try self.makeString(".shstrtab"), | |
| 260 | .sh_type = elf.SHT_STRTAB, | |
| 261 | .sh_flags = 0, | |
| 262 | .sh_addr = 0, | |
| 263 | .sh_offset = off, | |
| 264 | .sh_size = self.shstrtab.items.len, | |
| 265 | .sh_link = 0, | |
| 266 | .sh_info = 0, | |
| 267 | .sh_addralign = 1, | |
| 268 | .sh_entsize = 0, | |
| 269 | }); | |
| 270 | shstrtab_dirty = true; | |
| 271 | shdr_table_dirty = true; | |
| 272 | } | |
| 273 | if (self.text_section_index == null) { | |
| 274 | self.text_section_index = @intCast(u16, self.sections.items.len); | |
| 275 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | |
| 276 | ||
| 277 | try self.sections.append(.{ | |
| 278 | .sh_name = try self.makeString(".text"), | |
| 279 | .sh_type = elf.SHT_PROGBITS, | |
| 280 | .sh_flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR, | |
| 281 | .sh_addr = phdr.p_vaddr, | |
| 282 | .sh_offset = phdr.p_offset, | |
| 283 | .sh_size = phdr.p_filesz, | |
| 284 | .sh_link = 0, | |
| 285 | .sh_info = 0, | |
| 286 | .sh_addralign = phdr.p_align, | |
| 287 | .sh_entsize = 0, | |
| 288 | }); | |
| 289 | shdr_table_dirty = true; | |
| 290 | } | |
| 291 | if (self.symtab_section_index == null) { | |
| 292 | self.symtab_section_index = @intCast(u16, self.sections.items.len); | |
| 293 | const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); | |
| 294 | const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); | |
| 295 | const file_size = self.module.exports.len * each_size; | |
| 296 | const off = self.findFreeSpace(file_size, min_align); | |
| 297 | //std.debug.warn("found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | |
| 298 | ||
| 299 | try self.sections.append(.{ | |
| 300 | .sh_name = try self.makeString(".symtab"), | |
| 301 | .sh_type = elf.SHT_SYMTAB, | |
| 302 | .sh_flags = 0, | |
| 303 | .sh_addr = 0, | |
| 304 | .sh_offset = off, | |
| 305 | .sh_size = file_size, | |
| 306 | // The section header index of the associated string table. | |
| 307 | .sh_link = self.shstrtab_index.?, | |
| 308 | .sh_info = @intCast(u32, self.module.exports.len), | |
| 309 | .sh_addralign = min_align, | |
| 310 | .sh_entsize = each_size, | |
| 311 | }); | |
| 312 | symtab_dirty = true; | |
| 313 | shdr_table_dirty = true; | |
| 314 | } | |
| 315 | const shsize: u64 = switch (ptr_width) { | |
| 316 | .p32 => @sizeOf(elf.Elf32_Shdr), | |
| 317 | .p64 => @sizeOf(elf.Elf64_Shdr), | |
| 318 | }; | |
| 319 | const shalign: u16 = switch (ptr_width) { | |
| 320 | .p32 => @alignOf(elf.Elf32_Shdr), | |
| 321 | .p64 => @alignOf(elf.Elf64_Shdr), | |
| 322 | }; | |
| 323 | if (self.shdr_table_offset == null) { | |
| 324 | self.shdr_table_offset = self.findFreeSpace(self.sections.items.len * shsize, shalign); | |
| 325 | shdr_table_dirty = true; | |
| 326 | } | |
| 327 | const phsize: u64 = switch (ptr_width) { | |
| 328 | .p32 => @sizeOf(elf.Elf32_Phdr), | |
| 329 | .p64 => @sizeOf(elf.Elf64_Phdr), | |
| 330 | }; | |
| 331 | const phalign: u16 = switch (ptr_width) { | |
| 332 | .p32 => @alignOf(elf.Elf32_Phdr), | |
| 333 | .p64 => @alignOf(elf.Elf64_Phdr), | |
| 334 | }; | |
| 335 | if (self.phdr_table_offset == null) { | |
| 336 | self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign); | |
| 337 | phdr_table_dirty = true; | |
| 338 | } | |
| 339 | const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 199 | 340 | |
| 200 | //if (shared) { | |
| 201 | // lj->args.append("-soname"); | |
| 202 | // lj->args.append(buf_ptr(soname)); | |
| 203 | //} | |
| 341 | try self.writeCodeAndSymbols(phdr_table_dirty, shdr_table_dirty); | |
| 204 | 342 | |
| 205 | // .o files | |
| 206 | for (ctx.comp.link_objects) |link_object| { | |
| 207 | const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object); | |
| 208 | try ctx.args.append(@ptrCast([*:0]const u8, link_obj_with_null.ptr)); | |
| 209 | } | |
| 210 | try addFnObjects(ctx); | |
| 211 | ||
| 212 | //if (g->out_type == OutTypeExe || g->out_type == OutTypeLib) { | |
| 213 | // if (g->libc_link_lib == nullptr) { | |
| 214 | // Buf *builtin_o_path = build_o(g, "builtin"); | |
| 215 | // lj->args.append(buf_ptr(builtin_o_path)); | |
| 216 | // } | |
| 217 | ||
| 218 | // // sometimes libgcc is missing stuff, so we still build compiler_rt and rely on weak linkage | |
| 219 | // Buf *compiler_rt_o_path = build_compiler_rt(g); | |
| 220 | // lj->args.append(buf_ptr(compiler_rt_o_path)); | |
| 221 | //} | |
| 222 | ||
| 223 | //for (size_t i = 0; i < g->link_libs_list.length; i += 1) { | |
| 224 | // LinkLib *link_lib = g->link_libs_list.at(i); | |
| 225 | // if (buf_eql_str(link_lib->name, "c")) { | |
| 226 | // continue; | |
| 227 | // } | |
| 228 | // Buf *arg; | |
| 229 | // if (buf_starts_with_str(link_lib->name, "/") || buf_ends_with_str(link_lib->name, ".a") || | |
| 230 | // buf_ends_with_str(link_lib->name, ".so")) | |
| 231 | // { | |
| 232 | // arg = link_lib->name; | |
| 233 | // } else { | |
| 234 | // arg = buf_sprintf("-l%s", buf_ptr(link_lib->name)); | |
| 235 | // } | |
| 236 | // lj->args.append(buf_ptr(arg)); | |
| 237 | //} | |
| 238 | ||
| 239 | // libc dep | |
| 240 | if (ctx.comp.haveLibC()) { | |
| 241 | if (ctx.comp.is_static) { | |
| 242 | try ctx.args.append("--start-group"); | |
| 243 | try ctx.args.append("-lgcc"); | |
| 244 | try ctx.args.append("-lgcc_eh"); | |
| 245 | try ctx.args.append("-lc"); | |
| 246 | try ctx.args.append("-lm"); | |
| 247 | try ctx.args.append("--end-group"); | |
| 343 | if (phdr_table_dirty) { | |
| 344 | const allocated_size = self.allocatedSize(self.phdr_table_offset.?); | |
| 345 | const needed_size = self.program_headers.items.len * phsize; | |
| 346 | ||
| 347 | if (needed_size > allocated_size) { | |
| 348 | self.phdr_table_offset = null; // free the space | |
| 349 | self.phdr_table_offset = self.findFreeSpace(needed_size, phalign); | |
| 350 | } | |
| 351 | ||
| 352 | const allocator = self.program_headers.allocator; | |
| 353 | switch (ptr_width) { | |
| 354 | .p32 => { | |
| 355 | const buf = try allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len); | |
| 356 | defer allocator.free(buf); | |
| 357 | ||
| 358 | for (buf) |*phdr, i| { | |
| 359 | phdr.* = progHeaderTo32(self.program_headers.items[i]); | |
| 360 | if (foreign_endian) { | |
| 361 | bswapAllFields(elf.Elf32_Phdr, phdr); | |
| 362 | } | |
| 363 | } | |
| 364 | try self.file.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); | |
| 365 | }, | |
| 366 | .p64 => { | |
| 367 | const buf = try allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len); | |
| 368 | defer allocator.free(buf); | |
| 369 | ||
| 370 | for (buf) |*phdr, i| { | |
| 371 | phdr.* = self.program_headers.items[i]; | |
| 372 | if (foreign_endian) { | |
| 373 | bswapAllFields(elf.Elf64_Phdr, phdr); | |
| 374 | } | |
| 375 | } | |
| 376 | try self.file.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); | |
| 377 | }, | |
| 378 | } | |
| 379 | } | |
| 380 | ||
| 381 | { | |
| 382 | const shstrtab_sect = &self.sections.items[self.shstrtab_index.?]; | |
| 383 | if (shstrtab_dirty or self.shstrtab.items.len != shstrtab_sect.sh_size) { | |
| 384 | const allocated_size = self.allocatedSize(shstrtab_sect.sh_offset); | |
| 385 | const needed_size = self.shstrtab.items.len; | |
| 386 | ||
| 387 | if (needed_size > allocated_size) { | |
| 388 | shstrtab_sect.sh_size = 0; // free the space | |
| 389 | shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); | |
| 390 | } | |
| 391 | shstrtab_sect.sh_size = needed_size; | |
| 392 | //std.debug.warn("shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); | |
| 393 | ||
| 394 | try self.file.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); | |
| 395 | if (!shdr_table_dirty) { | |
| 396 | // Then it won't get written with the others and we need to do it. | |
| 397 | try self.writeSectHeader(self.shstrtab_index.?); | |
| 398 | } | |
| 399 | } | |
| 400 | } | |
| 401 | if (shdr_table_dirty) { | |
| 402 | const allocated_size = self.allocatedSize(self.shdr_table_offset.?); | |
| 403 | const needed_size = self.sections.items.len * phsize; | |
| 404 | ||
| 405 | if (needed_size > allocated_size) { | |
| 406 | self.shdr_table_offset = null; // free the space | |
| 407 | self.shdr_table_offset = self.findFreeSpace(needed_size, phalign); | |
| 408 | } | |
| 409 | ||
| 410 | const allocator = self.sections.allocator; | |
| 411 | switch (ptr_width) { | |
| 412 | .p32 => { | |
| 413 | const buf = try allocator.alloc(elf.Elf32_Shdr, self.sections.items.len); | |
| 414 | defer allocator.free(buf); | |
| 415 | ||
| 416 | for (buf) |*shdr, i| { | |
| 417 | shdr.* = sectHeaderTo32(self.sections.items[i]); | |
| 418 | if (foreign_endian) { | |
| 419 | bswapAllFields(elf.Elf32_Shdr, shdr); | |
| 420 | } | |
| 421 | } | |
| 422 | try self.file.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?); | |
| 423 | }, | |
| 424 | .p64 => { | |
| 425 | const buf = try allocator.alloc(elf.Elf64_Shdr, self.sections.items.len); | |
| 426 | defer allocator.free(buf); | |
| 427 | ||
| 428 | for (buf) |*shdr, i| { | |
| 429 | shdr.* = self.sections.items[i]; | |
| 430 | //std.debug.warn("writing section {}\n", .{shdr.*}); | |
| 431 | if (foreign_endian) { | |
| 432 | bswapAllFields(elf.Elf64_Shdr, shdr); | |
| 433 | } | |
| 434 | } | |
| 435 | try self.file.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?); | |
| 436 | }, | |
| 437 | } | |
| 438 | } | |
| 439 | if (self.entry_addr == null) { | |
| 440 | const msg = try std.fmt.allocPrint(self.errors.allocator, "no entry point found", .{}); | |
| 441 | errdefer self.errors.allocator.free(msg); | |
| 442 | try self.errors.append(.{ | |
| 443 | .byte_offset = 0, | |
| 444 | .msg = msg, | |
| 445 | }); | |
| 248 | 446 | } else { |
| 249 | try ctx.args.append("-lgcc"); | |
| 250 | try ctx.args.append("--as-needed"); | |
| 251 | try ctx.args.append("-lgcc_s"); | |
| 252 | try ctx.args.append("--no-as-needed"); | |
| 253 | try ctx.args.append("-lc"); | |
| 254 | try ctx.args.append("-lm"); | |
| 255 | try ctx.args.append("-lgcc"); | |
| 256 | try ctx.args.append("--as-needed"); | |
| 257 | try ctx.args.append("-lgcc_s"); | |
| 258 | try ctx.args.append("--no-as-needed"); | |
| 447 | try self.writeElfHeader(); | |
| 259 | 448 | } |
| 449 | // TODO find end pos and truncate | |
| 260 | 450 | } |
| 261 | 451 | |
| 262 | // crt end | |
| 263 | if (ctx.link_in_crt) { | |
| 264 | try addPathJoin(ctx, ctx.libc.crt_dir.?, "crtn.o"); | |
| 265 | } | |
| 452 | fn writeElfHeader(self: *Update) !void { | |
| 453 | var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; | |
| 266 | 454 | |
| 267 | //if (ctx.comp.target != Target.Native) { | |
| 268 | // try ctx.args.append("--allow-shlib-undefined"); | |
| 269 | //} | |
| 270 | } | |
| 455 | var index: usize = 0; | |
| 456 | hdr_buf[0..4].* = "\x7fELF".*; | |
| 457 | index += 4; | |
| 271 | 458 | |
| 272 | fn addPathJoin(ctx: *Context, dirname: []const u8, basename: []const u8) !void { | |
| 273 | const full_path = try std.fs.path.join(&ctx.arena.allocator, &[_][]const u8{ dirname, basename }); | |
| 274 | const full_path_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, full_path); | |
| 275 | try ctx.args.append(@ptrCast([*:0]const u8, full_path_with_null.ptr)); | |
| 276 | } | |
| 459 | const ptr_width: enum { p32, p64 } = switch (self.module.target.cpu.arch.ptrBitWidth()) { | |
| 460 | 32 => .p32, | |
| 461 | 64 => .p64, | |
| 462 | else => return error.UnsupportedArchitecture, | |
| 463 | }; | |
| 464 | hdr_buf[index] = switch (ptr_width) { | |
| 465 | .p32 => elf.ELFCLASS32, | |
| 466 | .p64 => elf.ELFCLASS64, | |
| 467 | }; | |
| 468 | index += 1; | |
| 277 | 469 | |
| 278 | fn constructLinkerArgsCoff(ctx: *Context) !void { | |
| 279 | try ctx.args.append("-NOLOGO"); | |
| 470 | const endian = self.module.target.cpu.arch.endian(); | |
| 471 | hdr_buf[index] = switch (endian) { | |
| 472 | .Little => elf.ELFDATA2LSB, | |
| 473 | .Big => elf.ELFDATA2MSB, | |
| 474 | }; | |
| 475 | index += 1; | |
| 280 | 476 | |
| 281 | if (!ctx.comp.strip) { | |
| 282 | try ctx.args.append("-DEBUG"); | |
| 283 | } | |
| 477 | hdr_buf[index] = 1; // ELF version | |
| 478 | index += 1; | |
| 284 | 479 | |
| 285 | switch (ctx.comp.target.cpu.arch) { | |
| 286 | .i386 => try ctx.args.append("-MACHINE:X86"), | |
| 287 | .x86_64 => try ctx.args.append("-MACHINE:X64"), | |
| 288 | .aarch64 => try ctx.args.append("-MACHINE:ARM"), | |
| 289 | else => return error.UnsupportedLinkArchitecture, | |
| 290 | } | |
| 480 | // OS ABI, often set to 0 regardless of target platform | |
| 481 | // ABI Version, possibly used by glibc but not by static executables | |
| 482 | // padding | |
| 483 | mem.set(u8, hdr_buf[index..][0..9], 0); | |
| 484 | index += 9; | |
| 291 | 485 | |
| 292 | const is_library = ctx.comp.kind == .Lib; | |
| 486 | assert(index == 16); | |
| 293 | 487 | |
| 294 | const out_arg = try std.fmt.allocPrint(&ctx.arena.allocator, "-OUT:{}\x00", .{ctx.out_file_path.span()}); | |
| 295 | try ctx.args.append(@ptrCast([*:0]const u8, out_arg.ptr)); | |
| 488 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf.ET.EXEC), endian); | |
| 489 | index += 2; | |
| 296 | 490 | |
| 297 | if (ctx.comp.haveLibC()) { | |
| 298 | try ctx.args.append(@ptrCast([*:0]const u8, (try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", .{ctx.libc.msvc_lib_dir.?})).ptr)); | |
| 299 | try ctx.args.append(@ptrCast([*:0]const u8, (try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", .{ctx.libc.kernel32_lib_dir.?})).ptr)); | |
| 300 | try ctx.args.append(@ptrCast([*:0]const u8, (try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", .{ctx.libc.crt_dir.?})).ptr)); | |
| 301 | } | |
| 491 | const machine = self.module.target.cpu.arch.toElfMachine(); | |
| 492 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian); | |
| 493 | index += 2; | |
| 302 | 494 | |
| 303 | if (ctx.link_in_crt) { | |
| 304 | const lib_str = if (ctx.comp.is_static) "lib" else ""; | |
| 305 | const d_str = if (ctx.comp.build_mode == .Debug) "d" else ""; | |
| 495 | // ELF Version, again | |
| 496 | mem.writeInt(u32, hdr_buf[index..][0..4], 1, endian); | |
| 497 | index += 4; | |
| 306 | 498 | |
| 307 | if (ctx.comp.is_static) { | |
| 308 | const cmt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "libcmt{}.lib\x00", .{d_str}); | |
| 309 | try ctx.args.append(@ptrCast([*:0]const u8, cmt_lib_name.ptr)); | |
| 310 | } else { | |
| 311 | const msvcrt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "msvcrt{}.lib\x00", .{d_str}); | |
| 312 | try ctx.args.append(@ptrCast([*:0]const u8, msvcrt_lib_name.ptr)); | |
| 313 | } | |
| 499 | switch (ptr_width) { | |
| 500 | .p32 => { | |
| 501 | // e_entry | |
| 502 | mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.entry_addr.?), endian); | |
| 503 | index += 4; | |
| 504 | ||
| 505 | // e_phoff | |
| 506 | mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.phdr_table_offset.?), endian); | |
| 507 | index += 4; | |
| 314 | 508 | |
| 315 | const vcruntime_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "{}vcruntime{}.lib\x00", .{ | |
| 316 | lib_str, | |
| 317 | d_str, | |
| 318 | }); | |
| 319 | try ctx.args.append(@ptrCast([*:0]const u8, vcruntime_lib_name.ptr)); | |
| 320 | ||
| 321 | const crt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "{}ucrt{}.lib\x00", .{ lib_str, d_str }); | |
| 322 | try ctx.args.append(@ptrCast([*:0]const u8, crt_lib_name.ptr)); | |
| 323 | ||
| 324 | // Visual C++ 2015 Conformance Changes | |
| 325 | // https://msdn.microsoft.com/en-us/library/bb531344.aspx | |
| 326 | try ctx.args.append("legacy_stdio_definitions.lib"); | |
| 327 | ||
| 328 | // msvcrt depends on kernel32 | |
| 329 | try ctx.args.append("kernel32.lib"); | |
| 330 | } else { | |
| 331 | try ctx.args.append("-NODEFAULTLIB"); | |
| 332 | if (!is_library) { | |
| 333 | try ctx.args.append("-ENTRY:WinMainCRTStartup"); | |
| 509 | // e_shoff | |
| 510 | mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.shdr_table_offset.?), endian); | |
| 511 | index += 4; | |
| 512 | }, | |
| 513 | .p64 => { | |
| 514 | // e_entry | |
| 515 | mem.writeInt(u64, hdr_buf[index..][0..8], self.entry_addr.?, endian); | |
| 516 | index += 8; | |
| 517 | ||
| 518 | // e_phoff | |
| 519 | mem.writeInt(u64, hdr_buf[index..][0..8], self.phdr_table_offset.?, endian); | |
| 520 | index += 8; | |
| 521 | ||
| 522 | // e_shoff | |
| 523 | mem.writeInt(u64, hdr_buf[index..][0..8], self.shdr_table_offset.?, endian); | |
| 524 | index += 8; | |
| 525 | }, | |
| 334 | 526 | } |
| 335 | } | |
| 336 | 527 | |
| 337 | if (is_library and !ctx.comp.is_static) { | |
| 338 | try ctx.args.append("-DLL"); | |
| 339 | } | |
| 528 | const e_flags = 0; | |
| 529 | mem.writeInt(u32, hdr_buf[index..][0..4], e_flags, endian); | |
| 530 | index += 4; | |
| 340 | 531 | |
| 341 | for (ctx.comp.link_objects) |link_object| { | |
| 342 | const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object); | |
| 343 | try ctx.args.append(@ptrCast([*:0]const u8, link_obj_with_null.ptr)); | |
| 344 | } | |
| 345 | try addFnObjects(ctx); | |
| 532 | const e_ehsize: u16 = switch (ptr_width) { | |
| 533 | .p32 => @sizeOf(elf.Elf32_Ehdr), | |
| 534 | .p64 => @sizeOf(elf.Elf64_Ehdr), | |
| 535 | }; | |
| 536 | mem.writeInt(u16, hdr_buf[index..][0..2], e_ehsize, endian); | |
| 537 | index += 2; | |
| 346 | 538 | |
| 347 | switch (ctx.comp.kind) { | |
| 348 | .Exe, .Lib => { | |
| 349 | if (!ctx.comp.haveLibC()) { | |
| 350 | @panic("TODO"); | |
| 351 | } | |
| 352 | }, | |
| 353 | .Obj => {}, | |
| 354 | } | |
| 355 | } | |
| 539 | const e_phentsize: u16 = switch (ptr_width) { | |
| 540 | .p32 => @sizeOf(elf.Elf32_Phdr), | |
| 541 | .p64 => @sizeOf(elf.Elf64_Phdr), | |
| 542 | }; | |
| 543 | mem.writeInt(u16, hdr_buf[index..][0..2], e_phentsize, endian); | |
| 544 | index += 2; | |
| 356 | 545 | |
| 357 | fn constructLinkerArgsMachO(ctx: *Context) !void { | |
| 358 | try ctx.args.append("-demangle"); | |
| 546 | const e_phnum = @intCast(u16, self.program_headers.items.len); | |
| 547 | mem.writeInt(u16, hdr_buf[index..][0..2], e_phnum, endian); | |
| 548 | index += 2; | |
| 359 | 549 | |
| 360 | if (ctx.comp.linker_rdynamic) { | |
| 361 | try ctx.args.append("-export_dynamic"); | |
| 362 | } | |
| 550 | const e_shentsize: u16 = switch (ptr_width) { | |
| 551 | .p32 => @sizeOf(elf.Elf32_Shdr), | |
| 552 | .p64 => @sizeOf(elf.Elf64_Shdr), | |
| 553 | }; | |
| 554 | mem.writeInt(u16, hdr_buf[index..][0..2], e_shentsize, endian); | |
| 555 | index += 2; | |
| 363 | 556 | |
| 364 | const is_lib = ctx.comp.kind == .Lib; | |
| 365 | const shared = !ctx.comp.is_static and is_lib; | |
| 366 | if (ctx.comp.is_static) { | |
| 367 | try ctx.args.append("-static"); | |
| 368 | } else { | |
| 369 | try ctx.args.append("-dynamic"); | |
| 370 | } | |
| 557 | const e_shnum = @intCast(u16, self.sections.items.len); | |
| 558 | mem.writeInt(u16, hdr_buf[index..][0..2], e_shnum, endian); | |
| 559 | index += 2; | |
| 371 | 560 | |
| 372 | try ctx.args.append("-arch"); | |
| 373 | try ctx.args.append(util.getDarwinArchString(ctx.comp.target)); | |
| 561 | mem.writeInt(u16, hdr_buf[index..][0..2], self.shstrtab_index.?, endian); | |
| 562 | index += 2; | |
| 374 | 563 | |
| 375 | const platform = try DarwinPlatform.get(ctx.comp); | |
| 376 | switch (platform.kind) { | |
| 377 | .MacOS => try ctx.args.append("-macosx_version_min"), | |
| 378 | .IPhoneOS => try ctx.args.append("-iphoneos_version_min"), | |
| 379 | .IPhoneOSSimulator => try ctx.args.append("-ios_simulator_version_min"), | |
| 564 | assert(index == e_ehsize); | |
| 565 | ||
| 566 | try self.file.pwriteAll(hdr_buf[0..index], 0); | |
| 380 | 567 | } |
| 381 | const ver_str = try std.fmt.allocPrint(&ctx.arena.allocator, "{}.{}.{}\x00", .{ | |
| 382 | platform.major, | |
| 383 | platform.minor, | |
| 384 | platform.micro, | |
| 385 | }); | |
| 386 | try ctx.args.append(@ptrCast([*:0]const u8, ver_str.ptr)); | |
| 387 | ||
| 388 | if (ctx.comp.kind == .Exe) { | |
| 389 | if (ctx.comp.is_static) { | |
| 390 | try ctx.args.append("-no_pie"); | |
| 391 | } else { | |
| 392 | try ctx.args.append("-pie"); | |
| 568 | ||
| 569 | fn writeCodeAndSymbols(self: *Update, phdr_table_dirty: bool, shdr_table_dirty: bool) !void { | |
| 570 | // index 0 is always a null symbol | |
| 571 | try self.symbols.resize(1); | |
| 572 | self.symbols.items[0] = .{ | |
| 573 | .st_name = 0, | |
| 574 | .st_info = 0, | |
| 575 | .st_other = 0, | |
| 576 | .st_shndx = 0, | |
| 577 | .st_value = 0, | |
| 578 | .st_size = 0, | |
| 579 | }; | |
| 580 | ||
| 581 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | |
| 582 | var vaddr: u64 = phdr.p_vaddr; | |
| 583 | var file_off: u64 = phdr.p_offset; | |
| 584 | ||
| 585 | var code = std.ArrayList(u8).init(self.sections.allocator); | |
| 586 | defer code.deinit(); | |
| 587 | ||
| 588 | for (self.module.exports) |exp| { | |
| 589 | code.shrink(0); | |
| 590 | var symbol = try codegen.generateSymbol(exp.typed_value, self.module.*, &code); | |
| 591 | defer symbol.deinit(code.allocator); | |
| 592 | if (symbol.errors.len != 0) { | |
| 593 | for (symbol.errors) |err| { | |
| 594 | const msg = try mem.dupe(self.errors.allocator, u8, err.msg); | |
| 595 | errdefer self.errors.allocator.free(msg); | |
| 596 | try self.errors.append(.{ | |
| 597 | .byte_offset = err.byte_offset, | |
| 598 | .msg = msg, | |
| 599 | }); | |
| 600 | } | |
| 601 | continue; | |
| 602 | } | |
| 603 | try self.file.pwriteAll(code.items, file_off); | |
| 604 | ||
| 605 | if (mem.eql(u8, exp.name, "_start")) { | |
| 606 | self.entry_addr = vaddr; | |
| 607 | } | |
| 608 | (try self.symbols.addOne()).* = .{ | |
| 609 | .st_name = try self.makeString(exp.name), | |
| 610 | .st_info = (elf.STB_LOCAL << 4) | elf.STT_FUNC, | |
| 611 | .st_other = 0, | |
| 612 | .st_shndx = self.text_section_index.?, | |
| 613 | .st_value = vaddr, | |
| 614 | .st_size = code.items.len, | |
| 615 | }; | |
| 616 | vaddr += code.items.len; | |
| 617 | } | |
| 618 | ||
| 619 | { | |
| 620 | // Now that we know the code size, we need to update the program header for executable code | |
| 621 | phdr.p_memsz = vaddr - phdr.p_vaddr; | |
| 622 | phdr.p_filesz = phdr.p_memsz; | |
| 623 | ||
| 624 | const shdr = &self.sections.items[self.text_section_index.?]; | |
| 625 | shdr.sh_size = phdr.p_filesz; | |
| 626 | ||
| 627 | if (!phdr_table_dirty) { | |
| 628 | // Then it won't get written with the others and we need to do it. | |
| 629 | try self.writeProgHeader(self.phdr_load_re_index.?); | |
| 630 | } | |
| 631 | if (!shdr_table_dirty) { | |
| 632 | // Then it won't get written with the others and we need to do it. | |
| 633 | try self.writeSectHeader(self.text_section_index.?); | |
| 634 | } | |
| 393 | 635 | } |
| 636 | ||
| 637 | return self.writeSymbols(); | |
| 394 | 638 | } |
| 395 | 639 | |
| 396 | try ctx.args.append("-o"); | |
| 397 | try ctx.args.append(ctx.out_file_path.span()); | |
| 398 | ||
| 399 | if (shared) { | |
| 400 | try ctx.args.append("-headerpad_max_install_names"); | |
| 401 | } else if (ctx.comp.is_static) { | |
| 402 | try ctx.args.append("-lcrt0.o"); | |
| 403 | } else { | |
| 404 | switch (platform.kind) { | |
| 405 | .MacOS => { | |
| 406 | if (platform.versionLessThan(10, 5)) { | |
| 407 | try ctx.args.append("-lcrt1.o"); | |
| 408 | } else if (platform.versionLessThan(10, 6)) { | |
| 409 | try ctx.args.append("-lcrt1.10.5.o"); | |
| 410 | } else if (platform.versionLessThan(10, 8)) { | |
| 411 | try ctx.args.append("-lcrt1.10.6.o"); | |
| 640 | fn writeProgHeader(self: *Update, index: usize) !void { | |
| 641 | const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 642 | const offset = self.program_headers.items[index].p_offset; | |
| 643 | switch (self.module.target.cpu.arch.ptrBitWidth()) { | |
| 644 | 32 => { | |
| 645 | var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; | |
| 646 | if (foreign_endian) { | |
| 647 | bswapAllFields(elf.Elf32_Phdr, &phdr[0]); | |
| 412 | 648 | } |
| 649 | return self.file.pwriteAll(mem.sliceAsBytes(&phdr), offset); | |
| 413 | 650 | }, |
| 414 | .IPhoneOS => { | |
| 415 | if (ctx.comp.target.cpu.arch == .aarch64) { | |
| 416 | // iOS does not need any crt1 files for arm64 | |
| 417 | } else if (platform.versionLessThan(3, 1)) { | |
| 418 | try ctx.args.append("-lcrt1.o"); | |
| 419 | } else if (platform.versionLessThan(6, 0)) { | |
| 420 | try ctx.args.append("-lcrt1.3.1.o"); | |
| 651 | 64 => { | |
| 652 | var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]}; | |
| 653 | if (foreign_endian) { | |
| 654 | bswapAllFields(elf.Elf64_Phdr, &phdr[0]); | |
| 421 | 655 | } |
| 656 | return self.file.pwriteAll(mem.sliceAsBytes(&phdr), offset); | |
| 422 | 657 | }, |
| 423 | .IPhoneOSSimulator => {}, // no crt1.o needed | |
| 658 | else => return error.UnsupportedArchitecture, | |
| 424 | 659 | } |
| 425 | 660 | } |
| 426 | 661 | |
| 427 | for (ctx.comp.link_objects) |link_object| { | |
| 428 | const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object); | |
| 429 | try ctx.args.append(@ptrCast([*:0]const u8, link_obj_with_null.ptr)); | |
| 430 | } | |
| 431 | try addFnObjects(ctx); | |
| 432 | ||
| 433 | // TODO | |
| 434 | //if (ctx.comp.target == Target.Native) { | |
| 435 | // for (ctx.comp.link_libs_list.span()) |lib| { | |
| 436 | // if (mem.eql(u8, lib.name, "c")) { | |
| 437 | // // on Darwin, libSystem has libc in it, but also you have to use it | |
| 438 | // // to make syscalls because the syscall numbers are not documented | |
| 439 | // // and change between versions. | |
| 440 | // // so we always link against libSystem | |
| 441 | // try ctx.args.append("-lSystem"); | |
| 442 | // } else { | |
| 443 | // if (mem.indexOfScalar(u8, lib.name, '/') == null) { | |
| 444 | // const arg = try std.fmt.allocPrint(&ctx.arena.allocator, "-l{}\x00", .{lib.name}); | |
| 445 | // try ctx.args.append(@ptrCast([*:0]const u8, arg.ptr)); | |
| 446 | // } else { | |
| 447 | // const arg = try std.cstr.addNullByte(&ctx.arena.allocator, lib.name); | |
| 448 | // try ctx.args.append(@ptrCast([*:0]const u8, arg.ptr)); | |
| 449 | // } | |
| 450 | // } | |
| 451 | // } | |
| 452 | //} else { | |
| 453 | // try ctx.args.append("-undefined"); | |
| 454 | // try ctx.args.append("dynamic_lookup"); | |
| 455 | //} | |
| 456 | ||
| 457 | if (platform.kind == .MacOS) { | |
| 458 | if (platform.versionLessThan(10, 5)) { | |
| 459 | try ctx.args.append("-lgcc_s.10.4"); | |
| 460 | } else if (platform.versionLessThan(10, 6)) { | |
| 461 | try ctx.args.append("-lgcc_s.10.5"); | |
| 662 | fn writeSectHeader(self: *Update, index: usize) !void { | |
| 663 | const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 664 | const offset = self.sections.items[index].sh_offset; | |
| 665 | switch (self.module.target.cpu.arch.ptrBitWidth()) { | |
| 666 | 32 => { | |
| 667 | var shdr: [1]elf.Elf32_Shdr = undefined; | |
| 668 | shdr[0] = sectHeaderTo32(self.sections.items[index]); | |
| 669 | if (foreign_endian) { | |
| 670 | bswapAllFields(elf.Elf32_Shdr, &shdr[0]); | |
| 671 | } | |
| 672 | return self.file.pwriteAll(mem.sliceAsBytes(&shdr), offset); | |
| 673 | }, | |
| 674 | 64 => { | |
| 675 | var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]}; | |
| 676 | if (foreign_endian) { | |
| 677 | bswapAllFields(elf.Elf64_Shdr, &shdr[0]); | |
| 678 | } | |
| 679 | return self.file.pwriteAll(mem.sliceAsBytes(&shdr), offset); | |
| 680 | }, | |
| 681 | else => return error.UnsupportedArchitecture, | |
| 462 | 682 | } |
| 463 | } else { | |
| 464 | @panic("TODO"); | |
| 465 | 683 | } |
| 466 | } | |
| 467 | ||
| 468 | fn constructLinkerArgsWasm(ctx: *Context) void { | |
| 469 | @panic("TODO"); | |
| 470 | } | |
| 471 | 684 | |
| 472 | fn addFnObjects(ctx: *Context) !void { | |
| 473 | const held = ctx.comp.fn_link_set.acquire(); | |
| 474 | defer held.release(); | |
| 475 | ||
| 476 | var it = held.value.first; | |
| 477 | while (it) |node| { | |
| 478 | const fn_val = node.data orelse { | |
| 479 | // handle the tombstone. See Value.Fn.destroy. | |
| 480 | it = node.next; | |
| 481 | held.value.remove(node); | |
| 482 | ctx.comp.gpa().destroy(node); | |
| 483 | continue; | |
| 685 | fn writeSymbols(self: *Update) !void { | |
| 686 | const ptr_width: enum { p32, p64 } = switch (self.module.target.cpu.arch.ptrBitWidth()) { | |
| 687 | 32 => .p32, | |
| 688 | 64 => .p64, | |
| 689 | else => return error.UnsupportedArchitecture, | |
| 484 | 690 | }; |
| 485 | try ctx.args.append(fn_val.containing_object.span()); | |
| 486 | it = node.next; | |
| 691 | const small_ptr = ptr_width == .p32; | |
| 692 | const syms_sect = &self.sections.items[self.symtab_section_index.?]; | |
| 693 | const sym_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); | |
| 694 | const sym_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); | |
| 695 | ||
| 696 | const allocated_size = self.allocatedSize(syms_sect.sh_offset); | |
| 697 | const needed_size = self.symbols.items.len * sym_size; | |
| 698 | if (needed_size > allocated_size) { | |
| 699 | syms_sect.sh_size = 0; // free the space | |
| 700 | syms_sect.sh_offset = self.findFreeSpace(needed_size, sym_align); | |
| 701 | //std.debug.warn("moved symtab to 0x{x} to 0x{x}\n", .{ syms_sect.sh_offset, syms_sect.sh_offset + needed_size }); | |
| 702 | } | |
| 703 | //std.debug.warn("symtab start=0x{x} end=0x{x}\n", .{ syms_sect.sh_offset, syms_sect.sh_offset + needed_size }); | |
| 704 | syms_sect.sh_size = needed_size; | |
| 705 | syms_sect.sh_info = @intCast(u32, self.symbols.items.len); | |
| 706 | const allocator = self.symbols.allocator; | |
| 707 | const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 708 | switch (ptr_width) { | |
| 709 | .p32 => { | |
| 710 | const buf = try allocator.alloc(elf.Elf32_Sym, self.symbols.items.len); | |
| 711 | defer allocator.free(buf); | |
| 712 | ||
| 713 | for (buf) |*sym, i| { | |
| 714 | sym.* = .{ | |
| 715 | .st_name = self.symbols.items[i].st_name, | |
| 716 | .st_value = @intCast(u32, self.symbols.items[i].st_value), | |
| 717 | .st_size = @intCast(u32, self.symbols.items[i].st_size), | |
| 718 | .st_info = self.symbols.items[i].st_info, | |
| 719 | .st_other = self.symbols.items[i].st_other, | |
| 720 | .st_shndx = self.symbols.items[i].st_shndx, | |
| 721 | }; | |
| 722 | if (foreign_endian) { | |
| 723 | bswapAllFields(elf.Elf32_Sym, sym); | |
| 724 | } | |
| 725 | } | |
| 726 | try self.file.pwriteAll(mem.sliceAsBytes(buf), syms_sect.sh_offset); | |
| 727 | }, | |
| 728 | .p64 => { | |
| 729 | const buf = try allocator.alloc(elf.Elf64_Sym, self.symbols.items.len); | |
| 730 | defer allocator.free(buf); | |
| 731 | ||
| 732 | for (buf) |*sym, i| { | |
| 733 | sym.* = .{ | |
| 734 | .st_name = self.symbols.items[i].st_name, | |
| 735 | .st_value = self.symbols.items[i].st_value, | |
| 736 | .st_size = self.symbols.items[i].st_size, | |
| 737 | .st_info = self.symbols.items[i].st_info, | |
| 738 | .st_other = self.symbols.items[i].st_other, | |
| 739 | .st_shndx = self.symbols.items[i].st_shndx, | |
| 740 | }; | |
| 741 | if (foreign_endian) { | |
| 742 | bswapAllFields(elf.Elf64_Sym, sym); | |
| 743 | } | |
| 744 | } | |
| 745 | try self.file.pwriteAll(mem.sliceAsBytes(buf), syms_sect.sh_offset); | |
| 746 | }, | |
| 747 | } | |
| 487 | 748 | } |
| 488 | } | |
| 749 | }; | |
| 489 | 750 | |
| 490 | const DarwinPlatform = struct { | |
| 491 | kind: Kind, | |
| 492 | major: u32, | |
| 493 | minor: u32, | |
| 494 | micro: u32, | |
| 751 | /// Truncates the existing file contents and overwrites the contents. | |
| 752 | /// Returns an error if `file` is not already open with +read +write +seek abilities. | |
| 753 | pub fn writeExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | |
| 754 | var update = Update{ | |
| 755 | .file = file, | |
| 756 | .module = &module, | |
| 757 | .sections = std.ArrayList(elf.Elf64_Shdr).init(allocator), | |
| 758 | .shdr_table_offset = null, | |
| 759 | .program_headers = std.ArrayList(elf.Elf64_Phdr).init(allocator), | |
| 760 | .phdr_table_offset = null, | |
| 761 | .phdr_load_re_index = null, | |
| 762 | .entry_addr = null, | |
| 763 | .shstrtab = std.ArrayList(u8).init(allocator), | |
| 764 | .shstrtab_index = null, | |
| 765 | .text_section_index = null, | |
| 766 | .symtab_section_index = null, | |
| 767 | ||
| 768 | .symbols = std.ArrayList(elf.Elf64_Sym).init(allocator), | |
| 769 | ||
| 770 | .errors = std.ArrayList(ErrorMsg).init(allocator), | |
| 771 | }; | |
| 772 | defer update.deinit(); | |
| 495 | 773 | |
| 496 | const Kind = enum { | |
| 497 | MacOS, | |
| 498 | IPhoneOS, | |
| 499 | IPhoneOSSimulator, | |
| 774 | try update.perform(); | |
| 775 | return Result{ | |
| 776 | .errors = update.errors.toOwnedSlice(), | |
| 500 | 777 | }; |
| 778 | } | |
| 501 | 779 | |
| 502 | fn get(comp: *Compilation) !DarwinPlatform { | |
| 503 | var result: DarwinPlatform = undefined; | |
| 504 | const ver_str = switch (comp.darwin_version_min) { | |
| 505 | .MacOS => |ver| blk: { | |
| 506 | result.kind = .MacOS; | |
| 507 | break :blk ver; | |
| 508 | }, | |
| 509 | .Ios => |ver| blk: { | |
| 510 | result.kind = .IPhoneOS; | |
| 511 | break :blk ver; | |
| 512 | }, | |
| 513 | .None => blk: { | |
| 514 | assert(comp.target.os.tag == .macosx); | |
| 515 | result.kind = .MacOS; | |
| 516 | break :blk "10.14"; | |
| 517 | }, | |
| 518 | }; | |
| 780 | /// Returns error.IncrFailed if incremental update could not be performed. | |
| 781 | fn updateExecutableFileInner(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | |
| 782 | //var ehdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; | |
| 519 | 783 | |
| 520 | var had_extra: bool = undefined; | |
| 521 | try darwinGetReleaseVersion( | |
| 522 | ver_str, | |
| 523 | &result.major, | |
| 524 | &result.minor, | |
| 525 | &result.micro, | |
| 526 | &had_extra, | |
| 527 | ); | |
| 528 | if (had_extra or result.major != 10 or result.minor >= 100 or result.micro >= 100) { | |
| 529 | return error.InvalidDarwinVersionString; | |
| 530 | } | |
| 784 | // TODO implement incremental linking | |
| 785 | return error.IncrFailed; | |
| 786 | } | |
| 531 | 787 | |
| 532 | if (result.kind == .IPhoneOS) { | |
| 533 | switch (comp.target.cpu.arch) { | |
| 534 | .i386, | |
| 535 | .x86_64, | |
| 536 | => result.kind = .IPhoneOSSimulator, | |
| 537 | else => {}, | |
| 538 | } | |
| 539 | } | |
| 540 | return result; | |
| 541 | } | |
| 788 | /// Saturating multiplication | |
| 789 | fn satMul(a: var, b: var) @TypeOf(a, b) { | |
| 790 | const T = @TypeOf(a, b); | |
| 791 | return std.math.mul(T, a, b) catch std.math.maxInt(T); | |
| 792 | } | |
| 542 | 793 | |
| 543 | fn versionLessThan(self: DarwinPlatform, major: u32, minor: u32) bool { | |
| 544 | if (self.major < major) | |
| 545 | return true; | |
| 546 | if (self.major > major) | |
| 547 | return false; | |
| 548 | if (self.minor < minor) | |
| 549 | return true; | |
| 550 | return false; | |
| 551 | } | |
| 552 | }; | |
| 794 | fn bswapAllFields(comptime S: type, ptr: *S) void { | |
| 795 | @panic("TODO implement bswapAllFields"); | |
| 796 | } | |
| 553 | 797 | |
| 554 | /// Parse (([0-9]+)(.([0-9]+)(.([0-9]+)?))?)? and return the | |
| 555 | /// grouped values as integers. Numbers which are not provided are set to 0. | |
| 556 | /// return true if the entire string was parsed (9.2), or all groups were | |
| 557 | /// parsed (10.3.5extrastuff). | |
| 558 | fn darwinGetReleaseVersion(str: []const u8, major: *u32, minor: *u32, micro: *u32, had_extra: *bool) !void { | |
| 559 | major.* = 0; | |
| 560 | minor.* = 0; | |
| 561 | micro.* = 0; | |
| 562 | had_extra.* = false; | |
| 563 | ||
| 564 | if (str.len == 0) | |
| 565 | return error.InvalidDarwinVersionString; | |
| 566 | ||
| 567 | var start_pos: usize = 0; | |
| 568 | for ([_]*u32{ major, minor, micro }) |v| { | |
| 569 | const dot_pos = mem.indexOfScalarPos(u8, str, start_pos, '.'); | |
| 570 | const end_pos = dot_pos orelse str.len; | |
| 571 | v.* = std.fmt.parseUnsigned(u32, str[start_pos..end_pos], 10) catch return error.InvalidDarwinVersionString; | |
| 572 | start_pos = (dot_pos orelse return) + 1; | |
| 573 | if (start_pos == str.len) return; | |
| 574 | } | |
| 575 | had_extra.* = true; | |
| 798 | fn progHeaderTo32(phdr: elf.Elf64_Phdr) elf.Elf32_Phdr { | |
| 799 | return .{ | |
| 800 | .p_type = phdr.p_type, | |
| 801 | .p_flags = phdr.p_flags, | |
| 802 | .p_offset = @intCast(u32, phdr.p_offset), | |
| 803 | .p_vaddr = @intCast(u32, phdr.p_vaddr), | |
| 804 | .p_paddr = @intCast(u32, phdr.p_paddr), | |
| 805 | .p_filesz = @intCast(u32, phdr.p_filesz), | |
| 806 | .p_memsz = @intCast(u32, phdr.p_memsz), | |
| 807 | .p_align = @intCast(u32, phdr.p_align), | |
| 808 | }; | |
| 809 | } | |
| 810 | ||
| 811 | fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr { | |
| 812 | return .{ | |
| 813 | .sh_name = shdr.sh_name, | |
| 814 | .sh_type = shdr.sh_type, | |
| 815 | .sh_flags = @intCast(u32, shdr.sh_flags), | |
| 816 | .sh_addr = @intCast(u32, shdr.sh_addr), | |
| 817 | .sh_offset = @intCast(u32, shdr.sh_offset), | |
| 818 | .sh_size = @intCast(u32, shdr.sh_size), | |
| 819 | .sh_link = shdr.sh_link, | |
| 820 | .sh_info = shdr.sh_info, | |
| 821 | .sh_addralign = @intCast(u32, shdr.sh_addralign), | |
| 822 | .sh_entsize = @intCast(u32, shdr.sh_entsize), | |
| 823 | }; | |
| 576 | 824 | } |
src-self-hosted/value.zig+50| ... | ... | @@ -264,6 +264,56 @@ pub const Value = extern union { |
| 264 | 264 | } |
| 265 | 265 | } |
| 266 | 266 | |
| 267 | /// Asserts the value is an integer and it fits in a u64 | |
| 268 | pub fn toUnsignedInt(self: Value) u64 { | |
| 269 | switch (self.tag()) { | |
| 270 | .ty, | |
| 271 | .u8_type, | |
| 272 | .i8_type, | |
| 273 | .isize_type, | |
| 274 | .usize_type, | |
| 275 | .c_short_type, | |
| 276 | .c_ushort_type, | |
| 277 | .c_int_type, | |
| 278 | .c_uint_type, | |
| 279 | .c_long_type, | |
| 280 | .c_ulong_type, | |
| 281 | .c_longlong_type, | |
| 282 | .c_ulonglong_type, | |
| 283 | .c_longdouble_type, | |
| 284 | .f16_type, | |
| 285 | .f32_type, | |
| 286 | .f64_type, | |
| 287 | .f128_type, | |
| 288 | .c_void_type, | |
| 289 | .bool_type, | |
| 290 | .void_type, | |
| 291 | .type_type, | |
| 292 | .anyerror_type, | |
| 293 | .comptime_int_type, | |
| 294 | .comptime_float_type, | |
| 295 | .noreturn_type, | |
| 296 | .fn_naked_noreturn_no_args_type, | |
| 297 | .single_const_pointer_to_comptime_int_type, | |
| 298 | .const_slice_u8_type, | |
| 299 | .void_value, | |
| 300 | .noreturn_value, | |
| 301 | .bool_true, | |
| 302 | .bool_false, | |
| 303 | .function, | |
| 304 | .ref, | |
| 305 | .ref_val, | |
| 306 | .bytes, | |
| 307 | => unreachable, | |
| 308 | ||
| 309 | .zero => return 0, | |
| 310 | ||
| 311 | .int_u64 => return self.cast(Payload.Int_u64).?.int, | |
| 312 | .int_i64 => return @intCast(u64, self.cast(Payload.Int_u64).?.int), | |
| 313 | .int_big => return self.cast(Payload.IntBig).?.big_int.to(u64) catch unreachable, | |
| 314 | } | |
| 315 | } | |
| 316 | ||
| 267 | 317 | /// Asserts the value is an integer, and the destination type is ComptimeInt or Int. |
| 268 | 318 | pub fn intFitsInType(self: Value, ty: Type, target: Target) bool { |
| 269 | 319 | switch (self.tag()) { |