@tishlang/tish-format 1.0.13 → 2.0.2
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 +2 -0
- package/bin/tish-format +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +1 -0
- package/crates/tish/Cargo.toml +10 -2
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cli_help.rs +15 -4
- package/crates/tish/src/main.rs +93 -21
- package/crates/tish/src/repl_completion.rs +0 -1
- package/crates/tish/tests/error_source_location.rs +36 -0
- package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
- package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
- package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
- package/crates/tish/tests/integration_test.rs +402 -91
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/src/ast.rs +37 -8
- package/crates/tish_builtins/Cargo.toml +2 -0
- package/crates/tish_builtins/src/array.rs +375 -13
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +59 -19
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +86 -6
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +5 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +2 -2
- package/crates/tish_builtins/src/string.rs +19 -20
- package/crates/tish_builtins/src/symbol.rs +1 -1
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/src/chunk.rs +69 -1
- package/crates/tish_bytecode/src/compiler.rs +933 -89
- package/crates/tish_bytecode/src/encoding.rs +2 -0
- package/crates/tish_bytecode/src/lib.rs +2 -1
- package/crates/tish_bytecode/src/opcode.rs +47 -4
- package/crates/tish_bytecode/src/serialize.rs +31 -1
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +2334 -349
- package/crates/tish_compile/src/infer.rs +1395 -6
- package/crates/tish_compile/src/lib.rs +50 -8
- package/crates/tish_compile/src/resolve.rs +584 -21
- package/crates/tish_compile/src/types.rs +106 -2
- package/crates/tish_compile_js/src/codegen.rs +67 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +64 -0
- package/crates/tish_core/Cargo.toml +7 -1
- package/crates/tish_core/src/console_style.rs +11 -1
- package/crates/tish_core/src/json.rs +81 -38
- package/crates/tish_core/src/lib.rs +3 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/value.rs +679 -25
- package/crates/tish_core/src/vmref.rs +13 -8
- package/crates/tish_cranelift/src/link.rs +17 -4
- package/crates/tish_cranelift_runtime/Cargo.toml +1 -0
- package/crates/tish_eval/Cargo.toml +6 -0
- package/crates/tish_eval/src/eval.rs +665 -117
- package/crates/tish_eval/src/http.rs +4 -1
- package/crates/tish_eval/src/natives.rs +165 -13
- package/crates/tish_eval/src/value.rs +31 -13
- package/crates/tish_eval/src/value_convert.rs +10 -4
- package/crates/tish_ffi/Cargo.toml +26 -0
- package/crates/tish_ffi/src/lib.rs +518 -0
- package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
- package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
- package/crates/tish_ffi/tests/loader.rs +65 -0
- package/crates/tish_fmt/Cargo.toml +1 -1
- package/crates/tish_fmt/src/lib.rs +61 -5
- package/crates/tish_lexer/src/lib.rs +397 -9
- package/crates/tish_lexer/src/token.rs +7 -0
- package/crates/tish_lint/src/lib.rs +2 -10
- package/crates/tish_lsp/src/import_goto.rs +2 -0
- package/crates/tish_lsp/src/main.rs +439 -26
- package/crates/tish_native/src/build.rs +55 -1
- package/crates/tish_opt/src/lib.rs +126 -23
- package/crates/tish_parser/src/lib.rs +55 -1
- package/crates/tish_parser/src/parser.rs +456 -34
- package/crates/tish_pg/src/lib.rs +3 -3
- package/crates/tish_resolve/src/lib.rs +99 -59
- package/crates/tish_runtime/Cargo.toml +4 -0
- package/crates/tish_runtime/src/http.rs +66 -17
- package/crates/tish_runtime/src/http_fetch.rs +29 -8
- package/crates/tish_runtime/src/http_hyper.rs +25 -2
- package/crates/tish_runtime/src/lib.rs +299 -44
- package/crates/tish_runtime/src/promise.rs +328 -18
- package/crates/tish_runtime/src/timers.rs +13 -7
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +35 -18
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +2 -2
- package/crates/tish_ui/src/jsx.rs +10 -0
- package/crates/tish_ui/src/runtime/hooks.rs +19 -15
- package/crates/tish_ui/src/runtime/mod.rs +15 -12
- package/crates/tish_vm/Cargo.toml +14 -1
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +2 -0
- package/crates/tish_vm/src/vm.rs +1546 -202
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
- package/crates/tish_wasm/src/lib.rs +6 -2
- package/crates/tish_wasm_runtime/src/gpu.rs +17 -1
- package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
- package/crates/tishlang_cargo_bindgen/src/lib.rs +2 -2
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +1 -1
- package/justfile +8 -0
- package/package.json +2 -2
- package/platform/darwin-arm64/tish-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- package/platform/win32-x64/tish-fmt.exe +0 -0
- package/README.md +0 -138
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
//! Typed arrays (`Float32Array`, `Float64Array`, `Int8Array`, `Uint8Array`, `Uint8ClampedArray`,
|
|
2
|
+
//! `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`) for the non-JS targets.
|
|
3
|
+
//!
|
|
4
|
+
//! ## Representation
|
|
5
|
+
//! Unlike `Date`/`Set`/`Map`, typed arrays are array-LIKE — they need indexing (`ta[i]`), `.length`,
|
|
6
|
+
//! `for…of`, and the array methods. So a typed array is just a **`Value::Array`** (which gives all of
|
|
7
|
+
//! that for free across every backend), with each element **coerced to the view's element type at
|
|
8
|
+
//! construction**: `Float32Array` rounds to f32 precision, the integer views truncate-and-wrap
|
|
9
|
+
//! (`ToInt8`/`ToUint32`/…), and `Uint8ClampedArray` clamps to `0..=255` (round-half-to-even).
|
|
10
|
+
//!
|
|
11
|
+
//! ## v1 scope / gaps (documented in tishlang-web)
|
|
12
|
+
//! - **Element coercion happens on construction / `.from` / `.of`, not on element assignment.**
|
|
13
|
+
//! `ta[i] = 300` stores `300` (a plain array write); rebuild via `Uint8Array.of(...)` if you need
|
|
14
|
+
//! the wrap. (A packed-native representation enforces write-coercion via the Rust element type — a
|
|
15
|
+
//! fast-follow.)
|
|
16
|
+
//! - No `ArrayBuffer` / `DataView` / `.buffer` / `.byteLength` / `.subarray` / `.set` yet.
|
|
17
|
+
//! - A typed array is a regular array at runtime, so `Array.isArray(ta)` is `true` and there is no
|
|
18
|
+
//! `instanceof` distinction.
|
|
19
|
+
|
|
20
|
+
use std::sync::Arc;
|
|
21
|
+
use tishlang_core::{ObjectMap, Value, VmRef};
|
|
22
|
+
|
|
23
|
+
const CONSTRUCT: &str = "__construct";
|
|
24
|
+
|
|
25
|
+
/// Element type of a typed-array view.
|
|
26
|
+
#[derive(Clone, Copy)]
|
|
27
|
+
enum Kind {
|
|
28
|
+
F64,
|
|
29
|
+
F32,
|
|
30
|
+
I8,
|
|
31
|
+
U8,
|
|
32
|
+
U8Clamped,
|
|
33
|
+
I16,
|
|
34
|
+
U16,
|
|
35
|
+
I32,
|
|
36
|
+
U32,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// `ToUintN(x)` — ECMAScript truncate-then-wrap into `[0, 2^bits)`.
|
|
40
|
+
fn to_uint(x: f64, bits: u32) -> f64 {
|
|
41
|
+
if !x.is_finite() {
|
|
42
|
+
return 0.0;
|
|
43
|
+
}
|
|
44
|
+
let m = 2f64.powi(bits as i32);
|
|
45
|
+
// `trunc` rounds toward zero (ToInteger); `rem_euclid` yields a non-negative remainder.
|
|
46
|
+
x.trunc().rem_euclid(m)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// `ToIntN(x)` — `ToUintN` reinterpreted as a signed `bits`-wide integer.
|
|
50
|
+
fn to_int(x: f64, bits: u32) -> f64 {
|
|
51
|
+
let u = to_uint(x, bits);
|
|
52
|
+
let half = 2f64.powi(bits as i32 - 1);
|
|
53
|
+
if u >= half {
|
|
54
|
+
u - 2.0 * half
|
|
55
|
+
} else {
|
|
56
|
+
u
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Coerce one number to the view's element type.
|
|
61
|
+
fn coerce(kind: Kind, x: f64) -> f64 {
|
|
62
|
+
match kind {
|
|
63
|
+
Kind::F64 => x,
|
|
64
|
+
Kind::F32 => x as f32 as f64,
|
|
65
|
+
Kind::U8Clamped => {
|
|
66
|
+
if x.is_nan() || x <= 0.0 {
|
|
67
|
+
0.0
|
|
68
|
+
} else if x >= 255.0 {
|
|
69
|
+
255.0
|
|
70
|
+
} else {
|
|
71
|
+
x.round_ties_even()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
Kind::I8 => to_int(x, 8),
|
|
75
|
+
Kind::U8 => to_uint(x, 8),
|
|
76
|
+
Kind::I16 => to_int(x, 16),
|
|
77
|
+
Kind::U16 => to_uint(x, 16),
|
|
78
|
+
Kind::I32 => to_int(x, 32),
|
|
79
|
+
Kind::U32 => to_uint(x, 32),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fn bytes_per_element(kind: Kind) -> f64 {
|
|
84
|
+
match kind {
|
|
85
|
+
Kind::F64 => 8.0,
|
|
86
|
+
Kind::I32 | Kind::U32 | Kind::F32 => 4.0,
|
|
87
|
+
Kind::I16 | Kind::U16 => 2.0,
|
|
88
|
+
Kind::I8 | Kind::U8 | Kind::U8Clamped => 1.0,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Array-like `value` → its elements (arrays and packed number-arrays; otherwise empty).
|
|
93
|
+
fn elements(value: &Value) -> Vec<Value> {
|
|
94
|
+
match value {
|
|
95
|
+
Value::Array(a) => a.borrow().clone(),
|
|
96
|
+
Value::NumberArray(a) => a.borrow().iter().map(|n| Value::Number(*n)).collect(),
|
|
97
|
+
_ => Vec::new(),
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Build the backing `Value::Array`, coercing every element to `kind`. Non-numeric elements become
|
|
102
|
+
/// `NaN` first (→ `0` for the integer views).
|
|
103
|
+
fn from_values(kind: Kind, vals: &[Value]) -> Value {
|
|
104
|
+
let out: Vec<Value> = vals
|
|
105
|
+
.iter()
|
|
106
|
+
.map(|v| Value::Number(coerce(kind, v.as_number().unwrap_or(f64::NAN))))
|
|
107
|
+
.collect();
|
|
108
|
+
Value::Array(VmRef::new(out))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// `new X(...)`: a length `n` (zero-filled), or an array-like to copy+coerce, or empty.
|
|
112
|
+
fn construct(kind: Kind, args: &[Value]) -> Value {
|
|
113
|
+
match args.first() {
|
|
114
|
+
None => Value::Array(VmRef::new(Vec::new())),
|
|
115
|
+
// `0` coerces to `0` for every element type, so a length-`n` view is `n` zeros.
|
|
116
|
+
Some(Value::Number(n)) => {
|
|
117
|
+
let len = n.max(0.0) as usize;
|
|
118
|
+
Value::Array(VmRef::new(vec![Value::Number(0.0); len]))
|
|
119
|
+
}
|
|
120
|
+
Some(v) => from_values(kind, &elements(v)),
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Native-codegen-only packed constructor for `new Float64Array(...)`.
|
|
125
|
+
///
|
|
126
|
+
/// `Float64Array` is the one view whose element type *is* `f64`, so it needs no coercion and maps
|
|
127
|
+
/// exactly onto the packed [`Value::NumberArray`] (`Vec<f64>`) representation — eliminating the
|
|
128
|
+
/// per-element `Value` boxing the generic boxed `Value::Array` backing pays. When
|
|
129
|
+
/// [`Value::packed_arrays_enabled`] is **off** (the default), this returns the *identical* boxed
|
|
130
|
+
/// value the generic constructor would, so default builds stay byte-for-byte unchanged; the packed
|
|
131
|
+
/// form is only ever produced under `TISH_PACKED_ARRAYS=1`.
|
|
132
|
+
///
|
|
133
|
+
/// This lives behind the native codegen (interp/VM keep the boxed `Value::Array` — their value
|
|
134
|
+
/// bridges have no `NumberArray` variant), so on the native path a `NumberArray` is *always* a
|
|
135
|
+
/// `Float64Array`. That makes storing writes as `f64` the correct view semantics (and closes the
|
|
136
|
+
/// construction-only-coercion gap for this one view). Any op without a packed fast path materialises
|
|
137
|
+
/// it back to a boxed array, so every array method keeps working.
|
|
138
|
+
pub fn float64_array_packed(args: &[Value]) -> Value {
|
|
139
|
+
if !Value::packed_arrays_enabled() {
|
|
140
|
+
// Byte-identical fallback to the generic boxed `Value::Array` backing.
|
|
141
|
+
return construct(Kind::F64, args);
|
|
142
|
+
}
|
|
143
|
+
let nums: Vec<f64> = match args.first() {
|
|
144
|
+
None => Vec::new(),
|
|
145
|
+
// Length form: `new Float64Array(n)` → `n` zeros (0.0 is the F64 coercion of 0).
|
|
146
|
+
Some(Value::Number(n)) => vec![0.0; n.max(0.0) as usize],
|
|
147
|
+
// Array-like copy: mirror `from_values(F64, …)` — non-numeric elements become `NaN`.
|
|
148
|
+
Some(v) => elements(v)
|
|
149
|
+
.iter()
|
|
150
|
+
.map(|e| e.as_number().unwrap_or(f64::NAN))
|
|
151
|
+
.collect(),
|
|
152
|
+
};
|
|
153
|
+
Value::number_array(nums)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// The constructor object for one view kind: `__construct` + `from` / `of` / `BYTES_PER_ELEMENT`.
|
|
157
|
+
fn make_constructor(kind: Kind) -> Value {
|
|
158
|
+
let mut m = ObjectMap::default();
|
|
159
|
+
m.insert(
|
|
160
|
+
Arc::from(CONSTRUCT),
|
|
161
|
+
Value::native(move |args: &[Value]| construct(kind, args)),
|
|
162
|
+
);
|
|
163
|
+
m.insert(
|
|
164
|
+
Arc::from("from"),
|
|
165
|
+
Value::native(move |args: &[Value]| {
|
|
166
|
+
from_values(kind, &args.first().map(elements).unwrap_or_default())
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
m.insert(
|
|
170
|
+
Arc::from("of"),
|
|
171
|
+
Value::native(move |args: &[Value]| from_values(kind, args)),
|
|
172
|
+
);
|
|
173
|
+
m.insert(
|
|
174
|
+
Arc::from("BYTES_PER_ELEMENT"),
|
|
175
|
+
Value::Number(bytes_per_element(kind)),
|
|
176
|
+
);
|
|
177
|
+
Value::object(m)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
macro_rules! ctor_fn {
|
|
181
|
+
($name:ident, $kind:expr) => {
|
|
182
|
+
pub fn $name() -> Value {
|
|
183
|
+
make_constructor($kind)
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
ctor_fn!(float64_array_constructor_value, Kind::F64);
|
|
189
|
+
ctor_fn!(float32_array_constructor_value, Kind::F32);
|
|
190
|
+
ctor_fn!(int8_array_constructor_value, Kind::I8);
|
|
191
|
+
ctor_fn!(uint8_array_constructor_value, Kind::U8);
|
|
192
|
+
ctor_fn!(uint8_clamped_array_constructor_value, Kind::U8Clamped);
|
|
193
|
+
ctor_fn!(int16_array_constructor_value, Kind::I16);
|
|
194
|
+
ctor_fn!(uint16_array_constructor_value, Kind::U16);
|
|
195
|
+
ctor_fn!(int32_array_constructor_value, Kind::I32);
|
|
196
|
+
ctor_fn!(uint32_array_constructor_value, Kind::U32);
|
|
197
|
+
|
|
198
|
+
#[cfg(test)]
|
|
199
|
+
mod tests {
|
|
200
|
+
use super::*;
|
|
201
|
+
|
|
202
|
+
fn nums(v: &Value) -> Vec<f64> {
|
|
203
|
+
match v {
|
|
204
|
+
Value::Array(a) => a
|
|
205
|
+
.borrow()
|
|
206
|
+
.iter()
|
|
207
|
+
.map(|e| match e {
|
|
208
|
+
Value::Number(n) => *n,
|
|
209
|
+
_ => f64::NAN,
|
|
210
|
+
})
|
|
211
|
+
.collect(),
|
|
212
|
+
_ => vec![],
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#[test]
|
|
217
|
+
fn float32_rounds_to_f32_precision() {
|
|
218
|
+
// 1.1 is not representable in f32; the stored value is the f32-rounded double.
|
|
219
|
+
let v = from_values(Kind::F32, &[Value::Number(1.1)]);
|
|
220
|
+
assert_eq!(nums(&v)[0], 1.1f32 as f64);
|
|
221
|
+
assert_ne!(nums(&v)[0], 1.1);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#[test]
|
|
225
|
+
fn uint8_wraps() {
|
|
226
|
+
let v = from_values(Kind::U8, &[Value::Number(300.0), Value::Number(-1.0), Value::Number(256.0)]);
|
|
227
|
+
assert_eq!(nums(&v), vec![44.0, 255.0, 0.0]);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#[test]
|
|
231
|
+
fn int8_wraps_signed() {
|
|
232
|
+
let v = from_values(Kind::I8, &[Value::Number(127.0), Value::Number(128.0), Value::Number(-129.0)]);
|
|
233
|
+
assert_eq!(nums(&v), vec![127.0, -128.0, 127.0]);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#[test]
|
|
237
|
+
fn uint8_clamped_clamps_and_rounds_half_even() {
|
|
238
|
+
let v = from_values(
|
|
239
|
+
Kind::U8Clamped,
|
|
240
|
+
&[Value::Number(-5.0), Value::Number(300.0), Value::Number(2.5), Value::Number(3.5)],
|
|
241
|
+
);
|
|
242
|
+
// 2.5 → 2 (round to even), 3.5 → 4 (round to even).
|
|
243
|
+
assert_eq!(nums(&v), vec![0.0, 255.0, 2.0, 4.0]);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#[test]
|
|
247
|
+
fn int_views_map_nan_to_zero() {
|
|
248
|
+
let v = from_values(Kind::I32, &[Value::Null, Value::String("x".into())]);
|
|
249
|
+
assert_eq!(nums(&v), vec![0.0, 0.0]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#[test]
|
|
253
|
+
fn construct_length_is_zero_filled() {
|
|
254
|
+
let v = construct(Kind::U16, &[Value::Number(3.0)]);
|
|
255
|
+
assert_eq!(nums(&v), vec![0.0, 0.0, 0.0]);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
#[test]
|
|
259
|
+
fn uint32_wraps_large() {
|
|
260
|
+
let v = from_values(Kind::U32, &[Value::Number(4294967296.0), Value::Number(4294967297.0)]);
|
|
261
|
+
assert_eq!(nums(&v), vec![0.0, 1.0]);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// `float64_array_packed` toggles on a process-global env var. No other test in this crate reads
|
|
265
|
+
// `packed_arrays_enabled`, so the set/remove here can't perturb a concurrent test; we restore the
|
|
266
|
+
// default (off) on exit regardless.
|
|
267
|
+
#[test]
|
|
268
|
+
fn float64_packed_respects_flag() {
|
|
269
|
+
// Flag off (default): byte-identical boxed `Value::Array` fallback, no packed value produced.
|
|
270
|
+
std::env::remove_var("TISH_PACKED_ARRAYS");
|
|
271
|
+
let boxed = float64_array_packed(&[Value::Number(3.0)]);
|
|
272
|
+
assert!(matches!(boxed, Value::Array(_)), "packed-off must return boxed Array");
|
|
273
|
+
assert_eq!(nums(&boxed), vec![0.0, 0.0, 0.0]);
|
|
274
|
+
|
|
275
|
+
// Flag on: packed `Value::NumberArray`. F64 needs no coercion (exact), non-numeric → NaN
|
|
276
|
+
// (matching the boxed `from_values(F64, …)`), and the length form zero-fills.
|
|
277
|
+
std::env::set_var("TISH_PACKED_ARRAYS", "1");
|
|
278
|
+
let packed = float64_array_packed(&[Value::Array(VmRef::new(vec![
|
|
279
|
+
Value::Number(1.1),
|
|
280
|
+
Value::Number(2.2),
|
|
281
|
+
Value::Null,
|
|
282
|
+
]))]);
|
|
283
|
+
match &packed {
|
|
284
|
+
Value::NumberArray(a) => {
|
|
285
|
+
let v = a.borrow();
|
|
286
|
+
assert_eq!(v[0], 1.1);
|
|
287
|
+
assert_eq!(v[1], 2.2);
|
|
288
|
+
assert!(v[2].is_nan());
|
|
289
|
+
}
|
|
290
|
+
_ => panic!("packed-on must return NumberArray"),
|
|
291
|
+
}
|
|
292
|
+
assert!(matches!(
|
|
293
|
+
float64_array_packed(&[Value::Number(2.0)]),
|
|
294
|
+
Value::NumberArray(_)
|
|
295
|
+
));
|
|
296
|
+
std::env::remove_var("TISH_PACKED_ARRAYS");
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
//! Bytecode chunk: instructions and constants.
|
|
2
2
|
|
|
3
|
+
use std::sync::atomic::AtomicU64;
|
|
3
4
|
use std::sync::Arc;
|
|
4
5
|
use tishlang_core::Value;
|
|
5
6
|
|
|
7
|
+
/// Per-property-name inline cache for object access (the JavaScriptCore inline-cache idea), indexed by
|
|
8
|
+
/// the name index that `GetMember`/`SetMember` already carry. Each cell packs
|
|
9
|
+
/// `(shape_id:u32 << 32) | slot_index:u32`; `0` = uncached. A racy `Relaxed` load/store is sound: a
|
|
10
|
+
/// stale read just falls to the slow path, which re-checks the object's shape and refills. This is a
|
|
11
|
+
/// runtime cache, NOT program data — a cloned `Chunk` (e.g. each closure instance) starts empty.
|
|
12
|
+
#[derive(Debug, Default)]
|
|
13
|
+
pub struct InlineCaches(pub Vec<AtomicU64>);
|
|
14
|
+
|
|
15
|
+
impl Clone for InlineCaches {
|
|
16
|
+
fn clone(&self) -> Self {
|
|
17
|
+
InlineCaches(self.0.iter().map(|_| AtomicU64::new(0)).collect())
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
6
21
|
/// A constant in the constants table.
|
|
7
22
|
#[derive(Debug, Clone)]
|
|
8
23
|
pub enum Constant {
|
|
@@ -19,7 +34,7 @@ impl Constant {
|
|
|
19
34
|
pub fn to_value(&self) -> Value {
|
|
20
35
|
match self {
|
|
21
36
|
Constant::Number(n) => Value::Number(*n),
|
|
22
|
-
Constant::String(s) => Value::String(
|
|
37
|
+
Constant::String(s) => Value::String(tishlang_core::ArcStr::from(s.as_ref())),
|
|
23
38
|
Constant::Bool(b) => Value::Bool(*b),
|
|
24
39
|
Constant::Null => Value::Null,
|
|
25
40
|
Constant::Closure(_) => {
|
|
@@ -45,6 +60,25 @@ pub struct Chunk {
|
|
|
45
60
|
pub rest_param_index: u16,
|
|
46
61
|
/// Number of leading names that are parameters (for proper closure arg binding).
|
|
47
62
|
pub param_count: u16,
|
|
63
|
+
/// Number of local variable slots this chunk's call frame needs (params + body locals).
|
|
64
|
+
/// Frame `locals` Vec is sized to this. Only meaningful when `slot_based`.
|
|
65
|
+
pub num_slots: u16,
|
|
66
|
+
/// When true, this chunk resolves its locals via integer frame slots
|
|
67
|
+
/// (`LoadLocal`/`StoreLocal`) instead of name-keyed scope maps. Set for
|
|
68
|
+
/// self-contained functions (no free-variable / global references), whose
|
|
69
|
+
/// call frame is a bare `Vec<Value>` of length `num_slots` — no per-call
|
|
70
|
+
/// hashmap, no name lookups. Name-based chunks (top level, closures that
|
|
71
|
+
/// capture outer scope) leave this `false` and use the legacy path.
|
|
72
|
+
pub slot_based: bool,
|
|
73
|
+
/// Inline caches for object property access, one cell per entry in `names` (so indexed by the
|
|
74
|
+
/// same name index `GetMember`/`SetMember` carry). Runtime-only; not part of the serialized program.
|
|
75
|
+
pub inline_caches: InlineCaches,
|
|
76
|
+
/// Source line table: `(code_offset, line)` pairs, sorted by offset, one entry per line change
|
|
77
|
+
/// (issue #74). Consulted only when formatting a runtime error, so it adds zero execution
|
|
78
|
+
/// overhead. Debug-only / runtime-only — not serialized (persisted bytecode loses line info).
|
|
79
|
+
pub lines: Vec<(u32, u32)>,
|
|
80
|
+
/// Source file path for error messages (`file:line`); propagated to nested chunks. Runtime-only.
|
|
81
|
+
pub source: Option<Arc<str>>,
|
|
48
82
|
}
|
|
49
83
|
|
|
50
84
|
impl Chunk {
|
|
@@ -56,6 +90,39 @@ impl Chunk {
|
|
|
56
90
|
nested: Vec::new(),
|
|
57
91
|
rest_param_index: super::NO_REST_PARAM,
|
|
58
92
|
param_count: 0,
|
|
93
|
+
num_slots: 0,
|
|
94
|
+
slot_based: false,
|
|
95
|
+
inline_caches: InlineCaches::default(),
|
|
96
|
+
lines: Vec::new(),
|
|
97
|
+
source: None,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Record that the instruction starting at `offset` originates from source `line` (1-based).
|
|
102
|
+
/// Only stored when the line changes, keeping the table compact. Issue #74.
|
|
103
|
+
pub fn mark_line(&mut self, offset: usize, line: u32) {
|
|
104
|
+
if line == 0 {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
match self.lines.last() {
|
|
108
|
+
Some(&(_, last_line)) if last_line == line => {}
|
|
109
|
+
_ => self.lines.push((offset as u32, line)),
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Source line for a bytecode `offset` (the line of the nearest preceding `mark_line`), or
|
|
114
|
+
/// `None` if no line info is available (e.g. deserialized bytecode). Issue #74.
|
|
115
|
+
pub fn line_at(&self, offset: usize) -> Option<u32> {
|
|
116
|
+
if self.lines.is_empty() {
|
|
117
|
+
return None;
|
|
118
|
+
}
|
|
119
|
+
let off = offset as u32;
|
|
120
|
+
// Largest recorded offset <= `off`.
|
|
121
|
+
let idx = self.lines.partition_point(|&(o, _)| o <= off);
|
|
122
|
+
if idx == 0 {
|
|
123
|
+
Some(self.lines[0].1)
|
|
124
|
+
} else {
|
|
125
|
+
Some(self.lines[idx - 1].1)
|
|
59
126
|
}
|
|
60
127
|
}
|
|
61
128
|
|
|
@@ -79,6 +146,7 @@ impl Chunk {
|
|
|
79
146
|
}
|
|
80
147
|
let idx = self.names.len();
|
|
81
148
|
self.names.push(name);
|
|
149
|
+
self.inline_caches.0.push(AtomicU64::new(0)); // keep the IC table sized to `names`
|
|
82
150
|
idx as u16
|
|
83
151
|
}
|
|
84
152
|
|