link: introduce the concept of output mode and link mode
parent
28729efe29
commit
f89dbe6c4e
|
@ -404,6 +404,7 @@ pub const Target = struct {
|
|||
};
|
||||
|
||||
pub const ObjectFormat = enum {
|
||||
/// TODO Get rid of this one.
|
||||
unknown,
|
||||
coff,
|
||||
elf,
|
||||
|
|
|
@ -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 ", .{}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue