Skip to content

[pull] master from GaijinEntertainment:master#1029

Merged
pull[bot] merged 30 commits into
forksnd:masterfrom
GaijinEntertainment:master
May 24, 2026
Merged

[pull] master from GaijinEntertainment:master#1029
pull[bot] merged 30 commits into
forksnd:masterfrom
GaijinEntertainment:master

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented May 24, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

borisbat and others added 30 commits May 23, 2026 13:42
Widen MSVC-only gates to Windows-wide where the code is actually
general Windows API, not MSVC-specific. clang-mingw64 (clang 22.1.4 +
lld + libc++) now builds a working daslang.exe end-to-end.

Most changes are mechanical `_MSC_VER` -> `_WIN32` for code that uses
plain `<windows.h>` API. The structural ones:

- CMakeLists.txt: `/wd4251 /wd4661 /wd4275` were gated on `if(WIN32)`
  (MSVC-only flags; clang reads them as filenames). Narrowed to
  `if(MSVC)`. Else-branch already does the right thing on non-MSVC
  Windows (PIC is a no-op there per CMakeLists.txt:106-109).

- CMakeLists.txt: source files reference `ws2_32`, `mswsock`,
  `advapi32`, `rpcrt4` via `#pragma comment(lib,...)`. MSVC honors
  those; ld.lld in mingw mode does not always pick up .drectve
  defaultlib directives. List them explicitly. Harmless on MSVC.

- platform.h DAS_API family: the MSVC-only gate left clang-mingw with
  `__attribute__((visibility("default")))` -- ELF visibility, no-op
  on PE/COFF. lld then defaulted to "export every global" and
  overflowed the PE 16-bit ordinal table (108277 > 65535). Widening
  the `__declspec(dllexport)` gate to `_WIN32` makes lld switch to
  "export only tagged symbols", as intended on every Windows toolchain.

- platform.h `_aligned_*` allocator: widened gate so clang-mingw uses
  `_aligned_malloc`/`_aligned_free`/`_aligned_msize` (the MS CRT
  family mingw-w64 also ships) instead of POSIX `posix_memalign` /
  `malloc_usable_size`. Also includes `<malloc.h>` on Windows.

- aot_builtin_fio.h: tripartite include split. `<io.h>`/`<direct.h>`
  on every Windows toolchain; `<dirent.h>`/`<unistd.h>` on non-MSVC
  (POSIX shims mingw-w64 ships); `<libgen.h>`/`<sys/mman.h>` on
  non-Windows only.

- project_specific_crash_handler.cpp: kept `__try`/`__except` SEH
  path under `_MSC_VER` (covers MSVC + clang-cl, which defines
  `_MSC_VER`). Added a `_WIN32` branch for clang-mingw / gcc-mingw
  using `VirtualQuery` to probe page protection -- safe to call from
  a signal handler, no SEH needed.

- daScriptModule.h: `register_builtin_modules` is defined in
  `src/builtin/modules.cpp` which ships in the compiler lib
  (`DAS_CC_EXPORTS`), not the runtime lib (`DAS_EXPORTS`). Declaration
  was tagged `DAS_API` instead of `DAS_CC_API`. MSVC tolerates the
  mismatch; clang-mingw drops the dllimport tag and the symbol stays
  unexported.

- sysos.cpp, performance_time.cpp, debug_break.cpp,
  module_builtin_fio.cpp: ~20 individual `_MSC_VER` -> `_WIN32`
  widenings for code calling plain Win32 API. The two
  `#pragma warning(push/pop)` blocks in module_builtin_fio.cpp stay
  under `_MSC_VER` (genuinely MSVC-only directive).

No CI coverage for mingw, so this is best-effort tested via:
  bin/daslang.exe _smoke.das
which loads, parses gen2, compiles, executes hello + arithmetic +
for-loop -- all clean.
Two fixes:

- dasGLFW.main.cpp: `_MSC_VER` -> `_WIN32` so clang-mingw / gcc-mingw
  reach the Win32-native-handle path (`glfwGetWin32Window`) instead of
  the `#else` fallback. Also added the missing
  `DAS_glfwGetNativeDisplay` stub to the fallback branch -- existing
  latent bug that would link-fail on any platform that hits `#else`
  (Haiku, future targets).

- CMakeLists.txt: GLFW v3.4 produces different import-lib filenames
  on mingw than on MSVC. Split the Windows branch:
    MSVC : lib/glfw3.lib (static), lib/glfw3dll.lib (import lib)
    mingw: lib/libglfw3.a (static), lib/libglfw3dll.a (import lib)
  The runtime .dll itself is named glfw3.dll on both, so the install
  line stays unchanged.

Runtime caveat (NOT fixed in this commit): the mingw build places
glfw3.dll under bin/Release/ rather than directly in bin/. Loading
dasModuleGlfw.shared_module via LoadLibraryEx then fails because
glfw3.dll is not in any of the searched directories
(SEARCH_DLL_LOAD_DIR / SEARCH_APPLICATION_DIR / SEARCH_DEFAULT_DIRS).
Workaround: manually copy glfw3.dll into bin/ alongside daslang.exe,
or next to modules/dasGlfw/dasModuleGlfw.shared_module. A POST_BUILD
copy step is the right long-term fix.
Three Windows-branch fixes:

- Split WIN32 into MSVC vs mingw. The MSVC path stays as-is (builds
  OpenSSL from source via `perl Configure VC-WIN64A` + nmake, which
  only work with MSVC). The new mingw path uses the system OpenSSL
  shipped by mingw-w64-clang-x86_64-openssl (or the gcc-mingw64
  equivalent).

- Auto-derive OPENSSL_ROOT_DIR from the C compiler path on mingw.
  FindOpenSSL.cmake on Windows doesn't look in the mingw sysroot by
  default, so even though clang64 ships libcrypto.dll.a + libssl.dll.a,
  find_package would find the headers (sees the version) but fail
  to locate the libraries. Setting OPENSSL_ROOT_DIR to <SYSROOT>
  (derived from <SYSROOT>/bin/clang.exe) makes it find both.

- Use libhv_static.a (not libhv.a) on mingw and link winmm
  explicitly. On POSIX libhv renames hv_static's OUTPUT_NAME to "hv"
  (-> libhv.a); on mingw it keeps the original (-> libhv_static.a).
  libhv calls timeSetEvent/timeKillEvent from winmm.dll, which MSVC
  auto-pulls via #pragma comment(lib, "winmm") but ld.lld in mingw
  mode does not honor reliably.

Verified by:
  bin/daslang.exe _smoke_hv.das
  -> "dashv + dashv_boost loaded" / exit 0
loading dashv (the native module) + dashv_boost.das (the daslib
wrapper) confirms the libhv static lib + OpenSSL dynamic libs both
link and load at runtime under clang-mingw64.

Runtime caveat: dasModuleHV.shared_module depends on libssl-3-x64.dll
+ libcrypto-3-x64.dll from clang64. PATH=C:\msys64\clang64\bin
during invocation suffices today; a POST_BUILD copy step (matching
glfw3.dll TODO) would be cleaner.
Two cosmetic / ergonomic cleanups for the clang-mingw64 build, both
of which only fire on Windows-non-MSVC and leave MSVC + POSIX
behavior unchanged.

1. Double-lib prefix on shared and static library outputs.

   Mingw's ADD_LIBRARY auto-prepends "lib" to the output filename.
   When the target name already starts with "lib" -- which is the
   case for every daslang SHARED lib (libDaScript / libDaScriptDyn /
   libDaScript_runtime / libDaScriptDyn_runtime) and every static
   module lib (libDasModuleAudio / libDasModuleGlfw / ...) -- this
   produced ugly disk names like liblibDaScriptDyn.dll and
   liblibDasModuleAudio.a.

   Added STRIP_DOUBLE_LIB_PREFIX_ON_MINGW(${lib}) macro that sets
   PREFIX "" + IMPORT_PREFIX "" when WIN32 AND NOT MSVC AND the
   target name starts with "lib". Wired into both
   SETUP_DAS_SHARED_LIBRARY and ADD_DAS_STATIC_LIBRARY so every
   affected target gets the fix automatically.

   Linux/macOS keep their conventional `lib` prefix (which they
   need to be linkable as -lname).

2. glfw3.dll lands in the wrong directory on mingw.

   The existing Windows POST_BUILD copy used "${EXECUTABLE_OUTPUT_PATH}/$<CONFIG>/glfw3.dll"
   as the destination. That's correct for MSVC multi-config
   (.exe sits in bin/Release/ too), but wrong for mingw single-config
   where daslang.exe sits in bin/ directly -- the .dll was being
   stranded in bin/Release/ and LoadLibraryEx couldn't find it.

   Switched to a configure-time choice keyed off
   CMAKE_CONFIGURATION_TYPES: multi-config keeps the per-config
   subdir; single-config drops it. Avoided $<TARGET_FILE_DIR:daslang>
   even though it would have been cleaner -- that creates a
   dependency cycle because daslang already depends on
   dasModuleGlfw transitively.

Verified end-to-end: after a clean rebuild, bin/ contains
daslang.exe + glfw3.dll + libDaScriptDyn.dll (no double-lib) side
by side, and a script that does `require glfw` loads cleanly with
no manual dll-copy step.
build-mingw/ is the clang-mingw64 out-of-tree build dir established earlier
in this branch; _smoke*.das / _smoke*.cpp / _smoke*.o are ad-hoc repro
scripts kept locally to verify each module. None of them belong in source
control - gitignore them so the working tree stays clean while iterating
on dasLLVM / dasClangBind.
The JIT-to-native-DLL path (daslang -jit, llvm_macro::write_dll) was
gated to MSVC + linux + macOS only. On clang-mingw64 daslang.exe, three
things had to change before the link succeeded:

1. The platform gate around create_shared_library / link_wasm widened
   from `_MSC_VER` to `_WIN32`. Game consoles (xbox/durango) still get
   the stub-return-true; mingw daslang now reaches the real link path.

2. Inside the Windows branch of get_real_lib_linker_paths and
   create_shared_library, split MSVC from mingw:
     MSVC (incl. clang-cl) keeps the existing flow — clang-cl.exe with
     `-DLL`/-link/`-OUT:` syntax, .lib import libs, msvcrt.lib.
     mingw uses clang.exe (mingw-flavored Unix driver) with
     `-shared`/`-o` syntax, .dll.a import libs, no msvcrt.lib reference,
     `-Wl,--no-as-needed` only for the linkWholeLib path. Outer
     `\"...2>&1\"` quote wrap mirrors the MSVC branch — popen
     -> cmd.exe strips one quote pair, inner per-arg quotes survive.
     No rpath: Windows resolves DLLs via PATH + LoadLibrary search order
     so the Linux branch's `-Wl,-rpath,$ORIGIN` escape is dropped.

3. New `host_jit_triple()` C builtin returns the daslang-host toolchain's
   LLVM triple (x86_64-w64-windows-gnu on clang-mingw64; empty elsewhere
   meaning "use LLVMGetDefaultTargetTriple"). daslib/llvm_jit_common.das
   with_default_target_machine now prefers it when non-empty.

The motivation for #3: the prebuilt LLVM.dll shipped via dasLLVM's
FetchContent is built with MSVC clang-cl, so LLVMGetDefaultTargetTriple()
always returns `x86_64-pc-windows-msvc` regardless of what compiled
daslang.exe. On mingw, that triple emits MSVC-ABI .o files referencing
msvcrt marker symbols like `_fltused` that mingw's ld.lld + CRT cannot
satisfy. Forcing `x86_64-w64-windows-gnu` makes LLVM emit a
mingw-flavored .o with no MSVC-ism residue; the link against
libDaScriptDyn_runtime.dll.a then succeeds and the resulting DLL calls
into mingw daslang.exe via the same Win64 C ABI without CRT-mismatch
hazards.

Verified on clang-mingw64 (MSYS2 clang64, Clang 22.1.4):
  - modules/dasLLVM/examples/{hello_jit,hello_llvm_jit,hello_llvm_jit_function}.das
    all run with -jit and exit 0.
  - dastest -jit on tests/jit: 19/19 passed.
  - dastest -jit on tests/algorithm: 162/162 passed.
  - DLL cache hit path (second run skips codegen) also works.

MSVC / linux / macOS paths unchanged in behavior; only the previously
empty mingw branch is filled in.
llvm_jit_common.das has carried a `LLVM_ENABLE_DIAGNOSTIC = false` flag
and a 3-line dead branch since the 2023 LLVM-debug-info-wip commit
(d68fdbb) — the flag is always false, and the function it would call,
`set_context_diagnostics_to_log`, was never actually defined anywhere in
the tree (`Grep` returns only the call site itself).

The lint pass surfaces this as a 30341 unresolved-function error
whenever a PR touches this file (lint compiles each changed .das
standalone). Because no recent PR has edited llvm_jit_common.das,
master CI has not surfaced it — but the clang-mingw64 JIT work in
this branch touches the file and unmasks it.

Removing the dead branch is the minimal fix: no behaviour change
(branch never executed), no debug toggle preserved (the unimplemented
hook was a half-formed feature, not a functional path). If the
diagnostic-logging idea is wanted in the future, it should land
alongside its actual implementation rather than as a stranded call.
Four fixes, one commit:

1. project_specific_crash_handler.cpp safe_read (mingw):
   - reject PAGE_GUARD / PAGE_NOACCESS — the old `mbi.Protect & 0xFF`
     mask dropped the high-byte PAGE_GUARD flag, so guard pages were
     classified readable and reading them would raise
     EXCEPTION_GUARD_PAGE — defeating the entire point of safe_read.
   - validate that [addr, addr+size) is fully covered by readable
     committed memory, requerying for the next region when the read
     spans a boundary. The old code ignored `size` and could fault on
     a 4/8-byte read near end-of-region by crossing into an unmapped
     or PAGE_GUARD page.

2. module_jit.cpp host_jit_triple — derive arch from preprocessor
   macros instead of hardcoding x86_64. clang32 (i686), clang64
   (x86_64), clangarm64 (aarch64), clangarm32 (armv7) all share the
   `-w64-windows-gnu` vendor/os/env tail but differ on arch.
   Hardcoding x86_64 would have made LLVM emit wrong-arch .o files
   on non-x86_64 mingw hosts. Unknown arch falls back to LLVM
   default and the user can still override via customLinker.

3. debug_break.cpp — use WinAPI DebugBreak() on mingw, keep the
   __debugbreak() intrinsic only for MSVC (incl. clang-cl).
   __debugbreak isn't reliably exposed on gcc-mingw without
   <intrin.h> and the right configuration; DebugBreak from kernel32
   is the portable Windows API that works on every Windows compiler
   at the cost of one extra call frame (negligible vs the win of
   mingw-safe builds).

4. module_jit.cpp create_shared_library — drop the fixed
   `char cmd[1024]` stack buffer in favor of fmt::format → string.
   Long paths (deep build roots, spaces, long extraLinkerArgs) can
   plausibly exceed 1024 bytes when combined with the format-string
   constants and the runtime/compiler library paths — the old code
   would silently corrupt the stack. fmt::format allocates on the
   heap so growth is unbounded; matches the pattern link_wasm
   already uses in the same file.

Verified on clang-mingw64 (Clang 22.1.4):
  - daslang rebuilds clean
  - hello_jit.das exit=0, JIT'd add(11,12) returns 23
build_decs_inner_for_pruned previously bailed to the unpruned bind path
whenever the body had a whole-var ref to decs_tup (`decs_lst := decs_tup`,
`push_clone(buf, decs_tup)`, `pred(decs_tup)`, etc) — forcing a per-iter
`var decs_tup = (n1=iter1, ...)` wrap even when slot pruning was infeasible
anyway.

DecsTupWholeRewriter rewrites each whole-var ref into a freshly synthesized
`(userName1=iter1, ...)` literal — exactly what build_decs_tup_bind would
produce, but inlined at each use site. After rewrite the body contains only
`decs_tup.field` refs (flattenable) plus iter-var refs in the synthesized
literals (picked up by the usage scanner). The existing flatten + bind-elision
path then unlocks for the bare-to-array / whole-element-terminator shapes,
saving the per-iteration named-tuple construction.

Per-element body before:
    {
        var decs_tup = (id=car_id, name=car_name, ...);
        if (decs_tup.price > 500) {
            decs_lst = decs_tup
        }
    }

After:
    if (car_price > 500) {
        decs_lst = (id=car_id, name=car_name, price=car_price, ...)
    }

100K-row m4 INTERP bench wins (vs master after PR #2837):
    sort_first         23.9 → 13.4 (Δ-10.5, BEATS m3f 41.3)
    sort_take          31.0 → 20.1 (Δ-10.9, +2 vs m3f 22.7)
    order_take_desc    30.9 → 20.0 (Δ-10.9, +2 vs m3f 22.3)
    select_where_order_take 24.7 → 14.9 (Δ-9.8, BEATS m3f 21.4)
    groupby_first      29.9 → 19.2 (Δ-10.7, +0.6 vs m3f 18.6)
    single_match       14.8 →  5.4 (Δ-9.4,  +2.5 vs m3f 2.9)
    last_match         17.2 → 14.0 (Δ-3.2,  +8.2 residual)
    select_where       23.2 → 18.4 (Δ-4.8,  +7.3 residual)
    bare_order_where  135.6 → 130.8 (Δ-4.8)

10 lanes improved, 0 regressions.

Verification:
- 1390/1390 linq + 245/245 decs + 782/782 dasSQLITE INTERP + AOT green
- 1390/1390 linq JIT green
- test_wave4_unpruned_bare_to_array_splice_shape extended: now asserts
  describe_count(body_expr, "decs_tup") == 0 (bind elided despite the
  push_clone(decs_tup) whole-ref that originally forced unpruned bind)

Closes Session 2 of the m4 close-out roadmap and carries over wins to the
Session 1 (sort/order family) and Session 3 (groupby_first) targets.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…arly-exit-terminator-investigation

linq_fold: elide decs_tup bind via whole-ref → make-tuple rewriter
…64-support

Support clang-mingw64 toolchain (core + 7 modules + JIT)
PR #2838 enabled the clang-mingw64 toolchain on Windows but added no
CI coverage; the mingw path in src/hal/debug_break.cpp,
src/hal/project_specific_crash_handler.cpp, and src/builtin/module_jit.cpp
would silently rot on the next Windows-touching change.

Adds a separate top-level build_windows_mingw job sharing the
windows-latest-fat runner pool with the MSVC matrix. Steps mirror the
MSVC Windows test sequence: build (Ninja Release), interpreter sweep,
JIT sweep, ctest small, test_aot.

dasClangBind stays disabled here (libclang 16.0.6 strict-version pin
unavailable in the msys2 clang64 sysroot); regen against Clang 22 is
tracked separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symmetric mirror of PR #2837's plan_decs_order_family fix, applied to
the array-side splice. When the key is inline-able, `_order_by + first`
splices to a per-walk streaming-min state (single `best` + `seen` flag)
instead of dispatching through `min_by(arr, keyLambda)`. `_order_by + take(N)`
splices to a bounded-heap walk instead of materializing then `top_n_by`.

Two new emit branches in plan_order_family:

**Streaming-min** (`firstName != "" && inlineCmp != null`):
    invoke($(source : srcParamType) : elemType {
        var order_best = default<elemType>
        var order_seen = false
        for (it in source) {
            if (!order_seen) { order_best := it; order_seen = true }
            elif (LESS(it, order_best)) { order_best := it }
        }
        panic-if-not-seen (first) OR return-default-if-not-seen (first_or_default)
        return order_best
    }, top)

Avoids the per-element `invoke(keyLambda, x)` cost in min_by_impl's
`key(x)` calls.

**Bounded-heap** (`takeExpr != null && inlineCmp != null && firstName == ""`):
    invoke($(source : srcParamType) : array<elemType> {
        let n = takeExpr
        var order_buf : array<elemType>
        return <- order_buf if (n <= 0)
        for (it in source) {
            if (length(order_buf) < n) {
                order_buf |> push_clone(it)
                spliced_push_heap(order_buf, cmp)
            } elif (LESS(it, order_buf[0])) {
                spliced_pop_heap(order_buf, cmp)
                order_buf[len-1] := it
                spliced_push_heap(order_buf, cmp)
            }
        }
        order_inplace(order_buf, cmp)
        return <- order_buf
    }, top)

Avoids materializing the full filtered set in a prefilter buffer before
top_n_by. Both branches handle where_/no-where uniformly via optional
`if (whereCond)` wrap around perElement, like the decs equivalent.

## Latent bug fix in make_inline_less_call

PR #2837's make_inline_less_call had a latent bug: when the key body is
ExprVar at the root (e.g. `_order_by(_)` — identity key body == lambda
arg), `apply_template`'s visitor would substitute the root via
visitExprVar's return value, but the helper ignored the return value of
apply_template — leaving the caller's `b1`/`b2` pointing at the un-substituted
clone. The bug never surfaced in #2837 because every decs test had a
field-access key (`_.price`), where substitution happens on the
ExprField's child, not the root.

Surfaced here when the array-side fix exposed it via the broad set of
`_order_by(_)` tests in test_linq_fold.das. Fix: capture and reassign
`b1 = apply_template(r1, b1.at, b1)`.

## Bench (100K rows, INTERP, m3f deltas)

    sort_first_m3f              41.1 → 11.0  (-30.1, was 27.8 SLOWER than m4)
    select_where_order_take_m3f 21.4 → 12.1  (-9.3, now BEATS m4 by 2.8)
    sort_take_m3f               22.4 → 15.9  (-6.5)
    order_take_desc_m3f         22.4 → 16.0  (-6.4)

All four m3f order-family lanes were slower than m4 by 2-28 ns/op before
this PR; now m3f and m4 are within ±5 ns/op across the board (m4 still
slightly faster on take by the archetype/streaming-walk advantage, m3f
slightly faster on first thanks to direct array indexing).

## Verification

- 1390/1390 linq + 245/245 decs + 782/782 dasSQLITE INTERP + AOT + JIT green
- 7 AST shape tests updated: top_n_by_with_cmp / min_by / max_by → streaming-min
  (less-call) / bounded-heap (spliced_push_heap + spliced_pop_heap + order_inplace)
- CI-style lint clean on both touched files

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rder-family-streaming-min-port

linq_fold: port streaming-min / bounded-heap to plan_order_family
dasModuleHV.shared_module on mingw transitively depends on
libssl-3-x64.dll + libcrypto-3-x64.dll from msys2's clang64 sysroot.
When daslang.exe runs from a cmd.exe context whose %PATH% does not
include <msys64>/clang64/bin (e.g. ninja-spawned cmd.exe during
build-time AOT codegen), LoadLibrary cannot resolve these DLLs and
the .das_module initializer's register_dynamic_module call silently
fails — "require dashv" then surfaces as 20605 missing-prerequisite.

Mirrors the glfw3.dll POST_BUILD pattern from #2838 task #15: glob
the openssl DLLs and copy them next to daslang.exe (under
EXECUTABLE_OUTPUT_PATH). The glob handles version suffix bumps
without CMake churn (libssl-3-x64.dll today, libssl-4-x64.dll in a
future openssl major). MSVC has its own openssl build path
(no-shared above) so the copy is gated on NOT MSVC.

Also: fix CI workflow flag name DAS_CLANGBIND_DISABLED ->
DAS_CLANG_BIND_DISABLED. The typo'd flag was a no-op since
dasClangBind defaults to ON in the option declaration; functional
impact zero but the flag now actually drives the option.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DAS_LLVM_DISABLED defaults to ON in the CMakeLists.txt option. Without
an explicit -DDAS_LLVM_DISABLED=OFF, dasLLVM was being skipped, LLVM.dll
never got fetched via FetchContent, and the JIT sweep then failed with
'can't load library LLVM.dll' for every dasLLVM/bindings/llvm_func.das
[extern] declaration. Mirror the MSVC matrix entries that pass
-DDAS_LLVM_DISABLED=${{ env.das_llvm_disabled }} (where env value is OFF).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The flag was carried over from the linux branch's create_shared_library
in PR #2838 but has no effect on PE/COFF: Windows linkers always record
every listed library in the import table — there is no lazy-bind /
DT_NEEDED concept to suppress. Worse, ld.lld on newer mingw sysroots
(msys2 clang64 with clang 22.1.4 as of CI provisioning) rejects
--no-as-needed as an unknown argument, breaking every daslang.exe -exe
invocation that links a compilerLibrary (dascov, dastest, daspkg, mcp,
aot, detect-dupe). Older clangs / GNU ld silently ignored the flag,
which is why this only surfaced when the Windows mingw CI worker came
online (PR #2840) and not during PR #2838's local validation.

Behavior on Boris's local mingw is unchanged: the linker was already
linking the compilerLibrary unconditionally; --no-as-needed was a
no-op. Verified by rebuilding daslang.exe and running `daslang.exe
-exe -output ... utils/dascov/main.das` — exe links successfully.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The WASM build step has been silently failing when emscripten / clang
hiccups: continue-on-error: true paints the step green, the staging
step's file-exists guard skips the real /playground/ copy, and we ship
site/playground/placeholder.html ("Runtime rebuild in progress.").
Master shows green; the live playground is the fallback page.

Most recent example: pages run 26345453541 (commit 1c8ff91, 2026-05-23
22:40) — em++ bus-errored in clang::Lexer::SkipBlockComment while
compiling utils/dasFormatter/ds_parser.cpp:6484. Four prior runs today
on the same emsdk pin (5.0.3, resolved to LLVM commit e5927fec from
2026-03-13) built ds_parser.cpp fine, so this is an intermittent
preprocessor flake in clang 23.0.0git rather than a deterministic bug.
We want the deploy to fail red so the next flake is visible immediately
rather than re-running quietly with the placeholder for hours.

Also retire the misleading "5.0.7 crashes, 5.0.3 known-good" pin
comment — 5.0.3 itself just crashed, and the pin is really about staying
close enough to the LLVM main snapshot family that the rest of the code
compiles. Note the family characteristic (clang 23.0.0git preview) and
the next-downgrade target (4.0.23, pre-LLVM-22.1.0 main snapshot) for
future Claude / human triage.

The placeholder-staging else-branch survives as defense-in-depth: still
useful if a future workflow change re-introduces continue-on-error or
the artifacts go missing despite a green build, since
actions/deploy-pages publishes _site as a complete snapshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trims each bench to three lanes (SQL / Array / Decs == m1 / m3f / m4) by
dropping the m3 eager-linq lane that the splice ladder rendered
redundant; also drops sort_take's legacy m3_topn_array / m3_topn_iter
algorithm-comparison wrappers.

Back-fills the SQL lane for join_count (sqlite_linq's _join+_select
accepts the chain when the projection is a named tuple — the existing
"db handle wiring" comment was wrong about the real blocker, which was
the positional-tuple shape). Refreshes the 5 keep-out comments
(distinct_by_count, groupby_first, groupby_select_sum,
take_count_filtered, take_sum_aggregate) with dated TODO markers
quoting the actual sqlite_linq error each chain produces, and the 2
by-design absences (take_*_aggregate LIMIT-on-aggregate semantics,
zip_dot_product no relational analog). The join_count decs (m4) lane
stays absent with a TODO referencing plan_join in linq_fold.das.

Adds benchmarks/sql/results.md with both INTERP and JIT tables
(generated 2026-05-23 from master 1c8ff91) plus per-gap footnotes
that quote each ".das" source comment verbatim so the file is the
single source of truth for "why is this cell empty." Updates
benchmarks/README.md sql/ section with a per-file table.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds tutorials/language/55_linq_decs.das (runnable, self-contained
[export] def main) covering from_decs_template, the _fold splice over
decs entities, and three canonical chain shapes (where+count,
order_by+first streaming-min, group_by+select). Paired RST at
doc/source/reference/tutorials/55_linq_decs.rst with the standard
label / seealso / download blocks; added to the language toctree
after 54_glob.

Adds doc/source/reference/linq_fold_patterns.rst — a lookup-oriented
catalog of every chain shape that _fold recognizes, with the splice
arm (plan_*/emit_* in daslib/linq_fold.das) each one fires. Organized
by source type (array vs decs vs zip) plus a "what falls back" tail
listing the chains that drop to fold_linq_default. Linked from the
reference toctree (doc/source/reference/index.rst).

Links the new patterns page from the linq_fold module docs by adding
a "See also" block to doc/source/stdlib/handmade/module-linq_fold.rst
(the handmade source that feeds the generated linq_fold.rst).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
to_cpp_float was using "{:e}f" which defaults to 6 digits after the
decimal point — 7 significant figures total. IEEE 754 single requires
FLT_DECIMAL_DIG=9 significant digits to guarantee a text→float→text→float
roundtrip is bit-identical. With only 7 digits, AOT-emitted float
literals can parse back to a value 1 ULP off from the runtime-computed
constant, shifting boundary results in the AOT'd C++ relative to the
interpreter/JIT.

Surfaced during PR #2840 diagnosis: tracing a tests/language/
random_numbers.das AOT failure (which turned out to have a separate
root cause — see the -fwrapv commit) revealed that 1.0f/32767.0f was
being serialized as "3.051851e-05f" instead of "3.05185094e-05f".
The roundtrip happened to land on the same float here, but the
precision shortfall is real and worth fixing standalone.

to_cpp_double already uses "{:.17e}" (DBL_DECIMAL_DIG=17), so this
brings the float emitter in line with its double counterpart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
daslang's signed integer arithmetic is defined to wrap on overflow,
but AOT lowers it to plain C++ int*int / int+int which is UB at the
C++ level. Modern clangs exploit this UB aggressively. CMakeCommon.txt
already worked around it for AppleClang 21+; the same exploit now
fires under mingw clang 22.1.4 -O2, surfaced by PR #2840's new Windows
mingw CI worker:

  daslib/random.das random_int:
    seed.x = 214013 * seed.x + 2531011   // signed overflow
    return (seed.x >> 16) & LCG_RAND_MAX  // mathematically <= 32767

With -O2 + UB exploitation, the optimizer drops the trailing `& 0x7FFF`
mask reasoning and returns values up to 65535, so float(r) * (1.0/32767)
exceeds 1.0 and tests/language/random_numbers.das's "f < 1.0" assertion
flips. Confirmed locally: -fwrapv added → 13/13 pass, full AOT sweep
(tests/) green at 8620/8626.

Promotes the existing Apple-scoped ADD_COMPILE_OPTIONS(-fwrapv) to a
unified gate near the top of SETUP_COMPILER, applied to GNU, Clang
(non-MSVC frontend), and AppleClang. clang-cl and MSVC are excluded —
clang-cl rejects the flag, MSVC defaults to wrap-around in practice
and doesn't have the equivalent option. Tiny perf cost: x86 already
wraps in hardware, the flag only prevents the optimizer from assuming
no overflow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop `from_decs` (runtime-list form) from the 55_linq_decs tutorial
  header bullets and RST index — tutorial body only demonstrates
  `from_decs_template`; mention `from_decs` in the RST intro as
  explicitly out of scope.
- Fix benchmarks/README.md sql/ lane table: the Decs lane uses
  `from_decs_template(type<DecsCar>)`, not `type<Car>` — `DecsCar` is
  the `[decs_template]` fixture in `_common.das`.
- Rewrite linq_fold_patterns.rst dispatch-order section to match the
  actual `LinqFold.visit` sequence (12 steps, decs/array variants
  interleaved per source-type-first matching), instead of the abstract
  "Decs source → Zip → Array" grouping that didn't match the code.
- linq_fold_patterns.rst Array-source `count` row: replace
  "Per-archetype counter" (decs terminology) with "Single counter, no
  allocation; one pass over the array."
- benchmarks/sql/results.md: switch the embedded `_sql` error
  messages from single-backtick inline code (which can't contain
  backticks) to fenced code blocks. GitHub markdown rendering now
  shows the error text verbatim.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-pin-and-fail-loud

pages.yml: drop continue-on-error on WASM build step
…gw-ci-job

ci: add Windows clang-mingw64 worker
…leanup-tutorials-patterns-doc

benchmarks/sql cleanup + linq_decs tutorial + linq-fold patterns reference
5.0.3 ships clang 23.0.0git (LLVM main snapshot from 2026-03-13), and
that preview compiler has now failed on utils/dasFormatter/ds_parser.cpp
two separate ways on the same runner SHA:

  • Pages run 26345453541 (2026-05-23 22:40, master 1c8ff91):
    em++ bus-errored in clang::Lexer::SkipBlockComment at line 6484,
    code=135 / SIGBUS.

  • Pages run 26348244284 (2026-05-24 01:11, master bfb04c7):
    em++ rejected ds_parser.hpp with "unterminated conditional
    directive" at line 38 (YY_DAS_YY_DS_PARSER_HPP_INCLUDED guard) and
    line 102 (DAS_YYTOKENTYPE) — both have matching #endif on disk.
    Native clang in step 1 of the SAME run compiled the same file
    fine 10 minutes earlier, so the file isn't corrupt — clang-23's
    preprocessor is.

Since #2842 dropped continue-on-error: true on the WASM step, master's
pages deploy is now red. The live playground is whatever the last green
deploy staged (the placeholder page) and will stay that way until the
WASM build succeeds again.

4.0.23 is the last 4.x release before the 5.0.x family cutover
(2026-01-10), pinning LLVM main from 2026-01-09 — pre-LLVM-22.1.0-
release, roughly clang 22.0.0git. That's months older than the preview
clang 23 snapshot 5.0.3 was on, so it doesn't carry the preprocessor
regression, and it's the closest emsdk we have to dasLLVM's 22.1.5 pin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two new chain shapes lower in `_sql(...)`:

- `|> long_count()` → `SELECT COUNT(*)` with an int64 column binding,
  symmetric with `|> count()` (int). Also recognised in the grouped
  projection arm so `_select((..., N = _._1 |> long_count()))` widens
  the per-group counter to int64.
- `|> _distinct_by(_.col) |> count()` (and the long_count variant) →
  `SELECT COUNT(DISTINCT "col")`. Receiver-pinned to ExprVar so
  `u.opt.X` keys reject loudly instead of silently emitting the wrong
  COUNT.

`peel_count_terminal` factors the shared count/long_count peel so both
share the distinct-by recognition path.

Test surface:
- `tests/dasSQLITE/test_31_long_count_distinct_by_canary.das` — 8 new
  tests covering SQL text emission + runtime values for both terminals,
  plus the int64-grouped projection.
- `failed_sql_macro.das` case 23 — computed `_distinct_by` key (e.g.
  `_.Price % 100`) rejects with a precise diagnostic.

Bench corpus:
- `benchmarks/sql/distinct_by_count.das` — m1 lane backfilled
  (`_sql(... |> _distinct_by(_.brand) |> count())`); the dated TODO
  comment is removed.
- `benchmarks/sql/long_count_aggregate.das` — m1 lane now actually
  calls `long_count()` instead of `count()`; the int64 accept matches
  m3f/m4. Latent bench-bug fixed.
- `benchmarks/sql/results.md` — refreshed INTERP + JIT tables; commit
  header bumped; the `distinct_by_count SQL` bullet in "Notes on
  missing lanes" is removed now that the lane has a number.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-downgrade-4-0-23

pages.yml: downgrade emsdk pin 5.0.3 → 4.0.23
…-distinct-by-count-and-long-count

sqlite_linq: add long_count + _distinct_by dispatch arms
@pull pull Bot locked and limited conversation to collaborators May 24, 2026
@pull pull Bot added the ⤵️ pull label May 24, 2026
@pull pull Bot merged commit 31e4339 into forksnd:master May 24, 2026
@pull pull Bot had a problem deploying to github-pages May 24, 2026 02:58 Error
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant