Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

misc: enables adding extern functions through c api #169

Merged
merged 2 commits into from
May 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions crates/mun_abi/src/autogen_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,20 @@ impl fmt::Display for FunctionSignature {
}
}

impl PartialEq for FunctionSignature {
fn eq(&self, other: &Self) -> bool {
self.return_type() == other.return_type()
&& self.arg_types().len() == other.arg_types().len()
&& self
.arg_types()
.iter()
.zip(other.arg_types().iter())
.all(|(a, b)| PartialEq::eq(a, b))
}
}

impl Eq for FunctionSignature {}

unsafe impl Send for FunctionSignature {}
unsafe impl Sync for FunctionSignature {}

Expand Down
45 changes: 22 additions & 23 deletions crates/mun_runtime/src/assembly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,33 @@ impl Assembly {
.map(|f| f.prototype.name())
.collect();

for (fn_ptr, fn_sig) in self.info.dispatch_table.iter() {
for (fn_ptr, fn_prototype) in self.info.dispatch_table.iter() {
// Only take signatures into account that do *not* yet have a function pointer assigned
// by the compiler.
if !fn_ptr.is_null() {
continue;
}

let fn_name = fn_sig.name();
// ASSUMPTION: If we removed a function from the `Assembly`, we expect the compiler to
// have failed for any old internal references to it. Therefore it is safe to leave the
// `old_assembly`'s functions in the `runtime_dispatch_table` as we perform this check.
if !fn_names.contains(fn_name) && runtime_dispatch_table.get_fn(fn_name).is_none() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("Failed to link: function `{}` is missing.", fn_name),
));
// Ensure that the required function is in the runtime dispatch table and that its signature
// is the same.
match runtime_dispatch_table.get_fn(fn_prototype.name()) {
Some(fn_definition) => {
if fn_prototype.signature != fn_definition.prototype.signature {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("Failed to link: function '{}' is missing. A function with the same name does exist, but the signatures do not match (expected: {}, found: {}).", fn_prototype.name(), fn_prototype, fn_definition.prototype),
));
}
}
None => {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!(
"Failed to link: function `{}` is missing.",
fn_prototype.name()
),
))
}
}
}

Expand All @@ -102,19 +113,7 @@ impl Assembly {
.get(fn_definition.prototype.name())
.expect("The dependency must exist after the previous check.");

// TODO: This is a hack
if fn_definition.prototype.signature.return_type()
!= fn_prototype.signature.return_type()
|| fn_definition.prototype.signature.arg_types().len()
!= fn_prototype.signature.arg_types().len()
|| !fn_definition
.prototype
.signature
.arg_types()
.iter()
.zip(fn_prototype.signature.arg_types().iter())
.all(|(a, b)| PartialEq::eq(a, b))
{
if fn_prototype.signature != fn_definition.prototype.signature {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("Failed to link: function '{}' is missing. A function with the same name does exist, but the signatures do not match (expected: {}, found: {}).", fn_prototype.name(), fn_prototype, fn_definition.prototype),
Expand Down
22 changes: 20 additions & 2 deletions crates/mun_runtime/tests/marshalling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,24 @@ fn extern_fn_missing() {
assert_invoke_eq!(isize, 16, driver, "main");
}

#[test]
fn extern_fn_invalid_signature() {
extern "C" fn add_int() -> i32 {
0
}

let result = TestDriver::new(
r#"
extern fn add(a: i32, b: i32) -> i32;
pub fn main() -> i32 { add(3,4) }
"#,
)
.insert_fn("add", add_int as extern "C" fn() -> i32)
.spawn();

assert!(result.is_err());
}

#[test]
#[should_panic]
fn extern_fn_invalid_sig() {
Expand Down Expand Up @@ -596,7 +614,7 @@ fn test_primitive_types() {

#[test]
fn can_add_external_without_return() {
extern "C" fn foo(a: i64) {
extern "C" fn foo(a: i32) {
println!("{}", a);
}

Expand All @@ -606,7 +624,7 @@ fn can_add_external_without_return() {
pub fn main(){ foo(3); }
"#,
)
.insert_fn("foo", foo as extern "C" fn(i64) -> ());
.insert_fn("foo", foo as extern "C" fn(i32) -> ());
let _: () = invoke_fn!(driver.runtime_mut(), "main").unwrap();
}

Expand Down
68 changes: 64 additions & 4 deletions crates/mun_runtime_capi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use std::{os::raw::c_char, time::Duration};
use crate::error::ErrorHandle;
use crate::hub::HUB;
use failure::err_msg;
use runtime::{Runtime, RuntimeOptions};
use runtime::Runtime;

pub(crate) type Token = usize;

Expand All @@ -36,6 +36,40 @@ pub trait TypedHandle {
#[derive(Clone, Copy)]
pub struct RuntimeHandle(*mut c_void);

/// Options required to construct a [`RuntimeHandle`] through [`mun_runtime_create`]
///
/// # Safety
///
/// This struct contains raw pointers as parameters. Passing pointers to invalid data, will lead to
/// undefined behavior.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct RuntimeOptions {
/// The interval at which changes to the disk are detected. `0` will initialize this value to
/// default.
pub delay_ms: u32,

/// Function definitions that should be inserted in the runtime before a mun library is loaded.
/// This is useful to initialize `extern` functions used in a mun library.
///
/// If the [`num_functions`] fields is non-zero this field must contain a pointer to an array
/// of [`abi::FunctionDefinition`]s.
pub functions: *const abi::FunctionDefinition,

/// The number of functions in the [`functions`] array.
pub num_functions: u32,
}

impl Default for RuntimeOptions {
fn default() -> Self {
RuntimeOptions {
delay_ms: 0,
functions: std::ptr::null(),
num_functions: 0,
}
}
}

/// Constructs a new runtime that loads the library at `library_path` and its dependencies. If
/// successful, the runtime `handle` is set, otherwise a non-zero error handle is returned.
///
Expand All @@ -51,6 +85,7 @@ pub struct RuntimeHandle(*mut c_void);
#[no_mangle]
pub unsafe extern "C" fn mun_runtime_create(
library_path: *const c_char,
options: RuntimeOptions,
handle: *mut RuntimeHandle,
) -> ErrorHandle {
if library_path.is_null() {
Expand All @@ -59,6 +94,12 @@ pub unsafe extern "C" fn mun_runtime_create(
.register(err_msg("Invalid argument: 'library_path' is null pointer."));
}

if options.num_functions > 0 && options.functions.is_null() {
return HUB
.errors
.register(err_msg("Invalid argument: 'functions' is null pointer."));
}

let library_path = match CStr::from_ptr(library_path).to_str() {
Ok(path) => path,
Err(_) => {
Expand All @@ -77,10 +118,29 @@ pub unsafe extern "C" fn mun_runtime_create(
}
};

let runtime_options = RuntimeOptions {
let delay_ms = if options.delay_ms > 0 {
options.delay_ms
} else {
10
};

let user_functions =
std::slice::from_raw_parts(options.functions, options.num_functions as usize)
.iter()
.map(|def| {
abi::FunctionDefinitionStorage::new_function(
def.prototype.name(),
def.prototype.signature.arg_types(),
def.prototype.signature.return_type(),
def.fn_ptr,
)
})
.collect();

let runtime_options = runtime::RuntimeOptions {
library_path: library_path.into(),
delay: Duration::from_millis(10),
user_functions: Default::default(),
delay: Duration::from_millis(delay_ms.into()),
user_functions,
};

let runtime = match Runtime::new(runtime_options) {
Expand Down
28 changes: 23 additions & 5 deletions crates/mun_runtime_capi/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ fn make_runtime(lib_path: &Path) -> RuntimeHandle {
let lib_path = CString::new(lib_path).unwrap();

let mut handle = RuntimeHandle(ptr::null_mut());
let error = unsafe { mun_runtime_create(lib_path.as_ptr(), &mut handle as *mut _) };
let error = unsafe {
mun_runtime_create(
lib_path.as_ptr(),
RuntimeOptions::default(),
&mut handle as *mut _,
)
};
assert_eq!(error.token(), 0, "Failed to create runtime");
handle
}
Expand Down Expand Up @@ -95,7 +101,8 @@ test_invalid_runtime!(

#[test]
fn test_runtime_create_invalid_lib_path() {
let handle = unsafe { mun_runtime_create(ptr::null(), ptr::null_mut()) };
let handle =
unsafe { mun_runtime_create(ptr::null(), RuntimeOptions::default(), ptr::null_mut()) };
assert_ne!(handle.token(), 0);

let message = unsafe { CStr::from_ptr(mun_error_message(handle)) };
Expand All @@ -111,8 +118,13 @@ fn test_runtime_create_invalid_lib_path() {
fn test_runtime_create_invalid_lib_path_encoding() {
let invalid_encoding = ['�', '\0'];

let handle =
unsafe { mun_runtime_create(invalid_encoding.as_ptr() as *const _, ptr::null_mut()) };
let handle = unsafe {
mun_runtime_create(
invalid_encoding.as_ptr() as *const _,
RuntimeOptions::default(),
ptr::null_mut(),
)
};
assert_ne!(handle.token(), 0);

let message = unsafe { CStr::from_ptr(mun_error_message(handle)) };
Expand All @@ -128,7 +140,13 @@ fn test_runtime_create_invalid_lib_path_encoding() {
fn test_runtime_create_invalid_handle() {
let lib_path = CString::new("some/path").expect("Invalid library path");

let handle = unsafe { mun_runtime_create(lib_path.into_raw(), ptr::null_mut()) };
let handle = unsafe {
mun_runtime_create(
lib_path.into_raw(),
RuntimeOptions::default(),
ptr::null_mut(),
)
};
assert_ne!(handle.token(), 0);

let message = unsafe { CStr::from_ptr(mun_error_message(handle)) };
Expand Down