The Glorious New Update Mechanism ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Simon & Jim Dec 93 Return convention ~~~~~~~~~~~~~~~~~ When a constructor returns it makes sure that R2 contains the info pointer for the constructor R1,R3.. contain the components (if return in regs) R1 points to the constructor object itself (if return in heap) The info table for a constructor contains a pointer to the constructor's update code. If a constructor returns to an update frame, the update frame's code just jumps direct to the constructor's update code, via the info pointer in R2. This penalises slightly the return of a new constructor, because we have to load R2 with the info ptr. [Fact: in runs of the compiler, 20-30% of all returns are of a new constructor; 70-80% are existing constructors.] Info tables ~~~~~~~~~~~ Each dynamic-heap-allocated constructor has *two* info tables: * the "NewCon" info table is put into R2 when returning a new constructor, which does not yet exist in the heap; R1 is dead! The "NewCon" info table has no GC entries, because it's only ever used when returning in regs, never installed in a real constructor. The NewCon table also needs a valid tag field (see taggery below) * the "ExistingCon" info table is used for all constructors allocated in the heap. The update code for a constructor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The update code for a constructor actually performs the update right away. [At present, the update is deferred until we get back to the case expression.] It knows how to do the update because the update code is constructor-specific. Once it's done the update, it makes R1 point to the constructor object in the heap (which'll either be freshly-allocated, if big, or the updated thing itself), and (for non-niladic constructors) makes R2 point to the "ExistingCon" info table for the constructor. (Of course the new constructor will also have an ExistingCon info ptr.) For niladic constructors, we do *not* use the "ExistingCon" info table. We continue to overwrite updatees in-place, because this saves us an indirection prior to garbage collection (and the extra niladic constructors disappear during the next garbage collection anyway). The update code in the ExistingCon info table simply updates with an indirection, using R1. I *think* this can be one standard piece of code. The only doubt here concerns GC; if updating with an indirection can cause GC (possible on GRIP? or generational GC?), then we need to know which regs are live. We can solve this by putting a liveness mask in the info table too. [Arguably we want that anyway; consider returning to the bottom of a stack object.] So a liveness mask in the info table is probably a good idea. Constructors which return in heap return with an ExistingCon info ptr. They don't need a NewCon info table at all. Notice that this means that when we return an *existing* constructor, to an update frame, the update is done with an indirection, rather than [as now] copying the constructor afresh. This solves the space duplication problem which shows up in "clausify". GC: R1 might be dead; R2 is a non-ptr. So this return convention relies on using liveness masks for GC reg-liveness info, not the old no-of-live-ptrs info. Taggery ~~~~~~~ [Current: For unvectored returns with more than one constructor, we currently load TagReg, and scrutinise it in the case expression. Worse, we also have to scrutinise TagReg in the update entry of the return vector.] In the new world, updates are handled without any nonsense. No need to look at any register, becase we just jump to the constructor specific update code. Since we have an info ptr in R2, we can get the tag out of the info table, thus getting rid of TagReg altogether. (This could conceivably be a *lose* on a machine with lots of regs, because it replaces a immediate small-const load by a memory fetch of the tag from the info table. Not clear whether this is worth trying to improve. Could a) #define TagReg to be a register or an offset from R2 b) issue a SET_TAG macro in the entry code for a constructor, which usually expands to nothing [NB 75-95% of all returns are vectored in runs of the compiler itself.] The entry code for a constructor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The real reason the registers are assigned as above is to make the entry code for a constructor simple. When the entry code is executed, we have a new entry convention: R1 points to the object R2 is its info pointer (Why? because we usually enter it by indirecting through its info table, so it seems a shame to load the info ptr from memory twice.) So all the entry code has to do is to return (perhaps vectored-ly). (Maybe load TagReg, usually not --- see above.) NB this entry convention applies, of course, to all thunks as well as constructors -- whenever we enter an unknown object via R1 (Node). Case expressions ~~~~~~~~~~~~~~~~ Return vectors no longer need update code. Unvectored returns can therefore be *direct* to the code, rather than *indirect* via a 2-entry vector. Penalty for this improvement: "polymorphic" return vectors, notably that in an update frame, needs to accomodate either a direct or a vectored return. So it has to look like: UpdVec: jmp UnvectoredUpd .word UpdVec0 .word UpdVec1 ... that is, the return vector is offset by some fixed amount from the pointer put on the stack. Or, it could be done backwards: ... .word UpdVec1 .word UpdVec0 UpdVec: ...code for UnvectoredUpd... and then vectored returns would use negative offsets. This grunge is necessary *only* for a fixed set of polymorphic return vectors, part of the runtime system: - update frames - restore cost centres - seq, par - thread base - stack object base Case expressions generate either a direct return, or a vector, but never a combination. Update Frames ~~~~~~~~~~~~~ Standard update frames are still required if we don't know the type of the constructor being returned. However, we often do know the type. In this case, we can generate a type-specific updating return-vector to place in the update frame rather than the StdUpdRetVector. This saves us one level of indirection. Partial applications ~~~~~~~~~~~~~~~~~~~~ PAPs are basically handled just the same way as at present. Changes from now ~~~~~~~~~~~~~~~~ * UpdReg dies. * TagReg dies. * RetVecReg dies. (Previously needed to do return after update.) * Return vectors have half the number of entries. * Unvectored returns go direct. * Polymorphic seq/par and friends. * No space duplication problem (cf clausify) Glosses ~~~~~~~ Tag and update code are needed only for constructor info tables. It seems a shame to take up space in other info tables (ie 99% of them). Possibilities: - use an indirection to GC code, so the vari-sized gc stuff becomes fixed - put the tag/upd code ptrs before the start of the info table. (or between the info table and code when reversing info tables...) Looks tricky to me.