@tishlang/tish-format 2.38.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-format +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 +812 -47
- 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_fmt/Cargo.toml +1 -1
- package/crates/tish_lsp/Cargo.toml +2 -0
- 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/http_hyper.rs +70 -0
- package/crates/tish_runtime/src/lib.rs +55 -10
- package/crates/tish_runtime/src/ws.rs +94 -21
- 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-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- package/platform/win32-x64/tish-fmt.exe +0 -0
|
@@ -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.
|
|
@@ -1702,6 +1960,7 @@ impl Codegen {
|
|
|
1702
1960
|
"Server" => Some("Value::native(|args: &[Value]| tish_ws_server_construct(args))"),
|
|
1703
1961
|
"wsSend" => Some("Value::native(|args: &[Value]| Value::Bool(tishlang_runtime::ws_send_native(args.first().unwrap_or(&Value::Null), &args.get(1).map(|v| v.to_display_string()).unwrap_or_default())))"),
|
|
1704
1962
|
"wsBroadcast" => Some("Value::native(|args: &[Value]| tishlang_runtime::ws_broadcast_native(args))"),
|
|
1963
|
+
"wsAccept" => Some("Value::native(|args: &[Value]| tishlang_runtime::ws_serve_accept(args))"),
|
|
1705
1964
|
_ => None,
|
|
1706
1965
|
},
|
|
1707
1966
|
"tish:tty" if self.has_feature("tty") => match export_name {
|
|
@@ -2244,15 +2503,38 @@ impl Codegen {
|
|
|
2244
2503
|
}
|
|
2245
2504
|
|
|
2246
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
|
+
}
|
|
2247
2512
|
self.is_async = program_uses_async(program);
|
|
2248
2513
|
self.program_has_jsx = tishlang_ui::jsx::program_contains_jsx(program);
|
|
2249
2514
|
self.program_fun_decl_names = tishlang_ui::jsx::collect_fun_decl_names(program);
|
|
2250
2515
|
self.program_uses_document = crate::resolve::program_uses_document(program);
|
|
2251
|
-
self.
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
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");
|
|
2256
2538
|
if self.program_has_jsx {
|
|
2257
2539
|
self.write("use tishlang_ui::{fragment_value, install_thread_local_host, native_create_root, native_use_state, ui_h, ui_text, HeadlessHost};\n");
|
|
2258
2540
|
}
|
|
@@ -2268,6 +2550,10 @@ impl Codegen {
|
|
|
2268
2550
|
self.write("use tishlang_runtime::register_static_route as tish_register_static_route;\n");
|
|
2269
2551
|
if self.is_async {
|
|
2270
2552
|
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve, promise_object as tish_promise_object, await_promise as tish_await_promise, await_promise_throw as tish_await_promise_throw};\n");
|
|
2553
|
+
} else if self.has_feature("ws") {
|
|
2554
|
+
// ws programs get `Promise.spawn` (send-values OS-thread spawn) even when not async,
|
|
2555
|
+
// so a server can run a background accept/pump loop alongside the blocking `serve()`.
|
|
2556
|
+
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve, promise_object as tish_promise_object};\n");
|
|
2271
2557
|
} else {
|
|
2272
2558
|
self.write("use tishlang_runtime::{fetch_promise as tish_fetch_promise, fetch_all_promise as tish_fetch_all_promise, http_serve as tish_http_serve};\n");
|
|
2273
2559
|
}
|
|
@@ -2304,6 +2590,16 @@ impl Codegen {
|
|
|
2304
2590
|
// and `x.field` becomes a direct field access.
|
|
2305
2591
|
self.emit_named_struct_decls();
|
|
2306
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
|
+
|
|
2307
2603
|
if self.is_async && self.emit_mode == crate::NativeEmitMode::DesktopBin {
|
|
2308
2604
|
self.writeln("#[tokio::main]");
|
|
2309
2605
|
self.writeln("async fn main() {");
|
|
@@ -2326,9 +2622,36 @@ impl Codegen {
|
|
|
2326
2622
|
self.writeln("}");
|
|
2327
2623
|
self.writeln("");
|
|
2328
2624
|
}
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
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 {
|
|
2332
2655
|
self.native_numeric_globals =
|
|
2333
2656
|
Self::collect_native_numeric_globals(&program.statements);
|
|
2334
2657
|
if !self.native_numeric_globals.is_empty() {
|
|
@@ -2373,21 +2696,21 @@ impl Codegen {
|
|
|
2373
2696
|
self.writeln("");
|
|
2374
2697
|
}
|
|
2375
2698
|
}
|
|
2376
|
-
// #177 (S-E/S-F,
|
|
2699
|
+
// #177 (S-E/S-F, default-on via native_opts_enabled): de-virtualize the nbody-shape
|
|
2377
2700
|
// aggregate fns into native Rust free fns operating on an unboxed `Vec<TishStruct_alias>`
|
|
2378
2701
|
// threaded by reference. Computed + emitted here (before `run()`); if any fn can't be
|
|
2379
2702
|
// lowered the whole path is disabled and we fall back to the boxed closures unchanged.
|
|
2380
2703
|
if crate::native_opts_enabled() {
|
|
2381
2704
|
self.setup_aggregate_fns(program);
|
|
2382
2705
|
}
|
|
2383
|
-
// #178 (
|
|
2706
|
+
// #178 (default-on via native_opts_enabled): de-virtualize recursive-struct builders/consumers into
|
|
2384
2707
|
// native free fns over `Option<Box<T>>` structs (the real binary_trees fix). Structural,
|
|
2385
2708
|
// name-independent; emits before `run()` with scratch-buffer rollback so any unsupported
|
|
2386
2709
|
// construct cleanly disables the path and falls back to the boxed closures unchanged.
|
|
2387
2710
|
if crate::native_opts_enabled() {
|
|
2388
2711
|
self.setup_rec_struct_plan(program);
|
|
2389
2712
|
}
|
|
2390
|
-
// #175 (
|
|
2713
|
+
// #175 (default-on via native_opts_enabled): de-virtualize fns over plain `number[]`/`boolean[]` params
|
|
2391
2714
|
// into native free fns threaded by `&/&mut Vec<T>` (spectral_norm / queens). Runs after the
|
|
2392
2715
|
// aggregate path so it can skip any fn that path already claimed; emits before `run()` with
|
|
2393
2716
|
// its own scratch-buffer rollback so a failure leaves the boxed closures untouched.
|
|
@@ -2398,7 +2721,7 @@ impl Codegen {
|
|
|
2398
2721
|
self.inline_fns = Self::collect_inline_fns(program);
|
|
2399
2722
|
self.setup_native_vec_fns(program);
|
|
2400
2723
|
}
|
|
2401
|
-
// #320 (
|
|
2724
|
+
// #320 (default-on via native_opts_enabled): normal fns taking read-only `number[]` params get
|
|
2402
2725
|
// those params unboxed to native owned `Vec<f64>` (boxed body + boxed return otherwise), so
|
|
2403
2726
|
// `seq[i+j]` indexes natively. The SAME pure detection infer reads to keep the caller's array
|
|
2404
2727
|
// `number[]`, so the two never disagree about which args pass as native arrays.
|
|
@@ -2436,7 +2759,9 @@ impl Codegen {
|
|
|
2436
2759
|
}
|
|
2437
2760
|
self.nonneg_locals = self.collect_nonneg_locals(&program.statements);
|
|
2438
2761
|
self.shift_half_of = Self::collect_shift_half_of(&program.statements);
|
|
2439
|
-
|
|
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 {
|
|
2440
2765
|
self.writeln("async fn run() -> Result<(), Box<dyn std::error::Error>> {");
|
|
2441
2766
|
} else if self.emit_mode == crate::NativeEmitMode::EmbeddedLib {
|
|
2442
2767
|
self.writeln("pub fn run() -> Result<(), Box<dyn std::error::Error>> {");
|
|
@@ -2540,6 +2865,15 @@ impl Codegen {
|
|
|
2540
2865
|
}
|
|
2541
2866
|
self.writeln("(Arc::from(\"PI\"), Value::Number(std::f64::consts::PI)),");
|
|
2542
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
|
+
);
|
|
2543
2877
|
self.indent -= 1;
|
|
2544
2878
|
self.writeln("]));");
|
|
2545
2879
|
self.writeln("let JSON = Value::object(ObjectMap::from([");
|
|
@@ -2618,6 +2952,12 @@ impl Codegen {
|
|
|
2618
2952
|
self.writeln(
|
|
2619
2953
|
"(Arc::from(\"getOwnPropertyNames\"), Value::native(|args: &[Value]| tish_object_keys(args))),",
|
|
2620
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
|
+
);
|
|
2621
2961
|
self.writeln(
|
|
2622
2962
|
"(Arc::from(\"keys\"), Value::native(|args: &[Value]| tish_object_keys(args))),",
|
|
2623
2963
|
);
|
|
@@ -2693,7 +3033,7 @@ impl Codegen {
|
|
|
2693
3033
|
"let fetch = Value::native(|args: &[Value]| tish_fetch_promise(args.to_vec()));",
|
|
2694
3034
|
);
|
|
2695
3035
|
self.writeln("let fetchAll = Value::native(|args: &[Value]| tish_fetch_all_promise(args.to_vec()));");
|
|
2696
|
-
if self.is_async {
|
|
3036
|
+
if self.is_async || self.has_feature("ws") {
|
|
2697
3037
|
self.writeln("let Promise = tish_promise_object();");
|
|
2698
3038
|
}
|
|
2699
3039
|
// `serve` supports two shapes:
|
|
@@ -2815,6 +3155,14 @@ impl Codegen {
|
|
|
2815
3155
|
self.indent -= 1;
|
|
2816
3156
|
self.writeln("}");
|
|
2817
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
|
+
}
|
|
2818
3166
|
Ok(())
|
|
2819
3167
|
}
|
|
2820
3168
|
|
|
@@ -2837,7 +3185,12 @@ impl Codegen {
|
|
|
2837
3185
|
/// unchanged, and `PropMap` dedup + hidden-class shapes key by text, not pointer — so shapes,
|
|
2838
3186
|
/// insertion order, and `Object.keys` are all unaffected. Same inline-`static` idiom as the
|
|
2839
3187
|
/// per-site `PropIC` caches.
|
|
2840
|
-
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
|
+
}
|
|
2841
3194
|
format!(
|
|
2842
3195
|
"{{ static K: ::std::sync::OnceLock<::std::sync::Arc<str>> = \
|
|
2843
3196
|
::std::sync::OnceLock::new(); K.get_or_init(|| ::std::sync::Arc::from({:?})).clone() }}",
|
|
@@ -2974,7 +3327,24 @@ impl Codegen {
|
|
|
2974
3327
|
}
|
|
2975
3328
|
}
|
|
2976
3329
|
}
|
|
2977
|
-
|
|
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
|
+
) {
|
|
2978
3348
|
if let Expr::Index { object, index, .. } = value.as_ref() {
|
|
2979
3349
|
if let Expr::Ident { name: arr_name, .. } = object.as_ref() {
|
|
2980
3350
|
if self.index_in_bounds(index, arr_name.as_ref()) {
|
|
@@ -3024,6 +3394,47 @@ impl Codegen {
|
|
|
3024
3394
|
}
|
|
3025
3395
|
// `s += x` etc. in statement position: native f64 compound op, no boxed return.
|
|
3026
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
|
+
}
|
|
3027
3438
|
if self.type_context.get_type(name.as_ref()) == RustType::F64 {
|
|
3028
3439
|
let n = Self::escape_ident(name.as_ref());
|
|
3029
3440
|
let is_refcell = self.refcell_wrapped_vars.contains(name.as_ref());
|
|
@@ -5052,10 +5463,10 @@ impl Codegen {
|
|
|
5052
5463
|
// re-boxing each element to `Value::Number` for the loop body.
|
|
5053
5464
|
self.writeln("Value::NumberArray(ref _arr) => {");
|
|
5054
5465
|
self.indent += 1;
|
|
5055
|
-
self.writeln("for _v in _arr.borrow().
|
|
5466
|
+
self.writeln("for _v in _arr.borrow().to_values() {");
|
|
5056
5467
|
self.indent += 1;
|
|
5057
5468
|
self.writeln(&format!(
|
|
5058
|
-
"let {} =
|
|
5469
|
+
"let {} = _v;",
|
|
5059
5470
|
Self::escape_ident(name.as_ref())
|
|
5060
5471
|
));
|
|
5061
5472
|
self.emit_statement(body)?;
|
|
@@ -5838,14 +6249,15 @@ impl Codegen {
|
|
|
5838
6249
|
sibling_escaped, sibling_escaped
|
|
5839
6250
|
));
|
|
5840
6251
|
}
|
|
5841
|
-
// Extract just the parameter names
|
|
6252
|
+
// Extract just the parameter names for capture bookkeeping (typed params get
|
|
6253
|
+
// native shadows below — the M1 block)
|
|
5842
6254
|
let current_param_names: Vec<String> = params
|
|
5843
6255
|
.iter()
|
|
5844
6256
|
.flat_map(|p| p.bound_names())
|
|
5845
6257
|
.map(|n| n.to_string())
|
|
5846
6258
|
.collect();
|
|
5847
6259
|
let formal_span = *span;
|
|
5848
|
-
// M1 (keystone
|
|
6260
|
+
// M1 (keystone; default-on via native_opts_enabled): a typed scalar param
|
|
5849
6261
|
// normally arrives boxed (`args.get(i).cloned()`), which poisons native math in
|
|
5850
6262
|
// the body (e.g. `i*N+k` boxes). Bind a *native shadow* — coerce once to f64/
|
|
5851
6263
|
// bool/String — so the body lowers it like a native local. Conservative: only
|
|
@@ -5888,7 +6300,9 @@ impl Codegen {
|
|
|
5888
6300
|
// `arr[oob]`→NaN) is unreachable for sound inputs but never panics.
|
|
5889
6301
|
self.writeln(&format!(
|
|
5890
6302
|
"let mut {}: Vec<f64> = match args.get({}) {{ \
|
|
5891
|
-
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() }}, \
|
|
5892
6306
|
Some(Value::Array(arr)) => arr.borrow().iter().map(|v| match v {{ Value::Number(n) => *n, _ => f64::NAN }}).collect(), \
|
|
5893
6307
|
_ => Vec::new() }};",
|
|
5894
6308
|
Self::escape_ident(tp.name.as_ref()),
|
|
@@ -7256,7 +7670,7 @@ impl Codegen {
|
|
|
7256
7670
|
}
|
|
7257
7671
|
"reduce" => {
|
|
7258
7672
|
let initial = arg_exprs.get(1).cloned().unwrap_or_else(|| "Value::Null".to_string());
|
|
7259
|
-
// Fused reduce (
|
|
7673
|
+
// Fused reduce (default-on via native_opts_enabled): `arr.reduce((acc, x) => acc OP x, init)`
|
|
7260
7674
|
// with a plain binop of the two params → a native fold using the SAME
|
|
7261
7675
|
// runtime Value op the closure body would, eliminating the per-element
|
|
7262
7676
|
// `value_call`. Sound (identical Value semantics, incl. string `+`).
|
|
@@ -7595,10 +8009,11 @@ impl Codegen {
|
|
|
7595
8009
|
tishlang_runtime::get_prop(&o, {}) }} }}",
|
|
7596
8010
|
obj, key
|
|
7597
8011
|
)
|
|
7598
|
-
} else if ic_eligible {
|
|
8012
|
+
} else if ic_eligible && self.emit_mode != crate::NativeEmitMode::Gba {
|
|
7599
8013
|
// Per-site inline cache: a block-scoped `static` cell unique to this member-read
|
|
7600
8014
|
// site (#179). Caches (shape, slot) so repeat reads skip the key scan. Behaviour is
|
|
7601
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.
|
|
7602
8017
|
format!(
|
|
7603
8018
|
"{{ static IC: tishlang_runtime::PropIC = tishlang_runtime::PropIC::new(); \
|
|
7604
8019
|
tishlang_runtime::get_prop_ic(&{}, {}, &IC) }}",
|
|
@@ -7725,7 +8140,7 @@ impl Codegen {
|
|
|
7725
8140
|
match prop {
|
|
7726
8141
|
ObjectProp::KeyValue(k, v, _) => {
|
|
7727
8142
|
let val = self.emit_expr(v)?;
|
|
7728
|
-
let key =
|
|
8143
|
+
let key = self.cached_object_key(k.as_ref());
|
|
7729
8144
|
if self.should_clone(v) {
|
|
7730
8145
|
parts.push(format!("_obj.insert({}, ({}).clone());", key, val));
|
|
7731
8146
|
} else {
|
|
@@ -7748,7 +8163,7 @@ impl Codegen {
|
|
|
7748
8163
|
init = format!("let mut _obj: PropMap = PropMap::with_capacity({});", literal_keys);
|
|
7749
8164
|
}
|
|
7750
8165
|
format!(
|
|
7751
|
-
"{{ {} {} Value::Object(VmRef::new(ObjectData {{ strings: _obj, symbols: None }})) }}",
|
|
8166
|
+
"{{ {} {} Value::Object(VmRef::new(ObjectData {{ strings: _obj, symbols: None, frozen: false }})) }}",
|
|
7752
8167
|
init,
|
|
7753
8168
|
parts.join(" ")
|
|
7754
8169
|
)
|
|
@@ -7757,7 +8172,7 @@ impl Codegen {
|
|
|
7757
8172
|
for prop in props {
|
|
7758
8173
|
if let ObjectProp::KeyValue(k, v, _) = prop {
|
|
7759
8174
|
let val = self.emit_expr(v)?;
|
|
7760
|
-
let key =
|
|
8175
|
+
let key = self.cached_object_key(k.as_ref());
|
|
7761
8176
|
if self.should_clone(v) {
|
|
7762
8177
|
parts.push(format!("({}, ({}).clone())", key, val));
|
|
7763
8178
|
} else {
|
|
@@ -8253,6 +8668,27 @@ impl Codegen {
|
|
|
8253
8668
|
)
|
|
8254
8669
|
}
|
|
8255
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
|
+
}
|
|
8256
8692
|
self.native_module_rust_init(spec.as_ref(), export_name.as_ref())
|
|
8257
8693
|
.ok_or_else(|| CompileError {
|
|
8258
8694
|
message: if crate::resolve::is_builtin_native_spec(spec.as_ref()) {
|
|
@@ -9514,6 +9950,187 @@ impl Codegen {
|
|
|
9514
9950
|
}
|
|
9515
9951
|
}
|
|
9516
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
|
+
|
|
9517
10134
|
fn collect_destruct_names(pattern: &DestructPattern, names: &mut HashSet<String>) {
|
|
9518
10135
|
match pattern {
|
|
9519
10136
|
DestructPattern::Array(elements) => {
|
|
@@ -9772,7 +10389,14 @@ impl Codegen {
|
|
|
9772
10389
|
}
|
|
9773
10390
|
}
|
|
9774
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, …) })`).
|
|
9775
10397
|
for builtin in &[
|
|
10398
|
+
"Boolean",
|
|
10399
|
+
"serve",
|
|
9776
10400
|
"console",
|
|
9777
10401
|
"Math",
|
|
9778
10402
|
"JSON",
|
|
@@ -9839,6 +10463,20 @@ impl Codegen {
|
|
|
9839
10463
|
.cloned()
|
|
9840
10464
|
.collect();
|
|
9841
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
|
+
}
|
|
9842
10480
|
let implicit_env_captures: Vec<String> = if self.value_fn_depth > 0 {
|
|
9843
10481
|
referenced
|
|
9844
10482
|
.iter()
|
|
@@ -9846,6 +10484,13 @@ impl Codegen {
|
|
|
9846
10484
|
!param_names.contains(name.as_str())
|
|
9847
10485
|
&& !local_var_names.contains(name.as_str())
|
|
9848
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())
|
|
9849
10494
|
})
|
|
9850
10495
|
.cloned()
|
|
9851
10496
|
.collect()
|
|
@@ -10259,7 +10904,7 @@ impl Codegen {
|
|
|
10259
10904
|
}
|
|
10260
10905
|
}
|
|
10261
10906
|
|
|
10262
|
-
// Native typed-array HOFs (
|
|
10907
|
+
// Native typed-array HOFs (default-on via native_opts_enabled): `xs.reduce/map/filter/some/every(<arrow>)`
|
|
10263
10908
|
// whose native result type matches this binding's target → emit the iterator chain
|
|
10264
10909
|
// directly, with NO box/unbox round-trip (the per-element `value_call` is gone too).
|
|
10265
10910
|
if let Expr::Call { callee, args, .. } = expr {
|
|
@@ -10281,6 +10926,18 @@ impl Codegen {
|
|
|
10281
10926
|
if &typed_ty == target_type {
|
|
10282
10927
|
return Ok(typed_code);
|
|
10283
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
|
+
}
|
|
10284
10941
|
}
|
|
10285
10942
|
|
|
10286
10943
|
// Fall back to emit_expr + conversion
|
|
@@ -11712,6 +12369,37 @@ impl Codegen {
|
|
|
11712
12369
|
Ok(Some(assign))
|
|
11713
12370
|
}
|
|
11714
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
|
+
|
|
11715
12403
|
/// `for (i=0; i<r; i++) { arr[i] = arr[i+1] }` → `arr.copy_within(1..(r+1), 0)` (fannkuch perm1 rotation).
|
|
11716
12404
|
fn try_emit_vec_copy_within_shift_for(
|
|
11717
12405
|
&mut self,
|
|
@@ -12352,6 +13040,24 @@ impl Codegen {
|
|
|
12352
13040
|
}
|
|
12353
13041
|
}
|
|
12354
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
|
+
|
|
12355
13061
|
/// Names of `f64` locals provably integer-bounded within `(-2^53, 2^53)` across the whole
|
|
12356
13062
|
/// program. Seeds from integer-literal initializers and literal-bounded `for` counters, then
|
|
12357
13063
|
/// runs a join fixpoint over reassignments: a local keeps a bound only if its init and EVERY
|
|
@@ -14272,6 +14978,9 @@ impl Codegen {
|
|
|
14272
14978
|
Expr::Member { optional: false, .. } | Expr::Index { optional: false, .. } => {
|
|
14273
14979
|
found = true
|
|
14274
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,
|
|
14275
14984
|
_ => {}
|
|
14276
14985
|
}
|
|
14277
14986
|
});
|
|
@@ -14848,7 +15557,7 @@ impl Codegen {
|
|
|
14848
15557
|
|
|
14849
15558
|
fn module_const_f64_array_as_value(static_name: &str) -> String {
|
|
14850
15559
|
format!(
|
|
14851
|
-
"Value::NumberArray(VmRef::new({}.iter().copied().collect::<Vec<f64>>()))",
|
|
15560
|
+
"Value::NumberArray(VmRef::new(tishlang_runtime::NumArrayBacking::Packed({}.iter().copied().collect::<Vec<f64>>())))",
|
|
14852
15561
|
static_name
|
|
14853
15562
|
)
|
|
14854
15563
|
}
|
|
@@ -16630,6 +17339,14 @@ impl Codegen {
|
|
|
16630
17339
|
code
|
|
16631
17340
|
} else if ty == RustType::Value {
|
|
16632
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)
|
|
16633
17350
|
} else {
|
|
16634
17351
|
code
|
|
16635
17352
|
})
|
|
@@ -17187,6 +17904,7 @@ impl Codegen {
|
|
|
17187
17904
|
name,
|
|
17188
17905
|
params,
|
|
17189
17906
|
rest_param: None,
|
|
17907
|
+
return_type,
|
|
17190
17908
|
body,
|
|
17191
17909
|
..
|
|
17192
17910
|
} = s
|
|
@@ -17194,6 +17912,13 @@ impl Codegen {
|
|
|
17194
17912
|
if nonnumeric.contains(name.as_ref()) {
|
|
17195
17913
|
continue;
|
|
17196
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
|
+
}
|
|
17197
17922
|
if let Some(sig) = Self::infer_vec_fn_sig(params, body) {
|
|
17198
17923
|
let has_array_param = sig
|
|
17199
17924
|
.params
|
|
@@ -17232,6 +17957,7 @@ impl Codegen {
|
|
|
17232
17957
|
name,
|
|
17233
17958
|
params,
|
|
17234
17959
|
rest_param: None,
|
|
17960
|
+
return_type,
|
|
17235
17961
|
body,
|
|
17236
17962
|
..
|
|
17237
17963
|
} = s
|
|
@@ -17245,6 +17971,11 @@ impl Codegen {
|
|
|
17245
17971
|
if nonnumeric.contains(name.as_ref()) {
|
|
17246
17972
|
continue;
|
|
17247
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
|
+
}
|
|
17248
17979
|
if let Some(sig) = Self::infer_vec_fn_sig_forwarding(params, body, &sigs) {
|
|
17249
17980
|
let has_array_param = sig
|
|
17250
17981
|
.params
|
|
@@ -18693,7 +19424,7 @@ impl Codegen {
|
|
|
18693
19424
|
// ====================================================================================
|
|
18694
19425
|
// #177 (S-E / S-F): interprocedural aggregate (unboxed struct + Vec<Struct>) free fns.
|
|
18695
19426
|
//
|
|
18696
|
-
// 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
|
|
18697
19428
|
// `: alias` / `: alias[]` annotations onto the nbody-shape factory/array/operator fns iff a
|
|
18698
19429
|
// whole-program candidacy predicate holds (monomorphic all-f64 struct, no `===`/escape/
|
|
18699
19430
|
// reshape, write-permitting field ops). Here we consume that: emit each such fn as a native
|
|
@@ -18707,7 +19438,7 @@ impl Codegen {
|
|
|
18707
19438
|
// identical to flag-off). So we never half-wire the feature or miscompile.
|
|
18708
19439
|
// ====================================================================================
|
|
18709
19440
|
|
|
18710
|
-
// ── #178: recursive-struct native lowering (
|
|
19441
|
+
// ── #178: recursive-struct native lowering (default-on via native_opts_enabled) ─────────
|
|
18711
19442
|
// Infers a recursive struct shape STRUCTURALLY (no identifier matching) from object literals
|
|
18712
19443
|
// whose fields are scalars or recursive self-references, and emits native builders
|
|
18713
19444
|
// (`name_rec(..) -> TishStruct_X`) + consumers (`name_rec(&TishStruct_X) -> f64`) over
|
|
@@ -21372,17 +22103,39 @@ impl Codegen {
|
|
|
21372
22103
|
// `emit_int32_operand`, which reads the i32 register directly. So coercing here only
|
|
21373
22104
|
// governs arithmetic/relational reads (e.g. the `h * 16777619` excursion), where the
|
|
21374
22105
|
// operand must be f64 to keep JS Number semantics.
|
|
21375
|
-
|
|
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() {
|
|
21376
22110
|
(format!("(({}) as f64)", l), RustType::F64)
|
|
21377
22111
|
} else {
|
|
21378
22112
|
(l, lt)
|
|
21379
22113
|
};
|
|
21380
|
-
let (r, rt) = if rt
|
|
22114
|
+
let (r, rt) = if rt.is_integer_scalar() {
|
|
21381
22115
|
(format!("(({}) as f64)", r), RustType::F64)
|
|
21382
22116
|
} else {
|
|
21383
22117
|
(r, rt)
|
|
21384
22118
|
};
|
|
21385
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
|
+
|
|
21386
22139
|
if let Some(result_ty) = RustType::result_type_of_binop(*op, <, &rt) {
|
|
21387
22140
|
// Bitwise/shift over numbers: lower the *whole* chain in the int32 domain so
|
|
21388
22141
|
// intermediate `to_int32`/`to_uint32`↔`f64` round-trips collapse (the win `>>>`
|
|
@@ -21861,7 +22614,7 @@ impl Codegen {
|
|
|
21861
22614
|
}
|
|
21862
22615
|
}
|
|
21863
22616
|
}
|
|
21864
|
-
// 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):
|
|
21865
22618
|
// `xs.reduce/map/filter/some/every(<arrow>)` → a direct Rust iterator chain,
|
|
21866
22619
|
// eliminating the per-element `value_call` and all `Value` boxing.
|
|
21867
22620
|
if let Some(res) = self.native_vec_hof_for_call(callee, args)? {
|
|
@@ -22077,16 +22830,25 @@ impl Codegen {
|
|
|
22077
22830
|
None
|
|
22078
22831
|
};
|
|
22079
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).
|
|
22080
22838
|
return Ok(Some(format!(
|
|
22081
22839
|
"{{ let _init0 = {init}; let _arr = ({obj}).clone(); \
|
|
22082
|
-
|
|
22840
|
+
match _arr {{ Value::Array(ref _a) => {{ let _b = _a.borrow(); \
|
|
22083
22841
|
let mut _accn: f64 = 0.0; let mut _ok = false; \
|
|
22084
22842
|
if let Value::Number(_i0) = &_init0 {{ _accn = *_i0; _ok = true; }} \
|
|
22085
22843
|
if _ok {{ for _el in _b.iter() {{ \
|
|
22086
22844
|
if let Value::Number(_n) = _el {{ _accn {nat_op} *_n; }} else {{ _ok = false; break; }} }} }} \
|
|
22087
22845
|
if _ok {{ Value::Number(_accn) }} \
|
|
22088
22846
|
else {{ let mut _acc = _init0; for _el in _b.iter() {{ let _x = _el.clone(); _acc = {body}; }} _acc }} \
|
|
22089
|
-
}}
|
|
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 }} }}",
|
|
22090
22852
|
init = initial,
|
|
22091
22853
|
obj = obj_expr,
|
|
22092
22854
|
nat_op = nat_op,
|
|
@@ -22094,10 +22856,13 @@ impl Codegen {
|
|
|
22094
22856
|
)));
|
|
22095
22857
|
}
|
|
22096
22858
|
|
|
22859
|
+
// #508: same NumberArray arm for the general (non-arithmetic-op) fold.
|
|
22097
22860
|
Ok(Some(format!(
|
|
22098
22861
|
"{{ let mut _acc = {init}; let _arr = ({obj}).clone(); \
|
|
22099
|
-
|
|
22100
|
-
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 }}",
|
|
22101
22866
|
init = initial,
|
|
22102
22867
|
obj = obj_expr,
|
|
22103
22868
|
body = body_code
|
|
@@ -22590,7 +23355,7 @@ impl Codegen {
|
|
|
22590
23355
|
"let mut __out: Vec<f64> = Vec::new(); \
|
|
22591
23356
|
for &__f0 in __na.iter() {{ let mut __f = __f0; {inner} \
|
|
22592
23357
|
let {p}: f64 = __f; __out.push({term}); }} \
|
|
22593
|
-
Value::NumberArray(VmRef::new(__out))",
|
|
23358
|
+
Value::NumberArray(VmRef::new(tishlang_runtime::NumArrayBacking::Packed(__out)))",
|
|
22594
23359
|
inner = inner, p = pesc, term = term
|
|
22595
23360
|
)
|
|
22596
23361
|
}
|
|
@@ -22602,7 +23367,7 @@ impl Codegen {
|
|
|
22602
23367
|
"let mut __out: Vec<f64> = Vec::new(); \
|
|
22603
23368
|
for &__f0 in __na.iter() {{ let mut __f = __f0; {inner} \
|
|
22604
23369
|
let {p}: f64 = __f; if {pred} {{ __out.push({p}); }} }} \
|
|
22605
|
-
Value::NumberArray(VmRef::new(__out))",
|
|
23370
|
+
Value::NumberArray(VmRef::new(tishlang_runtime::NumArrayBacking::Packed(__out)))",
|
|
22606
23371
|
inner = inner, p = pesc, pred = pred
|
|
22607
23372
|
)
|
|
22608
23373
|
}
|
|
@@ -22612,7 +23377,7 @@ impl Codegen {
|
|
|
22612
23377
|
}
|
|
22613
23378
|
|
|
22614
23379
|
/// If `callee(args)` is `<Vec<f64>-ident>.reduce/map/filter/some/every(<arrow>)` and the
|
|
22615
|
-
///
|
|
23380
|
+
/// native opts are enabled (default-on), lower it to a native iterator chain. Shared by
|
|
22616
23381
|
/// `emit_typed_expr` (native sub-expressions) and `emit_native_expr` (typed `let` RHS), so a
|
|
22617
23382
|
/// typed-array HOF lowers natively whether its result flows into arithmetic or a binding.
|
|
22618
23383
|
fn native_vec_hof_for_call(
|
|
@@ -22649,7 +23414,7 @@ impl Codegen {
|
|
|
22649
23414
|
self.try_native_vec_hof(&recv_code, &inner, recv_name.as_ref(), method.as_ref(), args)
|
|
22650
23415
|
}
|
|
22651
23416
|
|
|
22652
|
-
/// Native typed-array HOFs (`
|
|
23417
|
+
/// Native typed-array HOFs (default-on via `native_opts_enabled`): when the receiver is a native `Vec<f64>`
|
|
22653
23418
|
/// (a typed `number[]`), lower `reduce`/`map`/`filter`/`some`/`every` to a direct Rust
|
|
22654
23419
|
/// iterator chain — no per-element `value_call`, no `Value` boxing.
|
|
22655
23420
|
///
|