authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-10-30 18:33:06-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-10-30 23:43:53-07:00
logf2dcfe0e408e1abf0c75293d75c4d92aed033eae
treeac640108ecaa7f43b5c61cc2c46b744093e4926e
parentf5ade5e2071fecf6b8a0b025b6ff9581082b9b5c

link.File.Wasm: parse inputs in compilation pipeline

Primarily, this moves linker input parsing from flush() into the linker task queue, which is executed simultaneously with the frontend. I also made it avoid redundantly opening the same archive file N times for each object file inside. Furthermore, hard code fixed buffer stream rather than using a generic stream type. Finally, I fixed the error handling of the Wasm.Archive.parse function. Please pay attention to this pattern of returning a struct rather than accepting a mutable struct as an argument. This ensures function-level atomicity and makes resource management straightforward. Deletes the file and path fields from Archive and Object. Removed a well-meaning but ultimately misguided suggestion about how to think about ZigObject since thinking about it that way has led to problematic anti-DOD patterns.

6 files changed, 735 insertions(+), 840 deletions(-)

src/link.zig+1-1
...@@ -1085,7 +1085,7 @@ pub const File = struct {...@@ -1085,7 +1085,7 @@ pub const File = struct {
1085 const use_lld = build_options.have_llvm and base.comp.config.use_lld;1085 const use_lld = build_options.have_llvm and base.comp.config.use_lld;
1086 if (use_lld) return;1086 if (use_lld) return;
1087 switch (base.tag) {1087 switch (base.tag) {
1088 inline .elf => |tag| {1088 inline .elf, .wasm => |tag| {
1089 dev.check(tag.devFeature());1089 dev.check(tag.devFeature());
1090 return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);1090 return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);
1091 },1091 },
src/link/Elf.zig+2-3
...@@ -823,9 +823,8 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod...@@ -823,9 +823,8 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
823 const sub_prog_node = prog_node.start("ELF Flush", 0);823 const sub_prog_node = prog_node.start("ELF Flush", 0);
824 defer sub_prog_node.end();824 defer sub_prog_node.end();
825825
826 const directory = self.base.emit.root_dir; // Just an alias to make it shorter to type.
827 const module_obj_path: ?Path = if (self.base.zcu_object_sub_path) |path| .{826 const module_obj_path: ?Path = if (self.base.zcu_object_sub_path) |path| .{
828 .root_dir = directory,827 .root_dir = self.base.emit.root_dir,
829 .sub_path = if (fs.path.dirname(self.base.emit.sub_path)) |dirname|828 .sub_path = if (fs.path.dirname(self.base.emit.sub_path)) |dirname|
830 try fs.path.join(arena, &.{ dirname, path })829 try fs.path.join(arena, &.{ dirname, path })
831 else830 else
...@@ -1104,7 +1103,7 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void {...@@ -1104,7 +1103,7 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void {
1104pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void {1103pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void {
1105 const diags = &self.base.comp.link_diags;1104 const diags = &self.base.comp.link_diags;
1106 const obj = link.openObject(path, false, false) catch |err| {1105 const obj = link.openObject(path, false, false) catch |err| {
1107 switch (diags.failParse(path, "failed to open object {}: {s}", .{ path, @errorName(err) })) {1106 switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) {
1108 error.LinkFailure => return,1107 error.LinkFailure => return,
1109 }1108 }
1110 };1109 };
src/link/Wasm.zig+172-191
...@@ -156,7 +156,7 @@ function_table: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .empty,...@@ -156,7 +156,7 @@ function_table: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .empty,
156156
157/// All archive files that are lazy loaded.157/// All archive files that are lazy loaded.
158/// e.g. when an undefined symbol references a symbol from the archive.158/// e.g. when an undefined symbol references a symbol from the archive.
159archives: std.ArrayListUnmanaged(Archive) = .empty,159lazy_archives: std.ArrayListUnmanaged(LazyArchive) = .empty,
160160
161/// A map of global names (read: offset into string table) to their symbol location161/// A map of global names (read: offset into string table) to their symbol location
162globals: std.AutoHashMapUnmanaged(u32, SymbolLoc) = .empty,162globals: std.AutoHashMapUnmanaged(u32, SymbolLoc) = .empty,
...@@ -176,6 +176,10 @@ undefs: std.AutoArrayHashMapUnmanaged(u32, SymbolLoc) = .empty,...@@ -176,6 +176,10 @@ undefs: std.AutoArrayHashMapUnmanaged(u32, SymbolLoc) = .empty,
176/// Undefined (and synthetic) symbols do not have an Atom and therefore cannot be mapped.176/// Undefined (and synthetic) symbols do not have an Atom and therefore cannot be mapped.
177symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, Atom.Index) = .empty,177symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, Atom.Index) = .empty,
178178
179/// `--verbose-link` output.
180/// Initialized on creation, appended to as inputs are added, printed during `flush`.
181dump_argv_list: std.ArrayListUnmanaged([]const u8),
182
179/// Index into objects array or the zig object.183/// Index into objects array or the zig object.
180pub const ObjectId = enum(u16) {184pub const ObjectId = enum(u16) {
181 zig_object = std.math.maxInt(u16) - 1,185 zig_object = std.math.maxInt(u16) - 1,
...@@ -200,6 +204,18 @@ pub const OptionalObjectId = enum(u16) {...@@ -200,6 +204,18 @@ pub const OptionalObjectId = enum(u16) {
200 }204 }
201};205};
202206
207const LazyArchive = struct {
208 path: Path,
209 file_contents: []const u8,
210 archive: Archive,
211
212 fn deinit(la: *LazyArchive, gpa: Allocator) void {
213 gpa.free(la.path.sub_path);
214 gpa.free(la.file_contents);
215 la.* = undefined;
216 }
217};
218
203pub const Segment = struct {219pub const Segment = struct {
204 alignment: Alignment,220 alignment: Alignment,
205 size: u32,221 size: u32,
...@@ -450,6 +466,7 @@ pub fn createEmpty(...@@ -450,6 +466,7 @@ pub fn createEmpty(
450 .named => |name| name,466 .named => |name| name,
451 },467 },
452 .zig_object = null,468 .zig_object = null,
469 .dump_argv_list = .empty,
453 };470 };
454 if (use_llvm and comp.config.have_zcu) {471 if (use_llvm and comp.config.have_zcu) {
455 wasm.llvm_object = try LlvmObject.create(arena, comp);472 wasm.llvm_object = try LlvmObject.create(arena, comp);
...@@ -596,7 +613,10 @@ pub fn createEmpty(...@@ -596,7 +613,10 @@ pub fn createEmpty(
596 const zig_object = try arena.create(ZigObject);613 const zig_object = try arena.create(ZigObject);
597 wasm.zig_object = zig_object;614 wasm.zig_object = zig_object;
598 zig_object.* = .{615 zig_object.* = .{
599 .path = try std.fmt.allocPrint(gpa, "{s}.o", .{std.fs.path.stem(zcu.main_mod.root_src_path)}),616 .path = .{
617 .root_dir = std.Build.Cache.Directory.cwd(),
618 .sub_path = try std.fmt.allocPrint(gpa, "{s}.o", .{fs.path.stem(zcu.main_mod.root_src_path)}),
619 },
600 .stack_pointer_sym = .null,620 .stack_pointer_sym = .null,
601 };621 };
602 try zig_object.init(wasm);622 try zig_object.init(wasm);
...@@ -657,28 +677,34 @@ fn createSyntheticSymbolOffset(wasm: *Wasm, name_offset: u32, tag: Symbol.Tag) !...@@ -657,28 +677,34 @@ fn createSyntheticSymbolOffset(wasm: *Wasm, name_offset: u32, tag: Symbol.Tag) !
657 return loc;677 return loc;
658}678}
659679
660/// Parses the object file from given path. Returns true when the given file was an object680fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void {
661/// file and parsed successfully. Returns false when file is not an object file.
662/// May return an error instead when parsing failed.
663fn parseObjectFile(wasm: *Wasm, path: []const u8) !bool {
664 const diags = &wasm.base.comp.link_diags;681 const diags = &wasm.base.comp.link_diags;
682 const obj = link.openObject(path, false, false) catch |err| {
683 switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) {
684 error.LinkFailure => return,
685 }
686 };
687 wasm.parseObject(obj) catch |err| {
688 switch (diags.failParse(path, "failed to parse object: {s}", .{@errorName(err)})) {
689 error.LinkFailure => return,
690 }
691 };
692}
665693
666 const obj_file = try fs.cwd().openFile(path, .{});694fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {
667 errdefer obj_file.close();695 defer obj.file.close();
668
669 const gpa = wasm.base.comp.gpa;696 const gpa = wasm.base.comp.gpa;
670 var object = Object.create(wasm, obj_file, path, null) catch |err| switch (err) {697 try wasm.objects.ensureUnusedCapacity(gpa, 1);
671 error.InvalidMagicByte, error.NotObjectFile => return false,698 const stat = try obj.file.stat();
672 else => |e| {699 const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
673 var err_note = try diags.addErrorWithNotes(1);700
674 try err_note.addMsg("Failed parsing object file: {s}", .{@errorName(e)});701 const file_contents = try gpa.alloc(u8, size);
675 try err_note.addNote("while parsing '{s}'", .{path});702 defer gpa.free(file_contents);
676 return error.FlushFailure;703
677 },704 const n = try obj.file.preadAll(file_contents, 0);
678 };705 if (n != file_contents.len) return error.UnexpectedEndOfFile;
679 errdefer object.deinit(gpa);706
680 try wasm.objects.append(gpa, object);707 wasm.objects.appendAssumeCapacity(try Object.create(wasm, file_contents, obj.path, null));
681 return true;
682}708}
683709
684/// Creates a new empty `Atom` and returns its `Atom.Index`710/// Creates a new empty `Atom` and returns its `Atom.Index`
...@@ -703,43 +729,37 @@ pub fn getAtomPtr(wasm: *Wasm, index: Atom.Index) *Atom {...@@ -703,43 +729,37 @@ pub fn getAtomPtr(wasm: *Wasm, index: Atom.Index) *Atom {
703 return &wasm.managed_atoms.items[@intFromEnum(index)];729 return &wasm.managed_atoms.items[@intFromEnum(index)];
704}730}
705731
706/// Parses an archive file and will then parse each object file732fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void {
707/// that was found in the archive file.
708/// Returns false when the file is not an archive file.
709/// May return an error instead when parsing failed.
710///
711/// When `force_load` is `true`, it will for link all object files in the archive.
712/// When false, it will only link with object files that contain symbols that
713/// are referenced by other object files or Zig code.
714fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool {
715 const gpa = wasm.base.comp.gpa;733 const gpa = wasm.base.comp.gpa;
716 const diags = &wasm.base.comp.link_diags;
717734
718 const archive_file = try fs.cwd().openFile(path, .{});735 defer obj.file.close();
719 errdefer archive_file.close();
720736
721 var archive: Archive = .{737 const stat = try obj.file.stat();
722 .file = archive_file,738 const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
723 .name = path,739
724 };740 const file_contents = try gpa.alloc(u8, size);
725 archive.parse(gpa) catch |err| switch (err) {741 var keep_file_contents = false;
726 error.EndOfStream, error.NotArchive => {742 defer if (!keep_file_contents) gpa.free(file_contents);
727 archive.deinit(gpa);
728 return false;
729 },
730 else => |e| {
731 var err_note = try diags.addErrorWithNotes(1);
732 try err_note.addMsg("Failed parsing archive: {s}", .{@errorName(e)});
733 try err_note.addNote("while parsing archive {s}", .{path});
734 return error.FlushFailure;
735 },
736 };
737743
738 if (!force_load) {744 const n = try obj.file.preadAll(file_contents, 0);
745 if (n != file_contents.len) return error.UnexpectedEndOfFile;
746
747 var archive = try Archive.parse(gpa, file_contents);
748
749 if (!obj.must_link) {
739 errdefer archive.deinit(gpa);750 errdefer archive.deinit(gpa);
740 try wasm.archives.append(gpa, archive);751 try wasm.lazy_archives.append(gpa, .{
741 return true;752 .path = .{
753 .root_dir = obj.path.root_dir,
754 .sub_path = try gpa.dupe(u8, obj.path.sub_path),
755 },
756 .file_contents = file_contents,
757 .archive = archive,
758 });
759 keep_file_contents = true;
760 return;
742 }761 }
762
743 defer archive.deinit(gpa);763 defer archive.deinit(gpa);
744764
745 // In this case we must force link all embedded object files within the archive765 // In this case we must force link all embedded object files within the archive
...@@ -754,16 +774,9 @@ fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool {...@@ -754,16 +774,9 @@ fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool {
754 }774 }
755775
756 for (offsets.keys()) |file_offset| {776 for (offsets.keys()) |file_offset| {
757 const object = archive.parseObject(wasm, file_offset) catch |e| {777 const object = try archive.parseObject(wasm, file_contents[file_offset..], obj.path);
758 var err_note = try diags.addErrorWithNotes(1);
759 try err_note.addMsg("Failed parsing object: {s}", .{@errorName(e)});
760 try err_note.addNote("while parsing object in archive {s}", .{path});
761 return error.FlushFailure;
762 };
763 try wasm.objects.append(gpa, object);778 try wasm.objects.append(gpa, object);
764 }779 }
765
766 return true;
767}780}
768781
769fn requiresTLSReloc(wasm: *const Wasm) bool {782fn requiresTLSReloc(wasm: *const Wasm) bool {
...@@ -775,7 +788,7 @@ fn requiresTLSReloc(wasm: *const Wasm) bool {...@@ -775,7 +788,7 @@ fn requiresTLSReloc(wasm: *const Wasm) bool {
775 return false;788 return false;
776}789}
777790
778fn objectPath(wasm: *const Wasm, object_id: ObjectId) []const u8 {791fn objectPath(wasm: *const Wasm, object_id: ObjectId) Path {
779 const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.path;792 const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.path;
780 return obj.path;793 return obj.path;
781}794}
...@@ -854,7 +867,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {...@@ -854,7 +867,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
854 const gpa = wasm.base.comp.gpa;867 const gpa = wasm.base.comp.gpa;
855 const diags = &wasm.base.comp.link_diags;868 const diags = &wasm.base.comp.link_diags;
856 const obj_path = objectPath(wasm, object_id);869 const obj_path = objectPath(wasm, object_id);
857 log.debug("Resolving symbols in object: '{s}'", .{obj_path});870 log.debug("Resolving symbols in object: '{'}'", .{obj_path});
858 const symbols = objectSymbols(wasm, object_id);871 const symbols = objectSymbols(wasm, object_id);
859872
860 for (symbols, 0..) |symbol, i| {873 for (symbols, 0..) |symbol, i| {
...@@ -871,9 +884,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {...@@ -871,9 +884,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
871884
872 if (symbol.isLocal()) {885 if (symbol.isLocal()) {
873 if (symbol.isUndefined()) {886 if (symbol.isUndefined()) {
874 var err = try diags.addErrorWithNotes(1);887 diags.addParseError(obj_path, "local symbol '{s}' references import", .{sym_name});
875 try err.addMsg("Local symbols are not allowed to reference imports", .{});
876 try err.addNote("symbol '{s}' defined in '{s}'", .{ sym_name, obj_path });
877 }888 }
878 try wasm.resolved_symbols.putNoClobber(gpa, location, {});889 try wasm.resolved_symbols.putNoClobber(gpa, location, {});
879 continue;890 continue;
...@@ -892,7 +903,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {...@@ -892,7 +903,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
892903
893 const existing_loc = maybe_existing.value_ptr.*;904 const existing_loc = maybe_existing.value_ptr.*;
894 const existing_sym: *Symbol = wasm.symbolLocSymbol(existing_loc);905 const existing_sym: *Symbol = wasm.symbolLocSymbol(existing_loc);
895 const existing_file_path = if (existing_loc.file.unwrap()) |id| objectPath(wasm, id) else wasm.name;906 const existing_file_path: Path = if (existing_loc.file.unwrap()) |id| objectPath(wasm, id) else .{
907 .root_dir = std.Build.Cache.Directory.cwd(),
908 .sub_path = wasm.name,
909 };
896910
897 if (!existing_sym.isUndefined()) outer: {911 if (!existing_sym.isUndefined()) outer: {
898 if (!symbol.isUndefined()) inner: {912 if (!symbol.isUndefined()) inner: {
...@@ -905,8 +919,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {...@@ -905,8 +919,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
905 // both are defined and weak, we have a symbol collision.919 // both are defined and weak, we have a symbol collision.
906 var err = try diags.addErrorWithNotes(2);920 var err = try diags.addErrorWithNotes(2);
907 try err.addMsg("symbol '{s}' defined multiple times", .{sym_name});921 try err.addMsg("symbol '{s}' defined multiple times", .{sym_name});
908 try err.addNote("first definition in '{s}'", .{existing_file_path});922 try err.addNote("first definition in '{'}'", .{existing_file_path});
909 try err.addNote("next definition in '{s}'", .{obj_path});923 try err.addNote("next definition in '{'}'", .{obj_path});
910 }924 }
911925
912 try wasm.discarded.put(gpa, location, existing_loc);926 try wasm.discarded.put(gpa, location, existing_loc);
...@@ -916,8 +930,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {...@@ -916,8 +930,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
916 if (symbol.tag != existing_sym.tag) {930 if (symbol.tag != existing_sym.tag) {
917 var err = try diags.addErrorWithNotes(2);931 var err = try diags.addErrorWithNotes(2);
918 try err.addMsg("symbol '{s}' mismatching types '{s}' and '{s}'", .{ sym_name, @tagName(symbol.tag), @tagName(existing_sym.tag) });932 try err.addMsg("symbol '{s}' mismatching types '{s}' and '{s}'", .{ sym_name, @tagName(symbol.tag), @tagName(existing_sym.tag) });
919 try err.addNote("first definition in '{s}'", .{existing_file_path});933 try err.addNote("first definition in '{'}'", .{existing_file_path});
920 try err.addNote("next definition in '{s}'", .{obj_path});934 try err.addNote("next definition in '{'}'", .{obj_path});
921 }935 }
922936
923 if (existing_sym.isUndefined() and symbol.isUndefined()) {937 if (existing_sym.isUndefined() and symbol.isUndefined()) {
...@@ -940,8 +954,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {...@@ -940,8 +954,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
940 existing_name,954 existing_name,
941 module_name,955 module_name,
942 });956 });
943 try err.addNote("first definition in '{s}'", .{existing_file_path});957 try err.addNote("first definition in '{'}'", .{existing_file_path});
944 try err.addNote("next definition in '{s}'", .{obj_path});958 try err.addNote("next definition in '{'}'", .{obj_path});
945 }959 }
946 }960 }
947961
...@@ -956,8 +970,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {...@@ -956,8 +970,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
956 if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) {970 if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) {
957 var err = try diags.addErrorWithNotes(2);971 var err = try diags.addErrorWithNotes(2);
958 try err.addMsg("symbol '{s}' mismatching global types", .{sym_name});972 try err.addMsg("symbol '{s}' mismatching global types", .{sym_name});
959 try err.addNote("first definition in '{s}'", .{existing_file_path});973 try err.addNote("first definition in '{'}'", .{existing_file_path});
960 try err.addNote("next definition in '{s}'", .{obj_path});974 try err.addNote("next definition in '{'}'", .{obj_path});
961 }975 }
962 }976 }
963977
...@@ -968,8 +982,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {...@@ -968,8 +982,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
968 var err = try diags.addErrorWithNotes(3);982 var err = try diags.addErrorWithNotes(3);
969 try err.addMsg("symbol '{s}' mismatching function signatures.", .{sym_name});983 try err.addMsg("symbol '{s}' mismatching function signatures.", .{sym_name});
970 try err.addNote("expected signature {}, but found signature {}", .{ existing_ty, new_ty });984 try err.addNote("expected signature {}, but found signature {}", .{ existing_ty, new_ty });
971 try err.addNote("first definition in '{s}'", .{existing_file_path});985 try err.addNote("first definition in '{'}'", .{existing_file_path});
972 try err.addNote("next definition in '{s}'", .{obj_path});986 try err.addNote("next definition in '{'}'", .{obj_path});
973 }987 }
974 }988 }
975989
...@@ -983,8 +997,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {...@@ -983,8 +997,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
983997
984 // simply overwrite with the new symbol998 // simply overwrite with the new symbol
985 log.debug("Overwriting symbol '{s}'", .{sym_name});999 log.debug("Overwriting symbol '{s}'", .{sym_name});
986 log.debug(" old definition in '{s}'", .{existing_file_path});1000 log.debug(" old definition in '{'}'", .{existing_file_path});
987 log.debug(" new definition in '{s}'", .{obj_path});1001 log.debug(" new definition in '{'}'", .{obj_path});
988 try wasm.discarded.putNoClobber(gpa, existing_loc, location);1002 try wasm.discarded.putNoClobber(gpa, existing_loc, location);
989 maybe_existing.value_ptr.* = location;1003 maybe_existing.value_ptr.* = location;
990 try wasm.globals.put(gpa, sym_name_index, location);1004 try wasm.globals.put(gpa, sym_name_index, location);
...@@ -997,31 +1011,29 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {...@@ -997,31 +1011,29 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
997}1011}
9981012
999fn resolveSymbolsInArchives(wasm: *Wasm) !void {1013fn resolveSymbolsInArchives(wasm: *Wasm) !void {
1014 if (wasm.lazy_archives.items.len == 0) return;
1000 const gpa = wasm.base.comp.gpa;1015 const gpa = wasm.base.comp.gpa;
1001 const diags = &wasm.base.comp.link_diags;1016 const diags = &wasm.base.comp.link_diags;
1002 if (wasm.archives.items.len == 0) return;
10031017
1004 log.debug("Resolving symbols in archives", .{});1018 log.debug("Resolving symbols in lazy_archives", .{});
1005 var index: u32 = 0;1019 var index: u32 = 0;
1006 undef_loop: while (index < wasm.undefs.count()) {1020 undef_loop: while (index < wasm.undefs.count()) {
1007 const sym_name_index = wasm.undefs.keys()[index];1021 const sym_name_index = wasm.undefs.keys()[index];
10081022
1009 for (wasm.archives.items) |archive| {1023 for (wasm.lazy_archives.items) |lazy_archive| {
1010 const sym_name = wasm.string_table.get(sym_name_index);1024 const sym_name = wasm.string_table.get(sym_name_index);
1011 log.debug("Detected symbol '{s}' in archive '{s}', parsing objects..", .{ sym_name, archive.name });1025 log.debug("Detected symbol '{s}' in archive '{'}', parsing objects..", .{
1012 const offset = archive.toc.get(sym_name) orelse {1026 sym_name, lazy_archive.path,
1013 // symbol does not exist in this archive1027 });
1014 continue;1028 const offset = lazy_archive.archive.toc.get(sym_name) orelse continue; // symbol does not exist in this archive
1015 };
10161029
1017 // Symbol is found in unparsed object file within current archive.1030 // Symbol is found in unparsed object file within current archive.
1018 // Parse object and and resolve symbols again before we check remaining1031 // Parse object and and resolve symbols again before we check remaining
1019 // undefined symbols.1032 // undefined symbols.
1020 const object = archive.parseObject(wasm, offset.items[0]) catch |e| {1033 const file_contents = lazy_archive.file_contents[offset.items[0]..];
1021 var err_note = try diags.addErrorWithNotes(1);1034 const object = lazy_archive.archive.parseObject(wasm, file_contents, lazy_archive.path) catch |err| {
1022 try err_note.addMsg("Failed parsing object: {s}", .{@errorName(e)});1035 // TODO this fails to include information to identify which object failed
1023 try err_note.addNote("while parsing object in archive {s}", .{archive.name});1036 return diags.failParse(lazy_archive.path, "failed to parse object in archive: {s}", .{@errorName(err)});
1024 return error.FlushFailure;
1025 };1037 };
1026 try wasm.objects.append(gpa, object);1038 try wasm.objects.append(gpa, object);
1027 try wasm.resolveSymbolsInObject(@enumFromInt(wasm.objects.items.len - 1));1039 try wasm.resolveSymbolsInObject(@enumFromInt(wasm.objects.items.len - 1));
...@@ -1323,9 +1335,11 @@ fn validateFeatures(...@@ -1323,9 +1335,11 @@ fn validateFeatures(
1323 allowed[used_index] = is_enabled;1335 allowed[used_index] = is_enabled;
1324 emit_features_count.* += @intFromBool(is_enabled);1336 emit_features_count.* += @intFromBool(is_enabled);
1325 } else if (is_enabled and !allowed[used_index]) {1337 } else if (is_enabled and !allowed[used_index]) {
1326 var err = try diags.addErrorWithNotes(1);1338 diags.addParseError(
1327 try err.addMsg("feature '{}' not allowed, but used by linked object", .{@as(Feature.Tag, @enumFromInt(used_index))});1339 wasm.objects.items[used_set >> 1].path,
1328 try err.addNote("defined in '{s}'", .{wasm.objects.items[used_set >> 1].path});1340 "feature '{}' not allowed, but used by linked object",
1341 .{@as(Feature.Tag, @enumFromInt(used_index))},
1342 );
1329 valid_feature_set = false;1343 valid_feature_set = false;
1330 }1344 }
1331 }1345 }
...@@ -1337,10 +1351,10 @@ fn validateFeatures(...@@ -1337,10 +1351,10 @@ fn validateFeatures(
1337 if (shared_memory) {1351 if (shared_memory) {
1338 const disallowed_feature = disallowed[@intFromEnum(Feature.Tag.shared_mem)];1352 const disallowed_feature = disallowed[@intFromEnum(Feature.Tag.shared_mem)];
1339 if (@as(u1, @truncate(disallowed_feature)) != 0) {1353 if (@as(u1, @truncate(disallowed_feature)) != 0) {
1340 var err = try diags.addErrorWithNotes(0);1354 diags.addParseError(
1341 try err.addMsg(1355 wasm.objects.items[disallowed_feature >> 1].path,
1342 "shared-memory is disallowed by '{s}' because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled",1356 "shared-memory is disallowed because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled",
1343 .{wasm.objects.items[disallowed_feature >> 1].path},1357 .{},
1344 );1358 );
1345 valid_feature_set = false;1359 valid_feature_set = false;
1346 }1360 }
...@@ -1371,8 +1385,8 @@ fn validateFeatures(...@@ -1371,8 +1385,8 @@ fn validateFeatures(
1371 if (@as(u1, @truncate(disallowed_feature)) != 0) {1385 if (@as(u1, @truncate(disallowed_feature)) != 0) {
1372 var err = try diags.addErrorWithNotes(2);1386 var err = try diags.addErrorWithNotes(2);
1373 try err.addMsg("feature '{}' is disallowed, but used by linked object", .{feature.tag});1387 try err.addMsg("feature '{}' is disallowed, but used by linked object", .{feature.tag});
1374 try err.addNote("disallowed by '{s}'", .{wasm.objects.items[disallowed_feature >> 1].path});1388 try err.addNote("disallowed by '{'}'", .{wasm.objects.items[disallowed_feature >> 1].path});
1375 try err.addNote("used in '{s}'", .{object.path});1389 try err.addNote("used in '{'}'", .{object.path});
1376 valid_feature_set = false;1390 valid_feature_set = false;
1377 }1391 }
13781392
...@@ -1385,8 +1399,8 @@ fn validateFeatures(...@@ -1385,8 +1399,8 @@ fn validateFeatures(
1385 if (is_required and !object_used_features[feature_index]) {1399 if (is_required and !object_used_features[feature_index]) {
1386 var err = try diags.addErrorWithNotes(2);1400 var err = try diags.addErrorWithNotes(2);
1387 try err.addMsg("feature '{}' is required but not used in linked object", .{@as(Feature.Tag, @enumFromInt(feature_index))});1401 try err.addMsg("feature '{}' is required but not used in linked object", .{@as(Feature.Tag, @enumFromInt(feature_index))});
1388 try err.addNote("required by '{s}'", .{wasm.objects.items[required_feature >> 1].path});1402 try err.addNote("required by '{'}'", .{wasm.objects.items[required_feature >> 1].path});
1389 try err.addNote("missing in '{s}'", .{object.path});1403 try err.addNote("missing in '{'}'", .{object.path});
1390 valid_feature_set = false;1404 valid_feature_set = false;
1391 }1405 }
1392 }1406 }
...@@ -1460,19 +1474,25 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void {...@@ -1460,19 +1474,25 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void {
1460 const symbol = wasm.symbolLocSymbol(undef);1474 const symbol = wasm.symbolLocSymbol(undef);
1461 if (symbol.tag == .data) {1475 if (symbol.tag == .data) {
1462 found_undefined_symbols = true;1476 found_undefined_symbols = true;
1463 const file_name = switch (undef.file) {
1464 .zig_object => wasm.zig_object.?.path,
1465 .none => wasm.name,
1466 _ => wasm.objects.items[@intFromEnum(undef.file)].path,
1467 };
1468 const symbol_name = wasm.symbolLocName(undef);1477 const symbol_name = wasm.symbolLocName(undef);
1469 var err = try diags.addErrorWithNotes(1);1478 switch (undef.file) {
1470 try err.addMsg("could not resolve undefined symbol '{s}'", .{symbol_name});1479 .zig_object => {
1471 try err.addNote("defined in '{s}'", .{file_name});1480 // TODO: instead of saying the zig compilation unit, attach an actual source location
1481 // to this diagnostic
1482 diags.addError("unresolved symbol in Zig compilation unit: {s}", .{symbol_name});
1483 },
1484 .none => {
1485 diags.addError("internal linker bug: unresolved synthetic symbol: {s}", .{symbol_name});
1486 },
1487 _ => {
1488 const path = wasm.objects.items[@intFromEnum(undef.file)].path;
1489 diags.addParseError(path, "unresolved symbol: {s}", .{symbol_name});
1490 },
1491 }
1472 }1492 }
1473 }1493 }
1474 if (found_undefined_symbols) {1494 if (found_undefined_symbols) {
1475 return error.FlushFailure;1495 return error.LinkFailure;
1476 }1496 }
1477}1497}
14781498
...@@ -1493,9 +1513,8 @@ pub fn deinit(wasm: *Wasm) void {...@@ -1493,9 +1513,8 @@ pub fn deinit(wasm: *Wasm) void {
1493 object.deinit(gpa);1513 object.deinit(gpa);
1494 }1514 }
14951515
1496 for (wasm.archives.items) |*archive| {1516 for (wasm.lazy_archives.items) |*lazy_archive| lazy_archive.deinit(gpa);
1497 archive.deinit(gpa);1517 wasm.lazy_archives.deinit(gpa);
1498 }
14991518
1500 if (wasm.findGlobalSymbol("__wasm_init_tls")) |loc| {1519 if (wasm.findGlobalSymbol("__wasm_init_tls")) |loc| {
1501 const atom = wasm.symbol_atom.get(loc).?;1520 const atom = wasm.symbol_atom.get(loc).?;
...@@ -1514,7 +1533,6 @@ pub fn deinit(wasm: *Wasm) void {...@@ -1514,7 +1533,6 @@ pub fn deinit(wasm: *Wasm) void {
1514 wasm.data_segments.deinit(gpa);1533 wasm.data_segments.deinit(gpa);
1515 wasm.segment_info.deinit(gpa);1534 wasm.segment_info.deinit(gpa);
1516 wasm.objects.deinit(gpa);1535 wasm.objects.deinit(gpa);
1517 wasm.archives.deinit(gpa);
15181536
1519 // free output sections1537 // free output sections
1520 wasm.imports.deinit(gpa);1538 wasm.imports.deinit(gpa);
...@@ -1527,6 +1545,7 @@ pub fn deinit(wasm: *Wasm) void {...@@ -1527,6 +1545,7 @@ pub fn deinit(wasm: *Wasm) void {
1527 wasm.exports.deinit(gpa);1545 wasm.exports.deinit(gpa);
15281546
1529 wasm.string_table.deinit(gpa);1547 wasm.string_table.deinit(gpa);
1548 wasm.dump_argv_list.deinit(gpa);
1530}1549}
15311550
1532pub fn updateFunc(wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {1551pub fn updateFunc(wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
...@@ -2584,7 +2603,7 @@ pub fn getMatchingSegment(wasm: *Wasm, object_id: ObjectId, symbol_index: Symbol...@@ -2584,7 +2603,7 @@ pub fn getMatchingSegment(wasm: *Wasm, object_id: ObjectId, symbol_index: Symbol
2584 } else {2603 } else {
2585 var err = try diags.addErrorWithNotes(1);2604 var err = try diags.addErrorWithNotes(1);
2586 try err.addMsg("found unknown section '{s}'", .{section_name});2605 try err.addMsg("found unknown section '{s}'", .{section_name});
2587 try err.addNote("defined in '{s}'", .{objectPath(wasm, object_id)});2606 try err.addNote("defined in '{'}'", .{objectPath(wasm, object_id)});
2588 return error.UnexpectedValue;2607 return error.UnexpectedValue;
2589 }2608 }
2590 },2609 },
...@@ -2603,6 +2622,32 @@ fn appendDummySegment(wasm: *Wasm) !void {...@@ -2603,6 +2622,32 @@ fn appendDummySegment(wasm: *Wasm) !void {
2603 });2622 });
2604}2623}
26052624
2625pub fn loadInput(wasm: *Wasm, input: link.Input) !void {
2626 const comp = wasm.base.comp;
2627 const gpa = comp.gpa;
2628
2629 if (comp.verbose_link) {
2630 comp.mutex.lock(); // protect comp.arena
2631 defer comp.mutex.unlock();
2632
2633 const argv = &wasm.dump_argv_list;
2634 switch (input) {
2635 .res => unreachable,
2636 .dso_exact => unreachable,
2637 .dso => unreachable,
2638 .object, .archive => |obj| try argv.append(gpa, try obj.path.toString(comp.arena)),
2639 }
2640 }
2641
2642 switch (input) {
2643 .res => unreachable,
2644 .dso_exact => unreachable,
2645 .dso => unreachable,
2646 .object => |obj| try parseObject(wasm, obj),
2647 .archive => |obj| try parseArchive(wasm, obj),
2648 }
2649}
2650
2606pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {2651pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
2607 const comp = wasm.base.comp;2652 const comp = wasm.base.comp;
2608 const use_lld = build_options.have_llvm and comp.config.use_lld;2653 const use_lld = build_options.have_llvm and comp.config.use_lld;
...@@ -2613,7 +2658,6 @@ pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: st...@@ -2613,7 +2658,6 @@ pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: st
2613 return wasm.flushModule(arena, tid, prog_node);2658 return wasm.flushModule(arena, tid, prog_node);
2614}2659}
26152660
2616/// Uses the in-house linker to link one or multiple object -and archive files into a WebAssembly binary.
2617pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {2661pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
2618 const tracy = trace(@src());2662 const tracy = trace(@src());
2619 defer tracy.end();2663 defer tracy.end();
...@@ -2626,85 +2670,22 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no...@@ -2626,85 +2670,22 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
2626 if (use_lld) return;2670 if (use_lld) return;
2627 }2671 }
26282672
2673 if (comp.verbose_link) Compilation.dump_argv(wasm.dump_argv_list.items);
2674
2629 const sub_prog_node = prog_node.start("Wasm Flush", 0);2675 const sub_prog_node = prog_node.start("Wasm Flush", 0);
2630 defer sub_prog_node.end();2676 defer sub_prog_node.end();
26312677
2632 const directory = wasm.base.emit.root_dir; // Just an alias to make it shorter to type.2678 const module_obj_path: ?Path = if (wasm.base.zcu_object_sub_path) |path| .{
2633 const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.emit.sub_path});2679 .root_dir = wasm.base.emit.root_dir,
2634 const module_obj_path: ?[]const u8 = if (wasm.base.zcu_object_sub_path) |path| blk: {2680 .sub_path = if (fs.path.dirname(wasm.base.emit.sub_path)) |dirname|
2635 if (fs.path.dirname(full_out_path)) |dirname| {2681 try fs.path.join(arena, &.{ dirname, path })
2636 break :blk try fs.path.join(arena, &.{ dirname, path });2682 else
2637 } else {2683 path,
2638 break :blk path;
2639 }
2640 } else null;2684 } else null;
26412685
2642 // Positional arguments to the linker such as object files and static archives.2686 if (wasm.zig_object) |zig_object| try zig_object.flushModule(wasm, tid);
2643 // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list.
2644 var positionals = std.ArrayList([]const u8).init(arena);
2645 try positionals.ensureUnusedCapacity(comp.link_inputs.len);
26462687
2647 const target = comp.root_mod.resolved_target.result;2688 if (module_obj_path) |path| openParseObjectReportingFailure(wasm, path);
2648 const output_mode = comp.config.output_mode;
2649 const link_mode = comp.config.link_mode;
2650 const link_libc = comp.config.link_libc;
2651 const link_libcpp = comp.config.link_libcpp;
2652 const wasi_exec_model = comp.config.wasi_exec_model;
2653
2654 if (wasm.zig_object) |zig_object| {
2655 try zig_object.flushModule(wasm, tid);
2656 }
2657
2658 // When the target os is WASI, we allow linking with WASI-LIBC
2659 if (target.os.tag == .wasi) {
2660 const is_exe_or_dyn_lib = output_mode == .Exe or
2661 (output_mode == .Lib and link_mode == .dynamic);
2662 if (is_exe_or_dyn_lib) {
2663 for (comp.wasi_emulated_libs) |crt_file| {
2664 try positionals.append(try comp.crtFileAsString(
2665 arena,
2666 wasi_libc.emulatedLibCRFileLibName(crt_file),
2667 ));
2668 }
2669
2670 if (link_libc) {
2671 try positionals.append(try comp.crtFileAsString(
2672 arena,
2673 wasi_libc.execModelCrtFileFullName(wasi_exec_model),
2674 ));
2675 try positionals.append(try comp.crtFileAsString(arena, "libc.a"));
2676 }
2677
2678 if (link_libcpp) {
2679 try positionals.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena));
2680 try positionals.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena));
2681 }
2682 }
2683 }
2684
2685 if (module_obj_path) |path| {
2686 try positionals.append(path);
2687 }
2688
2689 for (comp.link_inputs) |link_input| switch (link_input) {
2690 .object, .archive => |obj| try positionals.append(try obj.path.toString(arena)),
2691 .dso => |dso| try positionals.append(try dso.path.toString(arena)),
2692 .dso_exact => unreachable, // forbidden by frontend
2693 .res => unreachable, // windows only
2694 };
2695
2696 for (comp.c_object_table.keys()) |c_object| {
2697 try positionals.append(try c_object.status.success.object_path.toString(arena));
2698 }
2699
2700 if (comp.compiler_rt_lib) |lib| try positionals.append(try lib.full_object_path.toString(arena));
2701 if (comp.compiler_rt_obj) |obj| try positionals.append(try obj.full_object_path.toString(arena));
2702
2703 for (positionals.items) |path| {
2704 if (try wasm.parseObjectFile(path)) continue;
2705 if (try wasm.parseArchive(path, false)) continue; // load archives lazily
2706 log.warn("Unexpected file format at path: '{s}'", .{path});
2707 }
27082689
2709 if (wasm.zig_object != null) {2690 if (wasm.zig_object != null) {
2710 try wasm.resolveSymbolsInObject(.zig_object);2691 try wasm.resolveSymbolsInObject(.zig_object);
...@@ -3594,7 +3575,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:...@@ -3594,7 +3575,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
3594 // regarding eliding redundant object -> object transformations.3575 // regarding eliding redundant object -> object transformations.
3595 return error.NoObjectsToLink;3576 return error.NoObjectsToLink;
3596 };3577 };
3597 try std.fs.Dir.copyFile(3578 try fs.Dir.copyFile(
3598 the_object_path.root_dir.handle,3579 the_object_path.root_dir.handle,
3599 the_object_path.sub_path,3580 the_object_path.sub_path,
3600 directory.handle,3581 directory.handle,
src/link/Wasm/Archive.zig+60-66
...@@ -1,18 +1,17 @@...@@ -1,18 +1,17 @@
1file: fs.File,1header: ar_hdr,
2name: []const u8,
3
4header: ar_hdr = undefined,
52
6/// A list of long file names, delimited by a LF character (0x0a).3/// A list of long file names, delimited by a LF character (0x0a).
7/// This is stored as a single slice of bytes, as the header-names4/// This is stored as a single slice of bytes, as the header-names
8/// point to the character index of a file name, rather than the index5/// point to the character index of a file name, rather than the index
9/// in the list.6/// in the list.
10long_file_names: []const u8 = undefined,7long_file_names: []const u8,
118
12/// Parsed table of contents.9/// Parsed table of contents.
13/// Each symbol name points to a list of all definition10/// Each symbol name points to a list of all definition
14/// sites within the current static archive.11/// sites within the current static archive.
15toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .empty,12toc: Toc,
13
14const Toc = std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32));
1615
17// Archive files start with the ARMAG identifying string. Then follows a16// Archive files start with the ARMAG identifying string. Then follows a
18// `struct ar_hdr', and as many bytes of member file data as its `ar_size'17// `struct ar_hdr', and as many bytes of member file data as its `ar_size'
...@@ -82,35 +81,39 @@ const ar_hdr = extern struct {...@@ -82,35 +81,39 @@ const ar_hdr = extern struct {
82 }81 }
83};82};
8483
85pub fn deinit(archive: *Archive, allocator: Allocator) void {84pub fn deinit(archive: *Archive, gpa: Allocator) void {
86 archive.file.close();85 deinitToc(gpa, &archive.toc);
87 for (archive.toc.keys()) |*key| {86 gpa.free(archive.long_file_names);
88 allocator.free(key.*);87 archive.* = undefined;
89 }88}
90 for (archive.toc.values()) |*value| {89
91 value.deinit(allocator);90fn deinitToc(gpa: Allocator, toc: *Toc) void {
92 }91 for (toc.keys()) |key| gpa.free(key);
93 archive.toc.deinit(allocator);92 for (toc.values()) |*value| value.deinit(gpa);
94 allocator.free(archive.long_file_names);93 toc.deinit(gpa);
95}94}
9695
97pub fn parse(archive: *Archive, allocator: Allocator) !void {96pub fn parse(gpa: Allocator, file_contents: []const u8) !Archive {
98 const reader = archive.file.reader();97 var fbs = std.io.fixedBufferStream(file_contents);
98 const reader = fbs.reader();
9999
100 const magic = try reader.readBytesNoEof(SARMAG);100 const magic = try reader.readBytesNoEof(SARMAG);
101 if (!mem.eql(u8, &magic, ARMAG)) {101 if (!mem.eql(u8, &magic, ARMAG)) return error.BadArchiveMagic;
102 log.debug("invalid magic: expected '{s}', found '{s}'", .{ ARMAG, magic });
103 return error.NotArchive;
104 }
105102
106 archive.header = try reader.readStruct(ar_hdr);103 const header = try reader.readStruct(ar_hdr);
107 if (!mem.eql(u8, &archive.header.ar_fmag, ARFMAG)) {104 if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) return error.BadHeaderDelimiter;
108 log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, archive.header.ar_fmag });
109 return error.NotArchive;
110 }
111105
112 try archive.parseTableOfContents(allocator, reader);106 var toc = try parseTableOfContents(gpa, header, reader);
113 try archive.parseNameTable(allocator, reader);107 errdefer deinitToc(gpa, &toc);
108
109 const long_file_names = try parseNameTable(gpa, reader);
110 errdefer gpa.free(long_file_names);
111
112 return .{
113 .header = header,
114 .toc = toc,
115 .long_file_names = long_file_names,
116 };
114}117}
115118
116fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 {119fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 {
...@@ -124,24 +127,27 @@ fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 {...@@ -124,24 +127,27 @@ fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 {
124 }127 }
125}128}
126129
127fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype) !void {130fn parseTableOfContents(gpa: Allocator, header: ar_hdr, reader: anytype) !Toc {
128 // size field can have extra spaces padded in front as well as the end,131 // size field can have extra spaces padded in front as well as the end,
129 // so we trim those first before parsing the ASCII value.132 // so we trim those first before parsing the ASCII value.
130 const size_trimmed = mem.trim(u8, &archive.header.ar_size, " ");133 const size_trimmed = mem.trim(u8, &header.ar_size, " ");
131 const sym_tab_size = try std.fmt.parseInt(u32, size_trimmed, 10);134 const sym_tab_size = try std.fmt.parseInt(u32, size_trimmed, 10);
132135
133 const num_symbols = try reader.readInt(u32, .big);136 const num_symbols = try reader.readInt(u32, .big);
134 const symbol_positions = try allocator.alloc(u32, num_symbols);137 const symbol_positions = try gpa.alloc(u32, num_symbols);
135 defer allocator.free(symbol_positions);138 defer gpa.free(symbol_positions);
136 for (symbol_positions) |*index| {139 for (symbol_positions) |*index| {
137 index.* = try reader.readInt(u32, .big);140 index.* = try reader.readInt(u32, .big);
138 }141 }
139142
140 const sym_tab = try allocator.alloc(u8, sym_tab_size - 4 - (4 * num_symbols));143 const sym_tab = try gpa.alloc(u8, sym_tab_size - 4 - (4 * num_symbols));
141 defer allocator.free(sym_tab);144 defer gpa.free(sym_tab);
142145
143 reader.readNoEof(sym_tab) catch return error.IncompleteSymbolTable;146 reader.readNoEof(sym_tab) catch return error.IncompleteSymbolTable;
144147
148 var toc: Toc = .empty;
149 errdefer deinitToc(gpa, &toc);
150
145 var i: usize = 0;151 var i: usize = 0;
146 var pos: usize = 0;152 var pos: usize = 0;
147 while (i < num_symbols) : (i += 1) {153 while (i < num_symbols) : (i += 1) {
...@@ -149,19 +155,21 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype...@@ -149,19 +155,21 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype
149 pos += string.len + 1;155 pos += string.len + 1;
150 if (string.len == 0) continue;156 if (string.len == 0) continue;
151157
152 const name = try allocator.dupe(u8, string);158 const name = try gpa.dupe(u8, string);
153 errdefer allocator.free(name);159 errdefer gpa.free(name);
154 const gop = try archive.toc.getOrPut(allocator, name);160 const gop = try toc.getOrPut(gpa, name);
155 if (gop.found_existing) {161 if (gop.found_existing) {
156 allocator.free(name);162 gpa.free(name);
157 } else {163 } else {
158 gop.value_ptr.* = .{};164 gop.value_ptr.* = .{};
159 }165 }
160 try gop.value_ptr.append(allocator, symbol_positions[i]);166 try gop.value_ptr.append(gpa, symbol_positions[i]);
161 }167 }
168
169 return toc;
162}170}
163171
164fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !void {172fn parseNameTable(gpa: Allocator, reader: anytype) ![]const u8 {
165 const header: ar_hdr = try reader.readStruct(ar_hdr);173 const header: ar_hdr = try reader.readStruct(ar_hdr);
166 if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) {174 if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) {
167 return error.InvalidHeaderDelimiter;175 return error.InvalidHeaderDelimiter;
...@@ -170,40 +178,25 @@ fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !voi...@@ -170,40 +178,25 @@ fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !voi
170 return error.MissingTableName;178 return error.MissingTableName;
171 }179 }
172 const table_size = try header.size();180 const table_size = try header.size();
173 const long_file_names = try allocator.alloc(u8, table_size);181 const long_file_names = try gpa.alloc(u8, table_size);
174 errdefer allocator.free(long_file_names);182 errdefer gpa.free(long_file_names);
175 try reader.readNoEof(long_file_names);183 try reader.readNoEof(long_file_names);
176 archive.long_file_names = long_file_names;184
185 return long_file_names;
177}186}
178187
179/// From a given file offset, starts reading for a file header.188/// From a given file offset, starts reading for a file header.
180/// When found, parses the object file into an `Object` and returns it.189/// When found, parses the object file into an `Object` and returns it.
181pub fn parseObject(archive: Archive, wasm_file: *const Wasm, file_offset: u32) !Object {190pub fn parseObject(archive: Archive, wasm: *const Wasm, file_contents: []const u8, path: Path) !Object {
182 const gpa = wasm_file.base.comp.gpa;191 var fbs = std.io.fixedBufferStream(file_contents);
183 try archive.file.seekTo(file_offset);192 const header = try fbs.reader().readStruct(ar_hdr);
184 const reader = archive.file.reader();
185 const header = try reader.readStruct(ar_hdr);
186 const current_offset = try archive.file.getPos();
187 try archive.file.seekTo(0);
188193
189 if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) {194 if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) return error.BadArchiveHeaderDelimiter;
190 return error.InvalidHeaderDelimiter;
191 }
192195
193 const object_name = try archive.parseName(header);196 const object_name = try archive.parseName(header);
194 const name = name: {
195 var buffer: [std.fs.max_path_bytes]u8 = undefined;
196 const path = try std.posix.realpath(archive.name, &buffer);
197 break :name try std.fmt.allocPrint(gpa, "{s}({s})", .{ path, object_name });
198 };
199 defer gpa.free(name);
200
201 const object_file = try std.fs.cwd().openFile(archive.name, .{});
202 errdefer object_file.close();
203
204 const object_file_size = try header.size();197 const object_file_size = try header.size();
205 try object_file.seekTo(current_offset);198
206 return Object.create(wasm_file, object_file, name, object_file_size);199 return Object.create(wasm, file_contents[@sizeOf(ar_hdr)..][0..object_file_size], path, object_name);
207}200}
208201
209const std = @import("std");202const std = @import("std");
...@@ -211,6 +204,7 @@ const assert = std.debug.assert;...@@ -211,6 +204,7 @@ const assert = std.debug.assert;
211const fs = std.fs;204const fs = std.fs;
212const log = std.log.scoped(.archive);205const log = std.log.scoped(.archive);
213const mem = std.mem;206const mem = std.mem;
207const Path = std.Build.Cache.Path;
214208
215const Allocator = mem.Allocator;209const Allocator = mem.Allocator;
216const Object = @import("Object.zig");210const Object = @import("Object.zig");
src/link/Wasm/Object.zig+496-576
...@@ -12,15 +12,19 @@ const std = @import("std");...@@ -12,15 +12,19 @@ const std = @import("std");
12const Allocator = std.mem.Allocator;12const Allocator = std.mem.Allocator;
13const leb = std.leb;13const leb = std.leb;
14const meta = std.meta;14const meta = std.meta;
15const Path = std.Build.Cache.Path;
1516
16const log = std.log.scoped(.object);17const log = std.log.scoped(.object);
1718
18/// Wasm spec version used for this `Object`19/// Wasm spec version used for this `Object`
19version: u32 = 0,20version: u32 = 0,
20/// The file descriptor that represents the wasm object file.21/// For error reporting purposes only.
21file: ?std.fs.File = null,22/// Name (read path) of the object or archive file.
22/// Name (read path) of the object file.23path: Path,
23path: []const u8,24/// For error reporting purposes only.
25/// If this represents an object in an archive, it's the basename of the
26/// object, and path refers to the archive.
27archive_member_name: ?[]const u8,
24/// Parsed type section28/// Parsed type section
25func_types: []const std.wasm.Type = &.{},29func_types: []const std.wasm.Type = &.{},
26/// A list of all imports for this module30/// A list of all imports for this module
...@@ -117,40 +121,28 @@ pub const RelocatableData = struct {...@@ -117,40 +121,28 @@ pub const RelocatableData = struct {
117 }121 }
118};122};
119123
120pub const InitError = error{NotObjectFile} || ParseError || std.fs.File.ReadError;
121
122/// Initializes a new `Object` from a wasm object file.124/// Initializes a new `Object` from a wasm object file.
123/// This also parses and verifies the object file.125/// This also parses and verifies the object file.
124/// When a max size is given, will only parse up to the given size,126/// When a max size is given, will only parse up to the given size,
125/// else will read until the end of the file.127/// else will read until the end of the file.
126pub fn create(wasm_file: *const Wasm, file: std.fs.File, name: []const u8, maybe_max_size: ?usize) InitError!Object {128pub fn create(
127 const gpa = wasm_file.base.comp.gpa;129 wasm: *const Wasm,
130 file_contents: []const u8,
131 path: Path,
132 archive_member_name: ?[]const u8,
133) !Object {
134 const gpa = wasm.base.comp.gpa;
128 var object: Object = .{135 var object: Object = .{
129 .file = file,136 .path = path,
130 .path = try gpa.dupe(u8, name),137 .archive_member_name = archive_member_name,
131 };138 };
132139
133 var is_object_file: bool = false;140 var parser: Parser = .{
134 const size = maybe_max_size orelse size: {141 .object = &object,
135 errdefer gpa.free(object.path);142 .wasm = wasm,
136 const stat = try file.stat();143 .reader = std.io.fixedBufferStream(file_contents),
137 break :size @as(usize, @intCast(stat.size));
138 };144 };
139145 try parser.parseObject(gpa);
140 const file_contents = try gpa.alloc(u8, size);
141 defer gpa.free(file_contents);
142 var file_reader = file.reader();
143 var read: usize = 0;
144 while (read < size) {
145 const n = try file_reader.read(file_contents[read..]);
146 std.debug.assert(n != 0);
147 read += n;
148 }
149 var fbs = std.io.fixedBufferStream(file_contents);
150
151 try object.parse(gpa, wasm_file, fbs.reader(), &is_object_file);
152 errdefer object.deinit(gpa);
153 if (!is_object_file) return error.NotObjectFile;
154146
155 return object;147 return object;
156}148}
...@@ -158,9 +150,6 @@ pub fn create(wasm_file: *const Wasm, file: std.fs.File, name: []const u8, maybe...@@ -158,9 +150,6 @@ pub fn create(wasm_file: *const Wasm, file: std.fs.File, name: []const u8, maybe
158/// Frees all memory of `Object` at once. The given `Allocator` must be150/// Frees all memory of `Object` at once. The given `Allocator` must be
159/// the same allocator that was used when `init` was called.151/// the same allocator that was used when `init` was called.
160pub fn deinit(object: *Object, gpa: Allocator) void {152pub fn deinit(object: *Object, gpa: Allocator) void {
161 if (object.file) |file| {
162 file.close();
163 }
164 for (object.func_types) |func_ty| {153 for (object.func_types) |func_ty| {
165 gpa.free(func_ty.params);154 gpa.free(func_ty.params);
166 gpa.free(func_ty.returns);155 gpa.free(func_ty.returns);
...@@ -199,7 +188,6 @@ pub fn deinit(object: *Object, gpa: Allocator) void {...@@ -199,7 +188,6 @@ pub fn deinit(object: *Object, gpa: Allocator) void {
199 }188 }
200 object.relocatable_data.deinit(gpa);189 object.relocatable_data.deinit(gpa);
201 object.string_table.deinit(gpa);190 object.string_table.deinit(gpa);
202 gpa.free(object.path);
203 object.* = undefined;191 object.* = undefined;
204}192}
205193
...@@ -221,8 +209,8 @@ pub fn findImport(object: *const Object, sym: Symbol) Wasm.Import {...@@ -221,8 +209,8 @@ pub fn findImport(object: *const Object, sym: Symbol) Wasm.Import {
221/// we initialize a new table symbol that corresponds to that import and return that symbol.209/// we initialize a new table symbol that corresponds to that import and return that symbol.
222///210///
223/// When the object file is *NOT* MVP, we return `null`.211/// When the object file is *NOT* MVP, we return `null`.
224fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?Symbol {212fn checkLegacyIndirectFunctionTable(object: *Object, wasm: *const Wasm) !?Symbol {
225 const diags = &wasm_file.base.comp.link_diags;213 const diags = &wasm.base.comp.link_diags;
226214
227 var table_count: usize = 0;215 var table_count: usize = 0;
228 for (object.symtable) |sym| {216 for (object.symtable) |sym| {
...@@ -233,28 +221,19 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S...@@ -233,28 +221,19 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S
233 if (object.imported_tables_count == table_count) return null;221 if (object.imported_tables_count == table_count) return null;
234222
235 if (table_count != 0) {223 if (table_count != 0) {
236 var err = try diags.addErrorWithNotes(1);224 return diags.failParse(object.path, "expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{
237 try err.addMsg("Expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{
238 object.imported_tables_count,225 object.imported_tables_count,
239 table_count,226 table_count,
240 });227 });
241 try err.addNote("defined in '{s}'", .{object.path});
242 return error.MissingTableSymbols;
243 }228 }
244229
245 // MVP object files cannot have any table definitions, only imports (for the indirect function table).230 // MVP object files cannot have any table definitions, only imports (for the indirect function table).
246 if (object.tables.len > 0) {231 if (object.tables.len > 0) {
247 var err = try diags.addErrorWithNotes(1);232 return diags.failParse(object.path, "unexpected table definition without representing table symbols.", .{});
248 try err.addMsg("Unexpected table definition without representing table symbols.", .{});
249 try err.addNote("defined in '{s}'", .{object.path});
250 return error.UnexpectedTable;
251 }233 }
252234
253 if (object.imported_tables_count != 1) {235 if (object.imported_tables_count != 1) {
254 var err = try diags.addErrorWithNotes(1);236 return diags.failParse(object.path, "found more than one table import, but no representing table symbols", .{});
255 try err.addMsg("Found more than one table import, but no representing table symbols", .{});
256 try err.addNote("defined in '{s}'", .{object.path});
257 return error.MissingTableSymbols;
258 }237 }
259238
260 const table_import: Wasm.Import = for (object.imports) |imp| {239 const table_import: Wasm.Import = for (object.imports) |imp| {
...@@ -264,10 +243,9 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S...@@ -264,10 +243,9 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S
264 } else unreachable;243 } else unreachable;
265244
266 if (!std.mem.eql(u8, object.string_table.get(table_import.name), "__indirect_function_table")) {245 if (!std.mem.eql(u8, object.string_table.get(table_import.name), "__indirect_function_table")) {
267 var err = try diags.addErrorWithNotes(1);246 return diags.failParse(object.path, "non-indirect function table import '{s}' is missing a corresponding symbol", .{
268 try err.addMsg("Non-indirect function table import '{s}' is missing a corresponding symbol", .{object.string_table.get(table_import.name)});247 object.string_table.get(table_import.name),
269 try err.addNote("defined in '{s}'", .{object.path});248 });
270 return error.MissingTableSymbols;
271 }249 }
272250
273 var table_symbol: Symbol = .{251 var table_symbol: Symbol = .{
...@@ -282,576 +260,518 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S...@@ -282,576 +260,518 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S
282 return table_symbol;260 return table_symbol;
283}261}
284262
285/// Error set containing parsing errors.263const Parser = struct {
286/// Merged with reader's errorset by `Parser`264 reader: std.io.FixedBufferStream([]const u8),
287pub const ParseError = error{265 /// Object file we're building
288 /// The magic byte is either missing or does not contain \0Asm266 object: *Object,
289 InvalidMagicByte,267 /// Read-only reference to the WebAssembly linker
290 /// The wasm version is either missing or does not match the supported version.268 wasm: *const Wasm,
291 InvalidWasmVersion,
292 /// Expected the functype byte while parsing the Type section but did not find it.
293 ExpectedFuncType,
294 /// Missing an 'end' opcode when defining a constant expression.
295 MissingEndForExpression,
296 /// Missing an 'end' opcode at the end of a body expression.
297 MissingEndForBody,
298 /// The size defined in the section code mismatches with the actual payload size.
299 MalformedSection,
300 /// Stream has reached the end. Unreachable for caller and must be handled internally
301 /// by the parser.
302 EndOfStream,
303 /// Ran out of memory when allocating.
304 OutOfMemory,
305 /// A non-zero flag was provided for comdat info
306 UnexpectedValue,
307 /// An import symbol contains an index to an import that does
308 /// not exist, or no imports were defined.
309 InvalidIndex,
310 /// The section "linking" contains a version that is not supported.
311 UnsupportedVersion,
312 /// When reading the data in leb128 compressed format, its value was overflown.
313 Overflow,
314 /// Found table definitions but no corresponding table symbols
315 MissingTableSymbols,
316 /// Did not expect a table definition, but did find one
317 UnexpectedTable,
318 /// Object file contains a feature that is unknown to the linker
319 UnknownFeature,
320};
321269
322fn parse(object: *Object, gpa: Allocator, wasm_file: *const Wasm, reader: anytype, is_object_file: *bool) Parser(@TypeOf(reader)).Error!void {270 fn parseObject(parser: *Parser, gpa: Allocator) anyerror!void {
323 var parser = Parser(@TypeOf(reader)).init(object, wasm_file, reader);271 {
324 return parser.parseObject(gpa, is_object_file);272 var magic_bytes: [4]u8 = undefined;
325}273 try parser.reader.reader().readNoEof(&magic_bytes);
274 if (!std.mem.eql(u8, &magic_bytes, &std.wasm.magic)) return error.BadObjectMagic;
275 }
326276
327fn Parser(comptime ReaderType: type) type {277 const version = try parser.reader.reader().readInt(u32, .little);
328 return struct {278 parser.object.version = version;
329 const ObjectParser = @This();
330 const Error = ReaderType.Error || ParseError;
331279
332 reader: std.io.CountingReader(ReaderType),280 var saw_linking_section = false;
333 /// Object file we're building
334 object: *Object,
335 /// Read-only reference to the WebAssembly linker
336 wasm_file: *const Wasm,
337281
338 fn init(object: *Object, wasm_file: *const Wasm, reader: ReaderType) ObjectParser {282 var section_index: u32 = 0;
339 return .{ .object = object, .wasm_file = wasm_file, .reader = std.io.countingReader(reader) };283 while (parser.reader.reader().readByte()) |byte| : (section_index += 1) {
340 }284 const len = try readLeb(u32, parser.reader.reader());
285 var limited_reader = std.io.limitedReader(parser.reader.reader(), len);
286 const reader = limited_reader.reader();
287 switch (@as(std.wasm.Section, @enumFromInt(byte))) {
288 .custom => {
289 const name_len = try readLeb(u32, reader);
290 const name = try gpa.alloc(u8, name_len);
291 defer gpa.free(name);
292 try reader.readNoEof(name);
341293
342 /// Verifies that the first 4 bytes contains \0Asm294 if (std.mem.eql(u8, name, "linking")) {
343 fn verifyMagicBytes(parser: *ObjectParser) Error!void {295 saw_linking_section = true;
344 var magic_bytes: [4]u8 = undefined;296 try parser.parseMetadata(gpa, @as(usize, @intCast(reader.context.bytes_left)));
297 } else if (std.mem.startsWith(u8, name, "reloc")) {
298 try parser.parseRelocations(gpa);
299 } else if (std.mem.eql(u8, name, "target_features")) {
300 try parser.parseFeatures(gpa);
301 } else if (std.mem.startsWith(u8, name, ".debug")) {
302 const gop = try parser.object.relocatable_data.getOrPut(gpa, .custom);
303 var relocatable_data: std.ArrayListUnmanaged(RelocatableData) = .empty;
304 defer relocatable_data.deinit(gpa);
305 if (!gop.found_existing) {
306 gop.value_ptr.* = &.{};
307 } else {
308 relocatable_data = std.ArrayListUnmanaged(RelocatableData).fromOwnedSlice(gop.value_ptr.*);
309 }
310 const debug_size = @as(u32, @intCast(reader.context.bytes_left));
311 const debug_content = try gpa.alloc(u8, debug_size);
312 errdefer gpa.free(debug_content);
313 try reader.readNoEof(debug_content);
314
315 try relocatable_data.append(gpa, .{
316 .type = .custom,
317 .data = debug_content.ptr,
318 .size = debug_size,
319 .index = try parser.object.string_table.put(gpa, name),
320 .offset = 0, // debug sections only contain 1 entry, so no need to calculate offset
321 .section_index = section_index,
322 });
323 gop.value_ptr.* = try relocatable_data.toOwnedSlice(gpa);
324 } else {
325 try reader.skipBytes(reader.context.bytes_left, .{});
326 }
327 },
328 .type => {
329 for (try readVec(&parser.object.func_types, reader, gpa)) |*type_val| {
330 if ((try reader.readByte()) != std.wasm.function_type) return error.ExpectedFuncType;
345331
346 try parser.reader.reader().readNoEof(&magic_bytes);332 for (try readVec(&type_val.params, reader, gpa)) |*param| {
347 if (!std.mem.eql(u8, &magic_bytes, &std.wasm.magic)) {333 param.* = try readEnum(std.wasm.Valtype, reader);
348 log.debug("Invalid magic bytes '{s}'", .{&magic_bytes});334 }
349 return error.InvalidMagicByte;335
350 }336 for (try readVec(&type_val.returns, reader, gpa)) |*result| {
351 }337 result.* = try readEnum(std.wasm.Valtype, reader);
338 }
339 }
340 try assertEnd(reader);
341 },
342 .import => {
343 for (try readVec(&parser.object.imports, reader, gpa)) |*import| {
344 const module_len = try readLeb(u32, reader);
345 const module_name = try gpa.alloc(u8, module_len);
346 defer gpa.free(module_name);
347 try reader.readNoEof(module_name);
352348
353 fn parseObject(parser: *ObjectParser, gpa: Allocator, is_object_file: *bool) Error!void {
354 errdefer parser.object.deinit(gpa);
355 try parser.verifyMagicBytes();
356 const version = try parser.reader.reader().readInt(u32, .little);
357 parser.object.version = version;
358
359 var section_index: u32 = 0;
360 while (parser.reader.reader().readByte()) |byte| : (section_index += 1) {
361 const len = try readLeb(u32, parser.reader.reader());
362 var limited_reader = std.io.limitedReader(parser.reader.reader(), len);
363 const reader = limited_reader.reader();
364 switch (@as(std.wasm.Section, @enumFromInt(byte))) {
365 .custom => {
366 const name_len = try readLeb(u32, reader);349 const name_len = try readLeb(u32, reader);
367 const name = try gpa.alloc(u8, name_len);350 const name = try gpa.alloc(u8, name_len);
368 defer gpa.free(name);351 defer gpa.free(name);
369 try reader.readNoEof(name);352 try reader.readNoEof(name);
370353
371 if (std.mem.eql(u8, name, "linking")) {354 const kind = try readEnum(std.wasm.ExternalKind, reader);
372 is_object_file.* = true;355 const kind_value: std.wasm.Import.Kind = switch (kind) {
373 try parser.parseMetadata(gpa, @as(usize, @intCast(reader.context.bytes_left)));356 .function => val: {
374 } else if (std.mem.startsWith(u8, name, "reloc")) {357 parser.object.imported_functions_count += 1;
375 try parser.parseRelocations(gpa);358 break :val .{ .function = try readLeb(u32, reader) };
376 } else if (std.mem.eql(u8, name, "target_features")) {359 },
377 try parser.parseFeatures(gpa);360 .memory => .{ .memory = try readLimits(reader) },
378 } else if (std.mem.startsWith(u8, name, ".debug")) {361 .global => val: {
379 const gop = try parser.object.relocatable_data.getOrPut(gpa, .custom);362 parser.object.imported_globals_count += 1;
380 var relocatable_data: std.ArrayListUnmanaged(RelocatableData) = .empty;363 break :val .{ .global = .{
381 defer relocatable_data.deinit(gpa);
382 if (!gop.found_existing) {
383 gop.value_ptr.* = &.{};
384 } else {
385 relocatable_data = std.ArrayListUnmanaged(RelocatableData).fromOwnedSlice(gop.value_ptr.*);
386 }
387 const debug_size = @as(u32, @intCast(reader.context.bytes_left));
388 const debug_content = try gpa.alloc(u8, debug_size);
389 errdefer gpa.free(debug_content);
390 try reader.readNoEof(debug_content);
391
392 try relocatable_data.append(gpa, .{
393 .type = .custom,
394 .data = debug_content.ptr,
395 .size = debug_size,
396 .index = try parser.object.string_table.put(gpa, name),
397 .offset = 0, // debug sections only contain 1 entry, so no need to calculate offset
398 .section_index = section_index,
399 });
400 gop.value_ptr.* = try relocatable_data.toOwnedSlice(gpa);
401 } else {
402 try reader.skipBytes(reader.context.bytes_left, .{});
403 }
404 },
405 .type => {
406 for (try readVec(&parser.object.func_types, reader, gpa)) |*type_val| {
407 if ((try reader.readByte()) != std.wasm.function_type) return error.ExpectedFuncType;
408
409 for (try readVec(&type_val.params, reader, gpa)) |*param| {
410 param.* = try readEnum(std.wasm.Valtype, reader);
411 }
412
413 for (try readVec(&type_val.returns, reader, gpa)) |*result| {
414 result.* = try readEnum(std.wasm.Valtype, reader);
415 }
416 }
417 try assertEnd(reader);
418 },
419 .import => {
420 for (try readVec(&parser.object.imports, reader, gpa)) |*import| {
421 const module_len = try readLeb(u32, reader);
422 const module_name = try gpa.alloc(u8, module_len);
423 defer gpa.free(module_name);
424 try reader.readNoEof(module_name);
425
426 const name_len = try readLeb(u32, reader);
427 const name = try gpa.alloc(u8, name_len);
428 defer gpa.free(name);
429 try reader.readNoEof(name);
430
431 const kind = try readEnum(std.wasm.ExternalKind, reader);
432 const kind_value: std.wasm.Import.Kind = switch (kind) {
433 .function => val: {
434 parser.object.imported_functions_count += 1;
435 break :val .{ .function = try readLeb(u32, reader) };
436 },
437 .memory => .{ .memory = try readLimits(reader) },
438 .global => val: {
439 parser.object.imported_globals_count += 1;
440 break :val .{ .global = .{
441 .valtype = try readEnum(std.wasm.Valtype, reader),
442 .mutable = (try reader.readByte()) == 0x01,
443 } };
444 },
445 .table => val: {
446 parser.object.imported_tables_count += 1;
447 break :val .{ .table = .{
448 .reftype = try readEnum(std.wasm.RefType, reader),
449 .limits = try readLimits(reader),
450 } };
451 },
452 };
453
454 import.* = .{
455 .module_name = try parser.object.string_table.put(gpa, module_name),
456 .name = try parser.object.string_table.put(gpa, name),
457 .kind = kind_value,
458 };
459 }
460 try assertEnd(reader);
461 },
462 .function => {
463 for (try readVec(&parser.object.functions, reader, gpa)) |*func| {
464 func.* = .{ .type_index = try readLeb(u32, reader) };
465 }
466 try assertEnd(reader);
467 },
468 .table => {
469 for (try readVec(&parser.object.tables, reader, gpa)) |*table| {
470 table.* = .{
471 .reftype = try readEnum(std.wasm.RefType, reader),
472 .limits = try readLimits(reader),
473 };
474 }
475 try assertEnd(reader);
476 },
477 .memory => {
478 for (try readVec(&parser.object.memories, reader, gpa)) |*memory| {
479 memory.* = .{ .limits = try readLimits(reader) };
480 }
481 try assertEnd(reader);
482 },
483 .global => {
484 for (try readVec(&parser.object.globals, reader, gpa)) |*global| {
485 global.* = .{
486 .global_type = .{
487 .valtype = try readEnum(std.wasm.Valtype, reader),364 .valtype = try readEnum(std.wasm.Valtype, reader),
488 .mutable = (try reader.readByte()) == 0x01,365 .mutable = (try reader.readByte()) == 0x01,
489 },366 } };
490 .init = try readInit(reader),367 },
491 };368 .table => val: {
492 }369 parser.object.imported_tables_count += 1;
493 try assertEnd(reader);370 break :val .{ .table = .{
494 },371 .reftype = try readEnum(std.wasm.RefType, reader),
495 .@"export" => {372 .limits = try readLimits(reader),
496 for (try readVec(&parser.object.exports, reader, gpa)) |*exp| {373 } };
497 const name_len = try readLeb(u32, reader);374 },
498 const name = try gpa.alloc(u8, name_len);
499 defer gpa.free(name);
500 try reader.readNoEof(name);
501 exp.* = .{
502 .name = try parser.object.string_table.put(gpa, name),
503 .kind = try readEnum(std.wasm.ExternalKind, reader),
504 .index = try readLeb(u32, reader),
505 };
506 }
507 try assertEnd(reader);
508 },
509 .start => {
510 parser.object.start = try readLeb(u32, reader);
511 try assertEnd(reader);
512 },
513 .element => {
514 for (try readVec(&parser.object.elements, reader, gpa)) |*elem| {
515 elem.table_index = try readLeb(u32, reader);
516 elem.offset = try readInit(reader);
517
518 for (try readVec(&elem.func_indexes, reader, gpa)) |*idx| {
519 idx.* = try readLeb(u32, reader);
520 }
521 }
522 try assertEnd(reader);
523 },
524 .code => {
525 const start = reader.context.bytes_left;
526 var index: u32 = 0;
527 const count = try readLeb(u32, reader);
528 const imported_function_count = parser.object.imported_functions_count;
529 var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count);
530 defer relocatable_data.deinit();
531 while (index < count) : (index += 1) {
532 const code_len = try readLeb(u32, reader);
533 const offset = @as(u32, @intCast(start - reader.context.bytes_left));
534 const data = try gpa.alloc(u8, code_len);
535 errdefer gpa.free(data);
536 try reader.readNoEof(data);
537 relocatable_data.appendAssumeCapacity(.{
538 .type = .code,
539 .data = data.ptr,
540 .size = code_len,
541 .index = imported_function_count + index,
542 .offset = offset,
543 .section_index = section_index,
544 });
545 }
546 try parser.object.relocatable_data.put(gpa, .code, try relocatable_data.toOwnedSlice());
547 },
548 .data => {
549 const start = reader.context.bytes_left;
550 var index: u32 = 0;
551 const count = try readLeb(u32, reader);
552 var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count);
553 defer relocatable_data.deinit();
554 while (index < count) : (index += 1) {
555 const flags = try readLeb(u32, reader);
556 const data_offset = try readInit(reader);
557 _ = flags; // TODO: Do we need to check flags to detect passive/active memory?
558 _ = data_offset;
559 const data_len = try readLeb(u32, reader);
560 const offset = @as(u32, @intCast(start - reader.context.bytes_left));
561 const data = try gpa.alloc(u8, data_len);
562 errdefer gpa.free(data);
563 try reader.readNoEof(data);
564 relocatable_data.appendAssumeCapacity(.{
565 .type = .data,
566 .data = data.ptr,
567 .size = data_len,
568 .index = index,
569 .offset = offset,
570 .section_index = section_index,
571 });
572 }
573 try parser.object.relocatable_data.put(gpa, .data, try relocatable_data.toOwnedSlice());
574 },
575 else => try parser.reader.reader().skipBytes(len, .{}),
576 }
577 } else |err| switch (err) {
578 error.EndOfStream => {}, // finished parsing the file
579 else => |e| return e,
580 }
581 }
582
583 /// Based on the "features" custom section, parses it into a list of
584 /// features that tell the linker what features were enabled and may be mandatory
585 /// to be able to link.
586 /// Logs an info message when an undefined feature is detected.
587 fn parseFeatures(parser: *ObjectParser, gpa: Allocator) !void {
588 const diags = &parser.wasm_file.base.comp.link_diags;
589 const reader = parser.reader.reader();
590 for (try readVec(&parser.object.features, reader, gpa)) |*feature| {
591 const prefix = try readEnum(Wasm.Feature.Prefix, reader);
592 const name_len = try leb.readUleb128(u32, reader);
593 const name = try gpa.alloc(u8, name_len);
594 defer gpa.free(name);
595 try reader.readNoEof(name);
596
597 const tag = Wasm.known_features.get(name) orelse {
598 var err = try diags.addErrorWithNotes(1);
599 try err.addMsg("Object file contains unknown feature: {s}", .{name});
600 try err.addNote("defined in '{s}'", .{parser.object.path});
601 return error.UnknownFeature;
602 };
603 feature.* = .{
604 .prefix = prefix,
605 .tag = tag,
606 };
607 }
608 }
609
610 /// Parses a "reloc" custom section into a list of relocations.
611 /// The relocations are mapped into `Object` where the key is the section
612 /// they apply to.
613 fn parseRelocations(parser: *ObjectParser, gpa: Allocator) !void {
614 const reader = parser.reader.reader();
615 const section = try leb.readUleb128(u32, reader);
616 const count = try leb.readUleb128(u32, reader);
617 const relocations = try gpa.alloc(Wasm.Relocation, count);
618 errdefer gpa.free(relocations);
619
620 log.debug("Found {d} relocations for section ({d})", .{
621 count,
622 section,
623 });
624
625 for (relocations) |*relocation| {
626 const rel_type = try reader.readByte();
627 const rel_type_enum = std.meta.intToEnum(Wasm.Relocation.RelocationType, rel_type) catch return error.MalformedSection;
628 relocation.* = .{
629 .relocation_type = rel_type_enum,
630 .offset = try leb.readUleb128(u32, reader),
631 .index = try leb.readUleb128(u32, reader),
632 .addend = if (rel_type_enum.addendIsPresent()) try leb.readIleb128(i32, reader) else 0,
633 };
634 log.debug("Found relocation: type({s}) offset({d}) index({d}) addend({?d})", .{
635 @tagName(relocation.relocation_type),
636 relocation.offset,
637 relocation.index,
638 relocation.addend,
639 });
640 }
641
642 try parser.object.relocations.putNoClobber(gpa, section, relocations);
643 }
644
645 /// Parses the "linking" custom section. Versions that are not
646 /// supported will be an error. `payload_size` is required to be able
647 /// to calculate the subsections we need to parse, as that data is not
648 /// available within the section itparser.
649 fn parseMetadata(parser: *ObjectParser, gpa: Allocator, payload_size: usize) !void {
650 var limited = std.io.limitedReader(parser.reader.reader(), payload_size);
651 const limited_reader = limited.reader();
652
653 const version = try leb.readUleb128(u32, limited_reader);
654 log.debug("Link meta data version: {d}", .{version});
655 if (version != 2) return error.UnsupportedVersion;
656
657 while (limited.bytes_left > 0) {
658 try parser.parseSubsection(gpa, limited_reader);
659 }
660 }
661
662 /// Parses a `spec.Subsection`.
663 /// The `reader` param for this is to provide a `LimitedReader`, which allows
664 /// us to only read until a max length.
665 ///
666 /// `parser` is used to provide access to other sections that may be needed,
667 /// such as access to the `import` section to find the name of a symbol.
668 fn parseSubsection(parser: *ObjectParser, gpa: Allocator, reader: anytype) !void {
669 const sub_type = try leb.readUleb128(u8, reader);
670 log.debug("Found subsection: {s}", .{@tagName(@as(Wasm.SubsectionType, @enumFromInt(sub_type)))});
671 const payload_len = try leb.readUleb128(u32, reader);
672 if (payload_len == 0) return;
673
674 var limited = std.io.limitedReader(reader, payload_len);
675 const limited_reader = limited.reader();
676
677 // every subsection contains a 'count' field
678 const count = try leb.readUleb128(u32, limited_reader);
679
680 switch (@as(Wasm.SubsectionType, @enumFromInt(sub_type))) {
681 .WASM_SEGMENT_INFO => {
682 const segments = try gpa.alloc(Wasm.NamedSegment, count);
683 errdefer gpa.free(segments);
684 for (segments) |*segment| {
685 const name_len = try leb.readUleb128(u32, reader);
686 const name = try gpa.alloc(u8, name_len);
687 errdefer gpa.free(name);
688 try reader.readNoEof(name);
689 segment.* = .{
690 .name = name,
691 .alignment = @enumFromInt(try leb.readUleb128(u32, reader)),
692 .flags = try leb.readUleb128(u32, reader),
693 };375 };
694 log.debug("Found segment: {s} align({d}) flags({b})", .{
695 segment.name,
696 segment.alignment,
697 segment.flags,
698 });
699376
700 // support legacy object files that specified being TLS by the name instead of the TLS flag.377 import.* = .{
701 if (!segment.isTLS() and (std.mem.startsWith(u8, segment.name, ".tdata") or std.mem.startsWith(u8, segment.name, ".tbss"))) {378 .module_name = try parser.object.string_table.put(gpa, module_name),
702 // set the flag so we can simply check for the flag in the rest of the linker.379 .name = try parser.object.string_table.put(gpa, name),
703 segment.flags |= @intFromEnum(Wasm.NamedSegment.Flags.WASM_SEG_FLAG_TLS);380 .kind = kind_value,
704 }381 };
382 }
383 try assertEnd(reader);
384 },
385 .function => {
386 for (try readVec(&parser.object.functions, reader, gpa)) |*func| {
387 func.* = .{ .type_index = try readLeb(u32, reader) };
388 }
389 try assertEnd(reader);
390 },
391 .table => {
392 for (try readVec(&parser.object.tables, reader, gpa)) |*table| {
393 table.* = .{
394 .reftype = try readEnum(std.wasm.RefType, reader),
395 .limits = try readLimits(reader),
396 };
397 }
398 try assertEnd(reader);
399 },
400 .memory => {
401 for (try readVec(&parser.object.memories, reader, gpa)) |*memory| {
402 memory.* = .{ .limits = try readLimits(reader) };
705 }403 }
706 parser.object.segment_info = segments;404 try assertEnd(reader);
707 },405 },
708 .WASM_INIT_FUNCS => {406 .global => {
709 const funcs = try gpa.alloc(Wasm.InitFunc, count);407 for (try readVec(&parser.object.globals, reader, gpa)) |*global| {
710 errdefer gpa.free(funcs);408 global.* = .{
711 for (funcs) |*func| {409 .global_type = .{
712 func.* = .{410 .valtype = try readEnum(std.wasm.Valtype, reader),
713 .priority = try leb.readUleb128(u32, reader),411 .mutable = (try reader.readByte()) == 0x01,
714 .symbol_index = try leb.readUleb128(u32, reader),412 },
413 .init = try readInit(reader),
715 };414 };
716 log.debug("Found function - prio: {d}, index: {d}", .{ func.priority, func.symbol_index });
717 }415 }
718 parser.object.init_funcs = funcs;416 try assertEnd(reader);
719 },417 },
720 .WASM_COMDAT_INFO => {418 .@"export" => {
721 const comdats = try gpa.alloc(Wasm.Comdat, count);419 for (try readVec(&parser.object.exports, reader, gpa)) |*exp| {
722 errdefer gpa.free(comdats);420 const name_len = try readLeb(u32, reader);
723 for (comdats) |*comdat| {
724 const name_len = try leb.readUleb128(u32, reader);
725 const name = try gpa.alloc(u8, name_len);421 const name = try gpa.alloc(u8, name_len);
726 errdefer gpa.free(name);422 defer gpa.free(name);
727 try reader.readNoEof(name);423 try reader.readNoEof(name);
728424 exp.* = .{
729 const flags = try leb.readUleb128(u32, reader);425 .name = try parser.object.string_table.put(gpa, name),
730 if (flags != 0) {426 .kind = try readEnum(std.wasm.ExternalKind, reader),
731 return error.UnexpectedValue;427 .index = try readLeb(u32, reader),
732 }
733
734 const symbol_count = try leb.readUleb128(u32, reader);
735 const symbols = try gpa.alloc(Wasm.ComdatSym, symbol_count);
736 errdefer gpa.free(symbols);
737 for (symbols) |*symbol| {
738 symbol.* = .{
739 .kind = @as(Wasm.ComdatSym.Type, @enumFromInt(try leb.readUleb128(u8, reader))),
740 .index = try leb.readUleb128(u32, reader),
741 };
742 }
743
744 comdat.* = .{
745 .name = name,
746 .flags = flags,
747 .symbols = symbols,
748 };428 };
749 }429 }
430 try assertEnd(reader);
431 },
432 .start => {
433 parser.object.start = try readLeb(u32, reader);
434 try assertEnd(reader);
435 },
436 .element => {
437 for (try readVec(&parser.object.elements, reader, gpa)) |*elem| {
438 elem.table_index = try readLeb(u32, reader);
439 elem.offset = try readInit(reader);
750440
751 parser.object.comdat_info = comdats;441 for (try readVec(&elem.func_indexes, reader, gpa)) |*idx| {
442 idx.* = try readLeb(u32, reader);
443 }
444 }
445 try assertEnd(reader);
752 },446 },
753 .WASM_SYMBOL_TABLE => {447 .code => {
754 var symbols = try std.ArrayList(Symbol).initCapacity(gpa, count);448 const start = reader.context.bytes_left;
755449 var index: u32 = 0;
756 var i: usize = 0;450 const count = try readLeb(u32, reader);
757 while (i < count) : (i += 1) {451 const imported_function_count = parser.object.imported_functions_count;
758 const symbol = symbols.addOneAssumeCapacity();452 var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count);
759 symbol.* = try parser.parseSymbol(gpa, reader);453 defer relocatable_data.deinit();
760 log.debug("Found symbol: type({s}) name({s}) flags(0b{b:0>8})", .{454 while (index < count) : (index += 1) {
761 @tagName(symbol.tag),455 const code_len = try readLeb(u32, reader);
762 parser.object.string_table.get(symbol.name),456 const offset = @as(u32, @intCast(start - reader.context.bytes_left));
763 symbol.flags,457 const data = try gpa.alloc(u8, code_len);
458 errdefer gpa.free(data);
459 try reader.readNoEof(data);
460 relocatable_data.appendAssumeCapacity(.{
461 .type = .code,
462 .data = data.ptr,
463 .size = code_len,
464 .index = imported_function_count + index,
465 .offset = offset,
466 .section_index = section_index,
764 });467 });
765 }468 }
766469 try parser.object.relocatable_data.put(gpa, .code, try relocatable_data.toOwnedSlice());
767 // we found all symbols, check for indirect function table470 },
768 // in case of an MVP object file471 .data => {
769 if (try parser.object.checkLegacyIndirectFunctionTable(parser.wasm_file)) |symbol| {472 const start = reader.context.bytes_left;
770 try symbols.append(symbol);473 var index: u32 = 0;
771 log.debug("Found legacy indirect function table. Created symbol", .{});474 const count = try readLeb(u32, reader);
772 }475 var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count);
773476 defer relocatable_data.deinit();
774 // Not all debug sections may be represented by a symbol, for those sections477 while (index < count) : (index += 1) {
775 // we manually create a symbol.478 const flags = try readLeb(u32, reader);
776 if (parser.object.relocatable_data.get(.custom)) |custom_sections| {479 const data_offset = try readInit(reader);
777 for (custom_sections) |*data| {480 _ = flags; // TODO: Do we need to check flags to detect passive/active memory?
778 if (!data.represented) {481 _ = data_offset;
779 try symbols.append(.{482 const data_len = try readLeb(u32, reader);
780 .name = data.index,483 const offset = @as(u32, @intCast(start - reader.context.bytes_left));
781 .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),484 const data = try gpa.alloc(u8, data_len);
782 .tag = .section,485 errdefer gpa.free(data);
783 .virtual_address = 0,486 try reader.readNoEof(data);
784 .index = data.section_index,487 relocatable_data.appendAssumeCapacity(.{
785 });488 .type = .data,
786 data.represented = true;489 .data = data.ptr,
787 log.debug("Created synthetic custom section symbol for '{s}'", .{parser.object.string_table.get(data.index)});490 .size = data_len,
788 }491 .index = index,
789 }492 .offset = offset,
493 .section_index = section_index,
494 });
790 }495 }
791496 try parser.object.relocatable_data.put(gpa, .data, try relocatable_data.toOwnedSlice());
792 parser.object.symtable = try symbols.toOwnedSlice();
793 },497 },
498 else => try parser.reader.reader().skipBytes(len, .{}),
794 }499 }
500 } else |err| switch (err) {
501 error.EndOfStream => {}, // finished parsing the file
502 else => |e| return e,
795 }503 }
504 if (!saw_linking_section) return error.MissingLinkingSection;
505 }
796506
797 /// Parses the symbol information based on its kind,507 /// Based on the "features" custom section, parses it into a list of
798 /// requires access to `Object` to find the name of a symbol when it's508 /// features that tell the linker what features were enabled and may be mandatory
799 /// an import and flag `WASM_SYM_EXPLICIT_NAME` is not set.509 /// to be able to link.
800 fn parseSymbol(parser: *ObjectParser, gpa: Allocator, reader: anytype) !Symbol {510 /// Logs an info message when an undefined feature is detected.
801 const tag = @as(Symbol.Tag, @enumFromInt(try leb.readUleb128(u8, reader)));511 fn parseFeatures(parser: *Parser, gpa: Allocator) !void {
802 const flags = try leb.readUleb128(u32, reader);512 const diags = &parser.wasm.base.comp.link_diags;
803 var symbol: Symbol = .{513 const reader = parser.reader.reader();
804 .flags = flags,514 for (try readVec(&parser.object.features, reader, gpa)) |*feature| {
515 const prefix = try readEnum(Wasm.Feature.Prefix, reader);
516 const name_len = try leb.readUleb128(u32, reader);
517 const name = try gpa.alloc(u8, name_len);
518 defer gpa.free(name);
519 try reader.readNoEof(name);
520
521 const tag = Wasm.known_features.get(name) orelse {
522 return diags.failParse(parser.object.path, "object file contains unknown feature: {s}", .{name});
523 };
524 feature.* = .{
525 .prefix = prefix,
805 .tag = tag,526 .tag = tag,
806 .name = undefined,
807 .index = undefined,
808 .virtual_address = undefined,
809 };527 };
528 }
529 }
810530
811 switch (tag) {531 /// Parses a "reloc" custom section into a list of relocations.
812 .data => {532 /// The relocations are mapped into `Object` where the key is the section
533 /// they apply to.
534 fn parseRelocations(parser: *Parser, gpa: Allocator) !void {
535 const reader = parser.reader.reader();
536 const section = try leb.readUleb128(u32, reader);
537 const count = try leb.readUleb128(u32, reader);
538 const relocations = try gpa.alloc(Wasm.Relocation, count);
539 errdefer gpa.free(relocations);
540
541 log.debug("Found {d} relocations for section ({d})", .{
542 count,
543 section,
544 });
545
546 for (relocations) |*relocation| {
547 const rel_type = try reader.readByte();
548 const rel_type_enum = std.meta.intToEnum(Wasm.Relocation.RelocationType, rel_type) catch return error.MalformedSection;
549 relocation.* = .{
550 .relocation_type = rel_type_enum,
551 .offset = try leb.readUleb128(u32, reader),
552 .index = try leb.readUleb128(u32, reader),
553 .addend = if (rel_type_enum.addendIsPresent()) try leb.readIleb128(i32, reader) else 0,
554 };
555 log.debug("Found relocation: type({s}) offset({d}) index({d}) addend({?d})", .{
556 @tagName(relocation.relocation_type),
557 relocation.offset,
558 relocation.index,
559 relocation.addend,
560 });
561 }
562
563 try parser.object.relocations.putNoClobber(gpa, section, relocations);
564 }
565
566 /// Parses the "linking" custom section. Versions that are not
567 /// supported will be an error. `payload_size` is required to be able
568 /// to calculate the subsections we need to parse, as that data is not
569 /// available within the section itparser.
570 fn parseMetadata(parser: *Parser, gpa: Allocator, payload_size: usize) !void {
571 var limited = std.io.limitedReader(parser.reader.reader(), payload_size);
572 const limited_reader = limited.reader();
573
574 const version = try leb.readUleb128(u32, limited_reader);
575 log.debug("Link meta data version: {d}", .{version});
576 if (version != 2) return error.UnsupportedVersion;
577
578 while (limited.bytes_left > 0) {
579 try parser.parseSubsection(gpa, limited_reader);
580 }
581 }
582
583 /// Parses a `spec.Subsection`.
584 /// The `reader` param for this is to provide a `LimitedReader`, which allows
585 /// us to only read until a max length.
586 ///
587 /// `parser` is used to provide access to other sections that may be needed,
588 /// such as access to the `import` section to find the name of a symbol.
589 fn parseSubsection(parser: *Parser, gpa: Allocator, reader: anytype) !void {
590 const sub_type = try leb.readUleb128(u8, reader);
591 log.debug("Found subsection: {s}", .{@tagName(@as(Wasm.SubsectionType, @enumFromInt(sub_type)))});
592 const payload_len = try leb.readUleb128(u32, reader);
593 if (payload_len == 0) return;
594
595 var limited = std.io.limitedReader(reader, payload_len);
596 const limited_reader = limited.reader();
597
598 // every subsection contains a 'count' field
599 const count = try leb.readUleb128(u32, limited_reader);
600
601 switch (@as(Wasm.SubsectionType, @enumFromInt(sub_type))) {
602 .WASM_SEGMENT_INFO => {
603 const segments = try gpa.alloc(Wasm.NamedSegment, count);
604 errdefer gpa.free(segments);
605 for (segments) |*segment| {
813 const name_len = try leb.readUleb128(u32, reader);606 const name_len = try leb.readUleb128(u32, reader);
814 const name = try gpa.alloc(u8, name_len);607 const name = try gpa.alloc(u8, name_len);
815 defer gpa.free(name);608 errdefer gpa.free(name);
816 try reader.readNoEof(name);609 try reader.readNoEof(name);
817 symbol.name = try parser.object.string_table.put(gpa, name);610 segment.* = .{
818611 .name = name,
819 // Data symbols only have the following fields if the symbol is defined612 .alignment = @enumFromInt(try leb.readUleb128(u32, reader)),
820 if (symbol.isDefined()) {613 .flags = try leb.readUleb128(u32, reader),
821 symbol.index = try leb.readUleb128(u32, reader);614 };
822 // @TODO: We should verify those values615 log.debug("Found segment: {s} align({d}) flags({b})", .{
823 _ = try leb.readUleb128(u32, reader);616 segment.name,
824 _ = try leb.readUleb128(u32, reader);617 segment.alignment,
618 segment.flags,
619 });
620
621 // support legacy object files that specified being TLS by the name instead of the TLS flag.
622 if (!segment.isTLS() and (std.mem.startsWith(u8, segment.name, ".tdata") or std.mem.startsWith(u8, segment.name, ".tbss"))) {
623 // set the flag so we can simply check for the flag in the rest of the linker.
624 segment.flags |= @intFromEnum(Wasm.NamedSegment.Flags.WASM_SEG_FLAG_TLS);
825 }625 }
826 },626 }
827 .section => {627 parser.object.segment_info = segments;
828 symbol.index = try leb.readUleb128(u32, reader);628 },
829 const section_data = parser.object.relocatable_data.get(.custom).?;629 .WASM_INIT_FUNCS => {
830 for (section_data) |*data| {630 const funcs = try gpa.alloc(Wasm.InitFunc, count);
831 if (data.section_index == symbol.index) {631 errdefer gpa.free(funcs);
832 symbol.name = data.index;632 for (funcs) |*func| {
633 func.* = .{
634 .priority = try leb.readUleb128(u32, reader),
635 .symbol_index = try leb.readUleb128(u32, reader),
636 };
637 log.debug("Found function - prio: {d}, index: {d}", .{ func.priority, func.symbol_index });
638 }
639 parser.object.init_funcs = funcs;
640 },
641 .WASM_COMDAT_INFO => {
642 const comdats = try gpa.alloc(Wasm.Comdat, count);
643 errdefer gpa.free(comdats);
644 for (comdats) |*comdat| {
645 const name_len = try leb.readUleb128(u32, reader);
646 const name = try gpa.alloc(u8, name_len);
647 errdefer gpa.free(name);
648 try reader.readNoEof(name);
649
650 const flags = try leb.readUleb128(u32, reader);
651 if (flags != 0) {
652 return error.UnexpectedValue;
653 }
654
655 const symbol_count = try leb.readUleb128(u32, reader);
656 const symbols = try gpa.alloc(Wasm.ComdatSym, symbol_count);
657 errdefer gpa.free(symbols);
658 for (symbols) |*symbol| {
659 symbol.* = .{
660 .kind = @as(Wasm.ComdatSym.Type, @enumFromInt(try leb.readUleb128(u8, reader))),
661 .index = try leb.readUleb128(u32, reader),
662 };
663 }
664
665 comdat.* = .{
666 .name = name,
667 .flags = flags,
668 .symbols = symbols,
669 };
670 }
671
672 parser.object.comdat_info = comdats;
673 },
674 .WASM_SYMBOL_TABLE => {
675 var symbols = try std.ArrayList(Symbol).initCapacity(gpa, count);
676
677 var i: usize = 0;
678 while (i < count) : (i += 1) {
679 const symbol = symbols.addOneAssumeCapacity();
680 symbol.* = try parser.parseSymbol(gpa, reader);
681 log.debug("Found symbol: type({s}) name({s}) flags(0b{b:0>8})", .{
682 @tagName(symbol.tag),
683 parser.object.string_table.get(symbol.name),
684 symbol.flags,
685 });
686 }
687
688 // we found all symbols, check for indirect function table
689 // in case of an MVP object file
690 if (try parser.object.checkLegacyIndirectFunctionTable(parser.wasm)) |symbol| {
691 try symbols.append(symbol);
692 log.debug("Found legacy indirect function table. Created symbol", .{});
693 }
694
695 // Not all debug sections may be represented by a symbol, for those sections
696 // we manually create a symbol.
697 if (parser.object.relocatable_data.get(.custom)) |custom_sections| {
698 for (custom_sections) |*data| {
699 if (!data.represented) {
700 try symbols.append(.{
701 .name = data.index,
702 .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
703 .tag = .section,
704 .virtual_address = 0,
705 .index = data.section_index,
706 });
833 data.represented = true;707 data.represented = true;
834 break;708 log.debug("Created synthetic custom section symbol for '{s}'", .{parser.object.string_table.get(data.index)});
835 }709 }
836 }710 }
837 },711 }
838 else => {712
713 parser.object.symtable = try symbols.toOwnedSlice();
714 },
715 }
716 }
717
718 /// Parses the symbol information based on its kind,
719 /// requires access to `Object` to find the name of a symbol when it's
720 /// an import and flag `WASM_SYM_EXPLICIT_NAME` is not set.
721 fn parseSymbol(parser: *Parser, gpa: Allocator, reader: anytype) !Symbol {
722 const tag = @as(Symbol.Tag, @enumFromInt(try leb.readUleb128(u8, reader)));
723 const flags = try leb.readUleb128(u32, reader);
724 var symbol: Symbol = .{
725 .flags = flags,
726 .tag = tag,
727 .name = undefined,
728 .index = undefined,
729 .virtual_address = undefined,
730 };
731
732 switch (tag) {
733 .data => {
734 const name_len = try leb.readUleb128(u32, reader);
735 const name = try gpa.alloc(u8, name_len);
736 defer gpa.free(name);
737 try reader.readNoEof(name);
738 symbol.name = try parser.object.string_table.put(gpa, name);
739
740 // Data symbols only have the following fields if the symbol is defined
741 if (symbol.isDefined()) {
839 symbol.index = try leb.readUleb128(u32, reader);742 symbol.index = try leb.readUleb128(u32, reader);
840 const is_undefined = symbol.isUndefined();743 // @TODO: We should verify those values
841 const explicit_name = symbol.hasFlag(.WASM_SYM_EXPLICIT_NAME);744 _ = try leb.readUleb128(u32, reader);
842 symbol.name = if (!is_undefined or (is_undefined and explicit_name)) name: {745 _ = try leb.readUleb128(u32, reader);
843 const name_len = try leb.readUleb128(u32, reader);746 }
844 const name = try gpa.alloc(u8, name_len);747 },
845 defer gpa.free(name);748 .section => {
846 try reader.readNoEof(name);749 symbol.index = try leb.readUleb128(u32, reader);
847 break :name try parser.object.string_table.put(gpa, name);750 const section_data = parser.object.relocatable_data.get(.custom).?;
848 } else parser.object.findImport(symbol).name;751 for (section_data) |*data| {
849 },752 if (data.section_index == symbol.index) {
850 }753 symbol.name = data.index;
851 return symbol;754 data.represented = true;
755 break;
756 }
757 }
758 },
759 else => {
760 symbol.index = try leb.readUleb128(u32, reader);
761 const is_undefined = symbol.isUndefined();
762 const explicit_name = symbol.hasFlag(.WASM_SYM_EXPLICIT_NAME);
763 symbol.name = if (!is_undefined or (is_undefined and explicit_name)) name: {
764 const name_len = try leb.readUleb128(u32, reader);
765 const name = try gpa.alloc(u8, name_len);
766 defer gpa.free(name);
767 try reader.readNoEof(name);
768 break :name try parser.object.string_table.put(gpa, name);
769 } else parser.object.findImport(symbol).name;
770 },
852 }771 }
853 };772 return symbol;
854}773 }
774};
855775
856/// First reads the count from the reader and then allocate776/// First reads the count from the reader and then allocate
857/// a slice of ptr child's element type.777/// a slice of ptr child's element type.
src/link/Wasm/ZigObject.zig+4-3
...@@ -1,9 +1,9 @@...@@ -1,9 +1,9 @@
1//! ZigObject encapsulates the state of the incrementally compiled Zig module.1//! ZigObject encapsulates the state of the incrementally compiled Zig module.
2//! It stores the associated input local and global symbols, allocated atoms,2//! It stores the associated input local and global symbols, allocated atoms,
3//! and any relocations that may have been emitted.3//! and any relocations that may have been emitted.
4//! Think about this as fake in-memory Object file for the Zig module.
54
6path: []const u8,5/// For error reporting purposes only.
6path: Path,
7/// Map of all `Nav` that are currently alive.7/// Map of all `Nav` that are currently alive.
8/// Each index maps to the corresponding `NavInfo`.8/// Each index maps to the corresponding `NavInfo`.
9navs: std.AutoHashMapUnmanaged(InternPool.Nav.Index, NavInfo) = .empty,9navs: std.AutoHashMapUnmanaged(InternPool.Nav.Index, NavInfo) = .empty,
...@@ -210,7 +210,7 @@ pub fn deinit(zig_object: *ZigObject, wasm: *Wasm) void {...@@ -210,7 +210,7 @@ pub fn deinit(zig_object: *ZigObject, wasm: *Wasm) void {
210 if (zig_object.dwarf) |*dwarf| {210 if (zig_object.dwarf) |*dwarf| {
211 dwarf.deinit();211 dwarf.deinit();
212 }212 }
213 gpa.free(zig_object.path);213 gpa.free(zig_object.path.sub_path);
214 zig_object.* = undefined;214 zig_object.* = undefined;
215}215}
216216
...@@ -1236,6 +1236,7 @@ const codegen = @import("../../codegen.zig");...@@ -1236,6 +1236,7 @@ const codegen = @import("../../codegen.zig");
1236const link = @import("../../link.zig");1236const link = @import("../../link.zig");
1237const log = std.log.scoped(.zig_object);1237const log = std.log.scoped(.zig_object);
1238const std = @import("std");1238const std = @import("std");
1239const Path = std.Build.Cache.Path;
12391240
1240const Air = @import("../../Air.zig");1241const Air = @import("../../Air.zig");
1241const Atom = Wasm.Atom;1242const Atom = Wasm.Atom;