authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-05-27 15:23:27-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-05-27 15:23:27-04:00
logc7ca1fe6f7b8796a42de908faeaa6ec24e8eb118
treece90d5e83280aea66fcad38382840d326ec2a9fb
parent2ae9e06363b294f60b62e765d31fce4417e14a9d

self-hosted: introduce a virtual address allocation scheme

The binary file abstraction changed its struct named "Decl" to "TextBlock" and it now represents an allocated slice of memory in the .text section. It has two new fields: prev and next, making it a linked list node. This allows a TextBlock to find its neighbors. The ElfFile struct now has free_list and last_text_block fields. Doc comments for free_list are reproduced here: A list of text blocks that have surplus capacity. This list can have false positives, as functions grow and shrink over time, only sometimes being added or removed from the freelist. A text block has surplus capacity when its overcapacity value is greater than minimum_text_block_size * alloc_num / alloc_den. That is, when it has so much extra capacity, that we could fit a small new symbol in it, itself with ideal_capacity or more. Ideal capacity is defined by size * alloc_num / alloc_den. Overcapacity is measured by actual_capacity - ideal_capacity. Note that overcapacity can be negative. A simple way to have negative overcapacity is to allocate a fresh text block, which will have ideal capacity, and then grow it by 1 byte. It will then have -1 overcapacity. The last_text_block keeps track of the end of the .text section. Allocation, freeing, and resizing decls are all now more sophisticated, and participate in the virtual address allocation scheme. There is no longer the possibility for virtual address collisions.

3 files changed, 351 insertions(+), 142 deletions(-)

src-self-hosted/Module.zig+5-5
...@@ -134,7 +134,7 @@ pub const Decl = struct {...@@ -134,7 +134,7 @@ pub const Decl = struct {
134134
135 /// Represents the position of the code in the output file.135 /// Represents the position of the code in the output file.
136 /// This is populated regardless of semantic analysis and code generation.136 /// This is populated regardless of semantic analysis and code generation.
137 link: link.ElfFile.Decl = link.ElfFile.Decl.empty,137 link: link.ElfFile.TextBlock = link.ElfFile.TextBlock.empty,
138138
139 /// The shallow set of other decls whose typed_value could possibly change if this Decl's139 /// The shallow set of other decls whose typed_value could possibly change if this Decl's
140 /// typed_value is modified.140 /// typed_value is modified.
...@@ -759,7 +759,7 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void {...@@ -759,7 +759,7 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void {
759759
760 for (src_module.decls) |decl| {760 for (src_module.decls) |decl| {
761 if (decl.cast(zir.Inst.Export)) |export_inst| {761 if (decl.cast(zir.Inst.Export)) |export_inst| {
762 _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.Decl.empty);762 _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.TextBlock.empty);
763 }763 }
764 }764 }
765 },765 },
...@@ -800,7 +800,7 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void {...@@ -800,7 +800,7 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void {
800 }800 }
801 }801 }
802 } else if (src_decl.cast(zir.Inst.Export)) |export_inst| {802 } else if (src_decl.cast(zir.Inst.Export)) |export_inst| {
803 _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.Decl.empty);803 _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.TextBlock.empty);
804 }804 }
805 }805 }
806 },806 },
...@@ -840,7 +840,7 @@ fn resolveDecl(...@@ -840,7 +840,7 @@ fn resolveDecl(
840 self: *Module,840 self: *Module,
841 scope: *Scope,841 scope: *Scope,
842 old_inst: *zir.Inst,842 old_inst: *zir.Inst,
843 bin_file_link: link.ElfFile.Decl,843 bin_file_link: link.ElfFile.TextBlock,
844) InnerError!*Decl {844) InnerError!*Decl {
845 const hash = Decl.hashSimpleName(old_inst.name);845 const hash = Decl.hashSimpleName(old_inst.name);
846 if (self.decl_table.get(hash)) |kv| {846 if (self.decl_table.get(hash)) |kv| {
...@@ -907,7 +907,7 @@ fn resolveDecl(...@@ -907,7 +907,7 @@ fn resolveDecl(
907}907}
908908
909fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl {909fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl {
910 const decl = try self.resolveDecl(scope, old_inst, link.ElfFile.Decl.empty);910 const decl = try self.resolveDecl(scope, old_inst, link.ElfFile.TextBlock.empty);
911 switch (decl.analysis) {911 switch (decl.analysis) {
912 .initial_in_progress => unreachable,912 .initial_in_progress => unreachable,
913 .repeat_in_progress => unreachable,913 .repeat_in_progress => unreachable,
src-self-hosted/link.zig+275-137
...@@ -138,11 +138,39 @@ pub const ElfFile = struct {...@@ -138,11 +138,39 @@ pub const ElfFile = struct {
138138
139 error_flags: ErrorFlags = ErrorFlags{},139 error_flags: ErrorFlags = ErrorFlags{},
140140
141 /// A list of text blocks that have surplus capacity. This list can have false
142 /// positives, as functions grow and shrink over time, only sometimes being added
143 /// or removed from the freelist.
144 ///
145 /// A text block has surplus capacity when its overcapacity value is greater than
146 /// minimum_text_block_size * alloc_num / alloc_den. That is, when it has so
147 /// much extra capacity, that we could fit a small new symbol in it, itself with
148 /// ideal_capacity or more.
149 ///
150 /// Ideal capacity is defined by size * alloc_num / alloc_den.
151 ///
152 /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that
153 /// overcapacity can be negative. A simple way to have negative overcapacity is to
154 /// allocate a fresh text block, which will have ideal capacity, and then grow it
155 /// by 1 byte. It will then have -1 overcapacity.
156 free_list: std.ArrayListUnmanaged(*TextBlock) = std.ArrayListUnmanaged(*TextBlock){},
157 last_text_block: ?*TextBlock = null,
158
159 /// `alloc_num / alloc_den` is the factor of padding when allocating.
160 const alloc_num = 4;
161 const alloc_den = 3;
162
163 /// In order for a slice of bytes to be considered eligible to keep metadata pointing at
164 /// it as a possible place to put new symbols, it must have enough room for this many bytes
165 /// (plus extra for reserved capacity).
166 const minimum_text_block_size = 64;
167 const min_text_capacity = minimum_text_block_size * alloc_num / alloc_den;
168
141 pub const ErrorFlags = struct {169 pub const ErrorFlags = struct {
142 no_entry_point_found: bool = false,170 no_entry_point_found: bool = false,
143 };171 };
144172
145 pub const Decl = struct {173 pub const TextBlock = struct {
146 /// Each decl always gets a local symbol with the fully qualified name.174 /// Each decl always gets a local symbol with the fully qualified name.
147 /// The vaddr and size are found here directly.175 /// The vaddr and size are found here directly.
148 /// The file offset is found by computing the vaddr offset from the section vaddr176 /// The file offset is found by computing the vaddr offset from the section vaddr
...@@ -152,11 +180,43 @@ pub const ElfFile = struct {...@@ -152,11 +180,43 @@ pub const ElfFile = struct {
152 local_sym_index: u32,180 local_sym_index: u32,
153 /// This field is undefined for symbols with size = 0.181 /// This field is undefined for symbols with size = 0.
154 offset_table_index: u32,182 offset_table_index: u32,
183 /// Points to the previous and next neighbors, based on the `text_offset`.
184 /// This can be used to find, for example, the capacity of this `TextBlock`.
185 prev: ?*TextBlock,
186 next: ?*TextBlock,
155187
156 pub const empty = Decl{188 pub const empty = TextBlock{
157 .local_sym_index = 0,189 .local_sym_index = 0,
158 .offset_table_index = undefined,190 .offset_table_index = undefined,
191 .prev = null,
192 .next = null,
159 };193 };
194
195 /// Returns how much room there is to grow in virtual address space.
196 /// File offset relocation happens transparently, so it is not included in
197 /// this calculation.
198 fn capacity(self: TextBlock, elf_file: ElfFile) u64 {
199 const self_sym = elf_file.local_symbols.items[self.local_sym_index];
200 if (self.next) |next| {
201 const next_sym = elf_file.local_symbols.items[next.local_sym_index];
202 return next_sym.st_value - self_sym.st_value;
203 } else {
204 // We are the last block. The capacity is limited only by virtual address space.
205 return std.math.maxInt(u32) - self_sym.st_value;
206 }
207 }
208
209 fn freeListEligible(self: TextBlock, elf_file: ElfFile) bool {
210 // No need to keep a free list node for the last block.
211 const next = self.next orelse return false;
212 const self_sym = elf_file.local_symbols.items[self.local_sym_index];
213 const next_sym = elf_file.local_symbols.items[next.local_sym_index];
214 const cap = next_sym.st_value - self_sym.st_value;
215 const ideal_cap = self_sym.st_size * alloc_num / alloc_den;
216 if (cap <= ideal_cap) return false;
217 const surplus = cap - ideal_cap;
218 return surplus >= min_text_capacity;
219 }
160 };220 };
161221
162 pub const Export = struct {222 pub const Export = struct {
...@@ -193,10 +253,6 @@ pub const ElfFile = struct {...@@ -193,10 +253,6 @@ pub const ElfFile = struct {
193 });253 });
194 }254 }
195255
196 // `alloc_num / alloc_den` is the factor of padding when allocation
197 const alloc_num = 4;
198 const alloc_den = 3;
199
200 /// Returns end pos of collision, if any.256 /// Returns end pos of collision, if any.
201 fn detectAllocCollision(self: *ElfFile, start: u64, size: u64) ?u64 {257 fn detectAllocCollision(self: *ElfFile, start: u64, size: u64) ?u64 {
202 const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32;258 const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32;
...@@ -448,6 +504,13 @@ pub const ElfFile = struct {...@@ -448,6 +504,13 @@ pub const ElfFile = struct {
448 self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign);504 self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign);
449 self.phdr_table_dirty = true;505 self.phdr_table_dirty = true;
450 }506 }
507 {
508 // Iterate over symbols, populating free_list and last_text_block.
509 if (self.local_symbols.items.len != 1) {
510 @panic("TODO implement setting up free_list and last_text_block from existing ELF file");
511 }
512 // We are starting with an empty file. The default values are correct, null and empty list.
513 }
451 }514 }
452515
453 /// Commit pending changes and write headers.516 /// Commit pending changes and write headers.
...@@ -577,7 +640,6 @@ pub const ElfFile = struct {...@@ -577,7 +640,6 @@ pub const ElfFile = struct {
577 self.error_flags.no_entry_point_found = false;640 self.error_flags.no_entry_point_found = false;
578 try self.writeElfHeader();641 try self.writeElfHeader();
579 }642 }
580 // TODO find end pos and truncate
581643
582 // The point of flush() is to commit changes, so nothing should be dirty after this.644 // The point of flush() is to commit changes, so nothing should be dirty after this.
583 assert(!self.phdr_table_dirty);645 assert(!self.phdr_table_dirty);
...@@ -709,71 +771,170 @@ pub const ElfFile = struct {...@@ -709,71 +771,170 @@ pub const ElfFile = struct {
709 try self.file.?.pwriteAll(hdr_buf[0..index], 0);771 try self.file.?.pwriteAll(hdr_buf[0..index], 0);
710 }772 }
711773
712 const AllocatedBlock = struct {774 fn freeTextBlock(self: *ElfFile, text_block: *TextBlock) void {
713 vaddr: u64,775 var already_have_free_list_node = false;
714 file_offset: u64,776 {
715 size_capacity: u64,777 var i: usize = 0;
716 };778 while (i < self.free_list.items.len) {
779 if (self.free_list.items[i] == text_block) {
780 _ = self.free_list.swapRemove(i);
781 continue;
782 }
783 if (self.free_list.items[i] == text_block.prev) {
784 already_have_free_list_node = true;
785 }
786 i += 1;
787 }
788 }
717789
718 fn allocateTextBlock(self: *ElfFile, new_block_size: u64, alignment: u64) !AllocatedBlock {790 if (self.last_text_block == text_block) {
719 const phdr = &self.program_headers.items[self.phdr_load_re_index.?];791 self.last_text_block = text_block.prev;
720 const shdr = &self.sections.items[self.text_section_index.?];792 }
793
794 if (text_block.prev) |prev| {
795 prev.next = text_block.next;
721796
722 // TODO Also detect virtual address collisions.797 if (!already_have_free_list_node and prev.freeListEligible(self.*)) {
723 const text_capacity = self.allocatedSize(shdr.sh_offset);798 // The free list is heuristics, it doesn't have to be perfect, so we can
724 // TODO instead of looping here, maintain a free list and a pointer to the end.799 // ignore the OOM here.
725 var last_start: u64 = phdr.p_vaddr;800 self.free_list.append(self.allocator, prev) catch {};
726 var last_size: u64 = 0;
727 for (self.local_symbols.items) |sym| {
728 if (sym.st_value + sym.st_size > last_start + last_size) {
729 last_start = sym.st_value;
730 last_size = sym.st_size;
731 }801 }
802 } else {
803 text_block.prev = null;
732 }804 }
733 const end_vaddr = last_start + (last_size * alloc_num / alloc_den);805
734 const aligned_start_vaddr = mem.alignForwardGeneric(u64, end_vaddr, alignment);806 if (text_block.next) |next| {
735 const needed_size = (aligned_start_vaddr + new_block_size) - phdr.p_vaddr;807 next.prev = text_block.prev;
736 if (needed_size > text_capacity) {808 } else {
737 // Must move the entire text section.809 text_block.next = null;
738 const new_offset = self.findFreeSpace(needed_size, 0x1000);
739 const text_size = (last_start + last_size) - phdr.p_vaddr;
740 const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, text_size);
741 if (amt != text_size) return error.InputOutput;
742 shdr.sh_offset = new_offset;
743 phdr.p_offset = new_offset;
744 }810 }
745 // Now that we know the code size, we need to update the program header for executable code
746 shdr.sh_size = needed_size;
747 phdr.p_memsz = needed_size;
748 phdr.p_filesz = needed_size;
749
750 self.phdr_table_dirty = true; // TODO look into making only the one program header dirty
751 self.shdr_table_dirty = true; // TODO look into making only the one section dirty
752
753 return AllocatedBlock{
754 .vaddr = aligned_start_vaddr,
755 .file_offset = shdr.sh_offset + (aligned_start_vaddr - phdr.p_vaddr),
756 .size_capacity = text_capacity - needed_size,
757 };
758 }811 }
759812
760 fn findAllocatedTextBlock(self: *ElfFile, sym: elf.Elf64_Sym) AllocatedBlock {813 fn shrinkTextBlock(self: *ElfFile, text_block: *TextBlock, new_block_size: u64) void {
814 // TODO check the new capacity, and if it crosses the size threshold into a big enough
815 // capacity, insert a free list node for it.
816 }
817
818 fn growTextBlock(self: *ElfFile, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {
819 const sym = self.local_symbols.items[text_block.local_sym_index];
820 const align_ok = mem.alignBackwardGeneric(u64, sym.st_value, alignment) == sym.st_value;
821 const need_realloc = !align_ok or new_block_size > text_block.capacity(self.*);
822 if (!need_realloc) return sym.st_value;
823 return self.allocateTextBlock(text_block, new_block_size, alignment);
824 }
825
826 fn allocateTextBlock(self: *ElfFile, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {
761 const phdr = &self.program_headers.items[self.phdr_load_re_index.?];827 const phdr = &self.program_headers.items[self.phdr_load_re_index.?];
762 const shdr = &self.sections.items[self.text_section_index.?];828 const shdr = &self.sections.items[self.text_section_index.?];
829 const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den;
830
831 // We use these to indicate our intention to update metadata, placing the new block,
832 // and possibly removing a free list node.
833 // It would be simpler to do it inside the for loop below, but that would cause a
834 // problem if an error was returned later in the function. So this action
835 // is actually carried out at the end of the function, when errors are no longer possible.
836 var block_placement: ?*TextBlock = null;
837 var free_list_removal: ?usize = null;
838
839 // First we look for an appropriately sized free list node.
840 // The list is unordered. We'll just take the first thing that works.
841 const vaddr = blk: {
842 var i: usize = 0;
843 while (i < self.free_list.items.len) {
844 const big_block = self.free_list.items[i];
845 // We now have a pointer to a live text block that has too much capacity.
846 // Is it enough that we could fit this new text block?
847 const sym = self.local_symbols.items[big_block.local_sym_index];
848 const capacity = big_block.capacity(self.*);
849 const ideal_capacity = capacity * alloc_num / alloc_den;
850 const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity;
851 const capacity_end_vaddr = sym.st_value + capacity;
852 const new_start_vaddr_unaligned = capacity_end_vaddr - new_block_ideal_capacity;
853 const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment);
854 if (new_start_vaddr < ideal_capacity_end_vaddr) {
855 // Additional bookkeeping here to notice if this free list node
856 // should be deleted because the block that it points to has grown to take up
857 // more of the extra capacity.
858 if (!big_block.freeListEligible(self.*)) {
859 _ = self.free_list.swapRemove(i);
860 } else {
861 i += 1;
862 }
863 continue;
864 }
865 // At this point we know that we will place the new block here. But the
866 // remaining question is whether there is still yet enough capacity left
867 // over for there to still be a free list node.
868 const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
869 const keep_free_list_node = remaining_capacity >= min_text_capacity;
870
871 // Set up the metadata to be updated, after errors are no longer possible.
872 block_placement = big_block;
873 if (!keep_free_list_node) {
874 free_list_removal = i;
875 }
876 break :blk new_start_vaddr;
877 } else if (self.last_text_block) |last| {
878 const sym = self.local_symbols.items[last.local_sym_index];
879 const ideal_capacity = sym.st_size * alloc_num / alloc_den;
880 const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity;
881 const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment);
882 // Set up the metadata to be updated, after errors are no longer possible.
883 block_placement = last;
884 break :blk new_start_vaddr;
885 } else {
886 break :blk phdr.p_vaddr;
887 }
888 };
889
890 const expand_text_section = block_placement == null or block_placement.?.next == null;
891 if (expand_text_section) {
892 const text_capacity = self.allocatedSize(shdr.sh_offset);
893 const needed_size = (vaddr + new_block_size) - phdr.p_vaddr;
894 if (needed_size > text_capacity) {
895 // Must move the entire text section.
896 const new_offset = self.findFreeSpace(needed_size, 0x1000);
897 const text_size = if (self.last_text_block) |last| blk: {
898 const sym = self.local_symbols.items[last.local_sym_index];
899 break :blk (sym.st_value + sym.st_size) - phdr.p_vaddr;
900 } else 0;
901 const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, text_size);
902 if (amt != text_size) return error.InputOutput;
903 shdr.sh_offset = new_offset;
904 phdr.p_offset = new_offset;
905 }
906 self.last_text_block = text_block;
907
908 shdr.sh_size = needed_size;
909 phdr.p_memsz = needed_size;
910 phdr.p_filesz = needed_size;
763911
764 // Find the next sym after this one.912 self.phdr_table_dirty = true; // TODO look into making only the one program header dirty
765 // TODO look into using a hash map to speed up perf.913 self.shdr_table_dirty = true; // TODO look into making only the one section dirty
766 const text_capacity = self.allocatedSize(shdr.sh_offset);
767 var next_vaddr_start = phdr.p_vaddr + text_capacity;
768 for (self.local_symbols.items) |elem| {
769 if (elem.st_value < sym.st_value) continue;
770 if (elem.st_value < next_vaddr_start) next_vaddr_start = elem.st_value;
771 }914 }
772 return .{915
773 .vaddr = sym.st_value,916 // This function can also reallocate a text block.
774 .file_offset = shdr.sh_offset + (sym.st_value - phdr.p_vaddr),917 // In this case we need to "unplug" it from its previous location before
775 .size_capacity = next_vaddr_start - sym.st_value,918 // plugging it in to its new location.
776 };919 if (text_block.prev) |prev| {
920 prev.next = text_block.next;
921 }
922 if (text_block.next) |next| {
923 next.prev = text_block.prev;
924 }
925
926 if (block_placement) |big_block| {
927 text_block.prev = big_block;
928 text_block.next = big_block.next;
929 big_block.next = text_block;
930 } else {
931 text_block.prev = null;
932 text_block.next = null;
933 }
934 if (free_list_removal) |i| {
935 _ = self.free_list.swapRemove(i);
936 }
937 return vaddr;
777 }938 }
778939
779 pub fn allocateDeclIndexes(self: *ElfFile, decl: *Module.Decl) !void {940 pub fn allocateDeclIndexes(self: *ElfFile, decl: *Module.Decl) !void {
...@@ -793,16 +954,13 @@ pub const ElfFile = struct {...@@ -793,16 +954,13 @@ pub const ElfFile = struct {
793 .st_value = phdr.p_vaddr,954 .st_value = phdr.p_vaddr,
794 .st_size = 0,955 .st_size = 0,
795 });956 });
796 errdefer self.local_symbols.shrink(self.allocator, self.local_symbols.items.len - 1);
797 self.offset_table.appendAssumeCapacity(0);957 self.offset_table.appendAssumeCapacity(0);
798 errdefer self.offset_table.shrink(self.allocator, self.offset_table.items.len - 1);
799958
800 self.offset_table_count_dirty = true;959 self.offset_table_count_dirty = true;
801960
802 decl.link = .{961 //std.debug.warn("allocating symbol index {}\n", .{local_sym_index});
803 .local_sym_index = @intCast(u32, local_sym_index),962 decl.link.local_sym_index = @intCast(u32, local_sym_index);
804 .offset_table_index = @intCast(u32, offset_table_index),963 decl.link.offset_table_index = @intCast(u32, offset_table_index);
805 };
806 }964 }
807965
808 pub fn updateDecl(self: *ElfFile, module: *Module, decl: *Module.Decl) !void {966 pub fn updateDecl(self: *ElfFile, module: *Module, decl: *Module.Decl) !void {
...@@ -822,80 +980,60 @@ pub const ElfFile = struct {...@@ -822,80 +980,60 @@ pub const ElfFile = struct {
822980
823 const required_alignment = typed_value.ty.abiAlignment(self.options.target);981 const required_alignment = typed_value.ty.abiAlignment(self.options.target);
824982
825 const file_offset = blk: {983 const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) {
826 const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) {984 .Fn => elf.STT_FUNC,
827 .Fn => elf.STT_FUNC,985 else => elf.STT_OBJECT,
828 else => elf.STT_OBJECT,986 };
829 };
830987
831 if (decl.link.local_sym_index != 0) {988 assert(decl.link.local_sym_index != 0); // Caller forgot to allocateDeclIndexes()
832 const local_sym = &self.local_symbols.items[decl.link.local_sym_index];989 const local_sym = &self.local_symbols.items[decl.link.local_sym_index];
833 const existing_block = self.findAllocatedTextBlock(local_sym.*);990 if (local_sym.st_size != 0) {
834 const need_realloc = local_sym.st_size == 0 or991 const capacity = decl.link.capacity(self.*);
835 code.len > existing_block.size_capacity or992 const need_realloc = code.len > capacity or
836 !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment);993 !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment);
837 // TODO check for collision with another symbol994 if (need_realloc) {
838 const file_offset = if (need_realloc) fo: {995 const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment);
839 const new_block = try self.allocateTextBlock(code.len, required_alignment);996 //std.debug.warn("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr });
840 local_sym.st_value = new_block.vaddr;997 if (vaddr != local_sym.st_value) {
841 self.offset_table.items[decl.link.offset_table_index] = new_block.vaddr;998 local_sym.st_value = vaddr;
842999
843 //std.debug.warn("{}: writing got index {}=0x{x}\n", .{1000 //std.debug.warn(" (writing new offset table entry)\n", .{});
844 // decl.name,1001 self.offset_table.items[decl.link.offset_table_index] = vaddr;
845 // decl.link.offset_table_index,
846 // self.offset_table.items[decl.link.offset_table_index],
847 //});
848 try self.writeOffsetTableEntry(decl.link.offset_table_index);1002 try self.writeOffsetTableEntry(decl.link.offset_table_index);
8491003 }
850 break :fo new_block.file_offset;1004 } else if (code.len < local_sym.st_size) {
851 } else existing_block.file_offset;1005 self.shrinkTextBlock(&decl.link, code.len);
852 local_sym.st_size = code.len;
853 local_sym.st_name = try self.updateString(local_sym.st_name, mem.spanZ(decl.name));
854 local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits;
855 local_sym.st_other = 0;
856 local_sym.st_shndx = self.text_section_index.?;
857 // TODO this write could be avoided if no fields of the symbol were changed.
858 try self.writeSymbol(decl.link.local_sym_index);
859
860 //std.debug.warn("updating {} at vaddr 0x{x}\n", .{ decl.name, local_sym.st_value });
861 break :blk file_offset;
862 } else {
863 try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1);
864 try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1);
865 const decl_name = mem.spanZ(decl.name);
866 const name_str_index = try self.makeString(decl_name);
867 const new_block = try self.allocateTextBlock(code.len, required_alignment);
868 const local_sym_index = self.local_symbols.items.len;
869 const offset_table_index = self.offset_table.items.len;
870
871 //std.debug.warn("add symbol for {} at vaddr 0x{x}, size {}\n", .{ decl.name, new_block.vaddr, code.len });
872 self.local_symbols.appendAssumeCapacity(.{
873 .st_name = name_str_index,
874 .st_info = (elf.STB_LOCAL << 4) | stt_bits,
875 .st_other = 0,
876 .st_shndx = self.text_section_index.?,
877 .st_value = new_block.vaddr,
878 .st_size = code.len,
879 });
880 errdefer self.local_symbols.shrink(self.allocator, self.local_symbols.items.len - 1);
881 self.offset_table.appendAssumeCapacity(new_block.vaddr);
882 errdefer self.offset_table.shrink(self.allocator, self.offset_table.items.len - 1);
883
884 self.offset_table_count_dirty = true;
885
886 try self.writeSymbol(local_sym_index);
887 try self.writeOffsetTableEntry(offset_table_index);
888
889 decl.link = .{
890 .local_sym_index = @intCast(u32, local_sym_index),
891 .offset_table_index = @intCast(u32, offset_table_index),
892 };
893
894 //std.debug.warn("writing new {} at vaddr 0x{x}\n", .{ decl.name, new_block.vaddr });
895 break :blk new_block.file_offset;
896 }1006 }
897 };1007 local_sym.st_size = code.len;
1008 local_sym.st_name = try self.updateString(local_sym.st_name, mem.spanZ(decl.name));
1009 local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits;
1010 local_sym.st_other = 0;
1011 local_sym.st_shndx = self.text_section_index.?;
1012 // TODO this write could be avoided if no fields of the symbol were changed.
1013 try self.writeSymbol(decl.link.local_sym_index);
1014 } else {
1015 const decl_name = mem.spanZ(decl.name);
1016 const name_str_index = try self.makeString(decl_name);
1017 const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment);
1018 //std.debug.warn("allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr });
1019 errdefer self.freeTextBlock(&decl.link);
1020
1021 local_sym.* = .{
1022 .st_name = name_str_index,
1023 .st_info = (elf.STB_LOCAL << 4) | stt_bits,
1024 .st_other = 0,
1025 .st_shndx = self.text_section_index.?,
1026 .st_value = vaddr,
1027 .st_size = code.len,
1028 };
1029 self.offset_table.items[decl.link.offset_table_index] = vaddr;
1030
1031 try self.writeSymbol(decl.link.local_sym_index);
1032 try self.writeOffsetTableEntry(decl.link.offset_table_index);
1033 }
8981034
1035 const section_offset = local_sym.st_value - self.program_headers.items[self.phdr_load_re_index.?].p_vaddr;
1036 const file_offset = self.sections.items[self.text_section_index.?].sh_offset + section_offset;
899 try self.file.?.pwriteAll(code, file_offset);1037 try self.file.?.pwriteAll(code, file_offset);
9001038
901 // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.1039 // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
test/stage2/zir.zig+71
...@@ -200,6 +200,73 @@ pub fn addCases(ctx: *TestContext) void {...@@ -200,6 +200,73 @@ pub fn addCases(ctx: *TestContext) void {
200 \\@9 = str("_start")200 \\@9 = str("_start")
201 \\@10 = ref(@9)201 \\@10 = ref(@9)
202 \\@11 = export(@10, @start)202 \\@11 = export(@10, @start)
203// ,
204// \\@noreturn = primitive(noreturn)
205// \\@void = primitive(void)
206// \\@usize = primitive(usize)
207// \\@0 = int(0)
208// \\@1 = int(1)
209// \\@2 = int(2)
210// \\@3 = int(3)
211// \\
212// \\@syscall_array = str("syscall")
213// \\@sysoutreg_array = str("={rax}")
214// \\@rax_array = str("{rax}")
215// \\@rdi_array = str("{rdi}")
216// \\@rcx_array = str("rcx")
217// \\@r11_array = str("r11")
218// \\@rdx_array = str("{rdx}")
219// \\@rsi_array = str("{rsi}")
220// \\@memory_array = str("memory")
221// \\@len_array = str("len")
222// \\
223// \\@msg = str("Hello, world!\n")
224// \\@msg2 = str("Editing the same msg2 decl but this time with a much longer message which will\ncause the data to need to be relocated in virtual address space.\n")
225// \\
226// \\@start_fnty = fntype([], @noreturn, cc=Naked)
227// \\@start = fn(@start_fnty, {
228// \\ %SYS_exit_group = int(231)
229// \\ %exit_code = as(@usize, @0)
230// \\
231// \\ %syscall = ref(@syscall_array)
232// \\ %sysoutreg = ref(@sysoutreg_array)
233// \\ %rax = ref(@rax_array)
234// \\ %rdi = ref(@rdi_array)
235// \\ %rcx = ref(@rcx_array)
236// \\ %rdx = ref(@rdx_array)
237// \\ %rsi = ref(@rsi_array)
238// \\ %r11 = ref(@r11_array)
239// \\ %memory = ref(@memory_array)
240// \\
241// \\ %SYS_write = as(@usize, @1)
242// \\ %STDOUT_FILENO = as(@usize, @1)
243// \\
244// \\ %msg_ptr = ref(@msg2)
245// \\ %msg_addr = ptrtoint(%msg_ptr)
246// \\
247// \\ %len_name = ref(@len_array)
248// \\ %msg_len_ptr = fieldptr(%msg_ptr, %len_name)
249// \\ %msg_len = deref(%msg_len_ptr)
250// \\ %rc_write = asm(%syscall, @usize,
251// \\ volatile=1,
252// \\ output=%sysoutreg,
253// \\ inputs=[%rax, %rdi, %rsi, %rdx],
254// \\ clobbers=[%rcx, %r11, %memory],
255// \\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len])
256// \\
257// \\ %rc_exit = asm(%syscall, @usize,
258// \\ volatile=1,
259// \\ output=%sysoutreg,
260// \\ inputs=[%rax, %rdi],
261// \\ clobbers=[%rcx, %r11, %memory],
262// \\ args=[%SYS_exit_group, %exit_code])
263// \\
264// \\ %99 = unreachable()
265// \\});
266// \\
267// \\@9 = str("_start")
268// \\@10 = ref(@9)
269// \\@11 = export(@10, @start)
203 },270 },
204 &[_][]const u8{271 &[_][]const u8{
205 \\Hello, world!272 \\Hello, world!
...@@ -207,6 +274,10 @@ pub fn addCases(ctx: *TestContext) void {...@@ -207,6 +274,10 @@ pub fn addCases(ctx: *TestContext) void {
207 ,274 ,
208 \\HELL WORLD275 \\HELL WORLD
209 \\276 \\
277// ,
278// \\Editing the same msg2 decl but this time with a much longer message which will
279// \\cause the data to need to be relocated in virtual address space.
280// \\
210 },281 },
211 );282 );
212283