Decompiling States
How can I tell if a file has states?
Search in the ir2.asm
file for .type state
. If you see something like this:
.type state
L28:
.symbol plat-button-move-downward
.symbol #f
.symbol #f
.symbol #f
that's a state, and you can expect the file to have defstate
s.
Virtual vs. Non-Virtual States
A non-virtual state is stored in a global variable with the same name as the state. This is just like a global function. You don't have to do anything special with this - the decompiler will insert a defstate
with the appropriate name and make sure that the name of the symbol and the name stored in the state
itself match.
A virtual state is stored in the method table of a type, similar to a method. When doing a go
, the virtual state will be looked up from the method table of the current process, allowing a child type of process to override the state of its parent. You can tell if a state is virtual by looking for a call to inherit-state
or method-set!
.
Decompiling state handers
Each state has up to 6 handler functions: enter
, post
, exit
, trans
, code
, and event
. In order for the decompiler to recognize these, you must have the top-level function decompile. Do not attempt to decompile these functions until the top-level function passes.
The top-level analysis will find state handlers and name them appropriately. For non-virtual states, they will be named like (<handler-name> <state-name>)
. Like (code teetertotter-launch)
. For virtual states, the name will be (<handler-name> <state-name> <type-name>)
. Use these names in type casts, stack structures, etc. These names will not work unless the top-level has been decompiled.
The type of the state handlers will be set up automatically by the type system, but requires that you get the type of the state itself correct.
Note: inside of find_defstates.cpp
there is an option to enable rename prints that will print out the old name of the function before the rename.
State Types
Each state object must have its type set. The type of a state is:
(state <arg0> <arg1> ... <parent-type>)
The args are the arguments given to the enter/code function. Both enter and code get the same arguments, and some may be unused, but this is ok.
The parent type is the type that the state belongs to. It must be process
or a child of process
. All state handlers are automatically behaviors of this type.
Here are two examples:
(define-extern silostep-rise (state symbol silostep))
will make all the silostep-rise
handlers a behavior of silostep
and will make enter
and code
take a single symbol
argument.
Go
The go
macro changes state. Internally it uses enter-state
. Do not insert casts for go
, it should work automatically.
TODO: there may be issues with the decompiler casting arguments to go
- this is a bit tricky and I couldn't find a test case yet.
Special Cases for Virtual States
There are a few special cases for virtual states.
- The state must be declared in the
deftype
, within the list of methods. - The name of the method must be correct.
- Any
go
should be in a behavior of the appropriate process.
The next three sections explain these in more detail
Declaring a virtual state
Do not use define-extern
to declare a virtual state. Instead, use the method list in the deftype
.
As an example:
lui v1, L30 ;; [ 25] (set! gp-0 L30) [] -> [gp: state ]
ori gp, v1, L30
lw t9, inherit-state(s7)
or a0, gp, r0
lw v1, plat-button(s7)
lwu a1, 104(v1)
jalr ra, t9
sll v0, ra, 0
lw t9, method-set!(s7)
lw a0, sunken-elevator(s7)
addiu a1, r0, 22
or a2, gp, r0
jalr ra, t9
This means
- The state object is
L30
. - The state is for type
sunken-elevator
. - The parent type of
sunken-elevator
should beplat-button
- The method ID is 22.
The correct declaration should go under :methods
. Like a normal method, it only needs to be defined in the parent type that first defines it. So we only have to put it in plat-button
and can leave it out of sunken-elevator
.
(plat-button-pressed () _type_ :state 22)
The first thing is the state name (described more in the next section). The next thing is a list of argument types given to the enter
and code
functions.
The ID should match the ID given to method-set!
and it will be checked just like the normal method IDs.
If you get this wrong, you will get an error message like this:
virtual defstate attempted on something that isn't a state: (the-as state (method-of-type plat-button plat-button-move-upward))
Did you forget to put :state in the method declaration?
which means that the plat-button-move-upward
entry in :methods
in (deftype plat-button
is missing the :state
Name of a virtual state
The decompiler will check that the name in the method is correct. If you get it wrong, there will be an error that tells you the right name.
For example:
Disagreement between state name and type system name. The state is named plat-button-move-upward, but the slot is named dummy-24, defined in type plat-button
This means you should rename dummy-24
in plat-button
to plat-button-move-upward
.
Go to a virtual state
You must be in a behavior in order for go-virtual
to decompile successfully.
The return value of event
problem
There is one place that seems to rely on the return value of event
. As a result, the default is to assume that event
returns an object
. However, there are sometimes event
functions that clearly don't return a value and will refuse to decompile. The recommendation is:
- Try to make all
event
functions return a value. - If it is absolutely not possible, make the function type return
none
, then the defstate should automatically insert a cast.
Unsupported
Calls to find-parent-method
that actually return a state
will have type function
. You must manually cast it. Make sure you get the argument types correct. This same problem exists for finding methods, but it has been very rare. If needed we can add special support in the decompiler/compiler to make this work.
If there is a function with multiple virtual go
s which assume a different type at compile-time (accessing different parts of the type tree), then it is not possible to insert the right kind of cast yet.