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

[WIP] Expose gas metering in the API #68

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
329 changes: 155 additions & 174 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ categories = ["wasm"]
crate-type = ["cdylib"]

[dependencies]
wasmer-runtime-c-api = "0.6.0"
wasmer-runtime-c-api = { git = "https://github.com/confio/wasmer", branch = "metering", features = ["metering"] }
10 changes: 8 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ build-bin go-build-args='-v':
rust:
cargo build --release

rust-install:
cargo build --release
# grabs the most recent *.so file - TODO: only works on linux
ls -ltR target/release/deps/libwasmer_runtime_c_api-*.so | head -1
cp $(ls -tR target/release/deps/libwasmer_runtime_c_api-*.so | head -1) wasmer/libwasmer_runtime_c_api.so

# Generate cgo debug objects.
debug-cgo:
cd wasmer/ && \
Expand All @@ -41,9 +47,9 @@ test:
# Run the tests.
GODEBUG=cgocheck=2 go test -test.v $(find test -type f \( -name "*_test.go" \! -name "example_*.go" \! -name "benchmark*.go" \) ) test/imports.go
# Run the short examples.
go test -test.v example_test.go
go test -count=1 -test.v example_test.go
# Run the long examples.
go test -test.v $(find . -type f \( -name "example_*_test.go" \! -name "_example_import_test.go" \) )
go test -count=1 -test.v $(find . -type f \( -name "example_*_test.go" \! -name "_example_import_test.go" \) )

# Run benchmarks. Subjects can be `wasmer`, `wagon` or `life`. Filter is a regex to select the benchmarks.
bench subject='wasmer' filter='.*':
Expand Down
25 changes: 25 additions & 0 deletions wasmer/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,31 @@ func cWasmerCompile(module **cWasmerModuleT, wasmBytes *cUchar, wasmBytesLength
))
}


// TODO: is this an auto-gen file? how should I create this?
func cWasmerCompileWithLimit(module **cWasmerModuleT, wasmBytes *cUchar, wasmBytesLength cUint, gasLimit uint64) cWasmerResultT {
return (cWasmerResultT)(C.wasmer_compile_with_limit(
(**C.wasmer_module_t)(unsafe.Pointer(module)),
(*C.uchar)(wasmBytes),
(C.uint)(wasmBytesLength),
(C.uint64_t)(gasLimit),
))
}

func cWasmerInstanceGetPointsUsed(instance *cWasmerInstanceT) uint64 {
return uint64(C.wasmer_instance_get_points_used(
(*C.wasmer_instance_t)(instance),
))
}

func cWasmerInstanceSetPointsUsed(instance *cWasmerInstanceT, points uint64) {
C.wasmer_instance_set_points_used(
(*C.wasmer_instance_t)(instance),
(C.uint64_t)(points),
)
}
// End TODO: autogen?

func cWasmerExportDescriptorKind(exportDescriptor *cWasmerExportDescriptorT) cWasmerImportExportKind {
return (cWasmerImportExportKind)(C.wasmer_export_descriptor_kind(
(*C.wasmer_export_descriptor_t)(exportDescriptor),
Expand Down
3 changes: 2 additions & 1 deletion wasmer/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package wasmer_test

import (
"fmt"
wasm "github.com/wasmerio/go-ext-wasm/wasmer"
"path"
"runtime"

wasm "github.com/wasmerio/go-ext-wasm/wasmer"
)

func GetBytes() []byte {
Expand Down
8 changes: 8 additions & 0 deletions wasmer/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,3 +439,11 @@ func (instance *Instance) Close() {
cWasmerInstanceDestroy(instance.instance)
}
}

func (instance *Instance) GetPointsUsed() uint64 {
return cWasmerInstanceGetPointsUsed(instance.instance)
}

func (instance *Instance) SetPointsUsed(points uint64) {
cWasmerInstanceSetPointsUsed(instance.instance, points)
}
Binary file modified wasmer/libwasmer_runtime_c_api.so
100644 → 100755
Binary file not shown.
23 changes: 23 additions & 0 deletions wasmer/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,29 @@ func Compile(bytes []byte) (Module, error) {
return Module{module, exports, imports}, nil
}

// Compile compiles a WebAssembly module from bytes, with a fixed gas limit
func CompileWithLimit(bytes []byte, gasLimit uint64) (Module, error) {
var module *cWasmerModuleT

var compileResult = cWasmerCompileWithLimit(
&module,
(*cUchar)(unsafe.Pointer(&bytes[0])),
cUint(len(bytes)),
gasLimit,
)

var emptyModule = Module{module: nil}

if compileResult != cWasmerOk {
return emptyModule, NewModuleError("Failed to compile the module.")
}

var exports = moduleExports(module)
var imports = moduleImports(module)

return Module{module, exports, imports}, nil
}

func moduleExports(module *cWasmerModuleT) []ExportDescriptor {
var exportDescriptors *cWasmerExportDescriptorsT
cWasmerExportDescriptors(module, &exportDescriptors)
Expand Down
73 changes: 71 additions & 2 deletions wasmer/test/module_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package wasmertest

import (
"github.com/stretchr/testify/assert"
wasm "github.com/wasmerio/go-ext-wasm/wasmer"
"path"
"runtime"
"testing"

"github.com/stretchr/testify/assert"
wasm "github.com/wasmerio/go-ext-wasm/wasmer"
)

func TestValidate(t *testing.T) {
Expand Down Expand Up @@ -58,6 +59,74 @@ func TestModuleInstantiate(t *testing.T) {
assert.Equal(t, wasm.I32(3), result)
}

func TestModuleInstantiateWithLimit(t *testing.T) {
module, err := wasm.CompileWithLimit(GetBytes(), 20000)
defer module.Close()

assert.NoError(t, err)

instance, err := module.Instantiate()
defer instance.Close()

assert.NoError(t, err)

result, _ := instance.Exports["sum"](1, 2)

assert.Equal(t, wasm.I32(3), result)
}

// TODO: this only passes if the metering feature flag is enabled in the rust binary
func TestModuleRunWithLimit(t *testing.T) {
module, err := wasm.CompileWithLimit(GetBytes(), 20000)
defer module.Close()
assert.NoError(t, err)

instance, err := module.Instantiate()
defer instance.Close()
assert.NoError(t, err)

// we start at 0
points := instance.GetPointsUsed()
assert.Equal(t, points, uint64(0))

// if we set it, it is updated in get
instance.SetPointsUsed(123)
points = instance.GetPointsUsed()
assert.Equal(t, points, uint64(123))

result, _ := instance.Exports["sum"](1, 2)
assert.Equal(t, wasm.I32(3), result)

// running the contract adds 4 points
points = instance.GetPointsUsed()
assert.Equal(t, points, uint64(127))
}

// TODO: this only passes if the metering feature flag is enabled in the rust binary
func TestModuleRunExceedsLimit(t *testing.T) {
module, err := wasm.CompileWithLimit(GetBytes(), 5)
defer module.Close()
assert.NoError(t, err)

instance, err := module.Instantiate()
defer instance.Close()
assert.NoError(t, err)

// we start at 0
points := instance.GetPointsUsed()
assert.Equal(t, points, uint64(0))
// but let's hit the limit
instance.SetPointsUsed(1001)

// this should be out-of-gas
_, err = instance.Exports["sum"](1, 2)
assert.Error(t, err)

// and 4 points are added anyway :(
points = instance.GetPointsUsed()
assert.Equal(t, points, uint64(1005))
}

func TestModuleSerialize(t *testing.T) {
module1, err := wasm.Compile(GetBytes())
defer module1.Close()
Expand Down
83 changes: 78 additions & 5 deletions wasmer/wasmer.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
#include <stdint.h>
#include <stdlib.h>

/**
* List of export/import kinds.
*/
enum wasmer_import_export_kind {
WASM_FUNCTION,
WASM_GLOBAL,
Expand All @@ -31,6 +34,9 @@ typedef struct {

} wasmer_module_t;

/**
* Opaque pointer to `NamedExportDescriptor`.
*/
typedef struct {

} wasmer_export_descriptor_t;
Expand All @@ -40,10 +46,16 @@ typedef struct {
uint32_t bytes_len;
} wasmer_byte_array;

/**
* Opaque pointer to `NamedExportDescriptors`.
*/
typedef struct {

} wasmer_export_descriptors_t;

/**
* Opaque pointer to `wasmer_export_t`.
*/
typedef struct {

} wasmer_export_func_t;
Expand All @@ -60,6 +72,9 @@ typedef struct {
wasmer_value value;
} wasmer_value_t;

/**
* Opaque pointer to `NamedExport`.
*/
typedef struct {

} wasmer_export_t;
Expand All @@ -68,6 +83,9 @@ typedef struct {

} wasmer_memory_t;

/**
* Opaque pointer to `NamedExports`.
*/
typedef struct {

} wasmer_exports_t;
Expand Down Expand Up @@ -95,16 +113,15 @@ typedef struct {

typedef struct {

} wasmer_instance_t;

typedef struct {

} wasmer_instance_context_t;
} wasmer_import_object_t;

typedef struct {

} wasmer_table_t;

/**
* Union of import/export value.
*/
typedef union {
const wasmer_import_func_t *func;
const wasmer_table_t *table;
Expand All @@ -119,6 +136,14 @@ typedef struct {
wasmer_import_export_value value;
} wasmer_import_t;

typedef struct {

} wasmer_instance_t;

typedef struct {

} wasmer_instance_context_t;

typedef struct {
bool has_some;
uint32_t some;
Expand Down Expand Up @@ -155,6 +180,17 @@ wasmer_result_t wasmer_compile(wasmer_module_t **module,
uint8_t *wasm_bytes,
uint32_t wasm_bytes_len);

/**
* 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.
*/
wasmer_result_t wasmer_compile_with_limit(wasmer_module_t **module,
uint8_t *wasm_bytes,
uint32_t wasm_bytes_len,
uint64_t gas_limit);

/**
* Gets export descriptor kind
*/
Expand Down Expand Up @@ -392,6 +428,24 @@ wasmer_result_t wasmer_import_func_returns(const wasmer_import_func_t *func,
wasmer_result_t wasmer_import_func_returns_arity(const wasmer_import_func_t *func,
uint32_t *result);

/**
* Frees memory of the given ImportObject
*/
void wasmer_import_object_destroy(wasmer_import_object_t *import_object);

/**
* Extends an existing import object with new imports
*/
wasmer_result_t wasmer_import_object_extend(wasmer_import_object_t *import_object,
wasmer_import_t *imports,
unsigned int imports_len);

/**
* Creates a new empty import object.
* See also `wasmer_import_object_append`
*/
wasmer_import_object_t *wasmer_import_object_new(void);

/**
* Calls an instances exported function by `name` with the provided parameters.
* Results are set using the provided `results` pointer.
Expand All @@ -417,6 +471,11 @@ void *wasmer_instance_context_data_get(const wasmer_instance_context_t *ctx);
*/
void wasmer_instance_context_data_set(wasmer_instance_t *instance, void *data_ptr);

/**
* Extracts the instance's context and returns it.
*/
const wasmer_instance_context_t *wasmer_instance_context_get(wasmer_instance_t *instance);

/**
* Gets the memory within the context at the index `memory_idx`.
* The index is always 0 until multiple memories are supported.
Expand All @@ -435,6 +494,10 @@ void wasmer_instance_destroy(wasmer_instance_t *instance);
*/
void wasmer_instance_exports(wasmer_instance_t *instance, wasmer_exports_t **exports);

uint64_t wasmer_instance_get_points_used(wasmer_instance_t *instance);

void wasmer_instance_set_points_used(wasmer_instance_t *instance, uint64_t new_gas);

/**
* Creates a new Instance from the given wasm bytes and imports.
* Returns `wasmer_result_t::WASMER_OK` upon success.
Expand Down Expand Up @@ -526,6 +589,16 @@ wasmer_result_t wasmer_module_deserialize(wasmer_module_t **module,
*/
void wasmer_module_destroy(wasmer_module_t *module);

/**
* Given:
* A prepared `wasmer` import-object
* A compiled wasmer module
* Instantiates a wasmer instance
*/
wasmer_result_t wasmer_module_import_instantiate(wasmer_instance_t **instance,
const wasmer_module_t *module,
const wasmer_import_object_t *import_object);

/**
* Creates a new Instance from the given module and imports.
* Returns `wasmer_result_t::WASMER_OK` upon success.
Expand Down