| 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,6 +404,7 @@ pub const Target = struct { |
| 404 | }; | 404 | }; |
| 405 | 405 | ||
| 406 | pub const ObjectFormat = enum { | 406 | pub const ObjectFormat = enum { |
| 407 | /// TODO Get rid of this one. | ||
| 407 | unknown, | 408 | unknown, |
| 408 | coff, | 409 | coff, |
| 409 | elf, | 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,7 +39,7 @@ pub fn generateSymbol(typed_value: ir.TypedValue, module: ir.Module, code: *std. |
| 39 | defer function.inst_table.deinit(); | 39 | defer function.inst_table.deinit(); |
| 40 | defer function.errors.deinit(); | 40 | defer function.errors.deinit(); |
| 41 | 41 | ||
| 42 | for (module_fn.body) |inst| { | 42 | for (module_fn.body.instructions) |inst| { |
| 43 | const new_inst = function.genFuncInst(inst) catch |err| switch (err) { | 43 | const new_inst = function.genFuncInst(inst) catch |err| switch (err) { |
| 44 | error.CodegenFail => { | 44 | error.CodegenFail => { |
| 45 | assert(function.errors.items.len != 0); | 45 | assert(function.errors.items.len != 0); |
| ... | @@ -77,32 +77,62 @@ const Function = struct { | ... | @@ -77,32 +77,62 @@ const Function = struct { |
| 77 | 77 | ||
| 78 | fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue { | 78 | fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue { |
| 79 | switch (inst.tag) { | 79 | switch (inst.tag) { |
| 80 | .unreach => return self.genPanic(inst.src), | 80 | .unreach => return MCValue{ .unreach = {} }, |
| 81 | .constant => unreachable, // excluded from function bodies | 81 | .constant => unreachable, // excluded from function bodies |
| 82 | .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?), | 82 | .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?), |
| 83 | .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?), | 83 | .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?), |
| 84 | .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), | 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 { | 93 | fn genBreakpoint(self: *Function, src: usize) !MCValue { |
| 89 | // TODO change this to call the panic function | ||
| 90 | switch (self.module.target.cpu.arch) { | 94 | switch (self.module.target.cpu.arch) { |
| 91 | .i386, .x86_64 => { | 95 | .i386, .x86_64 => { |
| 92 | try self.code.append(0xcc); // int3 | 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 | return .unreach; | 100 | return .unreach; |
| 97 | } | 101 | } |
| 98 | 102 | ||
| 99 | fn genRet(self: *Function, src: usize) !void { | 103 | fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue { |
| 100 | // TODO change this to call the panic function | ||
| 101 | switch (self.module.target.cpu.arch) { | 104 | switch (self.module.target.cpu.arch) { |
| 102 | .i386, .x86_64 => { | 105 | .i386, .x86_64 => { |
| 103 | try self.code.append(0xc3); // ret | 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,6 +156,9 @@ pub const Module = struct { |
| 156 | arena: std.heap.ArenaAllocator, | 156 | arena: std.heap.ArenaAllocator, |
| 157 | fns: []Fn, | 157 | fns: []Fn, |
| 158 | target: Target, | 158 | target: Target, |
| 159 | link_mode: std.builtin.LinkMode, | ||
| 160 | output_mode: std.builtin.OutputMode, | ||
| 161 | object_format: std.Target.ObjectFormat, | ||
| 159 | 162 | ||
| 160 | pub const Export = struct { | 163 | pub const Export = struct { |
| 161 | name: []const u8, | 164 | name: []const u8, |
| ... | @@ -190,7 +193,14 @@ pub const ErrorMsg = struct { | ... | @@ -190,7 +193,14 @@ pub const ErrorMsg = struct { |
| 190 | msg: []const u8, | 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 | var ctx = Analyze{ | 204 | var ctx = Analyze{ |
| 195 | .allocator = allocator, | 205 | .allocator = allocator, |
| 196 | .arena = std.heap.ArenaAllocator.init(allocator), | 206 | .arena = std.heap.ArenaAllocator.init(allocator), |
| ... | @@ -199,7 +209,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) ! | ... | @@ -199,7 +209,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) ! |
| 199 | .decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator), | 209 | .decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator), |
| 200 | .exports = std.ArrayList(Module.Export).init(allocator), | 210 | .exports = std.ArrayList(Module.Export).init(allocator), |
| 201 | .fns = std.ArrayList(Module.Fn).init(allocator), | 211 | .fns = std.ArrayList(Module.Fn).init(allocator), |
| 202 | .target = target, | 212 | .target = options.target, |
| 203 | }; | 213 | }; |
| 204 | defer ctx.errors.deinit(); | 214 | defer ctx.errors.deinit(); |
| 205 | defer ctx.decl_table.deinit(); | 215 | defer ctx.decl_table.deinit(); |
| ... | @@ -218,7 +228,10 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) ! | ... | @@ -218,7 +228,10 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) ! |
| 218 | .errors = ctx.errors.toOwnedSlice(), | 228 | .errors = ctx.errors.toOwnedSlice(), |
| 219 | .fns = ctx.fns.toOwnedSlice(), | 229 | .fns = ctx.fns.toOwnedSlice(), |
| 220 | .arena = ctx.arena, | 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,7 +1254,11 @@ pub fn main() anyerror!void { |
| 1241 | 1254 | ||
| 1242 | const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{}); | 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 | defer analyzed_module.deinit(allocator); | 1262 | defer analyzed_module.deinit(allocator); |
| 1246 | 1263 | ||
| 1247 | if (analyzed_module.errors.len != 0) { | 1264 | if (analyzed_module.errors.len != 0) { |
| ... | @@ -1263,31 +1280,17 @@ pub fn main() anyerror!void { | ... | @@ -1263,31 +1280,17 @@ pub fn main() anyerror!void { |
| 1263 | try bos.flush(); | 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 | const link = @import("link.zig"); | 1283 | const link = @import("link.zig"); |
| 1281 | //var result = try link.updateExecutableFilePath(allocator, analyzed_module, std.fs.cwd(), "a.out"); | 1284 | var result = try link.updateFilePath(allocator, analyzed_module, std.fs.cwd(), "zir.o"); |
| 1282 | //defer result.deinit(allocator); | 1285 | defer result.deinit(allocator); |
| 1283 | //if (result.errors.len != 0) { | 1286 | if (result.errors.len != 0) { |
| 1284 | // for (result.errors) |err_msg| { | 1287 | for (result.errors) |err_msg| { |
| 1285 | // const loc = std.zig.findLineColumn(source, err_msg.byte_offset); | 1288 | 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 }); | 1289 | std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg }); |
| 1287 | // } | 1290 | } |
| 1288 | // if (debug_error_trace) return error.LinkFailure; | 1291 | if (debug_error_trace) return error.LinkFailure; |
| 1289 | // std.process.exit(1); | 1292 | std.process.exit(1); |
| 1290 | //} | 1293 | } |
| 1291 | } | 1294 | } |
| 1292 | 1295 | ||
| 1293 | // Performance optimization ideas: | 1296 | // Performance optimization ideas: |
src-self-hosted/link.zig+40-16| ... | @@ -7,11 +7,6 @@ const fs = std.fs; | ... | @@ -7,11 +7,6 @@ const fs = std.fs; |
| 7 | const elf = std.elf; | 7 | const elf = std.elf; |
| 8 | const codegen = @import("codegen.zig"); | 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 | const default_entry_addr = 0x8000000; | 10 | const default_entry_addr = 0x8000000; |
| 16 | 11 | ||
| 17 | pub const ErrorMsg = struct { | 12 | pub const ErrorMsg = struct { |
| ... | @@ -35,29 +30,29 @@ pub const Result = struct { | ... | @@ -35,29 +30,29 @@ pub const Result = struct { |
| 35 | /// If incremental linking fails, falls back to truncating the file and rewriting it. | 30 | /// 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. | 31 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. |
| 37 | /// This operation is not atomic. | 32 | /// This operation is not atomic. |
| 38 | pub fn updateExecutableFilePath( | 33 | pub fn updateFilePath( |
| 39 | allocator: *Allocator, | 34 | allocator: *Allocator, |
| 40 | module: ir.Module, | 35 | module: ir.Module, |
| 41 | dir: fs.Dir, | 36 | dir: fs.Dir, |
| 42 | sub_path: []const u8, | 37 | sub_path: []const u8, |
| 43 | ) !Result { | 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 | defer file.close(); | 40 | defer file.close(); |
| 46 | 41 | ||
| 47 | return updateExecutableFile(allocator, module, file); | 42 | return updateFile(allocator, module, file); |
| 48 | } | 43 | } |
| 49 | 44 | ||
| 50 | /// Atomically overwrites the old file, if present. | 45 | /// Atomically overwrites the old file, if present. |
| 51 | pub fn writeExecutableFilePath( | 46 | pub fn writeFilePath( |
| 52 | allocator: *Allocator, | 47 | allocator: *Allocator, |
| 53 | module: ir.Module, | 48 | module: ir.Module, |
| 54 | dir: fs.Dir, | 49 | dir: fs.Dir, |
| 55 | sub_path: []const u8, | 50 | sub_path: []const u8, |
| 56 | ) !Result { | 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 | defer af.deinit(); | 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 | try af.finish(); | 56 | try af.finish(); |
| 62 | return result; | 57 | return result; |
| 63 | } | 58 | } |
| ... | @@ -67,10 +62,10 @@ pub fn writeExecutableFilePath( | ... | @@ -67,10 +62,10 @@ pub fn writeExecutableFilePath( |
| 67 | /// Returns an error if `file` is not already open with +read +write +seek abilities. | 62 | /// 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. | 63 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. |
| 69 | /// This operation is not atomic. | 64 | /// This operation is not atomic. |
| 70 | pub fn updateExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | 65 | pub fn updateFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { |
| 71 | return updateExecutableFileInner(allocator, module, file) catch |err| switch (err) { | 66 | return updateFileInner(allocator, module, file) catch |err| switch (err) { |
| 72 | error.IncrFailed => { | 67 | error.IncrFailed => { |
| 73 | return writeExecutableFile(allocator, module, file); | 68 | return writeFile(allocator, module, file); |
| 74 | }, | 69 | }, |
| 75 | else => |e| return e, | 70 | else => |e| return e, |
| 76 | }; | 71 | }; |
| ... | @@ -750,7 +745,20 @@ const Update = struct { | ... | @@ -750,7 +745,20 @@ const Update = struct { |
| 750 | 745 | ||
| 751 | /// Truncates the existing file contents and overwrites the contents. | 746 | /// Truncates the existing file contents and overwrites the contents. |
| 752 | /// Returns an error if `file` is not already open with +read +write +seek abilities. | 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 | var update = Update{ | 762 | var update = Update{ |
| 755 | .file = file, | 763 | .file = file, |
| 756 | .module = &module, | 764 | .module = &module, |
| ... | @@ -778,7 +786,7 @@ pub fn writeExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.Fi | ... | @@ -778,7 +786,7 @@ pub fn writeExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.Fi |
| 778 | } | 786 | } |
| 779 | 787 | ||
| 780 | /// Returns error.IncrFailed if incremental update could not be performed. | 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 | //var ehdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; | 790 | //var ehdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; |
| 783 | 791 | ||
| 784 | // TODO implement incremental linking | 792 | // TODO implement incremental linking |
| ... | @@ -822,3 +830,19 @@ fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr { | ... | @@ -822,3 +830,19 @@ fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr { |
| 822 | .sh_entsize = @intCast(u32, shdr.sh_entsize), | 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 | } |