RISC-V Syscalls

Making a syscall

An ecall asks the simulator to provide a service such as printing text, reading input, reading calendar time, or working with a file. These services are part of this simulator environment; they are not a RISC-V operating-system interface, and another RISC-V system may use different service numbers or conventions.

The same service numbers and register conventions work in RV32 and RV64. Each syscall has a unique code that is used to identify it. You must put the syscall code inside the a7 register before calling the ecall instruction. As an example:

# Load the integer 42 into register a0, which is
# the register that will be printed by the syscall
li a0, 42
# Set the syscall code for printing an integer
li a7, 1
ecall

A C or C++ program reaches the same services through <sim.h>. Each entry names its function in From C; sim_print_int(42); performs the same service.

Files and folders

File services read and change the files shown in the editor's file explorer. They do not access your computer's disk. Outside a Testcase, file changes remain after the program ends, and Undo can reverse them. Each Testcase works on its own copy, so its changes are discarded.

Paths start from the project's top folder, which is always the current folder. For example, data/scores.txt and /data/scores.txt name the same file. / and . name the top folder. Within a path, . leaves the location unchanged and .. moves up one folder. Trying to go above the top folder fails.

Open, read, write and close use services 1024, 63, 64 and 57. Opening a file returns a descriptor, a number used to identify that open file in later calls. The following services work with paths or list an open folder:

ServiceWhat it does
1026 unlinkDeletes a file.
1030 mkdirCreates a folder.
1031 rmdirDeletes an empty folder.
1034 renameRenames a file or moves it into another folder.
1038 statReports whether a path is a file or folder, and its size.
1065 getdentsLists a folder opened with service 1024.

These six services return a negative error number on failure, such as -2 for a missing path. Open, read, write and seek return only -1 on failure. Close has no return value. Test for a0 < 0 to detect either kind of failure, then inspect the value when the service supplies a specific error number.

ResultMeaning
-2The path or a folder leading to it does not exist.
-17mkdir found a file or folder at the requested path.
-20A file was used where a folder was required.
-21A folder was used where a file was required.
-39The folder contains a file or another folder.

Folder operations follow these rules:

  • mkdir creates an empty folder for the current run. After the run, a folder remains only if a file still exists inside it, directly or in a subfolder. An empty file such as .keep is enough. It does not need any contents. Removing the final file also removes the saved folder. A folder created by mkdir remains available during the current run even if its final file is deleted.
  • An existing path is not necessarily a folder. When mkdir returns -17, use stat to confirm that the path is a folder before treating it as success.
  • rmdir deletes only an empty folder. Use unlink to delete files and rmdir to delete empty subfolders first.
  • rename moves files. It can replace an existing destination file. Moving a folder or replacing a folder with a file returns -21. Replacing a file with a folder returns -20. Renaming a path to itself does nothing, except that the top folder cannot be renamed.
  • A listing starts with . and .., the folder itself and its parent, followed by its files and subfolders in name order. At the top folder, both entries refer to the top folder. Each entry has an inode number, an identifier that stays the same throughout the run.

Listing a folder from assembly

Open the folder with service 1024 and flag 0, then call getdents (1065) with its descriptor and a buffer. It stores one record per entry and returns the total byte count. Call again to continue until it returns 0.

Each record starts with an inode number and a next-entry position, both 4 bytes in RV32 or 8 bytes in RV64. A 2-byte record length and a NUL-terminated name follow. The record's final byte gives its type: 4 for a folder, 8 for a file.

FieldRV32 offsetRV64 offset
Record length (2 bytes)816
Name1018
Type (last byte)length - 1length - 1

The following program uses the RV32 record layout. For RV64, change addi a0, s1, 10 to addi a0, s1, 18, and lhu t0, 8(s1) to lhu t0, 16(s1).

# Lists the top folder, one name per line, with a / after each folder.
        .data
here:   .asciz "."
slash:  .asciz "/"
newline: .asciz "\n"
        .align 3
buffer: .space 512
        .text
main:
        li   a7, 1024         # open "." for reading (flag 0); it is a folder
        la   a0, here
        li   a1, 0
        li   a2, 0
        ecall
        bltz a0, done         # -1: it could not be opened
        mv   s0, a0           # s0 = the folder's descriptor
fill:
        li   a7, 1065         # getdents: fill the buffer with records
        mv   a0, s0
        la   a1, buffer
        li   a2, 512
        ecall
        blez a0, close        # 0: every entry listed; negative: an error
        la   s1, buffer       # s1 = the record being read
        add  s2, s1, a0       # s2 = just past the last record stored
record:
        addi a0, s1, 10       # the name starts at offset 10 (18 in RV64)
        li   a7, 4
        ecall
        lhu  t0, 8(s1)        # the record's length, at offset 8 (16 in RV64)
        add  t1, s1, t0
        lbu  t2, -1(t1)       # its last byte: 4 for a folder, 8 for a file
        li   t3, 4
        bne  t2, t3, line
        la   a0, slash
        li   a7, 4
        ecall
line:
        la   a0, newline
        li   a7, 4
        ecall
        add  s1, s1, t0       # on to the next record
        bltu s1, s2, record
        j    fill             # ask for the next records
close:
        li   a7, 57           # close the folder
        mv   a0, s0
        ecall
done:
        li   a7, 10
        ecall

Folders from C

The C library provides mkdir and stat in <sys/stat.h>, unlink and rmdir in <unistd.h>, rename and remove in <stdio.h>, and opendir, readdir and closedir in <dirent.h>.

mkdir, stat, unlink, rmdir, rename and remove return -1 on failure and set errno. opendir and readdir return NULL on failure instead, also setting errno. readdir also returns NULL at the end of a listing, leaving errno unchanged.

This example creates and lists notes. It assumes that notes is absent or already a folder, and that opening and closing .keep succeeds. The empty .keep file lets the folder remain after the run:

#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>

int main(void) {
    /* The second time the program runs, the folder is there already: not a failure. */
    if (mkdir("notes", 0777) != 0 && errno != EEXIST) {
        perror("mkdir notes");
        return 1;
    }
    /* A folder outlasts the program only while a file is inside it. */
    FILE *keep = fopen("notes/.keep", "w");
    if (keep) fclose(keep);

    DIR *dir = opendir("notes");
    if (!dir) {
        perror("opendir notes");
        return 1;
    }
    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL)
        printf("%s%s\n", entry->d_name, entry->d_type == DT_DIR ? "/" : "");
    closedir(dir);
    return 0;
}

If the path may already name a file, use stat and S_ISDIR to check its type before accepting EEXIST. If keeping the folder matters, also handle failure from fopen and fclose. The example skips this check when fopen returns NULL.

The listing loop above stops on either end of listing or a read error. To distinguish them, set errno = 0 immediately before each readdir call and inspect it when that call returns NULL, before making another library call. Zero means the listing ended. A nonzero value is an error.

The <sim.h> functions such as sim_mkdir and sim_getdents return the raw service result, including negative error numbers, without setting errno. See the Runtime library for the C functions and their return values.

Sound

Services 31 and 33 play a note with a pitch, instrument, volume, and duration in milliseconds. Service 31 starts the note and continues immediately, so notes started close together can overlap to make a chord. Service 33 waits for the requested duration before the next instruction, which lets a sequence of notes keep time as a melody. Notes finish their duration even if the program exits; Stop or a new Build stops them.

For programmable waveforms and sample playback, this editor adds the Synth, a sound chip whose registers are memory at 0xffff0100. RARS does not have it.

Pitch numbers run from 0 to 127. Each increase of 1 moves up a semitone: one piano-key step, counting both white and black keys. For example, 60 is middle C, 62 is D, 64 is E, 67 is G, and 72 is the C one octave above middle C.

General MIDI is a standard list of instrument sounds. These services number instruments from 0 to 127: 0 is acoustic grand piano, 40 is violin, 56 is trumpet, and 73 is flute. No MIDI file or external instrument is needed. Volume runs from 0 (silent) to 127 (loudest).

The editor supplies a General MIDI sound bank, a collection of instrument sounds. The browser loads it automatically when the editor page first plays a note or MIDI file, so that first request waits for loading. The bank is shared across program runs and projects until the page is reloaded.

After a program requests sound, the Sound button appears in the execution dock as a mute/unmute control. It sets Play sound in Preferences, where Sound volume is also available. Both settings apply across projects. Muting leaves program-visible timing and results unchanged.

This editor differs from RARS in two ways:

  • At most 64 notes sound at once, where RARS has no limit. Starting another stops the oldest.
  • Testcases are silent. Service 33 still waits for the requested duration on the testcase's virtual clock, so the wait completes immediately and advances that clock.

Program arguments

Enter Arguments on the Terminal's first line, the Command line, or in the project's Settings panel. They take effect at the next Build; during a Debug session, the line shows what the current run started with. Stop the Debug session to edit the Command line. Arguments are startup strings, separate from text the program reads later through Terminal input.

A Testcase can supply its own Arguments. An empty or whitespace-only field inherits the project's Arguments, so it cannot request an empty argument list while project Arguments are set.

Entering argument strings

The field uses these quoting rules:

  • Whitespace separates arguments unless quoted or escaped.
  • Single quotes ('two words') and double quotes ("two words") keep text in one argument. The quote marks are removed; '' and "" each create an empty argument.
  • Outside quotes, a backslash takes the next character literally: two\ words is one argument. A trailing backslash stays a backslash.
  • Inside single quotes, every character is literal, including a backslash. Inside double quotes, a backslash escapes only a double quote, backslash, dollar sign, or backtick; before other characters it remains a backslash.
  • Adjacent quoted and unquoted parts form one argument: pre"two words" becomes pretwo words.
  • Nothing expands and no command runs. Characters such as $, *, ~, #, |, >, ;, and & are ordinary text.

An unfinished quote stops Build with an Arguments problem naming the column where that quote opened.

For a program named ./main, entering hello "two words" '' a\ b produces argc = 5:

IndexString
argv[0]./main
argv[1]hello
argv[2]two words
argv[3]An empty string
argv[4]a b

Access from assembly and C/C++

The program starts with a0 = argc, the count of all strings, and a1 = argv, the address of an array of pointers to them. Each string is UTF-8 with a terminating NUL; argv[argc] is a null pointer ending the array. argv[0] is the program name: ./ plus the Entry path without its final extension (./main for main.s), or the C or C++ source path without its final extension for a compiled program (./main for main.c). User Arguments begin at argv[1], so argc is at least 1.

Pointers are 4 bytes in RV32 and 8 bytes in RV64. To print the first user argument with the print-string service, perform these operations in order:

OperationRV32RV64
Skip the load if argc < 2li t0, 2, then bltu a0, t0, no_argumentli t0, 2, then bltu a0, t0, no_argument
Load the pointer in argv[1]lw a0, 4(a1)ld a0, 8(a1)
Print the NUL-terminated string at that pointerli a7, 4, then ecallli a7, 4, then ecall

no_argument is a label you supply for the case with no user argument. Loading the pointer into a0 replaces the count, so save argc and argv first if you need them after this service call.

For C or C++, declare int main(int argc, char *argv[]). Check argc > 1 before using argv[1]; for example, if (argc > 1) printf("%s\n", argv[1]); prints the first user argument when <stdio.h> is included. See the Runtime library for startup and function support.

Memory layout and limits

The strings and pointer array occupy the Arguments memory region above the stack, below 0x7FFFFFFC. sp keeps its initial value, 0x7FFFEFFC. The memory view labels the array argv and the strings argv[0], argv[1], … . With the program name ./main and Arguments hello "two words", argc is 3:

ItemRV32RV64
a0 (argc)33
a1 (address of argv)0x7FFFFFD40x7FFFFFC0
argv[1] (address of hello)0x7FFFFFEC0x7FFFFFEC

Undo back to the start keeps these initial arguments. A Testcase's starting registers are applied afterwards, so overriding the argument registers replaces the supplied count or array address.

The Arguments field may contain at most 1,024 arguments, excluding argv[0]. The entire stored block may use at most 4,080 bytes. That total includes the program name and argument strings as UTF-8 bytes, one terminating NUL byte per string, one pointer per string, the final null pointer, and padding to align the pointer array. Pointers use 4 bytes in RV32 and 8 bytes in RV64; alignment padding uses 0–3 bytes or 0–7 bytes respectively. A string cannot contain a NUL character. Build stops with an Arguments problem if the count or storage limit is exceeded.

When porting code from RARS, another RISC-V simulator, account for its different argument layout: RARS omits the program name, starts argv with the first user argument, and places argc and the pointers below the stack, lowering sp. It also uses 4-byte pointers in RV64. In this editor, keep the program-name slot and use the pointer width for your selected architecture.

Print integer

a7 = 1
In
a0 = Integer to print
From C
void sim_print_int(int value)

Print float

a7 = 2
In
fa0 = Float to print
From C
void sim_print_float(float value)

Print double

a7 = 3
In
fa0 = Double to print
From C
void sim_print_double(double value)

Print string

a7 = 4
In
a0 = Address of NUL-terminated UTF-8 string to print
From C
void sim_print_string(const char *text)

Read integer

a7 = 5
Out
a0 = Contains the integer parsed from the input line
Note
Waits for a line typed at the keyboard in the Terminal and Enter, or reads scripted input in a Testcase. Trims surrounding whitespace and parses it as an integer. Invalid input stops the program with an input error.
From C
int sim_read_int(void)

Read float

a7 = 6
Out
fa0 = Contains the floating-point value parsed from the input line
Note
Waits for a line typed at the keyboard in the Terminal and Enter, or reads scripted input in a Testcase. Trims surrounding whitespace and parses it as a floating-point value. Invalid input stops the program with an input error.
From C
float sim_read_float(void)

Read double

a7 = 7
Out
fa0 = Contains the double-precision value parsed from the input line
Note
Waits for a line typed at the keyboard in the Terminal and Enter, or reads scripted input in a Testcase. Trims surrounding whitespace and parses it as a double-precision value. Invalid input stops the program with an input error.
From C
double sim_read_double(void)

Read string

a7 = 8
In
a0 = Address of input buffer; a1 = Size of input buffer in bytes
Out
Writes memory at the address in a0.
Note
Waits for a line typed at the keyboard in the Terminal and Enter, or reads scripted input in a Testcase. Stores UTF-8 with fgets-style limits: for buffer size n, writes at most n-1 bytes, adds a newline if there is room, then a NUL byte. Truncation can split a multibyte character. If n = 1, ignores input and writes only NUL; if n < 1, ignores input and writes nothing.
From C
void sim_read_string(char *buffer, int size)

Sbrk (allocate heap memory)

a7 = 9
In
a0 = Number of bytes to allocate
Out
a0 = Contains address of allocated memory
Note
The heap ends at 0x14000000, 64 MB from the data segment, where RARS stops at 0x10400000; asking for more stops execution with a runtime error.
From C
void *sim_sbrk(int bytes)

Exit (terminate execution)

a7 = 10
Note
Ends the program successfully with exit code 0.
From C
void sim_exit(void)

Print character

a7 = 11
In
a0 = Character to print
Note
Prints the ASCII character whose code is in the low-order byte.
From C
void sim_print_char(int character)

Read character

a7 = 12
Out
a0 = Contains the input character code
Note
Waits for one key in the Terminal and completes immediately; Enter returns 10. A Testcase supplies scripted input.
From C
int sim_read_char(void)

Get cwd

a7 = 17
In
a0 = Address of the buffer to write the path into; a1 = Length of the buffer
Out
a0 = Contains -1 if the path and its terminating null do not fit in the buffer, and is left unchanged otherwise; Writes memory at the address in a0.
Note
Writes /, the top folder, followed by a null byte: the current folder is always the top folder. The minimum buffer size is 2.
From C
int sim_get_cwd(char *buffer, int size)

Time (program time)

a7 = 30
Out
a0 = Low order 32 bits of the program time; a1 = High order 32 bits of the program time
Note
Returns milliseconds since 1970-01-01 UTC. In a Testcase, calendar time starts at 2000-01-01 UTC and advances through waits of service 32. The time CSR (and timeh in RV32) reads this same clock. Undo restores recorded register and CSR readings.
From C
long long sim_time(void)

MIDI out

a7 = 31
In
a0 = Pitch, 0 to 127: 60 is middle C, 62 is D, 64 is E; each step is one semitone; a1 = Duration in milliseconds; a2 = Instrument sound from the General MIDI list, numbered from 0 to 127: 0 piano, 40 violin, 56 trumpet, 73 flute; a3 = Volume, 0 (silent) to 127 (loudest)
Note
Plays a note and continues immediately, allowing notes to overlap. Values outside 0 to 127 use these defaults: invalid pitch becomes 60 (middle C), invalid instrument becomes 0 (piano), and invalid volume becomes 100. A negative duration becomes 1000 milliseconds, as in RARS. The first note or MIDI-file request after opening or reloading the editor page waits while the browser loads the sound bank, the collection of instrument sounds; later runs share it. At most 64 notes sound at once, where RARS has no limit: starting another stops the oldest. Notes finish their duration after the program exits. Testcases are silent.
From C
void sim_midi_out(int pitch, int duration, int instrument, int volume)

Sleep

a7 = 32
In
a0 = The length of time to sleep in milliseconds
Note
Service 32 - Lets that much program time pass before the next instruction. The editor stays responsive while it waits and the wait costs no instructions, so a program idling on the keyboard never reaches the execution limit; in a testcase it completes at once and advances the virtual clock read by service 30 and the time CSR (timeh in RV32) instead.
From C
void sim_sleep(int milliseconds)

MIDI out synchronous

a7 = 33
In
a0 = Pitch, 0 to 127: 60 is middle C, 62 is D, 64 is E; each step is one semitone; a1 = Duration in milliseconds; a2 = Instrument sound from the General MIDI list, numbered from 0 to 127: 0 piano, 40 violin, 56 trumpet, 73 flute; a3 = Volume, 0 (silent) to 127 (loudest)
Note
Plays a note with the same argument defaults as service 31, then waits for the requested duration before the next instruction. A sequence of these calls can play a melody. Muting or stopping the audible note does not shorten the wait. Testcases are silent; the wait completes immediately and advances the virtual clock by the duration.
From C
void sim_midi_out_sync(int pitch, int duration, int instrument, int volume)

Print integer in hexadecimal

a7 = 34
In
a0 = Integer to print
Note
Displayed value is 8 hexadecimal digits, left-padding with zeroes if necessary.
From C
void sim_print_hex(int value)

Print integer in binary

a7 = 35
In
a0 = Integer to print
Note
Displayed value is 32 bits, left-padding with zeroes if necessary.
From C
void sim_print_binary(int value)

Print integer as unsigned

a7 = 36
In
a0 = Integer to print
Note
Displayed as unsigned decimal value.
From C
void sim_print_unsigned(unsigned value)

Set seed

a7 = 40
In
a0 = Generator ID: any integer selects an independent pseudorandom sequence; a1 = Seed for corresponding pseudorandom number generator
Note
Seeds the generator selected by a0. Each generator ID has an independent sequence. An unseeded ID starts from host randomness in an interactive run and a fixed per-ID seed in a testcase, making testcase runs repeatable. Values follow Java’s Random sequence for the same seed. Undo restores the state before a draw or reseed.
From C
void sim_random_seed(int generator, int seed)

Random int

a7 = 41
In
a0 = Generator ID: any integer selects an independent pseudorandom sequence
Out
a0 = Contains the next pseudorandom, uniformly distributed int value from this random number generator's sequence
Note
Each generator ID has an independent sequence. An unseeded ID starts from host randomness in an interactive run and a fixed per-ID seed in a testcase, making testcase runs repeatable. Service 40 sets an explicit seed. Values follow Java’s Random sequence for the same seed. Undo restores the state before a draw or reseed.
From C
int sim_random_int(int generator)

Random int range

a7 = 42
In
a0 = Generator ID: any integer selects an independent pseudorandom sequence; a1 = Upper bound of range of returned values
Out
a0 = Contains pseudorandom, uniformly distributed int value in the range 0 <= [int] < [upper bound], drawn from this random number generator's sequence
Note
Each generator ID has an independent sequence. An unseeded ID starts from host randomness in an interactive run and a fixed per-ID seed in a testcase, making testcase runs repeatable. Service 40 sets an explicit seed. Values follow Java’s Random sequence for the same seed. Undo restores the state before a draw or reseed.
From C
int sim_random_int_range(int generator, int bound)

Random float

a7 = 43
In
a0 = Generator ID: any integer selects an independent pseudorandom sequence
Out
fa0 = Contains the next pseudorandom, uniformly distributed float value in the range 0.0 <= f < 1.0 from this random number generator's sequence
Note
Each generator ID has an independent sequence. An unseeded ID starts from host randomness in an interactive run and a fixed per-ID seed in a testcase, making testcase runs repeatable. Service 40 sets an explicit seed. Values follow Java’s Random sequence for the same seed. Undo restores the state before a draw or reseed.
From C
float sim_random_float(int generator)

Random double

a7 = 44
In
a0 = Generator ID: any integer selects an independent pseudorandom sequence
Out
fa0 = Contains the next pseudorandom, uniformly distributed double value in the range 0.0 <= f < 1.0 from this random number generator's sequence
Note
Each generator ID has an independent sequence. An unseeded ID starts from host randomness in an interactive run and a fixed per-ID seed in a testcase, making testcase runs repeatable. Service 40 sets an explicit seed. Values follow Java’s Random sequence for the same seed. Undo restores the state before a draw or reseed.
From C
double sim_random_double(int generator)

ConfirmDialog

a7 = 50
In
a0 = Address of NUL-terminated UTF-8 string that is the message to user
Out
a0 = Contains value of user-chosen option 0: Yes 1: No 2: Cancel
From C
int sim_confirm_dialog(const char *message)

InputDialogInt

a7 = 51
In
a0 = Address of NUL-terminated UTF-8 string that is the message to user
Out
a0 = Contains int read; a1 = Contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field
From C
int sim_input_dialog_int(const char *message, int *status)

InputDialogFloat

a7 = 52
In
a0 = Address of NUL-terminated UTF-8 string that is the message to user
Out
f0 = Contains the floating-point value parsed from the input line; a1 = Contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field
From C
float sim_input_dialog_float(const char *message, int *status)

InputDialogDouble

a7 = 53
In
a0 = Address of NUL-terminated UTF-8 string that is the message to user
Out
fa0 = Contains the double-precision value parsed from the input line; a1 = Contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field
From C
double sim_input_dialog_double(const char *message, int *status)

InputDialogString

a7 = 54
In
a0 = Address of NUL-terminated UTF-8 string that is the message to user; a1 = Address of input buffer; a2 = Size of input buffer in bytes
Out
a1 = Contains status value 0: OK status. Buffer contains the input string. -2: Cancel was chosen. No change to buffer. -3: OK was chosen but no data had been input into field. No change to buffer. -4: length of the input string exceeded the specified maximum. Buffer contains the maximum allowable input string plus a terminating null.; Writes memory at the address in a1.
Note
Stores the entered string as UTF-8 in the buffer: at most n-1 bytes for buffer size n, followed by a newline if there is room and a terminating NUL. Truncation can split a multibyte character.
From C
int sim_input_dialog_string(const char *message, char *buffer, int size)

MessageDialog

a7 = 55
In
a0 = Address of NUL-terminated UTF-8 string that is the message to user; a1 = The type of message to be displayed: 0: error message, indicated by Error icon 1: information message, indicated by Information icon 2: warning message, indicated by Warning icon 3: question message, indicated by Question icon other: plain message (no icon displayed)
From C
void sim_message_dialog(const char *message, int type)

MessageDialogInt

a7 = 56
In
a0 = Address of NUL-terminated UTF-8 string that is an information-type message to user; a1 = Int value to display in string form after the first string
From C
void sim_message_dialog_int(const char *message, int value)

Close file

a7 = 57
In
a0 = File or folder descriptor to close
From C
void sim_close(int fd)

MessageDialogDouble

a7 = 58
In
a0 = Address of NUL-terminated UTF-8 string that is an information-type message to user; fa0 = Double value to display in string form after the first string
From C
void sim_message_dialog_double(const char *message, double value)

MessageDialogString

a7 = 59
In
a0 = Address of NUL-terminated UTF-8 string that is an information-type message to user; a1 = Address of NUL-terminated UTF-8 string to display after the first string
From C
void sim_message_dialog_string(const char *message, const char *text)

MessageDialogFloat

a7 = 60
In
a0 = Address of NUL-terminated UTF-8 string that is an information-type message to user; fa1 = Float value to display in string form after the first string
From C
void sim_message_dialog_float(const char *message, float value)

Lseek (move file position)

a7 = 62
In
a0 = File descriptor; a1 = Offset in bytes; a2 = Where the offset counts from: 0 the start of the file, 1 the current position, 2 the end of the file
Out
a0 = Contains the new position, counted from the beginning of the file (-1 if error)
Note
Descriptors 0, 1, and 2 (standard input, output, and error) cannot be repositioned.
From C
int sim_lseek(int fd, int offset, int whence)

Read from file

a7 = 63
In
a0 = File descriptor; a1 = Address of the buffer to fill; a2 = Maximum number of bytes to read
Out
a0 = Number of bytes read, 0 at end of file or input, or -1 on failure; Writes memory at the address in a1.
Note
Reads up to the requested number of bytes into the buffer, without adding a string terminator. Returns the byte count, 0 at the end of the file or input, or -1 on failure. Descriptor 0 reads standard input from the Terminal. Enter a line and press Enter. Ctrl+D or End of input on an empty line ends standard input. In a Testcase, input ends when the scripted answers run out. See Files and folders for Undo and Testcase behavior.
From C
int sim_read(int fd, void *buffer, int length)

Write to file

a7 = 64
In
a0 = File descriptor; a1 = Address of the bytes to write; a2 = Number of bytes to write
Out
a0 = Number of bytes written, or -1 on failure
Note
Writes the requested bytes to an open file. Descriptors 1 and 2 write to the Terminal as standard output and standard error. Returns the number of bytes written or -1 on failure. See Files and folders for Undo and Testcase behavior.
From C
int sim_write(int fd, const void *buffer, int length)

Exit2 (terminate with value)

a7 = 93
In
a0 = Termination result
Note
Ends the program with the signed exit code in a0. The editor shows this code in the Log.
From C
void sim_exit2(int code)

Open file

a7 = 1024
In
a0 = Address of NUL-terminated UTF-8 path; a1 = Flags; a2 = Mode (ignored, pass 0)
Out
a0 = File or folder descriptor, or -1 on failure
Note
Opens a file and returns its descriptor, the number used by later read, write and close calls. | Flag | Access | Effect on the file | | ---- | ------ | ------------------ | | 0 | Read | Requires an existing file. | | 1 | Write | Creates a file or empties an existing one. | | 9 | Write, append | Creates a file if needed. Writes go to its end. | | 2 | Read and write | Requires an existing file. | | 3 | Read and write | Creates a file or empties an existing one. | | 10 | Read and write, append | Creates a file if needed. Writes go to its end. | Flags 2, 3 and 10 are editor extensions. Reads and writes on the same descriptor share one file position. Flag 0 can also open a folder for listing with service 1065; folders cannot be opened for writing. The mode argument is ignored. Descriptors 0, 1 and 2 are reserved for standard input, standard output and standard error. Opened files and folders receive descriptors starting at 3. Failure returns -1. See Files and folders for paths, persistence, Undo and Testcase behavior.
From C
int sim_open(const char *path, int flags, int mode)

Unlink (delete a file)

a7 = 1026
In
a0 = Address of NUL-terminated UTF-8 path of the file to delete
Out
a0 = 0 on success, or a negative error number
Note
Deletes the file at the path. Returns -2 if the path does not exist or -21 if it names a folder. To delete an empty folder, use rmdir. The shared file rules describe errors, Undo and Testcases.
From C
int sim_unlink(const char *path)

Mkdir (make a folder)

a7 = 1030
In
a0 = Address of NUL-terminated UTF-8 path of the new folder
Out
a0 = 0 on success, or a negative error number
Note
Creates an empty folder. Its parent must already exist: -2 means a missing parent and -20 means a file was used as a folder. Returns -17 if a file or folder already occupies the requested path. Before treating -17 as success, use stat to confirm that the existing path is a folder. The new folder lasts for the current run. It remains afterward only if a file still exists inside it, directly or in a subfolder. An empty .keep file is enough. See Files and folders for the full lifetime, Undo and Testcase rules.
From C
int sim_mkdir(const char *path)

Rmdir (delete an empty folder)

a7 = 1031
In
a0 = Address of NUL-terminated UTF-8 path of the folder to delete
Out
a0 = 0 on success, or a negative error number
Note
Deletes an empty folder. Returns -39 if it contains a file or subfolder, -20 if the path names a file, or -2 if it does not exist. The top folder cannot be deleted (-16). See the shared file rules for folder lifetime, Undo and Testcases.
From C
int sim_rmdir(const char *path)

Rename (rename or move a file)

a7 = 1034
In
a0 = Address of NUL-terminated UTF-8 path of the file; a1 = Address of the NUL-terminated UTF-8 destination path
Out
a0 = 0 on success, or a negative error number
Note
Renames a file or moves it into another folder. The destination folder must exist (-2 if missing, -20 if a file is used as a folder). An existing destination file is replaced. Renaming a file to its own path does nothing. Folders cannot be moved: a folder as the source or destination returns -21, except that a folder onto an existing file returns -20. Renaming a folder to itself does nothing. The top folder cannot be renamed. See the shared file rules for Undo and Testcases.
From C
int sim_rename(const char *from, const char *to)

Stat (describe a path)

a7 = 1038
In
a0 = Address of NUL-terminated UTF-8 path; a1 = Address of a buffer of 104 bytes in RV32 and 128 in RV64, which the service fills
Out
a0 = 0 on success, or a negative error number; Writes memory at the address in a1.
Note
Reports the type and size of the path in the Linux struct stat layout for this target. The buffer must hold 104 bytes in RV32 or 128 in RV64. Fields are little-endian: | Field | Byte offset in RV32 and RV64 | Size in bytes | | ----- | --------------------------- | ------------- | | Inode number | 8 | 8 | | Mode | 16 | 4 | | Link count | 20 | 4 | | File size | 48 | 8 | The type bits mode & 0xF000 are 0x4000 for a folder or 0x8000 for a file. File size counts bytes. A folder has size 0. The inode number identifies the entry throughout the run. Link count is 1, and all other fields, including times and owners, are 0. Returns -2 for a missing path or -20 if the path uses a file as a folder. From C, stat() in <sys/stat.h> accesses these fields by name.
From C
int sim_stat(const char *path, void *buffer)

Getdents (list a folder)

a7 = 1065
In
a0 = File descriptor of a folder, opened with service 1024 and flag 0; a1 = Address of the buffer to fill; a2 = Size of the buffer in bytes
Out
a0 = Number of bytes stored, 0 at the end of the listing, or a negative error number; Writes memory at the address in a1.
Note
Lists the entries present when the folder was opened: . and .. first, then files and subfolders in name order. It fills the buffer with complete records and returns their total byte count. Call again to continue. A return value of 0 means every entry has been listed. Each record has this little-endian layout: | Field | RV32 byte offset | RV64 byte offset | Size in bytes | | ----- | ---------------- | ---------------- | ------------- | | Inode number | 0 | 0 | 4 in RV32, 8 in RV64 | | Position of next entry | 4 | 8 | 4 in RV32, 8 in RV64 | | Record length | 8 | 16 | 2 | | NUL-terminated UTF-8 name | 10 | 18 | Variable | The record length includes padding and a final type byte: 4 for a folder, 8 for a file. Add that length to the record address to find the next record. The next-entry position counts entries, not bytes. Returns -22 if the buffer cannot hold the next record, -9 if the descriptor is not open, or -20 if it belongs to a file. Reading a folder with service 63 returns -1. Close it with service 57. From C, opendir() and readdir() in <dirent.h> handle these records for you.
From C
int sim_getdents(int fd, void *buffer, int length)