207 lines
7.2 KiB
Plaintext
207 lines
7.2 KiB
Plaintext
declass: class form -> prototype form
|
|
|
|
Plain text. Read it next to class_form.txt (the input) and proto_form.txt (the target).
|
|
Every instruction below is copied in the same dump format those files use.
|
|
|
|
The class in function "test" has this cpool:
|
|
|
|
cpool: [ FnById(2), FnById(3), FnById(4) ]
|
|
|
|
Const(0) = FnById(2) = the m1 method
|
|
Const(1) = FnById(3) = the constructor
|
|
Const(2) = FnById(4) = the fields initializer
|
|
|
|
|
|
====================================================================
|
|
STEP 1 in function "test": rewrite how the class is built
|
|
====================================================================
|
|
|
|
DETECT, abstractly:
|
|
|
|
push_const8 #ctor ; define_class @Name
|
|
( fclosure8 #method ; define_method @meth )*
|
|
( fclosure8 #fields ; set_home_object ; put_loc ; drop )?
|
|
|
|
which in the example is:
|
|
|
|
[5] = PushConst8 Const(1) [],
|
|
[6, 7] = DefineClass AtomU8 { atom: file_atom[2], arg: 0 } [5, 4],
|
|
[8] = Fclosure8 Const(0) [],
|
|
[9] = DefineMethod AtomU8 { atom: file_atom[5], arg: 0 } [8, 7],
|
|
[10] = Fclosure8 Const(2) [],
|
|
[11, 12] = SetHomeObject None [10, 9],
|
|
[] = PutLoc1 None [12],
|
|
[] = Drop None [11],
|
|
|
|
REPLACE it with:
|
|
|
|
[6] = Fclosure8 Const(1) [],
|
|
[8] = Fclosure8 Const(0) [],
|
|
[18] = GetField Atom(atom("prototype")) [6],
|
|
[] = PutField Atom(file_atom[5]) [8, 18],
|
|
|
|
INSTRUCTION BY INSTRUCTION:
|
|
|
|
PushConst8 Const(1) + DefineClass [5,4] -> Fclosure8 Const(1), keep dst [6]
|
|
Fclosure8 Const(0) -> unchanged (the method closure)
|
|
DefineMethod atom=A [8,7] -> GetField "prototype" [6]
|
|
then PutField atom=A [8,18]
|
|
Fclosure8 Const(2) -> deleted
|
|
SetHomeObject -> deleted
|
|
PutLoc1 [12] -> deleted
|
|
Drop [11] -> deleted
|
|
|
|
[18] is any fresh value id.
|
|
Repeat the DefineMethod line for each method in the class.
|
|
|
|
REMEMBER for step 2 and 3:
|
|
|
|
constructor function = Const(1) = FnById(3)
|
|
fields function = Const(2) = FnById(4)
|
|
class name = file_atom[2]
|
|
|
|
|
|
====================================================================
|
|
STEP 2 splice the field initialiser into the constructor FnById(3)
|
|
====================================================================
|
|
|
|
The constructor does not set its own fields. quickjs hoisted them into a
|
|
separate initialiser function (FnById(4)) that the constructor reaches through
|
|
the captured <class_fields_init> and runs with a call. Splice that function's
|
|
field writes straight into the constructor, in place of the call, so the
|
|
constructor initialises its fields inline and the separate initialiser is left
|
|
unused.
|
|
|
|
DETECT the call block, abstractly (from the get_var_ref_check of
|
|
<class_fields_init> through the next call_method):
|
|
|
|
get_var_ref_check <class_fields_init> ; dup ; if_false
|
|
; get_loc ; swap ; call_method
|
|
|
|
which in the example is:
|
|
|
|
[2] = GetVarRefCheck VarRef(0) [],
|
|
[3, 4] = Dup None [2],
|
|
[] = IfFalse8 Label { target: 14 } [4],
|
|
[5] = GetLoc0 None [],
|
|
[6, 7] = Swap None [5, 3],
|
|
[8] = CallMethod Npop(0) [7, 6],
|
|
|
|
REPLACE the call block by splicing in the field writes from the initialiser
|
|
FnById(4):
|
|
|
|
[9] = GetLoc0 None [],
|
|
[10] = Push1 None [],
|
|
[11] = DefineField Atom(file_atom[4]) [10, 9],
|
|
|
|
The initialiser FnById(4) in full looks like this:
|
|
|
|
[2] = SpecialObject U8(4) [],
|
|
[] = PutLoc1 None [2],
|
|
[3] = PushThis None [],
|
|
[] = PutLoc0 None [3],
|
|
[] = Goto8 Label { target: 7 } [],
|
|
[4] = GetLoc0 None [],
|
|
[5] = Push1 None [],
|
|
[6] = DefineField Atom(file_atom[4]) [5, 4],
|
|
[] = ReturnUndef None [],
|
|
|
|
HOW THE SPLICE IS BUILT:
|
|
|
|
Take the initialiser FnById(4). Drop these opcodes:
|
|
SpecialObject, PushThis, PutLoc0, PutLoc1, PutLoc2, PutLoc3,
|
|
Goto8, Goto, ReturnUndef
|
|
What is left is the field writes; these are what gets spliced in.
|
|
Renumber their value ids to fresh ids in the constructor: 4,5,6 -> 9,10,11.
|
|
The constructor's own PushThis / PutLoc0 / CheckCtor prologue and its
|
|
final ReturnUndef stay as they were, wrapping the spliced-in writes.
|
|
|
|
|
|
====================================================================
|
|
STEP 3 in the constructor FnById(3): fix its header
|
|
====================================================================
|
|
|
|
DETECT, abstractly:
|
|
|
|
a constructor (name "<null>") that captures <class_fields_init>
|
|
|
|
which in the example is:
|
|
|
|
name: "<null>",
|
|
captures: [ Capture { name: "<class_fields_init>", ... } ],
|
|
|
|
REPLACE with:
|
|
|
|
name: "Counter",
|
|
captures: [],
|
|
|
|
name becomes the class name from step 1.
|
|
Remove the <class_fields_init> capture.
|
|
If other captures remain after it, subtract 1 from every VarRef(n)
|
|
whose n is greater than the removed capture index.
|
|
|
|
|
|
====================================================================
|
|
ELIMINATED (opcodes this pass removes from live code)
|
|
====================================================================
|
|
|
|
Step 1 removes these from the class build:
|
|
|
|
DefineClass replaced by Fclosure8
|
|
DefineMethod replaced by GetField + PutField
|
|
SetHomeObject deleted
|
|
PushConst8 the class-template one, folded into Fclosure8
|
|
Fclosure8 the fields-init one, deleted
|
|
PutLoc1 the SetHomeObject leftover store, deleted
|
|
Drop the SetHomeObject leftover drop, deleted
|
|
|
|
Step 2 removes these from the constructor (the field-init call block):
|
|
|
|
GetVarRefCheck of the <class_fields_init> capture
|
|
Dup
|
|
IfFalse8
|
|
GetLoc0 the one feeding the call
|
|
Swap
|
|
CallMethod
|
|
|
|
After the pass, these three class-only opcodes never appear again anywhere:
|
|
|
|
DefineClass
|
|
DefineMethod
|
|
SetHomeObject
|
|
|
|
The rest (PushConst8, Fclosure8, GetVarRefCheck, Dup, IfFalse8, GetLoc0,
|
|
Swap, CallMethod, PutLoc1, Drop) still occur elsewhere for ordinary reasons.
|
|
Only the specific class-pattern instances listed above are removed.
|
|
|
|
|
|
====================================================================
|
|
UNSUPPORTED (the pass panics)
|
|
====================================================================
|
|
|
|
DefineClass AtomU8 { arg: 1 } derived class (extends)
|
|
|
|
Prototype form would have to flatten the parent's prototype into the
|
|
child's and lower `super`. Neither is implemented, so declass panics
|
|
rather than emit a class that has silently lost its parent.
|
|
|
|
|
|
====================================================================
|
|
NOT TOUCHED (passed through unchanged)
|
|
====================================================================
|
|
|
|
DefineMethodComputed ... computed method name
|
|
DefineClassComputed ... computed class name
|
|
|
|
Known gap: a getter or setter (DefineMethod with a non-zero kind) is
|
|
NOT yet passed through. It is wrongly lowered as an ordinary method,
|
|
to a put_field, giving a data property instead of an accessor. It
|
|
should instead make the whole class pass through untouched.
|
|
|
|
|
|
====================================================================
|
|
LEFT BEHIND (removed by a later pass)
|
|
====================================================================
|
|
|
|
FnById(4), the fields function. Nothing references it anymore.
|