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
9 changes: 9 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,15 @@ jobs:
;;
esac

# sccache on PRs: per-TU content-hash cache via GHA backend. Master clean.
- if: github.event_name == 'pull_request'
uses: mozilla-actions/sccache-action@v0.0.10
- if: github.event_name == 'pull_request'
run: |
echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
echo "CMAKE_C_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV
echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV

- name: "Build: Daslang"
run: |
set -eux
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/extended_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ jobs:
./vcpkg/vcpkg install openssl:x64-windows --binarycaching
echo "VCPKG_ROOT=$(pwd)/vcpkg" >> $GITHUB_ENV

# sccache on PRs: per-TU content-hash cache via GHA backend. Master clean.
- if: github.event_name == 'pull_request'
uses: mozilla-actions/sccache-action@v0.0.10
- if: github.event_name == 'pull_request'
run: |
echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
echo "CMAKE_C_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV
echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV

- name: "Build: Daslang (Release)"
run: |
set -eux
Expand Down
25 changes: 12 additions & 13 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,23 +73,22 @@ jobs:
working_directory: build/latex
continue_on_error: true

- name: Setup Emscripten
uses: emscripten-core/setup-emsdk@v11
with:
# Pinned: 5.0.7's clang crashes on utils/dasFormatter/ds_parser.cpp
# in diagnostic snippet rendering. 5.0.3 known-good.
version: '5.0.3'

- name: "Build WASM (Emscripten) for /playground/ + /files/wasm/"
run: |
set -eux
cd web
# step0 only clones the emsdk wrapper — the toolchain itself
# (emsdk/upstream/emscripten/) lands via the install+activate pair
# that web/step1_emsdk_activate_linux.sh does locally. Without these,
# cmake's -DCMAKE_TOOLCHAIN_FILE points at a path that doesn't
# exist yet and the WASM build silently fails under continue-on-error.
bash step0_emsdk_install.sh
./emsdk/emsdk install latest
./emsdk/emsdk activate latest
bash -c "source emsdk/emsdk_env.sh && \
mkdir -p build && cd build && \
cmake -DCMAKE_BUILD_TYPE=Release -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake ../ && \
ninja"
mkdir -p build && cd build
emcmake cmake -DCMAKE_BUILD_TYPE=Release -G Ninja ..
# daslang_static.wasm playground + stage_site_playground (vendored UI
# + js/wasm) + stage_site_playground_wasm (cross-compiled examples).
ninja daslang_static stage_site_playground stage_site_playground_wasm
continue-on-error: true

- name: "Fetch dasProfile benchmark JSON"
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/playground-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ on:
pull_request:
paths:
- 'site/**'
- 'web/ui/**'
- 'web/examples/ui/**'
- '.github/workflows/playground-e2e.yml'
push:
branches-ignore: [master]
paths:
- 'site/**'
- 'web/ui/**'
- 'web/examples/ui/**'
- '.github/workflows/playground-e2e.yml'
workflow_dispatch:

Expand Down Expand Up @@ -48,9 +48,9 @@ jobs:
cp site/downloads.html _site/ || true
cp -r site/files _site/files

# Playground: vendor web/ui IDE (no WASM artifacts)
cp -r web/ui/src/* _site/playground/
cp -r web/ui/samples _site/playground/samples 2>/dev/null || true
# Playground: vendor web/examples/ui IDE (no WASM artifacts)
cp -r web/examples/ui/src/* _site/playground/
cp -r web/examples/ui/samples _site/playground/samples 2>/dev/null || true
cp site/playground/index.html _site/playground/index.html
cp site/playground/forge-skin.css _site/playground/
# Forge playground scripts (init shim + tabs + share + splitter).
Expand Down
67 changes: 65 additions & 2 deletions .github/workflows/wasm_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,17 @@ jobs:
./emsdk/emsdk_env.bat
;;
*)
# macOS stock bison is 2.3; install Homebrew bison 3.x and shadow
# the system one on PATH so any FIND_FLEX_AND_BISON pickup is sane.
if [[ "$(uname -m)" == "arm64" ]]; then
brew install bison
echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.bash_profile
export PATH="/opt/homebrew/opt/bison/bin:$PATH"
else
brew install bison
echo 'export PATH="/usr/local/opt/bison/bin:$PATH"' >> ~/.bash_profile
export PATH="/usr/local/opt/bison/bin:$PATH"
fi
./emsdk/emsdk install latest
./emsdk/emsdk activate latest
source "/Users/runner/work/daScript/daScript/emsdk/emsdk_env.sh"
Expand All @@ -117,7 +128,6 @@ jobs:
run: |
set -eux
cd web
cp ../CMakeXxdImpl.txt .
rm -r -f cmake_temp
mkdir cmake_temp
cd cmake_temp
Expand All @@ -126,5 +136,58 @@ jobs:

- name: "Test: hello world via Node.js"
run: |
set -eux
# Use emsdk-bundled Node (deterministic version that supports the
# --experimental-wasm-exnref flag). System Node may be older or use
# a different spelling for the EH proposal flag.
source ./emsdk/emsdk_env.sh
cd web
# Modern wasm EH proposal (try_table / exnref) is gated in Node 22.
# Default-on in Node 24+. Force-enable for forward compat with current LTS.
"$EMSDK_NODE" --experimental-wasm-exnref test/dastest_wasm.js ../ ./output

###########################################################
wasm_cross:
###########################################################
# Cross-compile utility mains to wasm32 via dasLLVM and execute under
# wasmtime. Smoke-tests the runtime-linked cross-compile pipeline
# end-to-end (see modules/dasLLVM/README.md "Cross-compilation").
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'
runs-on: ubuntu-latest-fat
steps:
- name: "SCM Checkout"
uses: actions/checkout@v4

- uses: lukka/get-cmake@latest

- name: Setup Emscripten
uses: emscripten-core/setup-emsdk@v11
with:
# 5.0.7 ships clang commit 7b58716d that crashes in
# TextDiagnostic::emitSnippetAndCaret on utils/dasFormatter/ds_parser.cpp
# (std::length_error during diagnostic snippet rendering). 5.0.3 works.
version: '5.0.3'

- name: Setup Wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
with:
version: "44.0.1"

- name: "Install: graphics dev libs"
run: |
set -eux
sudo apt-get update -y
# OpenGL + GLFW headers needed by dasGlfw (enabled by default in host
# cmake). Same set as extended_checks.yml.
sudo apt-get install --no-install-recommends -y \
mesa-common-dev libglu1-mesa-dev libgl1-mesa-dev libglfw3-dev \
libx11-dev libxrandr-dev libxcursor-dev libxinerama-dev libxi-dev

- name: "Build + run cross-compiled wasm examples"
run: |
set -eux
cd web
node test/dastest_wasm.js ../ ./output
mkdir -p cmake_temp && cd cmake_temp
emcmake cmake -DCMAKE_BUILD_TYPE=Release -G Ninja ..
ninja run_wasm_examples
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ Full migration table (when reading older docs that say `var inscope` or `<-` for
| `unsafe(x + y)` / `unsafe { let d = x + y }` where nothing inside requires unsafe | drop the wrap | STYLE024: redundant `unsafe` — flagged when no descendant matches a known inherently-unsafe shape (reinterpret/upcast cast, `delete`, `addr`, table-index, variant-write, ExprCallFunc with `unsafeOperation`). Macro-generated subtrees (`genFlags.generated == true`) skipped per design |
| `unsafe { stmt1; stmt2; stmt_needing_unsafe }` (only ONE stmt actually needs unsafe) | `stmt1; stmt2; unsafe(<sub-expr>)` | STYLE025: narrow block-form unsafe to expression-form on the single unsafe-needing statement. Silent when ≥2 statements need unsafe (block is justified) |
| `unsafe { ...; unsafe { ... }; ... }` (nested `unsafe { }` block) | drop the inner wrap | STYLE026: outer `unsafe` already covers the whole inner scope, so the inner block is pure noise. Closure / lambda / generator bodies are NOT nested for this rule — they execute in a separate context where the outer wrap does not propagate |
| `for (s in A) { B \|> push(s) }` / `push_clone(s)` (iter-var only) | `B \|> push_from(A)` / `push_clone_from(A)` | PERF022: the bulk overload in builtin.das reserves combined capacity up front. Single name `push`/`push_clone` is overloaded between single-element and bulk (ambiguous when destination is `array<T[]>`); the `_from` suffix names the bulk intent. Source must be `array<T>` or C-array — range/iterator sources are not flagged. `emplace` is out of scope (const iter-var can't be moved) |

For path/filename ops use `fio` helpers (`base_name`/`dir_name`/`path_join`/etc.) — see `skills/filesystem.md`. Never hand-roll `rfind("/")` / slice — misses Windows separators.

Expand Down
18 changes: 18 additions & 0 deletions benchmarks/sql/M4_DECS_EXPANSION.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,21 @@ Two new planners `plan_decs_order_family` (mirrors `plan_order_family`) and `pla
All 6 rows improve 1.5-2.7× (avg ~2.1×). m4 lands within 2× of m3f on most rows — the remaining gap is the Wave 4 multi-component `get_ro` overhead (sort dominates the wall-clock so the gap doesn't fully close even when the splice fires). `bare_order_where` is the tightest squeeze (1.5×) because sort over 100K rows is the bottleneck; once the m4 buffer build matches m3f (~117 ns), the rest is pure sort time.

**Coverage:** bare `_order_by` + to_array, bare `_order_by_descending` + to_array, `_order_by` + take, `_order_by_descending` + take, `_order_by` + first, `_order_by_descending` + first, `_order_by` + first_or_default (nonempty + empty), `_where + _order_by + take`, `_where + _order_by + first`; `.reverse() + to_array`, `.reverse() + take(N)` (in-range, beyond-length, and zero — last uses runtime-var to defeat const-fold of PERF017 false-positive on splice-emitted `0 < length(buf)`), `.reverse() + count` (no-buffer counter loop), `_where + .reverse() + first_or_default` (empty + nonempty), `_where + .reverse() + to_array`, `_where + _select + .reverse() + to_array` (projection through reverse). AST shape gates for: order+take (top_n_by emit + no to_sequence + 1 for_each_archetype), order+first (min_by emit + panic-on-empty guard + no top_n_by), reverse+to_array (1 reverse_inplace), reverse+count (no decs_buf, no reverse_inplace). +20 tests (100 → 120 in file).

## Update — Slice 5c distinct/distinct_by on decs (2026-05-21, plan_decs_distinct)

New `plan_decs_distinct` planner mirrors `plan_distinct` — streaming dedup via `var inscope seen : table<typedecl(_::unique_key(...))>` hoisted above `for_each_archetype` so the seen-table persists across archetypes. Inserted in the cascade BEFORE `plan_distinct` (same rule as 5d/5e — decs bridge match is stricter). Two emit shapes:

- **Buffer-required** (`to_array` default, optionally `take(N)`): per-element splice computes the key, `key_exists` check, on miss does `taken++ → seen|>insert → buf|>push_clone`. When `take(N)` is present, outer iteration switches to `for_each_archetype_find` and the take-cap `return true` propagates across archetypes — true streaming early-exit. Take-limit bound to a `let` at outer scope so a side-effecting `take(arg)` evaluates exactly once.
- **Buffer-elided** (`count`/`long_count` → `length(seen)`/`int64(length(seen))`; `sum` folds inline at fresh-key site via `acc += <projection>`): no buffer allocation, only the seen-table is materialized. count + take both present cascades to tier 2 (matches array-side).

Side-effecting `_select(proj)` upstream binds once per element to a fresh `decs_pv` local — key + buf push (or sum fold) share the bind, matching array-side single-eval per source element. `distinct_by(key)` wraps the key block in `invoke(<keyBlock>, <projection-or-tup>)` and the seen-table's value type is derived via `_::unique_key(invoke(<keyBlock>, default<elemT>))` so the table key type tracks the key function's return type.

| benchmark | shape | m1 sql | m3 | m3f (array splice) | m4 (eager bridge, was) | m4 (Slice 5c, now) | m4 win |
|---|---|---:|---:|---:|---:|---:|---:|
| distinct_count | `_select(_.brand).distinct().to_array()` | 41 | 44 | 15 | 97 | **28** | 3.5× |
| distinct_take | `_select(_.brand).distinct().take(3).to_array()` | 0 | 30 | 0 | 34 | **0** | matches m3f |

`distinct_count` lands at 28 ns/op vs m3f's 15 — the ~13 ns gap is the Wave 4 multi-component `get_ro` floor (3 components participate in the inner for-loop even when chain only reads `brand`). `distinct_take` collapses to 0 ns/op — early-exit at the 3rd distinct brand visits only ~3 source elements regardless of N=100K, same as the array-side splice.

**Coverage:** `_select(_.brand).distinct()` + to_array / count / long_count / sum / take(N) / take(0) / take(N>num_distinct), `_where(_.id<8)._select(_.brand).distinct()`, `_distinct_by(_.brand)` + to_array / count / take(N), empty-decs distinct yields empty, side-effecting take(N) arg evaluates exactly once at invoke entry. AST shape gates for: distinct+count (no to_sequence, no decs_buf, single key_exists, plain for_each_archetype), distinct+take (for_each_archetype_find + decs_buf + decs_seen + decs_taken counters), distinct_by+to_array (unique_key wrapping the key invocation), distinct+sum (no decs_buf, decs_acc declared+folded). +17 tests (122 → 139 in file).
10 changes: 3 additions & 7 deletions daslib/algorithm.das
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,9 @@ def reverse(var a : array<auto>) {
def combine(a, b : array<auto(TT)>) {
//! Returns a new array containing elements from a followed by b.
var c : array<TT>
reserve(c, length(a) + length(b))
for (t in a) {
c |> push_clone(t)
}
for (t in b) {
c |> push_clone(t)
}
c |> reserve(long_length(a) + long_length(b))
c |> push_clone_from(a)
c |> push_clone_from(b)
return <- c
}

Expand Down
Loading
Loading