Nikki stdlib
The nikki standard library (stdlib) contains many (WIP) libraries to create programs. Calling functions in the stdlib is done the same as regular functions, though many of the functions in the stdblib are actually implemented in zig.
Calling functions
To call a function you must declare it in the constants sections. Here, we declare the std::print
function:
(def "my-module"
(let print (function "std" "print"))
; other declarations
...)
The form (function <module> <name>) declares a function in the constants table.
Modules are organized using double semicolons, for example,
std, std::math, std::fs, std::fmt. Then <name> is the name of the function to declare.
So the above declaration (function "std" "print") creates a reference to the print function
that lives inside the std module. Its full name would be std::print.
To call a function use the .calli opcode, like this:
(defun "<entrypoint>"
(.calli print)
; more code)
This will call the print function we declared before. Here’s a more complete example:
(def "my-module"
; Here we declare the function
(let print (function "std" "print"))
(defun "<entrypoint>"
; Here we call the function
(.calli print)
; more code...
(.ret)))
Sending data
Functions may receive parameters & return data. Each function defines what data it expects to receive and return.
To send data you use registers 11 to 19, by copying the data to that register, then
calling the function. In the next example we do that:
; std::print expects a String at rf11
(let print (function "std" "print"))
(let name (string "John Doe\n"))
(defun "<entrypoint>"
; Here we load the string `name` into rf11
(.lr rf11 name)
; register 11 is now set up, so the call will be able to use its data
(.calli print)
; more code
As another example, std::math::mod_u64 expects 2 parameters: a u64 in rv11 and a u64 in
rv12. So we would set up those 2 registers before calling with .calli.
As the programmer, it is your job to properly set up any register the function expects. And function implementors must document what registers & values are needed for a function to run.
Receiving data
Functions return data also through registers 11 to 19. So after calling a function, any
returned data will be stored in those registers.
The next example defines and calls std::math::mod_u64:
; std::math::mod_u64 expects:
; - a: u64 in rv11
; - b: u64 in rv12
; and returns:
; - the modulo `a % b` in rv11
(let mod_u64 (function "std::math" "mod_u64"))
(let a (u64 16))
(let b (u64 5))
(defun "<entrypoint>"
; Here we set up rv11 and rv12
(.lv rv11 a)
(.lv rv12 b)
; now we call the function
(.calli mod_u64)
; after the function runs, our result is in rv11
; so we use it for other operations:
(.mod_u64 __ rv11 __)
; more code
Error handling (TBD)
Nikki (and the nara vm) support errors as values. When a function may fail, it will return a
error value in register rv10/rf10, and the caller should check those to see.
For instance, say the function std::math::div_u64 may fail if b == 0. So that function:
- Receives
a: u64inrv11 - Receives
b: u64inrv12 - If there’s an error, it sets a value in
rv10andrv11is unspecified - Otherwise, the result is at
rv11
Furthermore, std::math::div_u64 specifies these values for rv10:
0, if there is no error.rv11is populated1, ifbis equal to0
So, after calling std::math::div_u64 the caller should check the value of rf10,
and then decide if its safe to use rv11.
Error convention
Registers 10 to 19 are caller saved.
When a function may error it uses rv10 to signal the error. In such functions:
- All non-error paths must set
rv10to0, to signal that there was no error. - A value of
0must be the only success value. No other value may be used to signal success.- Instead, the function must use
rv11-rv19to signal multiple possible success cases. For instance, it can return a union through those registers.
- Instead, the function must use
- When
rv10is not0, registersrv11-rv19andrf11-rf19are unspecified and must not be used.
The .calli-assert opcode is provided for convenience, it will call a function and halt execution
if rv10 is not set to 0.