When linking with shared-memory enabled, we must ensure to emit the "data count" section as well as emit the correct segment flags to tell the runtime/loader that each segment is passive. This is required as we don't emit the offsets for such segments but instead initialize each segment (for each thread) during runtime.
4341 lines
175 KiB
Zig
4341 lines
175 KiB
Zig
const Wasm = @This();
|
|
|
|
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const mem = std.mem;
|
|
const Allocator = std.mem.Allocator;
|
|
const assert = std.debug.assert;
|
|
const fs = std.fs;
|
|
const leb = std.leb;
|
|
const log = std.log.scoped(.link);
|
|
|
|
pub const Atom = @import("Wasm/Atom.zig");
|
|
const Dwarf = @import("Dwarf.zig");
|
|
const Module = @import("../Module.zig");
|
|
const Compilation = @import("../Compilation.zig");
|
|
const CodeGen = @import("../arch/wasm/CodeGen.zig");
|
|
const codegen = @import("../codegen.zig");
|
|
const link = @import("../link.zig");
|
|
const lldMain = @import("../main.zig").lldMain;
|
|
const trace = @import("../tracy.zig").trace;
|
|
const build_options = @import("build_options");
|
|
const wasi_libc = @import("../wasi_libc.zig");
|
|
const Cache = std.Build.Cache;
|
|
const Type = @import("../type.zig").Type;
|
|
const TypedValue = @import("../TypedValue.zig");
|
|
const LlvmObject = @import("../codegen/llvm.zig").Object;
|
|
const Air = @import("../Air.zig");
|
|
const Liveness = @import("../Liveness.zig");
|
|
const Symbol = @import("Wasm/Symbol.zig");
|
|
const Object = @import("Wasm/Object.zig");
|
|
const Archive = @import("Wasm/Archive.zig");
|
|
const types = @import("Wasm/types.zig");
|
|
|
|
pub const base_tag: link.File.Tag = .wasm;
|
|
|
|
base: link.File,
|
|
/// Output name of the file
|
|
name: []const u8,
|
|
/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
|
|
llvm_object: ?*LlvmObject = null,
|
|
/// When importing objects from the host environment, a name must be supplied.
|
|
/// LLVM uses "env" by default when none is given. This would be a good default for Zig
|
|
/// to support existing code.
|
|
/// TODO: Allow setting this through a flag?
|
|
host_name: []const u8 = "env",
|
|
/// List of all `Decl` that are currently alive.
|
|
/// Each index maps to the corresponding `Atom.Index`.
|
|
decls: std.AutoHashMapUnmanaged(Module.Decl.Index, Atom.Index) = .{},
|
|
/// Mapping between an `Atom` and its type index representing the Wasm
|
|
/// type of the function signature.
|
|
atom_types: std.AutoHashMapUnmanaged(Atom.Index, u32) = .{},
|
|
/// List of all symbols generated by Zig code.
|
|
symbols: std.ArrayListUnmanaged(Symbol) = .{},
|
|
/// List of symbol indexes which are free to be used.
|
|
symbols_free_list: std.ArrayListUnmanaged(u32) = .{},
|
|
/// Maps atoms to their segment index
|
|
atoms: std.AutoHashMapUnmanaged(u32, Atom.Index) = .{},
|
|
/// List of all atoms.
|
|
managed_atoms: std.ArrayListUnmanaged(Atom) = .{},
|
|
/// Represents the index into `segments` where the 'code' section
|
|
/// lives.
|
|
code_section_index: ?u32 = null,
|
|
/// The index of the segment representing the custom '.debug_info' section.
|
|
debug_info_index: ?u32 = null,
|
|
/// The index of the segment representing the custom '.debug_line' section.
|
|
debug_line_index: ?u32 = null,
|
|
/// The index of the segment representing the custom '.debug_loc' section.
|
|
debug_loc_index: ?u32 = null,
|
|
/// The index of the segment representing the custom '.debug_ranges' section.
|
|
debug_ranges_index: ?u32 = null,
|
|
/// The index of the segment representing the custom '.debug_pubnames' section.
|
|
debug_pubnames_index: ?u32 = null,
|
|
/// The index of the segment representing the custom '.debug_pubtypes' section.
|
|
debug_pubtypes_index: ?u32 = null,
|
|
/// The index of the segment representing the custom '.debug_pubtypes' section.
|
|
debug_str_index: ?u32 = null,
|
|
/// The index of the segment representing the custom '.debug_pubtypes' section.
|
|
debug_abbrev_index: ?u32 = null,
|
|
/// The count of imported functions. This number will be appended
|
|
/// to the function indexes as their index starts at the lowest non-extern function.
|
|
imported_functions_count: u32 = 0,
|
|
/// The count of imported wasm globals. This number will be appended
|
|
/// to the global indexes when sections are merged.
|
|
imported_globals_count: u32 = 0,
|
|
/// The count of imported tables. This number will be appended
|
|
/// to the table indexes when sections are merged.
|
|
imported_tables_count: u32 = 0,
|
|
/// Map of symbol locations, represented by its `types.Import`
|
|
imports: std.AutoHashMapUnmanaged(SymbolLoc, types.Import) = .{},
|
|
/// Represents non-synthetic section entries.
|
|
/// Used for code, data and custom sections.
|
|
segments: std.ArrayListUnmanaged(Segment) = .{},
|
|
/// Maps a data segment key (such as .rodata) to the index into `segments`.
|
|
data_segments: std.StringArrayHashMapUnmanaged(u32) = .{},
|
|
/// A table of `types.Segment` which provide meta data
|
|
/// about a data symbol such as its name where the key is
|
|
/// the segment index, which can be found from `data_segments`
|
|
segment_info: std.AutoArrayHashMapUnmanaged(u32, types.Segment) = .{},
|
|
/// Deduplicated string table for strings used by symbols, imports and exports.
|
|
string_table: StringTable = .{},
|
|
/// Debug information for wasm
|
|
dwarf: ?Dwarf = null,
|
|
|
|
// Output sections
|
|
/// Output type section
|
|
func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{},
|
|
/// Output function section where the key is the original
|
|
/// function index and the value is function.
|
|
/// This allows us to map multiple symbols to the same function.
|
|
functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, std.wasm.Func) = .{},
|
|
/// Output global section
|
|
wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},
|
|
/// Memory section
|
|
memories: std.wasm.Memory = .{ .limits = .{
|
|
.min = 0,
|
|
.max = undefined,
|
|
.flags = 0,
|
|
} },
|
|
/// Output table section
|
|
tables: std.ArrayListUnmanaged(std.wasm.Table) = .{},
|
|
/// Output export section
|
|
exports: std.ArrayListUnmanaged(types.Export) = .{},
|
|
/// List of initialization functions. These must be called in order of priority
|
|
/// by the (synthetic) __wasm_call_ctors function.
|
|
init_funcs: std.ArrayListUnmanaged(InitFuncLoc) = .{},
|
|
|
|
/// Indirect function table, used to call function pointers
|
|
/// When this is non-zero, we must emit a table entry,
|
|
/// as well as an 'elements' section.
|
|
///
|
|
/// Note: Key is symbol location, value represents the index into the table
|
|
function_table: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .{},
|
|
|
|
/// All object files and their data which are linked into the final binary
|
|
objects: std.ArrayListUnmanaged(Object) = .{},
|
|
/// All archive files that are lazy loaded.
|
|
/// e.g. when an undefined symbol references a symbol from the archive.
|
|
archives: std.ArrayListUnmanaged(Archive) = .{},
|
|
|
|
/// A map of global names (read: offset into string table) to their symbol location
|
|
globals: std.AutoHashMapUnmanaged(u32, SymbolLoc) = .{},
|
|
/// Maps discarded symbols and their positions to the location of the symbol
|
|
/// it was resolved to
|
|
discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .{},
|
|
/// List of all symbol locations which have been resolved by the linker and will be emit
|
|
/// into the final binary.
|
|
resolved_symbols: std.AutoArrayHashMapUnmanaged(SymbolLoc, void) = .{},
|
|
/// Symbols that remain undefined after symbol resolution.
|
|
undefs: std.StringArrayHashMapUnmanaged(SymbolLoc) = .{},
|
|
/// Maps a symbol's location to an atom. This can be used to find meta
|
|
/// data of a symbol, such as its size, or its offset to perform a relocation.
|
|
/// Undefined (and synthetic) symbols do not have an Atom and therefore cannot be mapped.
|
|
symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, Atom.Index) = .{},
|
|
/// Maps a symbol's location to its export name, which may differ from the decl's name
|
|
/// which does the exporting.
|
|
/// Note: The value represents the offset into the string table, rather than the actual string.
|
|
export_names: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .{},
|
|
|
|
/// Represents the symbol index of the error name table
|
|
/// When this is `null`, no code references an error using runtime `@errorName`.
|
|
/// During initializion, a symbol with corresponding atom will be created that is
|
|
/// used to perform relocations to the pointer of this table.
|
|
/// The actual table is populated during `flush`.
|
|
error_table_symbol: ?u32 = null,
|
|
|
|
// Debug section atoms. These are only set when the current compilation
|
|
// unit contains Zig code. The lifetime of these atoms are extended
|
|
// until the end of the compiler's lifetime. Meaning they're not freed
|
|
// during `flush()` in incremental-mode.
|
|
debug_info_atom: ?Atom.Index = null,
|
|
debug_line_atom: ?Atom.Index = null,
|
|
debug_loc_atom: ?Atom.Index = null,
|
|
debug_ranges_atom: ?Atom.Index = null,
|
|
debug_abbrev_atom: ?Atom.Index = null,
|
|
debug_str_atom: ?Atom.Index = null,
|
|
debug_pubnames_atom: ?Atom.Index = null,
|
|
debug_pubtypes_atom: ?Atom.Index = null,
|
|
|
|
pub const Segment = struct {
|
|
alignment: u32,
|
|
size: u32,
|
|
offset: u32,
|
|
flags: u32,
|
|
|
|
pub const Flag = enum(u32) {
|
|
WASM_DATA_SEGMENT_IS_PASSIVE = 0x01,
|
|
WASM_DATA_SEGMENT_HAS_MEMINDEX = 0x02,
|
|
};
|
|
|
|
pub fn isPassive(segment: Segment) bool {
|
|
return segment.flags & @enumToInt(Flag.WASM_DATA_SEGMENT_IS_PASSIVE) != 0;
|
|
}
|
|
};
|
|
|
|
pub const Export = struct {
|
|
sym_index: ?u32 = null,
|
|
};
|
|
|
|
pub const SymbolLoc = struct {
|
|
/// The index of the symbol within the specified file
|
|
index: u32,
|
|
/// The index of the object file where the symbol resides.
|
|
/// When this is `null` the symbol comes from a non-object file.
|
|
file: ?u16,
|
|
|
|
/// From a given location, returns the corresponding symbol in the wasm binary
|
|
pub fn getSymbol(loc: SymbolLoc, wasm_bin: *const Wasm) *Symbol {
|
|
if (wasm_bin.discarded.get(loc)) |new_loc| {
|
|
return new_loc.getSymbol(wasm_bin);
|
|
}
|
|
if (loc.file) |object_index| {
|
|
const object = wasm_bin.objects.items[object_index];
|
|
return &object.symtable[loc.index];
|
|
}
|
|
return &wasm_bin.symbols.items[loc.index];
|
|
}
|
|
|
|
/// From a given location, returns the name of the symbol.
|
|
pub fn getName(loc: SymbolLoc, wasm_bin: *const Wasm) []const u8 {
|
|
if (wasm_bin.discarded.get(loc)) |new_loc| {
|
|
return new_loc.getName(wasm_bin);
|
|
}
|
|
if (loc.file) |object_index| {
|
|
const object = wasm_bin.objects.items[object_index];
|
|
return object.string_table.get(object.symtable[loc.index].name);
|
|
}
|
|
return wasm_bin.string_table.get(wasm_bin.symbols.items[loc.index].name);
|
|
}
|
|
|
|
/// From a given symbol location, returns the final location.
|
|
/// e.g. when a symbol was resolved and replaced by the symbol
|
|
/// in a different file, this will return said location.
|
|
/// If the symbol wasn't replaced by another, this will return
|
|
/// the given location itwasm.
|
|
pub fn finalLoc(loc: SymbolLoc, wasm_bin: *const Wasm) SymbolLoc {
|
|
if (wasm_bin.discarded.get(loc)) |new_loc| {
|
|
return new_loc.finalLoc(wasm_bin);
|
|
}
|
|
return loc;
|
|
}
|
|
};
|
|
|
|
// Contains the location of the function symbol, as well as
|
|
/// the priority itself of the initialization function.
|
|
pub const InitFuncLoc = struct {
|
|
/// object file index in the list of objects.
|
|
/// Unlike `SymbolLoc` this cannot be `null` as we never define
|
|
/// our own ctors.
|
|
file: u16,
|
|
/// Symbol index within the corresponding object file.
|
|
index: u32,
|
|
/// The priority in which the constructor must be called.
|
|
priority: u32,
|
|
|
|
/// From a given `InitFuncLoc` returns the corresponding function symbol
|
|
fn getSymbol(loc: InitFuncLoc, wasm: *const Wasm) *Symbol {
|
|
return getSymbolLoc(loc).getSymbol(wasm);
|
|
}
|
|
|
|
/// Turns the given `InitFuncLoc` into a `SymbolLoc`
|
|
fn getSymbolLoc(loc: InitFuncLoc) SymbolLoc {
|
|
return .{ .file = loc.file, .index = loc.index };
|
|
}
|
|
|
|
/// Returns true when `lhs` has a higher priority (e.i. value closer to 0) than `rhs`.
|
|
fn lessThan(ctx: void, lhs: InitFuncLoc, rhs: InitFuncLoc) bool {
|
|
_ = ctx;
|
|
return lhs.priority < rhs.priority;
|
|
}
|
|
};
|
|
/// Generic string table that duplicates strings
|
|
/// and converts them into offsets instead.
|
|
pub const StringTable = struct {
|
|
/// Table that maps string offsets, which is used to de-duplicate strings.
|
|
/// Rather than having the offset map to the data, the `StringContext` holds all bytes of the string.
|
|
/// The strings are stored as a contigious array where each string is zero-terminated.
|
|
string_table: std.HashMapUnmanaged(
|
|
u32,
|
|
void,
|
|
std.hash_map.StringIndexContext,
|
|
std.hash_map.default_max_load_percentage,
|
|
) = .{},
|
|
/// Holds the actual data of the string table.
|
|
string_data: std.ArrayListUnmanaged(u8) = .{},
|
|
|
|
/// Accepts a string and searches for a corresponding string.
|
|
/// When found, de-duplicates the string and returns the existing offset instead.
|
|
/// When the string is not found in the `string_table`, a new entry will be inserted
|
|
/// and the new offset to its data will be returned.
|
|
pub fn put(table: *StringTable, allocator: Allocator, string: []const u8) !u32 {
|
|
const gop = try table.string_table.getOrPutContextAdapted(
|
|
allocator,
|
|
string,
|
|
std.hash_map.StringIndexAdapter{ .bytes = &table.string_data },
|
|
.{ .bytes = &table.string_data },
|
|
);
|
|
if (gop.found_existing) {
|
|
const off = gop.key_ptr.*;
|
|
log.debug("reusing string '{s}' at offset 0x{x}", .{ string, off });
|
|
return off;
|
|
}
|
|
|
|
try table.string_data.ensureUnusedCapacity(allocator, string.len + 1);
|
|
const offset = @intCast(u32, table.string_data.items.len);
|
|
|
|
log.debug("writing new string '{s}' at offset 0x{x}", .{ string, offset });
|
|
|
|
table.string_data.appendSliceAssumeCapacity(string);
|
|
table.string_data.appendAssumeCapacity(0);
|
|
|
|
gop.key_ptr.* = offset;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/// From a given offset, returns its corresponding string value.
|
|
/// Asserts offset does not exceed bounds.
|
|
pub fn get(table: StringTable, off: u32) []const u8 {
|
|
assert(off < table.string_data.items.len);
|
|
return mem.sliceTo(@ptrCast([*:0]const u8, table.string_data.items.ptr + off), 0);
|
|
}
|
|
|
|
/// Returns the offset of a given string when it exists.
|
|
/// Will return null if the given string does not yet exist within the string table.
|
|
pub fn getOffset(table: *StringTable, string: []const u8) ?u32 {
|
|
return table.string_table.getKeyAdapted(
|
|
string,
|
|
std.hash_map.StringIndexAdapter{ .bytes = &table.string_data },
|
|
);
|
|
}
|
|
|
|
/// Frees all resources of the string table. Any references pointing
|
|
/// to the strings will be invalid.
|
|
pub fn deinit(table: *StringTable, allocator: Allocator) void {
|
|
table.string_data.deinit(allocator);
|
|
table.string_table.deinit(allocator);
|
|
table.* = undefined;
|
|
}
|
|
};
|
|
|
|
pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
|
|
assert(options.target.ofmt == .wasm);
|
|
|
|
if (build_options.have_llvm and options.use_llvm and options.use_lld) {
|
|
return createEmpty(allocator, options);
|
|
}
|
|
|
|
const wasm_bin = try createEmpty(allocator, options);
|
|
errdefer wasm_bin.base.destroy();
|
|
|
|
// We are not using LLD at this point, so ensure we set the intermediary basename
|
|
if (build_options.have_llvm and options.use_llvm and options.module != null) {
|
|
// TODO this intermediary_basename isn't enough; in the case of `zig build-exe`,
|
|
// we also want to put the intermediary object file in the cache while the
|
|
// main emit directory is the cwd.
|
|
wasm_bin.base.intermediary_basename = try std.fmt.allocPrint(allocator, "{s}{s}", .{
|
|
options.emit.?.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch),
|
|
});
|
|
}
|
|
|
|
// TODO: read the file and keep valid parts instead of truncating
|
|
const file = try options.emit.?.directory.handle.createFile(sub_path, .{
|
|
.truncate = true,
|
|
.read = true,
|
|
.mode = if (fs.has_executable_bit)
|
|
if (options.target.os.tag == .wasi and options.output_mode == .Exe)
|
|
fs.File.default_mode | 0b001_000_000
|
|
else
|
|
fs.File.default_mode
|
|
else
|
|
0,
|
|
});
|
|
wasm_bin.base.file = file;
|
|
wasm_bin.name = sub_path;
|
|
|
|
// create stack pointer symbol
|
|
{
|
|
const loc = try wasm_bin.createSyntheticSymbol("__stack_pointer", .global);
|
|
const symbol = loc.getSymbol(wasm_bin);
|
|
// For object files we will import the stack pointer symbol
|
|
if (options.output_mode == .Obj) {
|
|
symbol.setUndefined(true);
|
|
symbol.index = @intCast(u32, wasm_bin.imported_globals_count);
|
|
wasm_bin.imported_globals_count += 1;
|
|
try wasm_bin.imports.putNoClobber(
|
|
allocator,
|
|
loc,
|
|
.{
|
|
.module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
|
|
.name = symbol.name,
|
|
.kind = .{ .global = .{ .valtype = .i32, .mutable = true } },
|
|
},
|
|
);
|
|
} else {
|
|
symbol.index = @intCast(u32, wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len);
|
|
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
|
|
const global = try wasm_bin.wasm_globals.addOne(allocator);
|
|
global.* = .{
|
|
.global_type = .{
|
|
.valtype = .i32,
|
|
.mutable = true,
|
|
},
|
|
.init = .{ .i32_const = 0 },
|
|
};
|
|
}
|
|
}
|
|
|
|
// create indirect function pointer symbol
|
|
{
|
|
const loc = try wasm_bin.createSyntheticSymbol("__indirect_function_table", .table);
|
|
const symbol = loc.getSymbol(wasm_bin);
|
|
const table: std.wasm.Table = .{
|
|
.limits = .{ .flags = 0, .min = 0, .max = undefined }, // will be overwritten during `mapFunctionTable`
|
|
.reftype = .funcref,
|
|
};
|
|
if (options.output_mode == .Obj or options.import_table) {
|
|
symbol.setUndefined(true);
|
|
symbol.index = @intCast(u32, wasm_bin.imported_tables_count);
|
|
wasm_bin.imported_tables_count += 1;
|
|
try wasm_bin.imports.put(allocator, loc, .{
|
|
.module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
|
|
.name = symbol.name,
|
|
.kind = .{ .table = table },
|
|
});
|
|
} else {
|
|
symbol.index = @intCast(u32, wasm_bin.imported_tables_count + wasm_bin.tables.items.len);
|
|
try wasm_bin.tables.append(allocator, table);
|
|
if (options.export_table) {
|
|
symbol.setFlag(.WASM_SYM_EXPORTED);
|
|
} else {
|
|
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
|
|
}
|
|
}
|
|
}
|
|
|
|
// create __wasm_call_ctors
|
|
{
|
|
const loc = try wasm_bin.createSyntheticSymbol("__wasm_call_ctors", .function);
|
|
const symbol = loc.getSymbol(wasm_bin);
|
|
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
|
|
// we do not know the function index until after we merged all sections.
|
|
// Therefore we set `symbol.index` and create its corresponding references
|
|
// at the end during `initializeCallCtorsFunction`.
|
|
}
|
|
|
|
// if (!options.strip and options.module != null) {
|
|
// wasm_bin.dwarf = Dwarf.init(allocator, &wasm_bin.base, options.target);
|
|
// try wasm_bin.initDebugSections();
|
|
// }
|
|
|
|
return wasm_bin;
|
|
}
|
|
|
|
pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {
|
|
const wasm = try gpa.create(Wasm);
|
|
errdefer gpa.destroy(wasm);
|
|
wasm.* = .{
|
|
.base = .{
|
|
.tag = .wasm,
|
|
.options = options,
|
|
.file = null,
|
|
.allocator = gpa,
|
|
},
|
|
.name = undefined,
|
|
};
|
|
|
|
const use_llvm = build_options.have_llvm and options.use_llvm;
|
|
if (use_llvm) {
|
|
wasm.llvm_object = try LlvmObject.create(gpa, options);
|
|
}
|
|
return wasm;
|
|
}
|
|
|
|
/// For a given name, creates a new global synthetic symbol.
|
|
/// Leaves index undefined and the default flags (0).
|
|
fn createSyntheticSymbol(wasm: *Wasm, name: []const u8, tag: Symbol.Tag) !SymbolLoc {
|
|
const name_offset = try wasm.string_table.put(wasm.base.allocator, name);
|
|
const sym_index = @intCast(u32, wasm.symbols.items.len);
|
|
const loc: SymbolLoc = .{ .index = sym_index, .file = null };
|
|
try wasm.symbols.append(wasm.base.allocator, .{
|
|
.name = name_offset,
|
|
.flags = 0,
|
|
.tag = tag,
|
|
.index = undefined,
|
|
.virtual_address = undefined,
|
|
});
|
|
try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, loc, {});
|
|
try wasm.globals.put(wasm.base.allocator, name_offset, loc);
|
|
return loc;
|
|
}
|
|
|
|
/// Initializes symbols and atoms for the debug sections
|
|
/// Initialization is only done when compiling Zig code.
|
|
/// When Zig is invoked as a linker instead, the atoms
|
|
/// and symbols come from the object files instead.
|
|
pub fn initDebugSections(wasm: *Wasm) !void {
|
|
if (wasm.dwarf == null) return; // not compiling Zig code, so no need to pre-initialize debug sections
|
|
assert(wasm.debug_info_index == null);
|
|
// this will create an Atom and set the index for us.
|
|
wasm.debug_info_atom = try wasm.createDebugSectionForIndex(&wasm.debug_info_index, ".debug_info");
|
|
wasm.debug_line_atom = try wasm.createDebugSectionForIndex(&wasm.debug_line_index, ".debug_line");
|
|
wasm.debug_loc_atom = try wasm.createDebugSectionForIndex(&wasm.debug_loc_index, ".debug_loc");
|
|
wasm.debug_abbrev_atom = try wasm.createDebugSectionForIndex(&wasm.debug_abbrev_index, ".debug_abbrev");
|
|
wasm.debug_ranges_atom = try wasm.createDebugSectionForIndex(&wasm.debug_ranges_index, ".debug_ranges");
|
|
wasm.debug_str_atom = try wasm.createDebugSectionForIndex(&wasm.debug_str_index, ".debug_str");
|
|
wasm.debug_pubnames_atom = try wasm.createDebugSectionForIndex(&wasm.debug_pubnames_index, ".debug_pubnames");
|
|
wasm.debug_pubtypes_atom = try wasm.createDebugSectionForIndex(&wasm.debug_pubtypes_index, ".debug_pubtypes");
|
|
}
|
|
|
|
fn parseInputFiles(wasm: *Wasm, files: []const []const u8) !void {
|
|
for (files) |path| {
|
|
if (try wasm.parseObjectFile(path)) continue;
|
|
if (try wasm.parseArchive(path, false)) continue; // load archives lazily
|
|
log.warn("Unexpected file format at path: '{s}'", .{path});
|
|
}
|
|
}
|
|
|
|
/// Parses the object file from given path. Returns true when the given file was an object
|
|
/// file and parsed successfully. Returns false when file is not an object file.
|
|
/// May return an error instead when parsing failed.
|
|
fn parseObjectFile(wasm: *Wasm, path: []const u8) !bool {
|
|
const file = try fs.cwd().openFile(path, .{});
|
|
errdefer file.close();
|
|
|
|
var object = Object.create(wasm.base.allocator, file, path, null) catch |err| switch (err) {
|
|
error.InvalidMagicByte, error.NotObjectFile => return false,
|
|
else => |e| return e,
|
|
};
|
|
errdefer object.deinit(wasm.base.allocator);
|
|
try wasm.objects.append(wasm.base.allocator, object);
|
|
return true;
|
|
}
|
|
|
|
/// For a given `Module.Decl.Index` returns its corresponding `Atom.Index`.
|
|
/// When the index was not found, a new `Atom` will be created, and its index will be returned.
|
|
/// The newly created Atom is empty with default fields as specified by `Atom.empty`.
|
|
pub fn getOrCreateAtomForDecl(wasm: *Wasm, decl_index: Module.Decl.Index) !Atom.Index {
|
|
const gop = try wasm.decls.getOrPut(wasm.base.allocator, decl_index);
|
|
if (!gop.found_existing) {
|
|
gop.value_ptr.* = try wasm.createAtom();
|
|
}
|
|
return gop.value_ptr.*;
|
|
}
|
|
|
|
/// Creates a new empty `Atom` and returns its `Atom.Index`
|
|
fn createAtom(wasm: *Wasm) !Atom.Index {
|
|
const index = @intCast(Atom.Index, wasm.managed_atoms.items.len);
|
|
const atom = try wasm.managed_atoms.addOne(wasm.base.allocator);
|
|
atom.* = Atom.empty;
|
|
atom.sym_index = try wasm.allocateSymbol();
|
|
try wasm.symbol_atom.putNoClobber(wasm.base.allocator, .{ .file = null, .index = atom.sym_index }, index);
|
|
|
|
return index;
|
|
}
|
|
|
|
pub inline fn getAtom(wasm: *const Wasm, index: Atom.Index) Atom {
|
|
return wasm.managed_atoms.items[index];
|
|
}
|
|
|
|
pub inline fn getAtomPtr(wasm: *Wasm, index: Atom.Index) *Atom {
|
|
return &wasm.managed_atoms.items[index];
|
|
}
|
|
|
|
/// Parses an archive file and will then parse each object file
|
|
/// that was found in the archive file.
|
|
/// Returns false when the file is not an archive file.
|
|
/// May return an error instead when parsing failed.
|
|
///
|
|
/// When `force_load` is `true`, it will for link all object files in the archive.
|
|
/// When false, it will only link with object files that contain symbols that
|
|
/// are referenced by other object files or Zig code.
|
|
fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool {
|
|
const file = try fs.cwd().openFile(path, .{});
|
|
errdefer file.close();
|
|
|
|
var archive: Archive = .{
|
|
.file = file,
|
|
.name = path,
|
|
};
|
|
archive.parse(wasm.base.allocator) catch |err| switch (err) {
|
|
error.EndOfStream, error.NotArchive => {
|
|
archive.deinit(wasm.base.allocator);
|
|
return false;
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
|
|
if (!force_load) {
|
|
errdefer archive.deinit(wasm.base.allocator);
|
|
try wasm.archives.append(wasm.base.allocator, archive);
|
|
return true;
|
|
}
|
|
defer archive.deinit(wasm.base.allocator);
|
|
|
|
// In this case we must force link all embedded object files within the archive
|
|
// We loop over all symbols, and then group them by offset as the offset
|
|
// notates where the object file starts.
|
|
var offsets = std.AutoArrayHashMap(u32, void).init(wasm.base.allocator);
|
|
defer offsets.deinit();
|
|
for (archive.toc.values()) |symbol_offsets| {
|
|
for (symbol_offsets.items) |sym_offset| {
|
|
try offsets.put(sym_offset, {});
|
|
}
|
|
}
|
|
|
|
for (offsets.keys()) |file_offset| {
|
|
const object = try wasm.objects.addOne(wasm.base.allocator);
|
|
object.* = try archive.parseObject(wasm.base.allocator, file_offset);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
fn resolveSymbolsInObject(wasm: *Wasm, object_index: u16) !void {
|
|
const object: Object = wasm.objects.items[object_index];
|
|
log.debug("Resolving symbols in object: '{s}'", .{object.name});
|
|
|
|
for (object.symtable, 0..) |symbol, i| {
|
|
const sym_index = @intCast(u32, i);
|
|
const location: SymbolLoc = .{
|
|
.file = object_index,
|
|
.index = sym_index,
|
|
};
|
|
const sym_name = object.string_table.get(symbol.name);
|
|
if (mem.eql(u8, sym_name, "__indirect_function_table")) {
|
|
continue;
|
|
}
|
|
const sym_name_index = try wasm.string_table.put(wasm.base.allocator, sym_name);
|
|
|
|
if (symbol.isLocal()) {
|
|
if (symbol.isUndefined()) {
|
|
log.err("Local symbols are not allowed to reference imports", .{});
|
|
log.err(" symbol '{s}' defined in '{s}'", .{ sym_name, object.name });
|
|
return error.UndefinedLocal;
|
|
}
|
|
try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, location, {});
|
|
continue;
|
|
}
|
|
|
|
const maybe_existing = try wasm.globals.getOrPut(wasm.base.allocator, sym_name_index);
|
|
if (!maybe_existing.found_existing) {
|
|
maybe_existing.value_ptr.* = location;
|
|
try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, location, {});
|
|
|
|
if (symbol.isUndefined()) {
|
|
try wasm.undefs.putNoClobber(wasm.base.allocator, sym_name, location);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const existing_loc = maybe_existing.value_ptr.*;
|
|
const existing_sym: *Symbol = existing_loc.getSymbol(wasm);
|
|
|
|
const existing_file_path = if (existing_loc.file) |file| blk: {
|
|
break :blk wasm.objects.items[file].name;
|
|
} else wasm.name;
|
|
|
|
if (!existing_sym.isUndefined()) outer: {
|
|
if (!symbol.isUndefined()) inner: {
|
|
if (symbol.isWeak()) {
|
|
break :inner; // ignore the new symbol (discard it)
|
|
}
|
|
if (existing_sym.isWeak()) {
|
|
break :outer; // existing is weak, while new one isn't. Replace it.
|
|
}
|
|
// both are defined and weak, we have a symbol collision.
|
|
log.err("symbol '{s}' defined multiple times", .{sym_name});
|
|
log.err(" first definition in '{s}'", .{existing_file_path});
|
|
log.err(" next definition in '{s}'", .{object.name});
|
|
return error.SymbolCollision;
|
|
}
|
|
|
|
try wasm.discarded.put(wasm.base.allocator, location, existing_loc);
|
|
continue; // Do not overwrite defined symbols with undefined symbols
|
|
}
|
|
|
|
if (symbol.tag != existing_sym.tag) {
|
|
log.err("symbol '{s}' mismatching type '{s}", .{ sym_name, @tagName(symbol.tag) });
|
|
log.err(" first definition in '{s}'", .{existing_file_path});
|
|
log.err(" next definition in '{s}'", .{object.name});
|
|
return error.SymbolMismatchingType;
|
|
}
|
|
|
|
if (existing_sym.isUndefined() and symbol.isUndefined()) {
|
|
// only verify module/import name for function symbols
|
|
if (symbol.tag == .function) {
|
|
const existing_name = if (existing_loc.file) |file_index| blk: {
|
|
const obj = wasm.objects.items[file_index];
|
|
const name_index = obj.findImport(symbol.tag.externalType(), existing_sym.index).module_name;
|
|
break :blk obj.string_table.get(name_index);
|
|
} else blk: {
|
|
const name_index = wasm.imports.get(existing_loc).?.module_name;
|
|
break :blk wasm.string_table.get(name_index);
|
|
};
|
|
|
|
const module_index = object.findImport(symbol.tag.externalType(), symbol.index).module_name;
|
|
const module_name = object.string_table.get(module_index);
|
|
if (!mem.eql(u8, existing_name, module_name)) {
|
|
log.err("symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{
|
|
sym_name,
|
|
existing_name,
|
|
module_name,
|
|
});
|
|
log.err(" first definition in '{s}'", .{existing_file_path});
|
|
log.err(" next definition in '{s}'", .{object.name});
|
|
return error.ModuleNameMismatch;
|
|
}
|
|
}
|
|
|
|
// both undefined so skip overwriting existing symbol and discard the new symbol
|
|
try wasm.discarded.put(wasm.base.allocator, location, existing_loc);
|
|
continue;
|
|
}
|
|
|
|
if (existing_sym.tag == .global) {
|
|
const existing_ty = wasm.getGlobalType(existing_loc);
|
|
const new_ty = wasm.getGlobalType(location);
|
|
if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) {
|
|
log.err("symbol '{s}' mismatching global types", .{sym_name});
|
|
log.err(" first definition in '{s}'", .{existing_file_path});
|
|
log.err(" next definition in '{s}'", .{object.name});
|
|
return error.GlobalTypeMismatch;
|
|
}
|
|
}
|
|
|
|
if (existing_sym.tag == .function) {
|
|
const existing_ty = wasm.getFunctionSignature(existing_loc);
|
|
const new_ty = wasm.getFunctionSignature(location);
|
|
if (!existing_ty.eql(new_ty)) {
|
|
log.err("symbol '{s}' mismatching function signatures.", .{sym_name});
|
|
log.err(" expected signature {}, but found signature {}", .{ existing_ty, new_ty });
|
|
log.err(" first definition in '{s}'", .{existing_file_path});
|
|
log.err(" next definition in '{s}'", .{object.name});
|
|
return error.FunctionSignatureMismatch;
|
|
}
|
|
}
|
|
|
|
// when both symbols are weak, we skip overwriting unless the existing
|
|
// symbol is weak and the new one isn't, in which case we *do* overwrite it.
|
|
if (existing_sym.isWeak() and symbol.isWeak()) blk: {
|
|
if (existing_sym.isUndefined() and !symbol.isUndefined()) break :blk;
|
|
try wasm.discarded.put(wasm.base.allocator, location, existing_loc);
|
|
continue;
|
|
}
|
|
|
|
// simply overwrite with the new symbol
|
|
log.debug("Overwriting symbol '{s}'", .{sym_name});
|
|
log.debug(" old definition in '{s}'", .{existing_file_path});
|
|
log.debug(" new definition in '{s}'", .{object.name});
|
|
try wasm.discarded.putNoClobber(wasm.base.allocator, existing_loc, location);
|
|
maybe_existing.value_ptr.* = location;
|
|
try wasm.globals.put(wasm.base.allocator, sym_name_index, location);
|
|
try wasm.resolved_symbols.put(wasm.base.allocator, location, {});
|
|
assert(wasm.resolved_symbols.swapRemove(existing_loc));
|
|
if (existing_sym.isUndefined()) {
|
|
_ = wasm.undefs.swapRemove(sym_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn resolveSymbolsInArchives(wasm: *Wasm) !void {
|
|
if (wasm.archives.items.len == 0) return;
|
|
|
|
log.debug("Resolving symbols in archives", .{});
|
|
var index: u32 = 0;
|
|
undef_loop: while (index < wasm.undefs.count()) {
|
|
const sym_name = wasm.undefs.keys()[index];
|
|
|
|
for (wasm.archives.items) |archive| {
|
|
const offset = archive.toc.get(sym_name) orelse {
|
|
// symbol does not exist in this archive
|
|
continue;
|
|
};
|
|
|
|
log.debug("Detected symbol '{s}' in archive '{s}', parsing objects..", .{ sym_name, archive.name });
|
|
// Symbol is found in unparsed object file within current archive.
|
|
// Parse object and and resolve symbols again before we check remaining
|
|
// undefined symbols.
|
|
const object_file_index = @intCast(u16, wasm.objects.items.len);
|
|
var object = try archive.parseObject(wasm.base.allocator, offset.items[0]);
|
|
try wasm.objects.append(wasm.base.allocator, object);
|
|
try wasm.resolveSymbolsInObject(object_file_index);
|
|
|
|
// continue loop for any remaining undefined symbols that still exist
|
|
// after resolving last object file
|
|
continue :undef_loop;
|
|
}
|
|
index += 1;
|
|
}
|
|
}
|
|
|
|
fn validateFeatures(
|
|
wasm: *const Wasm,
|
|
to_emit: *[@typeInfo(types.Feature.Tag).Enum.fields.len]bool,
|
|
emit_features_count: *u32,
|
|
) !void {
|
|
const cpu_features = wasm.base.options.target.cpu.features;
|
|
const infer = cpu_features.isEmpty(); // when the user did not define any features, we infer them from linked objects.
|
|
const known_features_count = @typeInfo(types.Feature.Tag).Enum.fields.len;
|
|
|
|
var allowed = [_]bool{false} ** known_features_count;
|
|
var used = [_]u17{0} ** known_features_count;
|
|
var disallowed = [_]u17{0} ** known_features_count;
|
|
var required = [_]u17{0} ** known_features_count;
|
|
|
|
// when false, we fail linking. We only verify this after a loop to catch all invalid features.
|
|
var valid_feature_set = true;
|
|
// will be set to true when there's any TLS segment found in any of the object files
|
|
var has_tls = false;
|
|
|
|
// When the user has given an explicit list of features to enable,
|
|
// we extract them and insert each into the 'allowed' list.
|
|
if (!infer) {
|
|
inline for (@typeInfo(std.Target.wasm.Feature).Enum.fields) |feature_field| {
|
|
if (cpu_features.isEnabled(feature_field.value)) {
|
|
allowed[feature_field.value] = true;
|
|
emit_features_count.* += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// extract all the used, disallowed and required features from each
|
|
// linked object file so we can test them.
|
|
for (wasm.objects.items, 0..) |object, object_index| {
|
|
for (object.features) |feature| {
|
|
const value = @intCast(u16, object_index) << 1 | @as(u1, 1);
|
|
switch (feature.prefix) {
|
|
.used => {
|
|
used[@enumToInt(feature.tag)] = value;
|
|
},
|
|
.disallowed => {
|
|
disallowed[@enumToInt(feature.tag)] = value;
|
|
},
|
|
.required => {
|
|
required[@enumToInt(feature.tag)] = value;
|
|
used[@enumToInt(feature.tag)] = value;
|
|
},
|
|
}
|
|
}
|
|
|
|
for (object.segment_info) |segment| {
|
|
if (segment.isTLS()) {
|
|
has_tls = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// when we infer the features, we allow each feature found in the 'used' set
|
|
// and insert it into the 'allowed' set. When features are not inferred,
|
|
// we validate that a used feature is allowed.
|
|
for (used, 0..) |used_set, used_index| {
|
|
const is_enabled = @truncate(u1, used_set) != 0;
|
|
if (infer) {
|
|
allowed[used_index] = is_enabled;
|
|
emit_features_count.* += @boolToInt(is_enabled);
|
|
} else if (is_enabled and !allowed[used_index]) {
|
|
log.err("feature '{}' not allowed, but used by linked object", .{@intToEnum(types.Feature.Tag, used_index)});
|
|
log.err(" defined in '{s}'", .{wasm.objects.items[used_set >> 1].name});
|
|
valid_feature_set = false;
|
|
}
|
|
}
|
|
|
|
if (!valid_feature_set) {
|
|
return error.InvalidFeatureSet;
|
|
}
|
|
|
|
if (wasm.base.options.shared_memory) {
|
|
const disallowed_feature = disallowed[@enumToInt(types.Feature.Tag.shared_mem)];
|
|
if (@truncate(u1, disallowed_feature) != 0) {
|
|
log.err(
|
|
"shared-memory is disallowed by '{s}' because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled",
|
|
.{wasm.objects.items[disallowed_feature >> 1].name},
|
|
);
|
|
valid_feature_set = false;
|
|
}
|
|
|
|
for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| {
|
|
if (!allowed[@enumToInt(feature)]) {
|
|
log.err("feature '{}' is not used but is required for shared-memory", .{feature});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (has_tls) {
|
|
for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| {
|
|
if (!allowed[@enumToInt(feature)]) {
|
|
log.err("feature '{}' is not used but is required for thread-local storage", .{feature});
|
|
}
|
|
}
|
|
}
|
|
// For each linked object, validate the required and disallowed features
|
|
for (wasm.objects.items) |object| {
|
|
var object_used_features = [_]bool{false} ** known_features_count;
|
|
for (object.features) |feature| {
|
|
if (feature.prefix == .disallowed) continue; // already defined in 'disallowed' set.
|
|
// from here a feature is always used
|
|
const disallowed_feature = disallowed[@enumToInt(feature.tag)];
|
|
if (@truncate(u1, disallowed_feature) != 0) {
|
|
log.err("feature '{}' is disallowed, but used by linked object", .{feature.tag});
|
|
log.err(" disallowed by '{s}'", .{wasm.objects.items[disallowed_feature >> 1].name});
|
|
log.err(" used in '{s}'", .{object.name});
|
|
valid_feature_set = false;
|
|
}
|
|
|
|
object_used_features[@enumToInt(feature.tag)] = true;
|
|
}
|
|
|
|
// validate the linked object file has each required feature
|
|
for (required, 0..) |required_feature, feature_index| {
|
|
const is_required = @truncate(u1, required_feature) != 0;
|
|
if (is_required and !object_used_features[feature_index]) {
|
|
log.err("feature '{}' is required but not used in linked object", .{@intToEnum(types.Feature.Tag, feature_index)});
|
|
log.err(" required by '{s}'", .{wasm.objects.items[required_feature >> 1].name});
|
|
log.err(" missing in '{s}'", .{object.name});
|
|
valid_feature_set = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!valid_feature_set) {
|
|
return error.InvalidFeatureSet;
|
|
}
|
|
|
|
to_emit.* = allowed;
|
|
}
|
|
|
|
/// Creates synthetic linker-symbols, but only if they are being referenced from
|
|
/// any object file. For instance, the `__heap_base` symbol will only be created,
|
|
/// if one or multiple undefined references exist. When none exist, the symbol will
|
|
/// not be created, ensuring we don't unneccesarily emit unreferenced symbols.
|
|
fn resolveLazySymbols(wasm: *Wasm) !void {
|
|
if (wasm.undefs.fetchSwapRemove("__heap_base")) |kv| {
|
|
const loc = try wasm.createSyntheticSymbol("__heap_base", .data);
|
|
try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
|
|
_ = wasm.resolved_symbols.swapRemove(loc); // we don't want to emit this symbol, only use it for relocations.
|
|
}
|
|
|
|
if (wasm.undefs.fetchSwapRemove("__heap_end")) |kv| {
|
|
const loc = try wasm.createSyntheticSymbol("__heap_end", .data);
|
|
try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
|
|
_ = wasm.resolved_symbols.swapRemove(loc);
|
|
}
|
|
|
|
if (!wasm.base.options.shared_memory) {
|
|
if (wasm.undefs.fetchSwapRemove("__tls_base")) |kv| {
|
|
const loc = try wasm.createSyntheticSymbol("__tls_base", .global);
|
|
try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tries to find a global symbol by its name. Returns null when not found,
|
|
/// and its location when it is found.
|
|
fn findGlobalSymbol(wasm: *Wasm, name: []const u8) ?SymbolLoc {
|
|
const offset = wasm.string_table.getOffset(name) orelse return null;
|
|
return wasm.globals.get(offset);
|
|
}
|
|
|
|
fn checkUndefinedSymbols(wasm: *const Wasm) !void {
|
|
if (wasm.base.options.output_mode == .Obj) return;
|
|
if (wasm.base.options.import_symbols) return;
|
|
|
|
var found_undefined_symbols = false;
|
|
for (wasm.undefs.values()) |undef| {
|
|
const symbol = undef.getSymbol(wasm);
|
|
if (symbol.tag == .data) {
|
|
found_undefined_symbols = true;
|
|
const file_name = if (undef.file) |file_index| name: {
|
|
break :name wasm.objects.items[file_index].name;
|
|
} else wasm.name;
|
|
const symbol_name = undef.getName(wasm);
|
|
log.err("could not resolve undefined symbol '{s}'", .{symbol_name});
|
|
log.err(" defined in '{s}'", .{file_name});
|
|
}
|
|
}
|
|
if (found_undefined_symbols) {
|
|
return error.UndefinedSymbol;
|
|
}
|
|
}
|
|
|
|
pub fn deinit(wasm: *Wasm) void {
|
|
const gpa = wasm.base.allocator;
|
|
if (build_options.have_llvm) {
|
|
if (wasm.llvm_object) |llvm_object| llvm_object.destroy(gpa);
|
|
}
|
|
|
|
for (wasm.func_types.items) |*func_type| {
|
|
func_type.deinit(gpa);
|
|
}
|
|
for (wasm.segment_info.values()) |segment_info| {
|
|
gpa.free(segment_info.name);
|
|
}
|
|
for (wasm.objects.items) |*object| {
|
|
object.deinit(gpa);
|
|
}
|
|
|
|
for (wasm.archives.items) |*archive| {
|
|
archive.deinit(gpa);
|
|
}
|
|
|
|
wasm.decls.deinit(gpa);
|
|
wasm.atom_types.deinit(gpa);
|
|
wasm.symbols.deinit(gpa);
|
|
wasm.symbols_free_list.deinit(gpa);
|
|
wasm.globals.deinit(gpa);
|
|
wasm.resolved_symbols.deinit(gpa);
|
|
wasm.undefs.deinit(gpa);
|
|
wasm.discarded.deinit(gpa);
|
|
wasm.symbol_atom.deinit(gpa);
|
|
wasm.export_names.deinit(gpa);
|
|
wasm.atoms.deinit(gpa);
|
|
for (wasm.managed_atoms.items) |*managed_atom| {
|
|
managed_atom.deinit(wasm);
|
|
}
|
|
wasm.managed_atoms.deinit(gpa);
|
|
wasm.segments.deinit(gpa);
|
|
wasm.data_segments.deinit(gpa);
|
|
wasm.segment_info.deinit(gpa);
|
|
wasm.objects.deinit(gpa);
|
|
wasm.archives.deinit(gpa);
|
|
|
|
// free output sections
|
|
wasm.imports.deinit(gpa);
|
|
wasm.func_types.deinit(gpa);
|
|
wasm.functions.deinit(gpa);
|
|
wasm.wasm_globals.deinit(gpa);
|
|
wasm.function_table.deinit(gpa);
|
|
wasm.tables.deinit(gpa);
|
|
wasm.init_funcs.deinit(gpa);
|
|
wasm.exports.deinit(gpa);
|
|
|
|
wasm.string_table.deinit(gpa);
|
|
|
|
if (wasm.dwarf) |*dwarf| {
|
|
dwarf.deinit();
|
|
}
|
|
}
|
|
|
|
/// Allocates a new symbol and returns its index.
|
|
/// Will re-use slots when a symbol was freed at an earlier stage.
|
|
pub fn allocateSymbol(wasm: *Wasm) !u32 {
|
|
try wasm.symbols.ensureUnusedCapacity(wasm.base.allocator, 1);
|
|
var symbol: Symbol = .{
|
|
.name = undefined, // will be set after updateDecl
|
|
.flags = @enumToInt(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
|
|
.tag = undefined, // will be set after updateDecl
|
|
.index = undefined, // will be set after updateDecl
|
|
.virtual_address = undefined, // will be set during atom allocation
|
|
};
|
|
if (wasm.symbols_free_list.popOrNull()) |index| {
|
|
wasm.symbols.items[index] = symbol;
|
|
return index;
|
|
}
|
|
const index = @intCast(u32, wasm.symbols.items.len);
|
|
wasm.symbols.appendAssumeCapacity(symbol);
|
|
return index;
|
|
}
|
|
|
|
pub fn updateFunc(wasm: *Wasm, mod: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
|
|
if (build_options.skip_non_native and builtin.object_format != .wasm) {
|
|
@panic("Attempted to compile for object format that was disabled by build configuration");
|
|
}
|
|
if (build_options.have_llvm) {
|
|
if (wasm.llvm_object) |llvm_object| return llvm_object.updateFunc(mod, func, air, liveness);
|
|
}
|
|
|
|
const tracy = trace(@src());
|
|
defer tracy.end();
|
|
|
|
const decl_index = func.owner_decl;
|
|
const decl = mod.declPtr(decl_index);
|
|
const atom_index = try wasm.getOrCreateAtomForDecl(decl_index);
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
atom.clear();
|
|
|
|
// var decl_state: ?Dwarf.DeclState = if (wasm.dwarf) |*dwarf| try dwarf.initDeclState(mod, decl_index) else null;
|
|
// defer if (decl_state) |*ds| ds.deinit();
|
|
|
|
var code_writer = std.ArrayList(u8).init(wasm.base.allocator);
|
|
defer code_writer.deinit();
|
|
// const result = try codegen.generateFunction(
|
|
// &wasm.base,
|
|
// decl.srcLoc(),
|
|
// func,
|
|
// air,
|
|
// liveness,
|
|
// &code_writer,
|
|
// if (decl_state) |*ds| .{ .dwarf = ds } else .none,
|
|
// );
|
|
const result = try codegen.generateFunction(
|
|
&wasm.base,
|
|
decl.srcLoc(),
|
|
func,
|
|
air,
|
|
liveness,
|
|
&code_writer,
|
|
.none,
|
|
);
|
|
|
|
const code = switch (result) {
|
|
.ok => code_writer.items,
|
|
.fail => |em| {
|
|
decl.analysis = .codegen_failure;
|
|
try mod.failed_decls.put(mod.gpa, decl_index, em);
|
|
return;
|
|
},
|
|
};
|
|
|
|
// if (wasm.dwarf) |*dwarf| {
|
|
// try dwarf.commitDeclState(
|
|
// mod,
|
|
// decl_index,
|
|
// // Actual value will be written after relocation.
|
|
// // For Wasm, this is the offset relative to the code section
|
|
// // which isn't known until flush().
|
|
// 0,
|
|
// code.len,
|
|
// &decl_state.?,
|
|
// );
|
|
// }
|
|
return wasm.finishUpdateDecl(decl_index, code);
|
|
}
|
|
|
|
// Generate code for the Decl, storing it in memory to be later written to
|
|
// the file on flush().
|
|
pub fn updateDecl(wasm: *Wasm, mod: *Module, decl_index: Module.Decl.Index) !void {
|
|
if (build_options.skip_non_native and builtin.object_format != .wasm) {
|
|
@panic("Attempted to compile for object format that was disabled by build configuration");
|
|
}
|
|
if (build_options.have_llvm) {
|
|
if (wasm.llvm_object) |llvm_object| return llvm_object.updateDecl(mod, decl_index);
|
|
}
|
|
|
|
const tracy = trace(@src());
|
|
defer tracy.end();
|
|
|
|
const decl = mod.declPtr(decl_index);
|
|
if (decl.val.castTag(.function)) |_| {
|
|
return;
|
|
} else if (decl.val.castTag(.extern_fn)) |_| {
|
|
return;
|
|
}
|
|
|
|
const atom_index = try wasm.getOrCreateAtomForDecl(decl_index);
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
atom.clear();
|
|
|
|
if (decl.isExtern()) {
|
|
const variable = decl.getVariable().?;
|
|
const name = mem.sliceTo(decl.name, 0);
|
|
return wasm.addOrUpdateImport(name, atom.sym_index, variable.lib_name, null);
|
|
}
|
|
const val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val;
|
|
|
|
var code_writer = std.ArrayList(u8).init(wasm.base.allocator);
|
|
defer code_writer.deinit();
|
|
|
|
const res = try codegen.generateSymbol(
|
|
&wasm.base,
|
|
decl.srcLoc(),
|
|
.{ .ty = decl.ty, .val = val },
|
|
&code_writer,
|
|
.none,
|
|
.{ .parent_atom_index = atom.sym_index },
|
|
);
|
|
|
|
const code = switch (res) {
|
|
.ok => code_writer.items,
|
|
.fail => |em| {
|
|
decl.analysis = .codegen_failure;
|
|
try mod.failed_decls.put(mod.gpa, decl_index, em);
|
|
return;
|
|
},
|
|
};
|
|
|
|
return wasm.finishUpdateDecl(decl_index, code);
|
|
}
|
|
|
|
pub fn updateDeclLineNumber(wasm: *Wasm, mod: *Module, decl_index: Module.Decl.Index) !void {
|
|
if (wasm.llvm_object) |_| return;
|
|
if (wasm.dwarf) |*dw| {
|
|
const tracy = trace(@src());
|
|
defer tracy.end();
|
|
|
|
const decl = mod.declPtr(decl_index);
|
|
const decl_name = try decl.getFullyQualifiedName(mod);
|
|
defer wasm.base.allocator.free(decl_name);
|
|
|
|
log.debug("updateDeclLineNumber {s}{*}", .{ decl_name, decl });
|
|
try dw.updateDeclLineNumber(mod, decl_index);
|
|
}
|
|
}
|
|
|
|
fn finishUpdateDecl(wasm: *Wasm, decl_index: Module.Decl.Index, code: []const u8) !void {
|
|
const mod = wasm.base.options.module.?;
|
|
const decl = mod.declPtr(decl_index);
|
|
const atom_index = wasm.decls.get(decl_index).?;
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
const symbol = &wasm.symbols.items[atom.sym_index];
|
|
const full_name = try decl.getFullyQualifiedName(mod);
|
|
defer wasm.base.allocator.free(full_name);
|
|
symbol.name = try wasm.string_table.put(wasm.base.allocator, full_name);
|
|
try atom.code.appendSlice(wasm.base.allocator, code);
|
|
try wasm.resolved_symbols.put(wasm.base.allocator, atom.symbolLoc(), {});
|
|
|
|
atom.size = @intCast(u32, code.len);
|
|
if (code.len == 0) return;
|
|
atom.alignment = decl.ty.abiAlignment(wasm.base.options.target);
|
|
}
|
|
|
|
/// From a given symbol location, returns its `wasm.GlobalType`.
|
|
/// Asserts the Symbol represents a global.
|
|
fn getGlobalType(wasm: *const Wasm, loc: SymbolLoc) std.wasm.GlobalType {
|
|
const symbol = loc.getSymbol(wasm);
|
|
assert(symbol.tag == .global);
|
|
const is_undefined = symbol.isUndefined();
|
|
if (loc.file) |file_index| {
|
|
const obj: Object = wasm.objects.items[file_index];
|
|
if (is_undefined) {
|
|
return obj.findImport(.global, symbol.index).kind.global;
|
|
}
|
|
const import_global_count = obj.importedCountByKind(.global);
|
|
return obj.globals[symbol.index - import_global_count].global_type;
|
|
}
|
|
if (is_undefined) {
|
|
return wasm.imports.get(loc).?.kind.global;
|
|
}
|
|
return wasm.wasm_globals.items[symbol.index].global_type;
|
|
}
|
|
|
|
/// From a given symbol location, returns its `wasm.Type`.
|
|
/// Asserts the Symbol represents a function.
|
|
fn getFunctionSignature(wasm: *const Wasm, loc: SymbolLoc) std.wasm.Type {
|
|
const symbol = loc.getSymbol(wasm);
|
|
assert(symbol.tag == .function);
|
|
const is_undefined = symbol.isUndefined();
|
|
if (loc.file) |file_index| {
|
|
const obj: Object = wasm.objects.items[file_index];
|
|
if (is_undefined) {
|
|
const ty_index = obj.findImport(.function, symbol.index).kind.function;
|
|
return obj.func_types[ty_index];
|
|
}
|
|
const import_function_count = obj.importedCountByKind(.function);
|
|
const type_index = obj.functions[symbol.index - import_function_count].type_index;
|
|
return obj.func_types[type_index];
|
|
}
|
|
if (is_undefined) {
|
|
const ty_index = wasm.imports.get(loc).?.kind.function;
|
|
return wasm.func_types.items[ty_index];
|
|
}
|
|
return wasm.func_types.items[wasm.functions.get(.{ .file = loc.file, .index = loc.index }).?.type_index];
|
|
}
|
|
|
|
/// Lowers a constant typed value to a local symbol and atom.
|
|
/// Returns the symbol index of the local
|
|
/// The given `decl` is the parent decl whom owns the constant.
|
|
pub fn lowerUnnamedConst(wasm: *Wasm, tv: TypedValue, decl_index: Module.Decl.Index) !u32 {
|
|
assert(tv.ty.zigTypeTag() != .Fn); // cannot create local symbols for functions
|
|
|
|
const mod = wasm.base.options.module.?;
|
|
const decl = mod.declPtr(decl_index);
|
|
|
|
// Create and initialize a new local symbol and atom
|
|
const atom_index = try wasm.createAtom();
|
|
const parent_atom_index = try wasm.getOrCreateAtomForDecl(decl_index);
|
|
const parent_atom = wasm.getAtomPtr(parent_atom_index);
|
|
const local_index = parent_atom.locals.items.len;
|
|
try parent_atom.locals.append(wasm.base.allocator, atom_index);
|
|
const fqdn = try decl.getFullyQualifiedName(mod);
|
|
defer wasm.base.allocator.free(fqdn);
|
|
const name = try std.fmt.allocPrintZ(wasm.base.allocator, "__unnamed_{s}_{d}", .{ fqdn, local_index });
|
|
defer wasm.base.allocator.free(name);
|
|
var value_bytes = std.ArrayList(u8).init(wasm.base.allocator);
|
|
defer value_bytes.deinit();
|
|
|
|
const code = code: {
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
atom.alignment = tv.ty.abiAlignment(wasm.base.options.target);
|
|
wasm.symbols.items[atom.sym_index] = .{
|
|
.name = try wasm.string_table.put(wasm.base.allocator, name),
|
|
.flags = @enumToInt(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
|
|
.tag = .data,
|
|
.index = undefined,
|
|
.virtual_address = undefined,
|
|
};
|
|
try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, atom.symbolLoc(), {});
|
|
|
|
const result = try codegen.generateSymbol(
|
|
&wasm.base,
|
|
decl.srcLoc(),
|
|
tv,
|
|
&value_bytes,
|
|
.none,
|
|
.{
|
|
.parent_atom_index = atom.sym_index,
|
|
.addend = null,
|
|
},
|
|
);
|
|
break :code switch (result) {
|
|
.ok => value_bytes.items,
|
|
.fail => |em| {
|
|
decl.analysis = .codegen_failure;
|
|
try mod.failed_decls.put(mod.gpa, decl_index, em);
|
|
return error.CodegenFail;
|
|
},
|
|
};
|
|
};
|
|
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
atom.size = @intCast(u32, code.len);
|
|
try atom.code.appendSlice(wasm.base.allocator, code);
|
|
return atom.sym_index;
|
|
}
|
|
|
|
/// Returns the symbol index from a symbol of which its flag is set global,
|
|
/// such as an exported or imported symbol.
|
|
/// If the symbol does not yet exist, creates a new one symbol instead
|
|
/// and then returns the index to it.
|
|
pub fn getGlobalSymbol(wasm: *Wasm, name: []const u8) !u32 {
|
|
const name_index = try wasm.string_table.put(wasm.base.allocator, name);
|
|
const gop = try wasm.globals.getOrPut(wasm.base.allocator, name_index);
|
|
if (gop.found_existing) {
|
|
return gop.value_ptr.*.index;
|
|
}
|
|
|
|
var symbol: Symbol = .{
|
|
.name = name_index,
|
|
.flags = 0,
|
|
.index = undefined, // index to type will be set after merging function symbols
|
|
.tag = .function,
|
|
.virtual_address = undefined,
|
|
};
|
|
symbol.setGlobal(true);
|
|
symbol.setUndefined(true);
|
|
|
|
const sym_index = if (wasm.symbols_free_list.popOrNull()) |index| index else blk: {
|
|
var index = @intCast(u32, wasm.symbols.items.len);
|
|
try wasm.symbols.ensureUnusedCapacity(wasm.base.allocator, 1);
|
|
wasm.symbols.items.len += 1;
|
|
break :blk index;
|
|
};
|
|
wasm.symbols.items[sym_index] = symbol;
|
|
gop.value_ptr.* = .{ .index = sym_index, .file = null };
|
|
try wasm.resolved_symbols.put(wasm.base.allocator, gop.value_ptr.*, {});
|
|
try wasm.undefs.putNoClobber(wasm.base.allocator, name, gop.value_ptr.*);
|
|
return sym_index;
|
|
}
|
|
|
|
/// For a given decl, find the given symbol index's atom, and create a relocation for the type.
|
|
/// Returns the given pointer address
|
|
pub fn getDeclVAddr(
|
|
wasm: *Wasm,
|
|
decl_index: Module.Decl.Index,
|
|
reloc_info: link.File.RelocInfo,
|
|
) !u64 {
|
|
const mod = wasm.base.options.module.?;
|
|
const decl = mod.declPtr(decl_index);
|
|
|
|
const target_atom_index = try wasm.getOrCreateAtomForDecl(decl_index);
|
|
const target_symbol_index = wasm.getAtom(target_atom_index).sym_index;
|
|
|
|
assert(reloc_info.parent_atom_index != 0);
|
|
const atom_index = wasm.symbol_atom.get(.{ .file = null, .index = reloc_info.parent_atom_index }).?;
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
const is_wasm32 = wasm.base.options.target.cpu.arch == .wasm32;
|
|
if (decl.ty.zigTypeTag() == .Fn) {
|
|
assert(reloc_info.addend == 0); // addend not allowed for function relocations
|
|
// We found a function pointer, so add it to our table,
|
|
// as function pointers are not allowed to be stored inside the data section.
|
|
// They are instead stored in a function table which are called by index.
|
|
try wasm.addTableFunction(target_symbol_index);
|
|
try atom.relocs.append(wasm.base.allocator, .{
|
|
.index = target_symbol_index,
|
|
.offset = @intCast(u32, reloc_info.offset),
|
|
.relocation_type = if (is_wasm32) .R_WASM_TABLE_INDEX_I32 else .R_WASM_TABLE_INDEX_I64,
|
|
});
|
|
} else {
|
|
try atom.relocs.append(wasm.base.allocator, .{
|
|
.index = target_symbol_index,
|
|
.offset = @intCast(u32, reloc_info.offset),
|
|
.relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_I32 else .R_WASM_MEMORY_ADDR_I64,
|
|
.addend = @intCast(i32, reloc_info.addend),
|
|
});
|
|
}
|
|
// we do not know the final address at this point,
|
|
// as atom allocation will determine the address and relocations
|
|
// will calculate and rewrite this. Therefore, we simply return the symbol index
|
|
// that was targeted.
|
|
return target_symbol_index;
|
|
}
|
|
|
|
pub fn deleteDeclExport(wasm: *Wasm, decl_index: Module.Decl.Index) void {
|
|
if (wasm.llvm_object) |_| return;
|
|
const atom_index = wasm.decls.get(decl_index) orelse return;
|
|
const sym_index = wasm.getAtom(atom_index).sym_index;
|
|
const loc: SymbolLoc = .{ .file = null, .index = sym_index };
|
|
const symbol = loc.getSymbol(wasm);
|
|
const symbol_name = wasm.string_table.get(symbol.name);
|
|
log.debug("Deleting export for decl '{s}'", .{symbol_name});
|
|
if (wasm.export_names.fetchRemove(loc)) |kv| {
|
|
assert(wasm.globals.remove(kv.value));
|
|
} else {
|
|
assert(wasm.globals.remove(symbol.name));
|
|
}
|
|
}
|
|
|
|
pub fn updateDeclExports(
|
|
wasm: *Wasm,
|
|
mod: *Module,
|
|
decl_index: Module.Decl.Index,
|
|
exports: []const *Module.Export,
|
|
) !void {
|
|
if (build_options.skip_non_native and builtin.object_format != .wasm) {
|
|
@panic("Attempted to compile for object format that was disabled by build configuration");
|
|
}
|
|
if (build_options.have_llvm) {
|
|
if (wasm.llvm_object) |llvm_object| return llvm_object.updateDeclExports(mod, decl_index, exports);
|
|
}
|
|
|
|
const decl = mod.declPtr(decl_index);
|
|
const atom_index = try wasm.getOrCreateAtomForDecl(decl_index);
|
|
const atom = wasm.getAtom(atom_index);
|
|
|
|
for (exports) |exp| {
|
|
if (exp.options.section) |section| {
|
|
try mod.failed_exports.putNoClobber(mod.gpa, exp, try Module.ErrorMsg.create(
|
|
mod.gpa,
|
|
decl.srcLoc(),
|
|
"Unimplemented: ExportOptions.section '{s}'",
|
|
.{section},
|
|
));
|
|
continue;
|
|
}
|
|
|
|
const export_name = try wasm.string_table.put(wasm.base.allocator, exp.options.name);
|
|
if (wasm.globals.getPtr(export_name)) |existing_loc| {
|
|
if (existing_loc.index == atom.sym_index) continue;
|
|
const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;
|
|
|
|
const exp_is_weak = exp.options.linkage == .Internal or exp.options.linkage == .Weak;
|
|
// When both the to-be-exported symbol and the already existing symbol
|
|
// are strong symbols, we have a linker error.
|
|
// In the other case we replace one with the other.
|
|
if (!exp_is_weak and !existing_sym.isWeak()) {
|
|
try mod.failed_exports.put(mod.gpa, exp, try Module.ErrorMsg.create(
|
|
mod.gpa,
|
|
decl.srcLoc(),
|
|
\\LinkError: symbol '{s}' defined multiple times
|
|
\\ first definition in '{s}'
|
|
\\ next definition in '{s}'
|
|
,
|
|
.{ exp.options.name, wasm.name, wasm.name },
|
|
));
|
|
continue;
|
|
} else if (exp_is_weak) {
|
|
continue; // to-be-exported symbol is weak, so we keep the existing symbol
|
|
} else {
|
|
// TODO: Revisit this, why was this needed?
|
|
existing_loc.index = atom.sym_index;
|
|
existing_loc.file = null;
|
|
// exp.link.wasm.sym_index = existing_loc.index;
|
|
}
|
|
}
|
|
|
|
const exported_atom_index = try wasm.getOrCreateAtomForDecl(exp.exported_decl);
|
|
const exported_atom = wasm.getAtom(exported_atom_index);
|
|
const sym_loc = exported_atom.symbolLoc();
|
|
const symbol = sym_loc.getSymbol(wasm);
|
|
switch (exp.options.linkage) {
|
|
.Internal => {
|
|
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
|
|
},
|
|
.Weak => {
|
|
symbol.setFlag(.WASM_SYM_BINDING_WEAK);
|
|
},
|
|
.Strong => {}, // symbols are strong by default
|
|
.LinkOnce => {
|
|
try mod.failed_exports.putNoClobber(mod.gpa, exp, try Module.ErrorMsg.create(
|
|
mod.gpa,
|
|
decl.srcLoc(),
|
|
"Unimplemented: LinkOnce",
|
|
.{},
|
|
));
|
|
continue;
|
|
},
|
|
}
|
|
// Ensure the symbol will be exported using the given name
|
|
if (!mem.eql(u8, exp.options.name, sym_loc.getName(wasm))) {
|
|
try wasm.export_names.put(wasm.base.allocator, sym_loc, export_name);
|
|
}
|
|
|
|
symbol.setGlobal(true);
|
|
symbol.setUndefined(false);
|
|
try wasm.globals.put(
|
|
wasm.base.allocator,
|
|
export_name,
|
|
sym_loc,
|
|
);
|
|
|
|
// if the symbol was previously undefined, remove it as an import
|
|
_ = wasm.imports.remove(sym_loc);
|
|
_ = wasm.undefs.swapRemove(exp.options.name);
|
|
}
|
|
}
|
|
|
|
pub fn freeDecl(wasm: *Wasm, decl_index: Module.Decl.Index) void {
|
|
if (build_options.have_llvm) {
|
|
if (wasm.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index);
|
|
}
|
|
const mod = wasm.base.options.module.?;
|
|
const decl = mod.declPtr(decl_index);
|
|
const atom_index = wasm.decls.get(decl_index).?;
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
wasm.symbols_free_list.append(wasm.base.allocator, atom.sym_index) catch {};
|
|
_ = wasm.decls.remove(decl_index);
|
|
wasm.symbols.items[atom.sym_index].tag = .dead;
|
|
for (atom.locals.items) |local_atom_index| {
|
|
const local_atom = wasm.getAtom(local_atom_index);
|
|
const local_symbol = &wasm.symbols.items[local_atom.sym_index];
|
|
local_symbol.tag = .dead; // also for any local symbol
|
|
wasm.symbols_free_list.append(wasm.base.allocator, local_atom.sym_index) catch {};
|
|
assert(wasm.resolved_symbols.swapRemove(local_atom.symbolLoc()));
|
|
assert(wasm.symbol_atom.remove(local_atom.symbolLoc()));
|
|
}
|
|
|
|
if (decl.isExtern()) {
|
|
_ = wasm.imports.remove(atom.symbolLoc());
|
|
}
|
|
_ = wasm.resolved_symbols.swapRemove(atom.symbolLoc());
|
|
_ = wasm.symbol_atom.remove(atom.symbolLoc());
|
|
|
|
// if (wasm.dwarf) |*dwarf| {
|
|
// dwarf.freeDecl(decl_index);
|
|
// }
|
|
|
|
if (atom.next) |next_atom_index| {
|
|
const next_atom = wasm.getAtomPtr(next_atom_index);
|
|
next_atom.prev = atom.prev;
|
|
atom.next = null;
|
|
}
|
|
if (atom.prev) |prev_index| {
|
|
const prev_atom = wasm.getAtomPtr(prev_index);
|
|
prev_atom.next = atom.next;
|
|
atom.prev = null;
|
|
}
|
|
}
|
|
|
|
/// Appends a new entry to the indirect function table
|
|
pub fn addTableFunction(wasm: *Wasm, symbol_index: u32) !void {
|
|
const index = @intCast(u32, wasm.function_table.count());
|
|
try wasm.function_table.put(wasm.base.allocator, .{ .file = null, .index = symbol_index }, index);
|
|
}
|
|
|
|
/// Assigns indexes to all indirect functions.
|
|
/// Starts at offset 1, where the value `0` represents an unresolved function pointer
|
|
/// or null-pointer
|
|
fn mapFunctionTable(wasm: *Wasm) void {
|
|
var it = wasm.function_table.valueIterator();
|
|
var index: u32 = 1;
|
|
while (it.next()) |value_ptr| : (index += 1) {
|
|
value_ptr.* = index;
|
|
}
|
|
|
|
if (wasm.base.options.import_table or wasm.base.options.output_mode == .Obj) {
|
|
const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?;
|
|
const import = wasm.imports.getPtr(sym_loc).?;
|
|
import.kind.table.limits.min = index - 1; // we start at index 1.
|
|
} else if (index > 1) {
|
|
log.debug("Appending indirect function table", .{});
|
|
const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?;
|
|
const symbol = sym_loc.getSymbol(wasm);
|
|
const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count];
|
|
table.limits = .{ .min = index, .max = index, .flags = 0x1 };
|
|
}
|
|
}
|
|
|
|
/// Either creates a new import, or updates one if existing.
|
|
/// When `type_index` is non-null, we assume an external function.
|
|
/// In all other cases, a data-symbol will be created instead.
|
|
pub fn addOrUpdateImport(
|
|
wasm: *Wasm,
|
|
/// Name of the import
|
|
name: []const u8,
|
|
/// Symbol index that is external
|
|
symbol_index: u32,
|
|
/// Optional library name (i.e. `extern "c" fn foo() void`
|
|
lib_name: ?[*:0]const u8,
|
|
/// The index of the type that represents the function signature
|
|
/// when the extern is a function. When this is null, a data-symbol
|
|
/// is asserted instead.
|
|
type_index: ?u32,
|
|
) !void {
|
|
assert(symbol_index != 0);
|
|
// For the import name, we use the decl's name, rather than the fully qualified name
|
|
// Also mangle the name when the lib name is set and not equal to "C" so imports with the same
|
|
// name but different module can be resolved correctly.
|
|
const mangle_name = lib_name != null and
|
|
!std.mem.eql(u8, std.mem.sliceTo(lib_name.?, 0), "c");
|
|
const full_name = if (mangle_name) full_name: {
|
|
break :full_name try std.fmt.allocPrint(wasm.base.allocator, "{s}|{s}", .{ name, lib_name.? });
|
|
} else name;
|
|
defer if (mangle_name) wasm.base.allocator.free(full_name);
|
|
|
|
const decl_name_index = try wasm.string_table.put(wasm.base.allocator, full_name);
|
|
const symbol: *Symbol = &wasm.symbols.items[symbol_index];
|
|
symbol.setUndefined(true);
|
|
symbol.setGlobal(true);
|
|
symbol.name = decl_name_index;
|
|
if (mangle_name) {
|
|
// we specified a specific name for the symbol that does not match the import name
|
|
symbol.setFlag(.WASM_SYM_EXPLICIT_NAME);
|
|
}
|
|
const global_gop = try wasm.globals.getOrPut(wasm.base.allocator, decl_name_index);
|
|
if (!global_gop.found_existing) {
|
|
const loc: SymbolLoc = .{ .file = null, .index = symbol_index };
|
|
global_gop.value_ptr.* = loc;
|
|
try wasm.resolved_symbols.put(wasm.base.allocator, loc, {});
|
|
try wasm.undefs.putNoClobber(wasm.base.allocator, full_name, loc);
|
|
}
|
|
|
|
if (type_index) |ty_index| {
|
|
const gop = try wasm.imports.getOrPut(wasm.base.allocator, .{ .index = symbol_index, .file = null });
|
|
const module_name = if (lib_name) |l_name| blk: {
|
|
break :blk mem.sliceTo(l_name, 0);
|
|
} else wasm.host_name;
|
|
if (!gop.found_existing) {
|
|
gop.value_ptr.* = .{
|
|
.module_name = try wasm.string_table.put(wasm.base.allocator, module_name),
|
|
.name = try wasm.string_table.put(wasm.base.allocator, name),
|
|
.kind = .{ .function = ty_index },
|
|
};
|
|
}
|
|
} else {
|
|
symbol.tag = .data;
|
|
return; // non-functions will not be imported from the runtime, but only resolved during link-time
|
|
}
|
|
}
|
|
|
|
/// Kind represents the type of an Atom, which is only
|
|
/// used to parse a decl into an Atom to define in which section
|
|
/// or segment it should be placed.
|
|
const Kind = union(enum) {
|
|
/// Represents the segment the data symbol should
|
|
/// be inserted into.
|
|
/// TODO: Add TLS segments
|
|
data: enum {
|
|
read_only,
|
|
uninitialized,
|
|
initialized,
|
|
},
|
|
function: void,
|
|
|
|
/// Returns the segment name the data kind represents.
|
|
/// Asserts `kind` has its active tag set to `data`.
|
|
fn segmentName(kind: Kind) []const u8 {
|
|
switch (kind.data) {
|
|
.read_only => return ".rodata.",
|
|
.uninitialized => return ".bss.",
|
|
.initialized => return ".data.",
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Parses an Atom and inserts its metadata into the corresponding sections.
|
|
fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
const symbol = (SymbolLoc{ .file = null, .index = atom.sym_index }).getSymbol(wasm);
|
|
const final_index: u32 = switch (kind) {
|
|
.function => result: {
|
|
const index = @intCast(u32, wasm.functions.count() + wasm.imported_functions_count);
|
|
const type_index = wasm.atom_types.get(atom_index).?;
|
|
try wasm.functions.putNoClobber(
|
|
wasm.base.allocator,
|
|
.{ .file = null, .index = index },
|
|
.{ .type_index = type_index },
|
|
);
|
|
symbol.tag = .function;
|
|
symbol.index = index;
|
|
|
|
if (wasm.code_section_index == null) {
|
|
wasm.code_section_index = @intCast(u32, wasm.segments.items.len);
|
|
try wasm.segments.append(wasm.base.allocator, .{
|
|
.alignment = atom.alignment,
|
|
.size = atom.size,
|
|
.offset = 0,
|
|
.flags = 0,
|
|
});
|
|
}
|
|
|
|
break :result wasm.code_section_index.?;
|
|
},
|
|
.data => result: {
|
|
const segment_name = try std.mem.concat(wasm.base.allocator, u8, &.{
|
|
kind.segmentName(),
|
|
wasm.string_table.get(symbol.name),
|
|
});
|
|
errdefer wasm.base.allocator.free(segment_name);
|
|
const segment_info: types.Segment = .{
|
|
.name = segment_name,
|
|
.alignment = atom.alignment,
|
|
.flags = 0,
|
|
};
|
|
symbol.tag = .data;
|
|
|
|
// when creating an object file, or importing memory and the data belongs in the .bss segment
|
|
// we set the entire region of it to zeroes.
|
|
// We do not have to do this when exporting the memory (the default) because the runtime
|
|
// will do it for us, and we do not emit the bss segment at all.
|
|
if ((wasm.base.options.output_mode == .Obj or wasm.base.options.import_memory) and kind.data == .uninitialized) {
|
|
std.mem.set(u8, atom.code.items, 0);
|
|
}
|
|
|
|
const should_merge = wasm.base.options.output_mode != .Obj;
|
|
const gop = try wasm.data_segments.getOrPut(wasm.base.allocator, segment_info.outputName(should_merge));
|
|
if (gop.found_existing) {
|
|
const index = gop.value_ptr.*;
|
|
wasm.segments.items[index].size += atom.size;
|
|
|
|
symbol.index = @intCast(u32, wasm.segment_info.getIndex(index).?);
|
|
// segment info already exists, so free its memory
|
|
wasm.base.allocator.free(segment_name);
|
|
break :result index;
|
|
} else {
|
|
const index = @intCast(u32, wasm.segments.items.len);
|
|
var flags: u32 = 0;
|
|
if (wasm.base.options.shared_memory) {
|
|
flags |= @enumToInt(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE);
|
|
}
|
|
try wasm.segments.append(wasm.base.allocator, .{
|
|
.alignment = atom.alignment,
|
|
.size = 0,
|
|
.offset = 0,
|
|
.flags = flags,
|
|
});
|
|
gop.value_ptr.* = index;
|
|
|
|
const info_index = @intCast(u32, wasm.segment_info.count());
|
|
try wasm.segment_info.put(wasm.base.allocator, index, segment_info);
|
|
symbol.index = info_index;
|
|
break :result index;
|
|
}
|
|
},
|
|
};
|
|
|
|
const segment: *Segment = &wasm.segments.items[final_index];
|
|
segment.alignment = std.math.max(segment.alignment, atom.alignment);
|
|
|
|
try wasm.appendAtomAtIndex(final_index, atom_index);
|
|
}
|
|
|
|
/// From a given index, append the given `Atom` at the back of the linked list.
|
|
/// Simply inserts it into the map of atoms when it doesn't exist yet.
|
|
pub fn appendAtomAtIndex(wasm: *Wasm, index: u32, atom_index: Atom.Index) !void {
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
if (wasm.atoms.getPtr(index)) |last_index_ptr| {
|
|
const last = wasm.getAtomPtr(last_index_ptr.*);
|
|
last.*.next = atom_index;
|
|
atom.prev = last_index_ptr.*;
|
|
last_index_ptr.* = atom_index;
|
|
} else {
|
|
try wasm.atoms.putNoClobber(wasm.base.allocator, index, atom_index);
|
|
}
|
|
}
|
|
|
|
/// Allocates debug atoms into their respective debug sections
|
|
/// to merge them with maybe-existing debug atoms from object files.
|
|
fn allocateDebugAtoms(wasm: *Wasm) !void {
|
|
if (wasm.dwarf == null) return;
|
|
|
|
const allocAtom = struct {
|
|
fn f(bin: *Wasm, maybe_index: *?u32, atom_index: Atom.Index) !void {
|
|
const index = maybe_index.* orelse idx: {
|
|
const index = @intCast(u32, bin.segments.items.len);
|
|
try bin.appendDummySegment();
|
|
maybe_index.* = index;
|
|
break :idx index;
|
|
};
|
|
const atom = bin.getAtomPtr(atom_index);
|
|
atom.size = @intCast(u32, atom.code.items.len);
|
|
bin.symbols.items[atom.sym_index].index = index;
|
|
try bin.appendAtomAtIndex(index, atom_index);
|
|
}
|
|
}.f;
|
|
|
|
try allocAtom(wasm, &wasm.debug_info_index, wasm.debug_info_atom.?);
|
|
try allocAtom(wasm, &wasm.debug_line_index, wasm.debug_line_atom.?);
|
|
try allocAtom(wasm, &wasm.debug_loc_index, wasm.debug_loc_atom.?);
|
|
try allocAtom(wasm, &wasm.debug_str_index, wasm.debug_str_atom.?);
|
|
try allocAtom(wasm, &wasm.debug_ranges_index, wasm.debug_ranges_atom.?);
|
|
try allocAtom(wasm, &wasm.debug_abbrev_index, wasm.debug_abbrev_atom.?);
|
|
try allocAtom(wasm, &wasm.debug_pubnames_index, wasm.debug_pubnames_atom.?);
|
|
try allocAtom(wasm, &wasm.debug_pubtypes_index, wasm.debug_pubtypes_atom.?);
|
|
}
|
|
|
|
fn allocateAtoms(wasm: *Wasm) !void {
|
|
// first sort the data segments
|
|
try sortDataSegments(wasm);
|
|
try allocateDebugAtoms(wasm);
|
|
|
|
var it = wasm.atoms.iterator();
|
|
while (it.next()) |entry| {
|
|
const segment = &wasm.segments.items[entry.key_ptr.*];
|
|
var atom_index = entry.value_ptr.*;
|
|
var offset: u32 = 0;
|
|
while (true) {
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
const symbol_loc = atom.symbolLoc();
|
|
if (wasm.code_section_index) |index| {
|
|
if (index == entry.key_ptr.*) {
|
|
if (!wasm.resolved_symbols.contains(symbol_loc)) {
|
|
// only allocate resolved function body's.
|
|
atom_index = atom.prev orelse break;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
offset = std.mem.alignForwardGeneric(u32, offset, atom.alignment);
|
|
atom.offset = offset;
|
|
log.debug("Atom '{s}' allocated from 0x{x:0>8} to 0x{x:0>8} size={d}", .{
|
|
symbol_loc.getName(wasm),
|
|
offset,
|
|
offset + atom.size,
|
|
atom.size,
|
|
});
|
|
offset += atom.size;
|
|
atom_index = atom.prev orelse break;
|
|
}
|
|
segment.size = std.mem.alignForwardGeneric(u32, offset, segment.alignment);
|
|
}
|
|
}
|
|
|
|
/// For each data symbol, sets the virtual address.
|
|
fn allocateVirtualAddresses(wasm: *Wasm) void {
|
|
for (wasm.resolved_symbols.keys()) |loc| {
|
|
const symbol = loc.getSymbol(wasm);
|
|
if (symbol.tag != .data) {
|
|
continue; // only data symbols have virtual addresses
|
|
}
|
|
const atom_index = wasm.symbol_atom.get(loc) orelse {
|
|
// synthetic symbol that does not contain an atom
|
|
continue;
|
|
};
|
|
|
|
const atom = wasm.getAtom(atom_index);
|
|
const merge_segment = wasm.base.options.output_mode != .Obj;
|
|
const segment_info = if (atom.file) |object_index| blk: {
|
|
break :blk wasm.objects.items[object_index].segment_info;
|
|
} else wasm.segment_info.values();
|
|
const segment_name = segment_info[symbol.index].outputName(merge_segment);
|
|
const segment_index = wasm.data_segments.get(segment_name).?;
|
|
const segment = wasm.segments.items[segment_index];
|
|
symbol.virtual_address = atom.offset + segment.offset;
|
|
}
|
|
}
|
|
|
|
fn sortDataSegments(wasm: *Wasm) !void {
|
|
var new_mapping: std.StringArrayHashMapUnmanaged(u32) = .{};
|
|
try new_mapping.ensureUnusedCapacity(wasm.base.allocator, wasm.data_segments.count());
|
|
errdefer new_mapping.deinit(wasm.base.allocator);
|
|
|
|
const keys = try wasm.base.allocator.dupe([]const u8, wasm.data_segments.keys());
|
|
defer wasm.base.allocator.free(keys);
|
|
|
|
const SortContext = struct {
|
|
fn sort(_: void, lhs: []const u8, rhs: []const u8) bool {
|
|
return order(lhs) <= order(rhs);
|
|
}
|
|
|
|
fn order(name: []const u8) u8 {
|
|
if (mem.startsWith(u8, name, ".rodata")) return 0;
|
|
if (mem.startsWith(u8, name, ".data")) return 1;
|
|
if (mem.startsWith(u8, name, ".text")) return 2;
|
|
return 3;
|
|
}
|
|
};
|
|
|
|
std.sort.sort([]const u8, keys, {}, SortContext.sort);
|
|
for (keys) |key| {
|
|
const segment_index = wasm.data_segments.get(key).?;
|
|
new_mapping.putAssumeCapacity(key, segment_index);
|
|
}
|
|
wasm.data_segments.deinit(wasm.base.allocator);
|
|
wasm.data_segments = new_mapping;
|
|
}
|
|
|
|
/// Obtains all initfuncs from each object file, verifies its function signature,
|
|
/// and then appends it to our final `init_funcs` list.
|
|
/// After all functions have been inserted, the functions will be ordered based
|
|
/// on their priority.
|
|
/// NOTE: This function must be called before we merged any other section.
|
|
/// This is because all init funcs in the object files contain references to the
|
|
/// original functions and their types. We need to know the type to verify it doesn't
|
|
/// contain any parameters.
|
|
fn setupInitFunctions(wasm: *Wasm) !void {
|
|
for (wasm.objects.items, 0..) |object, file_index| {
|
|
try wasm.init_funcs.ensureUnusedCapacity(wasm.base.allocator, object.init_funcs.len);
|
|
for (object.init_funcs) |init_func| {
|
|
const symbol = object.symtable[init_func.symbol_index];
|
|
const ty: std.wasm.Type = if (symbol.isUndefined()) ty: {
|
|
const imp: types.Import = object.findImport(.function, symbol.index);
|
|
break :ty object.func_types[imp.kind.function];
|
|
} else ty: {
|
|
const func_index = symbol.index - object.importedCountByKind(.function);
|
|
const func = object.functions[func_index];
|
|
break :ty object.func_types[func.type_index];
|
|
};
|
|
if (ty.params.len != 0) {
|
|
log.err("constructor functions cannot take arguments: '{s}'", .{object.string_table.get(symbol.name)});
|
|
return error.InvalidInitFunc;
|
|
}
|
|
log.debug("appended init func '{s}'\n", .{object.string_table.get(symbol.name)});
|
|
wasm.init_funcs.appendAssumeCapacity(.{
|
|
.index = init_func.symbol_index,
|
|
.file = @intCast(u16, file_index),
|
|
.priority = init_func.priority,
|
|
});
|
|
}
|
|
}
|
|
|
|
// sort the initfunctions based on their priority
|
|
std.sort.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan);
|
|
}
|
|
|
|
/// Creates a function body for the `__wasm_call_ctors` symbol.
|
|
/// Loops over all constructors found in `init_funcs` and calls them
|
|
/// respectively based on their priority which was sorted by `setupInitFunctions`.
|
|
/// NOTE: This function must be called after we merged all sections to ensure the
|
|
/// references to the function stored in the symbol have been finalized so we end
|
|
/// up calling the resolved function.
|
|
fn initializeCallCtorsFunction(wasm: *Wasm) !void {
|
|
// No code to emit, so also no ctors to call
|
|
if (wasm.code_section_index == null) {
|
|
// Make sure to remove it from the resolved symbols so we do not emit
|
|
// it within any section. TODO: Remove this once we implement garbage collection.
|
|
const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?;
|
|
std.debug.assert(wasm.resolved_symbols.swapRemove(loc));
|
|
return;
|
|
}
|
|
|
|
var function_body = std.ArrayList(u8).init(wasm.base.allocator);
|
|
defer function_body.deinit();
|
|
const writer = function_body.writer();
|
|
|
|
// Create the function body
|
|
{
|
|
// Write locals count (we have none)
|
|
try leb.writeULEB128(writer, @as(u32, 0));
|
|
|
|
// call constructors
|
|
for (wasm.init_funcs.items) |init_func_loc| {
|
|
const symbol = init_func_loc.getSymbol(wasm);
|
|
const func = wasm.functions.values()[symbol.index - wasm.imported_functions_count];
|
|
const ty = wasm.func_types.items[func.type_index];
|
|
|
|
// Call function by its function index
|
|
try writer.writeByte(std.wasm.opcode(.call));
|
|
try leb.writeULEB128(writer, symbol.index);
|
|
|
|
// drop all returned values from the stack as __wasm_call_ctors has no return value
|
|
for (ty.returns) |_| {
|
|
try writer.writeByte(std.wasm.opcode(.drop));
|
|
}
|
|
}
|
|
|
|
// End function body
|
|
try writer.writeByte(std.wasm.opcode(.end));
|
|
}
|
|
|
|
const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?;
|
|
const symbol = loc.getSymbol(wasm);
|
|
// create type (() -> nil) as we do not have any parameters or return value.
|
|
const ty_index = try wasm.putOrGetFuncType(.{ .params = &[_]std.wasm.Valtype{}, .returns = &[_]std.wasm.Valtype{} });
|
|
// create function with above type
|
|
const func_index = wasm.imported_functions_count + @intCast(u32, wasm.functions.count());
|
|
try wasm.functions.putNoClobber(
|
|
wasm.base.allocator,
|
|
.{ .file = null, .index = func_index },
|
|
.{ .type_index = ty_index },
|
|
);
|
|
symbol.index = func_index;
|
|
|
|
// create the atom that will be output into the final binary
|
|
const atom_index = @intCast(Atom.Index, wasm.managed_atoms.items.len);
|
|
const atom = try wasm.managed_atoms.addOne(wasm.base.allocator);
|
|
atom.* = .{
|
|
.size = @intCast(u32, function_body.items.len),
|
|
.offset = 0,
|
|
.sym_index = loc.index,
|
|
.file = null,
|
|
.alignment = 1,
|
|
.next = null,
|
|
.prev = null,
|
|
.code = function_body.moveToUnmanaged(),
|
|
};
|
|
try wasm.appendAtomAtIndex(wasm.code_section_index.?, atom_index);
|
|
try wasm.symbol_atom.putNoClobber(wasm.base.allocator, loc, atom_index);
|
|
|
|
// `allocateAtoms` has already been called, set the atom's offset manually.
|
|
// This is fine to do manually as we insert the atom at the very end.
|
|
const prev_atom = wasm.getAtom(atom.prev.?);
|
|
atom.offset = prev_atom.offset + prev_atom.size;
|
|
}
|
|
|
|
fn setupImports(wasm: *Wasm) !void {
|
|
log.debug("Merging imports", .{});
|
|
var discarded_it = wasm.discarded.keyIterator();
|
|
while (discarded_it.next()) |discarded| {
|
|
if (discarded.file == null) {
|
|
// remove an import if it was resolved
|
|
if (wasm.imports.remove(discarded.*)) {
|
|
log.debug("Removed symbol '{s}' as an import", .{
|
|
discarded.getName(wasm),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
for (wasm.resolved_symbols.keys()) |symbol_loc| {
|
|
if (symbol_loc.file == null) {
|
|
// imports generated by Zig code are already in the `import` section
|
|
continue;
|
|
}
|
|
|
|
const symbol = symbol_loc.getSymbol(wasm);
|
|
if (std.mem.eql(u8, symbol_loc.getName(wasm), "__indirect_function_table")) {
|
|
continue;
|
|
}
|
|
if (!symbol.requiresImport()) {
|
|
continue;
|
|
}
|
|
|
|
log.debug("Symbol '{s}' will be imported from the host", .{symbol_loc.getName(wasm)});
|
|
const object = wasm.objects.items[symbol_loc.file.?];
|
|
const import = object.findImport(symbol.tag.externalType(), symbol.index);
|
|
|
|
// We copy the import to a new import to ensure the names contain references
|
|
// to the internal string table, rather than of the object file.
|
|
var new_imp: types.Import = .{
|
|
.module_name = try wasm.string_table.put(wasm.base.allocator, object.string_table.get(import.module_name)),
|
|
.name = try wasm.string_table.put(wasm.base.allocator, object.string_table.get(import.name)),
|
|
.kind = import.kind,
|
|
};
|
|
// TODO: De-duplicate imports when they contain the same names and type
|
|
try wasm.imports.putNoClobber(wasm.base.allocator, symbol_loc, new_imp);
|
|
}
|
|
|
|
// Assign all indexes of the imports to their representing symbols
|
|
var function_index: u32 = 0;
|
|
var global_index: u32 = 0;
|
|
var table_index: u32 = 0;
|
|
var it = wasm.imports.iterator();
|
|
while (it.next()) |entry| {
|
|
const symbol = entry.key_ptr.*.getSymbol(wasm);
|
|
const import: types.Import = entry.value_ptr.*;
|
|
switch (import.kind) {
|
|
.function => {
|
|
symbol.index = function_index;
|
|
function_index += 1;
|
|
},
|
|
.global => {
|
|
symbol.index = global_index;
|
|
global_index += 1;
|
|
},
|
|
.table => {
|
|
symbol.index = table_index;
|
|
table_index += 1;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
wasm.imported_functions_count = function_index;
|
|
wasm.imported_globals_count = global_index;
|
|
wasm.imported_tables_count = table_index;
|
|
|
|
log.debug("Merged ({d}) functions, ({d}) globals, and ({d}) tables into import section", .{
|
|
function_index,
|
|
global_index,
|
|
table_index,
|
|
});
|
|
}
|
|
|
|
/// Takes the global, function and table section from each linked object file
|
|
/// and merges it into a single section for each.
|
|
fn mergeSections(wasm: *Wasm) !void {
|
|
for (wasm.resolved_symbols.keys()) |sym_loc| {
|
|
if (sym_loc.file == null) {
|
|
// Zig code-generated symbols are already within the sections and do not
|
|
// require to be merged
|
|
continue;
|
|
}
|
|
|
|
const object = &wasm.objects.items[sym_loc.file.?];
|
|
const symbol = &object.symtable[sym_loc.index];
|
|
if (symbol.isUndefined() or (symbol.tag != .function and symbol.tag != .global and symbol.tag != .table)) {
|
|
// Skip undefined symbols as they go in the `import` section
|
|
// Also skip symbols that do not need to have a section merged.
|
|
continue;
|
|
}
|
|
|
|
const offset = object.importedCountByKind(symbol.tag.externalType());
|
|
const index = symbol.index - offset;
|
|
switch (symbol.tag) {
|
|
.function => {
|
|
const gop = try wasm.functions.getOrPut(
|
|
wasm.base.allocator,
|
|
.{ .file = sym_loc.file, .index = symbol.index },
|
|
);
|
|
if (!gop.found_existing) {
|
|
gop.value_ptr.* = object.functions[index];
|
|
}
|
|
symbol.index = @intCast(u32, gop.index) + wasm.imported_functions_count;
|
|
},
|
|
.global => {
|
|
const original_global = object.globals[index];
|
|
symbol.index = @intCast(u32, wasm.wasm_globals.items.len) + wasm.imported_globals_count;
|
|
try wasm.wasm_globals.append(wasm.base.allocator, original_global);
|
|
},
|
|
.table => {
|
|
const original_table = object.tables[index];
|
|
symbol.index = @intCast(u32, wasm.tables.items.len) + wasm.imported_tables_count;
|
|
try wasm.tables.append(wasm.base.allocator, original_table);
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
log.debug("Merged ({d}) functions", .{wasm.functions.count()});
|
|
log.debug("Merged ({d}) globals", .{wasm.wasm_globals.items.len});
|
|
log.debug("Merged ({d}) tables", .{wasm.tables.items.len});
|
|
}
|
|
|
|
/// Merges function types of all object files into the final
|
|
/// 'types' section, while assigning the type index to the representing
|
|
/// section (import, export, function).
|
|
fn mergeTypes(wasm: *Wasm) !void {
|
|
// A map to track which functions have already had their
|
|
// type inserted. If we do this for the same function multiple times,
|
|
// it will be overwritten with the incorrect type.
|
|
var dirty = std.AutoHashMap(u32, void).init(wasm.base.allocator);
|
|
try dirty.ensureUnusedCapacity(@intCast(u32, wasm.functions.count()));
|
|
defer dirty.deinit();
|
|
|
|
for (wasm.resolved_symbols.keys()) |sym_loc| {
|
|
if (sym_loc.file == null) {
|
|
// zig code-generated symbols are already present in final type section
|
|
continue;
|
|
}
|
|
const object = wasm.objects.items[sym_loc.file.?];
|
|
const symbol = object.symtable[sym_loc.index];
|
|
if (symbol.tag != .function) {
|
|
// Only functions have types
|
|
continue;
|
|
}
|
|
|
|
if (symbol.isUndefined()) {
|
|
log.debug("Adding type from extern function '{s}'", .{sym_loc.getName(wasm)});
|
|
const import: *types.Import = wasm.imports.getPtr(sym_loc) orelse continue;
|
|
const original_type = object.func_types[import.kind.function];
|
|
import.kind.function = try wasm.putOrGetFuncType(original_type);
|
|
} else if (!dirty.contains(symbol.index)) {
|
|
log.debug("Adding type from function '{s}'", .{sym_loc.getName(wasm)});
|
|
const func = &wasm.functions.values()[symbol.index - wasm.imported_functions_count];
|
|
func.type_index = try wasm.putOrGetFuncType(object.func_types[func.type_index]);
|
|
dirty.putAssumeCapacityNoClobber(symbol.index, {});
|
|
}
|
|
}
|
|
log.debug("Completed merging and deduplicating types. Total count: ({d})", .{wasm.func_types.items.len});
|
|
}
|
|
|
|
fn setupExports(wasm: *Wasm) !void {
|
|
if (wasm.base.options.output_mode == .Obj) return;
|
|
log.debug("Building exports from symbols", .{});
|
|
|
|
const force_exp_names = wasm.base.options.export_symbol_names;
|
|
if (force_exp_names.len > 0) {
|
|
var failed_exports = false;
|
|
|
|
for (force_exp_names) |exp_name| {
|
|
const loc = wasm.findGlobalSymbol(exp_name) orelse {
|
|
log.err("could not export '{s}', symbol not found", .{exp_name});
|
|
failed_exports = true;
|
|
continue;
|
|
};
|
|
|
|
const symbol = loc.getSymbol(wasm);
|
|
symbol.setFlag(.WASM_SYM_EXPORTED);
|
|
}
|
|
|
|
if (failed_exports) {
|
|
return error.MissingSymbol;
|
|
}
|
|
}
|
|
|
|
for (wasm.resolved_symbols.keys()) |sym_loc| {
|
|
const symbol = sym_loc.getSymbol(wasm);
|
|
if (!symbol.isExported(wasm.base.options.rdynamic)) continue;
|
|
|
|
const sym_name = sym_loc.getName(wasm);
|
|
const export_name = if (wasm.export_names.get(sym_loc)) |name| name else blk: {
|
|
if (sym_loc.file == null) break :blk symbol.name;
|
|
break :blk try wasm.string_table.put(wasm.base.allocator, sym_name);
|
|
};
|
|
const exp: types.Export = if (symbol.tag == .data) exp: {
|
|
const global_index = @intCast(u32, wasm.imported_globals_count + wasm.wasm_globals.items.len);
|
|
try wasm.wasm_globals.append(wasm.base.allocator, .{
|
|
.global_type = .{ .valtype = .i32, .mutable = false },
|
|
.init = .{ .i32_const = @intCast(i32, symbol.virtual_address) },
|
|
});
|
|
break :exp .{
|
|
.name = export_name,
|
|
.kind = .global,
|
|
.index = global_index,
|
|
};
|
|
} else .{
|
|
.name = export_name,
|
|
.kind = symbol.tag.externalType(),
|
|
.index = symbol.index,
|
|
};
|
|
log.debug("Exporting symbol '{s}' as '{s}' at index: ({d})", .{
|
|
sym_name,
|
|
wasm.string_table.get(exp.name),
|
|
exp.index,
|
|
});
|
|
try wasm.exports.append(wasm.base.allocator, exp);
|
|
}
|
|
|
|
log.debug("Completed building exports. Total count: ({d})", .{wasm.exports.items.len});
|
|
}
|
|
|
|
fn setupStart(wasm: *Wasm) !void {
|
|
const entry_name = wasm.base.options.entry orelse "_start";
|
|
|
|
const symbol_loc = wasm.findGlobalSymbol(entry_name) orelse {
|
|
if (wasm.base.options.output_mode == .Exe) {
|
|
if (wasm.base.options.wasi_exec_model == .reactor) return; // Not required for reactors
|
|
} else {
|
|
return; // No entry point needed for non-executable wasm files
|
|
}
|
|
log.err("Entry symbol '{s}' missing", .{entry_name});
|
|
return error.MissingSymbol;
|
|
};
|
|
|
|
const symbol = symbol_loc.getSymbol(wasm);
|
|
if (symbol.tag != .function) {
|
|
log.err("Entry symbol '{s}' is not a function", .{entry_name});
|
|
return error.InvalidEntryKind;
|
|
}
|
|
|
|
// Ensure the symbol is exported so host environment can access it
|
|
if (wasm.base.options.output_mode != .Obj) {
|
|
symbol.setFlag(.WASM_SYM_EXPORTED);
|
|
}
|
|
}
|
|
|
|
/// Sets up the memory section of the wasm module, as well as the stack.
|
|
fn setupMemory(wasm: *Wasm) !void {
|
|
log.debug("Setting up memory layout", .{});
|
|
const page_size = std.wasm.page_size; // 64kb
|
|
// Use the user-provided stack size or else we use 1MB by default
|
|
const stack_size = wasm.base.options.stack_size_override orelse page_size * 16;
|
|
const stack_alignment = 16; // wasm's stack alignment as specified by tool-convention
|
|
const heap_alignment = 16; // wasm's heap alignment as specified by tool-convention
|
|
|
|
// Always place the stack at the start by default
|
|
// unless the user specified the global-base flag
|
|
var place_stack_first = true;
|
|
var memory_ptr: u64 = if (wasm.base.options.global_base) |base| blk: {
|
|
place_stack_first = false;
|
|
break :blk base;
|
|
} else 0;
|
|
|
|
const is_obj = wasm.base.options.output_mode == .Obj;
|
|
|
|
if (place_stack_first and !is_obj) {
|
|
memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, stack_alignment);
|
|
memory_ptr += stack_size;
|
|
// We always put the stack pointer global at index 0
|
|
wasm.wasm_globals.items[0].init.i32_const = @bitCast(i32, @intCast(u32, memory_ptr));
|
|
}
|
|
|
|
var offset: u32 = @intCast(u32, memory_ptr);
|
|
var data_seg_it = wasm.data_segments.iterator();
|
|
while (data_seg_it.next()) |entry| {
|
|
const segment = &wasm.segments.items[entry.value_ptr.*];
|
|
memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, segment.alignment);
|
|
|
|
// set TLS-related symbols
|
|
if (mem.eql(u8, entry.key_ptr.*, ".tdata")) {
|
|
if (wasm.findGlobalSymbol("__tls_base")) |loc| {
|
|
const sym = loc.getSymbol(wasm);
|
|
sym.index = @intCast(u32, wasm.wasm_globals.items.len) + wasm.imported_globals_count;
|
|
try wasm.wasm_globals.append(wasm.base.allocator, .{
|
|
.global_type = .{ .valtype = .i32, .mutable = false },
|
|
.init = .{ .i32_const = @intCast(i32, memory_ptr) },
|
|
});
|
|
}
|
|
}
|
|
|
|
memory_ptr += segment.size;
|
|
segment.offset = offset;
|
|
offset += segment.size;
|
|
}
|
|
|
|
if (!place_stack_first and !is_obj) {
|
|
memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, stack_alignment);
|
|
memory_ptr += stack_size;
|
|
wasm.wasm_globals.items[0].init.i32_const = @bitCast(i32, @intCast(u32, memory_ptr));
|
|
}
|
|
|
|
// One of the linked object files has a reference to the __heap_base symbol.
|
|
// We must set its virtual address so it can be used in relocations.
|
|
if (wasm.findGlobalSymbol("__heap_base")) |loc| {
|
|
const symbol = loc.getSymbol(wasm);
|
|
symbol.virtual_address = @intCast(u32, mem.alignForwardGeneric(u64, memory_ptr, heap_alignment));
|
|
}
|
|
|
|
// Setup the max amount of pages
|
|
// For now we only support wasm32 by setting the maximum allowed memory size 2^32-1
|
|
const max_memory_allowed: u64 = (1 << 32) - 1;
|
|
|
|
if (wasm.base.options.initial_memory) |initial_memory| {
|
|
if (!std.mem.isAlignedGeneric(u64, initial_memory, page_size)) {
|
|
log.err("Initial memory must be {d}-byte aligned", .{page_size});
|
|
return error.MissAlignment;
|
|
}
|
|
if (memory_ptr > initial_memory) {
|
|
log.err("Initial memory too small, must be at least {d} bytes", .{memory_ptr});
|
|
return error.MemoryTooSmall;
|
|
}
|
|
if (initial_memory > max_memory_allowed) {
|
|
log.err("Initial memory exceeds maximum memory {d}", .{max_memory_allowed});
|
|
return error.MemoryTooBig;
|
|
}
|
|
memory_ptr = initial_memory;
|
|
}
|
|
memory_ptr = mem.alignForwardGeneric(u64, memory_ptr, std.wasm.page_size);
|
|
// In case we do not import memory, but define it ourselves,
|
|
// set the minimum amount of pages on the memory section.
|
|
wasm.memories.limits.min = @intCast(u32, memory_ptr / page_size);
|
|
log.debug("Total memory pages: {d}", .{wasm.memories.limits.min});
|
|
|
|
if (wasm.findGlobalSymbol("__heap_end")) |loc| {
|
|
const symbol = loc.getSymbol(wasm);
|
|
symbol.virtual_address = @intCast(u32, memory_ptr);
|
|
}
|
|
|
|
if (wasm.base.options.max_memory) |max_memory| {
|
|
if (!std.mem.isAlignedGeneric(u64, max_memory, page_size)) {
|
|
log.err("Maximum memory must be {d}-byte aligned", .{page_size});
|
|
return error.MissAlignment;
|
|
}
|
|
if (memory_ptr > max_memory) {
|
|
log.err("Maxmimum memory too small, must be at least {d} bytes", .{memory_ptr});
|
|
return error.MemoryTooSmall;
|
|
}
|
|
if (max_memory > max_memory_allowed) {
|
|
log.err("Maximum memory exceeds maxmium amount {d}", .{max_memory_allowed});
|
|
return error.MemoryTooBig;
|
|
}
|
|
wasm.memories.limits.max = @intCast(u32, max_memory / page_size);
|
|
wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_HAS_MAX);
|
|
if (wasm.base.options.shared_memory) {
|
|
wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_IS_SHARED);
|
|
}
|
|
log.debug("Maximum memory pages: {?d}", .{wasm.memories.limits.max});
|
|
}
|
|
}
|
|
|
|
/// From a given object's index and the index of the segment, returns the corresponding
|
|
/// index of the segment within the final data section. When the segment does not yet
|
|
/// exist, a new one will be initialized and appended. The new index will be returned in that case.
|
|
pub fn getMatchingSegment(wasm: *Wasm, object_index: u16, relocatable_index: u32) !?u32 {
|
|
const object: Object = wasm.objects.items[object_index];
|
|
const relocatable_data = object.relocatable_data[relocatable_index];
|
|
const index = @intCast(u32, wasm.segments.items.len);
|
|
|
|
switch (relocatable_data.type) {
|
|
.data => {
|
|
const segment_info = object.segment_info[relocatable_data.index];
|
|
const merge_segment = wasm.base.options.output_mode != .Obj;
|
|
const result = try wasm.data_segments.getOrPut(wasm.base.allocator, segment_info.outputName(merge_segment));
|
|
if (!result.found_existing) {
|
|
result.value_ptr.* = index;
|
|
var flags: u32 = 0;
|
|
if (wasm.base.options.shared_memory) {
|
|
flags |= @enumToInt(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE);
|
|
}
|
|
try wasm.segments.append(wasm.base.allocator, .{
|
|
.alignment = 1,
|
|
.size = 0,
|
|
.offset = 0,
|
|
.flags = flags,
|
|
});
|
|
return index;
|
|
} else return result.value_ptr.*;
|
|
},
|
|
.code => return wasm.code_section_index orelse blk: {
|
|
wasm.code_section_index = index;
|
|
try wasm.appendDummySegment();
|
|
break :blk index;
|
|
},
|
|
.debug => {
|
|
const debug_name = object.getDebugName(relocatable_data);
|
|
if (mem.eql(u8, debug_name, ".debug_info")) {
|
|
return wasm.debug_info_index orelse blk: {
|
|
wasm.debug_info_index = index;
|
|
try wasm.appendDummySegment();
|
|
break :blk index;
|
|
};
|
|
} else if (mem.eql(u8, debug_name, ".debug_line")) {
|
|
return wasm.debug_line_index orelse blk: {
|
|
wasm.debug_line_index = index;
|
|
try wasm.appendDummySegment();
|
|
break :blk index;
|
|
};
|
|
} else if (mem.eql(u8, debug_name, ".debug_loc")) {
|
|
return wasm.debug_loc_index orelse blk: {
|
|
wasm.debug_loc_index = index;
|
|
try wasm.appendDummySegment();
|
|
break :blk index;
|
|
};
|
|
} else if (mem.eql(u8, debug_name, ".debug_ranges")) {
|
|
return wasm.debug_line_index orelse blk: {
|
|
wasm.debug_ranges_index = index;
|
|
try wasm.appendDummySegment();
|
|
break :blk index;
|
|
};
|
|
} else if (mem.eql(u8, debug_name, ".debug_pubnames")) {
|
|
return wasm.debug_pubnames_index orelse blk: {
|
|
wasm.debug_pubnames_index = index;
|
|
try wasm.appendDummySegment();
|
|
break :blk index;
|
|
};
|
|
} else if (mem.eql(u8, debug_name, ".debug_pubtypes")) {
|
|
return wasm.debug_pubtypes_index orelse blk: {
|
|
wasm.debug_pubtypes_index = index;
|
|
try wasm.appendDummySegment();
|
|
break :blk index;
|
|
};
|
|
} else if (mem.eql(u8, debug_name, ".debug_abbrev")) {
|
|
return wasm.debug_abbrev_index orelse blk: {
|
|
wasm.debug_abbrev_index = index;
|
|
try wasm.appendDummySegment();
|
|
break :blk index;
|
|
};
|
|
} else if (mem.eql(u8, debug_name, ".debug_str")) {
|
|
return wasm.debug_str_index orelse blk: {
|
|
wasm.debug_str_index = index;
|
|
try wasm.appendDummySegment();
|
|
break :blk index;
|
|
};
|
|
} else {
|
|
log.warn("found unknown debug section '{s}'", .{debug_name});
|
|
log.warn(" debug section will be skipped", .{});
|
|
return null;
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Appends a new segment with default field values
|
|
fn appendDummySegment(wasm: *Wasm) !void {
|
|
try wasm.segments.append(wasm.base.allocator, .{
|
|
.alignment = 1,
|
|
.size = 0,
|
|
.offset = 0,
|
|
.flags = 0,
|
|
});
|
|
}
|
|
|
|
/// Returns the symbol index of the error name table.
|
|
///
|
|
/// When the symbol does not yet exist, it will create a new one instead.
|
|
pub fn getErrorTableSymbol(wasm: *Wasm) !u32 {
|
|
if (wasm.error_table_symbol) |symbol| {
|
|
return symbol;
|
|
}
|
|
|
|
// no error was referenced yet, so create a new symbol and atom for it
|
|
// and then return said symbol's index. The final table will be populated
|
|
// during `flush` when we know all possible error names.
|
|
|
|
const atom_index = try wasm.createAtom();
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
const slice_ty = Type.initTag(.const_slice_u8_sentinel_0);
|
|
atom.alignment = slice_ty.abiAlignment(wasm.base.options.target);
|
|
const sym_index = atom.sym_index;
|
|
|
|
const sym_name = try wasm.string_table.put(wasm.base.allocator, "__zig_err_name_table");
|
|
const symbol = &wasm.symbols.items[sym_index];
|
|
symbol.* = .{
|
|
.name = sym_name,
|
|
.tag = .data,
|
|
.flags = 0,
|
|
.index = 0,
|
|
.virtual_address = undefined,
|
|
};
|
|
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
|
|
|
|
try wasm.resolved_symbols.put(wasm.base.allocator, atom.symbolLoc(), {});
|
|
|
|
log.debug("Error name table was created with symbol index: ({d})", .{sym_index});
|
|
wasm.error_table_symbol = sym_index;
|
|
return sym_index;
|
|
}
|
|
|
|
/// Populates the error name table, when `error_table_symbol` is not null.
|
|
///
|
|
/// This creates a table that consists of pointers and length to each error name.
|
|
/// The table is what is being pointed to within the runtime bodies that are generated.
|
|
fn populateErrorNameTable(wasm: *Wasm) !void {
|
|
const symbol_index = wasm.error_table_symbol orelse return;
|
|
const atom_index = wasm.symbol_atom.get(.{ .file = null, .index = symbol_index }).?;
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
|
|
// Rather than creating a symbol for each individual error name,
|
|
// we create a symbol for the entire region of error names. We then calculate
|
|
// the pointers into the list using addends which are appended to the relocation.
|
|
const names_atom_index = try wasm.createAtom();
|
|
const names_atom = wasm.getAtomPtr(names_atom_index);
|
|
names_atom.alignment = 1;
|
|
const sym_name = try wasm.string_table.put(wasm.base.allocator, "__zig_err_names");
|
|
const names_symbol = &wasm.symbols.items[names_atom.sym_index];
|
|
names_symbol.* = .{
|
|
.name = sym_name,
|
|
.tag = .data,
|
|
.flags = 0,
|
|
.index = 0,
|
|
.virtual_address = undefined,
|
|
};
|
|
names_symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
|
|
|
|
log.debug("Populating error names", .{});
|
|
|
|
// Addend for each relocation to the table
|
|
var addend: u32 = 0;
|
|
const mod = wasm.base.options.module.?;
|
|
for (mod.error_name_list.items) |error_name| {
|
|
const len = @intCast(u32, error_name.len + 1); // names are 0-termianted
|
|
|
|
const slice_ty = Type.initTag(.const_slice_u8_sentinel_0);
|
|
const offset = @intCast(u32, atom.code.items.len);
|
|
// first we create the data for the slice of the name
|
|
try atom.code.appendNTimes(wasm.base.allocator, 0, 4); // ptr to name, will be relocated
|
|
try atom.code.writer(wasm.base.allocator).writeIntLittle(u32, len - 1);
|
|
// create relocation to the error name
|
|
try atom.relocs.append(wasm.base.allocator, .{
|
|
.index = names_atom.sym_index,
|
|
.relocation_type = .R_WASM_MEMORY_ADDR_I32,
|
|
.offset = offset,
|
|
.addend = @intCast(i32, addend),
|
|
});
|
|
atom.size += @intCast(u32, slice_ty.abiSize(wasm.base.options.target));
|
|
addend += len;
|
|
|
|
// as we updated the error name table, we now store the actual name within the names atom
|
|
try names_atom.code.ensureUnusedCapacity(wasm.base.allocator, len);
|
|
names_atom.code.appendSliceAssumeCapacity(error_name);
|
|
names_atom.code.appendAssumeCapacity(0);
|
|
|
|
log.debug("Populated error name: '{s}'", .{error_name});
|
|
}
|
|
names_atom.size = addend;
|
|
|
|
const name_loc = names_atom.symbolLoc();
|
|
try wasm.resolved_symbols.put(wasm.base.allocator, name_loc, {});
|
|
try wasm.symbol_atom.put(wasm.base.allocator, name_loc, names_atom_index);
|
|
|
|
// link the atoms with the rest of the binary so they can be allocated
|
|
// and relocations will be performed.
|
|
try wasm.parseAtom(atom_index, .{ .data = .read_only });
|
|
try wasm.parseAtom(names_atom_index, .{ .data = .read_only });
|
|
}
|
|
|
|
/// From a given index variable, creates a new debug section.
|
|
/// This initializes the index, appends a new segment,
|
|
/// and finally, creates a managed `Atom`.
|
|
pub fn createDebugSectionForIndex(wasm: *Wasm, index: *?u32, name: []const u8) !Atom.Index {
|
|
const new_index = @intCast(u32, wasm.segments.items.len);
|
|
index.* = new_index;
|
|
try wasm.appendDummySegment();
|
|
|
|
const atom_index = try wasm.createAtom();
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
wasm.symbols.items[atom.sym_index] = .{
|
|
.tag = .section,
|
|
.name = try wasm.string_table.put(wasm.base.allocator, name),
|
|
.index = 0,
|
|
.flags = @enumToInt(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
|
|
};
|
|
|
|
atom.alignment = 1; // debug sections are always 1-byte-aligned
|
|
return atom_index;
|
|
}
|
|
|
|
fn resetState(wasm: *Wasm) void {
|
|
for (wasm.segment_info.values()) |segment_info| {
|
|
wasm.base.allocator.free(segment_info.name);
|
|
}
|
|
|
|
var atom_it = wasm.decls.valueIterator();
|
|
while (atom_it.next()) |atom_index| {
|
|
const atom = wasm.getAtomPtr(atom_index.*);
|
|
atom.next = null;
|
|
atom.prev = null;
|
|
|
|
for (atom.locals.items) |local_atom_index| {
|
|
const local_atom = wasm.getAtomPtr(local_atom_index);
|
|
local_atom.next = null;
|
|
local_atom.prev = null;
|
|
}
|
|
}
|
|
|
|
wasm.functions.clearRetainingCapacity();
|
|
wasm.exports.clearRetainingCapacity();
|
|
wasm.segments.clearRetainingCapacity();
|
|
wasm.segment_info.clearRetainingCapacity();
|
|
wasm.data_segments.clearRetainingCapacity();
|
|
wasm.atoms.clearRetainingCapacity();
|
|
wasm.symbol_atom.clearRetainingCapacity();
|
|
wasm.code_section_index = null;
|
|
wasm.debug_info_index = null;
|
|
wasm.debug_line_index = null;
|
|
wasm.debug_loc_index = null;
|
|
wasm.debug_str_index = null;
|
|
wasm.debug_ranges_index = null;
|
|
wasm.debug_abbrev_index = null;
|
|
wasm.debug_pubnames_index = null;
|
|
wasm.debug_pubtypes_index = null;
|
|
}
|
|
|
|
pub fn flush(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
|
|
if (wasm.base.options.emit == null) {
|
|
if (build_options.have_llvm) {
|
|
if (wasm.llvm_object) |llvm_object| {
|
|
return try llvm_object.flushModule(comp, prog_node);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (build_options.have_llvm and wasm.base.options.use_lld) {
|
|
return wasm.linkWithLLD(comp, prog_node);
|
|
} else if (build_options.have_llvm and wasm.base.options.use_llvm and !wasm.base.options.use_lld) {
|
|
return wasm.linkWithZld(comp, prog_node);
|
|
} else {
|
|
return wasm.flushModule(comp, prog_node);
|
|
}
|
|
}
|
|
|
|
/// Uses the in-house linker to link one or multiple object -and archive files into a WebAssembly binary.
|
|
fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
|
|
const tracy = trace(@src());
|
|
defer tracy.end();
|
|
|
|
const gpa = wasm.base.allocator;
|
|
const options = wasm.base.options;
|
|
|
|
// Used for all temporary memory allocated during flushin
|
|
var arena_instance = std.heap.ArenaAllocator.init(gpa);
|
|
defer arena_instance.deinit();
|
|
const arena = arena_instance.allocator();
|
|
|
|
const directory = options.emit.?.directory; // Just an alias to make it shorter to type.
|
|
const full_out_path = try directory.join(arena, &[_][]const u8{options.emit.?.sub_path});
|
|
|
|
// If there is no Zig code to compile, then we should skip flushing the output file because it
|
|
// will not be part of the linker line anyway.
|
|
const module_obj_path: ?[]const u8 = if (options.module != null) blk: {
|
|
assert(options.use_llvm); // `linkWithZld` should never be called when the Wasm backend is used
|
|
try wasm.flushModule(comp, prog_node);
|
|
|
|
if (fs.path.dirname(full_out_path)) |dirname| {
|
|
break :blk try fs.path.join(arena, &.{ dirname, wasm.base.intermediary_basename.? });
|
|
} else {
|
|
break :blk wasm.base.intermediary_basename.?;
|
|
}
|
|
} else null;
|
|
|
|
var sub_prog_node = prog_node.start("Wasm Flush", 0);
|
|
sub_prog_node.activate();
|
|
defer sub_prog_node.end();
|
|
|
|
const is_obj = options.output_mode == .Obj;
|
|
const compiler_rt_path: ?[]const u8 = if (options.include_compiler_rt and !is_obj)
|
|
comp.compiler_rt_lib.?.full_object_path
|
|
else
|
|
null;
|
|
const id_symlink_basename = "zld.id";
|
|
|
|
var man: Cache.Manifest = undefined;
|
|
defer if (!options.disable_lld_caching) man.deinit();
|
|
var digest: [Cache.hex_digest_len]u8 = undefined;
|
|
|
|
// NOTE: The following section must be maintained to be equal
|
|
// as the section defined in `linkWithLLD`
|
|
if (!options.disable_lld_caching) {
|
|
man = comp.cache_parent.obtain();
|
|
|
|
// We are about to obtain this lock, so here we give other processes a chance first.
|
|
wasm.base.releaseLock();
|
|
|
|
comptime assert(Compilation.link_hash_implementation_version == 7);
|
|
|
|
for (options.objects) |obj| {
|
|
_ = try man.addFile(obj.path, null);
|
|
man.hash.add(obj.must_link);
|
|
}
|
|
for (comp.c_object_table.keys()) |key| {
|
|
_ = try man.addFile(key.status.success.object_path, null);
|
|
}
|
|
try man.addOptionalFile(module_obj_path);
|
|
try man.addOptionalFile(compiler_rt_path);
|
|
man.hash.addOptionalBytes(options.entry);
|
|
man.hash.addOptional(options.stack_size_override);
|
|
man.hash.add(wasm.base.options.build_id);
|
|
man.hash.add(options.import_memory);
|
|
man.hash.add(options.import_table);
|
|
man.hash.add(options.export_table);
|
|
man.hash.addOptional(options.initial_memory);
|
|
man.hash.addOptional(options.max_memory);
|
|
man.hash.add(options.shared_memory);
|
|
man.hash.addOptional(options.global_base);
|
|
man.hash.add(options.export_symbol_names.len);
|
|
// strip does not need to go into the linker hash because it is part of the hash namespace
|
|
for (options.export_symbol_names) |symbol_name| {
|
|
man.hash.addBytes(symbol_name);
|
|
}
|
|
|
|
// We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
|
|
_ = try man.hit();
|
|
digest = man.final();
|
|
|
|
var prev_digest_buf: [digest.len]u8 = undefined;
|
|
const prev_digest: []u8 = Cache.readSmallFile(
|
|
directory.handle,
|
|
id_symlink_basename,
|
|
&prev_digest_buf,
|
|
) catch |err| blk: {
|
|
log.debug("WASM LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) });
|
|
// Handle this as a cache miss.
|
|
break :blk prev_digest_buf[0..0];
|
|
};
|
|
if (mem.eql(u8, prev_digest, &digest)) {
|
|
log.debug("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)});
|
|
// Hot diggity dog! The output binary is already there.
|
|
wasm.base.lock = man.toOwnedLock();
|
|
return;
|
|
}
|
|
log.debug("WASM LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) });
|
|
|
|
// We are about to change the output file to be different, so we invalidate the build hash now.
|
|
directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
|
|
error.FileNotFound => {},
|
|
else => |e| return e,
|
|
};
|
|
}
|
|
|
|
// Positional arguments to the linker such as object files and static archives.
|
|
var positionals = std.ArrayList([]const u8).init(arena);
|
|
try positionals.ensureUnusedCapacity(options.objects.len);
|
|
|
|
// When the target os is WASI, we allow linking with WASI-LIBC
|
|
if (options.target.os.tag == .wasi) {
|
|
const is_exe_or_dyn_lib = wasm.base.options.output_mode == .Exe or
|
|
(wasm.base.options.output_mode == .Lib and wasm.base.options.link_mode == .Dynamic);
|
|
if (is_exe_or_dyn_lib) {
|
|
const wasi_emulated_libs = wasm.base.options.wasi_emulated_libs;
|
|
for (wasi_emulated_libs) |crt_file| {
|
|
try positionals.append(try comp.get_libc_crt_file(
|
|
arena,
|
|
wasi_libc.emulatedLibCRFileLibName(crt_file),
|
|
));
|
|
}
|
|
|
|
if (wasm.base.options.link_libc) {
|
|
try positionals.append(try comp.get_libc_crt_file(
|
|
arena,
|
|
wasi_libc.execModelCrtFileFullName(wasm.base.options.wasi_exec_model),
|
|
));
|
|
try positionals.append(try comp.get_libc_crt_file(arena, "libc.a"));
|
|
}
|
|
|
|
if (wasm.base.options.link_libcpp) {
|
|
try positionals.append(comp.libcxx_static_lib.?.full_object_path);
|
|
try positionals.append(comp.libcxxabi_static_lib.?.full_object_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (module_obj_path) |path| {
|
|
try positionals.append(path);
|
|
}
|
|
|
|
for (options.objects) |object| {
|
|
try positionals.append(object.path);
|
|
}
|
|
|
|
for (comp.c_object_table.keys()) |c_object| {
|
|
try positionals.append(c_object.status.success.object_path);
|
|
}
|
|
|
|
if (comp.compiler_rt_lib) |lib| {
|
|
try positionals.append(lib.full_object_path);
|
|
}
|
|
|
|
try wasm.parseInputFiles(positionals.items);
|
|
|
|
for (wasm.objects.items, 0..) |_, object_index| {
|
|
try wasm.resolveSymbolsInObject(@intCast(u16, object_index));
|
|
}
|
|
|
|
var emit_features_count: u32 = 0;
|
|
var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
|
|
try wasm.validateFeatures(&enabled_features, &emit_features_count);
|
|
try wasm.resolveSymbolsInArchives();
|
|
try wasm.resolveLazySymbols();
|
|
try wasm.checkUndefinedSymbols();
|
|
|
|
try wasm.setupInitFunctions();
|
|
try wasm.setupStart();
|
|
try wasm.setupImports();
|
|
|
|
for (wasm.objects.items, 0..) |*object, object_index| {
|
|
try object.parseIntoAtoms(gpa, @intCast(u16, object_index), wasm);
|
|
}
|
|
|
|
try wasm.allocateAtoms();
|
|
try wasm.setupMemory();
|
|
wasm.allocateVirtualAddresses();
|
|
wasm.mapFunctionTable();
|
|
try wasm.mergeSections();
|
|
try wasm.mergeTypes();
|
|
try wasm.initializeCallCtorsFunction();
|
|
try wasm.setupExports();
|
|
try wasm.writeToFile(enabled_features, emit_features_count, arena);
|
|
|
|
if (!wasm.base.options.disable_lld_caching) {
|
|
// Update the file with the digest. If it fails we can continue; it only
|
|
// means that the next invocation will have an unnecessary cache miss.
|
|
Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
|
|
log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)});
|
|
};
|
|
// Again failure here only means an unnecessary cache miss.
|
|
man.writeManifest() catch |err| {
|
|
log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)});
|
|
};
|
|
// We hang on to this lock so that the output file path can be used without
|
|
// other processes clobbering it.
|
|
wasm.base.lock = man.toOwnedLock();
|
|
}
|
|
}
|
|
|
|
pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
|
|
const tracy = trace(@src());
|
|
defer tracy.end();
|
|
|
|
if (build_options.have_llvm) {
|
|
if (wasm.llvm_object) |llvm_object| {
|
|
return try llvm_object.flushModule(comp, prog_node);
|
|
}
|
|
}
|
|
|
|
var sub_prog_node = prog_node.start("Wasm Flush", 0);
|
|
sub_prog_node.activate();
|
|
defer sub_prog_node.end();
|
|
|
|
// ensure the error names table is populated when an error name is referenced
|
|
try wasm.populateErrorNameTable();
|
|
|
|
// Used for all temporary memory allocated during flushin
|
|
var arena_instance = std.heap.ArenaAllocator.init(wasm.base.allocator);
|
|
defer arena_instance.deinit();
|
|
const arena = arena_instance.allocator();
|
|
|
|
// Positional arguments to the linker such as object files and static archives.
|
|
var positionals = std.ArrayList([]const u8).init(arena);
|
|
try positionals.ensureUnusedCapacity(wasm.base.options.objects.len);
|
|
|
|
for (wasm.base.options.objects) |object| {
|
|
positionals.appendAssumeCapacity(object.path);
|
|
}
|
|
|
|
for (comp.c_object_table.keys()) |c_object| {
|
|
try positionals.append(c_object.status.success.object_path);
|
|
}
|
|
|
|
if (comp.compiler_rt_lib) |lib| {
|
|
try positionals.append(lib.full_object_path);
|
|
}
|
|
|
|
try wasm.parseInputFiles(positionals.items);
|
|
|
|
for (wasm.objects.items, 0..) |_, object_index| {
|
|
try wasm.resolveSymbolsInObject(@intCast(u16, object_index));
|
|
}
|
|
|
|
var emit_features_count: u32 = 0;
|
|
var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
|
|
try wasm.validateFeatures(&enabled_features, &emit_features_count);
|
|
try wasm.resolveSymbolsInArchives();
|
|
try wasm.resolveLazySymbols();
|
|
try wasm.checkUndefinedSymbols();
|
|
|
|
// When we finish/error we reset the state of the linker
|
|
// So we can rebuild the binary file on each incremental update
|
|
defer wasm.resetState();
|
|
try wasm.setupInitFunctions();
|
|
try wasm.setupStart();
|
|
try wasm.setupImports();
|
|
if (wasm.base.options.module) |mod| {
|
|
var decl_it = wasm.decls.iterator();
|
|
while (decl_it.next()) |entry| {
|
|
const decl = mod.declPtr(entry.key_ptr.*);
|
|
if (decl.isExtern()) continue;
|
|
const atom_index = entry.value_ptr.*;
|
|
if (decl.ty.zigTypeTag() == .Fn) {
|
|
try wasm.parseAtom(atom_index, .function);
|
|
} else if (decl.getVariable()) |variable| {
|
|
if (!variable.is_mutable) {
|
|
try wasm.parseAtom(atom_index, .{ .data = .read_only });
|
|
} else if (variable.init.isUndefDeep()) {
|
|
try wasm.parseAtom(atom_index, .{ .data = .uninitialized });
|
|
} else {
|
|
try wasm.parseAtom(atom_index, .{ .data = .initialized });
|
|
}
|
|
} else {
|
|
try wasm.parseAtom(atom_index, .{ .data = .read_only });
|
|
}
|
|
|
|
// also parse atoms for a decl's locals
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
for (atom.locals.items) |local_atom_index| {
|
|
try wasm.parseAtom(local_atom_index, .{ .data = .read_only });
|
|
}
|
|
}
|
|
|
|
if (wasm.dwarf) |*dwarf| {
|
|
try dwarf.flushModule(wasm.base.options.module.?);
|
|
}
|
|
}
|
|
|
|
for (wasm.objects.items, 0..) |*object, object_index| {
|
|
try object.parseIntoAtoms(wasm.base.allocator, @intCast(u16, object_index), wasm);
|
|
}
|
|
|
|
try wasm.allocateAtoms();
|
|
try wasm.setupMemory();
|
|
wasm.allocateVirtualAddresses();
|
|
wasm.mapFunctionTable();
|
|
try wasm.mergeSections();
|
|
try wasm.mergeTypes();
|
|
try wasm.initializeCallCtorsFunction();
|
|
try wasm.setupExports();
|
|
try wasm.writeToFile(enabled_features, emit_features_count, arena);
|
|
}
|
|
|
|
/// Writes the WebAssembly in-memory module to the file
|
|
fn writeToFile(
|
|
wasm: *Wasm,
|
|
enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool,
|
|
feature_count: u32,
|
|
arena: Allocator,
|
|
) !void {
|
|
// Size of each section header
|
|
const header_size = 5 + 1;
|
|
// The amount of sections that will be written
|
|
var section_count: u32 = 0;
|
|
// Index of the code section. Used to tell relocation table where the section lives.
|
|
var code_section_index: ?u32 = null;
|
|
// Index of the data section. Used to tell relocation table where the section lives.
|
|
var data_section_index: ?u32 = null;
|
|
const is_obj = wasm.base.options.output_mode == .Obj or (!wasm.base.options.use_llvm and wasm.base.options.use_lld);
|
|
|
|
var binary_bytes = std.ArrayList(u8).init(wasm.base.allocator);
|
|
defer binary_bytes.deinit();
|
|
const binary_writer = binary_bytes.writer();
|
|
|
|
// We write the magic bytes at the end so they will only be written
|
|
// if everything succeeded as expected. So populate with 0's for now.
|
|
try binary_writer.writeAll(&[_]u8{0} ** 8);
|
|
// (Re)set file pointer to 0
|
|
try wasm.base.file.?.setEndPos(0);
|
|
try wasm.base.file.?.seekTo(0);
|
|
|
|
// Type section
|
|
if (wasm.func_types.items.len != 0) {
|
|
const header_offset = try reserveVecSectionHeader(&binary_bytes);
|
|
log.debug("Writing type section. Count: ({d})", .{wasm.func_types.items.len});
|
|
for (wasm.func_types.items) |func_type| {
|
|
try leb.writeULEB128(binary_writer, std.wasm.function_type);
|
|
try leb.writeULEB128(binary_writer, @intCast(u32, func_type.params.len));
|
|
for (func_type.params) |param_ty| {
|
|
try leb.writeULEB128(binary_writer, std.wasm.valtype(param_ty));
|
|
}
|
|
try leb.writeULEB128(binary_writer, @intCast(u32, func_type.returns.len));
|
|
for (func_type.returns) |ret_ty| {
|
|
try leb.writeULEB128(binary_writer, std.wasm.valtype(ret_ty));
|
|
}
|
|
}
|
|
|
|
try writeVecSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
.type,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
|
|
@intCast(u32, wasm.func_types.items.len),
|
|
);
|
|
section_count += 1;
|
|
}
|
|
|
|
// Import section
|
|
const import_memory = wasm.base.options.import_memory or is_obj;
|
|
if (wasm.imports.count() != 0 or import_memory) {
|
|
const header_offset = try reserveVecSectionHeader(&binary_bytes);
|
|
|
|
var it = wasm.imports.iterator();
|
|
while (it.next()) |entry| {
|
|
assert(entry.key_ptr.*.getSymbol(wasm).isUndefined());
|
|
const import = entry.value_ptr.*;
|
|
try wasm.emitImport(binary_writer, import);
|
|
}
|
|
|
|
if (import_memory) {
|
|
const mem_name = if (is_obj) "__linear_memory" else "memory";
|
|
const mem_imp: types.Import = .{
|
|
.module_name = try wasm.string_table.put(wasm.base.allocator, wasm.host_name),
|
|
.name = try wasm.string_table.put(wasm.base.allocator, mem_name),
|
|
.kind = .{ .memory = wasm.memories.limits },
|
|
};
|
|
try wasm.emitImport(binary_writer, mem_imp);
|
|
}
|
|
|
|
try writeVecSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
.import,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
|
|
@intCast(u32, wasm.imports.count() + @boolToInt(import_memory)),
|
|
);
|
|
section_count += 1;
|
|
}
|
|
|
|
// Function section
|
|
if (wasm.functions.count() != 0) {
|
|
const header_offset = try reserveVecSectionHeader(&binary_bytes);
|
|
for (wasm.functions.values()) |function| {
|
|
try leb.writeULEB128(binary_writer, function.type_index);
|
|
}
|
|
|
|
try writeVecSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
.function,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
|
|
@intCast(u32, wasm.functions.count()),
|
|
);
|
|
section_count += 1;
|
|
}
|
|
|
|
// Table section
|
|
if (wasm.tables.items.len > 0) {
|
|
const header_offset = try reserveVecSectionHeader(&binary_bytes);
|
|
|
|
for (wasm.tables.items) |table| {
|
|
try leb.writeULEB128(binary_writer, std.wasm.reftype(table.reftype));
|
|
try emitLimits(binary_writer, table.limits);
|
|
}
|
|
|
|
try writeVecSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
.table,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
|
|
@intCast(u32, wasm.tables.items.len),
|
|
);
|
|
section_count += 1;
|
|
}
|
|
|
|
// Memory section
|
|
if (!import_memory) {
|
|
const header_offset = try reserveVecSectionHeader(&binary_bytes);
|
|
|
|
try emitLimits(binary_writer, wasm.memories.limits);
|
|
try writeVecSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
.memory,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
|
|
@as(u32, 1), // wasm currently only supports 1 linear memory segment
|
|
);
|
|
section_count += 1;
|
|
}
|
|
|
|
// Global section (used to emit stack pointer)
|
|
if (wasm.wasm_globals.items.len > 0) {
|
|
const header_offset = try reserveVecSectionHeader(&binary_bytes);
|
|
|
|
for (wasm.wasm_globals.items) |global| {
|
|
try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype));
|
|
try binary_writer.writeByte(@boolToInt(global.global_type.mutable));
|
|
try emitInit(binary_writer, global.init);
|
|
}
|
|
|
|
try writeVecSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
.global,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
|
|
@intCast(u32, wasm.wasm_globals.items.len),
|
|
);
|
|
section_count += 1;
|
|
}
|
|
|
|
// Export section
|
|
if (wasm.exports.items.len != 0 or !import_memory) {
|
|
const header_offset = try reserveVecSectionHeader(&binary_bytes);
|
|
|
|
for (wasm.exports.items) |exp| {
|
|
const name = wasm.string_table.get(exp.name);
|
|
try leb.writeULEB128(binary_writer, @intCast(u32, name.len));
|
|
try binary_writer.writeAll(name);
|
|
try leb.writeULEB128(binary_writer, @enumToInt(exp.kind));
|
|
try leb.writeULEB128(binary_writer, exp.index);
|
|
}
|
|
|
|
if (!import_memory) {
|
|
try leb.writeULEB128(binary_writer, @intCast(u32, "memory".len));
|
|
try binary_writer.writeAll("memory");
|
|
try binary_writer.writeByte(std.wasm.externalKind(.memory));
|
|
try leb.writeULEB128(binary_writer, @as(u32, 0));
|
|
}
|
|
|
|
try writeVecSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
.@"export",
|
|
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
|
|
@intCast(u32, wasm.exports.items.len) + @boolToInt(!import_memory),
|
|
);
|
|
section_count += 1;
|
|
}
|
|
|
|
// element section (function table)
|
|
if (wasm.function_table.count() > 0) {
|
|
const header_offset = try reserveVecSectionHeader(&binary_bytes);
|
|
|
|
const table_loc = wasm.findGlobalSymbol("__indirect_function_table").?;
|
|
const table_sym = table_loc.getSymbol(wasm);
|
|
|
|
var flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually
|
|
try leb.writeULEB128(binary_writer, flags);
|
|
if (flags == 0x02) {
|
|
try leb.writeULEB128(binary_writer, table_sym.index);
|
|
}
|
|
try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid
|
|
if (flags == 0x02) {
|
|
try leb.writeULEB128(binary_writer, @as(u8, 0)); // represents funcref
|
|
}
|
|
try leb.writeULEB128(binary_writer, @intCast(u32, wasm.function_table.count()));
|
|
var symbol_it = wasm.function_table.keyIterator();
|
|
while (symbol_it.next()) |symbol_loc_ptr| {
|
|
try leb.writeULEB128(binary_writer, symbol_loc_ptr.*.getSymbol(wasm).index);
|
|
}
|
|
|
|
try writeVecSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
.element,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
|
|
@as(u32, 1),
|
|
);
|
|
section_count += 1;
|
|
}
|
|
|
|
// When the shared-memory option is enabled, we *must* emit the 'data count' section.
|
|
const data_segments_count = wasm.data_segments.count() - @boolToInt(wasm.data_segments.contains(".bss") and import_memory);
|
|
if (data_segments_count != 0 and wasm.base.options.shared_memory) {
|
|
const header_offset = try reserveVecSectionHeader(&binary_bytes);
|
|
try writeVecSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
.data_count,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
|
|
@intCast(u32, data_segments_count),
|
|
);
|
|
}
|
|
|
|
// Code section
|
|
var code_section_size: u32 = 0;
|
|
if (wasm.code_section_index) |code_index| {
|
|
const header_offset = try reserveVecSectionHeader(&binary_bytes);
|
|
var atom_index = wasm.atoms.get(code_index).?;
|
|
|
|
// The code section must be sorted in line with the function order.
|
|
var sorted_atoms = try std.ArrayList(*Atom).initCapacity(wasm.base.allocator, wasm.functions.count());
|
|
defer sorted_atoms.deinit();
|
|
|
|
while (true) {
|
|
var atom = wasm.getAtomPtr(atom_index);
|
|
if (wasm.resolved_symbols.contains(atom.symbolLoc())) {
|
|
if (!is_obj) {
|
|
atom.resolveRelocs(wasm);
|
|
}
|
|
sorted_atoms.appendAssumeCapacity(atom);
|
|
}
|
|
// atom = if (atom.prev) |prev| wasm.getAtomPtr(prev) else break;
|
|
atom_index = atom.prev orelse break;
|
|
}
|
|
|
|
const atom_sort_fn = struct {
|
|
fn sort(ctx: *const Wasm, lhs: *const Atom, rhs: *const Atom) bool {
|
|
const lhs_sym = lhs.symbolLoc().getSymbol(ctx);
|
|
const rhs_sym = rhs.symbolLoc().getSymbol(ctx);
|
|
return lhs_sym.index < rhs_sym.index;
|
|
}
|
|
}.sort;
|
|
|
|
std.sort.sort(*Atom, sorted_atoms.items, wasm, atom_sort_fn);
|
|
|
|
for (sorted_atoms.items) |sorted_atom| {
|
|
try leb.writeULEB128(binary_writer, sorted_atom.size);
|
|
try binary_writer.writeAll(sorted_atom.code.items);
|
|
}
|
|
|
|
code_section_size = @intCast(u32, binary_bytes.items.len - header_offset - header_size);
|
|
try writeVecSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
.code,
|
|
code_section_size,
|
|
@intCast(u32, wasm.functions.count()),
|
|
);
|
|
code_section_index = section_count;
|
|
section_count += 1;
|
|
}
|
|
|
|
// Data section
|
|
if (data_segments_count != 0) {
|
|
const header_offset = try reserveVecSectionHeader(&binary_bytes);
|
|
|
|
var it = wasm.data_segments.iterator();
|
|
var segment_count: u32 = 0;
|
|
while (it.next()) |entry| {
|
|
// do not output 'bss' section unless we import memory and therefore
|
|
// want to guarantee the data is zero initialized
|
|
if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue;
|
|
const segment_index = entry.value_ptr.*;
|
|
const segment = wasm.segments.items[segment_index];
|
|
if (segment.size == 0) continue; // do not emit empty segments
|
|
segment_count += 1;
|
|
var atom_index = wasm.atoms.get(segment_index).?;
|
|
|
|
try leb.writeULEB128(binary_writer, segment.flags);
|
|
if (segment.flags & @enumToInt(Wasm.Segment.Flag.WASM_DATA_SEGMENT_HAS_MEMINDEX) != 0) {
|
|
try leb.writeULEB128(binary_writer, @as(u32, 0)); // memory is always index 0 as we only have 1 memory entry
|
|
}
|
|
// when a segment is passive, it's initialized during runtime.
|
|
if (!segment.isPassive()) {
|
|
try emitInit(binary_writer, .{ .i32_const = @bitCast(i32, segment.offset) });
|
|
}
|
|
// offset into data section
|
|
try leb.writeULEB128(binary_writer, segment.size);
|
|
|
|
// fill in the offset table and the data segments
|
|
var current_offset: u32 = 0;
|
|
while (true) {
|
|
const atom = wasm.getAtomPtr(atom_index);
|
|
if (!is_obj) {
|
|
atom.resolveRelocs(wasm);
|
|
}
|
|
|
|
// Pad with zeroes to ensure all segments are aligned
|
|
if (current_offset != atom.offset) {
|
|
const diff = atom.offset - current_offset;
|
|
try binary_writer.writeByteNTimes(0, diff);
|
|
current_offset += diff;
|
|
}
|
|
assert(current_offset == atom.offset);
|
|
assert(atom.code.items.len == atom.size);
|
|
try binary_writer.writeAll(atom.code.items);
|
|
|
|
current_offset += atom.size;
|
|
if (atom.prev) |prev| {
|
|
atom_index = prev;
|
|
} else {
|
|
// also pad with zeroes when last atom to ensure
|
|
// segments are aligned.
|
|
if (current_offset != segment.size) {
|
|
try binary_writer.writeByteNTimes(0, segment.size - current_offset);
|
|
current_offset += segment.size - current_offset;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
assert(current_offset == segment.size);
|
|
}
|
|
|
|
try writeVecSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
.data,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
|
|
@intCast(u32, segment_count),
|
|
);
|
|
data_section_index = section_count;
|
|
section_count += 1;
|
|
}
|
|
|
|
if (is_obj) {
|
|
// relocations need to point to the index of a symbol in the final symbol table. To save memory,
|
|
// we never store all symbols in a single table, but store a location reference instead.
|
|
// This means that for a relocatable object file, we need to generate one and provide it to the relocation sections.
|
|
var symbol_table = std.AutoArrayHashMap(SymbolLoc, u32).init(arena);
|
|
try wasm.emitLinkSection(&binary_bytes, &symbol_table);
|
|
if (code_section_index) |code_index| {
|
|
try wasm.emitCodeRelocations(&binary_bytes, code_index, symbol_table);
|
|
}
|
|
if (data_section_index) |data_index| {
|
|
try wasm.emitDataRelocations(&binary_bytes, data_index, symbol_table);
|
|
}
|
|
} else if (!wasm.base.options.strip) {
|
|
try wasm.emitNameSection(&binary_bytes, arena);
|
|
}
|
|
|
|
if (!wasm.base.options.strip) {
|
|
// The build id must be computed on the main sections only,
|
|
// so we have to do it now, before the debug sections.
|
|
if (wasm.base.options.build_id) {
|
|
try emitBuildIdSection(&binary_bytes);
|
|
}
|
|
|
|
// if (wasm.dwarf) |*dwarf| {
|
|
// const mod = wasm.base.options.module.?;
|
|
// try dwarf.writeDbgAbbrev();
|
|
// // for debug info and ranges, the address is always 0,
|
|
// // as locations are always offsets relative to 'code' section.
|
|
// try dwarf.writeDbgInfoHeader(mod, 0, code_section_size);
|
|
// try dwarf.writeDbgAranges(0, code_section_size);
|
|
// try dwarf.writeDbgLineHeader();
|
|
// }
|
|
|
|
var debug_bytes = std.ArrayList(u8).init(wasm.base.allocator);
|
|
defer debug_bytes.deinit();
|
|
|
|
const DebugSection = struct {
|
|
name: []const u8,
|
|
index: ?u32,
|
|
};
|
|
|
|
const debug_sections: []const DebugSection = &.{
|
|
.{ .name = ".debug_info", .index = wasm.debug_info_index },
|
|
.{ .name = ".debug_pubtypes", .index = wasm.debug_pubtypes_index },
|
|
.{ .name = ".debug_abbrev", .index = wasm.debug_abbrev_index },
|
|
.{ .name = ".debug_line", .index = wasm.debug_line_index },
|
|
.{ .name = ".debug_str", .index = wasm.debug_str_index },
|
|
.{ .name = ".debug_pubnames", .index = wasm.debug_pubnames_index },
|
|
.{ .name = ".debug_loc", .index = wasm.debug_loc_index },
|
|
.{ .name = ".debug_ranges", .index = wasm.debug_ranges_index },
|
|
};
|
|
|
|
for (debug_sections) |item| {
|
|
if (item.index) |index| {
|
|
var atom = wasm.getAtomPtr(wasm.atoms.get(index).?);
|
|
while (true) {
|
|
atom.resolveRelocs(wasm);
|
|
try debug_bytes.appendSlice(atom.code.items);
|
|
atom = if (atom.prev) |prev| wasm.getAtomPtr(prev) else break;
|
|
}
|
|
try emitDebugSection(&binary_bytes, debug_bytes.items, item.name);
|
|
debug_bytes.clearRetainingCapacity();
|
|
}
|
|
}
|
|
|
|
try emitProducerSection(&binary_bytes);
|
|
if (feature_count > 0) {
|
|
try emitFeaturesSection(&binary_bytes, &enabled_features, feature_count);
|
|
}
|
|
}
|
|
|
|
// Only when writing all sections executed properly we write the magic
|
|
// bytes. This allows us to easily detect what went wrong while generating
|
|
// the final binary.
|
|
mem.copy(u8, binary_bytes.items, &(std.wasm.magic ++ std.wasm.version));
|
|
|
|
// finally, write the entire binary into the file.
|
|
var iovec = [_]std.os.iovec_const{.{
|
|
.iov_base = binary_bytes.items.ptr,
|
|
.iov_len = binary_bytes.items.len,
|
|
}};
|
|
try wasm.base.file.?.writevAll(&iovec);
|
|
}
|
|
|
|
fn emitDebugSection(binary_bytes: *std.ArrayList(u8), data: []const u8, name: []const u8) !void {
|
|
if (data.len == 0) return;
|
|
const header_offset = try reserveCustomSectionHeader(binary_bytes);
|
|
const writer = binary_bytes.writer();
|
|
try leb.writeULEB128(writer, @intCast(u32, name.len));
|
|
try writer.writeAll(name);
|
|
|
|
const start = binary_bytes.items.len - header_offset;
|
|
log.debug("Emit debug section: '{s}' start=0x{x:0>8} end=0x{x:0>8}", .{ name, start, start + data.len });
|
|
try writer.writeAll(data);
|
|
|
|
try writeCustomSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - 6),
|
|
);
|
|
}
|
|
|
|
fn emitProducerSection(binary_bytes: *std.ArrayList(u8)) !void {
|
|
const header_offset = try reserveCustomSectionHeader(binary_bytes);
|
|
|
|
const writer = binary_bytes.writer();
|
|
const producers = "producers";
|
|
try leb.writeULEB128(writer, @intCast(u32, producers.len));
|
|
try writer.writeAll(producers);
|
|
|
|
try leb.writeULEB128(writer, @as(u32, 2)); // 2 fields: Language + processed-by
|
|
|
|
// used for the Zig version
|
|
var version_buf: [100]u8 = undefined;
|
|
const version = try std.fmt.bufPrint(&version_buf, "{}", .{build_options.semver});
|
|
|
|
// language field
|
|
{
|
|
const language = "language";
|
|
try leb.writeULEB128(writer, @intCast(u32, language.len));
|
|
try writer.writeAll(language);
|
|
|
|
// field_value_count (TODO: Parse object files for producer sections to detect their language)
|
|
try leb.writeULEB128(writer, @as(u32, 1));
|
|
|
|
// versioned name
|
|
{
|
|
try leb.writeULEB128(writer, @as(u32, 3)); // len of "Zig"
|
|
try writer.writeAll("Zig");
|
|
|
|
try leb.writeULEB128(writer, @intCast(u32, version.len));
|
|
try writer.writeAll(version);
|
|
}
|
|
}
|
|
|
|
// processed-by field
|
|
{
|
|
const processed_by = "processed-by";
|
|
try leb.writeULEB128(writer, @intCast(u32, processed_by.len));
|
|
try writer.writeAll(processed_by);
|
|
|
|
// field_value_count (TODO: Parse object files for producer sections to detect other used tools)
|
|
try leb.writeULEB128(writer, @as(u32, 1));
|
|
|
|
// versioned name
|
|
{
|
|
try leb.writeULEB128(writer, @as(u32, 3)); // len of "Zig"
|
|
try writer.writeAll("Zig");
|
|
|
|
try leb.writeULEB128(writer, @intCast(u32, version.len));
|
|
try writer.writeAll(version);
|
|
}
|
|
}
|
|
|
|
try writeCustomSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - 6),
|
|
);
|
|
}
|
|
|
|
fn emitBuildIdSection(binary_bytes: *std.ArrayList(u8)) !void {
|
|
const header_offset = try reserveCustomSectionHeader(binary_bytes);
|
|
|
|
const writer = binary_bytes.writer();
|
|
const build_id = "build_id";
|
|
try leb.writeULEB128(writer, @intCast(u32, build_id.len));
|
|
try writer.writeAll(build_id);
|
|
|
|
var id: [16]u8 = undefined;
|
|
std.crypto.hash.sha3.TurboShake128(null).hash(binary_bytes.items, &id, .{});
|
|
var uuid: [36]u8 = undefined;
|
|
_ = try std.fmt.bufPrint(&uuid, "{s}-{s}-{s}-{s}-{s}", .{
|
|
std.fmt.fmtSliceHexLower(id[0..4]), std.fmt.fmtSliceHexLower(id[4..6]), std.fmt.fmtSliceHexLower(id[6..8]),
|
|
std.fmt.fmtSliceHexLower(id[8..10]), std.fmt.fmtSliceHexLower(id[10..]),
|
|
});
|
|
|
|
try leb.writeULEB128(writer, @as(u32, 1));
|
|
try leb.writeULEB128(writer, @as(u32, uuid.len));
|
|
try writer.writeAll(&uuid);
|
|
|
|
try writeCustomSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - 6),
|
|
);
|
|
}
|
|
|
|
fn emitFeaturesSection(binary_bytes: *std.ArrayList(u8), enabled_features: []const bool, features_count: u32) !void {
|
|
const header_offset = try reserveCustomSectionHeader(binary_bytes);
|
|
|
|
const writer = binary_bytes.writer();
|
|
const target_features = "target_features";
|
|
try leb.writeULEB128(writer, @intCast(u32, target_features.len));
|
|
try writer.writeAll(target_features);
|
|
|
|
try leb.writeULEB128(writer, features_count);
|
|
for (enabled_features, 0..) |enabled, feature_index| {
|
|
if (enabled) {
|
|
const feature: types.Feature = .{ .prefix = .used, .tag = @intToEnum(types.Feature.Tag, feature_index) };
|
|
try leb.writeULEB128(writer, @enumToInt(feature.prefix));
|
|
var buf: [100]u8 = undefined;
|
|
const string = try std.fmt.bufPrint(&buf, "{}", .{feature.tag});
|
|
try leb.writeULEB128(writer, @intCast(u32, string.len));
|
|
try writer.writeAll(string);
|
|
}
|
|
}
|
|
|
|
try writeCustomSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - 6),
|
|
);
|
|
}
|
|
|
|
fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem.Allocator) !void {
|
|
const Name = struct {
|
|
index: u32,
|
|
name: []const u8,
|
|
|
|
fn lessThan(context: void, lhs: @This(), rhs: @This()) bool {
|
|
_ = context;
|
|
return lhs.index < rhs.index;
|
|
}
|
|
};
|
|
|
|
// we must de-duplicate symbols that point to the same function
|
|
var funcs = std.AutoArrayHashMap(u32, Name).init(arena);
|
|
try funcs.ensureUnusedCapacity(wasm.functions.count() + wasm.imported_functions_count);
|
|
var globals = try std.ArrayList(Name).initCapacity(arena, wasm.wasm_globals.items.len + wasm.imported_globals_count);
|
|
var segments = try std.ArrayList(Name).initCapacity(arena, wasm.data_segments.count());
|
|
|
|
for (wasm.resolved_symbols.keys()) |sym_loc| {
|
|
const symbol = sym_loc.getSymbol(wasm).*;
|
|
const name = sym_loc.getName(wasm);
|
|
switch (symbol.tag) {
|
|
.function => {
|
|
const gop = funcs.getOrPutAssumeCapacity(symbol.index);
|
|
if (!gop.found_existing) {
|
|
gop.value_ptr.* = .{ .index = symbol.index, .name = name };
|
|
}
|
|
},
|
|
.global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = name }),
|
|
else => {},
|
|
}
|
|
}
|
|
// data segments are already 'ordered'
|
|
var data_segment_index: u32 = 0;
|
|
for (wasm.data_segments.keys()) |key| {
|
|
// bss section is not emitted when this condition holds true, so we also
|
|
// do not output a name for it.
|
|
if (!wasm.base.options.import_memory and std.mem.eql(u8, key, ".bss")) continue;
|
|
segments.appendAssumeCapacity(.{ .index = data_segment_index, .name = key });
|
|
data_segment_index += 1;
|
|
}
|
|
|
|
std.sort.sort(Name, funcs.values(), {}, Name.lessThan);
|
|
std.sort.sort(Name, globals.items, {}, Name.lessThan);
|
|
|
|
const header_offset = try reserveCustomSectionHeader(binary_bytes);
|
|
const writer = binary_bytes.writer();
|
|
try leb.writeULEB128(writer, @intCast(u32, "name".len));
|
|
try writer.writeAll("name");
|
|
|
|
try wasm.emitNameSubsection(.function, funcs.values(), writer);
|
|
try wasm.emitNameSubsection(.global, globals.items, writer);
|
|
try wasm.emitNameSubsection(.data_segment, segments.items, writer);
|
|
|
|
try writeCustomSectionHeader(
|
|
binary_bytes.items,
|
|
header_offset,
|
|
@intCast(u32, binary_bytes.items.len - header_offset - 6),
|
|
);
|
|
}
|
|
|
|
fn emitNameSubsection(wasm: *Wasm, section_id: std.wasm.NameSubsection, names: anytype, writer: anytype) !void {
|
|
// We must emit subsection size, so first write to a temporary list
|
|
var section_list = std.ArrayList(u8).init(wasm.base.allocator);
|
|
defer section_list.deinit();
|
|
const sub_writer = section_list.writer();
|
|
|
|
try leb.writeULEB128(sub_writer, @intCast(u32, names.len));
|
|
for (names) |name| {
|
|
log.debug("Emit symbol '{s}' type({s})", .{ name.name, @tagName(section_id) });
|
|
try leb.writeULEB128(sub_writer, name.index);
|
|
try leb.writeULEB128(sub_writer, @intCast(u32, name.name.len));
|
|
try sub_writer.writeAll(name.name);
|
|
}
|
|
|
|
// From now, write to the actual writer
|
|
try leb.writeULEB128(writer, @enumToInt(section_id));
|
|
try leb.writeULEB128(writer, @intCast(u32, section_list.items.len));
|
|
try writer.writeAll(section_list.items);
|
|
}
|
|
|
|
fn emitLimits(writer: anytype, limits: std.wasm.Limits) !void {
|
|
try writer.writeByte(limits.flags);
|
|
try leb.writeULEB128(writer, limits.min);
|
|
if (limits.hasFlag(.WASM_LIMITS_FLAG_HAS_MAX)) {
|
|
try leb.writeULEB128(writer, limits.max);
|
|
}
|
|
}
|
|
|
|
fn emitInit(writer: anytype, init_expr: std.wasm.InitExpression) !void {
|
|
switch (init_expr) {
|
|
.i32_const => |val| {
|
|
try writer.writeByte(std.wasm.opcode(.i32_const));
|
|
try leb.writeILEB128(writer, val);
|
|
},
|
|
.i64_const => |val| {
|
|
try writer.writeByte(std.wasm.opcode(.i64_const));
|
|
try leb.writeILEB128(writer, val);
|
|
},
|
|
.f32_const => |val| {
|
|
try writer.writeByte(std.wasm.opcode(.f32_const));
|
|
try writer.writeIntLittle(u32, @bitCast(u32, val));
|
|
},
|
|
.f64_const => |val| {
|
|
try writer.writeByte(std.wasm.opcode(.f64_const));
|
|
try writer.writeIntLittle(u64, @bitCast(u64, val));
|
|
},
|
|
.global_get => |val| {
|
|
try writer.writeByte(std.wasm.opcode(.global_get));
|
|
try leb.writeULEB128(writer, val);
|
|
},
|
|
}
|
|
try writer.writeByte(std.wasm.opcode(.end));
|
|
}
|
|
|
|
fn emitImport(wasm: *Wasm, writer: anytype, import: types.Import) !void {
|
|
const module_name = wasm.string_table.get(import.module_name);
|
|
try leb.writeULEB128(writer, @intCast(u32, module_name.len));
|
|
try writer.writeAll(module_name);
|
|
|
|
const name = wasm.string_table.get(import.name);
|
|
try leb.writeULEB128(writer, @intCast(u32, name.len));
|
|
try writer.writeAll(name);
|
|
|
|
try writer.writeByte(@enumToInt(import.kind));
|
|
switch (import.kind) {
|
|
.function => |type_index| try leb.writeULEB128(writer, type_index),
|
|
.global => |global_type| {
|
|
try leb.writeULEB128(writer, std.wasm.valtype(global_type.valtype));
|
|
try writer.writeByte(@boolToInt(global_type.mutable));
|
|
},
|
|
.table => |table| {
|
|
try leb.writeULEB128(writer, std.wasm.reftype(table.reftype));
|
|
try emitLimits(writer, table.limits);
|
|
},
|
|
.memory => |limits| {
|
|
try emitLimits(writer, limits);
|
|
},
|
|
}
|
|
}
|
|
|
|
fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !void {
|
|
const tracy = trace(@src());
|
|
defer tracy.end();
|
|
|
|
var arena_allocator = std.heap.ArenaAllocator.init(wasm.base.allocator);
|
|
defer arena_allocator.deinit();
|
|
const arena = arena_allocator.allocator();
|
|
|
|
const directory = wasm.base.options.emit.?.directory; // Just an alias to make it shorter to type.
|
|
const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.options.emit.?.sub_path});
|
|
|
|
// If there is no Zig code to compile, then we should skip flushing the output file because it
|
|
// will not be part of the linker line anyway.
|
|
const module_obj_path: ?[]const u8 = if (wasm.base.options.module != null) blk: {
|
|
try wasm.flushModule(comp, prog_node);
|
|
|
|
if (fs.path.dirname(full_out_path)) |dirname| {
|
|
break :blk try fs.path.join(arena, &.{ dirname, wasm.base.intermediary_basename.? });
|
|
} else {
|
|
break :blk wasm.base.intermediary_basename.?;
|
|
}
|
|
} else null;
|
|
|
|
var sub_prog_node = prog_node.start("LLD Link", 0);
|
|
sub_prog_node.activate();
|
|
sub_prog_node.context.refresh();
|
|
defer sub_prog_node.end();
|
|
|
|
const is_obj = wasm.base.options.output_mode == .Obj;
|
|
|
|
const compiler_rt_path: ?[]const u8 = if (wasm.base.options.include_compiler_rt and !is_obj)
|
|
comp.compiler_rt_lib.?.full_object_path
|
|
else
|
|
null;
|
|
|
|
const target = wasm.base.options.target;
|
|
|
|
const id_symlink_basename = "lld.id";
|
|
|
|
var man: Cache.Manifest = undefined;
|
|
defer if (!wasm.base.options.disable_lld_caching) man.deinit();
|
|
|
|
var digest: [Cache.hex_digest_len]u8 = undefined;
|
|
|
|
if (!wasm.base.options.disable_lld_caching) {
|
|
man = comp.cache_parent.obtain();
|
|
|
|
// We are about to obtain this lock, so here we give other processes a chance first.
|
|
wasm.base.releaseLock();
|
|
|
|
comptime assert(Compilation.link_hash_implementation_version == 7);
|
|
|
|
for (wasm.base.options.objects) |obj| {
|
|
_ = try man.addFile(obj.path, null);
|
|
man.hash.add(obj.must_link);
|
|
}
|
|
for (comp.c_object_table.keys()) |key| {
|
|
_ = try man.addFile(key.status.success.object_path, null);
|
|
}
|
|
try man.addOptionalFile(module_obj_path);
|
|
try man.addOptionalFile(compiler_rt_path);
|
|
man.hash.addOptionalBytes(wasm.base.options.entry);
|
|
man.hash.addOptional(wasm.base.options.stack_size_override);
|
|
man.hash.add(wasm.base.options.build_id);
|
|
man.hash.add(wasm.base.options.import_memory);
|
|
man.hash.add(wasm.base.options.import_table);
|
|
man.hash.add(wasm.base.options.export_table);
|
|
man.hash.addOptional(wasm.base.options.initial_memory);
|
|
man.hash.addOptional(wasm.base.options.max_memory);
|
|
man.hash.add(wasm.base.options.shared_memory);
|
|
man.hash.addOptional(wasm.base.options.global_base);
|
|
man.hash.add(wasm.base.options.export_symbol_names.len);
|
|
// strip does not need to go into the linker hash because it is part of the hash namespace
|
|
for (wasm.base.options.export_symbol_names) |symbol_name| {
|
|
man.hash.addBytes(symbol_name);
|
|
}
|
|
|
|
// We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
|
|
_ = try man.hit();
|
|
digest = man.final();
|
|
|
|
var prev_digest_buf: [digest.len]u8 = undefined;
|
|
const prev_digest: []u8 = Cache.readSmallFile(
|
|
directory.handle,
|
|
id_symlink_basename,
|
|
&prev_digest_buf,
|
|
) catch |err| blk: {
|
|
log.debug("WASM LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) });
|
|
// Handle this as a cache miss.
|
|
break :blk prev_digest_buf[0..0];
|
|
};
|
|
if (mem.eql(u8, prev_digest, &digest)) {
|
|
log.debug("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)});
|
|
// Hot diggity dog! The output binary is already there.
|
|
wasm.base.lock = man.toOwnedLock();
|
|
return;
|
|
}
|
|
log.debug("WASM LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) });
|
|
|
|
// We are about to change the output file to be different, so we invalidate the build hash now.
|
|
directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
|
|
error.FileNotFound => {},
|
|
else => |e| return e,
|
|
};
|
|
}
|
|
|
|
if (is_obj) {
|
|
// LLD's WASM driver does not support the equivalent of `-r` so we do a simple file copy
|
|
// here. TODO: think carefully about how we can avoid this redundant operation when doing
|
|
// build-obj. See also the corresponding TODO in linkAsArchive.
|
|
const the_object_path = blk: {
|
|
if (wasm.base.options.objects.len != 0)
|
|
break :blk wasm.base.options.objects[0].path;
|
|
|
|
if (comp.c_object_table.count() != 0)
|
|
break :blk comp.c_object_table.keys()[0].status.success.object_path;
|
|
|
|
if (module_obj_path) |p|
|
|
break :blk p;
|
|
|
|
// TODO I think this is unreachable. Audit this situation when solving the above TODO
|
|
// regarding eliding redundant object -> object transformations.
|
|
return error.NoObjectsToLink;
|
|
};
|
|
// This can happen when using --enable-cache and using the stage1 backend. In this case
|
|
// we can skip the file copy.
|
|
if (!mem.eql(u8, the_object_path, full_out_path)) {
|
|
try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
|
|
}
|
|
} else {
|
|
// Create an LLD command line and invoke it.
|
|
var argv = std.ArrayList([]const u8).init(wasm.base.allocator);
|
|
defer argv.deinit();
|
|
// We will invoke ourselves as a child process to gain access to LLD.
|
|
// This is necessary because LLD does not behave properly as a library -
|
|
// it calls exit() and does not reset all global data between invocations.
|
|
const linker_command = "wasm-ld";
|
|
try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command });
|
|
try argv.append("--error-limit=0");
|
|
|
|
if (wasm.base.options.lto) {
|
|
switch (wasm.base.options.optimize_mode) {
|
|
.Debug => {},
|
|
.ReleaseSmall => try argv.append("-O2"),
|
|
.ReleaseFast, .ReleaseSafe => try argv.append("-O3"),
|
|
}
|
|
}
|
|
|
|
if (wasm.base.options.import_memory) {
|
|
try argv.append("--import-memory");
|
|
}
|
|
|
|
if (wasm.base.options.import_table) {
|
|
assert(!wasm.base.options.export_table);
|
|
try argv.append("--import-table");
|
|
}
|
|
|
|
if (wasm.base.options.export_table) {
|
|
assert(!wasm.base.options.import_table);
|
|
try argv.append("--export-table");
|
|
}
|
|
|
|
if (wasm.base.options.strip) {
|
|
try argv.append("-s");
|
|
}
|
|
|
|
if (wasm.base.options.initial_memory) |initial_memory| {
|
|
const arg = try std.fmt.allocPrint(arena, "--initial-memory={d}", .{initial_memory});
|
|
try argv.append(arg);
|
|
}
|
|
|
|
if (wasm.base.options.max_memory) |max_memory| {
|
|
const arg = try std.fmt.allocPrint(arena, "--max-memory={d}", .{max_memory});
|
|
try argv.append(arg);
|
|
}
|
|
|
|
if (wasm.base.options.shared_memory) {
|
|
try argv.append("--shared-memory");
|
|
}
|
|
|
|
if (wasm.base.options.global_base) |global_base| {
|
|
const arg = try std.fmt.allocPrint(arena, "--global-base={d}", .{global_base});
|
|
try argv.append(arg);
|
|
} else {
|
|
// We prepend it by default, so when a stack overflow happens the runtime will trap correctly,
|
|
// rather than silently overwrite all global declarations. See https://github.com/ziglang/zig/issues/4496
|
|
//
|
|
// The user can overwrite this behavior by setting the global-base
|
|
try argv.append("--stack-first");
|
|
}
|
|
|
|
// Users are allowed to specify which symbols they want to export to the wasm host.
|
|
for (wasm.base.options.export_symbol_names) |symbol_name| {
|
|
const arg = try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name});
|
|
try argv.append(arg);
|
|
}
|
|
|
|
if (wasm.base.options.rdynamic) {
|
|
try argv.append("--export-dynamic");
|
|
}
|
|
|
|
if (wasm.base.options.entry) |entry| {
|
|
try argv.append("--entry");
|
|
try argv.append(entry);
|
|
}
|
|
|
|
// Increase the default stack size to a more reasonable value of 1MB instead of
|
|
// the default of 1 Wasm page being 64KB, unless overridden by the user.
|
|
try argv.append("-z");
|
|
const stack_size = wasm.base.options.stack_size_override orelse std.wasm.page_size * 16;
|
|
const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size});
|
|
try argv.append(arg);
|
|
|
|
if (wasm.base.options.output_mode == .Exe) {
|
|
if (wasm.base.options.wasi_exec_model == .reactor) {
|
|
// Reactor execution model does not have _start so lld doesn't look for it.
|
|
try argv.append("--no-entry");
|
|
// Make sure "_initialize" and other used-defined functions are exported if this is WASI reactor.
|
|
// If rdynamic is true, it will already be appended, so only verify if the user did not specify
|
|
// the flag in which case, we ensure `--export-dynamic` is called.
|
|
if (!wasm.base.options.rdynamic) {
|
|
try argv.append("--export-dynamic");
|
|
}
|
|
}
|
|
} else if (wasm.base.options.entry == null) {
|
|
try argv.append("--no-entry"); // So lld doesn't look for _start.
|
|
}
|
|
if (wasm.base.options.import_symbols) {
|
|
try argv.append("--allow-undefined");
|
|
}
|
|
|
|
// XXX - TODO: add when wasm-ld supports --build-id.
|
|
// if (wasm.base.options.build_id) {
|
|
// try argv.append("--build-id=tree");
|
|
// }
|
|
|
|
try argv.appendSlice(&.{ "-o", full_out_path });
|
|
|
|
if (target.cpu.arch == .wasm64) {
|
|
try argv.append("-mwasm64");
|
|
}
|
|
|
|
if (target.os.tag == .wasi) {
|
|
const is_exe_or_dyn_lib = wasm.base.options.output_mode == .Exe or
|
|
(wasm.base.options.output_mode == .Lib and wasm.base.options.link_mode == .Dynamic);
|
|
if (is_exe_or_dyn_lib) {
|
|
const wasi_emulated_libs = wasm.base.options.wasi_emulated_libs;
|
|
for (wasi_emulated_libs) |crt_file| {
|
|
try argv.append(try comp.get_libc_crt_file(
|
|
arena,
|
|
wasi_libc.emulatedLibCRFileLibName(crt_file),
|
|
));
|
|
}
|
|
|
|
if (wasm.base.options.link_libc) {
|
|
try argv.append(try comp.get_libc_crt_file(
|
|
arena,
|
|
wasi_libc.execModelCrtFileFullName(wasm.base.options.wasi_exec_model),
|
|
));
|
|
try argv.append(try comp.get_libc_crt_file(arena, "libc.a"));
|
|
}
|
|
|
|
if (wasm.base.options.link_libcpp) {
|
|
try argv.append(comp.libcxx_static_lib.?.full_object_path);
|
|
try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Positional arguments to the linker such as object files.
|
|
var whole_archive = false;
|
|
for (wasm.base.options.objects) |obj| {
|
|
if (obj.must_link and !whole_archive) {
|
|
try argv.append("-whole-archive");
|
|
whole_archive = true;
|
|
} else if (!obj.must_link and whole_archive) {
|
|
try argv.append("-no-whole-archive");
|
|
whole_archive = false;
|
|
}
|
|
try argv.append(obj.path);
|
|
}
|
|
if (whole_archive) {
|
|
try argv.append("-no-whole-archive");
|
|
whole_archive = false;
|
|
}
|
|
|
|
for (comp.c_object_table.keys()) |key| {
|
|
try argv.append(key.status.success.object_path);
|
|
}
|
|
if (module_obj_path) |p| {
|
|
try argv.append(p);
|
|
}
|
|
|
|
if (wasm.base.options.output_mode != .Obj and
|
|
!wasm.base.options.skip_linker_dependencies and
|
|
!wasm.base.options.link_libc)
|
|
{
|
|
try argv.append(comp.libc_static_lib.?.full_object_path);
|
|
}
|
|
|
|
if (compiler_rt_path) |p| {
|
|
try argv.append(p);
|
|
}
|
|
|
|
if (wasm.base.options.verbose_link) {
|
|
// Skip over our own name so that the LLD linker name is the first argv item.
|
|
Compilation.dump_argv(argv.items[1..]);
|
|
}
|
|
|
|
if (std.process.can_spawn) {
|
|
// If possible, we run LLD as a child process because it does not always
|
|
// behave properly as a library, unfortunately.
|
|
// https://github.com/ziglang/zig/issues/3825
|
|
var child = std.ChildProcess.init(argv.items, arena);
|
|
if (comp.clang_passthrough_mode) {
|
|
child.stdin_behavior = .Inherit;
|
|
child.stdout_behavior = .Inherit;
|
|
child.stderr_behavior = .Inherit;
|
|
|
|
const term = child.spawnAndWait() catch |err| {
|
|
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
|
|
return error.UnableToSpawnWasm;
|
|
};
|
|
switch (term) {
|
|
.Exited => |code| {
|
|
if (code != 0) {
|
|
std.process.exit(code);
|
|
}
|
|
},
|
|
else => std.process.abort(),
|
|
}
|
|
} else {
|
|
child.stdin_behavior = .Ignore;
|
|
child.stdout_behavior = .Ignore;
|
|
child.stderr_behavior = .Pipe;
|
|
|
|
try child.spawn();
|
|
|
|
const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024);
|
|
|
|
const term = child.wait() catch |err| {
|
|
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
|
|
return error.UnableToSpawnWasm;
|
|
};
|
|
|
|
switch (term) {
|
|
.Exited => |code| {
|
|
if (code != 0) {
|
|
comp.lockAndParseLldStderr(linker_command, stderr);
|
|
return error.LLDReportedFailure;
|
|
}
|
|
},
|
|
else => {
|
|
log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr });
|
|
return error.LLDCrashed;
|
|
},
|
|
}
|
|
|
|
if (stderr.len != 0) {
|
|
log.warn("unexpected LLD stderr:\n{s}", .{stderr});
|
|
}
|
|
}
|
|
} else {
|
|
const exit_code = try lldMain(arena, argv.items, false);
|
|
if (exit_code != 0) {
|
|
if (comp.clang_passthrough_mode) {
|
|
std.process.exit(exit_code);
|
|
} else {
|
|
return error.LLDReportedFailure;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Give +x to the .wasm file if it is an executable and the OS is WASI.
|
|
// Some systems may be configured to execute such binaries directly. Even if that
|
|
// is not the case, it means we will get "exec format error" when trying to run
|
|
// it, and then can react to that in the same way as trying to run an ELF file
|
|
// from a foreign CPU architecture.
|
|
if (fs.has_executable_bit and target.os.tag == .wasi and
|
|
wasm.base.options.output_mode == .Exe)
|
|
{
|
|
// TODO: what's our strategy for reporting linker errors from this function?
|
|
// report a nice error here with the file path if it fails instead of
|
|
// just returning the error code.
|
|
// chmod does not interact with umask, so we use a conservative -rwxr--r-- here.
|
|
try std.os.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0);
|
|
}
|
|
}
|
|
|
|
if (!wasm.base.options.disable_lld_caching) {
|
|
// Update the file with the digest. If it fails we can continue; it only
|
|
// means that the next invocation will have an unnecessary cache miss.
|
|
Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
|
|
log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)});
|
|
};
|
|
// Again failure here only means an unnecessary cache miss.
|
|
man.writeManifest() catch |err| {
|
|
log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)});
|
|
};
|
|
// We hang on to this lock so that the output file path can be used without
|
|
// other processes clobbering it.
|
|
wasm.base.lock = man.toOwnedLock();
|
|
}
|
|
}
|
|
|
|
fn reserveVecSectionHeader(bytes: *std.ArrayList(u8)) !u32 {
|
|
// section id + fixed leb contents size + fixed leb vector length
|
|
const header_size = 1 + 5 + 5;
|
|
const offset = @intCast(u32, bytes.items.len);
|
|
try bytes.appendSlice(&[_]u8{0} ** header_size);
|
|
return offset;
|
|
}
|
|
|
|
fn reserveCustomSectionHeader(bytes: *std.ArrayList(u8)) !u32 {
|
|
// unlike regular section, we don't emit the count
|
|
const header_size = 1 + 5;
|
|
const offset = @intCast(u32, bytes.items.len);
|
|
try bytes.appendSlice(&[_]u8{0} ** header_size);
|
|
return offset;
|
|
}
|
|
|
|
fn writeVecSectionHeader(buffer: []u8, offset: u32, section: std.wasm.Section, size: u32, items: u32) !void {
|
|
var buf: [1 + 5 + 5]u8 = undefined;
|
|
buf[0] = @enumToInt(section);
|
|
leb.writeUnsignedFixed(5, buf[1..6], size);
|
|
leb.writeUnsignedFixed(5, buf[6..], items);
|
|
mem.copy(u8, buffer[offset..], &buf);
|
|
}
|
|
|
|
fn writeCustomSectionHeader(buffer: []u8, offset: u32, size: u32) !void {
|
|
var buf: [1 + 5]u8 = undefined;
|
|
buf[0] = 0; // 0 = 'custom' section
|
|
leb.writeUnsignedFixed(5, buf[1..6], size);
|
|
mem.copy(u8, buffer[offset..], &buf);
|
|
}
|
|
|
|
fn emitLinkSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void {
|
|
const offset = try reserveCustomSectionHeader(binary_bytes);
|
|
const writer = binary_bytes.writer();
|
|
// emit "linking" custom section name
|
|
const section_name = "linking";
|
|
try leb.writeULEB128(writer, section_name.len);
|
|
try writer.writeAll(section_name);
|
|
|
|
// meta data version, which is currently '2'
|
|
try leb.writeULEB128(writer, @as(u32, 2));
|
|
|
|
// For each subsection type (found in types.Subsection) we can emit a section.
|
|
// Currently, we only support emitting segment info and the symbol table.
|
|
try wasm.emitSymbolTable(binary_bytes, symbol_table);
|
|
try wasm.emitSegmentInfo(binary_bytes);
|
|
|
|
const size = @intCast(u32, binary_bytes.items.len - offset - 6);
|
|
try writeCustomSectionHeader(binary_bytes.items, offset, size);
|
|
}
|
|
|
|
fn emitSymbolTable(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void {
|
|
const writer = binary_bytes.writer();
|
|
|
|
try leb.writeULEB128(writer, @enumToInt(types.SubsectionType.WASM_SYMBOL_TABLE));
|
|
const table_offset = binary_bytes.items.len;
|
|
|
|
var symbol_count: u32 = 0;
|
|
for (wasm.resolved_symbols.keys()) |sym_loc| {
|
|
const symbol = sym_loc.getSymbol(wasm).*;
|
|
if (symbol.tag == .dead) continue; // Do not emit dead symbols
|
|
try symbol_table.putNoClobber(sym_loc, symbol_count);
|
|
symbol_count += 1;
|
|
log.debug("Emit symbol: {}", .{symbol});
|
|
try leb.writeULEB128(writer, @enumToInt(symbol.tag));
|
|
try leb.writeULEB128(writer, symbol.flags);
|
|
|
|
const sym_name = if (wasm.export_names.get(sym_loc)) |exp_name| wasm.string_table.get(exp_name) else sym_loc.getName(wasm);
|
|
switch (symbol.tag) {
|
|
.data => {
|
|
try leb.writeULEB128(writer, @intCast(u32, sym_name.len));
|
|
try writer.writeAll(sym_name);
|
|
|
|
if (symbol.isDefined()) {
|
|
try leb.writeULEB128(writer, symbol.index);
|
|
const atom_index = wasm.symbol_atom.get(sym_loc).?;
|
|
const atom = wasm.getAtom(atom_index);
|
|
try leb.writeULEB128(writer, @as(u32, atom.offset));
|
|
try leb.writeULEB128(writer, @as(u32, atom.size));
|
|
}
|
|
},
|
|
.section => {
|
|
try leb.writeULEB128(writer, symbol.index);
|
|
},
|
|
else => {
|
|
try leb.writeULEB128(writer, symbol.index);
|
|
if (symbol.isDefined()) {
|
|
try leb.writeULEB128(writer, @intCast(u32, sym_name.len));
|
|
try writer.writeAll(sym_name);
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
var buf: [10]u8 = undefined;
|
|
leb.writeUnsignedFixed(5, buf[0..5], @intCast(u32, binary_bytes.items.len - table_offset + 5));
|
|
leb.writeUnsignedFixed(5, buf[5..], symbol_count);
|
|
try binary_bytes.insertSlice(table_offset, &buf);
|
|
}
|
|
|
|
fn emitSegmentInfo(wasm: *Wasm, binary_bytes: *std.ArrayList(u8)) !void {
|
|
const writer = binary_bytes.writer();
|
|
try leb.writeULEB128(writer, @enumToInt(types.SubsectionType.WASM_SEGMENT_INFO));
|
|
const segment_offset = binary_bytes.items.len;
|
|
|
|
try leb.writeULEB128(writer, @intCast(u32, wasm.segment_info.count()));
|
|
for (wasm.segment_info.values()) |segment_info| {
|
|
log.debug("Emit segment: {s} align({d}) flags({b})", .{
|
|
segment_info.name,
|
|
@ctz(segment_info.alignment),
|
|
segment_info.flags,
|
|
});
|
|
try leb.writeULEB128(writer, @intCast(u32, segment_info.name.len));
|
|
try writer.writeAll(segment_info.name);
|
|
try leb.writeULEB128(writer, @ctz(segment_info.alignment));
|
|
try leb.writeULEB128(writer, segment_info.flags);
|
|
}
|
|
|
|
var buf: [5]u8 = undefined;
|
|
leb.writeUnsignedFixed(5, &buf, @intCast(u32, binary_bytes.items.len - segment_offset));
|
|
try binary_bytes.insertSlice(segment_offset, &buf);
|
|
}
|
|
|
|
pub fn getULEB128Size(uint_value: anytype) u32 {
|
|
const T = @TypeOf(uint_value);
|
|
const U = if (@typeInfo(T).Int.bits < 8) u8 else T;
|
|
var value = @intCast(U, uint_value);
|
|
|
|
var size: u32 = 0;
|
|
while (value != 0) : (size += 1) {
|
|
value >>= 7;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
/// For each relocatable section, emits a custom "relocation.<section_name>" section
|
|
fn emitCodeRelocations(
|
|
wasm: *Wasm,
|
|
binary_bytes: *std.ArrayList(u8),
|
|
section_index: u32,
|
|
symbol_table: std.AutoArrayHashMap(SymbolLoc, u32),
|
|
) !void {
|
|
const code_index = wasm.code_section_index orelse return;
|
|
const writer = binary_bytes.writer();
|
|
const header_offset = try reserveCustomSectionHeader(binary_bytes);
|
|
|
|
// write custom section information
|
|
const name = "reloc.CODE";
|
|
try leb.writeULEB128(writer, @intCast(u32, name.len));
|
|
try writer.writeAll(name);
|
|
try leb.writeULEB128(writer, section_index);
|
|
const reloc_start = binary_bytes.items.len;
|
|
|
|
var count: u32 = 0;
|
|
var atom: *Atom = wasm.getAtomPtr(wasm.atoms.get(code_index).?);
|
|
// for each atom, we calculate the uleb size and append that
|
|
var size_offset: u32 = 5; // account for code section size leb128
|
|
while (true) {
|
|
size_offset += getULEB128Size(atom.size);
|
|
for (atom.relocs.items) |relocation| {
|
|
count += 1;
|
|
const sym_loc: SymbolLoc = .{ .file = atom.file, .index = relocation.index };
|
|
const symbol_index = symbol_table.get(sym_loc).?;
|
|
try leb.writeULEB128(writer, @enumToInt(relocation.relocation_type));
|
|
const offset = atom.offset + relocation.offset + size_offset;
|
|
try leb.writeULEB128(writer, offset);
|
|
try leb.writeULEB128(writer, symbol_index);
|
|
if (relocation.relocation_type.addendIsPresent()) {
|
|
try leb.writeILEB128(writer, relocation.addend);
|
|
}
|
|
log.debug("Emit relocation: {}", .{relocation});
|
|
}
|
|
atom = if (atom.prev) |prev| wasm.getAtomPtr(prev) else break;
|
|
}
|
|
if (count == 0) return;
|
|
var buf: [5]u8 = undefined;
|
|
leb.writeUnsignedFixed(5, &buf, count);
|
|
try binary_bytes.insertSlice(reloc_start, &buf);
|
|
const size = @intCast(u32, binary_bytes.items.len - header_offset - 6);
|
|
try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
|
|
}
|
|
|
|
fn emitDataRelocations(
|
|
wasm: *Wasm,
|
|
binary_bytes: *std.ArrayList(u8),
|
|
section_index: u32,
|
|
symbol_table: std.AutoArrayHashMap(SymbolLoc, u32),
|
|
) !void {
|
|
if (wasm.data_segments.count() == 0) return;
|
|
const writer = binary_bytes.writer();
|
|
const header_offset = try reserveCustomSectionHeader(binary_bytes);
|
|
|
|
// write custom section information
|
|
const name = "reloc.DATA";
|
|
try leb.writeULEB128(writer, @intCast(u32, name.len));
|
|
try writer.writeAll(name);
|
|
try leb.writeULEB128(writer, section_index);
|
|
const reloc_start = binary_bytes.items.len;
|
|
|
|
var count: u32 = 0;
|
|
// for each atom, we calculate the uleb size and append that
|
|
var size_offset: u32 = 5; // account for code section size leb128
|
|
for (wasm.data_segments.values()) |segment_index| {
|
|
var atom: *Atom = wasm.getAtomPtr(wasm.atoms.get(segment_index).?);
|
|
while (true) {
|
|
size_offset += getULEB128Size(atom.size);
|
|
for (atom.relocs.items) |relocation| {
|
|
count += 1;
|
|
const sym_loc: SymbolLoc = .{
|
|
.file = atom.file,
|
|
.index = relocation.index,
|
|
};
|
|
const symbol_index = symbol_table.get(sym_loc).?;
|
|
try leb.writeULEB128(writer, @enumToInt(relocation.relocation_type));
|
|
const offset = atom.offset + relocation.offset + size_offset;
|
|
try leb.writeULEB128(writer, offset);
|
|
try leb.writeULEB128(writer, symbol_index);
|
|
if (relocation.relocation_type.addendIsPresent()) {
|
|
try leb.writeILEB128(writer, relocation.addend);
|
|
}
|
|
log.debug("Emit relocation: {}", .{relocation});
|
|
}
|
|
atom = if (atom.prev) |prev| wasm.getAtomPtr(prev) else break;
|
|
}
|
|
}
|
|
if (count == 0) return;
|
|
|
|
var buf: [5]u8 = undefined;
|
|
leb.writeUnsignedFixed(5, &buf, count);
|
|
try binary_bytes.insertSlice(reloc_start, &buf);
|
|
const size = @intCast(u32, binary_bytes.items.len - header_offset - 6);
|
|
try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
|
|
}
|
|
|
|
pub fn getTypeIndex(wasm: *const Wasm, func_type: std.wasm.Type) ?u32 {
|
|
var index: u32 = 0;
|
|
while (index < wasm.func_types.items.len) : (index += 1) {
|
|
if (wasm.func_types.items[index].eql(func_type)) return index;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Searches for a matching function signature. When no matching signature is found,
|
|
/// a new entry will be made. The value returned is the index of the type within `wasm.func_types`.
|
|
pub fn putOrGetFuncType(wasm: *Wasm, func_type: std.wasm.Type) !u32 {
|
|
if (wasm.getTypeIndex(func_type)) |index| {
|
|
return index;
|
|
}
|
|
|
|
// functype does not exist.
|
|
const index = @intCast(u32, wasm.func_types.items.len);
|
|
const params = try wasm.base.allocator.dupe(std.wasm.Valtype, func_type.params);
|
|
errdefer wasm.base.allocator.free(params);
|
|
const returns = try wasm.base.allocator.dupe(std.wasm.Valtype, func_type.returns);
|
|
errdefer wasm.base.allocator.free(returns);
|
|
try wasm.func_types.append(wasm.base.allocator, .{
|
|
.params = params,
|
|
.returns = returns,
|
|
});
|
|
return index;
|
|
}
|
|
|
|
/// For the given `decl_index`, stores the corresponding type representing the function signature.
|
|
/// Asserts declaration has an associated `Atom`.
|
|
/// Returns the index into the list of types.
|
|
pub fn storeDeclType(wasm: *Wasm, decl_index: Module.Decl.Index, func_type: std.wasm.Type) !u32 {
|
|
const atom_index = wasm.decls.get(decl_index).?;
|
|
const index = try wasm.putOrGetFuncType(func_type);
|
|
try wasm.atom_types.put(wasm.base.allocator, atom_index, index);
|
|
return index;
|
|
}
|