Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0851ea0
linq_fold Phase 2B Rings 1+2: aggregates + early-exit terminators
borisbat May 17, 2026
cd1b565
linq: defuse ICE 50609 for `all`/`contains` via element-const overload
borisbat May 17, 2026
57e5154
linq_fold: collapse topIsIter if/else arms via shared param-type helper
borisbat May 17, 2026
74fdc84
linq_fold: collapse repeated emission patterns via helpers + single-$b
borisbat May 17, 2026
23c14cc
linq_fold: collapse emit_array_lane 3 arms via single-$b bodyStmts
borisbat May 17, 2026
a2c0d86
Merge pull request #2696 from GaijinEntertainment/bbatkin/linq-fold-p…
borisbat May 17, 2026
eac77b8
linq_fold Phase 2C Ring 3: take/skip splice in all 4 lanes
borisbat May 17, 2026
10e6cdc
ast_match: switch qmatch gensym from qm_{n} to qm`{n}
borisbat May 17, 2026
088d80e
linq_fold Phase 2C Ring 4: chained-select clone via := (drop prevWork…
borisbat May 17, 2026
ffcb275
cmake: gate daslang-target references when DAS_TOOLS_DISABLED=ON
borisbat May 17, 2026
969343a
linq_fold: fix take(N) non-positive N — `==` → `>=`
borisbat May 17, 2026
ceaa3be
cmake: fatal-error on DAS_BUILD_DOCUMENTATION + DAS_TOOLS_DISABLED
borisbat May 17, 2026
1402167
Merge pull request #2697 from GaijinEntertainment/bbatkin/linq-fold-p…
borisbat May 17, 2026
95e5967
examples/graphics: dark bg matching daslang theme + recolor circles
borisbat May 17, 2026
4a84435
mouse: 5 cards — linq_fold Phase 2C + dasImgui HDPI/build
borisbat May 17, 2026
368ec1a
Merge pull request #2698 from GaijinEntertainment/bbatkin/cmake-tools…
borisbat May 17, 2026
6fbfcfd
mcp: thread project arg through run_script and eval_expression
borisbat May 17, 2026
fcf127d
Merge pull request #2700 from GaijinEntertainment/bbatkin/mouse-cards…
borisbat May 17, 2026
5682168
Merge pull request #2699 from GaijinEntertainment/bbatkin/graphics-ex…
borisbat May 17, 2026
7ee8202
Merge pull request #2701 from GaijinEntertainment/claude/sweet-bose-f…
borisbat May 17, 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
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ Full migration table (when reading older docs that say `var inscope` or `<-` for
- `[unsafe_outside_of_for] def each(x) : iterator<T>` makes a type iterable in `for` loops
- When the iterator is named `each`, the call can be omitted: `for (v in each(x))` is identical to `for (v in x)`
- Other iterator names (e.g. `filter`, `map`) cannot be omitted
- **Iterator element-const variance is pointer-like:** `iterator<T -&>` flows into `iterator<T const -&>` (mut → const), not the reverse. So a generic param declared as `iterator<auto(TT) const>` takes both `each(array<T>)` (yields `iterator<T -&>`) and iterator-comprehension (yields `iterator<T const -&>`) sources via a single instantiation. The `const` qualifier alone is enough — do NOT add `&` (that's a separate ref-form modifier, not what you want for variance)
- **Generic-mangling pitfall:** instantiations of the same generic that differ only in inner element-const (`iterator<int -&>` vs `iterator<int const -&>`) currently hash-collide in the instance registry, producing `error[50609]: multiple instances of …` when both arise in one module. Workaround at the library level: declare the iterator overload as `iterator<auto(TT) const>` instead of `iterator<auto(TT)>` — both source flavors then converge on a single instance per the variance rule above. Caveat: the constify makes `it` inside the body const, so the body must not move from / mutate / call non-const operators on `it`. linq.das only constifies `all` and `contains` for this reason; the rest stay vulnerable until the mangler is fixed upstream

### String access functions

Expand Down
31 changes: 25 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,22 @@ ENDMACRO()
# Register a .das file to be run as a headless example check.
# Modules call this from their CMakeLists.txt for examples that
# can run without GUI, audio, or user interaction.
#
# When DAS_TOOLS_DISABLED=ON the `daslang` target is never created, so the
# run_examples target below (which references $<TARGET_FILE:daslang>) cannot
# be generated. Gate the registration here so every call site is automatically
# safe and DAS_EXAMPLES_TO_RUN stays empty.
MACRO(ADD_EXAMPLE_RUN das_file)
SET(_exe TRUE)
IF("${ARGN}" STREQUAL "FALSE")
SET(_exe FALSE)
IF(DAS_TOOLS_DISABLED)
MESSAGE(STATUS "EXAMPLE_RUN ${das_file} skipped (DAS_TOOLS_DISABLED=ON)")
ELSE()
SET(_exe TRUE)
IF("${ARGN}" STREQUAL "FALSE")
SET(_exe FALSE)
ENDIF()
MESSAGE("EXAMPLE_RUN ${das_file} (exe=${_exe})")
LIST(APPEND DAS_EXAMPLES_TO_RUN "${das_file}|${_exe}")
ENDIF()
MESSAGE("EXAMPLE_RUN ${das_file} (exe=${_exe})")
LIST(APPEND DAS_EXAMPLES_TO_RUN "${das_file}|${_exe}")
ENDMACRO()

ADD_EXAMPLE_RUN("${PROJECT_SOURCE_DIR}/examples/dasbind/dasbind_example.das")
Expand Down Expand Up @@ -1117,7 +1126,11 @@ SET(DAS_DASCRIPT_MAIN_SRC

# Tests
if (NOT ${DAS_TESTS_DISABLED})
include(examples/pathTracer/CMakeLists.txt)
# pathTracer links against libDaScriptAot, which is only created when
# AOT examples are enabled — gate the inclusion accordingly.
if(NOT ${DAS_AOT_EXAMPLES_DISABLED})
include(examples/pathTracer/CMakeLists.txt)
endif()
include(examples/crash/CMakeLists.txt)
if(NOT ${DAS_AOT_EXAMPLES_DISABLED} AND NOT (WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 4))
include(tests/aot/CMakeLists.txt)
Expand Down Expand Up @@ -1742,7 +1755,13 @@ install(FILES
add_subdirectory(tutorials)

# ── Documentation build (optional) ──────────────────────────────────────────
# Requires the `daslang` target (das2rst stdlib RST generator runs it). If the
# user explicitly asked for docs but also disabled tools, fail early rather
# than silently skipping — mirrors the DAS_AOT_EXAMPLES check above.
if(DAS_BUILD_DOCUMENTATION)
if(DAS_TOOLS_DISABLED)
message(FATAL_ERROR "DAS_BUILD_DOCUMENTATION requires DAS_TOOLS_DISABLED to be OFF (the docs pipeline runs daslang via das2rst.das)")
endif()
find_package(Python3 COMPONENTS Interpreter)
if(Python3_FOUND)
execute_process(
Expand Down
165 changes: 159 additions & 6 deletions benchmarks/sql/LINQ.md

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions benchmarks/sql/contains_match.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
options gen2
options persistent_heap

require _common public

let TARGET_ID = 50000

// contains compares each element (or projected value) against a fixed needle. The needle
// is bound once at the top of the invoke block so it's evaluated only once even if the
// argument is expensive. Early-exit on first match.

def run_m1(b : B?; n : int) {
with_sqlite(":memory:") $(db) {
fixture_db(db, n)
b |> run("m1_sql/{n}", n) {
// SQL doesn't have a direct CONTAINS for arbitrary values; use _any with _where.
let opt = _sql(db |> select_from(type<Car>) |> _where(_.id == TARGET_ID) |> _first_opt())
if (!is_some(opt)) {
b->failNow()
}
}
}
}

def run_m3(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3_array/{n}", n) {
// Project ids out then contains. Mirrors what `_fold(...select.contains(...))` does
// — an array-source linq chain materializes `select` first then iterates contains.
let yes = arr |> _select(_.id) |> contains(TARGET_ID)
if (!yes) {
b->failNow()
}
}
}

def run_m3f_old(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3f_old_array_fold/{n}", n) {
let yes = _old_fold(each(arr)._select(_.id).contains(TARGET_ID))
if (!yes) {
b->failNow()
}
}
}

def run_m3f(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3f_array_fold/{n}", n) {
let yes = _fold(each(arr)._select(_.id).contains(TARGET_ID))
if (!yes) {
b->failNow()
}
}
}

[benchmark]
def contains_match_m1(b : B?) {
run_m1(b, 100000)
}

[benchmark]
def contains_match_m3(b : B?) {
run_m3(b, 100000)
}

[benchmark]
def contains_match_m3f_old(b : B?) {
run_m3f_old(b, 100000)
}

[benchmark]
def contains_match_m3f(b : B?) {
run_m3f(b, 100000)
}
76 changes: 76 additions & 0 deletions benchmarks/sql/first_or_default_match.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
options gen2
options persistent_heap

require _common public

let THRESHOLD = 500
let SENTINEL_ID = -1

// first_or_default mirrors first but returns a caller-provided default on empty/no-match
// instead of panicking. Same early-exit characteristics; default bound once at the top of
// the invoke block (matches linq.das line 2397 eager evaluation).

def run_m1(b : B?; n : int) {
with_sqlite(":memory:") $(db) {
fixture_db(db, n)
b |> run("m1_sql/{n}", n) {
let row = _sql(db |> select_from(type<Car>) |> _where(_.price > THRESHOLD) |> _first())
if (row.price == 0) {
b->failNow()
}
}
}
}

def run_m3(b : B?; n : int) {
let arr <- fixture_array(n)
let sentinel = Car(id = SENTINEL_ID, name = "none", price = 0, brand = 0, year = 0, dealer_id = 0)
b |> run("m3_array/{n}", n) {
let row = arr |> _where(_.price > THRESHOLD) |> first_or_default(sentinel)
if (row.id == SENTINEL_ID) {
b->failNow()
}
}
}

def run_m3f_old(b : B?; n : int) {
let arr <- fixture_array(n)
let sentinel = Car(id = SENTINEL_ID, name = "none", price = 0, brand = 0, year = 0, dealer_id = 0)
b |> run("m3f_old_array_fold/{n}", n) {
let row = _old_fold(each(arr)._where(_.price > THRESHOLD).first_or_default(sentinel))
if (row.id == SENTINEL_ID) {
b->failNow()
}
}
}

def run_m3f(b : B?; n : int) {
let arr <- fixture_array(n)
let sentinel = Car(id = SENTINEL_ID, name = "none", price = 0, brand = 0, year = 0, dealer_id = 0)
b |> run("m3f_array_fold/{n}", n) {
let row = _fold(each(arr)._where(_.price > THRESHOLD).first_or_default(sentinel))
if (row.id == SENTINEL_ID) {
b->failNow()
}
}
}

[benchmark]
def first_or_default_match_m1(b : B?) {
run_m1(b, 100000)
}

[benchmark]
def first_or_default_match_m3(b : B?) {
run_m3(b, 100000)
}

[benchmark]
def first_or_default_match_m3f_old(b : B?) {
run_m3f_old(b, 100000)
}

[benchmark]
def first_or_default_match_m3f(b : B?) {
run_m3f(b, 100000)
}
72 changes: 72 additions & 0 deletions benchmarks/sql/long_count_aggregate.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
options gen2
options persistent_heap

require _common public

let THRESHOLD = 500

// long_count mirrors count but with int64 accumulator semantics — same access pattern,
// same fusion shape, just a wider counter. The 4-way comparison validates that the
// accumulator-lane planner matches counter-lane perf for the int64 variant.

def run_m1(b : B?; n : int) {
with_sqlite(":memory:") $(db) {
fixture_db(db, n)
b |> run("m1_sql/{n}", n) {
let c = _sql(db |> select_from(type<Car>) |> _where(_.price > THRESHOLD) |> count())
if (c == 0) {
b->failNow()
}
}
}
}

def run_m3(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3_array/{n}", n) {
let c = arr |> _where(_.price > THRESHOLD) |> long_count()
if (c == 0l) {
b->failNow()
}
}
}

def run_m3f_old(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3f_old_array_fold/{n}", n) {
let c = _old_fold(each(arr)._where(_.price > THRESHOLD).long_count())
if (c == 0l) {
b->failNow()
}
}
}

def run_m3f(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3f_array_fold/{n}", n) {
let c = _fold(each(arr)._where(_.price > THRESHOLD).long_count())
if (c == 0l) {
b->failNow()
}
}
}

[benchmark]
def long_count_aggregate_m1(b : B?) {
run_m1(b, 100000)
}

[benchmark]
def long_count_aggregate_m3(b : B?) {
run_m3(b, 100000)
}

[benchmark]
def long_count_aggregate_m3f_old(b : B?) {
run_m3f_old(b, 100000)
}

[benchmark]
def long_count_aggregate_m3f(b : B?) {
run_m3f(b, 100000)
}
56 changes: 56 additions & 0 deletions benchmarks/sql/take_count_filtered.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
options gen2
options persistent_heap

require _common public

let TAKE_N = 1000
let THRESHOLD = 500

// `_sql` rejects `take(n) |> count()` (LIMIT-before-aggregate collapses to one row regardless
// in SQLite), so the m1 variant is omitted. m3/m3f_old/m3f filter + bound + count over the
// array. Exercises counter-lane take splice with an upstream `where` predicate.

def run_m3(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3_array/{n}", n) {
let c = arr |> _where(_.price > THRESHOLD) |> take(TAKE_N) |> count()
if (c == 0) {
b->failNow()
}
}
}

def run_m3f_old(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3f_old_array_fold/{n}", n) {
let c = _old_fold(each(arr)._where(_.price > THRESHOLD).take(TAKE_N).count())
if (c == 0) {
b->failNow()
}
}
}

def run_m3f(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3f_array_fold/{n}", n) {
let c = _fold(each(arr)._where(_.price > THRESHOLD).take(TAKE_N).count())
if (c == 0) {
b->failNow()
}
}
}

[benchmark]
def take_count_filtered_m3(b : B?) {
run_m3(b, 100000)
}

[benchmark]
def take_count_filtered_m3f_old(b : B?) {
run_m3f_old(b, 100000)
}

[benchmark]
def take_count_filtered_m3f(b : B?) {
run_m3f(b, 100000)
}
Loading
Loading