Initial type inference impl
This commit is contained in:
+76
-22
@@ -34,16 +34,23 @@ DefineField both become DefineField on the fresh copy the function owns.
|
||||
PARAMETERS -- the rule that keeps this general
|
||||
====================================================================
|
||||
|
||||
A value the run USES but does not itself PRODUCE becomes a parameter, in
|
||||
first-use order. Everything produced inside the run (Object, constants,
|
||||
arithmetic that is wholly inside) moves into the function unchanged.
|
||||
A run is handed anything it does not make for itself. Everything else moves
|
||||
into the function unchanged. There are two kinds of parameter:
|
||||
|
||||
the object being mutated is used-but-not-produced -> it is a parameter.
|
||||
a constant field value (Push1) is produced inside -> it moves in.
|
||||
a field value from the enclosing frame (GetArg p) -> a parameter.
|
||||
FRAME. GetArg, GetLoc, GetVarRef and their numbered forms are frame
|
||||
reads. Each becomes a parameter, and the load stays at the call site.
|
||||
RUN. A nested build inside this run. That run is lifted first, and its
|
||||
call result is passed in. See NESTED BUILDS.
|
||||
|
||||
So a pure literal of constants lifts to a 0-parameter function; a mutation of
|
||||
`a` lifts to a 1-parameter function; `a.x = p + 1` lifts to a 2-parameter one.
|
||||
Everything else -- constants, arithmetic, field reads off a parameter -- is
|
||||
made by the run and moves in.
|
||||
|
||||
Parameters are taken in first-use order. A frame slot appears once however
|
||||
many times it is loaded, so a mutation's repeated loads of its object fold
|
||||
into the one parameter, and the object is always parameter 0.
|
||||
|
||||
So a literal of constants lifts to a 0-parameter function; a mutation of `a`
|
||||
lifts to a 1-parameter function; `a.x = p + 1` lifts to a 2-parameter one.
|
||||
|
||||
|
||||
====================================================================
|
||||
@@ -83,17 +90,32 @@ loads it with Fclosure8 Const(K).
|
||||
DETECTION (one linear walk)
|
||||
====================================================================
|
||||
|
||||
start a run at an Object, or at the first static-key write to an object.
|
||||
extend it while the next instruction is one of:
|
||||
Object (a nested object is its own run)
|
||||
a value computation feeding a write in this run
|
||||
DefineField, imm == Atom(F) (same object)
|
||||
PutField, imm == Atom(F) (same object)
|
||||
a (re)load of the same object
|
||||
A run cannot be matched opcode by opcode, the way a class build can,
|
||||
because <value> is any expression at all. FOLLOW THE OBJECT instead:
|
||||
|
||||
start a run at an Object (creation), or at a load of a slot that a
|
||||
static-key write then writes to (mutation).
|
||||
skip forward to the next instruction that USES the object.
|
||||
carry on only while that use is a static-key write to it.
|
||||
everything passed over on the way is the value computation, and it
|
||||
joins the run.
|
||||
|
||||
A creation and a mutation differ only in how the object carries from one
|
||||
write to the next:
|
||||
|
||||
DefineField hands the object back as a NEW value, so a creation run
|
||||
is one value chain.
|
||||
PutField hands nothing back, so a mutation re-loads the object before
|
||||
every write.
|
||||
|
||||
END the run at any of:
|
||||
the object is READ for anything but a field write -- passed to a call,
|
||||
used in an expression, branched on.
|
||||
a static-key write to a DIFFERENT object.
|
||||
a jump, or an instruction some jump lands on. A run is ONE straight
|
||||
line of code, so both sides of a branch are cut off. Without this
|
||||
an `if` in the middle of a sequence of writes joins the two sides
|
||||
into one run.
|
||||
|
||||
"Same object": a mutation re-loads the object before each write (each
|
||||
GetArg0 / GetLocCheck is a fresh value), so sameness is judged by the load
|
||||
@@ -103,6 +125,27 @@ DETECTION (one linear walk)
|
||||
|
||||
Ignore computed-key writes (DefineArrayEl / PutArrayEl) -- see extensions.
|
||||
|
||||
NESTED BUILDS: a run's own instructions are walked AGAIN, to find the
|
||||
object builds inside it. Every Object in the program ends up in exactly
|
||||
one lifted function, so one function describes one object shape.
|
||||
|
||||
{ x: { y: 1 }, z: { w: 2 } } lifts to THREE functions, not one.
|
||||
|
||||
An inner run is held INSIDE its parent's parameter list, as the Run
|
||||
parameter it becomes. So lifting is depth-first: lift a run's Run
|
||||
parameters, then the run itself, which needs their results.
|
||||
|
||||
The inner walk looks for BUILDS ONLY. A mutation writes its object over
|
||||
and over, so a walk that looked for anything would match the run's own
|
||||
second write as a run of its own:
|
||||
|
||||
a.x = 1 ; a.y = 2 one mutation run, NOT a run with one nested
|
||||
|
||||
ORDER IN THE PASS: demutate runs BEFORE declosure. declosure builds its
|
||||
closure objects out of Object and DefineField Atom(int(k)), which a run
|
||||
cannot tell apart from a user object. Run it after, and every closure gets
|
||||
lifted as if it were an object literal.
|
||||
|
||||
|
||||
====================================================================
|
||||
WORKED EXAMPLE (test), real instructions
|
||||
@@ -176,13 +219,24 @@ new FnById(Nm), name "<mutate>", arg_count 1:
|
||||
FIELD VALUE FORMS -- all handled the same way
|
||||
====================================================================
|
||||
|
||||
constant a.x = 1 Push1 moves into the function
|
||||
argument { x: p } GetArg becomes a parameter
|
||||
computed a.x = p + 1 p is a parameter; the Add moves in
|
||||
field read a.x = b.y b is a parameter; the GetField moves in
|
||||
nested object { x: { y: 1 } } the inner Object is its own run, lifted
|
||||
first; its result is a value the outer
|
||||
run uses -> a parameter of the outer
|
||||
One rule covers them all: a frame read (GetArg, GetLoc, GetVarRef) becomes a
|
||||
parameter and its load stays at the call site; everything else moves in.
|
||||
|
||||
a.x = 1 constant
|
||||
Push1 is produced inside the run, so it moves in.
|
||||
|
||||
{ x: p } argument
|
||||
GetArg is a frame read, so p becomes a parameter.
|
||||
|
||||
a.x = p + 1 computed
|
||||
p is a parameter; the Add is produced inside, so it moves in.
|
||||
|
||||
a.x = b.y field read
|
||||
b is a parameter; the GetField is produced inside, so it moves in.
|
||||
|
||||
{ x: { y: 1 } } nested object
|
||||
The inner Object is a run of its OWN. It is lifted first, and the
|
||||
outer run takes its result as a parameter. See NESTED BUILDS.
|
||||
|
||||
|
||||
====================================================================
|
||||
|
||||
Reference in New Issue
Block a user