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

Public API and Imports/Host Function DSL #25

Merged
merged 1 commit into from
Aug 31, 2024
Merged
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
37 changes: 24 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ Additionally, the runtime supports the following Stage 4 proposals

```kotlin
dependencies {
implementation("io.github.charlietap.chasm:chasm:0.7.1")
implementation("io.github.charlietap.chasm:chasm:0.9.0")
}
```

# Usage

### Invoking functions

Webassembly compilations output a [Module](./ast/src/commonMain/kotlin/io/github/charlietap/chasm/ast/module/Module.kt)
Webassembly compilations output a [Module](chasm/src/commonMain/kotlin/io/github/charlietap/chasm/embedding/shapes/Module.kt)
encoded as either a .wasm or .wat file, currently chasm supports decoding only .wasm binaries

```kotlin
Expand All @@ -46,7 +46,6 @@ val result = module(wasmFileAsByteArray)

Once a module has been decoded you'll need to instantiate it, and for that you'll need also need a store


```kotlin
val store = store()
val result = instance(store, module)
Expand All @@ -60,18 +59,30 @@ val result = invoke(store, instance, "fibonacci")

### Imports

Modules often depend on imports, which come in two flavours, either:
Modules often depend on [imports](chasm/src/commonMain/kotlin/io/github/charlietap/chasm/embedding/shapes/Import.kt), imports can be one of the following:

- Functions
- Globals
- Memories
- Tables

For the most part imports will actually be exports from other modules, this is the mechanism that wasm uses to share data/behaviour between modules.
However you can also allocate them separately using the factory functions:

- Host functions
- Exported functions from other wasm modules
```kotlin
val function = function(store, functionType, hostFunction)
val global = global(store, globalType, initialValue)
val memory = memory(store, memoryType)
val table = table(store, tableType, initialValue)
```

Both are represented by [ExternalValue](executor/runtime/src/commonMain/kotlin/io/github/charlietap/chasm/executor/runtime/instance/ExternalValue.kt)'s and can be imported at instantiation time
Once you have your importables you can pass them in at instantiation time.

```kotlin
val import = Import(
"import module name",
"import entity name",
externalValue,
function,
)
val result = instance(store, module, listOf(import))
```
Expand All @@ -81,17 +92,17 @@ val result = instance(store, module, listOf(import))
Host functions are kotlin functions that can be called by wasm programs at runtime.
The majority of host functions represent system calls, WASI for example is intended to be integrated as imports of host functions.

Allocation of a host function requires a [FunctionType](ast/src/commonMain/kotlin/io/github/charlietap/chasm/ast/type/FunctionType.kt)
Allocation of a host function requires a [FunctionType](chasm/src/commonMain/kotlin/io/github/charlietap/chasm/embedding/shapes/FunctionType.kt)

The function type describes the inputs and outputs of your function so the runtime can call it and use its results

Once you have function type you can allocate the host function like so
Once you have defined a function type you can allocate the host function like so

```kotlin
val functionType = FunctionType(ResultType(emptyList()), ResultType(emptyList()))
val hostFunction: HostFunction = {
val functionType = FunctionType(emptyList(), listOf(ValueType.Number.I32))
val hostFunction = HostFunction { params ->
println("Hello world")
emptyList()
listOf(Value.Number.I32(117))
}

val result = function(store, funcType, hostFunction)
Expand Down
5 changes: 2 additions & 3 deletions chasm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,13 @@ kotlin {

commonMain {
dependencies {
api(projects.ast)
api(projects.executor.runtime)
implementation(projects.ast)

implementation(projects.decoder)

implementation(projects.executor.instantiator)
implementation(projects.executor.invoker)

implementation(projects.executor.runtime)
implementation(projects.executor.memory)

implementation(projects.typeSystem)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.github.charlietap.chasm.embedding

import io.github.charlietap.chasm.embedding.shapes.Export
import io.github.charlietap.chasm.embedding.shapes.Importable
import io.github.charlietap.chasm.embedding.shapes.Instance
import io.github.charlietap.chasm.embedding.transform.BidirectionalMapper
import io.github.charlietap.chasm.embedding.transform.ImportableMapper
import io.github.charlietap.chasm.executor.runtime.instance.ExternalValue

fun exports(
instance: Instance,
): List<Export> = exports(
instance = instance,
importableMapper = ImportableMapper,
)

internal fun exports(
instance: Instance,
importableMapper: BidirectionalMapper<Importable, ExternalValue>,
): List<Export> = instance.instance.exports.map { exportInstance ->
Export(
exportInstance.name.name,
importableMapper.bimap(exportInstance.value),
)
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
package io.github.charlietap.chasm.embedding

import io.github.charlietap.chasm.ast.type.FunctionType
import io.github.charlietap.chasm.embedding.shapes.Function
import io.github.charlietap.chasm.embedding.shapes.FunctionType
import io.github.charlietap.chasm.embedding.shapes.HostFunction
import io.github.charlietap.chasm.embedding.shapes.Store
import io.github.charlietap.chasm.embedding.transform.FunctionTypeMapper
import io.github.charlietap.chasm.embedding.transform.HostFunctionMapper
import io.github.charlietap.chasm.embedding.transform.Mapper
import io.github.charlietap.chasm.executor.instantiator.allocation.function.HostFunctionAllocator
import io.github.charlietap.chasm.executor.instantiator.allocation.function.HostFunctionAllocatorImpl
import io.github.charlietap.chasm.executor.runtime.instance.ExternalValue
import io.github.charlietap.chasm.executor.runtime.instance.HostFunction
import io.github.charlietap.chasm.executor.runtime.store.Store
import io.github.charlietap.chasm.ast.type.FunctionType as InternalFunctionType
import io.github.charlietap.chasm.executor.runtime.instance.HostFunction as InternalHostFunction

fun function(
store: Store,
type: FunctionType,
function: HostFunction,
): ExternalValue.Function = function(
): Function = function(
store = store,
type = type,
function = function,
allocator = ::HostFunctionAllocatorImpl,
functionTypeMapper = FunctionTypeMapper.instance,
hostFunctionMapper = HostFunctionMapper.instance,
)

internal fun function(
store: Store,
type: FunctionType,
function: HostFunction,
allocator: HostFunctionAllocator,
): ExternalValue.Function = ExternalValue.Function(allocator(store, type, function))
functionTypeMapper: Mapper<FunctionType, InternalFunctionType>,
hostFunctionMapper: Mapper<HostFunction, InternalHostFunction>,
): Function {
val functionType = functionTypeMapper.map(type)
val hostFunction = hostFunctionMapper.map(function)
return Function(ExternalValue.Function(allocator(store.store, functionType, hostFunction)))
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
package io.github.charlietap.chasm.embedding

import io.github.charlietap.chasm.ast.type.GlobalType
import io.github.charlietap.chasm.embedding.shapes.Global
import io.github.charlietap.chasm.embedding.shapes.GlobalType
import io.github.charlietap.chasm.embedding.shapes.Store
import io.github.charlietap.chasm.embedding.shapes.Value
import io.github.charlietap.chasm.embedding.transform.GlobalTypeMapper
import io.github.charlietap.chasm.embedding.transform.Mapper
import io.github.charlietap.chasm.embedding.transform.ValueMapper
import io.github.charlietap.chasm.executor.instantiator.allocation.global.GlobalAllocator
import io.github.charlietap.chasm.executor.instantiator.allocation.global.GlobalAllocatorImpl
import io.github.charlietap.chasm.executor.runtime.instance.ExternalValue
import io.github.charlietap.chasm.executor.runtime.store.Store
import io.github.charlietap.chasm.executor.runtime.value.ExecutionValue
import io.github.charlietap.chasm.ast.type.GlobalType as InternalGlobalType

fun global(
store: Store,
type: GlobalType,
value: ExecutionValue,
): ExternalValue.Global = global(
value: Value,
): Global = global(
store = store,
type = type,
value = value,
allocator = ::GlobalAllocatorImpl,
globalTypeMapper = GlobalTypeMapper.instance,
valueMapper = ValueMapper.instance,
)

internal fun global(
store: Store,
type: GlobalType,
value: ExecutionValue,
value: Value,
allocator: GlobalAllocator,
): ExternalValue.Global = ExternalValue.Global(allocator(store, type, value))
globalTypeMapper: Mapper<GlobalType, InternalGlobalType>,
valueMapper: Mapper<Value, ExecutionValue>,
): Global {
val globalType = globalTypeMapper.map(type)
val executionValue = valueMapper.map(value)
return Global(ExternalValue.Global(allocator(store.store, globalType, executionValue)))
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,38 @@ package io.github.charlietap.chasm.embedding

import com.github.michaelbull.result.fold
import com.github.michaelbull.result.get
import com.github.michaelbull.result.map
import com.github.michaelbull.result.mapError
import io.github.charlietap.chasm.ChasmResult
import io.github.charlietap.chasm.ChasmResult.Error
import io.github.charlietap.chasm.ChasmResult.Success
import io.github.charlietap.chasm.ast.module.Module
import io.github.charlietap.chasm.embedding.import.Import
import io.github.charlietap.chasm.error.ChasmError
import io.github.charlietap.chasm.embedding.error.ChasmError
import io.github.charlietap.chasm.embedding.shapes.ChasmResult
import io.github.charlietap.chasm.embedding.shapes.ChasmResult.Error
import io.github.charlietap.chasm.embedding.shapes.ChasmResult.Success
import io.github.charlietap.chasm.embedding.shapes.Import
import io.github.charlietap.chasm.embedding.shapes.Importable
import io.github.charlietap.chasm.embedding.shapes.Instance
import io.github.charlietap.chasm.embedding.shapes.Module
import io.github.charlietap.chasm.embedding.shapes.Store
import io.github.charlietap.chasm.embedding.transform.ImportableMapper
import io.github.charlietap.chasm.embedding.transform.Mapper
import io.github.charlietap.chasm.executor.instantiator.ModuleInstantiator
import io.github.charlietap.chasm.executor.instantiator.ModuleInstantiatorImpl
import io.github.charlietap.chasm.executor.instantiator.import.ImportMatcher
import io.github.charlietap.chasm.executor.instantiator.import.ImportMatcherImpl
import io.github.charlietap.chasm.executor.runtime.error.InstantiationError
import io.github.charlietap.chasm.executor.runtime.instance.ModuleInstance
import io.github.charlietap.chasm.executor.runtime.store.Store
import io.github.charlietap.chasm.executor.runtime.instance.ExternalValue

fun instance(
store: Store,
module: Module,
imports: List<Import>,
): ChasmResult<ModuleInstance, ChasmError.ExecutionError> {
): ChasmResult<Instance, ChasmError.ExecutionError> {
return instance(
store = store,
module = module,
imports = imports,
importMatcher = ::ImportMatcherImpl,
instantiator = ::ModuleInstantiatorImpl,
importableMapper = ImportableMapper,
)
}

Expand All @@ -37,11 +43,13 @@ internal fun instance(
imports: List<Import>,
importMatcher: ImportMatcher,
instantiator: ModuleInstantiator,
): ChasmResult<ModuleInstance, ChasmError.ExecutionError> {
val mappedImports = imports.map { import -> Triple(import.moduleName, import.entityName, import.value) }
val orderedImports = importMatcher(store, module, mappedImports).get()
importableMapper: Mapper<Importable, ExternalValue>,
): ChasmResult<Instance, ChasmError.ExecutionError> {
val mappedImports = imports.map { import -> Triple(import.moduleName, import.entityName, importableMapper.map(import.value)) }
val orderedImports = importMatcher(store.store, module.module, mappedImports).get()
?: return Error(ChasmError.ExecutionError(InstantiationError.MissingImport))
return instantiator(store, module, orderedImports)
return instantiator(store.store, module.module, orderedImports)
.mapError(ChasmError::ExecutionError)
.map(::Instance)
.fold(::Success, ::Error)
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,56 @@
package io.github.charlietap.chasm.embedding

import com.github.michaelbull.result.fold
import com.github.michaelbull.result.map
import com.github.michaelbull.result.mapError
import io.github.charlietap.chasm.ChasmResult
import io.github.charlietap.chasm.ChasmResult.Error
import io.github.charlietap.chasm.ChasmResult.Success
import io.github.charlietap.chasm.error.ChasmError
import io.github.charlietap.chasm.embedding.error.ChasmError
import io.github.charlietap.chasm.embedding.shapes.ChasmResult
import io.github.charlietap.chasm.embedding.shapes.ChasmResult.Error
import io.github.charlietap.chasm.embedding.shapes.ChasmResult.Success
import io.github.charlietap.chasm.embedding.shapes.Instance
import io.github.charlietap.chasm.embedding.shapes.Store
import io.github.charlietap.chasm.embedding.shapes.Value
import io.github.charlietap.chasm.embedding.transform.BidirectionalMapper
import io.github.charlietap.chasm.embedding.transform.ValueMapper
import io.github.charlietap.chasm.executor.invoker.FunctionInvoker
import io.github.charlietap.chasm.executor.invoker.FunctionInvokerImpl
import io.github.charlietap.chasm.executor.runtime.error.InvocationError
import io.github.charlietap.chasm.executor.runtime.instance.ExternalValue
import io.github.charlietap.chasm.executor.runtime.instance.ModuleInstance
import io.github.charlietap.chasm.executor.runtime.store.Store
import io.github.charlietap.chasm.executor.runtime.value.ExecutionValue

fun invoke(
store: Store,
instance: ModuleInstance,
instance: Instance,
name: String,
args: List<ExecutionValue> = emptyList(),
): ChasmResult<List<ExecutionValue>, ChasmError.ExecutionError> = invoke(
args: List<Value> = emptyList(),
): ChasmResult<List<Value>, ChasmError.ExecutionError> = invoke(
store = store,
instance = instance,
name = name,
args = args,
invoker = ::FunctionInvokerImpl,
valueMapper = ValueMapper.instance,
)

internal fun invoke(
store: Store,
instance: ModuleInstance,
instance: Instance,
name: String,
args: List<ExecutionValue>,
args: List<Value>,
invoker: FunctionInvoker,
): ChasmResult<List<ExecutionValue>, ChasmError.ExecutionError> {
valueMapper: BidirectionalMapper<Value, ExecutionValue>,
): ChasmResult<List<Value>, ChasmError.ExecutionError> {

val extern = instance.exports.firstOrNull { export ->
val extern = instance.instance.exports.firstOrNull { export ->
export.name.name == name
}?.value

val address = (extern as? ExternalValue.Function)?.address ?: return Error(
ChasmError.ExecutionError(InvocationError.FunctionNotFound(name)),
)
val arguments = args.map(valueMapper::map)

return invoker(store, address, args)
return invoker(store.store, address, arguments)
.map { values -> values.map(valueMapper::bimap) }
.mapError(ChasmError::ExecutionError)
.fold(::Success, ::Error)
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
package io.github.charlietap.chasm.embedding

import io.github.charlietap.chasm.ast.type.MemoryType
import io.github.charlietap.chasm.embedding.shapes.Memory
import io.github.charlietap.chasm.embedding.shapes.MemoryType
import io.github.charlietap.chasm.embedding.shapes.Store
import io.github.charlietap.chasm.embedding.transform.Mapper
import io.github.charlietap.chasm.embedding.transform.MemoryTypeMapper
import io.github.charlietap.chasm.executor.instantiator.allocation.memory.MemoryAllocator
import io.github.charlietap.chasm.executor.instantiator.allocation.memory.MemoryAllocatorImpl
import io.github.charlietap.chasm.executor.runtime.instance.ExternalValue
import io.github.charlietap.chasm.executor.runtime.store.Store
import io.github.charlietap.chasm.ast.type.MemoryType as InternalMemoryType

fun memory(
store: Store,
type: MemoryType,
): ExternalValue.Memory = memory(
): Memory = memory(
store = store,
type = type,
allocator = ::MemoryAllocatorImpl,
memoryTypeMapper = MemoryTypeMapper.instance,
)

internal fun memory(
store: Store,
type: MemoryType,
allocator: MemoryAllocator,
): ExternalValue.Memory = ExternalValue.Memory(allocator(store, type))
memoryTypeMapper: Mapper<MemoryType, InternalMemoryType>,
): Memory {
val memoryType = memoryTypeMapper.map(type)
return Memory(ExternalValue.Memory(allocator(store.store, memoryType)))
}
Loading