I wasn't thinking of density, but rather the requirement (as originally outlined in the first post) was for deterministic timing (and I read this to mean all cases will execute with the same timing.
Now, on this assumption, I want to be able to enforce the compiler to do the table method regardless of size.
That's what the difference is between CASE_FAST (always uses the table method) and CASE (sometimes uses the table method, but only if some conditions about density are satisfied).
BTW I know spin/spin2 will never be truly deterministic, but we should be able to get somewhat close.
Interpreted Spin 1 on P1 actually is deterministic, in that running the same function with the same starting conditions will always take the same number of cycles. Also, the language maps very directly onto bytecodes, so you could even do cycle counting. I see no reason why Chip's Spin2 wouldn't have the same properties.
I see no reason to use x86 Asm on 3GHz machine in 2019,
C# can be grasped in a day if you ask a tutor to show you the quirks. https://code.visualstudio.com/
CAST is a completely illogical name. It means something completely different, either "throw" or "mold". SELECT, CHOOSE, INDEX, DISPATCH are all far better, and CASE with alternate syntax of some sort would also be better.
CAST is a completely illogical name. It means something completely different, either "throw" or "mold". SELECT, CHOOSE, INDEX, DISPATCH are all far better, and CASE with alternate syntax of some sort would also be better.
While I agree CAST is just plain wrong, i don’t like those other choices.
CASE_FAST or CASE FAST is also just plain wrong as it is not necessarily faster!!!!!
I would much prefer CASE with a modifier such as
CASE TABLE
CASE_T
CASET
CASETBL
These show that a table is being used and so each branch will be identical in execution speed (deterministic).
While I agree CAST is just plain wrong, i don’t like those other choices.
CASE_FAST or CASE FAST is also just plain wrong as it is not necessarily faster!!!!!
I would much prefer CASE with a modifier such as
CASE TABLE
CASE_T
CASET
CASETBL
These show that a table is being used and so each branch will be identical in execution speed (deterministic).
It's almost always faster, except when there are only a few cases (and perhaps in Chip's Spin interpreter it will always be faster? I guess that remains to be seen.)
I very much agree with @cgracey that we don't want to reserve the word TABLE, since that's a name that programmers often use for variables. Perhaps "CASE JMP" would work (JMP is already a reserved word in Spin). Or we could just stick with "CASE_FAST".
Automatic promotion from case to case_fast should probably be accompanied by a reserved constant or compile-time pragma that specifies the maximum length of auto-promoted jump tables, particularly where large gaps appear between cases.
Why not have support for arbitrary annotations in the Spin2 syntax?
The annotations are directives or optimization-hints for the Spin2 compiler.
Example for 'case' / 'case_fast' / 'case table':
Using square brackets for compiler annotations:
case expression [[table]]
1: do_this
...
Or using parens:
case expression ((table))
1: do_this
...
This way 'table' or any other compiler annotation does not collide with identifiers,
therefore 'table' does not need to be a keyword.
Having the annotation at the end of the line allows easy disabling or enabling by adding or removing a comment character.
I like this as a general approach. Given that SPIN2 is not going to be entrenched in ROM there are likely to be small variations to optimise certain aspects. If this approach were taken then there can be a generally applicable method of extending and morphing SPIN2 without cluttering the namespace.
I doubt that, especially not in the case I presented above where the table would contain 98 entries all pointing to the same default address; whereas only three compares would be required to accomplish the same function.
In a more typical case, and with Chip's word per entry version, I see them all being smaller unless you try to make it dumb. Like 0, 1, 250. And even that could be worked around with some math/checks.
I very much agree with @cgracey that we don't want to reserve the word TABLE, since that's a name that programmers often use for variables.
If the format were something like this:
case x table
you could still use table as a constant or variable, since there's no syntactic ambiguity between uses.
I don't think it's that simple. If the user writes
case table
did they mean to do a "case x table" statement but forgot the "x", or did they really mean to do a "case" statement with variable "table"? Not to mention that depending on the parser it can be pretty difficult to treat the same word as sometimes being a keyword and sometimes not. This would complicate (and slow down) fastspin's parser a fair amount, for example.
Currently one can use "case_fast' to use a table and "case" may use a table or may not depending on the compiler. This is uncool.
So basically we would need a "case_short" also to force the non-table approach.
No, I don't think we need that. With the adjustment to only use tables if they are "dense", the jump table approach to "case" will only be used when it is both faster and smaller than the if statement approach. For the original implementation there is a "cmp + jmp" for each case, for the table there is just a "jmp", so the table will be shorter if more than half of the entries are used.
If you really want to force the compiler not to use a table, you can always use an "if" statement instead of "case".
I very much agree with @cgracey that we don't want to reserve the word TABLE, since that's a name that programmers often use for variables.
If the format were something like this:
case x table
you could still use table as a constant or variable, since there's no syntactic ambiguity between uses.
I don't think it's that simple. If the user writes
case table
did they mean to do a "case x table" statement but forgot the "x", or did they really mean to do a "case" statement with variable "table"? Not to mention that depending on the parser it can be pretty difficult to treat the same word as sometimes being a keyword and sometimes not. This would complicate (and slow down) fastspin's parser a fair amount, for example.
I was thinking the same thing, Eric.
Inline PASM got finished this morning. The last thing I'm working on now is how to handle COGINIT, since it's more complex than it used to be.
Generic Spin instruction:
COGINIT(mode,pgm,ptr)
Raw PASM instruction:
COGINIT D/#,S/# {WC}
D/# = %0_x_xxxx The target cog loads its own registers $000..$1F7 from the hub,
starting at address S/#, then begins execution at address $000.
%1_x_xxxx The target cog begins execution at address S/#.
%x_0_CCCC The target cog’s ID is %CCCC.
%x_1_xxx0 If a cog is free (stopped), then start it.
To know if this succeeded, D must be a register and WC must be
used. If successful, C will be cleared and D will be over-
written with the target cog’s ID. Otherwise, C will be set and
D will be overwritten with $F.
%x_1_xxx1 If an even/odd cog pair is free (stopped), then start them.
To know if this succeeded, D must be a register and WC must be
used. If successful, C will be cleared and D will be over-
written with the even/lower target cog’s ID. Otherwise, C will
be set and D will be overwritten with $F.
There is a need for COGNEW, lest we make them type '16' for the mode value. Then, there's this issue of bit 5 which causes the cog to start in hub exec mode. We probably want to use that for launching Spin cogs because it can get right down to loading the upper cog RAM and LUT RAM, but how to differentiate all these possibilities? I've been putting off this thinking because it's one of the harder things to work out, but now it's all that's left before the whole thing can run.
There is a need for COGNEW, lest we make them type '16' for the mode value. Then, there's this issue of bit 5 which causes the cog to start in hub exec mode. We probably want to use that for launching Spin cogs because it can get right down to loading the upper cog RAM and LUT RAM, but how to differentiate all these possibilities?
Isn't the interpreter still needed loaded in each cog? It seems to me that the mode is implicit for Spin. No need for it in the parameters.
Chip, I would suggest keeping the simple case (COGNEW) as simple as possible. As Evanh suggested, for Spin the COGNEW mode should be implicit (whatever is needed to launch the Spin interpreter). For PASM COGNEW should use a mode of 16, which is probably the most useful for COGNEW.
COGINIT can take an explicit mode argument which will be the same as the assembly instruction, and users that need finer control over how COGs are allocated can use this.
There is a need for COGNEW, lest we make them type '16' for the mode value. Then, there's this issue of bit 5 which causes the cog to start in hub exec mode. We probably want to use that for launching Spin cogs because it can get right down to loading the upper cog RAM and LUT RAM, but how to differentiate all these possibilities?
Isn't the interpreter still needed loaded in each cog? It seems to me that the mode is implicit for Spin. No need for it in the parameters.
Yes, but we need new syntax for Spin vs PASM, I think.
Here is Spin2 syntax for launching PASM cogs:
COGINIT(CogID, CodeAddress, PTRAvalue)
Here is (tentative) Spin2 syntax for launching Spin2 cogs:
NEWSPIN(CogMethod, StackPtr)
RESPIN(CogNumber, CogMethod, StackPtr)
...where CogMethod is one of the following:
Method(AnyParameters)
Method[index](AnyParameters)
Object.Method(AnyParameters)
Object.Method[index](AnyParameters)
Object[index].Method(AnyParameters)
Object[index].Method[index](AnyParameters)
MethodPtr(AnyParameters)
EDIT: Got rid of @ and ~~ prefixes before method names and pointers, since they aren't needed.
Ah, right, shows my total lack of Spin experience again.
Well, could leave launching of pasm cogs out of the spin language altogether and the only way is for a small piece of inline pasm do it. I can see it's common practise to have an object.start and an object.stop for the prop1 already.
Yes, but we need new syntax for Spin vs PASM, I think.
Here is Spin2 syntax for launching PASM cogs:
COGINIT(CogID, CodeAddress, PTRAvalue)
Here is (tentative) Spin2 syntax for launching Spin2 cogs:
NEWSPIN(CogMethod, StackPtr)
RESPIN(CogNumber, CogMethod, StackPtr)
...where CogMethod is one of the following:
Method(AnyParameters)
Method[index](AnyParameters)
Object.Method(AnyParameters)
Object.Method[index](AnyParameters)
Object[index].Method(AnyParameters)
Object[index].Method[index](AnyParameters)
MethodPtr(AnyParameters)
Isn't this just the same as Spin1, where the Spin vs. PASM case can be decided based on the parameter (if it's a method or method pointer then it's starting a Spin COG, otherwise it's starting a PASM COG)? In which case "NEWSPIN" can be "COGNEW" and "RESPIN" can be "COGINIT", just like before.
(That's the way fastspin does it, anyway. I could trivially make "NEWSPIN" and alias for "COGNEW" and "RESPIN" an alias for "COGINIT", but I'm not sure I see the need.)
...where CogMethod is one of the following:
@Method(AnyParameters)
@Method(AnyParameters)
@Object.Method(AnyParameters)
@Object.Method(AnyParameters)
@Object.Method(AnyParameters)
@Object.Method(AnyParameters)
~~MethodPtr(AnyParameters)
Isn't this just the same as Spin1, where the Spin vs. PASM case can be decided based on the parameter (if it's a method or method pointer then it's starting a Spin COG, otherwise it's starting a PASM COG)? In which case "NEWSPIN" can be "COGNEW" and "RESPIN" can be "COGINIT", just like before.
(That's the way fastspin does it, anyway. I could trivially make "NEWSPIN" and alias for "COGNEW" and "RESPIN" an alias for "COGINIT", but I'm not sure I see the need.)
NEWSPIN would make the COGINIT 'D' value = %11_0000, in order to launch a new cog from hub RAM.
RESPIN would make the COGINIT 'D' value = %10_0000 | (CogNumber & $F), in order to launch a specific cog from hub RAM.
COGINIT is open-ended, but usable only for PASM code.
...where CogMethod is one of the following:
@Method(AnyParameters)
@Method(AnyParameters)
@Object.Method(AnyParameters)
@Object.Method(AnyParameters)
@Object.Method(AnyParameters)
@Object.Method(AnyParameters)
~~MethodPtr(AnyParameters)
Isn't this just the same as Spin1, where the Spin vs. PASM case can be decided based on the parameter (if it's a method or method pointer then it's starting a Spin COG, otherwise it's starting a PASM COG)? In which case "NEWSPIN" can be "COGNEW" and "RESPIN" can be "COGINIT", just like before.
(That's the way fastspin does it, anyway. I could trivially make "NEWSPIN" and alias for "COGNEW" and "RESPIN" an alias for "COGINIT", but I'm not sure I see the need.)
NEWSPIN would make the COGINIT 'D' value = %11_0000, in order to launch a new cog from hub RAM.
RESPIN would make the COGINIT 'D' value = %10_0000 | (CogNumber & $F), in order to launch a specific cog from hub RAM.
COGINIT is open-ended, but usable only for PASM code.
Is the Spin interpreter running from hub RAM? I thought it had to be in COG RAM and/or LUT RAM in order to use XBYTE?
In any case, I think this could still be implemented the same as Spin1 -- if you see a method as a parameter then you use the Spin values for the COGINIT mode, otherwise use the PASM values.
I think 1 and 2 should both be valid, that is, the numbers should be optional. I think requiring the numbers is a step back to the 1970s and 80s.
So does it just fall through and do nothing if there is no match and OTHER is not specified?
what about pick, is that used?
edit: Thinking about it, the numbers should not be used and only option 1 should be correct. That is because, your still free to number things if you like, but its the programmers job, not the compilers to keep track of it. Just like with BASIC implementations, we have gotten away from enforcing the numbering along with the side burns,skinny ties and mustaches.
I don't think it's that simple. If the user writes
case table
did they mean to do a "case x table" statement but forgot the "x", or did they really mean to do a "case" statement with variable "table"?
That would depend, of course, on whether table was declared as a constant or variable -- an easy check for the compiler to perform.
In any event, I have little sympathy for compiler writers who want to make their tasks easy, when the overriding objective should be to make the programmers' tasks easy and the language simple, clear, and elegant. A compiler only has to be written once; programs compiled with it could run in the millions of instances.
I think 1 and 2 should both be valid, that is, the numbers should be optional. I think requiring the numbers is a step back to the 1970s and 80s.
So does it just fall through and do nothing if there is no match and OTHER is not specified?
what about pick, is that used?
edit: Thinking about it, the numbers should not be used and only option 1 should be correct. That is because, your still free to number things if you like, but its the programmers job, not the compilers to keep track of it. Just like with BASIC implementations, we have gotten away from enforcing the numbering along with the side burns,skinny ties and mustaches.
Comments
Interpreted Spin 1 on P1 actually is deterministic, in that running the same function with the same starting conditions will always take the same number of cycles. Also, the language maps very directly onto bytecodes, so you could even do cycle counting. I see no reason why Chip's Spin2 wouldn't have the same properties.
C# can be grasped in a day if you ask a tutor to show you the quirks.
https://code.visualstudio.com/
While I agree CAST is just plain wrong, i don’t like those other choices.
CASE_FAST or CASE FAST is also just plain wrong as it is not necessarily faster!!!!!
I would much prefer CASE with a modifier such as
CASE TABLE
CASE_T
CASET
CASETBL
These show that a table is being used and so each branch will be identical in execution speed (deterministic).
It's almost always faster, except when there are only a few cases (and perhaps in Chip's Spin interpreter it will always be faster? I guess that remains to be seen.)
I very much agree with @cgracey that we don't want to reserve the word TABLE, since that's a name that programmers often use for variables. Perhaps "CASE JMP" would work (JMP is already a reserved word in Spin). Or we could just stick with "CASE_FAST".
FAST is just incorrect
I like this as a general approach. Given that SPIN2 is not going to be entrenched in ROM there are likely to be small variations to optimise certain aspects. If this approach were taken then there can be a generally applicable method of extending and morphing SPIN2 without cluttering the namespace.
you could still use table as a constant or variable, since there's no syntactic ambiguity between uses.
-Phil
I agree, and this approach also allows other modifiers from a defined set to be used as the last-on-the-line. Easy to read, and edit.
Yes, CASE_FAST is something of a misnomer.
Currently one can use "case_fast' to use a table and "case" may use a table or may not depending on the compiler. This is uncool.
So basically we would need a "case_short" also to force the non-table approach.
Sure right now 512 Kb sounds big, but on the P1 I often fight for every long and I am pretty sure it will be the same on a P2.
I am also not a fan of underscores, so I would more likely tend to "case", "casetable", "caseshort".
Or do it like COBOL would name it, case-table, case-short using "-" instead of "_".
Anyways "case_fast" sounds just wrong.
Enjoy!
Mike
-Phil
I don't think it's that simple. If the user writes did they mean to do a "case x table" statement but forgot the "x", or did they really mean to do a "case" statement with variable "table"? Not to mention that depending on the parser it can be pretty difficult to treat the same word as sometimes being a keyword and sometimes not. This would complicate (and slow down) fastspin's parser a fair amount, for example.
If you really want to force the compiler not to use a table, you can always use an "if" statement instead of "case".
I was thinking the same thing, Eric.
Inline PASM got finished this morning. The last thing I'm working on now is how to handle COGINIT, since it's more complex than it used to be.
Generic Spin instruction:
COGINIT(mode,pgm,ptr)
Raw PASM instruction:
There is a need for COGNEW, lest we make them type '16' for the mode value. Then, there's this issue of bit 5 which causes the cog to start in hub exec mode. We probably want to use that for launching Spin cogs because it can get right down to loading the upper cog RAM and LUT RAM, but how to differentiate all these possibilities? I've been putting off this thinking because it's one of the harder things to work out, but now it's all that's left before the whole thing can run.
COGINIT can take an explicit mode argument which will be the same as the assembly instruction, and users that need finer control over how COGs are allocated can use this.
Yes, but we need new syntax for Spin vs PASM, I think.
Here is Spin2 syntax for launching PASM cogs:
Here is (tentative) Spin2 syntax for launching Spin2 cogs:
EDIT: Got rid of @ and ~~ prefixes before method names and pointers, since they aren't needed.
Well, could leave launching of pasm cogs out of the spin language altogether and the only way is for a small piece of inline pasm do it. I can see it's common practise to have an object.start and an object.stop for the prop1 already.
NEWSPIN would make the COGINIT 'D' value = %11_0000, in order to launch a new cog from hub RAM.
RESPIN would make the COGINIT 'D' value = %10_0000 | (CogNumber & $F), in order to launch a specific cog from hub RAM.
COGINIT is open-ended, but usable only for PASM code.
Is the Spin interpreter running from hub RAM? I thought it had to be in COG RAM and/or LUT RAM in order to use XBYTE?
In any case, I think this could still be implemented the same as Spin1 -- if you see a method as a parameter then you use the Spin values for the COGINIT mode, otherwise use the PASM values.
So does it just fall through and do nothing if there is no match and OTHER is not specified?
what about pick, is that used?
edit: Thinking about it, the numbers should not be used and only option 1 should be correct. That is because, your still free to number things if you like, but its the programmers job, not the compilers to keep track of it. Just like with BASIC implementations, we have gotten away from enforcing the numbering along with the side burns,skinny ties and mustaches.
pick x
: block_0 ' -- Block 0
: block_1 ' -- Block 1
: block_2 ' -- Block 2
: block_3 ' -- Block 3
: block_4 ' -- Block 4
: block_5 ' -- Block 5
: block_6 ' -- Block 6
> block_other ' -- Other
as always, comments are optional, but good practice if you need to clarify things.
-Phil
In any event, I have little sympathy for compiler writers who want to make their tasks easy, when the overriding objective should be to make the programmers' tasks easy and the language simple, clear, and elegant. A compiler only has to be written once; programs compiled with it could run in the millions of instances.
-Phil
If there's no match, it falls through, unless OTHER catches that case.