Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
27115fd
style_lint: fix STYLE024/STYLE025 false positives on unsafe()
aleksisch May 20, 2026
3ee66e4
longarr phase 5: fusion variants for arr[i64] / arr[u64] indexing
borisbat May 20, 2026
78c2008
phase 5 bench: apples-to-apples for-loop normalization
borisbat May 20, 2026
91e4ef4
phase 5 bench: outer-loop amortization + cast-free body
borisbat May 20, 2026
a51afea
Merge pull request #2755 from GaijinEntertainment/bbatkin/longarr-pha…
borisbat May 20, 2026
06d8986
lint: PERF021 — hoist common workhorse cast out of ternary
borisbat May 20, 2026
7ae1010
PERF021: gate on tail-arg equality to avoid losing semantic args
borisbat May 20, 2026
0e76e9f
lint: allow enable/disable
aleksisch May 20, 2026
bf24937
daslang: add pre-push hook
aleksisch May 20, 2026
352a525
Merge pull request #2759 from GaijinEntertainment/bbatkin/perf-lint-p…
borisbat May 20, 2026
b30397c
longarr phase 8a: alignMask uint32 truncation + huge-array probes
borisbat May 20, 2026
ca489c3
Merge pull request #2761 from GaijinEntertainment/aleksisch/pre-push
borisbat May 20, 2026
ec1541c
benchmarks/sql: add m4_decs_fold lane to all SQL-vs-array benchmarks
borisbat May 20, 2026
0e16664
benchmarks/sql: defense-in-depth — accept() result in every bench run…
borisbat May 20, 2026
0bc3d0d
linq_fold: plan_order_family — order_by + first → min_by splice arm
borisbat May 20, 2026
151fb04
linq_fold: order+first preserves first()-panic-on-empty + sort_first …
borisbat May 20, 2026
d65d789
linq_fold: plan_order_family bails on order(arr, cmp) custom comparator
borisbat May 20, 2026
91840ca
daslib + linq_fold: drop PERF020-redundant casts in average/count emi…
borisbat May 20, 2026
9c0d82e
Merge pull request #2758 from GaijinEntertainment/aleksisch/fix-lint
borisbat May 20, 2026
691a640
.githooks/pre-push: replace mapfile with portable read loop (bash 3.2…
borisbat May 20, 2026
38c7b9a
Merge pull request #2762 from GaijinEntertainment/bbatkin/longarr-pha…
borisbat May 20, 2026
8a0c8f0
Merge pull request #2760 from GaijinEntertainment/bbatkin/linq-fold-b…
borisbat May 20, 2026
9e45069
Merge pull request #2763 from GaijinEntertainment/bbatkin/pre-push-ho…
borisbat May 20, 2026
7dc6b8f
linq_fold: plan_zip — accumulator + early-exit terminators on zip
borisbat May 19, 2026
e8f6ba5
linq_fold: plan_zip Copilot review fixes (long_count regression + nit)
borisbat May 19, 2026
3d76311
linq_fold: plan_zip Copilot review round 2 (bounds guard + deferred-f…
borisbat May 19, 2026
01c4927
phase 8b: typeinfo is_int / is_int64 + int|int64 disjunction spike
borisbat May 20, 2026
155f3b5
M4 doc: zip_dot_product fix (plan_zip accumulator/early-exit lanes)
borisbat May 20, 2026
a6559aa
Merge pull request #2764 from GaijinEntertainment/bbatkin/longarr-pha…
borisbat May 20, 2026
22e9e93
linq_fold: plan_decs_unroll Slice 3 — min/max/average, early-exit, to…
borisbat May 20, 2026
8a962d8
linq_fold: fix LINT003 — let got, not var got, in to_array parity test
borisbat May 20, 2026
32c8bc0
linq_fold: address Copilot review on PR #2751
borisbat May 20, 2026
a85c10c
linq_fold: plan_decs_unroll Slice 4 — chain extensions + predicate te…
borisbat May 20, 2026
8f7e42c
Merge pull request #2765 from GaijinEntertainment/bbatkin/linq-fold-p…
borisbat May 20, 2026
4dc0da8
fix: add null check for subexpression type in ExprAt handling
profelis May 20, 2026
c172301
Merge pull request #2766 from GaijinEntertainment/bbatkin/decs-unroll…
borisbat May 20, 2026
50df086
Merge pull request #2767 from profelis/master
borisbat May 20, 2026
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
50 changes: 50 additions & 0 deletions .githooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Repo-managed git hooks

Tracked hooks that mirror CI checks. Cross-platform: Linux, macOS, Windows (Git Bash).

## Enable (once per clone)

```sh
git config core.hooksPath .githooks
```

Reverts via `git config --unset core.hooksPath`. Skip once with `git push --no-verify`.

### Windows notes

- Git for Windows ships Git Bash — the `#!/usr/bin/env bash` shebang works out of the box. No extra install needed.
- If you use PowerShell or `cmd` for `git push`, that's fine — git invokes the hook through its own shell, not yours.
- File-mode permissions are ignored on Windows; just having the file in `.githooks/` is enough.

### Linux / macOS

Hooks must be executable. Cloning preserves the executable bit (it's set in the repo via `git update-index --chmod=+x`). If you ever lose it locally:

```sh
chmod +x .githooks/*
```

## Hooks

- **pre-push** — formatter (`--verify` on the whole tree) + lint on pushed `.das` files. Mirrors `.github/workflows/extended_checks.yml`.

## Requirements

A built `daslang` binary. The script auto-detects:

- `bin/daslang` (Linux / macOS, single-config Make/Ninja)
- `bin/daslang.exe` (MSYS / cygwin)
- `bin/Release/daslang.exe` / `bin/Debug/daslang.exe` (Windows MSVC)
- `build/daslang`, `build/bin/daslang`

Build before pushing:

```sh
cmake --build build --target daslang
```

Override the resolved path:

```sh
DASLANG=/custom/path/daslang git push
```
83 changes: 83 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# pre-push: mirror CI's formatter + lint (.github/workflows/extended_checks.yml).
# Cross-platform: runs under Git Bash on Windows, bash on Linux/macOS.
#
# Enable per-clone:
# git config core.hooksPath hooks
# Skip once:
# git push --no-verify
# Override binary path:
# DASLANG=/path/to/daslang git push

set -eu

ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT"

# Resolve daslang binary across single/multi-config layouts.
resolve_daslang() {
if [ -n "${DASLANG:-}" ] && [ -x "$DASLANG" ]; then
echo "$DASLANG"; return
fi
for p in \
"$ROOT/bin/daslang" \
"$ROOT/bin/daslang.exe" \
"$ROOT/bin/Release/daslang.exe" \
"$ROOT/bin/Debug/daslang.exe" \
"$ROOT/build/daslang" \
"$ROOT/build/bin/daslang"
do
[ -x "$p" ] && { echo "$p"; return; }
done
return 1
}

DASLANG="$(resolve_daslang || true)"
if [ -z "$DASLANG" ]; then
echo "pre-push: daslang not found — build it (cmake --build build --target daslang)" >&2
echo " or set DASLANG=/path/to/daslang" >&2
exit 1
fi

echo "pre-push: using $DASLANG"

# Formatter: whole tree, verify-only (CI parity).
echo "pre-push: formatter --verify ..."
"$DASLANG" "$ROOT/utils/das-fmt/dasfmt.das" -- --path "$ROOT" --verify

# Lint: only .das files changed in the pushed range vs remote tip
# (mirrors CI's `git diff origin/<base>...HEAD`).
ZERO40="0000000000000000000000000000000000000000"
ZERO64="0000000000000000000000000000000000000000000000000000000000000000"
RANGES=()
while read -r LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do
case "$LOCAL_SHA" in "$ZERO40"|"$ZERO64") continue;; esac # branch delete
case "$REMOTE_SHA" in
"$ZERO40"|"$ZERO64")
BASE="$(git merge-base "$LOCAL_SHA" origin/master 2>/dev/null || true)"
[ -n "$BASE" ] && RANGES+=("$BASE..$LOCAL_SHA")
;;
*)
RANGES+=("$REMOTE_SHA..$LOCAL_SHA")
;;
esac
done

if [ ${#RANGES[@]} -eq 0 ]; then
echo "pre-push: no ranges (deleting or empty); skipping lint"
exit 0
fi

# Portable read-into-array (mapfile is bash 4+; macOS default ships bash 3.2).
CHANGED=()
while IFS= read -r line; do
CHANGED+=("$line")
done < <(git diff --name-only --diff-filter=AM "${RANGES[@]}" -- '*.das' | sort -u)

if [ ${#CHANGED[@]} -eq 0 ]; then
echo "pre-push: no .das files changed; skipping lint"
exit 0
fi

echo "pre-push: linting ${#CHANGED[@]} .das file(s)"
"$DASLANG" "$ROOT/utils/lint/main.das" -- "${CHANGED[@]}" --quiet
82 changes: 82 additions & 0 deletions benchmarks/fusion/bench_arr_at_i64.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
options gen2

require dastest/testing_boost

// Apples-to-apples cost of int / int64 / uint64-indexed array access.
// All three benches use the same `for (i in <range-iter>(N))` shape,
// differing only in the index type. Write workload is `arr[i] = 1`
// (no cast in the body — isolates the index/fusion cost). The outer
// `for (j in range(OUTER))` amortizes the per-body harness overhead
// over OUTER * N inner ops.

let N = 10000
let OUTER = 10
let TOTAL = N * OUTER

[benchmark]
def arr_at_int_idx(b : B?) {
var arr : array<int>
arr |> resize(N)
b |> run("write_int_idx/{TOTAL}", TOTAL) {
for (_j in range(OUTER)) {
for (i in range(N)) {
arr[i] = 1
}
}
}
b |> run("read_int_idx/{TOTAL}", TOTAL) {
var sum = 0
for (_j in range(OUTER)) {
for (i in range(N)) {
sum += arr[i]
}
}
strictEqual(b, length(arr), N)
}
}

[benchmark]
def arr_at_int64_idx(b : B?) {
var arr : array<int>
arr |> resize(N)
let N64 = int64(N)
b |> run("write_int64_idx/{TOTAL}", TOTAL) {
for (_j in range(OUTER)) {
for (i in range64(N64)) {
arr[i] = 1
}
}
}
b |> run("read_int64_idx/{TOTAL}", TOTAL) {
var sum = 0
for (_j in range(OUTER)) {
for (i in range64(N64)) {
sum += arr[i]
}
}
strictEqual(b, length(arr), N)
}
}

[benchmark]
def arr_at_uint64_idx(b : B?) {
var arr : array<int>
arr |> resize(N)
let N64 = uint64(N)
b |> run("write_uint64_idx/{TOTAL}", TOTAL) {
for (_j in range(OUTER)) {
for (i in urange64(N64)) {
arr[i] = 1
}
}
}
b |> run("read_uint64_idx/{TOTAL}", TOTAL) {
var sum = 0
for (_j in range(OUTER)) {
for (i in urange64(N64)) {
sum += arr[i]
}
}
strictEqual(b, length(arr), N)
}
}
66 changes: 66 additions & 0 deletions benchmarks/fusion/bench_table_index_i64.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
options gen2

require dastest/testing_boost

// Apples-to-apples cost of int / int64 / uint64-keyed table indexing.
// All three benches use the same `for (k in <range-iter>(N))` shape,
// differing only in the key type. Read workload is `sum += tab[k]`
// (no cast in the body). The outer `for (j in range(OUTER))` amortizes
// per-body harness overhead over OUTER * N inner ops.

let N = 10000
let OUTER = 10
let TOTAL = N * OUTER

[benchmark]
def table_index_int_key(b : B?) {
var tab : table<int; int>
for (k in range(N)) {
tab[k] = 1
}
b |> run("read_int_key/{TOTAL}", TOTAL) {
var sum = 0
for (_j in range(OUTER)) {
for (k in range(N)) {
sum += tab[k]
}
}
strictEqual(b, length(tab), N)
}
}

[benchmark]
def table_index_int64_key(b : B?) {
var tab : table<int64; int>
let N64 = int64(N)
for (k in range64(N64)) {
tab[k] = 1
}
b |> run("read_int64_key/{TOTAL}", TOTAL) {
var sum = 0
for (_j in range(OUTER)) {
for (k in range64(N64)) {
sum += tab[k]
}
}
strictEqual(b, length(tab), N)
}
}

[benchmark]
def table_index_uint64_key(b : B?) {
var tab : table<uint64; int>
let N64 = uint64(N)
for (k in urange64(N64)) {
tab[k] = 1
}
b |> run("read_uint64_key/{TOTAL}", TOTAL) {
var sum = 0
for (_j in range(OUTER)) {
for (k in urange64(N64)) {
sum += tab[k]
}
}
strictEqual(b, length(tab), N)
}
}
4 changes: 4 additions & 0 deletions benchmarks/sql/LINQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -778,3 +778,7 @@ dastest reports `ns/op` in INTERP mode by default. To bump dataset size as the s
**`PERF009` suppression in `fold_linq_default`.** The macro's `var pass_N = call` + later `return <- pass_N` pattern triggers PERF009 on single-pass chains. The shape is load-bearing for the array-pipeline semantics (every stage binds so the next can reuse the buffer in-place), so we suppress inline at the qmacro_expr emission site and document why.

**Benchmark variants where SQL has no clean form.** `zip` (not a relational op), `_all(pred)` (no direct `_all` chain terminal in sqlite_linq), `join` with inner-select-from (wiring not exposed), `distinct |> count` (no `COUNT(DISTINCT col)` yet), `take/skip` before aggregate (LIMIT/OFFSET semantics conflict with aggregate-collapse). We either reformulate to a SQL-friendly shape (`count(where ¬p)` for all_match), omit the m1 column (zip, join), or terminate the chain in `to_array` instead of an aggregate (take/skip/distinct).

## Future work — 64-bit sweep

Gated on 64-bit arrays + tables landing in daslang. Once they do, sweep `daslib/linq.das` to add `int64` overloads on every count/index/N-parameter surface. Today only the count family is symmetric — `count(iter; pred)` / `count(arr; pred)` / `long_count(iter; pred)` / `long_count(arr; pred)` all exist with their bare (no-pred) forms. The rest of the surface (`take(N)`, `skip(N)`, `skip_last(N)`, `take_last(N)`, `top_n(N)`, `top_n_by(N)`, `top_n_by_descending(N)`, `top_n_by_with_cmp(N)`, `element_at(N)`) still takes `int` only. Add `int64` overloads alongside, route splice planners to pick the matching variant by argument type, and refresh benchmarks for the large-N regime.
Loading
Loading