master
   1const Object = @This();
   2
   3const Wasm = @import("../Wasm.zig");
   4const Alignment = Wasm.Alignment;
   5
   6const std = @import("std");
   7const Allocator = std.mem.Allocator;
   8const Path = std.Build.Cache.Path;
   9const log = std.log.scoped(.object);
  10const assert = std.debug.assert;
  11
  12/// Wasm spec version used for this `Object`
  13version: u32,
  14/// For error reporting purposes only.
  15/// Name (read path) of the object or archive file.
  16path: Path,
  17/// For error reporting purposes only.
  18/// If this represents an object in an archive, it's the basename of the
  19/// object, and path refers to the archive.
  20archive_member_name: Wasm.OptionalString,
  21/// Represents the function ID that must be called on startup.
  22/// This is `null` by default as runtimes may determine the startup
  23/// function themselves. This is essentially legacy.
  24start_function: Wasm.OptionalObjectFunctionIndex,
  25/// A slice of features that tell the linker what features are mandatory, used
  26/// (or therefore missing) and must generate an error when another object uses
  27/// features that are not supported by the other.
  28features: Wasm.Feature.Set,
  29/// Points into `Wasm.object_functions`
  30functions: RelativeSlice,
  31/// Points into `Wasm.object_function_imports`
  32function_imports: RelativeSlice,
  33/// Points into `Wasm.object_global_imports`
  34global_imports: RelativeSlice,
  35/// Points into `Wasm.object_table_imports`
  36table_imports: RelativeSlice,
  37// Points into `Wasm.object_data_imports`
  38data_imports: RelativeSlice,
  39/// Points into Wasm object_custom_segments
  40custom_segments: RelativeSlice,
  41/// Points into Wasm object_init_funcs
  42init_funcs: RelativeSlice,
  43/// Points into Wasm object_comdats
  44comdats: RelativeSlice,
  45/// Guaranteed to be non-null when functions has nonzero length.
  46code_section_index: ?Wasm.ObjectSectionIndex,
  47/// Guaranteed to be non-null when globals has nonzero length.
  48global_section_index: ?Wasm.ObjectSectionIndex,
  49/// Guaranteed to be non-null when data segments has nonzero length.
  50data_section_index: ?Wasm.ObjectSectionIndex,
  51is_included: bool,
  52
  53pub const RelativeSlice = struct {
  54    off: u32,
  55    len: u32,
  56};
  57
  58pub const SegmentInfo = struct {
  59    name: Wasm.String,
  60    flags: Flags,
  61
  62    /// Matches the ABI.
  63    pub const Flags = packed struct(u32) {
  64        /// Signals that the segment contains only null terminated strings allowing
  65        /// the linker to perform merging.
  66        strings: bool,
  67        /// The segment contains thread-local data. This means that a unique copy
  68        /// of this segment will be created for each thread.
  69        tls: bool,
  70        /// If the object file is included in the final link, the segment should be
  71        /// retained in the final output regardless of whether it is used by the
  72        /// program.
  73        retain: bool,
  74        alignment: Alignment,
  75
  76        _: u23 = 0,
  77    };
  78};
  79
  80pub const FunctionImport = struct {
  81    module_name: Wasm.String,
  82    name: Wasm.String,
  83    function_index: ScratchSpace.FuncTypeIndex,
  84};
  85
  86pub const GlobalImport = struct {
  87    module_name: Wasm.String,
  88    name: Wasm.String,
  89    valtype: std.wasm.Valtype,
  90    mutable: bool,
  91};
  92
  93pub const TableImport = struct {
  94    module_name: Wasm.String,
  95    name: Wasm.String,
  96    limits_min: u32,
  97    limits_max: u32,
  98    limits_has_max: bool,
  99    limits_is_shared: bool,
 100    ref_type: std.wasm.RefType,
 101};
 102
 103pub const DataSegmentFlags = enum(u32) { active, passive, active_memidx };
 104
 105pub const SubsectionType = enum(u8) {
 106    segment_info = 5,
 107    init_funcs = 6,
 108    comdat_info = 7,
 109    symbol_table = 8,
 110};
 111
 112/// Specified by https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md
 113pub const RelocationType = enum(u8) {
 114    function_index_leb = 0,
 115    table_index_sleb = 1,
 116    table_index_i32 = 2,
 117    memory_addr_leb = 3,
 118    memory_addr_sleb = 4,
 119    memory_addr_i32 = 5,
 120    type_index_leb = 6,
 121    global_index_leb = 7,
 122    function_offset_i32 = 8,
 123    section_offset_i32 = 9,
 124    event_index_leb = 10,
 125    memory_addr_rel_sleb = 11,
 126    table_index_rel_sleb = 12,
 127    global_index_i32 = 13,
 128    memory_addr_leb64 = 14,
 129    memory_addr_sleb64 = 15,
 130    memory_addr_i64 = 16,
 131    memory_addr_rel_sleb64 = 17,
 132    table_index_sleb64 = 18,
 133    table_index_i64 = 19,
 134    table_number_leb = 20,
 135    memory_addr_tls_sleb = 21,
 136    function_offset_i64 = 22,
 137    memory_addr_locrel_i32 = 23,
 138    table_index_rel_sleb64 = 24,
 139    memory_addr_tls_sleb64 = 25,
 140    function_index_i32 = 26,
 141};
 142
 143pub const Symbol = struct {
 144    flags: Wasm.SymbolFlags,
 145    name: Wasm.OptionalString,
 146    pointee: Pointee,
 147
 148    /// https://github.com/WebAssembly/tool-conventions/blob/df8d737539eb8a8f446ba5eab9dc670c40dfb81e/Linking.md#symbol-table-subsection
 149    const Tag = enum(u8) {
 150        function,
 151        data,
 152        global,
 153        section,
 154        event,
 155        table,
 156    };
 157
 158    const Pointee = union(enum) {
 159        function: Wasm.ObjectFunctionIndex,
 160        function_import: ScratchSpace.FuncImportIndex,
 161        data: Wasm.ObjectData.Index,
 162        data_import: void,
 163        global: Wasm.ObjectGlobalIndex,
 164        global_import: ScratchSpace.GlobalImportIndex,
 165        section: Wasm.ObjectSectionIndex,
 166        table: Wasm.ObjectTableIndex,
 167        table_import: ScratchSpace.TableImportIndex,
 168    };
 169};
 170
 171pub const ScratchSpace = struct {
 172    func_types: std.ArrayList(Wasm.FunctionType.Index) = .empty,
 173    func_type_indexes: std.ArrayList(FuncTypeIndex) = .empty,
 174    func_imports: std.ArrayList(FunctionImport) = .empty,
 175    global_imports: std.ArrayList(GlobalImport) = .empty,
 176    table_imports: std.ArrayList(TableImport) = .empty,
 177    symbol_table: std.ArrayList(Symbol) = .empty,
 178    segment_info: std.ArrayList(SegmentInfo) = .empty,
 179    exports: std.ArrayList(Export) = .empty,
 180
 181    const Export = struct {
 182        name: Wasm.String,
 183        pointee: Pointee,
 184
 185        const Pointee = union(std.wasm.ExternalKind) {
 186            function: Wasm.ObjectFunctionIndex,
 187            table: Wasm.ObjectTableIndex,
 188            memory: Wasm.ObjectMemory.Index,
 189            global: Wasm.ObjectGlobalIndex,
 190        };
 191    };
 192
 193    /// Index into `func_imports`.
 194    const FuncImportIndex = enum(u32) {
 195        _,
 196
 197        fn ptr(index: FuncImportIndex, ss: *const ScratchSpace) *FunctionImport {
 198            return &ss.func_imports.items[@intFromEnum(index)];
 199        }
 200    };
 201
 202    /// Index into `global_imports`.
 203    const GlobalImportIndex = enum(u32) {
 204        _,
 205
 206        fn ptr(index: GlobalImportIndex, ss: *const ScratchSpace) *GlobalImport {
 207            return &ss.global_imports.items[@intFromEnum(index)];
 208        }
 209    };
 210
 211    /// Index into `table_imports`.
 212    const TableImportIndex = enum(u32) {
 213        _,
 214
 215        fn ptr(index: TableImportIndex, ss: *const ScratchSpace) *TableImport {
 216            return &ss.table_imports.items[@intFromEnum(index)];
 217        }
 218    };
 219
 220    /// Index into `func_types`.
 221    const FuncTypeIndex = enum(u32) {
 222        _,
 223
 224        fn ptr(index: FuncTypeIndex, ss: *const ScratchSpace) *Wasm.FunctionType.Index {
 225            return &ss.func_types.items[@intFromEnum(index)];
 226        }
 227    };
 228
 229    pub fn deinit(ss: *ScratchSpace, gpa: Allocator) void {
 230        ss.exports.deinit(gpa);
 231        ss.func_types.deinit(gpa);
 232        ss.func_type_indexes.deinit(gpa);
 233        ss.func_imports.deinit(gpa);
 234        ss.global_imports.deinit(gpa);
 235        ss.table_imports.deinit(gpa);
 236        ss.symbol_table.deinit(gpa);
 237        ss.segment_info.deinit(gpa);
 238        ss.* = undefined;
 239    }
 240
 241    fn clear(ss: *ScratchSpace) void {
 242        ss.exports.clearRetainingCapacity();
 243        ss.func_types.clearRetainingCapacity();
 244        ss.func_type_indexes.clearRetainingCapacity();
 245        ss.func_imports.clearRetainingCapacity();
 246        ss.global_imports.clearRetainingCapacity();
 247        ss.table_imports.clearRetainingCapacity();
 248        ss.symbol_table.clearRetainingCapacity();
 249        ss.segment_info.clearRetainingCapacity();
 250    }
 251};
 252
 253pub fn parse(
 254    wasm: *Wasm,
 255    bytes: []const u8,
 256    path: Path,
 257    archive_member_name: ?[]const u8,
 258    host_name: Wasm.OptionalString,
 259    ss: *ScratchSpace,
 260    must_link: bool,
 261    gc_sections: bool,
 262) anyerror!Object {
 263    const comp = wasm.base.comp;
 264    const gpa = comp.gpa;
 265    const diags = &comp.link_diags;
 266
 267    var pos: usize = 0;
 268
 269    if (!std.mem.eql(u8, bytes[0..std.wasm.magic.len], &std.wasm.magic)) return error.BadObjectMagic;
 270    pos += std.wasm.magic.len;
 271
 272    const version = std.mem.readInt(u32, bytes[pos..][0..4], .little);
 273    pos += 4;
 274
 275    const data_segment_start: u32 = @intCast(wasm.object_data_segments.items.len);
 276    const custom_segment_start: u32 = @intCast(wasm.object_custom_segments.entries.len);
 277    const functions_start: u32 = @intCast(wasm.object_functions.items.len);
 278    const tables_start: u32 = @intCast(wasm.object_tables.items.len);
 279    const memories_start: u32 = @intCast(wasm.object_memories.items.len);
 280    const globals_start: u32 = @intCast(wasm.object_globals.items.len);
 281    const init_funcs_start: u32 = @intCast(wasm.object_init_funcs.items.len);
 282    const comdats_start: u32 = @intCast(wasm.object_comdats.items.len);
 283    const function_imports_start: u32 = @intCast(wasm.object_function_imports.entries.len);
 284    const global_imports_start: u32 = @intCast(wasm.object_global_imports.entries.len);
 285    const table_imports_start: u32 = @intCast(wasm.object_table_imports.entries.len);
 286    const data_imports_start: u32 = @intCast(wasm.object_data_imports.entries.len);
 287    const local_section_index_base = wasm.object_total_sections;
 288    const object_index: Wasm.ObjectIndex = @enumFromInt(wasm.objects.items.len);
 289    const source_location: Wasm.SourceLocation = .fromObject(object_index, wasm);
 290
 291    ss.clear();
 292
 293    var start_function: Wasm.OptionalObjectFunctionIndex = .none;
 294    var opt_features: ?Wasm.Feature.Set = null;
 295    var saw_linking_section = false;
 296    var has_tls = false;
 297    var table_import_symbol_count: usize = 0;
 298    var code_section_index: ?Wasm.ObjectSectionIndex = null;
 299    var global_section_index: ?Wasm.ObjectSectionIndex = null;
 300    var data_section_index: ?Wasm.ObjectSectionIndex = null;
 301    while (pos < bytes.len) : (wasm.object_total_sections += 1) {
 302        const section_index: Wasm.ObjectSectionIndex = @enumFromInt(wasm.object_total_sections);
 303
 304        const section_tag: std.wasm.Section = @enumFromInt(bytes[pos]);
 305        pos += 1;
 306
 307        const len, pos = readLeb(u32, bytes, pos);
 308        const section_end = pos + len;
 309        switch (section_tag) {
 310            .custom => {
 311                const section_name, pos = readBytes(bytes, pos);
 312                if (std.mem.eql(u8, section_name, "linking")) {
 313                    saw_linking_section = true;
 314                    const section_version, pos = readLeb(u32, bytes, pos);
 315                    log.debug("link meta data version: {d}", .{section_version});
 316                    if (section_version != 2) return error.UnsupportedVersion;
 317                    while (pos < section_end) {
 318                        const sub_type, pos = readLeb(u8, bytes, pos);
 319                        log.debug("found subsection: {s}", .{@tagName(@as(SubsectionType, @enumFromInt(sub_type)))});
 320                        const payload_len, pos = readLeb(u32, bytes, pos);
 321                        if (payload_len == 0) break;
 322
 323                        const count, pos = readLeb(u32, bytes, pos);
 324
 325                        switch (@as(SubsectionType, @enumFromInt(sub_type))) {
 326                            .segment_info => {
 327                                for (try ss.segment_info.addManyAsSlice(gpa, count)) |*segment| {
 328                                    const name, pos = readBytes(bytes, pos);
 329                                    const alignment, pos = readLeb(u32, bytes, pos);
 330                                    const flags_u32, pos = readLeb(u32, bytes, pos);
 331                                    const flags: SegmentInfo.Flags = @bitCast(flags_u32);
 332                                    const tls = flags.tls or
 333                                        // Supports legacy object files that specified
 334                                        // being TLS by the name instead of the TLS flag.
 335                                        std.mem.startsWith(u8, name, ".tdata") or
 336                                        std.mem.startsWith(u8, name, ".tbss");
 337                                    has_tls = has_tls or tls;
 338                                    segment.* = .{
 339                                        .name = try wasm.internString(name),
 340                                        .flags = .{
 341                                            .strings = flags.strings,
 342                                            .tls = tls,
 343                                            .alignment = @enumFromInt(alignment),
 344                                            .retain = flags.retain,
 345                                        },
 346                                    };
 347                                }
 348                            },
 349                            .init_funcs => {
 350                                for (try wasm.object_init_funcs.addManyAsSlice(gpa, count)) |*func| {
 351                                    const priority, pos = readLeb(u32, bytes, pos);
 352                                    const symbol_index, pos = readLeb(u32, bytes, pos);
 353                                    if (symbol_index > ss.symbol_table.items.len)
 354                                        return diags.failParse(path, "init_funcs before symbol table", .{});
 355                                    const sym = &ss.symbol_table.items[symbol_index];
 356                                    if (sym.pointee != .function) {
 357                                        return diags.failParse(path, "init_func symbol '{s}' not a function", .{
 358                                            sym.name.slice(wasm).?,
 359                                        });
 360                                    } else if (sym.flags.undefined) {
 361                                        return diags.failParse(path, "init_func symbol '{s}' is an import", .{
 362                                            sym.name.slice(wasm).?,
 363                                        });
 364                                    }
 365                                    func.* = .{
 366                                        .priority = priority,
 367                                        .function_index = sym.pointee.function,
 368                                    };
 369                                }
 370                            },
 371                            .comdat_info => {
 372                                for (try wasm.object_comdats.addManyAsSlice(gpa, count)) |*comdat| {
 373                                    const name, pos = readBytes(bytes, pos);
 374                                    const flags, pos = readLeb(u32, bytes, pos);
 375                                    if (flags != 0) return error.UnexpectedComdatFlags;
 376                                    const symbol_count, pos = readLeb(u32, bytes, pos);
 377                                    const start_off: u32 = @intCast(wasm.object_comdat_symbols.len);
 378                                    try wasm.object_comdat_symbols.ensureUnusedCapacity(gpa, symbol_count);
 379                                    for (0..symbol_count) |_| {
 380                                        const kind, pos = readEnum(Wasm.Comdat.Symbol.Type, bytes, pos);
 381                                        const index, pos = readLeb(u32, bytes, pos);
 382                                        if (true) @panic("TODO rebase index depending on kind");
 383                                        wasm.object_comdat_symbols.appendAssumeCapacity(.{
 384                                            .kind = kind,
 385                                            .index = index,
 386                                        });
 387                                    }
 388                                    comdat.* = .{
 389                                        .name = try wasm.internString(name),
 390                                        .flags = flags,
 391                                        .symbols = .{
 392                                            .off = start_off,
 393                                            .len = @intCast(wasm.object_comdat_symbols.len - start_off),
 394                                        },
 395                                    };
 396                                }
 397                            },
 398                            .symbol_table => {
 399                                for (try ss.symbol_table.addManyAsSlice(gpa, count)) |*symbol| {
 400                                    const tag, pos = readEnum(Symbol.Tag, bytes, pos);
 401                                    const flags, pos = readLeb(u32, bytes, pos);
 402                                    symbol.* = .{
 403                                        .flags = @bitCast(flags),
 404                                        .name = .none,
 405                                        .pointee = undefined,
 406                                    };
 407                                    symbol.flags.initZigSpecific(must_link, gc_sections);
 408
 409                                    switch (tag) {
 410                                        .data => {
 411                                            const name, pos = readBytes(bytes, pos);
 412                                            const interned_name = try wasm.internString(name);
 413                                            symbol.name = interned_name.toOptional();
 414                                            if (symbol.flags.undefined) {
 415                                                symbol.pointee = .data_import;
 416                                            } else {
 417                                                const segment_index, pos = readLeb(u32, bytes, pos);
 418                                                const segment_offset, pos = readLeb(u32, bytes, pos);
 419                                                const size, pos = readLeb(u32, bytes, pos);
 420                                                try wasm.object_datas.append(gpa, .{
 421                                                    .segment = @enumFromInt(data_segment_start + segment_index),
 422                                                    .offset = segment_offset,
 423                                                    .size = size,
 424                                                    .name = interned_name,
 425                                                    .flags = symbol.flags,
 426                                                });
 427                                                symbol.pointee = .{
 428                                                    .data = @enumFromInt(wasm.object_datas.items.len - 1),
 429                                                };
 430                                            }
 431                                        },
 432                                        .section => {
 433                                            const local_section, pos = readLeb(u32, bytes, pos);
 434                                            const section: Wasm.ObjectSectionIndex = @enumFromInt(local_section_index_base + local_section);
 435                                            symbol.pointee = .{ .section = section };
 436                                        },
 437
 438                                        .function => {
 439                                            const local_index, pos = readLeb(u32, bytes, pos);
 440                                            if (symbol.flags.undefined) {
 441                                                const function_import: ScratchSpace.FuncImportIndex = @enumFromInt(local_index);
 442                                                symbol.pointee = .{ .function_import = function_import };
 443                                                if (symbol.flags.explicit_name) {
 444                                                    const name, pos = readBytes(bytes, pos);
 445                                                    symbol.name = (try wasm.internString(name)).toOptional();
 446                                                } else {
 447                                                    symbol.name = function_import.ptr(ss).name.toOptional();
 448                                                }
 449                                            } else {
 450                                                symbol.pointee = .{ .function = @enumFromInt(functions_start + (local_index - ss.func_imports.items.len)) };
 451                                                const name, pos = readBytes(bytes, pos);
 452                                                symbol.name = (try wasm.internString(name)).toOptional();
 453                                            }
 454                                        },
 455                                        .global => {
 456                                            const local_index, pos = readLeb(u32, bytes, pos);
 457                                            if (symbol.flags.undefined) {
 458                                                const global_import: ScratchSpace.GlobalImportIndex = @enumFromInt(local_index);
 459                                                symbol.pointee = .{ .global_import = global_import };
 460                                                if (symbol.flags.explicit_name) {
 461                                                    const name, pos = readBytes(bytes, pos);
 462                                                    symbol.name = (try wasm.internString(name)).toOptional();
 463                                                } else {
 464                                                    symbol.name = global_import.ptr(ss).name.toOptional();
 465                                                }
 466                                            } else {
 467                                                symbol.pointee = .{ .global = @enumFromInt(globals_start + (local_index - ss.global_imports.items.len)) };
 468                                                const name, pos = readBytes(bytes, pos);
 469                                                symbol.name = (try wasm.internString(name)).toOptional();
 470                                            }
 471                                        },
 472                                        .table => {
 473                                            const local_index, pos = readLeb(u32, bytes, pos);
 474                                            if (symbol.flags.undefined) {
 475                                                table_import_symbol_count += 1;
 476                                                const table_import: ScratchSpace.TableImportIndex = @enumFromInt(local_index);
 477                                                symbol.pointee = .{ .table_import = table_import };
 478                                                if (symbol.flags.explicit_name) {
 479                                                    const name, pos = readBytes(bytes, pos);
 480                                                    symbol.name = (try wasm.internString(name)).toOptional();
 481                                                } else {
 482                                                    symbol.name = table_import.ptr(ss).name.toOptional();
 483                                                }
 484                                            } else {
 485                                                symbol.pointee = .{ .table = @enumFromInt(tables_start + (local_index - ss.table_imports.items.len)) };
 486                                                const name, pos = readBytes(bytes, pos);
 487                                                symbol.name = (try wasm.internString(name)).toOptional();
 488                                            }
 489                                        },
 490                                        else => {
 491                                            log.debug("unrecognized symbol type tag: {x}", .{@intFromEnum(tag)});
 492                                            return error.UnrecognizedSymbolType;
 493                                        },
 494                                    }
 495                                }
 496                            },
 497                        }
 498                    }
 499                } else if (std.mem.startsWith(u8, section_name, "reloc.")) {
 500                    // 'The "reloc." custom sections must come after the "linking" custom section'
 501                    if (!saw_linking_section) return error.RelocBeforeLinkingSection;
 502
 503                    // "Relocation sections start with an identifier specifying
 504                    // which section they apply to, and must be sequenced in
 505                    // the module after that section."
 506                    // "Relocation sections can only target code, data and custom sections."
 507                    const local_section, pos = readLeb(u32, bytes, pos);
 508                    const count, pos = readLeb(u32, bytes, pos);
 509                    const section: Wasm.ObjectSectionIndex = @enumFromInt(local_section_index_base + local_section);
 510
 511                    log.debug("found {d} relocations for section={d}", .{ count, section });
 512
 513                    var prev_offset: u32 = 0;
 514                    try wasm.object_relocations.ensureUnusedCapacity(gpa, count);
 515                    for (0..count) |_| {
 516                        const tag: RelocationType = @enumFromInt(bytes[pos]);
 517                        pos += 1;
 518                        const offset, pos = readLeb(u32, bytes, pos);
 519                        const index, pos = readLeb(u32, bytes, pos);
 520
 521                        if (offset < prev_offset)
 522                            return diags.failParse(path, "relocation entries not sorted by offset", .{});
 523                        prev_offset = offset;
 524
 525                        const sym = &ss.symbol_table.items[index];
 526
 527                        switch (tag) {
 528                            .memory_addr_leb,
 529                            .memory_addr_sleb,
 530                            .memory_addr_i32,
 531                            .memory_addr_rel_sleb,
 532                            .memory_addr_leb64,
 533                            .memory_addr_sleb64,
 534                            .memory_addr_i64,
 535                            .memory_addr_rel_sleb64,
 536                            .memory_addr_tls_sleb,
 537                            .memory_addr_locrel_i32,
 538                            .memory_addr_tls_sleb64,
 539                            => {
 540                                const addend: i32, pos = readLeb(i32, bytes, pos);
 541                                wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
 542                                    .data => |data| .{
 543                                        .tag = .fromType(tag),
 544                                        .offset = offset,
 545                                        .pointee = .{ .data = data },
 546                                        .addend = addend,
 547                                    },
 548                                    .data_import => .{
 549                                        .tag = .fromTypeImport(tag),
 550                                        .offset = offset,
 551                                        .pointee = .{ .symbol_name = sym.name.unwrap().? },
 552                                        .addend = addend,
 553                                    },
 554                                    else => unreachable,
 555                                });
 556                            },
 557                            .function_offset_i32, .function_offset_i64 => {
 558                                const addend: i32, pos = readLeb(i32, bytes, pos);
 559                                wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
 560                                    .function => .{
 561                                        .tag = .fromType(tag),
 562                                        .offset = offset,
 563                                        .pointee = .{ .function = sym.pointee.function },
 564                                        .addend = addend,
 565                                    },
 566                                    .function_import => .{
 567                                        .tag = .fromTypeImport(tag),
 568                                        .offset = offset,
 569                                        .pointee = .{ .symbol_name = sym.name.unwrap().? },
 570                                        .addend = addend,
 571                                    },
 572                                    else => unreachable,
 573                                });
 574                            },
 575                            .section_offset_i32 => {
 576                                const addend: i32, pos = readLeb(i32, bytes, pos);
 577                                wasm.object_relocations.appendAssumeCapacity(.{
 578                                    .tag = .section_offset_i32,
 579                                    .offset = offset,
 580                                    .pointee = .{ .section = sym.pointee.section },
 581                                    .addend = addend,
 582                                });
 583                            },
 584                            .type_index_leb => {
 585                                wasm.object_relocations.appendAssumeCapacity(.{
 586                                    .tag = .type_index_leb,
 587                                    .offset = offset,
 588                                    .pointee = .{ .type_index = ss.func_types.items[index] },
 589                                    .addend = undefined,
 590                                });
 591                            },
 592                            .function_index_leb,
 593                            .function_index_i32,
 594                            .table_index_sleb,
 595                            .table_index_i32,
 596                            .table_index_sleb64,
 597                            .table_index_i64,
 598                            .table_index_rel_sleb,
 599                            .table_index_rel_sleb64,
 600                            => {
 601                                wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
 602                                    .function => .{
 603                                        .tag = .fromType(tag),
 604                                        .offset = offset,
 605                                        .pointee = .{ .function = sym.pointee.function },
 606                                        .addend = undefined,
 607                                    },
 608                                    .function_import => .{
 609                                        .tag = .fromTypeImport(tag),
 610                                        .offset = offset,
 611                                        .pointee = .{ .symbol_name = sym.name.unwrap().? },
 612                                        .addend = undefined,
 613                                    },
 614                                    else => unreachable,
 615                                });
 616                            },
 617                            .global_index_leb, .global_index_i32 => {
 618                                wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
 619                                    .global => .{
 620                                        .tag = .fromType(tag),
 621                                        .offset = offset,
 622                                        .pointee = .{ .global = sym.pointee.global },
 623                                        .addend = undefined,
 624                                    },
 625                                    .global_import => .{
 626                                        .tag = .fromTypeImport(tag),
 627                                        .offset = offset,
 628                                        .pointee = .{ .symbol_name = sym.name.unwrap().? },
 629                                        .addend = undefined,
 630                                    },
 631                                    else => unreachable,
 632                                });
 633                            },
 634
 635                            .table_number_leb => {
 636                                wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
 637                                    .table => .{
 638                                        .tag = .fromType(tag),
 639                                        .offset = offset,
 640                                        .pointee = .{ .table = sym.pointee.table },
 641                                        .addend = undefined,
 642                                    },
 643                                    .table_import => .{
 644                                        .tag = .fromTypeImport(tag),
 645                                        .offset = offset,
 646                                        .pointee = .{ .symbol_name = sym.name.unwrap().? },
 647                                        .addend = undefined,
 648                                    },
 649                                    else => unreachable,
 650                                });
 651                            },
 652                            .event_index_leb => return diags.failParse(path, "unsupported relocation: R_WASM_EVENT_INDEX_LEB", .{}),
 653                        }
 654                    }
 655
 656                    try wasm.object_relocations_table.putNoClobber(gpa, section, .{
 657                        .off = @intCast(wasm.object_relocations.len - count),
 658                        .len = count,
 659                    });
 660                } else if (std.mem.eql(u8, section_name, "target_features")) {
 661                    opt_features, pos = try parseFeatures(wasm, bytes, pos, path);
 662                } else if (std.mem.startsWith(u8, section_name, ".debug")) {
 663                    const debug_content = bytes[pos..section_end];
 664                    pos = section_end;
 665
 666                    const data_off: u32 = @intCast(wasm.string_bytes.items.len);
 667                    try wasm.string_bytes.appendSlice(gpa, debug_content);
 668
 669                    try wasm.object_custom_segments.put(gpa, section_index, .{
 670                        .payload = .{
 671                            .off = @enumFromInt(data_off),
 672                            .len = @intCast(debug_content.len),
 673                        },
 674                        .flags = .{},
 675                        .section_name = try wasm.internString(section_name),
 676                    });
 677                } else {
 678                    pos = section_end;
 679                }
 680            },
 681            .type => {
 682                const func_types_len, pos = readLeb(u32, bytes, pos);
 683                for (try ss.func_types.addManyAsSlice(gpa, func_types_len)) |*func_type| {
 684                    if (bytes[pos] != std.wasm.function_type) return error.ExpectedFuncType;
 685                    pos += 1;
 686
 687                    const params, pos = readBytes(bytes, pos);
 688                    const returns, pos = readBytes(bytes, pos);
 689                    func_type.* = try wasm.addFuncType(.{
 690                        .params = .fromString(try wasm.internString(params)),
 691                        .returns = .fromString(try wasm.internString(returns)),
 692                    });
 693                }
 694            },
 695            .import => {
 696                const imports_len, pos = readLeb(u32, bytes, pos);
 697                for (0..imports_len) |_| {
 698                    const module_name, pos = readBytes(bytes, pos);
 699                    const name, pos = readBytes(bytes, pos);
 700                    const kind, pos = readEnum(std.wasm.ExternalKind, bytes, pos);
 701                    const interned_module_name = try wasm.internString(module_name);
 702                    const interned_name = try wasm.internString(name);
 703                    switch (kind) {
 704                        .function => {
 705                            const function, pos = readLeb(u32, bytes, pos);
 706                            try ss.func_imports.append(gpa, .{
 707                                .module_name = interned_module_name,
 708                                .name = interned_name,
 709                                .function_index = @enumFromInt(function),
 710                            });
 711                        },
 712                        .memory => {
 713                            const limits, pos = readLimits(bytes, pos);
 714                            const gop = try wasm.object_memory_imports.getOrPut(gpa, interned_name);
 715                            if (gop.found_existing) {
 716                                if (gop.value_ptr.module_name != interned_module_name) {
 717                                    var err = try diags.addErrorWithNotes(2);
 718                                    try err.addMsg("memory '{s}' mismatching module names", .{name});
 719                                    gop.value_ptr.source_location.addNote(&err, "module '{s}' here", .{
 720                                        gop.value_ptr.module_name.slice(wasm),
 721                                    });
 722                                    source_location.addNote(&err, "module '{s}' here", .{module_name});
 723                                }
 724                                // TODO error for mismatching flags
 725                                gop.value_ptr.limits_min = @min(gop.value_ptr.limits_min, limits.min);
 726                                gop.value_ptr.limits_max = @max(gop.value_ptr.limits_max, limits.max);
 727                            } else {
 728                                gop.value_ptr.* = .{
 729                                    .module_name = interned_module_name,
 730                                    .limits_min = limits.min,
 731                                    .limits_max = limits.max,
 732                                    .limits_has_max = limits.flags.has_max,
 733                                    .limits_is_shared = limits.flags.is_shared,
 734                                    .source_location = source_location,
 735                                };
 736                            }
 737                        },
 738                        .global => {
 739                            const valtype, pos = readEnum(std.wasm.Valtype, bytes, pos);
 740                            const mutable = bytes[pos] == 0x01;
 741                            pos += 1;
 742                            try ss.global_imports.append(gpa, .{
 743                                .name = interned_name,
 744                                .valtype = valtype,
 745                                .mutable = mutable,
 746                                .module_name = interned_module_name,
 747                            });
 748                        },
 749                        .table => {
 750                            const ref_type, pos = readEnum(std.wasm.RefType, bytes, pos);
 751                            const limits, pos = readLimits(bytes, pos);
 752                            try ss.table_imports.append(gpa, .{
 753                                .name = interned_name,
 754                                .module_name = interned_module_name,
 755                                .limits_min = limits.min,
 756                                .limits_max = limits.max,
 757                                .limits_has_max = limits.flags.has_max,
 758                                .limits_is_shared = limits.flags.is_shared,
 759                                .ref_type = ref_type,
 760                            });
 761                        },
 762                    }
 763                }
 764            },
 765            .function => {
 766                const functions_len, pos = readLeb(u32, bytes, pos);
 767                for (try ss.func_type_indexes.addManyAsSlice(gpa, functions_len)) |*func_type_index| {
 768                    const i, pos = readLeb(u32, bytes, pos);
 769                    func_type_index.* = @enumFromInt(i);
 770                }
 771            },
 772            .table => {
 773                const tables_len, pos = readLeb(u32, bytes, pos);
 774                for (try wasm.object_tables.addManyAsSlice(gpa, tables_len)) |*table| {
 775                    const ref_type, pos = readEnum(std.wasm.RefType, bytes, pos);
 776                    const limits, pos = readLimits(bytes, pos);
 777                    table.* = .{
 778                        .name = .none,
 779                        .module_name = .none,
 780                        .flags = .{
 781                            .ref_type = .from(ref_type),
 782                            .limits_has_max = limits.flags.has_max,
 783                            .limits_is_shared = limits.flags.is_shared,
 784                        },
 785                        .limits_min = limits.min,
 786                        .limits_max = limits.max,
 787                    };
 788                }
 789            },
 790            .memory => {
 791                const memories_len, pos = readLeb(u32, bytes, pos);
 792                for (try wasm.object_memories.addManyAsSlice(gpa, memories_len)) |*memory| {
 793                    const limits, pos = readLimits(bytes, pos);
 794                    memory.* = .{
 795                        .name = .none,
 796                        .flags = .{
 797                            .limits_has_max = limits.flags.has_max,
 798                            .limits_is_shared = limits.flags.is_shared,
 799                        },
 800                        .limits_min = limits.min,
 801                        .limits_max = limits.max,
 802                    };
 803                }
 804            },
 805            .global => {
 806                if (global_section_index != null)
 807                    return diags.failParse(path, "object has more than one global section", .{});
 808                global_section_index = section_index;
 809
 810                const section_start = pos;
 811                const globals_len, pos = readLeb(u32, bytes, pos);
 812                for (try wasm.object_globals.addManyAsSlice(gpa, globals_len)) |*global| {
 813                    const valtype, pos = readEnum(std.wasm.Valtype, bytes, pos);
 814                    const mutable = bytes[pos] == 0x01;
 815                    pos += 1;
 816                    const init_start = pos;
 817                    const expr, pos = try readInit(wasm, bytes, pos);
 818                    global.* = .{
 819                        .name = .none,
 820                        .flags = .{
 821                            .global_type = .{
 822                                .valtype = .from(valtype),
 823                                .mutable = mutable,
 824                            },
 825                        },
 826                        .expr = expr,
 827                        .object_index = object_index,
 828                        .offset = @intCast(init_start - section_start),
 829                        .size = @intCast(pos - init_start),
 830                    };
 831                }
 832            },
 833            .@"export" => {
 834                const exports_len, pos = readLeb(u32, bytes, pos);
 835                // Read into scratch space, and then later add this data as if
 836                // it were extra symbol table entries, but allow merging with
 837                // existing symbol table data if the name matches.
 838                for (try ss.exports.addManyAsSlice(gpa, exports_len)) |*exp| {
 839                    const name, pos = readBytes(bytes, pos);
 840                    const kind: std.wasm.ExternalKind = @enumFromInt(bytes[pos]);
 841                    pos += 1;
 842                    const index, pos = readLeb(u32, bytes, pos);
 843                    exp.* = .{
 844                        .name = try wasm.internString(name),
 845                        .pointee = switch (kind) {
 846                            .function => .{ .function = @enumFromInt(functions_start + (index - ss.func_imports.items.len)) },
 847                            .table => .{ .table = @enumFromInt(tables_start + (index - ss.table_imports.items.len)) },
 848                            .memory => .{ .memory = @enumFromInt(memories_start + index) },
 849                            .global => .{ .global = @enumFromInt(globals_start + (index - ss.global_imports.items.len)) },
 850                        },
 851                    };
 852                }
 853            },
 854            .start => {
 855                const index, pos = readLeb(u32, bytes, pos);
 856                start_function = @enumFromInt(functions_start + index);
 857            },
 858            .element => {
 859                log.warn("unimplemented: element section in {f} {?s}", .{ path, archive_member_name });
 860                pos = section_end;
 861            },
 862            .code => {
 863                if (code_section_index != null)
 864                    return diags.failParse(path, "object has more than one code section", .{});
 865                code_section_index = section_index;
 866
 867                const start = pos;
 868                const count, pos = readLeb(u32, bytes, pos);
 869                for (try wasm.object_functions.addManyAsSlice(gpa, count)) |*elem| {
 870                    const code_len, pos = readLeb(u32, bytes, pos);
 871                    const offset: u32 = @intCast(pos - start);
 872                    const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..code_len]);
 873                    pos += code_len;
 874                    elem.* = .{
 875                        .flags = .{}, // populated from symbol table
 876                        .name = .none, // populated from symbol table
 877                        .type_index = undefined, // populated from func_types
 878                        .code = payload,
 879                        .offset = offset,
 880                        .object_index = object_index,
 881                    };
 882                }
 883            },
 884            .data => {
 885                if (data_section_index != null)
 886                    return diags.failParse(path, "object has more than one data section", .{});
 887                data_section_index = section_index;
 888
 889                const section_start = pos;
 890                const count, pos = readLeb(u32, bytes, pos);
 891                for (try wasm.object_data_segments.addManyAsSlice(gpa, count)) |*elem| {
 892                    const flags, pos = readEnum(DataSegmentFlags, bytes, pos);
 893                    if (flags == .active_memidx) {
 894                        const memidx, pos = readLeb(u32, bytes, pos);
 895                        if (memidx != 0) return diags.failParse(path, "data section uses mem index {d}", .{memidx});
 896                    }
 897                    //const expr, pos = if (flags != .passive) try readInit(wasm, bytes, pos) else .{ .none, pos };
 898                    if (flags != .passive) pos = try skipInit(bytes, pos);
 899                    const data_len, pos = readLeb(u32, bytes, pos);
 900                    const segment_start = pos;
 901                    const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..data_len]);
 902                    pos += data_len;
 903                    elem.* = .{
 904                        .payload = payload,
 905                        .name = .none, // Populated from segment_info
 906                        .flags = .{
 907                            .is_passive = flags == .passive,
 908                        }, // Remainder populated from segment_info
 909                        .offset = @intCast(segment_start - section_start),
 910                        .object_index = object_index,
 911                    };
 912                }
 913            },
 914            else => pos = section_end,
 915        }
 916        if (pos != section_end) return error.MalformedSection;
 917    }
 918    if (!saw_linking_section) return error.MissingLinkingSection;
 919
 920    const cpu = comp.root_mod.resolved_target.result.cpu;
 921
 922    if (has_tls) {
 923        if (!cpu.has(.wasm, .atomics))
 924            return diags.failParse(path, "object has TLS segment but target CPU feature atomics is disabled", .{});
 925        if (!cpu.has(.wasm, .bulk_memory))
 926            return diags.failParse(path, "object has TLS segment but target CPU feature bulk_memory is disabled", .{});
 927    }
 928
 929    const features = opt_features orelse return error.MissingFeatures;
 930    for (features.slice(wasm)) |feat| {
 931        log.debug("feature: {s}{s}", .{ @tagName(feat.prefix), @tagName(feat.tag) });
 932        switch (feat.prefix) {
 933            .invalid => unreachable,
 934            .@"-" => switch (feat.tag) {
 935                .@"shared-mem" => if (comp.config.shared_memory) {
 936                    return diags.failParse(path, "object forbids shared-mem but compilation enables it", .{});
 937                },
 938                else => {
 939                    const f = feat.tag.toCpuFeature().?;
 940                    if (cpu.has(.wasm, f)) {
 941                        return diags.failParse(
 942                            path,
 943                            "object forbids {s} but specified target features include {s}",
 944                            .{ @tagName(feat.tag), @tagName(f) },
 945                        );
 946                    }
 947                },
 948            },
 949            .@"+", .@"=" => switch (feat.tag) {
 950                .@"shared-mem" => if (!comp.config.shared_memory) {
 951                    return diags.failParse(path, "object requires shared-mem but compilation disables it", .{});
 952                },
 953                else => {
 954                    const f = feat.tag.toCpuFeature().?;
 955                    if (!cpu.has(.wasm, f)) {
 956                        return diags.failParse(
 957                            path,
 958                            "object requires {s} but specified target features exclude {s}",
 959                            .{ @tagName(feat.tag), @tagName(f) },
 960                        );
 961                    }
 962                },
 963            },
 964        }
 965    }
 966
 967    // Apply function type information.
 968    for (ss.func_type_indexes.items, wasm.object_functions.items[functions_start..]) |func_type, *func| {
 969        func.type_index = func_type.ptr(ss).*;
 970    }
 971
 972    // Apply symbol table information.
 973    for (ss.symbol_table.items) |symbol| switch (symbol.pointee) {
 974        .function_import => |index| {
 975            const ptr = index.ptr(ss);
 976            const name = symbol.name.unwrap() orelse ptr.name;
 977            if (symbol.flags.binding == .local) {
 978                diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)});
 979                continue;
 980            }
 981            const gop = try wasm.object_function_imports.getOrPut(gpa, name);
 982            const fn_ty_index = ptr.function_index.ptr(ss).*;
 983            if (gop.found_existing) {
 984                if (gop.value_ptr.type != fn_ty_index) {
 985                    var err = try diags.addErrorWithNotes(2);
 986                    try err.addMsg("symbol '{s}' mismatching function signatures", .{name.slice(wasm)});
 987                    gop.value_ptr.source_location.addNote(&err, "imported as {f} here", .{
 988                        gop.value_ptr.type.fmt(wasm),
 989                    });
 990                    source_location.addNote(&err, "imported as {f} here", .{fn_ty_index.fmt(wasm)});
 991                    continue;
 992                }
 993                if (gop.value_ptr.module_name != ptr.module_name.toOptional()) {
 994                    var err = try diags.addErrorWithNotes(2);
 995                    try err.addMsg("symbol '{s}' mismatching module names", .{name.slice(wasm)});
 996                    if (gop.value_ptr.module_name.slice(wasm)) |module_name| {
 997                        gop.value_ptr.source_location.addNote(&err, "module '{s}' here", .{module_name});
 998                    } else {
 999                        gop.value_ptr.source_location.addNote(&err, "no module here", .{});
1000                    }
1001                    source_location.addNote(&err, "module '{s}' here", .{ptr.module_name.slice(wasm)});
1002                    continue;
1003                }
1004                if (gop.value_ptr.name != ptr.name) {
1005                    var err = try diags.addErrorWithNotes(2);
1006                    try err.addMsg("symbol '{s}' mismatching import names", .{name.slice(wasm)});
1007                    gop.value_ptr.source_location.addNote(&err, "imported as '{s}' here", .{gop.value_ptr.name.slice(wasm)});
1008                    source_location.addNote(&err, "imported as '{s}' here", .{ptr.name.slice(wasm)});
1009                    continue;
1010                }
1011            } else {
1012                gop.value_ptr.* = .{
1013                    .flags = symbol.flags,
1014                    .module_name = ptr.module_name.toOptional(),
1015                    .name = ptr.name,
1016                    .source_location = source_location,
1017                    .resolution = .unresolved,
1018                    .type = fn_ty_index,
1019                };
1020            }
1021        },
1022        .global_import => |index| {
1023            const ptr = index.ptr(ss);
1024            const name = symbol.name.unwrap() orelse ptr.name;
1025            if (symbol.flags.binding == .local) {
1026                diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)});
1027                continue;
1028            }
1029            const gop = try wasm.object_global_imports.getOrPut(gpa, name);
1030            if (gop.found_existing) {
1031                const existing_ty = gop.value_ptr.type();
1032                if (ptr.valtype != existing_ty.valtype) {
1033                    var err = try diags.addErrorWithNotes(2);
1034                    try err.addMsg("symbol '{s}' mismatching global types", .{name.slice(wasm)});
1035                    gop.value_ptr.source_location.addNote(&err, "type {s} here", .{@tagName(existing_ty.valtype)});
1036                    source_location.addNote(&err, "type {s} here", .{@tagName(ptr.valtype)});
1037                    continue;
1038                }
1039                if (ptr.mutable != existing_ty.mutable) {
1040                    var err = try diags.addErrorWithNotes(2);
1041                    try err.addMsg("symbol '{s}' mismatching global mutability", .{name.slice(wasm)});
1042                    gop.value_ptr.source_location.addNote(&err, "{s} here", .{
1043                        if (existing_ty.mutable) "mutable" else "not mutable",
1044                    });
1045                    source_location.addNote(&err, "{s} here", .{
1046                        if (ptr.mutable) "mutable" else "not mutable",
1047                    });
1048                    continue;
1049                }
1050                if (gop.value_ptr.module_name != ptr.module_name.toOptional()) {
1051                    var err = try diags.addErrorWithNotes(2);
1052                    try err.addMsg("symbol '{s}' mismatching module names", .{name.slice(wasm)});
1053                    if (gop.value_ptr.module_name.slice(wasm)) |module_name| {
1054                        gop.value_ptr.source_location.addNote(&err, "module '{s}' here", .{module_name});
1055                    } else {
1056                        gop.value_ptr.source_location.addNote(&err, "no module here", .{});
1057                    }
1058                    source_location.addNote(&err, "module '{s}' here", .{ptr.module_name.slice(wasm)});
1059                    continue;
1060                }
1061                if (gop.value_ptr.name != ptr.name) {
1062                    var err = try diags.addErrorWithNotes(2);
1063                    try err.addMsg("symbol '{s}' mismatching import names", .{name.slice(wasm)});
1064                    gop.value_ptr.source_location.addNote(&err, "imported as '{s}' here", .{gop.value_ptr.name.slice(wasm)});
1065                    source_location.addNote(&err, "imported as '{s}' here", .{ptr.name.slice(wasm)});
1066                    continue;
1067                }
1068            } else {
1069                gop.value_ptr.* = .{
1070                    .flags = symbol.flags,
1071                    .module_name = ptr.module_name.toOptional(),
1072                    .name = ptr.name,
1073                    .source_location = source_location,
1074                    .resolution = .unresolved,
1075                };
1076                gop.value_ptr.flags.global_type = .{
1077                    .valtype = .from(ptr.valtype),
1078                    .mutable = ptr.mutable,
1079                };
1080            }
1081        },
1082        .table_import => |index| {
1083            const ptr = index.ptr(ss);
1084            const name = symbol.name.unwrap() orelse ptr.name;
1085            if (symbol.flags.binding == .local) {
1086                diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)});
1087                continue;
1088            }
1089            const gop = try wasm.object_table_imports.getOrPut(gpa, name);
1090            if (gop.found_existing) {
1091                const existing_reftype = gop.value_ptr.flags.ref_type.to();
1092                if (ptr.ref_type != existing_reftype) {
1093                    var err = try diags.addErrorWithNotes(2);
1094                    try err.addMsg("symbol '{s}' mismatching table reftypes", .{name.slice(wasm)});
1095                    gop.value_ptr.source_location.addNote(&err, "{s} here", .{@tagName(existing_reftype)});
1096                    source_location.addNote(&err, "{s} here", .{@tagName(ptr.ref_type)});
1097                    continue;
1098                }
1099                if (gop.value_ptr.module_name != ptr.module_name) {
1100                    var err = try diags.addErrorWithNotes(2);
1101                    try err.addMsg("symbol '{s}' mismatching module names", .{name.slice(wasm)});
1102                    gop.value_ptr.source_location.addNote(&err, "module '{s}' here", .{
1103                        gop.value_ptr.module_name.slice(wasm),
1104                    });
1105                    source_location.addNote(&err, "module '{s}' here", .{ptr.module_name.slice(wasm)});
1106                    continue;
1107                }
1108                if (gop.value_ptr.name != ptr.name) {
1109                    var err = try diags.addErrorWithNotes(2);
1110                    try err.addMsg("symbol '{s}' mismatching import names", .{name.slice(wasm)});
1111                    gop.value_ptr.source_location.addNote(&err, "imported as '{s}' here", .{gop.value_ptr.name.slice(wasm)});
1112                    source_location.addNote(&err, "imported as '{s}' here", .{ptr.name.slice(wasm)});
1113                    continue;
1114                }
1115                if (symbol.flags.binding == .strong) gop.value_ptr.flags.binding = .strong;
1116                if (!symbol.flags.visibility_hidden) gop.value_ptr.flags.visibility_hidden = false;
1117                if (symbol.flags.no_strip) gop.value_ptr.flags.no_strip = true;
1118            } else {
1119                gop.value_ptr.* = .{
1120                    .flags = symbol.flags,
1121                    .module_name = ptr.module_name,
1122                    .name = ptr.name,
1123                    .source_location = source_location,
1124                    .resolution = .unresolved,
1125                    .limits_min = ptr.limits_min,
1126                    .limits_max = ptr.limits_max,
1127                };
1128                gop.value_ptr.flags.limits_has_max = ptr.limits_has_max;
1129                gop.value_ptr.flags.limits_is_shared = ptr.limits_is_shared;
1130                gop.value_ptr.flags.ref_type = .from(ptr.ref_type);
1131            }
1132        },
1133        .data_import => {
1134            const name = symbol.name.unwrap().?;
1135            if (symbol.flags.binding == .local) {
1136                diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)});
1137                continue;
1138            }
1139            const gop = try wasm.object_data_imports.getOrPut(gpa, name);
1140            if (!gop.found_existing) gop.value_ptr.* = .{
1141                .flags = symbol.flags,
1142                .source_location = source_location,
1143                .resolution = .unresolved,
1144            };
1145        },
1146        .function => |index| {
1147            assert(!symbol.flags.undefined);
1148            const ptr = index.ptr(wasm);
1149            ptr.name = symbol.name;
1150            ptr.flags = symbol.flags;
1151            if (symbol.flags.binding == .local) continue; // No participation in symbol resolution.
1152            const name = symbol.name.unwrap().?;
1153            const gop = try wasm.object_function_imports.getOrPut(gpa, name);
1154            if (gop.found_existing) {
1155                if (gop.value_ptr.type != ptr.type_index) {
1156                    var err = try diags.addErrorWithNotes(2);
1157                    try err.addMsg("function signature mismatch: {s}", .{name.slice(wasm)});
1158                    gop.value_ptr.source_location.addNote(&err, "exported as {f} here", .{
1159                        ptr.type_index.fmt(wasm),
1160                    });
1161                    const word = if (gop.value_ptr.resolution == .unresolved) "imported" else "exported";
1162                    source_location.addNote(&err, "{s} as {f} here", .{ word, gop.value_ptr.type.fmt(wasm) });
1163                    continue;
1164                }
1165                if (gop.value_ptr.resolution == .unresolved or gop.value_ptr.flags.binding == .weak) {
1166                    // Intentional: if they're both weak, take the last one.
1167                    gop.value_ptr.source_location = source_location;
1168                    gop.value_ptr.module_name = host_name;
1169                    gop.value_ptr.resolution = .fromObjectFunction(wasm, index);
1170                    gop.value_ptr.flags = symbol.flags;
1171                    continue;
1172                }
1173                if (ptr.flags.binding == .weak) {
1174                    // Keep the existing one.
1175                    continue;
1176                }
1177                var err = try diags.addErrorWithNotes(2);
1178                try err.addMsg("symbol collision: {s}", .{name.slice(wasm)});
1179                gop.value_ptr.source_location.addNote(&err, "exported as {f} here", .{ptr.type_index.fmt(wasm)});
1180                source_location.addNote(&err, "exported as {f} here", .{gop.value_ptr.type.fmt(wasm)});
1181                continue;
1182            } else {
1183                gop.value_ptr.* = .{
1184                    .flags = symbol.flags,
1185                    .module_name = host_name,
1186                    .name = name,
1187                    .source_location = source_location,
1188                    .resolution = .fromObjectFunction(wasm, index),
1189                    .type = ptr.type_index,
1190                };
1191            }
1192        },
1193        .global => |index| {
1194            assert(!symbol.flags.undefined);
1195            const ptr = index.ptr(wasm);
1196            ptr.name = symbol.name;
1197            ptr.flags = symbol.flags;
1198            if (symbol.flags.binding == .local) continue; // No participation in symbol resolution.
1199            const name = symbol.name.unwrap().?;
1200            const new_ty = ptr.type();
1201            const gop = try wasm.object_global_imports.getOrPut(gpa, name);
1202            if (gop.found_existing) {
1203                const existing_ty = gop.value_ptr.type();
1204                if (new_ty.valtype != existing_ty.valtype) {
1205                    var err = try diags.addErrorWithNotes(2);
1206                    try err.addMsg("symbol '{s}' mismatching global types", .{name.slice(wasm)});
1207                    gop.value_ptr.source_location.addNote(&err, "type {s} here", .{@tagName(existing_ty.valtype)});
1208                    source_location.addNote(&err, "type {s} here", .{@tagName(new_ty.valtype)});
1209                    continue;
1210                }
1211                if (new_ty.mutable != existing_ty.mutable) {
1212                    var err = try diags.addErrorWithNotes(2);
1213                    try err.addMsg("symbol '{s}' mismatching global mutability", .{name.slice(wasm)});
1214                    gop.value_ptr.source_location.addNote(&err, "{s} here", .{
1215                        if (existing_ty.mutable) "mutable" else "not mutable",
1216                    });
1217                    source_location.addNote(&err, "{s} here", .{
1218                        if (new_ty.mutable) "mutable" else "not mutable",
1219                    });
1220                    continue;
1221                }
1222                if (gop.value_ptr.resolution == .unresolved or gop.value_ptr.flags.binding == .weak) {
1223                    // Intentional: if they're both weak, take the last one.
1224                    gop.value_ptr.source_location = source_location;
1225                    gop.value_ptr.module_name = host_name;
1226                    gop.value_ptr.resolution = .fromObjectGlobal(wasm, index);
1227                    gop.value_ptr.flags = symbol.flags;
1228                    continue;
1229                }
1230                if (ptr.flags.binding == .weak) {
1231                    // Keep the existing one.
1232                    continue;
1233                }
1234                var err = try diags.addErrorWithNotes(2);
1235                try err.addMsg("symbol collision: {s}", .{name.slice(wasm)});
1236                gop.value_ptr.source_location.addNote(&err, "exported as {s} here", .{@tagName(existing_ty.valtype)});
1237                source_location.addNote(&err, "exported as {s} here", .{@tagName(new_ty.valtype)});
1238                continue;
1239            } else {
1240                gop.value_ptr.* = .{
1241                    .flags = symbol.flags,
1242                    .module_name = .none,
1243                    .name = name,
1244                    .source_location = source_location,
1245                    .resolution = .fromObjectGlobal(wasm, index),
1246                };
1247                gop.value_ptr.flags.global_type = .{
1248                    .valtype = .from(new_ty.valtype),
1249                    .mutable = new_ty.mutable,
1250                };
1251            }
1252        },
1253        .table => |i| {
1254            assert(!symbol.flags.undefined);
1255            const ptr = i.ptr(wasm);
1256            ptr.name = symbol.name;
1257            ptr.flags = symbol.flags;
1258        },
1259        .data => |index| {
1260            assert(!symbol.flags.undefined);
1261            const ptr = index.ptr(wasm);
1262            const name = ptr.name;
1263            assert(name.toOptional() == symbol.name);
1264            ptr.flags = symbol.flags;
1265            if (symbol.flags.binding == .local) continue; // No participation in symbol resolution.
1266            const gop = try wasm.object_data_imports.getOrPut(gpa, name);
1267            if (gop.found_existing) {
1268                if (gop.value_ptr.resolution == .unresolved or gop.value_ptr.flags.binding == .weak) {
1269                    // Intentional: if they're both weak, take the last one.
1270                    gop.value_ptr.source_location = source_location;
1271                    gop.value_ptr.resolution = .fromObjectDataIndex(wasm, index);
1272                    gop.value_ptr.flags = symbol.flags;
1273                    continue;
1274                }
1275                if (ptr.flags.binding == .weak) {
1276                    // Keep the existing one.
1277                    continue;
1278                }
1279                var err = try diags.addErrorWithNotes(2);
1280                try err.addMsg("symbol collision: {s}", .{name.slice(wasm)});
1281                gop.value_ptr.source_location.addNote(&err, "exported here", .{});
1282                source_location.addNote(&err, "exported here", .{});
1283                continue;
1284            } else {
1285                gop.value_ptr.* = .{
1286                    .flags = symbol.flags,
1287                    .source_location = source_location,
1288                    .resolution = .fromObjectDataIndex(wasm, index),
1289                };
1290            }
1291        },
1292        .section => |i| {
1293            // Name is provided by the section directly; symbol table does not have it.
1294            //const ptr = i.ptr(wasm);
1295            //ptr.flags = symbol.flags;
1296            _ = i;
1297            if (symbol.flags.undefined and symbol.flags.binding == .local) {
1298                const name = symbol.name.slice(wasm).?;
1299                diags.addParseError(path, "local symbol '{s}' references import", .{name});
1300            }
1301        },
1302    };
1303
1304    // Apply export section info. This is done after the symbol table above so
1305    // that the symbol table can take precedence, overriding the export name.
1306    for (ss.exports.items) |*exp| {
1307        switch (exp.pointee) {
1308            inline .function, .table, .memory, .global => |index| {
1309                const ptr = index.ptr(wasm);
1310                ptr.name = exp.name.toOptional();
1311                ptr.flags.exported = true;
1312            },
1313        }
1314    }
1315
1316    // Apply segment_info.
1317    const data_segments = wasm.object_data_segments.items[data_segment_start..];
1318    if (data_segments.len != ss.segment_info.items.len) {
1319        return diags.failParse(path, "expected {d} segment_info entries; found {d}", .{
1320            data_segments.len, ss.segment_info.items.len,
1321        });
1322    }
1323    for (data_segments, ss.segment_info.items) |*data, info| {
1324        data.name = info.name.toOptional();
1325        data.flags = .{
1326            .is_passive = data.flags.is_passive,
1327            .strings = info.flags.strings,
1328            .tls = info.flags.tls,
1329            .retain = info.flags.retain,
1330            .alignment = info.flags.alignment,
1331        };
1332    }
1333
1334    // Check for indirect function table in case of an MVP object file.
1335    legacy_indirect_function_table: {
1336        // If there is a symbol for each import table, this is not a legacy object file.
1337        if (ss.table_imports.items.len == table_import_symbol_count) break :legacy_indirect_function_table;
1338        if (table_import_symbol_count != 0) {
1339            return diags.failParse(path, "expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{
1340                ss.table_imports.items.len, table_import_symbol_count,
1341            });
1342        }
1343        // MVP object files cannot have any table definitions, only imports
1344        // (for the indirect function table).
1345        const tables = wasm.object_tables.items[tables_start..];
1346        if (tables.len > 0) {
1347            return diags.failParse(path, "table definition without representing table symbols", .{});
1348        }
1349        if (ss.table_imports.items.len != 1) {
1350            return diags.failParse(path, "found more than one table import, but no representing table symbols", .{});
1351        }
1352        const table_import_name = ss.table_imports.items[0].name;
1353        if (table_import_name != wasm.preloaded_strings.__indirect_function_table) {
1354            return diags.failParse(path, "non-indirect function table import '{s}' is missing a corresponding symbol", .{
1355                table_import_name.slice(wasm),
1356            });
1357        }
1358        const ptr = wasm.object_table_imports.getPtr(table_import_name).?;
1359        ptr.flags = .{
1360            .undefined = true,
1361            .no_strip = true,
1362        };
1363    }
1364
1365    for (wasm.object_init_funcs.items[init_funcs_start..]) |init_func| {
1366        const func = init_func.function_index.ptr(wasm);
1367        const params = func.type_index.ptr(wasm).params.slice(wasm);
1368        if (params.len != 0) diags.addError("constructor function '{s}' has non-empty parameter list", .{
1369            func.name.slice(wasm).?,
1370        });
1371    }
1372
1373    const functions_len: u32 = @intCast(wasm.object_functions.items.len - functions_start);
1374    if (functions_len > 0 and code_section_index == null)
1375        return diags.failParse(path, "code section missing ({d} functions)", .{functions_len});
1376
1377    return .{
1378        .version = version,
1379        .path = path,
1380        .archive_member_name = try wasm.internOptionalString(archive_member_name),
1381        .start_function = start_function,
1382        .features = features,
1383        .functions = .{
1384            .off = functions_start,
1385            .len = functions_len,
1386        },
1387        .function_imports = .{
1388            .off = function_imports_start,
1389            .len = @intCast(wasm.object_function_imports.entries.len - function_imports_start),
1390        },
1391        .global_imports = .{
1392            .off = global_imports_start,
1393            .len = @intCast(wasm.object_global_imports.entries.len - global_imports_start),
1394        },
1395        .table_imports = .{
1396            .off = table_imports_start,
1397            .len = @intCast(wasm.object_table_imports.entries.len - table_imports_start),
1398        },
1399        .data_imports = .{
1400            .off = data_imports_start,
1401            .len = @intCast(wasm.object_data_imports.entries.len - data_imports_start),
1402        },
1403        .init_funcs = .{
1404            .off = init_funcs_start,
1405            .len = @intCast(wasm.object_init_funcs.items.len - init_funcs_start),
1406        },
1407        .comdats = .{
1408            .off = comdats_start,
1409            .len = @intCast(wasm.object_comdats.items.len - comdats_start),
1410        },
1411        .custom_segments = .{
1412            .off = custom_segment_start,
1413            .len = @intCast(wasm.object_custom_segments.entries.len - custom_segment_start),
1414        },
1415        .code_section_index = code_section_index,
1416        .global_section_index = global_section_index,
1417        .data_section_index = data_section_index,
1418        .is_included = must_link,
1419    };
1420}
1421
1422/// Based on the "features" custom section, parses it into a list of
1423/// features that tell the linker what features were enabled and may be mandatory
1424/// to be able to link.
1425fn parseFeatures(
1426    wasm: *Wasm,
1427    bytes: []const u8,
1428    start_pos: usize,
1429    path: Path,
1430) error{ OutOfMemory, LinkFailure }!struct { Wasm.Feature.Set, usize } {
1431    const gpa = wasm.base.comp.gpa;
1432    const diags = &wasm.base.comp.link_diags;
1433    const features_len, var pos = readLeb(u32, bytes, start_pos);
1434    // This temporary allocation could be avoided by using the string_bytes buffer as a scratch space.
1435    const feature_buffer = try gpa.alloc(Wasm.Feature, features_len);
1436    defer gpa.free(feature_buffer);
1437    for (feature_buffer) |*feature| {
1438        const prefix: Wasm.Feature.Prefix = switch (bytes[pos]) {
1439            '-' => .@"-",
1440            '+' => .@"+",
1441            '=' => .@"=",
1442            else => |b| return diags.failParse(path, "invalid feature prefix: 0x{x}", .{b}),
1443        };
1444        pos += 1;
1445        const name, pos = readBytes(bytes, pos);
1446        const tag = std.meta.stringToEnum(Wasm.Feature.Tag, name) orelse {
1447            return diags.failParse(path, "unrecognized wasm feature in object: {s}", .{name});
1448        };
1449        feature.* = .{
1450            .prefix = prefix,
1451            .tag = tag,
1452        };
1453    }
1454    std.mem.sortUnstable(Wasm.Feature, feature_buffer, {}, Wasm.Feature.lessThan);
1455
1456    return .{
1457        .fromString(try wasm.internString(@ptrCast(feature_buffer))),
1458        pos,
1459    };
1460}
1461
1462fn readLeb(comptime T: type, bytes: []const u8, pos: usize) struct { T, usize } {
1463    var reader: std.Io.Reader = .fixed(bytes[pos..]);
1464    return .{
1465        reader.takeLeb128(T) catch unreachable,
1466        pos + reader.seek,
1467    };
1468}
1469
1470fn readBytes(bytes: []const u8, start_pos: usize) struct { []const u8, usize } {
1471    const len, const pos = readLeb(u32, bytes, start_pos);
1472    return .{
1473        bytes[pos..][0..len],
1474        pos + len,
1475    };
1476}
1477
1478fn readEnum(comptime T: type, bytes: []const u8, pos: usize) struct { T, usize } {
1479    const Tag = @typeInfo(T).@"enum".tag_type;
1480    const int, const new_pos = readLeb(Tag, bytes, pos);
1481    return .{ @enumFromInt(int), new_pos };
1482}
1483
1484fn readLimits(bytes: []const u8, start_pos: usize) struct { std.wasm.Limits, usize } {
1485    const flags: std.wasm.Limits.Flags = @bitCast(bytes[start_pos]);
1486    const min, const max_pos = readLeb(u32, bytes, start_pos + 1);
1487    const max, const end_pos = if (flags.has_max) readLeb(u32, bytes, max_pos) else .{ 0, max_pos };
1488    return .{ .{
1489        .flags = flags,
1490        .min = min,
1491        .max = max,
1492    }, end_pos };
1493}
1494
1495fn readInit(wasm: *Wasm, bytes: []const u8, pos: usize) !struct { Wasm.Expr, usize } {
1496    const end_pos = try skipInit(bytes, pos); // one after the end opcode
1497    return .{ try wasm.addExpr(bytes[pos..end_pos]), end_pos };
1498}
1499
1500pub fn exprEndPos(bytes: []const u8, pos: usize) error{InvalidInitOpcode}!usize {
1501    const opcode = bytes[pos];
1502    return switch (@as(std.wasm.Opcode, @enumFromInt(opcode))) {
1503        .i32_const => readLeb(i32, bytes, pos + 1)[1],
1504        .i64_const => readLeb(i64, bytes, pos + 1)[1],
1505        .f32_const => pos + 5,
1506        .f64_const => pos + 9,
1507        .global_get => readLeb(u32, bytes, pos + 1)[1],
1508        else => return error.InvalidInitOpcode,
1509    };
1510}
1511
1512fn skipInit(bytes: []const u8, pos: usize) !usize {
1513    const end_pos = try exprEndPos(bytes, pos);
1514    const op, const final_pos = readEnum(std.wasm.Opcode, bytes, end_pos);
1515    if (op != .end) return error.InitExprMissingEnd;
1516    return final_pos;
1517}