From ae25d172ffb688a3f8fa5491555c71bf3abb7c13 Mon Sep 17 00:00:00 2001 From: CharlieTap Date: Sun, 15 Dec 2024 19:56:56 +0000 Subject: [PATCH] add host function exception and handler that allows host function to stop the vm gracefully --- chasm/build.gradle.kts | 1 + .../integration/HostFunctionExceptionTest.kt | 59 ++++++++++++++++++ .../resources/integration/host_function.wasm | Bin 0 -> 119 bytes .../resources/integration/host_function.wat | 5 ++ executor/invoker/build.gradle.kts | 1 + .../invoker/function/HostFunctionCall.kt | 7 ++- .../executor/runtime/error/InvocationError.kt | 3 + host/build.gradle.kts | 21 +++++++ .../chasm/host/HostFunctionException.kt | 3 + settings.gradle.kts | 2 + 10 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 chasm/src/commonTest/kotlin/io/github/charlietap/chasm/integration/HostFunctionExceptionTest.kt create mode 100644 chasm/src/commonTest/resources/integration/host_function.wasm create mode 100644 chasm/src/commonTest/resources/integration/host_function.wat create mode 100644 host/build.gradle.kts create mode 100644 host/src/commonMain/kotlin/io/github/charlietap/chasm/host/HostFunctionException.kt diff --git a/chasm/build.gradle.kts b/chasm/build.gradle.kts index ddfa7ecee..ac1712f20 100644 --- a/chasm/build.gradle.kts +++ b/chasm/build.gradle.kts @@ -40,6 +40,7 @@ kotlin { sourceSets { commonMain { dependencies { + api(projects.host) api(projects.stream) implementation(projects.ast) diff --git a/chasm/src/commonTest/kotlin/io/github/charlietap/chasm/integration/HostFunctionExceptionTest.kt b/chasm/src/commonTest/kotlin/io/github/charlietap/chasm/integration/HostFunctionExceptionTest.kt new file mode 100644 index 000000000..4a9bb0c7c --- /dev/null +++ b/chasm/src/commonTest/kotlin/io/github/charlietap/chasm/integration/HostFunctionExceptionTest.kt @@ -0,0 +1,59 @@ +package io.github.charlietap.chasm.integration + +import io.github.charlietap.chasm.embedding.error.ChasmError +import io.github.charlietap.chasm.embedding.fixture.publicFunctionType +import io.github.charlietap.chasm.embedding.fixture.publicImport +import io.github.charlietap.chasm.embedding.fixture.publicStore +import io.github.charlietap.chasm.embedding.function +import io.github.charlietap.chasm.embedding.shapes.ChasmResult +import io.github.charlietap.chasm.embedding.shapes.HostFunction +import io.github.charlietap.chasm.embedding.shapes.ValueType +import io.github.charlietap.chasm.executor.runtime.error.InvocationError +import io.github.charlietap.chasm.fixture.executor.runtime.store +import io.github.charlietap.chasm.host.HostFunctionException +import kotlin.test.Test +import kotlin.test.assertEquals + +class HostFunctionExceptionTest { + + @Test + fun `can run a host function that throws an exception and return a chasm error`() { + + val store = publicStore(store()) + + val functionType = publicFunctionType( + emptyList(), + listOf(ValueType.Number.I32), + ) + val reason = "Fail gracefully" + val exception = HostFunctionException(reason) + val hostFunction: HostFunction = { + throw exception + } + val functionExternal = function(store, functionType, hostFunction) + val functionImport = publicImport( + "env", + "host_function", + functionExternal, + ) + val result = testRunner( + fileName = "host_function.wasm", + fileDirectory = FILE_DIR, + functionName = "call_host_function", + store = store, + imports = listOf( + functionImport, + ), + ) + + val expected = ChasmError.ExecutionError( + InvocationError.HostFunctionError(reason).toString(), + ) + + assertEquals(ChasmResult.Error(expected), result) + } + + companion object { + private const val FILE_DIR = "src/commonTest/resources/integration/" + } +} diff --git a/chasm/src/commonTest/resources/integration/host_function.wasm b/chasm/src/commonTest/resources/integration/host_function.wasm new file mode 100644 index 0000000000000000000000000000000000000000..075851890d3d4c5d1d09f62f5d304da6ba5fecaf GIT binary patch literal 119 zcmZQbEY4+QU|?WmWlUgTtY;EsWKPX19Q)N>0ql giAR-V Result @@ -39,7 +40,11 @@ internal fun HostFunctionCall( store, frame.instance, ) - val results = function.function.invoke(functionContext, params) + val results = try { + function.function.invoke(functionContext, params) + } catch (e: HostFunctionException) { + Err(InvocationError.HostFunctionError(e.reason)).bind() + } type.results.types.forEachIndexed { index, valueType -> val result = results.getOrNull(index) diff --git a/executor/runtime/src/commonMain/kotlin/io/github/charlietap/chasm/executor/runtime/error/InvocationError.kt b/executor/runtime/src/commonMain/kotlin/io/github/charlietap/chasm/executor/runtime/error/InvocationError.kt index 30e7e4fb7..fbc7b3b4d 100644 --- a/executor/runtime/src/commonMain/kotlin/io/github/charlietap/chasm/executor/runtime/error/InvocationError.kt +++ b/executor/runtime/src/commonMain/kotlin/io/github/charlietap/chasm/executor/runtime/error/InvocationError.kt @@ -117,6 +117,9 @@ sealed interface InvocationError : ModuleTrapError { val actualValue: ExecutionValue?, ) : InvocationError + @JvmInline + value class HostFunctionError(val error: String) : InvocationError + data object ProgramFinishedInconsistentState : InvocationError sealed interface Trap : InvocationError { diff --git a/host/build.gradle.kts b/host/build.gradle.kts new file mode 100644 index 000000000..f3787ff36 --- /dev/null +++ b/host/build.gradle.kts @@ -0,0 +1,21 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + alias(libs.plugins.kotlin.multiplatform) + + alias(libs.plugins.conventions.kmp) + alias(libs.plugins.conventions.linting) + alias(libs.plugins.conventions.publishing) +} + +configure { + name = "host" + description = "host system interface" +} + +tasks.withType().configureEach { + compilerOptions { + jvmTarget = JvmTarget.fromTarget(libs.versions.java.bytecode.version.get()) + } +} diff --git a/host/src/commonMain/kotlin/io/github/charlietap/chasm/host/HostFunctionException.kt b/host/src/commonMain/kotlin/io/github/charlietap/chasm/host/HostFunctionException.kt new file mode 100644 index 000000000..0b6ce9686 --- /dev/null +++ b/host/src/commonMain/kotlin/io/github/charlietap/chasm/host/HostFunctionException.kt @@ -0,0 +1,3 @@ +package io.github.charlietap.chasm.host + +class HostFunctionException(val reason: String) : Exception() diff --git a/settings.gradle.kts b/settings.gradle.kts index e962699d9..05b733dc8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -46,6 +46,8 @@ include(":executor:invoker") include(":executor:memory") include(":executor:runtime") +include(":host") + include(":libs:sse2") include(":libs:stack")