Wednesday, February 7, 2018

Linking Labels


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