Shop OBEX P1 Docs P2 Docs Learn Events
SpinWrap - a tool for allowing C or C++ to use Spin Objects — Parallax Forums

SpinWrap - a tool for allowing C or C++ to use Spin Objects

David BetzDavid Betz Posts: 14,511
edited 2014-03-02 08:42 in Propeller 1
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.
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:
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.
Also, from reading Dave's overlay demonstration code it seems that a Spin binary file starts like this:
$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)&params>>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
«1345

Comments

  • Dave HeinDave Hein Posts: 6,347
    edited 2014-02-16 05:12
    Ross' code allows for having separate memory spaces for the stack and VAR variables. If you want VAR space to be contiguous with the program you can use something like this:
    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 cognum
    
    It 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.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-16 05:37
    Dave Hein wrote: »
    Ross' code allows for having separate memory spaces for the stack and VAR variables. If you want VAR space to be contiguous with the program you can use something like this: ...
    Thanks for the code! Does this come from Spinix?
    It should be fairly easy to convert this to C since it uses C-like Spin methods.
    Yes, it looks very easy to understand.
    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.
    I realized after I posted my message that I should have remembered that was the checksum since I have to recompute that after patching Spin binaries in propeller-load before downloading them.
    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.
    Thanks for the explanation of those two extra bytes. I wondered about those.
    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.
    I'd like to experiment with this. Can you tell me how to parse the method table?

    Thanks for all of your help!

    David
  • jazzedjazzed Posts: 11,803
    edited 2014-02-16 08:22
    David Betz wrote: »
    Can you tell me how to parse the method table?
    Try this: http://forums.parallax.com/showthread.php/104847-Spin-Object-Binary-Structure?p=736449&viewfull=1#post736449

    As always on this or any forum you have to filter a through certain amount of junk.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-16 09:29
    jazzed wrote: »
    Try this: http://forums.parallax.com/showthread.php/104847-Spin-Object-Binary-Structure?p=736449&viewfull=1#post736449

    As always on this or any forum you have to filter a through certain amount of junk.
    Thanks! That link is extremely helpful. I think you pointed me to that a long time ago as well but I had lost track of the link. I admit that I have trouble finding things like this on the forum. I did a search before I posted my question but I must not have chosen my search terms well.
  • Dave HeinDave Hein Posts: 6,347
    edited 2014-02-16 10:24
    David Betz wrote: »
    Thanks for the code! Does this come from Spinix?
    ...
    I'd like to experiment with this. Can you tell me how to parse the method table?
    Yes, the code came from an earlier version of Spinix.

    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
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-17 06:39
    Thanks to everyone who replied to my original message, I think I have collected enough information to attempt this. My current plan is to write a command line tool to do the following:

    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:
    int myobj_myfunction(void *instance, int arg1, int arg2, int arg2);
    
    or
    
    int myobj::myfunction(int arg1, int arg2, int arg3);
    
    I'll also create C/C++ code to load the compiled Spin object into a COG and start it.
  • Dave HeinDave Hein Posts: 6,347
    edited 2014-02-17 08:36
    David, in an earlier versions of spinix I used a remote method call technique to file I/O services from a resident kernel. It provided a mechanism for a Spin program to call a method in another Spin program. I think it's similar to what you are planning, except that there wasn't a tool to automatically generate the stub file and the message handler file. In spinix, the stub file was cfileio_os.spin, and the message handler was in kernel.spin.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-17 08:52
    Dave Hein wrote: »
    David, in an earlier versions of spinix I used a remote method call technique to file I/O services from a resident kernel. It provided a mechanism for a Spin program to call a method in another Spin program. I think it's similar to what you are planning, except that there wasn't a tool to automatically generate the stub file and the message handler file. In spinix, the stub file was cfileio_os.spin, and the message handler was in kernel.spin.
    Thanks Dave! This should be very helpful. As I've said in another place, if this project of mine actually comes to light, it will mostly be due to work that you and others have already done. I'm just putting together ideas I've gotten from you and others on this forum.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-18 20:50
    It seems I've underestimated this project. It would be relatively easy if the Spin VM were adequately documented but I now realize that I need to understand the opcodes that are used to call methods and I don't have a complete description of any of the Spin VM opcodes.Is there such a thing. Has anyone undertaken to document the Spin VM? I know Dave Hein must understand it because he wrote an emulator for it but is that knowledge captured in a document anywhere? If so, I'd appreciate a pointer to it. Otherwise, I'm stalled on this. :-(

    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.
  • jazzedjazzed Posts: 11,803
    edited 2014-02-18 21:38
    Sounds like it was an interesting trip if nothing else.

    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.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-19 03:07
    jazzed wrote: »
    Sounds like it was an interesting trip if nothing else.

    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 approach I was attempting did not "waste a COG for a dispatcher" any more than what Catalina does wastes a COG. The extra COG was there to run the Spin code. Aside, from using something like spin2cpp, there isn't any other way to do this that I know of. The Spin code can only run in the same COG as the main program if it is translated to C first. If it is to run as native Spin code, the Spin VM must be running in its own COG. What you call the "dispatcher" is actually a COG that is running the target Spin object (or maybe more than one) along with a tiny proxy. It is the tiny proxy that I have been trying to write. It actually isn't all that difficult but it requires that I understand Spin bytecodes better than I do now.

    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.
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2014-02-19 03:33
    Don't give up yet - this is really interesting!

    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 :)
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-19 04:09
    Dr_Acula wrote: »
    Don't give up yet - this is really interesting!

    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 :)
    That instruction list is a start but the semantics of the instructions aren't given. What exactly does each instruction actually do to the state of the VM? What got me stalled is having to handle different numbers of arguments. Here is the output of BSTC showing the bytecodes for a simple three-argument function:
    8                              a := tst.method_3(1, 2, 3)
    Addr : 002F:             00  : Drop Anchor Push 
    Addr : 0030:             36  : Constant 2 $00000001
    Addr : 0031:          37 00  : Constant Mask Y=0 00000002
    Addr : 0033:          37 21  : Constant Mask Y=33 Decrement 00000003
    Addr : 0035:       06 02 04  : Call Obj.Sub 2 4
    Addr : 0038:             65  : Variable Operation Local Offset - 1 Write
    
    It looks like the "drop anchor push" opcode starts a stack frame. Then the instructions that start with "Constant" push constants on the stack but why is the first one "Constant" and the other two "Constant Mask"? Also, what does "Call Obj.Sub 2 4" mean? This method has three arguments but the call opcode includes 2 and 4 but not 3. What do those numbers mean? What I wanted to do is construct a stack frame manually and then just execute a CALL instruction but I'm not sure how to do that.
  • Dave HeinDave Hein Posts: 6,347
    edited 2014-02-19 05:37
    The constants -1, 0 and 1 are pushed onto the stack using the bytecodes $34, $35 and $36 respectively. The constants 2 and 3 could have been loaded using bytecode $38 followed by a byte value, which loads the byte value onto the stack. However, BST uses bytecode $37, which loads constants that can be derived from powers of two. The 5 LSBs of the following byte specify a bit-shift plus one. So $37 $00 means load the value of 1 << (0+1). The upper bits of the second byte select functions of subtracting 1 and inverting after the bit-shift. The bytecodes $37 $21 mean load the value (1 << (1+1))-1.

    "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.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-19 06:35
    Dave Hein wrote: »
    The constants -1, 0 and 1 are pushed onto the stack using the bytecodes $34, $35 and $36 respectively. The constants 2 and 3 could have been loaded using bytecode $38 followed by a byte value, which loads the byte value onto the stack. However, BST uses bytecode $37, which loads constants that can be derived from powers of two. The 5 LSBs of the following byte specify a bit-shift plus one. So $37 $00 means load the value of 1 << (0+1). The upper bits of the second byte select functions of subtracting 1 and inverting after the bit-shift. The bytecodes $37 $21 mean load the value (1 << (1+1))-1.

    "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.
    Thanks for the explanations of those opcodes. Is there a document somewhere that contains descriptions like that of each bytecode?
    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.
    Are you talking about the code you posted in message #2 in this thread? I don't see how that code allows me to call an arbitrary method within an object with an arbitrary number of parameters. What am I missing? Anyway, what you describe is exactly what I planned to do. None of it is hard except figuring out how to call an arbitrary method with an arbitrary number of arguments that are passed through a mailbox interface from the C code.
    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.
    Yes, locks will be necessary if the Spin object is to be called from multiple COGs. I may tackle that if I can get the rest of this sorted out.
    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.
    I'd prefer to use C++ since it will allow calls to Spin objects from C to look almost exactly like the corresponding calls from Spin but I will probably provide a C interface as well so it will work with Catalina.
  • Dave HeinDave Hein Posts: 6,347
    edited 2014-02-19 06:53
    The Spinix code I'm referring to is in post #8. It uses a stub object that performs remote method calls through a mailbox. The called object runs a kernel object on top of the original top object. The kernel waits for posts to the mailbox, and then does a proxy call to the method in the original top object. The returned value from the called method is then returned back through the mailbox.

    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.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-19 06:57
    Dave Hein wrote: »
    The Spinix code I'm referring to is in post #8.

    There is no formal document that describes the Spin bytecodes. The Spin interpreter source written by Chip does contain a lot of comments that describes it. One problem is that formal mnemonics where never defined for the Spin bytecodes. It's a bit awkward to talk about a bytecodes when all you have is the value $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.
    Excellent! That's very helpful. Sorry I missed the attachments to message #8. Thanks also for the list of the Spin opcodes! I guess I now have what I need to work more on this tonight.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-19 15:45
    David Betz wrote: »
    Excellent! That's very helpful. Sorry I missed the attachments to message #8. Thanks also for the list of the Spin opcodes! I guess I now have what I need to work more on this tonight.
    I looked at kernel.spin and I see it uses a CASE statement to dispatch based on method index. I was hoping to avoid that. That is why I was asking about Spin CALL instructions and calling sequences. I was hoping to just stuff the method index into some already existing code so that I could call the target method directly. However, maybe that isn't necessary. The last time I used a big CASE statement it was slow at matching cases toward the end of the list which made me think it was just doing a big nested IF/THEN/ELSE chain. Is that true? Or does it actually create a dispatch table?
  • Dave HeinDave Hein Posts: 6,347
    edited 2014-02-19 16:10
    Yes CASE does sequentially compare each case value until it finds a match. It's probably 2 or 3 times faster than doing a series of IF/ELSIF statements, but I've never timed it. Even if you use the method table technique you would still need to have a dummy method for each number of parameters. That is, you would need a dummy method with zero parameter, another one with one parameter, and so on.

    You could generate nested IF/ELSE statement to perform a binary search on the index. Only 5 tests would be needed for 32 methods.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-19 16:24
    Dave Hein wrote: »
    Yes CASE does sequentially compare each case value until it finds a match. It's probably 2 or 3 times faster than doing a series of IF/ELSIF statements, but I've never timed it. Even if you use the method table technique you would still need to have a dummy method for each number of parameters. That is, you would need a dummy method with zero parameter, another one with one parameter, and so on.

    You could generate nested IF/ELSE statement to perform a binary search on the index. Only 5 tests would be needed for 32 methods.
    When I first thought about just patching the method index into the code I didn't understand how the Spin bytecodes work. I didn't realize that there was a separate bytecode for starting a stack frame. This isn't true of my xbasic VM. It would be easy to hand-construct an xbasic stack frame with an arbitrary number of arguments. Anyway, thanks for your help. I may go ahead and use the CASE method as a test. It won't be too bad for objects that don't have a lot of methods.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-20 18:38
    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.
    You say that PCURR points to the first bytecode in the method. Is this the first method of the object? Will this be the same value as is found in the method table for the first method? In one object that I dumped it looks like the value of PCURR in the binary file header is actually the sum of the offset of the first object and the offset of the first method. In other words, the header on the file I dumped had $20 for PCURR but it had $10 for the offset to the first method
    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: 28
    
    So, 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
  • Dave HeinDave Hein Posts: 6,347
    edited 2014-02-20 18:44
    The 5 parameters in the 16-byte header contain absolute addresses. The addresses in the method table are relative to the start of the object. The starting address in a binary file is always the first method in the top object, so the numbers you are seeing all make sense.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-21 04:54
    Now I have a question about setting up the initial stack pointer. Here again is a dump of the beginning of a Spin binary file:
    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: 28
    
    I'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?
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-21 06:09
    Here is a summary of my progress to date. I'm using the TV_Text.spin file as my test program and it includes the TV.spin object.

    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 0
    

    I 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
    c
  • Dave HeinDave Hein Posts: 6,347
    edited 2014-02-21 06:22
    The extra 4 bytes are the RESULT variable. DBASE is the address of RESULT, and DCURR is (DBASE + NumberOfParms*4 + NumberOfLocalVariables*4 + 4). The first method of the top object normally has no calling parameters, but it is legal to declare them. I use this feature to implement a main(argc, argv) entry point in spinix apps. Spinix preloads the command-line arguments into argc and argv on the app's stack before starting it.

    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.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-21 07:27
    Dave Hein wrote: »
    The extra 4 bytes are the RESULT variable. DBASE is the address of RESULT, and DCURR is (DBASE + NumberOfParms*4 + NumberOfLocalVariables*4 + 4). The first method of the top object normally has no calling parameters, but it is legal to declare them. I use this feature to implement a main(argc, argv) entry point in spinix apps. Spinix preloads the command-line arguments into argc and argv on the app's stack before starting it.

    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.
    I have this code which is derived from an example that you posted a while back. Should I change it to seed the stack with the $FFF9FFFF, $FFF9FFFF you mention above?

    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 address
    

    Should 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 address
    
  • Dave HeinDave Hein Posts: 6,347
    edited 2014-02-21 08:40
    Either one will work. The purpose of the initial stack frame is to jump to the code at $FFF9 in the ROM that stops the cog. This happens when the first method in the top object returns, or a method performs an abort that isn't trapped by any of the callers. dbase[-1] is the return address. The values of dbase[-2] and dbase[-3] don't matter since the code at $FFF9 does not access any local or VAR variables. The only things that are used in dbase[-4] are the two least significant bits since the code at $FFF9 does not access any DAT variables or use a method table. Bit 1 of dbase[-4] is used to trap an abort, and a value of 2 or $FFFF sets this bit. Bit 0 of dbase[-4] controls whether the return value is saved on the stack or discarded. The value of this bit doesn't matter since the cog will be stopped after jumping to $FFF9.
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-21 09:17
    Dave Hein wrote: »
    Either one will work. The purpose of the initial stack frame is to jump to the code at $FFF9 in the ROM that stops the cog. This happens when the first method in the top object returns, or a method performs an abort that isn't trapped by any of the callers. dbase[-1] is the return address. The values of dbase[-2] and dbase[-3] don't matter since the code at $FFF9 does not access any local or VAR variables. The only things that are used in dbase[-4] are the two least significant bits since the code at $FFF9 does not access any DAT variables or use a method table. Bit 1 of dbase[-4] is used to trap an abort, and a value of 2 or $FFFF sets this bit. Bit 0 of dbase[-4] controls whether the return value is saved on the stack or discarded. The value of this bit doesn't matter since the cog will be stopped after jumping to $FFF9.

    Thanks! 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.
  • Dave HeinDave Hein Posts: 6,347
    edited 2014-02-21 10:07
    I looked at your code for setting up the Spin header, and I was confused at first until I realized that the first four bytes are missing. Because of this I think you will have to adjust the offset you add to PBASE and PCURR. It might be easier to include the first four bytes and not worry about the adjustment. Also, you are adding 4 to m_stack to get dbase, but m_stack is an int32 array, so that's adding 16 bytes. We should add 8 bytes to account for the initial stack frame, which is 2 int32's. Also, the space between dbase and dcurr should remain the same as it was in the original header. You are adding 32 to an int16 pointer, which is 64 bytes. I think it's better to compute the original difference between dbase and dcurr at the beginning, and then add it to the new dbase to get the new dcurr.

    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;
    
  • David BetzDavid Betz Posts: 14,511
    edited 2014-02-21 10:28
    Dave Hein wrote: »
    I looked at your code for setting up the Spin header, and I was confused at first until I realized that the first four bytes are missing. Because of this I think you will have to adjust the offset you add to PBASE and PCURR. It might be easier to include the first four bytes and not worry about the adjustment. Also, you are adding 4 to m_stack to get dbase, but m_stack is an int32 array, so that's adding 16 bytes. We should add 8 bytes to account for the initial stack frame, which is 2 int32's. Also, the space between dbase and dcurr should remain the same as it was in the original header. You are adding 32 to an int16 pointer, which is 64 bytes. I think it's better to compute the original difference between dbase and dcurr at the beginning, and then add it to the new dbase to get the new dcurr.

    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;
    
    Thanks for your comments. I've made those changes to spinwrap.c so that it will generate exactly what you suggest.
Sign In or Register to comment.