The following program assembles successfully with 64tass v1.53.1515 and v1.54.1900, but fails with v1.56.2625:
.cpu "65816"
sym = $1234
* = $1000
jsr $4567
jsr sym
zaddr jsr zaddr
.logical $660000 ;bank $66
jsr $664567
jsr sym+$660000
jsr zaddr+$660000 ;fail
.here
The error is:
Test.S:13:17: error: address in different program bank code '$661006'
jsr zaddr+$660000 ;fail
The SourceGen disassembler (https://6502bench.com/) has a general philosophy of "make whatever the user tells me to do work". If they want to JSR to a bank $00 address from bank $66, that's fine; maybe they're on some weird architecture with mirrored addresses or banked RAM where that makes sense.
64tass is unusual in that it requires a 24-bit argument to a 16-bit operation. Other assemblers (cc65, Merlin32) automatically add the program bank in, so you can "JSR $4567" from code outside of bank zero and it assembles. For 64tass I have to specify the current bank explicitly ("JSR $664567").
The situation is a little more complicated when there are labels. Other assemblers don't really pay attention to what's going on and are perfectly happy to "JSR zaddr" so long as zaddr is either a 16-bit value or a 24-bit address in the current bank. 64tass rejects the former case, so I add $660000 to it to convince the assembler that it's in the right bank.
Somewhere between v1.54 and v1.56, 64tass appears to have started screening the symbols more carefully. Now, an expression that involves an address label from a different bank is rejected. This behavior doesn't make sense to me... either the JSR should accept a 16-bit value, or it should accept a 24-bit value in the current bank. Rejecting an equation because part of it is deemed inappropriate seems problematic.
I don't know if this is intentional or an unintended side-effect of other work.
Also fails with v1.55.2176.
Well, this is going to be long. There are good reasons it works as-is.
First of all the JSR instruction can't cross banks and so it's destination must be in the current bank. Worse it's even enforced.
But other assemblers accept anything which fits on 16 bits or is in the current program bank. What's wrong with that?
The "or" as it makes it ambiguous and this leads to non-working code.
Let's say I put sub0 into bank 0 and sub66 in bank 66. Now if I do a JSR sub66 from bank 0 that fails to compile, wrong bank, fine. However a JSR sub0 in bank 66 still succeeds?! That won't execute as likely intended but because it happened to be representable as a 16 bit quantity it passed the check. That's not good.
Not sure if that was convincing but making sure an instruction has a target address within its reach without exception for 16 bit values is important.
Still there must be a way to just assemble a JSR some16bitvalue into "20 xx xx" regardless of the current program bank. And there is:
JSR some16bitvalue,k
Note the ",k" at the end. That means that the current program bank is intended to be used and the value in front is a 16-bit offset within that.
Therefore JSR $4567,k always translates to 20 67 45 regardless of what program bank it is assembled to at the moment.
Writing JSR sub0,k is accepted as well in bank 66 as this time the programmer explicitly asks for a 16-bit address within the current program bank and the address value of sub0 fulfils that (as everything in bank 0 is small enough).
And what's with sub0+$660000 that fails in bank 66 too, why? The assembler knows where the original base (sub0) was even though it got a large enough offset to fool it ;) The error message is there because such huge bank crossing offset might not be intended. Older versions did less checks and other assemblers get away with even less.
As for mirroring schemes those are not handled at the moment. I see the point that for example if part of bank 0 is visible in some higher banks as well then it's rather frustrating that those addresses don't map into the current bank and is awkward to access them.
This is most of the time not visible as usually data bank and direct page are both kept at 0 and so all bank 0 addresses end up with direct page or data bank addressing and that's what's normally wanted in higher banks too if the programmer keeps the DB register in sync with PB on the CPU.
However those instructions which are working with program bank addresses do complain about bank 0 addresses being far away unless manually adjusted to account for their mirroring.
For now I'd say if there's a mirrored bank 0 address in a different program bank which needs jumping to then adding a ",k" will work. If some higher bank is mirrored then the address needs to be cut down to the lower 16 bits. For example this way: "<>label,k".
Please do not add an offset as that's a bad workaround to say that the destination is in the current program bank. Especially that the offset depends on where the code is and so can't be moved to a different bank without manually adjusting it.
Also be careful with JMP which at the moment silently turns into JML without a warning. There will be a warning/error for this soon.
JSR does not turn into a JSL automatically because of the difference how one needs to return with a RTS or RTL. A silent JSL might end up badly and so is not done any more (was wrong in the past).
For JMP and JML I thought back then that there's no such problem however it can go wrong as well. In the above case unfortunately it does as without ",k" the resulting JML will send the CPU to bank 0. Oh well...
I'll give ",k" a try. Appending "+$xx0000" felt like a kluge, but I didn't realize there was something more appropriate.
cc65 emits JML/JSL if it sees a 24-bit hex operand on JMP/JSR, regardless of destination, so I'm used to fine-tuning operands. Automated regression tests FTW. (FWIW, "JMP" is technically the correct opcode mnemonic; "JML" is accepted as an alias. JSR/JSL are distinct for the reasons you mentioned.)
There's a meta-discussion to be had about the role of the assembler and where you draw the line on warning vs. error, but I'm not feeling argumentative. :-)
Thanks for the explanation!
if you need to do bank mirroring there is a "crafty way"
Not sure what you are using, bet lets imagine we have a SNES and LOROM. So you need Bank 00:0000-00:7FFF in banks 00-40 and then 80-BF.
So make a section that is the Shared bank
then
now at the start of the other banks you are using say 1 for example
The virtual puts everything from the section into you new section/bank and throws it all away. But keeps the labels and their relative offset. Now you have the variables in the same bank space while not actually having conflicting labels. because it is now BANK01.var1 which has an address of 010000 but that is fine as 01:0000=00:0000
the cost is now when you want to do something across banks, i.e JSL myCodeInOtherBank, you now have to do JSL BANK01.myCodeInOtherBank and if you move the bank go and change all the BANK01 designations but Find and Replace in Files makes this mostly trivial these days.
Your note made me realize I never followed up on this: the ",k" approach worked fine. You can see it in the output generated for one of the regression tests: https://github.com/fadden/6502bench/blob/master/SourceGen/SGTestData/Expected/20052-branches-and-banks_64tass.S
So you're welcome to close this as "working as intended".
Just a note. JMP/JML was also made stricter in 1.57 similarly to JSR/JSL for the reason above.