So, I had a go at trying out Atalan this weekend, and thought I'd summarize my experience with it for the benefit of others.
I approached it with a quite positive attitude. While some of the syntax might seem strange at first, I did find it rather elegant, and saving a lot of characters to write compared to C/Java syntax.
I decided to try and port the simple map editor I have written for my current game project, which allows you to do some simple editing of the map for easier debugging. This seemed like a good example of something I could try out a HLL for: performance is not a big issue and it requires a lot of repetitive code that becomes hard to maintain in asm. Besides, since it's not something you'd need in the final product, you wouldn't become dependent on it for the rest of your game.
But when trying out the version available from the website, switching the platform to nes with the '-p nes' option would make the compiler crash with a "Atalan has stopped working" windows dialog. Compiling for the default atari target did work though. (for 'hello world' and the other included examples at least)
Not wanting to give up just yet, I checked out the SVN source from the Atalan source repository trunk. And with this one, the 'hello world' example finally worked. However, when trying out the part of my map editor that I had re-written in Atalan, I got the same old compiler crash. Eventually, I tracked down that this code:
Code:
const SpriteImageEditCursor:array = (
4,
0,EDIT_CURSOR_TILEID&$FF,$00,0
0,EDIT_CURSOR_TILEID&$FF,$40,8
8,EDIT_CURSOR_TILEID&$FF,$80,0
8,EDIT_CURSOR_TILEID&$FF,$C0,8
)
...should really look like this:
Code:
const SpriteImageEditCursor:array = (
4,
0,EDIT_CURSOR_TILEID bitand $FF,$00,0
0,EDIT_CURSOR_TILEID bitand $FF,$40,8
8,EDIT_CURSOR_TILEID bitand $FF,$80,0
8,EDIT_CURSOR_TILEID bitand $FF,$C0,8
)
But I sure would have expected this to result in a syntax error rather than a crashing compiler.
Anyways, now I was somewhat back on track to re-write my assembly code in Atalan. Atalan uses the syntax of the MADS assembler for the assembly code it outputs, while my project uses CA65. But fortunately, rewriting the m6502.atl and nes.atl files to produce code in CA65 syntax wasn't that hard, and was one of the things I found exciting about the Atalan project - to easily be able to tune the backend for your own needs. It did help that I know Polish as a second language though, since the MADS documentation is in Polish and I had to find out how certain directives worked to know which CA65 counterparts they should use instead. :)
One strange error I never understood was how defining a byte value at a certain memory location like this:
Code:
joyP0@$15:byte
And then using it like this:
Code:
if joyP0 bitand JOY_SELECT <> 0
CopyMapToSRAM
return $80
Would produce "Logic error: Cannot infer type of result of operator 'bitand'." from the compiler (very common error message that kept creeping up in a lot of other cases as well)
The solution I found by trial and error was to replace the declaration with:
Code:
in joyP0@$15:byte
Which is really only supposed to be needed for hardware registers where you don't want the compiler to optimize away multiple reads from it.
I had read a lot about Atalan's optimization features, so I was very eager to inspect the resulting code. Here's the routines I wrote one-by-one:
* Atalan source *Code:
UploadEditCursorCHR:proc =
addr:0..65535
addr = EDIT_CURSOR_TILEID*16
PPU_DATA_ADR = hi addr
PPU_DATA_ADR = lo addr
for i:0..7
PPU_DATA = 0
for i:0..7
PPU_DATA = EditCursorCHR(i)
* Assembly output *Code:
.proc UploadEditCursorCHR
;### editmap.atl(55) addr = EDIT_CURSOR_TILEID*16
lda #240
sta UploadEditCursorCHR__addr+0
lda #31
sta UploadEditCursorCHR__addr+1
;### editmap.atl(56) PPU_DATA_ADR = hi addr
sta PPU_DATA_ADR
;### editmap.atl(57) PPU_DATA_ADR = lo addr
lda #240
sta PPU_DATA_ADR
;### editmap.atl(58) for i:0..7
lda #0
sta _s1__i
_lbl6:
;### editmap.atl(59) PPU_DATA = 0
lda #0
sta PPU_DATA
inc _s1__i
lda _s1__i
cmp #8
jne _lbl6
;### editmap.atl(60) for i:0..7
ldx #0
_lbl8:
;### editmap.atl(61) PPU_DATA = EditCursorCHR(i)
lda EditCursorCHR,x
sta PPU_DATA
inx
cpx #8
jne _lbl8
rts
.endproc
First, I would have expected the stores to the intermediate 'addr' variable to be optimized out by the compiler. But even more strange is how the first loop writing zeros to $2007 produces much worse code than the one copying the contents of my array.
Then finally, a small part of my original asm routine that updates the cursor movement in the map editor:
* Atalan source *Code:
UpdateMapEditor:proc >modifiedFlags:byte =
DrawEditCursor
if joyP0 bitand JOY_SELECT <> 0
CopyMapToSRAM
return $80
else if (joyP0 bitand JOY_LEFT <> 0) and editCursorX > 0
dec editCursorX
else if (joyP0 bitand JOY_RIGHT <> 0) and editCursorX < 15
inc editCursorX
else if (joyP0 bitand JOY_UP <> 0) and editCursorY > 0
dec editCursorY
else if (joyP0 bitand JOY_DOWN <> 0) and editCursorY < 14
inc editCursorY
return 0
* Assembly output *Code:
.proc UpdateMapEditor
;### editmap.atl(71) DrawEditCursor
jsr DrawEditCursor
;### editmap.atl(73) if joyP0 bitand JOY_SELECT <> 0
lda joyP0
and #root__JOY_SELECT
sta UpdateMapEditor___37
jeq _lbl9
;### editmap.atl(74) CopyMapToSRAM
jsr CopyMapToSRAM
;### editmap.atl(75) return $80
jmp _exit32766
_lbl9:
;### editmap.atl(76) else if (joyP0 bitand JOY_LEFT <> 0) and editCursorX > 0
lda joyP0
and #root__JOY_LEFT
sta UpdateMapEditor___39
jeq _lbl11
lda #0
cmp editCursorX
jcs _lbl11
dec editCursorX
jmp _exit32766
_lbl11:
;### editmap.atl(78) else if (joyP0 bitand JOY_RIGHT <> 0) and editCursorX < 15
lda joyP0
and #root__JOY_RIGHT
sta UpdateMapEditor___40
jeq _lbl12
lda editCursorX
cmp #15
jcs _lbl12
inc editCursorX
jmp _exit32766
_lbl12:
;### editmap.atl(80) else if (joyP0 bitand JOY_UP <> 0) and editCursorY > 0
lda joyP0
and #root__JOY_UP
sta UpdateMapEditor___41
jeq _lbl13
lda #0
cmp editCursorY
jcs _lbl13
dec editCursorY
jmp _exit32766
_lbl13:
;### editmap.atl(82) else if (joyP0 bitand JOY_DOWN <> 0) and editCursorY < 14
lda joyP0
and #root__JOY_DOWN
sta UpdateMapEditor___42
jeq _exit32766
lda editCursorY
cmp #14
jcs _exit32766
inc editCursorY
_exit32766:
rts
.endproc
The 'jmp' commands to _exit32766 could obviously be optimized away with an 'rts'. Also, the 'return N' commands don't seem to work at all since the return values aren't stored anywhere.
But most importantly, we again see lots of intermediate results being written to temporary variables for no good reason. For just this short piece of code, there are no less than 4 of them.
So this looks like the real showstopper to me and I probably won't attempt to convert the rest of my mapeditor to Atalan. While I could live with some of the slower/larger code produced, it seems like the memory usage will just explode in Atalan code. While it is true that this is only temporary variable space, and Atalan did correctly re-use the same memory areas in other routines in my short program, the unnecessary memory stores could easily eat up most of your zeropage memory in a more complex routine. And really, ~16 bytes should be enough temporary space for most projects I could think of.
So my conclusion would be that Atalan is a very fascinating proof-of-concept, and if it did perform what the documentation suggests, it would likely be the language I'd choose for non-performance critical AI code in my game. But at this point, the implementation is just not mature enough to be usable in practice. I hope that it reaches that stage in a few years from now, but looking at the source code repository and the discussion groups, development seems to have been ramping down quite a bit during the last year.
Still, I recommend anyone who's thinking about implementing a compiler for the 6502 (or any other low-level system for that matter) to have a serious look at Atalan for inspiration. If you do like to have a go at language implementation in your spare time, improving Atalan could be a good start. But personally, I don't want to get stuck in yet another time-consuming sideproject that keeps me from actually working on my project :)