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

FLIP 255: Cadence WebAssembly API #256

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
167 changes: 167 additions & 0 deletions cadence/20240225-cadence-webassembly-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
---
status: proposed
flip: 255
authors: darkdrag00n (darkdrag00n@proton.me)
sponsor: Bastian Müller (bastian.mueller@flowfoundation.org)
updated: 2024-02-25
---

# FLIP 255: Cadence Webassembly API

## Objective

This FLIP proposes addition of a WebAssembly (WASM) execution engine API to Cadence.

## Motivation

More and more projects are putting non-trivial computationally expensive logic in Cadence. Examples of this are games or zero-knowledge proof checks.

WASM could offer a great algorithmic throughput for such workloads by providing a way to offload the pure logic calculation to it without exposing Resource objects to it.

The motivation is explained in further details in [this forum post](https://forum.flow.com/t/idea-wasm-execution-engine-in-cadence/5164) by Dete.

## User Benefit

Users can benefit from the raw algorithmic throughput of WASM by offloading computationally expensive pure logic calculations to it while still keeping the security benefits of Cadence.

## Proposal

### Scope

As mentioned earlier, the feature is targeted towards utilizing algorithmic throughput provided by WASM. So the scope of this proposal is as follows:

1. Allow instantiating a WebAssembly module, without imports (i.e. the WebAssembly module has no access to Cadence)
2. Function exports with argument and return value types as i32 or i64. Floats (f32 or f64) will not be supported
3. The function exports could be called from Cadence, and receive the results back

### Design

The WebAssembly API will be implemented as a Cadence contract and will expose functions and structs to use them.

There are two main structs that will be exposed via the contract:
1. InstantiatedSource: Represents a source that has been compiled and instantiated via the Webassembly module.
2. Instance: Represents an instance of an instantiated webassebmly module that can be used to get the exports and use them in Cadence code.

The `InstantiatedSource` can be created using the `compileAndInstantiate` function which takes the WebAssembly binary code and compiles it into a Module.

```cadence
access(all)
contract WebAssembly {

/// Compile WebAssembly binary code into a Module
/// and instantiate it. Imports are not supported.
access(all)
view fun compileAndInstantiate(bytes: [UInt8]): &WebAssembly.InstantiatedSource

access(all)
struct InstantiatedSource {

/// The instance.
access(all)
let instance: &WebAssembly.Instance
}

struct Instance {

/// Get the exported value.
access(all)
view fun getExport<T: AnyStruct>(name: String): T
}
}
```

#### Example

Example usage of the WASM API to load a simple `add(i32, i32) -> i32` function, calling it with Cadence `Int32` values, and getting back the result as a Cadence `Int32`:

```cadence

access(all)
func main() {
// A simple program which exports a function `add` with type `(i32, i32) -> i32`,
// which sums the arguments and returns the result:
//
// (module
// (type (;0;) (func (param i32 i32) (result i32)))
// (func (;0;) (type 0) (param i32 i32) (result i32)
// local.get 0
// local.get 1
// i32.add)
// (export "add" (func 0)))
//
let addProgram: [UInt8] = [
0, 97, 115, 109, 1, 0, 0, 0, 1, 7, 1, 96,
2, 127, 127, 1, 127, 3, 2, 1, 0, 7, 7, 1,
3, 97, 100, 100, 0, 0, 10, 9, 1, 7, 0, 32,
0, 32, 1, 106, 11,
]

let instance = WebAssembly.compileAndInstantiate(bytes: program).instance
let addFn = instance.getExport<fun(Int32, Int32): Int32>(name: "add")

let sum1 = addFn(1, 2) // 3
let sum2 = addFn(1, -100) // -99
}
```

#### Metering

Like regular Cadence programs, code executed within the WASM engine will be metered and the memory consumption will be limited.

##### Computation

Popular WASM engines support some form of limits on the execution of the code. They will be utilized to implement the metering.

While the specifics are dependant on the exact engine used, the high level idea is the following:
1. FVM should provide a way to remaining computation units: A new function `RemainingComputation(kind)` will be added in FVM.
2. Computation units should be converted to its WASM engine equivalent. For example, in wasmtime, it is called `Fuel`.
Copy link
Member

Choose a reason for hiding this comment

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

We'll need to define how fuel translates to computation usage in Flow

3. The user code must be executed with the computed amount of wasm engine computation units.

##### Memory
To limit the memory being consumed, some constraints will be defined on the various ways that memory is used in the wasm-engine.
Copy link
Member

Choose a reason for hiding this comment

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


While the specifics are dependant on the exact engine used, the following memory usage areas can usually be limited by all popular engines:
1. Stack size
2. Linear memory
3. Tables

### Drawbacks

None

Copy link
Member

Choose a reason for hiding this comment

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

We'll need to define which WebAssembly features will be supported. Currently the PoC specifies: https://github.com/onflow/cadence/pull/2760/files#diff-e898ac8338568dfcac62c2017b09e956e5f99c1cda6eedb3f7c48b830595f9d1R43-R70

Copy link
Member

Choose a reason for hiding this comment

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

We'll need to point out that deterministic execution is required. WebAssmbly allows non-determinism through NaNs. However, some WebAssembly runtiumes allow determinism by enforcing NaN-normalization.

### Alternatives Considered
None

### Performance Implications

None

### Dependencies

None
Copy link
Member

Choose a reason for hiding this comment

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

We'll likely won't implement a new WebAssembly runtime and use an existing one


### Engineering Impact

It would require 3-4 weeks of engineering effort to implement, review & test the feature.

### Compatibility

This change has no impact on compatibility between systems (e.g. SDKs).

### User Impact

The proposed feature is a purely additive.
There is no impact on existing contracts and new transactions.

## Related Issues

None

## Questions and Discussion Topics

None

## Implementation
Will be done as part of https://github.com/onflow/cadence/issues/2853.

A proof of concept has already been done as part of https://github.com/onflow/cadence/pull/2760.
turbolent marked this conversation as resolved.
Show resolved Hide resolved