.data, .text and directives
RISC-V has sections, so the assembler knows which of your lines are code and which are data. The directives that fill a data section, the one that says where the program starts, and the ecall without which the program walks into your subroutines.
The overview of this topic is in Assembly basics. The same topic in M68K, Z80.
A program is instructions and the data they work on, and something has to say which lines are which. The M68K assembler has no answer to that: it walks your source from top to bottom and puts each line at the next free address. RISC-V has sections, so the two are separated by name and land in different parts of memory.
.data and .text
.data opens a data section and .text opens a code section. Everything after one of them belongs
to it until the next one, and the assembler collects all the .data in your file into one block and
all the .text into another.
.textgoes at0x00400000, four bytes per instruction..datagoes at0x10010000, as many bytes as each directive asks for.
.data 0x10000000 with an address after it puts that section somewhere else, which is what a
program that wants its data at a fixed place does.
t0 comes out at 10010000, t1 at 10010004 and t2 at 10010014. Open the memory panel at
10010000 and the first bytes are 48 69 00 00, the two characters of "Hi", its terminator and
the byte the .align 2 skipped over.
That is the shape of every RISC-V program in this course: constants at the top, a data section, then
a text section with main in it and an ecall at the end.
The data directives
| directive | what it writes |
|---|---|
.word 1, 2, 3 | one 4 byte word per value, aligned to a multiple of 4 |
.dword 1, 2 | one 8 byte value each, which "Going 64-bit" uses |
.half 1, 2 | one 2 byte half per value, aligned to a multiple of 2 |
.byte 1, 2, 3 | one byte per value, anywhere |
.ascii "Hi" | the characters, with no terminator |
.asciz "Hi" | the characters and a zero byte after them |
.string "Hi" | another name for .asciz |
.space 8 | that many bytes, left at zero and not aligned |
.align n | moves the next thing up to a multiple of 2 to the n |
.float, .double | floating point numbers, which this course does not use |
The spelling is .asciz, with one i and one z. MIPS writes .asciiz, and a program moved
between the two assemblers trips over that line first.
| label | address | bytes | what it is |
|---|---|---|---|
w | 0x10010000 | 44 33 22 11 | one word, lowest byte first |
h | 0x10010004 | 66 55 | one half |
b | 0x10010006 | 01 02 03 | three single bytes |
first | 0x10010009 | 48 69 | H and i, and nothing after them |
second | 0x1001000B | 48 69 00 | the same two, terminated |
room | 0x10010010 | eight zeroes | reserved, and word aligned |
first runs straight into second, so a program that prints first prints HiHi: .ascii writes
what you gave it and no more, and a string with nothing marking its end is a string nothing can find
the end of. .asciz is the one to use, and the z is for the zero.
room would have started at 0x1001000E without the .align 2, which is even but not a multiple of
four, so the first sw into it would have ended the run. .word, .half and .dword align
themselves; .space and the two string directives do not.
Labels and .eqv
A label goes at the start of a line and ends with a colon. It is a name for the address of whatever
comes next, and nothing distinguishes a label on an instruction from a label on a .word: both are
addresses, and la t0, main is as legal as la t0, numbers. The one name a label may not have is a
register's, so s1: and ra: are build errors and sum: is fine.
.eqv gives a name to a number, and the assembler replaces the name with the number everywhere it
appears. It reserves no memory and produces no instruction. The comma between the two operands is
optional, so .eqv SIZE, 4 and .eqv SIZE 4 both work.
t0 and t2 both come out at 4, and they got there in completely different ways: SIZE became a
4 inside the instruction, while values became the address 0x10010000 and the instruction went
to memory for what was there. t3 is 16.
The assembler does no arithmetic. li t0, SIZE*4 is a build error, and so is lw t2, values+4,
which the MIPS assembler would have taken. A name multiplied by something has to be multiplied by
the program, as the slli above does, and an offset from a label has to go in the offset(base) of
the load.
Use .eqv for anything you would write as a #define in C: the length of an array, the size of an
element, a service number, a screen width.
Where a program starts, and where it stops
Execution begins at the first instruction in .text, whatever it is called. Put a subroutine at
the top of your file and the program runs the subroutine, hits its ret with ra still 0, and ends
with Instruction load access error, because address 0 has no code in it.
.globl main fixes that. It marks the label main as global, and a global main becomes the entry
point wherever in the file it is written. .global is the same directive under a second spelling.
t0 is 1, t6 is 111 and t1 is 2, so main ran first and helper ran when it was called.
Delete the .globl main line and press Run: the program starts at helper, and the ret on its
second line jumps to address 0.
The other end matters as much. A RISC-V program ends with li a7, 10 and ecall, and without
it execution carries straight on into whatever is written next. If that is a subroutine, the program
runs it, returns to the middle of main through the ra the last call left there, and goes round
until the Playground's instruction budget runs out. Every program in this course that has a
subroutine ends with those two lines before the first one.
A program with no subroutines can leave them out, which is what the first lectures did: with nothing after the last instruction, the simulator has no next instruction to run and stops.
The rest of the directives
.globl namemakes a label visible outside the file.mainis the one that matters here..extern name sizedeclares a label defined somewhere else and reservessizebytes for it in the global data area, which is whatgppoints near..macroand.end_macrodefine a name that expands into the lines between them, with%in front of each parameter..include "file.asm"pulls in another file, which this editor's single-file projects have no use for..sectionnames a section the waygccwrites it, and is there so that compiler output assembles.
t0 comes out at 20. A macro is copied into the program at every use, so those two lines are two
add instructions, and a macro that took ten lines would be ten instructions each time. A
subroutine is the alternative that costs one call. Build it and the two expanded lines are marked
<2> in the disassembly, which is the macro expansion depth.
Your turn
Write a data section holding the three words 100, 200 and 300 at values, followed by eight bytes of
room at room, and leave the address of room in t0. Three words take twelve bytes, so it comes
out at 0x1001000C.
Show solution
The second one has the subroutine written above main, so the program starts in the wrong place and
ends on a jump to address 0. Add the one line that makes main the entry point.