link: introduce the concept of output mode and link mode

master
Andrew Kelley 2020-04-29 18:14:15 -04:00
parent 28729efe29
commit f89dbe6c4e
4 changed files with 110 additions and 52 deletions

View File

@ -404,6 +404,7 @@ pub const Target = struct {
};
pub const ObjectFormat = enum {
/// TODO Get rid of this one.
unknown,
coff,
elf,

View File

@ -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 ", .{}),
}
}

View File

@ -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:

View File

@ -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,
}
}