authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-07-12 14:36:33+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-07-12 14:36:33+02:00
log8033767082f2178416fba8cb4a4b03fef961d318
tree820252e8957cce74e365496d73debd6950e29f9c
parent7d2e14267985df0226a5deee96d0c17c94bf6eb2
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

wasm-linker: Implement linker tests (#12006)

* test/link: initial wasm support This adds basic parsing and dumping of wasm section so they can be tested using the new linker-test infrastructure. * test/link: all wasm sections parsing and dumping We now parse and dump all sections for the wasm binary format. Currently, this only dumps the name of a custom section. Later this should also dump symbol table, name, linking metadata and relocations. All of those live within the custom sections. * Add wasm linker test This also fixes a parser mistake in reading the flags. * test/link: implement linker tests wasm & fixes Adds several test cases to test the wasm self-hosted linker. This also introduces fixes that were caught during the implementation of those tests. * test-runner: obey omit_stage2 for standalone When a standalone test requires stage2, but stage2 is omit from the compiler, such test case will not be included as part of the test suite that is being ran. This is to support CI's where we omit stage2 to lower the memory usage.

16 files changed, 519 insertions(+), 9 deletions(-)

build.zig+2-2
...@@ -481,8 +481,8 @@ pub fn build(b: *Builder) !void {...@@ -481,8 +481,8 @@ pub fn build(b: *Builder) !void {
481 ));481 ));
482482
483 toolchain_step.dependOn(tests.addCompareOutputTests(b, test_filter, modes));483 toolchain_step.dependOn(tests.addCompareOutputTests(b, test_filter, modes));
484 toolchain_step.dependOn(tests.addStandaloneTests(b, test_filter, modes, skip_non_native, enable_macos_sdk, target));484 toolchain_step.dependOn(tests.addStandaloneTests(b, test_filter, modes, skip_non_native, enable_macos_sdk, target, omit_stage2));
485 toolchain_step.dependOn(tests.addLinkTests(b, test_filter, modes, enable_macos_sdk));485 toolchain_step.dependOn(tests.addLinkTests(b, test_filter, modes, enable_macos_sdk, omit_stage2));
486 toolchain_step.dependOn(tests.addStackTraceTests(b, test_filter, modes));486 toolchain_step.dependOn(tests.addStackTraceTests(b, test_filter, modes));
487 toolchain_step.dependOn(tests.addCliTests(b, test_filter, modes));487 toolchain_step.dependOn(tests.addCliTests(b, test_filter, modes));
488 toolchain_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, modes));488 toolchain_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, modes));
ci/azure/macos_script+1-1
...@@ -76,7 +76,7 @@ release/bin/zig build test-run-translated-c -Denable-macos-sdk...@@ -76,7 +76,7 @@ release/bin/zig build test-run-translated-c -Denable-macos-sdk
76release/bin/zig build docs -Denable-macos-sdk76release/bin/zig build docs -Denable-macos-sdk
77release/bin/zig build test-fmt -Denable-macos-sdk77release/bin/zig build test-fmt -Denable-macos-sdk
78release/bin/zig build test-cases -Denable-macos-sdk -Dsingle-threaded78release/bin/zig build test-cases -Denable-macos-sdk -Dsingle-threaded
79release/bin/zig build test-link -Denable-macos-sdk79release/bin/zig build test-link -Denable-macos-sdk -Domit-stage2
8080
81if [ "${BUILD_REASON}" != "PullRequest" ]; then81if [ "${BUILD_REASON}" != "PullRequest" ]; then
82 mv ../LICENSE release/82 mv ../LICENSE release/
lib/std/build.zig+9
...@@ -1618,6 +1618,7 @@ pub const LibExeObjStep = struct {...@@ -1618,6 +1618,7 @@ pub const LibExeObjStep = struct {
1618 want_lto: ?bool = null,1618 want_lto: ?bool = null,
1619 use_stage1: ?bool = null,1619 use_stage1: ?bool = null,
1620 use_llvm: ?bool = null,1620 use_llvm: ?bool = null,
1621 use_lld: ?bool = null,
1621 ofmt: ?std.Target.ObjectFormat = null,1622 ofmt: ?std.Target.ObjectFormat = null,
16221623
1623 output_path_source: GeneratedFile,1624 output_path_source: GeneratedFile,
...@@ -2474,6 +2475,14 @@ pub const LibExeObjStep = struct {...@@ -2474,6 +2475,14 @@ pub const LibExeObjStep = struct {
2474 }2475 }
2475 }2476 }
24762477
2478 if (self.use_lld) |use_lld| {
2479 if (use_lld) {
2480 try zig_args.append("-fLLD");
2481 } else {
2482 try zig_args.append("-fno-LLD");
2483 }
2484 }
2485
2477 if (self.ofmt) |ofmt| {2486 if (self.ofmt) |ofmt| {
2478 try zig_args.append(try std.fmt.allocPrint(builder.allocator, "-ofmt={s}", .{@tagName(ofmt)}));2487 try zig_args.append(try std.fmt.allocPrint(builder.allocator, "-ofmt={s}", .{@tagName(ofmt)}));
2479 }2488 }
lib/std/build/CheckObjectStep.zig+296-1
...@@ -265,7 +265,10 @@ fn make(step: *Step) !void {...@@ -265,7 +265,10 @@ fn make(step: *Step) !void {
265 }),265 }),
266 .elf => @panic("TODO elf parser"),266 .elf => @panic("TODO elf parser"),
267 .coff => @panic("TODO coff parser"),267 .coff => @panic("TODO coff parser"),
268 .wasm => @panic("TODO wasm parser"),268 .wasm => try WasmDumper.parseAndDump(contents, .{
269 .gpa = gpa,
270 .dump_symtab = self.dump_symtab,
271 }),
269 else => unreachable,272 else => unreachable,
270 };273 };
271274
...@@ -522,3 +525,295 @@ const MachODumper = struct {...@@ -522,3 +525,295 @@ const MachODumper = struct {
522 }525 }
523 }526 }
524};527};
528
529const WasmDumper = struct {
530 const symtab_label = "symbols";
531
532 fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 {
533 const gpa = opts.gpa orelse unreachable; // Wasm dumper requires an allocator
534 if (opts.dump_symtab) {
535 @panic("TODO: Implement symbol table parsing and dumping");
536 }
537
538 var fbs = std.io.fixedBufferStream(bytes);
539 const reader = fbs.reader();
540
541 const buf = try reader.readBytesNoEof(8);
542 if (!mem.eql(u8, buf[0..4], &std.wasm.magic)) {
543 return error.InvalidMagicByte;
544 }
545 if (!mem.eql(u8, buf[4..], &std.wasm.version)) {
546 return error.UnsupportedWasmVersion;
547 }
548
549 var output = std.ArrayList(u8).init(gpa);
550 errdefer output.deinit();
551 const writer = output.writer();
552
553 while (reader.readByte()) |current_byte| {
554 const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch |err| {
555 std.debug.print("Found invalid section id '{d}'\n", .{current_byte});
556 return err;
557 };
558
559 const section_length = try std.leb.readULEB128(u32, reader);
560 try parseAndDumpSection(section, bytes[fbs.pos..][0..section_length], writer);
561 fbs.pos += section_length;
562 } else |_| {} // reached end of stream
563
564 return output.toOwnedSlice();
565 }
566
567 fn parseAndDumpSection(section: std.wasm.Section, data: []const u8, writer: anytype) !void {
568 var fbs = std.io.fixedBufferStream(data);
569 const reader = fbs.reader();
570
571 try writer.print(
572 \\Section {s}
573 \\size {d}
574 , .{ @tagName(section), data.len });
575
576 switch (section) {
577 .type,
578 .import,
579 .function,
580 .table,
581 .memory,
582 .global,
583 .@"export",
584 .element,
585 .code,
586 .data,
587 => {
588 const entries = try std.leb.readULEB128(u32, reader);
589 try writer.print("\nentries {d}\n", .{entries});
590 try dumpSection(section, data[fbs.pos..], entries, writer);
591 },
592 .custom => {
593 const name_length = try std.leb.readULEB128(u32, reader);
594 const name = data[fbs.pos..][0..name_length];
595 fbs.pos += name_length;
596 try writer.print("\nname {s}\n", .{name});
597
598 if (mem.eql(u8, name, "name")) {
599 try parseDumpNames(reader, writer, data);
600 }
601 // TODO: Implement parsing and dumping other custom sections (such as relocations)
602 },
603 .start => {
604 const start = try std.leb.readULEB128(u32, reader);
605 try writer.print("\nstart {d}\n", .{start});
606 },
607 else => {}, // skip unknown sections
608 }
609 }
610
611 fn dumpSection(section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void {
612 var fbs = std.io.fixedBufferStream(data);
613 const reader = fbs.reader();
614
615 switch (section) {
616 .type => {
617 var i: u32 = 0;
618 while (i < entries) : (i += 1) {
619 const func_type = try reader.readByte();
620 if (func_type != std.wasm.function_type) {
621 std.debug.print("Expected function type, found byte '{d}'\n", .{func_type});
622 return error.UnexpectedByte;
623 }
624 const params = try std.leb.readULEB128(u32, reader);
625 try writer.print("params {d}\n", .{params});
626 var index: u32 = 0;
627 while (index < params) : (index += 1) {
628 try parseDumpType(std.wasm.Valtype, reader, writer);
629 } else index = 0;
630 const returns = try std.leb.readULEB128(u32, reader);
631 try writer.print("returns {d}\n", .{returns});
632 while (index < returns) : (index += 1) {
633 try parseDumpType(std.wasm.Valtype, reader, writer);
634 }
635 }
636 },
637 .import => {
638 var i: u32 = 0;
639 while (i < entries) : (i += 1) {
640 const module_name_len = try std.leb.readULEB128(u32, reader);
641 const module_name = data[fbs.pos..][0..module_name_len];
642 fbs.pos += module_name_len;
643 const name_len = try std.leb.readULEB128(u32, reader);
644 const name = data[fbs.pos..][0..name_len];
645 fbs.pos += name_len;
646
647 const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch |err| {
648 std.debug.print("Invalid import kind\n", .{});
649 return err;
650 };
651
652 try writer.print(
653 \\module {s}
654 \\name {s}
655 \\kind {s}
656 , .{ module_name, name, @tagName(kind) });
657 try writer.writeByte('\n');
658 switch (kind) {
659 .function => {
660 try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)});
661 },
662 .memory => {
663 try parseDumpLimits(reader, writer);
664 },
665 .global => {
666 try parseDumpType(std.wasm.Valtype, reader, writer);
667 try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)});
668 },
669 .table => {
670 try parseDumpType(std.wasm.RefType, reader, writer);
671 try parseDumpLimits(reader, writer);
672 },
673 }
674 }
675 },
676 .function => {
677 var i: u32 = 0;
678 while (i < entries) : (i += 1) {
679 try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)});
680 }
681 },
682 .table => {
683 var i: u32 = 0;
684 while (i < entries) : (i += 1) {
685 try parseDumpType(std.wasm.RefType, reader, writer);
686 try parseDumpLimits(reader, writer);
687 }
688 },
689 .memory => {
690 var i: u32 = 0;
691 while (i < entries) : (i += 1) {
692 try parseDumpLimits(reader, writer);
693 }
694 },
695 .global => {
696 var i: u32 = 0;
697 while (i < entries) : (i += 1) {
698 try parseDumpType(std.wasm.Valtype, reader, writer);
699 try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)});
700 try parseDumpInit(reader, writer);
701 }
702 },
703 .@"export" => {
704 var i: u32 = 0;
705 while (i < entries) : (i += 1) {
706 const name_len = try std.leb.readULEB128(u32, reader);
707 const name = data[fbs.pos..][0..name_len];
708 fbs.pos += name_len;
709 const kind_byte = try std.leb.readULEB128(u8, reader);
710 const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch |err| {
711 std.debug.print("invalid export kind value '{d}'\n", .{kind_byte});
712 return err;
713 };
714 const index = try std.leb.readULEB128(u32, reader);
715 try writer.print(
716 \\name {s}
717 \\kind {s}
718 \\index {d}
719 , .{ name, @tagName(kind), index });
720 try writer.writeByte('\n');
721 }
722 },
723 .element => {
724 var i: u32 = 0;
725 while (i < entries) : (i += 1) {
726 try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)});
727 try parseDumpInit(reader, writer);
728
729 const function_indexes = try std.leb.readULEB128(u32, reader);
730 var function_index: u32 = 0;
731 try writer.print("indexes {d}\n", .{function_indexes});
732 while (function_index < function_indexes) : (function_index += 1) {
733 try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)});
734 }
735 }
736 },
737 .code => {}, // code section is considered opaque to linker
738 .data => {
739 var i: u32 = 0;
740 while (i < entries) : (i += 1) {
741 const index = try std.leb.readULEB128(u32, reader);
742 try writer.print("memory index 0x{x}\n", .{index});
743 try parseDumpInit(reader, writer);
744 const size = try std.leb.readULEB128(u32, reader);
745 try writer.print("size {d}\n", .{size});
746 try reader.skipBytes(size, .{}); // we do not care about the content of the segments
747 }
748 },
749 else => unreachable,
750 }
751 }
752
753 fn parseDumpType(comptime WasmType: type, reader: anytype, writer: anytype) !void {
754 const type_byte = try reader.readByte();
755 const valtype = std.meta.intToEnum(WasmType, type_byte) catch |err| {
756 std.debug.print("Invalid wasm type value '{d}'\n", .{type_byte});
757 return err;
758 };
759 try writer.print("type {s}\n", .{@tagName(valtype)});
760 }
761
762 fn parseDumpLimits(reader: anytype, writer: anytype) !void {
763 const flags = try std.leb.readULEB128(u8, reader);
764 const min = try std.leb.readULEB128(u32, reader);
765
766 try writer.print("min {x}\n", .{min});
767 if (flags != 0) {
768 try writer.print("max {x}\n", .{try std.leb.readULEB128(u32, reader)});
769 }
770 }
771
772 fn parseDumpInit(reader: anytype, writer: anytype) !void {
773 const byte = try std.leb.readULEB128(u8, reader);
774 const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch |err| {
775 std.debug.print("invalid wasm opcode '{d}'\n", .{byte});
776 return err;
777 };
778 switch (opcode) {
779 .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}),
780 .i64_const => try writer.print("i64.const {x}\n", .{try std.leb.readILEB128(i64, reader)}),
781 .f32_const => try writer.print("f32.const {x}\n", .{@bitCast(f32, try reader.readIntLittle(u32))}),
782 .f64_const => try writer.print("f64.const {x}\n", .{@bitCast(f64, try reader.readIntLittle(u64))}),
783 .global_get => try writer.print("global.get {x}\n", .{try std.leb.readULEB128(u32, reader)}),
784 else => unreachable,
785 }
786 const end_opcode = try std.leb.readULEB128(u8, reader);
787 if (end_opcode != std.wasm.opcode(.end)) {
788 std.debug.print("expected 'end' opcode in init expression\n", .{});
789 return error.MissingEndOpcode;
790 }
791 }
792
793 fn parseDumpNames(reader: anytype, writer: anytype, data: []const u8) !void {
794 while (reader.context.pos < data.len) {
795 try parseDumpType(std.wasm.NameSubsection, reader, writer);
796 const size = try std.leb.readULEB128(u32, reader);
797 const entries = try std.leb.readULEB128(u32, reader);
798 try writer.print(
799 \\size {d}
800 \\names {d}
801 , .{ size, entries });
802 try writer.writeByte('\n');
803 var i: u32 = 0;
804 while (i < entries) : (i += 1) {
805 const index = try std.leb.readULEB128(u32, reader);
806 const name_len = try std.leb.readULEB128(u32, reader);
807 const pos = reader.context.pos;
808 const name = data[pos..][0..name_len];
809 reader.context.pos += name_len;
810
811 try writer.print(
812 \\index {d}
813 \\name {s}
814 , .{ index, name });
815 try writer.writeByte('\n');
816 }
817 }
818 }
819};
src/link.zig+4-4
...@@ -351,7 +351,7 @@ pub const File = struct {...@@ -351,7 +351,7 @@ pub const File = struct {
351351
352 pub fn makeWritable(base: *File) !void {352 pub fn makeWritable(base: *File) !void {
353 switch (base.tag) {353 switch (base.tag) {
354 .coff, .elf, .macho, .plan9 => {354 .coff, .elf, .macho, .plan9, .wasm => {
355 if (base.file != null) return;355 if (base.file != null) return;
356 const emit = base.options.emit orelse return;356 const emit = base.options.emit orelse return;
357 base.file = try emit.directory.handle.createFile(emit.sub_path, .{357 base.file = try emit.directory.handle.createFile(emit.sub_path, .{
...@@ -360,7 +360,7 @@ pub const File = struct {...@@ -360,7 +360,7 @@ pub const File = struct {
360 .mode = determineMode(base.options),360 .mode = determineMode(base.options),
361 });361 });
362 },362 },
363 .c, .wasm, .spirv, .nvptx => {},363 .c, .spirv, .nvptx => {},
364 }364 }
365 }365 }
366366
...@@ -394,7 +394,7 @@ pub const File = struct {...@@ -394,7 +394,7 @@ pub const File = struct {
394 base.file = null;394 base.file = null;
395 }395 }
396 },396 },
397 .coff, .elf, .plan9 => if (base.file) |f| {397 .coff, .elf, .plan9, .wasm => if (base.file) |f| {
398 if (base.intermediary_basename != null) {398 if (base.intermediary_basename != null) {
399 // The file we have open is not the final file that we want to399 // The file we have open is not the final file that we want to
400 // make executable, so we don't have to close it.400 // make executable, so we don't have to close it.
...@@ -403,7 +403,7 @@ pub const File = struct {...@@ -403,7 +403,7 @@ pub const File = struct {
403 f.close();403 f.close();
404 base.file = null;404 base.file = null;
405 },405 },
406 .c, .wasm, .spirv, .nvptx => {},406 .c, .spirv, .nvptx => {},
407 }407 }
408 }408 }
409409
src/link/Wasm.zig+10-1
...@@ -1206,6 +1206,14 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void {...@@ -1206,6 +1206,14 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void {
1206 };1206 };
1207 symbol.tag = .data;1207 symbol.tag = .data;
12081208
1209 // when creating an object file, or importing memory and the data belongs in the .bss segment
1210 // we set the entire region of it to zeroes.
1211 // We do not have to do this when exporting the memory (the default) because the runtime
1212 // will do it for us, and we do not emit the bss segment at all.
1213 if ((self.base.options.output_mode == .Obj or self.base.options.import_memory) and kind.data == .uninitialized) {
1214 std.mem.set(u8, atom.code.items, 0);
1215 }
1216
1209 const should_merge = self.base.options.output_mode != .Obj;1217 const should_merge = self.base.options.output_mode != .Obj;
1210 const gop = try self.data_segments.getOrPut(self.base.allocator, segment_info.outputName(should_merge));1218 const gop = try self.data_segments.getOrPut(self.base.allocator, segment_info.outputName(should_merge));
1211 if (gop.found_existing) {1219 if (gop.found_existing) {
...@@ -2014,9 +2022,10 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -2014,9 +2022,10 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
2014 }2022 }
20152023
2016 if (import_memory) {2024 if (import_memory) {
2025 const mem_name = if (is_obj) "__linear_memory" else "memory";
2017 const mem_imp: types.Import = .{2026 const mem_imp: types.Import = .{
2018 .module_name = try self.string_table.put(self.base.allocator, self.host_name),2027 .module_name = try self.string_table.put(self.base.allocator, self.host_name),
2019 .name = try self.string_table.put(self.base.allocator, "__linear_memory"),2028 .name = try self.string_table.put(self.base.allocator, mem_name),
2020 .kind = .{ .memory = self.memories.limits },2029 .kind = .{ .memory = self.memories.limits },
2021 };2030 };
2022 try self.emitImport(writer, mem_imp);2031 try self.emitImport(writer, mem_imp);
test/link.zig+20
...@@ -27,6 +27,26 @@ pub fn addCases(cases: *tests.StandaloneContext) void {...@@ -27,6 +27,26 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
27 .build_modes = true,27 .build_modes = true,
28 });28 });
2929
30 cases.addBuildFile("test/link/wasm/type/build.zig", .{
31 .build_modes = true,
32 .requires_stage2 = true,
33 });
34
35 cases.addBuildFile("test/link/wasm/segments/build.zig", .{
36 .build_modes = true,
37 .requires_stage2 = true,
38 });
39
40 cases.addBuildFile("test/link/wasm/stack_pointer/build.zig", .{
41 .build_modes = true,
42 .requires_stage2 = true,
43 });
44
45 cases.addBuildFile("test/link/wasm/bss/build.zig", .{
46 .build_modes = true,
47 .requires_stage2 = true,
48 });
49
30 if (builtin.os.tag == .macos) {50 if (builtin.os.tag == .macos) {
31 cases.addBuildFile("test/link/macho/entry/build.zig", .{51 cases.addBuildFile("test/link/macho/entry/build.zig", .{
32 .build_modes = true,52 .build_modes = true,
test/link/wasm/bss/build.zig created+41
...@@ -0,0 +1,41 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3
4pub fn build(b: *Builder) void {
5 const mode = b.standardReleaseOptions();
6
7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
9
10 const lib = b.addSharedLibrary("lib", "lib.zig", .unversioned);
11 lib.setBuildMode(mode);
12 lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
13 lib.use_llvm = false;
14 lib.use_stage1 = false;
15 lib.use_lld = false;
16 // to make sure the bss segment is emitted, we must import memory
17 lib.import_memory = true;
18 lib.install();
19
20 const check_lib = lib.checkObject(.wasm);
21
22 // since we import memory, make sure it exists with the correct naming
23 check_lib.checkStart("Section import");
24 check_lib.checkNext("entries 1");
25 check_lib.checkNext("module env"); // default module name is "env"
26 check_lib.checkNext("name memory"); // as per linker specification
27
28 // since we are importing memory, ensure it's not exported
29 check_lib.checkStart("Section export");
30 check_lib.checkNext("entries 1"); // we're exporting function 'foo' so only 1 entry
31
32 // validate the name of the stack pointer
33 check_lib.checkStart("Section custom");
34 check_lib.checkNext("type data_segment");
35 check_lib.checkNext("names 2");
36 check_lib.checkNext("index 0");
37 check_lib.checkNext("name .rodata");
38 check_lib.checkNext("index 1"); // bss section always last
39 check_lib.checkNext("name .bss");
40 test_step.dependOn(&check_lib.step);
41}
test/link/wasm/bss/lib.zig created+5
...@@ -0,0 +1,5 @@
1pub var bss: u32 = undefined;
2
3export fn foo() void {
4 _ = bss;
5}
test/link/wasm/segments/build.zig created+31
...@@ -0,0 +1,31 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3
4pub fn build(b: *Builder) void {
5 const mode = b.standardReleaseOptions();
6
7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
9
10 const lib = b.addSharedLibrary("lib", "lib.zig", .unversioned);
11 lib.setBuildMode(mode);
12 lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
13 lib.use_llvm = false;
14 lib.use_stage1 = false;
15 lib.use_lld = false;
16 lib.install();
17
18 const check_lib = lib.checkObject(.wasm);
19 check_lib.checkStart("Section data");
20 check_lib.checkNext("entries 2"); // rodata & data, no bss because we're exporting memory
21
22 check_lib.checkStart("Section custom");
23 check_lib.checkStart("name name"); // names custom section
24 check_lib.checkStart("type data_segment");
25 check_lib.checkNext("names 2");
26 check_lib.checkNext("index 0");
27 check_lib.checkNext("name .rodata");
28 check_lib.checkNext("index 1");
29 check_lib.checkNext("name .data");
30 test_step.dependOn(&check_lib.step);
31}
test/link/wasm/segments/lib.zig created+9
...@@ -0,0 +1,9 @@
1pub const rodata: u32 = 5;
2pub var data: u32 = 10;
3pub var bss: u32 = undefined;
4
5export fn foo() void {
6 _ = rodata;
7 _ = data;
8 _ = bss;
9}
test/link/wasm/stack_pointer/build.zig created+41
...@@ -0,0 +1,41 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3
4pub fn build(b: *Builder) void {
5 const mode = b.standardReleaseOptions();
6
7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
9
10 const lib = b.addSharedLibrary("lib", "lib.zig", .unversioned);
11 lib.setBuildMode(mode);
12 lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
13 lib.use_llvm = false;
14 lib.use_stage1 = false;
15 lib.use_lld = false;
16 lib.stack_size = std.wasm.page_size * 2; // set an explicit stack size
17 lib.install();
18
19 const check_lib = lib.checkObject(.wasm);
20
21 // ensure global exists and its initial value is equal to explitic stack size
22 check_lib.checkStart("Section global");
23 check_lib.checkNext("entries 1");
24 check_lib.checkNext("type i32"); // on wasm32 the stack pointer must be i32
25 check_lib.checkNext("mutable true"); // must be able to mutate the stack pointer
26 check_lib.checkNext("i32.const {stack_pointer}");
27 check_lib.checkComputeCompare("stack_pointer", .{ .op = .eq, .value = .{ .literal = lib.stack_size.? } });
28
29 // validate memory section starts after virtual stack
30 check_lib.checkNext("Section data");
31 check_lib.checkNext("i32.const {data_start}");
32 check_lib.checkComputeCompare("data_start", .{ .op = .eq, .value = .{ .variable = "stack_pointer" } });
33
34 // validate the name of the stack pointer
35 check_lib.checkStart("Section custom");
36 check_lib.checkNext("type global");
37 check_lib.checkNext("names 1");
38 check_lib.checkNext("index 0");
39 check_lib.checkNext("name __stack_pointer");
40 test_step.dependOn(&check_lib.step);
41}
test/link/wasm/stack_pointer/lib.zig created+1
...@@ -0,0 +1 @@
1export fn foo() void {}
test/link/wasm/type/build.zig created+32
...@@ -0,0 +1,32 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3
4pub fn build(b: *Builder) void {
5 const mode = b.standardReleaseOptions();
6
7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
9
10 const lib = b.addSharedLibrary("lib", "lib.zig", .unversioned);
11 lib.setBuildMode(mode);
12 lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
13 lib.use_llvm = false;
14 lib.use_stage1 = false;
15 lib.use_lld = false;
16 lib.install();
17
18 const check_lib = lib.checkObject(.wasm);
19 check_lib.checkStart("Section type");
20 // only 2 entries, although we have 3 functions.
21 // This is to test functions with the same function signature
22 // have their types deduplicated.
23 check_lib.checkNext("entries 2");
24 check_lib.checkNext("params 1");
25 check_lib.checkNext("type i32");
26 check_lib.checkNext("returns 1");
27 check_lib.checkNext("type i64");
28 check_lib.checkNext("params 0");
29 check_lib.checkNext("returns 0");
30
31 test_step.dependOn(&check_lib.step);
32}
test/link/wasm/type/lib.zig created+10
...@@ -0,0 +1,10 @@
1export fn foo(x: u32) u64 {
2 return bar(x);
3}
4
5fn bar(x: u32) u64 {
6 y();
7 return x;
8}
9
10fn y() void {}
test/tests.zig+7
...@@ -462,6 +462,7 @@ pub fn addStandaloneTests(...@@ -462,6 +462,7 @@ pub fn addStandaloneTests(
462 skip_non_native: bool,462 skip_non_native: bool,
463 enable_macos_sdk: bool,463 enable_macos_sdk: bool,
464 target: std.zig.CrossTarget,464 target: std.zig.CrossTarget,
465 omit_stage2: bool,
465) *build.Step {466) *build.Step {
466 const cases = b.allocator.create(StandaloneContext) catch unreachable;467 const cases = b.allocator.create(StandaloneContext) catch unreachable;
467 cases.* = StandaloneContext{468 cases.* = StandaloneContext{
...@@ -473,6 +474,7 @@ pub fn addStandaloneTests(...@@ -473,6 +474,7 @@ pub fn addStandaloneTests(
473 .skip_non_native = skip_non_native,474 .skip_non_native = skip_non_native,
474 .enable_macos_sdk = enable_macos_sdk,475 .enable_macos_sdk = enable_macos_sdk,
475 .target = target,476 .target = target,
477 .omit_stage2 = omit_stage2,
476 };478 };
477479
478 standalone.addCases(cases);480 standalone.addCases(cases);
...@@ -485,6 +487,7 @@ pub fn addLinkTests(...@@ -485,6 +487,7 @@ pub fn addLinkTests(
485 test_filter: ?[]const u8,487 test_filter: ?[]const u8,
486 modes: []const Mode,488 modes: []const Mode,
487 enable_macos_sdk: bool,489 enable_macos_sdk: bool,
490 omit_stage2: bool,
488) *build.Step {491) *build.Step {
489 const cases = b.allocator.create(StandaloneContext) catch unreachable;492 const cases = b.allocator.create(StandaloneContext) catch unreachable;
490 cases.* = StandaloneContext{493 cases.* = StandaloneContext{
...@@ -496,6 +499,7 @@ pub fn addLinkTests(...@@ -496,6 +499,7 @@ pub fn addLinkTests(
496 .skip_non_native = true,499 .skip_non_native = true,
497 .enable_macos_sdk = enable_macos_sdk,500 .enable_macos_sdk = enable_macos_sdk,
498 .target = .{},501 .target = .{},
502 .omit_stage2 = omit_stage2,
499 };503 };
500 link.addCases(cases);504 link.addCases(cases);
501 return cases.step;505 return cases.step;
...@@ -957,6 +961,7 @@ pub const StandaloneContext = struct {...@@ -957,6 +961,7 @@ pub const StandaloneContext = struct {
957 skip_non_native: bool,961 skip_non_native: bool,
958 enable_macos_sdk: bool,962 enable_macos_sdk: bool,
959 target: std.zig.CrossTarget,963 target: std.zig.CrossTarget,
964 omit_stage2: bool,
960965
961 pub fn addC(self: *StandaloneContext, root_src: []const u8) void {966 pub fn addC(self: *StandaloneContext, root_src: []const u8) void {
962 self.addAllArgs(root_src, true);967 self.addAllArgs(root_src, true);
...@@ -970,10 +975,12 @@ pub const StandaloneContext = struct {...@@ -970,10 +975,12 @@ pub const StandaloneContext = struct {
970 build_modes: bool = false,975 build_modes: bool = false,
971 cross_targets: bool = false,976 cross_targets: bool = false,
972 requires_macos_sdk: bool = false,977 requires_macos_sdk: bool = false,
978 requires_stage2: bool = false,
973 }) void {979 }) void {
974 const b = self.b;980 const b = self.b;
975981
976 if (features.requires_macos_sdk and !self.enable_macos_sdk) return;982 if (features.requires_macos_sdk and !self.enable_macos_sdk) return;
983 if (features.requires_stage2 and self.omit_stage2) return;
977984
978 const annotated_case_name = b.fmt("build {s}", .{build_file});985 const annotated_case_name = b.fmt("build {s}", .{build_file});
979 if (self.test_filter) |filter| {986 if (self.test_filter) |filter| {