Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: large page support for macOS #28977

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1037,16 +1037,18 @@ def configure_node(o):
else:
o['variables']['node_use_dtrace'] = 'false'

if options.node_use_large_pages and not flavor in ('linux', 'freebsd'):
if options.node_use_large_pages and not flavor in ('linux', 'freebsd', 'mac'):
raise Exception(
'Large pages are supported only on Linux Systems.')
if options.node_use_large_pages and flavor in ('linux', 'freebsd'):
'Large pages are supported only on Linux, FreeBSD and MacOS Systems.')
if options.node_use_large_pages and flavor in ('linux', 'freebsd', 'mac'):
if options.shared or options.enable_static:
raise Exception(
'Large pages are supported only while creating node executable.')
if target_arch!="x64":
raise Exception(
'Large pages are supported only x64 platform.')
if flavor == 'mac':
info('macOS server with 32GB or more is recommended')
if flavor == 'linux':
# Example full version string: 2.6.32-696.28.1.el6.x86_64
FULL_KERNEL_VERSION=os.uname()[2]
Expand Down
2 changes: 1 addition & 1 deletion node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@
}],
],
}],
[ 'node_use_large_pages=="true" and OS in "linux freebsd"', {
[ 'node_use_large_pages=="true" and OS in "linux freebsd mac"', {
'defines': [ 'NODE_ENABLE_LARGE_CODE_PAGES=1' ],
# The current implementation of Large Pages is under Linux.
# Other implementations are possible but not currently supported.
Expand Down
98 changes: 77 additions & 21 deletions src/large_pages/node_large_page.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
#if defined(__FreeBSD__)
#include <sys/sysctl.h>
#include <sys/user.h>
#elif defined(__APPLE__)
#include <mach/vm_map.h>
#endif
#include <unistd.h> // readlink

Expand Down Expand Up @@ -212,6 +214,42 @@ static struct text_region FindNodeTextRegion() {
}
start += cursz;
}
#elif defined(__APPLE__)
struct text_region nregion;
nregion.found_text_region = false;
struct vm_region_submap_info_64 map;
mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
vm_address_t addr = 0UL;
vm_size_t size = 0;
natural_t depth = 1;

while (true) {
if (vm_region_recurse_64(mach_task_self(), &addr, &size, &depth,
reinterpret_cast<vm_region_info_64_t>(&map),
&count) != KERN_SUCCESS) {
break;
}

if (map.is_submap) {
depth++;
} else {
char* start = reinterpret_cast<char*>(hugepage_align_up(addr));
char* end = reinterpret_cast<char*>(hugepage_align_down(addr+size));
size_t esize = end - start;

if (end > start && (map.protection & VM_PROT_READ) != 0 &&
(map.protection & VM_PROT_EXECUTE) != 0) {
nregion.found_text_region = true;
nregion.from = start;
nregion.to = end;
nregion.total_hugepages = esize / hps;
break;
}

addr += size;
size = 0;
}
}
#endif
return nregion;
}
Expand Down Expand Up @@ -267,11 +305,15 @@ static bool IsSuperPagesEnabled() {
// 2: This function should not call any function(s) that might be moved.
// a. map a new area and copy the original code there
// b. mmap using the start address with MAP_FIXED so we get exactly
// the same virtual address
// the same virtual address (except on macOS).
// c. madvise with MADV_HUGE_PAGE
// d. If successful copy the code there and unmap the original region
int
#if !defined(__APPLE__)
__attribute__((__section__(".lpstub")))
#else
__attribute__((__section__("__TEXT,__lpstub")))
#endif
__attribute__((__aligned__(hps)))
__attribute__((__noinline__))
MoveTextRegionToLargePages(const text_region& r) {
Expand All @@ -289,6 +331,9 @@ MoveTextRegionToLargePages(const text_region& r) {
PrintSystemError(errno);
return -1;
}
OnScopeLeave munmap_on_return([nmem, size]() {
if (-1 == munmap(nmem, size)) PrintSystemError(errno);
});

memcpy(nmem, r.from, size);

Expand All @@ -302,7 +347,6 @@ MoveTextRegionToLargePages(const text_region& r) {
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1 , 0);
if (tmem == MAP_FAILED) {
PrintSystemError(errno);
munmap(nmem, size);
return -1;
}

Expand All @@ -313,11 +357,6 @@ MoveTextRegionToLargePages(const text_region& r) {
if (ret == -1) {
PrintSystemError(errno);
}
ret = munmap(nmem, size);
if (ret == -1) {
PrintSystemError(errno);
}

return -1;
}
#elif defined(__FreeBSD__)
Expand All @@ -327,32 +366,46 @@ MoveTextRegionToLargePages(const text_region& r) {
MAP_ALIGNED_SUPER, -1 , 0);
if (tmem == MAP_FAILED) {
PrintSystemError(errno);
munmap(nmem, size);
return -1;
}
#endif

memcpy(start, nmem, size);
ret = mprotect(start, size, PROT_READ | PROT_EXEC);
#elif defined(__APPLE__)
// There is not enough room to reserve the mapping close
// to the region address so we content to give a hint
// without forcing the new address being closed to.
// We explicitally gives all permission since we plan
bnoordhuis marked this conversation as resolved.
Show resolved Hide resolved
// to write into it.
tmem = mmap(start, size,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS,
VM_FLAGS_SUPERPAGE_SIZE_2MB, 0);
if (tmem == MAP_FAILED) {
PrintSystemError(errno);
return -1;
}
memcpy(tmem, nmem, size);
ret = mprotect(start, size, PROT_READ | PROT_WRITE | PROT_EXEC);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why this is necessary? It's already mapped rwx, isn't it?

Should the mmap() call include MAP_FIXED in its flags like it does on Linux and FreeBSD? If not, can you update the comment atop of this function?

Aside: since there are a lot of munmap(nmem, size) calls now, it'd be nice to DRY it:

nmem = mmap(...);
OnScopeLeave munmap_on_return([nmem, size] () {
  if (-1 == munmap(nmem, size)) PrintSystemError(errno);
});

I guess you lose the ability to return an error from MoveTextRegionToLargePages() on munmap() failure but that doesn't seem too bad, it's not really a fatal error and probably hypothetical to boot. You could simply abort on failure.

Copy link
Contributor Author

@devnexen devnexen Aug 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To answer your first question it is rx but will add a comment (note that s start address not the new address). Ok for the rest (including the info change in configure.py).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to round out this discussion: using MAP_FIXED is not an option on macos? start is properly aligned, isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start is indeed aligned like other platforms, MAP_FIXED failed on this platform though with 24MB close to the start address reservation attempt.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'd have to go digging for the reason again but MAP_FIXED has long had problems with mmap for large page sizes on macos. Pretty sure the macos api docs recommend not to use it but if I recall correctly they don't really explain why.

if (ret == -1) {
PrintSystemError(errno);
ret = munmap(tmem, size);
if (ret == -1) {
PrintSystemError(errno);
}
ret = munmap(nmem, size);
if (ret == -1) {
PrintSystemError(errno);
}
return -1;
}
memcpy(start, tmem, size);
#else
memcpy(start, nmem, size);
#endif

// Release the old/temporary mapped region
ret = munmap(nmem, size);
ret = mprotect(start, size, PROT_READ | PROT_EXEC);
if (ret == -1) {
PrintSystemError(errno);
ret = munmap(tmem, size);
if (ret == -1) {
PrintSystemError(errno);
}
return -1;
}

return ret;
}

Expand All @@ -369,16 +422,19 @@ int MapStaticCodeToLargePages() {
return MoveTextRegionToLargePages(r);

return -1;
#elif defined(__FreeBSD__)
#elif defined(__FreeBSD__) || defined(__APPLE__)
return MoveTextRegionToLargePages(r);
#endif
}

bool IsLargePagesEnabled() {
#if defined(__linux__)
return IsTransparentHugePagesEnabled();
#else
#elif defined(__FreeBSD__)
return IsSuperPagesEnabled();
#elif defined(__APPLE__)
// pse-36 flag is present in recent mac x64 products.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How recent is recent? Node.js supports macOS 10.11 and up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it is fine El Capitan is recent enough :)

return true;
#endif
}

Expand Down