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

Add metered compile and check functions to c api #736

Closed
wants to merge 1 commit into from
Closed
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
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 27 additions & 3 deletions lib/runtime-c-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,37 @@ path = "../wasi"
version = "0.11.0"
optional = true

[dependencies.wasmer-middleware-common]
path = "../middleware-common"
version = "0.11.0"
optional = true

[dependencies.wasmer-singlepass-backend]
path = "../singlepass-backend"
version = "0.11.0"
optional = true

[dependencies.wasmer-llvm-backend]
path = "../llvm-backend"
version = "0.11.0"
optional = true

[dependencies.wasmer-clif-backend]
path = "../clif-backend"
version = "0.11.0"
optional = true

[features]
default = ["cranelift-backend", "wasi"]
debug = ["wasmer-runtime/debug"]
cranelift-backend = ["wasmer-runtime/cranelift", "wasmer-runtime/default-backend-cranelift"]
llvm-backend = ["wasmer-runtime/llvm", "wasmer-runtime/default-backend-llvm"]
singlepass-backend = ["wasmer-runtime/singlepass", "wasmer-runtime/default-backend-singlepass"]
cranelift-backend = ["wasmer-runtime/cranelift", "wasmer-runtime/default-backend-cranelift", "wasmer-clif-backend"]
# to enable any of the below, you must disable the default cranelift backend with --no-default--features flag
# or adding default-features=false to the toml dependencies
llvm-backend = ["wasmer-runtime/llvm", "wasmer-runtime/default-backend-llvm", "wasmer-llvm-backend"]
singlepass-backend = ["wasmer-runtime/singlepass", "wasmer-runtime/default-backend-singlepass", "wasmer-singlepass-backend"]
wasi = ["wasmer-wasi"]
# you need to enable llvm or singlepass backend for this to work (middleware not supported in cranelift)
metering = ["wasmer-middleware-common"]

[build-dependencies]
cbindgen = "0.9"
25 changes: 25 additions & 0 deletions lib/runtime-c-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,31 @@ $ make
$ make test
```

# Feature Flags

By default this uses the cranelift compiler. You can enable a different compiler
by using feature flags. However, make sure to disable default features, as exactly
one default compiler must be enabled.

LLVM: `cargo build --no-default-features --features llvm-backend`
single-pass: `cargo build --no-default-features --features singlepass-backend`

Note that the single-pass backend
[does not currently support serialization](https://github.com/wasmerio/wasmer/issues/811),
so the serialization related tests will fail there.

There is also a flag to enable gas metering which provides three new api endpoints:

These are replaced with stubs of a normal compiler if the metering flag is not provided,
to provide a consistent API and not break upstream packages at link time.

Note that the gas metering middleware is
[not currently compatible with the cranelift backend](https://github.com/wasmerio/wasmer/issues/819),
so you must select a different backend (see above) if you enable metering.eg

`cargo build --no-default-features --features llvm-backend,metering`

Once that issue is resolved, `cargo build --features metering` will give you a useful binary.

# License

Expand Down
1 change: 1 addition & 0 deletions lib/runtime-c-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ pub mod global;
pub mod import;
pub mod instance;
pub mod memory;
pub mod metering;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add the attribute #[cfg(feature = "metering")] here, and remove all the same attributes in metering.rs. I believe it will simplify the code :-).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that it would expose a different C ABI then, and the go wrapper would fail to compile. Since go doesn't support conditional compilation feature tags as rust does, I think it would break the go bindings to have different ABIs (okay, there is some stuff for architecture, but nothing like rust's feature switches).

I can change that out if this is not a problem, but wanted the least breaking change possible (especially since the abi is committed to the go-ext-wasmer repo)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Golang build tags can be used to select the proper C ABI object to link against.

pub mod module;
pub mod table;
// `not(target_family = "windows")` is simpler than `unix`. See build.rs
Expand Down
158 changes: 158 additions & 0 deletions lib/runtime-c-api/src/metering.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use crate::{
error::{update_last_error, CApiError},
instance::wasmer_instance_t,
module::wasmer_module_t,
wasmer_result_t,
};

use std::slice;

#[cfg(feature = "metering")]
use wasmer_runtime_core::backend::Compiler;

/// Creates a new Module with gas limit from the given wasm bytes.
///
/// Returns `wasmer_result_t::WASMER_OK` upon success.
///
/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length`
/// and `wasmer_last_error_message` to get an error message.
#[allow(clippy::cast_ptr_alignment)]
#[cfg(feature = "metering")]
#[no_mangle]
pub unsafe extern "C" fn wasmer_compile_with_gas_metering(
module: *mut *mut wasmer_module_t,
wasm_bytes: *mut u8,
wasm_bytes_len: u32,
gas_limit: u64,
) -> wasmer_result_t {
if module.is_null() {
update_last_error(CApiError {
msg: "module is null".to_string(),
});
return wasmer_result_t::WASMER_ERROR;
}
if wasm_bytes.is_null() {
update_last_error(CApiError {
msg: "wasm bytes is null".to_string(),
});
return wasmer_result_t::WASMER_ERROR;
}

let bytes: &[u8] = slice::from_raw_parts_mut(wasm_bytes, wasm_bytes_len as usize);
ethanfrey marked this conversation as resolved.
Show resolved Hide resolved
let result = wasmer_runtime_core::compile_with(bytes, &get_metered_compiler(gas_limit));
let new_module = match result {
Ok(instance) => instance,
Err(_) => {
update_last_error(CApiError {
msg: "compile error".to_string(),
});
return wasmer_result_t::WASMER_ERROR;
}
};
*module = Box::into_raw(Box::new(new_module)) as *mut wasmer_module_t;
wasmer_result_t::WASMER_OK
}

#[cfg(feature = "metering")]
fn get_metered_compiler(limit: u64) -> impl Compiler {
use wasmer_middleware_common::metering;
use wasmer_runtime_core::codegen::{MiddlewareChain, StreamingCompiler};

#[cfg(feature = "llvm-backend")]
use wasmer_llvm_backend::ModuleCodeGenerator as MeteredMCG;

#[cfg(feature = "singlepass-backend")]
use wasmer_singlepass_backend::ModuleCodeGenerator as MeteredMCG;

#[cfg(feature = "cranelift-backend")]
use wasmer_clif_backend::CraneliftModuleCodeGenerator as MeteredMCG;

let c: StreamingCompiler<MeteredMCG, _, _, _, _> = StreamingCompiler::new(move || {
let mut chain = MiddlewareChain::new();
chain.push(metering::Metering::new(limit));
chain
});
c
}

// returns gas used
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
#[cfg(feature = "metering")]
pub unsafe extern "C" fn wasmer_instance_get_points_used(instance: *mut wasmer_instance_t) -> u64 {
if instance.is_null() {
return 0;
}
use wasmer_middleware_common::metering;
let instance = &*(instance as *const wasmer_runtime::Instance);
ethanfrey marked this conversation as resolved.
Show resolved Hide resolved
let points = metering::get_points_used(instance);
points
}

// sets gas used
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
#[cfg(feature = "metering")]
pub unsafe extern "C" fn wasmer_instance_set_points_used(
instance: *mut wasmer_instance_t,
new_gas: u64,
) {
if instance.is_null() {
return;
}
use wasmer_middleware_common::metering;
let instance = &mut *(instance as *mut wasmer_runtime::Instance);
ethanfrey marked this conversation as resolved.
Show resolved Hide resolved
metering::set_points_used(instance, new_gas)
}

/*** placeholder implementation if metering feature off ***/

// Without metering, wasmer_compile_with_gas_metering is a copy of wasmer_compile
#[cfg(not(feature = "metering"))]
#[no_mangle]
pub unsafe extern "C" fn wasmer_compile_with_gas_metering(
module: *mut *mut wasmer_module_t,
wasm_bytes: *mut u8,
wasm_bytes_len: u32,
_: u64,
) -> wasmer_result_t {
if module.is_null() {
update_last_error(CApiError {
msg: "module is null".to_string(),
});
return wasmer_result_t::WASMER_ERROR;
}
if wasm_bytes.is_null() {
update_last_error(CApiError {
msg: "wasm bytes is null".to_string(),
});
return wasmer_result_t::WASMER_ERROR;
}

let bytes: &[u8] = slice::from_raw_parts_mut(wasm_bytes, wasm_bytes_len as usize);
// TODO: this implicitly uses default_compiler() is that proper? maybe we override default_compiler
let result = wasmer_runtime::compile(bytes);
ethanfrey marked this conversation as resolved.
Show resolved Hide resolved
let new_module = match result {
Ok(instance) => instance,
Err(error) => {
update_last_error(error);
return wasmer_result_t::WASMER_ERROR;
}
};
*module = Box::into_raw(Box::new(new_module)) as *mut wasmer_module_t;
wasmer_result_t::WASMER_OK
}

// returns gas used
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
#[cfg(not(feature = "metering"))]
pub unsafe extern "C" fn wasmer_instance_get_points_used(_: *mut wasmer_instance_t) -> u64 {
0
}

// sets gas used
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
#[cfg(not(feature = "metering"))]
pub unsafe extern "C" fn wasmer_instance_set_points_used(_: *mut wasmer_instance_t, _: u64) {}
2 changes: 2 additions & 0 deletions lib/runtime-c-api/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub unsafe extern "C" fn wasmer_compile(
wasm_bytes_len: u32,
) -> wasmer_result_t {
let bytes: &[u8] = slice::from_raw_parts_mut(wasm_bytes, wasm_bytes_len as usize);
// TODO: this implicitly uses default_compiler() is that proper? a better way to handle metering?
let result = compile(bytes);
let new_module = match result {
Ok(instance) => instance,
Expand Down Expand Up @@ -244,6 +245,7 @@ pub unsafe extern "C" fn wasmer_module_deserialize(
let serialized_module: &[u8] = &*(serialized_module as *const &[u8]);

match Artifact::deserialize(serialized_module) {
// TODO: we need to use a different call here to support middleware (or modify wasmer-runtime)
Ok(artifact) => match load_cache_with(artifact, &default_compiler()) {
Ok(deserialized_module) => {
*module = Box::into_raw(Box::new(deserialized_module)) as _;
Expand Down
1 change: 1 addition & 0 deletions lib/runtime-c-api/tests/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ test-module
test-module-exports
test-module-imports
test-module-serialize
test-module-metering-serialize
test-tables
test-validate
test-context
Expand Down
5 changes: 5 additions & 0 deletions lib/runtime-c-api/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_executable(test-module test-module.c)
add_executable(test-module-exports test-module-exports.c)
add_executable(test-module-imports test-module-imports.c)
add_executable(test-module-serialize test-module-serialize.c)
add_executable(test-module-metering-serialize test-module-metering-serialize.c)
add_executable(test-tables test-tables.c)
add_executable(test-validate test-validate.c)
add_executable(test-context test-context.c)
Expand Down Expand Up @@ -98,6 +99,10 @@ target_link_libraries(test-module-serialize general ${WASMER_LIB})
target_compile_options(test-module-serialize PRIVATE ${COMPILER_OPTIONS})
add_test(test-module-serialize test-module-serialize)

target_link_libraries(test-module-metering-serialize general ${WASMER_LIB})
target_compile_options(test-module-metering-serialize PRIVATE ${COMPILER_OPTIONS})
add_test(test-module-metering-serialize test-module-metering-serialize)

target_link_libraries(test-tables general ${WASMER_LIB})
target_compile_options(test-tables PRIVATE ${COMPILER_OPTIONS})
add_test(test-tables test-tables)
Expand Down
Loading