asyncify

104 views
Skip to first unread message

Soeren Balko

unread,
Jul 27, 2014, 7:30:50 AM7/27/14
to emscripte...@googlegroups.com
Just stumbled across the "asyncify" Wiki page by chance. Does this really do what it promises to do and without major performance degradations? As I understand, it breaks up a synchronous function, which makes a call to emscripten_sleep into two asynchronous functions, where the second one becomes a callback for the underlying setTimeout call. And as one cannot programmatically store and recover the stack in Javascript, the same needs to be be done all the way up in the call stack. For one, I would imagine this to be more costly than unwinding a plain synchronous call stack and secondly (to my knowledge), asm.js doesn't currently support function parameters, or does it? In other words, the asyncify-ed code wasn't asm.js compliant with accompanying performance implications in Firefox.  

But regardless, once this lands one can actually think of truly supporting shared-state multithreading (POSIX threads) by supporting seemingly synchronous worker communication for locking operations etc. This might also need some (compile-time) changes to the memory management approach to be able to "trace" what heap addresses are changed by some thread. 

Fantastic work!

Soeren

王璐

unread,
Jul 27, 2014, 8:29:06 AM7/27/14
to emscripte...@googlegroups.com
Hi,

There are definitely overhead for this transformation, mainly saving and restoring local variables. Usually this kind of overhead is acceptable, as async functions are usually supposed to be slow.

On the other hand, how would you define 'performance degration', as probably we don't have other options.

asm.js does support function pointers, although you need to know the signature, and asyncify is asm.js compliant. In fact asm.js is required for this feature.


regards,
- Lu

Sören Balko

unread,
Jul 27, 2014, 7:44:37 PM7/27/14
to emscripte...@googlegroups.com
Thanks for clarifying this for me - can’t wait to try it out. That’s definitely one of the most exciting new features in Emscripten!

In terms of saving and restoring local variables quickly: to avoid iterating over all variables, can you make them properties of an object at compile time (instead of atomic variables in the function scope)?  I other words, make the function scope explicitly accessible as an object? For example, for two the functions below:

void foo(int a) {
float b;
char * c;
bar(b);
...
}

void bar(float c) {
int d;
emscripten_sleep(1);
}

You could generate two explicit scope objects:

fooScope = {
parentScope: ...
a: ...
b: …
c: …
}

and 

barScope = {
parentScope: …
c: …
d: ...
}

When saving the local variables when calling emscripten_sleep, you merely had to store barScope. If bar was called from foo, its parentScope property was set to fooScope (this had to happen at runtime at a small cost). Restoring the scope is again, a single operation only. In return, the affected functions would need to access their variables as properties of the funcScope object. Not sure if asm.js allows for that. You would probably have to represent the scope structures on the heap and turn accesses to the local variables into heap accesses at some offset of the scope struct location. Also, the funcScope.parentScope property had to be populated when entering a function. So there was a trade-off, not sure how it would pan out. 

Soeren


--
You received this message because you are subscribed to a topic in the Google Groups "emscripten-discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/emscripten-discuss/kSMH2N0CoLg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to emscripten-disc...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



Lu Wang

unread,
Jul 27, 2014, 7:58:23 PM7/27/14
to emscripten-discuss
AFAIK, this is not allowed in asm.js.
Usually I understand asm.js as an almost truly assembly language.

regards,
- Lu

Sören Balko

unread,
Jul 27, 2014, 8:35:20 PM7/27/14
to emscripte...@googlegroups.com
The only think I am not sure about is whether asm.js allows objects as local variables. My take on the specification (http://asmjs.org/spec/latest/) is that it does, in fact, not support this - the asm.js type system is fairly restrictive. However, heap pointers are obviously supported and so is access to the heap or stack at a certain pointer plus offset. Basically, one had to apply the same rule with which a local struct variable is currently mapped. I strongly suspect it becomes a heap or stack pointer. 

I just checked what emscripten does to represent the following function:

static void foo(int a, float b) {
struct {
int a;
float b;
} bar;

bar.a = a;
bar.b = b;

printf("%i %f\n", bar.a, bar.b);
}

It becomes this asm.js Javascript function:

function _foo($a,$b) {
 $a = $a|0;
 $b = +$b;
 var $0 = 0, $1 = 0.0, $2 = 0, $3 = 0.0, $4 = 0, $5 = 0, $6 = 0, $7 = 0.0, $8 = 0.0, $bar = 0, $vararg_buffer = 0, $vararg_ptr1 = 0, label = 0, sp = 0;
 sp = STACKTOP;
 STACKTOP = STACKTOP + 32|0;
 $vararg_buffer = sp;
 $bar = sp + 16|0;
 $0 = $a;
 $1 = $b;
 $2 = $0;
 HEAP32[$bar>>2] = $2;
 $3 = $1;
 $4 = (($bar) + 4|0);
 HEAPF32[$4>>2] = $3;
 $5 = HEAP32[$bar>>2]|0;
 $6 = (($bar) + 4|0);
 $7 = +HEAPF32[$6>>2];
 $8 = $7;
 HEAP32[$vararg_buffer>>2] = $5;
 $vararg_ptr1 = (($vararg_buffer) + 4|0);
 HEAPF64[tempDoublePtr>>3]=$8;HEAP32[$vararg_ptr1>>2]=HEAP32[tempDoublePtr>>2];HEAP32[$vararg_ptr1+4>>2]=HEAP32[tempDoublePtr+4>>2];
 (_printf((8|0),($vararg_buffer|0))|0);
 STACKTOP = sp;return;
}

I have highlighted the interesting lines where the “bar" struct is essentially copied to a location in memory, pointed to by STACKTOP+16 and the function parameters $a and $b are assigned to the struct members (residing at the memory locations pointed to be $bar and $bar+4, respectively). Interestingly, emscripten uses the artificial stack (HEAP32 array at STACKTOP offset) for certain variables (such as structs and the vararg parameters for the printf call), whereas any variable that can be directly represented as a local variable (such as $a and $b) stays a local variable of the Javascript function.  

Coming back to the problem of efficiently saving and restoring local variables, my understanding is that you would need to save 16 local variables ($a, $b, $0 … $8, $bar, $vararg_buffer, $vararg_ptr1, label, sp). Some of these would probably be eliminated by better optimisation settings (like $2, $3, $4), but you may still end up with a whole lot of local variables to be taken care of. If you elevated all of the local variables to struct members like proposed before, you would in principle have fewer work to do for saving and restoring them. However, each access would turn into a HEAP32[(funcScopePtr + variableOffset|0)>>2] operation. I would *guess* that the performance implications are next to nothing (at the least in an AOT asm.js runtime).

Soeren

Soeren Balko, PhD
Founder & Director
zfaas Pty Ltd
Brisbane, QLD
Australia



Soeren Balko

unread,
Jul 27, 2014, 11:32:48 PM7/27/14
to emscripte...@googlegroups.com
Coming to think of this: I think that making the function scopes in call stacks explicit in dedicated objects that are managed in the emscripten HEAP had other advantages: 

For example, by introducing an asynchronous call "emscripten_hibernate_if_requested" similar to "emscripten_sleep" at the entry to every function (or even more frequently if a lot of CPU cycles are spent in a single function), one could relatively easily implement a "hibernate" feature. The "emscripten_hibernate_if_requested" would essentially asynchronously check whether the user (or some external component) has requested the application to hibernate (e.g., into some persistent storage like IndexedDB). If so, "emscripten_hibernate_if_requested" would store the local variable states in the "funcScope" chain, store the ArrayBuffer underlying the HEAPx arrays and other global state (STACKTOP, etc.). Recovering from the hibernated state was then very easy and meant re-instating the stored global and local state and calling the callback of "emscripten_hibernate_if_requested". This was really, really useful in a lot of situations. The only downside I see was that in an extreme case, *all* functions had to be "asyncified". Perhaps this could be relaxed when there is frequently called a main loop (like in graphics-intense applications like games) already, when only the state of the main loop had to be hibernated.  
Soeren

To unsubscribe from this group and all its topics, send an email to emscripten-discuss+unsub...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to a topic in the Google Groups "emscripten-discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/emscripten-discuss/kSMH2N0CoLg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to emscripten-discuss+unsub...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to a topic in the Google Groups "emscripten-discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/emscripten-discuss/kSMH2N0CoLg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to emscripten-discuss+unsub...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Lu Wang

unread,
Jul 27, 2014, 11:37:48 PM7/27/14
to emscripten-discuss
I'm not quite sure about the essential difference between your idea and the current implementation, except for that objects are not supported in asm.js. Can you show an example with a concrete example?


regards,
- Lu


To unsubscribe from this group and all its topics, send an email to emscripten-disc...@googlegroups.com.

Sören Balko

unread,
Jul 27, 2014, 11:40:24 PM7/27/14
to emscripte...@googlegroups.com
This would actually work with the as-is approach as well. The only “downside” (and this is by no means any criticism!!!) is that in my understanding you need to traverse all the different local variables in the function scope as they do not exist as properties of a single object. This has merely performance implications, but doesn’t rule out implementing a hibernate feature like the one below. 

Soeren

Lu Wang

unread,
Jul 27, 2014, 11:45:00 PM7/27/14
to emscripten-discuss
How could you create the scope object with all the necessary local variables, without traversing all of them?

Sören Balko

unread,
Jul 27, 2014, 11:53:36 PM7/27/14
to emscripte...@googlegroups.com
At compile time: there were no reall local variables any more, everything was in the HEAP array, starting at the address represented by STACKTOP. A function scope was no real JS object but merely a continuous range of bytes in the HEAP array.

Am 28 Jul 2014 um 1:44 pm schrieb Lu Wang <coolw...@gmail.com>:

How could you create the scope object with all the necessary local variables, without traversing all of them?

--

Lu Wang

unread,
Jul 27, 2014, 11:55:36 PM7/27/14
to emscripten-discuss
That's not completely correct, some are not stored in the heap/stack, for example, return values of functions called, or phi nodes.

Soeren Balko

unread,
Jul 28, 2014, 8:43:58 PM7/28/14
to emscripte...@googlegroups.com
I am not entirely sure why you would need to save return values, as producing that value and exiting from the function happens concurrently, i.e., there is no room for an asynchronous break-out to emscripten_sleep(...) in between, no? 

But regardless of all that - I think that instead of creating local variables in the resulting Javascript function, you *could* as well place all local variables in a continuous region in the asm.js HEAP array (as it already happens to any variables, which cannot be represented as an asm.js-compliant Javascript variable). Once all local variables are represented in this manner, saving them is as simple as remembering the current value of STACKTOP. The offset of the variables on the stack (HEAP array) is static information that is fixed at compile time (essentially, offsets from the current stack frame), i.e., no extra runtime effort is needed when restoring the local variables. 

But anyhow: that's an optimization only - perhaps saving all the variables by iteratively copying their values somewhere isn't all that costly.

Soeren


On Monday, July 28, 2014 1:55:36 PM UTC+10, 王璐 wrote:
That's not completely correct, some are not stored in the heap/stack, for example, return values of functions called, or phi nodes.
On Sun, Jul 27, 2014 at 8:53 PM, Sören Balko <soe...@zfaas.com> wrote:
At compile time: there were no reall local variables any more, everything was in the HEAP array, starting at the address represented by STACKTOP. A function scope was no real JS object but merely a continuous range of bytes in the HEAP array.

Am 28 Jul 2014 um 1:44 pm schrieb Lu Wang <coolw...@gmail.com>:

How could you create the scope object with all the necessary local variables, without traversing all of them?

--
You received this message because you are subscribed to a topic in the Google Groups "emscripten-discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/emscripten-discuss/kSMH2N0CoLg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to emscripten-discuss+unsub...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to a topic in the Google Groups "emscripten-discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/emscripten-discuss/kSMH2N0CoLg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to emscripten-discuss+unsub...@googlegroups.com.

Lu Wang

unread,
Jul 28, 2014, 9:02:41 PM7/28/14
to emscripten-discuss
Regarding return values: consider f(g()), where would you store the return value of g() ?

If you compile a normal c code with local primitive variables, you would probably see that most local variables are NOT stored in the HEAP. In your previous example you were using structures, but normally local variables are mostly integers or pointers.
E.g.
```
int s = 0;
for(int i = 0; i < 100; ++i) s += i;
```

I remember that putting local variables to HEAP is slower than in registers, also it is costly to iterating all the local variables, and I do want to avoid that if possible, but I don't want it affect the performance of the sync cases.


regards,
- Lu


To unsubscribe from this group and all its topics, send an email to emscripten-disc...@googlegroups.com.

Sören Balko

unread,
Jul 28, 2014, 9:11:00 PM7/28/14
to emscripte...@googlegroups.com
On 29 Jul 2014, at 11:02, Lu Wang <coolw...@gmail.com> wrote:

Regarding return values: consider f(g()), where would you store the return value of g() ?

Not at all - why would I want to store it? My understanding is that somewhere in g, there is a call to emscripten_sleep(). Hence you need to store all local variables of g and f that are declared and initialised before that. The return value of g is only produced after emscripten_sleep() returns - hence no need to store it. Or am I overlooking something. 


If you compile a normal c code with local primitive variables, you would probably see that most local variables are NOT stored in the HEAP. In your previous example you were using structures, but normally local variables are mostly integers or pointers.
E.g.
```
int s = 0;
for(int i = 0; i < 100; ++i) s += i;
```

I remember that putting local variables to HEAP is slower than in registers, also it is costly to iterating all the local variables, and I do want to avoid that if possible, but I don't want it affect the performance of the sync cases.

Fair point - I do not know if elements of a typed array (which is the representation of the HEAP array), are ever cached in registers. Portions of the array reside in a nearline CPU cache, but - admittedly - not necessarily in registers. 

Btw, do you also perform some data flow analysis to limit the number of to-be-saved local variables? I mean, you wouldn’t need to save local variables which were *only* used before the emscripten_sleep() call, but not after it. Same is true for local variables that are only used after emscripten_sleep() returns (like in case of the return value). 

Lu Wang

unread,
Jul 28, 2014, 9:19:31 PM7/28/14
to emscripten-discuss
Sorry I didn't make it clear, suppose that `func` is a function that we want to transform, which contains the following statements

f(g1(), g2(), g3());

Suppose that g1,g2,g3 are all async, and we the the return values of them.

Without the transformation, there's no point to store the return value of g*() into HEAP, but now suppose that g2() is called after g1(), we need to save the value of g1() before g2(), and restore it afterwards.

Also note that g*() could be both sync or async, e.g.

if(rand() % 2) emscripten_sleep();

And we want the sync case to be as fast as before, so we don't want to always save g*() to HEAP.

Does this make sense?



For the optimization, I think so, but not 100% sure about the cases you mentioned. Maybe you can do some experiments.


regards,
- Lu

Sören Balko

unread,
Jul 28, 2014, 9:26:35 PM7/28/14
to emscripte...@googlegroups.com
On 29 Jul 2014, at 11:19, Lu Wang <coolw...@gmail.com> wrote:

Sorry I didn't make it clear, suppose that `func` is a function that we want to transform, which contains the following statements

f(g1(), g2(), g3());

Suppose that g1,g2,g3 are all async, and we the the return values of them.

Without the transformation, there's no point to store the return value of g*() into HEAP, but now suppose that g2() is called after g1(), we need to save the value of g1() before g2(), and restore it afterwards.

Also note that g*() could be both sync or async, e.g.

if(rand() % 2) emscripten_sleep();

And we want the sync case to be as fast as before, so we don't want to always save g*() to HEAP.

Does this make sense?

It does - in case of the f(g1(), g2(), g3()) example. Even though I still do not see why you would need to store the result of rand() in the other example. It is only ever used _before_ you call emscripten_sleep() and never after this. Why would you need to restore it after the asynchronous callback from emscripten_sleep(). I mean if the code would still need it, it had to explicitly assign it to some local variable, no? 
 



For the optimization, I think so, but not 100% sure about the cases you mentioned. Maybe you can do some experiments.

I will, by manually modifying the example I posted yesterday. 

Lu Wang

unread,
Jul 28, 2014, 9:28:33 PM7/28/14
to emscripten-discuss
Yes, the return value of rand() should not be saved, is there anything confusing?
Reply all
Reply to author
Forward
0 new messages