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

Use proxy objects for MEMORY64 HEAP access over 4Gb #19737

Merged
merged 27 commits into from
Jul 27, 2023

Conversation

dakenf
Copy link
Contributor

@dakenf dakenf commented Jun 28, 2023

Currently HEAP arrays fail to create when memory size is > 4gb so proxy objets for HEAP(U)8 are created when allocated memory is >4gb and for HEAP(U)16 when >8gb.

Solution overrides TypedArray set, subarray, fill, copyWithin, and slice methods. As well as direct property access using memory pointers. It creates N TypedArray views with maximum of 4 * 1024 * 1024 * 1024 - 2 elements for each heap view.

Also, test_memory64_proxies test added to check correctness of the implementation.

See https://bugs.chromium.org/p/v8/issues/detail?id=4153

@sbc100
Copy link
Collaborator

sbc100 commented Jun 28, 2023

Thanks for working on this! I've been meaning to do something very similar myself but you have likely saved me a bunch of work here!

@dakenf
Copy link
Contributor Author

dakenf commented Jun 28, 2023

Sure! I will fix all the failing tests soon

src/preamble.js Outdated Show resolved Hide resolved
src/preamble.js Outdated Show resolved Hide resolved
src/library_webgpu.js Outdated Show resolved Hide resolved
sbc100 added a commit that referenced this pull request Jun 28, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 28, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 28, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 28, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 28, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 28, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 28, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 28, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 28, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 28, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 28, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
@dakenf
Copy link
Contributor Author

dakenf commented Jun 29, 2023

I've fixed tests and now proxies are created only if allocated memory is > 4gb
Will remove fixPointer once #19740 is merged (would like to have a fully working version for now)
Let me know if some other changes are needed

Also, memory growth does not work even if initial memory is >4gb, looks like chrome issue. Now going to try make it working with ASYNCIFY=2 and threads. First one throws bigint to number error somewhere, and second gives linker error. Guess the latter would require some deep digging

sbc100 added a commit that referenced this pull request Jun 29, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 30, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 30, 2023
…GB is set

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
sbc100 added a commit that referenced this pull request Jun 30, 2023
…GB is set (#19740)

This extends the use automatically-generated JS wrappers to handle
conversion of incoming i32 pointer to u32 JS numbers.  This is only
needed when CAN_ADDRESS_2GB is set.

See #19737
@juj
Copy link
Collaborator

juj commented Jul 3, 2023

Currently HEAP arrays fail to create when memory size is > 4gb

Isn't this something that browsers will need to fix as part of adding support for wasm64?

@dakenf
Copy link
Contributor Author

dakenf commented Jul 3, 2023

Isn't this something that browsers will need to fix as part of adding support for wasm64?

Yes. But who knows when that would happen. The workaround allows using >4gb now and can be removed without modifying any other code once full 64bit support is rolled out in browsers. It does not affect anything when <=4gb memory is used

emcc.py Outdated
@@ -3728,7 +3728,8 @@ def phase_binaryen(target, options, wasm_target):
# >=2GB heap support requires pointers in JS to be unsigned. rather than
# require all pointers to be unsigned by default, which increases code size
# a little, keep them signed, and just unsign them here if we need that.
if settings.CAN_ADDRESS_2GB:
Copy link
Collaborator

Choose a reason for hiding this comment

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

This line can now be reverted since CAN_ADDRESS_2GB is never set when MEMORY64 is set: See #19755

src/library_strings.js Outdated Show resolved Hide resolved
src/preamble.js Outdated
var maxArraySize = Math.min(b.byteLength, 4 * 1024 * 1024 * 1024 - 8);
var proxyHandler = (type, heapBlock) => ({
heapBlock,
copyWithin (target, start, end) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Our JS style has no space between function names and their arguments

src/runtime_safe_heap.js Outdated Show resolved Hide resolved
src/preamble.js Outdated
copyWithin (target, start, end) {
target = fixPointer(target)
start = fixPointer(start)
end = fixPointer(end)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm hoping you can simply remove fixPointer since all incoming pointers should already i53 (and unsigned). See #19740.

src/preamble.js Outdated Show resolved Hide resolved
src/preamble.js Outdated Show resolved Hide resolved
@sbc100
Copy link
Collaborator

sbc100 commented Jul 5, 2023

Can you add a comment to this new code and include a link to the upstream bug: https://bugs.chromium.org/p/v8/issues/detail?id=4153

@dakenf
Copy link
Contributor Author

dakenf commented Jul 5, 2023

Can you add a comment to this new code and include a link to the upstream bug: https://bugs.chromium.org/p/v8/issues/detail?id=4153

Sure. I've made a fix for ASYNCIFY=1 and MEMORY64=1. Should I include it into this PR or open a separate one?

@sbc100 sbc100 changed the title MEMORY64 HEAP proxy objects Use proxy objects for MEMORY64 HEAP access over 4Gb Jul 10, 2023
@sbc100
Copy link
Collaborator

sbc100 commented Jul 10, 2023

lgtm

I re-titled this PR for clarity.

# test that growth doesn't go beyond 2GB without the max being set for that,
# and that we can catch an allocation failure exception for that
self.emcc_args += ['-O2', '-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=2GB']
self.do_run_in_out_file_test('browser', 'test_2GB_fail.cpp')

@no_firefox('no 4GB support yet')
@also_with_wasm64
@requires_v8
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why this? In general if we can test on the command line I think we should prefer to do that. Browser tests tend to be more flaky and difficult to work with (for example we don't get to see the stdout in CI).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It fails with a weird assertion error which i cannot reproduce locally. Neither in command line, nor in the browser. Maybe because of actual out of memory issue?
Will dig a bit more once this test run finishes

@dakenf
Copy link
Contributor Author

dakenf commented Jul 11, 2023

I have disabled test_zzz_zzz_4gb_fail_wasm64 because it fails during stack overflow check

RuntimeError: Aborted(Assertion failed)
    at abort (/tmp/emscripten_test_browser_grkv22q_/test_4GB_fail.js:1:13321)
    at assert (/tmp/emscripten_test_browser_grkv22q_/test_4GB_fail.js:1:4639)
    at ptrToString (/tmp/emscripten_test_browser_grkv22q_/test_4GB_fail.js:1:20015)
    at checkStackCookie (/tmp/emscripten_test_browser_grkv22q_/test_4GB_fail.js:1:9970)
    at handleException (/tmp/emscripten_test_browser_grkv22q_/test_4GB_fail.js:1:24645)
    at callMain (/tmp/emscripten_test_browser_grkv22q_/test_4GB_fail.js:1:33035)
    at doRun (/tmp/emscripten_test_browser_grkv22q_/test_4GB_fail.js:1:33424)
    at run (/tmp/emscripten_test_browser_grkv22q_/test_4GB_fail.js:1:33592)
    at runCaller (/tmp/emscripten_test_browser_grkv22q_/test_4GB_fail.js:1:32614)
    at removeRunDependency (/tmp/emscripten_test_browser_grkv22q_/test_4GB_fail.js:1:13176)

in checkStackCookie function _emscripten_stack_get_end returns undefined. Which is later passed to ptrToString and it fails converting undefined to a string.

I can add if (typeof max === 'undefined) max = 0 here but it feels wrong

function checkStackCookie() {
#if !MINIMAL_RUNTIME
  if (ABORT) return;
#endif
  var max = _emscripten_stack_get_end();
#if RUNTIME_DEBUG
  dbg(`checkStackCookie: ${ptrToString(max)}`);
#endif
  // See writeStackCookie().
  if (max == 0) {
    max += 4;
  }
...

@sbc100
Copy link
Collaborator

sbc100 commented Jul 11, 2023

Interesting... _emscripten_stack_get_end is a native function and it should never return undefined. Something strange must be going on.

@dakenf
Copy link
Contributor Author

dakenf commented Jul 11, 2023

Interesting... _emscripten_stack_get_end is a native function and it should never return undefined. Something strange must be going on.

Indeed. And it runs successfully on my machine. So i'd guess it should be a hardware out of memory problem which gets the runtime mad
image

@dakenf
Copy link
Contributor Author

dakenf commented Jul 18, 2023

I've fixed tests from main branch here. LLVM update changed something in output size

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

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

lgtm if tests pass!

# means we don't compete for memory with anything else (and run it
# at the very very end, to reduce the risk of it OOM-killing the
# browser).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Restore this comment?

@@ -5371,14 +5371,7 @@ def test_wasm_worker_proxied_function(self):
self.btest(test_file('wasm_worker/proxied_function.c'), expected='0', args=['--js-library', test_file('wasm_worker/proxied_function.js'), '-sWASM_WORKERS', '-sASSERTIONS=0'])

@no_firefox('no 4GB support yet')
@requires_v8
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this needed?

def test_quick_exit(self):
self.do_other_test('test_quick_exit.c')


Copy link
Collaborator

Choose a reason for hiding this comment

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

Just one newline between methods in python

'-Wno-experimental',
'--extern-post-js', 'post.js'])
self.run_js('a.out.js')

Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove extra newline

@requires_node_canary
def test_memory64_proxies(self):
create_file('post.js', r'''
addOnPostRun(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe put this in its own file? (test/other/test_memory64_proxies.js?)

@dakenf
Copy link
Contributor Author

dakenf commented Jul 26, 2023

@sbc100 All done. Can you please also guide me on this one? WebAssembly/memory64#39

I've created a same proposal in WebAssembly/threads but the team has closed it as it should be part of the Memory64 spec. Currently it's not possible to create WebAssembly.Memory from JS with 64bit limits. And it's easy to fix in V8 source (just a couple of ifs). But I don't think my PR in V8 would be merged without a spec change as there's a comment like "update this when js spec is updated"

@sbc100
Copy link
Collaborator

sbc100 commented Jul 26, 2023

Can you try reverting all the test/code_size changes? I think that should help with a bunch of the current failures.

@dakenf
Copy link
Contributor Author

dakenf commented Jul 26, 2023

Can you try reverting all the test/code_size changes? I think that should help with a bunch of the current failures.

Yup. Should be good now

@sbc100 sbc100 merged commit 233e004 into emscripten-core:main Jul 27, 2023
@dakenf
Copy link
Contributor Author

dakenf commented Jul 27, 2023

Thanks! I's been a long way

@juj
Copy link
Collaborator

juj commented Aug 2, 2023

This PR only added polyfills for HEAP8 and HEAP16, but not HEAP32 or HEAP64. I don't see that being discussed in above, and wonder why that was the case?

@sbc100
Copy link
Collaborator

sbc100 commented Aug 2, 2023

This PR only added polyfills for HEAP8 and HEAP16, but not HEAP32 or HEAP64. I don't see that being discussed in above, and wonder why that was the case?

The reason is that the chromium issue/limit is on the number of elements in the view not the byte length. So even without this fix we can create HEAP32 views that can address 16gb. I seem to remember there are other implementation limits in chrome that will get hit before we get to 16gb. I'm hoping that by the we want to allow for >16gb memories this polyfill will no longer be needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants