SpinWrap - a tool for allowing C or C++ to use Spin Objects
David Betz
Posts: 14,519
SpinWrap is a C program that reads a Spin source file, finds all of the PUB declarations, and writes C or C++ wrappers for allowing the Spin functions to be called from C or C++. The current source is at the URL below.
Revision history (most to least recent):
2014-03-02 Added support for floating point values for CON symbols.
2014-03-02 Switched from dumb internal Spin parser to the OpenSpin -s option. Moved sources to GitHub.
2014-02-27 Added a README.txt file and also made the Makefile use payload for loading Catalina binaries.
2014-02-27 Add a "zip" target to the Makefile and fix an output formatting problem with Catalina.
2014-02-26 Add compile time option "CATALINA" to use "spinnaker" instead of "openspin" for compiling Spin programs.
2014-02-26 Handle Spin objects that have no variables and add a test for using the return value of a Spin function.
2014-02-25 Added reference counting to avoid stopping the COG when instances still exist.
2014-02-25 Rearranged the compiler conditionals and added a "runcatalina" target to the Makefile.
The spinwrap sources are now hosted on GitHub:
https://github.com/dbetz/spinwrap
************ ORIGINAL TOP MESSAGE ************
I'm trying to piece together the information I need to start a Spin binary from C code. Here is a compilation of information that Dave Hein posted to Dr_Acula's thread on Spin overlays:
Thanks!
David
usage: spinwrap [ -c ]
[ -d ]
[ -p <output-path-root> ]
[ -s <stack-size> ]
[ -S,<openspin option> ]... <spin-file>...
Revision history (most to least recent):
2014-03-02 Added support for floating point values for CON symbols.
2014-03-02 Switched from dumb internal Spin parser to the OpenSpin -s option. Moved sources to GitHub.
2014-02-27 Added a README.txt file and also made the Makefile use payload for loading Catalina binaries.
2014-02-27 Add a "zip" target to the Makefile and fix an output formatting problem with Catalina.
2014-02-26 Add compile time option "CATALINA" to use "spinnaker" instead of "openspin" for compiling Spin programs.
2014-02-26 Handle Spin objects that have no variables and add a test for using the return value of a Spin function.
2014-02-25 Added reference counting to avoid stopping the COG when instances still exist.
2014-02-25 Rearranged the compiler conditionals and added a "runcatalina" target to the Makefile.
The spinwrap sources are now hosted on GitHub:
https://github.com/dbetz/spinwrap
************ ORIGINAL TOP MESSAGE ************
I'm trying to piece together the information I need to start a Spin binary from C code. Here is a compilation of information that Dave Hein posted to Dr_Acula's thread on Spin overlays:
Also, from reading Dave's overlay demonstration code it seems that a Spin binary file starts like this:A Spin object can be loaded anywhere in hub RAM and called by properly setting up PBASE, VBASE, DBASE, PCURR and DCURR. The MethodPointer object does this for resident objects by storing the values of the first 4 state variables in a 4-word array, and then setting up DCURR based on the current stack pointer. So it would be fairly easy to implement overlays using this technique. I'm not sure how efficient this would be. It depends on how often you would have to switch overlays.
Two instances of the same object will have the same PBASE and PCURR values, since they are both executing the same code. PBASE is just the absolute starting address of the object, and PCURR is the Spin program counter. Each method within the object uses a value for PCURR that points to the first bytecode in the method.
VBASE is the absolute starting address of the object's VAR variables. This is different for each instance of the object. DBASE is the absolute address of the RESULT variable when calling a method. This is dependent on the location in the stack at the time that a method is called. DCURR is the stack pointer. When a method is called, DCURR is set to the current stack pointer value plus space for the stack frame, local variables and calling parameters.
The stack frame is a 4-word structure that is placed on the stack when calling another method. It contains the values of PBASE, VBASE, DBASE and PCURR just before a method is to be called. The stack frame is used by the RETURN and ABORT bytecodes to restore the Spin state variables. The stack frame also contains two additional bits to indicate whether the return value should be pushed to the stack, and whether an ABORT should stop returning at this point or cause a return to the previous method.
Jumps in Spin are relative. Variables and code positions are all relative to PBASE, VBASE and DBASE. This makes it very easy to relocate Spin objects.
$0000-$0003 CLKFREQ $0004 CLKMODE $0005 ???? $0006-$0007 PBASE (object base address) $0008-$0009 VBASE (address of object's variables) $000a-$000b DBASE (address of the result variable)I assume that when I load a Spin object PBASE, VBASE, and DBASE are set based on the object being loaded at the start of hub memory. Does that mean that I can relocate the object elsewhere by just adding the new base address onto PBASE, VBASE, and DBASE? Or maybe I have to add the new base address minus the size of the Spin binary header containing CLKFREQ, CLKMODE, and ????)? In other words, do something like this:
PBASE += new_base - 6; VBASE += new_base - 6; DBASE += new_base - 6;Is this correct? If so, how do I start this code? RossH's Catalina does this using this function:
int _coginit_Spin(void *code, void *data, void *stack, int start, int offs) {
short params[6];
int cog;
((unsigned long *)stack)[0] = 0xFFF9FFFF;
((unsigned long *)stack)[1] = 0xFFF9FFFF;
params[0] = 0;
params[1] = (short)((long)code);
params[2] = (short)((long)data);
params[3] = (short)((long)stack + 8);
params[4] = (short)((long)code + start);
params[5] = (short)((long)stack + 8 + offs);
// start the Spin interpreter contained in the ROM
cog = _coginit((int)¶ms>>2, 0xF004>>2, ANY_COG);
// small delay to allow cog to start properly before we re-use the RAM!
_waitcnt(_cnt() + (_clockfreq()/20));
return cog;
}
This will, I assume, start the main method. What if I wanted to start some method other than the main method? Also, can someone describe the format of the parameter block passed to coginit? What I'd love to see is a complete description of the Spin binary file format as well as the layout of a Spin binary image. Does that exist anywhere?Thanks!
David

Comments
PUB run(fname) | infile, size, AppAddr, i, cognum, vbase, dbase ' Open the Spin binary file infile := fopen(fname, string("r")) ifnot infile return -3 ' Determine the program size including the VAR section AppAddr := malloc(16) fread(AppAddr, 1, 16, infile) size := word[AppAddr][5] ' size is equal to the value of dbase free(AppAddr) fseek(infile, 0, SEEK_SET) ' Allocate the app memory and read file AppAddr := malloc(size + 800) ifnot AppAddr fclose(infile) return -2 fread(AppAddr, 1, size, infile) fclose(infile) ' Add offset to the data pointers repeat i from 3 to 7 word[AppAddr][i] += AppAddr ' Get VBASE and DBASE vbase := word[AppAddr][4] dbase := word[AppAddr][5] ' Initialize stack frame to return to cogstop code in ROM word[dbase][-4] := 2 ' pbase + abort-trap and return-value flags word[dbase][-3] := 0 ' vbase value doesn't matter word[dbase][-2] := 0 ' dbase value doesn't matter word[dbase][-1] := $fff9 ' Return address to cogstop code in ROM long[dbase]~ ' Result set to zero ' Zero the VAR area longfill(vbase, 0, (dbase - vbase - 8) >> 2) ' Start the Spin cog cognum := cognew($f004, AppAddr + 4) if cognum == -1 free(AppAddr) return -4 return cognumIt should be fairly easy to convert this to C since it uses C-like Spin methods.Byte 5 in the header is the Spin binary file checksum. The sum of the bytes in a Spin binary file should be $14, or if you add in the bytes in $FFF9FFFF twice you get zero.
A Spin cog is started by specifying the address of the Spin interpreter in ROM, which is $F004. The PAR is set to point to the PBASE minus 2. The minus 2 is used because PAR must be set to a long address, and PBASE is on an odd word address. The Spin interpreter adds 2 to PAR before it begins to copy PBASE, VBASE, DBASE, PCURR and DCURR from the header.
You could start some other method instead of the first one. You would have to compute it's address from the method table plus PBASE, and write it in PCURR in the header. If the method uses calling parameters you would have to preload them in the stack, and then adjust DCURR to account for the number of parameters and local variables used by the method.
Thanks for all of your help!
David
As always on this or any forum you have to filter a through certain amount of junk.
The link that Steve posted should provide information about the method table. The method table is at the beginning of each object, and it consist of an array of words that provide the following:
- object size
- number of methods/objects
- starting address and local variable space for each pub
- starting address and local variable space for each pri
- starting address and VAR address for each object
1) Parse a Spin source file to find all of the PUB declarations and create C prototypes for each. I will also keep track of the method index associated with each PUB method.
2) Compile the object with a proxy stub that can be patched to call any of the object's methods by index similar to what Dave Hein does in his overlay example.
3) Create a C header file with a prototype for each of the PUB methods
4) Create C functions that communicate with the proxy stub through a mailbox to invoke the Spin object's methods
5) Use OpenSpin to compile the proxy and the Spin object into a Spin binary
6) Parse the resulting Spin binary to find the method table
I'd also like to be able to support multiple instances of the Spin object. This should be possible by passing the VBASE associated with the instance along with the method parameters. These instances could even be dynamically allocated by the C code.
Another question is whether to use C or C++. If I use C++ then I can make the C calls to the Spin methods look pretty much the same as Spin calls would look. The downside is that this program wouldn't work with Catalina. I may try to support both options based on a command line option.
For example:
{ In myobj.spin } PUB myfunction(arg1, arg2, arg3) ...Could produce: I'll also create C/C++ code to load the compiled Spin object into a COG and start it.Just to be clear (I hope!), here is what I'd like to do:
The C stubs that are linked with the C program use a mailbox to communicate with a proxy for the Spin object. The mailbox has the following fields:
- method index
- object address
- argument count
- arrray of arguments
The proxy is supposed to call the method at the specified index with the specified arguments. This means that the proxy needs to setup the stack with the arguments and then execute the appropriate bytecode sequence to call the specified method. Without understanding at least some of the bytecodes I can't do this.
Spin2cpp is looking better all the time for this. It wouldn't waste any COG for a dispatcher for example. The main COG would be used for both the C code and the Spin equivalent.
The spin2cpp solution is probably better but it may involve peppering the Spin code with spin2cpp declarations to identify variables shared between COGs. This isn't necessary if you lelt the Spin compiler and Spin VM run the code. Anyway, until I dig up some documentation on the Spin bytecodes, I can't easily make any more progress.
Looking for the spin opcodes, is this the sort of thing you need? http://propeller.wikispaces.com/Spin+Byte+Code
I'm sure I've seen more documentation similar to this somewhere.
This was sort of where we were up to with the Overlay thread. We had typical objects split into the pasm and spin part. The next bit was loading in pure spin objects, running them and then discarding them. The technique ought to be similar for C.
I'm still trying to think through how it would work. Calling one method is pretty easy - call a method to add two numbers for instance and return a value. Where it might get complicated is a spin object with multiple methods. You might pass one value which is a pointer to an array which contains the PUB in the object to run and the data to pass. But it would get complicated coding because that object is a precompiled binary. Sort of like the mystery of DLL's. Coding it would be a bit complicated because you would need the object source open in another tab. Possible in the proptool but would require coffee and sharp thinking. More complicated if coding C and Spin at the same time.
It could be very handy though. Say there was a routine of string functions all collected up into one object. How handy and cool would it be to be able to call that same object from both C and from Spin, then discard it, and load in a floating point object etc etc?!
I'm wondering if an answer to this might be forming in the open source spin IDE thread, especially if there is an autocomplete function. Code in Spin or C. Add "MyObject". Then type "MyObject." and there is an autocomplete with all the methods. Select the correct one. Then send it to a compiler to sort out which method number you called without having to think about that, including precompiling self contained binary objects and sending them to an SD card. All entirely possible with a single F10, and it opens up ways of coding huge programs with the best features of C and Spin.
I'll ponder this some more
"Call Obj Sub 2 4" means call method 4 in object 2. Object 2 refers to an entry in the local method table. I forget if the "2" is an absolute long index into the table or whether it must be added to the number of local methods to get the table index. The "4" is used as an index into the called object's method table that contains the relative starting address of the method and the local variable space.
The space used by the calling parameters is not described in the method table, and is implicit in the definition of the method. The calling method must put the correct number of parameters on the stack before calling the method.
If I were you, I would just use the remote method call technique that is in the Spinix files I posted. This technique doesn't mess around with method tables. You can write a utility to generate the stub and kernel source files, which are compiled with the calling and called programs. For C, the utility would also generate a header with function prototypes for all of the PUB methods. Each entry would look like "int32_t method_name(int32_t arg1, ...)". You may also want to include the constants defined in the Spin code in the header file, but this could get messy if they reference other objects.
EDIT: The Spinix code uses a lock so that the kernel can be called from multiple cogs. I think for the purpose of calling Spin methods from C you will only be calling the code from one cog, so you don't need to use a lock. However, if you want to allow pthreads to call the Spin code you would need to use a lock.
You may want to prefix the Spin names with an object tag, such as "fds_" or "FullDuplexSerial_" to ensure that the stub doesn't contain conflicting names, such as start or stop from multiple objects. I'm not sure how to handle multiple instances of an object. Maybe you would need to pass an object index, or maybe there's a C++ way to do it.
There is no formal document that describes the Spin bytecodes. The Spin interpreter source written by Chip does contain a lot of comments that describe it. One problem is that formal mnemonics where never defined for the Spin bytecodes. It's a bit awkward to talk about a bytecode when all you have is the value, such as $36, or the text from BST "Constant 2 $00000001". I attempted to define a set of mnemonics, that are defined in the man entry for spasm, which is the Spinix Spin bytecode assembler. So I defined the bytecode $36 as ldli1, which stands for "LoaD Long Immediate 1". The spasm document is attached below.
You could generate nested IF/ELSE statement to perform a binary search on the index. Only 5 tests would be needed for 32 methods.
clkfreq: 12000000 clkmode: 00 chksum: b2 pbase: 0010 vbase: 0750 dbase: 0bf4 pcurr: 0020 dcurr: 0c14 Object 0010 next: 0084 methodcnt: 2 objectcnt: 1 Method 0 0014 code: 0010 locals: 28So, is PCURR always the offset of the first object plus the offset of the code for the first method within that object?Just trying to understand what I'm seeing when I dump a binary file.
Thanks,
David
clkfreq: 12000000 clkmode: 00 chksum: b2 pbase: 0010 vbase: 0750 dbase: 0bf4 pcurr: 0020 dcurr: 0c14 Object 0010 next: 0084 methodcnt: 2 objectcnt: 1 Method 0 0014 code: 0010 locals: 28I'm trying to figure out how the value of dcurr is determined in the file header. If I subtract dbase from dcurr I get 32 but it looks like the first method of the first object only requires 32 bytes of local variables. Where does the extra 4 bytes come from? Is that for the result?The generated C++ header file:
#include <stdint.h> class TV_Text { public: TV_Text(); ~TV_Text(); uint32_t start(uint32_t basepin); uint32_t stop(); uint32_t str(uint32_t stringptr); uint32_t dec(uint32_t value); uint32_t hex(uint32_t value, uint32_t digits); uint32_t bin(uint32_t value, uint32_t digits); uint32_t out(uint32_t c); uint32_t setcolors(uint32_t colorptr); private: uint8_t m_variables[1188]; static uint32_t m_stack[64]; static volatile uint32_t *m_mailbox; static int m_cogid; };The generated C++ source file:
#include <propeller.h> #include <string.h> #include "TV_Text.h" #define SPINVM 0xf004 volatile uint32_t *TV_Text::m_mailbox = 0; int TV_Text::m_cogid = -1; uint8_t spinBinary[] = { 0x00, 0xb2, 0x10, 0x00, 0x50, 0x07, 0xf4, 0x0b, 0x20, 0x00, 0x14, 0x0c, 0x84, 0x00, 0x02, 0x01, 0x10, 0x00, 0x1c, 0x00, 0x84, 0x00, 0x00, 0x00, 0x78, 0x56, 0x34, 0x12, 0xc4, 0x0c, 0xc0, 0x66, 0x80, 0x35, 0xfc, 0x0a, 0x02, 0x04, 0x75, 0x6b, 0x64, 0x38, 0x06, 0x1e, 0x38, 0x7b, 0x6c, 0x35, 0x0d, 0x1c, 0x36, 0x0d, 0x20, 0x37, 0x00, 0x0d, 0x22, 0x37, 0x21, 0x0d, 0x25, 0x37, 0x01, 0x0d, 0x28, 0x38, 0x05, 0x0d, 0x2c, 0x38, 0x06, 0x0d, 0x30, 0x37, 0x22, 0x0d, 0x33, 0x0c, 0x00, 0x70, 0x06, 0x02, 0x01, 0x61, 0x0c, 0x00, 0x06, 0x02, 0x02, 0x61, 0x0c, 0x00, 0x70, 0x06, 0x02, 0x03, 0x61, 0x0c, 0x00, 0x70, 0x06, 0x02, 0x04, 0x61, 0x0c, 0x00, 0x70, 0x74, 0x06, 0x02, 0x05, 0x61, 0x0c, 0x00, 0x70, 0x74, 0x06, 0x02, 0x06, 0x61, 0x0c, 0x00, 0x70, 0x06, 0x02, 0x07, 0x61, 0x0c, 0x00, 0x70, 0x06, 0x02, 0x08, 0x61, 0x0c, 0x60, 0x64, 0x35, 0xd1, 0x04, 0xff, 0x8e, 0x32, 0x00, 0x44, 0x02, 0x0b, 0x01, 0x78, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0xbd, 0x00, 0x04, 0x00, 0xfb, 0x00, 0x00, 0x00, 0x23, 0x01, 0x00, 0x00, 0x3a, 0x01, 0x08, 0x00, 0xb6, 0x01, 0x0c, 0x00, 0xf9, 0x01, 0x00, 0x00, 0x1f, 0x02, 0x04, 0x00, 0x44, 0x02, 0x98, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0a, 0x07, 0xbb, 0x9e, 0x9b, 0x04, 0x07, 0x3d, 0x3b, 0x6b, 0x6e, 0xbb, 0xce, 0x3c, 0x0a, 0x01, 0x87, 0x68, 0x05, 0x08, 0x01, 0x35, 0x05, 0x07, 0xcb, 0x50, 0xc7, 0x30, 0x38, 0x0e, 0x1e, 0x64, 0x38, 0x38, 0xe8, 0x36, 0xe3, 0x64, 0x37, 0x01, 0xe8, 0x37, 0x01, 0xfc, 0x38, 0x05, 0xe8, 0xea, 0xc9, 0x58, 0xab, 0x80, 0x88, 0xc9, 0x60, 0x53, 0xc9, 0x64, 0x00, 0xcb, 0x50, 0x06, 0x0b, 0x01, 0x61, 0x32, 0x01, 0x06, 0x0b, 0x02, 0x32, 0x64, 0x16, 0x08, 0x08, 0x01, 0x66, 0xae, 0x80, 0x05, 0x07, 0x09, 0x78, 0x32, 0x64, 0x35, 0xf9, 0x0a, 0x07, 0x66, 0x46, 0x01, 0x38, 0x2d, 0x05, 0x07, 0x3b, 0x3b, 0x9a, 0xca, 0x00, 0x69, 0x38, 0x0a, 0x08, 0x27, 0x64, 0x68, 0xfe, 0x0a, 0x10, 0x01, 0x64, 0x68, 0xf6, 0x38, 0x30, 0xec, 0x05, 0x07, 0x68, 0x66, 0x57, 0x62, 0x1c, 0x04, 0x0c, 0x60, 0x68, 0x36, 0xfc, 0xf2, 0x0a, 0x05, 0x01, 0x38, 0x30, 0x05, 0x07, 0x38, 0x0a, 0x6a, 0x56, 0x09, 0x59, 0x32, 0x37, 0x02, 0x68, 0xed, 0x37, 0x00, 0xe3, 0x66, 0x43, 0x68, 0x08, 0x1b, 0x01, 0x35, 0x39, 0x01, 0x1e, 0x37, 0x01, 0x66, 0xc1, 0x37, 0x23, 0xe8, 0x38, 0x30, 0x38, 0x39, 0x12, 0x38, 0x41, 0x38, 0x46, 0x12, 0x0f, 0x05, 0x07, 0x09, 0x65, 0x32, 0x37, 0x04, 0x68, 0xed, 0x66, 0x43, 0x68, 0x08, 0x0d, 0x01, 0x36, 0x66, 0xc1, 0x36, 0xe8, 0x38, 0x30, 0xec, 0x05, 0x07, 0x09, 0x73, 0x32, 0x39, 0x01, 0xb3, 0x4c, 0x35, 0x0d, 0x10, 0x38, 0x0a, 0x0d, 0x80, 0x5b, 0x38, 0x0b, 0x0d, 0x80, 0x5c, 0x38, 0x0c, 0x0d, 0x80, 0x5d, 0x0c, 0x39, 0x01, 0xa0, 0x64, 0x35, 0x0d, 0x1a, 0x36, 0x0d, 0x26, 0x37, 0x02, 0x0d, 0x27, 0x38, 0x09, 0x0d, 0x29, 0x38, 0x0a, 0x38, 0x0c, 0x0e, 0x2f, 0x38, 0x0d, 0x0d, 0x2f, 0x01, 0x64, 0x05, 0x09, 0x0c, 0xab, 0x80, 0x88, 0x39, 0x02, 0x20, 0x39, 0x02, 0x08, 0x19, 0x35, 0x46, 0x80, 0x41, 0x0c, 0x35, 0x46, 0x80, 0x41, 0x0c, 0x40, 0x0a, 0x02, 0x42, 0x3e, 0x0c, 0x01, 0x37, 0x04, 0x05, 0x09, 0x40, 0x37, 0x22, 0xe8, 0x0b, 0x75, 0x0c, 0x64, 0x4d, 0x32, 0x0c, 0x01, 0x05, 0x0a, 0x0c, 0x0c, 0x64, 0x38, 0x28, 0xf7, 0x41, 0x0c, 0x64, 0x38, 0x0d, 0xf7, 0x45, 0x0c, 0x64, 0x37, 0x22, 0xe8, 0x49, 0x0c, 0x35, 0x4d, 0x32, 0x35, 0x69, 0x64, 0x68, 0x36, 0xe3, 0x90, 0x6d, 0x64, 0x68, 0x36, 0xe3, 0x36, 0xec, 0x90, 0x71, 0x6c, 0x38, 0x18, 0xe3, 0x70, 0x37, 0x03, 0xe3, 0xec, 0x6c, 0x37, 0x02, 0xe3, 0xec, 0x70, 0xec, 0x68, 0x36, 0xe3, 0xd9, 0x10, 0x6c, 0x38, 0x18, 0xe3, 0x6c, 0x37, 0x03, 0xe3, 0xec, 0x70, 0x37, 0x02, 0xe3, 0xec, 0x70, 0xec, 0x68, 0x36, 0xe3, 0x36, 0xec, 0xd9, 0x10, 0x35, 0x37, 0x22, 0x6a, 0x02, 0x40, 0x32, 0x48, 0x36, 0xe3, 0x64, 0x36, 0xe8, 0xec, 0x38, 0x0a, 0xe3, 0x37, 0x08, 0xec, 0x64, 0x38, 0xfe, 0xe8, 0xec, 0x44, 0x38, 0x28, 0xf4, 0x40, 0xec, 0xb9, 0x80, 0x88, 0x42, 0xa6, 0x38, 0x28, 0xfc, 0x0a, 0x03, 0x01, 0x05, 0x0a, 0x32, 0x35, 0x41, 0x46, 0xa6, 0x38, 0x0d, 0xfc, 0x0a, 0x1a, 0x46, 0x3e, 0xab, 0x80, 0x88, 0x38, 0x28, 0xbb, 0x80, 0x88, 0x39, 0x01, 0xe0, 0x1d, 0x39, 0x01, 0xe0, 0xbb, 0x80, 0x88, 0x39, 0x02, 0x20, 0x38, 0x28, 0x19, 0x32, 0x00, 0x78, 0x04, 0x03, 0x00, 0x60, 0x04, 0x00, 0x00, 0x6e, 0x04, 0x00, 0x00, 0x5c, 0x2a, 0xfe, 0xa0, 0x0a, 0x36, 0xfe, 0xa0, 0x15, 0x2d, 0xbe, 0x5c, 0x02, 0x36, 0xfe, 0xe4, 0x5c, 0x2a, 0xfe, 0xa0, 0x01, 0x6a, 0x7e, 0x61, 0xfc, 0xf6, 0x8d, 0xa0, 0x02, 0x6a, 0x7e, 0x62, 0x2c, 0x37, 0xbe, 0xa0, 0x4a, 0xa4, 0xfc, 0x5c, 0x03, 0x5d, 0x3e, 0xfc, 0x15, 0x2d, 0xbe, 0x5c, 0x09, 0x36, 0xfe, 0xe4, 0xf0, 0xf5, 0x3d, 0x08, 0x20, 0x37, 0xbe, 0xa0, 0x45, 0x92, 0xfc, 0x5c, 0x36, 0x49, 0xbe, 0xa0, 0x39, 0x39, 0xbe, 0xa0, 0x3b, 0x45, 0xbe, 0xa0, 0x01, 0xf0, 0xe9, 0x6c, 0x2b, 0xf0, 0x69, 0xec, 0x4a, 0xa4, 0xfc, 0x5c, 0x1e, 0xff, 0xbf, 0xa0, 0x80, 0x4b, 0xbe, 0x6c, 0x00, 0x4a, 0x7e, 0xfc, 0x38, 0x37, 0xbe, 0xa0, 0x21, 0xff, 0xbf, 0xa0, 0x24, 0x4b, 0xbe, 0x04, 0xfd, 0x4a, 0xbe, 0x68, 0x06, 0x4a, 0xfe, 0x24, 0x25, 0x4d, 0xbe, 0x08, 0x10, 0x4a, 0xfe, 0x28, 0x25, 0x47, 0xbc, 0x50, 0x02, 0x48, 0xfe, 0x80, 0xfb, 0x4a, 0xbe, 0xa0, 0x80, 0x4b, 0xbe, 0x6c, 0x26, 0x4b, 0x3e, 0xfc, 0x1b, 0x36, 0xfe, 0xe4, 0x23, 0x49, 0xbe, 0x84, 0x1d, 0xff, 0xbf, 0xa0, 0xfb, 0x4a, 0xbe, 0xa0, 0x80, 0x4b, 0xbe, 0x6c, 0x00, 0x4a, 0x7e, 0xfc, 0x13, 0x44, 0xfe, 0xe4, 0xff, 0xfa, 0xbd, 0x20, 0x27, 0xfb, 0xbd, 0x81, 0xff, 0xfa, 0xbd, 0x24, 0x12, 0x00, 0x4c, 0x5c, 0x23, 0x49, 0xbe, 0x80, 0x12, 0x38, 0xfe, 0xe4, 0x01, 0xf0, 0xe9, 0x6e, 0x01, 0x6a, 0x7e, 0x61, 0x1f, 0x37, 0xbe, 0xa0, 0x01, 0x36, 0xd2, 0x80, 0x45, 0x92, 0xfc, 0x5c, 0xf0, 0xf3, 0x15, 0x08, 0x4a, 0xa4, 0xe4, 0x5c, 0x29, 0xff, 0xa7, 0xa0, 0x03, 0x5d, 0x26, 0xfc, 0xfc, 0xf6, 0xa5, 0x6c, 0x53, 0xb6, 0xfc, 0x5c, 0x04, 0xaf, 0xfc, 0x50, 0x05, 0xb3, 0xfc, 0x50, 0x55, 0xb6, 0xfc, 0x5c, 0x53, 0xb6, 0xfc, 0x5c, 0x2a, 0xff, 0x97, 0xa0, 0x03, 0x5d, 0x16, 0xfc, 0x08, 0x00, 0x68, 0x5c, 0x04, 0x00, 0x7c, 0x5c, 0x4a, 0xa4, 0xfc, 0x5c, 0x80, 0x4b, 0xbe, 0x6c, 0x00, 0x4a, 0x7e, 0xfc, 0x45, 0x36, 0xfe, 0xe4, 0x00, 0x00, 0x7c, 0x5c, 0x01, 0x6a, 0x7e, 0x61, 0xfc, 0xf6, 0xb1, 0x6c, 0x30, 0xff, 0xbf, 0xa0, 0xfb, 0x4a, 0xbe, 0xa0, 0x2e, 0x4b, 0xbe, 0x6c, 0x32, 0x4b, 0x3e, 0xfc, 0x28, 0xff, 0xbf, 0xa0, 0xfb, 0x4a, 0xbe, 0xa0, 0x00, 0x00, 0x7c, 0x5c, 0x02, 0xaf, 0xfc, 0x50, 0x03, 0xb3, 0xfc, 0x50, 0x2d, 0x37, 0xbe, 0xa0, 0x30, 0xff, 0xbf, 0xa0, 0x02, 0x5d, 0x3e, 0xfc, 0x31, 0xff, 0xbf, 0xa0, 0x03, 0x5d, 0x3e, 0xfc, 0x56, 0x36, 0xfe, 0xe4, 0x00, 0x00, 0x7c, 0x5c, 0xf0, 0x2f, 0xbe, 0xa0, 0x33, 0xc1, 0xfc, 0x54, 0x0d, 0x30, 0xfe, 0xa0, 0x04, 0x2e, 0xfe, 0x80, 0x17, 0x01, 0xbc, 0x08, 0xf5, 0xc0, 0xbc, 0x80, 0x5f, 0x30, 0xfe, 0xe4, 0x34, 0x2f, 0xbe, 0xa0, 0x08, 0x2e, 0x7e, 0x61, 0x00, 0x31, 0x8e, 0xa0, 0x01, 0x31, 0xb2, 0xa0, 0x40, 0x2e, 0x7e, 0x61, 0x01, 0x2e, 0xfe, 0x28, 0x03, 0x2e, 0xfe, 0x2c, 0x17, 0x31, 0xbe, 0x28, 0x18, 0xfd, 0xbf, 0x50, 0x06, 0x2e, 0xfe, 0x28, 0x17, 0xfd, 0xbf, 0x54, 0x03, 0x2e, 0xfe, 0x2c, 0xff, 0x30, 0xfe, 0x60, 0x17, 0x31, 0xbe, 0x2c, 0x18, 0xed, 0x8f, 0xa0, 0x00, 0xee, 0xcf, 0xa0, 0x00, 0xec, 0xf3, 0xa0, 0x18, 0xef, 0xb3, 0xa0, 0xe9, 0x66, 0x7e, 0xec, 0x16, 0x2b, 0xbe, 0x5c, 0x06, 0xf7, 0xfc, 0x50, 0x28, 0xff, 0xfc, 0x54, 0x07, 0x2e, 0xfe, 0xa0, 0x01, 0x6a, 0x7e, 0x61, 0x00, 0x30, 0xbe, 0xa0, 0x01, 0xf6, 0xfc, 0x80, 0x10, 0x30, 0xce, 0x2c, 0x10, 0x30, 0xfe, 0x28, 0x18, 0x01, 0xbc, 0xa0, 0xf5, 0xfe, 0xbc, 0x80, 0x7b, 0x2e, 0xfe, 0xe4, 0x0d, 0x0d, 0xcd, 0x50, 0x0e, 0x0d, 0xf1, 0x50, 0x2f, 0x0d, 0xfd, 0x54, 0x04, 0x2e, 0xfe, 0xa0, 0x00, 0x00, 0xbc, 0xa0, 0xf7, 0x0c, 0xbd, 0x80, 0x86, 0x2e, 0xfe, 0xe4, 0x00, 0x2e, 0xfe, 0x08, 0x01, 0x2e, 0xfe, 0x28, 0xf3, 0x2e, 0x3e, 0x85, 0x00, 0x7c, 0xf2, 0xa0, 0x01, 0x2e, 0xfe, 0x28, 0x2f, 0x2f, 0x3e, 0x85, 0xe9, 0x00, 0x70, 0x5c, 0x16, 0x2b, 0xbe, 0x5c, 0x2f, 0x2f, 0xbe, 0xa0, 0xdc, 0xc4, 0xfd, 0x5c, 0x01, 0x6a, 0x7e, 0x61, 0x0f, 0xf0, 0xf3, 0x58, 0x0e, 0xf0, 0xcf, 0x58, 0x01, 0x30, 0xce, 0x2c, 0x18, 0xf5, 0xbf, 0xa0, 0x16, 0x2b, 0xbe, 0x5c, 0x3e, 0x2f, 0xbe, 0xa0, 0x00, 0x30, 0xfe, 0xa0, 0xa3, 0x2e, 0x7e, 0xec, 0xf3, 0x2e, 0xbe, 0x48, 0xf4, 0x2e, 0xbe, 0x4c, 0x0c, 0x30, 0xfe, 0xa0, 0x01, 0x2e, 0xfe, 0x28, 0x17, 0xe7, 0x3d, 0x85, 0x01, 0x30, 0xf2, 0x80, 0x9f, 0x00, 0x70, 0x5c, 0x18, 0xf3, 0xbf, 0x58, 0xdc, 0xc4, 0xfd, 0x5c, 0x18, 0xf7, 0xbf, 0xa0, 0x16, 0x2b, 0xbe, 0x5c, 0xa0, 0x2e, 0xfe, 0xa0, 0x01, 0x68, 0x7e, 0x61, 0x40, 0x2e, 0xf2, 0x68, 0x08, 0x6a, 0x7e, 0x61, 0x10, 0x2e, 0xce, 0x68, 0x04, 0x6a, 0x7e, 0x61, 0x08, 0x2e, 0xce, 0x68, 0x07, 0x7e, 0xfe, 0x60, 0x3f, 0x2f, 0xbe, 0x68, 0x17, 0xfd, 0xbf, 0x58, 0x3a, 0x43, 0xbe, 0xa0, 0x08, 0x42, 0xfe, 0x2c, 0x3a, 0x43, 0xbe, 0x68, 0x04, 0x42, 0xfe, 0x2c, 0x38, 0x47, 0xbe, 0xa0, 0x01, 0x46, 0xfe, 0x2c, 0x38, 0x2f, 0xbe, 0xa0, 0x3a, 0x31, 0xbe, 0xa0, 0xe3, 0xd0, 0xfd, 0x5c, 0x28, 0x3b, 0xbe, 0xa0, 0x17, 0x3b, 0xbe, 0x84, 0x01, 0x3a, 0xfe, 0x29, 0x3c, 0x3d, 0xbe, 0xa0, 0x1d, 0x3d, 0xbe, 0xc8, 0x3c, 0x3b, 0xbe, 0x84, 0x39, 0x2f, 0xbe, 0xa0, 0x3b, 0x31, 0xbe, 0xa0, 0xe3, 0xd0, 0xfd, 0x5c, 0x10, 0x6a, 0x7e, 0x61, 0x01, 0xfe, 0xfd, 0x70, 0xfe, 0x4e, 0xbe, 0xa0, 0x01, 0x4e, 0xf2, 0x28, 0x01, 0x2e, 0xf2, 0x2c, 0x02, 0x6a, 0x7e, 0x61, 0x01, 0x2e, 0xf2, 0x28, 0x2b, 0x3f, 0xbe, 0xa0, 0x17, 0x3f, 0xbe, 0x84, 0x01, 0x3e, 0xfe, 0x29, 0x3d, 0x41, 0xbe, 0xa4, 0x1f, 0x41, 0xbe, 0xc8, 0x3d, 0x3f, 0xbe, 0x80, 0x02, 0x6a, 0xfe, 0x6c, 0x16, 0x2b, 0xbe, 0x5c, 0x0d, 0x2e, 0xfe, 0xa0, 0xd7, 0x30, 0xbe, 0xa0, 0x07, 0x30, 0xfe, 0x28, 0xfc, 0x30, 0xfe, 0x60, 0x37, 0x31, 0xbe, 0x80, 0x18, 0x01, 0xbf, 0x08, 0xf5, 0xae, 0xbd, 0x80, 0xf6, 0xae, 0xbd, 0x64, 0xd3, 0x2e, 0xfe, 0xe4, 0xd1, 0x00, 0x7c, 0x5c, 0x00, 0x32, 0xfe, 0x08, 0x21, 0x34, 0xfe, 0xa0, 0x19, 0x2f, 0xbe, 0xe1, 0x01, 0x30, 0xfe, 0x34, 0x01, 0x2e, 0xfe, 0x2c, 0xde, 0x34, 0xfe, 0xe4, 0x00, 0x00, 0x7c, 0x5c, 0x0b, 0x30, 0xfe, 0x2c, 0x08, 0x32, 0xfe, 0xa0, 0x01, 0x2e, 0xfe, 0x29, 0x18, 0x2f, 0xb2, 0x80, 0xe5, 0x32, 0xfe, 0xe4, 0x00, 0x00, 0x7c, 0x5c, 0x00, 0xf0, 0xff, 0xa0, 0x00, 0xf2, 0xff, 0xa0, 0x00, 0xfc, 0xff, 0xa0, 0xf0, 0xe9, 0x3f, 0x08, 0x00, 0x2e, 0xfe, 0x08, 0x08, 0x2e, 0xfe, 0x28, 0x03, 0x2e, 0xfe, 0x48, 0xf1, 0x2f, 0xbe, 0x80, 0x00, 0x2e, 0xfe, 0xf8, 0x00, 0x00, 0x7c, 0x5c, 0x00, 0x12, 0x7a, 0x00, 0x00, 0x20, 0xa1, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0f, 0x70, 0xf0, 0x77, 0x7f, 0xf7, 0xff, 0xa5, 0x56, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xa5, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x6a, 0xc8, 0x0b, 0x6c, 0x0e, 0xac, 0x04, 0x8e, 0x05, 0x1c, 0x07, 0xde, 0x08, 0xf3, 0x00, 0x1e, 0x01, 0x0a, 0x00, 0x12, 0x00, 0x06, 0x00, 0x05, 0x00, 0x8a, 0x02, 0xaa, 0x02, 0x99, 0x9e, 0x36, 0x00, 0xd2, 0xa6, 0x43, 0x00, 0x70, 0x72, 0x02, 0x00, 0x50, 0x53, 0x03, 0x00, 0xac, 0x34, 0x04, 0x00, 0x8e, 0xf5, 0x04, 0x00, 0xa5, 0xaa, 0x06, 0x50, 0xa5, 0xaa, 0x01, 0x54, 0x01, 0x05, 0x02, 0x34, 0xc7, 0x0c, 0x64, 0x28, 0x36, 0xec, 0x42, 0x80, 0x61, 0x32, 0x40, 0x0a, 0x05, 0x42, 0x98, 0x36, 0xed, 0x21, 0x32, 0x00, }; typedef struct { uint16_t unused; uint16_t pbase; uint16_t vbase; uint16_t dbase; uint16_t pcurr; uint16_t dcurr; } Params; TV_Text::TV_Text() { memset(m_variables, 0, sizeof(m_variables)); if (m_cogid < 0) { Params *params = (Params *)spinBinary; uint16_t *dbase = (uint16_t *)(uint32_t)params->dbase; uint32_t *dat = (uint32_t *)(spinBinary + 0x001c); params->pbase += (uint16_t)spinBinary; params->vbase = (uint16_t)(uint32_t)m_variables; params->dbase = (uint16_t)(uint32_t)(m_stack + 4); params->pcurr += (uint16_t)spinBinary; params->dcurr = params->dbase + 32; dbase[-4] = 2; // pbase + abort-trap + return-value dbase[-3] = 0; // vbase (not used) dbase[-2] = 0; // dbase (not used) dbase[-1] = 0xfff9; // return address *(uint32_t *)dbase = 0; // result dat[0] = (uint32_t)&m_mailbox; m_cogid = cognew(SPINVM, spinBinary); } } TV_Text::~TV_Text() { if (m_cogid >= 0) { cogstop(m_cogid); m_cogid = -1; } } uint32_t TV_Text::start(uint32_t basepin) { uint32_t params[3]; params[0] = (uint32_t)m_variables; params[1] = 0; params[2] = basepin; m_mailbox = params; while (m_mailbox) ; return params[0]; } uint32_t TV_Text::stop() { uint32_t params[2]; params[0] = (uint32_t)m_variables; params[1] = 1; m_mailbox = params; while (m_mailbox) ; return params[0]; } uint32_t TV_Text::str(uint32_t stringptr) { uint32_t params[3]; params[0] = (uint32_t)m_variables; params[1] = 2; params[2] = stringptr; m_mailbox = params; while (m_mailbox) ; return params[0]; } uint32_t TV_Text::dec(uint32_t value) { uint32_t params[3]; params[0] = (uint32_t)m_variables; params[1] = 3; params[2] = value; m_mailbox = params; while (m_mailbox) ; return params[0]; } uint32_t TV_Text::hex(uint32_t value, uint32_t digits) { uint32_t params[4]; params[0] = (uint32_t)m_variables; params[1] = 4; params[2] = value; params[3] = digits; m_mailbox = params; while (m_mailbox) ; return params[0]; } uint32_t TV_Text::bin(uint32_t value, uint32_t digits) { uint32_t params[4]; params[0] = (uint32_t)m_variables; params[1] = 5; params[2] = value; params[3] = digits; m_mailbox = params; while (m_mailbox) ; return params[0]; } uint32_t TV_Text::out(uint32_t c) { uint32_t params[3]; params[0] = (uint32_t)m_variables; params[1] = 6; params[2] = c; m_mailbox = params; while (m_mailbox) ; return params[0]; } uint32_t TV_Text::setcolors(uint32_t colorptr) { uint32_t params[3]; params[0] = (uint32_t)m_variables; params[1] = 7; params[2] = colorptr; m_mailbox = params; while (m_mailbox) ; return params[0]; }The generated Spin source file:
OBJ x : "TV_Text" PUB dispatch | params, object, index, arg1, arg2, arg3, arg4 repeat repeat while (params := long[mailbox]) == 0 longmove(@object, params, 6) case index 0: result := x.start({basepin} arg1) 1: result := x.stop 2: result := x.str({stringptr} arg1) 3: result := x.dec({value} arg1) 4: result := x.hex({value} arg1, {digits} arg2) 5: result := x.bin({value} arg1, {digits} arg2) 6: result := x.out({c} arg1) 7: result := x.setcolors({colorptr} arg1) long[params][0] := result DAT mailbox long 0I compile the Spin code with OpenSpin and include a patched binary in the C++ source file. That's the big array of bytes you see in that file. The only value that I patch is the address of the mailbox in the DAT section.
I still haven't tried this because I haven't figured out how to handle multiple instances of the Spin object yet.
I've attached the C source code that generates this although please excuse the lack of comments. I'll add those once I get it working.
spinwrap.c
Another bit of trivia is that DBASE will be equal to (VBASE + 8) if there are no VAR variables. The 8 bytes consist of the initial stack frame, which contain $FFF9FFFF, $FFF9FFFF. If the program does contain a VAR section then DBASE will be (VBASE + VarSize + 8).
In the Prop Tool you can write a simple one-line Spin program containing only "pub main", and hit F8 to see the header bytes. The 5 variables will be $10, $1C, $24, $18 and $28. You can then add calling parameters, local variables and VAR variables to see the affect on the header bytes.
Current:
dbase[-4] = 2; // pbase + abort-trap + return-value dbase[-3] = 0; // vbase (not used) dbase[-2] = 0; // dbase (not used) dbase[-1] = 0xfff9; // return addressShould I change to to this?
[code] dbase[-4] = 0xffff; // pbase + abort-trap + return-value dbase[-3] = 0xfff9; // vbase (not used) dbase[-2] = 0xffff; // dbase (not used) dbase[-1] = 0xfff9; // return addressThanks! I guess I'll just leave my code the way it is then if that will work as well as the version with the $FFFF values.
Here are my suggested changes.
Params *params = (Params *)spinBinary; uint16_t *dbase = (uint16_t *)(uint32_t)params->dbase; uint32_t *dat = (uint32_t *)(spinBinary + 0x001c); uint32_t space = params->dcurr - params->dbase; params->pbase += (uint16_t)spinBinary - 4; params->vbase = (uint16_t)(uint32_t)m_variables; params->dbase = (uint16_t)(uint32_t)(m_stack + 2); params->pcurr += (uint16_t)spinBinary - 4; params->dcurr = params->dbase + space;