[*] This chapter implements the abstract RTL interface such that an attribute is almost a recognizer for instructions. The ``almost'' comes in because the actual recognizer for an instruction depends on the recognizers for its operands, so what we really have is a function that, given recognizers for operands, produces recognizers for instructins.
This implementation is currently incomplete, but here are the interesting elements.
RtlPattern module.
This module is designed to be easy to compile into recognizers for
RTLs; we'll see if it actually works.
if as applied to
expressions and locations.
To help, I have resurrected the ancient Guard module from an old,
untyped version of the \-RTL translator.
RtlPattern stuff.
If I expect to unify this code with the creation code, keeping the
binding-time stuff here would be a good plan.
<urecrtl.sml>= [D->]
structure PatternGuardExp : GUARD_EXP = struct
structure P = RtlPattern
type exp = P.exp
val true' = P.BOOL true
val false' = P.BOOL false
fun negate (P.BOOL b) = P.BOOL (not b)
| negate (P.APP (("not", []), [e])) = e
| negate (P.APP (("conjoin", []), l)) = P.APP (("disjoin", []), map negate l)
| negate (P.APP (("disjoin", []), l)) = P.APP (("conjoin", []), map negate l)
| negate (P.APP (("eq", ws), es)) = P.APP (("ne", ws), es)
| negate (P.APP (("ne", ws), es)) = P.APP (("eq", ws), es)
| negate (P.APP (("lt", ws), es)) = P.APP (("ge", ws), es)
| negate (P.APP (("ge", ws), es)) = P.APP (("lt", ws), es)
| negate (P.APP (("gt", ws), es)) = P.APP (("le", ws), es)
| negate (P.APP (("le", ws), es)) = P.APP (("gt", ws), es)
| negate e = P.APP (("not", []), [e])
fun isTrue (P.BOOL b) = b
| isTrue (P.APP (("not", []), [e])) = isFalse e
| isTrue (P.APP (("conjoin", []), l)) = List.all isTrue l
| isTrue (P.APP (("disjoin", []), l)) = List.exists isTrue l
| isTrue e = false
and isFalse (P.BOOL b) = not b
| isFalse (P.APP (("not", []), [e])) = isTrue e
| isFalse (P.APP (("conjoin", []), l)) = List.exists isFalse l
| isFalse (P.APP (("disjoin", []), l)) = List.all isFalse l
| isFalse e = false
(**************
fun foldConjunction f (e, answer) =
case e
of P.APP (("conjoin", []), l) => foldl (foldConjunction f) answer l
| e => f (e, answer)
fun combine ((atomic, atomic'), conjunction) =
if existsConjunction (fn e =>
**********)
fun existsConjunction f (P.APP (("conjoin", []), l)) =
List.exists (existsConjunction f) l
| existsConjunction f e = f e
fun filterConjunction conjoin f (P.APP (("conjoin", []), l)) =
foldl conjoin true' (map (filterConjunction conjoin f) l)
| filterConjunction conjoin f c = if f c then c else true'
fun equalityIsContradicted (P.APP (("eq", _), [l , P.INT (n, w)]), q) =
let fun ne n n' = IntInf.compare (n, n') <> EQUAL
fun contradicts (P.APP (("eq", _), [l' , P.INT (n', w')])) =
w = w' andalso ne n n' andalso l = l'
| contradicts _ = false
in existsConjunction contradicts q
end
| equalityIsContradicted _ = false
fun simpleConjoin (p, q) = P.APP (("conjoin", []), [p, q])
(* this could probably all be tidied up very simply using relop stuff *)
fun withConstantEquality p (l, n, w) q =
let fun ne n n' = IntInf.compare (n, n') <> EQUAL
fun eq n n' = IntInf.compare (n, n') = EQUAL
fun contradicts (P.APP (("eq", _), [l' , P.INT (n', w')])) =
w = w' andalso ne n n' andalso l = l'
| contradicts (P.APP (("ne", _), [l' , P.INT (n', w')])) =
w = w' andalso eq n n' andalso l = l'
| contradicts _ = false
fun isImplied (P.APP (("ne", _), [l' , P.INT (n', w')])) =
w = w' andalso l = l' andalso ne n n'
| isImplied _ = false (* could do lt, gt, ge, le, etc, but no need yet *)
in if existsConjunction contradicts q then false'
else simpleConjoin (p, filterConjunction conjoin (not o isImplied) q)
end
and conjoin (p, q) =
if isTrue p then q else if isTrue q then p
else if isFalse p then p else if isFalse q then q
else case p
of P.APP (("eq", _), [l , P.INT (n, w)]) =>
withConstantEquality p (l, n, w) q
| _ => simpleConjoin (p, q)
end
Definescombine,conjoin,equalityIsContradicted,existsConjunction,exp,false',filterConjunction,foldConjunction,isFalse,isTrue,negate,P,PatternGuardExp,simpleConjoin,true',withConstantEquality(links are to index).
I expose more than just the abstract RTL interface: also the guard
module and the recognizer type.
The debug function works around what would otherwise be a circular
dependency with RtlPattern.
<urecrtl.sig>= signature PATTERN_RTL = sig include ABSTRACT_RTL structure G : GUARD where type Exp.exp = RtlPattern.exp (* structure Creation : ABSTRACT_RTL *) <recognizer type> val debug : (recognizer -> PP.pretty) ref end
Definesdebug,G,PATTERN_RTL(links are to index).
Most of the recognizer is actually RtlPattern; here we just keep
track of which kind of recognizer we have at any moment.
With expressions and locations, we have guards (grab explanation from
previous version---try Nov 1 2000).
With effects, we have converted some guards to creation-time
conditions.
<recognizer type>= (<-U U->) datatype recognizer = EFFECT of (RtlPattern.exp * RtlPattern.rtl) list | EXP of RtlPattern.exp G.with_guards | LOC of RtlPattern.loc G.with_guards | TUPLE of recognizer list | RECORD of (string * recognizer) list | VECTOR of recognizer list
Definesrecognizer(links are to index).
The rest of the implementation is straightforward; the only tricky bit
is the attribute type, which enables us to inherit the attributes
of the operands in order to compute the attributes of an instance.
<urecrtl.sml>+= [<-D] (* structure RtlRecognizer : PATTERN_RTL = struct *) signature RECOGNIZER_CREATION_EXP = sig structure R : ABSTRACT_RTL structure E : GUARD_EXP where type exp = R.exp end structure RtlRecognizer (* (Creation : ABSTRACT_RTL) *) : PATTERN_RTL = struct (* structure C = Creation *) structure P = RtlPattern structure G = GuardFun(PatternGuardExp) structure R = RtlOperators.Agg structure VI = VarInfo (* structure Creation = Creation *) <recognizer type> type 'a recog = (string -> recognizer) -> 'a G.with_guards type attribute = (string -> recognizer) -> recognizer type effect = P.effect recog type loc = P.loc recog type exp = P.exp recog type cell = (char * P.exp) recog type operator = P.operator type width = int type agg = width -> width -> cell -> loc exception Failure of string <helpers> <operators> <expressions> <locations> <cells> <effects> <attributes> end
Definesagg,attribute,cell,E,effect,exp,Failure,G,loc,operator,P,R,recog,RECOGNIZER_CREATION_EXP,RtlRecognizer,VI,width(links are to index).
These helper functions are intended to make the monad operations a little more readable.
<helpers>= (<-U) [D->] fun gcase x f = G.map f x fun gcasepair p f = gcase (G.crossPair p) f
Definesgcase,gcasepair(links are to index).
An operator is just a pair of name and widths.
<operators>= (<-U) fun rtlop name ws = (name, ws)
Definesrtlop(links are to index).
The raison d'être of this implementation is to exploit the guard module to handle conditions.
<expressions>= (<-U) [D->]
fun ifExp (c, t, f) rho = G.ifMap (c rho, t rho, f rho)
(*
fun ifExp (c : exp, t : exp , f : exp) rho =
let val (c, t, f) = (c rho, t rho, f rho)
fun onExps (c : P.exp) =
let val (early, late) = split c
in GC.guard (early, GC.single (G.ifMap (G.single late, t, f)))
: P.exp guarded
end
in gmap onExps c
end
*)
(*
fun ifExp (c : exp, t : exp , f : exp) rho =
let val (c, t, f) = (c rho, t rho, f rho)
fun onExps (c : P.exp, t : P.exp, f : P.exp) =
let val (early, late) = split c
in GC.guard (early, GC.single (G.ifMap (G.single late, t, f)))
: P.exp guarded
end
in gmap onExps (gmap triple (gcross [c, t, f])) : exp
end
val lift : 'a G.with_guards -> 'a G.with_guards GC.with_guards =
let fun lift {guard=g, value=x} =
let val (early, late) = split g
in
fun ifExp (c, t, f) rho =
let val (c, t, f) = (c rho, t rho, f rho)
val c = GC.map (G.map split) c (* now condition is split *)
val (early, late) = (GC.map (G.map #1) c, GC.map (G.map #2) c)
val lateIf = GC.map (fn
(* c is condition with_guards with_guards *)
*)
fun t rho = G.single G.Exp.true'
fun f rho = G.single G.Exp.false'
Definesf,ifExp,lift,t(links are to index).
The other stuff requires suitable unit and map operations on the guards monad.
<expressions>+= (<-U) [<-D] fun int w n rho = G.single (P.INT (n, w)) fun fetch w l rho = gcase (l rho) (fn l => P.FETCH(l, w)) fun app (rator, args) rho = gcase (G.crossProduct (applyall rho args)) (fn args => P.APP (rator, args))
Definesapp,fetch,int(links are to index).
Helper applyall deals with the attribute inheritance.
<helpers>+= (<-U) [<-D->] fun applyall x l = List.map (fn f => f x) l
Definesapplyall(links are to index).
There's a problem here with loc; without being able to look up a
cell, I don't know what sizes to pass to the identity aggregation.
<locations>= (<-U)
val ifLoc = ifExp (* polymorphic *)
fun agg a n w cell rho =
G.map (fn (space, addr) => P.UNSLICED (a, n, w, space, addr)) (cell rho)
fun bigE x = agg (R.USER "B") x
fun littleE x = agg (R.USER "L") x
fun loc x = agg R.ID 99 99 x
fun sliceLoc {width=w, argwidth=aw, lsbwidth=lw} {lsb, arg} rho =
gcasepair (lsb rho, arg rho)
(fn (lsb, arg) =>
P.SLICED {width=w, argwidth=aw, lsbwidth=lw, lsb=lsb, arg=arg})
Definesagg,bigE,ifLoc,littleE,loc,sliceLoc(links are to index).
<cells>= (<-U) fun cell (c, e) rho = gcase (e rho) (fn e => (c, e))
Definescell(links are to index).
Again, I exploit the Guard module.
I don't implement sequential composition; this module should be
wrapped in ``sequential to parallel'' if necessary.
<effects>= (<-U) fun guard (g, rtl) rho = G.metaGuard (g rho, rtl rho) fun seq (ef1, ef2) = Impossible.unimp "sequential composition" fun par (ef1, ef2) rho = ef1 rho @ ef2 rho fun store w (l, e) rho = gcasepair (l rho, e rho) (fn (l, e) => P.STORE (l, e, w)) fun kill l rho = gcase (l rho) P.KILL fun skip rho = []
Definesguard,kill,par,seq,skip,store(links are to index).
To compute attributes, we need to split guards. The idea is that we split a guard into
Attribute mapping is straightforward.
<attributes>= (<-U) [D->]
val debug : (recognizer -> PP.pretty) ref =
ref (fn _ => PP.TEXT "attribute debugger not installed")
fun rtl e : (RtlPattern.exp * RtlPattern.rtl) list =
let val conjunctPairs =
foldl (fn ({guard, value}, pairs) => addpairs (guard, pairs)) [] e
fun cvt e = P.RTL (map (fn {guard, value} => (guard, value)) e)
fun onCondition (pairs, results) =
let val e = G.dropFalse (map (fn ({guard=g, value=eff}) =>
{guard=guardGiven pairs g, value=eff}) e)
val g = foldl (fn ({true=t, false=f}, c) => G.Exp.conjoin (t, c))
G.Exp.true' pairs
in if G.Exp.isFalse g then results else (g, cvt e) :: results
end
in allConditions onCondition conjunctPairs
end
and allConditions f pairs =
let fun negate {true=t, false=f} = {true=f, false=t}
fun doWith (decided, [], answer) = f (decided, answer)
| doWith (decided, cond :: undecided, answer) =
doWith (cond :: decided, undecided,
doWith (negate cond :: decided, undecided, answer))
in doWith ([], pairs, [])
end
structure Attribute = struct
fun fromEffect e rho =
let val a = EFFECT (rtl (e rho))
val _ = List.app print ["computed effect = ", PP.flatten (!debug a), "\n"]
in a
end
fun unEffect (EFFECT l) =
let fun unRtl (g, P.RTL l) =
let val effects = map (fn (g, v) => {guard=g, value=v}) l
in G.dropFalse (G.guard (g, effects))
end
in List.concat (map unRtl l)
end
| unEffect _ = Impossible.impossible "non-effect"
fun toEffect a rho = unEffect (a rho)
fun fromExp (e : exp) = (fn rho => EXP (e rho)) : attribute
fun unExp (EXP e) = e
| unExp a = (*Impossible.impossible "non-exp"*)
G.single (P.EXPVAR ("this can't happen (non-exp " ^
PP.flatten (!debug a) ^ ")"))
fun toExp a rho = unExp (a rho)
fun fromLoc (e : loc) = (fn rho => LOC (e rho)) : attribute
fun unLoc (LOC l) = l
| unLoc _ = Impossible.impossible "non-loc"
fun toLoc a rho = unLoc (a rho)
end
DefinesallConditions,Attribute,debug,fromEffect,fromExp,fromLoc,rtl,toEffect,toExp,toLoc,unEffect,unExp,unLoc(links are to index).
<attributes>+= (<-U) [<-D->]
fun tuple l rho = TUPLE (map (fn x => x rho) l)
fun tupleSelect k a rho =
case a rho
of TUPLE l =>
(List.nth (l, k-1) handle Subscript =>
Impossible.impossible ("subscript " ^ Int.toString k ^ " out of bounds " ^
"on tuple of size " ^ Int.toString (length l)))
| a => a (* bogus *)
Definestuple,tupleSelect(links are to index).
<attributes>+= (<-U) [<-D->]
fun record l rho = RECORD (map (fn (n, x) => (n, x rho)) l)
fun recordSelect n a rho =
case a rho
of RECORD l =>
(case List.find (fn (n', x) => n = n') l
of SOME (_, x) => x
| NONE => Impossible.impossible "Record-type mismatch")
| a => a (* bogus *)
Definesrecord,recordSelect(links are to index).
<attributes>+= (<-U) [<-D->] fun vector l rho = VECTOR (map (fn x => x rho) l) fun sub (v, i) rho = Impossible.unimp "vector subscript for recognizers"
Definessub,vector(links are to index).
It's not clear if this is the right lookup for operands, but it will do until something else comes along.
<attributes>+= (<-U) [<-D]
fun var (VI.INT {early}) v rho = EXP (G.single (P.EXPVAR v))
| var (VI.LOC) v rho = LOC (G.single (P.LOCVAR v))
| var (VI.ATT s) v rho = rho s (* ???? *)
Definesvar(links are to index).
Dealing with the binding-time split is a bear. Here's the general idea:
A basic utility just visits every conjunct in a conjunction.
<helpers>+= (<-U) [<-D->]
fun foldConjunction f (e, answer) =
case e
of P.APP (("conjoin", []), l) => foldl (foldConjunction f) answer l
| e => f (e, answer)
DefinesfoldConjunction(links are to index).
A conjunct is interesting only if it can be evaluated early. [Things get worse. If we have an early conjunct we can't express in the source language, we've got big troubles.]
<helpers>+= (<-U) [<-D->] fun early (P.INT _) = true | early (P.BOOL _) = true | early (P.FETCH _) = false | early (P.APP (_, args)) = List.all early args | early (P.EXPVAR _) = true
Definesearly(links are to index).
Here's where we accumulate early conjunct pairs.
<helpers>+= (<-U) [<-D->]
fun cpair e = {true = e, false = G.Exp.negate e}
fun hasExp e = List.exists (fn {true=t, false=f} => t = e orelse f = e)
fun hasTrue e = List.exists (fn {true=t, false=f} => t = e)
fun hasFalse e = List.exists (fn {true=t, false=f} => f = e)
fun addpairs (e, pairs) =
foldConjunction
(fn (e, pairs) =>
if not (early e) orelse hasExp e pairs then pairs else cpair e :: pairs)
(e, pairs)
Definesaddpairs,cpair,hasExp,hasFalse,hasTrue(links are to index).
If we're given a bunch of pairs, what happens to the guard? It's just as we've said above.
<helpers>+= (<-U) [<-D]
fun guardGiven pairs g =
foldConjunction
(fn (e, g) => if hasFalse e pairs then P.BOOL false
else if hasTrue e pairs then g
else G.Exp.conjoin (e, g))
(g, P.BOOL true)
DefinesguardGiven(links are to index).