@tishlang/tish-format 3.2.2 → 3.3.3

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.
@@ -30,6 +30,12 @@ fn gba_numerics() -> bool {
30
30
  GBA_NUMERICS.with(|c| c.get())
31
31
  }
32
32
 
33
+ /// Public view of the same switch, for passes that must behave differently only where the extended
34
+ /// numeric vocabulary exists — see the native-fn bitwise widening in `codegen.rs`.
35
+ pub fn gba_numerics_enabled() -> bool {
36
+ gba_numerics()
37
+ }
38
+
33
39
  /// Concrete Rust type representation for code generation.
34
40
  #[derive(Debug, Clone, PartialEq)]
35
41
  pub enum RustType {
@@ -0,0 +1,113 @@
1
+ //! M5 native fns, with an `i32` calling convention.
2
+ //!
3
+ //! `collect_native_fns` gated parameters and returns on `RustType::F64`, so `: number` qualified for
4
+ //! a native free `fn` and `: i32` did not — and `: i32` is the annotation a device with no FPU
5
+ //! wants. Every argument and every return of such a call was therefore an i32→f64→i32 round trip,
6
+ //! which on ARM7TDMI is a soft-float call apiece.
7
+ //!
8
+ //! The shape is ALL-OR-NOTHING: every parameter and the return annotated `: i32`. Mixing is what
9
+ //! makes it unsound — an `: i32` parameter reached through the boxed path is ToInt32-coerced on
10
+ //! entry, and promoted to a native `f64` parameter it would not be, so 3.7 would arrive as 3 one
11
+ //! way and 3.7 the other.
12
+
13
+ use std::path::PathBuf;
14
+
15
+ use tishlang_compile::{compile_project_full_emit, NativeEmitMode};
16
+
17
+ /// Compile for GBA — `: i32` is only a native scalar under the GBA numeric vocabulary
18
+ /// (`types.rs`: `"i32" if gba_numerics()`), so on a desktop target this ABI cannot exist and the
19
+ /// assertions below would be vacuous.
20
+ fn compile_src(stem: &str, src: &str) -> String {
21
+ let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../target/perf_codegen_i32_abi");
22
+ std::fs::create_dir_all(&dir).unwrap();
23
+ let path = dir.join(format!("{stem}.tish"));
24
+ std::fs::write(&path, src).unwrap();
25
+ let path = path.canonicalize().unwrap();
26
+ let (rust, _, _, _) =
27
+ compile_project_full_emit(&path, path.parent(), &[], true, NativeEmitMode::Gba, None)
28
+ .unwrap();
29
+ rust
30
+ }
31
+
32
+ const ALL_I32: &str = r#"
33
+ function mix(a: i32, b: i32): i32 { return ((a * 3) ^ (b << 2)) & 65535 }
34
+ let acc: i32 = 0
35
+ let j: i32 = 0
36
+ while (j < 10) { acc = (acc + mix(j, acc)) & 65535; j = j + 1 }
37
+ console.log(acc)
38
+ "#;
39
+
40
+ #[test]
41
+ fn an_all_i32_signature_gets_an_i32_native_fn() {
42
+ let rust = compile_src("all_i32", ALL_I32);
43
+ assert!(
44
+ rust.contains("fn mix_native(mut a: i32, mut b: i32) -> i32"),
45
+ "expected an i32-signature native fn, got:\n{}",
46
+ rust.lines().filter(|l| l.contains("mix_native")).collect::<Vec<_>>().join("\n")
47
+ );
48
+ // And the call must reach it directly rather than through `value_call`.
49
+ assert!(rust.contains("mix_native("), "the call site should target the native fn");
50
+ }
51
+
52
+ const MIXED: &str = r#"
53
+ function half(a: i32, b: number): i32 { return (a + b) | 0 }
54
+ console.log(half(7, 2.5))
55
+ "#;
56
+
57
+ #[test]
58
+ fn a_mixed_signature_does_not_get_the_i32_abi() {
59
+ let rust = compile_src("mixed", MIXED);
60
+ assert!(
61
+ !rust.contains("fn half_native(mut a: i32"),
62
+ "a mixed i32/number signature must not take the i32 ABI — an `: i32` param is \
63
+ ToInt32-coerced on entry and an f64 native param is not"
64
+ );
65
+ }
66
+
67
+ const LIAR: &str = r#"
68
+ function liar(x: i32): i32 {
69
+ if (x > 0) { return "not a number" }
70
+ return 7
71
+ }
72
+ console.log(liar(1))
73
+ "#;
74
+
75
+ #[test]
76
+ fn the_annotation_alone_does_not_qualify_a_fn() {
77
+ // Return annotations are erased and unchecked: this program prints the STRING today. The i32
78
+ // ABI must not be handed out on the strength of `: i32`, only on the fixpoint's proof that
79
+ // every return is numeric — otherwise this would unbox a string as a number.
80
+ let rust = compile_src("liar", LIAR);
81
+ assert!(
82
+ !rust.contains("fn liar_native"),
83
+ "a fn with a non-numeric return path must stay boxed regardless of its annotation"
84
+ );
85
+ }
86
+
87
+ const READS_MODULE_STATE: &str = r#"
88
+ let ARR: i32[] = []
89
+ let k: i32 = 0
90
+ while (k < 8) { ARR.push(k); k = k + 1 }
91
+ function pure(a: i32, b: i32): i32 { return (a ^ b) & 255 }
92
+ function reads(i: i32): i32 { return ARR[i] & 255 }
93
+ let x: i32 = pure(1, 2) + reads(3)
94
+ console.log(x)
95
+ "#;
96
+
97
+ #[test]
98
+ fn a_fn_reading_module_state_stays_boxed() {
99
+ // A native fn is emitted at TOP LEVEL, where a module-scope `let` — a local of `run()`, closed
100
+ // over by the boxed closures — does not exist. `native_safe_expr`'s index arm accepted any
101
+ // identifier as the indexed object, so this compiled to a `reads_native` whose body named
102
+ // `ARR` and rustc rejected the generated program with E0425. The sibling `pure` must still be
103
+ // promoted: the check has to reject the out-of-scope read, not indexing as such.
104
+ let rust = compile_src("reads_module_state", READS_MODULE_STATE);
105
+ assert!(
106
+ !rust.contains("fn reads_native"),
107
+ "a fn indexing a module-scope binding cannot become a top-level free fn"
108
+ );
109
+ assert!(
110
+ rust.contains("fn pure_native(mut a: i32, mut b: i32) -> i32"),
111
+ "its parameter-only sibling must still get the native i32 fn"
112
+ );
113
+ }