Description
This issue is to introduce the idea of proposing WASI-dyntype
APIs.
Introduction
The type system of WebAssembly is entirely static in nature, precluding the direct compilation of dynamic programming languages into WebAssembly. Currently, the primary method of supporting dynamic languages involves compiling the language runtime into WebAssembly. This approach involves running virtual machines of individual languages within the WebAssembly environment (VM inside VM), which results in notable performance overhead and significant memory consumption.
Modern dynamic languages tend to incorporate more type annotations (such as TypeScript or type hints in Python). If these type annotations could be leveraged for static compilation, they would contribute to enhancing application performance. However, the aforementioned VM-inside-VM approach falls short of achieving this goal.
The objective of this proposal is to furnish a standardized set of public APIs designed for handling dynamically-typed objects. These APIs would facilitate the management and access of dynamic objects hosted by the external environment, thereby affording opportunities for statically compiling other objects with sufficient type information.
Strategy
- All dynamic objects are represented as an externref, the actual object is managed by external environment.
- Operating on these objects requires invoking the defined APIs
Goal
- Support creating/accessing dynamic objects managed by external environment.
- Support re-using existing built-in objects/methods from an external language engine.
Non-Goal
- Define how to represent dynamic objects
- reason: the implementer can select any acceptable solution or just delegate to an existing language runtime
- Garbage collection mechanism between wasm and external world
- reason: diverse scenarios necessitate varying garbage collection strategies, the implementer should consider how to collect the resources referenced by the externref based on their GC strategy.
- Efficient accessing on dynamic object
- reason: The efficiency of accessing dynamic objects depends on the data layout design, cache strategy and so on which is out of this proposal's scope. The primary benefit of this proposal lies in affording the opportunity to segregate dynamic and static typing handling. This separation empowers code with sufficient type information to fully leverage the performance advantages offered by static compilation techniques.
- Support dynamic function
- reason: our intention is to introduce only the component responsible for object management, the dynamic function necessitates the inclusion of the respective language interpreter, which is beyond the scope of this proposal.
- Operation between dynamic objects
- reason: different languages have different rules for operations, the handling of operations between dynamic types should be left to the discretion of compiler implementers.
API walk-through
object creation and property accessing
let obj : any = {};
obj.f1 = 1;
obj.f2 = 'Hello';
This can be generated as:
obj_ref = new_object();
set_property(obj_ref, "f1", new_number(1));
set_property(obj_ref, "f2", new_string("Hello"));
runtime type checking
let obj : any = 100;
let num : number = obj as number;
This can be generated as:
obj_ref = new_number(100);
if (is_number(obj_ref)) {
num = to_number(obj_ref);
}
subtyping
a instanceof b
This can be generated as:
instance_of(a_ref, b_ref);
exception
let a: any = ...;
throw a;
This can be generated as:
throw(a_ref);
re-use existing built-in objects and methdos
let m: any = new Map();
m.set('a', 1);
This can be generated as:
m_ref = new_object_with_class("Map");
invoke(m_ref, "set", "a", 1);
^ va-args
Detailed design discussion
- Should we support all dynamic languages?
WASI-dyntype
should not bound to a specific language, however, the MVP version should mostly focus on requirement of JavaScript, there may be further extensions to support type system of other dynamic languages. - Will invoking these APIs be faster than compiling language runtime into WebAssembly?
No, if the language is pure dynamic (e.g. JavaScript), the VM inside VM is preferred approach. This proposal doesn't aim to improve the performance of dynamic object accessing, it just provides an escape hatch for pure dynamic types so other objects in the source code with sufficient type information can have the opportunity to be statically compiled to WebAssembly.
Given this code as an example:
class A {
x: number = 1;
}
let num: number = 100;
let a_inst = new A();
let obj : any = {};
This can be generated as such WebAssembly opcodes:
(local $num f64)
(local $a_inst ref $A)
(local $obj ref extern)
(local.set $num (f64.const 100))
(local.set $a_inst (struct.new_fixed $A (f64.const 100)))
(local.set $obj (call create_object))
Accessing dynamic object $a
will be slow because it involves invoking host APIs, but accessing the $num
and $a_inst
would be much faster than the VM-inside-VM
approach.