;;; 001-sbcl-vops.txt On the nature of the :from and :to options for VOP operands, and the behavior of :target. Alastair Bridgewater, August 2006 The :from and :to options of the operands and temporaries of a VOP control the lifetimes of the associated TNs, and thus their register packing. The :target option is used to indicate that two TNs should be packed into the same location. Careful use of these options will allow one to reduce the number of registers requred for a VOP to the minimum. Each operand (argument or result), and each temporary defined in a VOP is associated with a TN. A time-spec indicates when a TN is born (the :from option) or dies (the :to option). Two TNs cannot coexist in the same place at the same time, so if you want an argument to be able to pack to the same register as a temporary, you must have the temporary be born at some point after the death of the argument. A time-spec consists of a phase and a sub-phase. The phase is one of the keywords :load, :argument, :eval, :result, or :save (in that order, timewise). The sub-phase is a positive integer used for ordering births and deaths within a phase. A time-spec can either be a list (phase sub-phase), or just the keyword for the phase (which is treated as (phase 0)). An temporary has both :from and :to time-specs, describing its entire lifetime. An argument has only a :to time-spec, and is treated as being born before the VOP starts. A result has only a :from time-spec, and is treated as dying after the VOP ends. The default time-specs for a temporary are :from :load :to :save. This means that the temporary is live for the entire VOP. The default :to time-spec for the Nth argument is (:argument N). The default :from time-spec for the Nth result is (:result N). The :target option tells the system to pack a TN at the same offset within an SB as another TN if their lifetimes don't overlap, no other TN gets there first, and the moon is waxing gibbous (waning gibbous, in SBCL prior to 0.8.9). Fortunately, the only TNs which can conflict are all under our control, being the operands and temporaries for the VOP. An example will hopefully make this all a little clearer. Let's suppose we have a VOP that takes two arguments, called arg0 and arg1, and has a temporary called edx which is set :sc any-reg :offset edx-offset. We would like arg1 to pack in the same location as edx, but adding :target edx to arg1 isn't doing the trick, because the lifetimes aren't set up correctly. The default lifetimes and relevant part of the VOP definition are as follows: :load (:argument 0) (:argument 1) :eval --//-- :save arg0: -------------| arg1: ----------------------------| edx: |-------------------------------------------------| (:args (arg0 :scs (descriptor-reg)) (arg1 :scs (descriptor-reg) :target edx)) (:temporary (:sc descriptor-reg :offset edx-offset) edx) The first thing to do is to change edx to be :from (:argument 1), which means its lifetime no longer overlaps arg1: :load (:argument 0) (:argument 1) :eval --//-- :save arg0: -------------| arg1: ----------------------------| edx: |-----------------------| (:args (arg0 :scs (descriptor-reg)) (arg1 :scs (descriptor-reg) :target edx)) (:temporary (:sc descriptor-reg :offset edx-offset :from (:argument 1)) edx) At this point, however, the compiler will gleefully pack arg0 into the same location as edx despite the :target option. Whoops. To prevent this from happening, we change arg0 to be :to :eval, and we have the following: :load (:argument 0) (:argument 1) :eval --//-- :save arg0: ---------------------------------------| arg1: ----------------------------| edx: |-----------------------| (:args (arg0 :scs (descriptor-reg) :to :eval) (arg1 :scs (descriptor-reg) :target edx)) (:temporary (:sc descriptor-reg :offset edx-offset :from (:argument 1)) edx) At this point, arg1 is reliably packed in the same register as edx, and the VOP will use as few registers as possible. Hopefully this all makes sense now. ;;; EOF