Files
jsrt/docs/declass_algo.txt
T
2026-09-04 13:53:59 +00:00

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.