f �n� X i : Mode is set by get structure � if deref ( X i ) is a REF cell ( i.e. , unbound variable), then f �n pushed onto binds to a new STR cell pointing to the heap and mode is set to WRITE ; � otherwise, – if it is an STR cell pointing to functor f �n , then register S is set to the heap address following that functor cell’s and mode is set to READ . – If it is not an STR cell or if the functor is not f �n , the program fails. [22]
f �n� X i get structure � deref ( X i ); ≡ addr case STORE[ addr ] of h REF � i : HEAP[ H ] � h STR � H + 1 i ; � f �n ; HEAP[ H + 1 ] � H ); bind ( addr � H + 2; H � WRITE ; mode h STR � a i : if HEAP[ a ] = f �n then begin � a + 1; S � READ mode end � true ; else fail � true ; other : fail endcase ; M 0 machine instruction get structure [23]
unify variable X i : � in READ mode, sets register X i to the contents of the heap at address S ; � in WRITE mode, a new unbound REF cell is pushed on the heap and copied into X i . In both modes, S is then incremented by one. unify value X i : � in READ mode, the value of X i must be unified with the heap term at address S ; � in WRITE mode, a new cell is pushed onto the heap and set to the value of register X i . Again, in either mode, S is incremented. [24]
unify variable X i ≡ case mode of : X i � HEAP[ S ] ; READ � h REF � H i ; WRITE : HEAP[ H ] X i � HEAP[ H ] ; � H + 1; H endcase ; � S + 1; S unify value X i ≡ case mode of : unify ( X i � S ); READ � X i ; WRITE : HEAP[ H ] � H + 1; H endcase ; � S + 1; S M 0 unify machine instructions [25]
Variable Binding bind is performed on two store addresses, at least one of which is that of an unbound REF cell. For now: � it binds the unbound one to the other— i.e. , change the data field of the unbound REF cell to contain the address of the other cell; � if both arguments are unbound REF ’s, the binding direction is chosen arbitrarily. NOTE: bind may also perform an occurs-check test in order to prevent formation of cyclic terms — by failing at that point. [26]
procedure unify ( a 1 � a 2 : address ); push ( a 1 � PDL ); push ( a 2 � PDL ); � false ; fail while ¬ ( empty ( PDL ) ∨ fail ) do begin d 1 � deref ( pop ( PDL )); d 2 � deref ( pop ( PDL )); d 1 ≠ d 2 then if begin h t 1 � v 1 i � STORE[ d 1 ] ; h t 2 � v 2 i � STORE[ d 2 ] ; if ( t 1 = REF ) ∨ ( t 2 = REF ) then bind ( d 1 � d 2 ) else begin f 1 �n 1 � STORE[ v 1 ] ; f 2 �n 2 � STORE[ v 2 ] ; if ( f 1 = f 2 ) ∧ ( n 1 = n 2 ) then i � 1 to n 1 do for begin push ( v 1 + i� PDL ); push ( v 2 + i� PDL ) end � true else fail end end end end unify ; [27]
Language L 1 We now make a distinction between: � atoms (terms whose functor is a predicate); and, � terms (arguments to a predicate). L 0 into L 1 : Extend � Syntax: L 0 but now a program may be a set of similar to first-order atoms each defining at most one fact per predicate name. � Semantics: execution of a query connects to the appropriate def- inition to use for solving a given unification equation, or fails if none exists for the predicate invoked. [28]
I 1 contains all those in I 0 . The set of instructions M 1 , compiled code is stored in a code area ( CODE ), In an array of possibly labeled instructions consisting of an opcode followed by operands. a ( i.e. , The size of an instruction stored at address CODE[ a ] ) is given by the expression instruction size ( a ) . Labels are symbolic entry points into the code area that may be used as operands of instructions for transferring control to the code labeled accordingly. Therefore, there is no need to store a procedure name in the heap as it denotes a key into a compiled instruction sequence. [29]
Control Instructions The standard execution order of instructions is sequen- tial. P Program Register: P keeps the address of the next instruction to execute. Unless failure occurs, most machine instructions are implicitly assumed, to increment P by instruction size ( P ) . Some instructions break sequential execution or connect to some other instruction at the end of a sequence. These instructions are called control instructions as they typically set P in a non-standard way. [30]
M 1 ’s control instructions are: � call p�n ≡ P � @( p�n ); where @( p�n ) is the address in the code area of p�n . If the procedure p�n is not instruction labeled defined, failure occurs and overall execution aborts. � proceed indicates the end of a fact’s instruction sequence. [31]
Argument registers L 1 , unification between fact and query terms amounts In to solving, not one, but many equations, simultaneously. As X 1 in M 0 always contains the (single) term root, M 1 registers X 1 � . . . � X n are systematically allocated in n arguments of an n -ary to contain the roots of the predicate. Then, we speak of argument registers, and we write A i rather than X i when the i -th register contains the i -th argument. Where register X i is not used as an argument register, it is written X i , as usual. (NOTE: this is just notation—the A i ’s and the X i ’s are the same.) p ( Z � h ( Z � W ) � f ( W )) , M 1 allocates regis- e.g. , for atom ters: Z A 1 = h ( A 1 � X 4) A 2 = f ( X 4) A 3 = W � X 4 = [32]
Argument instructions M 1 to handle variable arguments. These are needed in L 0 , instructions correspond to when a variable As in argument is a first or later occurrence, either in a query or a fact. In a query: � the first occurrence of a variable in i -th argument position pushes a new unbound REF cell onto the heap and copies it into that variable’s register as well as argument register A i ; � a later occurrence copies its value into argument register A i . In a fact: � the first occurrence of a variable in i -th argument position sets it to the value of argument register A i ; � a later occurrence unifies it with the value of A i . [33]
The corresponding instructions, respectively: put variable X n� A i ≡ HEAP[ H ] � h REF � H i ; X n � HEAP[ H ] ; A i � HEAP[ H ] ; � H + 1; H put value X n� A i ≡ A i � X n get variable X n� A i ≡ X n � A i get value X n� A i ≡ unify ( X n � A i ) M 1 instructions for variable arguments [34]
put variable X 4 � A 1 ?- p ( Z � % h� 2 � A 2 h put structure % ( Z � set value X 4 % W ) � set variable X 5 % f � 1 � A 3 f put structure % ( W ) set value X 5 % p� 3 ) � % call L 1 query Argument registers for ?- p ( Z � h ( Z � W ) � f ( W )) � [35]
p� 3 : get structure f � 1 � A 1 p ( f % ( X ) � unify variable X 4 % h� 2 � A 2 h % get structure ( Y � unify variable X 5 % X 6) � unify variable X 6 % � A 3 Y ) � get value X 5 % f � 1 � X 6 f % X 6 = get structure ( X 7) � unify variable X 7 % a� 0 � X 7 a % X 7 = get structure � % proceed L 1 fact p ( f ( X ) � h ( Y � f ( a )) � Y ) � Argument registers for [36]
Language L 2 : Flat Resolution L 2 is Prolog without backtracking: � it extends L 1 with procedures which are no longer reduced only to facts but may also have bodies; � a body defines a procedure as a conjunctive sequence of atoms; � there is at most one defining clause per predicate name. [37]
Syntax of L 2 L 2 program is a set of procedure definitions of the An form ‘ a 0 :- a 1 � . . . � a � ’ where n � 0 and the a i ’s are n atoms. As before, when n = 0 , the clause is called a fact and written without the ‘ :- ’ implication symbol. n � 0 , the clause is called a rule . When A rule with exactly one body goal is called a chain (rule). Other rules are called deep rules. L 2 query is a sequence of goals, of the form ‘ ?- g 1 � . . . � g � ’ An k where k � 0 . As in Prolog, the scope of variables is limited to the clause or query in which they appear. [38]
Semantics of L 2 Executing a query ‘ ?- g 1 � . . . � g � ’ in the context of a pro- k gram made up of a set of procedure-defining clauses consists of repeated application of leftmost resolution until the empty query, or failure, is obtained. Leftmost resolution: � unify the goal g 1 with its definition’s head (or failing if none exists); then, � if this succeeds, transform the query replacing g 1 by its definition body, variables in scope bearing the binding side-effects of unification. L 2 either: Therefore, executing a query in � terminates with success; or, � terminates with failure; or, � never terminates. The “result” of an L 2 query whose execution terminates with success is the (dereferenced) binding of its original variables after termination. [39]
Compiling L 2 L 2 clause head, M 1 ’s fact instructions are To compile an sufficient. As a first approximation, compiled code for a query (resp., a clause body) is the concatenation of the compiled code L 1 query. of each goal as an M 2 must take two measures of caution re- However, garding: � continuation of execution of a goal sequence; � avoiding conflicts in the use of argument registers. [40]
L 2 Facts Now proceed must continue execution, after success- fully returning from a call to a fact, back to the instruction in the goal sequence following the call. CP Continuation Point Register: M 2 to save and restore the ad- CP is used by dress of the next instruction to follow up with upon successful return from a call. L 2 ’s facts, M 2 alters M 1 ’s control instructions Thus, for to: p�n ≡ CP � P + instruction size ( P ); call � @( p�n ); P � CP ; proceed ≡ P As before, when the procedure p�n is not defined, exe- cution fails. L 2 facts are translated ex- With this simple adjustment, actly as were L 1 facts. [41]
Rules and queries As first approximation, translate a rule p 0 (. . .) :- p 1 (. . .) � . . . � p n (. . .) � following the pattern: p 0 get arguments of p 1 put arguments of p 1 call . . . p put arguments of n p call n (The case of a query is the particular case of a rule with no head instructions.) � Variables which occur in more than one body goal are called permanent as they have to outlive the procedure call where they first appear. � All other variables in a scope that are not permanent are called temporary . [42]
Problem: Because the same variable registers are used by every body goal, permanent variables run the risk of being overwritten by intervening goals. e.g. , in p ( X � Y ) :- q ( X � Z ) � r ( Z � Y ) � Y � Z are no guarantee can be made that the variables q . still in registers after executing NOTE: To determine whether a variable is permanent or temporary in a rule, the head atom is considered to be X in example above is part of the first body goal ( e.g. , temporary). Solution: Save temporary variables in an environment associated with each activation of the procedure they appear in. [43]
M 2 saves a procedure’s permanent variables and regis- ter CP in a run-time stack, a data area (called STACK ), of procedure activation frames called environments . E Environment Register: E keeps the address of the latest environment on STACK . M 2 ’s STACK is organized as a linked list of frames of the form: E CE ( previous environment ) E + 1 CP ( continuation point ) n ( number of permanent variables ) E + 2 E + 3 Y 1 ( permanent variable 1 ) . . . n + 2 Y n ( permanent variable n ) E + (We write a permanent variable as Y i , and use X i as before for temporary variables.) [44]
An environment is pushed onto STACK upon a (non- fact) procedure entry call, and popped from STACK upon return; i.e. , L 2 rule: p 0 (. . .) :- p 1 (. . .) � . . . � p n (. . .) � M 2 code: is translated in N allocate p 0 get arguments of p 1 put arguments of p 1 call . . . p put arguments of n p call n deallocate � allocate N N perma- create and pushe an environment frame for nent variables onto STACK ; � deallocate discard the environment frame on top of STACK and set execution to continue at continuation point recov- ered from the environment being discarded. [45]
That is, N ≡ newE � E + STACK[ E + 2 ] + 3; allocate � E ; STACK[ newE ] � CP ; STACK[ newE + 1 ] � N ; STACK[ newE + 2 ] � newE ; E � P + instruction size ( P ); P ≡ � STACK[ E + 1 ] ; deallocate ≡ P � STACK[ E ] ; E [46]
p� 2 : allocate 2 p % get variable X 3 � A 1 ( X � % get variable Y 1 � A 2 Y ) :- % � A 1 q ( X � put value X 3 % put variable Y 2 � A 2 Z % q � 2 ) � call % � A 1 r ( Z � put value Y 2 % � A 2 Y put value Y 1 % r � 2 % ) call � % deallocate M 2 machine code for rule p ( X � Y ) :- q ( X � Z ) � r ( Z � Y ) � [47]
Language L 3 : Pure Prolog L 3 Syntax of � L 3 extends the language L 2 to allow disjunctive defi- nitions. � As in L 2 , an L 3 program is a set of procedure defini- tions. � In L 3 , a definition is an ordered sequence of clauses ( i.e. , a sequence of facts or rules) consisting of all and only those whose head atoms share the same predi- cate name — the name of the procedure specified by the definition. � L 3 queries are the same as those of L 2 . [48]
Semantics of L 3 � operates using top-down leftmost resolution, an ap- proximation of SLD resolution. � failure of unification no longer yields irrevocable abor- tion of execution but considers alternative choices by chronological backtracking; i.e. , the latest choice at the moment of failure is reexamined first. [49]
M 3 alters M 2 ’s design so as to save the state of compu- tation at each procedure call offering alternatives. We call such a state a choice point : It contains all relevant information needed for a correct state of computation to be restored to try the next alternative, with all effects of the failed computation undone. M 3 manages choice points as frames in a stack (just like environments). To distinguish the two stacks, we call the environment stack the AND-stack and the choice point stack the OR- stack . [50]
B Backtrack Register: B keeps the address of the latest choice point. � upon failure, computation is resumed from the state recovered from the choice point frame indicated by B ; � if the frame offers no more alternatives, it is popped off the OR-stack by resetting B to its predecessor if one exists; otherwise, computation fails terminally. NOTE: if a definition contains only one clause, there is no need to create a choice point frame, exactly as was the case in M 2 . For definitions with more than one alternative, � a choice point frame is created by the first alternative; � then, it is updated (as far as which alternative to try next) by intermediate (but non ultimate) alternatives; � finally, it is discarded by the last alternative. [51]
Environment protection Problem: L 2 , it is safe for M 2 to deallocate an In (deterministic) environment frame at the end of a rule. M 3 : later failure may force This is no longer true for reconsidering a choice from a computation state in the middle of a rule whose environment has long been deal- located. Example Program: a :- b ( X ) � c ( X ) � b ( X ) :- e ( X ) � c (1) � e ( X ) :- f ( X ) � e ( X ) :- g ( X ) � f (2) � g (1) � Query: a� ?- [52]
� allocate environment for a ; � call b ; � allocate environment for b ; � call e : e ; – create and push choice point for e ; – allocate environment for . . . Environment for a . . b Environment for . � Environment for e � Choice point for e E B [53]
� call f ; � succeed ( X = 2) ; � deallocate environment for e ; � deallocate environment for b ; . . . . . . � Environment for a � Choice point for e E B [54]
a ’s body: Continuing with execution of � call c ; � failure ( X = 2 ≠ 1) ; The choice point indicated by B shows an alternative clause for e , but at this point b ’s environment has been lost. [55]
M 3 must prevent unrecoverable deallocation of environ- ment frames that chronologically precede any existing choice point. IDEA: every choice point must “protect” from deallocation all environment frames existing before its creation. Solution: M 3 uses the same stack for both environments and choice points: a choice point now caps all older environ- ments: � As long as a choice point is active, it forces alloca- tion of further environments on top of it, precluding overwriting of the (even explicitly deallocated) older environments. � Safe resurrection of a deallocated protected environ- ment is automatic when coming back to an alternative from this choice point. � Protection lasts just as long as it is needed: as soon as the choice point disappears, all explicitly deallocated environments may be safely overwritten. [56]
Back to our example: � allocate environment for a ; � call b ; � allocate environment for b ; � call e : e ; – create and push choice point for e ; – allocate environment for . . . Environment for a b Environment for � Choice point for e B � Environment for e E [57]
� call f ; � succeed ( X = 2) ; � deallocate environment for e ; � deallocate environment for b ; . . . � a Environment for E b Deallocated environment for � e Choice point for B [58]
a ’s body: Continuing with execution of � call c ; � failure ( X = 2 ≠ 1) ; M 3 can safely recover the state from the choice Now, e indicated by B , in which the saved environment point for to restore is the one current at the time of this choice b . point’s creation— i.e. , that (still existing) of � backtrack; � discard choice point for e ; Protection is now (safely) ended. e proceeds with: Execution of the last alternative for � B . . . a Environment for b Environment for � Environment for e E [59]
Undoing bindings Binding effects must be undone when reconsidering a choice. M 3 records in a data area called the trail ( TRAIL ) all variables which need to be reset to ‘unbound’ upon backtracking. TR Trail Register: TR keeps the next available address on TRAIL . NOTE: Only conditional bindings need to be trailed. A conditional binding is one affecting a variable existing before creation of the current choice point. HB Heap Backtrack Register: HB keeps the value of H at the time of the latest choice point’s creation. � HEAP[ a ] is conditional iff a � HB ; � STACK[ a ] is conditional iff a � B . [60]
[60]
What’s in a choice point? � The argument registers A 1 , ..., A n , where n is the arity of the procedure offering alternative choices of definitions. � The current environment (value of register E ), to re- cover as a protected environment. � The continuation pointer (value of register CP ), as the current choice will overwrite it. � The latest choice point (value of register B ), where to backtrack in case all alternatives offered by the current choice point fail. � The next clause , to try in this definition in case the currently chosen one fails. This slot is updated at each backtracking to this choice point if more alternatives exist. � The current trail pointer (value of register TR ), which is needed as the boundary where to unwind the trail upon backtracking. � The current top of heap (value of register H ), which is needed to recover (garbage) heap space of all the structures and variables constructed during the failed attempt. [61]
Choice point frame: n ( number of arguments ) B A 1 ( argument register 1) B + 1 � � � n A n ( argument register n ) B + n + 1 CE ( continuation environment ) B + n + 2 CP ( continuation pointer ) B + n + 3 ( previous choice point ) B + B n + 4 BP ( next clause ) B + n + 5 TR ( trail pointer ) B + n + 6 ( heap pointer ) B + H [62]
M 3 must alter M 2 ’s definition of allocate to: NOTE: N ≡ if E � B allocate � E + STACK[ E + 2 ] + 3 then newE � B + STACK[ B ] + 7; else newE � E ; STACK[ newE ] � CP ; STACK[ newE + 1 ] � N ; STACK[ newE + 2 ] � newE ; E � P + instruction size ( P ); P [63]
Choice instructions M 3 use three instruc- Given a multiple-clause definition, tions to deal with, respectively: 1. the first clause; 2. an intermediate (but non ultimate) clause; 3. the last clause. They are, respectively: L 1. try me else allocate a new choice point frame on the stack setting L and the other fields according its next clause field to to the current context, and set B to point to it; L 2. retry me else reset all the necessary information from the current L ; choice point and update its next clause field to 3. trust me reset all the necessary information from the current choice point, then discard it by resetting B to the value of its predecessor slot. [64]
Bactracking M 3 , all M 2 instructions where failure may occur ( i.e. , In some unification instructions and all procedure calls) are altered to end with a test checking whether failure has indeed occurred and, if such is the case, to perform the following operation: � STACK[ B + STACK[ B ] + 4 ] ; backtrack ≡ P as opposed to setting P unconditionally to follow the normal sequence. If there is no more choice point on the stack, this is a terminal failure and execution aborts. [65]
Recapitulation of L 3 compilation � The M 3 code generated for a single-clause definition in L 3 is identical to what is generated for an L 2 program M 2 . on � For a two-clause definition for a procedure p�n , the pattern is: p�n L : try me else code for first clause L : trust me code for second clause [66]
� and for more than two clauses: p�n L 1 : try me else code for first clause L 1 L 2 : retry me else code for second clause . . . L L � 1 : retry me else k k code for penultimate clause L : trust me k code for last clause where each clause is translated as it would be as a L 2 clause for M 2 . single Example, p ( X � a ) � p ( b� X ) � p ( X � Y ) :- p ( X � a ) � p ( b� Y ) � [67]
[67]
p� 2 : try me else L 1 p % get variable X 3 � A 1 ( X � % a� 0 � A 2 a ) % get structure � % proceed L 1 L 2 p : retry me else % b� 0 � A 1 ( b� get structure % get variable X 3 � A 2 X ) % � % proceed L 2 : trust me % p allocate 1 % get variable X 3 � A 1 ( X � % get variable Y 1 � A 2 Y ) :- % � A 1 p ( X � put value X 3 % a� 0 � A 2 a % put structure p� 2 ) � % call b� 0 � A 1 p ( b� % put structure � A 2 Y put value Y 1 % p� 2 % ) call � % deallocate M 3 code for a multiple-clause procedure [68]
Optimizing the Design WAM Principle 1 Heap space is to be used as sparingly as possible, as terms built on the heap turn out to be relatively persistent. WAM Principle 2 Registers must be allocated in such a way as to avoid unnecessary data movement, and minimize code size as well. WAM Principle 3 Particular situations that occur very of- ten, even though correctly handled by general-case in- structions, are to be accommodated by special ones if space and/or time may be saved thanks to their speci- ficity. [69]
Heap representation p ( Z � h ( Z � W ) � f ( W )) is: A better heap representation for h� 2 0 1 REF 1 2 2 REF f � 1 3 4 REF 2 p� 3 5 6 REF 1 7 0 STR 8 3 STR provided that all reference to it from the store or registers h STR � 5 i . is a cell of the form Hence, there is actually no need to allot a systematic STR cell before each functor cell. For this, need only change put structure to: f �n� X i ≡ HEAP[ H ] � f �n ; put structure X i � h STR � H i ; � H + 1; H [70]
Constants, lists, and anonymous variables Constants unify variable X i c� 0 � X i get structure is simplified into one specialized instruction: c unify constant and c� 0 � X i put structure set variable X i is simplified into: c set constant Similarly, put and get instructions can also be sim- plified from those of structures to deal specifically with constants. [71]
We need a new sort of data cell tagged CON , indicating a constant. e.g. , heap representation starting at address 10 for the f ( b� g ( a )) : structure g � 1 8 a 9 CON f � 2 10 b 11 CON 12 STR 8 Heap space for a constant is saved when loading a register with it, or binding a variable to it: it is treated as a literal value. Constant-handling instructions: � put constant c� X i � get constant c� X i � set constant c � unify constant c [72]
c� X i ≡ X i � h CON � c i ; put constant c� X i ≡ get constant � deref ( X i ); addr case STORE[ addr ] of h REF � i : STORE[ addr ] � h CON � c i ; trail ( addr ); � � ); h CON � c i : fail � ( c ≠ c � true ; other : fail endcase ; c � h CON � c i ; set constant ≡ HEAP[ H ] � H + 1; H c ≡ unify constant case mode of � deref ( S ); : addr READ case STORE[ addr ] of h REF � i : STORE[ addr ] � h CON � c i ; trail ( addr ); � � ); h CON � c i : fail � ( c ≠ c � true ; other : fail endcase ; � h CON � c i ; WRITE : HEAP[ H ] � H + 1; H endcase ; [73]
Lists Non-empty list functors need not be represented explicitly on the heap. Use tag LIS to indicate that a cell containss the heap address of the first of a list pair. List-handling instructions: put list X i ≡ X i � h LIS � H i ; get list X i ≡ addr � deref ( X i ); case STORE[ addr ] of h REF � i : HEAP[ H ] � h LIS � H + 1 i ; � H ); bind ( addr � H + 1; H � WRITE ; mode h LIS � a i : S � a ; � READ ; mode � true ; other : fail endcase ; [74]
put list X 5 % ?-X 5 = [ W j set variable X 6 % []] � set constant [] % put variable X 4 � A 1 p ( Z � % put list A 2 % [ Z j set value X 4 % X 5] � set value X 5 % f � 1 � A 3 f % put structure ( W ) set value X 6 % p� 3 ) � call % Specialized code for query ?- p ( Z � [ Z � W ] � f ( W )) � [75]
p� 3 : get structure f � 1 � A 1 p ( f % ( X � unify variable X 4 % get list A 2 % [ Y j unify variable X 5 % X 6] � unify variable X 6 % � A 3 Y ) � get value X 5 % get list X 6 % X 6 = [ j unify variable X 7 % X 7 []] � unify constant [] % f � 1 � X 7 f % X 7 = get structure a ( a ) % unify constant � proceed % p ( f ( X ) � [ Y � f ( a )] � Y ) � Specialized code for fact [76]
Anonymous variables A single-occurrence variable in a non-argument positions needs no register. f ( � � ) they can be all be If many occur in a row as in processed in one swoop. Anonymous variable instructions: � set void n n new unbound REF cells on the heap; push � unify void n n ; in WRITE mode, behave like set void n heap cells starting at in READ mode, skip the next location S . [77]
n i � H to H + n � 1 do set void ≡ for HEAP[ i ] � h REF � i i ; � H + n ; H n ≡ case mode of unify void � S + n ; READ : S i � H to H + n � 1 do WRITE : for HEAP[ i ] � h REF � i i ; � H + n ; H endcase [78]
NOTE: an anonymous head argument is simply ignored; since, get variable X i � A i is clearly vacuous. p� 3 : get structure g � 1 � A 2 p ( � g % ( X ) � unify void 1 % f � 3 � A 3 f get structure % � Y � ) unify void 3 % ( ) � % proceed p ( � g ( X ) � f ( � Y � )) � Instructions for fact [79]
Register allocation Clever register allocation allows peep-hole optimization. e.g. , code for fact conc ([] � L� L ) � is: � 3 : get constant [] � A 1 conc ([] � conc % � A 2 L� get variable X 4 % � A 3 L ) get value X 4 % � % proceed L : use A 2 ! It is silly to use X 4 for variable � � A 2 is a no-op and can be elimi- get variable A 2 nated: � 3 : get constant [] � A 1 conc ([] � conc % � A 3 L� L ) get value A 2 % � % proceed Generally, allocate registers so vacuous operations: get variable X i� A i put value X i � A i may be eliminated. (See [2] for more.) [80]
p� 2 : allocate 2 p % get variable X 3 � A 1 ( X � % get variable Y 1 � A 2 Y ) :- % � A 1 q ( X � put value X 3 % put variable Y 2 � A 2 Z % q � 2 ) � % call � A 1 r ( Z � put value Y 2 % � A 2 Y put value Y 1 % r � 2 % ) call � deallocate % p ( X � Y ) :- q ( X � Z ) � r ( Z � Y ) � Na¨ ıve code for [81]
p� 2 : allocate 2 p % get variable Y 1 � A 2 ( X � Y ) :- % put variable Y 2 � A 2 q ( X � Z % q � 2 ) � call % � A 1 r ( Z � put value Y 2 % � A 2 Y put value Y 1 % r � 2 call % ) � % deallocate p ( X � Y ) :- q ( X � Z ) � r ( Z � Y ) � Better register use for [82]
Last call optimization LCO generalizes tail-recursion optimization as a stack frame recovery process. IDEA: Permanent variables are no longer needed after all the put instructions preceding the last call in the body. � Discard the current environment before the last call in a rule’s body. SIMPLE: Just swap the call , deallocate sequence that always conclude a rule’s instruction sequence ( i.e. , into deallocate , call ). [83]
CAUTION: deallocate is no longer the last instruction; so it must reset CP , rather than P : � STACK[ E + 1 ] ; deallocate ≡ CP � STACK[ E ] ; E � P + instruction size ( P ) P CAUTION: But when call is the last instruction, it must not set CP but P . So we cannot modify call , since it is correct when not last. For last call, use execute p�n : p�n ≡ P � @( p�n ); execute [84]
p� 2 : allocate 2 p % get variable Y 1 � A 2 ( X � Y ) :- % put variable Y 2 � A 2 q ( X � Z % q � 2 ) � % call � A 1 r ( Z � put value Y 2 % � A 2 Y put value Y 1 % % ) deallocate r � 2 � % execute p ( X � Y ) :- q ( X � Z ) � r ( Z � Y ) � with LCO [85]
Chain rules Applying LCO, translating a chain rule of the form p (. . .) :- q (. . .) � gives: p : allocate N p get arguments of q put arguments of deallocate q execute But all variables in a chain rule are necessarily temporary. � With LCO, allocate / deallocate are useless in a chain rule — Eliminate them! i.e. , translate a chain rule of the form p (. . .) :- q (. . .) � as: p : get arguments of p q put arguments of q execute Chain rules need no stack frame at all! [86]
Environment trimming Sharpens LCO: discard a permanent variable as soon as it is no longer needed. � The current environment frame will shrink gradually, until it eventually vanishes altogether by LCO. Rank the PV’s of a rule: the later a PV’s last goal, the lower its offset in the current environment frame. e.g. , in p ( X � Y � Z ) :- q ( U� V � W ) � r ( Y � Z � U ) � s ( U� W ) � t ( X � V ) � all variables are permanent: Variable Last goal Offset X t Y 1 Y r Y 5 Z r Y 6 U s Y 3 V t Y 2 W s Y 4 Now call takes a second argument counting the number of PV’s still needed after the call. [87]
CAUTION: Modify allocate to reflect always a correct stack offset. FACT: the CP field of the environment, STACK[ E + 1 ] , al- ways contains the address of the instruction immediately following the call P � N where N is the desired offset. � allocate no longer needs its argument and envi- ronments no longer need an offset field. E CE ( continuation environment ) E + 1 CP ( continuation point ) E + 2 Y 1 ( permanent variable 1 ) . . . [88]
Alter allocate to retrieve the correct trimmed offset as � 1 ] : CODE[STACK[ E + 1 ] allocate ≡ � B if E � E + CODE[STACK[ E + 1 ] � 1 ] + 2 then newE � B + STACK[ B ] + 7; else newE � E ; STACK[ newE ] � CP ; STACK[ newE + 1 ] � newE ; E � P + instruction size ( P ); P (Similarly for try me else ...) [89]
p� 3 : allocate p % get variable Y 1 � A 1 ( X � % get variable Y 5 � A 2 Y � % get variable Y 6 � A 3 Z ) :- % put variable Y 3 � A 1 q ( U� % put variable Y 2 � A 2 V � % put variable Y 4 � A 3 W % q � 3 � 6 ) � % call put value Y 5 � A 1 r ( Y � % put value Y 6 � A 2 Z � % put value Y 3 � A 3 U % r � 3 � 4 ) � % call put value Y 3 � A 1 s ( U� % put value Y 4 � A 2 W % s� 2 � 2 ) � call % put value Y 1 � A 1 t ( X � % put value Y 2 � A 2 V % % ) deallocate t� 2 � execute % Environment trimming code [90]
Stack variables A PV Y n that first occurs in the body of a rule as a goal argument is initialized with a put variable Y n� A i . This systematically sets both Y n and argument register A i to point to a new cell on HEAP . � Modify put variable to work differently on PV’s so not to allocate a heap cell as for TV’s. i.e. , put variable Y n� A i ≡ � E + n + 1; addr � h REF � addr i ; STACK[ addr ] A i � STACK[ addr ] ; Unfortunately, there are rather insidious conse- quences to this apparently innocuous change as it interferes with ET and LCO. [91]
Trouble PV’s may be discarded (by LCO and ET) while still unbound. � DANGER: risk of dangling references! e.g. , � it is incorrect for bind to choose an arbitrary pointer direction between two unbound variables. � some instructions are now incorrect if used blindly in some situations: put value and set value (thus also unify value in WRITE mode). Treatment � keep a correct binding convention; � analyze what is wrong with put value , set value , and unify value to avert trouble on the fly – i.e. , only when really needed. [92]
Variable binding and memory layout As it turns out, most correct bindings can be ensured following a simple chronological reference rule: WAM Binding Rule 1 Always make the variable of higher address reference that of lower address. In other words, an older (less recently created) vari- able cannot reference a younger (more recently created) variable. Benefit of WAM Binding Rule 1 Three possibilities of variable-variable bindings: (1) heap-heap, (2) stack-stack, (3) heap-stack. [93]
� Case (1): unconditional bindings are favored over conditional ones: � no unnecessary trailing; � swift heap space recovery upon backtracking. � Case (2): same applies, but also works consistently with PV ranking for ET within an environment. Unfortunately, this is not sufficient to prevent all danger of dangling references. � Case (3): references to STACK are unsafe; also need: WAM Binding Rule 2 Heap variables must never be set to a reference into the stack; and follow a specific memory layout convention make this naturally consistent with WAM Binding Rule 1: WAM Binding Rule 3 The stack must be allocated at higher addresses than the heap, in the same global address space. [94]
Unsafe variables Remaining problem WAM Binding Rule 2 can still be violated by put value , set value , and unify value . A PV which is initialized by a put variable ( i.e. , which first occurs as the argument of a body goal) is called unsafe . e.g. , in p ( X ) :- q ( Y � X ) � r ( Y � X ) � X and Y are PV’s, but only Y is unsafe. both p is called with an unbound argument; Assume e.g. , put variable X i � A 1 p� 1 execute [95]
h 0 i p� 1 : allocate p % h 1 i get variable Y 1 � A 1 ( X ) :- % h 2 i put variable Y 2 � A 1 q ( Y � % h 3 i � A 2 X put value Y 1 % h 4 i q � 2 � 2 ) � % call h 5 i � A 1 r ( Y � put value Y 2 % h 6 i � A 2 X put value Y 1 % h 7 i % ) deallocate h 8 i r � 2 � % execute Unsafe code for p ( X ) :- q ( Y � X ) � r ( Y � X ) � [96]
Before Line 0, A 1 points to the heap address (say, 36) of an unbound REF cell at the top of the heap: ( A 1) 36 REF HEAP 36 36 REF [97]
Then, allocate creates an environment on the stack (where, say, Y 1 is at address 77 and Y 2 at address 78 in the stack): ( A 1) REF 36 HEAP 36 36 REF STACK ( Y 1) 77 ( Y 2) 78 [98]
Recommend
More recommend