[quickjs-devel] C stack usage

  • From: Simon CORSIN <simon@xxxxxxxxx>
  • To: quickjs-devel@xxxxxxxxxxxxx
  • Date: Wed, 21 Aug 2019 11:44:38 -0400

Hey folks,

I have been experimenting with QuickJS and so far it's been great. I had
some questions regarding the C stack frame usage. It seems that each call
to *JS_CallInternal* uses about 10K of C stack in debug when running in
64bits on my machine, which hinders how many JS calls we can make before
getting a stack overflow. Compiling and running with optimization does help
significantly, but we are still far from what Node (V8) or Safari
(JavaScript) can achieve.

Consider this example:

(function() {
function countRecursion(obj) {
obj.total += 1;

countRecursion(obj);
}

const obj = {
total: 0
};

try {
countRecursion(obj);
} catch (err) {}

console.log('Total recursion: ', obj.total);
})();


If I compile qjs in debug mode and run it, I get *27* recursions before
getting a stack overflow. In release mode I get *259* (almost 10x
improvement). For comparison, in Node I get *13966* recursions, in Safari
*13933*, in Duktape *9998*.
I understand of course that those results will depend on the max stack size
value configured in each of those engines. QuickJS has a default of 256KB,
which seems like a reasonable default amount. For reference on iOS, the
default max stack size for secondary threads is 512KB, so the fact that
QuickJS is set to half of that seems fine to me.

I've been toying around trying to find a way to make *JS_CallInternal* use
less C stack. According to
http://events17.linuxfoundation.org/sites/events/files/slides/GCC%252FClang%20Optimizations%20for%20Embedded%20Linux.pdf
,
GCC has a *-fconserve-stack* option, however it doesn't seem to exist on
clang at least not on the version I have on my machine. I haven't found any
compiler options that helped in raising the number I get when I run my
recursion benchmark. I then tried to remove the inline functions within
quickjs itself and replaced them by just regular function calls, and got
those results:


*Before removing inlines:*
./qjs recursion.js
Total recursion:  259
./qjs-debug recursion.js
Total recursion:  27


*After removing inlines:*./qjs recursion.js
Total recursion:  292
./qjs-debug recursion.js
Total recursion:  35

Removing inline function calls did help raising the JS call limit a bit,
maybe at the cost of reduced performance (I didn't check the real impact on
performance though). I then tried to reduce the number of local variables
used, by re-using local variables instead of creating new ones. For
example, instead of using *JSValue obj1* it uses a shared *JSValue tmp*
used by all bytecode operations that requires to store a *JSValue* on the
stack. I didn't go too deep into this, just wanted to see if it could help
and this is the result I got:

./qjs recursion.js
Total recursion:  297
./qjs-debug recursion.js
Total recursion:  39

I was actually surprised to see that it helped at all. I'd expect the
compiler would be able to optimize that out fairly easily.

That being explained, these are my questions:
- What do you think would be the best way to significantly improve the JS
call limit?
- Can this be done without having to move the JS stack from the C stack
into the heap?
- If you can think of some low hanging fruits, can you give me any pointers
of what would need to be done? I'd be happy to help if needed.

Thanks again for the awesome work!

Cheers,
Simon

Other related posts: