| author | |
| committer | |
| log | f2dcfe0e408e1abf0c75293d75c4d92aed033eae |
| tree | ac640108ecaa7f43b5c61cc2c46b744093e4926e |
| parent | f5ade5e2071fecf6b8a0b025b6ff9581082b9b5c |
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(); |
| 825 | 825 | ||
| 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 | else | 830 | else |
| ... | @@ -1104,7 +1103,7 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void { | ... | @@ -1104,7 +1103,7 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void { |
| 1104 | pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void { | 1103 | pub 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, |
| 156 | 156 | ||
| 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. |
| 159 | archives: std.ArrayListUnmanaged(Archive) = .empty, | 159 | lazy_archives: std.ArrayListUnmanaged(LazyArchive) = .empty, |
| 160 | 160 | ||
| 161 | /// A map of global names (read: offset into string table) to their symbol location | 161 | /// A map of global names (read: offset into string table) to their symbol location |
| 162 | globals: std.AutoHashMapUnmanaged(u32, SymbolLoc) = .empty, | 162 | globals: 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. |
| 177 | symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, Atom.Index) = .empty, | 177 | symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, Atom.Index) = .empty, |
| 178 | 178 | ||
| 179 | /// `--verbose-link` output. | ||
| 180 | /// Initialized on creation, appended to as inputs are added, printed during `flush`. | ||
| 181 | dump_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. |
| 180 | pub const ObjectId = enum(u16) { | 184 | pub 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 | }; |
| 202 | 206 | ||
| 207 | const 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 | |||
| 203 | pub const Segment = struct { | 219 | pub 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 | } |
| 659 | 679 | ||
| 660 | /// Parses the object file from given path. Returns true when the given file was an object | 680 | fn 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. | ||
| 663 | fn 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 | } | ||
| 665 | 693 | ||
| 666 | const obj_file = try fs.cwd().openFile(path, .{}); | 694 | fn 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 | } |
| 683 | 709 | ||
| 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 | } |
| 705 | 731 | ||
| 706 | /// Parses an archive file and will then parse each object file | 732 | fn 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. | ||
| 714 | fn 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; | ||
| 717 | 734 | ||
| 718 | const archive_file = try fs.cwd().openFile(path, .{}); | 735 | defer obj.file.close(); |
| 719 | errdefer archive_file.close(); | ||
| 720 | 736 | ||
| 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 | }; | ||
| 737 | 743 | ||
| 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); |
| 744 | 764 | ||
| 745 | // In this case we must force link all embedded object files within the archive | 765 | // 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 | } |
| 755 | 775 | ||
| 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 | } |
| 768 | 781 | ||
| 769 | fn requiresTLSReloc(wasm: *const Wasm) bool { | 782 | fn 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 | } |
| 777 | 790 | ||
| 778 | fn objectPath(wasm: *const Wasm, object_id: ObjectId) []const u8 { | 791 | fn 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); |
| 859 | 872 | ||
| 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 { |
| 871 | 884 | ||
| 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 { |
| 892 | 903 | ||
| 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 | }; | ||
| 896 | 910 | ||
| 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 | } |
| 911 | 925 | ||
| 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 | } |
| 922 | 936 | ||
| 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 | } |
| 947 | 961 | ||
| ... | @@ -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 | } |
| 963 | 977 | ||
| ... | @@ -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 | } |
| 975 | 989 | ||
| ... | @@ -983,8 +997,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { | ... | @@ -983,8 +997,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { |
| 983 | 997 | ||
| 984 | // simply overwrite with the new symbol | 998 | // 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 | } |
| 998 | 1012 | ||
| 999 | fn resolveSymbolsInArchives(wasm: *Wasm) !void { | 1013 | fn 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; | ||
| 1003 | 1017 | ||
| 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]; |
| 1008 | 1022 | ||
| 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 archive | 1027 | }); |
| 1014 | continue; | 1028 | const offset = lazy_archive.archive.toc.get(sym_name) orelse continue; // symbol does not exist in this archive |
| 1015 | }; | ||
| 1016 | 1029 | ||
| 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 remaining | 1031 | // 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 | } |
| 1378 | 1392 | ||
| ... | @@ -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 | } |
| 1478 | 1498 | ||
| ... | @@ -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 | } |
| 1495 | 1515 | ||
| 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 | } | ||
| 1499 | 1518 | ||
| 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); | ||
| 1518 | 1536 | ||
| 1519 | // free output sections | 1537 | // 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); |
| 1528 | 1546 | ||
| 1529 | wasm.string_table.deinit(gpa); | 1547 | wasm.string_table.deinit(gpa); |
| 1548 | wasm.dump_argv_list.deinit(gpa); | ||
| 1530 | } | 1549 | } |
| 1531 | 1550 | ||
| 1532 | pub fn updateFunc(wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { | 1551 | pub 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 | } |
| 2605 | 2624 | ||
| 2625 | pub 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 | |||
| 2606 | pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { | 2651 | pub 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 | } |
| 2615 | 2660 | ||
| 2616 | /// Uses the in-house linker to link one or multiple object -and archive files into a WebAssembly binary. | ||
| 2617 | pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { | 2661 | pub 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 | } |
| 2628 | 2672 | ||
| 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(); |
| 2631 | 2677 | ||
| 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; |
| 2641 | 2685 | ||
| 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); | ||
| 2646 | 2687 | ||
| 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 | } | ||
| 2708 | 2689 | ||
| 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 @@ |
| 1 | file: fs.File, | 1 | header: ar_hdr, |
| 2 | name: []const u8, | ||
| 3 | |||
| 4 | header: ar_hdr = undefined, | ||
| 5 | 2 | ||
| 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-names | 4 | /// 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 index | 5 | /// point to the character index of a file name, rather than the index |
| 9 | /// in the list. | 6 | /// in the list. |
| 10 | long_file_names: []const u8 = undefined, | 7 | long_file_names: []const u8, |
| 11 | 8 | ||
| 12 | /// Parsed table of contents. | 9 | /// Parsed table of contents. |
| 13 | /// Each symbol name points to a list of all definition | 10 | /// Each symbol name points to a list of all definition |
| 14 | /// sites within the current static archive. | 11 | /// sites within the current static archive. |
| 15 | toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .empty, | 12 | toc: Toc, |
| 13 | |||
| 14 | const Toc = std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)); | ||
| 16 | 15 | ||
| 17 | // Archive files start with the ARMAG identifying string. Then follows a | 16 | // 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 | }; |
| 84 | 83 | ||
| 85 | pub fn deinit(archive: *Archive, allocator: Allocator) void { | 84 | pub 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); | 90 | fn 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 | } |
| 96 | 95 | ||
| 97 | pub fn parse(archive: *Archive, allocator: Allocator) !void { | 96 | pub 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(); | ||
| 99 | 99 | ||
| 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 | } | ||
| 105 | 102 | ||
| 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 | } | ||
| 111 | 105 | ||
| 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 | } |
| 115 | 118 | ||
| 116 | fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 { | 119 | 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,24 +127,27 @@ fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 { |
| 124 | } | 127 | } |
| 125 | } | 128 | } |
| 126 | 129 | ||
| 127 | fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype) !void { | 130 | fn 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); |
| 132 | 135 | ||
| 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 | } |
| 139 | 142 | ||
| 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); |
| 142 | 145 | ||
| 143 | reader.readNoEof(sym_tab) catch return error.IncompleteSymbolTable; | 146 | reader.readNoEof(sym_tab) catch return error.IncompleteSymbolTable; |
| 144 | 147 | ||
| 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; |
| 151 | 157 | ||
| 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 | } |
| 163 | 171 | ||
| 164 | fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !void { | 172 | fn 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 | } |
| 178 | 187 | ||
| 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. |
| 181 | pub fn parseObject(archive: Archive, wasm_file: *const Wasm, file_offset: u32) !Object { | 190 | pub 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); | ||
| 188 | 193 | ||
| 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 | } | ||
| 192 | 195 | ||
| 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 | } |
| 208 | 201 | ||
| 209 | const std = @import("std"); | 202 | const std = @import("std"); |
| ... | @@ -211,6 +204,7 @@ const assert = std.debug.assert; | ... | @@ -211,6 +204,7 @@ const assert = std.debug.assert; |
| 211 | const fs = std.fs; | 204 | const fs = std.fs; |
| 212 | const log = std.log.scoped(.archive); | 205 | const log = std.log.scoped(.archive); |
| 213 | const mem = std.mem; | 206 | const mem = std.mem; |
| 207 | const Path = std.Build.Cache.Path; | ||
| 214 | 208 | ||
| 215 | const Allocator = mem.Allocator; | 209 | const Allocator = mem.Allocator; |
| 216 | const Object = @import("Object.zig"); | 210 | const 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"); |
| 12 | const Allocator = std.mem.Allocator; | 12 | const Allocator = std.mem.Allocator; |
| 13 | const leb = std.leb; | 13 | const leb = std.leb; |
| 14 | const meta = std.meta; | 14 | const meta = std.meta; |
| 15 | const Path = std.Build.Cache.Path; | ||
| 15 | 16 | ||
| 16 | const log = std.log.scoped(.object); | 17 | const log = std.log.scoped(.object); |
| 17 | 18 | ||
| 18 | /// Wasm spec version used for this `Object` | 19 | /// Wasm spec version used for this `Object` |
| 19 | version: u32 = 0, | 20 | version: u32 = 0, |
| 20 | /// The file descriptor that represents the wasm object file. | 21 | /// For error reporting purposes only. |
| 21 | file: ?std.fs.File = null, | 22 | /// Name (read path) of the object or archive file. |
| 22 | /// Name (read path) of the object file. | 23 | path: Path, |
| 23 | path: []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. | ||
| 27 | archive_member_name: ?[]const u8, | ||
| 24 | /// Parsed type section | 28 | /// Parsed type section |
| 25 | func_types: []const std.wasm.Type = &.{}, | 29 | func_types: []const std.wasm.Type = &.{}, |
| 26 | /// A list of all imports for this module | 30 | /// 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 | }; |
| 119 | 123 | ||
| 120 | pub 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. |
| 126 | pub fn create(wasm_file: *const Wasm, file: std.fs.File, name: []const u8, maybe_max_size: ?usize) InitError!Object { | 128 | pub 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 | }; |
| 132 | 139 | ||
| 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 | }; |
| 139 | 145 | 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; | ||
| 154 | 146 | ||
| 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 be | 150 | /// 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. |
| 160 | pub fn deinit(object: *Object, gpa: Allocator) void { | 152 | pub 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 | } |
| 205 | 193 | ||
| ... | @@ -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`. |
| 224 | fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?Symbol { | 212 | fn checkLegacyIndirectFunctionTable(object: *Object, wasm: *const Wasm) !?Symbol { |
| 225 | const diags = &wasm_file.base.comp.link_diags; | 213 | const diags = &wasm.base.comp.link_diags; |
| 226 | 214 | ||
| 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; |
| 234 | 222 | ||
| 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 | } |
| 244 | 229 | ||
| 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 | } |
| 252 | 234 | ||
| 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 | } |
| 259 | 238 | ||
| 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; |
| 265 | 244 | ||
| 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 | } |
| 272 | 250 | ||
| 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 | } |
| 284 | 262 | ||
| 285 | /// Error set containing parsing errors. | 263 | const Parser = struct { |
| 286 | /// Merged with reader's errorset by `Parser` | 264 | reader: std.io.FixedBufferStream([]const u8), |
| 287 | pub const ParseError = error{ | 265 | /// Object file we're building |
| 288 | /// The magic byte is either missing or does not contain \0Asm | 266 | 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 | }; | ||
| 321 | 269 | ||
| 322 | fn 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 | } | ||
| 326 | 276 | ||
| 327 | fn 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; | ||
| 331 | 279 | ||
| 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, | ||
| 337 | 281 | ||
| 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); | ||
| 341 | 293 | ||
| 342 | /// Verifies that the first 4 bytes contains \0Asm | 294 | 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; | ||
| 345 | 331 | ||
| 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); | ||
| 352 | 348 | ||
| 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); |
| 370 | 353 | ||
| 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 | }); | ||
| 699 | 376 | ||
| 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); |
| 728 | 424 | 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); | ||
| 750 | 440 | ||
| 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; |
| 755 | 449 | 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 | } |
| 766 | 469 | try parser.object.relocatable_data.put(gpa, .code, try relocatable_data.toOwnedSlice()); | |
| 767 | // we found all symbols, check for indirect function table | 470 | }, |
| 768 | // in case of an MVP object file | 471 | .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); |
| 773 | 476 | defer relocatable_data.deinit(); | |
| 774 | // Not all debug sections may be represented by a symbol, for those sections | 477 | 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 | } |
| 791 | 496 | 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 | } | ||
| 796 | 506 | ||
| 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's | 508 | /// 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 | } | ||
| 810 | 530 | ||
| 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.* = .{ |
| 818 | 611 | .name = name, | |
| 819 | // Data symbols only have the following fields if the symbol is defined | 612 | .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 values | 615 | 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 | }; | ||
| 855 | 775 | ||
| 856 | /// First reads the count from the reader and then allocate | 776 | /// 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. | ||
| 5 | 4 | ||
| 6 | path: []const u8, | 5 | /// For error reporting purposes only. |
| 6 | path: 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`. |
| 9 | navs: std.AutoHashMapUnmanaged(InternPool.Nav.Index, NavInfo) = .empty, | 9 | navs: 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 | } |
| 216 | 216 | ||
| ... | @@ -1236,6 +1236,7 @@ const codegen = @import("../../codegen.zig"); | ... | @@ -1236,6 +1236,7 @@ const codegen = @import("../../codegen.zig"); |
| 1236 | const link = @import("../../link.zig"); | 1236 | const link = @import("../../link.zig"); |
| 1237 | const log = std.log.scoped(.zig_object); | 1237 | const log = std.log.scoped(.zig_object); |
| 1238 | const std = @import("std"); | 1238 | const std = @import("std"); |
| 1239 | const Path = std.Build.Cache.Path; | ||
| 1239 | 1240 | ||
| 1240 | const Air = @import("../../Air.zig"); | 1241 | const Air = @import("../../Air.zig"); |
| 1241 | const Atom = Wasm.Atom; | 1242 | const Atom = Wasm.Atom; |