authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-12-19 16:50:25+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-12-19 16:50:25+01:00
log6f44e2d1d3dc1ac7942082dc98947f7beaa1f205
tree185feb74c344b1b17dc823d746446d9af7967c4a
parent8eac2e30c995ab5d36bd06765f94c2c432bbd96b
signaturelock-open Commit is signed but in an unrecognized format.

wasm-linker: consolidate writing to file

This merges the paths from flushModule and linkWithZld to a single function that will write the entire WebAssembly module to the file. This reduces the chance of mistakes as we do not have to duplicate the logic. A similar action may be needed later for linkWithLLD.

1 files changed, 19 insertions(+), 446 deletions(-)

src/link/Wasm.zig+19-446
......@@ -2341,13 +2341,6 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
23412341 };
23422342 }
23432343
2344 // The amount of sections that will be written
2345 var section_count: u32 = 0;
2346 // Index of the code section. Used to tell relocation table where the section lives.
2347 var code_section_index: ?u32 = null;
2348 // Index of the data section. Used to tell relocation table where the section lives.
2349 var data_section_index: ?u32 = null;
2350
23512344 // Positional arguments to the linker such as object files and static archives.
23522345 var positionals = std.ArrayList([]const u8).init(arena);
23532346 try positionals.ensureUnusedCapacity(options.objects.len);
......@@ -2406,7 +2399,6 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
24062399 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
24072400 try wasm.validateFeatures(&enabled_features, &emit_features_count);
24082401 try wasm.resolveSymbolsInArchives();
2409 // try wasm.checkUndefinedSymbols();
24102402
24112403 try wasm.setupStart();
24122404 try wasm.setupImports();
......@@ -2421,435 +2413,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
24212413 try wasm.mergeSections();
24222414 try wasm.mergeTypes();
24232415 try wasm.setupExports();
2424
2425 const header_size = 5 + 1;
2426
2427 var binary_bytes = std.ArrayList(u8).init(gpa);
2428 defer binary_bytes.deinit();
2429 const binary_writer = binary_bytes.writer();
2430
2431 // We write the magic bytes at the end so they will only be written
2432 // if everything succeeded as expected. So populate with 0's for now.
2433 try binary_writer.writeAll(&[_]u8{0} ** 8);
2434 // (Re)set file pointer to 0
2435 try wasm.base.file.?.setEndPos(0);
2436 try wasm.base.file.?.seekTo(0);
2437
2438 // Type section
2439 if (wasm.func_types.items.len != 0) {
2440 const header_offset = try reserveVecSectionHeader(&binary_bytes);
2441 log.debug("Writing type section. Count: ({d})", .{wasm.func_types.items.len});
2442 for (wasm.func_types.items) |func_type| {
2443 try leb.writeULEB128(binary_writer, std.wasm.function_type);
2444 try leb.writeULEB128(binary_writer, @intCast(u32, func_type.params.len));
2445 for (func_type.params) |param_ty| {
2446 try leb.writeULEB128(binary_writer, std.wasm.valtype(param_ty));
2447 }
2448 try leb.writeULEB128(binary_writer, @intCast(u32, func_type.returns.len));
2449 for (func_type.returns) |ret_ty| {
2450 try leb.writeULEB128(binary_writer, std.wasm.valtype(ret_ty));
2451 }
2452 }
2453
2454 try writeVecSectionHeader(
2455 binary_bytes.items,
2456 header_offset,
2457 .type,
2458 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2459 @intCast(u32, wasm.func_types.items.len),
2460 );
2461 section_count += 1;
2462 }
2463
2464 // Import section
2465 const import_memory = options.import_memory or is_obj;
2466 const import_table = options.import_table or is_obj;
2467 if (wasm.imports.count() != 0 or import_memory or import_table) {
2468 const header_offset = try reserveVecSectionHeader(&binary_bytes);
2469
2470 // import table is always first table so emit that first
2471 if (import_table) {
2472 const table_imp: types.Import = .{
2473 .module_name = try wasm.string_table.put(gpa, wasm.host_name),
2474 .name = try wasm.string_table.put(gpa, "__indirect_function_table"),
2475 .kind = .{
2476 .table = .{
2477 .limits = .{
2478 .min = @intCast(u32, wasm.function_table.count()),
2479 .max = null,
2480 },
2481 .reftype = .funcref,
2482 },
2483 },
2484 };
2485 try wasm.emitImport(binary_writer, table_imp);
2486 }
2487
2488 var it = wasm.imports.iterator();
2489 while (it.next()) |entry| {
2490 assert(entry.key_ptr.*.getSymbol(wasm).isUndefined());
2491 const import = entry.value_ptr.*;
2492 try wasm.emitImport(binary_writer, import);
2493 }
2494
2495 if (import_memory) {
2496 const mem_name = if (is_obj) "__linear_memory" else "memory";
2497 const mem_imp: types.Import = .{
2498 .module_name = try wasm.string_table.put(gpa, wasm.host_name),
2499 .name = try wasm.string_table.put(gpa, mem_name),
2500 .kind = .{ .memory = wasm.memories.limits },
2501 };
2502 try wasm.emitImport(binary_writer, mem_imp);
2503 }
2504
2505 try writeVecSectionHeader(
2506 binary_bytes.items,
2507 header_offset,
2508 .import,
2509 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2510 @intCast(u32, wasm.imports.count() + @boolToInt(import_memory) + @boolToInt(import_table)),
2511 );
2512 section_count += 1;
2513 }
2514
2515 // Function section
2516 if (wasm.functions.count() != 0) {
2517 const header_offset = try reserveVecSectionHeader(&binary_bytes);
2518 for (wasm.functions.values()) |function| {
2519 try leb.writeULEB128(binary_writer, function.type_index);
2520 }
2521
2522 try writeVecSectionHeader(
2523 binary_bytes.items,
2524 header_offset,
2525 .function,
2526 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2527 @intCast(u32, wasm.functions.count()),
2528 );
2529 section_count += 1;
2530 }
2531
2532 // Table section
2533 const export_table = options.export_table;
2534 if (!import_table and wasm.function_table.count() != 0) {
2535 const header_offset = try reserveVecSectionHeader(&binary_bytes);
2536
2537 try leb.writeULEB128(binary_writer, std.wasm.reftype(.funcref));
2538 try emitLimits(binary_writer, .{
2539 .min = @intCast(u32, wasm.function_table.count()) + 1,
2540 .max = null,
2541 });
2542
2543 try writeVecSectionHeader(
2544 binary_bytes.items,
2545 header_offset,
2546 .table,
2547 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2548 @as(u32, 1),
2549 );
2550 section_count += 1;
2551 }
2552
2553 // Memory section
2554 if (!import_memory) {
2555 const header_offset = try reserveVecSectionHeader(&binary_bytes);
2556
2557 try emitLimits(binary_writer, wasm.memories.limits);
2558 try writeVecSectionHeader(
2559 binary_bytes.items,
2560 header_offset,
2561 .memory,
2562 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2563 @as(u32, 1), // wasm currently only supports 1 linear memory segment
2564 );
2565 section_count += 1;
2566 }
2567
2568 // Global section (used to emit stack pointer)
2569 if (wasm.wasm_globals.items.len > 0) {
2570 const header_offset = try reserveVecSectionHeader(&binary_bytes);
2571
2572 var global_count: u32 = 0;
2573 for (wasm.wasm_globals.items) |global| {
2574 try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype));
2575 try binary_writer.writeByte(@boolToInt(global.global_type.mutable));
2576 try emitInit(binary_writer, global.init);
2577 global_count += 1;
2578 }
2579
2580 for (wasm.address_globals.items) |sym_loc| {
2581 const atom = wasm.symbol_atom.get(sym_loc).?;
2582 try binary_writer.writeByte(std.wasm.valtype(.i32));
2583 try binary_writer.writeByte(0); // immutable
2584 try emitInit(binary_writer, .{
2585 .i32_const = @bitCast(i32, atom.offset),
2586 });
2587 global_count += 1;
2588 }
2589
2590 try writeVecSectionHeader(
2591 binary_bytes.items,
2592 header_offset,
2593 .global,
2594 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2595 @intCast(u32, global_count),
2596 );
2597 section_count += 1;
2598 }
2599
2600 // Export section
2601 if (wasm.exports.items.len != 0 or export_table or !import_memory) {
2602 const header_offset = try reserveVecSectionHeader(&binary_bytes);
2603
2604 for (wasm.exports.items) |exp| {
2605 const name = wasm.string_table.get(exp.name);
2606 try leb.writeULEB128(binary_writer, @intCast(u32, name.len));
2607 try binary_writer.writeAll(name);
2608 try leb.writeULEB128(binary_writer, @enumToInt(exp.kind));
2609 try leb.writeULEB128(binary_writer, exp.index);
2610 }
2611
2612 if (export_table) {
2613 try leb.writeULEB128(binary_writer, @intCast(u32, "__indirect_function_table".len));
2614 try binary_writer.writeAll("__indirect_function_table");
2615 try binary_writer.writeByte(std.wasm.externalKind(.table));
2616 try leb.writeULEB128(binary_writer, @as(u32, 0)); // function table is always the first table
2617 }
2618
2619 if (!import_memory) {
2620 try leb.writeULEB128(binary_writer, @intCast(u32, "memory".len));
2621 try binary_writer.writeAll("memory");
2622 try binary_writer.writeByte(std.wasm.externalKind(.memory));
2623 try leb.writeULEB128(binary_writer, @as(u32, 0));
2624 }
2625
2626 try writeVecSectionHeader(
2627 binary_bytes.items,
2628 header_offset,
2629 .@"export",
2630 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2631 @intCast(u32, wasm.exports.items.len) + @boolToInt(export_table) + @boolToInt(!import_memory),
2632 );
2633 section_count += 1;
2634 }
2635
2636 // element section (function table)
2637 if (wasm.function_table.count() > 0) {
2638 const header_offset = try reserveVecSectionHeader(&binary_bytes);
2639
2640 var flags: u32 = 0x2; // Yes we have a table
2641 try leb.writeULEB128(binary_writer, flags);
2642 try leb.writeULEB128(binary_writer, @as(u32, 0)); // index of that table. TODO: Store synthetic symbols
2643 try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid
2644 try leb.writeULEB128(binary_writer, @as(u8, 0));
2645 try leb.writeULEB128(binary_writer, @intCast(u32, wasm.function_table.count()));
2646 var symbol_it = wasm.function_table.keyIterator();
2647 while (symbol_it.next()) |symbol_loc_ptr| {
2648 try leb.writeULEB128(binary_writer, symbol_loc_ptr.*.getSymbol(wasm).index);
2649 }
2650
2651 try writeVecSectionHeader(
2652 binary_bytes.items,
2653 header_offset,
2654 .element,
2655 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2656 @as(u32, 1),
2657 );
2658 section_count += 1;
2659 }
2660
2661 // Code section
2662 var code_section_size: u32 = 0;
2663 if (wasm.code_section_index) |code_index| {
2664 const header_offset = try reserveVecSectionHeader(&binary_bytes);
2665 var atom: *Atom = wasm.atoms.get(code_index).?.getFirst();
2666
2667 // The code section must be sorted in line with the function order.
2668 var sorted_atoms = try std.ArrayList(*Atom).initCapacity(gpa, wasm.functions.count());
2669 defer sorted_atoms.deinit();
2670
2671 while (true) {
2672 if (wasm.resolved_symbols.contains(atom.symbolLoc())) {
2673 if (!is_obj) {
2674 atom.resolveRelocs(wasm);
2675 }
2676 sorted_atoms.appendAssumeCapacity(atom);
2677 }
2678 atom = atom.next orelse break;
2679 }
2680
2681 const atom_sort_fn = struct {
2682 fn sort(ctx: *const Wasm, lhs: *const Atom, rhs: *const Atom) bool {
2683 const lhs_sym = lhs.symbolLoc().getSymbol(ctx);
2684 const rhs_sym = rhs.symbolLoc().getSymbol(ctx);
2685 return lhs_sym.index < rhs_sym.index;
2686 }
2687 }.sort;
2688
2689 std.sort.sort(*Atom, sorted_atoms.items, wasm, atom_sort_fn);
2690
2691 for (sorted_atoms.items) |sorted_atom| {
2692 try leb.writeULEB128(binary_writer, sorted_atom.size);
2693 try binary_writer.writeAll(sorted_atom.code.items);
2694 }
2695
2696 code_section_size = @intCast(u32, binary_bytes.items.len - header_offset - header_size);
2697 try writeVecSectionHeader(
2698 binary_bytes.items,
2699 header_offset,
2700 .code,
2701 code_section_size,
2702 @intCast(u32, wasm.functions.count()),
2703 );
2704 code_section_index = section_count;
2705 section_count += 1;
2706 }
2707
2708 // Data section
2709 if (wasm.data_segments.count() != 0) {
2710 const header_offset = try reserveVecSectionHeader(&binary_bytes);
2711
2712 var it = wasm.data_segments.iterator();
2713 var segment_count: u32 = 0;
2714 while (it.next()) |entry| {
2715 // do not output 'bss' section unless we import memory and therefore
2716 // want to guarantee the data is zero initialized
2717 if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue;
2718 segment_count += 1;
2719 const atom_index = entry.value_ptr.*;
2720 var atom: *Atom = wasm.atoms.getPtr(atom_index).?.*.getFirst();
2721 const segment = wasm.segments.items[atom_index];
2722
2723 // flag and index to memory section (currently, there can only be 1 memory section in wasm)
2724 try leb.writeULEB128(binary_writer, @as(u32, 0));
2725 // offset into data section
2726 try emitInit(binary_writer, .{ .i32_const = @bitCast(i32, segment.offset) });
2727 try leb.writeULEB128(binary_writer, segment.size);
2728
2729 // fill in the offset table and the data segments
2730 var current_offset: u32 = 0;
2731 while (true) {
2732 if (!wasm.resolved_symbols.contains(atom.symbolLoc())) {
2733 atom = atom.next orelse break;
2734 continue;
2735 }
2736 if (!is_obj) {
2737 atom.resolveRelocs(wasm);
2738 }
2739
2740 // Pad with zeroes to ensure all segments are aligned
2741 if (current_offset != atom.offset) {
2742 const diff = atom.offset - current_offset;
2743 try binary_writer.writeByteNTimes(0, diff);
2744 current_offset += diff;
2745 }
2746 assert(current_offset == atom.offset);
2747 assert(atom.code.items.len == atom.size);
2748 try binary_writer.writeAll(atom.code.items);
2749
2750 current_offset += atom.size;
2751 if (atom.next) |next| {
2752 atom = next;
2753 } else {
2754 // also pad with zeroes when last atom to ensure
2755 // segments are aligned.
2756 if (current_offset != segment.size) {
2757 try binary_writer.writeByteNTimes(0, segment.size - current_offset);
2758 current_offset += segment.size - current_offset;
2759 }
2760 break;
2761 }
2762 }
2763 assert(current_offset == segment.size);
2764 }
2765
2766 try writeVecSectionHeader(
2767 binary_bytes.items,
2768 header_offset,
2769 .data,
2770 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2771 @intCast(u32, segment_count),
2772 );
2773 data_section_index = section_count;
2774 section_count += 1;
2775 }
2776
2777 if (is_obj) {
2778 // relocations need to point to the index of a symbol in the final symbol table. To save memory,
2779 // we never store all symbols in a single table, but store a location reference instead.
2780 // This means that for a relocatable object file, we need to generate one and provide it to the relocation sections.
2781 var symbol_table = std.AutoArrayHashMap(SymbolLoc, u32).init(arena);
2782 try wasm.emitLinkSection(&binary_bytes, &symbol_table);
2783 if (code_section_index) |code_index| {
2784 try wasm.emitCodeRelocations(&binary_bytes, code_index, symbol_table);
2785 }
2786 if (data_section_index) |data_index| {
2787 try wasm.emitDataRelocations(&binary_bytes, data_index, symbol_table);
2788 }
2789 } else if (!options.strip) {
2790 try wasm.emitNameSection(&binary_bytes, arena);
2791 }
2792
2793 if (!options.strip) {
2794 if (wasm.dwarf) |*dwarf| {
2795 const mod = options.module.?;
2796 try dwarf.writeDbgAbbrev();
2797 // for debug info and ranges, the address is always 0,
2798 // as locations are always offsets relative to 'code' section.
2799 try dwarf.writeDbgInfoHeader(mod, 0, code_section_size);
2800 try dwarf.writeDbgAranges(0, code_section_size);
2801 try dwarf.writeDbgLineHeader();
2802 }
2803
2804 var debug_bytes = std.ArrayList(u8).init(gpa);
2805 defer debug_bytes.deinit();
2806
2807 const DebugSection = struct {
2808 name: []const u8,
2809 index: ?u32,
2810 };
2811
2812 const debug_sections: []const DebugSection = &.{
2813 .{ .name = ".debug_info", .index = wasm.debug_info_index },
2814 .{ .name = ".debug_pubtypes", .index = wasm.debug_pubtypes_index },
2815 .{ .name = ".debug_abbrev", .index = wasm.debug_abbrev_index },
2816 .{ .name = ".debug_line", .index = wasm.debug_line_index },
2817 .{ .name = ".debug_str", .index = wasm.debug_str_index },
2818 .{ .name = ".debug_pubnames", .index = wasm.debug_pubnames_index },
2819 .{ .name = ".debug_loc", .index = wasm.debug_loc_index },
2820 .{ .name = ".debug_ranges", .index = wasm.debug_ranges_index },
2821 };
2822
2823 for (debug_sections) |item| {
2824 if (item.index) |index| {
2825 var atom = wasm.atoms.get(index).?.getFirst();
2826 while (true) {
2827 atom.resolveRelocs(wasm);
2828 try debug_bytes.appendSlice(atom.code.items);
2829 atom = atom.next orelse break;
2830 }
2831 try emitDebugSection(&binary_bytes, debug_bytes.items, item.name);
2832 debug_bytes.clearRetainingCapacity();
2833 }
2834 }
2835
2836 try emitProducerSection(&binary_bytes);
2837 if (emit_features_count > 0) {
2838 try emitFeaturesSection(&binary_bytes, &enabled_features, emit_features_count);
2839 }
2840 }
2841
2842 // Only when writing all sections executed properly we write the magic
2843 // bytes. This allows us to easily detect what went wrong while generating
2844 // the final binary.
2845 mem.copy(u8, binary_bytes.items, &(std.wasm.magic ++ std.wasm.version));
2846
2847 // finally, write the entire binary into the file.
2848 var iovec = [_]std.os.iovec_const{.{
2849 .iov_base = binary_bytes.items.ptr,
2850 .iov_len = binary_bytes.items.len,
2851 }};
2852 try wasm.base.file.?.writevAll(&iovec);
2416 try wasm.writeToFile(enabled_features, emit_features_count, arena);
28532417
28542418 if (!wasm.base.options.disable_lld_caching) {
28552419 // Update the file with the digest. If it fails we can continue; it only
......@@ -2884,13 +2448,6 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
28842448 // ensure the error names table is populated when an error name is referenced
28852449 try wasm.populateErrorNameTable();
28862450
2887 // The amount of sections that will be written
2888 var section_count: u32 = 0;
2889 // Index of the code section. Used to tell relocation table where the section lives.
2890 var code_section_index: ?u32 = null;
2891 // Index of the data section. Used to tell relocation table where the section lives.
2892 var data_section_index: ?u32 = null;
2893
28942451 // Used for all temporary memory allocated during flushin
28952452 var arena_instance = std.heap.ArenaAllocator.init(wasm.base.allocator);
28962453 defer arena_instance.deinit();
......@@ -2970,8 +2527,24 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
29702527 try wasm.mergeSections();
29712528 try wasm.mergeTypes();
29722529 try wasm.setupExports();
2530 try wasm.writeToFile(enabled_features, emit_features_count, arena);
2531}
29732532
2533/// Writes the WebAssembly in-memory module to the file
2534fn writeToFile(
2535 wasm: *Wasm,
2536 enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool,
2537 feature_count: u32,
2538 arena: Allocator,
2539) !void {
2540 // Size of each section header
29742541 const header_size = 5 + 1;
2542 // The amount of sections that will be written
2543 var section_count: u32 = 0;
2544 // Index of the code section. Used to tell relocation table where the section lives.
2545 var code_section_index: ?u32 = null;
2546 // Index of the data section. Used to tell relocation table where the section lives.
2547 var data_section_index: ?u32 = null;
29752548 const is_obj = wasm.base.options.output_mode == .Obj or (!wasm.base.options.use_llvm and wasm.base.options.use_lld);
29762549
29772550 var binary_bytes = std.ArrayList(u8).init(wasm.base.allocator);
......@@ -3378,8 +2951,8 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
33782951 }
33792952
33802953 try emitProducerSection(&binary_bytes);
3381 if (emit_features_count > 0) {
3382 try emitFeaturesSection(&binary_bytes, &enabled_features, emit_features_count);
2954 if (feature_count > 0) {
2955 try emitFeaturesSection(&binary_bytes, &enabled_features, feature_count);
33832956 }
33842957 }
33852958