The goals of this branch are to: * compile faster when using the wasm linker and backend * enable saving compiler state by directly copying in-memory linker state to disk. * more efficient compiler memory utilization * introduce integer type safety to wasm linker code * generate better WebAssembly code * fully participate in incremental compilation * do as much work as possible outside of flush(), while continuing to do linker garbage collection. * avoid unnecessary heap allocations * avoid unnecessary indirect function calls In order to accomplish this goals, this removes the ZigObject abstraction, as well as Symbol and Atom. These abstractions resulted in overly generic code, doing unnecessary work, and needless complications that simply go away by creating a better in-memory data model and emitting more things lazily. For example, this makes wasm codegen emit MIR which is then lowered to wasm code during linking, with optimal function indexes etc, or relocations are emitted if outputting an object. Previously, this would always emit relocations, which are fully unnecessary when emitting an executable, and required all function calls to use the maximum size LEB encoding. This branch introduces the concept of the "prelink" phase which occurs after all object files have been parsed, but before any Zcu updates are sent to the linker. This allows the linker to fully parse all objects into a compact memory model, which is guaranteed to be complete when Zcu code is generated. This commit is not a complete implementation of all these goals; it is not even passing semantic analysis.
987 lines
47 KiB
Zig
987 lines
47 KiB
Zig
const Object = @This();
|
|
|
|
const Wasm = @import("../Wasm.zig");
|
|
const Alignment = Wasm.Alignment;
|
|
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const Path = std.Build.Cache.Path;
|
|
const log = std.log.scoped(.object);
|
|
const assert = std.debug.assert;
|
|
|
|
/// Wasm spec version used for this `Object`
|
|
version: u32,
|
|
/// For error reporting purposes only.
|
|
/// Name (read path) of the object or archive file.
|
|
path: Path,
|
|
/// For error reporting purposes only.
|
|
/// If this represents an object in an archive, it's the basename of the
|
|
/// object, and path refers to the archive.
|
|
archive_member_name: ?[]const u8,
|
|
/// Represents the function ID that must be called on startup.
|
|
/// This is `null` by default as runtimes may determine the startup
|
|
/// function themselves. This is essentially legacy.
|
|
start_function: Wasm.OptionalObjectFunctionIndex,
|
|
/// A slice of features that tell the linker what features are mandatory, used
|
|
/// (or therefore missing) and must generate an error when another object uses
|
|
/// features that are not supported by the other.
|
|
features: Wasm.Feature.Set,
|
|
/// Points into Wasm functions
|
|
functions: RelativeSlice,
|
|
/// Points into Wasm object_globals_imports
|
|
globals_imports: RelativeSlice,
|
|
/// Points into Wasm object_tables_imports
|
|
tables_imports: RelativeSlice,
|
|
/// Points into Wasm object_custom_segments
|
|
custom_segments: RelativeSlice,
|
|
/// For calculating local section index from `Wasm.SectionIndex`.
|
|
local_section_index_base: u32,
|
|
/// Points into Wasm object_init_funcs
|
|
init_funcs: RelativeSlice,
|
|
/// Points into Wasm object_comdats
|
|
comdats: RelativeSlice,
|
|
|
|
pub const RelativeSlice = struct {
|
|
off: u32,
|
|
len: u32,
|
|
};
|
|
|
|
pub const SegmentInfo = struct {
|
|
name: Wasm.String,
|
|
flags: Flags,
|
|
|
|
const Flags = packed struct(u32) {
|
|
/// Signals that the segment contains only null terminated strings allowing
|
|
/// the linker to perform merging.
|
|
strings: bool,
|
|
/// The segment contains thread-local data. This means that a unique copy
|
|
/// of this segment will be created for each thread.
|
|
tls: bool,
|
|
/// If the object file is included in the final link, the segment should be
|
|
/// retained in the final output regardless of whether it is used by the
|
|
/// program.
|
|
retain: bool,
|
|
alignment: Alignment,
|
|
|
|
_: u23 = 0,
|
|
};
|
|
};
|
|
|
|
pub const FunctionImport = struct {
|
|
module_name: Wasm.String,
|
|
name: Wasm.String,
|
|
function_index: ScratchSpace.FuncTypeIndex,
|
|
};
|
|
|
|
pub const DataSegmentFlags = enum(u32) { active, passive, active_memidx };
|
|
|
|
pub const SubsectionType = enum(u8) {
|
|
segment_info = 5,
|
|
init_funcs = 6,
|
|
comdat_info = 7,
|
|
symbol_table = 8,
|
|
};
|
|
|
|
pub const Symbol = struct {
|
|
flags: Wasm.SymbolFlags,
|
|
name: Wasm.OptionalString,
|
|
pointee: Pointee,
|
|
|
|
/// https://github.com/WebAssembly/tool-conventions/blob/df8d737539eb8a8f446ba5eab9dc670c40dfb81e/Linking.md#symbol-table-subsection
|
|
const Tag = enum(u8) {
|
|
function,
|
|
data,
|
|
global,
|
|
section,
|
|
event,
|
|
table,
|
|
};
|
|
|
|
const Pointee = union(enum) {
|
|
function: Wasm.ObjectFunctionIndex,
|
|
function_import: ScratchSpace.FuncImportIndex,
|
|
data: struct {
|
|
segment_index: Wasm.DataSegment.Index,
|
|
segment_offset: u32,
|
|
size: u32,
|
|
},
|
|
data_import: void,
|
|
global: Wasm.ObjectGlobalIndex,
|
|
global_import: Wasm.ObjectGlobalImportIndex,
|
|
section: Wasm.ObjectSectionIndex,
|
|
table: Wasm.ObjectTableIndex,
|
|
table_import: Wasm.ObjectTableImportIndex,
|
|
};
|
|
};
|
|
|
|
pub const ScratchSpace = struct {
|
|
func_types: std.ArrayListUnmanaged(Wasm.FunctionType.Index) = .empty,
|
|
func_type_indexes: std.ArrayListUnmanaged(FuncTypeIndex) = .empty,
|
|
func_imports: std.ArrayListUnmanaged(FunctionImport) = .empty,
|
|
symbol_table: std.ArrayListUnmanaged(Symbol) = .empty,
|
|
segment_info: std.ArrayListUnmanaged(SegmentInfo) = .empty,
|
|
|
|
/// Index into `func_imports`.
|
|
const FuncImportIndex = enum(u32) {
|
|
_,
|
|
|
|
fn ptr(index: FunctionImport, ss: *const ScratchSpace) *FunctionImport {
|
|
return &ss.func_imports.items[@intFromEnum(index)];
|
|
}
|
|
};
|
|
|
|
/// Index into `func_types`.
|
|
const FuncTypeIndex = enum(u32) {
|
|
_,
|
|
|
|
fn ptr(index: FuncTypeIndex, ss: *const ScratchSpace) *Wasm.FunctionType.Index {
|
|
return &ss.func_types.items[@intFromEnum(index)];
|
|
}
|
|
};
|
|
|
|
pub fn deinit(ss: *ScratchSpace, gpa: Allocator) void {
|
|
ss.func_types.deinit(gpa);
|
|
ss.func_type_indexes.deinit(gpa);
|
|
ss.func_imports.deinit(gpa);
|
|
ss.symbol_table.deinit(gpa);
|
|
ss.segment_info.deinit(gpa);
|
|
ss.* = undefined;
|
|
}
|
|
|
|
fn clear(ss: *ScratchSpace) void {
|
|
ss.func_types.clearRetainingCapacity();
|
|
ss.func_type_indexes.clearRetainingCapacity();
|
|
ss.func_imports.clearRetainingCapacity();
|
|
ss.symbol_table.clearRetainingCapacity();
|
|
ss.segment_info.clearRetainingCapacity();
|
|
}
|
|
};
|
|
|
|
fn parse(
|
|
wasm: *Wasm,
|
|
bytes: []const u8,
|
|
path: Path,
|
|
archive_member_name: ?[]const u8,
|
|
host_name: Wasm.String,
|
|
ss: *ScratchSpace,
|
|
must_link: bool,
|
|
gc_sections: bool,
|
|
) anyerror!Object {
|
|
const gpa = wasm.base.comp.gpa;
|
|
const diags = &wasm.base.comp.link_diags;
|
|
|
|
var pos: usize = 0;
|
|
|
|
if (!std.mem.eql(u8, bytes[0..std.wasm.magic.len], &std.wasm.magic)) return error.BadObjectMagic;
|
|
pos += std.wasm.magic.len;
|
|
|
|
const version = std.mem.readInt(u32, bytes[pos..][0..4], .little);
|
|
pos += 4;
|
|
|
|
const data_segment_start: u32 = @intCast(wasm.object_data_segments.items.len);
|
|
const custom_segment_start: u32 = @intCast(wasm.object_custom_segments.items.len);
|
|
const imports_start: u32 = @intCast(wasm.object_imports.items.len);
|
|
const functions_start: u32 = @intCast(wasm.object_functions.items.len);
|
|
const tables_start: u32 = @intCast(wasm.object_tables.items.len);
|
|
const memories_start: u32 = @intCast(wasm.object_memories.items.len);
|
|
const globals_start: u32 = @intCast(wasm.object_globals.items.len);
|
|
const init_funcs_start: u32 = @intCast(wasm.object_init_funcs.items.len);
|
|
const comdats_start: u32 = @intCast(wasm.object_comdats.items.len);
|
|
const global_imports_start: u32 = @intCast(wasm.object_global_imports.items.len);
|
|
const table_imports_start: u32 = @intCast(wasm.object_table_imports.items.len);
|
|
const local_section_index_base = wasm.object_total_sections;
|
|
const source_location: Wasm.SourceLocation = .fromObjectIndex(wasm.objects.items.len);
|
|
|
|
ss.clear();
|
|
|
|
var start_function: Wasm.OptionalObjectFunctionIndex = .none;
|
|
var opt_features: ?Wasm.Feature.Set = null;
|
|
var saw_linking_section = false;
|
|
var has_tls = false;
|
|
var local_section_index: u32 = 0;
|
|
var table_count: usize = 0;
|
|
while (pos < bytes.len) : (local_section_index += 1) {
|
|
const section_index: Wasm.SectionIndex = @enumFromInt(local_section_index_base + local_section_index);
|
|
|
|
const section_tag: std.wasm.Section = @enumFromInt(bytes[pos]);
|
|
pos += 1;
|
|
|
|
const len, pos = readLeb(u32, bytes, pos);
|
|
const section_end = pos + len;
|
|
switch (section_tag) {
|
|
.custom => {
|
|
const section_name, pos = readBytes(bytes, pos);
|
|
if (std.mem.eql(u8, section_name, "linking")) {
|
|
saw_linking_section = true;
|
|
const section_version, pos = readLeb(u32, bytes, pos);
|
|
log.debug("link meta data version: {d}", .{section_version});
|
|
if (section_version != 2) return error.UnsupportedVersion;
|
|
while (pos < section_end) {
|
|
const sub_type, pos = readLeb(u8, bytes, pos);
|
|
log.debug("found subsection: {s}", .{@tagName(@as(SubsectionType, @enumFromInt(sub_type)))});
|
|
const payload_len, pos = readLeb(u32, bytes, pos);
|
|
if (payload_len == 0) break;
|
|
|
|
const count, pos = readLeb(u32, bytes, pos);
|
|
|
|
switch (@as(SubsectionType, @enumFromInt(sub_type))) {
|
|
.segment_info => {
|
|
for (try ss.segment_info.addManyAsSlice(gpa, count)) |*segment| {
|
|
const name, pos = readBytes(bytes, pos);
|
|
const alignment, pos = readLeb(u32, bytes, pos);
|
|
const flags_u32, pos = readLeb(u32, bytes, pos);
|
|
const flags: SegmentInfo.Flags = @bitCast(flags_u32);
|
|
const tls = flags.tls or
|
|
// Supports legacy object files that specified
|
|
// being TLS by the name instead of the TLS flag.
|
|
std.mem.startsWith(u8, name, ".tdata") or
|
|
std.mem.startsWith(u8, name, ".tbss");
|
|
has_tls = has_tls or tls;
|
|
segment.* = .{
|
|
.name = try wasm.internString(name),
|
|
.flags = .{
|
|
.strings = flags.strings,
|
|
.tls = tls,
|
|
.alignment = @enumFromInt(alignment),
|
|
.no_strip = flags.retain,
|
|
},
|
|
};
|
|
}
|
|
},
|
|
.init_funcs => {
|
|
for (try wasm.object_init_funcs.addManyAsSlice(gpa, count)) |*func| {
|
|
const priority, pos = readLeb(u32, bytes, pos);
|
|
const symbol_index, pos = readLeb(u32, bytes, pos);
|
|
if (symbol_index > ss.symbol_table.items.len)
|
|
return diags.failParse(path, "init_funcs before symbol table", .{});
|
|
const sym = &ss.symbol_table.items[symbol_index];
|
|
if (sym.tag != .function) {
|
|
return diags.failParse(path, "init_func symbol '{s}' not a function", .{
|
|
wasm.stringSlice(sym.name),
|
|
});
|
|
} else if (sym.flags.undefined) {
|
|
return diags.failParse(path, "init_func symbol '{s}' is an import", .{
|
|
wasm.stringSlice(sym.name),
|
|
});
|
|
}
|
|
func.* = .{
|
|
.priority = priority,
|
|
.function_index = sym.pointee.function,
|
|
};
|
|
}
|
|
},
|
|
.comdat_info => {
|
|
for (try wasm.object_comdats.addManyAsSlice(gpa, count)) |*comdat| {
|
|
const name, pos = readBytes(bytes, pos);
|
|
const flags, pos = readLeb(u32, bytes, pos);
|
|
if (flags != 0) return error.UnexpectedComdatFlags;
|
|
const symbol_count, pos = readLeb(u32, bytes, pos);
|
|
const start_off: u32 = @intCast(wasm.object_comdat_symbols.items.len);
|
|
for (try wasm.object_comdat_symbols.addManyAsSlice(gpa, symbol_count)) |*symbol| {
|
|
const kind, pos = readEnum(Wasm.Comdat.Symbol.Type, bytes, pos);
|
|
const index, pos = readLeb(u32, bytes, pos);
|
|
if (true) @panic("TODO rebase index depending on kind");
|
|
symbol.* = .{
|
|
.kind = kind,
|
|
.index = index,
|
|
};
|
|
}
|
|
comdat.* = .{
|
|
.name = try wasm.internString(name),
|
|
.flags = flags,
|
|
.symbols = .{
|
|
.off = start_off,
|
|
.len = @intCast(wasm.object_comdat_symbols.items.len - start_off),
|
|
},
|
|
};
|
|
}
|
|
},
|
|
.symbol_table => {
|
|
for (try ss.symbol_table.addManyAsSlice(gpa, count)) |*symbol| {
|
|
const tag, pos = readEnum(Symbol.Tag, bytes, pos);
|
|
const flags, pos = readLeb(u32, bytes, pos);
|
|
symbol.* = .{
|
|
.flags = @bitCast(flags),
|
|
.name = .none,
|
|
.pointee = undefined,
|
|
};
|
|
symbol.flags.initZigSpecific(must_link, gc_sections);
|
|
|
|
switch (tag) {
|
|
.data => {
|
|
const name, pos = readBytes(bytes, pos);
|
|
symbol.name = (try wasm.internString(name)).toOptional();
|
|
if (symbol.flags.undefined) {
|
|
symbol.pointee = .data_import;
|
|
} else {
|
|
const segment_index, pos = readLeb(u32, bytes, pos);
|
|
const segment_offset, pos = readLeb(u32, bytes, pos);
|
|
const size, pos = readLeb(u32, bytes, pos);
|
|
|
|
symbol.pointee = .{ .data = .{
|
|
.index = @enumFromInt(data_segment_start + segment_index),
|
|
.segment_offset = segment_offset,
|
|
.size = size,
|
|
} };
|
|
}
|
|
},
|
|
.section => {
|
|
const local_section, pos = readLeb(u32, bytes, pos);
|
|
const section: Wasm.SectionIndex = @enumFromInt(local_section_index_base + local_section);
|
|
symbol.pointee = .{ .section = section };
|
|
},
|
|
|
|
.function => {
|
|
const local_index, pos = readLeb(u32, bytes, pos);
|
|
if (symbol.flags.undefined) {
|
|
symbol.pointee = .{ .function_import = @enumFromInt(local_index) };
|
|
if (flags.explicit_name) {
|
|
const name, pos = readBytes(bytes, pos);
|
|
symbol.name = (try wasm.internString(name)).toOptional();
|
|
}
|
|
} else {
|
|
symbol.pointee = .{ .function = @enumFromInt(functions_start + local_index) };
|
|
const name, pos = readBytes(bytes, pos);
|
|
symbol.name = (try wasm.internString(name)).toOptional();
|
|
}
|
|
},
|
|
.global => {
|
|
const local_index, pos = readLeb(u32, bytes, pos);
|
|
if (symbol.flags.undefined) {
|
|
symbol.pointee = .{ .global_import = @enumFromInt(global_imports_start + local_index) };
|
|
if (flags.explicit_name) {
|
|
const name, pos = readBytes(bytes, pos);
|
|
symbol.name = (try wasm.internString(name)).toOptional();
|
|
}
|
|
} else {
|
|
symbol.pointee = .{ .global = @enumFromInt(globals_start + local_index) };
|
|
const name, pos = readBytes(bytes, pos);
|
|
symbol.name = (try wasm.internString(name)).toOptional();
|
|
}
|
|
},
|
|
.table => {
|
|
table_count += 1;
|
|
const local_index, pos = readLeb(u32, bytes, pos);
|
|
if (symbol.flags.undefined) {
|
|
symbol.pointee = .{ .table_import = @enumFromInt(table_imports_start + local_index) };
|
|
if (flags.explicit_name) {
|
|
const name, pos = readBytes(bytes, pos);
|
|
symbol.name = (try wasm.internString(name)).toOptional();
|
|
}
|
|
} else {
|
|
symbol.pointee = .{ .table = @enumFromInt(tables_start + local_index) };
|
|
const name, pos = readBytes(bytes, pos);
|
|
symbol.name = (try wasm.internString(name)).toOptional();
|
|
}
|
|
},
|
|
else => {
|
|
log.debug("unrecognized symbol type tag: {x}", .{tag});
|
|
return error.UnrecognizedSymbolType;
|
|
},
|
|
}
|
|
log.debug("found symbol: {}", .{symbol});
|
|
}
|
|
},
|
|
}
|
|
}
|
|
} else if (std.mem.startsWith(u8, section_name, "reloc.")) {
|
|
// 'The "reloc." custom sections must come after the "linking" custom section'
|
|
if (!saw_linking_section) return error.RelocBeforeLinkingSection;
|
|
|
|
// "Relocation sections start with an identifier specifying
|
|
// which section they apply to, and must be sequenced in
|
|
// the module after that section."
|
|
// "Relocation sections can only target code, data and custom sections."
|
|
const local_section, pos = readLeb(u32, bytes, pos);
|
|
const count, pos = readLeb(u32, bytes, pos);
|
|
const section: Wasm.SectionIndex = @enumFromInt(local_section_index_base + local_section);
|
|
|
|
log.debug("found {d} relocations for section={d}", .{ count, section });
|
|
|
|
var prev_offset: u32 = 0;
|
|
try wasm.relocations.ensureUnusedCapacity(gpa, count);
|
|
for (0..count) |_| {
|
|
const tag: Wasm.Relocation.Tag = @enumFromInt(bytes[pos]);
|
|
pos += 1;
|
|
const offset, pos = readLeb(u32, bytes, pos);
|
|
const index, pos = readLeb(u32, bytes, pos);
|
|
|
|
if (offset < prev_offset)
|
|
return diags.failParse(path, "relocation entries not sorted by offset", .{});
|
|
prev_offset = offset;
|
|
|
|
switch (tag) {
|
|
.MEMORY_ADDR_LEB,
|
|
.MEMORY_ADDR_SLEB,
|
|
.MEMORY_ADDR_I32,
|
|
.MEMORY_ADDR_REL_SLEB,
|
|
.MEMORY_ADDR_LEB64,
|
|
.MEMORY_ADDR_SLEB64,
|
|
.MEMORY_ADDR_I64,
|
|
.MEMORY_ADDR_REL_SLEB64,
|
|
.MEMORY_ADDR_TLS_SLEB,
|
|
.MEMORY_ADDR_LOCREL_I32,
|
|
.MEMORY_ADDR_TLS_SLEB64,
|
|
.FUNCTION_OFFSET_I32,
|
|
.SECTION_OFFSET_I32,
|
|
=> {
|
|
const addend: i32, pos = readLeb(i32, bytes, pos);
|
|
wasm.relocations.appendAssumeCapacity(.{
|
|
.tag = tag,
|
|
.offset = offset,
|
|
.pointee = .{ .section = ss.symbol_table.items[index].pointee.section },
|
|
.addend = addend,
|
|
});
|
|
},
|
|
.TYPE_INDEX_LEB => {
|
|
wasm.relocations.appendAssumeCapacity(.{
|
|
.tag = tag,
|
|
.offset = offset,
|
|
.pointee = .{ .type_index = ss.func_types.items[index] },
|
|
.addend = undefined,
|
|
});
|
|
},
|
|
.FUNCTION_INDEX_LEB,
|
|
.GLOBAL_INDEX_LEB,
|
|
=> {
|
|
wasm.relocations.appendAssumeCapacity(.{
|
|
.tag = tag,
|
|
.offset = offset,
|
|
.pointee = .{ .symbol_name = ss.symbol_table.items[index].name.unwrap().? },
|
|
.addend = undefined,
|
|
});
|
|
},
|
|
}
|
|
}
|
|
|
|
try wasm.object_relocations_table.putNoClobber(gpa, section, .{
|
|
.off = @intCast(wasm.relocations.items.len - count),
|
|
.len = count,
|
|
});
|
|
} else if (std.mem.eql(u8, section_name, "target_features")) {
|
|
opt_features, pos = try parseFeatures(wasm, bytes, pos, path);
|
|
} else if (std.mem.startsWith(u8, section_name, ".debug")) {
|
|
const debug_content = bytes[pos..section_end];
|
|
pos = section_end;
|
|
|
|
const data_off: u32 = @enumFromInt(wasm.string_bytes.items.len);
|
|
try wasm.string_bytes.appendSlice(gpa, debug_content);
|
|
|
|
try wasm.object_custom_segments.put(gpa, section_index, .{
|
|
.data_off = data_off,
|
|
.flags = .{
|
|
.data_len = @intCast(debug_content.len),
|
|
.represented = false, // set when scanning symbol table
|
|
},
|
|
.section_name = try wasm.internString(section_name),
|
|
});
|
|
} else {
|
|
pos = section_end;
|
|
}
|
|
},
|
|
.type => {
|
|
const func_types_len, pos = readLeb(u32, bytes, pos);
|
|
for (ss.func_types.addManyAsSlice(gpa, func_types_len)) |*func_type| {
|
|
if (bytes[pos] != std.wasm.function_type) return error.ExpectedFuncType;
|
|
pos += 1;
|
|
|
|
const params, pos = readBytes(bytes, pos);
|
|
const returns, pos = readBytes(bytes, pos);
|
|
func_type.* = try wasm.addFuncType(.{
|
|
.params = .fromString(try wasm.internString(params)),
|
|
.returns = .fromString(try wasm.internString(returns)),
|
|
});
|
|
}
|
|
},
|
|
.import => {
|
|
const imports_len, pos = readLeb(u32, bytes, pos);
|
|
for (0..imports_len) |_| {
|
|
const module_name, pos = readBytes(bytes, pos);
|
|
const name, pos = readBytes(bytes, pos);
|
|
const kind, pos = readEnum(std.wasm.ExternalKind, bytes, pos);
|
|
const interned_module_name = try wasm.internString(module_name);
|
|
const interned_name = try wasm.internString(name);
|
|
switch (kind) {
|
|
.function => {
|
|
const function, pos = readLeb(u32, bytes, pos);
|
|
try ss.function_imports.append(gpa, .{
|
|
.module_name = interned_module_name,
|
|
.name = interned_name,
|
|
.index = function,
|
|
});
|
|
},
|
|
.memory => {
|
|
const limits, pos = readLimits(bytes, pos);
|
|
try wasm.object_memory_imports.append(gpa, .{
|
|
.module_name = interned_module_name,
|
|
.name = interned_name,
|
|
.limits_min = limits.min,
|
|
.limits_max = limits.max,
|
|
.limits_has_max = limits.flags.has_max,
|
|
.limits_is_shared = limits.flags.is_shared,
|
|
});
|
|
},
|
|
.global => {
|
|
const valtype, pos = readEnum(std.wasm.Valtype, bytes, pos);
|
|
const mutable = bytes[pos] == 0x01;
|
|
pos += 1;
|
|
try wasm.object_global_imports.append(gpa, .{
|
|
.module_name = interned_module_name,
|
|
.name = interned_name,
|
|
.mutable = mutable,
|
|
.valtype = valtype,
|
|
});
|
|
},
|
|
.table => {
|
|
const reftype, pos = readEnum(std.wasm.RefType, bytes, pos);
|
|
const limits, pos = readLimits(bytes, pos);
|
|
try wasm.object_table_imports.append(gpa, .{
|
|
.module_name = interned_module_name,
|
|
.name = interned_name,
|
|
.limits_min = limits.min,
|
|
.limits_max = limits.max,
|
|
.limits_has_max = limits.flags.has_max,
|
|
.limits_is_shared = limits.flags.is_shared,
|
|
.reftype = reftype,
|
|
});
|
|
},
|
|
}
|
|
}
|
|
},
|
|
.function => {
|
|
const functions_len, pos = readLeb(u32, bytes, pos);
|
|
for (try ss.func_type_indexes.addManyAsSlice(gpa, functions_len)) |*func_type_index| {
|
|
func_type_index.*, pos = readLeb(u32, bytes, pos);
|
|
}
|
|
},
|
|
.table => {
|
|
const tables_len, pos = readLeb(u32, bytes, pos);
|
|
for (try wasm.object_tables.addManyAsSlice(gpa, tables_len)) |*table| {
|
|
const reftype, pos = readEnum(std.wasm.RefType, bytes, pos);
|
|
const limits, pos = readLimits(bytes, pos);
|
|
table.* = .{
|
|
.reftype = reftype,
|
|
.limits = limits,
|
|
};
|
|
}
|
|
},
|
|
.memory => {
|
|
const memories_len, pos = readLeb(u32, bytes, pos);
|
|
for (try wasm.object_memories.addManyAsSlice(gpa, memories_len)) |*memory| {
|
|
const limits, pos = readLimits(bytes, pos);
|
|
memory.* = .{ .limits = limits };
|
|
}
|
|
},
|
|
.global => {
|
|
const globals_len, pos = readLeb(u32, bytes, pos);
|
|
for (try wasm.object_globals.addManyAsSlice(gpa, globals_len)) |*global| {
|
|
const valtype, pos = readEnum(std.wasm.Valtype, bytes, pos);
|
|
const mutable = bytes[pos] == 0x01;
|
|
pos += 1;
|
|
const expr, pos = try readInit(wasm, bytes, pos);
|
|
global.* = .{
|
|
.valtype = valtype,
|
|
.mutable = mutable,
|
|
.expr = expr,
|
|
};
|
|
}
|
|
},
|
|
.@"export" => {
|
|
const exports_len, pos = readLeb(u32, bytes, pos);
|
|
// TODO: instead, read into scratch space, and then later
|
|
// add this data as if it were extra symbol table entries,
|
|
// but allow merging with existing symbol table data if the name matches.
|
|
for (try wasm.object_exports.addManyAsSlice(gpa, exports_len)) |*exp| {
|
|
const name, pos = readBytes(bytes, pos);
|
|
const kind: std.wasm.ExternalKind = @enumFromInt(bytes[pos]);
|
|
pos += 1;
|
|
const index, pos = readLeb(u32, bytes, pos);
|
|
const rebased_index = index + switch (kind) {
|
|
.function => functions_start,
|
|
.table => tables_start,
|
|
.memory => memories_start,
|
|
.global => globals_start,
|
|
};
|
|
exp.* = .{
|
|
.name = try wasm.internString(name),
|
|
.kind = kind,
|
|
.index = rebased_index,
|
|
};
|
|
}
|
|
},
|
|
.start => {
|
|
const index, pos = readLeb(u32, bytes, pos);
|
|
start_function = @enumFromInt(functions_start + index);
|
|
},
|
|
.element => {
|
|
log.warn("unimplemented: element section in {}", .{path});
|
|
pos = section_end;
|
|
},
|
|
.code => {
|
|
const start = pos;
|
|
const count, pos = readLeb(u32, bytes, pos);
|
|
for (try wasm.object_functions.addManyAsSlice(gpa, count)) |*elem| {
|
|
const code_len, pos = readLeb(u32, bytes, pos);
|
|
const offset: u32 = @intCast(pos - start);
|
|
const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..code_len]);
|
|
pos += code_len;
|
|
elem.* = .{
|
|
.flags = .{}, // populated from symbol table
|
|
.name = .none, // populated from symbol table
|
|
.type_index = undefined, // populated from func_types
|
|
.code = payload,
|
|
.offset = offset,
|
|
.section_index = section_index,
|
|
.source_location = source_location,
|
|
};
|
|
}
|
|
},
|
|
.data => {
|
|
const start = pos;
|
|
const count, pos = readLeb(u32, bytes, pos);
|
|
for (try wasm.object_data_segments.addManyAsSlice(gpa, count)) |*elem| {
|
|
const flags, pos = readEnum(DataSegmentFlags, bytes, pos);
|
|
if (flags == .active_memidx) {
|
|
const memidx, pos = readLeb(u32, bytes, pos);
|
|
if (memidx != 0) return diags.failParse(path, "data section uses mem index {d}", .{memidx});
|
|
}
|
|
//const expr, pos = if (flags != .passive) try readInit(wasm, bytes, pos) else .{ .none, pos };
|
|
if (flags != .passive) pos = try skipInit(bytes, pos);
|
|
const data_len, pos = readLeb(u32, bytes, pos);
|
|
const segment_offset: u32 = @intCast(pos - start);
|
|
const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..data_len]);
|
|
pos += data_len;
|
|
elem.* = .{
|
|
.payload = payload,
|
|
.segment_offset = segment_offset,
|
|
.section_index = section_index,
|
|
.name = .none, // Populated from symbol table
|
|
.flags = .{}, // Populated from symbol table and segment_info
|
|
};
|
|
}
|
|
},
|
|
else => pos = section_end,
|
|
}
|
|
if (pos != section_end) return error.MalformedSection;
|
|
}
|
|
if (!saw_linking_section) return error.MissingLinkingSection;
|
|
|
|
wasm.object_total_sections = local_section_index_base + local_section_index;
|
|
|
|
if (has_tls) {
|
|
const cpu_features = wasm.base.comp.root_mod.resolved_target.result.cpu.features;
|
|
if (!std.Target.wasm.featureSetHas(cpu_features, .atomics))
|
|
return diags.failParse(path, "object has TLS segment but target CPU feature atomics is disabled", .{});
|
|
if (!std.Target.wasm.featureSetHas(cpu_features, .bulk_memory))
|
|
return diags.failParse(path, "object has TLS segment but target CPU feature bulk_memory is disabled", .{});
|
|
}
|
|
|
|
const features = opt_features orelse return error.MissingFeatures;
|
|
if (true) @panic("iterate features, match against target features");
|
|
|
|
// Apply function type information.
|
|
for (ss.func_types.items, wasm.object_functions.items[functions_start..]) |func_type, *func| {
|
|
func.type_index = func_type;
|
|
}
|
|
|
|
// Apply symbol table information.
|
|
for (ss.symbol_table.items) |symbol| switch (symbol.pointee) {
|
|
.function_import => |index| {
|
|
const ptr = index.ptr(ss);
|
|
const name = symbol.name.unwrap().?;
|
|
if (symbol.flags.binding == .local) {
|
|
diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)});
|
|
continue;
|
|
}
|
|
const gop = try wasm.object_function_imports.getOrPut(gpa, name);
|
|
const fn_ty_index = ptr.function_index.ptr(ss).*;
|
|
if (gop.found_existing) {
|
|
if (gop.value_ptr.type != fn_ty_index) {
|
|
var err = try diags.addErrorWithNotes(2);
|
|
try err.addMsg("symbol '{s}' mismatching function signatures", .{name.slice(wasm)});
|
|
try err.addSrcNote(gop.value_ptr.source_location, "imported as {} here", .{gop.value_ptr.type.fmt(wasm)});
|
|
try err.addSrcNote(source_location, "imported as {} here", .{fn_ty_index.fmt(wasm)});
|
|
continue;
|
|
}
|
|
if (gop.value_ptr.module_name != ptr.module_name) {
|
|
var err = try diags.addErrorWithNotes(2);
|
|
try err.addMsg("symbol '{s}' mismatching module names", .{name.slice(wasm)});
|
|
try err.addSrcNote(gop.value_ptr.source_location, "module '{s}' here", .{gop.value_ptr.module_name.slice(wasm)});
|
|
try err.addSrcNote(source_location, "module '{s}' here", .{ptr.module_name.slice(wasm)});
|
|
continue;
|
|
}
|
|
if (symbol.flags.binding == .strong) gop.value_ptr.flags.binding = .strong;
|
|
if (!symbol.flags.visibility_hidden) gop.value_ptr.flags.visibility_hidden = false;
|
|
if (symbol.flags.no_strip) gop.value_ptr.flags.no_strip = true;
|
|
} else {
|
|
gop.value_ptr.* = .{
|
|
.flags = symbol.flags,
|
|
.module_name = ptr.module_name,
|
|
.source_location = source_location,
|
|
.resolution = .unresolved,
|
|
.type = fn_ty_index,
|
|
};
|
|
}
|
|
},
|
|
.function => |index| {
|
|
assert(!symbol.flags.undefined);
|
|
const ptr = index.ptr();
|
|
ptr.name = symbol.name;
|
|
ptr.flags = symbol.flags;
|
|
if (symbol.flags.binding == .local) continue; // No participation in symbol resolution.
|
|
const name = symbol.name.unwrap().?;
|
|
const gop = try wasm.object_function_imports.getOrPut(gpa, name);
|
|
if (gop.found_existing) {
|
|
if (gop.value_ptr.type != ptr.type_index) {
|
|
var err = try diags.addErrorWithNotes(2);
|
|
try err.addMsg("function signature mismatch: {s}", .{name.slice(wasm)});
|
|
try err.addSrcNote(gop.value_ptr.source_location, "exported as {} here", .{ptr.type_index.fmt(wasm)});
|
|
const word = if (gop.value_ptr.resolution == .none) "imported" else "exported";
|
|
try err.addSrcNote(source_location, "{s} as {} here", .{ word, gop.value_ptr.type.fmt(wasm) });
|
|
continue;
|
|
}
|
|
if (gop.value_ptr.resolution == .none or gop.value_ptr.flags.binding == .weak) {
|
|
// Intentional: if they're both weak, take the last one.
|
|
gop.value_ptr.source_location = source_location;
|
|
gop.value_ptr.module_name = host_name;
|
|
gop.value_ptr.resolution = .fromObjectFunction(index);
|
|
continue;
|
|
}
|
|
var err = try diags.addErrorWithNotes(2);
|
|
try err.addMsg("symbol collision: {s}", .{name.slice(wasm)});
|
|
try err.addSrcNote(gop.value_ptr.source_location, "exported as {} here", .{ptr.type_index.fmt(wasm)});
|
|
try err.addSrcNote(source_location, "exported as {} here", .{gop.value_ptr.type.fmt(wasm)});
|
|
continue;
|
|
} else {
|
|
gop.value_ptr.* = .{
|
|
.flags = symbol.flags,
|
|
.module_name = host_name,
|
|
.source_location = source_location,
|
|
.resolution = .fromObjectFunction(index),
|
|
.type = ptr.type_index,
|
|
};
|
|
}
|
|
},
|
|
|
|
inline .global, .global_import, .table, .table_import => |i| {
|
|
const ptr = i.ptr(wasm);
|
|
ptr.name = symbol.name;
|
|
ptr.flags = symbol.flags;
|
|
if (symbol.flags.undefined and symbol.flags.binding == .local) {
|
|
const name = wasm.stringSlice(ptr.name.unwrap().?);
|
|
diags.addParseError(path, "local symbol '{s}' references import", .{name});
|
|
}
|
|
},
|
|
.section => |i| {
|
|
// Name is provided by the section directly; symbol table does not have it.
|
|
const ptr = i.ptr(wasm);
|
|
ptr.flags = symbol.flags;
|
|
if (symbol.flags.undefined and symbol.flags.binding == .local) {
|
|
const name = wasm.stringSlice(ptr.name);
|
|
diags.addParseError(path, "local symbol '{s}' references import", .{name});
|
|
}
|
|
},
|
|
.data_import => {
|
|
const name = symbol.name.unwrap().?;
|
|
log.warn("TODO data import '{s}'", .{name.slice(wasm)});
|
|
},
|
|
.data => |data| {
|
|
const ptr = data.ptr(wasm);
|
|
const is_passive = ptr.flags.is_passive;
|
|
ptr.name = symbol.name;
|
|
ptr.flags = symbol.flags;
|
|
ptr.flags.is_passive = is_passive;
|
|
ptr.offset = data.segment_offset;
|
|
ptr.size = data.size;
|
|
},
|
|
};
|
|
|
|
// Apply segment_info.
|
|
for (wasm.object_data_segments.items[data_segment_start..], ss.segment_info.items) |*data, info| {
|
|
data.name = info.name.toOptional();
|
|
data.flags.strings = info.flags.strings;
|
|
data.flags.tls = data.flags.tls or info.flags.tls;
|
|
data.flags.no_strip = info.flags.retain;
|
|
data.flags.alignment = info.flags.alignment;
|
|
if (data.flags.undefined and data.flags.binding == .local) {
|
|
const name = wasm.stringSlice(info.name);
|
|
diags.addParseError(path, "local symbol '{s}' references import", .{name});
|
|
}
|
|
}
|
|
|
|
// Check for indirect function table in case of an MVP object file.
|
|
legacy_indirect_function_table: {
|
|
const table_imports = wasm.object_table_imports.items[table_imports_start..];
|
|
// If there is a symbol for each import table, this is not a legacy object file.
|
|
if (table_imports.len == table_count) break :legacy_indirect_function_table;
|
|
if (table_count != 0) {
|
|
return diags.failParse(path, "expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{
|
|
table_imports.len, table_count,
|
|
});
|
|
}
|
|
// MVP object files cannot have any table definitions, only
|
|
// imports (for the indirect function table).
|
|
const tables = wasm.object_tables.items[tables_start..];
|
|
if (tables.len > 0) {
|
|
return diags.failParse(path, "table definition without representing table symbols", .{});
|
|
}
|
|
if (table_imports.len != 1) {
|
|
return diags.failParse(path, "found more than one table import, but no representing table symbols", .{});
|
|
}
|
|
const table_import_name = table_imports[0].name;
|
|
if (table_import_name != wasm.preloaded_strings.__indirect_function_table) {
|
|
return diags.failParse(path, "non-indirect function table import '{s}' is missing a corresponding symbol", .{
|
|
wasm.stringSlice(table_import_name),
|
|
});
|
|
}
|
|
table_imports[0].flags = .{
|
|
.undefined = true,
|
|
.no_strip = true,
|
|
};
|
|
}
|
|
|
|
for (wasm.object_init_funcs.items[init_funcs_start..]) |init_func| {
|
|
const func = init_func.function_index.ptr(wasm);
|
|
const params = func.type_index.ptr(wasm).params.slice(wasm);
|
|
if (params.len != 0) diags.addError("constructor function '{s}' has non-empty parameter list", .{
|
|
func.name.slice(wasm).?,
|
|
});
|
|
}
|
|
|
|
return .{
|
|
.version = version,
|
|
.path = path,
|
|
.archive_member_name = archive_member_name,
|
|
.start_function = start_function,
|
|
.features = features,
|
|
.imports = .{
|
|
.off = imports_start,
|
|
.len = @intCast(wasm.object_imports.items.len - imports_start),
|
|
},
|
|
.functions = .{
|
|
.off = functions_start,
|
|
.len = @intCast(wasm.functions.items.len - functions_start),
|
|
},
|
|
.tables = .{
|
|
.off = tables_start,
|
|
.len = @intCast(wasm.object_tables.items.len - tables_start),
|
|
},
|
|
.memories = .{
|
|
.off = memories_start,
|
|
.len = @intCast(wasm.object_memories.items.len - memories_start),
|
|
},
|
|
.globals = .{
|
|
.off = globals_start,
|
|
.len = @intCast(wasm.object_globals.items.len - globals_start),
|
|
},
|
|
.init_funcs = .{
|
|
.off = init_funcs_start,
|
|
.len = @intCast(wasm.object_init_funcs.items.len - init_funcs_start),
|
|
},
|
|
.comdats = .{
|
|
.off = comdats_start,
|
|
.len = @intCast(wasm.object_comdats.items.len - comdats_start),
|
|
},
|
|
.custom_segments = .{
|
|
.off = custom_segment_start,
|
|
.len = @intCast(wasm.object_custom_segments.items.len - custom_segment_start),
|
|
},
|
|
.local_section_index_base = local_section_index_base,
|
|
};
|
|
}
|
|
|
|
/// Based on the "features" custom section, parses it into a list of
|
|
/// features that tell the linker what features were enabled and may be mandatory
|
|
/// to be able to link.
|
|
fn parseFeatures(
|
|
wasm: *Wasm,
|
|
bytes: []const u8,
|
|
start_pos: usize,
|
|
path: Path,
|
|
) error{ OutOfMemory, LinkFailure }!struct { Wasm.Feature.Set, usize } {
|
|
const gpa = wasm.base.comp.gpa;
|
|
const diags = &wasm.base.comp.link_diags;
|
|
const features_len, var pos = readLeb(u32, bytes, start_pos);
|
|
// This temporary allocation could be avoided by using the string_bytes buffer as a scratch space.
|
|
const feature_buffer = try gpa.alloc(Wasm.Feature, features_len);
|
|
defer gpa.free(feature_buffer);
|
|
for (feature_buffer) |*feature| {
|
|
const prefix: Wasm.Feature.Prefix = switch (bytes[pos]) {
|
|
'-' => .@"-",
|
|
'+' => .@"+",
|
|
'=' => .@"=",
|
|
else => return error.InvalidFeaturePrefix,
|
|
};
|
|
pos += 1;
|
|
const name, pos = readBytes(bytes, pos);
|
|
const tag = std.meta.stringToEnum(Wasm.Feature.Tag, name) orelse {
|
|
return diags.failParse(path, "unrecognized wasm feature in object: {s}", .{name});
|
|
};
|
|
feature.* = .{
|
|
.prefix = prefix,
|
|
.tag = tag,
|
|
};
|
|
}
|
|
std.mem.sortUnstable(Wasm.Feature, feature_buffer, {}, Wasm.Feature.lessThan);
|
|
|
|
return .{
|
|
.fromString(try wasm.internString(@bitCast(feature_buffer))),
|
|
pos,
|
|
};
|
|
}
|
|
|
|
fn readLeb(comptime T: type, bytes: []const u8, pos: usize) struct { T, usize } {
|
|
var fbr = std.io.fixedBufferStream(bytes[pos..]);
|
|
return .{
|
|
switch (@typeInfo(T).int.signedness) {
|
|
.signed => std.leb.readIleb128(T, fbr.reader()) catch unreachable,
|
|
.unsigned => std.leb.readUleb128(T, fbr.reader()) catch unreachable,
|
|
},
|
|
pos + fbr.pos,
|
|
};
|
|
}
|
|
|
|
fn readBytes(bytes: []const u8, start_pos: usize) struct { []const u8, usize } {
|
|
const len, const pos = readLeb(u32, bytes, start_pos);
|
|
return .{
|
|
bytes[pos..][0..len],
|
|
pos + len,
|
|
};
|
|
}
|
|
|
|
fn readEnum(comptime T: type, bytes: []const u8, pos: usize) struct { T, usize } {
|
|
const Tag = @typeInfo(T).@"enum".tag_type;
|
|
const int, const new_pos = readLeb(Tag, bytes, pos);
|
|
return .{ @enumFromInt(int), new_pos };
|
|
}
|
|
|
|
fn readLimits(bytes: []const u8, start_pos: usize) struct { std.wasm.Limits, usize } {
|
|
const flags = bytes[start_pos];
|
|
const min, const max_pos = readLeb(u32, bytes, start_pos + 1);
|
|
const max, const end_pos = if (flags.has_max) readLeb(u32, bytes, max_pos) else .{ undefined, max_pos };
|
|
return .{ .{
|
|
.flags = flags,
|
|
.min = min,
|
|
.max = max,
|
|
}, end_pos };
|
|
}
|
|
|
|
fn readInit(wasm: *Wasm, bytes: []const u8, pos: usize) !struct { Wasm.Expr, usize } {
|
|
const end_pos = skipInit(bytes, pos); // one after the end opcode
|
|
return .{ try wasm.addExpr(bytes[pos..end_pos]), end_pos };
|
|
}
|
|
|
|
fn skipInit(bytes: []const u8, pos: usize) !usize {
|
|
const opcode = bytes[pos];
|
|
const end_pos = switch (@as(std.wasm.Opcode, @enumFromInt(opcode))) {
|
|
.i32_const => readLeb(i32, bytes, pos + 1)[1],
|
|
.i64_const => readLeb(i64, bytes, pos + 1)[1],
|
|
.f32_const => pos + 5,
|
|
.f64_const => pos + 9,
|
|
.global_get => readLeb(u32, bytes, pos + 1)[1],
|
|
else => return error.InvalidInitOpcode,
|
|
};
|
|
if (readEnum(std.wasm.Opcode, bytes, end_pos) != .end) return error.InitExprMissingEnd;
|
|
return end_pos + 1;
|
|
}
|