ARMAssembly:LdrStr:B

Copyright 2018, Clinton A Staley

Concepts

  1. Data directives
  2. Alignment directive
  3. Pseudoinstructions
  4. Mvn and negation

Memory data, ldr, str, mla, mvn, segments, etc. (Prg3.s)

Prg3.s introduces a host of new concepts, building the prior segment's discussion of ldr and str. It performs an assignment that in C would read:

valA = 143 - (atoi("2097") - (valB + -200))

with the value of valB initialized to 2257, and with atoi being the C function that converts a string of digits into a number. There's no particular significance to the assignment, but it illustrates a lot of important concepts, which is the point.

Data directives

We'll start with lines 2-10, ignoring for the moment the .text assembly directive on line 1.

So far, our assembly code has described assembly instructions, with some labels. But programs also have data, and every assembly language includes directives that describe memory areas containing data. These can be intermingled with assembly instructions, and the result is a machine language translation that has code and also data areas, often with prefilled values when the program starts.

Some data directives describe just the size of the data area, leaving the area unininitialized. But more of them describe both a data area and its initial contents. The initial contents become part of the assembled executable, loaded as part of the program when it starts.

There are three pieces of memory data that our assignment statement needs: two are the variables valA and valB, with valB initialized to 2257. The third is the string "2097".

.word directive

Let's start with the valB variable. On line 3, labelled as valB, is a .word directive. This reserves a word, or 4 bytes, of memory, containing the integer value indicated, 2257.

.ascii directive

Next we add the string "2097". The .ascii directive on line 6 describes a quote-delimited sequence or string of five bytes of memory containing ASCII codes, for "2" (code 54), "0" (code 48), "9" (code 57), "7" (code 55) and a fifth byte for ASCII code 0. That 5-byte sequence of data is labelled as numStr; we'll use it via that label in the code.

ASCII code 0 represents no useful symbol. It's usually used as a marker for the end of a string. A C compiler automatically adds it to the end of any string like "2097".

I'll represent these in the diagram with a 4-byte long box containing 2057, and a 5-byte long box containing those 5 ASCII codes, one byte each.

Question 1

Why do we need the \0 escape code for a single ASCII code of 0. Why not simply write "20970"?

Answer 1

Because that would end the string with the ASCII code for the symbol 0, which is actually code 48.

Labels as values

The .word directive on line 10 reserves a word at the label numStrLoc. But instead of a direct number, it has the value "numStr". Numstr is the address of the .ascii string at line 6. Labels like numStr represent memory addresses, and can be used like integer constants in some contexts, including as the value of a .word directive. The word reserved at line 10 is thus an indirect address. We saw registers that contained addresses in the prior segment. Here we have a memory location that contains the address of another location. We'll draw that with a pointer in the diagram.

Question 2

OK, so assuming the entire program starts at address 0, then what value would be at location numStrLoc? What address value exactly does numStr represent?

Answer 2

There is just one data word before numStr (the valB word) so numStr represents address 4, which will be the content at numStrLoc. Note that labels themselves don't take up space in the memory; they're just names for memory addresses. Only instructions and data directives occupy space.

Question 3

Assuming that the string at numStr starts at an address that is divisible by 4, what is wrong with the word at numStrLoc following immediately after it in memory? (Consult earlier lectures on machine language fundamentals if necessary.)

Answer 3

In earlier lecture we talked about how 4-byte quantities needed to start at divisible-by-4 addresses in order to work with a 32-bit memory "bus" that loads memory 4 bytes at a time. If the word at numStrLoc starts right after the five byte "2097\0" string, it won't begin at such an address.

.align directive

The .align 2 directive on line 7 solves the bus-alignment problem just discussed in Question 3. It tells the assembler to move the next data item (.word numStr) so that it starts at an address divisible by 4. This means there will be three unused bytes after the end of "2097\0". I'll expand the diagram to show those extra unused bytes, marking them with '_'.

The "2" argument would suggest a divisible-by-2 address, but it's misleading. There are cases where one might to align by different rules, e.g. by addresses divisible by 2, or even 8. But, alignment is always by some power of 2, e.g. 2, 4, 8. No one tries to align at a divisible-by-3 address, for instance. So, for better or worse, the argument to .align is a power of 2. E.g. .align 2 means to align at an address divisible by 2^2, or 4.

Question 4

So, to be sure that's clear, what assembler directive would ensure the next data item aligns at an address divisible by 8?

Answer 4

.align 3, since 2^3 is 8

And valA

Variable valA is defined via another .word directive at line 48, with a value of 0. This is just another variable, but it is in a different area from the other data. We'll come back to that later.

Prg3.s Code

Now let's look at code for Prg3.s starting on line 15.

Pseudoinstructions

The first line, ldr R0, valB, loads the value 2257 at valB into r0.

But, this doesn't follow the ldr form discussed in the prior segment. For one thing, there's no address register. And it looks as though a direct address, represented by valB, is part of the instruction, when we made a big point about how the ldr has no room for a full address, allowing at most a smallish address offset relative to the address register.

This line is a pseudoinstruction, an assembler shorthand that stands for a more complex (and less readable) real instruction. As the comment indicates, line 15 really means:

ldr R0, [pc, #-16]

Recall from the prior segment that ldr may use the pc as an address register, including offsetting from it. Thus the real instruction offsets -16 bytes from its own location, to arrive at valB, from which it loads 2257.

Question 5

Why, exactly, do we need an offset of -16 from the current PC? What 16 bytes are we backing over, and what is in them?

Answer 5

The numStrLoc word, plus the 8 bytes for the "2097\0" string, including the 3 bytes of padding generated by .align 2, and then finally the 4 bytes of the valB word itself, to get to the starting address of that word.

Question 6

Why not just write mov R0, #2257? We've used mov instructions to load constants into registers in earlier examples.

Answer 6

Recall from earlier segments that the mov instruction allows only a limited range of constant values. Values above 255 are not guaranteed to work with a mov, but a word in memory can contain any value we like. So if you want to put an arbitrary large constant into a register, you set up a memory word containing the constant, and load it in this fashion.

So, in summary, you may write the pseudoinstruction:

ldr RX, someLabel

to load the memory word at someLabel into RX, and the assembler will translate this into the real ldr instruction

ldr RX, [pc, #the_right_offset]

to get the desired value. This works for any label as long as it's not more than 4095 bytes before or after the instruction in question, since that's the limit of ldrs memory offset.

Question 7

Rewrite the ldr on line 20 as a pc-relative ldr, with the correct offset. Remember instructions are each 4 bytes.

Answer 7

We have to back up across 4 instructions, plus the numStrLoc-labelled word itself, so that's 5 words, or 20 bytes: ldr r1, [pc, #-20] or ldr r1, [r15, #-20]

Mvn

The next instruction is a mvn or "move negated". You cannot put even a small negative value into a register using mov, since it allows only positive constants. Instead, use mvn, which is like mov, but it negates the value being copied into the target register, whether it's a constant or the value of another register.

However, for reasons that will be clear when we discuss two's-complement notation in the next module, mvn produces a number one less than would be expected. So, for instance, where we might expect -199 as the value in R1 after line 16, we'll get -200. More on that later; take in on faith for now.

Adding the 2257 in R0 to the -200 in R1 on line 17, we get R0 holding 2257-200, or 2057, the first step in computing the target expression.

To Be Continued...

In the next segment we'll look at the loop on lines 19-34, which converts "2097\0", to integer 2097, and introduces a bunch of new ideas along the way.