Bug 1743760 Comment 0 Edit History

Note: The actual edited comment in the bug view page will always show the original commenter’s name and original timestamp.

For the indirect stubs optimization we create indirect stubs for functions stored into a table if we believe the table may be exposed to multiple modules, as in this case a call may need to switch instances.  This check is conservative: only a table created in a module and not exported from the module avoids the creation of indirect stubs.  Tables created from JS or tables exported or imported from a module all trigger stub creation.

It is in principle possible to do better by assuming optimistically that a table that is exported is not going to be imported or updated anywhere else, and then to upgrade the pointers in the table from private to stubbed when that turns out (dynamically) not to be the case.  In the worst case, we get no more stubs than now and perform no more computation, but we can get better call performance for what is dynamically a single-module program, as far as the table is concerned.

Before doing all of the work it would be meaningful to perform a simple corpus analysis to ensure that the optimization will pay off.  Some of the work must be done: we must mark modules as private and then detect when they would be upgraded to public, but this does not involve rejiggering the stubs code.
For the indirect stubs optimization we create indirect stubs for functions stored into a table if we believe the table may be exposed to multiple modules, as in this case a call may need to switch instances.  This check is conservative: only a table created in a module and not exported from the module avoids the creation of indirect stubs.  Tables created from JS or tables exported or imported from a module all trigger stub creation.

It is in principle possible to do better by assuming optimistically that a table that is exported is not going to be imported or updated anywhere else, and then to upgrade the pointers in the table from private to stubbed when that turns out (dynamically) not to be the case.  In the worst case, we get no more stubs than now and perform no more computation, but we can get better call performance for what is dynamically a single-module program, as far as the table is concerned.

Before doing all of the work it would be meaningful to perform a simple corpus analysis to ensure that the optimization will pay off.  Some of the work must be done: we must mark modules as private and then detect when they would be upgraded to public, but this does not involve rejiggering the stubs code.

(Edit: this is a bit more subtle, I'm still thinking through the consequences.)
For the indirect stubs optimization we must create an indirect stub for a function F stored into a table if F is going to be called from another function G that is not in the same instance as F (because the call must switch to F's instance).  If this is not going to be happen the no stub is needed and the same-instance call can proceed at great speed.

Obviously whether F is going to be called from such a G is statically uncomputable, and we don't want to discover this fact at the call point - that would just be another stub to trigger stub creation, and we don't want a stub there if we can avoid it.  So we have heuristics in place to decide on stub creation when F is stored into the table by a passive or active initializer, table.set instruction, or (from JS) Table.set.

The current heuristic gives F a stub in a table in an instance if:
* the table is imported or exported
* F is imported and is stored via a passive or active initializer (ie via Instance::initElems)
* F is stored via wasm `table.set`, `table.fill`, `table.grow`
* F is stored via JS `new Table`, `Table.p.set`, `Table.p.grow` (there's no`Table.p.fill`)

The third bullet should be optimizable, as it would not be necessary to create a stub if the function's instance is the same instance as the table's owning instance and the table is private.

Whether the table is imported or exported is currently a static check.  But going back to earlier princples, a better heuristic for whether a function can avoid having a stub is whether the table is referenced only from a single instance and contains only functions from within that instance.  This is something that can be discovered at run-time during module instantiation (when a table changes state from not-referenced to singly-referenced to multiply-referenced) or during table creation, initialization, and update (when a table changes state from has-no-functions to functions-from-one-instance to functions-from-several-instances).

Once a table enters the multiply-referenced or functions-from-several-instances states, indirect stubs could be created for all its functions.  In the worst case, we get no more stubs than now and perform no more computation, but we can get better call performance for what is dynamically a single-module program, as far as the table is concerned.

Before doing all of the work it would be meaningful to perform a simple corpus analysis to ensure that the optimization will pay off.  Some of the work must be done: we must mark modules as private and then detect when they would be upgraded to public, but this does not involve rejiggering the stubs code.
For the indirect stubs optimization we must create an indirect stub for a function F stored into a table if F is going to be called from another function G that is not in the same instance as F (because the call must switch to F's instance).  If this is not going to be happen the no stub is needed and the same-instance call can proceed at great speed.

Obviously whether F is going to be called from such a G is statically uncomputable, and we don't want to discover this fact at the call point - that would just be another stub to trigger stub creation, and we don't want a stub there if we can avoid it.  So we have heuristics in place to decide on stub creation when F is stored into the table by a passive or active initializer, table.set instruction, or (from JS) Table.set.

The current heuristic gives F a stub in a table in an instance if:
* the table is imported or exported
* F is imported and is stored via a passive or active initializer (ie via Instance::initElems)
* F is stored via wasm `table.set`, `table.fill`, `table.grow`
* F is stored via JS `new Table`, `Table.p.set`, `Table.p.grow` (there's no`Table.p.fill`)

The third bullet should be optimizable, as it would not be necessary to create a stub if the function's instance is the same instance as the table's owning instance and the table is private.

Whether the table is imported or exported is currently a static check.  But going back to earlier princples, a better heuristic for whether a function can avoid having a stub is whether the table is referenced only from a single instance and contains only functions from within that instance.  This is something that can be discovered at run-time during module instantiation (when a table changes state from not-referenced to singly-referenced to multiply-referenced) or during table creation, initialization, and update (when a table changes state from has-no-functions to functions-from-one-instance to functions-from-several-instances).

Once a table enters the multiply-referenced or functions-from-several-instances states, indirect stubs could be created for all its functions.  In the worst case, we get no more stubs than now and perform no more computation, but we can get better call performance for what is dynamically a single-module program, as far as the table is concerned.

Before doing all of the work it would be meaningful to perform a simple corpus analysis to ensure that the optimization will pay off.  Some of the work must be done: we must mark modules as private and then detect when they would be upgraded to public, but this does not involve rejiggering the stubs code.

Finally, if it turns out that this optimization pays off then we should reconsider whether it's worth it for the stub to contain a dynamic same-instance check.  It might not, and as this check slows down cross-instance calls; it only pays for itself if many stubbed calls are dynamically to a same-instance function.
For the indirect stubs optimization we must create an indirect stub for a function F stored into a table if F is going to be called from another function G that is not in the same instance as F (because the call must switch to F's instance).  If this is not going to be happen the no stub is needed and the same-instance call can proceed at great speed.

Obviously whether F is going to be called from such a G is statically uncomputable, and we don't want to discover this fact at the call point - that would just be another stub to trigger stub creation, and we don't want a stub there if we can avoid it.  So we have heuristics in place to decide on stub creation when F is stored into the table by a passive or active initializer, table.set/.grow/.fill/.copy instruction, or (from JS) Table.set/.grow.

The current heuristic gives F a stub in a table in an instance if:
* the table is imported or exported
* F is imported and is stored via a passive or active initializer (ie via Instance::initElems)
* F is stored via wasm `table.set`, `table.fill`, `table.grow`, `table.copy`
* F is stored via JS `new Table`, `Table.p.set`, `Table.p.grow` (there's no`Table.p.fill`)

The third bullet should be optimizable, as it would not be necessary to create a stub if the function's instance is the same instance as the table's owning instance and the table is private.

Whether the table is imported or exported is currently a static check.  But going back to earlier princples, a better heuristic for whether a function can avoid having a stub is whether the table is referenced only from a single instance and contains only functions from within that instance.  This is something that can be discovered at run-time during module instantiation (when a table changes state from not-referenced to singly-referenced to multiply-referenced) or during table creation, initialization, and update (when a table changes state from has-no-functions to functions-from-one-instance to functions-from-several-instances).

Once a table enters the multiply-referenced or functions-from-several-instances states, indirect stubs could be created for all its functions.  In the worst case, we get no more stubs than now and perform no more computation, but we can get better call performance for what is dynamically a single-module program, as far as the table is concerned.

Before doing all of the work it would be meaningful to perform a simple corpus analysis to ensure that the optimization will pay off.  Some of the work must be done: we must mark modules as private and then detect when they would be upgraded to public, but this does not involve rejiggering the stubs code.

Finally, if it turns out that this optimization pays off then we should reconsider whether it's worth it for the stub to contain a dynamic same-instance check.  It might not, and as this check slows down cross-instance calls; it only pays for itself if many stubbed calls are dynamically to a same-instance function.

Back to Bug 1743760 Comment 0