.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.

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.

  • .text goes at 0x00400000, four bytes per instruction.
  • .data goes at 0x10010000, 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

directivewhat it writes
.word 1, 2, 3one 4 byte word per value, aligned to a multiple of 4
.dword 1, 2one 8 byte value each, which "Going 64-bit" uses
.half 1, 2one 2 byte half per value, aligned to a multiple of 2
.byte 1, 2, 3one 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 8that many bytes, left at zero and not aligned
.align nmoves the next thing up to a multiple of 2 to the n
.float, .doublefloating 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.

labeladdressbyteswhat it is
w0x1001000044 33 22 11one word, lowest byte first
h0x1001000466 55one half
b0x1001000601 02 03three single bytes
first0x1001000948 69H and i, and nothing after them
second0x1001000B48 69 00the same two, terminated
room0x10010010eight zeroesreserved, 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 name makes a label visible outside the file. main is the one that matters here.
  • .extern name size declares a label defined somewhere else and reserves size bytes for it in the global data area, which is what gp points near.
  • .macro and .end_macro define 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.
  • .section names a section the way gcc writes 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.

Show solution