Inlining contexts requires care to ensure that it's possible to
de-optimise them safely and that the semantics are never changed
even when code is changed or when reflection is used.
Things to consider:
* Blocks returning from the method and accessing state.
* Changing code
* debuggers and profilers (stack walking)
* become: and changeClassTo: and #primitiveChangeClassTo:
* other reflection including #instVarAt:put:
At the moment it's possible to de-optimise any context individually
which is very convenient but means that the VM must not need to be
aware of whether a context is native or interpreted except when it's
executing it. The VM will refer to the home context of a block, so
everything it accesses must be in the same locations whether
interpreted or compiled. Being able to de-optimise a context without
needing to worry if there are block contexts is a great simplification
when ensuring Exupery doesn't cause crashes on de-optimisation.
De-optimisation can be triggered by various things like saving the
image, modifying a context, saving it in Seaside. Native contexts
can not be in the image when it's saved as the machine code they
depend on will not be there, and even if it is it's unlikely to
be at the same location. Seaside, and the profiler have opposite
problems for de-optimisation with Seaside we want to de-optimise
the context to make it safe to persist while with the profiler we
want to leave the context optimised so profiling doesn't change
the performance characteristics.
Currently de-optimisation only converts a context from native to
interpreted, it uses the same object to represent both. With inlining
de-optimisation will need to create contexts. Because we don't know
what the contexts are for we should preserve identity including
during de-optimisation. Any context that creates a block is referred
to from the block so it'll need to be created.
Only changing code will require removing inlining. Because of
reflection we can not guarantee that data will stay the same from when
we leave a method until when we return. But the method is decided at
send time, so the type check that replaces the send is good enough to
deal with this case. Inlined code will need to sanity check it's state
on re-entry if it's relying on knowing the class of any variable, this
can not be avoided because the class could have changed via
primitiveChangeClassTo: or become:.
Simple methods that don't create contexts will be fully inlined
into the calling context. A slot will be kept to hold a pointer
to a "proxy" context if it is ever accessed via reflection, this
lets the proxy context be reused if it's ever accessed again. The
proxy context will access state in the parent context.
If a method creates a block a real object will be created. It's
variables and stack will be used, and so will it's PC. This is
so that the block can return out of it via a ^ return into the
surrounding part of the inlined context. While a context object
will be created, the code dealing with it will be inlined, so
the inlined method will manage multiple contexts depending on
where it is.
Always creating the context that creates a block ensures that
it and the block can be de-optimised as de-optimisation will not
change the location of the context's variables. If it was inlined
into another context object then it's variables and stack would
be located at a different location. Also the return code would need
to do something special to return into it.
If a block is inlined for it's entire lifetime then it can be removed
fully. This is the the optimisation's goal because it's probably
inlined an entire loop and if not sends will be converted to simple
jumps inside the inlined method.
If code is changed then initially all effected contexts will be
de-optimised. This involves a full memory scan via allInstances to
find the Exupery contexts. This could be optimised by either
de-activating the inlining by modifying the code or using a
context stack to minimise the amount of memory to scan.
Modifying code to de-activate the inlining is the simpler
optimisation, it just involves writing an unconditional jump over
the type test to jump directly to the unexpected case thus avoiding
the inlined code.
ContextPart>>sender will need to be modified so the sender can
answer it's newest inlined context rather than itself if it's
an inlined context.
De-optimisation will be done in the image without requiring any
It may be worthwhile introducing a "uncommon" trap that lets
compiled code bail out and de-optimise if the assumptions
it's built for become invalid. At the moment native code can
deal with both the expected and unexpected cases. Removing the
need to deal with the unexpected cases should reduce the size
of native code needed.
Exupery mailing list
Thanks very much for these emails, these are useful to those of us
following from the sidelines but I think they will also be useful as a
rudimentary form of documentation as this project progresses.
Exupery mailing list
signature.asc (196 bytes) Download Attachment
|Free forum by Nabble||Edit this page|