Skip to content
Permalink
Browse files

Add support for FFI calls into C library functions!

This patch also drops the tiny amount of asm stdlib for C to make use of
existing test functions.

This is something I've wanted for so long! Woo! 🥇

The actual implementation turned out to be a lot simpler than the version in my
head because I was preparing for a general purpose register allocator. Since all
scheme functions are still called through stack, the one function called via FFI
at any time gets an exclusive access to the 6 registers making implementation
dead simple. This would be a completely different story if either the scheme
functions needed those registers or if I had to allocate the registers for the C
stdlib.
  • Loading branch information...
jaseemabid committed Oct 4, 2019
1 parent 0dd19ff commit 069ccc07e1d3c218b2355563f5b552baf9e02989
Showing with 89 additions and 23 deletions.
  1. +1 −1 rs/Makefile
  2. +1 −1 rs/src/cli.rs
  3. +6 −6 rs/src/compiler.rs
  4. +14 −0 rs/src/core.rs
  5. +58 −1 rs/src/runtime.rs
  6. +9 −0 rs/stdlib.c
  7. +0 −14 rs/stdlib.s
@@ -25,7 +25,7 @@

CCFLAGS = -g -ggdb3 -m64 -Wall -Wpedantic -Wno-override-module -fno-asynchronous-unwind-tables -fomit-frame-pointer

inc: runtime.c stdlib.s inc.s
inc: runtime.c stdlib.c inc.s
gcc $(CCFLAGS) $^ -o inc

.PHONY: test
@@ -37,7 +37,7 @@ pub fn build(config: &Config) -> bool {
.arg("-fomit-frame-pointer")
.arg("-fno-asynchronous-unwind-tables")
.arg("-O0")
.arg("stdlib.s")
.arg("stdlib.c")
.arg("runtime.c")
.arg(&config.asm())
.arg("-o")
@@ -169,8 +169,8 @@ pub mod emit {
use crate::{
compiler::state::State,
core::Expr::{self, *},
immediate, lambda, lang, primitives, runtime, strings,
x86::{self, Ins, Reference, Register::*, Relative, ASM},
*,
};

/// Clear (mask) all except the least significant 3 tag bits
@@ -255,13 +255,13 @@ pub mod emit {
Cond { pred, then, alt } => cond(s, pred, then, alt),

List(list) => match list.as_slice() {
[Identifier(f), args @ ..] =>
// User defined or runtime functions
{
if s.functions.contains(f) || runtime::FUNCTIONS.contains(&&f.as_str()) {
return lambda::call(s, f, &args);
[Identifier(f), args @ ..] => {
if s.functions.contains(f) {
lambda::call(s, f, &args)
} else if let Some(x) = primitives::call(s, f, args) {
x
} else if runtime::contains(f.as_str()) {
runtime::ffi(s, f, &args)
} else {
panic!("Unknown function {} called with args: {:?}", f, &args)
}
@@ -47,6 +47,20 @@ pub struct Code {
pub body: Vec<Expr>,
}

impl Expr {
/// Is this expression a constant that would fit in a word?
/// This is useful for FFI into C
pub fn unbox(self: &Expr) -> Option<i64> {
match self {
Expr::Number(n) => Some(*n),
Expr::Boolean(true) => Some(1),
Expr::Boolean(false) => Some(0),
Expr::Char(c) => Some(i64::from(*c)),
_ => None,
}
}
}

/// Pretty print an Expr
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -1,2 +1,59 @@
//! Runtime functions implemented in C or ASM
pub const FUNCTIONS: [&str; 1] = ["string-length"];
use crate::{
compiler::{emit::eval, state::State},
core::Expr,
x86::{
self,
Reference::*,
Register::{self, *},
ASM, WORDSIZE,
},
};

const SYMBOLS: [&str; 2] = ["string-length", "exit"];

/// Call a function with System V calling convention
///
/// See x86 module docs for details. Probably a very good idea to read this
/// [guide to linux syscalls][guide] twice before jumping into this.
///
/// [guide]: https://blog.packagecloud.io/eng/2016/04/05/the-definitive-guide-to-linux-system-calls
pub fn ffi(s: &mut State, name: &str, args: &[Expr]) -> ASM {
let mut asm = ASM(vec![]);

if args.len() > 6 {
panic!("foreign function {} called with more than 6 arguments: {:?}", &name, args)
}

let name = name.replace("-", "_");

for (i, arg) in args.iter().enumerate() {
let target: Register = x86::SYS_V[i];

asm += match arg.unbox() {
Some(c) => x86::mov(Register(target), Const(c)).into(),
None => eval(s, &arg) + x86::mov(Register(target), Register(RAX)),
}
}

// Extend stack to hold the current local variables before creating a
// new frame for the function call. `si` is the next available empty
// slot, `(+ si wordsize)` is the current usage. Add this to `RSP` to
// reserve this space before the function gets called. Not doing this
// will result in the called function to override this space with its
// local variables and corrupt the stack.
let locals = s.si + WORDSIZE;
if locals != 0 {
asm += x86::add(RSP.into(), locals.into());
asm += x86::call(&name);
asm += x86::sub(RSP.into(), locals.into());
} else {
asm += x86::call(&name)
}

asm
}

pub fn contains(name: &str) -> bool {
SYMBOLS.contains(&name)
}
@@ -0,0 +1,9 @@
#include <inttypes.h>

int64_t string_length(int64_t val) {

int64_t *p = (int64_t *)(val - 5);
int64_t len = *(p + 0);

return len * 8;
}

This file was deleted.

0 comments on commit 069ccc0

Please sign in to comment.
You can’t perform that action at this time.