Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
slug: are-there-parity-tests-in-tests-linq-that-compare-fold-output-to-the-underlying-linq-operators
title: Are there parity tests in tests/linq/ that compare `_fold` output to the underlying linq operators?
created: 2026-05-16
last_verified: 2026-05-16
links: []
---

There's no file named "parity" or similar. The parity-test surface IS the broader [tests/linq/](tests/linq/) directory:

- `test_linq.das` — comprehension basics
- `test_linq_aggregation.das` — count/sum/min/max/avg
- `test_linq_querying.das` — any/all/contains
- `test_linq_transform.das` — select / select_many / zip
- `test_linq_sorting.das` — order / reverse
- `test_linq_group_by.das` — group_by / having
- `test_linq_join.das` — joins
- `test_linq_partition.das` — take / skip / chunk / take_while / skip_while
- `test_linq_set.das` — distinct / union / except / intersect / unique
- `test_linq_element.das` — first / last / single / element_at
- `test_linq_concat.das` — concat / prepend / append
- `test_linq_generation.das` — range / repeat
- `test_linq_bugs.das` — regressions

Each file uses `[test]` functions with `t |> run("name") @(t) { ... }` blocks asserting `t |> equal(actual, expected)`. These exercise the regular linq operators (`where_`, `select`, `count`, ...) directly — they're not split into "fold-on" vs "fold-off" variants.

Dedicated `_fold` tests live in `test_linq_fold.das` (functional output) and `test_linq_fold_ast.das` (AST-shape verification — pattern-matches the macro expansion). These DO compare `_fold(chain)` output against the plain `chain` output for the shapes the macro recognizes.

When the user says "parity tests" in linq context, treat the full `test_linq_*.das` suite as the operator-coverage map. Phase-2+ benchmark/splice PRs should add a `benchmarks/sql/` entry for each shape exercised here that isn't already covered (tracked as a checklist in `benchmarks/sql/LINQ.md`).

## Questions
- Are there parity tests in tests/linq/ that compare `_fold` output to the underlying linq operators?
- What's the "parity test" coverage surface for linq?
- Where are tests for linq operators?

## Questions
- Are there parity tests in tests/linq/ that compare `_fold` output to the underlying linq operators?
Comment on lines +35 to +37
68 changes: 68 additions & 0 deletions mouse-data/docs/cpp-profiler-macos-samply-instruments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
slug: cpp-profiler-macos-samply-instruments
title: What C++ sampling profiler should I use on macOS for daslang (and how do I run it)?
created: 2026-05-16
last_verified: 2026-05-16
links: []
---

# C++ sampling profiler on macOS (Apple Silicon)

VS Code has **no first-class C++ profiler integration on macOS** — the "Performance Profiler" / similar extensions wrap Linux `perf` and don't help here. Skip them. Run a sampler from the integrated terminal and view results in browser/Instruments.

## samply (default choice)

Rust-built, Firefox-Profiler frontend, zero config.

```bash
cargo install samply
samply record ./build/daslang script.das
```

- Opens flamegraph in browser automatically.
- Symbolicates Mach-O cleanly if you build `-DCMAKE_BUILD_TYPE=RelWithDebInfo` (do NOT use plain `Release` — symbols are stripped).
- Works without sudo on Apple Silicon.
- Good for "where does the CPU go" questions.

## Xcode Instruments — Time Profiler (second opinion)

Native macOS sampler, kernel-assisted, best symbolication on Apple Silicon. Use when samply's view is ambiguous or you want call-tree + timeline together.

```bash
xcrun xctrace record --template 'Time Profiler' --launch -- ./build/daslang script.das
```

Then open the resulting `.trace` bundle (Instruments launches). UI is outside VS Code.

## daslang-specific recipe

Pair the sampler with the per-module compile-time logging (`-log-compile-time` CLI flag, added on branch `bbatkin/log-compile-time-cli`):

```bash
cmake --build build --config RelWithDebInfo -j 64
samply record ./build/daslang -log-compile-time path/to/script.das
```

- `-log-compile-time` tells you which module is slow.
- Sampling tells you which function inside that module is hot.
- Together they narrow "compile is slow" to a specific phase + symbol.

## What NOT to use

- `perf` — Linux only, doesn't exist on Darwin.
- Intel VTune — x86-mostly, ignore on Apple Silicon.
- `gprof` — instrumenting, not sampling; ancient.
- VS Code C++ profiler extensions — see above, all are Linux/perf wrappers or toys.
- `hyperfine` / `poop` — benchmarking (whole-program timing), not profiling (per-function hotspots). Different question.

## Build flag reminder

Both samply and Instruments need symbols. The two viable build types on this repo:

- `RelWithDebInfo` — fast code + symbols. Use this for profiling.
- `Debug` — slow code; profile reflects debug overhead, not real hotspots. Avoid.

Plain `Release` strips symbols and you'll get `???` everywhere in the flamegraph.

## Questions
- What C++ sampling profiler should I use on macOS for daslang (and how do I run it)?
33 changes: 33 additions & 0 deletions mouse-data/docs/daslang-generic-instance-detect-via-fromgeneric.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
slug: daslang-generic-instance-detect-via-fromgeneric
title: How do I detect that an ExprCall is to a daslang generic (e.g. each, length, find) when func.name is the mangled instance name and not the original generic's name?
created: 2026-05-16
last_verified: 2026-05-16
links: []
---

When a daslang generic function (`def each(a : array<auto(TT)>) : iterator<TT&>`, `def length(a : auto | #) : int`, etc.) is resolved against a concrete type at infer time, the resolved `Function?` instance gets a **mangled name** like `` `builtin`each`30908`12345 ``. Macro code that compares `call.func.name == "each"` will never match a typed instance.

The original generic's identity lives in `call.func.fromGeneric`:

```das
[macro_function]
def private is_each_call(call : ExprCall?) : bool {
if (call == null || call.func == null) return false
return (call.func.name == "each"
|| (call.func.fromGeneric != null && call.func.fromGeneric.name == "each"))
}
```

The `name == "each"` branch covers the unusual case where you see the call before the typer has specialized it (e.g. inside a custom call_macro that runs early). The `fromGeneric.name` branch is the normal case for any post-infer chain.

**When this bites:** writing a `[macro_function]` that pattern-matches on a stdlib helper by name — `each`, `length`, `key_exists`, `find`, `set_insert`, all the generic `to_array`/`to_table` variants. Without the `fromGeneric` check, every typed chain silently falls through your match and your macro behaves as if the helper wasn't there.

**Generalizes beyond function calls:** same applies to method overload resolution. `call.func.fromGeneric` is the canonical "which generic was this instantiated from?" link. There's no `originalName` field — the chain is `func → func.fromGeneric → fromGeneric.name`.

**Doesn't apply to:** C++ builtins from `addExtern<>` (no fromGeneric, the `func.name` is the bound name directly). Builtins also have `func.flags.builtIn = true` if you need to distinguish.

See [[my-fold-macro-emits-a-loop-with-for-it-in-source-acc-reserve-length-source-but-the-reserve-doesn-t-fire-when-the-chain-starts-wi]] for the concrete case where this broke `peel_each` in `daslib/linq_fold.das`.

## Questions
- How do I detect that an ExprCall is to a daslang generic (e.g. each, length, find) when func.name is the mangled instance name and not the original generic's name?
43 changes: 43 additions & 0 deletions mouse-data/docs/daslib-macro-boost-has-sideeffects-predicate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
slug: daslib-macro-boost-has-sideeffects-predicate
title: Is there a conservative side-effect detector for Expression nodes in daslib macro_boost — something I can call from a call_macro to know if it's safe to elide an evaluation at macro time?
created: 2026-05-16
last_verified: 2026-05-16
links: []
---

Yes — `has_sideeffects(expr : Expression?) : bool` in `daslib/macro_boost` (added in PR #2691, follow-up to Phase 2A loop planner). Returns `true` if the expression has or **might have** side effects; `false` ONLY when provably pure.

```das
require daslib/macro_boost public

if (has_sideeffects(projection)) {
// Emit the bind — projection must run for its side effects.
sideEffectStmts |> push <| qmacro_expr() {
var $i(finalBindName) = $e(projection)
}
} else {
// Skip the bind — pure projection, no observable effect.
}
```

**Conservative — false is a promise:**

- SAFE leaves: `ExprVar`, all `ExprConst*`, `ExprAddr`, `ExprTypeInfo/Decl/Tag`.
- SAFE via recursion: `ExprField`, `ExprSafeField`, `ExprSwizzle`, `ExprRef2Value/Ptr`, `ExprPtr2Ref`, `ExprIs`, `ExprAsVariant`, `ExprIsVariant`, `ExprSafeAsVariant`, `ExprCast`, `ExprNullCoalescing`, `ExprStringBuilder` (string heap is no-op per compiler), `ExprKeyExists` (pure container read).
- `ExprAt`: safe when `subexpr._type` is NOT `isGoodTableType` (tables auto-insert default on missing key — a write). `ExprSafeAt` (`?[...]`) always safe.
- `ExprOp1/Op2/Op3`: op-name allowlist for pure ops on workhorse types — `+ - * == != < <= > >= & | ^ << >> && || ?:` (Op2), `- ! ~ +` (Op1). Falls back to `func.flags.builtIn && !knownSideEffects && !unsafeOperation`. `/` and `%` BLACKLISTED (div-by-zero panic).
- `ExprCall`/`ExprCallFunc`: allowed when `func.flags.builtIn && !knownSideEffects && !unsafeOperation`, then recurse args.
- Everything else (including `ExprNew`, all `ExprMake*`, user-defined calls, `ExprInvoke`, `ExprYield`, statement-context exprs): UNSAFE.

**Known limitations / when it returns conservative-unsafe:**

- daslang-generic helpers like `length(arr)` and `key_exists(tab, k)` — the resolved `func.name` is the mangled instance, and the typer hasn't always reached the `flags.builtIn=true` C++ overload before the call_macro fires. They show up as user-call shapes and get rejected. Workaround: don't rely on this for length/key_exists in projections (they appear in `has_sideeffects` tests as `target_generic_length_unresolved` / `target_key_exists_unresolved` returning `true`).
- User-defined pure helpers — there's no `[no_side_effects]` annotation yet. The compiler's `expr.flags.noSideEffects` fast path catches some cases (set during infer), but anything the typer didn't tag falls through to UNSAFE.

**Tests:** `tests/macro_boost/test_has_sideeffects.das` has 24 cases (17 safe + 5 unsafe + 2 conservative-unsafe) wired via a `_test_has_sideeffects` probe `call_macro` ([`tests/macro_boost/_has_sideeffects_probe.das`](../../tests/macro_boost/_has_sideeffects_probe.das)) that runs the predicate at macro time and emits `ExprConstBool` of the result. Use the same probe pattern when testing any new predicate that needs to run at macro time but be exercised via runtime tests.

**Real use:** `daslib/linq_fold.das` `plan_loop_or_count` uses it for three optimizations: discardable `var vfinal =` bind elision, count→length shortcut gate (whole loop elided when no filter + all projections pure + source has length), and tracking `allProjectionsPure` across chained selects. select_count benchmark went from 2 → 0 ns/op.

## Questions
- Is there a conservative side-effect detector for Expression nodes in daslib macro_boost — something I can call from a call_macro to know if it's safe to elide an evaluation at macro time?
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
slug: how-do-i-call-a-dasimgui-or-any-managed-c-method-on-a-struct-field-that-s-bound-as-a-raw-pointer-e-g-addfontfromfilettf-on-getio
title: How do I call a dasImgui (or any managed C++) method on a struct field that's bound as a raw pointer — e.g. AddFontFromFileTTF on GetIO().Fonts?
created: 2026-05-16
last_verified: 2026-05-16
links: []
---

## TL;DR

When a managed struct's field is bound as a pointer (`T?`) and the method on that pointed-to struct expects the value by-ref (`T implicit`), you must explicitly **dereference**. Plain `field |> method(...)` errors with mismatched types.

## The error you'll hit

```
error[30341]: no matching functions or generics: AddFontFromFileTTF(imgui::ImFontAtlas?&, string const&, ...)
candidate function: ImFontAtlas implicit ...
invalid argument 'self' (0). expecting 'imgui::ImFontAtlas implicit', passing 'imgui::ImFontAtlas?&'
```

The `?` is the giveaway — `GetIO().Fonts` is `ImFontAtlas?` (raw pointer; field bound via `addField<DAS_BIND_MANAGED_FIELD(Fonts)>` against C++ `ImFontAtlas* Fonts`), but the method binding `das_call_member< ImFont * (ImFontAtlas::*)(...) >` takes the receiver by-value/ref.

## The fix

Bind a local ref through `unsafe(*ptr)`, then call as usual:

```daslang
var atlas & = unsafe(*GetIO().Fonts)
let f = atlas |> AddFontFromFileTTF(ttf, 14.0f, null, null)
```

Equivalent inline form:

```daslang
unsafe(*GetIO().Fonts) |> AddFontFromFileTTF(ttf, 14.0f, null, null)
```

## Why each part

- **`*ptr`** is daslang's pointer-deref syntax (see `daslib/if_not_null.rst`: *"a dereferenced call: ``if (ptr != null) { call(*ptr, args) }``"*). The alternative `deref(ptr)` exists too but is rarer in modules; `*` is the idiom.
- **`unsafe(...)`** is required because dereferencing a raw `T?` is unsafe (no null check, no lifetime guarantee).
- **`var atlas &`** binds a local *reference* — without `&` you'd be copying the whole `ImFontAtlas` struct into a stack temporary, which (a) wastes memory and (b) means any mutation the method does (font atlas builds, glyph rasterization) hits the copy and is lost.
- **The pipe `|>` works fine on the local ref** — `atlas |> method(x, y)` desugars to `method(atlas, x, y)` and the `implicit` first-param accepts the ref directly.

## Why NOT the other shapes

- `GetIO().Fonts.AddFontFromFileTTF(...)` — `.method()` sugar is sugar for `method(self, ...)` only when `self` is a struct value. CLAUDE.md explicitly: *"Does NOT work on: primitives, tuples/arrays, and lambda typedefs"* — and (this case) raw pointers. Field *access* on a pointer auto-derefs (`GetIO().Fonts.TexID` works); method dispatch does not.
- `GetIO().Fonts->AddFontFromFileTTF(...)` — `->` is for class instances (smart_ptr / class types), not raw C-struct pointers from `ManagedStructureAnnotation`.
- `deref(GetIO().Fonts) |> AddFontFromFileTTF(...)` — works but the pipe gets a temporary value not a ref; mutations on the receiver disappear. Use `var x & = unsafe(*p)` instead.

## When this comes up

Anywhere a C++ binding exposes a struct field as `T*` (typical for "owns-an-atlas" or "owns-a-context" patterns):
- `ImGuiIO::Fonts` → `ImFontAtlas?`
- `ImDrawData::CmdLists` → indirection on lists
- anything bound via raw `addField<DAS_BIND_MANAGED_FIELD(X)>` where the C++ type is `Foo*`

If the C++ field were a value (`ImFontAtlas Fonts;` instead of `ImFontAtlas* Fonts;`), it'd bind as the struct directly and the pipe would just work.

## Related

- [[dasimgui-new-state-struct-widget-auto-emit-just-works]] — different topic (state-struct registration) but same module family.
- [[how-do-i-pack-an-im-col32-color-from-dasimgui-v2-code-without-depending-on-the-v1-daslib-imgui-boost-path]] — sibling dasImgui idiom.

## Questions
- How do I call a dasImgui (or any managed C++) method on a struct field that's bound as a raw pointer — e.g. AddFontFromFileTTF on GetIO().Fonts?
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
slug: how-do-i-run-dastest-in-benchmark-only-mode-and-what-s-the-command-line-syntax
title: How do I run dastest in benchmark-only mode and what's the command-line syntax?
created: 2026-05-16
last_verified: 2026-05-16
links: []
---

Benchmarks are functions annotated with `[benchmark]` from `dastest/testing_boost.das`. Run them via the dastest harness with `--bench`:

```bash
# All benchmarks in a directory (skip the regular tests)
./bin/daslang dastest/dastest.das -- --bench --test benchmarks/sql --test-names none

# Just one file
./bin/daslang dastest/dastest.das -- --bench --test benchmarks/sql/count_aggregate.das --test-names none

# Filter by [benchmark] function-name prefix (substring match on the function name)
./bin/daslang dastest/dastest.das -- --bench --bench-names sum_ --test benchmarks/sql --test-names none

# Collect N samples for variance / averaging
./bin/daslang dastest/dastest.das -- --bench --test benchmarks/sql/count_aggregate.das --test-names none --count 5
```

Key flags:
- `--bench` — enable benchmark execution
- `--test <path>` — folder or single file (NOT positional)
- `--test-names none` — skip regular `[test]` discovery (benchmarks only)
- `--bench-names <prefix>` — filter benchmarks by function-name prefix
- `--bench-format <native|go|json>` — output format
- `--count <n>` — repeat all benchmarks N times

Benchmarks only run after all module **tests** have passed; that's why `--test-names none` is the canonical "skip tests, run benchmarks" combo.

Output is `<sub_name> N ns/op <bytes>/op <allocs>/op <SB>/op <strings>/op`. If the benchmark `b |> run(name, chunk_size, op)` form passes a chunk_size (typically the dataset size), the displayed ns/op is **divided by that chunk_size** — i.e. per-element time, not per-op-call time. Sub-nanosecond results (`0 ns/op`) usually mean early-exit hit the answer in O(1) regardless of dataset size.

Reference: `dastest/README.md` and `dastest/dastest_clargs.das`.

## Questions
- How do I run dastest in benchmark-only mode and what's the command-line syntax?
- What's the dastest --bench command line?
- How do I filter dastest benchmarks by name?

## Questions
- How do I run dastest in benchmark-only mode and what's the command-line syntax?
Loading