hippy said...
I hated PICmicro assembler so much, unable to get my head round "SUBWF fred,F" etc, that I designed a simple language which looks similar to what you have; "fred = fred-W" for the above...
You don't need to abandon operator precedence just because you don't have a stack. The first Fortran compilers were run on processors that had no stack, yet still honored operator precedence.
I was kind of hoping you were going in the direction of a pre-parser that would change some higher-level code into PASM in place. Then this could be sent into Prop Tool. That would be the simplest thing for noobs. If your higher-level language were very similar to SPIN, it would be even better. It would allow many more people familiar with SPIN to overcome the fear of PASM programming.
However, by reading this thread it looks like you guys are going in a different direction altogether.
I like coding PASM directly, but it would be nice to have a higher level language that I could use to throw together some PASM code and then go back in and tweak it.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ·"I have always wished that my computer would be as easy to use as my telephone.· My wish has come true.· I no longer know how to use my telephone."
@Mark,
I think the recursive-descent expression algorithm has most of us spoiled for arithmetic order of ops parsing.
A Spin-Cognac might be limited, but it would allow some easing-in to pasm for many people who visit here.
Heater has a point that allowing the designer to take his approach would allow for faster development results.
If it's too complicated or time consuming, it's easy to just skip on to other interesting things.
Good morning all, I've had a night to sleep on all this. Didn't come up with anything except that COGNAC has to be kept simple. I don't want to be biting off more than I can chew.
@jazzed: I'm starting to think having the SPIN to COG interface generated by COGNAC is a must . i.e. it creates a Spin section containing a stub method to get the COGNAC loaded, parameters passed, and COG started. An author of a COGNAC program should not have to mess around with "par". Starting out with SPIN and PASM getting parameters into PASM was one of the few pains I felt. I'm not into "library API-able" or any kind of binary object linking.
@mpark: What's that link? Won't work for me.
@Mark: True a stack in the CPU hardware is not actually required. You can always create a stack in software, all be it slower. I have done that with my LMM generating compiler experiment. It is my understanding that early compilers were written in a time when CPUs may not have had hardware stacks and programmers had not really got the idea of recursion. However they wanted operator precedence and parenthesis in expressions. The result was that they went through enormous contortions in compilers to get them. Implementing multiple stacks, one for operators, one for variables etc. And coming up with complex algorithms to achieve the goal. In general I believe precedence and parenthesis requires a stack of some kind some where. One can of course limit the depth of stack, in the COGs case severly, at which point you may as well just "rejig-temporaries" as Hippy says. So you end up with (A+B) * (C+D) allowed but say (X+Y) & ((A+B) * (C+D)) fails to compile.
@Ken Peterson + mctrivia + Closo No, No don't worry. COGNAC will in its first incarnation and forever after that will have an output that is a human readable .spin file containing PASM source code in a DAT section with Spin PUB method to handle start up. Just drop that into a Prop Tool project and hit F10.
I'm kind of taken with this idea of ending up using COGNAC as a kind of pseudo code in which you write a first draft of your required program, it gets you up and running with Spin to "par" parameters etc all sorted out. Then, as you say, start tweaking the generated PASM or better still replacing COGNAC source statements with in line PASM.
When/if we get into "binary blob" output it will be an option. After all it's much easier to develop COGNAC with human readable output.
I will borrow Spin operators and some Spin features so it's familiar to those who've learned Spin but will NOT use white space block delimiting.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
With all of this discussion about passing parameters and using PAR, I thought I'd point out that I almost never use PAR to pass parameters.· I just set up a bunch of pointers in DAT using a SPIN init() routine,·and then have them loaded into the cog along with the program.· It's much easier in my opinion.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ·"I have always wished that my computer would be as easy to use as my telephone.· My wish has come true.· I no longer know how to use my telephone."
Here's some code illustrating how I usually pass parameters to PASM. Might be useful for COGNAC?
{{
demo for passing parameters to PASM
}}
con
array_length = 10
var
long array1[noparse][[/noparse]array_length]
long count
byte cog
pub init
array1_ptr := @array1
count_ptr := @count
initval_ptr := @initval
count := array_length
cog := cognew(@pasm_start, 0) + 1
dat
pasm_start 'pasm code here
rdlong loop_idx, count_ptr
:loop rdlong accumulator, array1_ptr
add array1_ptr, #4
'
' process accumulator value here
'
djnz loop_idx, #:loop
'
'
array1_ptr long 0
count_ptr long 0
initval long 0
initval_ptr long 0
accumulator long 0
loop_idx long 0
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ·"I have always wished that my computer would be as easy to use as my telephone.· My wish has come true.· I no longer know how to use my telephone."
Funny you mention Python Ken. For a fleeting moment today I thought about using Python as the implementation language for COGNAC. It's plenty cross-platform enough. Having had a go at writing parsers and such in C, Pascal and ADA (God help me) I was wondering what next?
You see, despite my loathing of white space syntax, Python quite won me over when I used it professionally a couple of years ago. Perhaps that's because I was doing things with it that would otherwise have been done in Perl or Bash. Both of which give me terrible headache to look at. Python has this property of being, well, pretty. Something like Python makes rapid prototyping a breeze and would make symbol tables and such a doddle . There would be no hassle with having to create and maintain Linux, Windows, Mac... versions.
There is always Java, blech...
A neat trick would be to write the COGNAC compiler in Spin. Then we could do self hosted development on the Propeller.
I digress...
I'm quite taken with your Spin to PAM parameter passing idea. In fact that's how I did it for a couple of things in my old 8080 emulator without really thinking about it. "par" What's that?
I had kind of discounted it on the grounds that the COGNAC output could be a binary blob so any loader would not know where to put he parameter pointers, but of course we could formalize the location of parameters and a count of the number of parameters.
And even better if we are emitting Spin stub code to load parameters, in your style, it could just poke the parameters into the binary blob as it would know where they are where ever they happen to be.
Your method dispenses with any need for PASM code to fetch parameters from "par" saving valuable LONG space.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
First a caveat; I know nothing at all about Propeller assembler other than having read the manual. However I do have a lot of compiler experience and have designed or extended two commercially available languages (third underway). I would offer the following comments to consider:
Stack impact on compiler design:
a) The lack of hardware stack does not really mandate any particular HL language restrictions; a software maintained stack has the same functionality. I can outline some techniques for this if useful
b) Even when a stack is available most good HLL compilers do everything they can to avoid using it; careful allocation of global resources almost -always- produces better results. EG: the JPI compilers of the early nineties led the field in Intel code generation for a long time primarily by using registers rather than the stack to pass parameters.
c) For expression evaluation the rule of thumb is: if you have a single accumulator and no swap instruction you need one more temporary than the nesting level you support. If you have multiple accumulators or a swap instruction then the number of temporaries is the nesting level. Supporting operator precedence counts as adding one level of nesting.
d) If you do not allow function calls as part of an expression (other than the ultra simple T := MyFunctionCall) then the temporaries may be global
Language Design:
#1 rule - decide WHAT you are trying to do before you figure out how to do it.
In fact based upon most of your comments it is not entirely clear that you want a High-Level language at all. Rather it sounds like you want something from that often neglected category of Low-Level language. Something intermediate between assembler (instruction set fixed in hardware) and high level language (machine independant).
Most people fake a Low Level language using a macro-assembler; that is not an option to ignore. Macro-assemblers can be terrific if all you really want to do is 'orthogonalize' an instruction set a little bit. It is also more than probable that you can simply find the 'macro' part of a macro assembler on the web and simple write the macros for it.
However if the Macro Assembler doesn't cut it you may still find that adding 'imaginary opcodes' to the assembler you already have gets you to where you want to be. Alternatively simply cleaning up the opcode syntax a little (which is 'interesting') may give you what you need.
Back to HLL - things you may wish to look at
Whilst I don't have deep experiance of the Propeller a quick look at the specs remind me very much of the transputer of the late eighties. In particular it had the same stack issues, quirky memory space and emphasis on parallel processing. There was a 'highish' level language designed for it called Occam - you may wish to check it out.
It has most of the parallel processing things you may wish built in which might give a nice paradigm for cog management.
Weirdly, one of the main things people hated about occam was the fact that indentation was a significant indicator of block structure - which is, of course, a feature of spin.
Anyhow, hope the above helped, or at least gave some food for thought.
David, It's great to have a professional looking over this. I will have to take a little while to absorb all you have said in your carefully thought out post.
Be aware that I am a total ignoramus when it comes to compiler writing. I did work my way through Jack Crenshaw's famous series 'Let's build a compiler' some time ago ending up with a version of his TINY language implemented in C and generating x86 assembler. His was Pascal and 68000. I also added types to it, signed/unsigned BYTE, WORD, LONG. Recently I created another version of a TINY like language emitting Propeller PASM for running under a Large Memory Model LMM kernel. It is very crude and inefficient and will probably not progress further.
So that's where I'm at. I'm still in awe of the fact that I can turn text into code that runs at all [noparse]:)[/noparse] Compiles have been a total mystery to me for such a long time. Mostly still are.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
Some comments on Occam and similar multi-processing languages ...
The Propeller is quite different from the Transputer in that the Propeller has 8 pretty much independent identical processors with a few shared resources like hub ram, the I/O pins, a common clock, and a pool of locks. There's no provision for executing short fragments of code on an available processor although this, like many other features, can be provided through an interpreter of some sort (but not in native code).
As I've mentioned before, I like PL360 as a model for this except that the underlying processor is very different. The biggest difficulty is how to handle pointer dereferencing (and subscripts) using self-modifying code. It's quite possible to just generate the code to handle this and hide it from the programmer. In PL360, a subscript could be a constant or a register or a register plus or minus a constant with the constant worked into the address in the instruction. In the case of the Propeller, it could be the same except using any variable instead of the 360's registers. This would generate a MOVD or MOVS for the variable followed by any necessary ADD or SUB. In the case of the destination field, the constant(s) would need to be shifted and compiled as long constants rather than immediate values.
It would be nice to have provisions for explicit use of the conditional execution flags and result flags, but I'm not sure what sort of notation would work for the majority of cases. Some kind of "pragma" might work using brackets, maybe like {}. For the most common cases, simply embedding their use in the various operators and statements would work like IF XXX < 5 THEN <stmt>. The compiler could do some simple optimization unless asked not to do it and figure out whether a conditional jump or just using conditional execution would be more efficient. If <stmt> compiles into a single or maybe two instructions, then conditional execution would be used.
Mike,
Obviously·my statement was subjective! However, having looked again, the similarities run pretty deep. Whilst the transputer did have common ram between all processes the assembly language was not really configured to look that way. You essentially had your three local registers and your workspace pointer (a bit like PAR); and your workspace was really supposed to be your workspace only! (Of course the transputer didn't have memory protection so we did find ways to wriggle out of our workspace [noparse]:)[/noparse] )
Also, of course, transputers were not really designed to be used solo. Most transputer programs were written with a view to some of the extra tasks being shipped down one of the links to another transputer which then became 'shared almost nothing'.
For anyone interested, the description of the transputer on Wiki is pretty good.
I am slightly surprised by one thing you said though. The description of COGINIT in the Prop assembler reference LOOKS like it is precisely starting up a new cog with a designated piece of code and designated scratchpad to store the results. If that isn't what it is doing - what IS it doing?
David
Mike Green said...
Some comments on Occam and similar multi-processing languages ...
The Propeller is quite different from the Transputer in that the Propeller has 8 pretty much independent identical processors with a few shared resources like hub ram, the I/O pins, a common clock, and a pool of locks. There's no provision for executing short fragments of code on an available processor although this, like many other features, can be provided through an interpreter of some sort (but not in native code).
then my Low Level langauge idea might be an excellent (and quick) place to start. It is moderately trivial to write a program which inputs an assembly language file and then outputs an identical file - this is the first pass at writing a low level language compiler! Then every time you find yourself writing a piece of assembler and cursing because you don't like the syntax - invent a new piece of syntax you do like - and then modify your 'pass through' program to translate what you DO like into what you didn't like ...
Warning: you will not produce the next Pascal, C++ or Algol using this approach - but you MAY actually get what you want [noparse]:)[/noparse]
If you really want a HLL then I always start the same way. Writing programs in my new language. Once I have written at least half a dozen programs in my new language - then I worry about producing a compiler for it [noparse]:)[/noparse]
If you really want to go High Level, then perhaps you can design a language that will be compiled to either SPIN or PASM, depending on what makes the most sense for a particular section of code. I imagine this would be a daunting task, but it would be the ultimate way to program the Propeller!
I have always thought of SPIN as a fairly low-level language. Just one step up from assembler. I has to be in order for the interpreter to fit in 496 longs!
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ·"I have always wished that my computer would be as easy to use as my telephone.· My wish has come true.· I no longer know how to use my telephone."
David, I'm still mulling over your post to which I will reply later but I want to chime in here:
The transputer may not look like a Prop on its own but as David says it was not designed to be solo. If they could have put 8 CPUs on a chip at the time I'm sure they would have. The whole system of the Transputer + Occam was based on the idea of Communicating Sequential Processes (CSP) as expounded by Tony Hoare. Hence the PARallel construct in Occam and the communication links and the idea of NO shared RAM. Now technology has moved on and we have the Prop (among others) with true Parallelism on chip. Sadly the Prop lacks the communication links.
David, the thing about COGINIT is that you point it at a junk of PASM code + data, up to 496 LONGs, in HUB memory. It then loads all of that into the COG register space from where it runs. When its up and running the COG has to use special rdlong/wrlong instructions to get anything out of HUB RAM. In doing so it has to wait for it's access time slot as HUB memory is shared round robin style. Ans here is the rub for your PASM program is limited to 496 LONGs including any you use for variables. COGNAC has to work in that space.
David, I like your Low Level Language idea. Thing is I never find myself cursing PASM syntax. It's so simple and regular unlike many assemblers I have used. If I start a low level language the way you describe I might never progress from PASM in PASM out[noparse]:)[/noparse]
So why create COGNAC if PASM is so easy? I have to think about that. Oh yeah, there is the messy issue of getting parameters in to a COG and setting up shared data areas in HUB RAM which seems to be done differently by everyone in an adhoc way. And high on my list is the issue of communicating COG to COG (PASM to PASM) through shared HUB RAM. Currently that does not happen much except inside multi COG drivers. For example if I want to use the FullDuplexSerial object I am give a Spin interface to it. Fine but I want to send/receive bytes from PASM so now I have to study the driver to find out where and how to poke data. Yuck. I want COGNAC to take care of this nicely somehow. Begins to sound like CSP doesn't it.
Are there ANY driver objects out there with published interfaces for PASM clients?
There are some things in the surrounding environment, (In Spin, in the Prop tool etc) that annoy me though.
"Writing programs in my new language." Is something I was just pondering. It's easy to invent syntax and BNF but what will it look like when it's used to write a program that actually does something useful ? It occurred to me to take the most gacky looking PASM I've written, think about what it does and how I would like to write that clearly, and then think about how to turn what I would write into that gacky looking (but efficient) piece of PASM. Actually I guess that is an example of you Low Level Language design technique.
Ken, no way am I aiming that high. I guess we could debate what we mean by "High Level Language". I'm of the old school,
if I can write "A := B + C + 3" and have it produce:
MOV A,B
ADD A, C
ADD A,#3
Then that's "high level".
Cheers all.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
heater: If you can find a way to call SPIN code from PASM code, then you'll really be onto something. It almost always has to be done via some sort of message passing mechanism. Also, referencing objects in PASM code would pretty much amount to adding some type of header to be included in the DAT section (no encapsulation) so it wouldn't work like a SPIN object. I suppose if someone can come up with a message passing "object" that sets up some standardized scheme, then some standard interface code can be included in any PASM program that would need to use the message passing system. There would have to be some way of registering message senders and receivers, etc.
But this is getting way beyond a compiler. More like an operating system.
This does have me thinking....·· You could have a centralized message queue that all cogs can subscribe to, or you could have individual message queues for one-to-one communication.· An object that might possibly be used by PASM code would have to use this messaging system (which could be included as a singleton object).· I'm just wondering how an object can provide PASM code to be loaded into a cog while still preserving encapsulation.· Hmmm....lots of details to work out.
kcp
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ·"I have always wished that my computer would be as easy to use as my telephone.· My wish has come true.· I no longer know how to use my telephone."
- Bjarne Stroustrup
Post Edited (Ken Peterson) : 2/11/2009 8:58:23 PM GMT
Ken, calling Spin from CONAC is way off the radar, at least for now. Is it even possible in any no horrible way?
Any kind of intermediate message passing object is also out, to slow, probably eats a valuable COG and gets way more complicated than I like.
I had a feeling this whole PASM to PASM communication thing could get far bigger and more complicated than I like. But lets take a simple and frequent case as an example of what I would like to tackle:
Lets say you write a super duper new serial driver and I want to try it out.
So I ask "how do I interface to this driver?"
You say "easy just include this Spin object in your project and call superdriver.tx(abyte) etc".
I say "how can I do that from PASM?"
You say "ah wait a minute...Oh yeah drop your byte into this buffer using this index, advance the index and then write the command TX into the command LONG at the start of the data block."
I say "OK where is this shared data blcok in the HUB?"
You say "Wait a minute..Oh yeah, well the thing is the command block and FIFO are private to my driver but I can add a spin function to return the pointer to it."
I say "OK How do I access that from PASM"
You say "Wait a minute......
Now what I would like to see is:
Super duper driver is written in COGNAC, possibly with its main work done in an embedded PASM block.
My client is written in COGNAC also with optional PASM block.
BUT the interface part is defined in COGNAC.
So you would say "Easy, just compile my CONAC driver together with yours and the drop the resulting output (.spin file) into the Prop Tool.
I say "Wow that was easy, great driver by the way"
How achieve we do that ?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
Having declared it you can now invoke it, maybe with:
MyProcess(6,2,Mylocalvar,pipename)
The process can then be defined with
MyPROCESS PROCESS
.... normal assembly goes here ... the 'local' variables a,b,c all exist for me already and somehow magically started with values already filled in ....
ENDPROCESS
This deals with all of the cog-init / copy from par / add on the number you first thought of stuff ...
Now I think you may also want to be able to declare a PIPE which acts as an IPC area in HUB ram. It would be declared as:
and could be passed into processes as above. In the assembler you would then have two or·four new 'opcodes':
READ LocalVariable, Pipe ! Blocks if nothing avaialble
WRITE LocalVariable, Pipe ! Blocks if no space available
READREADY sets a flag (or jumps) to indicate buffer availability
WRITEREADY sets a flag (or jumps) to indicate buffer availability
I think that gives you everything you have mentioned.... the parsing is also fairly trivial. You would need to spend some time working on the assembler for the pipe implementation (probably want to have little library routines to call) and you would want to think about the right way to pass parameters. But in terms of LANGUAGE the above is fairly trivial.
David, sounds good. So its back to the PASM in PASM out compiler and the add bits of high level something where we want to "tidy up".
As a another simple case in my Z80 emulator, which uses 4 COGs for CPU emulation and at least 1 more to handle Z80 INs/OUTs, there is in the emulation of OUT a need to pass data to the IO handling COG and it looks like this:
wrbyte data_8, io_port
rdbyte data_8, a_reg 'Write output data from accumulator
wrbyte data_8, io_data
mov data_8, #io_cmd_out 'Set I/O command to OUT
wrbyte data_8, io_command
:io_wait rdbyte data_8, io_command wz
if_nz jmp :io_wait
Where io_xxxx are pointers to HUB variables. The IO handling COG constantly looks at io_command and when it is non zero looks at the port and data to decide what to do. Send data to the console or disk emulation etc.
What I'd rather do here is just write "io.out(port, data);" or some such.
So where does the code that implements io.out come from?. Well that is written by the author of the IO COGs code after all he "owns" the io shared HUB variables. So he writes somewhere:
interface out (port, data)
wrbyte data_8, io_port
rdbyte data_8, a_reg 'Write output data from accumulator
wrbyte data_8, io_data
mov data_8, #io_cmd_out 'Set I/O command to OUT
wrbyte data_8, io_command
:io_wait rdbyte data_8, io_command wz
if_nz jmp :io_wait
end_interface
And that PASM some how gets exported into my client COGs code when called. If the io.out interface changes I, as a client, don't have to know. In a way it becomes a little library routine but at the PASM source level.
But how do the io_xx pointers get into the client COG? By some magic in the process header as you say. In fact in this case the writer of the client COG code does not even want to know about those pointers. They should just magically appear in the compiled PASM output appropriately set up.
I like the idea of pipes. A standard simple interface. Only problem would be the speed of reading/writing one LONG or whatever at a time perhaps.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
Heater,
Have to head so I've only given your posting a brief glance but I think the transfering of data could could still be handled by a pipe. Perhaps the Write localvariable,pipe was wrong. Perhaps it should have been Write Pipe, localvar1, localvar2 etc - that way you can pass as much as you want it one go. If I've understood your code the IO control piece goes away as the READ is auto-blocking ....
The upside is that NOONE has to write the interface code - you are just reading and writing to a pipe which are now 'native' functions of your 'new and improved' PASM
(I love low level languages - they allow me to build microprocessors without having to ever touch silicon !)
David, as I said I like the pipe idea. If the nobody has to write the code at each end that's great.
Blocking or not? Is a good point. Quite often non-blocking s required, for example when looking to see if a key hit has arrived whilst doing something else.
That IO example of mine blocks until the IO subsystem has done something, however when debugging I comment out that wait loop some times.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
Here's a thought while passing by ( I'll have to catch up on the other posts later ), but when I
did my 'high-level assembler language' I took the approach of "what would the 'assembler' be
and easy to use for a novice who understands coding", so it was evolving the high-level from
the low-level ( then write a compiler for that ) rather then design a high-level language and
try and work out what it should compile for, so for PASM you could have things like ...
tick++
If ( Carry ) Then count := count+1
Long[noparse][[/noparse]ptr] := count
All of which translate into one PASM opcode each. Now doesn't that look like Basic which anyone
touching a Propeller for the first time is likely to know. If that's offensive then drop the "Then",
add a semicolon at the end of each line, sprinkle with braces and it looks like C. Easy. You can
go insane and add System.Parallax.Propeller. as a prefix to everything and call it PASM.Net
Hippy, you have hit the nail on the head. "Evolving the high level from the low level"
I tried to make that objective clear at the start when talking about imagining COGs were the only computer we a have and we have not invented the stack yet and how to make use of PASM features in the high level constructs. Perhaps I did not make that clear enough.
For sure the opposite of dreaming up a language like ADA and then spending a billion man hours trying to coerce it into existence and ending up with a horribly inefficient nightmare.
Your simple code snippet is an excellent example. Do some operation, "tick++", and there is a "carry" variable waiting for you and "parity" as well.
? or what about cases where you want to preserve the existing carry?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ·"I have always wished that my computer would be as easy to use as my telephone.· My wish has come true.· I no longer know how to use my telephone."
Comments
However, by reading this thread it looks like you guys are going in a different direction altogether.
I like coding PASM directly, but it would be nice to have a higher level language that I could use to throw together some PASM code and then go back in and tweak it.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
·"I have always wished that my computer would be as easy to use as my telephone.· My wish has come true.· I no longer know how to use my telephone."
- Bjarne Stroustrup
I think the recursive-descent expression algorithm has most of us spoiled for arithmetic order of ops parsing.
A Spin-Cognac might be limited, but it would allow some easing-in to pasm for many people who visit here.
Heater has a point that allowing the designer to take his approach would allow for faster development results.
If it's too complicated or time consuming, it's easy to just skip on to other interesting things.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
My 2c
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Links to other interesting threads:
· Home of the MultiBladeProps (SixBladeProp)
· Prop Tools under Development or Completed (Index)
· Emulators (Micros eg Altair, and Terminals eg VT100) - index
· Search the Propeller forums (via Google)
My cruising website is: ·www.bluemagic.biz
@jazzed: I'm starting to think having the SPIN to COG interface generated by COGNAC is a must . i.e. it creates a Spin section containing a stub method to get the COGNAC loaded, parameters passed, and COG started. An author of a COGNAC program should not have to mess around with "par". Starting out with SPIN and PASM getting parameters into PASM was one of the few pains I felt. I'm not into "library API-able" or any kind of binary object linking.
@mpark: What's that link? Won't work for me.
@Mark: True a stack in the CPU hardware is not actually required. You can always create a stack in software, all be it slower. I have done that with my LMM generating compiler experiment. It is my understanding that early compilers were written in a time when CPUs may not have had hardware stacks and programmers had not really got the idea of recursion. However they wanted operator precedence and parenthesis in expressions. The result was that they went through enormous contortions in compilers to get them. Implementing multiple stacks, one for operators, one for variables etc. And coming up with complex algorithms to achieve the goal. In general I believe precedence and parenthesis requires a stack of some kind some where. One can of course limit the depth of stack, in the COGs case severly, at which point you may as well just "rejig-temporaries" as Hippy says. So you end up with (A+B) * (C+D) allowed but say (X+Y) & ((A+B) * (C+D)) fails to compile.
@Ken Peterson + mctrivia + Closo No, No don't worry. COGNAC will in its first incarnation and forever after that will have an output that is a human readable .spin file containing PASM source code in a DAT section with Spin PUB method to handle start up. Just drop that into a Prop Tool project and hit F10.
I'm kind of taken with this idea of ending up using COGNAC as a kind of pseudo code in which you write a first draft of your required program, it gets you up and running with Spin to "par" parameters etc all sorted out. Then, as you say, start tweaking the generated PASM or better still replacing COGNAC source statements with in line PASM.
When/if we get into "binary blob" output it will be an option. After all it's much easier to develop COGNAC with human readable output.
I will borrow Spin operators and some Spin features so it's familiar to those who've learned Spin but will NOT use white space block delimiting.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
With all of this discussion about passing parameters and using PAR, I thought I'd point out that I almost never use PAR to pass parameters.· I just set up a bunch of pointers in DAT using a SPIN init() routine,·and then have them loaded into the cog along with the program.· It's much easier in my opinion.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
·"I have always wished that my computer would be as easy to use as my telephone.· My wish has come true.· I no longer know how to use my telephone."
- Bjarne Stroustrup
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
·"I have always wished that my computer would be as easy to use as my telephone.· My wish has come true.· I no longer know how to use my telephone."
- Bjarne Stroustrup
You see, despite my loathing of white space syntax, Python quite won me over when I used it professionally a couple of years ago. Perhaps that's because I was doing things with it that would otherwise have been done in Perl or Bash. Both of which give me terrible headache to look at. Python has this property of being, well, pretty. Something like Python makes rapid prototyping a breeze and would make symbol tables and such a doddle . There would be no hassle with having to create and maintain Linux, Windows, Mac... versions.
There is always Java, blech...
A neat trick would be to write the COGNAC compiler in Spin. Then we could do self hosted development on the Propeller.
I digress...
I'm quite taken with your Spin to PAM parameter passing idea. In fact that's how I did it for a couple of things in my old 8080 emulator without really thinking about it. "par" What's that?
I had kind of discounted it on the grounds that the COGNAC output could be a binary blob so any loader would not know where to put he parameter pointers, but of course we could formalize the location of parameters and a count of the number of parameters.
And even better if we are emitting Spin stub code to load parameters, in your style, it could just poke the parameters into the binary blob as it would know where they are where ever they happen to be.
Your method dispenses with any need for PASM code to fetch parameters from "par" saving valuable LONG space.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
Stack impact on compiler design:
a) The lack of hardware stack does not really mandate any particular HL language restrictions; a software maintained stack has the same functionality. I can outline some techniques for this if useful
b) Even when a stack is available most good HLL compilers do everything they can to avoid using it; careful allocation of global resources almost -always- produces better results. EG: the JPI compilers of the early nineties led the field in Intel code generation for a long time primarily by using registers rather than the stack to pass parameters.
c) For expression evaluation the rule of thumb is: if you have a single accumulator and no swap instruction you need one more temporary than the nesting level you support. If you have multiple accumulators or a swap instruction then the number of temporaries is the nesting level. Supporting operator precedence counts as adding one level of nesting.
d) If you do not allow function calls as part of an expression (other than the ultra simple T := MyFunctionCall) then the temporaries may be global
Language Design:
#1 rule - decide WHAT you are trying to do before you figure out how to do it.
In fact based upon most of your comments it is not entirely clear that you want a High-Level language at all. Rather it sounds like you want something from that often neglected category of Low-Level language. Something intermediate between assembler (instruction set fixed in hardware) and high level language (machine independant).
Most people fake a Low Level language using a macro-assembler; that is not an option to ignore. Macro-assemblers can be terrific if all you really want to do is 'orthogonalize' an instruction set a little bit. It is also more than probable that you can simply find the 'macro' part of a macro assembler on the web and simple write the macros for it.
However if the Macro Assembler doesn't cut it you may still find that adding 'imaginary opcodes' to the assembler you already have gets you to where you want to be. Alternatively simply cleaning up the opcode syntax a little (which is 'interesting') may give you what you need.
Back to HLL - things you may wish to look at
Whilst I don't have deep experiance of the Propeller a quick look at the specs remind me very much of the transputer of the late eighties. In particular it had the same stack issues, quirky memory space and emphasis on parallel processing. There was a 'highish' level language designed for it called Occam - you may wish to check it out.
It has most of the parallel processing things you may wish built in which might give a nice paradigm for cog management.
Weirdly, one of the main things people hated about occam was the fact that indentation was a significant indicator of block structure - which is, of course, a feature of spin.
Anyhow, hope the above helped, or at least gave some food for thought.
Be aware that I am a total ignoramus when it comes to compiler writing. I did work my way through Jack Crenshaw's famous series 'Let's build a compiler' some time ago ending up with a version of his TINY language implemented in C and generating x86 assembler. His was Pascal and 68000. I also added types to it, signed/unsigned BYTE, WORD, LONG. Recently I created another version of a TINY like language emitting Propeller PASM for running under a Large Memory Model LMM kernel. It is very crude and inefficient and will probably not progress further.
So that's where I'm at. I'm still in awe of the fact that I can turn text into code that runs at all [noparse]:)[/noparse] Compiles have been a total mystery to me for such a long time. Mostly still are.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
The Propeller is quite different from the Transputer in that the Propeller has 8 pretty much independent identical processors with a few shared resources like hub ram, the I/O pins, a common clock, and a pool of locks. There's no provision for executing short fragments of code on an available processor although this, like many other features, can be provided through an interpreter of some sort (but not in native code).
As I've mentioned before, I like PL360 as a model for this except that the underlying processor is very different. The biggest difficulty is how to handle pointer dereferencing (and subscripts) using self-modifying code. It's quite possible to just generate the code to handle this and hide it from the programmer. In PL360, a subscript could be a constant or a register or a register plus or minus a constant with the constant worked into the address in the instruction. In the case of the Propeller, it could be the same except using any variable instead of the 360's registers. This would generate a MOVD or MOVS for the variable followed by any necessary ADD or SUB. In the case of the destination field, the constant(s) would need to be shifted and compiled as long constants rather than immediate values.
It would be nice to have provisions for explicit use of the conditional execution flags and result flags, but I'm not sure what sort of notation would work for the majority of cases. Some kind of "pragma" might work using brackets, maybe like {}. For the most common cases, simply embedding their use in the various operators and statements would work like IF XXX < 5 THEN <stmt>. The compiler could do some simple optimization unless asked not to do it and figure out whether a conditional jump or just using conditional execution would be more efficient. If <stmt> compiles into a single or maybe two instructions, then conditional execution would be used.
Obviously·my statement was subjective! However, having looked again, the similarities run pretty deep. Whilst the transputer did have common ram between all processes the assembly language was not really configured to look that way. You essentially had your three local registers and your workspace pointer (a bit like PAR); and your workspace was really supposed to be your workspace only! (Of course the transputer didn't have memory protection so we did find ways to wriggle out of our workspace [noparse]:)[/noparse] )
Also, of course, transputers were not really designed to be used solo. Most transputer programs were written with a view to some of the extra tasks being shipped down one of the links to another transputer which then became 'shared almost nothing'.
For anyone interested, the description of the transputer on Wiki is pretty good.
I am slightly surprised by one thing you said though. The description of COGINIT in the Prop assembler reference LOOKS like it is precisely starting up a new cog with a designated piece of code and designated scratchpad to store the results. If that isn't what it is doing - what IS it doing?
David
then my Low Level langauge idea might be an excellent (and quick) place to start. It is moderately trivial to write a program which inputs an assembly language file and then outputs an identical file - this is the first pass at writing a low level language compiler! Then every time you find yourself writing a piece of assembler and cursing because you don't like the syntax - invent a new piece of syntax you do like - and then modify your 'pass through' program to translate what you DO like into what you didn't like ...
Warning: you will not produce the next Pascal, C++ or Algol using this approach - but you MAY actually get what you want [noparse]:)[/noparse]
If you really want a HLL then I always start the same way. Writing programs in my new language. Once I have written at least half a dozen programs in my new language - then I worry about producing a compiler for it [noparse]:)[/noparse]
I have always thought of SPIN as a fairly low-level language. Just one step up from assembler. I has to be in order for the interpreter to fit in 496 longs!
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
·"I have always wished that my computer would be as easy to use as my telephone.· My wish has come true.· I no longer know how to use my telephone."
- Bjarne Stroustrup
The transputer may not look like a Prop on its own but as David says it was not designed to be solo. If they could have put 8 CPUs on a chip at the time I'm sure they would have. The whole system of the Transputer + Occam was based on the idea of Communicating Sequential Processes (CSP) as expounded by Tony Hoare. Hence the PARallel construct in Occam and the communication links and the idea of NO shared RAM. Now technology has moved on and we have the Prop (among others) with true Parallelism on chip. Sadly the Prop lacks the communication links.
David, the thing about COGINIT is that you point it at a junk of PASM code + data, up to 496 LONGs, in HUB memory. It then loads all of that into the COG register space from where it runs. When its up and running the COG has to use special rdlong/wrlong instructions to get anything out of HUB RAM. In doing so it has to wait for it's access time slot as HUB memory is shared round robin style. Ans here is the rub for your PASM program is limited to 496 LONGs including any you use for variables. COGNAC has to work in that space.
David, I like your Low Level Language idea. Thing is I never find myself cursing PASM syntax. It's so simple and regular unlike many assemblers I have used. If I start a low level language the way you describe I might never progress from PASM in PASM out[noparse]:)[/noparse]
So why create COGNAC if PASM is so easy? I have to think about that. Oh yeah, there is the messy issue of getting parameters in to a COG and setting up shared data areas in HUB RAM which seems to be done differently by everyone in an adhoc way. And high on my list is the issue of communicating COG to COG (PASM to PASM) through shared HUB RAM. Currently that does not happen much except inside multi COG drivers. For example if I want to use the FullDuplexSerial object I am give a Spin interface to it. Fine but I want to send/receive bytes from PASM so now I have to study the driver to find out where and how to poke data. Yuck. I want COGNAC to take care of this nicely somehow. Begins to sound like CSP doesn't it.
Are there ANY driver objects out there with published interfaces for PASM clients?
There are some things in the surrounding environment, (In Spin, in the Prop tool etc) that annoy me though.
"Writing programs in my new language." Is something I was just pondering. It's easy to invent syntax and BNF but what will it look like when it's used to write a program that actually does something useful ? It occurred to me to take the most gacky looking PASM I've written, think about what it does and how I would like to write that clearly, and then think about how to turn what I would write into that gacky looking (but efficient) piece of PASM. Actually I guess that is an example of you Low Level Language design technique.
Ken, no way am I aiming that high. I guess we could debate what we mean by "High Level Language". I'm of the old school,
if I can write "A := B + C + 3" and have it produce:
MOV A,B
ADD A, C
ADD A,#3
Then that's "high level".
Cheers all.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
But this is getting way beyond a compiler. More like an operating system.
This does have me thinking....· · You could have a centralized message queue that all cogs can subscribe to, or you could have individual message queues for one-to-one communication.· An object that might possibly be used by PASM code would have to use this messaging system (which could be included as a singleton object).· I'm just wondering how an object can provide PASM code to be loaded into a cog while still preserving encapsulation.· Hmmm....lots of details to work out.
kcp
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
·"I have always wished that my computer would be as easy to use as my telephone.· My wish has come true.· I no longer know how to use my telephone."
- Bjarne Stroustrup
Post Edited (Ken Peterson) : 2/11/2009 8:58:23 PM GMT
Any kind of intermediate message passing object is also out, to slow, probably eats a valuable COG and gets way more complicated than I like.
I had a feeling this whole PASM to PASM communication thing could get far bigger and more complicated than I like. But lets take a simple and frequent case as an example of what I would like to tackle:
Lets say you write a super duper new serial driver and I want to try it out.
So I ask "how do I interface to this driver?"
You say "easy just include this Spin object in your project and call superdriver.tx(abyte) etc".
I say "how can I do that from PASM?"
You say "ah wait a minute...Oh yeah drop your byte into this buffer using this index, advance the index and then write the command TX into the command LONG at the start of the data block."
I say "OK where is this shared data blcok in the HUB?"
You say "Wait a minute..Oh yeah, well the thing is the command block and FIFO are private to my driver but I can add a spin function to return the pointer to it."
I say "OK How do I access that from PASM"
You say "Wait a minute......
Now what I would like to see is:
Super duper driver is written in COGNAC, possibly with its main work done in an embedded PASM block.
My client is written in COGNAC also with optional PASM block.
BUT the interface part is defined in COGNAC.
So you would say "Easy, just compile my CONAC driver together with yours and the drop the resulting output (.spin file) into the Prop Tool.
I say "Wow that was easy, great driver by the way"
How achieve we do that ?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
Ok, sounds like process invocation & IPC are two of the major things you would like to tackle.
How about a language that is identical to PASM except :
You can define a PROCESS with input parameters - perhaps a bit like this -
More about PIPEs in a minute
MyProcess(a:long,b:byte,c:long,d[noparse]:p[/noparse]ipe);
Having declared it you can now invoke it, maybe with:
MyProcess(6,2,Mylocalvar,pipename)
The process can then be defined with
MyPROCESS PROCESS
.... normal assembly goes here ... the 'local' variables a,b,c all exist for me already and somehow magically started with values already filled in ....
ENDPROCESS
This deals with all of the cog-init / copy from par / add on the number you first thought of stuff ...
Now I think you may also want to be able to declare a PIPE which acts as an IPC area in HUB ram. It would be declared as:
MyPIPE·· PIPE(_possible_parameter_definining_buffering_length)
and could be passed into processes as above. In the assembler you would then have two or·four new 'opcodes':
READ LocalVariable, Pipe ! Blocks if nothing avaialble
WRITE LocalVariable, Pipe ! Blocks if no space available
READREADY sets a flag (or jumps) to indicate buffer availability
WRITEREADY sets a flag (or jumps) to indicate buffer availability
I think that gives you everything you have mentioned.... the parsing is also fairly trivial. You would need to spend some time working on the assembler for the pipe implementation (probably want to have little library routines to call) and you would want to think about the right way to pass parameters. But in terms of LANGUAGE the above is fairly trivial.
MACRO INTERPRETER / MACRO PROCESSOR--LEVEL1
.................................|
.................................|
.................................v
LEVEL2
.................................|
....................Spin-Like Language
.................................|
LEVEL3
................. Native Assembly Code
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
JMH
As a another simple case in my Z80 emulator, which uses 4 COGs for CPU emulation and at least 1 more to handle Z80 INs/OUTs, there is in the emulation of OUT a need to pass data to the IO handling COG and it looks like this:
Where io_xxxx are pointers to HUB variables. The IO handling COG constantly looks at io_command and when it is non zero looks at the port and data to decide what to do. Send data to the console or disk emulation etc.
What I'd rather do here is just write "io.out(port, data);" or some such.
So where does the code that implements io.out come from?. Well that is written by the author of the IO COGs code after all he "owns" the io shared HUB variables. So he writes somewhere:
And that PASM some how gets exported into my client COGs code when called. If the io.out interface changes I, as a client, don't have to know. In a way it becomes a little library routine but at the PASM source level.
But how do the io_xx pointers get into the client COG? By some magic in the process header as you say. In fact in this case the writer of the client COG code does not even want to know about those pointers. They should just magically appear in the compiled PASM output appropriately set up.
I like the idea of pipes. A standard simple interface. Only problem would be the speed of reading/writing one LONG or whatever at a time perhaps.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
JMH
Have to head so I've only given your posting a brief glance but I think the transfering of data could could still be handled by a pipe. Perhaps the Write localvariable,pipe was wrong. Perhaps it should have been Write Pipe, localvar1, localvar2 etc - that way you can pass as much as you want it one go. If I've understood your code the IO control piece goes away as the READ is auto-blocking ....
The upside is that NOONE has to write the interface code - you are just reading and writing to a pipe which are now 'native' functions of your 'new and improved' PASM
(I love low level languages - they allow me to build microprocessors without having to ever touch silicon !)
David
Blocking or not? Is a good point. Quite often non-blocking s required, for example when looking to see if a key hit has arrived whilst doing something else.
That IO example of mine blocks until the IO subsystem has done something, however when debugging I comment out that wait loop some times.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
you have the 'ready' variants so you can always know before entering the routine if it will block you
Read blocks waiting for intput
Write only blocks if the buffer is full
David
did my 'high-level assembler language' I took the approach of "what would the 'assembler' be
and easy to use for a novice who understands coding", so it was evolving the high-level from
the low-level ( then write a compiler for that ) rather then design a high-level language and
try and work out what it should compile for, so for PASM you could have things like ...
tick++
If ( Carry ) Then count := count+1
Long[noparse][[/noparse]ptr] := count
All of which translate into one PASM opcode each. Now doesn't that look like Basic which anyone
touching a Propeller for the first time is likely to know. If that's offensive then drop the "Then",
add a semicolon at the end of each line, sprinkle with braces and it looks like C. Easy. You can
go insane and add System.Parallax.Propeller. as a prefix to everything and call it PASM.Net
I tried to make that objective clear at the start when talking about imagining COGs were the only computer we a have and we have not invented the stack yet and how to make use of PASM features in the high level constructs. Perhaps I did not make that clear enough.
For sure the opposite of dreaming up a language like ADA and then spending a billion man hours trying to coerce it into existence and ending up with a horribly inefficient nightmare.
Your simple code snippet is an excellent example. Do some operation, "tick++", and there is a "carry" variable waiting for you and "parity" as well.
Actually my syntax would be more like:
tick++
if carry
count := count + 1
endif
Long[noparse][[/noparse]ptr] := count
No semicolons required. No white space indenting.
"PASM.net" Oh yeah....
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
tick++ WC
? or what about cases where you want to preserve the existing carry?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
·"I have always wished that my computer would be as easy to use as my telephone.· My wish has come true.· I no longer know how to use my telephone."
- Bjarne Stroustrup
foo, carry, zero := bar + 4
to get the result and flags out. Carry and zero would be reserved identifiers.
foo, carry := bar + 4
would leave the zero flag untouched
zero := bar + 4
Gets you the flag but no result.
Perhaps there are better constructs.
Next problem then is how to specify that "+" is ADD or ADDX (with carry)
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.