64tass wants to place its output into a 64KB region of memory,
starting at the address "*" is set to, and continuing without
wrapping around the end of the bank. Some files aren't meant to be
handled that way, so we need to generate the output differently.
If the file's output fits nicely, it's considered "loadable", and
is generated in the usual way. If it doesn't, it's treated as
"streamable", and the initial "* = addr" directive is omitted
(leaving "*" at zero), and we go straight to ".logical" directives.
65816 code with an initial address outside bank 0 is treated as
"streamable" whether or not the contents fit nicely in the designated
64K area. This caused a minor change to a few of the 65816 tests.
A new test, 20240-large-overlay, exercises "streamable" by creating
a file with eight overlapping 8KB segments that load at $8000.
While the file as a whole fits in 64KB, it wouldn't if loaded at
the desired start address.
Also, updated the regression test harness to report assembler
failure independently of overall test failure. This makes it easier
to confirm that (say) ACME v0.96.4 still works with the code we
generate, even though it doesn't match the expected output (which
was generated for v0.97).
(problem was raised in issue #98)
The initial implementation was testing the byte value rather than
the converted value, so backslashes were getting through in high
ASCII strings. PETSCII and C64 screen codes don't really have a
backslash so it's not really an issue there.
The new implementation handles high ASCII correctly. The various
201n0-char-encoding-x regression tests have been updated to verify
this.
Two things changed: (1) string literals can now hold backslash
escapes like "\n"; (2) MVN/MVP operands can now be prefixed with '#'.
The former was a breaking change because any string with "\" must
be changed to "\\". This is now handled by the string operand
formatter.
Also, improved test harness output. Show the assembler versions at
the end, and include assembler failure messages in the collected
output.
Added a compiled C implementation of strlen(). The most interesting
part about this is that it references a 16-bit value via direct-page
address $ff, which means you'd want a local variable with
address=$ff and width=2. The current UI prevents this.
The test for max allowed value was assuming 16-bit addresses.
We had no tests for 24-bit values, so this adds a 65816-specific
version of 20170-external-symbols.
Generate a 6502 test from the 65816 version by substituting the
16-bit instructions with 8-bit no-ops. There's a lot of project
edits and weird stuff in the test, so this was much easier than
starting over.
The 65816 variant is largely unchanged, though it could now be
stripped down to the stack-offset instructions.
Split into 6502/65816 portions. The 6502 version is the original
with a few in-place substitutions (e.g. JMP for BRL). The 65816
version is only needed to exercise special handling of PEA/PER.
We have a single character-encoding test that is cloned 3x so we can
exercise the different values for the project's default character
set. It was a 65816 test because it tested 16-bit immediate char
operands, but that's a very small part of it.
The 65816-specific portion is now 20122-char-encoding. The rest is
now 201{2,3,4}0-char-encoding-X.
Tests 10022-embedded-instructions and 10032-flags-and-branches were
a mix of 6502 and 65816 code. The 6502 code has been separated into
its own file, so that the tests can be run on 8-bit-only assemblers.
We append an assembler identifier to generated code. For Merlin 32,
this was "_Merlin32". All of the other assemblers use a lower-case
string, which makes Merlin look a little weird, so it has been
changed to "_merlin32".
Windows filesystems are generally case-insensitive, so this won't
likely affect anything.
The 10042-data-recognition test has no 65816-specific content, so it
should be named 10040-data-recognition.
Also, remove header comment from 20102-label-dp.
C64 PRG files are pretty common. Their salient feature is that they
start with a 16-bit value that is used as the load address. The
value is commonly generated by the assembler itself, rather than
explicitly added to the source file.
Not all assemblers know what a PRG file is, and some of them handle
it in ways that are difficult to guarantee in SourceGen. ACME adds
the 16-bit header when the output file name ends in ".prg", cc65
uses a modified config file, 64tass uses a different command-line
option, and Merlin 32 has no idea what they are.
This change adds PRG file detection and handling to the 64tass code
generator. Doing so required making a few changes to the gen/asm
interfaces, because we now need to have the generator pass additional
flags to the assembler, and sometimes we need code generation to
start somewhere other than offset zero. Overall the changes were
pretty minor.
The 20042-address-changes test needed a 6502-only variant. A new test
(20040-address-changes) has been added and given a PRG header. As
part of this change the 65816 variant was changed to use addresses
in bank 2, which uncovered a code generation bug that this change
also fixes.
The 64tass --long-address flag doesn't appear to be necessary for
files <= 65536 bytes long, so we no longer emit it for those.
(issue #90)
Modified the asm source generators and on-screen display to show the
DP arg for BBR/BBS as hex. The instructions are otherwise treated
as relative branches, e.g. the DP arg doesn't get factored into the
cross-reference table.
ACME/cc65 put the bit number in the mnemonic, 64tass wants it to be
in the first argument, and Merlin32 wants nothing to do with any of
this because it's incompatible with the 65816.
Added an "all ops" test for W65C02.
We weren't altering the status flags after a BRK because of the
assumption that a BRK was a crash. For an inline BRK, such as a SOS
call, execution continues. We need to mark NVZC indeterminate or
we may incorrectly handle conditional branches that follow.
The BRK instruction now uses the same flag updater as JSR, since it's
effectively a subroutine call to unknown code. If execution doesn't
continue across the BRK then the flags don't matter.
Updated 20182-extension-scripts to exercise this.
The "smart" PLP handler tries to recover the flags from an earlier
PHP. The non-smart version just marks all the flags as indeterminate.
This doesn't work well on the 65816 in native mode, because having
the M/X flags in an indeterminate state is rarely what you want.
Code rarely uses PLP to reset the flags to a specific state, preferring
explicit SEP/REP. The analyzer is more likely to get the correct
answer by simply leaving the flags in their prior state.
A test case has been added to 20052-branches-and-banks, which now has
"smart PLP" disabled.
When we have relocation data available, the code currently skips the
process of matching an address with a label for a PEA instruction when
the instruction in question doesn't have reloc data. This does a
great job of separating code that pushes parts of addresses from code
that pushes constants.
This change expands the behavior to exclude instructions with 16-bit
address operands that use the Data Bank Register, e.g. "LDA abs"
and "LDA abs,X". This is particularly useful for code that accesses
structured data using the operand as the structure offset, e.g.
"LDX addr" / "LDA $0000,X"
The 20212-reloc-data test has been updated to check the behavior.
Add 20222-data-bank to regression test suite. This exercises handling
of 16-bit operands with inter- and intra-bank references, and tests the
smartness in "smart PLB".
Also, update a couple of older tests that broke because the DBR is no
longer always the same as the PBR. This just required adding "B=K"
in a few places to restore the original output.
If code accesses the high/low parts of a 32-bit address value with
no label, it auto-generates labels for addr+2 and addr. The reloc
handler was replacing the unformatted bytes with a single multi-byte
format, hiding the label at addr+2.
The easy fix is to have the reloc data handler skip the entry. This
is less useful than other approaches, but much simpler.
Added a test to 20212-reloc-data.
This test exercises the relocation data feature. The test file is
generated from a multi-segment OMF file that was hex-edited to have
specific attributes (see 20212-reloc-data-lnk.S for instructions).
The test also serves as a way to exercise the OMF converter.
Also, implement the Bank Relative flag.
The Absolute Indirect and Absolute Indirect Long addressing modes
(e.g. "JMP (addr)" and "JMP [addr]") are 16-bit values in bank 0.
The code analyzer was placing them in the program bank, which
meant the wrong symbol was being used.
Also, tweak some docs.
Code generated for 64tass was incorrect for JSR/JMP to a location
outside the file bounds. A test added to 20052-branches-and-banks
revealed an issue with cc65 generation as well.
Two basic problems:
(1) cc65, being a one-pass assembler, can't tell if a forward-referenced
label is 16-bit or 24-bit. If the operand is potentially ambiguous,
such as "LDA label", we need to add an operand width disambiguator.
(The existing tests managed to only do backward references.)
(2) 64tass wants the labels on JMP/JSR absolute operands to have 24-bit
values that match the current program bank. This is the opposite of
cc65, which requires 16-bit values. We need to distinguish PBR vs.
DBR instructions (i.e. "LDA abs" vs. "JMP abs") and handle them
differently when formatting for "Common".
Merlin32 doesn't care, and ACME doesn't work at all, so neither of
those needed updating.
The 20052-branches-and-banks test was expanded to cover the problematic
cases.
My original goal was to add a sign-extended decimal format, but that
turned out to be awkward. It works for data items and instructions
with immediate operands (e.g. "LDA #-1"), but is either wrong or
useless for address operands, since most assemblers treat integers
as 32-bit values. (LDA -1 is not LDA $FFFF, it's LDA $FFFFFFFF,
which is not useful unless your asm is doing an implicit mod.)
There's also a bit of variability in how assemblers treat negative
values, so I'm shelving the idea for now. I'm keeping the updated
tests, which are now split into 6502 / 65816 parts.
Also, updated the formatter to output all decimal values as unsigned.
Most assemblers were fine with negative values, but 64tass .dword
insists on positive. Rather than make the opcode conditional on the
value's range, we now just always output unsigned decimal, which
all current assemblers accept.
Add a 6502-only version of the 20032-labels-and-symbols test. The
65816 version could get away with just the 65816-specific stuff, but
there's no real need to modify it. (The next time I update it I may
remove the duplicate label since that requires hand-editing.)
The regression tests were written with the assumption that all cross
assemblers would support 6502, 65C02, and 65816 code. There are a
few that support 65816 partially (e.g. ACME) or not at all. To best
support these, we need to split some of the tests into pieces, so
that important 6502 tests aren't skipped simply because parts of the
test also exercise 65816 code.
The first step is to change the regression test naming scheme. The
old system used 1xxx for tests without project files, and 2xxx for
tests with project files. The new system uses 1xxxN / 2xxxN, where
N indicates the CPU type: 0 for 6502, 1 for 65C02, and 2 for 65816.
For the 1xxxN tests the new value determines which CPU is used,
which allows us to move the "allops" 6502/65C02 tests into the
no-project category. For 2xxxN it just allows the 6502 and 65816
versions to have the same base name and number.
This change updates the first batch of tests. It involves minor
changes to the test harness and a whole bunch of renaming.
ACME has a "real" PC and a "pseudo" PC. The "real" PC determines the
initial position in a 64KB buffer used to hold assembler output. If
the amount of code generated runs off the end, the assembler fails
with "produced too much code".
The source code generator in SourceGen was outputting a "real" PC
for the first address range and "psuedo" PCs for any address ranges
that followed. This produced nice results for code with a single
range, but caused problems for multi-range sources if the initial
range was high in memory and a later range was lower in memory.
While the assembler isn't actually generating more than 64KB of code,
ACME's buffer management was detecting an overflow.
Now, if a source file has multiple address ranges, we set the "real"
PC to $0000 and use a "pseudo" PC for all ranges. Output for projects
with a single address range is unmodified.
JSR/JSL calls with inline data have the option of reporting that
they don't continue, which causes the code analyzer to treat them
as JMPs instead. There was a bug that was causing the no-continue
flag to be lost in certain circumstances.
The code now explicitly records the plugin's response in an Anattrib
flag. Test 2022-extension-scripts has been updated with a test case
that exercises this situation.
The code was making an unwarranted assumption about how the flags
were being set. For example, ORA #$00 can't know if the previous
contents of the accumulator were nonzero, only that the instruction
hasn't made them nonzero, but instead of marking the Z-flag
"indeterminate" it was leaving the flag in its previous state. This
produces incorrect results if the previous instruction didn't set
its flags from the accumulator contents, e.g. it was an LDX.
Test 1003-flags-and-branches has been updated to test these states.
These were being overlooked because they didn't actually cause
anything to happen (a no-op .ORG sets the address to what it would
already have been). The assembly source generator works in a way
that causes them to be skipped, so everybody was happy.
This seemed like the sort of thing that was likely to cause problems
down the road, however, so we now split regions correctly when a
no-op .ORG is encountered. This affects the uncategorized data
analyzer and selection grouping.
This changed the behavior of the 2004-numeric-types test, which was
visibly weird in the UI but generated correct output.
Added the 2024-ui-edge-cases test to provide a place to exercise
edge cases when testing the UI by hand. It has some value for the
automated regression test, so it's included there.
Also, changed the AddressMapEntry objects to be immutable. This
is handy when passing lists of them around.
For nonzero values we were leaving Z=prev, which is wrong when Z=0
because the AND result might be zero. Now if Z=1 we leave it alone,
but if Z=0 we now set it to Z=?.
Test 1003-flags-and-branches was testing for the (incorrect)
behavior, so we're now running into a BRK. This is fine.
We want to be able to declare a symbol for a struct or buffer that
spans the entire width, and then declare more-specific items within
it that take precedence. This worked for everything but the very
first byte, because on an exact match we were resolving the conflict
alphabetically.
Now, if one is wider than the other, we use the narrower definition.
Updated 2021-external-symbols with some additional test cases.
We're doing this for user labels but not for project/platform
symbols. So if you have a constant named "BCC" you can't assemble
your code with certain assemblers. Now we rename it automatically.
Added a quick test to 2007-labels-and-symbols. (No change to ACME,
which barfs on the test.)
In 1.5.0-dev1, as part of changes to the way label localization
works, the local variable de-duplicator started checking against a
filtered copy of the symbol table. Unfortunately it never
re-generated the table, so a long-lived LocalVariableLookup (like
the one used by LineListGen) would set up the dup map wrong and
be inconsistent with other parts of the program.
We now regenerate the table on every Reset().
The de-duplication stuff also had problems when opcodes and
operands were double-clicked on. When the opcode is clicked, the
selection should jump to the appropriate variable declaration, but
it wasn't being found because the label generated in the list was
in its original form. Fixed.
When an instruction operand is double-clicked, the instruction operand
editor opens with an "edit variable" shortcut. This was showing
the de-duplicated name, which isn't necessarily a bad thing, but it
was passing that value on to the DefSymbol editor, which thought it
was being asked to create a new entry. Fixed. (Entering the editor
through the LvTable editor works correctly, with nary a de-duplicated
name in sight. You'll be forced to rename it because it'll fail the
uniqueness test.)
References to de-duplicated local variables were getting lost when
the symbol's label was replaced (due largely to a convenient but
flawed shortcut: xrefs are attached to DefSymbol objects). Fixed by
linking the XrefSets.
Given the many issues and their relative subtlety, I decided to make
the modified names more obvious, and went back to the "_DUPn" naming
strategy. (I'm also considering just making it an error and
discarding conflicting entries during analysis... this is much more
complicated than I expected it to be.)
Quick tests can be performed in 2019-local-variables:
- go to +000026, double-click on the opcode, confirm sel change
- go to +000026, double-click on the operand, confirm orig name
shown in shortcut and that shortcut opens editor with orig name
- go to +00001a, down a line, click on PROJ_ZERO_DUP1 and confirm
that it has a single reference (from +000026)
- double-click on var table and confirm editing entry
The list of EQUs at the top of the file is sorted, by type, then
value, then name. This adds width as an additional check, so that
if you have overlapping items the widest comes first.
This is nice when you have a general entry for a block of data, and
then specific entries for some locations within the block.
We emit address adjustments like "LDA thing+1", which are usually
small values. Sometimes they're large, e.g. "LDA thing-61440",
which is harder to understand than "LDA thing-$F000". So now we
show small adjustments in decimal, and large adjustments in hex.
The current definition of "small" is abs(adjust) < 256.
The uncategorized data scanner isn't supposed to create strings or
".fill" directives that straddle labels, long comments, notes,
visualizations, or ORG directives. The test for crossing an ORG
directive is incomplete, and doesn't correctly handle no-op ORGs
(where the new address is the same as the old address).
The code generator doesn't output ORGs that are hidden inside other
things, so we're not generating bad code, but it looks funny on
screen and may cause problems later on. The 2004-numeric-types test
has the basic .align/.fill/.bulk directive tests, and now has an
extended set of tests for uncategorized data region splitting.
It's pretty common for code to access BUFFER-1,X, but it's rare for
the buffer to live on zero page memory. More often than not we're
auto-formatting zero-page operands with a nearby symbol when they're
just simple variables. It's more confusing than useful, so we don't
do that anymore.
Correct handling of local variables. We now correctly uniquify them
with regard to non-unique labels. Because local vars can effectively
have global scope we mostly want to treat them as global, but they're
uniquified relative to other globals very late in the process, so we
can't just throw them in the symbol table and be done. Fortunately
local variables exist in a separate namespace, so we just need to
uniquify the variables relative to the post-localization symbol table.
In other words, we take the symbol table, apply the label map, and
rename any variable that clashes.
This also fixes an older problem where we weren't masking the
leading '_' on variable labels when generating 64tass output.
The code list now makes non-unique labels obvious, but you can't tell
the difference between unique global and unique local. What's more,
the default type value in Edit Label is now adjusted to Global for
unique locals that were auto-generated. To make it a bit easier to
figure out what's what, the Info panel now has a "label type" line
that reports the type.
The 2023-non-unique-labels test had some additional tests added to
exercise conflicts with local variables. The 2019-local-variables
test output changed slightly because the de-duplicated variable
naming convention was simplified.