Skip to main content

Calling C Code

·784 words·4 mins
Documentation - This article is part of a series.
Part 11: This Article

Defining Foreign Functions In Sage
#

Sage allows you to call functions from target backends, such as C, using the extern keyword. This keyword tells the compiler to expect the symbol to be provided by the target backend.

// Signatures for the C functions `getchar` and `putchar`.
extern fun getchar(): Char;
extern fun putchar(ch: Char);

// A signature for a function that wraps the C function `memcpy`.
extern fun memcpy(dst: &mut Int, src: &Int, cells: Int);

let a = [1, 2, 3, 4, 5];
let mut b = [0, 0, 0, 0, 0];

// Call our C function to copy the first 5 elements of `a` into `b`.
memcpy(&mut b, &a, 5);
About Sage: The web-demo of Sage provides only two extern functions in the backend: alert and eval. You can use eval to run arbitrary JavaScript code, so it’s still complete to interoperate with other code in the browser.

Writing C Code For Sage
#

Now that we can tell Sage to expect C functions, we need to write the C code that will be called. The compiled output will automatically include an ffi.h file containing your foreign function implementations if you provide one! Copy and paste your C function definitions into the ffi.h file, and Sage will take care of the rest.

Simple add Function Example
#

To demonstrate how to write C code for Sage, let’s write a simple add function in C. This will take two integers from the Sage VM and give back their sum.

The Sage FFI performs communication with extern functions using a buffer. This buffer contains the arguments and return values for foreign functions. Whenever Sage calls an extern function, it pushes its arguments onto the buffer and then calls the function. The function reads the arguments from the buffer, performs its computation, and then pushes the return value onto the buffer. Sage then reads the return value from the buffer and continues execution.

So, our add function will read two integers from the buffer, add them together, and then push the result back onto the buffer.

Filename: main.sg
// Our external function we want to call from Sage
extern fun add(a: Int, b: Int): Int;

// Call our C function to add 2 and 3
println(add(3, 4));

This will call our add function defined below in ffi.h and compute the result 7.

Filename: ffi.h
// The C backend implementation for
// 
//     `extern fun add(a: Int, b: Int): Int`
// 
// The backend automatically adds two underscores to the
// function name to avoid conflicts with other symbols.
void __add() {
    // Get our two numbers from the buffer.
    // Arguments are passed in order. There are two arguments,
    // and the last value is always at ffi_ptr[0].
    // So, the first argument is at ffi_ptr[-1], and the
    // second is at ffi_ptr[0].
    cell a = ffi_ptr[-1], b = ffi_ptr[0];
    // Pop the arguments off the buffer.
    ffi_ptr -= 2;

    // Add them together
    cell result;
    result.i = a.i + b.i;

    // Push the result back onto the buffer
    ++ffi_ptr; // Add space for the result
    *ffi_ptr = result; // Push the result
}
sage -tc main.sg
gcc out.c -o main
./main
Output:
7

Calling memcpy From Sage
#

Let’s write a more complex example: calling the C function memcpy from Sage. This function copies an array of bytes from one location to another. This is useful for efficiently copying large amounts of data.

Filename: main.sg
// Our external function we want to call from Sage
extern fun memcpy(dst: &mut Int, src: &Int, cells: Int);

let a = [1, 2, 3, 4, 5];
let mut b = [0, 0, 0, 0, 0];

// Call our C function to copy the first 5 elements of `a` into `b`.
memcpy(&mut b, &a, 5);
println(b);

This will call our memcpy function defined below in ffi.h and print the result [1, 2, 3, 4, 5].

Filename: ffi.h
#include <string.h>

// The C backend implementation for
//
//     `extern fun memcpy(dst: &mut Int, src: &Int, cells: Int)`
//
// The backend automatically adds two underscores to the
// function name to avoid conflicts with other symbols.
void __memcpy() {
    // Get our three numbers from the buffer.
    // Arguments are passed in order. There are three arguments,
    // and the last value is always at ffi_ptr[0].
    // So, the first argument is at ffi_ptr[-2], the
    // second is at ffi_ptr[-1], and the third is at ffi_ptr[0].
    cell dst = ffi_ptr[-2], src = ffi_ptr[-1], cells = ffi_ptr[0];
    // Pop the arguments off the buffer.
    ffi_ptr -= 3;

    // Copy the memory
    memcpy(dst.p, src.p, cells.i * sizeof(cell));
}
sage -tc main.sg
gcc out.c -o main
./main
Output:
[1, 2, 3, 4, 5]
Documentation - This article is part of a series.
Part 11: This Article