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
22 changes: 22 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,28 @@ a focused subset with:
./rayforce.test -f <substring>
```

A `.rfl` file that needs a POSIX shell or filesystem (fixtures built or
checked through `.sys.exec`, `/proc`, `/dev/tcp`, …) carries the line
`;; @requires: posix`; on Windows the runner reports it as `SKIP` rather
than running it. C tests use `#ifndef RAY_OS_WINDOWS` / `SKIP(...)` for the
same purpose. Prefer the shell-free helpers `ray_test_rm_rf` /
`ray_test_mkdir_p` (`test/test.h`) over `system("rm -rf …")` in C tests.

### Windows

Build with the MSYS2 CLANG64 (or MINGW64) toolchain — `pacman -S
mingw-w64-clang-x86_64-clang make` — from an MSYS2 shell or with
`C:\msys64\clang64\bin` and `C:\msys64\usr\bin` on `PATH`:

```sh
make # debug build (ASan + UBSan)
make test
make release
```

The debug binaries load the ASan runtime DLL from `clang64\bin`, so keep it on
`PATH` when running them.

## Stability tooling

Beyond the ASan/UBSan test run, the repo carries a stability toolset. These
Expand Down
20 changes: 19 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,25 @@ COVERAGE_CFLAGS = -fPIC $(WARNS) -std=$(STD) -g -O0 -march=$(RAY_MARCH) -DDEBUG
-fno-omit-frame-pointer -fprofile-instr-generate -fcoverage-mapping
COVERAGE_LDFLAGS = -fprofile-instr-generate -fcoverage-mapping

ifeq ($(UNAME_S),Linux)
# Windows: MSYS2 CLANG64/MINGW64 toolchain (x86_64-w64-windows-gnu). MSYS2's
# own make hides $(OS), so also match `uname -s` (MINGW64_NT-*, CLANG64_NT-*,
# MSYS_NT-*); a native make started from PowerShell/cmd sees OS=Windows_NT.
RAY_WINDOWS := $(if $(filter Windows_NT,$(OS))$(findstring _NT-,$(UNAME_S)),1,)

ifeq ($(RAY_WINDOWS),1)
# 64-bit off_t / struct stat.st_size: MinGW defaults both to 32 bits, which
# silently truncates sizes of files over 2 GiB (stat, lseek, ftruncate).
DEFS += -D_FILE_OFFSET_BITS=64
# --stack: 8 MiB like a Linux main thread (the Windows default is 1 MiB,
# too little for deep DAG/eval recursion). CreateThread(size 0) inherits
# it too, so pool workers get the same. winpthreads (sched_yield,
# clock_gettime) is linked statically so the binary needs no MSYS2 DLL; the
# UCRT it also uses ships with Windows 10+.
LIBS = -lws2_32 -lmswsock -lkernel32 -ladvapi32 \
-Wl,-Bstatic -lpthread -Wl,-Bdynamic \
-Wl,--stack,8388608
RELEASE_LDFLAGS = -Wl,--gc-sections
else ifeq ($(UNAME_S),Linux)
LIBS = -lm -lpthread
RELEASE_LDFLAGS = -Wl,--gc-sections -Wl,--as-needed
else
Expand Down
8 changes: 4 additions & 4 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Each release publishes, in addition to the source:

## Platform support

Linux and macOS binaries are published today. Windows is not build-ready yet
(IOCP backend is a stub, `main.c`/`heap.c` have unguarded POSIX calls, and the
Makefile has no Windows toolchain path); once ported, add a `windows-latest` row
to the `build` matrix in `.github/workflows/release.yml`.
Linux and macOS binaries are published today. Windows builds and passes the
test suite from source with the MSYS2 CLANG64 toolchain (see CONTRIBUTING.md),
but no Windows binary is published yet; to ship one, add a `windows-latest`
row (MSYS2 CLANG64) to the `build` matrix in `.github/workflows/release.yml`.
14 changes: 13 additions & 1 deletion bench/agg_v2/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@
#include <string.h>
#include <time.h>
#include <math.h>
#include <sys/resource.h>
#if defined(_WIN32)
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <psapi.h>
#else
# include <sys/resource.h>
#endif

/* ---------- timing ---------- */
static double now_ms(void) {
Expand All @@ -69,12 +75,18 @@ static double vmin(const double* arr, int n) {
return m;
}
static long max_rss_kb(void) {
#if defined(_WIN32)
PROCESS_MEMORY_COUNTERS pmc;
if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) return 0;
return (long)(pmc.PeakWorkingSetSize / 1024);
#else
struct rusage ru; getrusage(RUSAGE_SELF, &ru);
#if defined(__APPLE__)
return ru.ru_maxrss / 1024;
#else
return ru.ru_maxrss;
#endif
#endif
}

/* ---------- deterministic PRNG (splitmix64) ---------- */
Expand Down
14 changes: 13 additions & 1 deletion bench/alloc/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
#include <stdatomic.h>
#include <time.h>
#include <pthread.h>
#include <sys/resource.h>
#if defined(_WIN32)
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <psapi.h>
#else
# include <sys/resource.h>
#endif

static double now_s(void) {
struct timespec ts;
Expand Down Expand Up @@ -66,13 +72,19 @@ static void* consumer(void* _) {
}

static long max_rss_kb(void) {
#if defined(_WIN32)
PROCESS_MEMORY_COUNTERS pmc;
if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) return 0;
return (long)(pmc.PeakWorkingSetSize / 1024);
#else
struct rusage ru; getrusage(RUSAGE_SELF, &ru);
/* Linux: ru_maxrss is KB; macOS: bytes. Normalize to KB. */
#if defined(__APPLE__)
return ru.ru_maxrss / 1024;
#else
return ru.ru_maxrss;
#endif
#endif
}

int main(void) {
Expand Down
92 changes: 92 additions & 0 deletions bench/bottleneck/windows_vs_linux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Windows vs Linux sanity check

Not a performance study: a coarse check that the Windows port lands in the
same ballpark as Linux, run while porting (`serhii/windows-port`). The two
sides do not share a compiler, an allocator-visible kernel, or a filesystem,
so only order-of-magnitude gaps are meaningful here.

## Environment

**CPU**: 11th Gen Intel Core i7 (8 logical cores) — one laptop, both runs
**Windows**: Windows 11, clang 20.1.8 (MSYS2 CLANG64), 32 GiB
**Linux**: WSL2 (kernel 6.6.87.2-microsoft-standard-WSL2, Ubuntu 24.04), gcc 13.3.0, 15 GiB to the VM
**Build**: `make release` both sides (`-O3 -march=native`, no sanitizers — `nm bench-alloc | grep -ci asan` → 0)

WSL2 is a virtual machine with its own memory budget and a virtualised
filesystem; that alone moves I/O and page-fault numbers. Treat the file-backed
rows as indicative only.

## Allocator micro-benchmark (`bench/alloc`)

| case | Windows | Linux |
|------|---------|-------|
| atom-64B | 85.7 Mops/s | 80.9 Mops/s |
| vec-256B | 85.5 Mops/s | 78.6 Mops/s |
| morsel-8K | 88.2 Mops/s | 80.3 Mops/s |
| morsel-16K | 88.4 Mops/s | 78.2 Mops/s |
| large-1M | 62.7 Mops/s | 56.4 Mops/s |
| producer-consumer | 9.2 Mops/s, peak RSS 28 MB | 6.0 Mops/s, peak RSS 68 MB |

## Engine operations (5M rows, `timeit`, median of 3)

| operation | Windows (ms) | Linux (ms) | Win/Lin |
|-----------|-------------:|-----------:|--------:|
| arith-f64 (`sum (* f 1.5)`) | 7.94 | 5.10 | 1.56 |
| sort-i64 | 9.89 | 8.78 | 1.13 |
| distinct-i64 | 2.96 | 4.69 | 0.63 |
| group-by sym | 3.44 | 3.93 | 0.88 |
| select where | 7.13 | 14.14 | 0.50 |
| inner-join | 108.6 | 108.3 | 1.00 |
| csv write (5M rows) | 2201 | 1654 | 1.33 |
| csv read | 155 | 186 | 0.83 |
| splayed set | 509 | 580 | 0.88 |
| splayed get + count | 5.9 | 0.28 | 21 |

`sum`/`avg` are omitted: both platforms report ~0.005 ms, the DAG elides them.

## Reading

Compute paths agree within a factor of ~1.6 either way, which is compiler and
noise territory, and the allocator is slightly ahead on Windows.

The one real gap is `splayed get + count` (5.9 ms vs 0.28 ms). It opens and
maps one file per column, so it measures file-open cost, not the engine:
`CreateFileA` + `CreateFileMapping` + `MapViewOfFile` per column, plus
whatever on-access scanning is installed. The absolute cost is small and it is
paid per table open, but a wide table opened in a loop would feel it.

## The benches recent PRs shipped

Same binaries, built from the release objects on each side.

**`bench/join_nullfree` (#598, null-free key fast path).** The optimisation
engages on Windows — the `nullfree` counter advances on the null-free cases
and stays put on the nullable one, as on Linux.

| case | Windows median (baseline → fast) | Linux median |
|------|---------------------------------|--------------|
| SYM2 | 355.3 → 336.0 ms (-5.4%) | 413.6 → 408.4 ms (-1.3%) |
| SYM2-NULL (must not fire) | 348.2 → 349.8 ms (+0.5%) | 407.1 → 412.9 ms (+1.4%) |
| I64 | 102.7 → 89.2 ms (-13.2%) | 100.9 → 96.3 ms (-4.6%) |

**`bench/join_dup` (duplicate-key fallback).** The pathological case is fixed
on both: CATASTROPHIC-INNER post-fix ~170 ms on Windows and ~230 ms on Linux,
against ~2.6 s pre-fix (Windows) — the same order-of-magnitude win.

**`bench/join_buildside` (build-side swap).** The swap fires on Windows and
pays off by the same factor: MANY-TO-MANY 207 ms swapped vs 494 ms legacy
(2.4x); Linux 188 vs 431 (2.3x). HEAVY-DUP-WIN: 1.7 s vs 6.2 s (Windows),
1.8 s vs 8.2 s (Linux).

**`bench/idx_route` Q3** (1000 lookups/rep): indexed 0.014 ms/batch on
Windows, 0.009 on Linux; the unindexed control is 0.004 on both.

Not runnable as-is:

- `bench/group_pushdown` and `bench/agg_v2` no longer compile **on either
platform** — they use `ray_op.inputs` and `ray_group2/3`, which the engine
no longer has. Pre-existing, unrelated to the port.
- `bench/groupby_shapes/*.py` needs python3, which a stock Windows lacks; the
`.rfl` cases in that directory run directly under `rayforce` on both.
- `scripts/soak.sh` and `scripts/fuzz-seed-*.sh` are bash and stay POSIX-only
(the fuzzing runtime is Linux-only anyway, see the Makefile).
2 changes: 1 addition & 1 deletion docs/docs/getting-started/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ The Rayfall REPL provides an interactive environment with syntax highlighting, b
./rayforce
```

You will see the `‣` prompt (a green triangle bullet):
You will see the `‣` prompt (a green triangle bullet; `►` on Windows, whose console fonts have no `‣`):

```text
Expand Down
4 changes: 0 additions & 4 deletions docs/docs/guides/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,6 @@ total-mem | 16777216000
| `page-size` | OS page size in bytes |
| `total-mem` | Total physical RAM in bytes |

!!! note "Note"

On Windows, only `cores` is currently reported.

## 5. Progress Monitoring

Long-running queries display a progress bar automatically in the REPL. The bar appears after approximately 2 seconds of execution and shows real-time feedback.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Embeddable columnar analytics and graph traversal engine in pure C.

Rayforce combines morsel-driven vectorized execution, a multi-pass query optimizer, and a native CSR graph engine in a single pipeline. It is queried through the **Rayfall** language, exposes a C API for embedding, and runs on Linux and macOS (Windows is planned — the IOCP backend is still a stub).
Rayforce combines morsel-driven vectorized execution, a multi-pass query optimizer, and a native CSR graph engine in a single pipeline. It is queried through the **Rayfall** language, exposes a C API for embedding, and runs on Linux and macOS, and builds on Windows from source (MSYS2 CLANG64).

[Quick Start](getting-started/quick-start.md){ .md-button .md-button--primary }
[Functions Reference](language/functions.md){ .md-button }
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/namespaces/sys.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Signature: `(.sys.build)`. Returns a dict with `version` (string) and `build-dat

## `.sys.info` { #sys-info }

Signature: `(.sys.info)`. Returns `{cores: i64, page-size: i64, total-mem: i64, pid: i64, hostname: str}` on POSIX. On Windows the machine facts fall back to `{cores: 1}` (the sysconf-backed values aren't wired), but `pid` and `hostname` are answered on both platforms.
Signature: `(.sys.info)`. Returns `{cores: i64, page-size: i64, total-mem: i64, pid: i64, hostname: str}` on every platform (on Windows from `GetSystemInfo` / `GlobalMemoryStatusEx`).

```lisp
(.sys.info)
Expand Down
19 changes: 17 additions & 2 deletions src/app/repl.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>

#if defined(RAY_OS_WINDOWS)
#include <io.h>
Expand All @@ -59,7 +60,6 @@
#define STDIN_FD 0
#else
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#define STDIN_FD STDIN_FILENO
#endif
Expand Down Expand Up @@ -353,7 +353,18 @@ static void get_cpu_name(char* buf, size_t sz) {
if (sysctlbyname("machdep.cpu.brand_string", buf, &len, NULL, 0) != 0)
snprintf(buf, sz, "unknown");
#elif defined(RAY_OS_WINDOWS)
snprintf(buf, sz, "unknown");
/* The brand string the firmware reported, same text as /proc/cpuinfo's
* "model name"; it is padded with trailing spaces, so trim them. */
DWORD n = (DWORD)sz;
if (RegGetValueA(HKEY_LOCAL_MACHINE,
"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
"ProcessorNameString", RRF_RT_REG_SZ, NULL,
buf, &n) == ERROR_SUCCESS) {
size_t len = strlen(buf);
while (len > 0 && buf[len - 1] == ' ') buf[--len] = '\0';
} else {
snprintf(buf, sz, "unknown");
}
#else
snprintf(buf, sz, "unknown");
#endif
Expand Down Expand Up @@ -385,7 +396,11 @@ static void print_banner(void) {
char cpu[256];
get_cpu_name(cpu, sizeof(cpu));
int64_t mem_mb = get_total_mem_mb();
#if defined(RAY_OS_WINDOWS)
int ncores = (int)ray_thread_count();
#else
int ncores = (int)sysconf(_SC_NPROCESSORS_ONLN);
#endif

/* "Using" count reflects the actual worker-pool size, not ncores.
* ray_pool_get() is a lazy initializer — callers might not have
Expand Down
19 changes: 17 additions & 2 deletions src/app/term.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,12 @@ typedef struct stat hist_stat_t;
#define RAY_BLOCK_FROM_DATA(ptr) ((ray_t*)((char*)(ptr) - sizeof(ray_t)))

/* Suppress -Wunused-result for terminal I/O writes to stdout. */
#if !defined(RAY_OS_WINDOWS)
#if defined(RAY_OS_WINDOWS)
static inline void term_write(const void* buf, size_t len) {
int r = _write(1, buf, (unsigned)len);
(void)r;
}
#else
static inline void term_write(const void* buf, size_t len) {
ssize_t r = write(STDOUT_FILENO, buf, len);
(void)r;
Expand Down Expand Up @@ -1512,8 +1517,18 @@ int32_t ray_term_count_unmatched(ray_term_t* term) {

/* ===== Prompt ===== */

/* Green ‣ (U+2023) prompt, matching Rayforce style */
/* Green ‣ (U+2023) prompt, matching Rayforce style.
*
* No console font Windows ships has U+2023 — not Consolas, Cascadia Mono,
* Lucida Console or Courier New — and the classic console does no font
* fallback, so the prompt renders as "?" there. Windows uses ► (U+25BA),
* the nearest filled triangle all four of them do have; it is also three
* UTF-8 bytes, so the byte and visual widths below are unchanged. */
#if defined(RAY_OS_WINDOWS)
#define PROMPT_STR "\033[32m\xe2\x96\xba\033[0m "
#else
#define PROMPT_STR "\033[32m\xe2\x80\xa3\033[0m "
#endif
#define PROMPT_LEN 13 /* ESC[32m (5) + ‣ (3) + ESC[0m (4) + space (1) = 13 bytes */
#define PROMPT_VIS 2 /* visual: ‣ + space */
#define CONT_PROMPT_STR "\033[90m\xe2\x80\xa6\033[0m " /* gray … (U+2026) */
Expand Down
5 changes: 4 additions & 1 deletion src/app/term.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@
#ifndef RAY_TERM_H
#define RAY_TERM_H

#include <rayforce.h>
#include "core/platform.h"

#if defined(RAY_OS_WINDOWS)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN /* keep <dlgs.h>/<winsock.h> macros out */
#endif
#include <windows.h>
#define KEYCODE_RETURN '\r'
#else
Expand Down
Loading
Loading