Skip to content

Commit ae25d17

Browse files
committed
add host function exception and handler that allows host function to stop the vm gracefully
1 parent 104302b commit ae25d17

File tree

10 files changed

+101
-1
lines changed

10 files changed

+101
-1
lines changed

chasm/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ kotlin {
4040
sourceSets {
4141
commonMain {
4242
dependencies {
43+
api(projects.host)
4344
api(projects.stream)
4445

4546
implementation(projects.ast)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package io.github.charlietap.chasm.integration
2+
3+
import io.github.charlietap.chasm.embedding.error.ChasmError
4+
import io.github.charlietap.chasm.embedding.fixture.publicFunctionType
5+
import io.github.charlietap.chasm.embedding.fixture.publicImport
6+
import io.github.charlietap.chasm.embedding.fixture.publicStore
7+
import io.github.charlietap.chasm.embedding.function
8+
import io.github.charlietap.chasm.embedding.shapes.ChasmResult
9+
import io.github.charlietap.chasm.embedding.shapes.HostFunction
10+
import io.github.charlietap.chasm.embedding.shapes.ValueType
11+
import io.github.charlietap.chasm.executor.runtime.error.InvocationError
12+
import io.github.charlietap.chasm.fixture.executor.runtime.store
13+
import io.github.charlietap.chasm.host.HostFunctionException
14+
import kotlin.test.Test
15+
import kotlin.test.assertEquals
16+
17+
class HostFunctionExceptionTest {
18+
19+
@Test
20+
fun `can run a host function that throws an exception and return a chasm error`() {
21+
22+
val store = publicStore(store())
23+
24+
val functionType = publicFunctionType(
25+
emptyList(),
26+
listOf(ValueType.Number.I32),
27+
)
28+
val reason = "Fail gracefully"
29+
val exception = HostFunctionException(reason)
30+
val hostFunction: HostFunction = {
31+
throw exception
32+
}
33+
val functionExternal = function(store, functionType, hostFunction)
34+
val functionImport = publicImport(
35+
"env",
36+
"host_function",
37+
functionExternal,
38+
)
39+
val result = testRunner(
40+
fileName = "host_function.wasm",
41+
fileDirectory = FILE_DIR,
42+
functionName = "call_host_function",
43+
store = store,
44+
imports = listOf(
45+
functionImport,
46+
),
47+
)
48+
49+
val expected = ChasmError.ExecutionError(
50+
InvocationError.HostFunctionError(reason).toString(),
51+
)
52+
53+
assertEquals(ChasmResult.Error(expected), result)
54+
}
55+
56+
companion object {
57+
private const val FILE_DIR = "src/commonTest/resources/integration/"
58+
}
59+
}
Binary file not shown.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
(module
2+
(import "env" "host_function" (func $host_function (result i32)))
3+
(func $call_host_function (result i32) call $host_function)
4+
(export "call_host_function" (func $call_host_function))
5+
)

executor/invoker/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ kotlin {
2222
api(libs.result)
2323

2424
implementation(projects.executor.memory)
25+
implementation(projects.host)
2526
implementation(projects.libs.sse2)
2627
}
2728
}

executor/invoker/src/commonMain/kotlin/io/github/charlietap/chasm/executor/invoker/function/HostFunctionCall.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import io.github.charlietap.chasm.executor.runtime.value.ExecutionValue
2020
import io.github.charlietap.chasm.executor.runtime.value.NumberValue
2121
import io.github.charlietap.chasm.executor.runtime.value.ReferenceValue
2222
import io.github.charlietap.chasm.executor.runtime.value.VectorValue
23+
import io.github.charlietap.chasm.host.HostFunctionException
2324

2425
internal typealias HostFunctionCall = (ExecutionContext, FunctionInstance.HostFunction) -> Result<Unit, InvocationError>
2526

@@ -39,7 +40,11 @@ internal fun HostFunctionCall(
3940
store,
4041
frame.instance,
4142
)
42-
val results = function.function.invoke(functionContext, params)
43+
val results = try {
44+
function.function.invoke(functionContext, params)
45+
} catch (e: HostFunctionException) {
46+
Err(InvocationError.HostFunctionError(e.reason)).bind()
47+
}
4348

4449
type.results.types.forEachIndexed { index, valueType ->
4550
val result = results.getOrNull(index)

executor/runtime/src/commonMain/kotlin/io/github/charlietap/chasm/executor/runtime/error/InvocationError.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ sealed interface InvocationError : ModuleTrapError {
117117
val actualValue: ExecutionValue?,
118118
) : InvocationError
119119

120+
@JvmInline
121+
value class HostFunctionError(val error: String) : InvocationError
122+
120123
data object ProgramFinishedInconsistentState : InvocationError
121124

122125
sealed interface Trap : InvocationError {

host/build.gradle.kts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3+
4+
plugins {
5+
alias(libs.plugins.kotlin.multiplatform)
6+
7+
alias(libs.plugins.conventions.kmp)
8+
alias(libs.plugins.conventions.linting)
9+
alias(libs.plugins.conventions.publishing)
10+
}
11+
12+
configure<PublishingConventionsExtension> {
13+
name = "host"
14+
description = "host system interface"
15+
}
16+
17+
tasks.withType<KotlinCompile>().configureEach {
18+
compilerOptions {
19+
jvmTarget = JvmTarget.fromTarget(libs.versions.java.bytecode.version.get())
20+
}
21+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package io.github.charlietap.chasm.host
2+
3+
class HostFunctionException(val reason: String) : Exception()

settings.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ include(":executor:invoker")
4646
include(":executor:memory")
4747
include(":executor:runtime")
4848

49+
include(":host")
50+
4951
include(":libs:sse2")
5052
include(":libs:stack")
5153

0 commit comments

Comments
 (0)