Skip to content

Commit 83049aa

Browse files
authored
Upgrade wasmtime dependencies (#37)
1 parent a65d9ef commit 83049aa

File tree

30 files changed

+337
-168
lines changed

30 files changed

+337
-168
lines changed

build.gradle

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ test {
5151
showExceptions true
5252
showCauses true
5353
showStackTraces true
54-
showStandardStreams false
54+
showStandardStreams true
5555
}
5656
}
5757

@@ -94,7 +94,7 @@ task metaProperties() {
9494
}
9595
}
9696

97-
def javah4xVersion = '0.1.0'
97+
def javah4xVersion = '0.2.0'
9898
task downloadJavah4x(type: Download) {
9999
src "https://github.com/kawamuray/javah4x/releases/download/v${javah4xVersion}/javah4x-bin-${javah4xVersion}.zip"
100100
dest new File(project.buildDir, "tmp")
@@ -112,7 +112,6 @@ task generateJniInterfaces(type:JavaExec) {
112112
'io.github.kawamuray.wasmtime.Engine',
113113
'io.github.kawamuray.wasmtime.Func',
114114
'io.github.kawamuray.wasmtime.Instance',
115-
'io.github.kawamuray.wasmtime.InterruptHandle',
116115
'io.github.kawamuray.wasmtime.Linker',
117116
'io.github.kawamuray.wasmtime.Memory',
118117
'io.github.kawamuray.wasmtime.Module',

src/main/java/io/github/kawamuray/wasmtime/Config.java

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,96 @@ long innerPtr() {
2222
/// By default this option is `false`.
2323
public native Config debugInfo(boolean enable);
2424

25-
/// Configures whether functions and loops will be interruptable via the
26-
/// [`Store::interruptHandle`] method.
27-
///
28-
/// For more information see the documentation on
29-
/// [`Store::interruptHandle`].
30-
///
31-
/// By default this option is `false`.
32-
public native Config interruptable(boolean enable);
25+
/// Enables epoch-based interruption.
26+
///
27+
/// When executing code in async mode, we sometimes want to
28+
/// implement a form of cooperative timeslicing: long-running Wasm
29+
/// guest code should periodically yield to the executor
30+
/// loop. This yielding could be implemented by using "fuel" (see
31+
/// [`consume_fuel`](Config::consume_fuel)). However, fuel
32+
/// instrumentation is somewhat expensive: it modifies the
33+
/// compiled form of the Wasm code so that it maintains a precise
34+
/// instruction count, frequently checking this count against the
35+
/// remaining fuel. If one does not need this precise count or
36+
/// deterministic interruptions, and only needs a periodic
37+
/// interrupt of some form, then It would be better to have a more
38+
/// lightweight mechanism.
39+
///
40+
/// Epoch-based interruption is that mechanism. There is a global
41+
/// "epoch", which is a counter that divides time into arbitrary
42+
/// periods (or epochs). This counter lives on the
43+
/// [`Engine`](crate::Engine) and can be incremented by calling
44+
/// [`Engine::increment_epoch`](crate::Engine::increment_epoch).
45+
/// Epoch-based instrumentation works by setting a "deadline
46+
/// epoch". The compiled code knows the deadline, and at certain
47+
/// points, checks the current epoch against that deadline. It
48+
/// will yield if the deadline has been reached.
49+
///
50+
/// The idea is that checking an infrequently-changing counter is
51+
/// cheaper than counting and frequently storing a precise metric
52+
/// (instructions executed) locally. The interruptions are not
53+
/// deterministic, but if the embedder increments the epoch in a
54+
/// periodic way (say, every regular timer tick by a thread or
55+
/// signal handler), then we can ensure that all async code will
56+
/// yield to the executor within a bounded time.
57+
///
58+
/// The deadline check cannot be avoided by malicious wasm code. It is safe
59+
/// to use epoch deadlines to limit the execution time of untrusted
60+
/// code.
61+
///
62+
/// The [`Store`](crate::Store) tracks the deadline, and controls
63+
/// what happens when the deadline is reached during
64+
/// execution. Several behaviors are possible:
65+
///
66+
/// - Trap if code is executing when the epoch deadline is
67+
/// met. See
68+
/// [`Store::epoch_deadline_trap`](crate::Store::epoch_deadline_trap).
69+
///
70+
/// - Call an arbitrary function. This function may chose to trap or
71+
/// increment the epoch. See
72+
/// [`Store::epoch_deadline_callback`](crate::Store::epoch_deadline_callback).
73+
///
74+
/// - Yield to the executor loop, then resume when the future is
75+
/// next polled. See
76+
/// [`Store::epoch_deadline_async_yield_and_update`](crate::Store::epoch_deadline_async_yield_and_update).
77+
///
78+
/// Trapping is the default. The yielding behaviour may be used for
79+
/// the timeslicing behavior described above.
80+
///
81+
/// This feature is available with or without async support.
82+
/// However, without async support, the timeslicing behaviour is
83+
/// not available. This means epoch-based interruption can only
84+
/// serve as a simple external-interruption mechanism.
85+
///
86+
/// An initial deadline must be set before executing code by calling
87+
/// [`Store::set_epoch_deadline`](crate::Store::set_epoch_deadline). If this
88+
/// deadline is not configured then wasm will immediately trap.
89+
///
90+
/// ## When to use fuel vs. epochs
91+
///
92+
/// In general, epoch-based interruption results in faster
93+
/// execution. This difference is sometimes significant: in some
94+
/// measurements, up to 2-3x. This is because epoch-based
95+
/// interruption does less work: it only watches for a global
96+
/// rarely-changing counter to increment, rather than keeping a
97+
/// local frequently-changing counter and comparing it to a
98+
/// deadline.
99+
///
100+
/// Fuel, in contrast, should be used when *deterministic*
101+
/// yielding or trapping is needed. For example, if it is required
102+
/// that the same function call with the same starting state will
103+
/// always either complete or trap with an out-of-fuel error,
104+
/// deterministically, then fuel with a fixed bound should be
105+
/// used.
106+
///
107+
/// # See Also
108+
///
109+
/// - [`Engine::increment_epoch`](crate::Engine::increment_epoch)
110+
/// - [`Store::set_epoch_deadline`](crate::Store::set_epoch_deadline)
111+
/// - [`Store::epoch_deadline_trap`](crate::Store::epoch_deadline_trap)
112+
/// - [`Store::epoch_deadline_callback`](crate::Store::epoch_deadline_callback)
113+
/// - [`Store::epoch_deadline_async_yield_and_update`](crate::Store::epoch_deadline_async_yield_and_update)
114+
public native Config epochInterruption(boolean enable);
33115

34116
/// Configures the maximum amount of native stack space available to
35117
/// executing WebAssembly code.

src/main/java/io/github/kawamuray/wasmtime/Engine.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public Engine(Config config){
2525
this(newEngineWithConfig(config));
2626
}
2727

28+
public native void incrementEpoch();
29+
2830
@Override
2931
public native void dispose();
3032

src/main/java/io/github/kawamuray/wasmtime/ImportType.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ public enum Type {
1313
GLOBAL,
1414
TABLE,
1515
MEMORY,
16-
// TODO: Currently Unsupported
17-
INSTANCE,
18-
MODULE
1916
}
2017

2118
@Getter

src/main/java/io/github/kawamuray/wasmtime/InterruptHandle.java

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/main/java/io/github/kawamuray/wasmtime/Store.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ public T data() {
5050
return (T) storedData();
5151
}
5252

53-
public InterruptHandle interruptHandle() {
54-
return new InterruptHandle(interruptHandlePtr());
55-
}
53+
public native void setEpochDeadline(long ticksBeyondCurrent);
5654

5755
@Override
5856
public native void dispose();
@@ -64,6 +62,4 @@ public InterruptHandle interruptHandle() {
6462
private native Object storedData();
6563

6664
public native void gc();
67-
68-
private native long interruptHandlePtr();
6965
}

src/main/java/io/github/kawamuray/wasmtime/Trap.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,59 @@
99
@Accessors(fluent = true)
1010
@AllArgsConstructor
1111
public class Trap {
12-
public enum Type {
12+
public enum Reason {
1313
MESSAGE,
1414
I32_EXIT,
15+
INSTRUCTION_TRAP,
1516
}
1617

17-
Type type;
18+
public enum TrapCode {
19+
UNKNOWN,
20+
// The current stack space was exhausted.
21+
STACK_OVERFLOW,
22+
// An out-of-bounds memory access.
23+
MEMORY_OUT_OF_BOUNDS,
24+
// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address.
25+
HEAP_MISALIGNED,
26+
// An out-of-bounds access to a table.
27+
TABLE_OUT_OF_BOUNDS,
28+
// Indirect call to a null table entry.
29+
INDIRECT_CALL_TO_NULL,
30+
// Signature mismatch on indirect call.
31+
BAD_SIGNATURE,
32+
// An integer arithmetic operation caused an overflow.
33+
INTEGER_OVERFLOW,
34+
// An integer division by zero.
35+
INTEGER_DIVISION_BY_ZERO,
36+
// Failed float-to-int conversion.
37+
BAD_CONVERSION_TO_INTEGER,
38+
// Code that was supposed to have been unreachable was reached.
39+
UNREACHABLE_CODE_REACHED,
40+
// Execution has potentially run too long and may be interrupted.
41+
INTERRUPT,
42+
// When the `component-model` feature is enabled this trap represents a
43+
// function that was `canon lift`'d, then `canon lower`'d, then called.
44+
// This combination of creation of a function in the component model
45+
// generates a function that always traps and, when called, produces this
46+
// flavor of trap.
47+
ALWAYS_TRAP_ADAPTER,
48+
}
49+
50+
Reason reason;
1851
String message;
1952
int exitCode;
53+
TrapCode trapCode;
2054

2155
public static Trap fromMessage(@NonNull String message) {
22-
return new Trap(Type.MESSAGE, message, 0);
56+
return new Trap(Reason.MESSAGE, message, 0, null);
2357
}
2458

2559
public static Trap fromExitCode(int exitCode) {
26-
return new Trap(Type.I32_EXIT, null, exitCode);
60+
return new Trap(Reason.I32_EXIT, null, exitCode, null);
61+
}
62+
63+
public static Trap fromTrapCode(TrapCode trapCode) {
64+
return new Trap(Reason.INSTRUCTION_TRAP, null, 0, trapCode);
2765
}
2866

2967
public static Trap fromException(Throwable e) {

src/test/java/io/github/kawamuray/wasmtime/FuncTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.Optional;
88
import java.util.concurrent.atomic.AtomicInteger;
99

10+
import io.github.kawamuray.wasmtime.Trap.Reason;
1011
import io.github.kawamuray.wasmtime.wasi.WasiCtx;
1112
import io.github.kawamuray.wasmtime.wasi.WasiCtxBuilder;
1213

@@ -131,7 +132,7 @@ public void testWasiExitTrap() {
131132
func.call(store);
132133
} catch (TrapException e) {
133134
Trap trap = e.trap();
134-
assertEquals(trap.type(), Trap.Type.I32_EXIT);
135+
assertEquals(trap.reason(), Reason.I32_EXIT);
135136
assertEquals(trap.exitCode(), 42);
136137
}
137138
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package io.github.kawamuray.wasmtime;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.fail;
5+
6+
import java.nio.charset.StandardCharsets;
7+
import java.util.Collections;
8+
9+
import org.junit.Test;
10+
11+
import io.github.kawamuray.wasmtime.Trap.TrapCode;
12+
import io.github.kawamuray.wasmtime.WasmFunctions.Consumer0;
13+
14+
public class TrapTest {
15+
private static final String INTERRUPT_WAT = "(module\n"
16+
+ " (func (export \"run\")\n"
17+
+ " (loop\n"
18+
+ " br 0)\n"
19+
+ " )\n"
20+
+ ")\n";
21+
22+
@Test
23+
public void interrupt() {
24+
try (Engine engine = new Engine(new Config().epochInterruption(true));
25+
Store<Void> store = new Store<>(null, engine)) {
26+
store.setEpochDeadline(1);
27+
28+
// Compile and instantiate a small example with an infinite loop.
29+
try (Module module = new Module(engine, INTERRUPT_WAT.getBytes(StandardCharsets.UTF_8));
30+
Instance instance = new Instance(store, module, Collections.emptyList());
31+
Func runFn = instance.getFunc(store, "run").get()) {
32+
Consumer0 run = WasmFunctions.consumer(store, runFn);
33+
34+
new Thread(() -> {
35+
try {
36+
Thread.sleep(1);
37+
} catch (InterruptedException e) {
38+
throw new RuntimeException(e);
39+
}
40+
engine.incrementEpoch();
41+
}).start();
42+
43+
try {
44+
run.accept();
45+
fail("no trap received");
46+
} catch (TrapException e) {
47+
assertEquals(TrapCode.INTERRUPT, e.trap().trapCode());
48+
}
49+
}
50+
}
51+
}
52+
}

wasmtime-jni/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ crate_type = ["cdylib"]
1111
jni = "0.19.0"
1212
thiserror = "1.0.30"
1313
anyhow = "1.0.56"
14-
wasmtime = "0.35.2"
15-
wasmtime-wasi = "0.35.2"
16-
wasi-common = "0.35.2"
17-
wasi-cap-std-sync = "0.35.2"
18-
cap-std = "0.24.2"
14+
wasmtime = "0.40.0"
15+
wasmtime-wasi = "0.40.0"
16+
wasi-common = "0.40.0"
17+
wasi-cap-std-sync = "0.40.0"
18+
cap-std = "0.25.2"

0 commit comments

Comments
 (0)