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

Get and Set Globals #540

Merged
merged 13 commits into from
Apr 5, 2024
89 changes: 44 additions & 45 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,46 +21,47 @@ Wasmtime rewrote their fuel-related API and simplified it. To remain consistent
The underlying implementation of the fuel system got rewritten as well. If you are using fuel in your app,
please check your fuel consumption values.

* Thanks to @RoyalIcing for helping us keeping our dependencies up to date for this release 💜
- Thanks to @RoyalIcing for helping us keeping our dependencies up to date for this release 💜

### Added

* official support for Elixir 1.15 and 1.16
* fuel-related API got rewritten, because the underlying Wasm library (wasmtime) changed their API and we want to be consistent. Added `Store.get_fuel/1` and `Store.set_fuel/2` which is a much simpler API than before.
- official support for Elixir 1.15 and 1.16
- fuel-related API got rewritten, because the underlying Wasm library (wasmtime) changed their API and we want to be consistent. Added `Store.get_fuel/1` and `Store.set_fuel/2` which is a much simpler API than before.
- read and write a global’s value with `Instance.get_global_value/3` and `Instance.set_global_value/4` ([#540](https://github.com/tessi/wasmex/pull/540))

### Removed

* removed support for Elixir 1.12
* with the fuel-related API changed, the existing methods on `Store` (`consume_fuel`, `fuel_remaining`, `add_fuel`) were removed. Please call `set_fuel/2` and `get_fuel/1` instead.
- removed support for Elixir 1.12
- with the fuel-related API changed, the existing methods on `Store` (`consume_fuel`, `fuel_remaining`, `add_fuel`) were removed. Please call `set_fuel/2` and `get_fuel/1` instead.

### Changed

* Dependency updates (most notably wasmtime and rustler)
* removed dialyzer
- Dependency updates (most notably wasmtime and rustler)
- removed dialyzer

## [0.8.4] - 2023-06-??

### Added

* added support for multi-value returns from WASM and elixir callbacks. This enables passing string return values safely by pointer and length, for example.
- added support for multi-value returns from WASM and elixir callbacks. This enables passing string return values safely by pointer and length, for example.

## [0.8.3] - 2023-05-24

### Added

* added support for `riscv64gc-unknown-linux-gnu`
* added support for OTP 26
- added support for `riscv64gc-unknown-linux-gnu`
- added support for OTP 26

### Changed

* updated rustler from 0.27.0 to 0.28.0
* updated wasmtime from 4.0.1 to 9.0.1
- updated rustler from 0.27.0 to 0.28.0
- updated wasmtime from 4.0.1 to 9.0.1

## [0.8.2] - 2023-01-08

## Added

* list `aarch64-unknown-linux-musl` in rustler targets, so we actually include it in our releases
- list `aarch64-unknown-linux-musl` in rustler targets, so we actually include it in our releases

## [0.8.1] - 2023-01-08

Expand All @@ -75,22 +76,21 @@ Today, a `Wasmex.Engine` already gives us a faster way to precompile modules wit

### Added

* Added precompiled binary for `aarch64-unknown-linux-musl`
* Added support for setting store limits. This allows users to limit memory usage, instance creation, table sizes and more. See `Wasmex.StoreLimits` for details.
* Added support for metering/fuel_consumption. This allows users to limit CPU usage. A `Wasmex.Store` can be given fuel, where each Wasm instruction of a running Wasm binary uses a certain amount of fuel. If no fuel remains, execution stops. See `Wasmex.EngineConfig` for details.
* Added `Wasmex.EngineConfig` as a place for more complex Wasm settings. With this release an engine can be configured to provide more detailed backtraces on errors during Wasm execution by setting the `wasm_backtrace_details` flag.
* Added `Wasmex.Engine.precompile_module/2` which allows module precompilation from a .wat or .wasm binary without the need to instantiate said module. A precompiled module can be hydrated with `Module.unsafe_deserialize/2`.
* Added `Wasmex.module/1` and `Wasmex.store/1` to access the module and store of a running Wasmex GenServer process.
* Added option to `Wasmex.EngineConfig` to configure the `cranelift_opt_level` (:none, :speed, :speed_and_size) allowing users to trade compilation time against execution speed
- Added precompiled binary for `aarch64-unknown-linux-musl`
- Added support for setting store limits. This allows users to limit memory usage, instance creation, table sizes and more. See `Wasmex.StoreLimits` for details.
- Added support for metering/fuel_consumption. This allows users to limit CPU usage. A `Wasmex.Store` can be given fuel, where each Wasm instruction of a running Wasm binary uses a certain amount of fuel. If no fuel remains, execution stops. See `Wasmex.EngineConfig` for details.
- Added `Wasmex.EngineConfig` as a place for more complex Wasm settings. With this release an engine can be configured to provide more detailed backtraces on errors during Wasm execution by setting the `wasm_backtrace_details` flag.
- Added `Wasmex.Engine.precompile_module/2` which allows module precompilation from a .wat or .wasm binary without the need to instantiate said module. A precompiled module can be hydrated with `Module.unsafe_deserialize/2`.
- Added `Wasmex.module/1` and `Wasmex.store/1` to access the module and store of a running Wasmex GenServer process.
- Added option to `Wasmex.EngineConfig` to configure the `cranelift_opt_level` (:none, :speed, :speed_and_size) allowing users to trade compilation time against execution speed

### Changed

* `mix.exs` now also requires at least Elixir 1.12
* `Module.unsafe_deserialize/2` now accepts a `Wasmex.Engine` in addition to the serialized module binary. It's best to hydrate a module using the same engine config used to serialize or precompile it. It has no harsh consequences today, but will be important when we add more Wasm features (e.g. SIMD support) in the future.
* added typespecs for all public `Wasmex` methods
* improved documentation and typespecs
* allow starting the `Wasmex` GenServer with a `%{bytes: bytes, store: store}` map as a convenience to spare users the task of manually compiling a `Wasmex.Module`

- `mix.exs` now also requires at least Elixir 1.12
- `Module.unsafe_deserialize/2` now accepts a `Wasmex.Engine` in addition to the serialized module binary. It's best to hydrate a module using the same engine config used to serialize or precompile it. It has no harsh consequences today, but will be important when we add more Wasm features (e.g. SIMD support) in the future.
- added typespecs for all public `Wasmex` methods
- improved documentation and typespecs
- allow starting the `Wasmex` GenServer with a `%{bytes: bytes, store: store}` map as a convenience to spare users the task of manually compiling a `Wasmex.Module`

## [0.8.0] - 2023-01-03

Expand All @@ -108,38 +108,37 @@ Please visit the list of changes below for more details.

### Added

* Added support for OTP 25
* Added support for Elixir 1.14
- Added support for OTP 25
- Added support for Elixir 1.14

### Removed

* Removed official support for OTP 22 and 23
* Removed official support for Elixir 1.12
* Removed `Wasmex.Module.set_name()` without replacement as this is not supported by Wasmtime
* Removed `Wasmex.Memory.bytes_per_element()` without replacement because we dropped support for different data types and now only handle bytes
* Removed `Wasmex.Pipe.set_len()` without replacement
* WASI directory/file preopens can not configure read/write/create permissions anymore because wasmtime does not support this feature well. We very much plan to add support back [once wasmtime allows](https://github.com/bytecodealliance/wasmtime/issues/4273).
- Removed official support for OTP 22 and 23
- Removed official support for Elixir 1.12
- Removed `Wasmex.Module.set_name()` without replacement as this is not supported by Wasmtime
- Removed `Wasmex.Memory.bytes_per_element()` without replacement because we dropped support for different data types and now only handle bytes
- Removed `Wasmex.Pipe.set_len()` without replacement
- WASI directory/file preopens can not configure read/write/create permissions anymore because wasmtime does not support this feature well. We very much plan to add support back [once wasmtime allows](https://github.com/bytecodealliance/wasmtime/issues/4273).

### Changed

* Changed the underlying Wasm engine from wasmer to [wasmtime](https://wasmtime.dev)
* Removed `Wasmex.Instance.new()` and `Wasmex.Instance.new_wasi()` in favor of `Wasmex.Store.new()` and `Wasmex.Store.new_wasi()`.
* WASI-options to `Wasmex.Store.new_wasi()` are now a proper struct `Wasmex.Wasi.WasiOptions` to improve typespecs, docs, and compile-time warnings.
* `Wasmex.Pipe` went through an internal rewrite. It is now a positioned read/write stream. You may change the read/write position with `Wasmex.Pipe.seek()`
* Renamed `Wasmex.Pipe.create()` to `Wasmex.Pipe.new()` to be consistent with other struct-creation calls
* Renamed `Wasmex.Memory.length()` to `Wasmex.Memory.size()` for consistenct with other `size` methods
* Renamed `Wasmex.Memory.set()` to `Wasmex.Memory.set_byte()`
* Renamed `Wasmex.Memory.get()` to `Wasmex.Memory.get_byte()`
* Updated and rewrote most of the docs - all examples are now doctests and tested on CI
* Updated all Elixir/Rust dependencies
- Changed the underlying Wasm engine from wasmer to [wasmtime](https://wasmtime.dev)
- Removed `Wasmex.Instance.new()` and `Wasmex.Instance.new_wasi()` in favor of `Wasmex.Store.new()` and `Wasmex.Store.new_wasi()`.
- WASI-options to `Wasmex.Store.new_wasi()` are now a proper struct `Wasmex.Wasi.WasiOptions` to improve typespecs, docs, and compile-time warnings.
- `Wasmex.Pipe` went through an internal rewrite. It is now a positioned read/write stream. You may change the read/write position with `Wasmex.Pipe.seek()`
- Renamed `Wasmex.Pipe.create()` to `Wasmex.Pipe.new()` to be consistent with other struct-creation calls
- Renamed `Wasmex.Memory.length()` to `Wasmex.Memory.size()` for consistenct with other `size` methods
- Renamed `Wasmex.Memory.set()` to `Wasmex.Memory.set_byte()`
- Renamed `Wasmex.Memory.get()` to `Wasmex.Memory.get_byte()`
- Updated and rewrote most of the docs - all examples are now doctests and tested on CI
- Updated all Elixir/Rust dependencies

## [0.7.1] - 2022-05-25

### Added

- Added an optional fourth parameter to `call_function`, `timeout`, which accepts a value in milliseconds that will cap the execution time of the function. The default behavior if not supplied is preserved, which is a 5 second timeout. Thanks @brooksmtownsend for this contribution


## [0.7.0] - 2022-03-27

### Added
Expand Down
69 changes: 69 additions & 0 deletions lib/wasmex/instance.ex
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,75 @@ defmodule Wasmex.Instance do
def memory(store, instance) do
Wasmex.Memory.from_instance(store, instance)
end

@doc ~S"""
Reads the value of an exported global.
RoyalIcing marked this conversation as resolved.
Show resolved Hide resolved

## Examples

iex> wat = "(module
...> (global $answer i32 (i32.const 42))
...> (export \"answer\" (global $answer))
...> )"
iex> {:ok, store} = Wasmex.Store.new()
iex> {:ok, module} = Wasmex.Module.compile(store, wat)
iex> {:ok, instance} = Wasmex.Instance.new(store, module, %{})
iex> Wasmex.Instance.get_global_value(store, instance, "answer")
{:ok, 42}
iex> Wasmex.Instance.get_global_value(store, instance, "not_a_global")
{:error, "exported global `not_a_global` not found"}
"""
@spec get_global_value(Wasmex.StoreOrCaller.t(), __MODULE__.t(), binary()) ::
{:ok, number()} | {:error, binary()}
def get_global_value(store_or_caller, instance, global_name) do
%{resource: store_or_caller_resource} = store_or_caller
%__MODULE__{resource: instance_resource} = instance

Wasmex.Native.instance_get_global_value(
store_or_caller_resource,
instance_resource,
global_name
)
|> case do
{:error, _reason} = term -> term
result when is_number(result) -> {:ok, result}
end
end

@doc ~S"""
Sets the value of an exported mutable global.

## Examples

iex> wat = "(module
...> (global $count (mut i32) (i32.const 0))
...> (export \"count\" (global $count))
...> )"
iex> {:ok, store} = Wasmex.Store.new()
iex> {:ok, module} = Wasmex.Module.compile(store, wat)
iex> {:ok, instance} = Wasmex.Instance.new(store, module, %{})
iex> Wasmex.Instance.set_global_value(store, instance, "count", 1)
:ok
iex> Wasmex.Instance.get_global_value(store, instance, "count")
{:ok, 1}
"""
@spec set_global_value(Wasmex.StoreOrCaller.t(), __MODULE__.t(), binary(), number()) ::
{:ok, number()} | {:error, binary()}
def set_global_value(store_or_caller, instance, global_name, new_value) do
%{resource: store_or_caller_resource} = store_or_caller
%__MODULE__{resource: instance_resource} = instance

Wasmex.Native.instance_set_global_value(
store_or_caller_resource,
instance_resource,
global_name,
new_value
)
|> case do
{} -> :ok
{:error, _reason} = term -> term
end
end
end

defimpl Inspect, for: Wasmex.Instance do
Expand Down
11 changes: 11 additions & 0 deletions lib/wasmex/native.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ defmodule Wasmex.Native do
),
do: error()

def instance_get_global_value(_store_or_caller_resource, _instance_resource, _global_name),
do: error()

def instance_set_global_value(
_store_or_caller_resource,
_instance_resource,
_global_name,
_new_value
),
do: error()

def memory_from_instance(_store_resource, _memory_resource), do: error()
def memory_size(_store_resource, _memory_resource), do: error()
def memory_grow(_store_resource, _memory_resource, _pages), do: error()
Expand Down
Loading
Loading