COGNAC - Cog Native Asm Compiler
heater
Posts: 3,370
There has been discussion here and there in the forum of a high level language compiler that would generate native assembler for COGs. The consensus is that this is not really practical for languages like C that rely on a stack and would like a lot more room than 496 instructions to do anything useful.
So the discussion turns this around to "Can we define a high level language that suits the COG instruction set?"
Lets assume that COGs are the only computers in the world and we have yet to invent the stack !
What are the consequences of this?
1. No stack means recursive function calls are not allowed. This is no loss as nobody ever uses recursion anyway and when you do you probably need more stack space than we have available.
2. No stack means no temporary store on the stack for local variables. This is no loss as it is only required for recursive calls which are not allowed by 1)
3. No stack means no parameter passing on the stack. Again no loss as per 2)
4. No stack means no operator precedence in expressions. Well what the hell, I can never remember the precedence of operators in C let alone SPIN which has so many more. Always end up using parenthesis.
5. No stack means no parenthesis in expression evaluation. Ah, well why can't we live with in order evaluation. If we want operations to happen in a specific order use more than one expression.
6. Don't know. Can anyone think of what else we loose without a stack? Possibly things like nested array indexing
x := a[noparse]/noparse]a[noparse][[/noparse]j for example. Haven't thought about arrays yet.
Expanding on points 2 and 3. Functions could still have variables that are local in scope and valid only during their "lifetime" and function calls with parameters could look syntactically like they do in C or Pascal or whatever. BUT locals and parameters would have to exist in some shared global store. That is, any given memory location from the shared pool could be used as a parameter or local for different functions at different times during execution.
Given that we have no recursion, the call graph for any program is a simple tree structure. So it must be possible for the compiler to workout which params and locals are in likely in use at any point in the code and reuse their memory space for other params/locals when they definitely are not.
So far so good. We can define a C-like or Pascal-like language with no stack and still be able to write useful code.
This morning I woke up to find my self thinking about what high level language constructs we could use or invent to make use of all the available COG opcodes. Firstly we can use all/most the operators of the operators of SPIN to make use of stuff like REV etc.
What about signed and unsigned arithmetic with or without carry/borrow? ADD, ADDS, ADDSX etc. And what about getting access to the carry and zero flags for conditional execution ?
Well what about an expression syntax like:
Where result, carry, and zero are set as you would expect if they exist on the right hand side. This would compile to ADD/SUB or ADDS/SUBS depending on the variables being signed or unsigned (slong/ulong) types. If the carry is a term on the left hand side the ADDSX/SUBSX is used. "carry" and "zero" are of course reserved identifiers.
Notice I have not declared "result" above. No idea yet what to do with mixed type expressions. Perhaps automatic type conversion is not allowed and casts will have to be specified like: "result := sfoo + slong(bar);
Note also the use of the X. It allows extended arithmetic, 64 bit say. I for sure don't want to have a LONG LONG type. Extended types will have to be created in COGNAC source as they are in PASM. Going the other way I'm not sure I want the complexity in COGNAC to handle WORD and BYTE types. The whole deal with types and compilers is horrible[noparse]:)[/noparse]
At first I assumed COGNAC would allow use of GOTO. Despite what Dijkstra says GOTO s damn useful in confined spaces and after all we want JMP instructions on demand. However I realize now it would not allow the use of recycling of memory locations for params/locals as described above. It would become impossible for the compiler to predict when particular variable is really not needed at a point in the code can can be reused. Same goes for jumping though a vector. We either don't use memory recycling or we don't have GOTO and vectored jumps.
Constants will have to be dealt with according to size for less than 10 bits put hem in the instruction with "#" for bigger create a literal pool.
For "rdlong" etc we could use a function like syntax "read_long(foo)" where foo is just a ulong or perhaps a type modifier in declarations "hub ulong foo" and generate rdlongs as required.
DJNZ could be pressed into service when coming across constructs like "for foo = bar to 0 step 1". Which looks a bit BASIC to me. Perhaps we need a "for all foo", there, much simpler.
OK that's enough speculation for now.
The idea is to define a dead simple language. Simple enough that a rank amateur compiler writer such as myself can possibly implement it in less than 100 years. The syntax has to be tailored to simple parsing. Traditional language features that don't fit the COG model have to go. New constructs that extract the best from the COG must be invented. By "define" I mean I'd like to at least a BNF description of COGNAC before even thinking about implementing it.
This may never be implemented but it's an interesting Gedankenexperiment anyway.
Any one have any ideas ?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
So the discussion turns this around to "Can we define a high level language that suits the COG instruction set?"
Lets assume that COGs are the only computers in the world and we have yet to invent the stack !
What are the consequences of this?
1. No stack means recursive function calls are not allowed. This is no loss as nobody ever uses recursion anyway and when you do you probably need more stack space than we have available.
2. No stack means no temporary store on the stack for local variables. This is no loss as it is only required for recursive calls which are not allowed by 1)
3. No stack means no parameter passing on the stack. Again no loss as per 2)
4. No stack means no operator precedence in expressions. Well what the hell, I can never remember the precedence of operators in C let alone SPIN which has so many more. Always end up using parenthesis.
5. No stack means no parenthesis in expression evaluation. Ah, well why can't we live with in order evaluation. If we want operations to happen in a specific order use more than one expression.
6. Don't know. Can anyone think of what else we loose without a stack? Possibly things like nested array indexing
x := a[noparse]/noparse]a[noparse][[/noparse]j for example. Haven't thought about arrays yet.
Expanding on points 2 and 3. Functions could still have variables that are local in scope and valid only during their "lifetime" and function calls with parameters could look syntactically like they do in C or Pascal or whatever. BUT locals and parameters would have to exist in some shared global store. That is, any given memory location from the shared pool could be used as a parameter or local for different functions at different times during execution.
Given that we have no recursion, the call graph for any program is a simple tree structure. So it must be possible for the compiler to workout which params and locals are in likely in use at any point in the code and reuse their memory space for other params/locals when they definitely are not.
So far so good. We can define a C-like or Pascal-like language with no stack and still be able to write useful code.
This morning I woke up to find my self thinking about what high level language constructs we could use or invent to make use of all the available COG opcodes. Firstly we can use all/most the operators of the operators of SPIN to make use of stuff like REV etc.
What about signed and unsigned arithmetic with or without carry/borrow? ADD, ADDS, ADDSX etc. And what about getting access to the carry and zero flags for conditional execution ?
Well what about an expression syntax like:
COGNAC Source: ulong foo, bar; slong sfoo, sbar; result := foo + bar; result, carry := sfoo + sbar; result, carry, zero := foo - bar - carry; result := sfoo + sbar + carry; carry, zero := sfoo + sbar; foo, zero += bar Which generates: foo long 0 'Signed bar long 0 'Signed sfoo long 0 'Unsigned sbar long 0 'Unsigned mov rslt, foo 'Get foo add rslt, bar 'Unsigned add bar mov rslt, sfoo 'Get sfoo adds rslt, sbar wc 'Signed add sbar mov rslt, foo 'Get foo subx rslt, foo wc, wz 'Unsigned subtract bar with carry mov rslt, sfoo 'Get sfoo addsx sbar 'Signed add bar with carry adds sfoo, sbar wc, wz, nr 'NO result so may as well work into sfoo add foo, bar wz 'Unsigned add to self
Where result, carry, and zero are set as you would expect if they exist on the right hand side. This would compile to ADD/SUB or ADDS/SUBS depending on the variables being signed or unsigned (slong/ulong) types. If the carry is a term on the left hand side the ADDSX/SUBSX is used. "carry" and "zero" are of course reserved identifiers.
Notice I have not declared "result" above. No idea yet what to do with mixed type expressions. Perhaps automatic type conversion is not allowed and casts will have to be specified like: "result := sfoo + slong(bar);
Note also the use of the X. It allows extended arithmetic, 64 bit say. I for sure don't want to have a LONG LONG type. Extended types will have to be created in COGNAC source as they are in PASM. Going the other way I'm not sure I want the complexity in COGNAC to handle WORD and BYTE types. The whole deal with types and compilers is horrible[noparse]:)[/noparse]
At first I assumed COGNAC would allow use of GOTO. Despite what Dijkstra says GOTO s damn useful in confined spaces and after all we want JMP instructions on demand. However I realize now it would not allow the use of recycling of memory locations for params/locals as described above. It would become impossible for the compiler to predict when particular variable is really not needed at a point in the code can can be reused. Same goes for jumping though a vector. We either don't use memory recycling or we don't have GOTO and vectored jumps.
Constants will have to be dealt with according to size for less than 10 bits put hem in the instruction with "#" for bigger create a literal pool.
For "rdlong" etc we could use a function like syntax "read_long(foo)" where foo is just a ulong or perhaps a type modifier in declarations "hub ulong foo" and generate rdlongs as required.
DJNZ could be pressed into service when coming across constructs like "for foo = bar to 0 step 1". Which looks a bit BASIC to me. Perhaps we need a "for all foo", there, much simpler.
OK that's enough speculation for now.
The idea is to define a dead simple language. Simple enough that a rank amateur compiler writer such as myself can possibly implement it in less than 100 years. The syntax has to be tailored to simple parsing. Traditional language features that don't fit the COG model have to go. New constructs that extract the best from the COG must be invented. By "define" I mean I'd like to at least a BNF description of COGNAC before even thinking about implementing it.
This may never be implemented but it's an interesting Gedankenexperiment anyway.
Any one have any ideas ?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
Comments
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
·"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 do not think it has to be that different from what we know already. I think it can be C or Pascal or even BASIC. Just no local variables, no recursion, no operator precedence and mono operation:
A = B + C - D
shoudl be written:
A = B + C
A = A - D
or something like that. With those elements even I can write something
Replace line numbers with labels and you have a very easy to convert to assembly language.
Arrays can be done through self modified code and or using rdbyte for hub memory. would need to define a method of reading hub vs cog memory maybe defining all hub addresses as part of 3 arrays BYTE[noparse][[/noparse]x], WORD[noparse][[/noparse]x], LONG[noparse][[/noparse]x] they all have same info but different size grouping
What's wrong with multiple terms?
A := B + C - D
generates:
MOV A,B
ADD C
SUB D
Or
A += C + D + E + F
Generates:
ADD A, C
ADD A, D
ADD A, E
ADD A, F
Of course if you want to propagate carry around that may be a problem as I defined it so far:
A := A + B + carry - D - carry
where the "+ carry" means use ADDX for the first "+" and the "- carry"means use SUBX for the first "-".
Starts to get weird. You have to parse ahead away to find out what operation you really want. Not good.
What about using a tick for "with carry":
A := J + K' + L'
Easy to miss in source code I suppose.
Or I might be the first this century to define a language that uses this "¤" [noparse]:)[/noparse]
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
Seems I understand what you are doing with multiple L values ... it would save having to do special carry/zflag statements for example, but is there a familiar precedent or other reason (beyond think different ...) ? Added: I like the post-tick idea:· A = b + c'· except that ' is a comment (ugh).
Have you considered a VB like BASIC or PBASIC with (for/do/while and labels rather than the 20 goto 10 variety)?
I'm a fan of foreach loops ... foreach index of arrayblah ....
Recursion is useful in "series" calculations, but I agree it is not very useful in an tiny embedded environment.
It seems that a priority would be tight resultant code otherwise the compiler might go unused. The great egos or proficients would not bother anyway.
Having something simple regardless of how tight may draw more regular users to your compiler and maybe using pasm assuming list files are available.
It would be prudent to plan for an LMM variant if you continue developing this.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve
therefor
generates:
Lets think about what's in a COG a how to get at it in a higher level than PASM. Language design seems to be a very subtle thing. Seemingly simple little one line changes to the specification can make the whole thing a thousand times harder to implement (or easier).
Inline or subroutine multiply and divide. NO PROBLEM I forgot to mention, multiply and divide only work for powers of 2.
Well seriously I haven't thought about it. Haven't needed either in PASM yet.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
for each bit that is a 1 in second number shift over and add.:
I.E
5*7 = 101*111
101+1010+10100=
5*9=101*1001
101+101000=
Funnily enough I used the Props carry bit to propagate into Add With Carry (ADC) in my 8080 emulator:
test flags, #carry_bit wc 'Set prop's carry from 8080 carry flag
addx alu, operand 'Do the op
Saves an instruction there.
Inline assembler statements is definitely a good idea. And so is the listing of generated code with the source statements. I used to use PL/M on Intel micros that did that very nicely.
This whole multiply/divide thing needs thinking about. For example what if your program only ever multiplies by a constant?. In that case one could in line a customized multiplier with the minimum number of shift/adds for the constant.
Or what if you only ever need a 24 bit result?
I might want to borrow from Spin. for example "repeat" by itself is a wonderful thing and all the operators that everyone has now learned. BUT I will not use indenting to delimit blocks. I want by braces "{" and "}" or DO/END or IF....ENDIF. Whatever.
@jazzed: "VO or XO ?" Some times I just don't care[noparse]:)[/noparse]
The multiple L values I have only ever seen in a small but elegant and powerful language called Lua.
Where you can write:
sum, diff, z = x+y, x-y, 3
for example. You can also swap the order of things like:
a,b,c,d = d,c,b,a
Seemed like just what we need to get what effectively multiple results out of an expression.
I really want a syntax that looks Cish or Pacalish, block structured. Tight code is the aim but I have no illusions about anyóne actually wanting to use this seriously. Unless we do better at it than I suspect at the moment. LMM...Let me think on that.
On thing I forgot to mention and for which the Prop is special is parallelism. We need to cater for getting data between running COGs. Perhaps just by implementing protected regions of code:
protected lockname
a := bla bla
b := bla bla
unprot
How we get the locks distributed to COGs I don't know.
Or a system of remote procedure calls.....
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
You can also hard code the lock numbers. You do not need the locknew command if you manually assign the lock numbers yourself.
Seems a lot of inter COG communication, to drivers for example, uses a pattern of having a block of data in which there is a command word. When the command is set the "slave" cog does whatever it should with the data in the block and then sets the command back to zero. In which case no locks are required.
Now this pattern is normally wrapped up in Spin interface functions so you write, for example, "TV.out(13)".
Given that we don't necessarily have or want any Spin involved we should be able to write something similar in COGNAC such that "TV" is used to determine which remote COG this command is for "out" is the command word that gets written to the shared data block and "13" is the data that gets written.
Actual shared memory data structures with locks looks more like it needs protected regions of code.
Any one with ides on this?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
·"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
Locks are not needed if 1 cog is doing the writing and the rest just read but if like in my graphics display object(not posted because specialized to my hardware) i have multiple cogs writing and only 1 reading i need to use locks so only 1 can write at a time.
I think the audience is the beginner who wants an intro into pasm without the complexities. So, keep it simple.
I suggest you discard the carry/signed numbers and just work with 32 bit integers.
Have two global variable types, one in cog, one in hub.
Always declare a cog variable "result".
You will also need to cater for strings.
I like each line to be listed with it's pasm translation following in a listing.
Allow inline pasm coding.
Keep the syntax simple (a basic style is simpler in my opinion)
Allow labels
A simple repeat and for would be nice.
Just 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
Perhaps we can loose signed numbers. At least in a first pass at it.
Can we live without access to CARRY and ZERO?. Well perhaps ZERO. I've always wanted access to carry in C for checking bits shifted out or detecting unsigned overflow/underflow.
Can we live without control over when they are set? It's quite fundamental to PASM.
Oh, and I forgot about parity. Sometimes very useful. My emulator likes it anyway.
Global variables declared as "hub" is a must. As is the listing format and in line asm.
Sadly "result" is a Prop Tool reserved word.
I guess a good way to look at it is could I write my emulator in COGNAC and be getting anywhere near that code density? And how pretty or ugly would the COGNAC source be to do it.
For sure the single COG emulator would be a challenge here, that code is really contorted to fit.
We need some form of "case" statement that generates self modifying code. Same for array indexing.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
One question on "COGNAC"
Can that compiled PASM routines be compatible to load on upper half on eeprom in ProtoBoard and loaded on fly?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Nothing is impossible, there are only different degrees of difficulty.
For every stupid question there is at least one intelligent answer.
Don't guess - ask instead.
If you don't ask you won't know.
If your gonna construct something, make it·as simple as·possible yet as versatile as posible.
Sapieha
What I had in mind was to just emit a .spin file containing the generated PASM together with a minimal SPIN section to start it such that it can just be dropped into a Propeller Tool Project. That is, leave the actual assembling to binary to the Prop tool or other Prop compiler.
If we wanted to output a compiled and assembled binary blob that could be stored in EEPROM or SD card or whatever then I would need an assembler also. Or I believe someone has a technique for extracting the PASM from the DAT section of a .spin file. Can't remember who or where to find it.
I do have a 70% complete PASM assembler that I could use I guess. I was really looking forward to not having to finish it. In a fit of dementia I wrote the thing in ADA. It not only assembles PASM but understands the SPIN syntax for constant declarations, enumerations etc. Which is quite a pain to get right. Not to mention the weird stuff you can do in DAT like:
foo long 0[noparse][[/noparse]20]
and
foo long "abc" + "xyz" 'Which in no way does what you might expect.
which I think are really odd and will not be in COGNAC.
When Bradc and mpark came out with their full featured Spin compilers I kind of lost interest in my monstrosity of an assembler.
Anyway I think you have an excellent idea.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
My idea is to have COG Routines/Programs that only reside on eeprom and load from this place and not reside in HUB at al.
And only use Buffers on High HUB addresses then litle Loader in SPIN loads them and Starts.
After Load it starts MAIN Spin program. With that I can have more fre memory in HUB.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Nothing is impossible, there are only different degrees of difficulty.
For every stupid question there is at least one intelligent answer.
Don't guess - ask instead.
If you don't ask you won't know.
If your gonna construct something, make it·as simple as·possible yet as versatile as posible.
Sapieha
As I say, it's an excellent idea. Lets see what we can do.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
No reason to be tied to one line is one opcode or to get hung up on not having a stack. It's reasonably easy to code an expression parser which can re-jig things to use temporaries and the Prop has two Cog temps as OUTB and DIRB which most people never use. Go as far as you can then emit "OMG ! Expression too complicated. L33t h4xr alert!", and leave it to the programmer to simplify
I'd also say don't get hung up on efficient code generation. Get it right but leave it to a separate optimisation pass to start with. Choose an intermediate opcode representation suitable for the high-level and PASM and you should be able to have a simple parser and simple back-end code generation. Plus, don't worry about specifying whether result is written whether carry is set or not in the source. Never used it any any other language I've coded in. Always store the result, always update carry and zero, then you can simply test the flag, set or clear it, in source whenever you feel like it. I'd argue to get something simple but useful working then worry about bells and whistles and clever tweakings.
After all, any high-level language is there to make writing and reading it easier, not primarily to allow the most efficient hand-optimised code to be created. Give those who want it a #asm directive. Maybe that's the first thing; be clear about what it is you wish to achieve, what problem you are trying to overcome.
Key to usefulness IMO is being able to generate LMM PASM as well as native PASM. Get that and other compiler writers can target yours as their intermediate language. I'd also say consider LMM-Thumb ( 16-bit ) to maximise memory capacity. I don't personally see a problem with data being all 32-bit but then I don't do much data processing.
Good luck with it. Maybe take a look at CC5X from B Knudsen Data, that's a pretty nice ( non-ANSI ) C for PICmicros which don't have stacks and is excellent when used as a high-level assembler, very tight code generation, deals with function parameter passing and all that.
If COGNAC generated code were the only code to exist in the Propeller the having HUB variables would be great. Especially when writing code for multiple COGs, which I think is a must. It would make it dead easy for COGs to find each others shared variables and communication/control blocks. Basically linking COG code together.
BUT In general we are not alone in the COG. Perhaps the COGNAC output a .spin source file and is dropped into a Propeller tool project, now we have no control over where our HUB variables are in memory. We end up having to somehow pass pointers from COG to COG at run time to get everything "linked".
Or perhaps, as Spieha suggests, the COGNAC out put is just a binary blob that gets pulled in from some media and loaded into a COG. In which case we don't even have the possibility to reserve anything in HUB.
So actually having declarations for HUB variables try to reserve space in HUB is not possible. This all has to be resolved at run time through pointers arriving via "par".
So should we:
A) Forget HUB declarations, assume we get some pointers to HUB variables via "par" which are basically just LONGs in our COG code and then just use them as as parameters to built in functions like "readword(some_pointer)". Basically what we do in PASM anyway.
Have HUB variable declarations, but rather than reserving space in HUB they are normal LONGS that will be somehow set up to point to HUB data via "par" When these longs are used they appear as normal variables, "a := some_hub_variable + 6" but the code generated uses rdlong etc to access them.
I kind of like b)
Speaking of "par" we need somewhere to start execution and get users parameters in. I'm thinking that the main line for a COG would something like:
cog uart (par)
{
' All code for a UART COG goes here.
...
}
or
cog led_flasher(par)
begin
'All code for a LED flasher goes here.
....
end
Or as "par" is a global anyway perhaps it does not need to be a parameter to the main line anyway. Just let the programmer sort out his parameters as he chooses.
Or should we have some smarter way formalize the getting and setting up of those HUB declarations from whatever "par" points to. In which case the cog entry function would have many parameters:
cog uart (long pins, long buad_rate, hub long control_block_ptr,...)
I choose to call the COGs main enty point "COG" because a COGNAC program may use several COGS. What the "main" for the whole "gearbox" looks like I don't know.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
You said " Or perhaps, as Spieha suggests, the COGNAC out put is just a binary blob that gets pulled in from some media and loaded into a COG. In which case we don't even have the possibility to reserve anything in HUB. "
In my idea I have thinking abaut one else more BYTE/LONG in start of that blob that describe for LOADER how many Variables to reserve in HIGH HUB ram
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Nothing is impossible, there are only different degrees of difficulty.
For every stupid question there is at least one intelligent answer.
Don't guess - ask instead.
If you don't ask you won't know.
If your gonna construct something, make it·as simple as·possible yet as versatile as posible.
Sapieha
if a := ina >> 8 & $FF
count += a
endif
for example becomes:
mov a, ina
shr a, #8
and a, #$FF wz
if_nz jmp lab_1
add count, a
lab_1....
Or some such, which of course is not optimal but without a stack to worry about far more efficient code seems to just fall out. I like the idea of "re-jigging temporaries" brings back some use of parenthesis.
I take my inspiration from Jack Crenshaw here so, as I say, it will have to be simple and any idea of optimization may have to come from those who know more about compilers than I do. When you speak of "intermediate opcode representation" and "back-end code generation" that may be more tan I can fathom at this time. Jack elegantly shows how you can compile straight to asm in one pass with tokenizer, no intermediate representations and back end generators etc. That is about the level I was pitching this at. I'm open to suggestions though.
Good advice about "be clear about what it is you wish to achieve". First thing I want to do is write out a spec. for all this complete with BNF definition. Such that anyone inclined to can tell me where it is screwy or how to do things better or what may be to complicate to implement in any reasonable time.
High on the wants list is that programs in COGNAC are pretty, or at least easy on the eye. That might not be so easy as I want to borrow all the funky operators from SPIN. Unlike some Perl I've seen[noparse]:)[/noparse] More like Pascal.
LMM...Hmm... I already have something that compiles stack based functions with parameters and local variables to LMM. It's pretty crude though.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
In a typical case nowadays we have something like:
DAT
command_block LONG
command LONG 0
data_length LONG 0
buffer LONG 0[noparse][[/noparse]16]
parameters LONG
pins LONG 33
baud_rate LONG 40
command_block_ptr LONG @command_block
...
PAR start
cognew(@enter, @parameters)
Then we code our PASM to use get those parms from "par"
But in COGNAC shouldn't we have a main entry point something like:
cog (pins, baud_rate, command_block_ptr...)
And the COGNAC compiler should generate that Spin start up stub (in a separate .spin file) for the user to put in his code. Even if the actual compiled PASM binary blob has no Spin in it.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
It is near that I think.
This DAT must reside in eeprom as header.
LOADER places it in HUB with any place and generate "command_block_ptr" for my Spin.
And Spin calculates rest if necessary.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Nothing is impossible, there are only different degrees of difficulty.
For every stupid question there is at least one intelligent answer.
Don't guess - ask instead.
If you don't ask you won't know.
If your gonna construct something, make it·as simple as·possible yet as versatile as posible.
Sapieha
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
--Steve