@tishlang/tish-lsp 2.39.0 → 2.43.8
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 +4 -1
- package/bin/tish-lsp +0 -0
- package/crates/tish/src/cli_help.rs +28 -0
- package/crates/tish/src/main.rs +41 -5
- package/crates/tish/tests/scope_merge.rs +79 -0
- package/crates/tish_build_utils/src/lib.rs +27 -1
- package/crates/tish_builtins/Cargo.toml +14 -6
- package/crates/tish_builtins/src/array.rs +130 -54
- package/crates/tish_builtins/src/collections.rs +9 -5
- package/crates/tish_builtins/src/construct.rs +7 -1
- package/crates/tish_builtins/src/date.rs +15 -1
- package/crates/tish_builtins/src/globals.rs +38 -3
- package/crates/tish_builtins/src/helpers.rs +7 -3
- package/crates/tish_builtins/src/iterator.rs +5 -1
- package/crates/tish_builtins/src/lib.rs +3 -0
- package/crates/tish_builtins/src/math.rs +14 -0
- package/crates/tish_builtins/src/number.rs +72 -6
- package/crates/tish_builtins/src/object.rs +6 -1
- package/crates/tish_builtins/src/string.rs +13 -2
- package/crates/tish_builtins/src/symbol.rs +17 -17
- package/crates/tish_builtins/src/typedarrays.rs +9 -2
- package/crates/tish_bytecode/src/compiler.rs +76 -2
- package/crates/tish_bytecode/src/lib.rs +1 -1
- package/crates/tish_bytecode/src/opcode.rs +77 -2
- package/crates/tish_compile/src/codegen.rs +806 -46
- package/crates/tish_compile/src/infer.rs +37 -41
- package/crates/tish_compile/src/lib.rs +13 -1
- package/crates/tish_compile/src/platform_resolve.rs +361 -0
- package/crates/tish_compile/src/resolve.rs +148 -10
- package/crates/tish_compile/src/schemes.rs +278 -0
- package/crates/tish_compile/src/types.rs +141 -2
- package/crates/tish_compile/tests/perf_codegen_320.rs +2 -2
- package/crates/tish_compile/tests/perf_codegen_module_const_forof.rs +3 -1
- package/crates/tish_compile/tests/platform_resolve_cli.rs +164 -0
- package/crates/tish_compile/tests/regr_gba_numerics_gated.rs +58 -0
- package/crates/tish_compiler_wasm/Cargo.toml +1 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +56 -6
- package/crates/tish_core/Cargo.toml +39 -4
- package/crates/tish_core/src/compat.rs +506 -0
- package/crates/tish_core/src/console_style.rs +19 -1
- package/crates/tish_core/src/json.rs +34 -10
- package/crates/tish_core/src/lib.rs +121 -25
- package/crates/tish_core/src/macros.rs +1 -1
- package/crates/tish_core/src/shape.rs +4 -2
- package/crates/tish_core/src/uri.rs +8 -1
- package/crates/tish_core/src/value.rs +182 -54
- package/crates/tish_core/src/vmref.rs +17 -5
- package/crates/tish_eval/Cargo.toml +2 -0
- package/crates/tish_eval/src/eval.rs +90 -3
- package/crates/tish_eval/src/value.rs +8 -0
- package/crates/tish_eval/src/value_convert.rs +316 -21
- package/crates/tish_lsp/Cargo.toml +3 -1
- package/crates/tish_lsp/src/import_goto.rs +41 -14
- package/crates/tish_lsp/src/main.rs +18 -0
- package/crates/tish_native/src/build.rs +246 -0
- package/crates/tish_native/src/config.rs +20 -0
- package/crates/tish_native/src/lib.rs +16 -6
- package/crates/tish_parser/src/lib.rs +28 -0
- package/crates/tish_parser/src/parser.rs +25 -4
- package/crates/tish_runtime/src/lib.rs +54 -9
- package/crates/tish_runtime_gba/Cargo.lock +837 -0
- package/crates/tish_runtime_gba/Cargo.toml +16 -0
- package/crates/tish_runtime_gba/src/gba.rs +211 -0
- package/crates/tish_runtime_gba/src/lib.rs +620 -0
- package/crates/tish_vm/src/jit.rs +1446 -164
- package/crates/tish_vm/src/vm.rs +535 -173
- package/justfile +9 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-lsp +0 -0
- package/platform/darwin-x64/tish-lsp +0 -0
- package/platform/linux-arm64/tish-lsp +0 -0
- package/platform/linux-x64/tish-lsp +0 -0
- package/platform/win32-x64/tish-lsp.exe +0 -0
|
@@ -589,6 +589,12 @@ pub fn compile_project_full_emit(
|
|
|
589
589
|
> {
|
|
590
590
|
use crate::resolve;
|
|
591
591
|
let root = project_root.unwrap_or_else(|| entry_path.parent().unwrap_or(Path::new(".")));
|
|
592
|
+
// Install the import-scheme registry (built-ins + any `tish.schemes` from package.json) for
|
|
593
|
+
// this build, so resolution and codegen recognize `asset:` and any project-declared scheme.
|
|
594
|
+
crate::schemes::set_active(crate::schemes::SchemeRegistry::from_project(root));
|
|
595
|
+
// (The narrow-int/`fixed`/`i32` numeric profile is set from `emit_mode` in
|
|
596
|
+
// `compile_with_native_modules_emit`, the funnel every entry point reaches — not here — so it
|
|
597
|
+
// can't leak across compiles on a shared thread. Resolution/inference don't lower annotations.)
|
|
592
598
|
let modules = resolve::resolve_project(entry_path, project_root).map_err(|e| CompileError {
|
|
593
599
|
message: e,
|
|
594
600
|
span: None,
|
|
@@ -660,6 +666,11 @@ pub fn compile_with_native_modules(
|
|
|
660
666
|
native_init: &std::collections::HashMap<String, crate::resolve::NativeModuleInit>,
|
|
661
667
|
optimize: bool,
|
|
662
668
|
) -> Result<String, CompileError> {
|
|
669
|
+
// This non-project entry (reached by `compile`/`compile_with_features`, never the GBA project
|
|
670
|
+
// path) compiles an already-resolved program, so no scheme registry was installed for it. Reset
|
|
671
|
+
// to the empty built-in set, in case a prior GBA compile on this thread left one active — a
|
|
672
|
+
// normal program has no scheme imports, but this keeps per-compile thread-local state hermetic.
|
|
673
|
+
crate::schemes::set_active(crate::schemes::SchemeRegistry::builtin());
|
|
663
674
|
compile_with_native_modules_emit(
|
|
664
675
|
program,
|
|
665
676
|
project_root,
|
|
@@ -708,6 +719,24 @@ pub fn compile_with_native_modules_emit(
|
|
|
708
719
|
optimize: bool,
|
|
709
720
|
emit_mode: crate::NativeEmitMode,
|
|
710
721
|
) -> Result<String, CompileError> {
|
|
722
|
+
// Per-compile thread-local state — the narrow-int/`fixed`/`i32` numeric profile and the import-
|
|
723
|
+
// scheme registry — must not outlive THIS compile. Both persist on the thread, so a later compile
|
|
724
|
+
// on the same thread (LSP, `cargo test` workers share threads) could inherit a prior GBA compile's
|
|
725
|
+
// state and lower those annotations natively off-GBA / see stale schemes — diverging from the
|
|
726
|
+
// interpreter. This funnel is the one point every entry reaches, so reset both to their defaults
|
|
727
|
+
// on the way OUT, even on panic (mirrors tish_resolve's guard). Then set the numeric profile for
|
|
728
|
+
// THIS compile from the always-correct `emit_mode`. The scheme registry is installed before
|
|
729
|
+
// resolution by `compile_project_full_emit` (it must exist during resolve); the guard only clears
|
|
730
|
+
// it afterward so it can't leak forward.
|
|
731
|
+
struct StateGuard;
|
|
732
|
+
impl Drop for StateGuard {
|
|
733
|
+
fn drop(&mut self) {
|
|
734
|
+
crate::types::set_gba_numerics(false);
|
|
735
|
+
crate::schemes::set_active(crate::schemes::SchemeRegistry::builtin());
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
let _state_guard = StateGuard;
|
|
739
|
+
crate::types::set_gba_numerics(emit_mode == crate::NativeEmitMode::Gba);
|
|
711
740
|
let program = if optimize {
|
|
712
741
|
tishlang_opt::optimize(program)
|
|
713
742
|
} else {
|
|
@@ -742,13 +771,77 @@ pub fn compile_with_native_modules_emit(
|
|
|
742
771
|
g.has_native_ui_host = native_modules.iter().any(|m| {
|
|
743
772
|
m.package_name == "tish-macos"
|
|
744
773
|
|| m.package_name == "tish-ios"
|
|
774
|
+
|| m.crate_name == "tish_macos"
|
|
745
775
|
|| m.crate_name == "tishlang_macos"
|
|
776
|
+
|| m.crate_name == "tish_ios"
|
|
746
777
|
|| m.crate_name == "tishlang_ios"
|
|
747
778
|
});
|
|
748
779
|
g.emit_program(&program)?;
|
|
749
780
|
Ok(g.output)
|
|
750
781
|
}
|
|
751
782
|
|
|
783
|
+
/// Rewrite the `std::` paths that survive in `Gba`-mode generated code into their
|
|
784
|
+
/// `core::` equivalents (the emitted crate is `#![no_std]`). Applied once to the
|
|
785
|
+
/// whole output. Each pattern is a specific fully-qualified Rust path — none occur
|
|
786
|
+
/// inside escaped user string literals — so the textual pass is safe. Callers add
|
|
787
|
+
/// to this table as the thumbv4t corpus build surfaces new sites.
|
|
788
|
+
fn gba_no_std_rewrite(src: String) -> String {
|
|
789
|
+
// Apply the fixups only to CODE, never to the contents of string literals: a user
|
|
790
|
+
// string that happens to contain one of these patterns (e.g. `let s = "std::mem::x"`)
|
|
791
|
+
// must be left byte-for-byte intact. We scan the generated source, copy each `"…"`
|
|
792
|
+
// literal through verbatim (respecting `\` escapes), and rewrite only the code between
|
|
793
|
+
// them. (`"` and `\` are ASCII, so a byte scan is UTF-8 safe; char literals can't hold
|
|
794
|
+
// these multi-char patterns, and any stray `'"'` at worst *misses* a fixup — a loud
|
|
795
|
+
// no_std compile error — never a silent string corruption.)
|
|
796
|
+
let bytes = src.as_bytes();
|
|
797
|
+
let mut out = String::with_capacity(src.len());
|
|
798
|
+
let mut seg_start = 0;
|
|
799
|
+
let mut i = 0;
|
|
800
|
+
while i < bytes.len() {
|
|
801
|
+
if bytes[i] == b'"' {
|
|
802
|
+
out.push_str(&gba_rewrite_code(&src[seg_start..i]));
|
|
803
|
+
let str_start = i;
|
|
804
|
+
i += 1;
|
|
805
|
+
while i < bytes.len() {
|
|
806
|
+
match bytes[i] {
|
|
807
|
+
b'\\' => i += 2, // skip the escape and the (ASCII) escaped byte
|
|
808
|
+
b'"' => {
|
|
809
|
+
i += 1;
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
_ => i += 1,
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
out.push_str(&src[str_start..i.min(bytes.len())]);
|
|
816
|
+
seg_start = i;
|
|
817
|
+
} else {
|
|
818
|
+
i += 1;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
out.push_str(&gba_rewrite_code(&src[seg_start..]));
|
|
822
|
+
out
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/// The std::→core::/facade path fixups, applied only to non-string-literal code by
|
|
826
|
+
/// [`gba_no_std_rewrite`]. Callers extend this table as the thumbv4t corpus build surfaces
|
|
827
|
+
/// new sites (a surviving `std::` path is a loud `#![no_std]` compile error, not silent).
|
|
828
|
+
fn gba_rewrite_code(src: &str) -> String {
|
|
829
|
+
src.replace("std::error::Error", "core::error::Error")
|
|
830
|
+
.replace("std::f64::consts", "core::f64::consts")
|
|
831
|
+
.replace("std::cmp::Ordering", "core::cmp::Ordering")
|
|
832
|
+
.replace("std::mem::", "core::mem::")
|
|
833
|
+
.replace("std::slice::", "core::slice::")
|
|
834
|
+
// Struct→Value boundary conversions (`object_from_pairs`) and a few other
|
|
835
|
+
// sites emit fully-qualified `::std::sync::Arc` / `std::sync::Arc`. The Gba
|
|
836
|
+
// prelude imports `Arc` (= facade Rc alias), so map both to it. The `::`
|
|
837
|
+
// form must be replaced first (it contains the non-`::` form).
|
|
838
|
+
.replace("::std::sync::Arc", "Arc")
|
|
839
|
+
.replace("std::sync::Arc", "Arc")
|
|
840
|
+
// hashbrown lacks `From<[_; N]>` with a custom hasher; route object-literal
|
|
841
|
+
// construction through the facade helper.
|
|
842
|
+
.replace("ObjectMap::from(", "tishlang_runtime::object_map_from(")
|
|
843
|
+
}
|
|
844
|
+
|
|
752
845
|
/// #177 (S-E/S-F): the return shape of a de-virtualized aggregate (struct/array) free fn.
|
|
753
846
|
#[derive(Debug, Clone, PartialEq)]
|
|
754
847
|
enum AggRet {
|
|
@@ -784,7 +877,7 @@ struct AggFnSig {
|
|
|
784
877
|
ret: AggRet,
|
|
785
878
|
}
|
|
786
879
|
|
|
787
|
-
/// #178 (
|
|
880
|
+
/// #178 (default-on via `native_opts_enabled`): a recursive struct shape inferred *structurally* (no name
|
|
788
881
|
/// matching) from object literals whose fields are either scalars or recursive self-references.
|
|
789
882
|
/// Drives native lowering of tree/list builders and consumers to `Option<Box<T>>` + native fns —
|
|
790
883
|
/// the real, name-independent binary_trees lowering. See docs/recursive-struct-native.md.
|
|
@@ -915,7 +1008,7 @@ pub(crate) struct Codegen {
|
|
|
915
1008
|
/// Variables currently wrapped in Rc<RefCell<Value>> for mutable capture in closures
|
|
916
1009
|
/// These need special handling: reads via .borrow().clone(), writes via *var.borrow_mut()
|
|
917
1010
|
refcell_wrapped_vars: std::collections::HashSet<String>,
|
|
918
|
-
/// #176 (
|
|
1011
|
+
/// #176 (default-on via `native_opts_enabled`): top-level `let` bindings lowered to `thread_local Cell<f64>`
|
|
919
1012
|
/// (`G_NAME`) when every use is a numeric read or a whole-binding numeric assign (fasta `seed`).
|
|
920
1013
|
native_numeric_globals: std::collections::HashMap<String, f64>,
|
|
921
1014
|
/// Top-level `let name = [f64 literals…]` never reassigned — emitted as `const G_name: [f64; N]`
|
|
@@ -940,7 +1033,7 @@ pub(crate) struct Codegen {
|
|
|
940
1033
|
/// of `str_char_code_at`'s `chars().nth(i)` = O(i). Maps the source ident → the hoisted `_scN`
|
|
941
1034
|
/// `Vec<char>` binding; populated for the loop body's extent only.
|
|
942
1035
|
hoisted_string_chars: std::collections::HashMap<String, String>,
|
|
943
|
-
/// M5 (
|
|
1036
|
+
/// M5 (default-on via `native_opts_enabled`): top-level functions eligible for a parallel
|
|
944
1037
|
/// free `fn f_native(f64,..)->f64` (all params `: number`, returns `number`, native-safe
|
|
945
1038
|
/// body). Direct calls to these route to the native fn, bypassing the boxed `value_call`.
|
|
946
1039
|
native_fns: std::collections::HashSet<String>,
|
|
@@ -1001,7 +1094,7 @@ pub(crate) struct Codegen {
|
|
|
1001
1094
|
/// nested call site these are already `&/&mut Vec<T>` references, so they pass through by reborrow
|
|
1002
1095
|
/// (`&mut *p`) rather than being address-of'd like a local `Vec` (`&mut local`).
|
|
1003
1096
|
vec_ref_params: std::collections::HashMap<String, bool>,
|
|
1004
|
-
/// #320 (
|
|
1097
|
+
/// #320 (default-on via `native_opts_enabled`): normal boxed-closure fns that take one or more READ-ONLY
|
|
1005
1098
|
/// `number[]` params (e.g. k_nucleotide's `kNucleotide(seq, k)` — indexes `seq` but returns a
|
|
1006
1099
|
/// boxed Map). Per param: `true` = unbox the boxed arg into an owned native `Vec<f64>` so the
|
|
1007
1100
|
/// body indexes it natively (`seq[i+j]` → f64), `false` = leave it on its normal path. The fn
|
|
@@ -1009,10 +1102,10 @@ pub(crate) struct Codegen {
|
|
|
1009
1102
|
/// native. Computed by `Self::native_arr_param_fns`, the SAME pure fn infer reads (agreement
|
|
1010
1103
|
/// contract — see `infer::native_vec_array_params`), disjoint from `native_vec_fns`.
|
|
1011
1104
|
native_arr_param_fns: std::collections::HashMap<String, Vec<bool>>,
|
|
1012
|
-
/// #177 S-E/S-F (
|
|
1105
|
+
/// #177 S-E/S-F (default-on via `native_opts_enabled`): the unboxed struct alias name
|
|
1013
1106
|
/// (e.g. `TishAnon_0`) when the interprocedural aggregate path is active for this program,
|
|
1014
1107
|
/// else `None`. Set in `emit_program` only after the de-virtualized fns emit successfully.
|
|
1015
|
-
/// #178: inferred recursive-struct lowering plan (
|
|
1108
|
+
/// #178: inferred recursive-struct lowering plan (default-on via `native_opts_enabled`).
|
|
1016
1109
|
rec_struct_plan: Option<RecStructPlan>,
|
|
1017
1110
|
aggregate_alias: Option<String>,
|
|
1018
1111
|
/// #177: fn name → its de-virtualized native signature. Calls to these route directly to the
|
|
@@ -1108,6 +1201,11 @@ pub(crate) struct Codegen {
|
|
|
1108
1201
|
/// closures it must not return a `Result` from a `Value`-returning closure.
|
|
1109
1202
|
value_fn_depth: u32,
|
|
1110
1203
|
emit_mode: crate::NativeEmitMode,
|
|
1204
|
+
/// Scheme imports as `(scheme_name, absolute_path)`, in first-seen order. Populated (Gba mode)
|
|
1205
|
+
/// before the entry is emitted from the active [`crate::schemes`] registry; drives the
|
|
1206
|
+
/// `mod __scheme_<name>_<j> { … }` blocks and their per-scheme registration. The handle a tish
|
|
1207
|
+
/// name binds to is its index AMONG same-scheme entries (= its slot in that scheme's arena).
|
|
1208
|
+
scheme_files: Vec<(String, String)>,
|
|
1111
1209
|
/// Program links `tish:macos` / `tish:ios` — skip HeadlessHost install.
|
|
1112
1210
|
has_native_ui_host: bool,
|
|
1113
1211
|
/// Program references browser global `document` — inject tish-canvas.
|
|
@@ -1120,6 +1218,10 @@ pub(crate) struct Codegen {
|
|
|
1120
1218
|
cse_subst: Vec<(Expr, String)>,
|
|
1121
1219
|
/// Monotonic counter for unique CSE temp names (`_cse0`, `_cse1`, …).
|
|
1122
1220
|
cse_temp_counter: usize,
|
|
1221
|
+
/// The program's used NON-ASCII characters (collected from every string literal), as a sorted
|
|
1222
|
+
/// string. Fed to import schemes via `{charset}` so a scheme can bake only the glyphs a program
|
|
1223
|
+
/// actually uses — e.g. `font<N>:` bakes ASCII + these, not all ~24k glyphs of a CJK font.
|
|
1224
|
+
string_charset: String,
|
|
1123
1225
|
}
|
|
1124
1226
|
|
|
1125
1227
|
/// One `map`/`filter` stage of a fused HOF chain (#317): the single-element-param closure's
|
|
@@ -1146,6 +1248,7 @@ impl Codegen {
|
|
|
1146
1248
|
indent: 0,
|
|
1147
1249
|
loop_label_index: 0,
|
|
1148
1250
|
is_async: false,
|
|
1251
|
+
string_charset: String::new(),
|
|
1149
1252
|
features,
|
|
1150
1253
|
native_module_init,
|
|
1151
1254
|
async_context_stack: Vec::new(),
|
|
@@ -1213,6 +1316,7 @@ impl Codegen {
|
|
|
1213
1316
|
program_fun_decl_names: std::collections::HashSet::new(),
|
|
1214
1317
|
value_fn_depth: 0,
|
|
1215
1318
|
emit_mode: crate::NativeEmitMode::DesktopBin,
|
|
1319
|
+
scheme_files: Vec::new(),
|
|
1216
1320
|
has_native_ui_host: false,
|
|
1217
1321
|
program_uses_document: false,
|
|
1218
1322
|
cse_subst: Vec::new(),
|
|
@@ -1346,6 +1450,160 @@ impl Codegen {
|
|
|
1346
1450
|
/// an object shape. Each generated struct derives `Clone` + `Debug`
|
|
1347
1451
|
/// (cheap; field types are all `Copy`-or-cheap-clone in practice) and
|
|
1348
1452
|
/// is named `TishStruct_<TishAlias>`.
|
|
1453
|
+
/// Split a lowered scheme spec (`<prefix><abspath>`) into `(scheme_name, abspath)` using the
|
|
1454
|
+
/// active registry. `None` if the spec isn't a registered scheme.
|
|
1455
|
+
fn split_scheme_spec(spec: &str) -> Option<(String, String)> {
|
|
1456
|
+
crate::schemes::with_active(|reg| {
|
|
1457
|
+
reg.matches(spec).map(|s| {
|
|
1458
|
+
let prefix = s.prefix();
|
|
1459
|
+
let rest = spec.strip_prefix(&prefix).unwrap_or(spec).to_string();
|
|
1460
|
+
(s.name.clone(), rest)
|
|
1461
|
+
})
|
|
1462
|
+
})
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
/// Populate [`Self::scheme_files`] from the program's scheme imports. Each was lowered (resolve
|
|
1466
|
+
/// phase) to a top-level `let name = NativeModuleLoad { spec: "<prefix><abspath>" }`, so a flat
|
|
1467
|
+
/// scan of the top-level statements finds them all. De-duplicated on `(scheme, path)`.
|
|
1468
|
+
fn collect_scheme_files(&mut self, stmts: &[Statement]) {
|
|
1469
|
+
for s in stmts {
|
|
1470
|
+
if let Statement::VarDecl {
|
|
1471
|
+
init: Some(Expr::NativeModuleLoad { spec, .. }),
|
|
1472
|
+
..
|
|
1473
|
+
} = s
|
|
1474
|
+
{
|
|
1475
|
+
if let Some(entry) = Self::split_scheme_spec(spec.as_ref()) {
|
|
1476
|
+
if !self.scheme_files.contains(&entry) {
|
|
1477
|
+
self.scheme_files.push(entry);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/// The runtime arena a scheme registers into, keyed by its (pre-render) `register` template.
|
|
1485
|
+
/// Schemes that share a template share an arena: `asset:` and `sheet:` both call
|
|
1486
|
+
/// `__asset_register_sheet`, so both index into the same runtime `Vec`. `None` for a scheme with
|
|
1487
|
+
/// no registration call (nothing to index into).
|
|
1488
|
+
fn scheme_arena_key(&self, scheme: &str) -> Option<String> {
|
|
1489
|
+
let emit = self.scheme_target_emit(scheme).ok()?;
|
|
1490
|
+
(!emit.register.is_empty()).then_some(emit.register)
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/// The handle a scheme spec binds to — its slot in the runtime ARENA it registers into. The
|
|
1494
|
+
/// arena is keyed by the scheme's `register` target, NOT its name: `asset:` and `sheet:` both
|
|
1495
|
+
/// push to `__asset_register_sheet`, so a program mixing them must number handles across BOTH
|
|
1496
|
+
/// (the entry registers every scheme file in one first-seen pass, so same-arena entries land in
|
|
1497
|
+
/// exactly this order). Numbering per-scheme-name collided their handles — an `asset:` and a
|
|
1498
|
+
/// `sheet:` import each got handle 0 while the runtime gave them slots 0 and 1 (#552).
|
|
1499
|
+
fn scheme_handle_of(&self, spec: &str) -> Option<usize> {
|
|
1500
|
+
let (scheme, path) = Self::split_scheme_spec(spec)?;
|
|
1501
|
+
let arena = self.scheme_arena_key(&scheme)?;
|
|
1502
|
+
let mut k = 0usize;
|
|
1503
|
+
for (s, p) in &self.scheme_files {
|
|
1504
|
+
if self.scheme_arena_key(s).as_deref() != Some(arena.as_str()) {
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
if *s == scheme && *p == path {
|
|
1508
|
+
return Some(k);
|
|
1509
|
+
}
|
|
1510
|
+
k += 1;
|
|
1511
|
+
}
|
|
1512
|
+
None
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/// The generated `mod` identifier for the `j`-th collected scheme file.
|
|
1516
|
+
fn scheme_mod_ident(scheme: &str, j: usize) -> String {
|
|
1517
|
+
format!("__scheme_{}_{}", scheme, j)
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
/// Emit one `mod __scheme_<name>_<j> { … }` per collected scheme file (Gba only), from the
|
|
1521
|
+
/// scheme's `gba` emit template: its `use`s, module body (the include macro, `{path}` → the
|
|
1522
|
+
/// absolute file), and a same-module accessor (which can see the macro's private statics — the
|
|
1523
|
+
/// only way a tagless image's sprites escape). Registration into the runtime arena happens in
|
|
1524
|
+
/// the entry below.
|
|
1525
|
+
/// Collect every NON-ASCII character appearing in the program's string literals, sorted+unique.
|
|
1526
|
+
/// Fed to schemes via `{charset}` (see `render_template`) so a `font<N>:` import can bake only the
|
|
1527
|
+
/// glyphs actually used. ASCII is always baked by the scheme, so it's excluded here. Runtime-built
|
|
1528
|
+
/// strings (concatenation, numbers) resolve to ASCII, which is covered; a non-ASCII char reachable
|
|
1529
|
+
/// only through a computed string is the rare gap (documented — add it to a literal if needed).
|
|
1530
|
+
fn collect_string_charset(stmts: &[Statement]) -> String {
|
|
1531
|
+
let mut set = std::collections::BTreeSet::new();
|
|
1532
|
+
fn add(s: &str, set: &mut std::collections::BTreeSet<char>) {
|
|
1533
|
+
for c in s.chars() {
|
|
1534
|
+
if (c as u32) >= 0x7F {
|
|
1535
|
+
set.insert(c);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
fn walk(stmts: &[Statement], set: &mut std::collections::BTreeSet<char>) {
|
|
1540
|
+
for s in stmts {
|
|
1541
|
+
Codegen::for_each_stmt_expr(s, &mut |e| {
|
|
1542
|
+
Codegen::nv_for_each_subexpr(e, &mut |x| {
|
|
1543
|
+
if let Expr::Literal { value: Literal::String(str), .. } = x {
|
|
1544
|
+
add(str, set);
|
|
1545
|
+
}
|
|
1546
|
+
if let Expr::TemplateLiteral { quasis, .. } = x {
|
|
1547
|
+
for q in quasis {
|
|
1548
|
+
add(q, set);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
});
|
|
1553
|
+
Codegen::for_each_child_stmt_list(s, &mut |list| walk(list, set));
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
walk(stmts, &mut set);
|
|
1557
|
+
set.into_iter().collect()
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
fn emit_scheme_modules(&mut self) -> Result<(), CompileError> {
|
|
1561
|
+
let files = self.scheme_files.clone();
|
|
1562
|
+
for (j, (scheme, path)) in files.iter().enumerate() {
|
|
1563
|
+
let emit = self.scheme_target_emit(scheme)?;
|
|
1564
|
+
let module_ident = Self::scheme_mod_ident(scheme, j);
|
|
1565
|
+
self.writeln(&format!("mod {} {{", module_ident));
|
|
1566
|
+
self.indent += 1;
|
|
1567
|
+
for u in &emit.uses {
|
|
1568
|
+
self.writeln(u);
|
|
1569
|
+
}
|
|
1570
|
+
self.writeln(&crate::schemes::render_template(
|
|
1571
|
+
&emit.module_body,
|
|
1572
|
+
path,
|
|
1573
|
+
&module_ident,
|
|
1574
|
+
&self.string_charset,
|
|
1575
|
+
));
|
|
1576
|
+
if !emit.accessor.is_empty() {
|
|
1577
|
+
self.writeln(&emit.accessor);
|
|
1578
|
+
}
|
|
1579
|
+
self.indent -= 1;
|
|
1580
|
+
self.writeln("}");
|
|
1581
|
+
}
|
|
1582
|
+
if !files.is_empty() {
|
|
1583
|
+
self.writeln("");
|
|
1584
|
+
}
|
|
1585
|
+
Ok(())
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/// The `gba` [`crate::schemes::TargetEmit`] for `scheme`, or a compile error if the scheme has
|
|
1589
|
+
/// no template for this target.
|
|
1590
|
+
fn scheme_target_emit(
|
|
1591
|
+
&self,
|
|
1592
|
+
scheme: &str,
|
|
1593
|
+
) -> Result<crate::schemes::TargetEmit, CompileError> {
|
|
1594
|
+
crate::schemes::with_active(|reg| {
|
|
1595
|
+
reg.matches(&format!("{}:", scheme))
|
|
1596
|
+
.and_then(|s| s.targets.get("gba").cloned())
|
|
1597
|
+
})
|
|
1598
|
+
.ok_or_else(|| CompileError {
|
|
1599
|
+
message: format!(
|
|
1600
|
+
"import scheme '{}:' has no emit template for target 'gba'",
|
|
1601
|
+
scheme
|
|
1602
|
+
),
|
|
1603
|
+
span: None,
|
|
1604
|
+
})
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1349
1607
|
fn emit_named_struct_decls(&mut self) {
|
|
1350
1608
|
// Snapshot keys + values so we can mutate `self` (writing the
|
|
1351
1609
|
// emitted source) inside the loop.
|
|
@@ -2245,15 +2503,38 @@ impl Codegen {
|
|
|
2245
2503
|
}
|
|
2246
2504
|
|
|
2247
2505
|
fn emit_program(&mut self, program: &Program) -> Result<(), CompileError> {
|
|
2506
|
+
// Only GBA `font<N>:`/`emoji:` schemes consume `{charset}` (and both `render_template`
|
|
2507
|
+
// callers that read `string_charset` are Gba-gated), so skip the whole-program string walk
|
|
2508
|
+
// for every other target — its result is never read off-GBA.
|
|
2509
|
+
if self.emit_mode == crate::NativeEmitMode::Gba {
|
|
2510
|
+
self.string_charset = Self::collect_string_charset(&program.statements);
|
|
2511
|
+
}
|
|
2248
2512
|
self.is_async = program_uses_async(program);
|
|
2249
2513
|
self.program_has_jsx = tishlang_ui::jsx::program_contains_jsx(program);
|
|
2250
2514
|
self.program_fun_decl_names = tishlang_ui::jsx::collect_fun_decl_names(program);
|
|
2251
2515
|
self.program_uses_document = crate::resolve::program_uses_document(program);
|
|
2252
|
-
self.
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2516
|
+
if self.emit_mode == crate::NativeEmitMode::Gba {
|
|
2517
|
+
// no_std GBA ROM: alloc-based, Arc aliases to Rc in the facade. The
|
|
2518
|
+
// prelude `use tishlang_runtime::{…}` below is unchanged — those names
|
|
2519
|
+
// are re-exported by the tishlang_runtime_gba facade.
|
|
2520
|
+
self.write("#![no_std]\n#![no_main]\n#![allow(unused, non_snake_case)]\n\n");
|
|
2521
|
+
self.write("extern crate alloc;\n");
|
|
2522
|
+
self.write("use core::cell::RefCell;\n");
|
|
2523
|
+
self.write("use alloc::rc::Rc;\n");
|
|
2524
|
+
self.write("use alloc::boxed::Box;\n");
|
|
2525
|
+
self.write("use alloc::vec::Vec;\n");
|
|
2526
|
+
self.write("use alloc::string::{String, ToString};\n");
|
|
2527
|
+
self.write("use alloc::{vec, format};\n");
|
|
2528
|
+
self.write("use tishlang_runtime::Arc;\n");
|
|
2529
|
+
// f64 transcendentals for inline math (`Math.sqrt(x)` → `x.sqrt()`).
|
|
2530
|
+
self.write("use tishlang_runtime::FloatExt;\n");
|
|
2531
|
+
} else {
|
|
2532
|
+
self.write("#![allow(unused, non_snake_case)]\n\n");
|
|
2533
|
+
self.write("use std::cell::RefCell;\n");
|
|
2534
|
+
self.write("use std::rc::Rc;\n");
|
|
2535
|
+
self.write("use std::sync::Arc;\n");
|
|
2536
|
+
}
|
|
2537
|
+
self.write("use tishlang_runtime::{console_debug as tish_console_debug, console_info as tish_console_info, console_log as tish_console_log, console_warn as tish_console_warn, console_error as tish_console_error, boolean as tish_boolean, decode_uri as tish_decode_uri, encode_uri as tish_encode_uri, encode_uri_component as tish_encode_uri_component, decode_uri_component as tish_decode_uri_component, string_escape_html_impl as tish_escape_html, in_operator as tish_in_operator, is_finite as tish_is_finite, is_nan as tish_is_nan, json_parse as tish_json_parse, json_stringify as tish_json_stringify, math_abs as tish_math_abs, math_ceil as tish_math_ceil, math_floor as tish_math_floor, math_max as tish_math_max, math_min as tish_math_min, math_round as tish_math_round, math_sqrt as tish_math_sqrt, parse_float as tish_parse_float, parse_int as tish_parse_int, math_random as tish_math_random, math_pow as tish_math_pow, math_sin as tish_math_sin, math_cos as tish_math_cos, math_tan as tish_math_tan, math_log as tish_math_log, math_exp as tish_math_exp, math_sign as tish_math_sign, math_trunc as tish_math_trunc, math_imul as tish_math_imul, math_sinh as tish_math_sinh, math_cosh as tish_math_cosh, math_tanh as tish_math_tanh, math_asinh as tish_math_asinh, math_acosh as tish_math_acosh, math_atanh as tish_math_atanh, math_cbrt as tish_math_cbrt, math_log2 as tish_math_log2, math_log10 as tish_math_log10, math_expm1 as tish_math_expm1, math_log1p as tish_math_log1p, math_clz32 as tish_math_clz32, math_fround as tish_math_fround, math_hypot as tish_math_hypot, math_atan2 as tish_math_atan2, math_asin as tish_math_asin, math_acos as tish_math_acos, math_atan as tish_math_atan, array_is_array as tish_array_is_array, array_of as tish_array_of, array_from as tish_array_from, object_is as tish_object_is, object_has_own as tish_object_has_own, structured_clone as tish_structured_clone, array_construct as tish_array_construct, string_from_char_code as tish_string_from_char_code, string_convert as tish_string_convert, number_convert as tish_number_convert, number_is_integer as tish_number_is_integer, number_is_safe_integer as tish_number_is_safe_integer, number_is_nan as tish_number_is_nan, number_is_finite as tish_number_is_finite, object_assign as tish_object_assign, object_freeze as tish_object_freeze, object_is_frozen as tish_object_is_frozen, object_keys as tish_object_keys, object_values as tish_object_values, object_entries as tish_object_entries, object_from_entries as tish_object_from_entries, symbol_object as tish_symbol_object, tish_construct, tish_error_constructor, tish_date_constructor, tish_set_constructor, tish_map_constructor, map_get as tish_map_get, map_has as tish_map_has, map_set as tish_map_set, map_values as tish_map_values, tish_float64_array_constructor, tish_float32_array_constructor, tish_int8_array_constructor, tish_uint8_array_constructor, tish_uint8_clamped_array_constructor, tish_int16_array_constructor, tish_uint16_array_constructor, tish_int32_array_constructor, tish_uint32_array_constructor, tish_audio_context_constructor, ObjectMap, ObjectData, PropMap, TishError, Value, VmRef};\n");
|
|
2257
2538
|
if self.program_has_jsx {
|
|
2258
2539
|
self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
|
|
2259
2540
|
}
|
|
@@ -2309,6 +2590,16 @@ impl Codegen {
|
|
|
2309
2590
|
// and `x.field` becomes a direct field access.
|
|
2310
2591
|
self.emit_named_struct_decls();
|
|
2311
2592
|
|
|
2593
|
+
// `asset:` imports (Gba only): collect the distinct files (each an i32 sheet handle) and
|
|
2594
|
+
// emit one `mod __asset_F { include_aseprite_inner!("<abspath>"); pub fn sheet() }` block.
|
|
2595
|
+
// The inner macro's `SPRITES` static is private, so the same-module `sheet()` accessor is
|
|
2596
|
+
// how the frames escape the module (a tagless image exposes no public tag). Registration
|
|
2597
|
+
// into the facade arena happens in the entry below, before `run()`.
|
|
2598
|
+
if self.emit_mode == crate::NativeEmitMode::Gba {
|
|
2599
|
+
self.collect_scheme_files(&program.statements);
|
|
2600
|
+
self.emit_scheme_modules()?;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2312
2603
|
if self.is_async && self.emit_mode == crate::NativeEmitMode::DesktopBin {
|
|
2313
2604
|
self.writeln("#[tokio::main]");
|
|
2314
2605
|
self.writeln("async fn main() {");
|
|
@@ -2331,9 +2622,36 @@ impl Codegen {
|
|
|
2331
2622
|
self.writeln("}");
|
|
2332
2623
|
self.writeln("");
|
|
2333
2624
|
}
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2625
|
+
if self.emit_mode == crate::NativeEmitMode::Gba {
|
|
2626
|
+
// agb runtime entry: hand the peripherals to the facade, run the
|
|
2627
|
+
// program body, then halt. (Async lands in P5 — `run()` is sync here.)
|
|
2628
|
+
self.writeln("#[agb::entry]");
|
|
2629
|
+
self.writeln("fn agb_main(gba: agb::Gba) -> ! {");
|
|
2630
|
+
self.indent += 1;
|
|
2631
|
+
self.writeln("tishlang_runtime::gba::init(gba);");
|
|
2632
|
+
// Register each scheme file into its runtime arena, in first-seen order, so the handle a
|
|
2633
|
+
// tish name binds to (its per-scheme index) matches the arena slot it gets here.
|
|
2634
|
+
let scheme_files = self.scheme_files.clone();
|
|
2635
|
+
for (j, (scheme, _path)) in scheme_files.iter().enumerate() {
|
|
2636
|
+
let emit = self.scheme_target_emit(scheme)?;
|
|
2637
|
+
if !emit.register.is_empty() {
|
|
2638
|
+
let module_ident = Self::scheme_mod_ident(scheme, j);
|
|
2639
|
+
let call = crate::schemes::render_template(&emit.register, "", &module_ident, &self.string_charset);
|
|
2640
|
+
self.writeln(&format!("{};", call));
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
self.writeln("let _ = run();");
|
|
2644
|
+
self.writeln("tishlang_runtime::gba::halt()");
|
|
2645
|
+
self.indent -= 1;
|
|
2646
|
+
self.writeln("}");
|
|
2647
|
+
self.writeln("");
|
|
2648
|
+
}
|
|
2649
|
+
// M5 (default-on via native_opts_enabled; TISH_NATIVE_OPT=0 opts out): emit a parallel
|
|
2650
|
+
// native `fn f_native` for each eligible top-level numeric fn at top level; direct calls
|
|
2651
|
+
// route to it in emit_typed_expr.
|
|
2652
|
+
// Gba mode: these passes emit `thread_local!` and other std-isms; gate them off initially
|
|
2653
|
+
// (they are pure perf — correctness is unaffected). Re-audit for no_std later.
|
|
2654
|
+
if crate::native_opts_enabled() && self.emit_mode != crate::NativeEmitMode::Gba {
|
|
2337
2655
|
self.native_numeric_globals =
|
|
2338
2656
|
Self::collect_native_numeric_globals(&program.statements);
|
|
2339
2657
|
if !self.native_numeric_globals.is_empty() {
|
|
@@ -2378,21 +2696,21 @@ impl Codegen {
|
|
|
2378
2696
|
self.writeln("");
|
|
2379
2697
|
}
|
|
2380
2698
|
}
|
|
2381
|
-
// #177 (S-E/S-F,
|
|
2699
|
+
// #177 (S-E/S-F, default-on via native_opts_enabled): de-virtualize the nbody-shape
|
|
2382
2700
|
// aggregate fns into native Rust free fns operating on an unboxed `Vec<TishStruct_alias>`
|
|
2383
2701
|
// threaded by reference. Computed + emitted here (before `run()`); if any fn can't be
|
|
2384
2702
|
// lowered the whole path is disabled and we fall back to the boxed closures unchanged.
|
|
2385
2703
|
if crate::native_opts_enabled() {
|
|
2386
2704
|
self.setup_aggregate_fns(program);
|
|
2387
2705
|
}
|
|
2388
|
-
// #178 (
|
|
2706
|
+
// #178 (default-on via native_opts_enabled): de-virtualize recursive-struct builders/consumers into
|
|
2389
2707
|
// native free fns over `Option<Box<T>>` structs (the real binary_trees fix). Structural,
|
|
2390
2708
|
// name-independent; emits before `run()` with scratch-buffer rollback so any unsupported
|
|
2391
2709
|
// construct cleanly disables the path and falls back to the boxed closures unchanged.
|
|
2392
2710
|
if crate::native_opts_enabled() {
|
|
2393
2711
|
self.setup_rec_struct_plan(program);
|
|
2394
2712
|
}
|
|
2395
|
-
// #175 (
|
|
2713
|
+
// #175 (default-on via native_opts_enabled): de-virtualize fns over plain `number[]`/`boolean[]` params
|
|
2396
2714
|
// into native free fns threaded by `&/&mut Vec<T>` (spectral_norm / queens). Runs after the
|
|
2397
2715
|
// aggregate path so it can skip any fn that path already claimed; emits before `run()` with
|
|
2398
2716
|
// its own scratch-buffer rollback so a failure leaves the boxed closures untouched.
|
|
@@ -2403,7 +2721,7 @@ impl Codegen {
|
|
|
2403
2721
|
self.inline_fns = Self::collect_inline_fns(program);
|
|
2404
2722
|
self.setup_native_vec_fns(program);
|
|
2405
2723
|
}
|
|
2406
|
-
// #320 (
|
|
2724
|
+
// #320 (default-on via native_opts_enabled): normal fns taking read-only `number[]` params get
|
|
2407
2725
|
// those params unboxed to native owned `Vec<f64>` (boxed body + boxed return otherwise), so
|
|
2408
2726
|
// `seq[i+j]` indexes natively. The SAME pure detection infer reads to keep the caller's array
|
|
2409
2727
|
// `number[]`, so the two never disagree about which args pass as native arrays.
|
|
@@ -2441,7 +2759,9 @@ impl Codegen {
|
|
|
2441
2759
|
}
|
|
2442
2760
|
self.nonneg_locals = self.collect_nonneg_locals(&program.statements);
|
|
2443
2761
|
self.shift_half_of = Self::collect_shift_half_of(&program.statements);
|
|
2444
|
-
|
|
2762
|
+
// Gba mode is sync in P2 (real async lowering is P5); `std::error::Error` in
|
|
2763
|
+
// the signature is rewritten to `core::error::Error` by the no_std post-pass.
|
|
2764
|
+
if self.is_async && self.emit_mode != crate::NativeEmitMode::Gba {
|
|
2445
2765
|
self.writeln("async fn run() -> Result<(), Box<dyn std::error::Error>> {");
|
|
2446
2766
|
} else if self.emit_mode == crate::NativeEmitMode::EmbeddedLib {
|
|
2447
2767
|
self.writeln("pub fn run() -> Result<(), Box<dyn std::error::Error>> {");
|
|
@@ -2545,6 +2865,15 @@ impl Codegen {
|
|
|
2545
2865
|
}
|
|
2546
2866
|
self.writeln("(Arc::from(\"PI\"), Value::Number(std::f64::consts::PI)),");
|
|
2547
2867
|
self.writeln("(Arc::from(\"E\"), Value::Number(std::f64::consts::E)),");
|
|
2868
|
+
// #539: the remaining ECMAScript Math value constants (were missing → `null`).
|
|
2869
|
+
self.writeln("(Arc::from(\"LN2\"), Value::Number(std::f64::consts::LN_2)),");
|
|
2870
|
+
self.writeln("(Arc::from(\"LN10\"), Value::Number(std::f64::consts::LN_10)),");
|
|
2871
|
+
self.writeln("(Arc::from(\"LOG2E\"), Value::Number(std::f64::consts::LOG2_E)),");
|
|
2872
|
+
self.writeln("(Arc::from(\"LOG10E\"), Value::Number(std::f64::consts::LOG10_E)),");
|
|
2873
|
+
self.writeln("(Arc::from(\"SQRT2\"), Value::Number(std::f64::consts::SQRT_2)),");
|
|
2874
|
+
self.writeln(
|
|
2875
|
+
"(Arc::from(\"SQRT1_2\"), Value::Number(std::f64::consts::FRAC_1_SQRT_2)),",
|
|
2876
|
+
);
|
|
2548
2877
|
self.indent -= 1;
|
|
2549
2878
|
self.writeln("]));");
|
|
2550
2879
|
self.writeln("let JSON = Value::object(ObjectMap::from([");
|
|
@@ -2623,6 +2952,12 @@ impl Codegen {
|
|
|
2623
2952
|
self.writeln(
|
|
2624
2953
|
"(Arc::from(\"getOwnPropertyNames\"), Value::native(|args: &[Value]| tish_object_keys(args))),",
|
|
2625
2954
|
);
|
|
2955
|
+
self.writeln(
|
|
2956
|
+
"(Arc::from(\"freeze\"), Value::native(|args: &[Value]| tish_object_freeze(args))),",
|
|
2957
|
+
);
|
|
2958
|
+
self.writeln(
|
|
2959
|
+
"(Arc::from(\"isFrozen\"), Value::native(|args: &[Value]| tish_object_is_frozen(args))),",
|
|
2960
|
+
);
|
|
2626
2961
|
self.writeln(
|
|
2627
2962
|
"(Arc::from(\"keys\"), Value::native(|args: &[Value]| tish_object_keys(args))),",
|
|
2628
2963
|
);
|
|
@@ -2820,6 +3155,14 @@ impl Codegen {
|
|
|
2820
3155
|
self.indent -= 1;
|
|
2821
3156
|
self.writeln("}");
|
|
2822
3157
|
}
|
|
3158
|
+
// Gba mode: rewrite the handful of `std::` paths that survive in emitted
|
|
3159
|
+
// code into their `core::` equivalents (the crate is `#![no_std]`). These
|
|
3160
|
+
// are specific Rust path patterns, not substrings that occur in escaped
|
|
3161
|
+
// user string literals, so a targeted textual pass is safe. Any new site
|
|
3162
|
+
// is caught by the thumbv4t corpus build.
|
|
3163
|
+
if self.emit_mode == crate::NativeEmitMode::Gba {
|
|
3164
|
+
self.output = gba_no_std_rewrite(core::mem::take(&mut self.output));
|
|
3165
|
+
}
|
|
2823
3166
|
Ok(())
|
|
2824
3167
|
}
|
|
2825
3168
|
|
|
@@ -2842,7 +3185,12 @@ impl Codegen {
|
|
|
2842
3185
|
/// unchanged, and `PropMap` dedup + hidden-class shapes key by text, not pointer — so shapes,
|
|
2843
3186
|
/// insertion order, and `Object.keys` are all unaffected. Same inline-`static` idiom as the
|
|
2844
3187
|
/// per-site `PropIC` caches.
|
|
2845
|
-
fn cached_object_key(k: &str) -> String {
|
|
3188
|
+
fn cached_object_key(&self, k: &str) -> String {
|
|
3189
|
+
if self.emit_mode == crate::NativeEmitMode::Gba {
|
|
3190
|
+
// No `OnceLock` on no_std GBA; emit a direct interned key (`Arc` = `Rc`).
|
|
3191
|
+
// Object-literal keys in a hot loop are a later perf item on GBA.
|
|
3192
|
+
return format!("Arc::from({:?})", k);
|
|
3193
|
+
}
|
|
2846
3194
|
format!(
|
|
2847
3195
|
"{{ static K: ::std::sync::OnceLock<::std::sync::Arc<str>> = \
|
|
2848
3196
|
::std::sync::OnceLock::new(); K.get_or_init(|| ::std::sync::Arc::from({:?})).clone() }}",
|
|
@@ -2979,7 +3327,24 @@ impl Codegen {
|
|
|
2979
3327
|
}
|
|
2980
3328
|
}
|
|
2981
3329
|
}
|
|
2982
|
-
|
|
3330
|
+
// Narrow int local (`let hp: u8 = …; hp = hp - 1`): compute the RHS (promoted to
|
|
3331
|
+
// `f64` by the binary emitter) and truncate-cast to the width on store. No
|
|
3332
|
+
// int32-register trick — these are plain storage locals.
|
|
3333
|
+
if rust_type.is_narrow_int() {
|
|
3334
|
+
let escaped = Self::escape_ident(name.as_ref());
|
|
3335
|
+
let native_val = self.emit_coerced_native(value, &rust_type)?;
|
|
3336
|
+
if self.refcell_wrapped_vars.contains(name.as_ref()) {
|
|
3337
|
+
return Ok(format!(
|
|
3338
|
+
"{{ let _t = {}; *{}.borrow_mut() = _t; }}",
|
|
3339
|
+
native_val, escaped
|
|
3340
|
+
));
|
|
3341
|
+
}
|
|
3342
|
+
return Ok(format!("{} = {}", escaped, native_val));
|
|
3343
|
+
}
|
|
3344
|
+
if matches!(
|
|
3345
|
+
rust_type,
|
|
3346
|
+
RustType::F64 | RustType::Fixed | RustType::Bool | RustType::String
|
|
3347
|
+
) {
|
|
2983
3348
|
if let Expr::Index { object, index, .. } = value.as_ref() {
|
|
2984
3349
|
if let Expr::Ident { name: arr_name, .. } = object.as_ref() {
|
|
2985
3350
|
if self.index_in_bounds(index, arr_name.as_ref()) {
|
|
@@ -3029,6 +3394,47 @@ impl Codegen {
|
|
|
3029
3394
|
}
|
|
3030
3395
|
// `s += x` etc. in statement position: native f64 compound op, no boxed return.
|
|
3031
3396
|
Expr::CompoundAssign { name, op, value, .. } => {
|
|
3397
|
+
// Narrow int compound-assign (`hp -= 3`): compute in `f64` (JS Number semantics),
|
|
3398
|
+
// truncate-cast back to the width — same model as a narrow-int plain assign, no box.
|
|
3399
|
+
if self.type_context.get_type(name.as_ref()).is_narrow_int() {
|
|
3400
|
+
let width = self.type_context.get_type(name.as_ref()).to_rust_type_str();
|
|
3401
|
+
let n = Self::escape_ident(name.as_ref()).into_owned();
|
|
3402
|
+
let is_refcell = self.refcell_wrapped_vars.contains(name.as_ref());
|
|
3403
|
+
let (rhs_code, rhs_ty) = self.emit_typed_expr(value)?;
|
|
3404
|
+
let rhs_f64 = if rhs_ty == RustType::F64 {
|
|
3405
|
+
rhs_code
|
|
3406
|
+
} else if rhs_ty.is_integer_scalar() {
|
|
3407
|
+
format!("(({}) as f64)", rhs_code)
|
|
3408
|
+
} else {
|
|
3409
|
+
let rhs_val = if rhs_ty.is_native() {
|
|
3410
|
+
rhs_ty.to_value_expr(&rhs_code)
|
|
3411
|
+
} else {
|
|
3412
|
+
rhs_code
|
|
3413
|
+
};
|
|
3414
|
+
format!("(match &({}) {{ Value::Number(n) => *n, v => panic!(\"compound assign: expected number, got {{:?}}\", v) }})", rhs_val)
|
|
3415
|
+
};
|
|
3416
|
+
let op_str = match op {
|
|
3417
|
+
CompoundOp::Add => "+",
|
|
3418
|
+
CompoundOp::Sub => "-",
|
|
3419
|
+
CompoundOp::Mul => "*",
|
|
3420
|
+
CompoundOp::Div => "/",
|
|
3421
|
+
CompoundOp::Mod => "%",
|
|
3422
|
+
};
|
|
3423
|
+
let read = if is_refcell {
|
|
3424
|
+
format!("(*{}.borrow())", n)
|
|
3425
|
+
} else {
|
|
3426
|
+
n.clone()
|
|
3427
|
+
};
|
|
3428
|
+
let new_val =
|
|
3429
|
+
format!("((({}) as f64) {} {}) as {}", read, op_str, rhs_f64, width);
|
|
3430
|
+
if is_refcell {
|
|
3431
|
+
return Ok(format!(
|
|
3432
|
+
"{{ let _t = {}; *{}.borrow_mut() = _t; }}",
|
|
3433
|
+
new_val, n
|
|
3434
|
+
));
|
|
3435
|
+
}
|
|
3436
|
+
return Ok(format!("{} = {}", n, new_val));
|
|
3437
|
+
}
|
|
3032
3438
|
if self.type_context.get_type(name.as_ref()) == RustType::F64 {
|
|
3033
3439
|
let n = Self::escape_ident(name.as_ref());
|
|
3034
3440
|
let is_refcell = self.refcell_wrapped_vars.contains(name.as_ref());
|
|
@@ -5057,10 +5463,10 @@ impl Codegen {
|
|
|
5057
5463
|
// re-boxing each element to `Value::Number` for the loop body.
|
|
5058
5464
|
self.writeln("Value::NumberArray(ref _arr) => {");
|
|
5059
5465
|
self.indent += 1;
|
|
5060
|
-
self.writeln("for _v in _arr.borrow().
|
|
5466
|
+
self.writeln("for _v in _arr.borrow().to_values() {");
|
|
5061
5467
|
self.indent += 1;
|
|
5062
5468
|
self.writeln(&format!(
|
|
5063
|
-
"let {} =
|
|
5469
|
+
"let {} = _v;",
|
|
5064
5470
|
Self::escape_ident(name.as_ref())
|
|
5065
5471
|
));
|
|
5066
5472
|
self.emit_statement(body)?;
|
|
@@ -5843,14 +6249,15 @@ impl Codegen {
|
|
|
5843
6249
|
sibling_escaped, sibling_escaped
|
|
5844
6250
|
));
|
|
5845
6251
|
}
|
|
5846
|
-
// Extract just the parameter names
|
|
6252
|
+
// Extract just the parameter names for capture bookkeeping (typed params get
|
|
6253
|
+
// native shadows below — the M1 block)
|
|
5847
6254
|
let current_param_names: Vec<String> = params
|
|
5848
6255
|
.iter()
|
|
5849
6256
|
.flat_map(|p| p.bound_names())
|
|
5850
6257
|
.map(|n| n.to_string())
|
|
5851
6258
|
.collect();
|
|
5852
6259
|
let formal_span = *span;
|
|
5853
|
-
// M1 (keystone
|
|
6260
|
+
// M1 (keystone; default-on via native_opts_enabled): a typed scalar param
|
|
5854
6261
|
// normally arrives boxed (`args.get(i).cloned()`), which poisons native math in
|
|
5855
6262
|
// the body (e.g. `i*N+k` boxes). Bind a *native shadow* — coerce once to f64/
|
|
5856
6263
|
// bool/String — so the body lowers it like a native local. Conservative: only
|
|
@@ -5893,7 +6300,9 @@ impl Codegen {
|
|
|
5893
6300
|
// `arr[oob]`→NaN) is unreachable for sound inputs but never panics.
|
|
5894
6301
|
self.writeln(&format!(
|
|
5895
6302
|
"let mut {}: Vec<f64> = match args.get({}) {{ \
|
|
5896
|
-
Some(Value::NumberArray(a)) => a.borrow()
|
|
6303
|
+
Some(Value::NumberArray(a)) => match &*a.borrow() {{ \
|
|
6304
|
+
tishlang_runtime::NumArrayBacking::Packed(p) => p.clone(), \
|
|
6305
|
+
tishlang_runtime::NumArrayBacking::Boxed(b) => b.iter().map(|v| match v {{ Value::Number(n) => *n, _ => f64::NAN }}).collect() }}, \
|
|
5897
6306
|
Some(Value::Array(arr)) => arr.borrow().iter().map(|v| match v {{ Value::Number(n) => *n, _ => f64::NAN }}).collect(), \
|
|
5898
6307
|
_ => Vec::new() }};",
|
|
5899
6308
|
Self::escape_ident(tp.name.as_ref()),
|
|
@@ -7261,7 +7670,7 @@ impl Codegen {
|
|
|
7261
7670
|
}
|
|
7262
7671
|
"reduce" => {
|
|
7263
7672
|
let initial = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7264
|
-
// Fused reduce (
|
|
7673
|
+
// Fused reduce (default-on via native_opts_enabled): `arr.reduce((acc, x) => acc OP x, init)`
|
|
7265
7674
|
// with a plain binop of the two params → a native fold using the SAME
|
|
7266
7675
|
// runtime Value op the closure body would, eliminating the per-element
|
|
7267
7676
|
// `value_call`. Sound (identical Value semantics, incl. string `+`).
|
|
@@ -7600,10 +8009,11 @@ impl Codegen {
|
|
|
7600
8009
|
tishlang_runtime::get_prop(&o, {}) }} }}",
|
|
7601
8010
|
obj, key
|
|
7602
8011
|
)
|
|
7603
|
-
} else if ic_eligible {
|
|
8012
|
+
} else if ic_eligible && self.emit_mode != crate::NativeEmitMode::Gba {
|
|
7604
8013
|
// Per-site inline cache: a block-scoped `static` cell unique to this member-read
|
|
7605
8014
|
// site (#179). Caches (shape, slot) so repeat reads skip the key scan. Behaviour is
|
|
7606
8015
|
// identical to `get_prop` — non-cacheable objects fall through inside `get_prop_ic`.
|
|
8016
|
+
// Gba: `PropIC` uses atomics (unavailable on thumbv4t) — use the plain path.
|
|
7607
8017
|
format!(
|
|
7608
8018
|
"{{ static IC: tishlang_runtime::PropIC = tishlang_runtime::PropIC::new(); \
|
|
7609
8019
|
tishlang_runtime::get_prop_ic(&{}, {}, &IC) }}",
|
|
@@ -7730,7 +8140,7 @@ impl Codegen {
|
|
|
7730
8140
|
match prop {
|
|
7731
8141
|
ObjectProp::KeyValue(k, v, _) => {
|
|
7732
8142
|
let val = self.emit_expr(v)?;
|
|
7733
|
-
let key =
|
|
8143
|
+
let key = self.cached_object_key(k.as_ref());
|
|
7734
8144
|
if self.should_clone(v) {
|
|
7735
8145
|
parts.push(format!("_obj.insert({}, ({}).clone());", key, val));
|
|
7736
8146
|
} else {
|
|
@@ -7753,7 +8163,7 @@ impl Codegen {
|
|
|
7753
8163
|
init = format!("let mut _obj: PropMap = PropMap::with_capacity({});", literal_keys);
|
|
7754
8164
|
}
|
|
7755
8165
|
format!(
|
|
7756
|
-
"{{ {} {} Value::Object(VmRef::new(ObjectData {{ strings: _obj, symbols: None }})) }}",
|
|
8166
|
+
"{{ {} {} Value::Object(VmRef::new(ObjectData {{ strings: _obj, symbols: None, frozen: false }})) }}",
|
|
7757
8167
|
init,
|
|
7758
8168
|
parts.join(" ")
|
|
7759
8169
|
)
|
|
@@ -7762,7 +8172,7 @@ impl Codegen {
|
|
|
7762
8172
|
for prop in props {
|
|
7763
8173
|
if let ObjectProp::KeyValue(k, v, _) = prop {
|
|
7764
8174
|
let val = self.emit_expr(v)?;
|
|
7765
|
-
let key =
|
|
8175
|
+
let key = self.cached_object_key(k.as_ref());
|
|
7766
8176
|
if self.should_clone(v) {
|
|
7767
8177
|
parts.push(format!("({}, ({}).clone())", key, val));
|
|
7768
8178
|
} else {
|
|
@@ -8258,6 +8668,27 @@ impl Codegen {
|
|
|
8258
8668
|
)
|
|
8259
8669
|
}
|
|
8260
8670
|
Expr::NativeModuleLoad { spec, export_name, .. } => {
|
|
8671
|
+
// A scheme import (`asset:`, or any project-declared scheme) binds to an i32 handle
|
|
8672
|
+
// — its per-scheme index, registered into a runtime arena by the generated
|
|
8673
|
+
// `agb_main`. Only valid in Gba mode; no other target bakes scheme files into a ROM.
|
|
8674
|
+
if crate::schemes::is_scheme_import(spec.as_ref()) {
|
|
8675
|
+
if self.emit_mode != crate::NativeEmitMode::Gba {
|
|
8676
|
+
return Err(CompileError {
|
|
8677
|
+
message: format!(
|
|
8678
|
+
"scheme import '{}' requires --target gba",
|
|
8679
|
+
spec.as_ref()
|
|
8680
|
+
),
|
|
8681
|
+
span: None,
|
|
8682
|
+
});
|
|
8683
|
+
}
|
|
8684
|
+
let handle = self.scheme_handle_of(spec.as_ref()).ok_or_else(|| {
|
|
8685
|
+
CompileError {
|
|
8686
|
+
message: format!("internal: scheme import '{}' not collected", spec.as_ref()),
|
|
8687
|
+
span: None,
|
|
8688
|
+
}
|
|
8689
|
+
})?;
|
|
8690
|
+
return Ok(format!("Value::Number({} as f64)", handle));
|
|
8691
|
+
}
|
|
8261
8692
|
self.native_module_rust_init(spec.as_ref(), export_name.as_ref())
|
|
8262
8693
|
.ok_or_else(|| CompileError {
|
|
8263
8694
|
message: if crate::resolve::is_builtin_native_spec(spec.as_ref()) {
|
|
@@ -9519,6 +9950,187 @@ impl Codegen {
|
|
|
9519
9950
|
}
|
|
9520
9951
|
}
|
|
9521
9952
|
|
|
9953
|
+
/// Every name BOUND anywhere inside `s` — `let`/`const` (incl. destructuring), fn/arrow
|
|
9954
|
+
/// params, `for…of`/`for…in` loop vars, `catch` params, and nested `FunDecl` names —
|
|
9955
|
+
/// descending into nested arrow and function bodies. Unlike `collect_local_var_names`
|
|
9956
|
+
/// (which skips arrow bodies) or `collect_binding_names` (which skips `let`/`const`),
|
|
9957
|
+
/// this is the complete binder set of the subtree. Used by the arrow emitter's implicit
|
|
9958
|
+
/// env-capture fallback so a NESTED closure's own bindings are never mistaken for capture
|
|
9959
|
+
/// requirements of the closure being emitted (#467: `(a)=>(b)=>(c)=> …` E0425'd because
|
|
9960
|
+
/// the middle arrow tried to capture the innermost arrow's param `c`).
|
|
9961
|
+
fn collect_bound_names_deep(s: &Statement, out: &mut HashSet<String>) {
|
|
9962
|
+
use Statement::*;
|
|
9963
|
+
let ce = Self::collect_bound_names_deep_expr;
|
|
9964
|
+
match s {
|
|
9965
|
+
VarDecl { name, init, .. } => {
|
|
9966
|
+
out.insert(name.to_string());
|
|
9967
|
+
if let Some(i) = init {
|
|
9968
|
+
ce(i, out);
|
|
9969
|
+
}
|
|
9970
|
+
}
|
|
9971
|
+
VarDeclDestructure { pattern, init, .. } => {
|
|
9972
|
+
Self::collect_destruct_names(pattern, out);
|
|
9973
|
+
ce(init, out);
|
|
9974
|
+
}
|
|
9975
|
+
FunDecl { name, params, body, .. } => {
|
|
9976
|
+
out.insert(name.to_string());
|
|
9977
|
+
for p in params {
|
|
9978
|
+
for n in p.bound_names() {
|
|
9979
|
+
out.insert(n.to_string());
|
|
9980
|
+
}
|
|
9981
|
+
}
|
|
9982
|
+
Self::collect_bound_names_deep(body, out);
|
|
9983
|
+
}
|
|
9984
|
+
ForOf { name, iterable, body, .. } => {
|
|
9985
|
+
out.insert(name.to_string());
|
|
9986
|
+
ce(iterable, out);
|
|
9987
|
+
Self::collect_bound_names_deep(body, out);
|
|
9988
|
+
}
|
|
9989
|
+
ForIn { name, object, body, .. } => {
|
|
9990
|
+
out.insert(name.to_string());
|
|
9991
|
+
ce(object, out);
|
|
9992
|
+
Self::collect_bound_names_deep(body, out);
|
|
9993
|
+
}
|
|
9994
|
+
Try { body, catch_param, catch_body, finally_body, .. } => {
|
|
9995
|
+
if let Some(cp) = catch_param {
|
|
9996
|
+
out.insert(cp.to_string());
|
|
9997
|
+
}
|
|
9998
|
+
Self::collect_bound_names_deep(body, out);
|
|
9999
|
+
if let Some(c) = catch_body {
|
|
10000
|
+
Self::collect_bound_names_deep(c, out);
|
|
10001
|
+
}
|
|
10002
|
+
if let Some(f) = finally_body {
|
|
10003
|
+
Self::collect_bound_names_deep(f, out);
|
|
10004
|
+
}
|
|
10005
|
+
}
|
|
10006
|
+
Block { statements, .. } | Multi { statements, .. } => {
|
|
10007
|
+
statements.iter().for_each(|x| Self::collect_bound_names_deep(x, out))
|
|
10008
|
+
}
|
|
10009
|
+
If { cond, then_branch, else_branch, .. } => {
|
|
10010
|
+
ce(cond, out);
|
|
10011
|
+
Self::collect_bound_names_deep(then_branch, out);
|
|
10012
|
+
if let Some(eb) = else_branch {
|
|
10013
|
+
Self::collect_bound_names_deep(eb, out);
|
|
10014
|
+
}
|
|
10015
|
+
}
|
|
10016
|
+
While { cond, body, .. } | DoWhile { body, cond, .. } => {
|
|
10017
|
+
ce(cond, out);
|
|
10018
|
+
Self::collect_bound_names_deep(body, out);
|
|
10019
|
+
}
|
|
10020
|
+
For { init, cond, update, body, .. } => {
|
|
10021
|
+
if let Some(i) = init {
|
|
10022
|
+
Self::collect_bound_names_deep(i, out);
|
|
10023
|
+
}
|
|
10024
|
+
if let Some(c) = cond {
|
|
10025
|
+
ce(c, out);
|
|
10026
|
+
}
|
|
10027
|
+
if let Some(u) = update {
|
|
10028
|
+
ce(u, out);
|
|
10029
|
+
}
|
|
10030
|
+
Self::collect_bound_names_deep(body, out);
|
|
10031
|
+
}
|
|
10032
|
+
ExprStmt { expr, .. } => ce(expr, out),
|
|
10033
|
+
Return { value: Some(v), .. } => ce(v, out),
|
|
10034
|
+
Throw { value, .. } => ce(value, out),
|
|
10035
|
+
Switch { expr, cases, default_body, .. } => {
|
|
10036
|
+
ce(expr, out);
|
|
10037
|
+
for (g, b) in cases {
|
|
10038
|
+
if let Some(ge) = g {
|
|
10039
|
+
ce(ge, out);
|
|
10040
|
+
}
|
|
10041
|
+
b.iter().for_each(|x| Self::collect_bound_names_deep(x, out));
|
|
10042
|
+
}
|
|
10043
|
+
if let Some(d) = default_body {
|
|
10044
|
+
d.iter().for_each(|x| Self::collect_bound_names_deep(x, out));
|
|
10045
|
+
}
|
|
10046
|
+
}
|
|
10047
|
+
Export { declaration, .. } => {
|
|
10048
|
+
if let tishlang_ast::ExportDeclaration::Named(inner) = declaration.as_ref() {
|
|
10049
|
+
Self::collect_bound_names_deep(inner, out);
|
|
10050
|
+
}
|
|
10051
|
+
}
|
|
10052
|
+
_ => {}
|
|
10053
|
+
}
|
|
10054
|
+
}
|
|
10055
|
+
|
|
10056
|
+
/// Expression side of [`Self::collect_bound_names_deep`]: recurse every child; an
|
|
10057
|
+
/// `ArrowFunction` contributes its params and descends into its body.
|
|
10058
|
+
fn collect_bound_names_deep_expr(e: &Expr, out: &mut HashSet<String>) {
|
|
10059
|
+
let ce = Self::collect_bound_names_deep_expr;
|
|
10060
|
+
match e {
|
|
10061
|
+
Expr::ArrowFunction { params, body, .. } => {
|
|
10062
|
+
for p in params {
|
|
10063
|
+
for n in p.bound_names() {
|
|
10064
|
+
out.insert(n.to_string());
|
|
10065
|
+
}
|
|
10066
|
+
}
|
|
10067
|
+
match body {
|
|
10068
|
+
ArrowBody::Expr(x) => ce(x, out),
|
|
10069
|
+
ArrowBody::Block(b) => Self::collect_bound_names_deep(b, out),
|
|
10070
|
+
}
|
|
10071
|
+
}
|
|
10072
|
+
Expr::Call { callee, args, .. } | Expr::New { callee, args, .. } => {
|
|
10073
|
+
ce(callee, out);
|
|
10074
|
+
for a in args {
|
|
10075
|
+
if let CallArg::Expr(x) | CallArg::Spread(x) = a {
|
|
10076
|
+
ce(x, out);
|
|
10077
|
+
}
|
|
10078
|
+
}
|
|
10079
|
+
}
|
|
10080
|
+
Expr::Binary { left, right, .. } | Expr::NullishCoalesce { left, right, .. } => {
|
|
10081
|
+
ce(left, out);
|
|
10082
|
+
ce(right, out);
|
|
10083
|
+
}
|
|
10084
|
+
Expr::Unary { operand, .. } => ce(operand, out),
|
|
10085
|
+
Expr::TypeOf { operand, .. } => ce(operand, out),
|
|
10086
|
+
Expr::Delete { target, .. } => ce(target, out),
|
|
10087
|
+
Expr::Await { operand, .. } => ce(operand, out),
|
|
10088
|
+
Expr::Conditional { cond, then_branch, else_branch, .. } => {
|
|
10089
|
+
ce(cond, out);
|
|
10090
|
+
ce(then_branch, out);
|
|
10091
|
+
ce(else_branch, out);
|
|
10092
|
+
}
|
|
10093
|
+
Expr::Index { object, index, .. } => {
|
|
10094
|
+
ce(object, out);
|
|
10095
|
+
ce(index, out);
|
|
10096
|
+
}
|
|
10097
|
+
Expr::IndexAssign { object, index, value, .. } => {
|
|
10098
|
+
ce(object, out);
|
|
10099
|
+
ce(index, out);
|
|
10100
|
+
ce(value, out);
|
|
10101
|
+
}
|
|
10102
|
+
Expr::Member { object, prop, .. } => {
|
|
10103
|
+
ce(object, out);
|
|
10104
|
+
if let MemberProp::Expr(pe) = prop {
|
|
10105
|
+
ce(pe, out);
|
|
10106
|
+
}
|
|
10107
|
+
}
|
|
10108
|
+
Expr::MemberAssign { object, value, .. } => {
|
|
10109
|
+
ce(object, out);
|
|
10110
|
+
ce(value, out);
|
|
10111
|
+
}
|
|
10112
|
+
Expr::Assign { value, .. }
|
|
10113
|
+
| Expr::CompoundAssign { value, .. }
|
|
10114
|
+
| Expr::LogicalAssign { value, .. } => ce(value, out),
|
|
10115
|
+
Expr::Array { elements, .. } => {
|
|
10116
|
+
for el in elements {
|
|
10117
|
+
match el {
|
|
10118
|
+
ArrayElement::Expr(x) | ArrayElement::Spread(x) => ce(x, out),
|
|
10119
|
+
}
|
|
10120
|
+
}
|
|
10121
|
+
}
|
|
10122
|
+
Expr::Object { props, .. } => {
|
|
10123
|
+
for pr in props {
|
|
10124
|
+
match pr {
|
|
10125
|
+
ObjectProp::KeyValue(_, x, _) | ObjectProp::Spread(x) => ce(x, out),
|
|
10126
|
+
}
|
|
10127
|
+
}
|
|
10128
|
+
}
|
|
10129
|
+
Expr::TemplateLiteral { exprs, .. } => exprs.iter().for_each(|x| ce(x, out)),
|
|
10130
|
+
_ => {}
|
|
10131
|
+
}
|
|
10132
|
+
}
|
|
10133
|
+
|
|
9522
10134
|
fn collect_destruct_names(pattern: &DestructPattern, names: &mut HashSet<String>) {
|
|
9523
10135
|
match pattern {
|
|
9524
10136
|
DestructPattern::Array(elements) => {
|
|
@@ -9777,7 +10389,14 @@ impl Codegen {
|
|
|
9777
10389
|
}
|
|
9778
10390
|
}
|
|
9779
10391
|
// Only clone builtins that are actually referenced (clone so outer scope can still use, e.g. process for PORT)
|
|
10392
|
+
// #513: every name on the BUILTINS skip-list below MUST also be clone-hoisted here (or
|
|
10393
|
+
// lower as a constant, like Infinity/NaN). A name skipped by the implicit env-capture
|
|
10394
|
+
// fallback but NOT hoisted is referenced raw inside the `move` closure — which MOVES it
|
|
10395
|
+
// out of the enclosing `Fn` closure's capture (E0507: `serve`/`Boolean` in any nested
|
|
10396
|
+
// value-closure that also captures a local, e.g. a `Promise.spawn(fn() { serve(port, …) })`).
|
|
9780
10397
|
for builtin in &[
|
|
10398
|
+
"Boolean",
|
|
10399
|
+
"serve",
|
|
9781
10400
|
"console",
|
|
9782
10401
|
"Math",
|
|
9783
10402
|
"JSON",
|
|
@@ -9844,6 +10463,20 @@ impl Codegen {
|
|
|
9844
10463
|
.cloned()
|
|
9845
10464
|
.collect();
|
|
9846
10465
|
already_captured.extend(BUILTINS.iter().map(|s| s.to_string()));
|
|
10466
|
+
// #467: names bound INSIDE this body's nested closures (their params and locals) show up
|
|
10467
|
+
// in `referenced` — `collect_referenced_idents` has no binder awareness — but they are not
|
|
10468
|
+
// free variables of THIS closure, and capturing them emits a clone of an ident that does
|
|
10469
|
+
// not exist in the enclosing scope (E0425: `(a)=>(b)=>(c)=> …`'s middle arrow tried to
|
|
10470
|
+
// capture the innermost arrow's own param `c`). Subtract the subtree's full binder set.
|
|
10471
|
+
// Corner accepted: a name that is BOTH bound in a nested closure AND genuinely free at
|
|
10472
|
+
// this level via the implicit fallback (an enclosing-closure local, not an outer param/
|
|
10473
|
+
// var) is skipped too — that fails loudly at compile time (E0425), never silently, and
|
|
10474
|
+
// the primary outer_params/outer_vars capture paths are unaffected by this subtraction.
|
|
10475
|
+
let mut nested_bound = HashSet::new();
|
|
10476
|
+
match body {
|
|
10477
|
+
ArrowBody::Expr(e) => Self::collect_bound_names_deep_expr(e, &mut nested_bound),
|
|
10478
|
+
ArrowBody::Block(s) => Self::collect_bound_names_deep(s, &mut nested_bound),
|
|
10479
|
+
}
|
|
9847
10480
|
let implicit_env_captures: Vec<String> = if self.value_fn_depth > 0 {
|
|
9848
10481
|
referenced
|
|
9849
10482
|
.iter()
|
|
@@ -9851,6 +10484,13 @@ impl Codegen {
|
|
|
9851
10484
|
!param_names.contains(name.as_str())
|
|
9852
10485
|
&& !local_var_names.contains(name.as_str())
|
|
9853
10486
|
&& !already_captured.contains(name.as_str())
|
|
10487
|
+
&& !nested_bound.contains(name.as_str())
|
|
10488
|
+
// #467: a native numeric GLOBAL (#176 TLS-lowered, e.g. `G_BASE`) has no
|
|
10489
|
+
// boxed binding anywhere — the body reads it via the thread-local — so a
|
|
10490
|
+
// capture emit here references a nonexistent ident (E0425 for any nested
|
|
10491
|
+
// arrow whose grandparent-scope var was TLS-lowered). Same exclusion the
|
|
10492
|
+
// FunDecl closure path and this fn's own outer_vars filter already apply.
|
|
10493
|
+
&& !self.native_numeric_globals.contains_key(name.as_str())
|
|
9854
10494
|
})
|
|
9855
10495
|
.cloned()
|
|
9856
10496
|
.collect()
|
|
@@ -10264,7 +10904,7 @@ impl Codegen {
|
|
|
10264
10904
|
}
|
|
10265
10905
|
}
|
|
10266
10906
|
|
|
10267
|
-
// Native typed-array HOFs (
|
|
10907
|
+
// Native typed-array HOFs (default-on via native_opts_enabled): `xs.reduce/map/filter/some/every(<arrow>)`
|
|
10268
10908
|
// whose native result type matches this binding's target → emit the iterator chain
|
|
10269
10909
|
// directly, with NO box/unbox round-trip (the per-element `value_call` is gone too).
|
|
10270
10910
|
if let Expr::Call { callee, args, .. } = expr {
|
|
@@ -10286,6 +10926,18 @@ impl Codegen {
|
|
|
10286
10926
|
if &typed_ty == target_type {
|
|
10287
10927
|
return Ok(typed_code);
|
|
10288
10928
|
}
|
|
10929
|
+
// Integer-scalar target from a native numeric source (`let hp: u8 = 100`,
|
|
10930
|
+
// `hp: dmg` where dmg is f64): truncate-cast directly instead of round-tripping
|
|
10931
|
+
// through `Value::Number`. Covers narrow widths and `i32` uniformly.
|
|
10932
|
+
if target_type.is_integer_scalar()
|
|
10933
|
+
&& (typed_ty == RustType::F64 || typed_ty.is_integer_scalar())
|
|
10934
|
+
{
|
|
10935
|
+
return Ok(format!("({}) as {}", typed_code, target_type.to_rust_type_str()));
|
|
10936
|
+
}
|
|
10937
|
+
// `f64` target from an integer-scalar source: widen directly.
|
|
10938
|
+
if *target_type == RustType::F64 && typed_ty.is_integer_scalar() {
|
|
10939
|
+
return Ok(format!("({}) as f64", typed_code));
|
|
10940
|
+
}
|
|
10289
10941
|
}
|
|
10290
10942
|
|
|
10291
10943
|
// Fall back to emit_expr + conversion
|
|
@@ -11717,6 +12369,37 @@ impl Codegen {
|
|
|
11717
12369
|
Ok(Some(assign))
|
|
11718
12370
|
}
|
|
11719
12371
|
|
|
12372
|
+
/// Emit `value` as a native expression of type `target_ty`, coercing without a
|
|
12373
|
+
/// `Value` round-trip where the conversion is a cheap numeric cast. Same-typed RHS
|
|
12374
|
+
/// passes through; f64↔i32 casts directly; everything else boxes to `Value` then
|
|
12375
|
+
/// converts (correct for Fixed/String/Bool/Value fields, at one box per store).
|
|
12376
|
+
fn emit_coerced_native(
|
|
12377
|
+
&mut self,
|
|
12378
|
+
value: &Expr,
|
|
12379
|
+
target_ty: &RustType,
|
|
12380
|
+
) -> Result<String, CompileError> {
|
|
12381
|
+
let (val_code, val_ty) = self.emit_typed_expr(value)?;
|
|
12382
|
+
if val_ty == *target_ty {
|
|
12383
|
+
return Ok(val_code);
|
|
12384
|
+
}
|
|
12385
|
+
// Cheap numeric coercions that avoid a `Value` round-trip: an integer scalar target
|
|
12386
|
+
// truncate-casts a numeric RHS (`(v) as u8`), and an `f64` target widens an integer
|
|
12387
|
+
// scalar RHS (`(v) as f64`). Covers i32↔f64 and every narrow width in one shape.
|
|
12388
|
+
if target_ty.is_integer_scalar() && (val_ty == RustType::F64 || val_ty.is_integer_scalar())
|
|
12389
|
+
{
|
|
12390
|
+
return Ok(format!("({}) as {}", val_code, target_ty.to_rust_type_str()));
|
|
12391
|
+
}
|
|
12392
|
+
if *target_ty == RustType::F64 && val_ty.is_integer_scalar() {
|
|
12393
|
+
return Ok(format!("({}) as f64", val_code));
|
|
12394
|
+
}
|
|
12395
|
+
let boxed = if val_ty.is_native() {
|
|
12396
|
+
val_ty.to_value_expr(&val_code)
|
|
12397
|
+
} else {
|
|
12398
|
+
val_code
|
|
12399
|
+
};
|
|
12400
|
+
Ok(target_ty.from_value_expr(&boxed))
|
|
12401
|
+
}
|
|
12402
|
+
|
|
11720
12403
|
/// `for (i=0; i<r; i++) { arr[i] = arr[i+1] }` → `arr.copy_within(1..(r+1), 0)` (fannkuch perm1 rotation).
|
|
11721
12404
|
fn try_emit_vec_copy_within_shift_for(
|
|
11722
12405
|
&mut self,
|
|
@@ -12357,6 +13040,24 @@ impl Codegen {
|
|
|
12357
13040
|
}
|
|
12358
13041
|
}
|
|
12359
13042
|
|
|
13043
|
+
/// A numeric literal folded to a compile-time `Fixed` (Q24.8) constant, or `None` if `e`
|
|
13044
|
+
/// isn't a plain number literal. Lets a `fixed ⊕ <literal>` op (`px + 1.5`, `hp * 2`) stay
|
|
13045
|
+
/// native `Num<i32,8>` arithmetic instead of boxing the literal side through `f64`. Uses the
|
|
13046
|
+
/// same truncating `(n * 256) as i32` conversion as the dynamic `fixed`↔`Value` boundary, so
|
|
13047
|
+
/// the constant is bit-identical to the boxed path.
|
|
13048
|
+
fn fixed_literal_of(e: &Expr) -> Option<String> {
|
|
13049
|
+
if let Expr::Literal {
|
|
13050
|
+
value: Literal::Number(n),
|
|
13051
|
+
..
|
|
13052
|
+
} = e
|
|
13053
|
+
{
|
|
13054
|
+
let raw = (n * 256.0) as i32;
|
|
13055
|
+
Some(format!("tishlang_runtime::Fixed::from_raw({}i32)", raw))
|
|
13056
|
+
} else {
|
|
13057
|
+
None
|
|
13058
|
+
}
|
|
13059
|
+
}
|
|
13060
|
+
|
|
12360
13061
|
/// Names of `f64` locals provably integer-bounded within `(-2^53, 2^53)` across the whole
|
|
12361
13062
|
/// program. Seeds from integer-literal initializers and literal-bounded `for` counters, then
|
|
12362
13063
|
/// runs a join fixpoint over reassignments: a local keeps a bound only if its init and EVERY
|
|
@@ -14277,6 +14978,9 @@ impl Codegen {
|
|
|
14277
14978
|
Expr::Member { optional: false, .. } | Expr::Index { optional: false, .. } => {
|
|
14278
14979
|
found = true
|
|
14279
14980
|
}
|
|
14981
|
+
// #437: a write to a frozen object throws a catchable TypeError (set_prop parks
|
|
14982
|
+
// it), so an assignment statement must checkpoint like a read/call does.
|
|
14983
|
+
Expr::MemberAssign { .. } | Expr::IndexAssign { .. } => found = true,
|
|
14280
14984
|
_ => {}
|
|
14281
14985
|
}
|
|
14282
14986
|
});
|
|
@@ -14853,7 +15557,7 @@ impl Codegen {
|
|
|
14853
15557
|
|
|
14854
15558
|
fn module_const_f64_array_as_value(static_name: &str) -> String {
|
|
14855
15559
|
format!(
|
|
14856
|
-
"Value::NumberArray(VmRef::new({}.iter().copied().collect::<Vec<f64>>()))",
|
|
15560
|
+
"Value::NumberArray(VmRef::new(tishlang_runtime::NumArrayBacking::Packed({}.iter().copied().collect::<Vec<f64>>())))",
|
|
14857
15561
|
static_name
|
|
14858
15562
|
)
|
|
14859
15563
|
}
|
|
@@ -16635,6 +17339,14 @@ impl Codegen {
|
|
|
16635
17339
|
code
|
|
16636
17340
|
} else if ty == RustType::Value {
|
|
16637
17341
|
RustType::F64.from_value_expr(&code)
|
|
17342
|
+
} else if ty.is_integer_scalar() {
|
|
17343
|
+
// An integer scalar (`i32` or a narrow width) is exact in `f64` — widen it, rather
|
|
17344
|
+
// than emitting the raw integer where an `f64` is required (e.g. `return total;`
|
|
17345
|
+
// from a native-vec `-> f64` fn whose body yields an `i32` local).
|
|
17346
|
+
format!("(({}) as f64)", code)
|
|
17347
|
+
} else if ty == RustType::Fixed {
|
|
17348
|
+
// Q24.8 → f64 (exact): raw / 256.
|
|
17349
|
+
format!("(({}).to_raw() as f64 / 256.0)", code)
|
|
16638
17350
|
} else {
|
|
16639
17351
|
code
|
|
16640
17352
|
})
|
|
@@ -17192,6 +17904,7 @@ impl Codegen {
|
|
|
17192
17904
|
name,
|
|
17193
17905
|
params,
|
|
17194
17906
|
rest_param: None,
|
|
17907
|
+
return_type,
|
|
17195
17908
|
body,
|
|
17196
17909
|
..
|
|
17197
17910
|
} = s
|
|
@@ -17199,6 +17912,13 @@ impl Codegen {
|
|
|
17199
17912
|
if nonnumeric.contains(name.as_ref()) {
|
|
17200
17913
|
continue;
|
|
17201
17914
|
}
|
|
17915
|
+
// The native-vec `_nv` lowering only expresses `f64`/`Vec<f64>`/unit returns
|
|
17916
|
+
// (`VecRetKind`). A `fixed`-returning fn would be forced to `-> f64` while its
|
|
17917
|
+
// body yields `Num<i32,8>` — a hard type error. Keep such fns on the (correct,
|
|
17918
|
+
// boxed) dynamic path until `VecRetKind` learns `Fixed`.
|
|
17919
|
+
if Self::ann_is_simple(return_type.as_ref(), "fixed") {
|
|
17920
|
+
continue;
|
|
17921
|
+
}
|
|
17202
17922
|
if let Some(sig) = Self::infer_vec_fn_sig(params, body) {
|
|
17203
17923
|
let has_array_param = sig
|
|
17204
17924
|
.params
|
|
@@ -17237,6 +17957,7 @@ impl Codegen {
|
|
|
17237
17957
|
name,
|
|
17238
17958
|
params,
|
|
17239
17959
|
rest_param: None,
|
|
17960
|
+
return_type,
|
|
17240
17961
|
body,
|
|
17241
17962
|
..
|
|
17242
17963
|
} = s
|
|
@@ -17250,6 +17971,11 @@ impl Codegen {
|
|
|
17250
17971
|
if nonnumeric.contains(name.as_ref()) {
|
|
17251
17972
|
continue;
|
|
17252
17973
|
}
|
|
17974
|
+
// A `fixed` return can't be expressed by `VecRetKind` (see the initial pass) — the
|
|
17975
|
+
// forwarding fixpoint must skip it too, or it re-adds the fn via `body_uses_local_vec_ops`.
|
|
17976
|
+
if Self::ann_is_simple(return_type.as_ref(), "fixed") {
|
|
17977
|
+
continue;
|
|
17978
|
+
}
|
|
17253
17979
|
if let Some(sig) = Self::infer_vec_fn_sig_forwarding(params, body, &sigs) {
|
|
17254
17980
|
let has_array_param = sig
|
|
17255
17981
|
.params
|
|
@@ -18698,7 +19424,7 @@ impl Codegen {
|
|
|
18698
19424
|
// ====================================================================================
|
|
18699
19425
|
// #177 (S-E / S-F): interprocedural aggregate (unboxed struct + Vec<Struct>) free fns.
|
|
18700
19426
|
//
|
|
18701
|
-
// The infer front-end (S-0..S-D,
|
|
19427
|
+
// The infer front-end (S-0..S-D, default-on via native_opts_enabled) stamps a struct alias and
|
|
18702
19428
|
// `: alias` / `: alias[]` annotations onto the nbody-shape factory/array/operator fns iff a
|
|
18703
19429
|
// whole-program candidacy predicate holds (monomorphic all-f64 struct, no `===`/escape/
|
|
18704
19430
|
// reshape, write-permitting field ops). Here we consume that: emit each such fn as a native
|
|
@@ -18712,7 +19438,7 @@ impl Codegen {
|
|
|
18712
19438
|
// identical to flag-off). So we never half-wire the feature or miscompile.
|
|
18713
19439
|
// ====================================================================================
|
|
18714
19440
|
|
|
18715
|
-
// ── #178: recursive-struct native lowering (
|
|
19441
|
+
// ── #178: recursive-struct native lowering (default-on via native_opts_enabled) ─────────
|
|
18716
19442
|
// Infers a recursive struct shape STRUCTURALLY (no identifier matching) from object literals
|
|
18717
19443
|
// whose fields are scalars or recursive self-references, and emits native builders
|
|
18718
19444
|
// (`name_rec(..) -> TishStruct_X`) + consumers (`name_rec(&TishStruct_X) -> f64`) over
|
|
@@ -21377,17 +22103,39 @@ impl Codegen {
|
|
|
21377
22103
|
// `emit_int32_operand`, which reads the i32 register directly. So coercing here only
|
|
21378
22104
|
// governs arithmetic/relational reads (e.g. the `h * 16777619` excursion), where the
|
|
21379
22105
|
// operand must be f64 to keep JS Number semantics.
|
|
21380
|
-
|
|
22106
|
+
// A narrow int storage read (`u8` HP, `i16` tile x) promotes the same way — read
|
|
22107
|
+
// as its exact `f64` value so arithmetic keeps JS Number semantics; the truncating
|
|
22108
|
+
// store back to the field happens at the assignment site.
|
|
22109
|
+
let (l, lt) = if lt.is_integer_scalar() {
|
|
21381
22110
|
(format!("(({}) as f64)", l), RustType::F64)
|
|
21382
22111
|
} else {
|
|
21383
22112
|
(l, lt)
|
|
21384
22113
|
};
|
|
21385
|
-
let (r, rt) = if rt
|
|
22114
|
+
let (r, rt) = if rt.is_integer_scalar() {
|
|
21386
22115
|
(format!("(({}) as f64)", r), RustType::F64)
|
|
21387
22116
|
} else {
|
|
21388
22117
|
(r, rt)
|
|
21389
22118
|
};
|
|
21390
22119
|
|
|
22120
|
+
// `fixed ⊕ <numeric-literal>` (`px + 1.5`, `hp * 2`, `y < 100.0`): fold the literal
|
|
22121
|
+
// operand to a compile-time `Fixed` constant so the op lowers to native `Num<i32,8>`
|
|
22122
|
+
// math via the `Fixed⊕Fixed` case below, instead of falling through to the boxed
|
|
22123
|
+
// `ops::*` path. Only a plain literal is folded — a runtime `f64` still boxes (mixing
|
|
22124
|
+
// a runtime f64 with fixed is genuinely lossy and stays on the safe dynamic path).
|
|
22125
|
+
let (l, lt, r, rt) = if lt == RustType::Fixed && rt != RustType::Fixed {
|
|
22126
|
+
match Self::fixed_literal_of(right) {
|
|
22127
|
+
Some(fx) => (l, lt, fx, RustType::Fixed),
|
|
22128
|
+
None => (l, lt, r, rt),
|
|
22129
|
+
}
|
|
22130
|
+
} else if rt == RustType::Fixed && lt != RustType::Fixed {
|
|
22131
|
+
match Self::fixed_literal_of(left) {
|
|
22132
|
+
Some(fx) => (fx, RustType::Fixed, r, rt),
|
|
22133
|
+
None => (l, lt, r, rt),
|
|
22134
|
+
}
|
|
22135
|
+
} else {
|
|
22136
|
+
(l, lt, r, rt)
|
|
22137
|
+
};
|
|
22138
|
+
|
|
21391
22139
|
if let Some(result_ty) = RustType::result_type_of_binop(*op, <, &rt) {
|
|
21392
22140
|
// Bitwise/shift over numbers: lower the *whole* chain in the int32 domain so
|
|
21393
22141
|
// intermediate `to_int32`/`to_uint32`↔`f64` round-trips collapse (the win `>>>`
|
|
@@ -21866,7 +22614,7 @@ impl Codegen {
|
|
|
21866
22614
|
}
|
|
21867
22615
|
}
|
|
21868
22616
|
}
|
|
21869
|
-
// Native typed-array HOFs over a `Vec<f64>` receiver (
|
|
22617
|
+
// Native typed-array HOFs over a `Vec<f64>` receiver (default-on via native_opts_enabled):
|
|
21870
22618
|
// `xs.reduce/map/filter/some/every(<arrow>)` → a direct Rust iterator chain,
|
|
21871
22619
|
// eliminating the per-element `value_call` and all `Value` boxing.
|
|
21872
22620
|
if let Some(res) = self.native_vec_hof_for_call(callee, args)? {
|
|
@@ -22082,16 +22830,25 @@ impl Codegen {
|
|
|
22082
22830
|
None
|
|
22083
22831
|
};
|
|
22084
22832
|
if let Some(nat_op) = nat_op {
|
|
22833
|
+
// #508: a packed `Value::NumberArray` receiver (e.g. `new Float64Array([…])` under
|
|
22834
|
+
// TISH_PACKED_ARRAYS=1) must fold too — the old `if let Value::Array … else init`
|
|
22835
|
+
// silently returned the INIT for it. Its elements are raw f64 by construction, so the
|
|
22836
|
+
// packed arm is the pure-f64 fold with zero boxing; a non-Number init falls back to
|
|
22837
|
+
// the boxed fold over `Value::Number` elements (identical semantics).
|
|
22085
22838
|
return Ok(Some(format!(
|
|
22086
22839
|
"{{ let _init0 = {init}; let _arr = ({obj}).clone(); \
|
|
22087
|
-
|
|
22840
|
+
match _arr {{ Value::Array(ref _a) => {{ let _b = _a.borrow(); \
|
|
22088
22841
|
let mut _accn: f64 = 0.0; let mut _ok = false; \
|
|
22089
22842
|
if let Value::Number(_i0) = &_init0 {{ _accn = *_i0; _ok = true; }} \
|
|
22090
22843
|
if _ok {{ for _el in _b.iter() {{ \
|
|
22091
22844
|
if let Value::Number(_n) = _el {{ _accn {nat_op} *_n; }} else {{ _ok = false; break; }} }} }} \
|
|
22092
22845
|
if _ok {{ Value::Number(_accn) }} \
|
|
22093
22846
|
else {{ let mut _acc = _init0; for _el in _b.iter() {{ let _x = _el.clone(); _acc = {body}; }} _acc }} \
|
|
22094
|
-
}}
|
|
22847
|
+
}}, Value::NumberArray(ref _a) => {{ let _vals = _a.borrow().to_values(); \
|
|
22848
|
+
if let (true, Value::Number(_i0)) = (_a.borrow().as_packed().is_some(), &_init0) {{ let mut _accn: f64 = *_i0; \
|
|
22849
|
+
for _v in &_vals {{ if let Value::Number(_n) = _v {{ _accn {nat_op} *_n; }} }} Value::Number(_accn) }} \
|
|
22850
|
+
else {{ let mut _acc = _init0; for _x in _vals {{ _acc = {body}; }} _acc }} \
|
|
22851
|
+
}}, _ => _init0 }} }}",
|
|
22095
22852
|
init = initial,
|
|
22096
22853
|
obj = obj_expr,
|
|
22097
22854
|
nat_op = nat_op,
|
|
@@ -22099,10 +22856,13 @@ impl Codegen {
|
|
|
22099
22856
|
)));
|
|
22100
22857
|
}
|
|
22101
22858
|
|
|
22859
|
+
// #508: same NumberArray arm for the general (non-arithmetic-op) fold.
|
|
22102
22860
|
Ok(Some(format!(
|
|
22103
22861
|
"{{ let mut _acc = {init}; let _arr = ({obj}).clone(); \
|
|
22104
|
-
|
|
22105
|
-
let _x = _el.clone(); _acc = {body}; }} }}
|
|
22862
|
+
match _arr {{ Value::Array(ref _a) => {{ for _el in _a.borrow().iter() {{ \
|
|
22863
|
+
let _x = _el.clone(); _acc = {body}; }} }} \
|
|
22864
|
+
Value::NumberArray(ref _a) => {{ for _x in _a.borrow().to_values() {{ \
|
|
22865
|
+
_acc = {body}; }} }} _ => {{}} }} _acc }}",
|
|
22106
22866
|
init = initial,
|
|
22107
22867
|
obj = obj_expr,
|
|
22108
22868
|
body = body_code
|
|
@@ -22595,7 +23355,7 @@ impl Codegen {
|
|
|
22595
23355
|
"let mut __out: Vec<f64> = Vec::new(); \
|
|
22596
23356
|
for &__f0 in __na.iter() {{ let mut __f = __f0; {inner} \
|
|
22597
23357
|
let {p}: f64 = __f; __out.push({term}); }} \
|
|
22598
|
-
Value::NumberArray(VmRef::new(__out))",
|
|
23358
|
+
Value::NumberArray(VmRef::new(tishlang_runtime::NumArrayBacking::Packed(__out)))",
|
|
22599
23359
|
inner = inner, p = pesc, term = term
|
|
22600
23360
|
)
|
|
22601
23361
|
}
|
|
@@ -22607,7 +23367,7 @@ impl Codegen {
|
|
|
22607
23367
|
"let mut __out: Vec<f64> = Vec::new(); \
|
|
22608
23368
|
for &__f0 in __na.iter() {{ let mut __f = __f0; {inner} \
|
|
22609
23369
|
let {p}: f64 = __f; if {pred} {{ __out.push({p}); }} }} \
|
|
22610
|
-
Value::NumberArray(VmRef::new(__out))",
|
|
23370
|
+
Value::NumberArray(VmRef::new(tishlang_runtime::NumArrayBacking::Packed(__out)))",
|
|
22611
23371
|
inner = inner, p = pesc, pred = pred
|
|
22612
23372
|
)
|
|
22613
23373
|
}
|
|
@@ -22617,7 +23377,7 @@ impl Codegen {
|
|
|
22617
23377
|
}
|
|
22618
23378
|
|
|
22619
23379
|
/// If `callee(args)` is `<Vec<f64>-ident>.reduce/map/filter/some/every(<arrow>)` and the
|
|
22620
|
-
///
|
|
23380
|
+
/// native opts are enabled (default-on), lower it to a native iterator chain. Shared by
|
|
22621
23381
|
/// `emit_typed_expr` (native sub-expressions) and `emit_native_expr` (typed `let` RHS), so a
|
|
22622
23382
|
/// typed-array HOF lowers natively whether its result flows into arithmetic or a binding.
|
|
22623
23383
|
fn native_vec_hof_for_call(
|
|
@@ -22654,7 +23414,7 @@ impl Codegen {
|
|
|
22654
23414
|
self.try_native_vec_hof(&recv_code, &inner, recv_name.as_ref(), method.as_ref(), args)
|
|
22655
23415
|
}
|
|
22656
23416
|
|
|
22657
|
-
/// Native typed-array HOFs (`
|
|
23417
|
+
/// Native typed-array HOFs (default-on via `native_opts_enabled`): when the receiver is a native `Vec<f64>`
|
|
22658
23418
|
/// (a typed `number[]`), lower `reduce`/`map`/`filter`/`some`/`every` to a direct Rust
|
|
22659
23419
|
/// iterator chain — no per-element `value_call`, no `Value` boxing.
|
|
22660
23420
|
///
|