Commit f89dbe6c4e

Andrew Kelley <andrew@ziglang.org>
2020-04-30 00:14:15
link: introduce the concept of output mode and link mode
1 parent 28729ef
Changed files (4)
lib
src-self-hosted
lib/std/target.zig
@@ -404,6 +404,7 @@ pub const Target = struct {
     };
 
     pub const ObjectFormat = enum {
+        /// TODO Get rid of this one.
         unknown,
         coff,
         elf,
src-self-hosted/codegen.zig
@@ -39,7 +39,7 @@ pub fn generateSymbol(typed_value: ir.TypedValue, module: ir.Module, code: *std.
             defer function.inst_table.deinit();
             defer function.errors.deinit();
 
-            for (module_fn.body) |inst| {
+            for (module_fn.body.instructions) |inst| {
                 const new_inst = function.genFuncInst(inst) catch |err| switch (err) {
                     error.CodegenFail => {
                         assert(function.errors.items.len != 0);
@@ -77,32 +77,62 @@ const Function = struct {
 
     fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue {
         switch (inst.tag) {
-            .unreach => return self.genPanic(inst.src),
+            .unreach => return MCValue{ .unreach = {} },
             .constant => unreachable, // excluded from function bodies
             .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?),
             .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?),
             .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?),
+            .ret => return self.genRet(inst.cast(ir.Inst.Ret).?),
+            .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?),
+            .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?),
+            .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?),
+            .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?),
         }
     }
 
-    fn genPanic(self: *Function, src: usize) !MCValue {
-        // TODO change this to call the panic function
+    fn genBreakpoint(self: *Function, src: usize) !MCValue {
         switch (self.module.target.cpu.arch) {
             .i386, .x86_64 => {
                 try self.code.append(0xcc); // int3
             },
-            else => return self.fail(src, "TODO implement panic for {}", .{self.module.target.cpu.arch}),
+            else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.module.target.cpu.arch}),
         }
         return .unreach;
     }
 
-    fn genRet(self: *Function, src: usize) !void {
-        // TODO change this to call the panic function
+    fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue {
         switch (self.module.target.cpu.arch) {
             .i386, .x86_64 => {
                 try self.code.append(0xc3); // ret
             },
-            else => return self.fail(src, "TODO implement ret for {}", .{self.module.target.cpu.arch}),
+            else => return self.fail(inst.base.src, "TODO implement return for {}", .{self.module.target.cpu.arch}),
+        }
+        return .unreach;
+    }
+
+    fn genCmp(self: *Function, inst: *ir.Inst.Cmp) !MCValue {
+        switch (self.module.target.cpu.arch) {
+            else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.module.target.cpu.arch}),
+        }
+    }
+
+    fn genCondBr(self: *Function, inst: *ir.Inst.CondBr) !MCValue {
+        switch (self.module.target.cpu.arch) {
+            else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.module.target.cpu.arch}),
+        }
+    }
+
+    fn genIsNull(self: *Function, inst: *ir.Inst.IsNull) !MCValue {
+        switch (self.module.target.cpu.arch) {
+            else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.module.target.cpu.arch}),
+        }
+    }
+
+    fn genIsNonNull(self: *Function, inst: *ir.Inst.IsNonNull) !MCValue {
+        // Here you can specialize this instruction if it makes sense to, otherwise the default
+        // will call genIsNull and invert the result.
+        switch (self.module.target.cpu.arch) {
+            else => return self.fail(inst.base.src, "TODO call genIsNull and invert the result ", .{}),
         }
     }
 
src-self-hosted/ir.zig
@@ -156,6 +156,9 @@ pub const Module = struct {
     arena: std.heap.ArenaAllocator,
     fns: []Fn,
     target: Target,
+    link_mode: std.builtin.LinkMode,
+    output_mode: std.builtin.OutputMode,
+    object_format: std.Target.ObjectFormat,
 
     pub const Export = struct {
         name: []const u8,
@@ -190,7 +193,14 @@ pub const ErrorMsg = struct {
     msg: []const u8,
 };
 
-pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) !Module {
+pub const AnalyzeOptions = struct {
+    target: Target,
+    output_mode: std.builtin.OutputMode,
+    link_mode: std.builtin.LinkMode,
+    object_format: ?std.Target.ObjectFormat = null,
+};
+
+pub fn analyze(allocator: *Allocator, old_module: text.Module, options: AnalyzeOptions) !Module {
     var ctx = Analyze{
         .allocator = allocator,
         .arena = std.heap.ArenaAllocator.init(allocator),
@@ -199,7 +209,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) !
         .decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator),
         .exports = std.ArrayList(Module.Export).init(allocator),
         .fns = std.ArrayList(Module.Fn).init(allocator),
-        .target = target,
+        .target = options.target,
     };
     defer ctx.errors.deinit();
     defer ctx.decl_table.deinit();
@@ -218,7 +228,10 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) !
         .errors = ctx.errors.toOwnedSlice(),
         .fns = ctx.fns.toOwnedSlice(),
         .arena = ctx.arena,
-        .target = target,
+        .target = ctx.target,
+        .link_mode = options.link_mode,
+        .output_mode = options.output_mode,
+        .object_format = options.object_format orelse ctx.target.getObjectFormat(),
     };
 }
 
@@ -1241,7 +1254,11 @@ pub fn main() anyerror!void {
 
     const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{});
 
-    var analyzed_module = try analyze(allocator, zir_module, native_info.target);
+    var analyzed_module = try analyze(allocator, zir_module, .{
+        .target = native_info.target,
+        .output_mode = .Obj,
+        .link_mode = .Static,
+    });
     defer analyzed_module.deinit(allocator);
 
     if (analyzed_module.errors.len != 0) {
@@ -1263,31 +1280,17 @@ pub fn main() anyerror!void {
         try bos.flush();
     }
 
-    // executable
-    //const link = @import("link.zig");
-    //var result = try link.updateExecutableFilePath(allocator, analyzed_module, std.fs.cwd(), "a.out");
-    //defer result.deinit(allocator);
-    //if (result.errors.len != 0) {
-    //    for (result.errors) |err_msg| {
-    //        const loc = std.zig.findLineColumn(source, err_msg.byte_offset);
-    //        std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
-    //    }
-    //    if (debug_error_trace) return error.LinkFailure;
-    //    std.process.exit(1);
-    //}
-
-    // object file
     const link = @import("link.zig");
-    //var result = try link.updateExecutableFilePath(allocator, analyzed_module, std.fs.cwd(), "a.out");
-    //defer result.deinit(allocator);
-    //if (result.errors.len != 0) {
-    //    for (result.errors) |err_msg| {
-    //        const loc = std.zig.findLineColumn(source, err_msg.byte_offset);
-    //        std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
-    //    }
-    //    if (debug_error_trace) return error.LinkFailure;
-    //    std.process.exit(1);
-    //}
+    var result = try link.updateFilePath(allocator, analyzed_module, std.fs.cwd(), "zir.o");
+    defer result.deinit(allocator);
+    if (result.errors.len != 0) {
+        for (result.errors) |err_msg| {
+            const loc = std.zig.findLineColumn(source, err_msg.byte_offset);
+            std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
+        }
+        if (debug_error_trace) return error.LinkFailure;
+        std.process.exit(1);
+    }
 }
 
 // Performance optimization ideas:
src-self-hosted/link.zig
@@ -7,11 +7,6 @@ const fs = std.fs;
 const elf = std.elf;
 const codegen = @import("codegen.zig");
 
-/// On common systems with a 0o022 umask, 0o777 will still result in a file created
-/// with 0o755 permissions, but it works appropriately if the system is configured
-/// more leniently. As another data point, C's fopen seems to open files with the
-/// 666 mode.
-const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777;
 const default_entry_addr = 0x8000000;
 
 pub const ErrorMsg = struct {
@@ -35,29 +30,29 @@ pub const Result = struct {
 /// If incremental linking fails, falls back to truncating the file and rewriting it.
 /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior.
 /// This operation is not atomic.
-pub fn updateExecutableFilePath(
+pub fn updateFilePath(
     allocator: *Allocator,
     module: ir.Module,
     dir: fs.Dir,
     sub_path: []const u8,
 ) !Result {
-    const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = executable_mode });
+    const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(module) });
     defer file.close();
 
-    return updateExecutableFile(allocator, module, file);
+    return updateFile(allocator, module, file);
 }
 
 /// Atomically overwrites the old file, if present.
-pub fn writeExecutableFilePath(
+pub fn writeFilePath(
     allocator: *Allocator,
     module: ir.Module,
     dir: fs.Dir,
     sub_path: []const u8,
 ) !Result {
-    const af = try dir.atomicFile(sub_path, .{ .mode = executable_mode });
+    const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(module) });
     defer af.deinit();
 
-    const result = try writeExecutableFile(allocator, module, af.file);
+    const result = try writeFile(allocator, module, af.file);
     try af.finish();
     return result;
 }
@@ -67,10 +62,10 @@ pub fn writeExecutableFilePath(
 /// Returns an error if `file` is not already open with +read +write +seek abilities.
 /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior.
 /// This operation is not atomic.
-pub fn updateExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
-    return updateExecutableFileInner(allocator, module, file) catch |err| switch (err) {
+pub fn updateFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
+    return updateFileInner(allocator, module, file) catch |err| switch (err) {
         error.IncrFailed => {
-            return writeExecutableFile(allocator, module, file);
+            return writeFile(allocator, module, file);
         },
         else => |e| return e,
     };
@@ -750,7 +745,20 @@ const Update = struct {
 
 /// Truncates the existing file contents and overwrites the contents.
 /// Returns an error if `file` is not already open with +read +write +seek abilities.
-pub fn writeExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
+pub fn writeFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
+    switch (module.output_mode) {
+        .Exe => {},
+        .Obj => return error.TODOImplementWritingObjectFiles,
+        .Lib => return error.TODOImplementWritingLibFiles,
+    }
+    switch (module.object_format) {
+        .unknown => unreachable, // TODO remove this tag from the enum
+        .coff => return error.TODOImplementWritingCOFF,
+        .elf => {},
+        .macho => return error.TODOImplementWritingMachO,
+        .wasm => return error.TODOImplementWritingWasmObjects,
+    }
+
     var update = Update{
         .file = file,
         .module = &module,
@@ -778,7 +786,7 @@ pub fn writeExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.Fi
 }
 
 /// Returns error.IncrFailed if incremental update could not be performed.
-fn updateExecutableFileInner(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
+fn updateFileInner(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
     //var ehdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined;
 
     // TODO implement incremental linking
@@ -822,3 +830,19 @@ fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr {
         .sh_entsize = @intCast(u32, shdr.sh_entsize),
     };
 }
+
+fn determineMode(module: ir.Module) fs.File.Mode {
+    // On common systems with a 0o022 umask, 0o777 will still result in a file created
+    // with 0o755 permissions, but it works appropriately if the system is configured
+    // more leniently. As another data point, C's fopen seems to open files with the
+    // 666 mode.
+    const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777;
+    switch (module.output_mode) {
+        .Lib => return switch (module.link_mode) {
+            .Dynamic => executable_mode,
+            .Static => fs.File.default_mode,
+        },
+        .Exe => return executable_mode,
+        .Obj => return fs.File.default_mode,
+    }
+}