@tishlang/tish-lsp 2.12.0 → 2.35.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.
Files changed (88) hide show
  1. package/Cargo.toml +3 -0
  2. package/bin/tish-lsp +0 -0
  3. package/crates/js_to_tish/src/transform/expr.rs +12 -2
  4. package/crates/tish/tests/fixtures/fs_parity_callbacks.tish +22 -0
  5. package/crates/tish/tests/fixtures/fs_parity_promises.tish +21 -0
  6. package/crates/tish/tests/fixtures/fs_parity_sync.tish +20 -0
  7. package/crates/tish/tests/fs_parity.rs +103 -0
  8. package/crates/tish/tests/integration_test.rs +80 -0
  9. package/crates/tish/tests/shortcircuit.rs +3 -3
  10. package/crates/tish_ast/src/ast.rs +84 -0
  11. package/crates/tish_builtins/Cargo.toml +1 -0
  12. package/crates/tish_builtins/src/array.rs +724 -72
  13. package/crates/tish_builtins/src/collections.rs +149 -40
  14. package/crates/tish_builtins/src/globals.rs +215 -25
  15. package/crates/tish_builtins/src/math.rs +65 -7
  16. package/crates/tish_builtins/src/number.rs +173 -0
  17. package/crates/tish_builtins/src/string.rs +263 -23
  18. package/crates/tish_bytecode/src/chunk.rs +7 -0
  19. package/crates/tish_bytecode/src/compiler.rs +611 -63
  20. package/crates/tish_bytecode/src/lib.rs +1 -1
  21. package/crates/tish_bytecode/src/opcode.rs +156 -2
  22. package/crates/tish_bytecode/src/serialize.rs +2 -0
  23. package/crates/tish_bytecode/tests/append_local_string_builder.rs +63 -0
  24. package/crates/tish_bytecode/tests/math_unary_intrinsic.rs +47 -0
  25. package/crates/tish_compile/src/codegen.rs +18219 -5283
  26. package/crates/tish_compile/src/infer.rs +1562 -30
  27. package/crates/tish_compile/src/lib.rs +29 -4
  28. package/crates/tish_compile/src/resolve.rs +151 -13
  29. package/crates/tish_compile/src/types.rs +224 -28
  30. package/crates/tish_compile/tests/dump_codegen.rs +21 -0
  31. package/crates/tish_compile/tests/perf_codegen_169_173.rs +8 -17
  32. package/crates/tish_compile/tests/perf_codegen_173_part3.rs +61 -0
  33. package/crates/tish_compile/tests/perf_codegen_174.rs +160 -0
  34. package/crates/tish_compile/tests/perf_codegen_175.rs +190 -0
  35. package/crates/tish_compile/tests/perf_codegen_176.rs +60 -0
  36. package/crates/tish_compile/tests/perf_codegen_177.rs +167 -0
  37. package/crates/tish_compile/tests/perf_codegen_178.rs +114 -0
  38. package/crates/tish_compile/tests/perf_codegen_178_rec.rs +124 -0
  39. package/crates/tish_compile/tests/perf_codegen_181.rs +36 -0
  40. package/crates/tish_compile/tests/perf_codegen_320.rs +211 -0
  41. package/crates/tish_compile/tests/perf_codegen_module_const_forof.rs +40 -0
  42. package/crates/tish_compile_js/src/codegen.rs +94 -3
  43. package/crates/tish_compile_js/src/tests_jsx.rs +27 -0
  44. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +115 -2
  45. package/crates/tish_core/Cargo.toml +4 -0
  46. package/crates/tish_core/src/json.rs +109 -6
  47. package/crates/tish_core/src/lib.rs +222 -2
  48. package/crates/tish_core/src/shape.rs +4 -2
  49. package/crates/tish_core/src/uri.rs +45 -0
  50. package/crates/tish_core/src/value.rs +571 -35
  51. package/crates/tish_core/src/vmref.rs +14 -0
  52. package/crates/tish_eval/Cargo.toml +2 -1
  53. package/crates/tish_eval/src/eval.rs +1328 -101
  54. package/crates/tish_eval/src/natives.rs +283 -18
  55. package/crates/tish_eval/src/regex.rs +47 -1
  56. package/crates/tish_eval/src/value.rs +76 -21
  57. package/crates/tish_ffi/src/lib.rs +11 -1
  58. package/crates/tish_ffi/tests/double_free.rs +35 -0
  59. package/crates/tish_fmt/src/lib.rs +94 -1
  60. package/crates/tish_lexer/src/lib.rs +76 -0
  61. package/crates/tish_lexer/src/token.rs +4 -0
  62. package/crates/tish_lint/src/lib.rs +126 -0
  63. package/crates/tish_lsp/Cargo.toml +1 -1
  64. package/crates/tish_lsp/README.md +2 -1
  65. package/crates/tish_lsp/src/main.rs +378 -28
  66. package/crates/tish_native/src/build.rs +41 -0
  67. package/crates/tish_opt/src/lib.rs +20 -1
  68. package/crates/tish_parser/Cargo.toml +4 -0
  69. package/crates/tish_parser/src/lib.rs +68 -0
  70. package/crates/tish_parser/src/parser.rs +479 -28
  71. package/crates/tish_resolve/src/lib.rs +75 -5
  72. package/crates/tish_runtime/Cargo.toml +10 -1
  73. package/crates/tish_runtime/src/fs_ext.rs +359 -0
  74. package/crates/tish_runtime/src/http.rs +28 -10
  75. package/crates/tish_runtime/src/http_fetch.rs +150 -1
  76. package/crates/tish_runtime/src/http_hyper.rs +41 -18
  77. package/crates/tish_runtime/src/http_prefork.rs +72 -11
  78. package/crates/tish_runtime/src/lib.rs +651 -51
  79. package/crates/tish_runtime/src/timers.rs +49 -2
  80. package/crates/tish_ui/src/jsx.rs +21 -3
  81. package/crates/tish_vm/src/jit.rs +2514 -117
  82. package/crates/tish_vm/src/vm.rs +1479 -224
  83. package/package.json +1 -1
  84. package/platform/darwin-arm64/tish-lsp +0 -0
  85. package/platform/darwin-x64/tish-lsp +0 -0
  86. package/platform/linux-arm64/tish-lsp +0 -0
  87. package/platform/linux-x64/tish-lsp +0 -0
  88. package/platform/win32-x64/tish-lsp.exe +0 -0
@@ -0,0 +1,190 @@
1
+ //! Generated-Rust assertions for #175 — native plain-array free fns.
2
+ //!
3
+ //! A top-level fn over `number[]`/`boolean[]` params (used only by index/length, no escape) whose
4
+ //! call sites pass pairwise-distinct array idents is de-virtualized to `fn name_nv(<f64..>,
5
+ //! <&/&mut Vec<T>..>) -> f64 | ()`; calls route there passing arrays by reference. A fn that calls a
6
+ //! boxed (non-native) closure, or whose array args can't be proven distinct/native, falls back to the
7
+ //! boxed closure (no `_nv`). Cross-backend soundness is covered by `tests/core/native_vec_params`.
8
+
9
+ use std::path::PathBuf;
10
+
11
+ use tishlang_compile::compile_project_full;
12
+
13
+ fn enable_typed_flags() {
14
+ }
15
+
16
+ fn compile_fixture_typed(rel: &str) -> String {
17
+ enable_typed_flags();
18
+ let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
19
+ let path = manifest.join("../..").join(rel).canonicalize().unwrap();
20
+ let (rust, _, _, _) = compile_project_full(&path, path.parent(), &[], true).unwrap();
21
+ rust
22
+ }
23
+
24
+ fn compile_fixture_embedded_lib(rel: &str) -> String {
25
+ compile_fixture_embedded_lib_with_features(rel, &[])
26
+ }
27
+
28
+ fn compile_fixture_embedded_lib_with_features(rel: &str, features: &[String]) -> String {
29
+ enable_typed_flags();
30
+ let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
31
+ let path = manifest.join("../..").join(rel).canonicalize().unwrap();
32
+ let (rust, _, _, _) = tishlang_compile::compile_project_full_emit(
33
+ &path,
34
+ path.parent(),
35
+ features,
36
+ true,
37
+ tishlang_compile::NativeEmitMode::EmbeddedLib,
38
+ None,
39
+ )
40
+ .unwrap();
41
+ rust
42
+ }
43
+
44
+ #[test]
45
+ fn native_vec_params_emit_ref_signatures_and_route() {
46
+ let rust = compile_fixture_typed("tests/core/native_vec_params.tish");
47
+ // Recursive `&mut boolean[]` fn.
48
+ assert!(
49
+ rust.contains("fn mark_nv(mut n: f64, mut row: f64, a: &mut Vec<bool>, b: &mut Vec<bool>)"),
50
+ "mark lowers to a native free fn over &mut Vec<bool> params:\n{}",
51
+ rust.lines().filter(|l| l.contains("mark_nv")).take(4).collect::<Vec<_>>().join("\n")
52
+ );
53
+ // Recursion reborrows the ref params through.
54
+ assert!(
55
+ rust.contains("mark_nv(n, (row + 1_f64), &mut *a, &mut *b)"),
56
+ "recursive call reborrows the &mut params:\n{}",
57
+ rust.lines().filter(|l| l.contains("mark_nv(")).take(4).collect::<Vec<_>>().join("\n")
58
+ );
59
+ // Read-only `&Vec<f64>` + write `&mut Vec<f64>`.
60
+ assert!(
61
+ rust.contains("fn scaleInto_nv(mut n: f64, src: &Vec<f64>, dst: &mut Vec<f64>)"),
62
+ "scaleInto distinguishes read (&) vs written (&mut) array params:\n{}",
63
+ rust.lines().filter(|l| l.contains("scaleInto_nv")).take(4).collect::<Vec<_>>().join("\n")
64
+ );
65
+ // Top-level call sites address-of native `Vec` locals.
66
+ assert!(
67
+ rust.contains("mark_nv(5_f64, 0_f64, &mut a, &mut b)"),
68
+ "the entry call passes the local Vecs by &mut:\n{}",
69
+ rust.lines().filter(|l| l.contains("mark_nv(5")).take(2).collect::<Vec<_>>().join("\n")
70
+ );
71
+ assert!(
72
+ rust.contains("scaleInto_nv(6_f64, &src, &mut dst)"),
73
+ "scaleInto call passes & / &mut:\n{}",
74
+ rust.lines().filter(|l| l.contains("scaleInto_nv(6")).take(2).collect::<Vec<_>>().join("\n")
75
+ );
76
+ // The local arrays became native Vecs (the escape into the native-vec fn is not a boxing escape).
77
+ assert!(
78
+ rust.contains("let mut a: Vec<bool>") && rust.contains("let mut src: Vec<f64>"),
79
+ "caller arrays are unboxed native Vecs"
80
+ );
81
+ }
82
+
83
+ #[test]
84
+ fn queens_place_devirtualizes_to_native_vec_fn() {
85
+ let rust = compile_fixture_typed("tests/perf/queens.tish");
86
+ assert!(
87
+ rust.contains("fn place_nv(mut n: f64, mut row: f64, cols: &mut Vec<bool>, diag1: &mut Vec<bool>, diag2: &mut Vec<bool>) -> f64"),
88
+ "queens' place lowers to a native free fn over three &mut Vec<bool> params:\n{}",
89
+ rust.lines().filter(|l| l.contains("place_nv")).take(3).collect::<Vec<_>>().join("\n")
90
+ );
91
+ assert!(
92
+ rust.contains("place_nv(5_f64, 0_f64") || rust.contains("place_nv(match") || rust.contains("place_nv("),
93
+ "the place call routes to place_nv"
94
+ );
95
+ }
96
+
97
+ #[test]
98
+ fn spectral_norm_devirtualizes_with_inlined_evala() {
99
+ // `multiplyAv`/`multiplyAtv` (over `&Vec<f64>` + `&mut Vec<f64>`) call `evalA`, a numeric leaf fn.
100
+ // `evalA` inlines at the native-f64 call site (no dispatch, no boxed reference), so the native-vec
101
+ // group de-virtualizes. The boxed `evalA` closure is left intact for any non-f64 callers.
102
+ let rust = compile_fixture_typed("tests/perf/spectral_norm.tish");
103
+ assert!(
104
+ rust.contains("fn multiplyAv_nv(") && (rust.contains("v: &Vec<f64>") || rust.contains("av: &mut Vec<f64>")),
105
+ "multiplyAv lowers to a native-vec fn:\n{}",
106
+ rust.lines().filter(|l| l.contains("multiplyAv_nv")).take(3).collect::<Vec<_>>().join("\n")
107
+ );
108
+ assert!(
109
+ rust.contains("fn multiplyAtAv_nv("),
110
+ "multiplyAtAv should forward to native-vec callees:\n{}",
111
+ rust.lines().filter(|l| l.contains("multiplyAtAv_nv")).take(3).collect::<Vec<_>>().join("\n")
112
+ );
113
+ // evalA is inlined: the native-vec body has the substituted body (a `_inl…` temp) and does NOT
114
+ // call evalA (no `value_call`/`evalA(` inside multiplyAv_nv).
115
+ let mav = rust
116
+ .lines()
117
+ .skip_while(|l| !l.contains("fn multiplyAv_nv("))
118
+ .take_while(|l| !l.trim_start().starts_with("fn ") || l.contains("multiplyAv_nv"))
119
+ .collect::<Vec<_>>()
120
+ .join("\n");
121
+ assert!(
122
+ mav.contains("_inl") && !mav.contains("value_call"),
123
+ "evalA must be inlined into multiplyAv_nv (substituted temps, no value_call):\n{}",
124
+ mav
125
+ );
126
+ if rust.contains("fn spectralNorm_nv(") {
127
+ let sn = rust.split("fn spectralNorm_nv(").nth(1).unwrap();
128
+ let sn = sn.split("fn run()").next().unwrap_or(sn);
129
+ assert!(
130
+ sn.contains("let mut u: Vec<f64>"),
131
+ "spectralNorm_nv should keep u/v/w as native Vec<f64> locals"
132
+ );
133
+ assert!(
134
+ sn.contains("multiplyAtAv_nv(") && !sn.contains("multiplyAtAv_native("),
135
+ "spectralNorm_nv should call multiplyAtAv_nv, not the boxed native shim"
136
+ );
137
+ }
138
+ }
139
+
140
+ #[test]
141
+ fn spectral_norm_embedded_lib_devirtualizes_to_native_vec_fns() {
142
+ // EmbeddedLib mode fully de-virtualizes the `multiply*` group to native-vec (`_nv`) fns over
143
+ // `Vec<f64>` params (#350-352 native struct-array codegen). Once the whole group de-virtualizes,
144
+ // the old boxed `*_native` shims this test used to assert are correctly no longer emitted —
145
+ // matching the typed-mode `spectral_norm_devirtualizes_with_inlined_evala` check above (which
146
+ // asserts `multiplyAtAv` routes to `_nv`, not the boxed `_native` shim). Cross-backend soundness
147
+ // of the de-virtualized code is covered by the gauntlet + `tests/core/native_vec_params`.
148
+ let rust = compile_fixture_embedded_lib("tests/perf/spectral_norm.tish");
149
+ assert!(
150
+ rust.contains("fn multiplyAv_nv(")
151
+ && rust.contains("fn multiplyAtv_nv(")
152
+ && rust.contains("fn multiplyAtAv_nv("),
153
+ "multiply* should de-virtualize to native-vec fns:\n{}",
154
+ rust.lines().filter(|l| l.contains("_nv(")).take(6).collect::<Vec<_>>().join("\n")
155
+ );
156
+ assert!(
157
+ rust.contains("v: &Vec<f64>") && rust.contains("av: &mut Vec<f64>"),
158
+ "native-vec fns take Vec<f64> params by &/&mut ref"
159
+ );
160
+ assert!(
161
+ !rust.contains("fn multiplyAv_native(")
162
+ && !rust.contains("fn multiplyAtv_native(")
163
+ && !rust.contains("fn multiplyAtAv_native("),
164
+ "the boxed `*_native` shims must be elided once the group fully de-virtualizes:\n{}",
165
+ rust.lines().filter(|l| l.contains("_native(")).take(6).collect::<Vec<_>>().join("\n")
166
+ );
167
+ }
168
+
169
+ #[test]
170
+ fn spectral_norm_embedded_lib_with_runtime_features_devirtualizes() {
171
+ // The same de-virtualization holds with the runtime features enabled — they swap the embedded
172
+ // runtime in, but don't change the native-vec lowering of the user fns (still no boxed shims).
173
+ let features: Vec<String> = [
174
+ "http", "timers", "fs", "process", "regex", "ws", "tty",
175
+ ]
176
+ .into_iter()
177
+ .map(String::from)
178
+ .collect();
179
+ let rust =
180
+ compile_fixture_embedded_lib_with_features("tests/perf/spectral_norm.tish", &features);
181
+ assert!(
182
+ rust.contains("fn multiplyAv_nv(") && rust.contains("fn multiplyAtAv_nv("),
183
+ "runtime-feature build still de-virtualizes multiply* to native-vec fns:\n{}",
184
+ rust.lines().filter(|l| l.contains("_nv(")).take(6).collect::<Vec<_>>().join("\n")
185
+ );
186
+ assert!(
187
+ !rust.contains("fn multiplyAv_native(") && !rust.contains("fn multiplyAtAv_native("),
188
+ "no boxed `*_native` shims under runtime features either"
189
+ );
190
+ }
@@ -0,0 +1,60 @@
1
+ //! Generated-Rust assertions for #176 — native numeric globals (`thread_local Cell<f64>`).
2
+
3
+ use std::path::PathBuf;
4
+
5
+ use tishlang_compile::compile_project_full;
6
+
7
+ fn enable_typed_flags() {
8
+ }
9
+
10
+ fn compile_fixture_typed(rel: &str) -> String {
11
+ enable_typed_flags();
12
+ let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
13
+ let path = manifest.join("../..").join(rel).canonicalize().unwrap();
14
+ let (rust, _, _, _) = compile_project_full(&path, path.parent(), &[], true).unwrap();
15
+ rust
16
+ }
17
+
18
+ #[test]
19
+ fn fasta_seed_lowers_to_thread_local_and_genrandom_native() {
20
+ let rust = compile_fixture_typed("tests/perf/fasta.tish");
21
+ assert!(
22
+ rust.contains("thread_local!"),
23
+ "fasta emits thread_local globals:\n{}",
24
+ rust.lines().filter(|l| l.contains("thread_local") || l.contains("G_SEED")).take(6).collect::<Vec<_>>().join("\n")
25
+ );
26
+ assert!(
27
+ rust.contains("static G_SEED: std::cell::Cell<f64>"),
28
+ "seed lowers to G_SEED Cell:\n{}",
29
+ rust.lines().filter(|l| l.contains("G_SEED")).take(4).collect::<Vec<_>>().join("\n")
30
+ );
31
+ assert!(
32
+ rust.contains("fn genRandom_native("),
33
+ "genRandom becomes M5-eligible after native global seed:\n{}",
34
+ rust.lines().filter(|l| l.contains("genRandom")).take(6).collect::<Vec<_>>().join("\n")
35
+ );
36
+ assert!(
37
+ rust.contains("G_SEED.with(|c| c.set("),
38
+ "genRandom_native mutates G_SEED:\n{}",
39
+ rust.lines().filter(|l| l.contains("G_SEED.with")).take(6).collect::<Vec<_>>().join("\n")
40
+ );
41
+ }
42
+
43
+ #[test]
44
+ fn native_numeric_global_fixture_interleaved_state() {
45
+ let rust = compile_fixture_typed("tests/core/native_numeric_global.tish");
46
+ assert!(
47
+ rust.contains("static G_COUNTER: std::cell::Cell<f64>"),
48
+ "counter global lowers:\n{}",
49
+ rust.lines().filter(|l| l.contains("G_COUNTER")).take(4).collect::<Vec<_>>().join("\n")
50
+ );
51
+ assert!(
52
+ rust.contains("G_COUNTER.with(|c| c.get())") && rust.contains("G_COUNTER.with(|c| c.set("),
53
+ "boxed + native paths share G_COUNTER:\n{}",
54
+ rust.lines()
55
+ .filter(|l| l.contains("G_COUNTER::with"))
56
+ .take(8)
57
+ .collect::<Vec<_>>()
58
+ .join("\n")
59
+ );
60
+ }
@@ -0,0 +1,167 @@
1
+ //! Generated-Rust assertions for #177 follow-on — Vec<f64> returns, mandel native, fasta cum path.
2
+
3
+ use std::path::PathBuf;
4
+
5
+ use tishlang_compile::compile_project_full;
6
+
7
+ fn enable_typed_flags() {
8
+ }
9
+
10
+ fn compile_fixture_typed(rel: &str) -> String {
11
+ enable_typed_flags();
12
+ let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
13
+ let path = manifest.join("../..").join(rel).canonicalize().unwrap();
14
+ let (rust, _, _, _) = compile_project_full(&path, path.parent(), &[], true).unwrap();
15
+ rust
16
+ }
17
+
18
+ #[test]
19
+ fn fasta_lowers_cumulative_and_native_arrays() {
20
+ let rust = compile_fixture_typed("tests/perf/fasta.tish");
21
+ assert!(rust.contains("fn cumulative_nv("), "cumulative_nv missing");
22
+ assert!(
23
+ rust.contains("const G_CODES") || rust.contains("let mut codes: Vec<f64>"),
24
+ "codes not lowered to module const or native vec"
25
+ );
26
+ assert!(
27
+ rust.contains("const G_PROBS_CUM"),
28
+ "precomputed cumulative array missing"
29
+ );
30
+ if rust.contains("fn fastaRandom_native(") {
31
+ assert!(
32
+ rust.contains("fastaRandom_native("),
33
+ "fastaRandom_native declared but not called"
34
+ );
35
+ assert!(
36
+ rust.contains("G_CODES["),
37
+ "fastaRandom_native should index G_CODES directly"
38
+ );
39
+ assert!(
40
+ rust.contains("_lcg_seed"),
41
+ "fastaRandom_native should hoist LCG seed to a local"
42
+ );
43
+ assert!(
44
+ rust.contains("_lcg_seed: i64") && rust.contains("3877i64"),
45
+ "fastaRandom_native should use integer LCG arithmetic"
46
+ );
47
+ }
48
+ }
49
+
50
+ #[test]
51
+ fn mandelbrot_lowers_mandel_native() {
52
+ let rust = compile_fixture_typed("tests/perf/mandelbrot.tish");
53
+ // Prefer top-level `mandel_native` when M5-eligible; the hot loop is already native f64
54
+ // inside the closure even when this is absent.
55
+ if rust.contains("fn mandel_native(") {
56
+ assert!(
57
+ rust.contains("mandel_native("),
58
+ "mandel_native declared but not called"
59
+ );
60
+ assert!(
61
+ rust.contains("for _usize_iter") && rust.contains("0..100"),
62
+ "mandel_native should use usize bounded escape loop for maxIter=100"
63
+ );
64
+ assert!(
65
+ rust.contains("_stayed_") && rust.contains("if _stayed_"),
66
+ "mandel_native should fuse iter===maxIter into stayed flag"
67
+ );
68
+ assert!(
69
+ rust.contains("count += 1_f64"),
70
+ "mandel_native should increment count with +="
71
+ );
72
+ if let Some(native) = rust.split("fn mandel_native(").nth(1) {
73
+ let native = native.split("fn run()").next().unwrap_or(native);
74
+ assert!(
75
+ !native.contains("iter = (iter + 1_f64)"),
76
+ "mandel_native should skip iter increment in usize escape loop"
77
+ );
78
+ assert!(
79
+ native.contains("0.0025_f64") && !native.contains("let mut py:"),
80
+ "mandel_native should fuse py/h and px/w into reciprocal coord init"
81
+ );
82
+ assert!(
83
+ !native.contains("let mut xt:"),
84
+ "mandel_native should fuse x2/y2/xy iteration temps"
85
+ );
86
+ assert!(
87
+ native.contains("let x2 =") && native.contains("let xy ="),
88
+ "mandel_native should emit fused iteration temps"
89
+ );
90
+ }
91
+ }
92
+ }
93
+
94
+ #[test]
95
+ fn fannkuch_nv_uses_direct_flip_indexing() {
96
+ let rust = compile_fixture_typed("tests/perf/fannkuch.tish");
97
+ if rust.contains("fn fannkuch_nv(") {
98
+ let nv = rust.split("fn fannkuch_nv(").nth(1).unwrap();
99
+ let nv = nv.split("fn run()").next().unwrap_or(nv);
100
+ assert!(
101
+ nv.contains("let ku =") && nv.contains("for _usize_flip_"),
102
+ "fannkuch_nv flip loop should fuse to ku half-loop"
103
+ );
104
+ assert!(
105
+ !nv.contains("let mut k2:") && !nv.contains("let mut temp:"),
106
+ "fannkuch_nv fused flip should not emit k2/temp"
107
+ );
108
+ assert!(
109
+ !nv.contains("perm.get(((k - i))"),
110
+ "fannkuch_nv should not use perm.get for k-i sub-index"
111
+ );
112
+ assert!(
113
+ nv.contains("copy_within(1..((r as usize) + 1), 0)")
114
+ || nv.contains("copy_within(1..((r) as usize + 1), 0)")
115
+ || nv.contains("copy_within(1..(_ru + 1), 0)"),
116
+ "fannkuch_nv rotation should shift perm1 via copy_within"
117
+ );
118
+ assert!(
119
+ nv.contains("perm1[(r) as usize] = perm0")
120
+ || nv.contains("perm1[r as usize] = perm0")
121
+ || nv.contains("perm1[(r as usize)] = perm0")
122
+ || nv.contains("perm1[(r) as usize] = (perm0) as i32"),
123
+ "fannkuch_nv rotation should assign perm1[r] without resize"
124
+ );
125
+ assert!(
126
+ nv.contains("count[(r) as usize] = (count[(r) as usize] - 1)")
127
+ || nv.contains("count[r as usize] = (count[r as usize] - 1)")
128
+ || nv.contains("count[(r) as usize] = (count[(r) as usize] - 1i32)")
129
+ || nv.contains("count[r as usize] = (count[r as usize] - 1i32)")
130
+ || nv.contains("count[(r) as usize] = (count[(r) as usize] - 1_f64)"),
131
+ "fannkuch_nv should decrement count[r] via direct indexing"
132
+ );
133
+ assert!(
134
+ nv.contains("perm = std::iter::repeat(0i32).take(10)")
135
+ || nv.contains("perm = std::iter::repeat(0_f64).take(10)")
136
+ || nv.contains("perm.extend(std::iter::repeat(0i32).take(10))"),
137
+ "fannkuch_nv should bulk-init perm array"
138
+ );
139
+ assert!(
140
+ nv.contains("perm1 = (0..10).collect()")
141
+ || nv.contains("perm1 = (0..10).map(|j| j as f64).collect()"),
142
+ "fannkuch_nv should iota-init perm1"
143
+ );
144
+ assert!(
145
+ nv.contains(".copy_from_slice(&perm1)"),
146
+ "fannkuch_nv should copy perm1 into perm via copy_from_slice"
147
+ );
148
+ assert!(
149
+ nv.contains("while r != 1_f64") && nv.contains("count[ri - 1] = r"),
150
+ "fannkuch_nv should fill count via while r!=1 loop (not bulk 1..n init)"
151
+ );
152
+ assert!(
153
+ !nv.contains("_usize_shift_"),
154
+ "fannkuch_nv rotation should use copy_within, not per-element shift loop"
155
+ );
156
+ assert!(
157
+ (nv.contains("if ((permCount as i64) & 1) == 0")
158
+ || (nv.contains("to_int_unchecked") && nv.contains("& 1"))),
159
+ "fannkuch_nv checksum parity should use fast int parity"
160
+ );
161
+ assert!(
162
+ nv.contains("count[ri - 1] = r as i32")
163
+ || nv.contains("count[ri - 1] = r"),
164
+ "fannkuch_nv count[r-1] init should use direct indexing in while loop"
165
+ );
166
+ }
167
+ }
@@ -0,0 +1,114 @@
1
+ //! #178 Bun-style fusion targets — AST shape checks + codegen assertions when fusion lands.
2
+
3
+ use std::fs;
4
+ use std::path::PathBuf;
5
+
6
+ use tishlang_ast::{BinOp, Expr, Statement};
7
+ use tishlang_compile::compile_project_full;
8
+ use tishlang_opt::optimize;
9
+ use tishlang_parser::parse;
10
+
11
+ fn enable_typed_flags() {
12
+ }
13
+
14
+ fn compile_fixture_typed(rel: &str) -> String {
15
+ enable_typed_flags();
16
+ let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
17
+ let path = manifest.join("../..").join(rel).canonicalize().unwrap();
18
+ let (rust, _, _, _) = compile_project_full(&path, path.parent(), &[], true).unwrap();
19
+ rust
20
+ }
21
+
22
+ fn stmt_slice_unwrapped(body: &Statement) -> Vec<&Statement> {
23
+ let mut cur = body;
24
+ loop {
25
+ match cur {
26
+ Statement::Block { statements, .. } | Statement::Multi { statements, .. } => {
27
+ if statements.len() == 1 {
28
+ cur = &statements[0];
29
+ } else {
30
+ return statements.iter().collect();
31
+ }
32
+ }
33
+ other => return vec![other],
34
+ }
35
+ }
36
+ }
37
+
38
+ fn find_flip_for_body(s: &Statement) -> Option<&Statement> {
39
+ match s {
40
+ Statement::For { cond, body, .. } => {
41
+ if let Some(cond) = cond.as_ref() {
42
+ if let Expr::Binary {
43
+ op: BinOp::Lt,
44
+ right,
45
+ ..
46
+ } = cond
47
+ {
48
+ if matches!(right.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "k2") {
49
+ return Some(body);
50
+ }
51
+ }
52
+ }
53
+ None
54
+ }
55
+ Statement::Block { statements, .. } | Statement::Multi { statements, .. } => {
56
+ for st in statements {
57
+ if let Some(b) = find_flip_for_body(st) {
58
+ return Some(b);
59
+ }
60
+ }
61
+ None
62
+ }
63
+ Statement::While { body, .. } => find_flip_for_body(body),
64
+ Statement::If { then_branch, .. } => find_flip_for_body(then_branch),
65
+ _ => None,
66
+ }
67
+ }
68
+
69
+ #[test]
70
+ fn fannkuch_flip_for_body_is_three_index_assigns() {
71
+ let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
72
+ let path = manifest
73
+ .join("../../tests/perf/fannkuch.tish")
74
+ .canonicalize()
75
+ .unwrap();
76
+ let program = optimize(&parse(&fs::read_to_string(&path).unwrap()).unwrap());
77
+ let body = program
78
+ .statements
79
+ .iter()
80
+ .find_map(|s| {
81
+ if let Statement::FunDecl { name, body, .. } = s {
82
+ if name.as_ref() == "fannkuch" {
83
+ return find_flip_for_body(body);
84
+ }
85
+ }
86
+ None
87
+ })
88
+ .expect("flip for body");
89
+ let stmts = stmt_slice_unwrapped(body);
90
+ assert_eq!(stmts.len(), 3);
91
+ for st in &stmts[1..] {
92
+ let Statement::ExprStmt { expr, .. } = st else {
93
+ panic!("expected expr stmt");
94
+ };
95
+ assert!(matches!(expr, Expr::IndexAssign { .. }));
96
+ }
97
+ }
98
+
99
+ #[test]
100
+ fn fannkuch_nv_uses_usize_sub_index_in_flip() {
101
+ let rust = compile_fixture_typed("tests/perf/fannkuch.tish");
102
+ if rust.contains("fn fannkuch_nv(") {
103
+ let nv = rust.split("fn fannkuch_nv(").nth(1).unwrap();
104
+ let nv = nv.split("fn run()").next().unwrap_or(nv);
105
+ assert!(
106
+ nv.contains("let ku =") && nv.contains("for _usize_flip_"),
107
+ "flip loop should fuse to ku half-loop with usize swaps"
108
+ );
109
+ assert!(
110
+ !nv.contains("let mut k2:") && !nv.contains("let mut temp:"),
111
+ "fused flip loop should not emit k2/temp temps"
112
+ );
113
+ }
114
+ }
@@ -0,0 +1,124 @@
1
+ //! #178 — recursive-struct native lowering.
2
+ //!
3
+ //! A developer-shaped recursive binary tree (arbitrary identifiers, NOT the fixture names
4
+ //! bottomUpTree/itemCheck/binaryTrees) must lower to a native arena: an `i32`-indexed struct,
5
+ //! native `build`/`check` free fns threading `&mut Vec<Node>` / `&Vec<Node>`, and a top-level
6
+ //! routed call — with NO `object_from_pairs` / `get_prop` / `value_call` on the hot path. This is
7
+ //! the real, name-independent fix that makes the boxed `binary_trees` path fast under any names.
8
+ //! See docs/recursive-struct-native.md.
9
+
10
+ use std::path::PathBuf;
11
+
12
+ use tishlang_compile::compile_project_full;
13
+
14
+ fn enable_flags() {
15
+ }
16
+
17
+ const SRC: &str = r#"
18
+ function buildNode(d) {
19
+ if (d > 0) { return { left: buildNode(d - 1), right: buildNode(d - 1) } }
20
+ return { left: null, right: null }
21
+ }
22
+ function sumNode(node) {
23
+ if (node.left === null) { return 1 }
24
+ return 1 + sumNode(node.left) + sumNode(node.right)
25
+ }
26
+ let t0 = Date.now()
27
+ let check = sumNode(buildNode(10))
28
+ console.log("GAUNTLET rec " + (Date.now() - t0) + " " + check)
29
+ "#;
30
+
31
+ #[test]
32
+ fn recursive_tree_lowers_to_native_arena() {
33
+ enable_flags();
34
+ let dir = PathBuf::from(env!("CARGO_TARGET_TMPDIR"));
35
+ let path = dir.join("rec_tree_dev_178.tish");
36
+ std::fs::write(&path, SRC).unwrap();
37
+
38
+ let (rust, _, _, _) = compile_project_full(&path, path.parent(), &[], true).unwrap();
39
+
40
+ // The synthesized arena node struct (i32 child indices, not Option<Box> / not boxed Value).
41
+ assert!(
42
+ rust.contains("struct TishStruct_TishRecNode"),
43
+ "expected a synthesized recursive node struct"
44
+ );
45
+ // Native builder + consumer free fns over the arena.
46
+ assert!(
47
+ rust.contains("fn buildNode_rec(") && rust.contains("__rec_arena: &mut Vec<"),
48
+ "expected a native arena builder fn"
49
+ );
50
+ assert!(
51
+ rust.contains("fn sumNode_rec(__rec_idx: i32, __rec_arena: &Vec<"),
52
+ "expected a native arena consumer fn"
53
+ );
54
+ // Top-level call routed through the arena, not the boxed closure.
55
+ assert!(
56
+ rust.contains("let __rec_root = buildNode_rec("),
57
+ "expected the top-level builder call to be routed to the native arena fn"
58
+ );
59
+ assert!(
60
+ rust.contains("sumNode_rec(__rec_root, &__rec_arena)"),
61
+ "expected the consumer to be invoked on the arena root index"
62
+ );
63
+ // No per-node boxed allocation on the recursive build path.
64
+ assert!(
65
+ rust.contains("__rec_arena.push(TishStruct_TishRecNode"),
66
+ "expected nodes to be bump-allocated into the arena Vec"
67
+ );
68
+ }
69
+
70
+ // The FULL binary_trees shape (with a loop-bearing orchestrator) — developer identifiers.
71
+ const SRC_ORCH: &str = r#"
72
+ function makeTree(d) {
73
+ if (d > 0) { return { left: makeTree(d - 1), right: makeTree(d - 1) } }
74
+ return { left: null, right: null }
75
+ }
76
+ function countTree(node) {
77
+ if (node.left === null) { return 1 }
78
+ return 1 + countTree(node.left) + countTree(node.right)
79
+ }
80
+ function run(maxDepth) {
81
+ let minDepth = 4
82
+ let total = countTree(makeTree(maxDepth + 1))
83
+ let longLived = makeTree(maxDepth)
84
+ let depth = minDepth
85
+ while (depth <= maxDepth) {
86
+ let iterations = 1 << (maxDepth - depth + minDepth)
87
+ let sum = 0
88
+ for (let i = 0; i < iterations; i++) { sum = sum + countTree(makeTree(depth)) }
89
+ total = total + sum
90
+ depth = depth + 2
91
+ }
92
+ total = total + countTree(longLived)
93
+ return total
94
+ }
95
+ let t0 = Date.now()
96
+ let check = run(8)
97
+ console.log("GAUNTLET t " + (Date.now() - t0) + " " + check)
98
+ "#;
99
+
100
+ #[test]
101
+ fn recursive_tree_orchestrator_lowers_to_native_arena() {
102
+ enable_flags();
103
+ let dir = PathBuf::from(env!("CARGO_TARGET_TMPDIR"));
104
+ let path = dir.join("rec_orch_dev_178.tish");
105
+ std::fs::write(&path, SRC_ORCH).unwrap();
106
+
107
+ let (rust, _, _, _) = compile_project_full(&path, path.parent(), &[], true).unwrap();
108
+
109
+ // The orchestrator becomes a native fn threading the arena and returning f64.
110
+ assert!(
111
+ rust.contains("fn run_rec(") && rust.contains("__rec_arena: &mut Vec<"),
112
+ "expected the orchestrator to lower to a native arena fn"
113
+ );
114
+ // It calls builders/consumers natively (no value_call on the hot path).
115
+ assert!(
116
+ rust.contains("makeTree_rec(") && rust.contains("countTree_rec("),
117
+ "expected the orchestrator to call builder/consumer fns natively"
118
+ );
119
+ // Top-level call routed through a fresh arena.
120
+ assert!(
121
+ rust.contains("let mut __rec_arena") && rust.contains("run_rec("),
122
+ "expected the top-level orchestrator call to set up and thread the arena"
123
+ );
124
+ }