As the assembler stands after last week, we have a
list of labels but once the first pass of the assembly code is processed we still need to
replace the placeholder branching addresses with actual address that the label
points to. I have created the function linkLabelsInMemory to handle this. It is
a fairly large function so I will just highlight the important parts of how it
works. For those of you who wish to browse the full function it is part of the
Assembler.kt file. The source is located at
https://github.com/BillySpelchan/VM2600 . The GIT repository is not in synch with this blog.
In fact, at the time I am posting this entry I have finished all but 3 of the
instructions in the 6502 Assembler and am ahead by a couple dozen or so articles.
I am writing the articles as I finish the relative work so the articles are
written as thing happen, errors and all. While I am way ahead right now things have a way of
getting suddenly busy so being ahead is a good thing as that way there won’t be
a lull in posts.
The first
issue that I really needed to deal with when reaching this stage was how to
handle errors. If this point has been reached, there is an assembled file in
the assembler’s banks but any branches that are based around labels have 0 as
their target address. There is a possiblity that some links are missing and I need a way to handle this. There is also the possiblity of duplicate targets, which might be okay but should issue a warning. I decided to simply create a list of error messages that
mix warnings and errors. All the labels that can be processed are, so in theory
the resulting binary could be ran even if there were some warning messages, but
the warnings are likely indication of problems with the code.
Kotlin has
the rather nice feature of protecting the programmer from null pointer issues.
It does this by requiring that variables have a value. It is possible to get
around this requirement by adding a question mark to the end of the name of the
variable type. The compiler doesn’t like using these variables unless the
programmer has checked for null or is using the exclamation point operator
telling the compiler that the programmer is positive it is not a null pointer
at this time. Of course, if the programmer is wrong then you will get a null
pointer exception when you run the program but that bug is on the programmer not the compiler.
The
processing of links is one of the cases where we need a null. This is because
it is possible for an assembly language file to have a label link token without
having the matching label declaration token. If there is a label that only has
link-type nodes then there is an error. This is why our fist step in linking
the labels is to find the target address for that link.
for ( (label, links) in labelList) {
var linkTarget:AssemblerLabel? =
null
for (asmlink in links) {
if (asmlink.typeOfLabel ==
AssemblerLabelTypes.TARGET_VALUE) {
if (linkTarget != null)
errorList.add("Warning: ${asmlink.labelName} declared multiple
times!")
linkTarget = asmlink;
}
}
Notice that
there is another issue that this pass through the list will detect. What if
more than one address is the target? This is a problem as a branch can only go
to one address. The solution here is to use the last provided address while
adding a warning to the list of errors. Even though I am calling this a
warning, because the resulting code is potentially valid, there is a very
strong possibility that this was a mistake on the programmer’s part.
If at the
end of this process, no target address is found then clearly there is an error.
Once we have a target address, which should be the normal result of this, we
can go through the list of labels again and process them for what type of link
it is. For the most part this is straight forward, with the only non-trivial
case is the relative link. This simply uses the address of the instruction
after the relative branching instruction as a base address to be subtracted
from the target address.
AssemblerLabelTypes.RELATIVE_ADDRESS
-> {
val baseAddress =
asmlink.addressOrValue + 1
val offset =
(linkTarget.addressOrValue - baseAddress) and 255
banks[asmlink.bank][asmlink.addressOrValue] = offset
}
Zero page
and low bytes are essentially the same with a high byte simply being the page
to use. These are calculated from the provided target value as follows:
val
targetHigh:Int = (linkTarget.addressOrValue / 256) and 255
val
targetLow = linkTarget.addressOrValue and 255
A full
address is two bytes and is stored with the low byte first followed by the high
byte. The code is not quite ready to handle banks properly so I am going to
have to revisit it. I am also thinking of adding a warning for cases where
relative links are out of range.
Some of you
may have noticed that there is no check for the case where there is a label
declaration but no label links to that declaration. While there is the
possibility that this is a mistake and the programmer forgot to use a label, it
is also possible that the label is there for clarification or for potentially
use in the future.
At this
point everything we need to assemble a program exists we just need to put it
all together, which will be next week.
No comments:
Post a Comment