@tishlang/tish-lint 3.12.1 → 3.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +1 -0
- package/bin/tish-lint +0 -0
- package/crates/tish/Cargo.toml +3 -1
- package/crates/tish/src/cli_help.rs +2 -1
- package/crates/tish/src/main.rs +11 -2
- package/crates/tish_compile/src/codegen.rs +93 -6
- package/crates/tish_compile/src/lib.rs +4 -0
- package/crates/tish_compile/src/resolve.rs +1 -0
- package/crates/tish_compile/tests/regr_ffi_module_emit.rs +120 -0
- package/crates/tish_core/src/lib.rs +43 -4
- package/crates/tish_cranelift_runtime/Cargo.toml +2 -0
- package/crates/tish_ffi/src/lib.rs +48 -25
- package/crates/tish_ffi/src/v2.rs +349 -0
- package/crates/tish_ffi/tests/fixtures/testmod_v2/Cargo.lock +282 -0
- package/crates/tish_ffi/tests/fixtures/testmod_v2/Cargo.toml +16 -0
- package/crates/tish_ffi/tests/fixtures/testmod_v2/src/lib.rs +111 -0
- package/crates/tish_ffi/tests/loader_v2.rs +159 -0
- package/crates/tish_ffi_guest/Cargo.toml +11 -0
- package/crates/tish_ffi_guest/src/lib.rs +333 -0
- package/crates/tish_lint/Cargo.toml +1 -1
- package/crates/tish_native/src/build.rs +48 -13
- package/crates/tish_native/src/config.rs +10 -0
- package/crates/tish_runtime/Cargo.toml +4 -0
- package/crates/tish_runtime/src/http.rs +44 -0
- package/crates/tish_runtime/src/http_fetch.rs +89 -16
- package/crates/tish_runtime/src/lib.rs +21 -0
- package/crates/tish_vm/Cargo.toml +2 -0
- package/crates/tish_vm/src/vm.rs +61 -6
- package/crates/tish_vm/tests/ffi_load_module.rs +32 -0
- package/crates/tish_vm/tests/jit_off_leaves_jit_state.rs +24 -0
- package/crates/tish_vm/tests/multi_vm_isolation.rs +123 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-lint +0 -0
- package/platform/darwin-x64/tish-lint +0 -0
- package/platform/linux-arm64/tish-lint +0 -0
- package/platform/linux-x64/tish-lint +0 -0
- package/platform/win32-x64/tish-lint.exe +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//! A JIT-off VM must not touch the process-global JIT state. Its own test binary (one test, one
|
|
2
|
+
//! process) because other tests JIT in parallel and would move the cache counts.
|
|
3
|
+
|
|
4
|
+
use tishlang_bytecode::compile;
|
|
5
|
+
use tishlang_vm::{jit_cache_lens, Vm};
|
|
6
|
+
|
|
7
|
+
#[test]
|
|
8
|
+
fn jit_off_vm_never_compiles() {
|
|
9
|
+
let src = "fn sq(x) { return x * x }\n\
|
|
10
|
+
fn sum(n) { let s = 0\nfor (let i = 0; i < n; i = i + 1) { s = s + sq(i) }\nreturn s }\n\
|
|
11
|
+
fn make() { fn calc(x) { return sq(x) + 1 }\nreturn calc }\n\
|
|
12
|
+
let total = sum(20000) + make()(3)";
|
|
13
|
+
let chunk = compile(&tishlang_parser::parse(src).expect("parse")).expect("compile");
|
|
14
|
+
|
|
15
|
+
let before = jit_cache_lens();
|
|
16
|
+
let mut off = Vm::new();
|
|
17
|
+
off.set_jit_enabled(false);
|
|
18
|
+
off.run(&chunk).expect("jit off runs");
|
|
19
|
+
assert_eq!(jit_cache_lens(), before, "a JIT-off VM compiled something");
|
|
20
|
+
|
|
21
|
+
let mut on = Vm::new();
|
|
22
|
+
on.run(&chunk).expect("jit on runs");
|
|
23
|
+
assert_ne!(jit_cache_lens(), before, "control: the same program JIT-compiles with the JIT on");
|
|
24
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//! Several mutually untrusted programs in one process, each in its own `Vm` (an embedder hosting
|
|
2
|
+
//! plugins). Two process-wide mechanisms leak between such VMs, and these tests pin the controls:
|
|
3
|
+
//!
|
|
4
|
+
//! - The numeric JIT's caches and callee registry are process-global, and every JIT-on `run` resets
|
|
5
|
+
//! the registry, which races with VMs running on other threads. `Vm::set_jit_enabled(false)` keeps
|
|
6
|
+
//! a VM out of the JIT entirely (`jit_off_leaves_jit_state.rs` checks it never touches the caches).
|
|
7
|
+
//! - `set_execution_deadline` is process-wide. `set_thread_execution_deadline` budgets only the
|
|
8
|
+
//! calling thread. The interpreter polls on loop back-edges and on calls; JIT-compiled loops do
|
|
9
|
+
//! not poll, so a budgeted VM runs JIT-off.
|
|
10
|
+
//!
|
|
11
|
+
//! Programs hand values out through bare top-level assignments (undeclared names are globals).
|
|
12
|
+
#![cfg(feature = "send-values")]
|
|
13
|
+
|
|
14
|
+
use std::sync::mpsc;
|
|
15
|
+
use std::time::{Duration, Instant};
|
|
16
|
+
|
|
17
|
+
use tishlang_bytecode::compile;
|
|
18
|
+
use tishlang_core::{set_thread_execution_deadline, Value, DEADLINE_ERROR};
|
|
19
|
+
use tishlang_vm::Vm;
|
|
20
|
+
|
|
21
|
+
fn run(src: &str, jit: bool) -> (Vm, Result<Value, String>) {
|
|
22
|
+
let chunk = compile(&tishlang_parser::parse(src).expect("parse")).expect("compile");
|
|
23
|
+
let mut vm = Vm::new();
|
|
24
|
+
vm.set_jit_enabled(jit);
|
|
25
|
+
let r = vm.run(&chunk);
|
|
26
|
+
(vm, r)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fn global_num(vm: &Vm, name: &str) -> f64 {
|
|
30
|
+
match vm.get_global(name) {
|
|
31
|
+
Some(Value::Number(n)) => n,
|
|
32
|
+
other => panic!("global `{name}` is not a number: {other:?}"),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fn global_fn(vm: &Vm, name: &str) -> tishlang_core::NativeFn {
|
|
37
|
+
match vm.get_global(name) {
|
|
38
|
+
Some(Value::Function(f)) => f,
|
|
39
|
+
other => panic!("global `{name}` is not a function: {other:?}"),
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Run `f` on a fresh thread and fail (instead of hanging the suite) if it takes longer than `limit`.
|
|
44
|
+
fn within<T: Send + 'static>(limit: Duration, f: impl FnOnce() -> T + Send + 'static) -> T {
|
|
45
|
+
let (tx, rx) = mpsc::channel();
|
|
46
|
+
std::thread::spawn(move || {
|
|
47
|
+
let _ = tx.send(f());
|
|
48
|
+
});
|
|
49
|
+
rx.recv_timeout(limit).expect("did not finish in time")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const SPIN: &str = "fn spin(n) { let s = 0\nlet i = 0\nwhile (i < n) { s = s + i\ni = i + 1 }\nreturn s }\n";
|
|
53
|
+
|
|
54
|
+
#[test]
|
|
55
|
+
fn jit_off_gives_the_same_results() {
|
|
56
|
+
let src = "fn fib(n) { if (n < 2) { return n }\nreturn fib(n - 1) + fib(n - 2) }\n\
|
|
57
|
+
fn sum(n) { let s = 0\nfor (let i = 0; i < n; i = i + 1) { s = s + i * 0.5 }\nreturn s }\n\
|
|
58
|
+
out = fib(20) + sum(50000)";
|
|
59
|
+
let (on, r) = run(src, true);
|
|
60
|
+
r.expect("jit on");
|
|
61
|
+
let (off, r) = run(src, false);
|
|
62
|
+
r.expect("jit off");
|
|
63
|
+
assert_eq!(global_num(&on, "out"), 6765.0 + 624987500.0);
|
|
64
|
+
assert_eq!(global_num(&on, "out"), global_num(&off, "out"));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// A hot numeric loop inside a function: with the JIT on it would be compiled and never poll.
|
|
68
|
+
#[test]
|
|
69
|
+
fn thread_deadline_stops_a_jit_off_hot_loop() {
|
|
70
|
+
let (r, elapsed) = within(Duration::from_secs(10), || {
|
|
71
|
+
let t0 = Instant::now();
|
|
72
|
+
set_thread_execution_deadline(Some(100));
|
|
73
|
+
let r = run(&format!("{SPIN}out = spin(1000000000000)"), false).1;
|
|
74
|
+
set_thread_execution_deadline(None);
|
|
75
|
+
(r, t0.elapsed())
|
|
76
|
+
});
|
|
77
|
+
let err = r.expect_err("the deadline must abort the loop");
|
|
78
|
+
assert!(err.contains(DEADLINE_ERROR), "{err}");
|
|
79
|
+
assert!(elapsed < Duration::from_secs(2), "took {elapsed:?}");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// No loop at all: naive recursion stays far below the call-depth limit but never hits a back-edge.
|
|
83
|
+
#[test]
|
|
84
|
+
fn thread_deadline_stops_loop_free_recursion() {
|
|
85
|
+
let (r, elapsed) = within(Duration::from_secs(10), || {
|
|
86
|
+
let t0 = Instant::now();
|
|
87
|
+
set_thread_execution_deadline(Some(100));
|
|
88
|
+
let r = run("fn fib(n) { return n < 2 ? n : fib(n - 1) + fib(n - 2) }\nout = fib(45)", false).1;
|
|
89
|
+
set_thread_execution_deadline(None);
|
|
90
|
+
(r, t0.elapsed())
|
|
91
|
+
});
|
|
92
|
+
let err = r.expect_err("the deadline must abort the recursion");
|
|
93
|
+
assert!(err.contains(DEADLINE_ERROR), "{err}");
|
|
94
|
+
assert!(elapsed < Duration::from_secs(2), "took {elapsed:?}");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#[test]
|
|
98
|
+
fn thread_deadline_is_per_thread() {
|
|
99
|
+
set_thread_execution_deadline(Some(1));
|
|
100
|
+
std::thread::sleep(Duration::from_millis(5));
|
|
101
|
+
let n = within(Duration::from_secs(20), || {
|
|
102
|
+
let (vm, r) = run("let s = 0\nfor (let i = 0; i < 3000000; i = i + 1) { s = s + 1 }\nout = s", false);
|
|
103
|
+
r.expect("another thread's expired deadline must not apply");
|
|
104
|
+
global_num(&vm, "out")
|
|
105
|
+
});
|
|
106
|
+
set_thread_execution_deadline(None);
|
|
107
|
+
assert_eq!(n, 3000000.0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#[test]
|
|
111
|
+
fn jit_setting_is_inherited_by_closures_called_from_the_host() {
|
|
112
|
+
let (vm, r) = run(&format!("{SPIN}spinFn = spin"), false);
|
|
113
|
+
r.expect("runs");
|
|
114
|
+
let spin = global_fn(&vm, "spinFn");
|
|
115
|
+
let thrown = within(Duration::from_secs(10), move || {
|
|
116
|
+
set_thread_execution_deadline(Some(100));
|
|
117
|
+
let _ = spin.call(&[Value::Number(1e12)]);
|
|
118
|
+
set_thread_execution_deadline(None);
|
|
119
|
+
tishlang_core::take_pending_throw().map(|v| v.to_display_string())
|
|
120
|
+
});
|
|
121
|
+
let thrown = thrown.expect("a host call into a JIT-off closure must hit the deadline and throw");
|
|
122
|
+
assert!(thrown.contains(DEADLINE_ERROR), "{thrown}");
|
|
123
|
+
}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|