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 {
404404 };
405405
406406 pub const ObjectFormat = enum {
407 /// TODO Get rid of this one.
407408 unknown,
408409 coff,
409410 elf,
src-self-hosted/codegen.zig+38-8
......@@ -39,7 +39,7 @@ pub fn generateSymbol(typed_value: ir.TypedValue, module: ir.Module, code: *std.
3939 defer function.inst_table.deinit();
4040 defer function.errors.deinit();
4141
42 for (module_fn.body) |inst| {
42 for (module_fn.body.instructions) |inst| {
4343 const new_inst = function.genFuncInst(inst) catch |err| switch (err) {
4444 error.CodegenFail => {
4545 assert(function.errors.items.len != 0);
......@@ -77,32 +77,62 @@ const Function = struct {
7777
7878 fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue {
7979 switch (inst.tag) {
80 .unreach => return self.genPanic(inst.src),
80 .unreach => return MCValue{ .unreach = {} },
8181 .constant => unreachable, // excluded from function bodies
8282 .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?),
8383 .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?),
8484 .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).?),
8590 }
8691 }
8792
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 {
9094 switch (self.module.target.cpu.arch) {
9195 .i386, .x86_64 => {
9296 try self.code.append(0xcc); // int3
9397 },
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}),
9599 }
96100 return .unreach;
97101 }
98102
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 {
101104 switch (self.module.target.cpu.arch) {
102105 .i386, .x86_64 => {
103106 try self.code.append(0xc3); // ret
104107 },
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 ", .{}),
106136 }
107137 }
108138
src-self-hosted/ir.zig+31-28
......@@ -156,6 +156,9 @@ pub const Module = struct {
156156 arena: std.heap.ArenaAllocator,
157157 fns: []Fn,
158158 target: Target,
159 link_mode: std.builtin.LinkMode,
160 output_mode: std.builtin.OutputMode,
161 object_format: std.Target.ObjectFormat,
159162
160163 pub const Export = struct {
161164 name: []const u8,
......@@ -190,7 +193,14 @@ pub const ErrorMsg = struct {
190193 msg: []const u8,
191194};
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 {
194204 var ctx = Analyze{
195205 .allocator = allocator,
196206 .arena = std.heap.ArenaAllocator.init(allocator),
......@@ -199,7 +209,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) !
199209 .decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator),
200210 .exports = std.ArrayList(Module.Export).init(allocator),
201211 .fns = std.ArrayList(Module.Fn).init(allocator),
202 .target = target,
212 .target = options.target,
203213 };
204214 defer ctx.errors.deinit();
205215 defer ctx.decl_table.deinit();
......@@ -218,7 +228,10 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) !
218228 .errors = ctx.errors.toOwnedSlice(),
219229 .fns = ctx.fns.toOwnedSlice(),
220230 .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(),
222235 };
223236}
224237
......@@ -1241,7 +1254,11 @@ pub fn main() anyerror!void {
12411254
12421255 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 });
12451262 defer analyzed_module.deinit(allocator);
12461263
12471264 if (analyzed_module.errors.len != 0) {
......@@ -1263,31 +1280,17 @@ pub fn main() anyerror!void {
12631280 try bos.flush();
12641281 }
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
12801283 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 }
12911294}
12921295
12931296// Performance optimization ideas:
src-self-hosted/link.zig+40-16
......@@ -7,11 +7,6 @@ const fs = std.fs;
77const elf = std.elf;
88const 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;
1510const default_entry_addr = 0x8000000;
1611
1712pub const ErrorMsg = struct {
......@@ -35,29 +30,29 @@ pub const Result = struct {
3530/// If incremental linking fails, falls back to truncating the file and rewriting it.
3631/// A malicious file is detected as incremental link failure and does not cause Illegal Behavior.
3732/// This operation is not atomic.
38pub fn updateExecutableFilePath(
33pub fn updateFilePath(
3934 allocator: *Allocator,
4035 module: ir.Module,
4136 dir: fs.Dir,
4237 sub_path: []const u8,
4338) !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) });
4540 defer file.close();
4641
47 return updateExecutableFile(allocator, module, file);
42 return updateFile(allocator, module, file);
4843}
4944
5045/// Atomically overwrites the old file, if present.
51pub fn writeExecutableFilePath(
46pub fn writeFilePath(
5247 allocator: *Allocator,
5348 module: ir.Module,
5449 dir: fs.Dir,
5550 sub_path: []const u8,
5651) !Result {
57 const af = try dir.atomicFile(sub_path, .{ .mode = executable_mode });
52 const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(module) });
5853 defer af.deinit();
5954
60 const result = try writeExecutableFile(allocator, module, af.file);
55 const result = try writeFile(allocator, module, af.file);
6156 try af.finish();
6257 return result;
6358}
......@@ -67,10 +62,10 @@ pub fn writeExecutableFilePath(
6762/// Returns an error if `file` is not already open with +read +write +seek abilities.
6863/// A malicious file is detected as incremental link failure and does not cause Illegal Behavior.
6964/// This operation is not atomic.
70pub fn updateExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
71 return updateExecutableFileInner(allocator, module, file) catch |err| switch (err) {
65pub fn updateFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
66 return updateFileInner(allocator, module, file) catch |err| switch (err) {
7267 error.IncrFailed => {
73 return writeExecutableFile(allocator, module, file);
68 return writeFile(allocator, module, file);
7469 },
7570 else => |e| return e,
7671 };
......@@ -750,7 +745,20 @@ const Update = struct {
750745
751746/// Truncates the existing file contents and overwrites the contents.
752747/// 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
754762 var update = Update{
755763 .file = file,
756764 .module = &module,
......@@ -778,7 +786,7 @@ pub fn writeExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.Fi
778786}
779787
780788/// 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 {
782790 //var ehdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined;
783791
784792 // TODO implement incremental linking
......@@ -822,3 +830,19 @@ fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr {
822830 .sh_entsize = @intCast(u32, shdr.sh_entsize),
823831 };
824832}
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}