| author | |
| committer | |
| log | f89dbe6c4ebe3fa1ffe3eb455ed96fe615e2c903 |
| tree | 33e63b5a6d5d422a28f9363d37dc80673bd3fec4 |
| parent | 28729efe2998579fc36a35e5bdab12727ece1e7a |
4 files changed, 110 insertions(+), 52 deletions(-)
lib/std/target.zig+1| ... | ... | @@ -404,6 +404,7 @@ pub const Target = struct { |
| 404 | 404 | }; |
| 405 | 405 | |
| 406 | 406 | pub const ObjectFormat = enum { |
| 407 | /// TODO Get rid of this one. | |
| 407 | 408 | unknown, |
| 408 | 409 | coff, |
| 409 | 410 | elf, |
src-self-hosted/codegen.zig+38-8| ... | ... | @@ -39,7 +39,7 @@ pub fn generateSymbol(typed_value: ir.TypedValue, module: ir.Module, code: *std. |
| 39 | 39 | defer function.inst_table.deinit(); |
| 40 | 40 | defer function.errors.deinit(); |
| 41 | 41 | |
| 42 | for (module_fn.body) |inst| { | |
| 42 | for (module_fn.body.instructions) |inst| { | |
| 43 | 43 | const new_inst = function.genFuncInst(inst) catch |err| switch (err) { |
| 44 | 44 | error.CodegenFail => { |
| 45 | 45 | assert(function.errors.items.len != 0); |
| ... | ... | @@ -77,32 +77,62 @@ const Function = struct { |
| 77 | 77 | |
| 78 | 78 | fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue { |
| 79 | 79 | switch (inst.tag) { |
| 80 | .unreach => return self.genPanic(inst.src), | |
| 80 | .unreach => return MCValue{ .unreach = {} }, | |
| 81 | 81 | .constant => unreachable, // excluded from function bodies |
| 82 | 82 | .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?), |
| 83 | 83 | .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?), |
| 84 | 84 | .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), |
| 85 | .ret => return self.genRet(inst.cast(ir.Inst.Ret).?), | |
| 86 | .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?), | |
| 87 | .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?), | |
| 88 | .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?), | |
| 89 | .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?), | |
| 85 | 90 | } |
| 86 | 91 | } |
| 87 | 92 | |
| 88 | fn genPanic(self: *Function, src: usize) !MCValue { | |
| 89 | // TODO change this to call the panic function | |
| 93 | fn genBreakpoint(self: *Function, src: usize) !MCValue { | |
| 90 | 94 | switch (self.module.target.cpu.arch) { |
| 91 | 95 | .i386, .x86_64 => { |
| 92 | 96 | try self.code.append(0xcc); // int3 |
| 93 | 97 | }, |
| 94 | else => return self.fail(src, "TODO implement panic for {}", .{self.module.target.cpu.arch}), | |
| 98 | else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.module.target.cpu.arch}), | |
| 95 | 99 | } |
| 96 | 100 | return .unreach; |
| 97 | 101 | } |
| 98 | 102 | |
| 99 | fn genRet(self: *Function, src: usize) !void { | |
| 100 | // TODO change this to call the panic function | |
| 103 | fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue { | |
| 101 | 104 | switch (self.module.target.cpu.arch) { |
| 102 | 105 | .i386, .x86_64 => { |
| 103 | 106 | try self.code.append(0xc3); // ret |
| 104 | 107 | }, |
| 105 | else => return self.fail(src, "TODO implement ret for {}", .{self.module.target.cpu.arch}), | |
| 108 | else => return self.fail(inst.base.src, "TODO implement return for {}", .{self.module.target.cpu.arch}), | |
| 109 | } | |
| 110 | return .unreach; | |
| 111 | } | |
| 112 | ||
| 113 | fn genCmp(self: *Function, inst: *ir.Inst.Cmp) !MCValue { | |
| 114 | switch (self.module.target.cpu.arch) { | |
| 115 | else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.module.target.cpu.arch}), | |
| 116 | } | |
| 117 | } | |
| 118 | ||
| 119 | fn genCondBr(self: *Function, inst: *ir.Inst.CondBr) !MCValue { | |
| 120 | switch (self.module.target.cpu.arch) { | |
| 121 | else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.module.target.cpu.arch}), | |
| 122 | } | |
| 123 | } | |
| 124 | ||
| 125 | fn genIsNull(self: *Function, inst: *ir.Inst.IsNull) !MCValue { | |
| 126 | switch (self.module.target.cpu.arch) { | |
| 127 | else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.module.target.cpu.arch}), | |
| 128 | } | |
| 129 | } | |
| 130 | ||
| 131 | fn genIsNonNull(self: *Function, inst: *ir.Inst.IsNonNull) !MCValue { | |
| 132 | // Here you can specialize this instruction if it makes sense to, otherwise the default | |
| 133 | // will call genIsNull and invert the result. | |
| 134 | switch (self.module.target.cpu.arch) { | |
| 135 | else => return self.fail(inst.base.src, "TODO call genIsNull and invert the result ", .{}), | |
| 106 | 136 | } |
| 107 | 137 | } |
| 108 | 138 |
src-self-hosted/ir.zig+31-28| ... | ... | @@ -156,6 +156,9 @@ pub const Module = struct { |
| 156 | 156 | arena: std.heap.ArenaAllocator, |
| 157 | 157 | fns: []Fn, |
| 158 | 158 | target: Target, |
| 159 | link_mode: std.builtin.LinkMode, | |
| 160 | output_mode: std.builtin.OutputMode, | |
| 161 | object_format: std.Target.ObjectFormat, | |
| 159 | 162 | |
| 160 | 163 | pub const Export = struct { |
| 161 | 164 | name: []const u8, |
| ... | ... | @@ -190,7 +193,14 @@ pub const ErrorMsg = struct { |
| 190 | 193 | msg: []const u8, |
| 191 | 194 | }; |
| 192 | 195 | |
| 193 | pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) !Module { | |
| 196 | pub const AnalyzeOptions = struct { | |
| 197 | target: Target, | |
| 198 | output_mode: std.builtin.OutputMode, | |
| 199 | link_mode: std.builtin.LinkMode, | |
| 200 | object_format: ?std.Target.ObjectFormat = null, | |
| 201 | }; | |
| 202 | ||
| 203 | pub fn analyze(allocator: *Allocator, old_module: text.Module, options: AnalyzeOptions) !Module { | |
| 194 | 204 | var ctx = Analyze{ |
| 195 | 205 | .allocator = allocator, |
| 196 | 206 | .arena = std.heap.ArenaAllocator.init(allocator), |
| ... | ... | @@ -199,7 +209,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) ! |
| 199 | 209 | .decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator), |
| 200 | 210 | .exports = std.ArrayList(Module.Export).init(allocator), |
| 201 | 211 | .fns = std.ArrayList(Module.Fn).init(allocator), |
| 202 | .target = target, | |
| 212 | .target = options.target, | |
| 203 | 213 | }; |
| 204 | 214 | defer ctx.errors.deinit(); |
| 205 | 215 | defer ctx.decl_table.deinit(); |
| ... | ... | @@ -218,7 +228,10 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) ! |
| 218 | 228 | .errors = ctx.errors.toOwnedSlice(), |
| 219 | 229 | .fns = ctx.fns.toOwnedSlice(), |
| 220 | 230 | .arena = ctx.arena, |
| 221 | .target = target, | |
| 231 | .target = ctx.target, | |
| 232 | .link_mode = options.link_mode, | |
| 233 | .output_mode = options.output_mode, | |
| 234 | .object_format = options.object_format orelse ctx.target.getObjectFormat(), | |
| 222 | 235 | }; |
| 223 | 236 | } |
| 224 | 237 | |
| ... | ... | @@ -1241,7 +1254,11 @@ pub fn main() anyerror!void { |
| 1241 | 1254 | |
| 1242 | 1255 | const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{}); |
| 1243 | 1256 | |
| 1244 | var analyzed_module = try analyze(allocator, zir_module, native_info.target); | |
| 1257 | var analyzed_module = try analyze(allocator, zir_module, .{ | |
| 1258 | .target = native_info.target, | |
| 1259 | .output_mode = .Obj, | |
| 1260 | .link_mode = .Static, | |
| 1261 | }); | |
| 1245 | 1262 | defer analyzed_module.deinit(allocator); |
| 1246 | 1263 | |
| 1247 | 1264 | if (analyzed_module.errors.len != 0) { |
| ... | ... | @@ -1263,31 +1280,17 @@ pub fn main() anyerror!void { |
| 1263 | 1280 | try bos.flush(); |
| 1264 | 1281 | } |
| 1265 | 1282 | |
| 1266 | // executable | |
| 1267 | //const link = @import("link.zig"); | |
| 1268 | //var result = try link.updateExecutableFilePath(allocator, analyzed_module, std.fs.cwd(), "a.out"); | |
| 1269 | //defer result.deinit(allocator); | |
| 1270 | //if (result.errors.len != 0) { | |
| 1271 | // for (result.errors) |err_msg| { | |
| 1272 | // const loc = std.zig.findLineColumn(source, err_msg.byte_offset); | |
| 1273 | // std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg }); | |
| 1274 | // } | |
| 1275 | // if (debug_error_trace) return error.LinkFailure; | |
| 1276 | // std.process.exit(1); | |
| 1277 | //} | |
| 1278 | ||
| 1279 | // object file | |
| 1280 | 1283 | const link = @import("link.zig"); |
| 1281 | //var result = try link.updateExecutableFilePath(allocator, analyzed_module, std.fs.cwd(), "a.out"); | |
| 1282 | //defer result.deinit(allocator); | |
| 1283 | //if (result.errors.len != 0) { | |
| 1284 | // for (result.errors) |err_msg| { | |
| 1285 | // const loc = std.zig.findLineColumn(source, err_msg.byte_offset); | |
| 1286 | // std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg }); | |
| 1287 | // } | |
| 1288 | // if (debug_error_trace) return error.LinkFailure; | |
| 1289 | // std.process.exit(1); | |
| 1290 | //} | |
| 1284 | var result = try link.updateFilePath(allocator, analyzed_module, std.fs.cwd(), "zir.o"); | |
| 1285 | defer result.deinit(allocator); | |
| 1286 | if (result.errors.len != 0) { | |
| 1287 | for (result.errors) |err_msg| { | |
| 1288 | const loc = std.zig.findLineColumn(source, err_msg.byte_offset); | |
| 1289 | std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg }); | |
| 1290 | } | |
| 1291 | if (debug_error_trace) return error.LinkFailure; | |
| 1292 | std.process.exit(1); | |
| 1293 | } | |
| 1291 | 1294 | } |
| 1292 | 1295 | |
| 1293 | 1296 | // Performance optimization ideas: |
src-self-hosted/link.zig+40-16| ... | ... | @@ -7,11 +7,6 @@ const fs = std.fs; |
| 7 | 7 | const elf = std.elf; |
| 8 | 8 | const codegen = @import("codegen.zig"); |
| 9 | 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 = if (std.Target.current.os.tag == .windows) 0 else 0o777; | |
| 15 | 10 | const default_entry_addr = 0x8000000; |
| 16 | 11 | |
| 17 | 12 | pub const ErrorMsg = struct { |
| ... | ... | @@ -35,29 +30,29 @@ pub const Result = struct { |
| 35 | 30 | /// If incremental linking fails, falls back to truncating the file and rewriting it. |
| 36 | 31 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. |
| 37 | 32 | /// This operation is not atomic. |
| 38 | pub fn updateExecutableFilePath( | |
| 33 | pub fn updateFilePath( | |
| 39 | 34 | allocator: *Allocator, |
| 40 | 35 | module: ir.Module, |
| 41 | 36 | dir: fs.Dir, |
| 42 | 37 | sub_path: []const u8, |
| 43 | 38 | ) !Result { |
| 44 | const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = executable_mode }); | |
| 39 | const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(module) }); | |
| 45 | 40 | defer file.close(); |
| 46 | 41 | |
| 47 | return updateExecutableFile(allocator, module, file); | |
| 42 | return updateFile(allocator, module, file); | |
| 48 | 43 | } |
| 49 | 44 | |
| 50 | 45 | /// Atomically overwrites the old file, if present. |
| 51 | pub fn writeExecutableFilePath( | |
| 46 | pub fn writeFilePath( | |
| 52 | 47 | allocator: *Allocator, |
| 53 | 48 | module: ir.Module, |
| 54 | 49 | dir: fs.Dir, |
| 55 | 50 | sub_path: []const u8, |
| 56 | 51 | ) !Result { |
| 57 | const af = try dir.atomicFile(sub_path, .{ .mode = executable_mode }); | |
| 52 | const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(module) }); | |
| 58 | 53 | defer af.deinit(); |
| 59 | 54 | |
| 60 | const result = try writeExecutableFile(allocator, module, af.file); | |
| 55 | const result = try writeFile(allocator, module, af.file); | |
| 61 | 56 | try af.finish(); |
| 62 | 57 | return result; |
| 63 | 58 | } |
| ... | ... | @@ -67,10 +62,10 @@ pub fn writeExecutableFilePath( |
| 67 | 62 | /// Returns an error if `file` is not already open with +read +write +seek abilities. |
| 68 | 63 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. |
| 69 | 64 | /// 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) { | |
| 65 | pub fn updateFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | |
| 66 | return updateFileInner(allocator, module, file) catch |err| switch (err) { | |
| 72 | 67 | error.IncrFailed => { |
| 73 | return writeExecutableFile(allocator, module, file); | |
| 68 | return writeFile(allocator, module, file); | |
| 74 | 69 | }, |
| 75 | 70 | else => |e| return e, |
| 76 | 71 | }; |
| ... | ... | @@ -750,7 +745,20 @@ const Update = struct { |
| 750 | 745 | |
| 751 | 746 | /// Truncates the existing file contents and overwrites the contents. |
| 752 | 747 | /// 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 { | |
| 748 | pub fn writeFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | |
| 749 | switch (module.output_mode) { | |
| 750 | .Exe => {}, | |
| 751 | .Obj => return error.TODOImplementWritingObjectFiles, | |
| 752 | .Lib => return error.TODOImplementWritingLibFiles, | |
| 753 | } | |
| 754 | switch (module.object_format) { | |
| 755 | .unknown => unreachable, // TODO remove this tag from the enum | |
| 756 | .coff => return error.TODOImplementWritingCOFF, | |
| 757 | .elf => {}, | |
| 758 | .macho => return error.TODOImplementWritingMachO, | |
| 759 | .wasm => return error.TODOImplementWritingWasmObjects, | |
| 760 | } | |
| 761 | ||
| 754 | 762 | var update = Update{ |
| 755 | 763 | .file = file, |
| 756 | 764 | .module = &module, |
| ... | ... | @@ -778,7 +786,7 @@ pub fn writeExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.Fi |
| 778 | 786 | } |
| 779 | 787 | |
| 780 | 788 | /// Returns error.IncrFailed if incremental update could not be performed. |
| 781 | fn updateExecutableFileInner(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | |
| 789 | fn updateFileInner(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | |
| 782 | 790 | //var ehdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; |
| 783 | 791 | |
| 784 | 792 | // TODO implement incremental linking |
| ... | ... | @@ -822,3 +830,19 @@ fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr { |
| 822 | 830 | .sh_entsize = @intCast(u32, shdr.sh_entsize), |
| 823 | 831 | }; |
| 824 | 832 | } |
| 833 | ||
| 834 | fn determineMode(module: ir.Module) fs.File.Mode { | |
| 835 | // On common systems with a 0o022 umask, 0o777 will still result in a file created | |
| 836 | // with 0o755 permissions, but it works appropriately if the system is configured | |
| 837 | // more leniently. As another data point, C's fopen seems to open files with the | |
| 838 | // 666 mode. | |
| 839 | const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777; | |
| 840 | switch (module.output_mode) { | |
| 841 | .Lib => return switch (module.link_mode) { | |
| 842 | .Dynamic => executable_mode, | |
| 843 | .Static => fs.File.default_mode, | |
| 844 | }, | |
| 845 | .Exe => return executable_mode, | |
| 846 | .Obj => return fs.File.default_mode, | |
| 847 | } | |
| 848 | } |