Skip to content

Commit

Permalink
feat: Implement #[rubyfunction] and Wasi::get_version.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hywan committed May 10, 2021
1 parent 8b81b39 commit 5749ae3
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 3 deletions.
178 changes: 178 additions & 0 deletions crates/rutie-derive-macros/src/function.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use proc_macro2::{Group, Ident, Span, TokenStream, TokenTree};
use quote::{quote, ToTokens};
use syn::{punctuated::Punctuated, token::Colon2, *};

pub fn entry(
_attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let function = parse_macro_input!(input as ItemFn);

if !function.attrs.is_empty() {
panic!("Functions in `#[rubyfunction]` do not support attributes for the moment");
}

let function_name = function.sig.ident.to_string();

if function.sig.constness.is_some() {
panic!("Function `{}` cannot be `const`", function_name);
}

if function.sig.asyncness.is_some() {
panic!("Function `{}` cannot be `async`", function_name);
}

if function.sig.unsafety.is_some() {
panic!("Function `{}` cannot be `unsafe`", function_name);
}

if function.sig.abi.is_some() {
panic!("Function `{}` cannot have a different ABI", function_name);
}

if !function.sig.generics.params.is_empty() {
panic!("Function `{}` cannot be generic", function_name);
}

if function.sig.variadic.is_some() {
panic!("Function `{}` cannot be variadic", function_name);
}

let ruby_function_name = &function.sig.ident;
let ruby_function_block = &function.block;
let ruby_function_visibility = &function.vis;

let ruby_arguments_parsing = {
let (ruby_input_names, ruby_input_types): (
Vec<Ident>,
Vec<Punctuated<PathSegment, Colon2>>,
) = function
.sig
.inputs
.iter()
.filter_map(|input| match input {
FnArg::Typed(PatType { pat, ty, .. }) => match (&**pat, &**ty) {
(
Pat::Ident(ident),
Type::Reference(TypeReference { elem, .. }),
) => match &**elem {
Type::Path(TypePath {
qself: None,
path: Path { segments: ty, .. },
}) => Some((ident.ident.clone(), ty.clone())),
_ => panic!(
"Typed input has an unsupported form (function `{}`)",
function_name
),
},
_ => panic!(
"Typed input has an unsupported form (function `{}`), it must be a reference type",
function_name
),
},

FnArg::Receiver(..) => unreachable!(),
})
.unzip();

if ruby_input_names.is_empty() {
quote! {}
} else {
quote! {
let ( #( #ruby_input_names ),* ) =
{
let arguments = rutie::util::parse_arguments(argc, argv);
let mut argument_nth = 0;

(
#(
{
let argument = arguments
.get(argument_nth)
.ok_or_else(|| {
<rutie::AnyException as rutie::Exception>::new(
"ArgumentError",
Some(&format!(concat!("Argument #{} (`", stringify!( #ruby_input_types ), "`) of function `", stringify!( #ruby_function_name ), "` is missing"), argument_nth)),
)
})
.and_then(|argument| {
<rutie::AnyObject as rutie::Object>
::try_convert_to::<< #ruby_input_types as rutie_derive::ClassInfo>::RubyClass>(argument)
})
.unwrap_or_else(|error| {
rutie::VM::raise_ex(error);
unreachable!()
});

argument_nth += 1;

argument
}
),*
)
};

let ( #( #ruby_input_names ),* ) =
(
#(
rutie_derive::UpcastRubyClass::<
<
< #ruby_input_types as rutie_derive::ClassInfo>::RubyClass as rutie_derive::ClassInfo
>::Class
>::upcast(&#ruby_input_names)
),*
);
}
}
};

let ruby_output = match function.sig.output {
ReturnType::Type(_, ty) => match *ty {
Type::Path(TypePath {
qself: None,
path:
Path {
leading_colon: None,
segments,
},
}) if segments.first().unwrap().ident.to_string() == "RubyResult" => {
match &segments.first().unwrap().arguments {
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
args,
..
}) => match args.first().unwrap() {
GenericArgument::Type(ty) => ty.clone(),
_ => panic!("Function has not well-formed rerturned type, expect `RubyResult<T>` where `T` is a type"),
},
_ => panic!("Function has not well-formed returned type, expect `RubyResult<T>`"),
}
}
_ => panic!("Function must wrap their output type inside `RubyResult<T>`"),
},
_ => panic!("Function must have an output of the form `RubyResult<T>`"),
};

(quote! {
#[allow(improper_ctypes_definitions)] // Not ideal but that's how Rutie works.
#ruby_function_visibility extern "C" fn #ruby_function_name(
argc: rutie::types::Argc,
argv: *const rutie::AnyObject,
_: rutie::AnyObject,
) -> #ruby_output {
#ruby_arguments_parsing

let block = || -> Result<_, rutie::AnyException> {
#ruby_function_block
};

match block() {
Ok(x) => x,
Err(e) => {
rutie::VM::raise_ex(e);
unreachable!()
}
}
}
})
.into()
}
9 changes: 9 additions & 0 deletions crates/rutie-derive-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod class;
mod function;
mod methods;

#[proc_macro_attribute]
Expand All @@ -16,3 +17,11 @@ pub fn rubymethods(
) -> proc_macro::TokenStream {
methods::entry(attr, input)
}

#[proc_macro_attribute]
pub fn rubyfunction(
attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
function::entry(attr, input)
}
2 changes: 1 addition & 1 deletion crates/rutie-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod upcast;

pub use rutie_derive_macros::{rubyclass, rubymethods};
pub use rutie_derive_macros::{rubyclass, rubyfunction, rubymethods};
pub use upcast::*;
9 changes: 8 additions & 1 deletion crates/wasmer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ macro_rules! ruby_define {
{
$( @const $constant_name:ident = $constant_value:expr; )*
$( $ruby_definition:ident ($method_rust_name:ident) $method_name:expr; )*
}; )*
};
)*

$( function ( $function_rust_name:path ) $function_name:expr; )*
) => {
$(
{
Expand All @@ -51,6 +54,8 @@ macro_rules! ruby_define {
;
}
)*

$( $module.define_module_function($function_name, $function_rust_name); )*
}
}

Expand Down Expand Up @@ -270,5 +275,7 @@ pub extern "C" fn Init_wasmer() {
class (wasi::ruby_environment) Environment {
def (generate_import_object) "generate_import_object";
};

function (wasi::get_version) "get_version";
};
}
2 changes: 1 addition & 1 deletion crates/wasmer/src/prelude.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pub use crate::error::RubyResult;
pub use lazy_static::lazy_static;
pub use rutie_derive::{rubyclass, rubymethods, ClassInfo, UpcastRubyClass};
pub use rutie_derive::{rubyclass, rubyfunction, rubymethods, ClassInfo, UpcastRubyClass};
8 changes: 8 additions & 0 deletions crates/wasmer/src/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,12 @@ impl Environment {
Ok(ImportObject::ruby_new(ImportObject::raw_new(import_object)))
}
}

#[rubyfunction]
pub fn get_version(module: &Module, strict: &Boolean) -> RubyResult<AnyObject> {
Ok(
wasmer_wasi::get_wasi_version(&module.inner(), strict.to_bool())
.map(|version| Version::from(&version).to_integer().to_any_object())
.unwrap_or_else(|| NilClass::new().to_any_object()),
)
}
54 changes: 54 additions & 0 deletions tests/wasi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Compiled to Wasm as follows:
//
// ```sh
// $ rustc --target wasm32-wasi -O wasi.rs -o wasi.raw.wasm
// $ wasm-strip wasi.raw.wasm
// $ wasm-opt -O4 -Oz wasi.raw.wasm -o wasi.wasm
// ```

use std::{env, fs};

fn main() {
// Arguments
{
let mut arguments = env::args().collect::<Vec<String>>();

println!("Found program name: `{}`", arguments[0]);

arguments = arguments[1..].to_vec();
println!(
"Found {} arguments: {}",
arguments.len(),
arguments.join(", ")
);
}

// Environment variables
{
let mut environment_variables = env::vars()
.map(|(arg, val)| format!("{}={}", arg, val))
.collect::<Vec<String>>();
environment_variables.sort();

println!(
"Found {} environment variables: {}",
environment_variables.len(),
environment_variables.join(", ")
);
}

// Directories.
{
let root = fs::read_dir("/")
.unwrap()
.map(|e| e.map(|inner| format!("{:?}", inner)))
.collect::<Result<Vec<String>, _>>()
.unwrap();

println!(
"Found {} preopened directories: {}",
root.len(),
root.join(", ")
);
}
}
Binary file added tests/wasi.wasm
Binary file not shown.
10 changes: 10 additions & 0 deletions tests/wasi_test.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
require "prelude"

class WasiTest < Minitest::Test
def bytes
IO.read File.expand_path("wasi.wasm", File.dirname(__FILE__)), mode: "rb"
end

def test_version
assert_equal Wasi::Version::LATEST, 1
assert_equal Wasi::Version::SNAPSHOT0, 2
assert_equal Wasi::Version::SNAPSHOT1, 3
end

def test_get_version
module_ = Module.new(Store.new, bytes)

assert_equal Wasi::get_version(module_, true), Wasi::Version::SNAPSHOT1
end

def test_state_builder
state_builder = Wasi::StateBuilder.new("test-program")
.arguments(["--foo", "--bar"])
Expand Down

0 comments on commit 5749ae3

Please sign in to comment.