We mentioned environment calls (aka ecalls, though they’re also called system calls or syscalls in other languages like MIPS) in chapter 0 when we were going over our "Hello World" program, but what exactly are they?
Essentially, they are the built in functions of an operating system; in this case, the simple operating system of the RARS simulator. They provide access to all the fundamental features, like input and output to/from both the console and files, allocating memory, and exiting. Those are the basics but RARS supports many more, for things ranging from playing MIDI sounds, to getting a random number, to creating GUI dialogs.[1]
Name | a7 | Arguments | Result |
---|---|---|---|
print integer |
1 |
a0 = integer to print |
|
print float |
2 |
fa0 = float to print |
|
print double |
3 |
fa0 = double to print |
|
print string |
4 |
a0 = address of string |
|
read integer |
5 |
a0 = integer read |
|
read float |
6 |
fa0 = float read |
|
read double |
7 |
fa0 = double read |
|
read string |
8 |
a0 = address of input buffer |
works like C’s fgets |
sbrk |
9 |
a0 = size in bytes to allocate |
a0 = address of allocated memory (sbrk is basically malloc but there is no free) |
exit |
10 |
program terminates |
|
print character |
11 |
a0 = character to print (ascii value) |
|
read character |
12 |
a0 = character read |
|
open file |
1024 |
a0 = address of filename |
a0 = file descriptor (negative if error) |
lseek |
62 |
a0 = file descriptor, a1 = offset from base, |
a0 = selected position from beginning of the file or -1 if error |
read from file |
63 |
a0 = file descriptor |
a0 = number of characters read, -1 if error |
write to file |
64 |
a0 = file descriptor |
a0 = number of characters written |
close file |
57 |
a0 = file descriptor |
|
exit2 |
93 |
a0 = termination result |
program terminates, returning number in a0 (only meaningful when run in the terminal, ignored in GUI) |
As you can see, they really only cover the basics. You can read or write the different types, do file I/O using calls identical to POSIX functions (open, read, write, close; see man pages), allocate memory, and exit. Even so, they’re sufficient to build anything you want.
So, what does that table mean? How do these actually work?
The process is:
-
Put the number for the ecall you want in
a7
-
Fill in the appropriate arguments, if any
-
Execute the ecall with
ecall
li a7, 1 # 1 is print integer
li a0, 42 # takes 1 arg in a0, the number to print
ecall # actually execute ecall
You can think of the above as print_integer(42);
. Let’s look at an actual
program that uses a few more ecalls next.
link:code/ecall_example.c[role=include]
I’m using fgets()
instead of scanf("%s", name)
because fgets works the same as the
read string ecall (8).
link:code/ecall_example.s[role=include]
There a few things to note from the example.
We don’t declare global variables for age or height. We could, but there’s no reason
to since we need them in registers to perform the addition anyway. Instead, we
copy/save age to t0
so we can use a0
for 2 more ecalls,
then add height to t0
.
This is generally how it works. Use registers for local variables unless required to do otherwise. We’ll cover more about register use when we cover the RISC-V calling convention.
Another thing is when we print their name, we don’t put 4 in a7
again because it
is still/already 4 from the lines above.
Lastly, many people will declare a string "\n"
and use print string to print a newline,
but it’s easier to use the print char ecall as we do right before exiting.