Currently each CallFrame in the CallStack has an Instance to which its state refers. The size_of<Instance> is 8 bytes, so for many recursive calls this is significant memory usage for large call stacks.
The problem with the current design is that it is optimizes for frequent changes of the currently used Instance which is quite rare usually and can only happen when calling imported functions that are hosted by another Wasm Instance. In order to further reduce memory consumption and eventually also boost the performance of call-intense executions we could factor out the Instance as follows:
struct CallStack {
frames: Vec<CallFrame>,
instances: Vec<(usize, Instance)>,
}
struct CallFrame { ... } // no more `instance` field
Where (usize, Instance) in the instances field represents the index: usize at this this Instance got active. Thus the CallStack starts out with the following initial configuration:
Where 0 denotes that at call height 0 the instance i was used.
If a CallFrame gets pushed onto the CallStack that does not change the currently used Instance then nothing gets pushed to instances. If a CallFrame gets pushed onto the CallStack that does change the currently used Instance then instances is pushed with a (index, Instance(x)) pair where index is the current length of frames and x the new Instance to be used from that point onwards.
If a CallFrame is popped from the CallStack we check if the last element of instances shared the index with the popped element and in this case also pop and return the associated Instance, otherwise we do nothing and return None.
Thus the following API for CallStack is implied:
impl CallStack {
// Note: Even if `instance` is `Some` this only pushes a new element to `instances` if `instance` and the last `Instance` in `instances` are not equal.
// Returns `Some(instance)` if `instances` had to push a new `Instance` pair and thus the currently operated-on `Instance` has changed with this new `CallFrame`.
pub fn push(&mut self, frame: CallFrame, instance: Option<Instance>) -> Option<Instance> { ... }
pub fn pop(&mut self) -> (CallFrame, Option<Instance>) { ... }
}
Currently each
CallFrame
in theCallStack
has anInstance
to which its state refers. Thesize_of<Instance>
is 8 bytes, so for many recursive calls this is significant memory usage for large call stacks.The problem with the current design is that it is optimizes for frequent changes of the currently used
Instance
which is quite rare usually and can only happen when calling imported functions that are hosted by another WasmInstance
. In order to further reduce memory consumption and eventually also boost the performance of call-intense executions we could factor out theInstance
as follows:Instead of
We'd have:
Where
(usize, Instance)
in theinstances
field represents theindex: usize
at this thisInstance
got active. Thus theCallStack
starts out with the following initial configuration:Where
0
denotes that at call height 0 the instancei
was used. If aCallFrame
gets pushed onto theCallStack
that does not change the currently usedInstance
then nothing gets pushed toinstances
. If aCallFrame
gets pushed onto theCallStack
that does change the currently usedInstance
theninstances
is pushed with a(index, Instance(x))
pair whereindex
is the current length offrames
andx
the newInstance
to be used from that point onwards.If a
CallFrame
is popped from theCallStack
we check if the last element ofinstances
shared theindex
with the popped element and in this case also pop and return the associatedInstance
, otherwise we do nothing and returnNone
.Thus the following API for
CallStack
is implied: