plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
2150594
— Peter John
2026-11-06T14:03:56+05:30
FBIP stage 1: pilot and verdict, and the simpler language plan
- compiler/lsp/src/completion.rs +6 -0
- docs/backlog.md +1 -1
- docs/plans/2026-11-06-fbip-stage1.progress.md +28 -0
- docs/plans/2026-11-06-fbip-stage1.results.md +130 -0
- docs/plans/2026-11-06-fbip.benchmark.md +4 -0
- docs/plans/2026-11-06-receiver-functions.review.md +0 -108
- docs/plans/2026-11-06-simpler-language.review.md +112 -0
- tests/fbip/slice_shapes.plum +197 -0
- tests/fbip/uvec.plum +22 -0
- tests/fbip/vec.plum +24 -0
- tests/fbip/vec_combined.plum +229 -0
- tests/fbip/vec_combined_shared.plum +251 -0
- tests/fbip/vec_fip.plum +33 -0
- tests/fbip/vec_frames.plum +34 -0
- tests/fbip/vec_frameslocal.plum +31 -0
- tests/fbip/vec_framesmut.plum +34 -0
- tests/fbip/vec_generic.plum +27 -0
- tests/fbip/vec_helper.plum +26 -0
- tests/fbip/vec_listoflists.plum +39 -0
- tests/fbip/vec_local.plum +24 -0
- tests/fbip/vec_mutfield.plum +29 -0
- tests/fbip/vec_nested.plum +27 -0
- tests/fbip/vec_readbetween.plum +25 -0
- tests/fbip/vec_returned.plum +30 -0
- tests/fbip/vec_returned2.plum +31 -0
- tests/fbip/vec_shared.plum +31 -0
- tests/fbip/vec_statespread.plum +29 -0
- tests/fbip/vec_twolists.plum +27 -0
- tests/fbip/vec_unique_names.plum +178 -0
compiler/lsp/src/completion.rs
CHANGED
|
@@ -37,6 +37,7 @@ const KEYWORDS: &[&str] = &[
|
|
|
37
37
|
"extern",
|
|
38
38
|
"export",
|
|
39
39
|
"abstract",
|
|
40
|
+
"fip",
|
|
40
41
|
"if",
|
|
41
42
|
"then",
|
|
42
43
|
"else",
|
|
@@ -656,4 +657,9 @@ mod tests {
|
|
|
656
657
|
let t = plum_checker::withFnScope(&f, &[], || paramPlumType(&ast::ParamType::Type(ty("T", vec![]))));
|
|
657
658
|
assert_eq!(t, PlumType::TParam("T".into()));
|
|
658
659
|
}
|
|
660
|
+
|
|
661
|
+
#[test]
|
|
662
|
+
fn fipIsOfferedAsKeyword() {
|
|
663
|
+
assert!(keywordItems().iter().any(|i| i.label == "fip"));
|
|
664
|
+
}
|
|
659
665
|
}
|
docs/backlog.md
CHANGED
|
@@ -4,7 +4,7 @@ Open work, in the order the user queued it, plus smaller items found during buil
|
|
|
4
4
|
|
|
5
5
|
## Planned (in order)
|
|
6
6
|
|
|
7
|
-
**Current order (user 2026-11-05):** explicit fn generics (done faa4d7e) → cleanup phase 3b (pass list, positioned errors; running) → Io primitive + extern methods → move Rust crates under `compiler/` → tooling + scripts moved under compiler/ (done) → cleanup phase 4 (done: node ids, typed codegen, `inferExpr` errors at spans; `docs/plans/2026-11-05-compiler-cleanup-phase4.results.md`) → phases 5 and 6 (done: file splits, type kinds, tests in Plum; `docs/plans/2026-11-05-compiler-cleanup-phase5-6.results.md`) → game engine refactor (done) → FBIP (
|
|
7
|
+
**Current order (user 2026-11-05):** explicit fn generics (done faa4d7e) → cleanup phase 3b (pass list, positioned errors; running) → Io primitive + extern methods → move Rust crates under `compiler/` → tooling + scripts moved under compiler/ (done) → cleanup phase 4 (done: node ids, typed codegen, `inferExpr` errors at spans; `docs/plans/2026-11-05-compiler-cleanup-phase4.results.md`) → phases 5 and 6 (done: file splits, type kinds, tests in Plum; `docs/plans/2026-11-05-compiler-cleanup-phase5-6.results.md`) → game engine refactor (done) → FBIP stage 1 (done: switch, proof, `Array.with`, `fip fun`, pilot; `docs/plans/2026-11-06-fbip-stage1.results.md`) → a simpler language, then the rest of FBIP (`docs/plans/2026-11-06-simpler-language.review.md`, needs the user's decisions) → benchmark (incl. iterator unboxing / niche Option, run C game A/B) → deterministic sim → kids' games. The items below keep their detail; this line wins on order.
|
|
8
8
|
|
|
9
9
|
1. **Compiler cleanup and architecture** — `docs/plans/2026-11-04-compiler-cleanup.review.md` (written in another session; meant to run after language-restrictions merged). Approved 2026-11-04; running on branch `superlite/compiler-cleanup`, phase 1 first.
|
|
10
10
|
- **Explicit function generics** (user 2026-11-04: right after cleanup phase 2) — functions declare their generics (`fun map[B](...)`) instead of every single capital letter being a generic; allows longer generic names and one-letter concrete types/tags. Grammar change + about 140 signatures in std/examples. Phase 2's "generics by scope" makes this mostly a grammar + signature rewrite.
|
docs/plans/2026-11-06-fbip-stage1.progress.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
- start eeaa05c
|
|
2
|
+
Ruling: user approved FBIP, keep mut for shells and imperative scene; took my recommendations on static proof and no ownership types - cost: none
|
|
3
|
+
Ruling: two stages with a gate: compiler parts and a pilot first, 363 call sites only after the pilot shows in-place updates happen - cost: stage 2 planned later
|
|
4
|
+
Ruling: lint --max-task-min 40, predictor history skewed - cost: a task may run long
|
|
5
|
+
Ruling: skip Wave 0 - cost: none
|
|
6
|
+
- unblocked: dispatched T1 at 10:54:34; waiting on fixes: none
|
|
7
|
+
- unblocked: dispatched T2 at 11:06:24; waiting on fixes: none
|
|
8
|
+
- checkpoint T1 87c3bcc7d7 baseline and probes
|
|
9
|
+
- checkpoint T2 c64fbda376 no-fbip switch
|
|
10
|
+
- unblocked: dispatched T3 at 11:12:16; waiting on fixes: none
|
|
11
|
+
- checkpoint T3 8806e4b41e proof: methods and loops
|
|
12
|
+
- unblocked: dispatched T4 prime at 11:31:41; waiting on fixes: T3 review
|
|
13
|
+
- checkpoint T3 f3adc511da proof: methods and loops
|
|
14
|
+
Ruling: T3 review, about 150 hostile programs, found 2 miscompiles (a closure or callable field named like a function), both fixed - cost: none
|
|
15
|
+
Ruling: two more bugs it found go to a fixer rather than the backlog: a miscompile is not left open - cost: one fix round
|
|
16
|
+
- unblocked: dispatched T4 at 11:53:57; waiting on fixes: F-SIMP-1 F-DEF-1
|
|
17
|
+
Ruling: T4 sent back - a separate local-only proof and a public withUnique don't meet the ruling, and would make the pilot fail by construction; rework on the real proof, with the flag on the call and the choice in codegen - cost: one rework round
|
|
18
|
+
Ruling: F-DEF-1 was a phase 4 regression (any boxed default uninlined); fixed, defaultSingleton removed. A default reading a parameter or self is now rejected; it read the caller's local before; no repo program affected - cost: none, but note a language tightening
|
|
19
|
+
Ruling: F-PUR-1 sent to the same fixer: a miscompile is not left open - cost: one fix round
|
|
20
|
+
Ruling: second attack round before T5, because the proof's rules changed again in T4 - cost: one review
|
|
21
|
+
- unblocked: dispatched T5 at 13:15:17; waiting on fixes: none
|
|
22
|
+
- checkpoint T4 9cf601c77c Array.with
|
|
23
|
+
- unblocked: dispatched T5 at 13:17:18; waiting on fixes: none
|
|
24
|
+
- checkpoint T5 0e5261f915 fip fun
|
|
25
|
+
- unblocked: dispatched T6 at 13:40:26; waiting on fixes: none
|
|
26
|
+
- checkpoint T6 d04a22bfa3 pilot and verdict
|
|
27
|
+
Ruling: pilot verdict is stop stage 2 until the proof is per type; the user approved FBIP but stage 2 now depends on a language plan with user decisions, so stop here and ask - cost: FBIP stage 2 waits
|
|
28
|
+
Ruling: no separate final-reviewer for stage 1 - it ran twice on packet-only evidence with no finding; the proof had two attack rounds, about 1650 programs and a fuzzer instead - cost: a skipped process step, on purpose
|
docs/plans/2026-11-06-fbip-stage1.results.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# FBIP stage 1: results and the verdict on stage 2
|
|
2
|
+
|
|
3
|
+
Taken 2026-11-06 on master, release build (`cargo build --release -p plum-cli`), `target/release/plum run --lib-path .`. Load average 1.6 to 3.8 during the runs (3.8 at the start, 3.2 at the end). The program clock has 1 ms resolution, so anything under 3 ms is a floor, not a measurement. Differences of 10 to 20 percent are noise.
|
|
4
|
+
|
|
5
|
+
## What stage 1 built
|
|
6
|
+
|
|
7
|
+
The pieces below are in the tree and are not re-measured here (see the T2 to T5 results):
|
|
8
|
+
|
|
9
|
+
* `--no-fbip` on `plum run`, `build`, `test` (`PassOptions.fbip`): no spread or array write is marked unique.
|
|
10
|
+
* A stronger uniqueness proof (`compiler/core/src/uniqueness.rs`): method `self` and receivers, overrides, `x = x.m(..)` hand-over in loops and fields, functions returning their own unique argument, read-only methods.
|
|
11
|
+
* `Array.with(self, i, v) -> Array[T]`: copies unless the call is marked unique, then `Array.withUnique` writes in place.
|
|
12
|
+
* `fip fun`: a compile error, at the spot, when a spread or `Array.with` in it is not proved in place.
|
|
13
|
+
* Aliasing tests in `tests/fbip/` and a differential fuzzer (`tests/fbip/fuzz`).
|
|
14
|
+
|
|
15
|
+
T6 adds the pilot: `tests/fbip/vec.plum`, a persistent `struct Vec[T](data: Array[T], size: Int)` with `push`, `set`, `at`, `length`, growing by doubling, in plain Plum with a spread and `Array.with` and nothing else. Twelve shapes (`tests/fbip/vec_<shape>.plum`, each importing `vec.plum`, each with a small checked test and a `main` that times the big size), one combined program of all shapes (`vec_combined.plum`, `vec_combined_shared.plum`), the same on `Slice` (`slice_shapes.plum`), a unique-name copy (`uvec.plum`, `vec_unique_names.plum`) and a `fip fun` version (`vec_fip.plum`).
|
|
16
|
+
|
|
17
|
+
## The central finding: names
|
|
18
|
+
|
|
19
|
+
The proof keys methods and fields by name across the whole program, and the standard library is part of the program. With the realistic names, `push`, `set`, `data` and `size`, nothing the pilot does is proved in place, in any shape, even the simplest (`v = v.push(i)` on a local). Reasons `--explain-fusion` gave for `Vec.push`, all from std code the program only imports:
|
|
20
|
+
|
|
21
|
+
* `push`: a call of `push` from `Str.split` on a `Slice` local (`result`), and calls from std's own `test` blocks on `Array`. The proof resolves `v.push(..)` to every method named `push`, so `Slice.push` and `Array.push` call sites count as callers of `Vec.push` with a value that is not unique.
|
|
22
|
+
* `set`: `Str.byteToStr` calls `out.set(..)` on an `Array`.
|
|
23
|
+
* `data`, `size`: `Slice` has fields of those names (`mut data`, `mut size`), written and passed on. Renaming only `push` and `set` is not enough: `Vec` was still copying on every push. All four names have to differ.
|
|
24
|
+
|
|
25
|
+
`at` and `length` do not matter (they never take part in an update).
|
|
26
|
+
|
|
27
|
+
Experiment, shape 1, 100,000 pushes (ms): realistic names 1281; `push` renamed only 1290; `set` renamed only 1278; `push`+`set` renamed 1276; `data`+`size`+`at`+`length` renamed 1277; `push`+`set`+`data`+`size` renamed 1; all unique 1.
|
|
28
|
+
|
|
29
|
+
So: only unique names work, and stage 2 as planned would fail in practice. A real `Slice`, `Map`, `Set` or `Heap` has to be named `push`, `set`, `data`. Also: the message for the failing case blames the caller (`v` is bound to a value that may be shared), not the name collision. `fip fun push` with the real names fails to compile, correctly, and the error is misleading for the same reason (tests/fbip: the failing text is `not in place: self may be shared by a caller: v is bound to a value that may be shared (call of push from fill)`; `v` is fine).
|
|
30
|
+
|
|
31
|
+
## The table of shapes
|
|
32
|
+
|
|
33
|
+
Times in ms. "real" = realistic names; "unique" = unique names (`UVec`, `udata`, `upush`, ...). 100,000 pushes per run unless stated; a copying run is quadratic, so 100,000 is what finishes (about 1.3 s). "1M" = 1,000,000 pushes, run only where the unique-name version is proved. `Slice` is the same shape in `slice_shapes.plum` (it mutates in place today; its own file because `Slice.push` shares a name with `Vec.push`). Median of 3 for the 1M columns.
|
|
34
|
+
|
|
35
|
+
| # | shape | real: proved? | real ms | unique: proved? | unique ms | unique 1M | Slice 100k | Slice 1M |
|
|
36
|
+
|---|---|---|---|---|---|---|---|---|
|
|
37
|
+
| 1 | local in a loop `v = v.push(i)` | no | 1281 | yes | 1 | 10 | 1 | 7 |
|
|
38
|
+
| 2 | `mut` field, `self.items = self.items.push(x)` | no | 1267 | yes | 1 | 12 | 0-1 | 10 |
|
|
39
|
+
| 3 | field of a nested struct | no | 1251 | yes | 1 | 11 | 1 | 8 |
|
|
40
|
+
| 4 | helper `v = grow(v, i)` | no | 1272 | yes | 1 | 10 | 1 | 7-9 |
|
|
41
|
+
| 5 | generic: `Vec[Int]` and `Vec[Vec2]` | no | 4027 | yes | 2 | 21 | 2 | 15 |
|
|
42
|
+
| 6 | length read between updates | no | 1270 | yes | 1 | 10 | 1 | 7 |
|
|
43
|
+
| 7 | two Vecs in one struct | no | 2586 | yes | 2 | 21 | 1-2 | 15 |
|
|
44
|
+
| 8a | Vec of Vecs, rows built then pushed | no | 47 | no (reason A) | 46 | - | 1 | 10 |
|
|
45
|
+
| 8b | `rows.set(j, rows.at(j).push(i))` | no | (copies the row, inherent) | no | - | - | - | - |
|
|
46
|
+
| 9 | returned from a function, pushed by the caller | no | 1260 | no (reason B) | 1270 | - | 1 | 6 |
|
|
47
|
+
| 9b | returned from a function, only read by the caller | no | 2774 | yes | 2 | - | - | - |
|
|
48
|
+
| 10 | immutable state `S(..s, items: s.items.push(x))` | no | 1264 | no (reason C) | 1274 | - | 1 | 9 |
|
|
49
|
+
| 11 | 20,000 items x 200 frames through a state struct field | no | 22272 | no (reasons C, D) | 22178 | - | 25 | 25 |
|
|
50
|
+
| 11b | same, `Vec` in a local handed through a function | no | 24335 | yes | 31 | - | - | - |
|
|
51
|
+
| 11c | same, `mut` field `w.items = w.items.set(i, ..)` | no | 24590 | no (reasons D, E) | 22270 | - | - | - |
|
|
52
|
+
| 11d | same on a persistent `List`, `l = l.map(..)` | - | - | - | - | - | - | 15-19 |
|
|
53
|
+
| 12 | one value shared on purpose | copies, correct | 0 | copies, correct | 0 | - | - | - |
|
|
54
|
+
|
|
55
|
+
Results are the same with `--no-fbip` (the sweep checks the checksums), and the answers in shape 12 hold in every program.
|
|
56
|
+
|
|
57
|
+
Reasons the unique-name versions are not proved (verbatim from `--explain-fusion`):
|
|
58
|
+
|
|
59
|
+
* A (8a): `row` is passed to method `upush`, which may keep it. The row is a local that is pushed into the outer Vec at its last use. The proof treats passing a value as an argument as possibly keeping it, so `row` stops being unique, and then the `push` calls on `row` fail, and the proof is by name, so the push on the outer Vec fails too.
|
|
60
|
+
* B (9): `v` is bound to a value that may be shared (call of `upush` from run9); a call from make9 passes a unique value. `make9` builds `v` in a loop with `v = v.upush(i)` and returns it, but the caller does not take `make9(n)` as fresh. 9b (the caller only reads, or pushes onto another vec) is proved, so the failure is the caller's later `v = v.upush(..)` on a returned value.
|
|
61
|
+
* C (10, 11): field `items` is passed on without being written back (call of `upush`/`uset` from `step10`/`frame11`). The functional form `State(..s, items: s.items.push(x))` hands the field to a user method inside a spread. The proof accepts `S(..s, f: s.f.with(..))` for the intrinsic `Array.with`, but not the same shape through a user method. Four variants, each in its own program, all failed the same way: a spread through a function, a constructor without a spread, a spread inline in the loop, and a local `v := s.items.push(x)` then `S(items: v, ..)`.
|
|
62
|
+
* D (11, 11c): `v` is stored in field `items` (call of `upush` from run11). Building the Vec in a local and moving it into `World(items: v, ..)` at its last use makes `v` non-unique.
|
|
63
|
+
* E (11c): field `items` may be shared (call of `uset` from frame14), the `mut`-field form, once D has poisoned the field.
|
|
64
|
+
|
|
65
|
+
### Poisoning between shapes
|
|
66
|
+
|
|
67
|
+
One shared name, one program. Unique names, shapes 1 to 7 (+13 or 15) in one program: all proved, 1 to 3 ms (`vec_unique_names.plum`, in the sweep). Adding any one of these to that program turns every other shape into a copy (1260 to 4000 ms): 8 (a Vec stored in a container), 9, 10, or 12 (a value shared on purpose, a test with `a.push` twice). A single shared use anywhere makes the method copy for every caller; the proof has no per-call-site copy (the header says so). Shape 12 alone is enough, so a program with one undo snapshot or one saved previous state loses in-place everywhere. In the realistic-name program (`vec_combined.plum`) every shape copies.
|
|
68
|
+
|
|
69
|
+
### Other observations
|
|
70
|
+
|
|
71
|
+
* `bigger := self.data.resize(n)` then `Vec(data: bigger.with(..), ..)` reported `bigger is read after the update` although it is not (growth branch). Chaining `self.data.resize(n).with(..)` is proved; `vec.plum` uses that form. A proof false negative that costs one extra copy per growth.
|
|
72
|
+
* In `plum run` the standard library's `test` blocks are part of the analysis ("a call from test Array ..."). Calls in imported std tests poison names in a program that never runs those tests.
|
|
73
|
+
* Cost where it works: `Vec` in place is 10 ms per 1M pushes against `Slice` 7 ms (1.4x), and 31 ms against 25 ms for 4M indexed updates (shape 11b vs 11).
|
|
74
|
+
* A persistent `List` rebuilt each frame with `l.map(|x| x + 1)` does the 4M updates of shape 11 in 15 to 19 ms, as fast as in-place `Slice` (25 ms). Rebuilding whole collections per frame is already cheap; FBIP only matters for updating or pushing one element of a big collection.
|
|
75
|
+
* 11 copying: 22 s for 4M updates, 5.5 microseconds per update of a 20,000-element Vec (an 8-byte-per-slot copy each time). Updating a handful of elements per frame in a copying Vec is affordable (about 50 microseconds for ten); updating every item is not.
|
|
76
|
+
|
|
77
|
+
## `fip fun`
|
|
78
|
+
|
|
79
|
+
`tests/fbip/vec_fip.plum`: `FVec` with `fip fun fpush` and `fip fun fset` (unique names) compiles and passes. A caller that holds the vec twice (`b := a; c := a.gpush(2)`) is refused with `type error: not in place: self may be shared by a caller: a is also held by b (call of gpush from keep)` at the call's line and column. That error is useful. The one for collisions is not: with the realistic names `fip fun push` is refused with `v is bound to a value that may be shared (call of push from fill)`, naming a local that is fine instead of the colliding `Slice.push` and `Str.split`. Another early failure while writing the test: `v := fill(20); v = v.fset(3, 99)` is refused (reason B above), correctly but surprisingly.
|
|
80
|
+
|
|
81
|
+
## Call sites in std and examples
|
|
82
|
+
|
|
83
|
+
Method: `grep -nE '\.(push|pop|set|removeAt|clear|add)\('` over `std` and `examples` `.plum` files, then a script that skips comment lines and splits sites inside `test` blocks from the rest, and classifies the rest by receiver text and method (no type information, so some `set` sites are guesses between `Array.set` and `Map.set`). The earlier figure of 363 was a different pattern; this count is 477 sites, of which 287 are inside `test` blocks (they test the APIs, and are ported with them) and 190 are code:
|
|
84
|
+
|
|
85
|
+
| category | sites | would work (with unique names and today's proof) | would not |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| `push` on a local or parameter (a builder: `out := Slice[T](); loop out.push(..); out`) | 68 | most: shape 1/9b. Fail if the result is pushed onto after being returned (shape 9) or if the vec is stored in a container first (8a) | the 9 and 8a cases, an estimate of 10 to 15 sites |
|
|
88
|
+
| Array element write inside a data structure (`self.data.set(..)` in Slice, Map, Deque, Heap, Grid, Scene's sort) | 46 | shape 2: already `self.data = self.data.with(..)` on a `mut` field; but all of these types share field names `data`, `keys`, `buf`, `items`, so need per-type keying; `Slice.slice` shares `data` (see views) | about the same 46 until per-type |
|
|
89
|
+
| scene-node `add` (the mutable tree API `parent.add(child)`) | 30 | not applicable: a tree with shared children | not a collection update |
|
|
90
|
+
| `Map.set` / `Slice.set` | 12 | Map.set returns nothing today; a persistent Map is a separate rewrite | |
|
|
91
|
+
| `pop` / `removeAt` / `clear` (shrinking, return a value) | 11 | need a new API (the removed element and the new collection) | all need a redesign |
|
|
92
|
+
| `push` on a `mut` struct field (`self.mines.push(..)`, `w.orbs.push(..)`) | 10 | shape 2 and 3 | |
|
|
93
|
+
| `canvas.clear` / `io.clear`, math `add`, other | 12 | not collections | |
|
|
94
|
+
| `push` on `self` (collection internals) | 1 | | |
|
|
95
|
+
|
|
96
|
+
So of the 190 code sites, about 22 are not collection updates and 30 are the scene tree. Of the other 138 collection updates, about 124 (the builders, `mut`-field pushes and Array writes) are in shapes that work in isolation, and only if per-type keying exists and no shared use of the same collection API exists anywhere in the program; about 25 to 30 of the pushes and all 11 shrinking ops need a changed API or a proof extension. Nearly all of them are at the mercy of the name collision, and every one would be at the mercy of a single shared use.
|
|
97
|
+
|
|
98
|
+
## Shared views: `Slice.slice`
|
|
99
|
+
|
|
100
|
+
`Slice.slice(i, j)` returns a `Slice` over the same `data` array with a different `start` and `size`; `set` on the view changes the parent. A persistent `Slice` cannot do that: two holders of one array make `data` non-unique, and the proof is by field name, so every `Slice` write would copy. Outside `std/Slice.plum`'s own tests, `.slice(` has one call in std and examples (a `Str.slice` test). `sublist` and `drop` already copy. So the sharing view is not used in real code and a persistent Slice can make `slice` copy (or drop it), losing nothing a program uses. A real read-only view (`SliceView`, borrows the array) would be a separate type with no write methods.
|
|
101
|
+
|
|
102
|
+
## Differential and fuzz
|
|
103
|
+
|
|
104
|
+
* `target/debug/plum test std examples tests`: 961 passed, 0 failed, 209 files (786 before the plan). With `--no-fbip`, `--no-fusion` and the default, `target/debug/plum test tests/fbip` gives 144 passed, 0 failed, 28 files each.
|
|
105
|
+
* `bash tests/fbip/fuzz/fuzz.sh 1000 1100`: ok=101, diff=0, err=0.
|
|
106
|
+
|
|
107
|
+
## VERDICT
|
|
108
|
+
|
|
109
|
+
**Stop stage 2 as planned. Go only after fixes, and the fixes are not small.** Porting `Slice`, `Map` and the rest to persistent APIs now would make them slower than today's mutating versions in the cases that matter (22 s for the game-update shape that `Slice` does in 25 ms), because none of the real names are proved.
|
|
110
|
+
|
|
111
|
+
The pilot does show the in-place machinery is sound and fast where it applies: with unique names, shapes 1 to 7 and 11b are in place at 1.4x the cost of `Slice`, the answers match with and without `--no-fbip`, and the fuzzer finds nothing. The work is in the proof's reach, not in `Array.with` or `fip fun`.
|
|
112
|
+
|
|
113
|
+
Fixes the proof needs, in order of how much they matter:
|
|
114
|
+
|
|
115
|
+
1. **Per-type keying.** Without it nothing works under real names. The checker already resolves every method call and field access; record the resolved callee and the field's owner type per `NodeId` and key the proof's tables by (type, method) and (type, field) instead of by name. This also removes std's tests and `Str.split` from the picture. Cost: the checker emits a side table per instantiation, `uniqueness.rs` (about 2,000 lines) changes its `methodIdx` and `fields` keys and its method resolution; the aliasing tests stay valid. The largest single item.
|
|
116
|
+
2. **No poisoning by a shared use.** A shared call site must only make that call site copy. The usual way is a second compiled copy of the method for shared receivers (`push` and `push#shared`), picked per call site by the proof; the cost is code size (each used method up to twice) and a proof that is per call site, not a fixpoint on the method. Without this, one snapshot in a program, which the deterministic simulation (replay, rewind) wants, turns all updates to copies.
|
|
117
|
+
3. **Hand-over forms.** `S(..s, f: s.f.m(..))` through a user method (shape 10, the functional game's form); a unique local moved into a constructor field, an argument or a container at its last use (shapes 8a, 11); a function returning its own unique local counting as fresh to its caller (shape 9). All three are one idea, ownership moves at the last use, and each is a rule in the proof with an aliasing test. Cost: moderate; each form is local to one function.
|
|
118
|
+
4. Smaller: the `bigger.with(..)` false negative; error messages that name the colliding method instead of blaming a local.
|
|
119
|
+
|
|
120
|
+
Rough sense of the order: 1 is a week of care, 2 and 3 are each smaller but 2 changes code generation. A stage 2 that ports only `Array`-backed types the engine controls, with unique-looking names, would work today but is exactly the workaround this pilot says real collections cannot use.
|
|
121
|
+
|
|
122
|
+
## What this means for removing the imperative scene and `mut`
|
|
123
|
+
|
|
124
|
+
Not supported by the numbers, in either direction:
|
|
125
|
+
|
|
126
|
+
* `mut` fields and the imperative scene are the only shapes that are fast today under real names (`Slice` at 7 ms per 1M pushes, 25 ms for the 4M-update frame loop). Removing them and returning the next game from `update` would make every collection update go through `Vec`-style code that copies, 22 s in the frame loop, until fixes 1 to 3 land.
|
|
127
|
+
* But the functional style that rebuilds per frame is not the problem. `List.map` per frame does the same 4M updates in 15 to 19 ms, so a game that returns a new world each frame from `update`, built from lists and spreads of small structs, is already fast. What is slow is a big array or map kept across frames and updated at a few indices; those are the shapes the proof does not reach.
|
|
128
|
+
* The scene tree (30 `add` sites) is a mutable tree with shared children whatever the proof does; it is not a candidate for this mechanism.
|
|
129
|
+
|
|
130
|
+
Recommendation: keep `mut` and the imperative scene for now, as the approved review plan says. Decide the removal after fixes 1 and 2 and a second pilot that repeats shapes 10 and 11 under real names. If fix 1 turns out larger than expected, an alternative worth weighing is `fip`-checked, explicitly owned collections at the type level instead of a program-wide proof.
|
docs/plans/2026-11-06-fbip.benchmark.md
CHANGED
|
@@ -50,3 +50,7 @@ Each probe has a `test` at size 1000 and the sweep covers them.
|
|
|
50
50
|
| `plum build examples/games/functional/game_state_view.plum -o <scratch>` (release), 3 runs | 0.77, 0.75, 0.75 s |
|
|
51
51
|
| `plum.wasm` for that game | 79,397 bytes |
|
|
52
52
|
| `target/debug/plum test std examples tests` | 61 s wall (83 s user), 786 tests, 178 files, load 5 to 8 |
|
|
53
|
+
|
|
54
|
+
## After stage 1 (T6)
|
|
55
|
+
|
|
56
|
+
The pilot persistent `Vec` and the same shapes on `Slice` are measured in docs/plans/2026-11-06-fbip-stage1.results.md (`tests/fbip/vec_*.plum`, `tests/fbip/slice_shapes.plum`). Release build, load 1.6 to 3.8. In short: `Slice` 1M pushes 7 ms, persistent `Vec` in place 10 ms with unique names, 1281 ms for 100k pushes (copying) with the realistic names `push`/`data`; per-frame update of 20,000 items for 200 frames: `Slice` 25 ms, `List.map` 15 to 19 ms, copying `Vec` 22 s, in-place `Vec` on a local 31 ms.
|
docs/plans/2026-11-06-receiver-functions.review.md
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# Top-level functions with receivers: review plan
|
|
2
|
-
|
|
3
|
-
**Status:** planned, not approved. Runs after FBIP, because both rewrite the same files. Nothing is built until you say so.
|
|
4
|
-
**Your idea (2026-11-06):** all functions are top level, written `fun Color.toStr(self)`. `primitive` is restricted to the standard library. Functions that need grouping go in their own module.
|
|
5
|
-
|
|
6
|
-
## Summary
|
|
7
|
-
|
|
8
|
-
Today a method is written inside its type's body, and a group of related functions goes in a `primitive`. This plan gives one way to define a function: always at the top level, with the type in its name when it belongs to one.
|
|
9
|
-
|
|
10
|
-
```plum
|
|
11
|
-
enum Color =
|
|
12
|
-
| Red
|
|
13
|
-
| Green
|
|
14
|
-
| Blue
|
|
15
|
-
|
|
16
|
-
fun Color.toStr(self) -> Str = match self
|
|
17
|
-
Red => "Red"
|
|
18
|
-
Green => "Green"
|
|
19
|
-
Blue => "Blue"
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Calling code doesn't change: `c.toStr()` still works, and so does everything built on it, such as generics, operators, `for` and overrides. The compiler already lifts nested methods to top-level functions with a receiver type internally, so this is mostly a change to the grammar and the source files.
|
|
23
|
-
|
|
24
|
-
## What you will get
|
|
25
|
-
|
|
26
|
-
- One way to write a function, and one less level of indentation everywhere.
|
|
27
|
-
- A type's declaration shows only its fields or variants, so it's easy to read at a glance.
|
|
28
|
-
- The definition looks like the call: `fun Vec2.add` and `Vec2.add` read the same way.
|
|
29
|
-
- User code has no `primitive` to learn.
|
|
30
|
-
- Modules do the grouping, like Gleam, which also gives Plum real namespaces.
|
|
31
|
-
|
|
32
|
-
## How it looks
|
|
33
|
-
|
|
34
|
-
```plum
|
|
35
|
-
struct Vec2(x: Float, y: Float)
|
|
36
|
-
|
|
37
|
-
fun Vec2.add(self, o: Vec2) -> Vec2 = # self: a method, called v.add(o)
|
|
38
|
-
Vec2(x: self.x + o.x, y: self.y + o.y)
|
|
39
|
-
|
|
40
|
-
fun Vec2.unit() -> Vec2 = # no self: a static, called Vec2.unit()
|
|
41
|
-
Vec2(x: 1.0, y: 1.0)
|
|
42
|
-
|
|
43
|
-
fun Box[T].map[B](self, f: fn(T) -> B) -> Box[B] = # generics
|
|
44
|
-
Box(value: f(self.value))
|
|
45
|
-
|
|
46
|
-
override fun Orbs.update(self, dt: Float) = ... # overrides
|
|
47
|
-
|
|
48
|
-
abstract fun Game.draw(self) # abstract methods
|
|
49
|
-
|
|
50
|
-
fun steer(ship: Ship, dt: Float) -> Ship = ... # a plain function
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## The real work: modules need to become namespaces
|
|
54
|
-
|
|
55
|
-
**This is the main finding, and the biggest part of the plan.** Grouping functions in a module only works if a module has its own names. Today it doesn't: the loader merges every imported file's items into one program, so all names share one global namespace. Two files can't both define `clamp`, as that's a "duplicate declaration" error, and there's no `math.clamp(x)` form. `Math.sqrt` only works today because `Math` is a `primitive`.
|
|
56
|
-
|
|
57
|
-
So user code needs something like this:
|
|
58
|
-
|
|
59
|
-
```plum
|
|
60
|
-
import app/rules
|
|
61
|
-
|
|
62
|
-
fun main(io: Io) =
|
|
63
|
-
n := rules.clamp(5, 0, 3)
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## What will be built
|
|
67
|
-
|
|
68
|
-
| Part | What it does | Size |
|
|
69
|
-
|---|---|---|
|
|
70
|
-
| Module namespaces | `import a/b` gives the name `b`, with `b.f()`, `b.T` and `b.Const`. Two modules can define the same name. Type methods come with their type | L |
|
|
71
|
-
| Receiver syntax | `fun T.m(self)`, statics, generics, `override`, `abstract`, `extern` on `Io`, in the grammar, parser, formatter and LSP | M |
|
|
72
|
-
| Top-level constants | `Vec2.Zero = ...` for a type, and `MaxRetries = 3` for a module (193 constants today, all inside bodies) | S |
|
|
73
|
-
| Migration tool | A script rewrites nested methods to top level. About 1,050 functions: 741 in `std`, 252 in `examples`, 64 in `tests` | M |
|
|
74
|
-
| User primitives | `Config`, `Grid`, `Id`, `Layout`, `Rules`, `Shelf` in the examples become types or modules, and `primitive` outside `std` becomes an error | S |
|
|
75
|
-
| Remove the old form | A method inside a type body becomes a parse error with a pointer to the new form | S |
|
|
76
|
-
| Docs | Language guide and examples | S |
|
|
77
|
-
|
|
78
|
-
## Decisions for you
|
|
79
|
-
|
|
80
|
-
1. **What about std's namespaces?** `Math`, `Os`, `Http`, `Testing`, `Base64`, `Ui`, `Key`, `Mouse` and `Blend` are `primitive` namespaces in `std`. You said `primitive` stays allowed in `std`, so `Math.sqrt` can stay as is. **I'd convert the plain namespaces to modules too,** keeping `primitive` for what it's truly for, declaring built-ins (`Int`, `Str`, `Io`, `Array`). Then `primitive` means one thing, and users see one model. That changes the case: `import std/math`, then `math.sqrt(x)`. It's more churn, though, and could be a later step.
|
|
81
|
-
|
|
82
|
-
2. **Must a method live in the same file as its type?** I'd say yes, like Go: there are no surprise methods from imports, and one file shows all of a type's behaviour. `std/Json` adds `toJson` to `Int`, `Str` and others, so std stays exempt, as it is today.
|
|
83
|
-
|
|
84
|
-
3. **How are names imported?** I'd start with `import a/b` then `b.f()`, like Gleam, plus types usable unqualified where the import names them. Does `import std/game/Game` still give plain `Game`, `Key` and `Canvas`? **I'd keep types and enum variants unqualified by import,** since `Game` and `Some` everywhere would otherwise get noisy, and require the module name only for plain functions and constants. Worth a look at real code before settling.
|
|
85
|
-
|
|
86
|
-
4. **Free functions in `main`'s file?** You earlier noted the opposite idea, that every function should belong to something. This plan replaces that note: plain functions stay, in modules. I'd also keep `steer(ship, dt)` style rules as plain functions.
|
|
87
|
-
|
|
88
|
-
## Risks
|
|
89
|
-
|
|
90
|
-
- **Namespaces touch the loader, checker, codegen and LSP.** They're the risky part, since names are assumed global throughout. Internally, the simplest route is to mangle names per module when merging, so later passes stay unchanged.
|
|
91
|
-
- **A large diff.** About 1,050 functions move, so review has to rely on tooling. The check is that example wasm stays byte-identical across the syntax migration, since the syntax changes and the program doesn't.
|
|
92
|
-
- **Self-hosting and FBIP.** The FBIP `fip` keyword has to fit the new form, as `fip fun T.m(self)`, and stage 2's call-site rewrites should land first.
|
|
93
|
-
- **Scattering.** Methods can drift away from their type within a file. `plum format` could keep them together if this becomes annoying.
|
|
94
|
-
|
|
95
|
-
## Out of scope
|
|
96
|
-
|
|
97
|
-
Package management, visibility (`pub`/private), and multi-file types.
|
|
98
|
-
|
|
99
|
-
## How we will know it works
|
|
100
|
-
|
|
101
|
-
- Wasm is byte-identical before and after the syntax migration, on every example.
|
|
102
|
-
- Two modules can define the same function name and both work.
|
|
103
|
-
- All tests pass, and the docs show one form only.
|
|
104
|
-
- `primitive` outside `std` and a method inside a type body each give a clear error.
|
|
105
|
-
|
|
106
|
-
## Rough time and cost
|
|
107
|
-
|
|
108
|
-
Two builds. Namespaces first, about 6 to 8 tasks, then receiver syntax and migration, about 5 to 6 tasks. Could land in either order, but namespaces first lets the migration move grouped functions straight into modules.
|
docs/plans/2026-11-06-simpler-language.review.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# A simpler language: review plan
|
|
2
|
+
|
|
3
|
+
**Status:** waiting for your decisions. Nothing here is built. This replaces the receiver-functions plan, whose `fun Orb.update(self)` syntax you rejected.
|
|
4
|
+
**Evidence:** FBIP stage 1 results, docs/plans/2026-11-06-fbip-stage1.results.md.
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
Remove inheritance, two of the three scene styles, and `primitive` namespaces from user code. Give Plum real modules. Make games plain structs. Then finish FBIP, which gets much simpler once inheritance is gone.
|
|
9
|
+
|
|
10
|
+
The pilot showed that FBIP can't go ahead as planned: the in-place proof works by name across the whole program, so a collection with ordinary names like `push` and `data` is never updated in place. That makes the work here a prerequisite, not just a tidy-up.
|
|
11
|
+
|
|
12
|
+
## What a game looks like
|
|
13
|
+
|
|
14
|
+
Methods stay indented under their type. Plain functions and constants live at the module level.
|
|
15
|
+
|
|
16
|
+
```plum
|
|
17
|
+
import std/game/gfx
|
|
18
|
+
import std/game/input
|
|
19
|
+
import std/game/scene
|
|
20
|
+
|
|
21
|
+
ArenaW = 800.0
|
|
22
|
+
|
|
23
|
+
struct Ship(x: Float, vx: Float) =
|
|
24
|
+
fun update(self, ax: Float, dt: Float) -> Ship =
|
|
25
|
+
Ship(..self, x: clamp(self.x + self.vx * dt, ArenaW), vx: self.vx + ax * 300.0 * dt)
|
|
26
|
+
|
|
27
|
+
fun render(self, sheet: Texture) -> Node =
|
|
28
|
+
Sprite(tex: sheet, x: self.x, y: 300.0, w: 32.0, h: 32.0)
|
|
29
|
+
|
|
30
|
+
struct OrbRun(ship: Ship, sheet: Texture) =
|
|
31
|
+
fun update(self, io: Io, dt: Float) -> OrbRun =
|
|
32
|
+
OrbRun(..self, ship: self.ship.update(input.axis(io, Key.Left, Key.Right), dt))
|
|
33
|
+
|
|
34
|
+
fun draw(self, io: Io) =
|
|
35
|
+
gfx.clear(io, Color.Black)
|
|
36
|
+
self.ship.render(self.sheet).draw(io)
|
|
37
|
+
|
|
38
|
+
fun clamp(v: Float, max: Float) -> Float =
|
|
39
|
+
v.max(0.0).min(max)
|
|
40
|
+
|
|
41
|
+
fun main(io: Io) -> OrbRun =
|
|
42
|
+
OrbRun(ship: Ship(x: 0.0, vx: 0.0), sheet: gfx.loadTexture(io, "sprites.png"))
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A game is any type with `update` and `draw`, found by duck typing, as with Ebiten.
|
|
46
|
+
|
|
47
|
+
## What will be built
|
|
48
|
+
|
|
49
|
+
In this order, because each step makes the next one easier.
|
|
50
|
+
|
|
51
|
+
| Step | Part | What it does | Size |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| 1 | Plain games | Any type with `update` and `draw` is a game, and `update` returns the next one. Ports every game and fixture | M |
|
|
54
|
+
| 1 | One scene style | Remove `std/game/imperative` and `std/game/pure` (about 2,250 lines) and their examples. State-and-view stays, as `std/game/scene` | M |
|
|
55
|
+
| 2 | No inheritance | Remove `abstract`, `override` and struct embedding: `embeds.rs`, subtyping, most of `ctors.rs`, override dispatch | L, mostly deletion |
|
|
56
|
+
| 3 | Modules | `import a/b` gives `b.f()` and `b.Const`. Two modules can reuse a name. Module-level constants. `extern` on module-level functions, with `io` first. `primitive` only for std's built-ins | L |
|
|
57
|
+
| 4 | Syntax | The changes in decision 3 below | M |
|
|
58
|
+
| 5 | Proof by type | The proof uses the checker's types, so `Vec.push` and `Slice.push` are separate. With no overrides, every call has exactly one target | L |
|
|
59
|
+
| 6 | Last-use moves | A local moved into a field or returned counts as handed over: `S(..s, f: s.f.push(x))` through a method, and a returned unique local | M |
|
|
60
|
+
| 7 | Shared and unique callers | One shared call no longer turns every call into a copy, by compiling a second version of the function | M |
|
|
61
|
+
| 8 | FBIP stage 2 | Run the pilot again. If it passes, port `Slice`, `Map`, `Buffer`, `Deque` and the rest | L |
|
|
62
|
+
|
|
63
|
+
## What the pilot measured
|
|
64
|
+
|
|
65
|
+
20,000 items updated for 200 frames, through a state struct field, on a release build:
|
|
66
|
+
|
|
67
|
+
| Version | Time |
|
|
68
|
+
|---|---|
|
|
69
|
+
| Today's `Slice` with `mut` | 25 ms |
|
|
70
|
+
| A persistent `List`, `l = l.map(..)` | 15 to 19 ms |
|
|
71
|
+
| A pilot `Vec` in a local, proved in place | 31 ms |
|
|
72
|
+
| A pilot `Vec` in a state struct field, copying because the proof fails | 22,000 ms |
|
|
73
|
+
|
|
74
|
+
Two things follow:
|
|
75
|
+
- **In-place works when it's proved,** close to `Slice`. When it isn't proved, it's about 900 times slower. That's the risk `fip fun` exists to catch.
|
|
76
|
+
- **A persistent `List` is already fast, even faster than `Slice`.** The state-and-view style holds lists, so games using it are safe now, before any of steps 5 to 8.
|
|
77
|
+
|
|
78
|
+
## Decisions for you
|
|
79
|
+
|
|
80
|
+
1. **Remove inheritance?** Recommended. It costs little in features (enums and structs of functions cover it), and it removes most of the difficulty in the proof.
|
|
81
|
+
2. **Remove the imperative scene?** Recommended. If you'd rather keep a tree of objects you can change, the alternative is to keep it, written with `mut` fields and enums.
|
|
82
|
+
3. **Which syntax changes?**
|
|
83
|
+
- **Block `if` as an expression, and no `return`.** Recommended, the most valuable and the most disruptive. Fusion skips any function with an early `return`, so this turns the optimiser on everywhere. `?` stays. Returning from inside a loop would need `fold` or a helper.
|
|
84
|
+
- **`self.items .= push(x)`** for `self.items = self.items.push(x)`. Recommended.
|
|
85
|
+
- **`State(..s, ship.x: 1.0)`**, paths in a spread. Recommended, because binding a field to a variable blocks the proof.
|
|
86
|
+
- `consume`, a move marker: only if steps 5 to 7 leave a real gap.
|
|
87
|
+
4. **Should std's namespaces become modules?** `Math.sqrt` becomes `math.sqrt`, and so on. Recommended, so `primitive` means one thing: declaring a built-in.
|
|
88
|
+
5. **How are names imported?** Recommended: types and enum variants are unqualified, and functions and constants use the module name: `Texture`, `Some`, but `gfx.rect`.
|
|
89
|
+
6. **Remove `mut`?** Later. It becomes possible after step 8, if the shells no longer need it. Decide then, with numbers.
|
|
90
|
+
|
|
91
|
+
## Risks
|
|
92
|
+
|
|
93
|
+
- **Silent copies are the top risk.** A shape the proof misses is about 900 times slower, with no error. `--explain-fusion` shows it and `fip fun` makes it an error, but a game that doesn't use `fip` just gets slow.
|
|
94
|
+
- **Steps 5 to 7 may not cover real game code.** The pilot re-run at step 8 decides that, and `List` remains a fast fallback either way.
|
|
95
|
+
- **Wrong proofs are silent bugs.** Each proof change gets the same attack as stage 1: hostile programs and the fuzzer in `tests/fbip/fuzz`, compared against `--no-fbip`. That found four real miscompiles in stage 1.
|
|
96
|
+
- **Losing the object model.** People from libGDX or Unity lose a familiar style, and very large worlds rely on fusion. Measure `stress_scene` and `stress_world` at step 1.
|
|
97
|
+
- **Modules touch every pass,** since names are assumed global throughout. Mangling names per module while merging keeps later passes unchanged.
|
|
98
|
+
|
|
99
|
+
## Out of scope
|
|
100
|
+
|
|
101
|
+
Package management, visibility (`pub`/private), multi-touch, and splitting `Io`.
|
|
102
|
+
|
|
103
|
+
## How we will know it works
|
|
104
|
+
|
|
105
|
+
- Each step keeps all tests green, compared against a recorded baseline.
|
|
106
|
+
- The pilot's table passes with realistic names, in particular a state struct field.
|
|
107
|
+
- `stress_scene` and `stress_world` are no slower than today, on a quiet machine.
|
|
108
|
+
- The compiler is smaller at the end than now.
|
|
109
|
+
|
|
110
|
+
## Rough time and cost
|
|
111
|
+
|
|
112
|
+
Large, but steps 1 and 2 are mostly deletion. Each step is its own build and can be stopped at. Steps 1 to 4 are worth having even if FBIP stops there.
|
tests/fbip/slice_shapes.plum
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/Slice
|
|
7
|
+
import std/List
|
|
8
|
+
import std/game/Vec2
|
|
9
|
+
|
|
10
|
+
# The comparison for tests/fbip/vec_*.plum: the same twelve shapes on `Slice`, which mutates in place
|
|
11
|
+
# today (`push`, `putAt`), and the per-frame update on a persistent `List` (`List.map`). Same
|
|
12
|
+
# checksums as the Vec versions. Kept in its own file because `Slice.push` and `Vec.push` share a
|
|
13
|
+
# name, which the uniqueness proof cannot tell apart.
|
|
14
|
+
|
|
15
|
+
fun s1(n: Int) -> Int =
|
|
16
|
+
v := Slice[Int]()
|
|
17
|
+
for i := range n
|
|
18
|
+
v.push(i)
|
|
19
|
+
v.length() + v.at(n - 1) + v.at(n / 2)
|
|
20
|
+
|
|
21
|
+
struct Bag2(items: Slice[Int], mut count: Int) =
|
|
22
|
+
fun add(self, x: Int) -> Unit =
|
|
23
|
+
self.items.push(x)
|
|
24
|
+
self.count = self.count + 1
|
|
25
|
+
|
|
26
|
+
fun s2(n: Int) -> Int =
|
|
27
|
+
b := Bag2(items: Slice[Int](), count: 0)
|
|
28
|
+
for i := range n
|
|
29
|
+
b.add(i)
|
|
30
|
+
b.items.length() + b.items.at(n - 1) + b.count
|
|
31
|
+
|
|
32
|
+
struct Inner3(items: Slice[Int])
|
|
33
|
+
struct Outer3(inner: Inner3, tag: Int)
|
|
34
|
+
|
|
35
|
+
fun s3(n: Int) -> Int =
|
|
36
|
+
o := Outer3(inner: Inner3(items: Slice[Int]()), tag: 5)
|
|
37
|
+
for i := range n
|
|
38
|
+
o.inner.items.push(i)
|
|
39
|
+
o.inner.items.length() + o.inner.items.at(n - 1) + o.tag
|
|
40
|
+
|
|
41
|
+
fun grow4(v: Slice[Int], x: Int) -> Slice[Int] =
|
|
42
|
+
v.push(x * 2)
|
|
43
|
+
v
|
|
44
|
+
|
|
45
|
+
fun s4(n: Int) -> Int =
|
|
46
|
+
v := Slice[Int]()
|
|
47
|
+
for i := range n
|
|
48
|
+
v = grow4(v, i)
|
|
49
|
+
v.length() + v.at(n - 1)
|
|
50
|
+
|
|
51
|
+
fun s5(n: Int) -> Int =
|
|
52
|
+
a := Slice[Int]()
|
|
53
|
+
b := Slice[Vec2]()
|
|
54
|
+
for i := range n
|
|
55
|
+
a.push(i)
|
|
56
|
+
b.push(Vec2(x: Float(i), y: 1.0))
|
|
57
|
+
r := a.length() + b.length() + a.at(n - 1)
|
|
58
|
+
if b.at(n - 1).x == Float(n - 1) then r else 0 - 1
|
|
59
|
+
|
|
60
|
+
fun s6(n: Int) -> Int =
|
|
61
|
+
v := Slice[Int]()
|
|
62
|
+
for i := range n
|
|
63
|
+
len := v.length()
|
|
64
|
+
v.push(len)
|
|
65
|
+
v.length() + v.at(n - 1)
|
|
66
|
+
|
|
67
|
+
struct Pair7(xs: Slice[Int], ys: Slice[Int])
|
|
68
|
+
|
|
69
|
+
fun s7(n: Int) -> Int =
|
|
70
|
+
p := Pair7(xs: Slice[Int](), ys: Slice[Int]())
|
|
71
|
+
for i := range n
|
|
72
|
+
p.xs.push(i)
|
|
73
|
+
p.ys.push(i * 2)
|
|
74
|
+
p.xs.length() + p.ys.length() + p.xs.at(n - 1) + p.ys.at(n - 1)
|
|
75
|
+
|
|
76
|
+
fun s8(n: Int) -> Int =
|
|
77
|
+
rows := Slice[Slice[Int]]()
|
|
78
|
+
for r := range n / 10
|
|
79
|
+
row := Slice[Int]()
|
|
80
|
+
for c := range 10
|
|
81
|
+
row.push(r * 10 + c)
|
|
82
|
+
rows.push(row)
|
|
83
|
+
rows.length() + rows.at(n / 10 - 1).at(9)
|
|
84
|
+
|
|
85
|
+
fun make9(n: Int) -> Slice[Int] =
|
|
86
|
+
v := Slice[Int]()
|
|
87
|
+
for i := range n
|
|
88
|
+
v.push(i)
|
|
89
|
+
v
|
|
90
|
+
|
|
91
|
+
fun s9(n: Int) -> Int =
|
|
92
|
+
v := make9(n)
|
|
93
|
+
for i := range n
|
|
94
|
+
v.push(i + n)
|
|
95
|
+
v.length() + v.at(2 * n - 1)
|
|
96
|
+
|
|
97
|
+
struct State10(items: Slice[Int], tick: Int)
|
|
98
|
+
|
|
99
|
+
fun step10(s: State10, x: Int) -> State10 =
|
|
100
|
+
s.items.push(x)
|
|
101
|
+
State10(..s, tick: s.tick + 1)
|
|
102
|
+
|
|
103
|
+
fun s10(n: Int) -> Int =
|
|
104
|
+
s := State10(items: Slice[Int](), tick: 0)
|
|
105
|
+
for i := range n
|
|
106
|
+
s = step10(s, i)
|
|
107
|
+
s.items.length() + s.items.at(n - 1) + s.tick
|
|
108
|
+
|
|
109
|
+
struct World11(items: Slice[Int], frame: Int)
|
|
110
|
+
|
|
111
|
+
fun frame11(w: World11, n: Int) -> World11 =
|
|
112
|
+
for i := range n
|
|
113
|
+
w.items.putAt(i, w.items.takeAt(i) + 1)
|
|
114
|
+
World11(..w, frame: w.frame + 1)
|
|
115
|
+
|
|
116
|
+
fun s11(n: Int, frames: Int) -> Int =
|
|
117
|
+
v := Slice[Int]()
|
|
118
|
+
for i := range n
|
|
119
|
+
v.push(0)
|
|
120
|
+
w := World11(items: v, frame: 0)
|
|
121
|
+
for f := range frames
|
|
122
|
+
w = frame11(w, n)
|
|
123
|
+
w.items.at(0) + w.items.at(n - 1) + w.frame
|
|
124
|
+
|
|
125
|
+
# the persistent alternative to Slice: a List, rebuilt each frame by `map`
|
|
126
|
+
fun s11list(n: Int, frames: Int) -> Int =
|
|
127
|
+
l: List[Int] := Nil
|
|
128
|
+
for i := range n
|
|
129
|
+
l = Cons(head: 0, tail: l)
|
|
130
|
+
for f := range frames
|
|
131
|
+
l = l.map(|x| x + 1)
|
|
132
|
+
l.at(0) + l.at(n - 1) + frames
|
|
133
|
+
|
|
134
|
+
fun main(io: Io) =
|
|
135
|
+
n := 100000
|
|
136
|
+
t := Time.now(io).toMillis()
|
|
137
|
+
r := s1(n)
|
|
138
|
+
u := Time.now(io).toMillis()
|
|
139
|
+
io.printLn("slice shape 1 result={r} ms={u - t}")
|
|
140
|
+
t = Time.now(io).toMillis()
|
|
141
|
+
r = s2(n)
|
|
142
|
+
u = Time.now(io).toMillis()
|
|
143
|
+
io.printLn("slice shape 2 result={r} ms={u - t}")
|
|
144
|
+
t = Time.now(io).toMillis()
|
|
145
|
+
r = s3(n)
|
|
146
|
+
u = Time.now(io).toMillis()
|
|
147
|
+
io.printLn("slice shape 3 result={r} ms={u - t}")
|
|
148
|
+
t = Time.now(io).toMillis()
|
|
149
|
+
r = s4(n)
|
|
150
|
+
u = Time.now(io).toMillis()
|
|
151
|
+
io.printLn("slice shape 4 result={r} ms={u - t}")
|
|
152
|
+
t = Time.now(io).toMillis()
|
|
153
|
+
r = s5(n)
|
|
154
|
+
u = Time.now(io).toMillis()
|
|
155
|
+
io.printLn("slice shape 5 result={r} ms={u - t}")
|
|
156
|
+
t = Time.now(io).toMillis()
|
|
157
|
+
r = s6(n)
|
|
158
|
+
u = Time.now(io).toMillis()
|
|
159
|
+
io.printLn("slice shape 6 result={r} ms={u - t}")
|
|
160
|
+
t = Time.now(io).toMillis()
|
|
161
|
+
r = s7(n)
|
|
162
|
+
u = Time.now(io).toMillis()
|
|
163
|
+
io.printLn("slice shape 7 result={r} ms={u - t}")
|
|
164
|
+
t = Time.now(io).toMillis()
|
|
165
|
+
r = s8(n)
|
|
166
|
+
u = Time.now(io).toMillis()
|
|
167
|
+
io.printLn("slice shape 8 result={r} ms={u - t}")
|
|
168
|
+
t = Time.now(io).toMillis()
|
|
169
|
+
r = s9(n / 2)
|
|
170
|
+
u = Time.now(io).toMillis()
|
|
171
|
+
io.printLn("slice shape 9 result={r} ms={u - t}")
|
|
172
|
+
t = Time.now(io).toMillis()
|
|
173
|
+
r = s10(n)
|
|
174
|
+
u = Time.now(io).toMillis()
|
|
175
|
+
io.printLn("slice shape 10 result={r} ms={u - t}")
|
|
176
|
+
t = Time.now(io).toMillis()
|
|
177
|
+
r = s11(20000, 200)
|
|
178
|
+
u = Time.now(io).toMillis()
|
|
179
|
+
io.printLn("slice shape 11 result={r} ms={u - t}")
|
|
180
|
+
t = Time.now(io).toMillis()
|
|
181
|
+
r = s11list(20000, 200)
|
|
182
|
+
u = Time.now(io).toMillis()
|
|
183
|
+
io.printLn("list map shape 11 result={r} ms={u - t}")
|
|
184
|
+
|
|
185
|
+
test "slice shapes agree with the vec shapes"
|
|
186
|
+
assert s1(1000) == 2499
|
|
187
|
+
assert s2(1000) == 2999
|
|
188
|
+
assert s3(1000) == 2004
|
|
189
|
+
assert s4(1000) == 2998
|
|
190
|
+
assert s5(1000) == 2999
|
|
191
|
+
assert s6(1000) == 1999
|
|
192
|
+
assert s7(1000) == 4997
|
|
193
|
+
assert s8(1000) == 1099
|
|
194
|
+
assert s9(500) == 1000 + 999
|
|
195
|
+
assert s10(1000) == 2999
|
|
196
|
+
assert s11(50, 5) == 15
|
|
197
|
+
assert s11list(50, 5) == 15
|
tests/fbip/uvec.plum
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Array
|
|
3
|
+
import std/Slice
|
|
4
|
+
|
|
5
|
+
# FBIP pilot, the unique-name copy of vec.plum: `udata`, `usize`, `upush`, `uset`, `uat`, `ulen`, names no std
|
|
6
|
+
# type uses, so the proof can see this type alone. Same code otherwise. Used by vec_unique_names.plum.
|
|
7
|
+
|
|
8
|
+
struct UVec[T](udata: Array[T], usize: Int) =
|
|
9
|
+
fun init() -> UVec[T] = UVec(udata: Array[T]().resize(0), usize: 0)
|
|
10
|
+
|
|
11
|
+
fun upush(self, v: T) -> UVec[T] =
|
|
12
|
+
if self.usize < self.udata.length()
|
|
13
|
+
UVec(..self, udata: self.udata.with(self.usize, v), usize: self.usize + 1)
|
|
14
|
+
else
|
|
15
|
+
UVec(udata: self.udata.resize(if self.usize == 0 then 8 else self.usize * 2).with(self.usize, v), usize: self.usize + 1)
|
|
16
|
+
|
|
17
|
+
fun uset(self, i: Int, v: T) -> UVec[T] =
|
|
18
|
+
UVec(..self, udata: self.udata.with(i, v))
|
|
19
|
+
|
|
20
|
+
fun uat(self, i: Int) -> T = self.udata.get(i)
|
|
21
|
+
|
|
22
|
+
fun ulen(self) -> Int = self.usize
|
tests/fbip/vec.plum
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Array
|
|
3
|
+
import std/Slice
|
|
4
|
+
|
|
5
|
+
# FBIP pilot: a persistent vector written only in Plum, with the realistic names a real
|
|
6
|
+
# collection would use (`data`, `size`, `push`, `set`, `at`, `length`). `push` and `set` return the
|
|
7
|
+
# new vector; they update in place only when the compiler proves nobody else holds the old one.
|
|
8
|
+
# Shapes that use it: tests/fbip/vec_*.plum. Results: docs/plans/2026-11-06-fbip-stage1.results.md.
|
|
9
|
+
|
|
10
|
+
struct Vec[T](data: Array[T], size: Int) =
|
|
11
|
+
fun init() -> Vec[T] = Vec(data: Array[T]().resize(0), size: 0)
|
|
12
|
+
|
|
13
|
+
fun push(self, v: T) -> Vec[T] =
|
|
14
|
+
if self.size < self.data.length()
|
|
15
|
+
Vec(..self, data: self.data.with(self.size, v), size: self.size + 1)
|
|
16
|
+
else
|
|
17
|
+
Vec(data: self.data.resize(if self.size == 0 then 8 else self.size * 2).with(self.size, v), size: self.size + 1)
|
|
18
|
+
|
|
19
|
+
fun set(self, i: Int, v: T) -> Vec[T] =
|
|
20
|
+
Vec(..self, data: self.data.with(i, v))
|
|
21
|
+
|
|
22
|
+
fun at(self, i: Int) -> T = self.data.get(i)
|
|
23
|
+
|
|
24
|
+
fun length(self) -> Int = self.size
|
tests/fbip/vec_combined.plum
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Every shape in one program, to see whether one shape's calls poison another's.
|
|
10
|
+
|
|
11
|
+
# Shape 1: a Vec in a local, `v = v.push(i)` in a loop.
|
|
12
|
+
|
|
13
|
+
fun run1(n: Int) -> Int =
|
|
14
|
+
v := Vec[Int]()
|
|
15
|
+
for i := range n
|
|
16
|
+
v = v.push(i)
|
|
17
|
+
v.length() + v.at(n - 1) + v.at(n / 2)
|
|
18
|
+
|
|
19
|
+
# Shape 2: a `mut` field of a struct, `self.items = self.items.push(x)` in a method.
|
|
20
|
+
|
|
21
|
+
struct Bag2(mut items: Vec[Int], mut count: Int) =
|
|
22
|
+
fun add(self, x: Int) -> Unit =
|
|
23
|
+
self.items = self.items.push(x)
|
|
24
|
+
self.count = self.count + 1
|
|
25
|
+
|
|
26
|
+
fun run2(n: Int) -> Int =
|
|
27
|
+
b := Bag2(items: Vec[Int](), count: 0)
|
|
28
|
+
for i := range n
|
|
29
|
+
b.add(i)
|
|
30
|
+
b.items.length() + b.items.at(n - 1) + b.count
|
|
31
|
+
|
|
32
|
+
# Shape 3: a Vec in a field of a nested struct, `o.inner.items = o.inner.items.push(i)`.
|
|
33
|
+
|
|
34
|
+
struct Inner3(mut items: Vec[Int])
|
|
35
|
+
struct Outer3(inner: Inner3, tag: Int)
|
|
36
|
+
|
|
37
|
+
fun run3(n: Int) -> Int =
|
|
38
|
+
o := Outer3(inner: Inner3(items: Vec[Int]()), tag: 5)
|
|
39
|
+
for i := range n
|
|
40
|
+
o.inner.items = o.inner.items.push(i)
|
|
41
|
+
o.inner.items.length() + o.inner.items.at(n - 1) + o.tag
|
|
42
|
+
|
|
43
|
+
# Shape 4: a helper that takes the Vec and returns it, `v = grow(v, i)`.
|
|
44
|
+
|
|
45
|
+
fun grow4(v: Vec[Int], x: Int) -> Vec[Int] = v.push(x * 2)
|
|
46
|
+
|
|
47
|
+
fun run4(n: Int) -> Int =
|
|
48
|
+
v := Vec[Int]()
|
|
49
|
+
for i := range n
|
|
50
|
+
v = grow4(v, i)
|
|
51
|
+
v.length() + v.at(n - 1)
|
|
52
|
+
|
|
53
|
+
# Shape 5: Vec[Int] and Vec[Vec2] in one program.
|
|
54
|
+
|
|
55
|
+
fun run5(n: Int) -> Int =
|
|
56
|
+
a := Vec[Int]()
|
|
57
|
+
b := Vec[Vec2]()
|
|
58
|
+
for i := range n
|
|
59
|
+
a = a.push(i)
|
|
60
|
+
b = b.push(Vec2(x: Float(i), y: 1.0))
|
|
61
|
+
r := a.length() + b.length() + a.at(n - 1)
|
|
62
|
+
if b.at(n - 1).x == Float(n - 1) then r else 0 - 1
|
|
63
|
+
|
|
64
|
+
# Shape 6: the length is read between updates, `n := v.length(); v = v.push(n)`.
|
|
65
|
+
|
|
66
|
+
fun run6(n: Int) -> Int =
|
|
67
|
+
v := Vec[Int]()
|
|
68
|
+
for i := range n
|
|
69
|
+
len := v.length()
|
|
70
|
+
v = v.push(len)
|
|
71
|
+
v.length() + v.at(n - 1)
|
|
72
|
+
|
|
73
|
+
# Shape 7: two Vecs in one struct, both pushed.
|
|
74
|
+
|
|
75
|
+
struct Pair7(mut xs: Vec[Int], mut ys: Vec[Int])
|
|
76
|
+
|
|
77
|
+
fun run7(n: Int) -> Int =
|
|
78
|
+
p := Pair7(xs: Vec[Int](), ys: Vec[Int]())
|
|
79
|
+
for i := range n
|
|
80
|
+
p.xs = p.xs.push(i)
|
|
81
|
+
p.ys = p.ys.push(i * 2)
|
|
82
|
+
p.xs.length() + p.ys.length() + p.xs.at(n - 1) + p.ys.at(n - 1)
|
|
83
|
+
|
|
84
|
+
# Shape 8: a Vec of Vecs. 8a builds rows and pushes them; 8b pushes onto a row stored in the outer Vec (`rows = rows.set(j, rows.at(j).push(i))`).
|
|
85
|
+
|
|
86
|
+
fun run8(n: Int) -> Int =
|
|
87
|
+
rows := Vec[Vec[Int]]()
|
|
88
|
+
for r := range n / 10
|
|
89
|
+
row := Vec[Int]()
|
|
90
|
+
for c := range 10
|
|
91
|
+
row = row.push(r * 10 + c)
|
|
92
|
+
rows = rows.push(row)
|
|
93
|
+
rows.length() + rows.at(n / 10 - 1).at(9)
|
|
94
|
+
|
|
95
|
+
fun run8b(n: Int) -> Int =
|
|
96
|
+
rows := Vec[Vec[Int]]()
|
|
97
|
+
for r := range 10
|
|
98
|
+
rows = rows.push(Vec[Int]())
|
|
99
|
+
for i := range n
|
|
100
|
+
j := i % 10
|
|
101
|
+
rows = rows.set(j, rows.at(j).push(i))
|
|
102
|
+
rows.at(0).length() + rows.at(9).at(n / 10 - 1)
|
|
103
|
+
|
|
104
|
+
# Shape 9: built by one function, returned, then pushed by the caller.
|
|
105
|
+
|
|
106
|
+
fun make9(n: Int) -> Vec[Int] =
|
|
107
|
+
v := Vec[Int]()
|
|
108
|
+
for i := range n
|
|
109
|
+
v = v.push(i)
|
|
110
|
+
v
|
|
111
|
+
|
|
112
|
+
fun run9(n: Int) -> Int =
|
|
113
|
+
v := make9(n)
|
|
114
|
+
for i := range n
|
|
115
|
+
v = v.push(i + n)
|
|
116
|
+
v.length() + v.at(2 * n - 1)
|
|
117
|
+
|
|
118
|
+
# Shape 10: an immutable state struct, `State(..s, items: s.items.push(x))`, in a step function.
|
|
119
|
+
|
|
120
|
+
struct State10(items: Vec[Int], tick: Int)
|
|
121
|
+
|
|
122
|
+
fun step10(s: State10, x: Int) -> State10 =
|
|
123
|
+
State10(..s, items: s.items.push(x), tick: s.tick + 1)
|
|
124
|
+
|
|
125
|
+
fun run10(n: Int) -> Int =
|
|
126
|
+
s := State10(items: Vec[Int](), tick: 0)
|
|
127
|
+
for i := range n
|
|
128
|
+
s = step10(s, i)
|
|
129
|
+
s.items.length() + s.items.at(n - 1) + s.tick
|
|
130
|
+
|
|
131
|
+
# Shape 11: a game-like update, every item of a 20,000-item Vec updated per frame, through a struct field of an immutable state, for many frames.
|
|
132
|
+
|
|
133
|
+
struct World11(items: Vec[Int], frame: Int)
|
|
134
|
+
|
|
135
|
+
fun frame11(w: World11, n: Int) -> World11 =
|
|
136
|
+
for i := range n
|
|
137
|
+
w = World11(..w, items: w.items.set(i, w.items.at(i) + 1))
|
|
138
|
+
World11(..w, frame: w.frame + 1)
|
|
139
|
+
|
|
140
|
+
fun run11(n: Int, frames: Int) -> Int =
|
|
141
|
+
v := Vec[Int]()
|
|
142
|
+
for i := range n
|
|
143
|
+
v = v.push(0)
|
|
144
|
+
w := World11(items: v, frame: 0)
|
|
145
|
+
for f := range frames
|
|
146
|
+
w = frame11(w, n)
|
|
147
|
+
w.items.at(0) + w.items.at(n - 1) + w.frame
|
|
148
|
+
|
|
149
|
+
fun main(io: Io) =
|
|
150
|
+
t1 := Time.now(io).toMillis()
|
|
151
|
+
r1 := run1(100000)
|
|
152
|
+
u1 := Time.now(io).toMillis()
|
|
153
|
+
io.printLn("vec_combined shape 1 result={r1} ms={u1 - t1}")
|
|
154
|
+
t2 := Time.now(io).toMillis()
|
|
155
|
+
r2 := run2(100000)
|
|
156
|
+
u2 := Time.now(io).toMillis()
|
|
157
|
+
io.printLn("vec_combined shape 2 result={r2} ms={u2 - t2}")
|
|
158
|
+
t3 := Time.now(io).toMillis()
|
|
159
|
+
r3 := run3(100000)
|
|
160
|
+
u3 := Time.now(io).toMillis()
|
|
161
|
+
io.printLn("vec_combined shape 3 result={r3} ms={u3 - t3}")
|
|
162
|
+
t4 := Time.now(io).toMillis()
|
|
163
|
+
r4 := run4(100000)
|
|
164
|
+
u4 := Time.now(io).toMillis()
|
|
165
|
+
io.printLn("vec_combined shape 4 result={r4} ms={u4 - t4}")
|
|
166
|
+
t5 := Time.now(io).toMillis()
|
|
167
|
+
r5 := run5(100000)
|
|
168
|
+
u5 := Time.now(io).toMillis()
|
|
169
|
+
io.printLn("vec_combined shape 5 result={r5} ms={u5 - t5}")
|
|
170
|
+
t6 := Time.now(io).toMillis()
|
|
171
|
+
r6 := run6(100000)
|
|
172
|
+
u6 := Time.now(io).toMillis()
|
|
173
|
+
io.printLn("vec_combined shape 6 result={r6} ms={u6 - t6}")
|
|
174
|
+
t7 := Time.now(io).toMillis()
|
|
175
|
+
r7 := run7(100000)
|
|
176
|
+
u7 := Time.now(io).toMillis()
|
|
177
|
+
io.printLn("vec_combined shape 7 result={r7} ms={u7 - t7}")
|
|
178
|
+
t8 := Time.now(io).toMillis()
|
|
179
|
+
r8 := run8(100000) + run8b(2000)
|
|
180
|
+
u8 := Time.now(io).toMillis()
|
|
181
|
+
io.printLn("vec_combined shape 8 result={r8} ms={u8 - t8}")
|
|
182
|
+
t9 := Time.now(io).toMillis()
|
|
183
|
+
r9 := run9(50000)
|
|
184
|
+
u9 := Time.now(io).toMillis()
|
|
185
|
+
io.printLn("vec_combined shape 9 result={r9} ms={u9 - t9}")
|
|
186
|
+
t10 := Time.now(io).toMillis()
|
|
187
|
+
r10 := run10(100000)
|
|
188
|
+
u10 := Time.now(io).toMillis()
|
|
189
|
+
io.printLn("vec_combined shape 10 result={r10} ms={u10 - t10}")
|
|
190
|
+
t11 := Time.now(io).toMillis()
|
|
191
|
+
r11 := run11(20000, 200)
|
|
192
|
+
u11 := Time.now(io).toMillis()
|
|
193
|
+
io.printLn("vec_combined shape 11 result={r11} ms={u11 - t11}")
|
|
194
|
+
|
|
195
|
+
test "vec local"
|
|
196
|
+
assert run1(1000) == 2499
|
|
197
|
+
|
|
198
|
+
test "vec mutfield"
|
|
199
|
+
assert run2(1000) == 2999
|
|
200
|
+
|
|
201
|
+
test "vec nested"
|
|
202
|
+
assert run3(1000) == 2004
|
|
203
|
+
|
|
204
|
+
test "vec helper"
|
|
205
|
+
assert run4(1000) == 2998
|
|
206
|
+
|
|
207
|
+
test "vec generic"
|
|
208
|
+
assert run5(1000) == 2999
|
|
209
|
+
|
|
210
|
+
test "vec readbetween"
|
|
211
|
+
assert run6(1000) == 1999
|
|
212
|
+
|
|
213
|
+
test "vec twolists"
|
|
214
|
+
assert run7(1000) == 4997
|
|
215
|
+
|
|
216
|
+
test "vec listoflists"
|
|
217
|
+
assert run8(1000) == 1099
|
|
218
|
+
|
|
219
|
+
test "vec listoflists (2)"
|
|
220
|
+
assert run8b(1000) == 100 + 999
|
|
221
|
+
|
|
222
|
+
test "vec returned"
|
|
223
|
+
assert run9(500) == 1000 + 999
|
|
224
|
+
|
|
225
|
+
test "vec statespread"
|
|
226
|
+
assert run10(1000) == 2999
|
|
227
|
+
|
|
228
|
+
test "vec frames"
|
|
229
|
+
assert run11(50, 5) == 15
|
tests/fbip/vec_combined_shared.plum
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Every shape in one program, to see whether one shape's calls poison another's.
|
|
10
|
+
|
|
11
|
+
# Shape 1: a Vec in a local, `v = v.push(i)` in a loop.
|
|
12
|
+
|
|
13
|
+
fun run1(n: Int) -> Int =
|
|
14
|
+
v := Vec[Int]()
|
|
15
|
+
for i := range n
|
|
16
|
+
v = v.push(i)
|
|
17
|
+
v.length() + v.at(n - 1) + v.at(n / 2)
|
|
18
|
+
|
|
19
|
+
# Shape 2: a `mut` field of a struct, `self.items = self.items.push(x)` in a method.
|
|
20
|
+
|
|
21
|
+
struct Bag2(mut items: Vec[Int], mut count: Int) =
|
|
22
|
+
fun add(self, x: Int) -> Unit =
|
|
23
|
+
self.items = self.items.push(x)
|
|
24
|
+
self.count = self.count + 1
|
|
25
|
+
|
|
26
|
+
fun run2(n: Int) -> Int =
|
|
27
|
+
b := Bag2(items: Vec[Int](), count: 0)
|
|
28
|
+
for i := range n
|
|
29
|
+
b.add(i)
|
|
30
|
+
b.items.length() + b.items.at(n - 1) + b.count
|
|
31
|
+
|
|
32
|
+
# Shape 3: a Vec in a field of a nested struct, `o.inner.items = o.inner.items.push(i)`.
|
|
33
|
+
|
|
34
|
+
struct Inner3(mut items: Vec[Int])
|
|
35
|
+
struct Outer3(inner: Inner3, tag: Int)
|
|
36
|
+
|
|
37
|
+
fun run3(n: Int) -> Int =
|
|
38
|
+
o := Outer3(inner: Inner3(items: Vec[Int]()), tag: 5)
|
|
39
|
+
for i := range n
|
|
40
|
+
o.inner.items = o.inner.items.push(i)
|
|
41
|
+
o.inner.items.length() + o.inner.items.at(n - 1) + o.tag
|
|
42
|
+
|
|
43
|
+
# Shape 4: a helper that takes the Vec and returns it, `v = grow(v, i)`.
|
|
44
|
+
|
|
45
|
+
fun grow4(v: Vec[Int], x: Int) -> Vec[Int] = v.push(x * 2)
|
|
46
|
+
|
|
47
|
+
fun run4(n: Int) -> Int =
|
|
48
|
+
v := Vec[Int]()
|
|
49
|
+
for i := range n
|
|
50
|
+
v = grow4(v, i)
|
|
51
|
+
v.length() + v.at(n - 1)
|
|
52
|
+
|
|
53
|
+
# Shape 5: Vec[Int] and Vec[Vec2] in one program.
|
|
54
|
+
|
|
55
|
+
fun run5(n: Int) -> Int =
|
|
56
|
+
a := Vec[Int]()
|
|
57
|
+
b := Vec[Vec2]()
|
|
58
|
+
for i := range n
|
|
59
|
+
a = a.push(i)
|
|
60
|
+
b = b.push(Vec2(x: Float(i), y: 1.0))
|
|
61
|
+
r := a.length() + b.length() + a.at(n - 1)
|
|
62
|
+
if b.at(n - 1).x == Float(n - 1) then r else 0 - 1
|
|
63
|
+
|
|
64
|
+
# Shape 6: the length is read between updates, `n := v.length(); v = v.push(n)`.
|
|
65
|
+
|
|
66
|
+
fun run6(n: Int) -> Int =
|
|
67
|
+
v := Vec[Int]()
|
|
68
|
+
for i := range n
|
|
69
|
+
len := v.length()
|
|
70
|
+
v = v.push(len)
|
|
71
|
+
v.length() + v.at(n - 1)
|
|
72
|
+
|
|
73
|
+
# Shape 7: two Vecs in one struct, both pushed.
|
|
74
|
+
|
|
75
|
+
struct Pair7(mut xs: Vec[Int], mut ys: Vec[Int])
|
|
76
|
+
|
|
77
|
+
fun run7(n: Int) -> Int =
|
|
78
|
+
p := Pair7(xs: Vec[Int](), ys: Vec[Int]())
|
|
79
|
+
for i := range n
|
|
80
|
+
p.xs = p.xs.push(i)
|
|
81
|
+
p.ys = p.ys.push(i * 2)
|
|
82
|
+
p.xs.length() + p.ys.length() + p.xs.at(n - 1) + p.ys.at(n - 1)
|
|
83
|
+
|
|
84
|
+
# Shape 8: a Vec of Vecs. 8a builds rows and pushes them; 8b pushes onto a row stored in the outer Vec (`rows = rows.set(j, rows.at(j).push(i))`).
|
|
85
|
+
|
|
86
|
+
fun run8(n: Int) -> Int =
|
|
87
|
+
rows := Vec[Vec[Int]]()
|
|
88
|
+
for r := range n / 10
|
|
89
|
+
row := Vec[Int]()
|
|
90
|
+
for c := range 10
|
|
91
|
+
row = row.push(r * 10 + c)
|
|
92
|
+
rows = rows.push(row)
|
|
93
|
+
rows.length() + rows.at(n / 10 - 1).at(9)
|
|
94
|
+
|
|
95
|
+
fun run8b(n: Int) -> Int =
|
|
96
|
+
rows := Vec[Vec[Int]]()
|
|
97
|
+
for r := range 10
|
|
98
|
+
rows = rows.push(Vec[Int]())
|
|
99
|
+
for i := range n
|
|
100
|
+
j := i % 10
|
|
101
|
+
rows = rows.set(j, rows.at(j).push(i))
|
|
102
|
+
rows.at(0).length() + rows.at(9).at(n / 10 - 1)
|
|
103
|
+
|
|
104
|
+
# Shape 9: built by one function, returned, then pushed by the caller.
|
|
105
|
+
|
|
106
|
+
fun make9(n: Int) -> Vec[Int] =
|
|
107
|
+
v := Vec[Int]()
|
|
108
|
+
for i := range n
|
|
109
|
+
v = v.push(i)
|
|
110
|
+
v
|
|
111
|
+
|
|
112
|
+
fun run9(n: Int) -> Int =
|
|
113
|
+
v := make9(n)
|
|
114
|
+
for i := range n
|
|
115
|
+
v = v.push(i + n)
|
|
116
|
+
v.length() + v.at(2 * n - 1)
|
|
117
|
+
|
|
118
|
+
# Shape 10: an immutable state struct, `State(..s, items: s.items.push(x))`, in a step function.
|
|
119
|
+
|
|
120
|
+
struct State10(items: Vec[Int], tick: Int)
|
|
121
|
+
|
|
122
|
+
fun step10(s: State10, x: Int) -> State10 =
|
|
123
|
+
State10(..s, items: s.items.push(x), tick: s.tick + 1)
|
|
124
|
+
|
|
125
|
+
fun run10(n: Int) -> Int =
|
|
126
|
+
s := State10(items: Vec[Int](), tick: 0)
|
|
127
|
+
for i := range n
|
|
128
|
+
s = step10(s, i)
|
|
129
|
+
s.items.length() + s.items.at(n - 1) + s.tick
|
|
130
|
+
|
|
131
|
+
# Shape 11: a game-like update, every item of a 20,000-item Vec updated per frame, through a struct field of an immutable state, for many frames.
|
|
132
|
+
|
|
133
|
+
struct World11(items: Vec[Int], frame: Int)
|
|
134
|
+
|
|
135
|
+
fun frame11(w: World11, n: Int) -> World11 =
|
|
136
|
+
for i := range n
|
|
137
|
+
w = World11(..w, items: w.items.set(i, w.items.at(i) + 1))
|
|
138
|
+
World11(..w, frame: w.frame + 1)
|
|
139
|
+
|
|
140
|
+
fun run11(n: Int, frames: Int) -> Int =
|
|
141
|
+
v := Vec[Int]()
|
|
142
|
+
for i := range n
|
|
143
|
+
v = v.push(0)
|
|
144
|
+
w := World11(items: v, frame: 0)
|
|
145
|
+
for f := range frames
|
|
146
|
+
w = frame11(w, n)
|
|
147
|
+
w.items.at(0) + w.items.at(n - 1) + w.frame
|
|
148
|
+
|
|
149
|
+
# Shape 12: one value shared on purpose. Every holder keeps its own contents; this is a correctness test.
|
|
150
|
+
|
|
151
|
+
fun run12() -> Int =
|
|
152
|
+
a := Vec[Int]()
|
|
153
|
+
for i := range 20
|
|
154
|
+
a = a.push(i)
|
|
155
|
+
b := a.push(100)
|
|
156
|
+
c := a.push(200)
|
|
157
|
+
d := a.set(0, 7)
|
|
158
|
+
e := b.set(1, 8)
|
|
159
|
+
ok := a.length() == 20 && a.at(0) == 0 && a.at(1) == 1
|
|
160
|
+
ok2 := b.length() == 21 && b.at(20) == 100 && b.at(1) == 1 && c.length() == 21 && c.at(20) == 200
|
|
161
|
+
ok3 := d.at(0) == 7 && e.at(1) == 8 && e.at(20) == 100 && b.at(0) == 0
|
|
162
|
+
if ok && ok2 && ok3 then 1 else 0
|
|
163
|
+
|
|
164
|
+
fun main(io: Io) =
|
|
165
|
+
t1 := Time.now(io).toMillis()
|
|
166
|
+
r1 := run1(100000)
|
|
167
|
+
u1 := Time.now(io).toMillis()
|
|
168
|
+
io.printLn("vec_combined shape 1 result={r1} ms={u1 - t1}")
|
|
169
|
+
t2 := Time.now(io).toMillis()
|
|
170
|
+
r2 := run2(100000)
|
|
171
|
+
u2 := Time.now(io).toMillis()
|
|
172
|
+
io.printLn("vec_combined shape 2 result={r2} ms={u2 - t2}")
|
|
173
|
+
t3 := Time.now(io).toMillis()
|
|
174
|
+
r3 := run3(100000)
|
|
175
|
+
u3 := Time.now(io).toMillis()
|
|
176
|
+
io.printLn("vec_combined shape 3 result={r3} ms={u3 - t3}")
|
|
177
|
+
t4 := Time.now(io).toMillis()
|
|
178
|
+
r4 := run4(100000)
|
|
179
|
+
u4 := Time.now(io).toMillis()
|
|
180
|
+
io.printLn("vec_combined shape 4 result={r4} ms={u4 - t4}")
|
|
181
|
+
t5 := Time.now(io).toMillis()
|
|
182
|
+
r5 := run5(100000)
|
|
183
|
+
u5 := Time.now(io).toMillis()
|
|
184
|
+
io.printLn("vec_combined shape 5 result={r5} ms={u5 - t5}")
|
|
185
|
+
t6 := Time.now(io).toMillis()
|
|
186
|
+
r6 := run6(100000)
|
|
187
|
+
u6 := Time.now(io).toMillis()
|
|
188
|
+
io.printLn("vec_combined shape 6 result={r6} ms={u6 - t6}")
|
|
189
|
+
t7 := Time.now(io).toMillis()
|
|
190
|
+
r7 := run7(100000)
|
|
191
|
+
u7 := Time.now(io).toMillis()
|
|
192
|
+
io.printLn("vec_combined shape 7 result={r7} ms={u7 - t7}")
|
|
193
|
+
t8 := Time.now(io).toMillis()
|
|
194
|
+
r8 := run8(100000) + run8b(2000)
|
|
195
|
+
u8 := Time.now(io).toMillis()
|
|
196
|
+
io.printLn("vec_combined shape 8 result={r8} ms={u8 - t8}")
|
|
197
|
+
t9 := Time.now(io).toMillis()
|
|
198
|
+
r9 := run9(50000)
|
|
199
|
+
u9 := Time.now(io).toMillis()
|
|
200
|
+
io.printLn("vec_combined shape 9 result={r9} ms={u9 - t9}")
|
|
201
|
+
t10 := Time.now(io).toMillis()
|
|
202
|
+
r10 := run10(100000)
|
|
203
|
+
u10 := Time.now(io).toMillis()
|
|
204
|
+
io.printLn("vec_combined shape 10 result={r10} ms={u10 - t10}")
|
|
205
|
+
t11 := Time.now(io).toMillis()
|
|
206
|
+
r11 := run11(20000, 200)
|
|
207
|
+
u11 := Time.now(io).toMillis()
|
|
208
|
+
io.printLn("vec_combined shape 11 result={r11} ms={u11 - t11}")
|
|
209
|
+
t12 := Time.now(io).toMillis()
|
|
210
|
+
r12 := run12()
|
|
211
|
+
u12 := Time.now(io).toMillis()
|
|
212
|
+
io.printLn("vec_combined shape 12 result={r12} ms={u12 - t12}")
|
|
213
|
+
|
|
214
|
+
test "vec local"
|
|
215
|
+
assert run1(1000) == 2499
|
|
216
|
+
|
|
217
|
+
test "vec mutfield"
|
|
218
|
+
assert run2(1000) == 2999
|
|
219
|
+
|
|
220
|
+
test "vec nested"
|
|
221
|
+
assert run3(1000) == 2004
|
|
222
|
+
|
|
223
|
+
test "vec helper"
|
|
224
|
+
assert run4(1000) == 2998
|
|
225
|
+
|
|
226
|
+
test "vec generic"
|
|
227
|
+
assert run5(1000) == 2999
|
|
228
|
+
|
|
229
|
+
test "vec readbetween"
|
|
230
|
+
assert run6(1000) == 1999
|
|
231
|
+
|
|
232
|
+
test "vec twolists"
|
|
233
|
+
assert run7(1000) == 4997
|
|
234
|
+
|
|
235
|
+
test "vec listoflists"
|
|
236
|
+
assert run8(1000) == 1099
|
|
237
|
+
|
|
238
|
+
test "vec listoflists (2)"
|
|
239
|
+
assert run8b(1000) == 100 + 999
|
|
240
|
+
|
|
241
|
+
test "vec returned"
|
|
242
|
+
assert run9(500) == 1000 + 999
|
|
243
|
+
|
|
244
|
+
test "vec statespread"
|
|
245
|
+
assert run10(1000) == 2999
|
|
246
|
+
|
|
247
|
+
test "vec frames"
|
|
248
|
+
assert run11(50, 5) == 15
|
|
249
|
+
|
|
250
|
+
test "vec shared"
|
|
251
|
+
assert run12() == 1
|
tests/fbip/vec_fip.plum
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Array
|
|
3
|
+
|
|
4
|
+
# `fip fun` on the pilot's shape: the compiler accepts a push/set written as a spread plus `Array.with`
|
|
5
|
+
# when it proves the update in place, and refuses the program (tests/fbip_u/ shows the error) when
|
|
6
|
+
# it does not. Names are unique (`fdata`, `fpush`) because the proof is by name.
|
|
7
|
+
|
|
8
|
+
struct FVec[T](fdata: Array[T], fsize: Int) =
|
|
9
|
+
fun init() -> FVec[T] = FVec(fdata: Array[T]().resize(0), fsize: 0)
|
|
10
|
+
|
|
11
|
+
fip fun fpush(self, v: T) -> FVec[T] =
|
|
12
|
+
if self.fsize < self.fdata.length()
|
|
13
|
+
FVec(..self, fdata: self.fdata.with(self.fsize, v), fsize: self.fsize + 1)
|
|
14
|
+
else
|
|
15
|
+
FVec(fdata: self.fdata.resize(if self.fsize == 0 then 8 else self.fsize * 2).with(self.fsize, v), fsize: self.fsize + 1)
|
|
16
|
+
|
|
17
|
+
fip fun fset(self, i: Int, v: T) -> FVec[T] =
|
|
18
|
+
FVec(..self, fdata: self.fdata.with(i, v))
|
|
19
|
+
|
|
20
|
+
fun fat(self, i: Int) -> T = self.fdata.get(i)
|
|
21
|
+
|
|
22
|
+
fun flen(self) -> Int = self.fsize
|
|
23
|
+
|
|
24
|
+
fun fill(n: Int) -> FVec[Int] =
|
|
25
|
+
v := FVec[Int]()
|
|
26
|
+
for i := range n
|
|
27
|
+
v = v.fpush(i)
|
|
28
|
+
v = v.fset(3, 99)
|
|
29
|
+
v
|
|
30
|
+
|
|
31
|
+
test "fip vec: push and set"
|
|
32
|
+
v := fill(20)
|
|
33
|
+
assert v.flen() == 20 && v.fat(3) == 99 && v.fat(19) == 19
|
tests/fbip/vec_frames.plum
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 11: a game-like update, every item of a 20,000-item Vec updated per frame, through a struct field of an immutable state, for many frames.
|
|
10
|
+
|
|
11
|
+
struct World11(items: Vec[Int], frame: Int)
|
|
12
|
+
|
|
13
|
+
fun frame11(w: World11, n: Int) -> World11 =
|
|
14
|
+
for i := range n
|
|
15
|
+
w = World11(..w, items: w.items.set(i, w.items.at(i) + 1))
|
|
16
|
+
World11(..w, frame: w.frame + 1)
|
|
17
|
+
|
|
18
|
+
fun run11(n: Int, frames: Int) -> Int =
|
|
19
|
+
v := Vec[Int]()
|
|
20
|
+
for i := range n
|
|
21
|
+
v = v.push(0)
|
|
22
|
+
w := World11(items: v, frame: 0)
|
|
23
|
+
for f := range frames
|
|
24
|
+
w = frame11(w, n)
|
|
25
|
+
w.items.at(0) + w.items.at(n - 1) + w.frame
|
|
26
|
+
|
|
27
|
+
fun main(io: Io) =
|
|
28
|
+
t11 := Time.now(io).toMillis()
|
|
29
|
+
r11 := run11(20000, 200)
|
|
30
|
+
u11 := Time.now(io).toMillis()
|
|
31
|
+
io.printLn("vec_frames shape 11 result={r11} ms={u11 - t11}")
|
|
32
|
+
|
|
33
|
+
test "vec frames"
|
|
34
|
+
assert run11(50, 5) == 15
|
tests/fbip/vec_frameslocal.plum
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 11b: the per-frame update on a Vec held in a local and handed through a function, no struct around it.
|
|
10
|
+
|
|
11
|
+
fun frame13(v: Vec[Int], n: Int) -> Vec[Int] =
|
|
12
|
+
for i := range n
|
|
13
|
+
v = v.set(i, v.at(i) + 1)
|
|
14
|
+
v
|
|
15
|
+
|
|
16
|
+
fun run13(n: Int, frames: Int) -> Int =
|
|
17
|
+
v := Vec[Int]()
|
|
18
|
+
for i := range n
|
|
19
|
+
v = v.push(0)
|
|
20
|
+
for f := range frames
|
|
21
|
+
v = frame13(v, n)
|
|
22
|
+
v.at(0) + v.at(n - 1)
|
|
23
|
+
|
|
24
|
+
fun main(io: Io) =
|
|
25
|
+
t13 := Time.now(io).toMillis()
|
|
26
|
+
r13 := run13(20000, 200)
|
|
27
|
+
u13 := Time.now(io).toMillis()
|
|
28
|
+
io.printLn("vec_frameslocal shape 13 result={r13} ms={u13 - t13}")
|
|
29
|
+
|
|
30
|
+
test "vec frameslocal"
|
|
31
|
+
assert run13(50, 5) == 10
|
tests/fbip/vec_framesmut.plum
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 11c: the per-frame update on a Vec in a `mut` field, `w.items = w.items.set(i, ..)`.
|
|
10
|
+
|
|
11
|
+
struct World14(mut items: Vec[Int], mut frame: Int)
|
|
12
|
+
|
|
13
|
+
fun frame14(w: World14, n: Int) -> Unit =
|
|
14
|
+
for i := range n
|
|
15
|
+
w.items = w.items.set(i, w.items.at(i) + 1)
|
|
16
|
+
w.frame = w.frame + 1
|
|
17
|
+
|
|
18
|
+
fun run14(n: Int, frames: Int) -> Int =
|
|
19
|
+
v := Vec[Int]()
|
|
20
|
+
for i := range n
|
|
21
|
+
v = v.push(0)
|
|
22
|
+
w := World14(items: v, frame: 0)
|
|
23
|
+
for f := range frames
|
|
24
|
+
frame14(w, n)
|
|
25
|
+
w.items.at(0) + w.items.at(n - 1) + w.frame
|
|
26
|
+
|
|
27
|
+
fun main(io: Io) =
|
|
28
|
+
t14 := Time.now(io).toMillis()
|
|
29
|
+
r14 := run14(20000, 200)
|
|
30
|
+
u14 := Time.now(io).toMillis()
|
|
31
|
+
io.printLn("vec_framesmut shape 14 result={r14} ms={u14 - t14}")
|
|
32
|
+
|
|
33
|
+
test "vec framesmut"
|
|
34
|
+
assert run14(50, 5) == 15
|
tests/fbip/vec_generic.plum
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 5: Vec[Int] and Vec[Vec2] in one program.
|
|
10
|
+
|
|
11
|
+
fun run5(n: Int) -> Int =
|
|
12
|
+
a := Vec[Int]()
|
|
13
|
+
b := Vec[Vec2]()
|
|
14
|
+
for i := range n
|
|
15
|
+
a = a.push(i)
|
|
16
|
+
b = b.push(Vec2(x: Float(i), y: 1.0))
|
|
17
|
+
r := a.length() + b.length() + a.at(n - 1)
|
|
18
|
+
if b.at(n - 1).x == Float(n - 1) then r else 0 - 1
|
|
19
|
+
|
|
20
|
+
fun main(io: Io) =
|
|
21
|
+
t5 := Time.now(io).toMillis()
|
|
22
|
+
r5 := run5(100000)
|
|
23
|
+
u5 := Time.now(io).toMillis()
|
|
24
|
+
io.printLn("vec_generic shape 5 result={r5} ms={u5 - t5}")
|
|
25
|
+
|
|
26
|
+
test "vec generic"
|
|
27
|
+
assert run5(1000) == 2999
|
tests/fbip/vec_helper.plum
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 4: a helper that takes the Vec and returns it, `v = grow(v, i)`.
|
|
10
|
+
|
|
11
|
+
fun grow4(v: Vec[Int], x: Int) -> Vec[Int] = v.push(x * 2)
|
|
12
|
+
|
|
13
|
+
fun run4(n: Int) -> Int =
|
|
14
|
+
v := Vec[Int]()
|
|
15
|
+
for i := range n
|
|
16
|
+
v = grow4(v, i)
|
|
17
|
+
v.length() + v.at(n - 1)
|
|
18
|
+
|
|
19
|
+
fun main(io: Io) =
|
|
20
|
+
t4 := Time.now(io).toMillis()
|
|
21
|
+
r4 := run4(100000)
|
|
22
|
+
u4 := Time.now(io).toMillis()
|
|
23
|
+
io.printLn("vec_helper shape 4 result={r4} ms={u4 - t4}")
|
|
24
|
+
|
|
25
|
+
test "vec helper"
|
|
26
|
+
assert run4(1000) == 2998
|
tests/fbip/vec_listoflists.plum
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 8: a Vec of Vecs. 8a builds rows and pushes them; 8b pushes onto a row stored in the outer Vec (`rows = rows.set(j, rows.at(j).push(i))`).
|
|
10
|
+
|
|
11
|
+
fun run8(n: Int) -> Int =
|
|
12
|
+
rows := Vec[Vec[Int]]()
|
|
13
|
+
for r := range n / 10
|
|
14
|
+
row := Vec[Int]()
|
|
15
|
+
for c := range 10
|
|
16
|
+
row = row.push(r * 10 + c)
|
|
17
|
+
rows = rows.push(row)
|
|
18
|
+
rows.length() + rows.at(n / 10 - 1).at(9)
|
|
19
|
+
|
|
20
|
+
fun run8b(n: Int) -> Int =
|
|
21
|
+
rows := Vec[Vec[Int]]()
|
|
22
|
+
for r := range 10
|
|
23
|
+
rows = rows.push(Vec[Int]())
|
|
24
|
+
for i := range n
|
|
25
|
+
j := i % 10
|
|
26
|
+
rows = rows.set(j, rows.at(j).push(i))
|
|
27
|
+
rows.at(0).length() + rows.at(9).at(n / 10 - 1)
|
|
28
|
+
|
|
29
|
+
fun main(io: Io) =
|
|
30
|
+
t8 := Time.now(io).toMillis()
|
|
31
|
+
r8 := run8(100000) + run8b(2000)
|
|
32
|
+
u8 := Time.now(io).toMillis()
|
|
33
|
+
io.printLn("vec_listoflists shape 8 result={r8} ms={u8 - t8}")
|
|
34
|
+
|
|
35
|
+
test "vec listoflists"
|
|
36
|
+
assert run8(1000) == 1099
|
|
37
|
+
|
|
38
|
+
test "vec listoflists (2)"
|
|
39
|
+
assert run8b(1000) == 100 + 999
|
tests/fbip/vec_local.plum
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 1: a Vec in a local, `v = v.push(i)` in a loop.
|
|
10
|
+
|
|
11
|
+
fun run1(n: Int) -> Int =
|
|
12
|
+
v := Vec[Int]()
|
|
13
|
+
for i := range n
|
|
14
|
+
v = v.push(i)
|
|
15
|
+
v.length() + v.at(n - 1) + v.at(n / 2)
|
|
16
|
+
|
|
17
|
+
fun main(io: Io) =
|
|
18
|
+
t1 := Time.now(io).toMillis()
|
|
19
|
+
r1 := run1(100000)
|
|
20
|
+
u1 := Time.now(io).toMillis()
|
|
21
|
+
io.printLn("vec_local shape 1 result={r1} ms={u1 - t1}")
|
|
22
|
+
|
|
23
|
+
test "vec local"
|
|
24
|
+
assert run1(1000) == 2499
|
tests/fbip/vec_mutfield.plum
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 2: a `mut` field of a struct, `self.items = self.items.push(x)` in a method.
|
|
10
|
+
|
|
11
|
+
struct Bag2(mut items: Vec[Int], mut count: Int) =
|
|
12
|
+
fun add(self, x: Int) -> Unit =
|
|
13
|
+
self.items = self.items.push(x)
|
|
14
|
+
self.count = self.count + 1
|
|
15
|
+
|
|
16
|
+
fun run2(n: Int) -> Int =
|
|
17
|
+
b := Bag2(items: Vec[Int](), count: 0)
|
|
18
|
+
for i := range n
|
|
19
|
+
b.add(i)
|
|
20
|
+
b.items.length() + b.items.at(n - 1) + b.count
|
|
21
|
+
|
|
22
|
+
fun main(io: Io) =
|
|
23
|
+
t2 := Time.now(io).toMillis()
|
|
24
|
+
r2 := run2(100000)
|
|
25
|
+
u2 := Time.now(io).toMillis()
|
|
26
|
+
io.printLn("vec_mutfield shape 2 result={r2} ms={u2 - t2}")
|
|
27
|
+
|
|
28
|
+
test "vec mutfield"
|
|
29
|
+
assert run2(1000) == 2999
|
tests/fbip/vec_nested.plum
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 3: a Vec in a field of a nested struct, `o.inner.items = o.inner.items.push(i)`.
|
|
10
|
+
|
|
11
|
+
struct Inner3(mut items: Vec[Int])
|
|
12
|
+
struct Outer3(inner: Inner3, tag: Int)
|
|
13
|
+
|
|
14
|
+
fun run3(n: Int) -> Int =
|
|
15
|
+
o := Outer3(inner: Inner3(items: Vec[Int]()), tag: 5)
|
|
16
|
+
for i := range n
|
|
17
|
+
o.inner.items = o.inner.items.push(i)
|
|
18
|
+
o.inner.items.length() + o.inner.items.at(n - 1) + o.tag
|
|
19
|
+
|
|
20
|
+
fun main(io: Io) =
|
|
21
|
+
t3 := Time.now(io).toMillis()
|
|
22
|
+
r3 := run3(100000)
|
|
23
|
+
u3 := Time.now(io).toMillis()
|
|
24
|
+
io.printLn("vec_nested shape 3 result={r3} ms={u3 - t3}")
|
|
25
|
+
|
|
26
|
+
test "vec nested"
|
|
27
|
+
assert run3(1000) == 2004
|
tests/fbip/vec_readbetween.plum
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 6: the length is read between updates, `n := v.length(); v = v.push(n)`.
|
|
10
|
+
|
|
11
|
+
fun run6(n: Int) -> Int =
|
|
12
|
+
v := Vec[Int]()
|
|
13
|
+
for i := range n
|
|
14
|
+
len := v.length()
|
|
15
|
+
v = v.push(len)
|
|
16
|
+
v.length() + v.at(n - 1)
|
|
17
|
+
|
|
18
|
+
fun main(io: Io) =
|
|
19
|
+
t6 := Time.now(io).toMillis()
|
|
20
|
+
r6 := run6(100000)
|
|
21
|
+
u6 := Time.now(io).toMillis()
|
|
22
|
+
io.printLn("vec_readbetween shape 6 result={r6} ms={u6 - t6}")
|
|
23
|
+
|
|
24
|
+
test "vec readbetween"
|
|
25
|
+
assert run6(1000) == 1999
|
tests/fbip/vec_returned.plum
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 9: built by one function, returned, then pushed by the caller.
|
|
10
|
+
|
|
11
|
+
fun make9(n: Int) -> Vec[Int] =
|
|
12
|
+
v := Vec[Int]()
|
|
13
|
+
for i := range n
|
|
14
|
+
v = v.push(i)
|
|
15
|
+
v
|
|
16
|
+
|
|
17
|
+
fun run9(n: Int) -> Int =
|
|
18
|
+
v := make9(n)
|
|
19
|
+
for i := range n
|
|
20
|
+
v = v.push(i + n)
|
|
21
|
+
v.length() + v.at(2 * n - 1)
|
|
22
|
+
|
|
23
|
+
fun main(io: Io) =
|
|
24
|
+
t9 := Time.now(io).toMillis()
|
|
25
|
+
r9 := run9(50000)
|
|
26
|
+
u9 := Time.now(io).toMillis()
|
|
27
|
+
io.printLn("vec_returned shape 9 result={r9} ms={u9 - t9}")
|
|
28
|
+
|
|
29
|
+
test "vec returned"
|
|
30
|
+
assert run9(500) == 1000 + 999
|
tests/fbip/vec_returned2.plum
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 9b: a function that returns a Vec it built in a local, called twice, the result pushed by the caller.
|
|
10
|
+
|
|
11
|
+
fun make15(n: Int) -> Vec[Int] =
|
|
12
|
+
v := Vec[Int]()
|
|
13
|
+
for i := range n
|
|
14
|
+
v = v.push(i)
|
|
15
|
+
v
|
|
16
|
+
|
|
17
|
+
fun run15(n: Int) -> Int =
|
|
18
|
+
v := make15(n)
|
|
19
|
+
w := Vec[Int]()
|
|
20
|
+
for i := range n
|
|
21
|
+
w = w.push(i)
|
|
22
|
+
v.length() + w.length()
|
|
23
|
+
|
|
24
|
+
fun main(io: Io) =
|
|
25
|
+
t15 := Time.now(io).toMillis()
|
|
26
|
+
r15 := run15(100000)
|
|
27
|
+
u15 := Time.now(io).toMillis()
|
|
28
|
+
io.printLn("vec_returned2 shape 15 result={r15} ms={u15 - t15}")
|
|
29
|
+
|
|
30
|
+
test "vec returned2"
|
|
31
|
+
assert run15(500) == 1000
|
tests/fbip/vec_shared.plum
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 12: one value shared on purpose. Every holder keeps its own contents; this is a correctness test.
|
|
10
|
+
|
|
11
|
+
fun run12() -> Int =
|
|
12
|
+
a := Vec[Int]()
|
|
13
|
+
for i := range 20
|
|
14
|
+
a = a.push(i)
|
|
15
|
+
b := a.push(100)
|
|
16
|
+
c := a.push(200)
|
|
17
|
+
d := a.set(0, 7)
|
|
18
|
+
e := b.set(1, 8)
|
|
19
|
+
ok := a.length() == 20 && a.at(0) == 0 && a.at(1) == 1
|
|
20
|
+
ok2 := b.length() == 21 && b.at(20) == 100 && b.at(1) == 1 && c.length() == 21 && c.at(20) == 200
|
|
21
|
+
ok3 := d.at(0) == 7 && e.at(1) == 8 && e.at(20) == 100 && b.at(0) == 0
|
|
22
|
+
if ok && ok2 && ok3 then 1 else 0
|
|
23
|
+
|
|
24
|
+
fun main(io: Io) =
|
|
25
|
+
t12 := Time.now(io).toMillis()
|
|
26
|
+
r12 := run12()
|
|
27
|
+
u12 := Time.now(io).toMillis()
|
|
28
|
+
io.printLn("vec_shared shape 12 result={r12} ms={u12 - t12}")
|
|
29
|
+
|
|
30
|
+
test "vec shared"
|
|
31
|
+
assert run12() == 1
|
tests/fbip/vec_statespread.plum
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 10: an immutable state struct, `State(..s, items: s.items.push(x))`, in a step function.
|
|
10
|
+
|
|
11
|
+
struct State10(items: Vec[Int], tick: Int)
|
|
12
|
+
|
|
13
|
+
fun step10(s: State10, x: Int) -> State10 =
|
|
14
|
+
State10(..s, items: s.items.push(x), tick: s.tick + 1)
|
|
15
|
+
|
|
16
|
+
fun run10(n: Int) -> Int =
|
|
17
|
+
s := State10(items: Vec[Int](), tick: 0)
|
|
18
|
+
for i := range n
|
|
19
|
+
s = step10(s, i)
|
|
20
|
+
s.items.length() + s.items.at(n - 1) + s.tick
|
|
21
|
+
|
|
22
|
+
fun main(io: Io) =
|
|
23
|
+
t10 := Time.now(io).toMillis()
|
|
24
|
+
r10 := run10(100000)
|
|
25
|
+
u10 := Time.now(io).toMillis()
|
|
26
|
+
io.printLn("vec_statespread shape 10 result={r10} ms={u10 - t10}")
|
|
27
|
+
|
|
28
|
+
test "vec statespread"
|
|
29
|
+
assert run10(1000) == 2999
|
tests/fbip/vec_twolists.plum
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/vec
|
|
8
|
+
|
|
9
|
+
# Shape 7: two Vecs in one struct, both pushed.
|
|
10
|
+
|
|
11
|
+
struct Pair7(mut xs: Vec[Int], mut ys: Vec[Int])
|
|
12
|
+
|
|
13
|
+
fun run7(n: Int) -> Int =
|
|
14
|
+
p := Pair7(xs: Vec[Int](), ys: Vec[Int]())
|
|
15
|
+
for i := range n
|
|
16
|
+
p.xs = p.xs.push(i)
|
|
17
|
+
p.ys = p.ys.push(i * 2)
|
|
18
|
+
p.xs.length() + p.ys.length() + p.xs.at(n - 1) + p.ys.at(n - 1)
|
|
19
|
+
|
|
20
|
+
fun main(io: Io) =
|
|
21
|
+
t7 := Time.now(io).toMillis()
|
|
22
|
+
r7 := run7(100000)
|
|
23
|
+
u7 := Time.now(io).toMillis()
|
|
24
|
+
io.printLn("vec_twolists shape 7 result={r7} ms={u7 - t7}")
|
|
25
|
+
|
|
26
|
+
test "vec twolists"
|
|
27
|
+
assert run7(1000) == 4997
|
tests/fbip/vec_unique_names.plum
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import std/Int
|
|
2
|
+
import std/Str
|
|
3
|
+
import std/Float
|
|
4
|
+
import std/Time
|
|
5
|
+
import std/Io
|
|
6
|
+
import std/game/Vec2
|
|
7
|
+
import tests/fbip/uvec
|
|
8
|
+
|
|
9
|
+
# The shapes that are proved in place, in one program, with names no std type uses (`udata`, `upush`, ...). With `data`
|
|
10
|
+
# and `push` (vec_combined.plum) the same code copies on every push.
|
|
11
|
+
|
|
12
|
+
# Shape 1: a Vec in a local, `v = v.push(i)` in a loop.
|
|
13
|
+
|
|
14
|
+
fun run1(n: Int) -> Int =
|
|
15
|
+
v := UVec[Int]()
|
|
16
|
+
for i := range n
|
|
17
|
+
v = v.upush(i)
|
|
18
|
+
v.ulen() + v.uat(n - 1) + v.uat(n / 2)
|
|
19
|
+
|
|
20
|
+
# Shape 2: a `mut` field of a struct, `self.items = self.items.push(x)` in a method.
|
|
21
|
+
|
|
22
|
+
struct Bag2(mut items: UVec[Int], mut count: Int) =
|
|
23
|
+
fun add(self, x: Int) -> Unit =
|
|
24
|
+
self.items = self.items.upush(x)
|
|
25
|
+
self.count = self.count + 1
|
|
26
|
+
|
|
27
|
+
fun run2(n: Int) -> Int =
|
|
28
|
+
b := Bag2(items: UVec[Int](), count: 0)
|
|
29
|
+
for i := range n
|
|
30
|
+
b.add(i)
|
|
31
|
+
b.items.ulen() + b.items.uat(n - 1) + b.count
|
|
32
|
+
|
|
33
|
+
# Shape 3: a Vec in a field of a nested struct, `o.inner.items = o.inner.items.push(i)`.
|
|
34
|
+
|
|
35
|
+
struct Inner3(mut items: UVec[Int])
|
|
36
|
+
struct Outer3(inner: Inner3, tag: Int)
|
|
37
|
+
|
|
38
|
+
fun run3(n: Int) -> Int =
|
|
39
|
+
o := Outer3(inner: Inner3(items: UVec[Int]()), tag: 5)
|
|
40
|
+
for i := range n
|
|
41
|
+
o.inner.items = o.inner.items.upush(i)
|
|
42
|
+
o.inner.items.ulen() + o.inner.items.uat(n - 1) + o.tag
|
|
43
|
+
|
|
44
|
+
# Shape 4: a helper that takes the Vec and returns it, `v = grow(v, i)`.
|
|
45
|
+
|
|
46
|
+
fun grow4(v: UVec[Int], x: Int) -> UVec[Int] = v.upush(x * 2)
|
|
47
|
+
|
|
48
|
+
fun run4(n: Int) -> Int =
|
|
49
|
+
v := UVec[Int]()
|
|
50
|
+
for i := range n
|
|
51
|
+
v = grow4(v, i)
|
|
52
|
+
v.ulen() + v.uat(n - 1)
|
|
53
|
+
|
|
54
|
+
# Shape 5: Vec[Int] and Vec[Vec2] in one program.
|
|
55
|
+
|
|
56
|
+
fun run5(n: Int) -> Int =
|
|
57
|
+
a := UVec[Int]()
|
|
58
|
+
b := UVec[Vec2]()
|
|
59
|
+
for i := range n
|
|
60
|
+
a = a.upush(i)
|
|
61
|
+
b = b.upush(Vec2(x: Float(i), y: 1.0))
|
|
62
|
+
r := a.ulen() + b.ulen() + a.uat(n - 1)
|
|
63
|
+
if b.uat(n - 1).x == Float(n - 1) then r else 0 - 1
|
|
64
|
+
|
|
65
|
+
# Shape 6: the length is read between updates, `n := v.length(); v = v.push(n)`.
|
|
66
|
+
|
|
67
|
+
fun run6(n: Int) -> Int =
|
|
68
|
+
v := UVec[Int]()
|
|
69
|
+
for i := range n
|
|
70
|
+
len := v.ulen()
|
|
71
|
+
v = v.upush(len)
|
|
72
|
+
v.ulen() + v.uat(n - 1)
|
|
73
|
+
|
|
74
|
+
# Shape 7: two Vecs in one struct, both pushed.
|
|
75
|
+
|
|
76
|
+
struct Pair7(mut xs: UVec[Int], mut ys: UVec[Int])
|
|
77
|
+
|
|
78
|
+
fun run7(n: Int) -> Int =
|
|
79
|
+
p := Pair7(xs: UVec[Int](), ys: UVec[Int]())
|
|
80
|
+
for i := range n
|
|
81
|
+
p.xs = p.xs.upush(i)
|
|
82
|
+
p.ys = p.ys.upush(i * 2)
|
|
83
|
+
p.xs.ulen() + p.ys.ulen() + p.xs.uat(n - 1) + p.ys.uat(n - 1)
|
|
84
|
+
|
|
85
|
+
# Shape 11b: the per-frame update on a Vec held in a local and handed through a function, no struct around it.
|
|
86
|
+
|
|
87
|
+
fun frame13(v: UVec[Int], n: Int) -> UVec[Int] =
|
|
88
|
+
for i := range n
|
|
89
|
+
v = v.uset(i, v.uat(i) + 1)
|
|
90
|
+
v
|
|
91
|
+
|
|
92
|
+
fun run13(n: Int, frames: Int) -> Int =
|
|
93
|
+
v := UVec[Int]()
|
|
94
|
+
for i := range n
|
|
95
|
+
v = v.upush(0)
|
|
96
|
+
for f := range frames
|
|
97
|
+
v = frame13(v, n)
|
|
98
|
+
v.uat(0) + v.uat(n - 1)
|
|
99
|
+
|
|
100
|
+
# Shape 9b: a function that returns a Vec it built in a local, called twice, the result pushed by the caller.
|
|
101
|
+
|
|
102
|
+
fun make15(n: Int) -> UVec[Int] =
|
|
103
|
+
v := UVec[Int]()
|
|
104
|
+
for i := range n
|
|
105
|
+
v = v.upush(i)
|
|
106
|
+
v
|
|
107
|
+
|
|
108
|
+
fun run15(n: Int) -> Int =
|
|
109
|
+
v := make15(n)
|
|
110
|
+
w := UVec[Int]()
|
|
111
|
+
for i := range n
|
|
112
|
+
w = w.upush(i)
|
|
113
|
+
v.ulen() + w.ulen()
|
|
114
|
+
|
|
115
|
+
fun main(io: Io) =
|
|
116
|
+
t1 := Time.now(io).toMillis()
|
|
117
|
+
r1 := run1(100000)
|
|
118
|
+
u1 := Time.now(io).toMillis()
|
|
119
|
+
io.printLn("uvec_combined shape 1 result={r1} ms={u1 - t1}")
|
|
120
|
+
t2 := Time.now(io).toMillis()
|
|
121
|
+
r2 := run2(100000)
|
|
122
|
+
u2 := Time.now(io).toMillis()
|
|
123
|
+
io.printLn("uvec_combined shape 2 result={r2} ms={u2 - t2}")
|
|
124
|
+
t3 := Time.now(io).toMillis()
|
|
125
|
+
r3 := run3(100000)
|
|
126
|
+
u3 := Time.now(io).toMillis()
|
|
127
|
+
io.printLn("uvec_combined shape 3 result={r3} ms={u3 - t3}")
|
|
128
|
+
t4 := Time.now(io).toMillis()
|
|
129
|
+
r4 := run4(100000)
|
|
130
|
+
u4 := Time.now(io).toMillis()
|
|
131
|
+
io.printLn("uvec_combined shape 4 result={r4} ms={u4 - t4}")
|
|
132
|
+
t5 := Time.now(io).toMillis()
|
|
133
|
+
r5 := run5(100000)
|
|
134
|
+
u5 := Time.now(io).toMillis()
|
|
135
|
+
io.printLn("uvec_combined shape 5 result={r5} ms={u5 - t5}")
|
|
136
|
+
t6 := Time.now(io).toMillis()
|
|
137
|
+
r6 := run6(100000)
|
|
138
|
+
u6 := Time.now(io).toMillis()
|
|
139
|
+
io.printLn("uvec_combined shape 6 result={r6} ms={u6 - t6}")
|
|
140
|
+
t7 := Time.now(io).toMillis()
|
|
141
|
+
r7 := run7(100000)
|
|
142
|
+
u7 := Time.now(io).toMillis()
|
|
143
|
+
io.printLn("uvec_combined shape 7 result={r7} ms={u7 - t7}")
|
|
144
|
+
t13 := Time.now(io).toMillis()
|
|
145
|
+
r13 := run13(20000, 200)
|
|
146
|
+
u13 := Time.now(io).toMillis()
|
|
147
|
+
io.printLn("uvec_combined shape 13 result={r13} ms={u13 - t13}")
|
|
148
|
+
t15 := Time.now(io).toMillis()
|
|
149
|
+
r15 := run15(100000)
|
|
150
|
+
u15 := Time.now(io).toMillis()
|
|
151
|
+
io.printLn("uvec_combined shape 15 result={r15} ms={u15 - t15}")
|
|
152
|
+
|
|
153
|
+
test "vec local"
|
|
154
|
+
assert run1(1000) == 2499
|
|
155
|
+
|
|
156
|
+
test "vec mutfield"
|
|
157
|
+
assert run2(1000) == 2999
|
|
158
|
+
|
|
159
|
+
test "vec nested"
|
|
160
|
+
assert run3(1000) == 2004
|
|
161
|
+
|
|
162
|
+
test "vec helper"
|
|
163
|
+
assert run4(1000) == 2998
|
|
164
|
+
|
|
165
|
+
test "vec generic"
|
|
166
|
+
assert run5(1000) == 2999
|
|
167
|
+
|
|
168
|
+
test "vec readbetween"
|
|
169
|
+
assert run6(1000) == 1999
|
|
170
|
+
|
|
171
|
+
test "vec twolists"
|
|
172
|
+
assert run7(1000) == 4997
|
|
173
|
+
|
|
174
|
+
test "vec frameslocal"
|
|
175
|
+
assert run13(50, 5) == 10
|
|
176
|
+
|
|
177
|
+
test "vec returned2"
|
|
178
|
+
assert run15(500) == 1000
|