It should be replaced instead by a real exception handler. This has zero setup cost if it's never hit, which would elimiante the overhead of newlexotic. It would also eliminate the lexotic cache. Returns would instead throw an exception whose handler is located lexically. Along the way, Rakudo's special op for returning should go away.
We need a way to avoid constructing a full exception object, while still being able to pass a payload. This can be done as:
nqp::throwpayloadlex(nqp::const::...RETURN, $retval) # NQP
nqp::throwpayloadlexcaller(nqp::const::...RETURN, $retval) # Rakudo
And the handler will be as follows:
nqp::handlepayload(
$ast,
'RETURN',
nqp::lastexpayload()
)
Meaning we can return
without causing any allocation.
As with normal goto-handlers, there's no exception object available, nor is there a handler still on the stack (and therefore we know resumption cannot happen with this handler type). However, an exception object
Note that the Rakudo use of nqp::throwpayloadlexcaller
means that return
can become a real multi-sub and so be far cheaper. On inlining, this op will
simply turn into nqp::throwpayloadlex
.
A further win is that we can look into optimizing these in spesh in the same
way that we optimize other exception handlers, or at least re-using the same
machinery, such that with enough inlining the return
ends up rewritten into
a set
and a goto
.
Problems:
- Involve custom code outside of MoarVM
- The VM doesn't understand them
- Can't specialize on type constraint, so can't lower assigns
- Way too expensive to guard (should be able to without function calls, especially in the JIT)
- Can't specialize on whence or lack thereof
- The whence mechanism isn't very efficient; it involves taking a closure, and that implies a closure clone in every single hash access, and in the array access slow path.
Ideas:
- New container spec inside of VM
- Configure the attribute holding a value
- Configure the attribute holding a descriptor, which is a "key" we can match in specialization
- Configure the attribute holding a value passed to a first-assignment callback
- Configure descriptor attributes are configured too
- Type constraint
- rw-ness
- Assignment callback
- Assume descriptor is immutable. (In fact, we might have a REPR for it that will both enforce the immutability and be directly known to the VM.)
Really annoying:
- Cause an extra check on every single attribute access
- No way to eliminate that check during specialization, so we get branches all over the place in the JITted code
- If we do the above container plan, it also slows down guards since they have to check it too
Possible fix:
- Only store attributes beyond the "overflow" in the indirected memory
- STable used as a runtime mixin type can never be specialized on, so spesh knows it's only ever going to be accessing attributes in the direct object body
- Slow-path attribute access gets an extra check that the access is not out of the bounds of the allocated object, and falls back to chasing the redirect pointer if so
Downside:
- Pessimizes mixins further
But in the future:
- We can be clever (re-format the object when the GC moves it, and install a version of the STable that spesh can understand)
Also, every object is bigger thanks to mixins needing that extra slot to hang the added memory off. We could in the future store the extra attributes in a weak hash, keyed on the object they belong to. Note, after teaching the VM about object hashes and weak references!