Bug 201028

Summary: [WebAssembly] iOS 13 RangeError: Maximum call stack size exceeded.
Product: WebKit Reporter: Kenneth Pouncey <kepounce>
Component: WebAssemblyAssignee: Nobody <webkit-unassigned>
Status: RESOLVED FIXED    
Severity: Blocker CC: anandtapangupta68, apple-rth, fpizlo, joe.fradley, keith_miller, kg, lewing, mark.lam, saam, webkit-bug-importer, ysuzuki
Priority: P2 Keywords: InRadar
Version: Safari 13   
Hardware: iPhone / iPad   
OS: iOS 13   
See Also: https://bugs.webkit.org/show_bug.cgi?id=201026

Description Kenneth Pouncey 2019-08-21 22:39:21 PDT
This is a follow up to the issue reported here https://bugs.webkit.org/show_bug.cgi?id=201026

The iOS 13 Beta is pretty much unusable running through mobile safari while they do load on desktop browsers including safari and android chrome.

The sites that do not run into the error as reported in the issue link above are running into other `RangeError: Maximum call stack size exceeded.` as well as `Unhandled Promise Rejection: RangeError: Maximum call stack size exceeded.`.  These sites used to load in mobile safari 12 but no longer load in the Beta 13.

Following the outline as described in the link above to the other issue the sites do load.

Is there a way to control the stack limit in mobile safari via a parameter to extend the call stack limit in mobile safari?  That way users can have a way to work around the issue.

Please let me know if this should be filed somewhere else as this is against a Beta 13.

For a list of sites and examples to look at one can see the issue here:  https://github.com/mono/mono/issues/16144

The same attachments can be used from here: https://bugs.webkit.org/show_bug.cgi?id=201026
Comment 1 Radar WebKit Bug Importer 2019-08-22 14:13:21 PDT
<rdar://problem/54613803>
Comment 2 Tapan Anand 2019-10-01 10:24:30 PDT
The issue exists on desktop Safari 13 as well. I created a simple example code where I am doing recursion upto 1000 levels and I am getting the `Maximum call stack size exceeded` error at about 670 recursion depth. So, it seems some limit has been set on the max stack size for WASM.
Comment 3 Saam Barati 2019-10-20 22:06:27 PDT
All browsers have stack depth limits. Is your bug report just that our limit has become smaller?
Comment 4 Kenneth Pouncey 2019-10-21 00:09:43 PDT
I will agree that `All browsers have stack depth limits.` It is not only that it has become smaller but applications that worked before in IOS12, no longer work.  

The troubling part is that each mobile device has a separate variable time where it will work.  

Same application but if a setTimeout, of a variable time from 500ms to 4 seconds depending on the device, is used before the first call it does work.  This suggests that the stack is smaller on startup for some reason and then becomes available later?  

Could this be something that is caused by the JITing/tiered JITing of the WebAssembly module when loading?  The stack that is available is being used by the compilation of the WebAssembly module and then freed back up again?

Is this controllable somehow?  Maybe some type of signal that says the WebAssembly module has finished doing what it is doing on mobile devices and can be called into now.  Other than onRuntimeIntialized of course as we are using that to start up the app now.

Which stack is actually exceeded to be exact.  Is it the JavaScript stack depth or the WebAssembly stack depth?

We get the same information for the different stack sizes reported no matter what browser we use.  For example:

 WASM: TOTALMEMORY: 33554432 TOTALSTACK: 5242880 STACKMAX: 726800 STACKTOP: 5969680 STACKBASE: 5969680

The above line is exactly the same across all browsers including safari desktop yet we are running into some limit on mobile safari but not sure exactly where.

Any information on the above would be greatly appreciated.

Thanks in advance.
Comment 5 Katelyn Gadd 2019-10-21 00:57:54 PDT
Just to clarify: The maximum stack depth for WASM in Safari appears to change depending on elapsed time, which would seem to be caused by the tiered JIT. It makes sense that JIT behavior would have changed between 12 and 13, especially because I've seen posts on the WebKit blog to suggest that changes have occurred.

The amount of time we need to wait seems to vary across devices (likely based on specs?) which adds weight to our theory that this is based on tiered jit.

From examining some of the details of the first tier (BBQ - build bytecode quickly? I can't remember the name) it seems like it could end up generating more stack bloat for complex functions than the final tier does. We have some relatively complex functions (an interpreter, etc) that should normally be very light but are exceeding stack in Safari. Basic tests with simple modules show that we can recurse quite far so it doesn't seem like we're hitting any trivial stack limits here - WASM can in fact recurse much further in Safari than it can in Chrome or Firefox - but instead the code generation seems to be really bad for some corner case we're hitting here.

Is there a way to manually reproduce the behavior of the first tier jit without the later tiers? I saw a mix of compile flags when digging through the WebKit source but it's very hard to understand all these systems, there aren't particularly detailed comments and I couldn't find any documentation of what flags iOS or OS X safari build JSC with.
Comment 6 Tapan Anand 2019-10-23 05:44:49 PDT
(In reply to Saam Barati from comment #3)
> All browsers have stack depth limits. Is your bug report just that our limit
> has become smaller?

Yeah Saam that's what I meant. Basically it seems that the stack limit was reduced in the newer version or there was some other logic change that affected the stack limit as is being discussed in comments
Comment 7 Joseph Fradley 2019-10-31 16:27:00 PDT
I measured the call stack depth using the following simple program:
/////////
void recurseHere(int cnt)
{
  if (cnt % 100 == 0)
    printf("recurseHere: %d\n", cnt);
  recurseHere(++cnt);
}

int main()
{
  recurseHere(0);
  return 0;
}
////////

I also measured the max stack size via a different simple program. Below are the results.

Max Call Stack Depth | Max Stack Size | OS          | Browser | Device
11,000+              | 5242848        |iOS 12.3.1   | Safari  | iPad Pro 10.5”
900+                 | 5242848        |iOS 13.2     | Safari  | iPad Pro 12.9”
900+                 | 5242848        |iOS 13.2     | Chrome  | iPad Pro 12.9”
17,800+              | 5242848        |macOS 10.15.1| Chrome  | MacBook 
16,700+              | 5242848        |macOS 10.15.1| Safari  | MacBook


iOS 13 has an order of magnitude less 'Max Call Stack Depth' from iOS 12, even though the 'Max Stack Size' is the same.

I tried throwing in different Sleep() calls to see if it effected it as other comments suggested but didn't see any difference.
Comment 8 Katelyn Gadd 2019-10-31 17:06:52 PDT
The massive drop in iOS 13 explains a lot - I wasn't able to test that and don't have an iOS 12 device to compare. We were observing regressions in desktop Safari as well, which I couldn't reproduce.

The Sleep solution is only going to work for very very large modules (ours are in the dozens of MB) and the fact that the required delay changes between devices suggests that it's based on compile speeds or something.

Interesting to see a stack depth limit of 15k for you. I approached around 80k in Safari on Catalina w/a recursive function that accepts 4 int32 arguments. Chrome and Firefox don't go as far, and their limits are dramatically different between Catalina and Windows 10. I suspect there's variability here based on other factors.

Being able to dump the generated JITcode for our functions would help us identify what's going on, but I haven't been able to find a way to do this other than doing some really complex trickery in a debugger - only possible on PC, not iOS - or compiling JSC from source, which would require us to know the exact build configuration Safari uses.
Comment 9 Joseph Fradley 2019-11-01 15:44:40 PDT
The differences in stack depth could possibly be because of differences in compilation options. I'm using all defaults for my wasm test apps `emcc hello_world.cpp -o hello.html`. Maybe compiling with more optimizations increases it.

As a final test I updated the one iPad to iOS 13.2 from 12.3.1 and confirmed the Max Call Stack depth dropped to 900+ from 11,000+. Here's an updated table:


Max Call Stack Depth | Max Stack Size | OS          | Browser | Device
11,000+              | 5242848        |iOS 12.3.1   | Safari  | iPad Pro 10.5”
**900+               | 5242848        |iOS 13.2     | Safari  | iPad Pro 10.5”
900+                 | 5242848        |iOS 13.2     | Safari  | iPad Pro 12.9”
900+                 | 5242848        |iOS 13.2     | Chrome  | iPad Pro 12.9”
17,800+              | 5242848        |macOS 10.15.1| Chrome  | MacBook 
16,700+              | 5242848        |macOS 10.15.1| Safari  | MacBook
Comment 10 Yusuke Suzuki 2021-04-20 01:55:49 PDT
I think this is fixed one year ago.