zig/src/link/Wasm/Object.zig

1518 lines
74 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: Wasm.OptionalString,
/// 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.object_functions`
functions: RelativeSlice,
/// Points into `Wasm.object_function_imports`
function_imports: RelativeSlice,
/// Points into `Wasm.object_global_imports`
global_imports: RelativeSlice,
/// Points into `Wasm.object_table_imports`
table_imports: RelativeSlice,
// Points into `Wasm.object_data_imports`
data_imports: RelativeSlice,
/// Points into Wasm object_custom_segments
custom_segments: RelativeSlice,
/// Points into Wasm object_init_funcs
init_funcs: RelativeSlice,
/// Points into Wasm object_comdats
comdats: RelativeSlice,
/// Guaranteed to be non-null when functions has nonzero length.
code_section_index: ?Wasm.ObjectSectionIndex,
/// Guaranteed to be non-null when globals has nonzero length.
global_section_index: ?Wasm.ObjectSectionIndex,
/// Guaranteed to be non-null when data segments has nonzero length.
data_section_index: ?Wasm.ObjectSectionIndex,
pub const RelativeSlice = struct {
off: u32,
len: u32,
};
pub const SegmentInfo = struct {
name: Wasm.String,
flags: Flags,
/// Matches the ABI.
pub 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 GlobalImport = struct {
module_name: Wasm.String,
name: Wasm.String,
valtype: std.wasm.Valtype,
mutable: bool,
};
pub const TableImport = struct {
module_name: Wasm.String,
name: Wasm.String,
limits_min: u32,
limits_max: u32,
limits_has_max: bool,
limits_is_shared: bool,
ref_type: std.wasm.RefType,
};
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,
};
/// Specified by https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md
pub const RelocationType = enum(u8) {
function_index_leb = 0,
table_index_sleb = 1,
table_index_i32 = 2,
memory_addr_leb = 3,
memory_addr_sleb = 4,
memory_addr_i32 = 5,
type_index_leb = 6,
global_index_leb = 7,
function_offset_i32 = 8,
section_offset_i32 = 9,
event_index_leb = 10,
memory_addr_rel_sleb = 11,
table_index_rel_sleb = 12,
global_index_i32 = 13,
memory_addr_leb64 = 14,
memory_addr_sleb64 = 15,
memory_addr_i64 = 16,
memory_addr_rel_sleb64 = 17,
table_index_sleb64 = 18,
table_index_i64 = 19,
table_number_leb = 20,
memory_addr_tls_sleb = 21,
function_offset_i64 = 22,
memory_addr_locrel_i32 = 23,
table_index_rel_sleb64 = 24,
memory_addr_tls_sleb64 = 25,
function_index_i32 = 26,
};
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: Wasm.ObjectData.Index,
data_import: void,
global: Wasm.ObjectGlobalIndex,
global_import: ScratchSpace.GlobalImportIndex,
section: Wasm.ObjectSectionIndex,
table: Wasm.ObjectTableIndex,
table_import: ScratchSpace.TableImportIndex,
};
};
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,
global_imports: std.ArrayListUnmanaged(GlobalImport) = .empty,
table_imports: std.ArrayListUnmanaged(TableImport) = .empty,
symbol_table: std.ArrayListUnmanaged(Symbol) = .empty,
segment_info: std.ArrayListUnmanaged(SegmentInfo) = .empty,
exports: std.ArrayListUnmanaged(Export) = .empty,
const Export = struct {
name: Wasm.String,
pointee: Pointee,
const Pointee = union(std.wasm.ExternalKind) {
function: Wasm.ObjectFunctionIndex,
table: Wasm.ObjectTableIndex,
memory: Wasm.ObjectMemory.Index,
global: Wasm.ObjectGlobalIndex,
};
};
/// Index into `func_imports`.
const FuncImportIndex = enum(u32) {
_,
fn ptr(index: FuncImportIndex, ss: *const ScratchSpace) *FunctionImport {
return &ss.func_imports.items[@intFromEnum(index)];
}
};
/// Index into `global_imports`.
const GlobalImportIndex = enum(u32) {
_,
fn ptr(index: GlobalImportIndex, ss: *const ScratchSpace) *GlobalImport {
return &ss.global_imports.items[@intFromEnum(index)];
}
};
/// Index into `table_imports`.
const TableImportIndex = enum(u32) {
_,
fn ptr(index: TableImportIndex, ss: *const ScratchSpace) *TableImport {
return &ss.table_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.exports.deinit(gpa);
ss.func_types.deinit(gpa);
ss.func_type_indexes.deinit(gpa);
ss.func_imports.deinit(gpa);
ss.global_imports.deinit(gpa);
ss.table_imports.deinit(gpa);
ss.symbol_table.deinit(gpa);
ss.segment_info.deinit(gpa);
ss.* = undefined;
}
fn clear(ss: *ScratchSpace) void {
ss.exports.clearRetainingCapacity();
ss.func_types.clearRetainingCapacity();
ss.func_type_indexes.clearRetainingCapacity();
ss.func_imports.clearRetainingCapacity();
ss.global_imports.clearRetainingCapacity();
ss.table_imports.clearRetainingCapacity();
ss.symbol_table.clearRetainingCapacity();
ss.segment_info.clearRetainingCapacity();
}
};
pub fn parse(
wasm: *Wasm,
bytes: []const u8,
path: Path,
archive_member_name: ?[]const u8,
host_name: Wasm.OptionalString,
ss: *ScratchSpace,
must_link: bool,
gc_sections: bool,
) anyerror!Object {
const comp = wasm.base.comp;
const gpa = comp.gpa;
const diags = &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.entries.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 function_imports_start: u32 = @intCast(wasm.object_function_imports.entries.len);
const global_imports_start: u32 = @intCast(wasm.object_global_imports.entries.len);
const table_imports_start: u32 = @intCast(wasm.object_table_imports.entries.len);
const data_imports_start: u32 = @intCast(wasm.object_data_imports.entries.len);
const local_section_index_base = wasm.object_total_sections;
const object_index: Wasm.ObjectIndex = @enumFromInt(wasm.objects.items.len);
const source_location: Wasm.SourceLocation = .fromObject(object_index, wasm);
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 table_import_symbol_count: usize = 0;
var code_section_index: ?Wasm.ObjectSectionIndex = null;
var global_section_index: ?Wasm.ObjectSectionIndex = null;
var data_section_index: ?Wasm.ObjectSectionIndex = null;
while (pos < bytes.len) : (wasm.object_total_sections += 1) {
const section_index: Wasm.ObjectSectionIndex = @enumFromInt(wasm.object_total_sections);
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),
.retain = 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.pointee != .function) {
return diags.failParse(path, "init_func symbol '{s}' not a function", .{
sym.name.slice(wasm).?,
});
} else if (sym.flags.undefined) {
return diags.failParse(path, "init_func symbol '{s}' is an import", .{
sym.name.slice(wasm).?,
});
}
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.len);
try wasm.object_comdat_symbols.ensureUnusedCapacity(gpa, symbol_count);
for (0..symbol_count) |_| {
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");
wasm.object_comdat_symbols.appendAssumeCapacity(.{
.kind = kind,
.index = index,
});
}
comdat.* = .{
.name = try wasm.internString(name),
.flags = flags,
.symbols = .{
.off = start_off,
.len = @intCast(wasm.object_comdat_symbols.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);
const interned_name = try wasm.internString(name);
symbol.name = interned_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);
try wasm.object_datas.append(gpa, .{
.segment = @enumFromInt(data_segment_start + segment_index),
.offset = segment_offset,
.size = size,
.name = interned_name,
.flags = symbol.flags,
});
symbol.pointee = .{
.data = @enumFromInt(wasm.object_datas.items.len - 1),
};
}
},
.section => {
const local_section, pos = readLeb(u32, bytes, pos);
const section: Wasm.ObjectSectionIndex = @enumFromInt(local_section_index_base + local_section);
symbol.pointee = .{ .section = section };
},
.function => {
const local_index, pos = readLeb(u32, bytes, pos);
if (symbol.flags.undefined) {
const function_import: ScratchSpace.FuncImportIndex = @enumFromInt(local_index);
symbol.pointee = .{ .function_import = function_import };
if (symbol.flags.explicit_name) {
const name, pos = readBytes(bytes, pos);
symbol.name = (try wasm.internString(name)).toOptional();
} else {
symbol.name = function_import.ptr(ss).name.toOptional();
}
} else {
symbol.pointee = .{ .function = @enumFromInt(functions_start + (local_index - ss.func_imports.items.len)) };
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) {
const global_import: ScratchSpace.GlobalImportIndex = @enumFromInt(local_index);
symbol.pointee = .{ .global_import = global_import };
if (symbol.flags.explicit_name) {
const name, pos = readBytes(bytes, pos);
symbol.name = (try wasm.internString(name)).toOptional();
} else {
symbol.name = global_import.ptr(ss).name.toOptional();
}
} else {
symbol.pointee = .{ .global = @enumFromInt(globals_start + (local_index - ss.global_imports.items.len)) };
const name, pos = readBytes(bytes, pos);
symbol.name = (try wasm.internString(name)).toOptional();
}
},
.table => {
const local_index, pos = readLeb(u32, bytes, pos);
if (symbol.flags.undefined) {
table_import_symbol_count += 1;
const table_import: ScratchSpace.TableImportIndex = @enumFromInt(local_index);
symbol.pointee = .{ .table_import = table_import };
if (symbol.flags.explicit_name) {
const name, pos = readBytes(bytes, pos);
symbol.name = (try wasm.internString(name)).toOptional();
} else {
symbol.name = table_import.ptr(ss).name.toOptional();
}
} else {
symbol.pointee = .{ .table = @enumFromInt(tables_start + (local_index - ss.table_imports.items.len)) };
const name, pos = readBytes(bytes, pos);
symbol.name = (try wasm.internString(name)).toOptional();
}
},
else => {
log.debug("unrecognized symbol type tag: {x}", .{@intFromEnum(tag)});
return error.UnrecognizedSymbolType;
},
}
}
},
}
}
} 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.ObjectSectionIndex = @enumFromInt(local_section_index_base + local_section);
log.debug("found {d} relocations for section={d}", .{ count, section });
var prev_offset: u32 = 0;
try wasm.object_relocations.ensureUnusedCapacity(gpa, count);
for (0..count) |_| {
const tag: RelocationType = @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;
const sym = &ss.symbol_table.items[index];
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,
=> {
const addend: i32, pos = readLeb(i32, bytes, pos);
wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
.data => |data| .{
.tag = .fromType(tag),
.offset = offset,
.pointee = .{ .data = data },
.addend = addend,
},
.data_import => .{
.tag = .fromTypeImport(tag),
.offset = offset,
.pointee = .{ .symbol_name = sym.name.unwrap().? },
.addend = addend,
},
else => unreachable,
});
},
.function_offset_i32, .function_offset_i64 => {
const addend: i32, pos = readLeb(i32, bytes, pos);
wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
.function => .{
.tag = .fromType(tag),
.offset = offset,
.pointee = .{ .function = sym.pointee.function },
.addend = addend,
},
.function_import => .{
.tag = .fromTypeImport(tag),
.offset = offset,
.pointee = .{ .symbol_name = sym.name.unwrap().? },
.addend = addend,
},
else => unreachable,
});
},
.section_offset_i32 => {
const addend: i32, pos = readLeb(i32, bytes, pos);
wasm.object_relocations.appendAssumeCapacity(.{
.tag = .section_offset_i32,
.offset = offset,
.pointee = .{ .section = sym.pointee.section },
.addend = addend,
});
},
.type_index_leb => {
wasm.object_relocations.appendAssumeCapacity(.{
.tag = .type_index_leb,
.offset = offset,
.pointee = .{ .type_index = ss.func_types.items[index] },
.addend = undefined,
});
},
.function_index_leb,
.function_index_i32,
.table_index_sleb,
.table_index_i32,
.table_index_sleb64,
.table_index_i64,
.table_index_rel_sleb,
.table_index_rel_sleb64,
=> {
wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
.function => .{
.tag = .fromType(tag),
.offset = offset,
.pointee = .{ .function = sym.pointee.function },
.addend = undefined,
},
.function_import => .{
.tag = .fromTypeImport(tag),
.offset = offset,
.pointee = .{ .symbol_name = sym.name.unwrap().? },
.addend = undefined,
},
else => unreachable,
});
},
.global_index_leb, .global_index_i32 => {
wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
.global => .{
.tag = .fromType(tag),
.offset = offset,
.pointee = .{ .global = sym.pointee.global },
.addend = undefined,
},
.global_import => .{
.tag = .fromTypeImport(tag),
.offset = offset,
.pointee = .{ .symbol_name = sym.name.unwrap().? },
.addend = undefined,
},
else => unreachable,
});
},
.table_number_leb => {
wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
.table => .{
.tag = .fromType(tag),
.offset = offset,
.pointee = .{ .table = sym.pointee.table },
.addend = undefined,
},
.table_import => .{
.tag = .fromTypeImport(tag),
.offset = offset,
.pointee = .{ .symbol_name = sym.name.unwrap().? },
.addend = undefined,
},
else => unreachable,
});
},
.event_index_leb => return diags.failParse(path, "unsupported relocation: R_WASM_EVENT_INDEX_LEB", .{}),
}
}
try wasm.object_relocations_table.putNoClobber(gpa, section, .{
.off = @intCast(wasm.object_relocations.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 = @intCast(wasm.string_bytes.items.len);
try wasm.string_bytes.appendSlice(gpa, debug_content);
try wasm.object_custom_segments.put(gpa, section_index, .{
.payload = .{
.off = @enumFromInt(data_off),
.len = @intCast(debug_content.len),
},
.flags = .{},
.section_name = try wasm.internString(section_name),
});
} else {
pos = section_end;
}
},
.type => {
const func_types_len, pos = readLeb(u32, bytes, pos);
for (try 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.func_imports.append(gpa, .{
.module_name = interned_module_name,
.name = interned_name,
.function_index = @enumFromInt(function),
});
},
.memory => {
const limits, pos = readLimits(bytes, pos);
const gop = try wasm.object_memory_imports.getOrPut(gpa, interned_name);
if (gop.found_existing) {
if (gop.value_ptr.module_name != interned_module_name) {
var err = try diags.addErrorWithNotes(2);
try err.addMsg("memory '{s}' mismatching module names", .{name});
gop.value_ptr.source_location.addNote(&err, "module '{s}' here", .{
gop.value_ptr.module_name.slice(wasm),
});
source_location.addNote(&err, "module '{s}' here", .{module_name});
}
// TODO error for mismatching flags
gop.value_ptr.limits_min = @min(gop.value_ptr.limits_min, limits.min);
gop.value_ptr.limits_max = @max(gop.value_ptr.limits_max, limits.max);
} else {
gop.value_ptr.* = .{
.module_name = interned_module_name,
.limits_min = limits.min,
.limits_max = limits.max,
.limits_has_max = limits.flags.has_max,
.limits_is_shared = limits.flags.is_shared,
.source_location = source_location,
};
}
},
.global => {
const valtype, pos = readEnum(std.wasm.Valtype, bytes, pos);
const mutable = bytes[pos] == 0x01;
pos += 1;
try ss.global_imports.append(gpa, .{
.name = interned_name,
.valtype = valtype,
.mutable = mutable,
.module_name = interned_module_name,
});
},
.table => {
const ref_type, pos = readEnum(std.wasm.RefType, bytes, pos);
const limits, pos = readLimits(bytes, pos);
try ss.table_imports.append(gpa, .{
.name = interned_name,
.module_name = interned_module_name,
.limits_min = limits.min,
.limits_max = limits.max,
.limits_has_max = limits.flags.has_max,
.limits_is_shared = limits.flags.is_shared,
.ref_type = ref_type,
});
},
}
}
},
.function => {
const functions_len, pos = readLeb(u32, bytes, pos);
for (try ss.func_type_indexes.addManyAsSlice(gpa, functions_len)) |*func_type_index| {
const i, pos = readLeb(u32, bytes, pos);
func_type_index.* = @enumFromInt(i);
}
},
.table => {
const tables_len, pos = readLeb(u32, bytes, pos);
for (try wasm.object_tables.addManyAsSlice(gpa, tables_len)) |*table| {
const ref_type, pos = readEnum(std.wasm.RefType, bytes, pos);
const limits, pos = readLimits(bytes, pos);
table.* = .{
.name = .none,
.module_name = .none,
.flags = .{
.ref_type = .from(ref_type),
.limits_has_max = limits.flags.has_max,
.limits_is_shared = limits.flags.is_shared,
},
.limits_min = limits.min,
.limits_max = limits.max,
};
}
},
.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.* = .{
.name = .none,
.flags = .{
.limits_has_max = limits.flags.has_max,
.limits_is_shared = limits.flags.is_shared,
},
.limits_min = limits.min,
.limits_max = limits.max,
};
}
},
.global => {
if (global_section_index != null)
return diags.failParse(path, "object has more than one global section", .{});
global_section_index = section_index;
const section_start = pos;
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 init_start = pos;
const expr, pos = try readInit(wasm, bytes, pos);
global.* = .{
.name = .none,
.flags = .{
.global_type = .{
.valtype = .from(valtype),
.mutable = mutable,
},
},
.expr = expr,
.object_index = object_index,
.offset = @intCast(init_start - section_start),
.size = @intCast(pos - init_start),
};
}
},
.@"export" => {
const exports_len, pos = readLeb(u32, bytes, pos);
// 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 ss.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);
exp.* = .{
.name = try wasm.internString(name),
.pointee = switch (kind) {
.function => .{ .function = @enumFromInt(functions_start + (index - ss.func_imports.items.len)) },
.table => .{ .table = @enumFromInt(tables_start + (index - ss.table_imports.items.len)) },
.memory => .{ .memory = @enumFromInt(memories_start + index) },
.global => .{ .global = @enumFromInt(globals_start + (index - ss.global_imports.items.len)) },
},
};
}
},
.start => {
const index, pos = readLeb(u32, bytes, pos);
start_function = @enumFromInt(functions_start + index);
},
.element => {
log.warn("unimplemented: element section in {} {s}", .{ path, archive_member_name.? });
pos = section_end;
},
.code => {
if (code_section_index != null)
return diags.failParse(path, "object has more than one code section", .{});
code_section_index = section_index;
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,
.object_index = object_index,
};
}
},
.data => {
if (data_section_index != null)
return diags.failParse(path, "object has more than one data section", .{});
data_section_index = section_index;
const section_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_start = pos;
const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..data_len]);
pos += data_len;
elem.* = .{
.payload = payload,
.name = .none, // Populated from segment_info
.flags = .{
.is_passive = flags == .passive,
}, // Remainder populated from segment_info
.offset = @intCast(segment_start - section_start),
.object_index = object_index,
};
}
},
else => pos = section_end,
}
if (pos != section_end) return error.MalformedSection;
}
if (!saw_linking_section) return error.MissingLinkingSection;
const target_features = comp.root_mod.resolved_target.result.cpu.features;
if (has_tls) {
if (!std.Target.wasm.featureSetHas(target_features, .atomics))
return diags.failParse(path, "object has TLS segment but target CPU feature atomics is disabled", .{});
if (!std.Target.wasm.featureSetHas(target_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;
for (features.slice(wasm)) |feat| {
log.debug("feature: {s}{s}", .{ @tagName(feat.prefix), @tagName(feat.tag) });
switch (feat.prefix) {
.invalid => unreachable,
.@"-" => switch (feat.tag) {
.@"shared-mem" => if (comp.config.shared_memory) {
return diags.failParse(path, "object forbids shared-mem but compilation enables it", .{});
},
else => {
const f = feat.tag.toCpuFeature().?;
if (std.Target.wasm.featureSetHas(target_features, f)) {
return diags.failParse(
path,
"object forbids {s} but specified target features include {s}",
.{ @tagName(feat.tag), @tagName(f) },
);
}
},
},
.@"+", .@"=" => switch (feat.tag) {
.@"shared-mem" => if (!comp.config.shared_memory) {
return diags.failParse(path, "object requires shared-mem but compilation disables it", .{});
},
else => {
const f = feat.tag.toCpuFeature().?;
if (!std.Target.wasm.featureSetHas(target_features, f)) {
return diags.failParse(
path,
"object requires {s} but specified target features exclude {s}",
.{ @tagName(feat.tag), @tagName(f) },
);
}
},
},
}
}
// Apply function type information.
for (ss.func_type_indexes.items, wasm.object_functions.items[functions_start..]) |func_type, *func| {
func.type_index = func_type.ptr(ss).*;
}
// 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() orelse ptr.name;
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)});
gop.value_ptr.source_location.addNote(&err, "imported as {} here", .{
gop.value_ptr.type.fmt(wasm),
});
source_location.addNote(&err, "imported as {} here", .{fn_ty_index.fmt(wasm)});
continue;
}
if (gop.value_ptr.module_name != ptr.module_name.toOptional()) {
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol '{s}' mismatching module names", .{name.slice(wasm)});
if (gop.value_ptr.module_name.slice(wasm)) |module_name| {
gop.value_ptr.source_location.addNote(&err, "module '{s}' here", .{module_name});
} else {
gop.value_ptr.source_location.addNote(&err, "no module here", .{});
}
source_location.addNote(&err, "module '{s}' here", .{ptr.module_name.slice(wasm)});
continue;
}
if (gop.value_ptr.name != ptr.name) {
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol '{s}' mismatching import names", .{name.slice(wasm)});
gop.value_ptr.source_location.addNote(&err, "imported as '{s}' here", .{gop.value_ptr.name.slice(wasm)});
source_location.addNote(&err, "imported as '{s}' here", .{ptr.name.slice(wasm)});
continue;
}
} else {
gop.value_ptr.* = .{
.flags = symbol.flags,
.module_name = ptr.module_name.toOptional(),
.name = ptr.name,
.source_location = source_location,
.resolution = .unresolved,
.type = fn_ty_index,
};
}
},
.global_import => |index| {
const ptr = index.ptr(ss);
const name = symbol.name.unwrap() orelse ptr.name;
if (symbol.flags.binding == .local) {
diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)});
continue;
}
const gop = try wasm.object_global_imports.getOrPut(gpa, name);
if (gop.found_existing) {
const existing_ty = gop.value_ptr.type();
if (ptr.valtype != existing_ty.valtype) {
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol '{s}' mismatching global types", .{name.slice(wasm)});
gop.value_ptr.source_location.addNote(&err, "type {s} here", .{@tagName(existing_ty.valtype)});
source_location.addNote(&err, "type {s} here", .{@tagName(ptr.valtype)});
continue;
}
if (ptr.mutable != existing_ty.mutable) {
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol '{s}' mismatching global mutability", .{name.slice(wasm)});
gop.value_ptr.source_location.addNote(&err, "{s} here", .{
if (existing_ty.mutable) "mutable" else "not mutable",
});
source_location.addNote(&err, "{s} here", .{
if (ptr.mutable) "mutable" else "not mutable",
});
continue;
}
if (gop.value_ptr.module_name != ptr.module_name.toOptional()) {
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol '{s}' mismatching module names", .{name.slice(wasm)});
if (gop.value_ptr.module_name.slice(wasm)) |module_name| {
gop.value_ptr.source_location.addNote(&err, "module '{s}' here", .{module_name});
} else {
gop.value_ptr.source_location.addNote(&err, "no module here", .{});
}
source_location.addNote(&err, "module '{s}' here", .{ptr.module_name.slice(wasm)});
continue;
}
if (gop.value_ptr.name != ptr.name) {
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol '{s}' mismatching import names", .{name.slice(wasm)});
gop.value_ptr.source_location.addNote(&err, "imported as '{s}' here", .{gop.value_ptr.name.slice(wasm)});
source_location.addNote(&err, "imported as '{s}' here", .{ptr.name.slice(wasm)});
continue;
}
} else {
gop.value_ptr.* = .{
.flags = symbol.flags,
.module_name = ptr.module_name.toOptional(),
.name = ptr.name,
.source_location = source_location,
.resolution = .unresolved,
};
gop.value_ptr.flags.global_type = .{
.valtype = .from(ptr.valtype),
.mutable = ptr.mutable,
};
}
},
.table_import => |index| {
const ptr = index.ptr(ss);
const name = symbol.name.unwrap() orelse ptr.name;
if (symbol.flags.binding == .local) {
diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)});
continue;
}
const gop = try wasm.object_table_imports.getOrPut(gpa, name);
if (gop.found_existing) {
const existing_reftype = gop.value_ptr.flags.ref_type.to();
if (ptr.ref_type != existing_reftype) {
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol '{s}' mismatching table reftypes", .{name.slice(wasm)});
gop.value_ptr.source_location.addNote(&err, "{s} here", .{@tagName(existing_reftype)});
source_location.addNote(&err, "{s} here", .{@tagName(ptr.ref_type)});
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)});
gop.value_ptr.source_location.addNote(&err, "module '{s}' here", .{
gop.value_ptr.module_name.slice(wasm),
});
source_location.addNote(&err, "module '{s}' here", .{ptr.module_name.slice(wasm)});
continue;
}
if (gop.value_ptr.name != ptr.name) {
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol '{s}' mismatching import names", .{name.slice(wasm)});
gop.value_ptr.source_location.addNote(&err, "imported as '{s}' here", .{gop.value_ptr.name.slice(wasm)});
source_location.addNote(&err, "imported as '{s}' here", .{ptr.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,
.name = ptr.name,
.source_location = source_location,
.resolution = .unresolved,
.limits_min = ptr.limits_min,
.limits_max = ptr.limits_max,
};
gop.value_ptr.flags.limits_has_max = ptr.limits_has_max;
gop.value_ptr.flags.limits_is_shared = ptr.limits_is_shared;
gop.value_ptr.flags.ref_type = .from(ptr.ref_type);
}
},
.data_import => {
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_data_imports.getOrPut(gpa, name);
if (!gop.found_existing) gop.value_ptr.* = .{
.flags = symbol.flags,
.source_location = source_location,
.resolution = .unresolved,
};
},
.function => |index| {
assert(!symbol.flags.undefined);
const ptr = index.ptr(wasm);
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)});
gop.value_ptr.source_location.addNote(&err, "exported as {} here", .{
ptr.type_index.fmt(wasm),
});
const word = if (gop.value_ptr.resolution == .unresolved) "imported" else "exported";
source_location.addNote(&err, "{s} as {} here", .{ word, gop.value_ptr.type.fmt(wasm) });
continue;
}
if (gop.value_ptr.resolution == .unresolved 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(wasm, index);
gop.value_ptr.flags = symbol.flags;
continue;
}
if (ptr.flags.binding == .weak) {
// Keep the existing one.
continue;
}
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol collision: {s}", .{name.slice(wasm)});
gop.value_ptr.source_location.addNote(&err, "exported as {} here", .{ptr.type_index.fmt(wasm)});
source_location.addNote(&err, "exported as {} here", .{gop.value_ptr.type.fmt(wasm)});
continue;
} else {
gop.value_ptr.* = .{
.flags = symbol.flags,
.module_name = host_name,
.name = name,
.source_location = source_location,
.resolution = .fromObjectFunction(wasm, index),
.type = ptr.type_index,
};
}
},
.global => |index| {
assert(!symbol.flags.undefined);
const ptr = index.ptr(wasm);
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 new_ty = ptr.type();
const gop = try wasm.object_global_imports.getOrPut(gpa, name);
if (gop.found_existing) {
const existing_ty = gop.value_ptr.type();
if (new_ty.valtype != existing_ty.valtype) {
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol '{s}' mismatching global types", .{name.slice(wasm)});
gop.value_ptr.source_location.addNote(&err, "type {s} here", .{@tagName(existing_ty.valtype)});
source_location.addNote(&err, "type {s} here", .{@tagName(new_ty.valtype)});
continue;
}
if (new_ty.mutable != existing_ty.mutable) {
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol '{s}' mismatching global mutability", .{name.slice(wasm)});
gop.value_ptr.source_location.addNote(&err, "{s} here", .{
if (existing_ty.mutable) "mutable" else "not mutable",
});
source_location.addNote(&err, "{s} here", .{
if (new_ty.mutable) "mutable" else "not mutable",
});
continue;
}
if (gop.value_ptr.resolution == .unresolved 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 = .fromObjectGlobal(wasm, index);
gop.value_ptr.flags = symbol.flags;
continue;
}
if (ptr.flags.binding == .weak) {
// Keep the existing one.
continue;
}
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol collision: {s}", .{name.slice(wasm)});
gop.value_ptr.source_location.addNote(&err, "exported as {s} here", .{@tagName(existing_ty.valtype)});
source_location.addNote(&err, "exported as {s} here", .{@tagName(new_ty.valtype)});
continue;
} else {
gop.value_ptr.* = .{
.flags = symbol.flags,
.module_name = .none,
.name = name,
.source_location = source_location,
.resolution = .fromObjectGlobal(wasm, index),
};
gop.value_ptr.flags.global_type = .{
.valtype = .from(new_ty.valtype),
.mutable = new_ty.mutable,
};
}
},
.table => |i| {
assert(!symbol.flags.undefined);
const ptr = i.ptr(wasm);
ptr.name = symbol.name;
ptr.flags = symbol.flags;
},
.data => |index| {
assert(!symbol.flags.undefined);
const ptr = index.ptr(wasm);
const name = ptr.name;
assert(name.toOptional() == symbol.name);
ptr.flags = symbol.flags;
if (symbol.flags.binding == .local) continue; // No participation in symbol resolution.
const gop = try wasm.object_data_imports.getOrPut(gpa, name);
if (gop.found_existing) {
if (gop.value_ptr.resolution == .unresolved 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.resolution = .fromObjectDataIndex(wasm, index);
gop.value_ptr.flags = symbol.flags;
continue;
}
if (ptr.flags.binding == .weak) {
// Keep the existing one.
continue;
}
var err = try diags.addErrorWithNotes(2);
try err.addMsg("symbol collision: {s}", .{name.slice(wasm)});
gop.value_ptr.source_location.addNote(&err, "exported here", .{});
source_location.addNote(&err, "exported here", .{});
continue;
} else {
gop.value_ptr.* = .{
.flags = symbol.flags,
.source_location = source_location,
.resolution = .fromObjectDataIndex(wasm, index),
};
}
},
.section => |i| {
// Name is provided by the section directly; symbol table does not have it.
//const ptr = i.ptr(wasm);
//ptr.flags = symbol.flags;
_ = i;
if (symbol.flags.undefined and symbol.flags.binding == .local) {
const name = symbol.name.slice(wasm).?;
diags.addParseError(path, "local symbol '{s}' references import", .{name});
}
},
};
// Apply export section info. This is done after the symbol table above so
// that the symbol table can take precedence, overriding the export name.
for (ss.exports.items) |*exp| {
switch (exp.pointee) {
inline .function, .table, .memory, .global => |index| {
const ptr = index.ptr(wasm);
ptr.name = exp.name.toOptional();
ptr.flags.exported = true;
},
}
}
// Apply segment_info.
const data_segments = wasm.object_data_segments.items[data_segment_start..];
if (data_segments.len != ss.segment_info.items.len) {
return diags.failParse(path, "expected {d} segment_info entries; found {d}", .{
data_segments.len, ss.segment_info.items.len,
});
}
for (data_segments, ss.segment_info.items) |*data, info| {
data.name = info.name.toOptional();
data.flags = .{
.is_passive = data.flags.is_passive,
.strings = info.flags.strings,
.tls = info.flags.tls,
.retain = info.flags.retain,
.alignment = info.flags.alignment,
};
}
// Check for indirect function table in case of an MVP object file.
legacy_indirect_function_table: {
// If there is a symbol for each import table, this is not a legacy object file.
if (ss.table_imports.items.len == table_import_symbol_count) break :legacy_indirect_function_table;
if (table_import_symbol_count != 0) {
return diags.failParse(path, "expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{
ss.table_imports.items.len, table_import_symbol_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 (ss.table_imports.items.len != 1) {
return diags.failParse(path, "found more than one table import, but no representing table symbols", .{});
}
const table_import_name = ss.table_imports.items[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", .{
table_import_name.slice(wasm),
});
}
const ptr = wasm.object_table_imports.getPtr(table_import_name).?;
ptr.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).?,
});
}
const functions_len: u32 = @intCast(wasm.object_functions.items.len - functions_start);
if (functions_len > 0 and code_section_index == null)
return diags.failParse(path, "code section missing ({d} functions)", .{functions_len});
return .{
.version = version,
.path = path,
.archive_member_name = try wasm.internOptionalString(archive_member_name),
.start_function = start_function,
.features = features,
.functions = .{
.off = functions_start,
.len = functions_len,
},
.function_imports = .{
.off = function_imports_start,
.len = @intCast(wasm.object_function_imports.entries.len - function_imports_start),
},
.global_imports = .{
.off = global_imports_start,
.len = @intCast(wasm.object_global_imports.entries.len - global_imports_start),
},
.table_imports = .{
.off = table_imports_start,
.len = @intCast(wasm.object_table_imports.entries.len - table_imports_start),
},
.data_imports = .{
.off = data_imports_start,
.len = @intCast(wasm.object_data_imports.entries.len - data_imports_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.entries.len - custom_segment_start),
},
.code_section_index = code_section_index,
.global_section_index = global_section_index,
.data_section_index = data_section_index,
};
}
/// 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 => |b| return diags.failParse(path, "invalid feature prefix: 0x{x}", .{b}),
};
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(@ptrCast(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: std.wasm.Limits.Flags = @bitCast(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 .{ 0, 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 = try skipInit(bytes, pos); // one after the end opcode
return .{ try wasm.addExpr(bytes[pos..end_pos]), end_pos };
}
pub fn exprEndPos(bytes: []const u8, pos: usize) error{InvalidInitOpcode}!usize {
const opcode = bytes[pos];
return 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,
};
}
fn skipInit(bytes: []const u8, pos: usize) !usize {
const end_pos = try exprEndPos(bytes, pos);
const op, const final_pos = readEnum(std.wasm.Opcode, bytes, end_pos);
if (op != .end) return error.InitExprMissingEnd;
return final_pos;
}