authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-04-29 18:14:15-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-05-01 06:47:20-04:00
logf89dbe6c4ebe3fa1ffe3eb455ed96fe615e2c903
tree33e63b5a6d5d422a28f9363d37dc80673bd3fec4
parent28729efe2998579fc36a35e5bdab12727ece1e7a

link: introduce the concept of output mode and link mode


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 };
405405
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();
4141
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 {
7777
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 bodies81 .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 }
8792
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); // int396 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 }
98102
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); // ret106 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 }
108138
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,
159162
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};
192195
193pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) !Module {196pub 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
203pub 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}
224237
...@@ -1241,7 +1254,11 @@ pub fn main() anyerror!void {...@@ -1241,7 +1254,11 @@ pub fn main() anyerror!void {
12411254
1242 const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{});1255 const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{});
12431256
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);
12461263
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 }
12651282
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}
12921295
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;
7const elf = std.elf;7const elf = std.elf;
8const codegen = @import("codegen.zig");8const codegen = @import("codegen.zig");
99
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.
14const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777;
15const default_entry_addr = 0x8000000;10const default_entry_addr = 0x8000000;
1611
17pub const ErrorMsg = struct {12pub 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.
38pub fn updateExecutableFilePath(33pub 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();
4641
47 return updateExecutableFile(allocator, module, file);42 return updateFile(allocator, module, file);
48}43}
4944
50/// Atomically overwrites the old file, if present.45/// Atomically overwrites the old file, if present.
51pub fn writeExecutableFilePath(46pub 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();
5954
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.
70pub fn updateExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {65pub 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 {
750745
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.
753pub fn writeExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {748pub 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}
779787
780/// Returns error.IncrFailed if incremental update could not be performed.788/// Returns error.IncrFailed if incremental update could not be performed.
781fn updateExecutableFileInner(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {789fn 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;
783791
784 // TODO implement incremental linking792 // 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
834fn 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}