stage2: move format-specific code to link.File.X

This makes the code outside of link.File.Elf less elf-specific and will
allow for easier implementation of other formats such as wasm.
This commit is contained in:
Isaac Freund 2020-08-02 12:16:03 +02:00 committed by Andrew Kelley
parent 5ae88e919b
commit 6123201f06
2 changed files with 144 additions and 197 deletions

View File

@ -790,7 +790,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
errdefer gpa.free(root_name);
const bin_file_dir = options.bin_file_dir orelse std.fs.cwd();
const bin_file = try link.openBinFilePath(gpa, bin_file_dir, options.bin_file_path, .{
const bin_file = try link.File.openPath(gpa, bin_file_dir, options.bin_file_path, .{
.root_name = root_name,
.root_src_dir_path = options.root_pkg.root_src_dir_path,
.target = options.target,

View File

@ -33,102 +33,27 @@ pub const Options = struct {
program_code_size_hint: u64 = 256 * 1024,
};
/// Attempts incremental linking, if the file already exists.
/// 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 openBinFilePath(
allocator: *Allocator,
dir: fs.Dir,
sub_path: []const u8,
options: Options,
) !*File {
const cbe = options.object_format == .c;
const file = try dir.createFile(sub_path, .{ .truncate = cbe, .read = true, .mode = determineMode(options) });
errdefer file.close();
if (cbe) {
var bin_file = try allocator.create(File.C);
errdefer allocator.destroy(bin_file);
bin_file.* = try openCFile(allocator, file, options);
return &bin_file.base;
} else {
var bin_file = try allocator.create(File.Elf);
errdefer allocator.destroy(bin_file);
bin_file.* = try openBinFile(allocator, file, options);
bin_file.owns_file_handle = true;
return &bin_file.base;
}
}
/// Atomically overwrites the old file, if present.
pub fn writeFilePath(
allocator: *Allocator,
dir: fs.Dir,
sub_path: []const u8,
module: Module,
errors: *std.ArrayList(Module.ErrorMsg),
) !void {
const options: Options = .{
.target = module.target,
.output_mode = module.output_mode,
.link_mode = module.link_mode,
.object_format = module.object_format,
.symbol_count_hint = module.decls.items.len,
.optimize_mode = module.optimize_mode,
};
const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(options) });
defer af.deinit();
const elf_file = try createElfFile(allocator, af.file, options);
for (module.decls.items) |decl| {
try elf_file.updateDecl(module, decl, errors);
}
try elf_file.flush();
if (elf_file.error_flags.no_entry_point_found) {
try errors.ensureCapacity(errors.items.len + 1);
errors.appendAssumeCapacity(.{
.byte_offset = 0,
.msg = try std.fmt.allocPrint(errors.allocator, "no entry point found", .{}),
});
}
try af.finish();
return result;
}
fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C {
return File.C{
.base = .{
.tag = .c,
.options = options,
},
.allocator = allocator,
.file = file,
.main = std.ArrayList(u8).init(allocator),
.header = std.ArrayList(u8).init(allocator),
.constants = std.ArrayList(u8).init(allocator),
.called = std.StringHashMap(void).init(allocator),
};
}
/// Attempts incremental linking, if the file already exists.
/// If incremental linking fails, falls back to truncating the file and rewriting it.
/// 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 openBinFile(allocator: *Allocator, file: fs.File, options: Options) !File.Elf {
return openBinFileInner(allocator, file, options) catch |err| switch (err) {
error.IncrFailed => {
return createElfFile(allocator, file, options);
},
else => |e| return e,
};
}
pub const File = struct {
tag: Tag,
options: Options,
/// Attempts incremental linking, if the file already exists. 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 openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File {
switch (options.object_format) {
.unknown => unreachable,
.coff => return error.TODOImplementCoff,
.elf => return Elf.openPath(allocator, dir, sub_path, options),
.macho => return error.TODOImplementMacho,
.wasm => return error.TODOImplementWasm,
.c => return C.openPath(allocator, dir, sub_path, options),
.hex => return error.TODOImplementHex,
.raw => return error.TODOImplementRaw,
}
}
pub fn cast(base: *File, comptime T: type) ?*T {
if (base.tag != T.base_tag)
return null;
@ -248,6 +173,31 @@ pub const File = struct {
need_noreturn: bool = false,
error_msg: *Module.ErrorMsg = undefined,
pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File {
assert(options.object_format == .c);
const file = try dir.createFile(sub_path, .{ .truncate = true, .read = true, .mode = determineMode(options) });
errdefer file.close();
var c_file = try allocator.create(C);
errdefer allocator.destroy(c_file);
c_file.* = File.C{
.base = .{
.tag = .c,
.options = options,
},
.allocator = allocator,
.file = file,
.main = std.ArrayList(u8).init(allocator),
.header = std.ArrayList(u8).init(allocator),
.constants = std.ArrayList(u8).init(allocator),
.called = std.StringHashMap(void).init(allocator),
};
return &c_file.base;
}
pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) !void {
self.error_msg = try Module.ErrorMsg.create(self.allocator, src, format, args);
return error.CGenFailure;
@ -452,6 +402,107 @@ pub const File = struct {
sym_index: ?u32 = null,
};
pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File {
assert(options.object_format == .elf);
const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(options) });
errdefer file.close();
var elf_file = try allocator.create(Elf);
errdefer allocator.destroy(elf_file);
elf_file.* = openFile(allocator, file, options) catch |err| switch (err) {
error.IncrFailed => try createFile(allocator, file, options),
else => |e| return e,
};
elf_file.owns_file_handle = true;
return &elf_file.base;
}
/// Returns error.IncrFailed if incremental update could not be performed.
fn openFile(allocator: *Allocator, file: fs.File, options: Options) !Elf {
switch (options.output_mode) {
.Exe => {},
.Obj => {},
.Lib => return error.IncrFailed,
}
var self: Elf = .{
.base = .{
.tag = .elf,
.options = options,
},
.allocator = allocator,
.file = file,
.owns_file_handle = false,
.ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
32 => .p32,
64 => .p64,
else => return error.UnsupportedELFArchitecture,
},
};
errdefer self.deinit();
// TODO implement reading the elf file
return error.IncrFailed;
//try self.populateMissingMetadata();
//return self;
}
/// Truncates the existing file contents and overwrites the contents.
/// Returns an error if `file` is not already open with +read +write +seek abilities.
fn createFile(allocator: *Allocator, file: fs.File, options: Options) !Elf {
switch (options.output_mode) {
.Exe => {},
.Obj => {},
.Lib => return error.TODOImplementWritingLibFiles,
}
var self: Elf = .{
.base = .{
.tag = .elf,
.options = options,
},
.allocator = allocator,
.file = file,
.ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
32 => .p32,
64 => .p64,
else => return error.UnsupportedELFArchitecture,
},
.shdr_table_dirty = true,
.owns_file_handle = false,
};
errdefer self.deinit();
// Index 0 is always a null symbol.
try self.local_symbols.append(allocator, .{
.st_name = 0,
.st_info = 0,
.st_other = 0,
.st_shndx = 0,
.st_value = 0,
.st_size = 0,
});
// There must always be a null section in index 0
try self.sections.append(allocator, .{
.sh_name = 0,
.sh_type = elf.SHT_NULL,
.sh_flags = 0,
.sh_addr = 0,
.sh_offset = 0,
.sh_size = 0,
.sh_link = 0,
.sh_info = 0,
.sh_addralign = 0,
.sh_entsize = 0,
});
try self.populateMissingMetadata();
return self;
}
pub fn deinit(self: *Elf) void {
self.sections.deinit(self.allocator);
self.program_headers.deinit(self.allocator);
@ -1939,110 +1990,6 @@ pub const File = 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 createElfFile(allocator: *Allocator, file: fs.File, options: Options) !File.Elf {
switch (options.output_mode) {
.Exe => {},
.Obj => {},
.Lib => return error.TODOImplementWritingLibFiles,
}
switch (options.object_format) {
.c => unreachable,
.unknown => unreachable, // TODO remove this tag from the enum
.coff => return error.TODOImplementWritingCOFF,
.elf => {},
.macho => return error.TODOImplementWritingMachO,
.wasm => return error.TODOImplementWritingWasmObjects,
.hex => return error.TODOImplementWritingHex,
.raw => return error.TODOImplementWritingRaw,
}
var self: File.Elf = .{
.base = .{
.tag = .elf,
.options = options,
},
.allocator = allocator,
.file = file,
.ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
32 => .p32,
64 => .p64,
else => return error.UnsupportedELFArchitecture,
},
.shdr_table_dirty = true,
.owns_file_handle = false,
};
errdefer self.deinit();
// Index 0 is always a null symbol.
try self.local_symbols.append(allocator, .{
.st_name = 0,
.st_info = 0,
.st_other = 0,
.st_shndx = 0,
.st_value = 0,
.st_size = 0,
});
// There must always be a null section in index 0
try self.sections.append(allocator, .{
.sh_name = 0,
.sh_type = elf.SHT_NULL,
.sh_flags = 0,
.sh_addr = 0,
.sh_offset = 0,
.sh_size = 0,
.sh_link = 0,
.sh_info = 0,
.sh_addralign = 0,
.sh_entsize = 0,
});
try self.populateMissingMetadata();
return self;
}
/// Returns error.IncrFailed if incremental update could not be performed.
fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !File.Elf {
switch (options.output_mode) {
.Exe => {},
.Obj => {},
.Lib => return error.IncrFailed,
}
switch (options.object_format) {
.unknown => unreachable, // TODO remove this tag from the enum
.c => unreachable,
.coff => return error.IncrFailed,
.elf => {},
.macho => return error.IncrFailed,
.wasm => return error.IncrFailed,
.hex => return error.IncrFailed,
.raw => return error.IncrFailed,
}
var self: File.Elf = .{
.base = .{
.tag = .elf,
.options = options,
},
.allocator = allocator,
.file = file,
.owns_file_handle = false,
.ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
32 => .p32,
64 => .p64,
else => return error.UnsupportedELFArchitecture,
},
};
errdefer self.deinit();
// TODO implement reading the elf file
return error.IncrFailed;
//try self.populateMissingMetadata();
//return self;
}
/// Saturating multiplication
fn satMul(a: anytype, b: anytype) @TypeOf(a, b) {
const T = @TypeOf(a, b);