Shop OBEX P1 Docs P2 Docs Learn Events
Homespun Spin compiler 0.31: Now open source - Page 4 — Parallax Forums

Homespun Spin compiler 0.31: Now open source

1246712

Comments

  • BradCBradC Posts: 2,601
    edited 2008-09-17 15:04
    Cluso99 said...
    @BradC: Because there are lots of times where you require this. It is known by the compiler, just not exposed. And to actually do it correctly (I mean not hardcoding offset to $0010) requires walking the object at runtime which is neither efficient nor intuitive. In my opinion, it is a compiler deficiency - others may not agree, but that's my opinion. The only reason for keeping it the way it is, is if there were separate linkers and relocatable objects, and we are way off from that.

    Actually, no.. it's not known by the compiler because the compiler works on objects being relocatable and re-linked at the last minute. But it _is_ known by the interpreter. You can access pbase directly from the interpreter. You also know the absolute hub address of any portion of the DAT area from spin at runtime with @@.

    Nothing hard about.

    DAT

    fred long

    PUB AAAA
    fred := @@fred

    If you want to get funky then perhaps another Spin command like COGID that returns PBASE.. which is easy enough to achieve. But what you are asking is pretty much a re-engineering of the way the compiler works. You may get a home-brewed compiler to do it, but Parallax would likely never even look at it. On the other hand, a direct read of PBASE/DBASE/VBASE is easy to do with no re-engineering of the compiler or interpreter.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pull my finger!
  • mparkmpark Posts: 1,305
    edited 2008-09-17 15:10
    I'm not so sure it is known by Proptool. Why would we need the runtime @@ operator if the compiler could do it at compile time?

    Anyway, Homespun certainly doesn't know, but I am thinking about it.

    As for breaking compatibility, I'm happy to let Homespun be a testbed for possible extensions to Spin, as long as said extensions are a) useful and b) easy for me to implement. Especially b!



    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Michael Park

    PS, BTW, and FYI:
    To search the forum, use search.parallax.com (do not use the Search button).
    Check out the Propeller Wiki: propeller.wikispaces.com/

    Post Edited (mpark) : 9/17/2008 4:33:31 PM GMT
  • SapiehaSapieha Posts: 2,964
    edited 2008-09-17 15:51
    Hi mpark.

    You question

    ""Sapieha, are you familiar Spin bytecode? Not just the opcodes""

    Not so familiar that I will be.
    But.
    My idea is simpler that You imagine.

    HUB 0

    Start vectors
    Dumy spin Object
    xxxx
    xxxx
    xxxx
    xxxx
    ORGSPIN XXX Spin code start (same yous after·_Space)
    ssss
    ssss
    ssss
    ssss
    ssss Spin code
    ssss
    ssss
    ssss
    ssss
    DAT
    cccc
    cccc
    COGTRANSP· (Reusable HUB memory)
    ctct
    ctct One time COG load code
    ctct

    ....
    .... Blank HUB
    ....

    ORGVAR XXX· ( Not sure what first? VAR else STACK)
    ORGSTACK XXX
    HUB end

    In first compiler pass You scan for ORGXXX statements and Adress positions.
    ( If no special ORGXXX directives compiler state it as standard Spin code and skips create DUMY Spin Object)
    NEXT·You calculate and·create DUMY Spin object.
    Rest is standart compiler passes.

    That construction have OVERLAY capablites to have only LOADER/COMMANDER in EEprom that is no longer that

    DUMY Spin object and Overlays placed on SD.

    Very handy to have on CHIP Compiler and more.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Nothing is impossible, there are only different degrees of difficulty.
    For every stuipid question there is atleast one inteligent answer
    If you dont ask you wont know
    If your gonna construct somthing, make it the simplest possible yet as versalite as posible


    Sapieha

    Post Edited (Sapieha) : 9/17/2008 4:17:45 PM GMT
  • Cluso99Cluso99 Posts: 18,069
    edited 2008-09-17 17:08
    Just about every (propeller) program I have written (mostly pasm) has required the location of objects in hub. These addresses are known for spin, but not for pasm. This means the compiler does indeed know them. I have seen other posts asking similar questions as to why this is not possible in the DAT code. However, I will still use the kludge until it bothers me so much that I have do something about it. At this time, objects are NOT relocatable, so the reason to hide this is irrelevant. If the objects become relocatable later, then a method will need to be provided to relocate the addresses anyway. Having to poke addresses into hub ram by spin before loading a cog is hardly an elegant solution.
  • hippyhippy Posts: 1,981
    edited 2008-09-17 17:31
    A halfway house which may be good enough for Sapieha and others might be to link the program image so that PASM ( and perhaps DAT ) isn't interspersed with Spin but placed at the end of Spin. That could be relocated to between Spin and Vars or Vars and Stack.

    The primary concern for Sapieha seems to be being able to re-use the Hub area occupied by PASM once it's been launched. As it stands, all that PASM is non-contiguous throughout Hub.

    There's no reason such PASM couldn't be relocated to the top of Hub where it can be ignored once launched. Even better, as I do with my hacking the .eeprom images to launch a RAM Interpreter or pre-load PinDefs on booting, I process the .eeprom file, relocate the initial stack, add a dummy Spin routine where stack was and run that at startup, then re-boot Cog 0 as it would have been had none of that happened.

    This is all transparent to the Spin program; PASM Cogs can be pre-loaded in the 'initial boot' phase and will be running when the PUB Main first gets executed and the area used by that PASM will have been automatically re-assigned as stack. This is how one starts a Propeller program using a RAM Spin Interpreter, the only thing there is it cannot be removed because subsequent CogNew/CogInit need to load that themselves.
  • mparkmpark Posts: 1,305
    edited 2008-09-17 17:38
    Cluso99 said...
    Just about every (propeller) program I have written (mostly pasm) has required the location of objects in hub. These addresses are known for spin, but not for pasm.
    Can you please give an example? I fear I'm still lagging behind.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Michael Park

    PS, BTW, and FYI:
    To search the forum, use search.parallax.com (do not use the Search button).
    Check out the Propeller Wiki: propeller.wikispaces.com/
  • SapiehaSapieha Posts: 2,964
    edited 2008-09-17 17:45
    Hi hippy.

    Sorry for MY not sufficient explains.
    It is only basic idea That with Yours help and others might be Much better united.
    My intention/concern is to have more usable compiler for advanced programing.
    And USE and REUSE HUB memory.

    Ps. If I explain bad ASK for more. I now I am write badly in English.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Nothing is impossible, there are only different degrees of difficulty.
    For every stuipid question there is atleast one inteligent answer
    If you dont ask you wont know
    If your gonna construct something, make it the simplest possible yet as versalite as posible


    Sapieha

    Post Edited (Sapieha) : 9/17/2008 5:57:29 PM GMT
  • BradCBradC Posts: 2,601
    edited 2008-09-17 17:47
    Cluso99 said...
    Just about every (propeller) program I have written (mostly pasm) has required the location of objects in hub. These addresses are known for spin,

    Again.. No.. no they are not.
    Where do you get the idea the compiler knows about them?

    Any hub based address in Spin is generated as an offset into the object and then an extra instruction to add the value to the current PBASE..

    DAT
    john long

    PUB AAAA | X1
    X1 := @@john

    0:1 : 0024 : C4 08 : Memory Op Long PBASE + READ Address = 0008
    0:1 : 0026 : 97 00 : Memory Op Byte PBASE + POP Index ADDRESS Address = 0000
    0:1 : 0028 : 65 : Variable Operation Local Offset - 1 Write
    0:1 : 0029 : 32 : Return

    The compiler never knows where an object is going to finally end up in the binary. That's the beauty of the way this all goes together. The Interpreter puts it all together at runtime.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pull my finger!
  • Cluso99Cluso99 Posts: 18,069
    edited 2008-09-17 18:24
    The compiler places all code together in a block. There is no linker, or if there is, then it is part of the compile process. It is part of this process that knows the absolute hub addresses of all code. Since the complier knows the block address and the offset, then this should be an easy matter for the compiler to add these together and provide a method (@@@ or whatever) to get a fixed hub address for the pasm code. Make no mistake, in the current regime, this is a shortcoming. If there were a separate linker, then this would be resolved at link time. The programmer would not have to figure this out. Sorry to labour the point.
  • BradCBradC Posts: 2,601
    edited 2008-09-17 19:04
    Cluso99 said...
    The compiler places all code together in a block. There is no linker, or if there is, then it is part of the compile process. It is part of this process that knows the absolute hub addresses of all code. Since the complier knows the block address and the offset, then this should be an easy matter for the compiler to add these together and provide a method (@@@ or whatever) to get a fixed hub address for the pasm code. Make no mistake, in the current regime, this is a shortcoming. If there were a separate linker, then this would be resolved at link time. The programmer would not have to figure this out. Sorry to labour the point.

    Ok, perhaps you don't understand how the compiler works.
    Each object is compiled as a completely stand alone and separate entity. It is compiled as a whole into a binary blob of
    .. object table
    .. any dat code
    .. spin methods

    That's it.. the compiler then simply places all the objects in order in a memory image and does a reverse comparison to remove duplicates.
    At no point in the process does the compiler know where anything is going to be in the image until the very last object distiallation. At this point all it is doing is moving around pre-compiled binary images and there is no rational way for it to know what in the image needs to be changed/inserted to achieve what you want to do.

    I make no mistake. The current "regime" is simple, elegant and quite sufficient to do what it set out to do.
    Using objects and the interpreter the way they were designed to be used, there is never a need for what you are asking.. and in fact, using the facilities available to you with these I still don't see why you need a compile time address..

    You can always get the address you are after in SPIN with the @@ operator and simply post process the long in the DAT block prior to launching the cog.

    There is another way I guess where we could allocate the unused spin opcode and place 5 of them in the binary image. In the linking stage we could insert a 4 byte constant there pushing the hub address, but it's ugly and inelegant.

    I just don't see why you need it. Aside from direct access to $1E0-$1EF in the interpreter, everything you are asking for can be done _now_ with a little pre-processing in SPIN.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pull my finger!
  • hippyhippy Posts: 1,981
    edited 2008-09-18 00:21
    I think the problem cluso99 has is the same I run into with PASM code, that a list of long @ pointers in DAT to data held in hub doesn't point to the right location in hub at run-time. That means having to add an offset whenever the pointer is used, effectively doing what Spin does with @/@@.

    Unfortunately that means having to pass in the offset for the PASM to use and that's not always ideal.

    I agree that's a deficiency arising from the way the current compiler works not knowing exactly where in hub the object ends up during compilation.
  • Cluso99Cluso99 Posts: 18,069
    edited 2008-09-18 02:15
    @BradC:

    As Hippy stated, and maybe not expressed properly by me, it has nothing to do with the $1E0-1FF registers or in fact the lower hub $000-1FF. What Hippy and I (and maybe others) are doing here (the registers and lower hub) is not what the average person ought to be doing, so we are fudging to get some special programs to work. I am doing it with the debuggers (spin and pasm) and the Interpreter.

    re @@@ (actual hub address for pasm):
    As you explain, the compiler does not know until it places the objects together. However, as I said (and Hippy concurrs) there is a mountain to overcome in PASM to get this information.

    I have resorted to plugging in this information in the hub ram by spin before the pasm object is loaded. I have noted Hippy looks at the binary to get the hub address and then goes back to change this physical address in the source and recompiles. This is both ineligant and dangerous for beginners. This is a deficiency, no matter how the current complier works. That will remain my opinion.
  • mparkmpark Posts: 1,305
    edited 2008-09-18 07:06
    Attached to this post is an experimental (well, they're all pretty experimental, but this one is extra-experimental) version of Homespun that implements @@@ to the best of my understanding. Cluso99 and hippy (and anyone else who's interested, of course), please see if it behaves the way you expect.

    Edit: Removed executable. Newer version later in thread.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Michael Park

    PS, BTW, and FYI:
    To search the forum, use search.parallax.com (do not use the Search button).
    Check out the Propeller Wiki: propeller.wikispaces.com/

    Post Edited (mpark) : 9/18/2008 3:43:11 PM GMT
  • SapiehaSapieha Posts: 2,964
    edited 2008-09-18 07:53
    Hi mpark.

    It is mayby dumb question.

    Aftre You have parsed and compiled You compiler stil have List of declared names and adresses why on binary image declared PUB, DAT,VARs etc.. is to be placed.
    Can You have one LOG/LIST file that export this List else it is to much work.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Nothing is impossible, there are only different degrees of difficulty.
    For every stuipid question there is atleast one inteligent answer
    If you dont ask you wont know
    If your gonna construct something, make it the simplest possible yet as versalite as posible


    Sapieha

    Post Edited (Sapieha) : 9/18/2008 8:06:57 AM GMT
  • AleAle Posts: 2,363
    edited 2008-09-18 08:07
    mpark: Is there a way to get a listing of the compiled code ? That would be very useful !

    I do not know if it was reported before, but it works _perfectly_ under wine/linux/mono (Ubuntu 7.04, wine 1.0, mono 1.9.2 for winblows.!!!
  • mparkmpark Posts: 1,305
    edited 2008-09-18 08:10
    Hi Sapieha. The /d option will dump the addresses of pubs, pris, and vars (but not dats, yet). Is that the sort of thing you had in mind? If it is, I can add dats. I don't really know what people would like to see in such a dump, so I welcome all suggestions to help guide me.

    Edit: And eventually the dump will include a code listing. That's the plan, anyway.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Michael Park

    PS, BTW, and FYI:
    To search the forum, use search.parallax.com (do not use the Search button).
    Check out the Propeller Wiki: propeller.wikispaces.com/

    Post Edited (mpark) : 9/18/2008 8:19:08 AM GMT
  • Cluso99Cluso99 Posts: 18,069
    edited 2008-09-18 08:34
    Michael, the code listing would be fantastic. May I ask that the left column contain the hub address. Not sure about the DAT (pasm) section. This would allow the debuggers to reference the listing file, pointing to the instruction or bytecode being executed in real time. I will try your test version later tonight.
  • SapiehaSapieha Posts: 2,964
    edited 2008-09-18 08:38
    Ni mpark.

    YES it is that list.
    But I wil hard patch some PASM code to work som overlays from SD Card else have separate EEProm with standart PASM librarys that program loads if it neds them and for that I must have all info I can get.
    For that I must to know Adress in image file of PASM routine, VAR positions to hard patch them.

    Samt at in default compiler direct that LOG to file with same name but .LST else LOG istead screen.

    Ps. I am sure it is many others that have other uses for that listing.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Nothing is impossible, there are only different degrees of difficulty.
    For every stuipid question there is atleast one inteligent answer
    If you dont ask you wont know
    If your gonna construct something, make it the simplest possible yet as versalite as posible


    Sapieha

    Post Edited (Sapieha) : 9/18/2008 8:54:09 AM GMT
  • Cluso99Cluso99 Posts: 18,069
    edited 2008-09-18 09:13
    Michael - Greatwork cool.gifcool.gifcool.gifcool.gifcool.gif

    The @@@ works a treat cool.gif Thankyou.
  • AleAle Posts: 2,363
    edited 2008-09-18 09:21
    A listing model could be like this:

    [noparse][[/noparse]File address or HUB memory address] | [noparse][[/noparse]COG address] | [noparse][[/noparse]symbol_name] | [noparse][[/noparse]opcodes] | original line (without symbol) or disassembled line if not taken from original [noparse][[/noparse]comments]

    Example (dots are placeholders for missing info, spaces are explicit):
    00000000  ...  00 B4 C4 04  ..........    long    $04C4B400
    00000004  ...  6F B6 10 00  ..........    long    $0010B66F
    
    00000020  024  28 96 FC A0  c1_doadc..    mov       k1_delay,#40            ' 500 ns, so 1msps
    00000024  028  4B 98 BC A0  ..........    mov       k1_delay2,k1_delay
    00000028  02C  04 98 FC 84  ..........    sub       k1_delay2,#4
    
    



    That would be nice!, thanks !
  • AleAle Posts: 2,363
    edited 2008-09-18 09:50
    mpark: When compiling the SD demo I get this null pointer error, not very descriptive:

    ale@orgpc043:~/.wine/drive_c/spin$ ale@orgpc043:~/.wine/drive_c/spin$ wine homespun015x.exe /d sdrw_test.spin
    Homespun Spin Compiler 0.15x
    parsing sdrw_test.spin
    parsing tv_text.spin
    parsing tv.spin
    parsing fsrw.spin
    parsing sdspiqasm.spin
    compiling sdrw_test.spin

    Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object
    at HomeSpun.CallExpr.MakeByteCode (Boolean leaveOnStack, System.Collections.ArrayList bytecodeList) [noparse][[/noparse]0x00000]
    at HomeSpun.BinaryAssignExpr.MakeByteCode (Boolean leaveOnStack, System.Collections.ArrayList bytecodeList) [noparse][[/noparse]0x00000]
    at HomeSpun.ExprStmt.MakeByteCode (System.Collections.ArrayList bytecodeList) [noparse][[/noparse]0x00000]
    at HomeSpun.MethodSymbolInfo.Compile (Int32 a) [noparse][[/noparse]0x00000]
    at HomeSpun.ObjectFileSymbolTable.CompileMethods () [noparse][[/noparse]0x00000]
    at HomeSpun.GlobalSymbolTable.CompileAll () [noparse][[/noparse]0x00000]
    at HomeSpun.Blah.Compile (System.String filename) [noparse][[/noparse]0x00000]
    at HomeSpun.Blah.Main (System.String[noparse]/noparse args) [noparse][[/noparse]0x00000]
  • mparkmpark Posts: 1,305
    edited 2008-09-18 10:07
    What is "the SD demo"? I'll need source to repro the bug.

    I did mention that this is all experimental, right?

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Michael Park

    PS, BTW, and FYI:
    To search the forum, use search.parallax.com (do not use the Search button).
    Check out the Propeller Wiki: propeller.wikispaces.com/
  • BradCBradC Posts: 2,601
    edited 2008-09-18 10:14
    This blows it up too...

    DAT
    fred long 1234
    john long 0[noparse][[/noparse]@@@fred]

    PUB AAA | X1
    X1 := john

    Unhandled Exception: System.IndexOutOfRangeException: Array index is out of range.
    at HomeSpun.ObjectFileSymbolTable.WriteDatBytes (Int32 dp, Int32 size, Int32 data) [noparse][[/noparse]0x00000]
    at HomeSpun.ObjectFileSymbolTable.DatPass2 () [noparse][[/noparse]0x00000]
    at HomeSpun.GlobalSymbolTable.CompileAll () [noparse][[/noparse]0x00000]
    at HomeSpun.Blah.Compile (System.String filename) [noparse][[/noparse]0x00000]
    at HomeSpun.Blah.Main (System.String[noparse]/noparse args) [noparse][[/noparse]0x00000]

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pull my finger!
  • AleAle Posts: 2,363
    edited 2008-09-18 10:18
    I downloaded it from the obex, now I do not see it, it was the fsrw-and-friends-1.5.zip file, where the fsrw package is. Using the default demo sdrw_demo.spin
  • BradCBradC Posts: 2,601
    edited 2008-09-18 10:20
    Here ya go...

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pull my finger!
  • SapiehaSapieha Posts: 2,964
    edited 2008-09-18 11:02
    Hi Cluso99.

    You said......
    Michael - Greatwork

    The @@@ works a treat Thankyou.

    Can do You post any Demo code how You use this?

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Nothing is impossible, there are only different degrees of difficulty.
    For every stupid question there is at least one intelligent answer
    If you don't ask you wont know
    If your gonna construct something, make it·as simple as·possible yet as versatile as posible


    Sapieha
  • mparkmpark Posts: 1,305
    edited 2008-09-18 15:45
    Thanks for the bug reports. There are two separate issues, only one of which I've resolved so far. Attached is version 0.15xx which fixes the bug that Brad found. I'm still working on the SD demo bug.

    Edit: removed attachment.·

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Michael Park

    PS, BTW, and FYI:
    To search the forum, use search.parallax.com (do not use the Search button).
    Check out the Propeller Wiki: propeller.wikispaces.com/

    Post Edited (mpark) : 9/19/2008 2:56:40 PM GMT
  • hippyhippy Posts: 1,981
    edited 2008-09-18 16:19
    BradC said...
    Cluso99 said...
    Just about every (propeller) program I have written (mostly pasm) has required the location of objects in hub. These addresses are known for spin,

    Again.. No.. no they are not.
    Where do you get the idea the compiler knows about them?

    Any hub based address in Spin is generated as an offset into the object and then an extra instruction to add the value to the current PBASE.

    That's very true. In effect "@" is not an indicator to the compiler to use the address of something
    as it is with C's "&" but more a function which returns the address of where the particular entity is
    at run-time.

    To achieve the same functionality in PASM is just a matter of adding an offset to the pointer to that
    entity but the difficulty comes in obtaining such an offset in the first place.

    For ones own PASM code that's not too much of a problem as the design can be to allow the offset
    to be determined and passed in using a parameter block via PAR. The real difficulty comes when it
    is replacement PASM for an existing routine and needs this information passing in but no means of
    doing so if how the user calls the PASM remains unchanged. This is particularly the case with any
    home-grown Spin Interpreter.
  • mparkmpark Posts: 1,305
    edited 2008-09-19 14:54
    New version (0.16) in first post.

    Includes @@@ and fixes the SD demo bug that Ale reported.

    I just realized that @@@ could mess up duplicate object detection, at least the way I do it. Highly improbable though. Please keep hammering on @@@ to make sure it does what you think it should do. Meanwhile I'll add an option to disable duplicate object detection.

    Edit: The more I think about it, the more opportunities I see for @@@ and duplicate object detection to mess up. It's a real bug.

    I'm pulling 0.16 and will post a new version as soon as I put in a fix.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Michael Park

    PS, BTW, and FYI:
    To search the forum, use search.parallax.com (do not use the Search button).
    Check out the Propeller Wiki: propeller.wikispaces.com/

    Post Edited (mpark) : 9/19/2008 3:36:39 PM GMT
  • mparkmpark Posts: 1,305
    edited 2008-09-19 17:43
    Version 0.17 in first post. If your Spin program uses @@@, Homespun will not eliminate duplicate objects based on their bytecode. If your program contains multiple references to the same object filename, only one copy of the object will end up in the compiled program. That always happens, whether @@@ is present or not. This change only affects those cases where two objects with different filenames coincidentally compile to identical bytecode.

    Would anyone object to changing "@@@" to "&"? When I saw hippy's post, I realized that a lot of my confusion could have been avoided if I'd just recognized the correspondence with C's &.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Michael Park

    PS, BTW, and FYI:
    To search the forum, use search.parallax.com (do not use the Search button).
    Check out the Propeller Wiki: propeller.wikispaces.com/
Sign In or Register to comment.