authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-07-20 20:33:07+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-07-20 20:33:07+02:00
loga442b165f1e219b429e497e1de26780612762871
treea29e87e96c8280185e7cffbaa7424e1eb2a78b1e
parent1843ecf51b240c43a4a9a9cadbcc1286b9b9f41a

macho: add stub relocs when adding extern fn

in self-hosted.

4 files changed, 105 insertions(+), 114 deletions(-)

src/codegen.zig+22-29
...@@ -2523,36 +2523,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2523,36 +2523,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2523 }2523 }
2524 } else if (func_value.castTag(.extern_fn)) |func_payload| {2524 } else if (func_value.castTag(.extern_fn)) |func_payload| {
2525 const decl = func_payload.data;2525 const decl = func_payload.data;
2526 const decl_name = try std.fmt.allocPrint(self.bin_file.allocator, "_{s}", .{decl.name});2526 const where_index = try macho_file.addExternFn(mem.spanZ(decl.name));
2527 defer self.bin_file.allocator.free(decl_name);2527 const offset = @intCast(u32, self.code.items.len);
2528 const already_defined = macho_file.symbol_resolver.contains(decl_name);2528 switch (arch) {
2529 const resolv = macho_file.symbol_resolver.get(decl_name) orelse blk: {2529 .x86_64 => {
2530 break :blk try macho_file.addExternFn(decl_name);2530 // callq
2531 };2531 try self.code.ensureCapacity(self.code.items.len + 5);
2532 const start = self.code.items.len;2532 self.code.appendSliceAssumeCapacity(&[5]u8{ 0xe8, 0x0, 0x0, 0x0, 0x0 });
2533 const len: usize = blk: {2533 },
2534 switch (arch) {2534 .aarch64 => {
2535 .x86_64 => {2535 // bl
2536 // callq2536 writeInt(u32, try self.code.addManyAsArray(4), Instruction.bl(0).toU32());
2537 try self.code.ensureCapacity(self.code.items.len + 5);2537 },
2538 self.code.appendSliceAssumeCapacity(&[5]u8{ 0xe8, 0x0, 0x0, 0x0, 0x0 });2538 else => unreachable, // unsupported architecture on MachO
2539 break :blk 5;2539 }
2540 },2540 // Add relocation to the decl.
2541 .aarch64 => {2541 try decl.link.macho.relocs.append(self.bin_file.allocator, .{
2542 // bl2542 .offset = offset,
2543 writeInt(u32, try self.code.addManyAsArray(4), 0);2543 .where = .import,
2544 break :blk 4;2544 .where_index = where_index,
2545 },2545 .payload = .{ .branch = .{
2546 else => unreachable, // unsupported architecture on MachO2546 .arch = arch,
2547 }2547 } },
2548 };
2549 try macho_file.stub_fixups.append(self.bin_file.allocator, .{
2550 .symbol = resolv.where_index,
2551 .already_defined = already_defined,
2552 .start = start,
2553 .len = len,
2554 });2548 });
2555 // We mark the space and fix it up later.
2556 } else {2549 } else {
2557 return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});2550 return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
2558 }2551 }
src/link/MachO.zig+81-81
...@@ -159,6 +159,14 @@ strtab_needs_relocation: bool = false,...@@ -159,6 +159,14 @@ strtab_needs_relocation: bool = false,
159has_dices: bool = false,159has_dices: bool = false,
160has_stabs: bool = false,160has_stabs: bool = false,
161161
162pending_updates: std.ArrayListUnmanaged(struct {
163 kind: enum {
164 got,
165 stub,
166 },
167 index: u32,
168}) = .{},
169
162/// A list of text blocks that have surplus capacity. This list can have false170/// A list of text blocks that have surplus capacity. This list can have false
163/// positives, as functions grow and shrink over time, only sometimes being added171/// positives, as functions grow and shrink over time, only sometimes being added
164/// or removed from the freelist.172/// or removed from the freelist.
...@@ -179,6 +187,7 @@ text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{},...@@ -179,6 +187,7 @@ text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{},
179/// Pointer to the last allocated text block187/// Pointer to the last allocated text block
180last_text_block: ?*TextBlock = null,188last_text_block: ?*TextBlock = null,
181189
190managed_blocks: std.ArrayListUnmanaged(TextBlock) = .{},
182blocks: std.AutoHashMapUnmanaged(MatchingSection, *TextBlock) = .{},191blocks: std.AutoHashMapUnmanaged(MatchingSection, *TextBlock) = .{},
183192
184/// A list of all PIE fixups required for this run of the linker.193/// A list of all PIE fixups required for this run of the linker.
...@@ -190,13 +199,6 @@ blocks: std.AutoHashMapUnmanaged(MatchingSection, *TextBlock) = .{},...@@ -190,13 +199,6 @@ blocks: std.AutoHashMapUnmanaged(MatchingSection, *TextBlock) = .{},
190/// backends.199/// backends.
191pie_fixups: std.ArrayListUnmanaged(PIEFixup) = .{},200pie_fixups: std.ArrayListUnmanaged(PIEFixup) = .{},
192201
193/// A list of all stub (extern decls) fixups required for this run of the linker.
194/// Warning, this is currently NOT thread-safe. See the TODO below.
195/// TODO Move this list inside `updateDecl` where it should be allocated
196/// prior to calling `generateSymbol`, and then immediately deallocated
197/// rather than sitting in the global scope.
198stub_fixups: std.ArrayListUnmanaged(StubFixup) = .{},
199
200const SymbolWithLoc = struct {202const SymbolWithLoc = struct {
201 // Table where the symbol can be found.203 // Table where the symbol can be found.
202 where: enum {204 where: enum {
...@@ -229,19 +231,6 @@ pub const PIEFixup = struct {...@@ -229,19 +231,6 @@ pub const PIEFixup = struct {
229 size: usize,231 size: usize,
230};232};
231233
232pub const StubFixup = struct {
233 /// Id of extern (lazy) symbol.
234 symbol: u32,
235 /// Signals whether the symbol has already been declared before. If so,
236 /// then there is no need to rewrite the stub entry and related.
237 already_defined: bool,
238 /// Where in the byte stream we should perform the fixup.
239 start: usize,
240 /// The length of the byte stream. For x86_64, this will be
241 /// variable. For aarch64, it will be fixed at 4 bytes.
242 len: usize,
243};
244
245/// When allocating, the ideal_capacity is calculated by234/// When allocating, the ideal_capacity is calculated by
246/// actual_capacity + (actual_capacity / ideal_factor)235/// actual_capacity + (actual_capacity / ideal_factor)
247const ideal_factor = 2;236const ideal_factor = 2;
...@@ -2244,9 +2233,7 @@ fn resolveSymbols(self: *MachO) !void {...@@ -2244,9 +2233,7 @@ fn resolveSymbols(self: *MachO) !void {
2244 .local_sym_index = local_sym_index,2233 .local_sym_index = local_sym_index,
2245 };2234 };
22462235
2247 const block = try self.base.allocator.create(TextBlock);2236 const block = try self.managed_blocks.addOne(self.base.allocator);
2248 errdefer self.base.allocator.destroy(block);
2249
2250 block.* = TextBlock.empty;2237 block.* = TextBlock.empty;
2251 block.local_sym_index = local_sym_index;2238 block.local_sym_index = local_sym_index;
2252 block.code = code;2239 block.code = code;
...@@ -2382,9 +2369,7 @@ fn resolveSymbols(self: *MachO) !void {...@@ -2382,9 +2369,7 @@ fn resolveSymbols(self: *MachO) !void {
2382 // We create an empty atom for this symbol.2369 // We create an empty atom for this symbol.
2383 // TODO perhaps we should special-case special symbols? Create a separate2370 // TODO perhaps we should special-case special symbols? Create a separate
2384 // linked list of atoms?2371 // linked list of atoms?
2385 const block = try self.base.allocator.create(TextBlock);2372 const block = try self.managed_blocks.addOne(self.base.allocator);
2386 errdefer self.base.allocator.destroy(block);
2387
2388 block.* = TextBlock.empty;2373 block.* = TextBlock.empty;
2389 block.local_sym_index = local_sym_index;2374 block.local_sym_index = local_sym_index;
2390 block.code = try self.base.allocator.alloc(u8, 0);2375 block.code = try self.base.allocator.alloc(u8, 0);
...@@ -3243,9 +3228,8 @@ pub fn deinit(self: *MachO) void {...@@ -3243,9 +3228,8 @@ pub fn deinit(self: *MachO) void {
3243 ds.deinit(self.base.allocator);3228 ds.deinit(self.base.allocator);
3244 }3229 }
32453230
3231 self.pending_updates.deinit(self.base.allocator);
3246 self.pie_fixups.deinit(self.base.allocator);3232 self.pie_fixups.deinit(self.base.allocator);
3247 self.stub_fixups.deinit(self.base.allocator);
3248 self.text_block_free_list.deinit(self.base.allocator);
3249 self.got_entries.deinit(self.base.allocator);3233 self.got_entries.deinit(self.base.allocator);
3250 self.got_entries_map.deinit(self.base.allocator);3234 self.got_entries_map.deinit(self.base.allocator);
3251 self.got_entries_free_list.deinit(self.base.allocator);3235 self.got_entries_free_list.deinit(self.base.allocator);
...@@ -3288,8 +3272,12 @@ pub fn deinit(self: *MachO) void {...@@ -3288,8 +3272,12 @@ pub fn deinit(self: *MachO) void {
3288 }3272 }
3289 self.load_commands.deinit(self.base.allocator);3273 self.load_commands.deinit(self.base.allocator);
32903274
3291 // TODO dealloc all blocks3275 for (self.managed_blocks.items) |*block| {
3276 block.deinit(self.base.allocator);
3277 }
3278 self.managed_blocks.deinit(self.base.allocator);
3292 self.blocks.deinit(self.base.allocator);3279 self.blocks.deinit(self.base.allocator);
3280 self.text_block_free_list.deinit(self.base.allocator);
3293}3281}
32943282
3295pub fn closeFiles(self: MachO) void {3283pub fn closeFiles(self: MachO) void {
...@@ -3302,6 +3290,9 @@ pub fn closeFiles(self: MachO) void {...@@ -3302,6 +3290,9 @@ pub fn closeFiles(self: MachO) void {
3302}3290}
33033291
3304fn freeTextBlock(self: *MachO, text_block: *TextBlock) void {3292fn freeTextBlock(self: *MachO, text_block: *TextBlock) void {
3293 log.debug("freeTextBlock {*}", .{text_block});
3294 // text_block.deinit(self.base.allocator);
3295
3305 var already_have_free_list_node = false;3296 var already_have_free_list_node = false;
3306 {3297 {
3307 var i: usize = 0;3298 var i: usize = 0;
...@@ -3467,18 +3458,22 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {...@@ -3467,18 +3458,22 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
3467 .val = decl.val,3458 .val = decl.val,
3468 }, &code_buffer, .none);3459 }, &code_buffer, .none);
34693460
3470 const code = switch (res) {3461 const code = blk: {
3471 .externally_managed => |x| x,3462 switch (res) {
3472 .appended => code_buffer.items,3463 .externally_managed => |x| break :blk x,
3473 .fail => |em| {3464 .appended => {
3474 // Clear any PIE fixups for this decl.3465 decl.link.macho.code = code_buffer.toOwnedSlice();
3475 self.pie_fixups.shrinkRetainingCapacity(0);3466 log.warn("WAT", .{});
3476 // Clear any stub fixups for this decl.3467 break :blk decl.link.macho.code;
3477 self.stub_fixups.shrinkRetainingCapacity(0);3468 },
3478 decl.analysis = .codegen_failure;3469 .fail => |em| {
3479 try module.failed_decls.put(module.gpa, decl, em);3470 // Clear any PIE fixups for this decl.
3480 return;3471 self.pie_fixups.shrinkRetainingCapacity(0);
3481 },3472 decl.analysis = .codegen_failure;
3473 try module.failed_decls.put(module.gpa, decl, em);
3474 return;
3475 },
3476 }
3482 };3477 };
34833478
3484 const required_alignment = decl.ty.abiAlignment(self.base.options.target);3479 const required_alignment = decl.ty.abiAlignment(self.base.options.target);
...@@ -3559,12 +3554,12 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {...@@ -3559,12 +3554,12 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
3559 switch (self.base.options.target.cpu.arch) {3554 switch (self.base.options.target.cpu.arch) {
3560 .x86_64 => {3555 .x86_64 => {
3561 const displacement = try math.cast(u32, target_addr - this_addr - 4);3556 const displacement = try math.cast(u32, target_addr - this_addr - 4);
3562 mem.writeIntLittle(u32, code_buffer.items[fixup.offset..][0..4], displacement);3557 mem.writeIntLittle(u32, decl.link.macho.code[fixup.offset..][0..4], displacement);
3563 },3558 },
3564 .aarch64 => {3559 .aarch64 => {
3565 // TODO optimize instruction based on jump length (use ldr(literal) + nop if possible).3560 // TODO optimize instruction based on jump length (use ldr(literal) + nop if possible).
3566 {3561 {
3567 const inst = code_buffer.items[fixup.offset..][0..4];3562 const inst = decl.link.macho.code[fixup.offset..][0..4];
3568 var parsed = mem.bytesAsValue(meta.TagPayload(3563 var parsed = mem.bytesAsValue(meta.TagPayload(
3569 aarch64.Instruction,3564 aarch64.Instruction,
3570 aarch64.Instruction.pc_relative_address,3565 aarch64.Instruction.pc_relative_address,
...@@ -3576,7 +3571,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {...@@ -3576,7 +3571,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
3576 parsed.immlo = @truncate(u2, pages);3571 parsed.immlo = @truncate(u2, pages);
3577 }3572 }
3578 {3573 {
3579 const inst = code_buffer.items[fixup.offset + 4 ..][0..4];3574 const inst = decl.link.macho.code[fixup.offset + 4 ..][0..4];
3580 var parsed = mem.bytesAsValue(meta.TagPayload(3575 var parsed = mem.bytesAsValue(meta.TagPayload(
3581 aarch64.Instruction,3576 aarch64.Instruction,
3582 aarch64.Instruction.load_store_register,3577 aarch64.Instruction.load_store_register,
...@@ -3590,39 +3585,24 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {...@@ -3590,39 +3585,24 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
3590 }3585 }
3591 }3586 }
35923587
3593 // Resolve stubs (if any)3588 // Resolve relocations
3594 const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;3589 try decl.link.macho.resolveRelocs(self);
3595 const stubs = text_segment.sections.items[self.stubs_section_index.?];
3596 for (self.stub_fixups.items) |fixup| {
3597 const stubs_index = self.stubs_map.get(fixup.symbol) orelse unreachable;
3598 const stub_addr = stubs.addr + stubs_index * stubs.reserved2;
3599 const text_addr = symbol.n_value + fixup.start;
3600 switch (self.base.options.target.cpu.arch) {
3601 .x86_64 => {
3602 assert(stub_addr >= text_addr + fixup.len);
3603 const displacement = try math.cast(u32, stub_addr - text_addr - fixup.len);
3604 var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)];
3605 mem.writeIntSliceLittle(u32, placeholder, displacement);
3606 },
3607 .aarch64 => {
3608 assert(stub_addr >= text_addr);
3609 const displacement = try math.cast(i28, stub_addr - text_addr);
3610 var placeholder = code_buffer.items[fixup.start..][0..fixup.len];
3611 mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(displacement).toU32());
3612 },
3613 else => unreachable, // unsupported target architecture
3614 }
3615 if (!fixup.already_defined) {
3616 try self.writeStub(stubs_index);
3617 try self.writeStubInStubHelper(stubs_index);
3618 try self.writeLazySymbolPointer(stubs_index);
36193590
3620 self.rebase_info_dirty = true;3591 // Apply pending updates
3621 self.lazy_binding_info_dirty = true;3592 while (self.pending_updates.popOrNull()) |update| {
3593 switch (update.kind) {
3594 .got => unreachable,
3595 .stub => {
3596 try self.writeStub(update.index);
3597 try self.writeStubInStubHelper(update.index);
3598 try self.writeLazySymbolPointer(update.index);
3599 self.rebase_info_dirty = true;
3600 self.lazy_binding_info_dirty = true;
3601 },
3622 }3602 }
3623 }3603 }
3624 self.stub_fixups.shrinkRetainingCapacity(0);
36253604
3605 const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
3626 const text_section = text_segment.sections.items[self.text_section_index.?];3606 const text_section = text_segment.sections.items[self.text_section_index.?];
3627 const section_offset = symbol.n_value - text_section.addr;3607 const section_offset = symbol.n_value - text_section.addr;
3628 const file_offset = text_section.offset + section_offset;3608 const file_offset = text_section.offset + section_offset;
...@@ -3756,6 +3736,7 @@ pub fn deleteExport(self: *MachO, exp: Export) void {...@@ -3756,6 +3736,7 @@ pub fn deleteExport(self: *MachO, exp: Export) void {
3756}3736}
37573737
3758pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {3738pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {
3739 log.debug("freeDecl {*}", .{decl});
3759 // Appending to free lists is allowed to fail because the free lists are heuristics based anyway.3740 // Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
3760 self.freeTextBlock(&decl.link.macho);3741 self.freeTextBlock(&decl.link.macho);
3761 if (decl.link.macho.local_sym_index != 0) {3742 if (decl.link.macho.local_sym_index != 0) {
...@@ -4314,7 +4295,8 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64,...@@ -4314,7 +4295,8 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64,
4314 // should be deleted because the block that it points to has grown to take up4295 // should be deleted because the block that it points to has grown to take up
4315 // more of the extra capacity.4296 // more of the extra capacity.
4316 if (!big_block.freeListEligible(self.*)) {4297 if (!big_block.freeListEligible(self.*)) {
4317 _ = self.text_block_free_list.swapRemove(i);4298 const bl = self.text_block_free_list.swapRemove(i);
4299 bl.deinit(self.base.allocator);
4318 } else {4300 } else {
4319 i += 1;4301 i += 1;
4320 }4302 }
...@@ -4386,25 +4368,43 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64,...@@ -4386,25 +4368,43 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64,
4386 return vaddr;4368 return vaddr;
4387}4369}
43884370
4389pub fn addExternFn(self: *MachO, name: []const u8) !SymbolWithLoc {4371pub fn addExternFn(self: *MachO, name: []const u8) !u32 {
4390 log.debug("adding new extern function '{s}' with dylib ordinal 1", .{name});4372 const sym_name = try std.fmt.allocPrint(self.base.allocator, "_{s}", .{name});
4373 const already_defined = self.symbol_resolver.contains(sym_name);
4374
4375 if (already_defined) {
4376 const resolv = self.symbol_resolver.get(sym_name) orelse unreachable;
4377 self.base.allocator.free(sym_name);
4378 return resolv.where_index;
4379 }
4380
4381 log.debug("adding new extern function '{s}' with dylib ordinal 1", .{sym_name});
4391 const import_sym_index = @intCast(u32, self.imports.items.len);4382 const import_sym_index = @intCast(u32, self.imports.items.len);
4392 try self.imports.append(self.base.allocator, .{4383 try self.imports.append(self.base.allocator, .{
4393 .n_strx = try self.makeString(name),4384 .n_strx = try self.makeString(sym_name),
4394 .n_type = macho.N_UNDF | macho.N_EXT,4385 .n_type = macho.N_UNDF | macho.N_EXT,
4395 .n_sect = 0,4386 .n_sect = 0,
4396 .n_desc = packDylibOrdinal(1),4387 .n_desc = packDylibOrdinal(1),
4397 .n_value = 0,4388 .n_value = 0,
4398 });4389 });
4399 const resolv = .{4390 try self.symbol_resolver.putNoClobber(self.base.allocator, sym_name, .{
4400 .where = .import,4391 .where = .import,
4401 .where_index = import_sym_index,4392 .where_index = import_sym_index,
4402 };4393 });
4403 try self.symbol_resolver.putNoClobber(self.base.allocator, try self.base.allocator.dupe(u8, name), resolv);4394
4404 const stubs_index = @intCast(u32, self.stubs.items.len);4395 const stubs_index = @intCast(u32, self.stubs.items.len);
4405 try self.stubs.append(self.base.allocator, import_sym_index);4396 try self.stubs.append(self.base.allocator, import_sym_index);
4406 try self.stubs_map.putNoClobber(self.base.allocator, import_sym_index, stubs_index);4397 try self.stubs_map.putNoClobber(self.base.allocator, import_sym_index, stubs_index);
4407 return resolv;4398
4399 // TODO discuss this. The caller context expects codegen.InnerError{ OutOfMemory, CodegenFail },
4400 // which obviously doesn't include file writing op errors. So instead of trying to write the stub
4401 // entry right here and now, queue it up and dispose of when updating decl.
4402 try self.pending_updates.append(self.base.allocator, .{
4403 .kind = .stub,
4404 .index = stubs_index,
4405 });
4406
4407 return import_sym_index;
4408}4408}
44094409
4410const NextSegmentAddressAndOffset = struct {4410const NextSegmentAddressAndOffset = struct {
src/link/MachO/Object.zig+1-3
...@@ -723,9 +723,7 @@ pub fn parseTextBlocks(self: *Object, macho_file: *MachO) !void {...@@ -723,9 +723,7 @@ pub fn parseTextBlocks(self: *Object, macho_file: *MachO) !void {
723 break :blk block_local_sym_index;723 break :blk block_local_sym_index;
724 };724 };
725725
726 const block = try self.allocator.create(TextBlock);726 const block = try macho_file.managed_blocks.addOne(macho_file.base.allocator);
727 errdefer self.allocator.destroy(block);
728
729 block.* = TextBlock.empty;727 block.* = TextBlock.empty;
730 block.local_sym_index = block_local_sym_index;728 block.local_sym_index = block_local_sym_index;
731 block.code = try self.allocator.dupe(u8, code);729 block.code = try self.allocator.dupe(u8, code);
src/link/MachO/TextBlock.zig+1-1
...@@ -558,9 +558,9 @@ pub fn deinit(self: *TextBlock, allocator: *Allocator) void {...@@ -558,9 +558,9 @@ pub fn deinit(self: *TextBlock, allocator: *Allocator) void {
558 self.bindings.deinit(allocator);558 self.bindings.deinit(allocator);
559 self.rebases.deinit(allocator);559 self.rebases.deinit(allocator);
560 self.relocs.deinit(allocator);560 self.relocs.deinit(allocator);
561 self.allocator.free(self.code);
562 self.contained.deinit(allocator);561 self.contained.deinit(allocator);
563 self.aliases.deinit(allocator);562 self.aliases.deinit(allocator);
563 allocator.free(self.code);
564}564}
565565
566/// Returns how much room there is to grow in virtual address space.566/// Returns how much room there is to grow in virtual address space.