Add utility for dumping information from AIN files.
For now this is pretty simplistic. The main use is to dump HLL function
prototypes to help with writing stubs.
Future features:
* dump to JSON format
* full AIN dump which could be rebuilt by a complementary tool, e.g.
to enable editing bytecode for testing purposes.
...and add OutputLog.dll stubs.
The calling convention no longer has HLL functions reaching into the
stack. The previous implementation didn't even work since the return
value could overwrite an argument that needed to be freed.
HLL libraries are compiled into the executable rather than dynamically
loaded as in early versions of System 4. These days AliceSoft does the
same thing.
All array instructions supported by the SDK compiler, anyway. There are
a few instructions that were added in later versions, like A_FIND and
A_REVERSE.
Local pages need to be reference counted like any other page. It is
possible to create references to local variables which escape the extent
of their function call, so using a stack doesn't work.
Caching is implemented for small page sizes to avoid invoking
malloc/free on every function call.
Only copy strings when they are mutated. This avoids copying strings in
cases where neither the copy nor the original would be modified during
the lifetime of the copy, e.g. when passing strings by-value.
This is purely an optimization.
Implement CALLMETHOD, SH_STRUCTREF and PUSHSTRUCTPAGE instructions.
Constructors are a bit tricky since SH_LOCALCREATE has to implicitly
call an arbitrary number of functions before it returns (just changing
the instruction pointer only allows for calling a single function).
To get around this, there is now a vm_call() function which is used to
call a function synchronously, before returning from the current
instruction.
Methods are implemented as functions, except that they get a struct page
as local variable -1.
Destructors are still not implemented.
A few things.
The S_REF instruction creates a _copy_ of the referenced string. Proof:
FUNC foo
// string local_string = "foo"
SH_LOCALREF local_string
S_PUSH "foo"
S_ASSIGN
S_POP
// prepare argument for bar()
PUSHLOCALPAGE
PUSH local_string
S_REF
// stealthily alter local_string
SH_LOCALREF local_string
S_PUSH "bar"
S_ASSIGN
S_POP
// bar(local_string)
CALLFUNC bar // local_string is still "foo" in the body of bar()!
Thus, it is not CALLFUNC's responsibility to copy by-value string
arguments. CALLFUNC need only copy the index from the stack into the
local page.
Objects pushed to the stack with SH_LOCALREF, SH_GLOBALREF and REF do
not gain a reference. They are already referenced by the page they
belong to.
I believe the implementation of S_REF is still not quite right. What
I've noticed is that System40.exe is able to distinguish between strings
pushed with S_REF/S_PUSH and strings pushed with
SH_LOCALREF/SH_GLOBALREF/REF. It seems the former are r-values and the
latter are l-values (i.e. the latter can be assigned to, but not the
former), and the VM will throw an error if you try to assign to an
r-value.
The underlying implementation is not clear to me. Both types appear to
be indices into the heap, which suggests that the distinguishing factor
must be stored either in the object itself, or as part of the pointer to
an object. Perhaps its as simple as a flag marking an object as an
r-value?
Building 'test/test.pje' with the SDK compiler produces a file at
'test/Run/test.ain' which can be run with xsystem4 to test various
features.
Not the greatest testing method, but it works.
Random note: The SDK compiler generates some pretty strange code for
'n++' when 'n' is a reference:
instruction state of stack after instruction
----------- --------------------------------
PUSHLOCALPAGE ; P(ref n)
PUSH <variable index of n> ; P(ref n),V(ref n)
REFREF ; P(n),V(n)
DUP2 ; P(n),V(n),P(n),V(n)
REF ; P(n),V(n),n
DUP_X2 ; n,P(n),V(n),n
POP ; n,P(n),V(n)
INC ; n
POP ;
This would seem to be completely equivalent to the following:
PUSHLOCALPAGE
PUSH <variable index of n>
REFREF
INC
Use an array of unions for the stack. I'm still not sure if System40.exe
uses separate stack for string operations or not. Need to check if POP
after S_PUSH removes the pushed string.
A bit of a hack: we insert an extra 'CALLSYS 0x0' instruction
(system.Exit) at the end of the code section, then set up a stack frame
so that when main() returns, it jumps to that instruction.
An assortment of instructions have been implemented so far, mostly basic
stuff such as stack management. The following program can be executed:
int main(void)
{
hello(2);
for (;;) {
system.Peek();
system.Sleep(1);
}
return 0;
}
void hello(int n)
{
int i;
for (i = 0; i < n; i++) {
system.Output("Hello, world! " + string(i));
}
}