vextoly/cryn
Fix native x86-64 backend: REX.W, SSE prefix ordering, setcc, lock_xchg
Commit b065a7b · niklas · 2026-10-22T19:26:15+02:00
Fix native x86-64 backend: REX.W, SSE prefix ordering, setcc, lock_xchg - Add REX.W to movsx_al/movsx_ax and wrap_wv for signed 8/16-bit ext - Fix conv_u: pxor instead of mov_ri64, JP instead of JNP, movzx for unsigned - Fix float->bool and float I_CMP: JP instead of JNP for NaN detection - Fix setcc_r: correct SETNE opcode (0x05 not 0x85) - Fix lock_xchg: use 0x86 for 8-bit exchanges - Fix SSE prefix ordering: emit prefix before REX - Inline SSE fixes in out_f_body - Add tests 31-35 and expected outputs - Update README for make test-native
Comments
No comments yet.
Diff
diff --git a/Makefile b/Makefile
index 6999493..67c3039 100644
--- a/Makefile
+++ b/Makefile
@@ -8,9 +8,9 @@ CC ?= cc
CFLAGS ?= -O2 -std=c11 -Wall -Wextra -Wno-unused-parameter
BIN = cryn
-SRCS = src/main.c src/util.c src/diag.c src/lex.c src/parse.c src/types.c \
+SRCS = src/main.c src/util.c src/diag.c src/lex.c src/parse.c src/types.c src/be_x64.c \
src/check.c src/opt.c src/be_c.c
-HDRS = src/util.h src/diag.h src/lex.h src/ast.h src/parse.h src/types.h \
+HDRS = src/util.h src/diag.h src/lex.h src/ast.h src/parse.h src/types.h src/be_x64.h \
src/ir.h src/check.h src/opt.h src/be_c.h src/rt_src.h
all: $(BIN)
@@ -27,7 +27,13 @@ $(BIN): $(SRCS) $(HDRS)
test: $(BIN)
sh tests/run.sh
+test-release: $(BIN)
+ CRYN_ARGS=--release sh tests/run.sh
+
+test-native: $(BIN)
+ CRYN_ARGS=--native sh tests/run.sh
+
clean:
rm -f $(BIN) tools/embed src/rt_src.h
-.PHONY: all test clean
+.PHONY: all test test-release test-native clean
diff --git a/README.md b/README.md
index a84872f..a5fd208 100644
--- a/README.md
+++ b/README.md
@@ -17,13 +17,21 @@ define main
end
```
-This compiles to a native executable through portable generated C:
+This compiles to a native executable through either of two backends:
```text
Cryn source -> lexer -> parser -> AST -> semantic analysis -> Cryn IR
- -> optimizer -> C backend -> host C compiler -> executable
+ -> optimizer -> C backend -> host C compiler -> executable
+ -> native backend -> ELF (x86-64) -> executable
```
+The C backend emits a self-contained C11 translation unit and hands it to
+the host C compiler (the portable reference path). The native backend
+lowers the IR directly to x86-64 machine code and writes an ELF executable
+itself — no C compiler, linker, or CRT objects are involved at program
+build time; the host C library is linked dynamically as the system-call
+interface.
+
The bootstrap compiler (`cryn`) is written in portable ISO C11 and has no
dependencies beyond a C compiler, pthreads, and libm (`fmod`, used by
float `remainder`).
@@ -34,7 +42,7 @@ float `remainder`).
> C-level power through a radically small set of composable primitives.
-* **37 commands total.** No `if`, `while`, `for`, `switch`, `array`,
+* **38 commands total.** No `if`, `while`, `for`, `switch`, `array`,
`struct`, `class`, or `pointer` keywords. Control flow is explicit
jumps; arrays are indexed connections; structures are blocks of memory;
pointers are address values.
@@ -80,6 +88,9 @@ src/
ir.h Cryn IR instruction definitions
opt.[ch] IR cleanup: unreachable code and dead label removal
be_c.[ch] C backend: emits a self-contained C11 translation unit
+ be_x64.[ch] native x86-64 backend: lowers the IR to machine code and
+ writes an ELF executable directly (host libc via a
+ hand-built GOT; musl and glibc `_start` conventions)
rt/
cryn_rt.h minimal runtime interface (embedded into output)
cryn_rt.c runtime implementation
@@ -107,8 +118,10 @@ semantic layer is tied to C generation.
## Building
```sh
-make # builds ./cryn
-make test # runs the test suite
+make # builds ./cryn
+make test # runs the test suite (C backend)
+make test-release # runs the test suite in release mode (no bounds checks)
+make test-native # runs the test suite through the native x86-64 backend
make clean
```
@@ -128,10 +141,16 @@ cryn help
cryn version
```
+Both `build` and `run` accept `--release` (no runtime bounds checks;
+statically provable checks are always eliminated at compile time) and
+`--native` (emit an x86-64 ELF executable directly instead of generated C;
+Linux, System V ABI, dynamic host libc).
+
`cryn` invokes the host C compiler (`$CC`, default `cc`) with
`-O2 -std=c11 -pthread` to turn generated C into an executable. Generated
programs are normal executables and need no Cryn runtime at runtime; the
-runtime is compiled into every generated translation unit.
+runtime is compiled into every generated translation unit. With `--native`
+no host C compiler is needed at all.
## How the compiler works
@@ -160,6 +179,16 @@ runtime is compiled into every generated translation unit.
stack and focus history live in a per-thread context struct. The
runtime sources are embedded and emitted into every translation unit,
making the output a single self-contained `.c` file.
+7. **Native backend** (`be_x64.c`): the same IR lowered straight to
+ x86-64. Each transformation gets a frame of callee-saved machine state
+ (`r12` comparison, `r13` focus, `r14` context, `r15` working value);
+ the runtime helpers (value stack, focus, blocks with headers, streams,
+ threads, float formatting) are emitted as machine code, and the writer
+ produces a static ELF with a hand-built GOT and SysV hash table that
+ links against the host C library for syscalls (stdio, malloc, pthreads,
+ `fmod`, and `snprintf`/`strtod` so float output is byte-identical to
+ the C backend). File handles, contexts, and block headers match the C
+ runtime's semantics.
## Runtime dependencies (complete list)
@@ -170,7 +199,7 @@ The runtime (`src/rt/cryn_rt.*`) provides only:
| `malloc`/`free` | `make <size> of`, anonymous `make`, `free` | 16-byte header for sizes plus a mutex-protected registry of live blocks; validation of stale addresses is defined, never undefined reads |
| value stack | `push`, `pop`, `swap`, `call` | growable array, per thread |
| focus history | `move` (relative), `back`, `home`| fixed 1024 entries per context (shared across active frames) |
-| FILE* streams | `input`, `output`, `open`, `use`, `close`, `flush`| stdin/stdout by default; `use` routes the context's streams through a handle |
+| FILE* streams | `input`, `output`, `open`, `use`, `close`, `flush`, `seek`| stdin/stdout by default; `use` routes the context's streams through a handle; `seek` positions handle streams at a byte offset |
| `system()` | `system` | shell escape |
| pthreads | `spawn`, `wait` | join is an ordering guarantee |
| formatting | `output` | shortest round-trip float printing |
@@ -183,27 +212,35 @@ library beyond the above.
## Testing
```sh
-make test
+make test # C backend
+make test-release # release mode: bounds checks removed
+make test-native # native x86-64 backend
```
-46 checks: arithmetic (including overflow wrap and division edge cases),
-unsigned arithmetic, floats, all six comparison conditions, branches,
-loops, nested loops, functions, recursion, mutual recursion, the value
-stack, cells and focus navigation, arrays (sieve of Eratosthenes),
-strings, manual memory, pointer arithmetic, linked lists, callbacks
-through transform references, file I/O with first-class handles
-(two files interleaved, append mode, flush), threads (`spawn`/`wait`),
-a spinlock built from `exchange`, a word
-counter, bubble sort, a free-list allocator, a limits stress test
-(301 cells, 300 labels, 302-deep focus, 1100-deep value stack), and
-sixteen programs that must fail to compile.
+48 checks each: arithmetic (including overflow wrap and division edge
+cases), unsigned arithmetic, floats (including `remainder`/fmod and
+NaN), all six comparison conditions, branches, loops, nested loops,
+functions, recursion, mutual recursion, the value stack, cells and focus
+navigation, arrays (sieve of Eratosthenes), strings, manual memory,
+pointer arithmetic, linked lists, callbacks through transform references,
+file I/O with first-class handles (two files interleaved, append mode,
+flush), `seek` repositioning an `open both` stream (interleaved reads and
+writes on one file), threads (`spawn`/`wait`), a spinlock built from
+`exchange`, compare-and-swap built from `exchange` and `wait` (the
+evaluation that closed further atomic primitives), a word counter, bubble
+sort, a free-list allocator, a limits stress test (301 cells, 300 labels,
+302-deep focus, 1100-deep value stack), and sixteen programs that must
+fail to compile. Every run test passes under all three modes.
## Current limitations
These are honest boundaries of the first implementation, not pretenses:
-* **Backend is C.** A native x86-64 backend is planned; the IR is ready
- for it.
+* **Two backends.** The C backend is the portable reference and needs a
+ host C compiler at build time. The native backend emits x86-64 ELF
+ directly (Linux, System V ABI, musl or glibc host libc; the interpreter
+ path and the libc SONAME are detected at compile time, override with
+ `CRYN_INTERP`). Other targets stay on the C backend.
* **Concurrency is minimal.** `spawn` starts a thread running a
zero-parameter transformation; `wait` joins all spawned contexts of the
current one. Shared world cells may be accessed concurrently; a race
@@ -225,8 +262,10 @@ These are honest boundaries of the first implementation, not pretenses:
not statically known. The callee's result is discarded and the caller's
working value (the reference) is preserved; results flow through world
cells or addresses.
-* **Bounds checks abort** with a diagnostic. A release mode that removes
- them is future work.
+* **Bounds checks abort** with a diagnostic in the default mode. Release
+ mode (`--release`) removes them; statically provable checks (literal
+ indices into static arrays, and literal indices provably within a
+ block's size) are eliminated at compile time in every mode.
* **Fixed limits:** 1024 focus history entries, 4096 labels, 4096 cells
per transformation, 4096 provable value stack depth, 256 concurrent
spawns per context, 256 open files per context. A single
@@ -235,8 +274,8 @@ These are honest boundaries of the first implementation, not pretenses:
by memory.
* **No separate compilation** yet: one `.cryn` file per program.
* **The compiler itself is not yet written in Cryn.** Self-hosting is the
- long-term goal; the architecture (small passes, plain data structures,
- no frameworks) was chosen for it.
+ remaining long-term goal; the architecture (small passes, plain data
+ structures, no frameworks) was chosen for it.
## Contributing
@@ -250,16 +289,21 @@ These are honest boundaries of the first implementation, not pretenses:
and `wiki/TUTORIAL.md`, published as
[Learning-Cryn](https://git.disroot.org/Vextoly/Cryn/wiki/Learning-Cryn)),
and a note in this README if it affects the runtime surface.
-* Run `make test` before committing.
+* Run `make test`, `make test-release`, and `make test-native` before
+ committing.
## Future work
-1. Native x86-64 backend (direct IR lowering; the C backend remains the
- portable reference).
-2. Self-hosting: rewrite the compiler in Cryn.
-3. Release mode without bounds checks; compile-time elimination of
- statically provable checks.
-4. File positioning (`seek`) for `open both` streams, if a use case
- cannot be met with the sequential byte-stream model plus `flush`.
-5. Further atomic primitives only when a use case cannot be met with
- `exchange` (which builds locks) and `wait`.
\ No newline at end of file
+1. Self-hosting: rewrite the compiler in Cryn (the C backend bootstraps
+ the port; the native backend then keeps the toolchain C-free).
+2. Extend the native backend beyond Linux/x86-64 (static PIE, more
+ libc targets) as demand appears; the C backend covers everything
+ else.
+
+Done from the original list: the native x86-64 backend (`--native`),
+release mode with compile-time elimination of statically provable bounds
+checks (`--release`), file positioning (`seek`) for `open both` streams,
+and the atomic-primitives evaluation — compare-and-swap and atomic
+arithmetic build from `exchange` and `wait` alone
+([tests/32_cas.cryn](tests/32_cas.cryn) is the proof), so no new
+primitive was added.
\ No newline at end of file
diff --git a/src/ast.h b/src/ast.h
index 48fd681..a4553a5 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -55,6 +55,7 @@ typedef enum {
A_RET, A_STOP,
A_INPUT, A_OUTPUT, A_OUTPUT_TEXT,
A_OPEN, A_CLOSE, A_SYSTEM, A_USE, A_FLUSH,
+ A_SEEK,
A_SPAWN, A_WAIT,
A_XCHG,
A_MARK
diff --git a/src/be_c.c b/src/be_c.c
index c9246ae..04a3ec2 100644
--- a/src/be_c.c
+++ b/src/be_c.c
@@ -184,7 +184,7 @@ static void emit_store(Buf* o, Insn* in) {
/* function emission */
/* ------------------------------------------------------------------ */
-static void emit_fn(Buf* o, ProgramIR* p, int fi) {
+static void emit_fn(Buf* o, ProgramIR* p, int fi, int release) {
FnIR* fn = &p->fns[fi];
emit_fn_sig(o, p, fi);
buf_puts(o, " {\n");
@@ -252,17 +252,16 @@ static void emit_fn(Buf* o, ProgramIR* p, int fi) {
const char* ct = ctype(in->t);
uint64_t esz = type_size(in->t);
if (in->op == I_MOVE_LIT) {
- if (in->b > 0)
- buf_printf(o, " crn_check_range((uint64_t)%" PRIu64 ", %d);\n",
- in->imm.u, in->b);
- else if (in->b < 0)
+ /* a literal index against a static bound (b > 0) was
+ proven in range by the checker: no check is needed */
+ if (in->b < 0 && !release)
buf_printf(o, " crn_check_block(F, (uint64_t)%" PRIu64 ", %" PRIu64 ");\n",
in->imm.u, esz);
buf_printf(o, " crn_focus_push(F); F = (%s*)F + %" PRIu64 ";\n", ct, in->imm.u);
} else {
- if (in->b > 0)
+ if (in->b > 0 && !release)
buf_printf(o, " crn_check_range(wv.u, %d);\n", in->b);
- else if (in->b < 0)
+ else if (in->b < 0 && !release)
buf_printf(o, " crn_check_block(F, wv.u, %" PRIu64 ");\n", esz);
buf_printf(o, " crn_focus_push(F); F = (%s*)F + wv.u;\n", ct);
}
@@ -716,6 +715,13 @@ static void emit_fn(Buf* o, ProgramIR* p, int fi) {
case I_FLUSH:
buf_puts(o, " crn_flush();\n");
break;
+ case I_SEEK:
+ if (in->b == 1)
+ buf_printf(o, " wv.i = crn_seek((int64_t)%" PRIu64 "ULL);\n", in->imm.u);
+ else
+ buf_puts(o, " wv.i = crn_seek((int64_t)wv.u);\n");
+ cur = ty_int(64);
+ break;
case I_SYSTEM:
buf_puts(o, " wv.i = crn_system((uint8_t*)(uintptr_t)wv.u);\n");
cur = ty_int(64);
@@ -753,7 +759,7 @@ static void emit_fn(Buf* o, ProgramIR* p, int fi) {
/* program emission */
/* ------------------------------------------------------------------ */
-void be_c_emit(ProgramIR* p, Buf* out) {
+void be_c_emit(ProgramIR* p, Buf* out, int release) {
buf_puts(out, "/* generated by the Cryn compiler - do not edit */\n");
buf_puts(out, "/* Cryn source compiled to portable C11 */\n\n");
buf_puts(out, cr_rt_h_src);
@@ -820,7 +826,7 @@ void be_c_emit(ProgramIR* p, Buf* out) {
/* functions */
for (int i = 0; i < p->nfns; i++)
- emit_fn(out, p, i);
+ emit_fn(out, p, i, release);
/* entry point */
buf_puts(out, "int main(void) {\n crn_init();\n crn_init_world();\n");
@@ -879,6 +885,7 @@ static const char* irop_name(IROp op) {
case I_CLOSE: return "close";
case I_USE: return "use";
case I_FLUSH: return "flush";
+ case I_SEEK: return "seek";
case I_SYSTEM: return "system";
case I_SPAWN: return "spawn";
case I_WAIT: return "wait";
diff --git a/src/be_c.h b/src/be_c.h
index 4506f56..4d17a8d 100644
--- a/src/be_c.h
+++ b/src/be_c.h
@@ -3,7 +3,9 @@
#include "ir.h"
-/* Emit a complete, self-contained C translation unit for the program. */
-void be_c_emit(ProgramIR* p, Buf* out);
+/* Emit a complete, self-contained C translation unit for the program.
+ release != 0 removes runtime bounds checks (statically provable
+ checks are always eliminated, in every mode). */
+void be_c_emit(ProgramIR* p, Buf* out, int release);
#endif
diff --git a/src/be_x64.c b/src/be_x64.c
new file mode 100644
index 0000000..7d475b3
--- /dev/null
+++ b/src/be_x64.c
@@ -0,0 +1,2463 @@
+/* be_x64.c - native x86-64 backend.
+ *
+ * Lowers the Cryn IR directly to x86-64 machine code and emits a static
+ * ELF64 executable. The only external dependency is the host C library,
+ * used as the system-call interface (stdio, malloc, pthreads, fmod,
+ * snprintf/strtod so float formatting is byte-identical to the C
+ * backend); it is linked dynamically through a hand-built GOT. No C
+ * compiler, no linker, and no CRT objects are involved at program build
+ * time.
+ *
+ * Internal calling convention (Val = 8-byte union carried in GP regs):
+ * rdi, rsi, rdx, rcx, r8, r9 arguments (then stack)
+ * rax result
+ * r12d comparison state (-1/0/1)
+ * r13 (F) focus pointer
+ * r14 (ctx) per-thread context
+ * r15 (wv) working value (floats as double bits)
+ * rbx scratch; all six are callee-saved by us
+ * The value stack is empty and the focus history is cut back at every
+ * call boundary (the checker guarantees it), so a per-thread context
+ * reached through r14 is exact.
+ *
+ * Stack discipline: generated function bodies keep rsp 16-aligned; a
+ * call therefore enters a helper with rsp == 8 (mod 16). Helpers that
+ * call libc use the frame push rbp; mov rbp,rsp; sub rsp,32 which
+ * restores 16-byte alignment. Frame-less helpers must only tail-jump
+ * to a die path from a state where rsp == 8 (mod 16).
+ */
+
+#include "be_x64.h"
+#include "util.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+
+/* ------------------------------------------------------------------ */
+/* emitter state */
+/* ------------------------------------------------------------------ */
+
+enum { SEG_RO, SEG_GOT, SEG_DATA };
+#define SEG_LBL 100 /* fix kind 4: rip-relative to a text label */
+
+typedef struct Fix {
+ int kind; /* 0: rel32 in text -> label
+ 1: disp32 in text, rip-relative to (seg, segoff)
+ 2: abs64 in data buffer at segoff -> label
+ 4: disp32 in text, rip-relative to label */
+ size_t at;
+ int label;
+ int seg;
+ size_t segoff;
+} Fix;
+
+typedef struct XE {
+ Buf text, ro, data;
+ size_t bss_size; /* data segment zero tail */
+ Fix* fixes;
+ int nfix, fixcap;
+ int* labels;
+ int nlabels, labelcap;
+ struct { const char* name; int is_data; } * imps;
+ int nimps, impcap;
+ int style_musl;
+ /* layout results */
+ uint64_t base, text_va, ro_va, got_va, dyn_va, data_va;
+ size_t start_off;
+ size_t interp_off, rx_filesz, rw_off, rw_filesz;
+} XE;
+
+static void fix_add(XE* e, int kind, size_t at, int label, int seg, size_t segoff) {
+ if (e->nfix == e->fixcap) {
+ e->fixcap = e->fixcap ? e->fixcap * 2 : 256;
+ e->fixes = xrealloc(e->fixes, (size_t)e->fixcap * sizeof(Fix));
+ }
+ Fix* f = &e->fixes[e->nfix++];
+ f->kind = kind; f->at = at; f->label = label; f->seg = seg; f->segoff = segoff;
+}
+
+static int lbl_new(XE* e) {
+ if (e->nlabels == e->labelcap) {
+ e->labelcap = e->labelcap ? e->labelcap * 2 : 512;
+ e->labels = xrealloc(e->labels, (size_t)e->labelcap * sizeof(int));
+ }
+ e->labels[e->nlabels] = -1;
+ return e->nlabels++;
+}
+static void lbl_here(XE* e, int l) { e->labels[l] = (int)e->text.len; }
+
+/* ------------------------------------------------------------------ */
+/* x86-64 encoding kit */
+/* ------------------------------------------------------------------ */
+
+/* regs: 0 ax,1 cx,2 dx,3 bx,4 sp,5 bp,6 si,7 di,8-15 r8-r15 */
+
+static void tc(XE* e, unsigned char b) { buf_putc(&e->text, (char)b); }
+static void tc2(XE* e, unsigned char a, unsigned char b) { tc(e, a); tc(e, b); }
+static void tc3(XE* e, unsigned char a, unsigned char b, unsigned char c) { tc(e, a); tc(e, b); tc(e, c); }
+static void tc4u(XE* e, uint32_t v) {
+ tc(e, (unsigned char)v); tc(e, (unsigned char)(v >> 8));
+ tc(e, (unsigned char)(v >> 16)); tc(e, (unsigned char)(v >> 24));
+}
+static void tc8u(XE* e, uint64_t v) { tc4u(e, (uint32_t)v); tc4u(e, (uint32_t)(v >> 32)); }
+static void tc4u_seq(XE* e, const unsigned char* p) { tc(e, p[0]); tc(e, p[1]); tc(e, p[2]); tc(e, p[3]); }
+static void tc5u_seq(XE* e, const unsigned char* p) { tc4u_seq(e, p); tc(e, p[4]); }
+
+static void R(XE* e, int w, int r, int b) {
+ unsigned char v = 0x40;
+ if (w) v |= 8;
+ if (r >= 8) v |= 4;
+ if (b >= 8) v |= 1;
+ if (v != 0x40) tc(e, v);
+}
+static void MR(XE* e, int mod, int reg, int rm) {
+ tc(e, (unsigned char)((mod << 6) | ((reg & 7) << 3) | (rm & 7)));
+}
+
+static void mov_rr(XE* e, int d, int s) { R(e, 1, s, d); tc(e, 0x89); MR(e, 3, s, d); }
+static void mov_ri64(XE* e, int r, uint64_t v) { R(e, 1, 0, r); tc(e, (unsigned char)(0xB8 + (r & 7))); tc8u(e, v); }
+static void mov_ri32(XE* e, int r, int32_t v) { R(e, 0, 0, r); tc(e, (unsigned char)(0xB8 + (r & 7))); tc4u(e, (uint32_t)v); }
+static void ld_d(XE* e, int reg, int base, int32_t d) { R(e, 1, reg, base); tc(e, 0x8B); MR(e, 2, reg, base); tc4u(e, (uint32_t)d); }
+static void st_d(XE* e, int base, int32_t d, int reg) { R(e, 1, reg, base); tc(e, 0x89); MR(e, 2, reg, base); tc4u(e, (uint32_t)d); }
+static void lea_rd(XE* e, int reg, int base, int32_t d) { R(e, 1, reg, base); tc(e, 0x8D); MR(e, 2, reg, base); tc4u(e, (uint32_t)d); }
+
+/* [base + idx*8 + d] */
+static void ld_s8(XE* e, int reg, int base, int idx, int32_t d) {
+ R(e, 1, reg, base); tc(e, 0x8B); MR(e, 2, reg, 4);
+ tc(e, (unsigned char)(0xC0 | ((idx & 7) << 3) | (base & 7)));
+ tc4u(e, (uint32_t)d);
+}
+static void st_s8(XE* e, int base, int idx, int32_t d, int reg) {
+ R(e, 1, reg, base); tc(e, 0x89); MR(e, 2, reg, 4);
+ tc(e, (unsigned char)(0xC0 | ((idx & 7) << 3) | (base & 7)));
+ tc4u(e, (uint32_t)d);
+}
+static void lea_s8(XE* e, int reg, int base, int idx, int32_t d) {
+ R(e, 1, reg, base); tc(e, 0x8D); MR(e, 2, reg, 4);
+ tc(e, (unsigned char)(0xC0 | ((idx & 7) << 3) | (base & 7)));
+ tc4u(e, (uint32_t)d);
+}
+static void lea_s4(XE* e, int reg, int base, int idx, int32_t d) {
+ R(e, 1, reg, base); tc(e, 0x8D); MR(e, 2, reg, 4);
+ tc(e, (unsigned char)(0x80 | ((idx & 7) << 3) | (base & 7)));
+ tc4u(e, (uint32_t)d);
+}
+
+static void lea_rip(XE* e, int reg, int seg, size_t off) {
+ R(e, 1, reg, 5); tc(e, 0x8D); MR(e, 0, reg, 5);
+ fix_add(e, 1, e->text.len, 0, seg, off); tc4u(e, 0);
+}
+static void lea_rip_lbl(XE* e, int reg, int label) {
+ R(e, 1, reg, 5); tc(e, 0x8D); MR(e, 0, reg, 5);
+ fix_add(e, 4, e->text.len, label, 0, 0); tc4u(e, 0);
+}
+static void mov_rip(XE* e, int reg, int seg, size_t off) {
+ R(e, 1, reg, 5); tc(e, 0x8B); MR(e, 0, reg, 5);
+ fix_add(e, 1, e->text.len, 0, seg, off); tc4u(e, 0);
+}
+static void call_rip(XE* e, int seg, size_t off) {
+ tc2(e, 0xFF, 0x15);
+ fix_add(e, 1, e->text.len, 0, seg, off); tc4u(e, 0);
+}
+static void jmp_l(XE* e, int l) { tc(e, 0xE9); fix_add(e, 0, e->text.len, l, 0, 0); tc4u(e, 0); }
+static void jcc_l(XE* e, int cc, int l) { tc2(e, 0x0F, (unsigned char)cc); fix_add(e, 0, e->text.len, l, 0, 0); tc4u(e, 0); }
+static void call_l(XE* e, int l) { tc(e, 0xE8); fix_add(e, 0, e->text.len, l, 0, 0); tc4u(e, 0); }
+
+/* ALU: ext 0=add 1=or 4=and 5=sub 6=xor 7=cmp */
+static void alu_rr(XE* e, int ext, int d, int s) { R(e, 1, s, d); tc(e, (unsigned char)(0x01 + 8 * ext)); MR(e, 3, s, d); }
+static void alu_ri(XE* e, int ext, int r, int32_t v) {
+ R(e, 1, 0, r);
+ if (v == (int8_t)v) { tc(e, 0x83); MR(e, 3, ext, r); tc(e, (unsigned char)v); }
+ else { tc(e, 0x81); MR(e, 3, ext, r); tc4u(e, (uint32_t)v); }
+}
+static void imul_r(XE* e, int d, int s) { R(e, 1, d, s); tc2(e, 0x0F, 0xAF); MR(e, 3, d, s); }
+static void imul_ri(XE* e, int d, int s, int32_t v) { R(e, 1, d, s); tc(e, 0x69); MR(e, 3, d, s); tc4u(e, (uint32_t)v); }
+static void push_r(XE* e, int r) { if (r >= 8) tc(e, 0x41); tc(e, (unsigned char)(0x50 + (r & 7))); }
+static void pop_r(XE* e, int r) { if (r >= 8) tc(e, 0x41); tc(e, (unsigned char)(0x58 + (r & 7))); }
+static void cqo(XE* e) { tc2(e, 0x48, 0x99); }
+static void idiv_r(XE* e, int r) { R(e, 1, 0, r); tc(e, 0xF7); MR(e, 3, 7, r); }
+static void neg_r(XE* e, int r) { R(e, 1, 0, r); tc(e, 0xF7); MR(e, 3, 3, r); }
+static void shl_ri(XE* e, int r, int n) { R(e, 1, 0, r); tc(e, 0xC1); MR(e, 3, 4, r); tc(e, (unsigned char)n); }
+static void test_rr(XE* e, int a, int b) { R(e, 1, b, a); tc(e, 0x85); MR(e, 3, b, a); }
+static void leave_(XE* e) { tc(e, 0xC9); }
+static void ret(XE* e) { tc(e, 0xC3); }
+static void sub_rsp(XE* e, int n) {
+ if (n == (int8_t)n) { tc3(e, 0x48, 0x83, 0xEC); tc(e, (unsigned char)n); }
+ else { tc3(e, 0x48, 0x81, 0xEC); tc4u(e, (uint32_t)n); }
+}
+static void add_rsp(XE* e, int n) {
+ if (n == (int8_t)n) { tc2(e, 0x48, 0x83); MR(e, 3, 0, 4); tc(e, (unsigned char)n); }
+ else { tc2(e, 0x48, 0x81); MR(e, 3, 0, 4); tc4u(e, (uint32_t)n); }
+}
+static void movsxd_rr(XE* e, int d, int s) { R(e, 1, d, s); tc(e, 0x63); MR(e, 3, d, s); }
+static void movsx_al(XE* e, int r) { R(e, 1, r, 0); tc2(e, 0x0F, 0xBE); MR(e, 3, r, 0); }
+static void movsx_ax(XE* e, int r) { R(e, 1, r, 0); tc2(e, 0x0F, 0xBF); MR(e, 3, r, 0); }
+static void movsx8(XE* e, int reg, int base, int32_t d) { R(e, 0, reg, base); tc2(e, 0x0F, 0xBE); MR(e, 2, reg, base); tc4u(e, (uint32_t)d); }
+static void movzx8(XE* e, int reg, int base, int32_t d) { R(e, 0, reg, base); tc2(e, 0x0F, 0xB6); MR(e, 2, reg, base); tc4u(e, (uint32_t)d); }
+static void movsx16(XE* e, int reg, int base, int32_t d) { R(e, 0, reg, base); tc2(e, 0x0F, 0xBF); MR(e, 2, reg, base); tc4u(e, (uint32_t)d); }
+static void movzx16(XE* e, int reg, int base, int32_t d) { R(e, 0, reg, base); tc2(e, 0x0F, 0xB7); MR(e, 2, reg, base); tc4u(e, (uint32_t)d); }
+static void movsx32m(XE* e, int reg, int base, int32_t d) { R(e, 1, reg, base); tc(e, 0x63); MR(e, 2, reg, base); tc4u(e, (uint32_t)d); }
+static void mov32m(XE* e, int reg, int base, int32_t d) { R(e, 0, reg, base); tc(e, 0x8B); MR(e, 2, reg, base); tc4u(e, (uint32_t)d); }
+static void st8(XE* e, int base, int32_t d, int reg) { R(e, 0, reg, base); tc(e, 0x88); MR(e, 2, reg, base); tc4u(e, (uint32_t)d); }
+static void st16(XE* e, int base, int32_t d, int reg) { tc(e, 0x66); R(e, 0, reg, base); tc(e, 0x89); MR(e, 2, reg, base); tc4u(e, (uint32_t)d); }
+static void st32(XE* e, int base, int32_t d, int reg) { R(e, 0, reg, base); tc(e, 0x89); MR(e, 2, reg, base); tc4u(e, (uint32_t)d); }
+static void st_zero64(XE* e, int base, int32_t d) {
+ R(e, 1, 0, base); tc(e, 0xC7); MR(e, 2, 0, base); tc4u(e, (uint32_t)d); tc4u(e, 0);
+}
+static void setcc_r(XE* e, int cc, int reg) {
+ R(e, 0, 0, reg); tc2(e, 0x0F, (unsigned char)(0x90 + cc)); MR(e, 3, 0, reg);
+ R(e, 0, reg, reg); tc2(e, 0x0F, 0xB6); MR(e, 3, reg, reg);
+}
+
+/* SSE */
+static void movq_xr(XE* e, int xmm, int r) { tc(e, 0x66); R(e, 1, xmm, r); tc2(e, 0x0F, 0x6E); MR(e, 3, xmm, r); }
+static void movq_rx(XE* e, int r, int xmm) { tc(e, 0x66); R(e, 1, xmm, r); tc2(e, 0x0F, 0x7E); MR(e, 3, xmm, r); }
+static void sse_op(XE* e, int prefix, int op, int d, int s) { tc(e, prefix); R(e, 0, d, s); tc(e, 0x0F); tc(e, op); MR(e, 3, d, s); }
+static void addsd(XE* e, int d, int s) { sse_op(e, 0xF2, 0x58, d, s); }
+static void subsd(XE* e, int d, int s) { sse_op(e, 0xF2, 0x5C, d, s); }
+static void mulsd(XE* e, int d, int s) { sse_op(e, 0xF2, 0x59, d, s); }
+static void divsd(XE* e, int d, int s) { sse_op(e, 0xF2, 0x5E, d, s); }
+static void cvtsi2sd(XE* e, int x, int r) { tc(e, 0xF2); R(e, 1, x, r); tc2(e, 0x0F, 0x2A); MR(e, 3, x, r); }
+static void cvttsd2si(XE* e, int r, int x) { tc(e, 0xF2); R(e, 1, x, r); tc2(e, 0x0F, 0x2C); MR(e, 3, x, r); }
+static void cvtsd2ss(XE* e, int d, int s) { sse_op(e, 0xF2, 0x5A, d, s); }
+static void cvtss2sd(XE* e, int d, int s) { sse_op(e, 0xF3, 0x5A, d, s); }
+static void ucomisd(XE* e, int a, int b) { sse_op(e, 0x66, 0x2E, a, b); }
+static void movsd_rip(XE* e, int xmm, int seg, size_t off) {
+ tc(e, 0xF2); R(e, 0, xmm, 5); tc2(e, 0x0F, 0x10); MR(e, 0, xmm, 5);
+ fix_add(e, 1, e->text.len, 0, seg, off); tc4u(e, 0);
+}
+static void pxor(XE* e, int a, int b) { tc(e, 0x66); R(e, 0, b, a); tc2(e, 0x0F, 0xEF); MR(e, 3, b, a); }
+static void xor_rr(XE* e, int d, int s) { R(e, 1, s, d); tc(e, 0x31); MR(e, 3, s, d); }
+static void movd_eax_xmm0(XE* e) { tc3(e, 0x66, 0x0F, 0x7E); tc(e, 0xC0); }
+static void movd_xmm0_eax(XE* e) { tc3(e, 0x66, 0x0F, 0x6E); tc(e, 0xC0); }
+static void movzx_al(XE* e, int r) { R(e, 0, r, 0); tc2(e, 0x0F, 0xB6); MR(e, 3, r, 0); }
+static void movzx_ax(XE* e, int r) { R(e, 0, r, 0); tc2(e, 0x0F, 0xB7); MR(e, 3, r, 0); }
+
+/* lock xchg r, m with size in bytes (1,2,4,8) */
+static void lock_xchg(XE* e, int base, int32_t d, int reg, int size) {
+ tc(e, 0xF0);
+ if (size == 2) tc(e, 0x66);
+ R(e, size == 8, reg, base);
+ tc(e, size == 1 ? 0x86 : 0x87);
+ MR(e, 2, reg, base);
+ tc4u(e, (uint32_t)d);
+}
+
+/* ------------------------------------------------------------------ */
+/* context layout (per thread, reached through r14) */
+/* ------------------------------------------------------------------ */
+
+#define CTX_STK 0
+#define CTX_SP 8
+#define CTX_CAP 16
+#define CTX_FH 24
+#define CTX_FHSP 8216
+#define CTX_IN 8224
+#define CTX_OUT 8232
+#define CTX_FILES 8240
+#define CTX_FMODES 10288
+#define CTX_SPAWNED 10544
+#define CTX_NSPAWN 12592
+#define CTX_SIZE 12596
+
+#define R_CMP 12
+#define R_F 13
+#define R_CTX 14
+#define R_WV 15
+
+/* ------------------------------------------------------------------ */
+/* imports, rodata, data objects */
+/* ------------------------------------------------------------------ */
+
+static int imp_idx(XE* e, const char* name, int is_data) {
+ for (int i = 0; i < e->nimps; i++)
+ if (strcmp(e->imps[i].name, name) == 0) return i;
+ if (e->nimps == e->impcap) {
+ e->impcap = e->impcap ? e->impcap * 2 : 32;
+ e->imps = xrealloc(e->imps, (size_t)e->impcap * sizeof(*e->imps));
+ }
+ e->imps[e->nimps].name = name;
+ e->imps[e->nimps].is_data = is_data;
+ return e->nimps++;
+}
+static void call_c(XE* e, const char* name) { call_rip(e, SEG_GOT, (size_t)(3 + imp_idx(e, name, 0)) * 8); }
+static void libc_data(XE* e, int reg, const char* name) { mov_rip(e, reg, SEG_GOT, (size_t)(3 + imp_idx(e, name, 1)) * 8); }
+
+static size_t ro_put(XE* e, const void* p, size_t n) {
+ size_t off = e->ro.len;
+ buf_put(&e->ro, p, n);
+ return off;
+}
+static size_t ro_str(XE* e, const char* s) { return ro_put(e, s, strlen(s) + 1); }
+static size_t ro_f64(XE* e, double d) { size_t o = e->ro.len; buf_put(&e->ro, &d, 8); return o; }
+static size_t data_put(XE* e, const void* p, size_t n) {
+ size_t off = e->data.len;
+ buf_put(&e->data, p, n);
+ return off;
+}
+
+/* ------------------------------------------------------------------ */
+/* helper runtime */
+/* ------------------------------------------------------------------ */
+
+typedef struct H {
+ int die, die_range;
+ int die_oom, die_div0, die_flushfail, die_focus, die_toolarge;
+ int die_notblock, die_free_null, die_usebad, die_closebad;
+ int die_indcall, die_spawnpar, die_spawnmax, die_threadfail, die_seekstreams;
+ int push, pop, fpush, fpop;
+ int alloc, free_, blockn, chk_block, chk_range;
+ int d2i64, d2u64, d2i8, d2i16, d2i32, d2u8, d2u16, d2u32;
+ int out_i64, out_u64, out_f64, out_f32, out_bool, out_char, out_addr, out_text;
+ int input, open, close, use, flush, seek, system;
+ int spawn, thread_main, wait, stop;
+ size_t fmt_err, fmt_range, fmt_lld, fmt_llu, fmt_hex, fmt_g;
+ size_t s_nan, s_true, s_false;
+ size_t modetab; /* "rb\0\0" "wb\0\0" "w+b\0" "ab\0\0" */
+ size_t k_2p63, k_2n63, k_2p64;
+ size_t fbuf; /* 64 writable bytes in .data */
+ size_t done_ctr; /* completed-thread counter in .data */
+} H;
+
+#define FRAME32(e) do { push_r(e, 5); mov_rr(e, 5, 4); sub_rsp(e, 32); } while (0)
+
+/* fprintf(stderr, "cryn: runtime error: %s\n", rdi); exit(1) */
+static void h_die(XE* e, H* h) {
+ lbl_here(e, h->die);
+ FRAME32(e);
+ st_d(e, 5, -16, 7);
+ libc_data(e, 0, "stderr");
+ ld_d(e, 7, 0, 0);
+ lea_rip(e, 6, SEG_RO, h->fmt_err);
+ ld_d(e, 2, 5, -16);
+ tc2(e, 0x31, 0xC0);
+ call_c(e, "fprintf");
+ mov_ri32(e, 7, 1);
+ call_c(e, "exit");
+}
+
+static void h_die_range(XE* e, H* h) {
+ lbl_here(e, h->die_range);
+ FRAME32(e);
+ st_d(e, 5, -16, 7); /* i */
+ st_d(e, 5, -24, 6); /* n */
+ libc_data(e, 0, "stderr");
+ ld_d(e, 7, 0, 0);
+ lea_rip(e, 6, SEG_RO, h->fmt_range);
+ ld_d(e, 2, 5, -16); /* i */
+ ld_d(e, 8, 5, -24); /* n */
+ tc2(e, 0x31, 0xC0);
+ call_c(e, "fprintf");
+ mov_ri32(e, 7, 1);
+ call_c(e, "exit");
+}
+
+static void h_msg_dies(XE* e, H* h) {
+ struct { int l; const char* m; } ds[] = {
+ {h->die_oom, "out of memory"},
+ {h->die_div0, "division by zero"},
+ {h->die_flushfail, "flush failed"},
+ {h->die_focus, "focus history underflow"},
+ {h->die_toolarge, "allocation too large"},
+ {h->die_notblock, "index into memory that is not a heap block (only the base address of a live block made with 'make <size> of <type>' is bounds checked)"},
+ {h->die_free_null, "free of the null address"},
+ {h->die_usebad, "use of a handle that is not open in this context (handles come from 'open'; 0 means the standard streams)"},
+ {h->die_closebad, "close of a handle that is not open in this context (handles come from 'open' and are closed once)"},
+ {h->die_indcall, "indirect call of a transformation that takes parameters"},
+ {h->die_spawnpar, "spawn of a transformation that takes parameters"},
+ {h->die_spawnmax, "too many outstanding spawned contexts"},
+ {h->die_threadfail, "thread creation failed"},
+ {h->die_seekstreams, "seek requires a stream routed through an open handle ('use' a handle from 'open' first)"},
+ };
+ for (size_t k = 0; k < sizeof ds / sizeof ds[0]; k++) {
+ lbl_here(e, ds[k].l);
+ lea_rip(e, 7, SEG_RO, ro_str(e, ds[k].m));
+ jmp_l(e, h->die); /* entered at rsp 8 */
+ }
+}
+
+static void h_stack(XE* e, H* h) {
+ /* push(rdi) */
+ lbl_here(e, h->push);
+ ld_d(e, 0, R_CTX, CTX_SP);
+ int grow = lbl_new(e), ok = lbl_new(e);
+ R(e, 1, 0, R_CTX); tc(e, 0x39); MR(e, 2, 0, R_CTX); tc4u(e, CTX_CAP);
+ jcc_l(e, 0x86, grow); /* jbe: cap <= sp -> grow */
+ jmp_l(e, ok);
+ lbl_here(e, grow);
+ push_r(e, 7); /* v */
+ push_r(e, 5); mov_rr(e, 5, 4);
+ sub_rsp(e, 8);
+ ld_d(e, 7, R_CTX, CTX_CAP);
+ test_rr(e, 7, 7);
+ int nz = lbl_new(e), set = lbl_new(e);
+ jcc_l(e, 0x85, nz); /* jnz */
+ mov_ri32(e, 7, 64);
+ jmp_l(e, set);
+ lbl_here(e, nz);
+ shl_ri(e, 7, 1);
+ lbl_here(e, set);
+ st_d(e, R_CTX, CTX_CAP, 7);
+ shl_ri(e, 7, 3); /* rdi = bytes */
+ mov_rr(e, 6, 7); /* rsi = bytes */
+ ld_d(e, 7, R_CTX, CTX_STK); /* rdi = old */
+ call_c(e, "realloc");
+ test_rr(e, 0, 0);
+ int oom = lbl_new(e);
+ jcc_l(e, 0x85, oom); /* jnz */
+ call_l(e, h->die_oom);
+ lbl_here(e, oom);
+ st_d(e, R_CTX, CTX_STK, 0);
+ ld_d(e, 0, R_CTX, CTX_SP);
+ add_rsp(e, 8);
+ pop_r(e, 5);
+ pop_r(e, 7);
+ lbl_here(e, ok);
+ ld_d(e, 1, R_CTX, CTX_STK);
+ st_s8(e, 1, 0, 0, 7);
+ R(e, 1, 0, R_CTX); tc(e, 0x83); MR(e, 2, 0, R_CTX); tc4u(e, CTX_SP); tc(e, 1);
+ ret(e);
+
+ /* pop() -> rax */
+ lbl_here(e, h->pop);
+ R(e, 1, 0, R_CTX); tc(e, 0x83); MR(e, 2, 5, R_CTX); tc4u(e, CTX_SP); tc(e, 1);
+ ld_d(e, 0, R_CTX, CTX_SP);
+ ld_d(e, 1, R_CTX, CTX_STK);
+ ld_s8(e, 0, 1, 0, 0);
+ ret(e);
+}
+
+static void h_focus(XE* e, H* h) {
+ lbl_here(e, h->fpush);
+ ld_d(e, 0, R_CTX, CTX_FHSP);
+ mov_ri32(e, 1, 1024);
+ alu_rr(e, 7, 0, 1); /* cmp rax, 1024 */
+ jcc_l(e, 0x83, h->die_focus); /* jae, tail jmp at rsp 8 */
+ st_s8(e, R_CTX, 0, CTX_FH, 7);
+ R(e, 1, 0, R_CTX); tc(e, 0x83); MR(e, 2, 0, R_CTX); tc4u(e, CTX_FHSP); tc(e, 1);
+ ret(e);
+ lbl_here(e, h->fpop);
+ R(e, 1, 0, R_CTX); tc(e, 0x83); MR(e, 2, 5, R_CTX); tc4u(e, CTX_FHSP); tc(e, 1);
+ ld_d(e, 0, R_CTX, CTX_FHSP);
+ ld_s8(e, 0, R_CTX, 0, CTX_FH);
+ ret(e);
+}
+
+static void h_heap(XE* e, H* h) {
+ /* alloc(rdi = bytes) -> rax = data */
+ lbl_here(e, h->alloc);
+ FRAME32(e);
+ st_d(e, 5, -16, 7);
+ mov_ri64(e, 1, (uint64_t)-32);
+ alu_rr(e, 7, 7, 1);
+ int ok1 = lbl_new(e);
+ jcc_l(e, 0x82, ok1); /* jb */
+ call_l(e, h->die_toolarge);
+ lbl_here(e, ok1);
+ ld_d(e, 7, 5, -16);
+ alu_ri(e, 0, 7, 16);
+ call_c(e, "malloc");
+ test_rr(e, 0, 0);
+ int ok2 = lbl_new(e);
+ jcc_l(e, 0x85, ok2); /* jnz */
+ call_l(e, h->die_oom);
+ lbl_here(e, ok2);
+ mov_ri64(e, 1, 0x4352594E424C4BULL);
+ st_d(e, 0, 0, 1);
+ ld_d(e, 2, 5, -16);
+ st_d(e, 0, 8, 2);
+ st_d(e, 5, -24, 0);
+ lea_rd(e, 7, 0, 16);
+ ld_d(e, 2, 5, -16);
+ tc2(e, 0x31, 0xF6);
+ call_c(e, "memset");
+ leave_(e);
+ ret(e);
+
+ /* free(rdi = ptr) */
+ lbl_here(e, h->free_);
+ FRAME32(e);
+ test_rr(e, 7, 7);
+ int ok3 = lbl_new(e);
+ jcc_l(e, 0x85, ok3);
+ call_l(e, h->die_free_null);
+ lbl_here(e, ok3);
+ mov_rr(e, 0, 7);
+ alu_ri(e, 5, 0, 16); /* rax = ptr - 16 (header) */
+ mov_ri64(e, 1, 0x4352594E424C4BULL);
+ R(e, 1, 1, 0); tc(e, 0x39); MR(e, 1, 1, 0); tc(e, 0);
+ int ok4 = lbl_new(e);
+ jcc_l(e, 0x84, ok4); /* je */
+ call_l(e, h->die_notblock);
+ lbl_here(e, ok4);
+ st_zero64(e, 0, 0);
+ mov_rr(e, 7, 0);
+ call_c(e, "free");
+ leave_(e);
+ ret(e);
+
+ /* blockn(rdi = data, rsi = elemsz) -> rax (frame-less) */
+ lbl_here(e, h->blockn);
+ test_rr(e, 7, 7);
+ jcc_l(e, 0x84, h->die_notblock);
+ ld_d(e, 0, 7, -16);
+ mov_ri64(e, 1, 0x4352594E424C4BULL);
+ alu_rr(e, 7, 0, 1);
+ jcc_l(e, 0x85, h->die_notblock);
+ ld_d(e, 0, 7, -8);
+ test_rr(e, 6, 6);
+ int nz = lbl_new(e);
+ jcc_l(e, 0x85, nz);
+ mov_ri32(e, 6, 1);
+ lbl_here(e, nz);
+ mov_rr(e, 1, 6);
+ tc2(e, 0x31, 0xD2);
+ idiv_r(e, 1);
+ ret(e);
+
+ /* chk_block(rdi = data, rsi = i, rdx = elemsz) */
+ lbl_here(e, h->chk_block);
+ mov_rr(e, 8, 6);
+ mov_rr(e, 6, 2);
+ call_l(e, h->blockn);
+ alu_rr(e, 7, 8, 0);
+ int ok = lbl_new(e);
+ jcc_l(e, 0x82, ok); /* jb */
+ mov_rr(e, 7, 8);
+ mov_rr(e, 6, 0);
+ jmp_l(e, h->die_range); /* rsp 8 */
+ lbl_here(e, ok);
+ ret(e);
+
+ /* chk_range(rdi = i, rsi = n) */
+ lbl_here(e, h->chk_range);
+ alu_rr(e, 7, 7, 6);
+ int okr = lbl_new(e);
+ jcc_l(e, 0x82, okr);
+ jmp_l(e, h->die_range);
+ lbl_here(e, okr);
+ ret(e);
+}
+
+/* signed conversion: xmm0 -> rax */
+static void conv_s(XE* e, int id, size_t ro_hi, uint64_t v_hi, size_t ro_lo, uint64_t v_lo, int widen) {
+ lbl_here(e, id);
+ int lhi = lbl_new(e), llo = lbl_new(e), lcvt = lbl_new(e);
+ movsd_rip(e, 1, SEG_RO, ro_hi);
+ ucomisd(e, 0, 1);
+ jcc_l(e, 0x83, lhi); /* jae hi */
+ movsd_rip(e, 1, SEG_RO, ro_lo);
+ ucomisd(e, 0, 1);
+ jcc_l(e, 0x86, llo); /* jbe lo */
+ jmp_l(e, lcvt);
+ lbl_here(e, lhi);
+ mov_ri64(e, 0, v_hi);
+ ret(e);
+ lbl_here(e, llo);
+ mov_ri64(e, 0, v_lo);
+ ret(e);
+ lbl_here(e, lcvt);
+ cvttsd2si(e, 0, 0);
+ if (widen == 1) movsx_al(e, 0);
+ else if (widen == 2) movsx_ax(e, 0);
+ else if (widen == 3) movsxd_rr(e, 0, 0);
+ ret(e);
+}
+
+/* unsigned conversion: NaN falls through to cvt (matches C on x86) */
+static void conv_u(XE* e, int id, size_t ro_lim, uint64_t v_hi, int widen) {
+ lbl_here(e, id);
+ int lhi = lbl_new(e), llo = lbl_new(e), lcvt = lbl_new(e);
+ movsd_rip(e, 1, SEG_RO, ro_lim);
+ ucomisd(e, 0, 1);
+ jcc_l(e, 0x83, lhi); /* jae hi */
+ pxor(e, 1, 1);
+ ucomisd(e, 0, 1); /* vs 0.0 */
+ jcc_l(e, 0x8A, lcvt); /* jp: NaN -> cvt */
+ jcc_l(e, 0x86, llo); /* jbe 0 */
+ jmp_l(e, lcvt);
+ lbl_here(e, lhi);
+ mov_ri64(e, 0, v_hi);
+ ret(e);
+ lbl_here(e, llo);
+ mov_ri64(e, 0, 0);
+ ret(e);
+ lbl_here(e, lcvt);
+ cvttsd2si(e, 0, 0);
+ if (widen == 1) movzx_al(e, 0);
+ else if (widen == 2) movzx_ax(e, 0);
+ else if (widen == 3) { R(e, 0, 0, 0); tc(e, 0x89); MR(e, 3, 0, 0); }
+ ret(e);
+}
+
+static void h_conv(XE* e, H* h) {
+ size_t c127 = ro_f64(e, 127.0), cm128 = ro_f64(e, -128.0);
+ size_t c32767 = ro_f64(e, 32767.0), cm32768 = ro_f64(e, -32768.0);
+ size_t c2p31 = ro_f64(e, 2147483648.0), cm2p31m1 = ro_f64(e, -2147483649.0);
+ size_t c255 = ro_f64(e, 255.0), c65535 = ro_f64(e, 65535.0);
+ size_t c2p32 = ro_f64(e, 4294967296.0);
+ conv_s(e, h->d2i64, h->k_2p63, 0x7FFFFFFFFFFFFFFFULL, h->k_2n63, 0x8000000000000000ULL, 0);
+ conv_s(e, h->d2i8, c127, 127, cm128, (uint64_t)(uint64_t)-128, 1);
+ conv_s(e, h->d2i16, c32767, 32767, cm32768, (uint64_t)-32768, 2);
+ conv_s(e, h->d2i32, c2p31, 2147483647, cm2p31m1, 0xFFFFFFFF80000000ULL, 3);
+ conv_u(e, h->d2u64, h->k_2p64, 0xFFFFFFFFFFFFFFFFULL, 0);
+ conv_u(e, h->d2u8, c255, 255, 1);
+ conv_u(e, h->d2u16, c65535, 65535, 2);
+ conv_u(e, h->d2u32, c2p32, 4294967295ULL, 3);
+}
+
+static void h_out(XE* e, H* h) {
+ /* out_i64(rdi = v) */
+ lbl_here(e, h->out_i64);
+ FRAME32(e);
+ st_d(e, 5, -16, 7);
+ ld_d(e, 7, R_CTX, CTX_OUT);
+ lea_rip(e, 6, SEG_RO, h->fmt_lld);
+ ld_d(e, 2, 5, -16);
+ tc2(e, 0x31, 0xC0);
+ call_c(e, "fprintf");
+ leave_(e);
+ ret(e);
+ /* out_u64 */
+ lbl_here(e, h->out_u64);
+ FRAME32(e);
+ st_d(e, 5, -16, 7);
+ ld_d(e, 7, R_CTX, CTX_OUT);
+ lea_rip(e, 6, SEG_RO, h->fmt_llu);
+ ld_d(e, 2, 5, -16);
+ tc2(e, 0x31, 0xC0);
+ call_c(e, "fprintf");
+ leave_(e);
+ ret(e);
+ /* out_addr */
+ lbl_here(e, h->out_addr);
+ FRAME32(e);
+ st_d(e, 5, -16, 7);
+ ld_d(e, 7, R_CTX, CTX_OUT);
+ lea_rip(e, 6, SEG_RO, h->fmt_hex);
+ ld_d(e, 2, 5, -16);
+ tc2(e, 0x31, 0xC0);
+ call_c(e, "fprintf");
+ leave_(e);
+ ret(e);
+ /* out_bool(rdi) */
+ lbl_here(e, h->out_bool);
+ FRAME32(e);
+ test_rr(e, 7, 7);
+ int t = lbl_new(e), go_ = lbl_new(e);
+ jcc_l(e, 0x85, t); /* jnz true */
+ lea_rip(e, 7, SEG_RO, h->s_false);
+ jmp_l(e, go_);
+ lbl_here(e, t);
+ lea_rip(e, 7, SEG_RO, h->s_true);
+ lbl_here(e, go_);
+ ld_d(e, 6, R_CTX, CTX_OUT);
+ call_c(e, "fputs");
+ leave_(e);
+ ret(e);
+ /* out_char(rdi = code) */
+ lbl_here(e, h->out_char);
+ FRAME32(e);
+ mov_ri32(e, 6, 0xFF);
+ alu_rr(e, 4, 7, 6); /* and */
+ ld_d(e, 6, R_CTX, CTX_OUT);
+ call_c(e, "fputc");
+ leave_(e);
+ ret(e);
+ /* out_text(rdi = bytes) */
+ lbl_here(e, h->out_text);
+ push_r(e, 5); mov_rr(e, 5, 4);
+ push_r(e, 3); push_r(e, 7);
+ sub_rsp(e, 8);
+ mov_rr(e, 3, 7); /* rbx = p */
+ int loop = lbl_new(e), done = lbl_new(e);
+ lbl_here(e, loop);
+ movzx8(e, 7, 3, 0);
+ test_rr(e, 7, 7);
+ jcc_l(e, 0x84, done); /* jz */
+ ld_d(e, 6, R_CTX, CTX_OUT);
+ call_c(e, "fputc");
+ alu_ri(e, 0, 3, 1);
+ jmp_l(e, loop);
+ lbl_here(e, done);
+ add_rsp(e, 8);
+ pop_r(e, 7); pop_r(e, 3); pop_r(e, 5);
+ ret(e);
+}
+
+/* out_f64(xmm0) and out_f32(xmm0, already widened to double) */
+static void out_f_body(XE* e, H* h, int id, int maxprec, int narrow) {
+ int done_lbl = lbl_new(e);
+ int exit_lbl = lbl_new(e);
+ lbl_here(e, id);
+ push_r(e, 5); mov_rr(e, 5, 4);
+ push_r(e, 3);
+ sub_rsp(e, 24);
+ /* movsd [rbp-24],xmm0 */
+ tc(e, 0xF2); R(e, 0, 0, 5); tc2(e, 0x0F, 0x11); MR(e, 2, 0, 5); tc4u(e, (uint32_t)-24);
+ ucomisd(e, 0, 0);
+ int notnan = lbl_new(e);
+ jcc_l(e, 0x8B, notnan); /* jp */
+ lea_rip(e, 7, SEG_RO, h->s_nan);
+ ld_d(e, 6, R_CTX, CTX_OUT);
+ call_c(e, "fputs");
+ jmp_l(e, exit_lbl);
+ lbl_here(e, notnan);
+ mov_ri32(e, 3, 1); /* rbx = prec */
+ int loop = lbl_new(e);
+ lbl_here(e, loop);
+ lea_rip(e, 7, SEG_DATA, h->fbuf);
+ mov_ri32(e, 6, 64);
+ lea_rip(e, 2, SEG_RO, h->fmt_g);
+ mov_rr(e, 1, 3);
+ tc(e, 0xF2); R(e, 0, 0, 5); tc2(e, 0x0F, 0x10); MR(e, 2, 0, 5); tc4u(e, (uint32_t)-24); // load xmm0
+ mov_ri32(e, 0, 1); /* al = 1 vector arg */
+ call_c(e, "snprintf");
+ lea_rip(e, 7, SEG_DATA, h->fbuf);
+ tc2(e, 0x31, 0xF6);
+ call_c(e, "strtod");
+ if (narrow) {
+ cvtsd2ss(e, 0, 0);
+ cvtss2sd(e, 0, 0);
+ }
+ tc(e, 0xF2); R(e, 0, 1, 5); tc2(e, 0x0F, 0x10); MR(e, 2, 1, 5); tc4u(e, (uint32_t)-24);
+ ucomisd(e, 0, 1);
+ jcc_l(e, 0x84, done_lbl); /* je done */
+ alu_ri(e, 0, 3, 1);
+ mov_ri32(e, 1, maxprec);
+ alu_rr(e, 7, 3, 1); /* cmp prec, max */
+ jcc_l(e, 0x8E, loop); /* jle */
+ lbl_here(e, done_lbl);
+ lea_rip(e, 7, SEG_DATA, h->fbuf);
+ ld_d(e, 6, R_CTX, CTX_OUT);
+ call_c(e, "fputs");
+ lbl_here(e, exit_lbl);
+ add_rsp(e, 24);
+ pop_r(e, 3);
+ pop_r(e, 5);
+ ret(e);
+}
+
+static void h_io(XE* e, H* h) {
+ /* input() -> rax */
+ lbl_here(e, h->input);
+ FRAME32(e);
+ ld_d(e, 7, R_CTX, CTX_IN);
+ call_c(e, "fgetc");
+ movsxd_rr(e, 0, 0);
+ leave_(e);
+ ret(e);
+ /* open(rdi = path, esi = mode) -> rax handle */
+ lbl_here(e, h->open);
+ FRAME32(e);
+ st_d(e, 5, -16, 7); /* path */
+ st32(e, 5, -28, 6); /* mode (clear of the f slot) */
+ lea_rip(e, 0, SEG_RO, h->modetab);
+ movsx32m(e, 1, 5, -28); /* rcx = (i64)mode */
+ lea_s4(e, 6, 0, 1, 0); /* rsi = &modes[mode] */
+ ld_d(e, 7, 5, -16);
+ call_c(e, "fopen");
+ test_rr(e, 0, 0);
+ int fail = lbl_new(e);
+ jcc_l(e, 0x84, fail); /* jz */
+ st_d(e, 5, -24, 0); /* f */
+ tc2(e, 0x31, 0xC9);
+ int scan = lbl_new(e), found = lbl_new(e), full = lbl_new(e);
+ lbl_here(e, scan);
+ ld_s8(e, 2, R_CTX, 1, CTX_FILES);
+ test_rr(e, 2, 2);
+ jcc_l(e, 0x84, found); /* jz -> free slot */
+ alu_ri(e, 0, 1, 1);
+ mov_ri32(e, 2, 256);
+ alu_rr(e, 7, 1, 2); /* cmp rcx, 256 */
+ jcc_l(e, 0x82, scan); /* jb */
+ jmp_l(e, full);
+ lbl_here(e, full);
+ ld_d(e, 7, 5, -24);
+ call_c(e, "fclose");
+ mov_ri64(e, 0, (uint64_t)-1);
+ leave_(e);
+ ret(e);
+ lbl_here(e, found);
+ ld_d(e, 2, 5, -24);
+ st_s8(e, R_CTX, 1, CTX_FILES, 2);
+ ld_d(e, 2, 5, -24); /* reload f (paranoia) */
+ movzx8(e, 2, 5, -28); /* mode byte from dword slot */
+ /* fmodes[rcx] = mode: st8 with index needs base+idx+disp byte store */
+ R(e, 0, 2, R_CTX); tc(e, 0x88); MR(e, 2, 2, 4);
+ tc(e, (unsigned char)((1 & 7) << 3) | (R_CTX & 7));
+ tc4u(e, CTX_FMODES);
+ lea_rd(e, 0, 1, 1); /* rax = rcx+1 */
+ leave_(e);
+ ret(e);
+ /* close(rdi = h) -> rax 0/-1 */
+ lbl_here(e, h->close);
+ FRAME32(e);
+ st_d(e, 5, -16, 7);
+ test_rr(e, 7, 7);
+ int d1 = lbl_new(e);
+ jcc_l(e, 0x85, d1);
+ call_l(e, h->die_closebad);
+ lbl_here(e, d1);
+ mov_ri32(e, 1, 1);
+ alu_rr(e, 7, 7, 1); /* cmp h,1 */
+ int d2 = lbl_new(e);
+ jcc_l(e, 0x8D, d2); /* jge */
+ call_l(e, h->die_closebad);
+ lbl_here(e, d2);
+ mov_ri32(e, 1, 256);
+ alu_rr(e, 7, 7, 1);
+ int d3 = lbl_new(e);
+ jcc_l(e, 0x8E, d3); /* jle */
+ call_l(e, h->die_closebad);
+ lbl_here(e, d3);
+ lea_rd(e, 1, 7, -1); /* rcx = h-1 */
+ ld_s8(e, 0, R_CTX, 1, CTX_FILES);
+ test_rr(e, 0, 0);
+ int d4 = lbl_new(e);
+ jcc_l(e, 0x85, d4);
+ call_l(e, h->die_closebad);
+ lbl_here(e, d4);
+ st_d(e, 5, -24, 0); /* f */
+ tc2(e, 0x31, 0xD2);
+ st_s8(e, R_CTX, 1, CTX_FILES, 2);
+ /* if (ctx->in == f) in = stdin */
+ ld_d(e, 2, R_CTX, CTX_IN);
+ ld_d(e, 0, 5, -24);
+ alu_rr(e, 7, 2, 0);
+ int s1 = lbl_new(e);
+ jcc_l(e, 0x85, s1);
+ libc_data(e, 0, "stdin");
+ ld_d(e, 2, 0, 0);
+ st_d(e, R_CTX, CTX_IN, 2);
+ lbl_here(e, s1);
+ ld_d(e, 2, R_CTX, CTX_OUT);
+ ld_d(e, 0, 5, -24);
+ alu_rr(e, 7, 2, 0);
+ int s2 = lbl_new(e);
+ jcc_l(e, 0x85, s2);
+ libc_data(e, 0, "stdout");
+ ld_d(e, 2, 0, 0);
+ st_d(e, R_CTX, CTX_OUT, 2);
+ lbl_here(e, s2);
+ ld_d(e, 7, 5, -24);
+ call_c(e, "fclose");
+ test_rr(e, 0, 0);
+ int cok = lbl_new(e);
+ jcc_l(e, 0x84, cok); /* jz ok */
+ mov_ri64(e, 0, (uint64_t)-1);
+ leave_(e);
+ ret(e);
+ lbl_here(e, cok);
+ tc2(e, 0x31, 0xC0);
+ leave_(e);
+ ret(e);
+ /* use(rdi = h) */
+ lbl_here(e, h->use);
+ FRAME32(e);
+ st_d(e, 5, -16, 7);
+ test_rr(e, 7, 7);
+ int u0 = lbl_new(e);
+ jcc_l(e, 0x85, u0); /* jnz */
+ libc_data(e, 0, "stdin");
+ ld_d(e, 2, 0, 0);
+ st_d(e, R_CTX, CTX_IN, 2);
+ libc_data(e, 0, "stdout");
+ ld_d(e, 2, 0, 0);
+ st_d(e, R_CTX, CTX_OUT, 2);
+ leave_(e);
+ ret(e);
+ lbl_here(e, u0);
+ mov_ri32(e, 1, 1);
+ alu_rr(e, 7, 7, 1);
+ int u1 = lbl_new(e);
+ jcc_l(e, 0x8D, u1); /* jge */
+ call_l(e, h->die_usebad);
+ lbl_here(e, u1);
+ mov_ri32(e, 1, 256);
+ alu_rr(e, 7, 7, 1);
+ int u2 = lbl_new(e);
+ jcc_l(e, 0x8E, u2);
+ call_l(e, h->die_usebad);
+ lbl_here(e, u2);
+ lea_rd(e, 1, 7, -1);
+ ld_s8(e, 2, R_CTX, 1, CTX_FILES);
+ test_rr(e, 2, 2);
+ int u3 = lbl_new(e);
+ jcc_l(e, 0x85, u3);
+ call_l(e, h->die_usebad);
+ lbl_here(e, u3);
+ st_d(e, 5, -24, 2); /* f */
+ /* r8 = fmodes[h-1] */
+ R(e, 0, 0, R_CTX); tc2(e, 0x0F, 0xB6); MR(e, 2, 0, 4);
+ tc(e, (unsigned char)((1 & 7) << 3) | (R_CTX & 7));
+ tc4u(e, CTX_FMODES);
+ movsxd_rr(e, 8, 0);
+ /* mode 2: in = f, out = f */
+ mov_ri32(e, 1, 2);
+ alu_rr(e, 7, 8, 1);
+ int m2 = lbl_new(e);
+ jcc_l(e, 0x85, m2); /* jne */
+ ld_d(e, 2, 5, -24);
+ st_d(e, R_CTX, CTX_IN, 2);
+ ld_d(e, 2, 5, -24);
+ st_d(e, R_CTX, CTX_OUT, 2);
+ leave_(e);
+ ret(e);
+ lbl_here(e, m2);
+ mov_ri32(e, 1, 0);
+ alu_rr(e, 7, 8, 1);
+ int m0 = lbl_new(e);
+ jcc_l(e, 0x85, m0); /* jne */
+ ld_d(e, 2, 5, -24);
+ st_d(e, R_CTX, CTX_IN, 2);
+ leave_(e);
+ ret(e);
+ lbl_here(e, m0);
+ ld_d(e, 2, 5, -24);
+ st_d(e, R_CTX, CTX_OUT, 2);
+ leave_(e);
+ ret(e);
+ /* flush() */
+ lbl_here(e, h->flush);
+ FRAME32(e);
+ ld_d(e, 7, R_CTX, CTX_OUT);
+ call_c(e, "fflush");
+ test_rr(e, 0, 0);
+ int fl = lbl_new(e);
+ jcc_l(e, 0x84, fl);
+ call_l(e, h->die_flushfail);
+ lbl_here(e, fl);
+ leave_(e);
+ ret(e);
+ /* seek(rdi = off) -> rax */
+ lbl_here(e, h->seek);
+ FRAME32(e);
+ st_d(e, 5, -16, 7); /* off */
+ st_zero64(e, 5, -24); /* any = 0 */
+ st_zero64(e, 5, -32); /* bad = 0 */
+ /* in stream */
+ ld_d(e, 7, R_CTX, CTX_IN);
+ libc_data(e, 1, "stdin");
+ ld_d(e, 2, 1, 0);
+ alu_rr(e, 7, 7, 2);
+ int k1 = lbl_new(e);
+ jcc_l(e, 0x84, k1); /* je skip */
+ ld_d(e, 7, R_CTX, CTX_IN);
+ ld_d(e, 6, 5, -16);
+ tc2(e, 0x31, 0xD2);
+ call_c(e, "fseek");
+ test_rr(e, 0, 0);
+ int kb1 = lbl_new(e);
+ jcc_l(e, 0x84, kb1);
+ mov_ri64(e, 0, 1);
+ st_d(e, 5, -32, 0);
+ lbl_here(e, kb1);
+ mov_ri64(e, 0, 1);
+ st_d(e, 5, -24, 0);
+ lbl_here(e, k1);
+ /* out stream */
+ ld_d(e, 7, R_CTX, CTX_OUT);
+ ld_d(e, 2, R_CTX, CTX_IN);
+ alu_rr(e, 7, 7, 2);
+ int k2 = lbl_new(e);
+ jcc_l(e, 0x84, k2); /* je (same file) */
+ libc_data(e, 1, "stdout");
+ ld_d(e, 2, 1, 0);
+ mov_rr(e, 7, 0); /* save out */ /* fix below */
+ ld_d(e, 7, R_CTX, CTX_OUT);
+ alu_rr(e, 7, 7, 2);
+ int k3 = lbl_new(e);
+ jcc_l(e, 0x84, k3); /* je stdout */
+ ld_d(e, 7, R_CTX, CTX_OUT);
+ ld_d(e, 6, 5, -16);
+ tc2(e, 0x31, 0xD2);
+ call_c(e, "fseek");
+ test_rr(e, 0, 0);
+ int kb2 = lbl_new(e);
+ jcc_l(e, 0x84, kb2);
+ mov_ri64(e, 0, 1);
+ st_d(e, 5, -32, 0);
+ lbl_here(e, kb2);
+ mov_ri64(e, 0, 1);
+ st_d(e, 5, -24, 0);
+ lbl_here(e, k3);
+ lbl_here(e, k2);
+ /* any? */
+ ld_d(e, 0, 5, -24);
+ test_rr(e, 0, 0);
+ int kany = lbl_new(e);
+ jcc_l(e, 0x85, kany);
+ call_l(e, h->die_seekstreams);
+ lbl_here(e, kany);
+ ld_d(e, 0, 5, -32);
+ test_rr(e, 0, 0);
+ int kbad = lbl_new(e);
+ jcc_l(e, 0x85, kbad); /* jnz -> bad */
+ tc2(e, 0x31, 0xC0);
+ leave_(e);
+ ret(e);
+ lbl_here(e, kbad);
+ mov_ri64(e, 0, (uint64_t)-1);
+ leave_(e);
+ ret(e);
+ /* system(rdi = cmd) -> rax */
+ lbl_here(e, h->system);
+ FRAME32(e);
+ call_c(e, "system");
+ movsxd_rr(e, 0, 0);
+ leave_(e);
+ ret(e);
+ /* stop(rdi = code) */
+ lbl_here(e, h->stop);
+ FRAME32(e);
+ mov_ri32(e, 6, 0xFF);
+ alu_rr(e, 4, 7, 6);
+ call_c(e, "exit");
+ /* spawn(rdi = CrFnRef*) */
+ lbl_here(e, h->spawn);
+ FRAME32(e);
+ st_d(e, 5, -16, 7);
+ mov32m(e, 1, 7, 8); /* ecx = np */
+ test_rr(e, 1, 1);
+ int sp1 = lbl_new(e);
+ jcc_l(e, 0x84, sp1);
+ call_l(e, h->die_spawnpar);
+ lbl_here(e, sp1);
+ movsx32m(e, 0, R_CTX, CTX_NSPAWN);
+ mov_ri32(e, 1, 256);
+ alu_rr(e, 7, 0, 1);
+ int sp2 = lbl_new(e);
+ jcc_l(e, 0x82, sp2); /* jb */
+ call_l(e, h->die_spawnmax);
+ lbl_here(e, sp2);
+ lea_s8(e, 7, R_CTX, 0, CTX_SPAWNED);
+ tc2(e, 0x31, 0xF6);
+ lea_rip_lbl(e, 2, h->thread_main);
+ ld_d(e, 1, 5, -16);
+ call_c(e, "pthread_create");
+ test_rr(e, 0, 0);
+ int sp3 = lbl_new(e);
+ jcc_l(e, 0x84, sp3);
+ call_l(e, h->die_threadfail);
+ lbl_here(e, sp3);
+ /* nspawn++ */
+ R(e, 0, 0, R_CTX); tc(e, 0xFF); MR(e, 2, 0, R_CTX); tc4u(e, CTX_NSPAWN);
+ leave_(e);
+ ret(e);
+ /* thread_main(rdi = ref): new thread root */
+ lbl_here(e, h->thread_main);
+ push_r(e, 5); mov_rr(e, 5, 4);
+ push_r(e, 3);
+ sub_rsp(e, 40);
+ mov_rr(e, 3, 7); /* rbx = ref */
+ mov_ri32(e, 7, CTX_SIZE);
+ call_c(e, "malloc");
+ test_rr(e, 0, 0);
+ int tm1 = lbl_new(e);
+ jcc_l(e, 0x85, tm1);
+ call_l(e, h->die_oom);
+ lbl_here(e, tm1);
+ mov_rr(e, R_CTX, 0);
+ mov_rr(e, 7, 0);
+ tc2(e, 0x31, 0xF6);
+ mov_ri32(e, 2, CTX_SIZE);
+ movsxd_rr(e, 2, 2);
+ call_c(e, "memset");
+ libc_data(e, 0, "stdin");
+ ld_d(e, 1, 0, 0);
+ st_d(e, R_CTX, CTX_IN, 1);
+ libc_data(e, 0, "stdout");
+ ld_d(e, 1, 0, 0);
+ st_d(e, R_CTX, CTX_OUT, 1);
+ ld_d(e, 0, 3, 0); /* rax = fn from ref */
+ R(e, 1, 0, 0); tc(e, 0xFF); MR(e, 3, 2, 0); /* call *rax */
+ /* lock incq done_ctr */
+ tc(e, 0xF0); R(e, 1, 0, 5); tc(e, 0xFF); MR(e, 0, 0, 5);
+ fix_add(e, 1, e->text.len, 0, SEG_DATA, h->done_ctr);
+ tc4u(e, 0);
+ mov_rr(e, 7, R_CTX);
+ call_c(e, "free");
+ tc2(e, 0x31, 0xC0);
+ add_rsp(e, 40);
+ pop_r(e, 3);
+ pop_r(e, 5);
+ ret(e);
+ /* wait() -> rax */
+ lbl_here(e, h->wait);
+ FRAME32(e);
+ int wspin = lbl_new(e);
+ lbl_here(e, wspin);
+ mov_rip(e, 0, SEG_DATA, h->done_ctr);
+ movsx32m(e, 2, R_CTX, CTX_NSPAWN);/* rdx = nspawn */
+ alu_rr(e, 7, 0, 2); /* cmp done, nspawn */
+ int wjoins = lbl_new(e);
+ jcc_l(e, 0x8D, wjoins); /* jge -> all done */
+ call_c(e, "sched_yield");
+ jmp_l(e, wspin);
+ lbl_here(e, wjoins);
+ tc2(e, 0x31, 0xC9); /* ecx = i */
+ int wloop = lbl_new(e), wdone = lbl_new(e);
+ lbl_here(e, wloop);
+ movsx32m(e, 0, R_CTX, CTX_NSPAWN);/* rax = nspawn */
+ mov_rr(e, 2, 0); /* rdx = nspawn */
+ alu_rr(e, 7, 1, 2); /* cmp rcx, rdx */
+ jcc_l(e, 0x8D, wdone); /* jge done */
+ mov_rr(e, 0, 1);
+ movsxd_rr(e, 0, 0);
+ ld_s8(e, 7, R_CTX, 0, CTX_SPAWNED);
+ tc2(e, 0x31, 0xF6);
+ call_c(e, "pthread_join");
+ R(e, 0, 0, 1); tc(e, 0xFF); MR(e, 3, 0, 1); /* inc ecx */
+ jmp_l(e, wloop);
+ lbl_here(e, wdone);
+ st32(e, R_CTX, CTX_NSPAWN, 0);
+ /* done_ctr = 0 */
+ tc2(e, 0x31, 0xC0);
+ R(e, 1, 0, 5); tc(e, 0x89); MR(e, 0, 0, 5);
+ fix_add(e, 1, e->text.len, 0, SEG_DATA, h->done_ctr);
+ tc4u(e, 0);
+ leave_(e);
+ ret(e);
+}
+
+static void h_all(XE* e, H* h) {
+ h_die(e, h);
+ h_die_range(e, h);
+ h_msg_dies(e, h);
+ h_stack(e, h);
+ h_focus(e, h);
+ h_heap(e, h);
+ h_conv(e, h);
+ h_out(e, h);
+ out_f_body(e, h, h->out_f64, 17, 0);
+ out_f_body(e, h, h->out_f32, 9, 1);
+ h_io(e, h);
+}
+
+/* ------------------------------------------------------------------ */
+/* codegen */
+/* ------------------------------------------------------------------ */
+
+typedef struct FnGen {
+ XE* e;
+ H* h;
+ ProgramIR* p;
+ FnIR* fn;
+ int release;
+ int* slot; /* local idx -> rbp offset (negative) */
+ int fh0off;
+ int epilogue;
+ Type* cur; /* static type of wv, tracked like be_c */
+ int lblbase; /* IR label ids are offset per function */
+ int fi; /* program fn index */
+ int* flabels; /* program fn idx -> text label */
+ size_t* glob_off; /* global idx -> .data/.bss offset (bss via data tail) */
+ size_t* str_off; /* string idx -> .data offset */
+ size_t* ref_off; /* fn idx -> fnref .data offset */
+ size_t ro_2p63;
+} FnGen;
+
+static uint64_t addr_elem_size(const Type* addr) {
+ const Type* pt = addr->inner;
+ if (pt->kind == TY_ARR) return type_size(pt->inner);
+ return type_size(pt);
+}
+
+static int ty_width(const Type* t) { return ty_int_bits(t); }
+
+/* truncate rax (64-bit) to width w with sign extension if signed */
+static void trunc_rax_w(FnGen* g, int w, int signed_) {
+ XE* e = g->e;
+ (void)signed_;
+ if (w >= 64) return;
+ if (w == 8) { R(e, 0, 0, 0); tc2(e, 0x0F, 0xBE); MR(e, 3, 0, 0); }
+ else if (w == 16) { R(e, 0, 0, 0); tc2(e, 0x0F, 0xBF); MR(e, 3, 0, 0); }
+ else if (w == 32) { R(e, 1, 0, 0); tc(e, 0x63); MR(e, 3, 0, 0); }
+ else { /* 1..7 bytes: mask */ }
+}
+static void truncu_rax_w(FnGen* g, int w) {
+ XE* e = g->e;
+ if (w >= 64) return;
+ if (w == 8) { R(e, 0, 0, 0); tc2(e, 0x0F, 0xB6); MR(e, 3, 0, 0); }
+ else if (w == 16) { R(e, 0, 0, 0); tc2(e, 0x0F, 0xB7); MR(e, 3, 0, 0); }
+ else if (w == 32) { R(e, 0, 0, 0); tc(e, 0x89); MR(e, 3, 0, 0); }
+ else { /* 1..7 bytes: mask */ }
+}
+
+/* rax (raw Val) -> typed store at [base+off] */
+static void store_typed(FnGen* g, int base, int32_t off, Type* t) {
+ XE* e = g->e;
+ switch (t->kind) {
+ case TY_BOOL: case TY_CHAR: case TY_INT8: case TY_UINT8:
+ st8(e, base, off, 0); break;
+ case TY_INT16: case TY_UINT16:
+ st16(e, base, off, 0); break;
+ case TY_INT32: case TY_UINT32:
+ st32(e, base, off, 0); break;
+ case TY_FLOAT32:
+ /* rax holds double bits; narrow */
+ movq_xr(e, 0, 0);
+ cvtsd2ss(e, 0, 0);
+ tc(e, 0xF3); R(e, 0, 0, base); tc2(e, 0x0F, 0x11); MR(e, 2, 0, base); tc4u(e, (uint32_t)off);
+ break;
+ case TY_FLOAT64:
+ movq_xr(e, 0, 0);
+ tc(e, 0xF2); R(e, 0, 0, base); tc2(e, 0x0F, 0x11); MR(e, 2, 0, base); tc4u(e, (uint32_t)off);
+ break;
+ default:
+ st_d(e, base, off, 0); break; /* 64-bit ints, addr, func */
+ }
+}
+
+/* typed load [base+off] -> r15 (floats widened to double bits) */
+static void load_typed(FnGen* g, int base, int32_t off, Type* t) {
+ XE* e = g->e;
+ switch (t->kind) {
+ case TY_INT8: movsx8(e, R_WV, base, off); break;
+ case TY_INT16: movsx16(e, R_WV, base, off); break;
+ case TY_INT32: movsx32m(e, R_WV, base, off); break;
+ case TY_BOOL: case TY_CHAR: case TY_UINT8: movzx8(e, R_WV, base, off); break;
+ case TY_UINT16: movzx16(e, R_WV, base, off); break;
+ case TY_UINT32: mov32m(e, R_WV, base, off); break;
+ case TY_FLOAT32:
+ tc(e, 0xF3); R(e, 0, R_WV, base); tc2(e, 0x0F, 0x10); MR(e, 2, R_WV, base); tc4u(e, (uint32_t)off);
+ cvtss2sd(e, R_WV, R_WV);
+ movq_rx(e, R_WV, R_WV);
+ break;
+ case TY_FLOAT64:
+ tc(e, 0xF2); R(e, 0, R_WV, base); tc2(e, 0x0F, 0x10); MR(e, 2, R_WV, base); tc4u(e, (uint32_t)off);
+ movq_rx(e, R_WV, R_WV);
+ break;
+ default:
+ ld_d(e, R_WV, base, off); break;
+ }
+}
+
+/* truncate r15 in place to integer type t (wrap), like (T) in be_c */
+static void wrap_wv(FnGen* g, Type* t) {
+ XE* e = g->e;
+ int w = ty_int_bits(t);
+ if (w == 64) return;
+ int signed_ = ty_is_signed_int(t);
+ if (w == 8) {
+ if (signed_) { R(e, 1, R_WV, R_WV); tc2(e, 0x0F, 0xBE); MR(e, 3, R_WV, R_WV); }
+ else { R(e, 0, R_WV, R_WV); tc2(e, 0x0F, 0xB6); MR(e, 3, R_WV, R_WV); }
+ } else if (w == 16) {
+ if (signed_) { R(e, 1, R_WV, R_WV); tc2(e, 0x0F, 0xBF); MR(e, 3, R_WV, R_WV); }
+ else { R(e, 0, R_WV, R_WV); tc2(e, 0x0F, 0xB7); MR(e, 3, R_WV, R_WV); }
+ } else if (w == 32) {
+ if (signed_) { R(e, 1, R_WV, R_WV); tc(e, 0x63); MR(e, 3, R_WV, R_WV); }
+ else { R(e, 0, R_WV, R_WV); tc(e, 0x89); MR(e, 3, R_WV, R_WV); }
+ }
+}
+
+static void load_b_reg(FnGen* g, Insn* in, int isfloat) {
+ /* operand b: imm (IV_*) or popped; lands in rbx */
+ XE* e = g->e;
+ if (in->b == 1) {
+ uint64_t bits;
+ if (in->imm.kind == IV_FLOAT) {
+ double d = in->imm.d;
+ memcpy(&bits, &d, 8);
+ } else if (isfloat) {
+ double d = in->imm.kind == IV_INT ? (double)(int64_t)in->imm.u : (double)in->imm.u;
+ memcpy(&bits, &d, 8);
+ } else {
+ bits = in->imm.u;
+ }
+ if (bits <= 0xFFFFFFFFULL) mov_ri32(e, 3, (int32_t)bits);
+ else mov_ri64(e, 3, bits);
+ } else {
+ call_l(e, g->h->pop);
+ mov_rr(e, 3, 0);
+ }
+}
+
+/* comparison state -> r12d from flags of cmp r15,rax (signed) etc. */
+static void cmp_combine_signed(FnGen* g) {
+ XE* e = g->e;
+ setcc_r(e, 0x0C, 1); /* setl cl (wv < b) */
+ setcc_r(e, 0x0F, 2); /* setg dl (wv > b) */
+ mov_rr(e, R_CMP, 2);
+ R(e, 0, 0, R_CMP); tc(e, 0x29); MR(e, 3, 1, R_CMP); /* sub r12d, ecx */
+}
+static void cmp_combine_unsigned(FnGen* g) {
+ XE* e = g->e;
+ setcc_r(e, 0x02, 1); /* setb cl */
+ setcc_r(e, 0x07, 2); /* seta dl */
+ mov_rr(e, R_CMP, 2);
+ R(e, 0, 0, R_CMP); tc(e, 0x29); MR(e, 3, 1, R_CMP);
+}
+
+static double imm_f64(ImmV* imm) {
+ if (imm->kind == IV_FLOAT) return imm->d;
+ if (imm->kind == IV_INT) return (double)(int64_t)imm->u;
+ return (double)imm->u;
+}
+
+/* ------------------------------------------------------------------ */
+/* per-instruction codegen */
+/* ------------------------------------------------------------------ */
+
+static void gen_insn(FnGen* g, Insn* in) {
+ XE* e = g->e;
+ H* h = g->h;
+ FnIR* fn = g->fn;
+ switch (in->op) {
+ case I_LBL:
+ lbl_here(e, in->a + g->lblbase);
+ return;
+ case I_JMP:
+ jmp_l(e, in->a + g->lblbase);
+ return;
+ case I_JMPCOND: {
+ int cc = in->b == CMP_LESS ? 0x8C : in->b == CMP_EQUAL ? 0x84 :
+ in->b == CMP_GREATER ? 0x8F : in->b == CMP_DIFFERENT ? 0x85 :
+ in->b == CMP_LESS_OR_EQUAL ? 0x8E : 0x8D;
+ /* test r12d, r12d; jcc */
+ tc3(e, 0x45, 0x85, 0xE4);
+ jcc_l(e, cc, in->a + g->lblbase);
+ return;
+ }
+ case I_MOVE_LOCAL:
+ ld_d(e, 0, 5, g->fh0off);
+ st_d(e, R_CTX, CTX_FHSP, 0);
+ lea_rd(e, R_F, 5, g->slot[in->a]);
+ return;
+ case I_MOVE_GLOBAL:
+ ld_d(e, 0, 5, g->fh0off);
+ st_d(e, R_CTX, CTX_FHSP, 0);
+ lea_rip(e, R_F, SEG_DATA, g->glob_off[in->a]);
+ return;
+ case I_MOVE_LIT: {
+ uint64_t esz = type_size(in->t);
+ if (in->b < 0 && !g->release) {
+ mov_rr(e, 7, R_F);
+ mov_ri64(e, 6, in->imm.u);
+ mov_ri32(e, 2, (int32_t)esz);
+ call_l(e, h->chk_block);
+ }
+ mov_rr(e, 7, R_F);
+ call_l(e, h->fpush);
+ lea_rd(e, R_F, R_F, (int32_t)(in->imm.u * esz));
+ return;
+ }
+ case I_MOVE_INDEX: {
+ uint64_t esz = type_size(in->t);
+ if (in->b > 0 && !g->release) {
+ mov_rr(e, 7, R_WV);
+ mov_ri32(e, 6, in->b);
+ call_l(e, h->chk_range);
+ } else if (in->b < 0 && !g->release) {
+ mov_rr(e, 7, R_F);
+ mov_rr(e, 6, R_WV);
+ mov_ri32(e, 2, (int32_t)esz);
+ call_l(e, h->chk_block);
+ }
+ mov_rr(e, 7, R_F);
+ call_l(e, h->fpush);
+ imul_ri(e, 1, R_WV, (int32_t)esz);
+ R(e, 1, 0, R_F); tc(e, 0x01); MR(e, 3, 1, R_F); /* add r13, rcx */
+ return;
+ }
+ case I_MOVE_AT:
+ mov_rr(e, 7, R_F);
+ call_l(e, h->fpush);
+ mov_rr(e, R_F, R_WV);
+ return;
+ case I_BACK: {
+ ld_d(e, 0, R_CTX, CTX_FHSP);
+ ld_d(e, 1, 5, g->fh0off);
+ alu_rr(e, 7, 0, 1); /* cmp fh_sp, fh0 */
+ int noback = lbl_new(e), done = lbl_new(e);
+ jcc_l(e, 0x86, noback); /* jbe */
+ call_l(e, h->fpop);
+ mov_rr(e, R_F, 0);
+ jmp_l(e, done);
+ lbl_here(e, noback);
+ xor_rr(e, R_F, R_F);
+ lbl_here(e, done);
+ return;
+ }
+ case I_HOME:
+ ld_d(e, 0, 5, g->fh0off);
+ st_d(e, R_CTX, CTX_FHSP, 0);
+ xor_rr(e, R_F, R_F);
+ return;
+ case I_ADDR_HERE:
+ mov_rr(e, R_WV, R_F);
+ g->cur = in->t;
+ return;
+ case I_ADDR_LOCAL:
+ lea_rd(e, R_WV, 5, g->slot[in->a]);
+ g->cur = in->t;
+ return;
+ case I_ADDR_GLOBAL:
+ lea_rip(e, R_WV, SEG_DATA, g->glob_off[in->a]);
+ g->cur = in->t;
+ return;
+ case I_ADDR_FN:
+ lea_rip(e, R_WV, SEG_DATA, g->ref_off[in->a]);
+ g->cur = ty_func();
+ return;
+ case I_MAKE_LOCAL: {
+ Type* t = fn->locals[in->a].type;
+ int off = g->slot[in->a];
+ if (t->kind == TY_ARR) {
+ lea_rd(e, 7, 5, off);
+ tc2(e, 0x31, 0xF6);
+ mov_ri32(e, 2, (int32_t)type_size(t));
+ movsxd_rr(e, 2, 2);
+ call_c(e, "memset");
+ } else {
+ st_zero64(e, 5, off);
+ }
+ return;
+ }
+ case I_MAKE_ANON:
+ mov_ri32(e, 7, (int32_t)type_size(in->t));
+ movsxd_rr(e, 7, 7);
+ call_l(e, h->alloc);
+ mov_rr(e, R_WV, 0);
+ g->cur = ty_addr_to(in->t);
+ return;
+ case I_MAKE_DYN: {
+ uint64_t esz = type_size(in->t);
+ uint64_t maxn = ((uint64_t)1 << 44) / (esz ? esz : 1);
+ if (in->b == 1) {
+ uint64_t n = in->imm.u;
+ if (n > maxn) { /* compile-time provable */ }
+ mov_ri32(e, 7, (int32_t)n);
+ movsxd_rr(e, 7, 7);
+ } else if (in->b == 0) {
+ Type* st = fn->locals[in->a].type;
+ load_typed(g, 5, g->slot[in->a], st);
+ mov_rr(e, 7, R_WV);
+ movsxd_rr(e, 7, 7);
+ } else {
+ Type* gt = g->p->globs[in->a].type;
+ lea_rip(e, 0, SEG_DATA, g->glob_off[in->a]);
+ load_typed(g, 0, 0, gt);
+ mov_rr(e, 7, R_WV);
+ }
+ mov_ri64(e, 6, maxn);
+ alu_rr(e, 7, 7, 6); /* cmp n, maxn */
+ int dynok = lbl_new(e);
+ jcc_l(e, 0x86, dynok); /* jbe */
+ call_l(e, h->die_toolarge);
+ lbl_here(e, dynok);
+ imul_ri(e, 7, 7, (int32_t)esz);
+ call_l(e, h->alloc);
+ mov_rr(e, R_WV, 0);
+ g->cur = ty_addr_to(ty_array(in->t, 0));
+ return;
+ }
+ case I_MAKE_STRLOCAL: {
+ LocalIR* l = &fn->locals[in->a];
+ size_t sl = l->str_initlen ? l->str_initlen : 1;
+ size_t soff = ro_put(e, l->str_init ? l->str_init : "", sl);
+ lea_rd(e, 7, 5, g->slot[in->a]);
+ lea_rip(e, 6, SEG_RO, soff);
+ mov_ri32(e, 2, (int32_t)sl);
+ movsxd_rr(e, 2, 2);
+ call_c(e, "memcpy");
+ return;
+ }
+ case I_MAKE_STRANON:
+ lea_rip(e, R_WV, SEG_DATA, g->str_off[in->a]);
+ g->cur = in->t;
+ return;
+ case I_BIND_LOCAL:
+ st_d(e, 5, g->slot[in->a], R_WV);
+ return;
+ case I_FREE:
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->free_);
+ return;
+ case I_LOAD:
+ load_typed(g, R_F, 0, in->t);
+ g->cur = in->t;
+ return;
+ case I_STORE: {
+ Type* t = in->t;
+ switch (in->b) {
+ case SC_EXACT:
+ if (t->kind == TY_FLOAT32) {
+ movq_xr(e, 0, R_WV);
+ cvtsd2ss(e, 0, 0);
+ tc(e, 0xF3); R(e, 0, 0, R_F); tc2(e, 0x0F, 0x11); MR(e, 2, 0, R_F); tc4u(e, 0);
+ } else if (t->kind == TY_FLOAT64) {
+ movq_xr(e, 0, R_WV);
+ tc(e, 0xF2); R(e, 0, 0, R_F); tc2(e, 0x0F, 0x11); MR(e, 2, 0, R_F); tc4u(e, 0);
+ } else {
+ mov_rr(e, 0, R_WV);
+ store_typed(g, R_F, 0, t);
+ }
+ break;
+ case SC_INTLIT:
+ mov_rr(e, 0, R_WV);
+ store_typed(g, R_F, 0, t);
+ break;
+ case SC_INTLIT_BOOL:
+ st8(e, R_F, 0, R_WV);
+ break;
+ case SC_INTLIT_FLOAT:
+ movq_xr(e, 0, R_WV);
+ if (t->kind == TY_FLOAT32) {
+ cvtsd2ss(e, 0, 0);
+ tc(e, 0xF3); R(e, 0, 0, R_F); tc2(e, 0x0F, 0x11); MR(e, 2, 0, R_F); tc4u(e, 0);
+ } else {
+ tc(e, 0xF2); R(e, 0, 0, R_F); tc2(e, 0x0F, 0x11); MR(e, 2, 0, R_F); tc4u(e, 0);
+ }
+ break;
+ case SC_F32:
+ movq_xr(e, 0, R_WV);
+ cvtsd2ss(e, 0, 0);
+ tc(e, 0xF3); R(e, 0, 0, R_F); tc2(e, 0x0F, 0x11); MR(e, 2, 0, R_F); tc4u(e, 0);
+ break;
+ }
+ return;
+ }
+ case I_SET: {
+ if (in->imm.kind == IV_FLOAT) {
+ double d = in->imm.d;
+ uint64_t bits;
+ memcpy(&bits, &d, 8);
+ mov_ri64(e, R_WV, bits);
+ } else if (in->imm.u <= 0xFFFFFFFFULL) {
+ mov_ri32(e, R_WV, (int32_t)in->imm.u);
+ } else {
+ mov_ri64(e, R_WV, in->imm.u);
+ }
+ g->cur = in->t;
+ return;
+ }
+ case I_ADD: case I_SUB: case I_MUL: case I_DIV: case I_MOD: {
+ Type* t = in->t;
+ int isfloat = ty_is_float(t);
+ int opext = in->op == I_ADD ? 0 : in->op == I_SUB ? 5 :
+ in->op == I_MUL ? -1 : 7; /* mul/div/mod special */
+ if (isfloat) {
+ load_b_reg(g, in, 1); /* rbx = b bits */
+ movq_xr(e, 0, R_WV);
+ movq_xr(e, 1, 3);
+ if (in->op == I_ADD) addsd(e, 0, 1);
+ else if (in->op == I_SUB) subsd(e, 0, 1);
+ else if (in->op == I_MUL) mulsd(e, 0, 1);
+ else if (in->op == I_DIV) divsd(e, 0, 1);
+ else {
+ /* fmod(x, y) */
+ call_c(e, "fmod");
+ }
+ movq_rx(e, R_WV, 0);
+ if (t->kind == TY_FLOAT32) {
+ movq_xr(e, 0, R_WV);
+ cvtsd2ss(e, 0, 0);
+ cvtss2sd(e, 0, 0);
+ movq_rx(e, R_WV, 0);
+ }
+ g->cur = t;
+ return;
+ }
+ if (t->kind == TY_ADDR) {
+ uint64_t esz = addr_elem_size(t);
+ load_b_reg(g, in, 0);
+ imul_ri(e, 1, 3, (int32_t)esz);
+ if (in->op == I_ADD) R(e, 1, 0, R_WV), tc(e, 0x01), MR(e, 3, 1, R_WV);
+ else R(e, 1, 0, R_WV), tc(e, 0x29), MR(e, 3, 1, R_WV);
+ g->cur = t;
+ return;
+ }
+ if (in->a == 1 && in->op == I_SUB) {
+ /* pointer difference */
+ call_l(e, h->pop);
+ R(e, 1, 0, R_WV); tc(e, 0x29); MR(e, 3, 1, R_WV); /* sub wv, popped */
+ mov_ri32(e, 1, (int32_t)addr_elem_size(g->cur));
+ cqo(e);
+ idiv_r(e, 1);
+ mov_rr(e, R_WV, 0);
+ g->cur = ty_int(64);
+ return;
+ }
+ load_b_reg(g, in, 0);
+ if (in->op == I_DIV || in->op == I_MOD) {
+ test_rr(e, 3, 3);
+ int nz = lbl_new(e);
+ jcc_l(e, 0x85, nz);
+ call_l(e, h->die_div0);
+ lbl_here(e, nz);
+ int w = ty_int_bits(t);
+ if (ty_is_signed_int(t) || t->kind == TY_INTLIT) {
+ (void)w;
+ mov_ri64(e, 1, (uint64_t)-1);
+ alu_rr(e, 7, 3, 1); /* cmp rbx, -1 */
+ int norm = lbl_new(e), spec = lbl_new(e), done = lbl_new(e);
+ jcc_l(e, 0x85, norm);
+ lbl_here(e, spec);
+ if (in->op == I_DIV) { mov_rr(e, 0, R_WV); neg_r(e, 0); }
+ else tc2(e, 0x31, 0xC0);
+ jmp_l(e, done);
+ lbl_here(e, norm);
+ mov_rr(e, 0, R_WV);
+ cqo(e);
+ idiv_r(e, 3);
+ if (in->op == I_MOD) { R(e, 1, 0, 2); tc(e, 0x89); MR(e, 3, 2, 0); } /* rax = rdx */
+ lbl_here(e, done);
+ } else {
+ mov_rr(e, 0, R_WV);
+ tc2(e, 0x31, 0xD2);
+ idiv_r(e, 3);
+ if (in->op == I_MOD) { R(e, 1, 0, 2); tc(e, 0x89); MR(e, 3, 2, 0); }
+ }
+ mov_rr(e, R_WV, 0);
+ wrap_wv(g, t);
+ g->cur = t;
+ return;
+ }
+ /* add/sub/mul */
+ mov_rr(e, 0, R_WV);
+ if (in->op == I_MUL) imul_r(e, 0, 3);
+ else if (in->op == I_ADD) R(e, 1, 3, 0), tc(e, 0x01), MR(e, 3, 3, 0);
+ else R(e, 1, 3, 0), tc(e, 0x29), MR(e, 3, 3, 0);
+ mov_rr(e, R_WV, 0);
+ wrap_wv(g, t);
+ g->cur = t;
+ (void)opext;
+ return;
+ }
+ case I_NEG: {
+ Type* t = in->t;
+ if (ty_is_float(t)) {
+ mov_ri64(e, 0, 0x8000000000000000ULL);
+ alu_rr(e, 6, R_WV, 0);
+ } else {
+ mov_rr(e, 0, R_WV);
+ neg_r(e, 0);
+ mov_rr(e, R_WV, 0);
+ wrap_wv(g, t);
+ }
+ return;
+ }
+ case I_PUSH:
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->push);
+ return;
+ case I_POP:
+ call_l(e, h->pop);
+ mov_rr(e, R_WV, 0);
+ g->cur = in->t;
+ return;
+ case I_SWAP:
+ call_l(e, h->pop);
+ mov_rr(e, 3, 0);
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->push);
+ mov_rr(e, R_WV, 3);
+ return;
+ case I_CMP: {
+ Type* t = in->t;
+ if (ty_is_float(t)) {
+ load_b_reg(g, in, 1);
+ movq_xr(e, 0, R_WV);
+ movq_xr(e, 1, 3);
+ tc2(e, 0x31, 0xC9); /* zero before flags use */
+ tc2(e, 0x31, 0xD2);
+ ucomisd(e, 0, 1);
+ int is_nan = lbl_new(e), fin = lbl_new(e);
+ jcc_l(e, 0x8A, is_nan); /* jp: NaN */
+ setcc_r(e, 0x02, 1); /* setb cl: a < b */
+ setcc_r(e, 0x07, 2); /* seta dl: a > b */
+ mov_rr(e, R_CMP, 2);
+ R(e, 0, 0, R_CMP); tc(e, 0x29); MR(e, 3, 1, R_CMP);
+ jmp_l(e, fin);
+ lbl_here(e, is_nan);
+ R(e, 0, 0, R_CMP); tc(e, 0x31); MR(e, 3, R_CMP, R_CMP);
+ lbl_here(e, fin);
+ } else if (ty_is_signed_int(t) || t->kind == TY_INTLIT) {
+ load_b_reg(g, in, 0);
+ mov_rr(e, 0, 3);
+ tc2(e, 0x31, 0xC9); /* xor ecx,ecx */
+ tc2(e, 0x31, 0xD2); /* xor edx,edx */
+ alu_rr(e, 7, R_WV, 0); /* cmp wv, b */
+ cmp_combine_signed(g);
+ } else {
+ load_b_reg(g, in, 0);
+ mov_rr(e, 0, 3);
+ tc2(e, 0x31, 0xC9);
+ tc2(e, 0x31, 0xD2);
+ alu_rr(e, 7, R_WV, 0);
+ cmp_combine_unsigned(g);
+ }
+ return;
+ }
+ case I_CONV: {
+ Type* from = in->u ? in->u : g->cur;
+ Type* to = in->t;
+ if (ty_is_float(from)) {
+ if (to->kind == TY_FLOAT64) {
+ /* nothing */
+ } else if (to->kind == TY_FLOAT32) {
+ movq_xr(e, 0, R_WV);
+ cvtsd2ss(e, 0, 0);
+ cvtss2sd(e, 0, 0);
+ movq_rx(e, R_WV, 0);
+ } else if (to->kind == TY_BOOL) {
+ movq_xr(e, 0, R_WV);
+ pxor(e, 1, 1);
+ ucomisd(e, 0, 1);
+ int isnan2 = lbl_new(e), fin2 = lbl_new(e);
+ jcc_l(e, 0x8A, isnan2);
+ setcc_r(e, 0x05, R_WV); /* setne r15d */
+ jmp_l(e, fin2);
+ lbl_here(e, isnan2);
+ mov_ri32(e, R_WV, 1);
+ lbl_here(e, fin2);
+ } else {
+ int hid = to->kind == TY_INT8 ? h->d2i8 : to->kind == TY_INT16 ? h->d2i16 :
+ to->kind == TY_INT32 ? h->d2i32 : to->kind == TY_UINT8 ? h->d2u8 :
+ to->kind == TY_UINT16 ? h->d2u16 : to->kind == TY_UINT32 ? h->d2u32 :
+ to->kind == TY_UINT64 ? h->d2u64 : h->d2i64;
+ movq_xr(e, 0, R_WV);
+ call_l(e, hid);
+ mov_rr(e, R_WV, 0);
+ }
+ } else if (to->kind == TY_FLOAT64 || to->kind == TY_FLOAT32) {
+ int was_signed = (from->kind == TY_INTLIT) || ty_is_signed_int(from);
+ if (was_signed) {
+ cvtsi2sd(e, 0, R_WV);
+ } else {
+ /* unsigned u64 -> double */
+ mov_rr(e, 0, R_WV);
+ R(e, 1, 0, 0); tc(e, 0x85); MR(e, 3, 0, 0); /* test rax,rax */
+ int negf = lbl_new(e), posf = lbl_new(e);
+ jcc_l(e, 0x89, negf); /* jns */
+ cvtsi2sd(e, 0, 0);
+ jmp_l(e, posf);
+ lbl_here(e, negf);
+ mov_rr(e, 0, R_WV);
+ cvtsi2sd(e, 0, 0);
+ movsd_rip(e, 1, SEG_RO, g->ro_2p63);
+ addsd(e, 0, 1);
+ lbl_here(e, posf);
+ }
+ if (to->kind == TY_FLOAT32) {
+ cvtsd2ss(e, 0, 0);
+ cvtss2sd(e, 0, 0);
+ }
+ movq_rx(e, R_WV, 0);
+ } else if (to->kind == TY_BOOL) {
+ test_rr(e, R_WV, R_WV);
+ setcc_r(e, 0x05, R_WV);
+ } else {
+ /* int -> int: truncate and extend per target */
+ if (to->kind == TY_UINT64 || to->kind == TY_ADDR) {
+ /* raw */
+ } else {
+ wrap_wv(g, to);
+ }
+ }
+ g->cur = to;
+ return;
+ }
+ case I_CALL: {
+ FnIR* callee = &g->p->fns[in->a];
+ int np = callee->nparams;
+ static const int argregs[6] = {7, 6, 2, 1, 8, 9};
+ if (np > 0) {
+ /* r11 = stk + (sp - np) * 8 */
+ ld_d(e, 1, R_CTX, CTX_STK); /* r11 = stk */
+ ld_d(e, 0, R_CTX, CTX_SP); /* rax = sp */
+ mov_ri32(e, 2, np);
+ movsxd_rr(e, 2, 2); /* rdx = np */
+ R(e, 1, 2, 0); tc(e, 0x29); MR(e, 3, 2, 0); /* sub rax, rdx */
+ lea_s8(e, 1, 1, 0, 0); /* r11 = r11 + rax*8 */
+ for (int k = 0; k < np && k < 6; k++)
+ ld_d(e, argregs[k], 1, 8 * k);
+ for (int k = 6; k < np; k++) {
+ ld_d(e, 0, 1, 8 * k);
+ R(e, 1, 0, 4); tc(e, 0x89); MR(e, 2, 0, 4);
+ tc(e, 0x24); tc4u(e, (uint32_t)(8 * (k - 6)));
+ }
+ mov_ri32(e, 0, np);
+ movsxd_rr(e, 0, 0);
+ R(e, 1, 0, R_CTX); tc(e, 0x29); MR(e, 2, 0, R_CTX); tc4u(e, CTX_SP);
+ }
+ call_l(e, g->flabels[in->a]);
+ if (callee->result->kind != TY_NOTHING) {
+ mov_rr(e, R_WV, 0);
+ g->cur = callee->result;
+ }
+ return;
+ }
+ case I_CALLIND:
+ mov_rr(e, 3, R_WV);
+ mov32m(e, 1, 3, 8);
+ test_rr(e, 1, 1);
+ int okind = lbl_new(e);
+ jcc_l(e, 0x84, okind);
+ call_l(e, h->die_indcall);
+ lbl_here(e, okind);
+ ld_d(e, 0, 3, 0); /* rax = fn pointer */
+ R(e, 1, 0, 0); tc(e, 0xFF); MR(e, 3, 2, 0); /* call *rax */
+ return;
+ case I_RET:
+ jmp_l(e, g->epilogue);
+ return;
+ case I_STOP: {
+ if (ty_is_float(g->cur)) {
+ movq_xr(e, 0, R_WV);
+ call_l(e, h->d2i64);
+ mov_rr(e, 7, 0);
+ } else if (g->cur->kind == TY_NOTHING || g->cur->kind == TY_FUNC) {
+ tc2(e, 0x31, 0xFF);
+ } else {
+ mov_rr(e, 7, R_WV);
+ }
+ call_l(e, h->stop);
+ return;
+ }
+ case I_INPUT:
+ call_l(e, h->input);
+ mov_rr(e, R_WV, 0);
+ g->cur = ty_int(64);
+ return;
+ case I_OUTPUT: {
+ Type* t = in->t;
+ if (ty_is_float(t)) {
+ movq_xr(e, 0, R_WV);
+ call_l(e, t->kind == TY_FLOAT32 ? h->out_f32 : h->out_f64);
+ } else if (t->kind == TY_CHAR || t->kind == TY_UINT8) {
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->out_char);
+ } else if (t->kind == TY_BOOL) {
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->out_bool);
+ } else if (t->kind == TY_ADDR) {
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->out_addr);
+ } else if (t->kind == TY_INTLIT) {
+ if (!t->litneg && t->litval > (uint64_t)INT64_MAX) {
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->out_u64);
+ } else {
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->out_i64);
+ }
+ } else if (ty_is_signed_int(t)) {
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->out_i64);
+ } else {
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->out_u64);
+ }
+ return;
+ }
+ case I_OUTTEXT:
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->out_text);
+ return;
+ case I_OPEN:
+ mov_rr(e, 7, R_WV);
+ mov_ri32(e, 6, in->b);
+ call_l(e, h->open);
+ mov_rr(e, R_WV, 0);
+ g->cur = ty_int(64);
+ return;
+ case I_CLOSE:
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->close);
+ mov_rr(e, R_WV, 0);
+ g->cur = ty_int(64);
+ return;
+ case I_USE:
+ if (in->b == 1) mov_ri32(e, 7, (int32_t)in->imm.u);
+ else mov_rr(e, 7, R_WV);
+ call_l(e, h->use);
+ return;
+ case I_FLUSH:
+ call_l(e, h->flush);
+ return;
+ case I_SEEK:
+ if (in->b == 1) mov_ri32(e, 7, (int32_t)in->imm.u);
+ else mov_rr(e, 7, R_WV);
+ call_l(e, h->seek);
+ mov_rr(e, R_WV, 0);
+ g->cur = ty_int(64);
+ return;
+ case I_SYSTEM:
+ mov_rr(e, 7, R_WV);
+ call_l(e, h->system);
+ mov_rr(e, R_WV, 0);
+ g->cur = ty_int(64);
+ return;
+ case I_SPAWN:
+ lea_rip(e, 7, SEG_DATA, g->ref_off[in->a]);
+ call_l(e, h->spawn);
+ return;
+ case I_WAIT:
+ call_l(e, h->wait);
+ mov_rr(e, R_WV, 0);
+ g->cur = ty_int(64);
+ return;
+ case I_XCHG: {
+ Type* t = in->t;
+ if (t->kind == TY_FLOAT64) {
+ mov_rr(e, 0, R_WV);
+ lock_xchg(e, R_F, 0, 0, 8);
+ mov_rr(e, R_WV, 0);
+ } else if (t->kind == TY_FLOAT32) {
+ movq_xr(e, 0, R_WV);
+ cvtsd2ss(e, 0, 0);
+ movd_eax_xmm0(e);
+ lock_xchg(e, R_F, 0, 0, 4);
+ movd_xmm0_eax(e);
+ cvtss2sd(e, 0, 0);
+ movq_rx(e, R_WV, 0);
+ } else {
+ int w = ty_int_bits(t);
+ int signed_ = ty_is_signed_int(t);
+ if (w == 64) {
+ mov_rr(e, 0, R_WV);
+ lock_xchg(e, R_F, 0, 0, 8);
+ mov_rr(e, R_WV, 0);
+ } else if (w == 32) {
+ mov_rr(e, 0, R_WV);
+ lock_xchg(e, R_F, 0, 0, 4);
+ if (signed_) movsxd_rr(e, R_WV, 0);
+ else { R(e, 0, 0, 0); tc(e, 0x89); MR(e, 3, 0, R_WV); mov_rr(e, R_WV, 0); }
+ } else if (w == 16) {
+ mov_rr(e, 0, R_WV);
+ lock_xchg(e, R_F, 0, 0, 2);
+ if (signed_) movsx_ax(e, 0);
+ else movzx_ax(e, 0);
+ mov_rr(e, R_WV, 0);
+ } else {
+ mov_rr(e, 0, R_WV);
+ lock_xchg(e, R_F, 0, 0, 1);
+ if (signed_) movsx_al(e, 0);
+ else movzx_al(e, 0);
+ mov_rr(e, R_WV, 0);
+ }
+ }
+ g->cur = in->t;
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* function generation */
+/* ------------------------------------------------------------------ */
+
+static void gen_fn(FnGen* g) {
+ XE* e = g->e;
+ FnIR* fn = g->fn;
+ static const int argregs[6] = {7, 6, 2, 1, 8, 9};
+
+ /* frame layout: [rbp-8..-40] saved r12,r13,r14,r15,rbx
+ locals below that, then fh0 slot; framesize ≡ 8 (mod 16) */
+ int cur = 48;
+ g->slot = xmalloc((size_t)(fn->nlocals ? fn->nlocals : 1) * sizeof(int));
+ int maxout = 0;
+ for (int i = 0; i < fn->nins; i++)
+ if (fn->ins[i].op == I_CALL) {
+ int np = g->p->fns[fn->ins[i].a].nparams;
+ if (np - 6 > maxout) maxout = np - 6;
+ }
+ int outgoing = maxout > 0 ? 8 * maxout : 0;
+ for (int i = 0; i < fn->nlocals; i++) {
+ Type* t = fn->locals[i].type;
+ uint64_t sz = t->kind == TY_ARR ? type_size(t) : 8;
+ sz = (sz + 7) & ~(uint64_t)7; /* 8-aligned slots */
+ cur += (int)sz;
+ g->slot[i] = -cur;
+ }
+ cur += 8;
+ g->fh0off = -cur;
+ int frame = cur + outgoing;
+ while (frame % 16 != 8) frame += 8;
+
+ lbl_here(e, g->flabels[g->fi]);
+ push_r(e, 5); mov_rr(e, 5, 4);
+ push_r(e, R_CMP); push_r(e, R_F); push_r(e, R_CTX); push_r(e, R_WV); push_r(e, 3);
+ sub_rsp(e, frame);
+
+ /* spill params (typed truncation like the C backend) */
+ for (int k = 0; k < fn->nparams; k++) {
+ Type* t = fn->locals[k].type;
+ if (k < 6) {
+ mov_rr(e, 0, argregs[k]);
+ store_typed(g, 5, g->slot[k], t);
+ } else {
+ ld_d(e, 0, 5, 16 + 8 * (k - 6));
+ store_typed(g, 5, g->slot[k], t);
+ }
+ }
+ /* fh0 = ctx->fh_sp */
+ ld_d(e, 0, R_CTX, CTX_FHSP);
+ st_d(e, 5, g->fh0off, 0);
+ xor_rr(e, R_F, R_F);
+
+ g->cur = ty_nothing();
+ g->epilogue = lbl_new(e);
+ for (int i = 0; i < fn->nins; i++)
+ gen_insn(g, &fn->ins[i]);
+
+ lbl_here(e, g->epilogue);
+ mov_rr(e, 0, R_WV);
+ ld_d(e, 1, 5, g->fh0off);
+ st_d(e, R_CTX, CTX_FHSP, 1);
+ add_rsp(e, frame);
+ pop_r(e, 3); pop_r(e, R_WV); pop_r(e, R_CTX); pop_r(e, R_F); pop_r(e, R_CMP);
+ pop_r(e, 5);
+ ret(e);
+ free(g->slot);
+}
+
+/* ------------------------------------------------------------------ */
+/* program assembly + entry */
+/* ------------------------------------------------------------------ */
+
+static void emit_entry(XE* e, H* h, int main_lbl) {
+ /* entry_main: libc main(argc, argv) */
+ int entry = lbl_new(e);
+ lbl_here(e, entry);
+ push_r(e, 5); mov_rr(e, 5, 4);
+ push_r(e, R_CMP); push_r(e, R_F); push_r(e, R_CTX); push_r(e, R_WV); push_r(e, 3);
+ sub_rsp(e, 56);
+ mov_ri32(e, 7, CTX_SIZE);
+ movsxd_rr(e, 7, 7);
+ call_c(e, "malloc");
+ test_rr(e, 0, 0);
+ int okm = lbl_new(e);
+ jcc_l(e, 0x85, okm);
+ call_l(e, h->die_oom);
+ lbl_here(e, okm);
+ mov_rr(e, R_CTX, 0);
+ mov_rr(e, 7, 0);
+ tc2(e, 0x31, 0xF6);
+ mov_ri32(e, 2, CTX_SIZE);
+ movsxd_rr(e, 2, 2);
+ call_c(e, "memset");
+ libc_data(e, 0, "stdin");
+ ld_d(e, 1, 0, 0);
+ st_d(e, R_CTX, CTX_IN, 1);
+ libc_data(e, 0, "stdout");
+ ld_d(e, 1, 0, 0);
+ st_d(e, R_CTX, CTX_OUT, 1);
+ call_l(e, main_lbl);
+ tc2(e, 0x31, 0xFF);
+ call_c(e, "fflush");
+ tc2(e, 0x31, 0xC0);
+ add_rsp(e, 56);
+ pop_r(e, 3); pop_r(e, R_WV); pop_r(e, R_CTX); pop_r(e, R_F); pop_r(e, R_CMP);
+ pop_r(e, 5);
+ ret(e);
+
+ /* _start: hand-rolled crt */
+ e->start_off = e->text.len;
+ tc2(e, 0x31, 0xED); /* xor ebp,ebp */
+ tc4u_seq(e, (const unsigned char*)"\x48\x8B\x34\x24"); /* mov rsi,[rsp] */
+ tc5u_seq(e, (const unsigned char*)"\x48\x8D\x54\x24\x08"); /* lea rdx,[rsp+8] */
+ tc2(e, 0x31, 0xC9); /* xor ecx,ecx */
+ tc3(e, 0x45, 0x31, 0xC0); /* xor r8d,r8d */
+ tc3(e, 0x45, 0x31, 0xC9); /* xor r9d,r9d */
+ lea_rip_lbl(e, 7, entry); /* rdi = entry */
+ tc4u_seq(e, (const unsigned char*)"\x48\x83\xE4\xF0"); /* and rsp,-16 */
+ mov_rip(e, 0, SEG_GOT, (size_t)(3 + imp_idx(e, "__libc_start_main", 0)) * 8);
+ tc2(e, 0xFF, 0xD0); /* call rax */
+ tc(e, 0xF4); /* hlt */
+}
+
+/* ------------------------------------------------------------------ */
+/* ELF writing */
+/* ------------------------------------------------------------------ */
+
+static uint32_t elf_hash(const char* s) {
+ uint32_t hh = 0;
+ for (; *s; s++) {
+ hh = (hh << 4) + (unsigned char)*s;
+ uint32_t g = hh & 0xF0000000u;
+ if (g) hh ^= g >> 24;
+ hh &= ~g;
+ }
+ return hh;
+}
+
+static const char* host_interp(void) {
+ const char* env = getenv("CRYN_INTERP");
+ if (env && env[0]) return env;
+ static const char* cands[] = {
+ "/lib/ld-musl-x86_64.so.1",
+ "/lib64/ld-linux-x86-64.so.2",
+ "/lib/ld-linux-x86-64.so.2",
+ "/lib64/ld-linux.so.2",
+ "/lib/ld-linux.so.2",
+ };
+ for (size_t i = 0; i < sizeof cands / sizeof cands[0]; i++)
+ if (access(cands[i], F_OK) == 0) return cands[i];
+ return "/lib64/ld-linux-x86-64.so.2";
+}
+
+static const char* elf_soname(const char* path) {
+ static char buf[256];
+ FILE* f = fopen(path, "rb");
+ if (!f) return NULL;
+ unsigned char eh[64];
+ if (fread(eh, 1, 64, f) != 64) { fclose(f); return NULL; }
+ if (memcmp(eh, "\x7F""ELF", 4) != 0) { fclose(f); return NULL; }
+ uint64_t phoff;
+ memcpy(&phoff, eh + 32, 8);
+ uint16_t phentsize, phnum;
+ memcpy(&phentsize, eh + 54, 2);
+ memcpy(&phnum, eh + 56, 2);
+ uint64_t dynv = 0;
+ int has_dyn = 0;
+ for (int i = 0; i < phnum; i++) {
+ unsigned char ph[56];
+ if (fseek(f, (long)(phoff + (uint64_t)i * phentsize), SEEK_SET) != 0) break;
+ if (fread(ph, 1, 56, f) != 56) break;
+ uint32_t ty;
+ memcpy(&ty, ph, 4);
+ if (ty == 2) {
+ memcpy(&dynv, ph + 16, 8);
+ has_dyn = 1;
+ }
+ }
+ if (!has_dyn) { fclose(f); return NULL; }
+ if (fseek(f, (long)dynv, SEEK_SET) != 0) { fclose(f); return NULL; }
+ uint64_t strtab = 0;
+ int64_t soname = -1;
+ for (;;) {
+ unsigned char d[16];
+ if (fread(d, 1, 16, f) != 16) break;
+ uint64_t tag, val;
+ memcpy(&tag, d, 8);
+ memcpy(&val, d + 8, 8);
+ if (tag == 0) break;
+ if (tag == 14) soname = (int64_t)val;
+ if (tag == 5) strtab = val;
+ }
+ if (soname < 0) { fclose(f); return NULL; }
+ if (fseek(f, (long)(strtab + (uint64_t)soname), SEEK_SET) != 0) { fclose(f); return NULL; }
+ size_t n = fread(buf, 1, sizeof buf - 1, f);
+ buf[n] = 0;
+ fclose(f);
+ return buf[0] ? buf : NULL;
+}
+
+static void w8b(Buf* b, uint64_t v) { buf_put(b, &v, 8); }
+static void w4b(Buf* b, uint32_t v) { buf_put(b, &v, 4); }
+static void w2b(Buf* b, uint16_t v) { buf_put(b, &v, 2); }
+
+static int write_elf(XE* e, const char* soname, const char* libm_needed, Buf* out) {
+ /* dynamic symbol table: null + imports */
+ int nsym = 1 + e->nimps;
+ Buf dynstr = {0};
+ uint32_t* nameoff = xmalloc((size_t)e->nimps * sizeof(uint32_t));
+ for (int i = 0; i < e->nimps; i++) {
+ nameoff[i] = (uint32_t)dynstr.len;
+ buf_puts(&dynstr, e->imps[i].name);
+ buf_putc(&dynstr, 0);
+ }
+ uint32_t soname_off = (uint32_t)dynstr.len;
+ buf_puts(&dynstr, soname);
+ buf_putc(&dynstr, 0);
+ uint32_t libm_off = 0;
+ if (libm_needed) {
+ libm_off = (uint32_t)dynstr.len;
+ buf_puts(&dynstr, libm_needed);
+ buf_putc(&dynstr, 0);
+ }
+
+ Buf dynsym = {0};
+ for (int k = 0; k < nsym; k++) { /* null + entries (fill below) */
+ for (int z = 0; z < 24; z++) buf_putc(&dynsym, 0);
+ }
+ for (int i = 0; i < e->nimps; i++) {
+ size_t s = 24 + (size_t)i * 24;
+ memcpy(dynsym.data + s, &nameoff[i], 4);
+ dynsym.data[s + 4] = (unsigned char)(e->imps[i].is_data ? 0x11 : 0x12);
+ }
+
+ /* relocations */
+ Buf rela = {0};
+ size_t got_slots = 3 + (size_t)e->nimps;
+ for (int i = 0; i < e->nimps; i++) {
+ uint64_t roff = (uint64_t)(24 + 8 * (size_t)i); /* + base at patch */
+ w8b(&rela, roff);
+ w8b(&rela, ((uint64_t)(i + 1) << 32) | 6); /* GLOB_DAT */
+ w8b(&rela, 0);
+ }
+
+ /* sysv hash */
+ uint32_t nbucket = nsym < 4 ? 4 : (uint32_t)nsym;
+ uint32_t* buckets = xmalloc(nbucket * 4);
+ uint32_t* chain = xmalloc((size_t)nsym * 4);
+ uint32_t* last = xmalloc(nbucket * 4);
+ memset(buckets, 0, nbucket * 4);
+ memset(chain, 0, (size_t)nsym * 4);
+ memset(last, 0, nbucket * 4);
+ for (int i = 0; i < e->nimps; i++) {
+ uint32_t hh = elf_hash(e->imps[i].name) % nbucket;
+ uint32_t sym = (uint32_t)(i + 1);
+ if (!buckets[hh]) buckets[hh] = sym;
+ else chain[last[hh]] = sym;
+ last[hh] = sym;
+ }
+ Buf hash = {0};
+ w4b(&hash, nbucket);
+ w4b(&hash, (uint32_t)nsym);
+ for (uint32_t i = 0; i < nbucket; i++) w4b(&hash, buckets[i]);
+ for (int i = 0; i < nsym; i++) w4b(&hash, chain[i]);
+
+ /* layout */
+ uint64_t base = 0x400000;
+ size_t phnum = 5;
+ size_t text_off = 64 + phnum * 56;
+ size_t ro_off = text_off + e->text.len;
+ size_t sym_off = (ro_off + e->ro.len + 7) & ~(size_t)7;
+ size_t str_off = sym_off + dynsym.len;
+ size_t rela_off = str_off + dynstr.len;
+ size_t hash_off = rela_off + rela.len;
+ const char* interp = host_interp();
+ size_t interp_off = hash_off + hash.len;
+ size_t ilen = strlen(interp) + 1;
+ size_t rx_filesz = interp_off + ilen;
+ size_t rw_off = (rx_filesz + 0xFFF) & ~(size_t)0xFFF;
+ uint64_t rw_va = base + rw_off;
+ size_t got_off = rw_off;
+ size_t dyn_off = got_off + got_slots * 8;
+ int ndyn = 10 + (libm_needed ? 1 : 0);
+ size_t data_off = dyn_off + (size_t)ndyn * 16;
+ size_t data_start_in_seg = data_off - rw_off; /* offset of .data within RW */
+ size_t rw_filesz = data_off - rw_off + e->data.len;
+
+ e->base = base;
+ e->text_va = base + text_off;
+ e->ro_va = base + ro_off;
+ e->got_va = rw_va;
+ e->dyn_va = rw_va + (got_slots * 8);
+ e->data_va = rw_va + data_start_in_seg;
+
+ /* patch fixups */
+ for (int i = 0; i < e->nfix; i++) {
+ Fix* f = &e->fixes[i];
+ if (f->kind == 0 || f->kind == 4) {
+ int64_t target = e->labels[f->label] < 0 ? 0 : e->labels[f->label];
+ int64_t disp = target - (int64_t)(f->at + 4);
+ memcpy(e->text.data + f->at, &disp, 4);
+ } else if (f->kind == 1) {
+ uint64_t t = f->seg == SEG_RO ? e->ro_va + f->segoff :
+ f->seg == SEG_GOT ? e->got_va + f->segoff :
+ e->data_va + f->segoff;
+ int64_t disp = (int64_t)t - (int64_t)(e->text_va + f->at + 4);
+ memcpy(e->text.data + f->at, &disp, 4);
+ } else if (f->kind == 2) {
+ uint64_t t = e->text_va + (uint64_t)e->labels[f->label];
+ memcpy(e->data.data + f->at, &t, 8);
+ }
+ }
+ /* patch reloc offsets with real vaddrs */
+ for (size_t i = 0; i < rela.len / 24; i++) {
+ uint64_t v = e->got_va + 24 + 8 * (uint64_t)i;
+ memcpy(rela.data + i * 24, &v, 8);
+ }
+
+ /* dynamic table content */
+ Buf dyn = {0};
+ #define DTB(t, v) do { w4b(&dyn, (t)); w4b(&dyn, 0); w8b(&dyn, (uint64_t)(v)); } while (0)
+ DTB(1, soname_off);
+ DTB(4, base + hash_off);
+ DTB(5, base + str_off);
+ DTB(6, base + sym_off);
+ DTB(10, dynstr.len);
+ DTB(11, 24);
+ DTB(7, base + rela_off);
+ DTB(8, rela.len);
+ DTB(9, 24);
+ if (libm_needed) DTB(1, libm_off);
+ DTB(0, 0);
+ #undef DTB
+
+ /* ehdr */
+ buf_puts(out, "");
+ buf_putc(out, 0x7F); buf_puts(out, "ELF");
+ buf_putc(out, 2); buf_putc(out, 1); buf_putc(out, 1);
+ for (int i = 7; i < 16; i++) buf_putc(out, 0);
+ w2b(out, 2); /* ET_EXEC */
+ w2b(out, 62); /* EM_X86_64 */
+ w4b(out, 1); /* version */
+ w8b(out, e->text_va + e->start_off); /* e_entry = _start */
+ w8b(out, 64); /* phoff */
+ w8b(out, 0); /* shoff */
+ w4b(out, 0); /* flags */
+ w2b(out, 64); /* ehsize */
+ w2b(out, 56); /* phentsize */
+ w2b(out, (uint16_t)phnum);
+ w2b(out, 0); w2b(out, 0); w2b(out, 0); /* shentsize, shnum, shstrndx */
+ struct { uint32_t t, fl; uint64_t o, v, fs, ms, a; } ph[5] = {
+ {1, 5, 0, base, rx_filesz, rx_filesz, 0x1000},
+ {1, 6, rw_off, rw_va, rw_filesz, rw_filesz + e->bss_size, 0x1000},
+ {3, 4, interp_off, base + interp_off, ilen, ilen, 1},
+ {2, 6, dyn_off, e->dyn_va, (size_t)ndyn * 16, (size_t)ndyn * 16, 8},
+ {0x6474E551, 6, 0, 0, 0, 0, 0x10},
+ };
+ /* pad phdr area */
+ while (out->len < 64) buf_putc(out, 0);
+ for (int i = 0; i < 5; i++) {
+ w4b(out, ph[i].t); w4b(out, ph[i].fl);
+ w8b(out, ph[i].o); w8b(out, ph[i].v); w8b(out, ph[i].v);
+ w8b(out, ph[i].fs); w8b(out, ph[i].ms); w8b(out, ph[i].a);
+ }
+ buf_put(out, e->text.data, e->text.len);
+ buf_put(out, e->ro.data, e->ro.len);
+ while (out->len < sym_off) buf_putc(out, 0);
+ buf_put(out, dynsym.data, dynsym.len);
+ buf_put(out, dynstr.data, dynstr.len);
+ buf_put(out, rela.data, rela.len);
+ buf_put(out, hash.data, hash.len);
+ buf_put(out, interp, ilen);
+ while (out->len < rw_off) buf_putc(out, 0);
+ /* GOT */
+ w8b(out, e->dyn_va); /* GOT[0] = _DYNAMIC */
+ w8b(out, 0); w8b(out, 0);
+ for (int i = 0; i < e->nimps; i++) w8b(out, 0);
+ /* dynamic */
+ buf_put(out, dyn.data, dyn.len);
+ /* data */
+ buf_put(out, e->data.data, e->data.len);
+
+ buf_free(&dynstr); buf_free(&dynsym); buf_free(&rela); buf_free(&hash); buf_free(&dyn);
+ free(nameoff); free(buckets); free(chain); free(last);
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+/* driver */
+/* ------------------------------------------------------------------ */
+
+int be_x64_emit(ProgramIR* p, int release, Buf* out) {
+ XE e;
+ memset(&e, 0, sizeof e);
+ H h;
+ memset(&h, 0, sizeof h);
+
+ h.fmt_err = ro_str(&e, "cryn: runtime error: %s\n");
+ h.fmt_range = ro_str(&e, "cryn: runtime error: index %llu out of bounds (size %llu)\n");
+ h.fmt_lld = ro_str(&e, "%lld");
+ h.fmt_llu = ro_str(&e, "%llu");
+ h.fmt_hex = ro_str(&e, "0x%llx");
+ h.fmt_g = ro_str(&e, "%.*g");
+ h.s_nan = ro_str(&e, "nan");
+ h.s_true = ro_str(&e, "true");
+ h.s_false = ro_str(&e, "false");
+ h.modetab = ro_put(&e, "rb\0\0wb\0\0w+b\0ab\0\0", 16);
+ h.k_2p63 = ro_f64(&e, 9223372036854775808.0);
+ h.k_2n63 = ro_f64(&e, -9223372036854775808.0);
+ h.k_2p64 = ro_f64(&e, 18446744073709551616.0);
+ h.fbuf = e.data.len;
+ for (int i = 0; i < 64; i++) buf_putc(&e.data, 0);
+ h.done_ctr = e.data.len;
+ for (int i = 0; i < 8; i++) buf_putc(&e.data, 0);
+
+ int ids[] = {
+ /* dies */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ /* stack */ 4, /* heap */ 5, /* conv */ 8, /* out */ 8, /* io */ 7, /* spawn */ 4,
+ };
+ (void)ids;
+ h.die = lbl_new(&e); h.die_range = lbl_new(&e);
+ h.die_oom = lbl_new(&e); h.die_div0 = lbl_new(&e); h.die_flushfail = lbl_new(&e);
+ h.die_focus = lbl_new(&e); h.die_toolarge = lbl_new(&e);
+ h.die_notblock = lbl_new(&e); h.die_free_null = lbl_new(&e);
+ h.die_usebad = lbl_new(&e); h.die_closebad = lbl_new(&e);
+ h.die_indcall = lbl_new(&e); h.die_spawnpar = lbl_new(&e);
+ h.die_spawnmax = lbl_new(&e); h.die_threadfail = lbl_new(&e);
+ h.die_seekstreams = lbl_new(&e);
+ h.push = lbl_new(&e); h.pop = lbl_new(&e);
+ h.fpush = lbl_new(&e); h.fpop = lbl_new(&e);
+ h.alloc = lbl_new(&e); h.free_ = lbl_new(&e); h.blockn = lbl_new(&e);
+ h.chk_block = lbl_new(&e); h.chk_range = lbl_new(&e);
+ h.d2i64 = lbl_new(&e); h.d2u64 = lbl_new(&e); h.d2i8 = lbl_new(&e);
+ h.d2i16 = lbl_new(&e); h.d2i32 = lbl_new(&e); h.d2u8 = lbl_new(&e);
+ h.d2u16 = lbl_new(&e); h.d2u32 = lbl_new(&e);
+ h.out_i64 = lbl_new(&e); h.out_u64 = lbl_new(&e);
+ h.out_f64 = lbl_new(&e); h.out_f32 = lbl_new(&e);
+ h.out_bool = lbl_new(&e); h.out_char = lbl_new(&e);
+ h.out_addr = lbl_new(&e); h.out_text = lbl_new(&e);
+ h.input = lbl_new(&e); h.open = lbl_new(&e); h.close = lbl_new(&e);
+ h.use = lbl_new(&e); h.flush = lbl_new(&e); h.seek = lbl_new(&e);
+ h.system = lbl_new(&e); h.spawn = lbl_new(&e);
+ h.thread_main = lbl_new(&e); h.wait = lbl_new(&e); h.stop = lbl_new(&e);
+
+ const char* interp = host_interp();
+ e.style_musl = strstr(interp, "musl") != NULL;
+ const char* soname;
+ const char* libm = NULL;
+ if (e.style_musl) {
+ soname = elf_soname(interp);
+ if (!soname) soname = "libc.musl-x86_64.so.1";
+ } else {
+ soname = "libc.so.6";
+ libm = "libm.so.6";
+ }
+
+ h_all(&e, &h);
+
+ /* program objects */
+ size_t* glob_off = xmalloc((size_t)(p->nglobs ? p->nglobs : 1) * sizeof(size_t));
+ for (int i = 0; i < p->nglobs; i++) {
+ GlobIR* gb = &p->globs[i];
+ uint64_t sz = type_size(gb->type);
+ if (gb->str_init) {
+ glob_off[i] = data_put(&e, gb->str_init, gb->str_initlen);
+ } else {
+ glob_off[i] = e.data.len;
+ e.bss_size += 0; /* keep zero bytes in .data for simplicity */
+ for (uint64_t k = 0; k < sz; k++) buf_putc(&e.data, 0);
+ }
+ }
+ size_t* str_off = xmalloc((size_t)(p->nstrs ? p->nstrs : 1) * sizeof(size_t));
+ for (int i = 0; i < p->nstrs; i++) {
+ StrIR* s = &p->strs[i];
+ str_off[i] = data_put(&e, s->bytes, s->len + 1);
+ }
+ int* flabels = xmalloc((size_t)(p->nfns ? p->nfns : 1) * sizeof(int));
+ size_t* ref_off = xmalloc((size_t)(p->nfns ? p->nfns : 1) * sizeof(size_t));
+ for (int i = 0; i < p->nfns; i++) flabels[i] = lbl_new(&e);
+ for (int i = 0; i < p->nfns; i++) {
+ ref_off[i] = e.data.len;
+ fix_add(&e, 2, ref_off[i], flabels[i], 0, 0);
+ for (int k = 0; k < 8; k++) buf_putc(&e.data, 0); /* fn ptr */
+ int32_t np = p->fns[i].nparams;
+ buf_put(&e.data, &np, 4);
+ buf_putc(&e.data, 0); buf_putc(&e.data, 0); buf_putc(&e.data, 0); buf_putc(&e.data, 0);
+ }
+
+ /* functions; label ids for IR labels continue after existing labels */
+ int lblbase = e.nlabels;
+ for (int i = 0; i < p->nfns; i++) {
+ FnIR* fn = &p->fns[i];
+ int base = e.nlabels;
+ for (int k = 0; k < fn->nlabels; k++) lbl_new(&e);
+ FnGen g;
+ memset(&g, 0, sizeof g);
+ g.e = &e; g.h = &h; g.p = p; g.fn = fn; g.release = release;
+ g.flabels = flabels; g.glob_off = glob_off; g.str_off = str_off;
+ g.ref_off = ref_off; g.lblbase = base; g.ro_2p63 = h.k_2p63; g.fi = i;
+ gen_fn(&g);
+ (void)lblbase;
+ }
+
+ /* entry needs main's label */
+ int mi = prog_find_fn(p, "main");
+ emit_entry(&e, &h, flabels[mi]);
+
+ return write_elf(&e, soname, libm, out);
+}
diff --git a/src/be_x64.h b/src/be_x64.h
new file mode 100644
index 0000000..eb2f36b
--- /dev/null
+++ b/src/be_x64.h
@@ -0,0 +1,12 @@
+#ifndef CR_BE_X64_H
+#define CR_BE_X64_H
+
+#include "ir.h"
+
+/* Native x86-64 backend: lower the IR directly to x86-64 machine code
+ and emit a static ELF64 executable that links against the host C
+ library (the system-call interface). No host C compiler is involved.
+ Returns 0 on success. */
+int be_x64_emit(ProgramIR* p, int release, Buf* out);
+
+#endif
diff --git a/src/check.c b/src/check.c
index 5abac09..314f616 100644
--- a/src/check.c
+++ b/src/check.c
@@ -483,6 +483,19 @@ static void check_insn(Chk* c, Node* n) {
type_name(st->wv, b, sizeof b));
return;
}
+ /* statically provable index: an integer-literal working value
+ within the static bound becomes a literal move, eliminating
+ the bounds check at compile time */
+ if (st->wv->kind == TY_INTLIT && !st->wv->litneg && f->cell->count &&
+ st->wv->litval < f->cell->count && st->wv->litval <= 0x7FFFFFFFULL) {
+ Insn* in = emit(c, I_MOVE_LIT, n->loc);
+ in->imm.kind = IV_UINT;
+ in->imm.u = st->wv->litval;
+ in->t = f->cell->inner;
+ in->b = (int)f->cell->count;
+ focus_push_cell(c, f->cell->inner, n->loc);
+ return;
+ }
Insn* in = emit(c, I_MOVE_INDEX, n->loc);
in->t = f->cell->inner;
in->b = f->cell->count ? (int)f->cell->count : -1;
@@ -1313,6 +1326,30 @@ static void check_insn(Chk* c, Node* n) {
case A_FLUSH:
emit(c, I_FLUSH, n->loc);
return;
+ case A_SEEK: {
+ if (n->has_imm) {
+ Insn* in = emit(c, I_SEEK, n->loc);
+ in->b = 1;
+ in->imm.kind = IV_UINT;
+ in->imm.u = n->imm.u;
+ st->wv = ty_int(64);
+ return;
+ }
+ if (!ty_is_any_int(st->wv)) {
+ char b[128];
+ err(n->loc, "'seek' requires an integer offset working value (found %s)",
+ type_name(st->wv, b, sizeof b));
+ return;
+ }
+ if (st->wv->kind == TY_INTLIT) {
+ Insn* cv = emit(c, I_CONV, n->loc);
+ cv->t = ty_int(64);
+ st->wv = ty_int(64);
+ }
+ emit(c, I_SEEK, n->loc)->b = 0;
+ st->wv = ty_int(64);
+ return;
+ }
case A_SYSTEM: {
Type* wv = st->wv;
if (!(wv->kind == TY_ADDR &&
diff --git a/src/ir.h b/src/ir.h
index 25994a0..ff72c8b 100644
--- a/src/ir.h
+++ b/src/ir.h
@@ -56,6 +56,7 @@ typedef enum {
I_CLOSE,
I_USE, /* b = 0 handle in working value, 1 imm handle */
I_FLUSH,
+ I_SEEK, /* b = 0 offset in working value, 1 imm offset; result in wv */
I_SYSTEM,
I_SPAWN, /* a = fn index */
I_WAIT,
diff --git a/src/lex.c b/src/lex.c
index c1e9806..2d35ec0 100644
--- a/src/lex.c
+++ b/src/lex.c
@@ -60,6 +60,7 @@ static const Kw k_keywords[] = {
{"stop", T_STOP}, {"input", T_INPUT}, {"output", T_OUTPUT}, {"open", T_OPEN},
{"close", T_CLOSE}, {"system", T_SYSTEM}, {"spawn", T_SPAWN}, {"wait", T_WAIT},
{"use", T_USE}, {"flush", T_FLUSH}, {"exchange", T_EXCHANGE},
+ {"seek", T_SEEK},
{"define", T_DEFINE}, {"end", T_END}, {"mark", T_MARK},
{"nothing", T_NOTHING}, {"bool", T_BOOL}, {"char", T_CHAR_T},
{"int8", T_INT8}, {"int16", T_INT16}, {"int32", T_INT32}, {"int64", T_INT64},
diff --git a/src/lex.h b/src/lex.h
index f37450a..86c88d8 100644
--- a/src/lex.h
+++ b/src/lex.h
@@ -23,7 +23,7 @@ typedef enum {
T_JUMP, T_CALL, T_RETURN, T_STOP,
T_INPUT, T_OUTPUT, T_OPEN, T_CLOSE, T_SYSTEM,
T_SPAWN, T_WAIT,
- T_USE, T_FLUSH, T_EXCHANGE,
+ T_USE, T_FLUSH, T_EXCHANGE, T_SEEK,
T_DEFINE, T_END, T_MARK,
/* type words */
diff --git a/src/main.c b/src/main.c
index 8ba3f7b..0f9cbab 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,5 +1,6 @@
/* cryn - The Cryn Programming Language compiler (bootstrap) */
#include "be_c.h"
+#include "be_x64.h"
#include "check.h"
#include "diag.h"
#include "lex.h"
@@ -10,11 +11,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
-#define CRYN_VERSION "0.2.1"
+#define CRYN_VERSION "0.3.0"
static void usage(void) {
printf(
@@ -22,8 +24,11 @@ static void usage(void) {
"\n"
"usage:\n"
" cryn <file.cryn> compile to an executable\n"
- " cryn build <file.cryn> [-o out]\n"
- " cryn run <file.cryn> compile and execute\n"
+ " cryn build <file.cryn> [-o out] [--release] [--native]\n"
+ " release: no runtime bounds checks\n"
+ " native: x86-64 ELF, no host cc\n"
+ " cryn run <file.cryn> [--release] [--native]\n"
+ " compile and execute\n"
" cryn check <file.cryn> type check only\n"
" cryn emit-c <file.cryn> [-o out.c]\n"
" write generated C\n"
@@ -62,7 +67,7 @@ static const char* tmp_dir(void) {
typedef enum { CMD_COMPILE, CMD_RUN, CMD_CHECK, CMD_EMITC, CMD_EMITIR } CmdKind;
-static int compile_file(const char* path, CmdKind kind, const char* out) {
+static int compile_file(const char* path, CmdKind kind, const char* out, int release, int native) {
char* src = NULL;
size_t len = 0;
if (read_file(path, &src, &len) != 0) {
@@ -92,7 +97,7 @@ static int compile_file(const char* path, CmdKind kind, const char* out) {
}
case CMD_EMITC: {
Buf b = {0};
- be_c_emit(ir, &b);
+ be_c_emit(ir, &b, release);
const char* dst = out ? out : "out.c";
if (write_file(dst, b.data, b.len) != 0) {
fprintf(stderr, "cryn: cannot write '%s'\n", dst);
@@ -114,19 +119,33 @@ static int compile_file(const char* path, CmdKind kind, const char* out) {
snprintf(cfile, sizeof cfile, "%s.c", base);
snprintf(exefile, sizeof exefile, "%s", base);
}
- Buf b = {0};
- be_c_emit(ir, &b);
- if (write_file(cfile, b.data, b.len) != 0) {
- fprintf(stderr, "cryn: cannot write '%s'\n", cfile);
- return 1;
- }
- char* cc = find_host_cc();
- int rc = run_host_cc(cc, cfile, exefile);
- free(cc);
- if (rc != 0) {
- fprintf(stderr, "cryn: host C compiler failed (exit %d)\n", rc);
- if (kind == CMD_RUN) remove(cfile);
- return 1;
+ if (native) {
+ /* native x86-64 backend: emit the ELF directly, no host cc */
+ Buf b = {0};
+ if (be_x64_emit(ir, release, &b) != 0) {
+ fprintf(stderr, "cryn: native backend failed\n");
+ return 1;
+ }
+ if (write_file(exefile, b.data, b.len) != 0) {
+ fprintf(stderr, "cryn: cannot write '%s'\n", exefile);
+ return 1;
+ }
+ chmod(exefile, 0755);
+ } else {
+ Buf b = {0};
+ be_c_emit(ir, &b, release);
+ if (write_file(cfile, b.data, b.len) != 0) {
+ fprintf(stderr, "cryn: cannot write '%s'\n", cfile);
+ return 1;
+ }
+ char* cc = find_host_cc();
+ int rc = run_host_cc(cc, cfile, exefile);
+ free(cc);
+ if (rc != 0) {
+ fprintf(stderr, "cryn: host C compiler failed (exit %d)\n", rc);
+ if (kind == CMD_RUN) remove(cfile);
+ return 1;
+ }
}
if (kind == CMD_COMPILE) {
remove(cfile);
@@ -134,8 +153,8 @@ static int compile_file(const char* path, CmdKind kind, const char* out) {
}
fflush(NULL);
int prc = system(exefile);
+ if (native) remove(exefile);
remove(cfile);
- remove(exefile);
if (prc == -1) return 1;
if (WIFEXITED(prc)) return WEXITSTATUS(prc);
return 1;
@@ -164,6 +183,7 @@ int main(int argc, char** argv) {
const char* out = NULL;
const char* file = NULL;
int has_sub = 1;
+ int release = 0, native = 0;
if (strcmp(cmd, "build") == 0) kind = CMD_COMPILE;
else if (strcmp(cmd, "run") == 0) kind = CMD_RUN;
else if (strcmp(cmd, "check") == 0) kind = CMD_CHECK;
@@ -181,6 +201,8 @@ int main(int argc, char** argv) {
int start = has_sub ? 3 : 2;
for (int i = start; i < argc; i++) {
if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) out = argv[++i];
+ else if (strcmp(argv[i], "--release") == 0) release = 1;
+ else if (strcmp(argv[i], "--native") == 0) native = 1;
}
- return compile_file(file, kind, out);
+ return compile_file(file, kind, out, release, native);
}
diff --git a/src/parse.c b/src/parse.c
index 887ef60..26f4ea0 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -305,6 +305,18 @@ static Node* parse_command(Parser* p) {
return n;
}
case T_FLUSH: next(p); return node_new(p, A_FLUSH);
+ case T_SEEK: {
+ next(p);
+ n = node_new(p, A_SEEK);
+ Token* v = peek(p);
+ if (v->kind == T_INT) {
+ if (v->neg) crn_error(v->loc, "seek offset literal cannot be negative");
+ next(p);
+ n->imm = imm_from_token(v);
+ n->has_imm = 1;
+ }
+ return n;
+ }
case T_SYSTEM: next(p); return node_new(p, A_SYSTEM);
case T_SPAWN: {
next(p);
diff --git a/src/rt/cryn_rt.c b/src/rt/cryn_rt.c
index d4d769f..1724ee1 100644
--- a/src/rt/cryn_rt.c
+++ b/src/rt/cryn_rt.c
@@ -453,6 +453,26 @@ void crn_flush(void) {
if (fflush(cr_ctx.out) != 0) crn_die("flush failed");
}
+int64_t crn_seek(int64_t off) {
+ FILE* a = cr_ctx.in;
+ FILE* b = cr_ctx.out;
+ int any = 0;
+ int bad = 0;
+ /* standard streams are not positionable; only handle streams are */
+ if (a && a != stdin) {
+ any = 1;
+ if (fseek(a, (long)off, SEEK_SET) != 0) bad = 1;
+ }
+ if (b && b != a && b != stdout) {
+ any = 1;
+ if (fseek(b, (long)off, SEEK_SET) != 0) bad = 1;
+ }
+ if (!any)
+ crn_die("seek requires a stream routed through an open handle "
+ "('use' a handle from 'open' first)");
+ return bad ? -1 : 0;
+}
+
int64_t crn_close(int64_t h) {
if (h == 0)
crn_die("close of handle 0 (the standard streams cannot be closed)");
diff --git a/src/rt/cryn_rt.h b/src/rt/cryn_rt.h
index cc82ed2..9bccb8d 100644
--- a/src/rt/cryn_rt.h
+++ b/src/rt/cryn_rt.h
@@ -128,6 +128,13 @@ int64_t crn_open(uint8_t* path, int mode);
the standard streams (input: stdin, output: stdout). */
void crn_use(int64_t h);
void crn_flush(void);
+/* Position the context's file streams at a byte offset from the start.
+ Applies fseek(SEEK_SET) to every current stream that belongs to an
+ open handle (both sides of an 'open both' stream are the same file and
+ are positioned once). Standard streams are not positionable; if no
+ current stream belongs to a handle, it is a runtime error. Returns 0
+ on success and -1 if the position could not be set. */
+int64_t crn_seek(int64_t off);
int64_t crn_close(int64_t h);
int64_t crn_system(uint8_t* cmd);
void crn_stop(int64_t code) CRN_NORETURN;
diff --git a/tests/31_seek.cryn b/tests/31_seek.cryn
new file mode 100644
index 0000000..a9dafd5
--- /dev/null
+++ b/tests/31_seek.cryn
@@ -0,0 +1,123 @@
+[# seek: positioning an 'open both' stream so read and write interleave
+ on one file — the one thing the sequential byte-stream model plus
+ flush cannot express #]
+define main
+ make string "cryn_test_31.txt" path
+ make string "ABCDE" data
+ make array 8 of uint8 buf
+ make int64 h
+ make int64 r
+ make int64 c1
+ make int64 c2
+ make int64 n
+ make int64 i
+
+ address path
+ open both
+ home
+ compare 0
+ jump fail when less
+ move h
+ write
+ push
+ use
+ home
+
+ address data
+ output text
+ flush
+
+ set 0
+ seek
+ move r
+ write
+
+ input
+ move c1
+ write
+ input
+ move c2
+ write
+
+ seek 3
+ set 88
+ convert uint8
+ output
+ flush
+
+ set 0
+ seek
+ set 0
+ move n
+ write
+ mark rd
+ input
+ compare -1
+ jump fin when equal
+ convert uint8
+ push
+ move n
+ read
+ move buf
+ move index
+ pop
+ write
+ move n
+ read
+ add 1
+ move n
+ write
+ jump rd
+ mark fin
+
+ pop
+ close
+ use 0
+
+ move r
+ read
+ output
+ move c1
+ read
+ convert uint8
+ output
+ move c2
+ read
+ convert uint8
+ output
+
+ set 0
+ home
+ mark pr
+ move n
+ read
+ push
+ move i
+ read
+ compare
+ jump donePr when greater-or-equal
+ set 0
+ home
+ move i
+ read
+ move buf
+ move index
+ read
+ output
+ move i
+ read
+ add 1
+ move i
+ write
+ set 0
+ home
+ jump pr
+ mark donePr
+ set 0
+ home
+ stop
+
+ mark fail
+ set 1
+ stop
+end
diff --git a/tests/32_cas.cryn b/tests/32_cas.cryn
new file mode 100644
index 0000000..a3b0ed8
--- /dev/null
+++ b/tests/32_cas.cryn
@@ -0,0 +1,137 @@
+[# the atomic-primitives evaluation: compare-and-swap and atomic
+ increment built from 'exchange' (which builds locks) alone — the
+ demonstration that closed "further atomic primitives": anything a new
+ primitive would express can be built from exchange and wait #]
+make int64 lock
+make int64 value
+make string "\n" nl
+
+define acquire
+ set 0
+ home
+ mark again
+ set 1
+ move lock
+ exchange
+ compare 0
+ set 0
+ home
+ jump again when different
+ return
+end
+
+define release
+ set 0
+ move lock
+ exchange
+ return
+end
+
+[# compare-and-swap: when value holds expected, store newv.
+ returns the value value held before the attempt #]
+define cas int64 expected int64 newv result int64
+ make int64 old
+ call acquire
+ move value
+ read
+ move old
+ write
+ move expected
+ read
+ push
+ move old
+ read
+ compare
+ jump skip when different
+ move newv
+ read
+ move value
+ write
+ mark skip
+ call release
+ move old
+ read
+ return
+end
+
+define worker
+ make int64 k
+ make int64 seen
+ set 0
+ move k
+ write
+ set 0
+ home
+ mark w
+ move k
+ read
+ compare 50
+ jump done when greater-or-equal
+ set 0
+ home
+
+ mark retry
+ move value
+ read
+ move seen
+ write
+ move seen
+ read
+ push
+ move seen
+ read
+ add 1
+ push
+ call cas
+ push
+ move seen
+ read
+ compare
+ set 0
+ home
+ jump retry when different
+
+ move k
+ read
+ add 1
+ move k
+ write
+ set 0
+ home
+ jump w
+
+ mark done
+ return
+end
+
+define main
+ set 7
+ move value
+ write
+ set 5
+ push
+ set 9
+ push
+ call cas
+ compare 7
+ jump bad when different
+ move value
+ read
+ compare 7
+ jump bad when different
+
+ spawn worker
+ spawn worker
+ wait
+
+ move value
+ read
+ output
+ address nl
+ output text
+ return
+
+ mark bad
+ set 1
+ stop
+end
diff --git a/tests/33_conversions.cryn b/tests/33_conversions.cryn
new file mode 100644
index 0000000..19e6925
--- /dev/null
+++ b/tests/33_conversions.cryn
@@ -0,0 +1,134 @@
+[# the conversion matrix: integer truncation keeps low bits, floats
+ saturate into integers, float32 narrows and widens exactly #]
+make string "\n" nl
+
+define newline result nothing
+ address nl
+ output text
+ set nothing
+ return
+end
+
+define main
+ [# integer to integer: low bits kept, then sign extended #]
+ set 300
+ convert uint8
+ convert int64
+ output
+ call newline
+
+ set -1
+ convert uint8
+ convert int64
+ output
+ call newline
+
+ set 130
+ convert int8
+ convert int64
+ output
+ call newline
+
+ set -1
+ convert uint16
+ convert int64
+ output
+ call newline
+
+ set 100000
+ convert uint16
+ convert int64
+ output
+ call newline
+
+ set -1
+ convert uint64
+ output
+ call newline
+
+ set 5000000000
+ convert uint32
+ convert int64
+ output
+ call newline
+
+ [# integer to float and back #]
+ set 7
+ convert float64
+ output
+ call newline
+
+ set -3
+ convert float32
+ output
+ call newline
+
+ [# float to integer saturates at the target range #]
+ set 300.5
+ convert uint8
+ convert int64
+ output
+ call newline
+
+ set -5.5
+ convert uint8
+ convert int64
+ output
+ call newline
+
+ set 1e18
+ convert int64
+ output
+ call newline
+
+ set 1e19
+ convert int64
+ output
+ call newline
+
+ set 0.9999999
+ convert int64
+ output
+ call newline
+
+ [# float32 narrows and the shortest form still round trips #]
+ set 0.1
+ convert float32
+ output
+ call newline
+
+ set 3.14159265358979
+ convert float32
+ output
+ call newline
+
+ set 16777217
+ convert float32
+ convert int64
+ output
+ call newline
+
+ [# bool conversion: nonzero is true, including fractions #]
+ set 5
+ convert bool
+ output
+ call newline
+
+ set 0
+ convert bool
+ output
+ call newline
+
+ set 0.5
+ convert bool
+ output
+ call newline
+
+ set 0.0
+ convert bool
+ output
+ call newline
+
+ set nothing
+ return
+end
diff --git a/tests/34_xchgwidths.c b/tests/34_xchgwidths.c
new file mode 100644
index 0000000..056ed85
--- /dev/null
+++ b/tests/34_xchgwidths.c
@@ -0,0 +1,863 @@
+/* generated by the Cryn compiler - do not edit */
+/* Cryn source compiled to portable C11 */
+
+#ifndef CRYN_RT_H
+#define CRYN_RT_H
+
+/*
+ * Minimal runtime support for compiled Cryn programs.
+ * Everything here exists only because it cannot be expressed as inline C
+ * in generated code (context management, heap headers, threads, output
+ * formatting). The language's semantics live in the compiler.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <pthread.h>
+
+#if defined(__GNUC__) || defined(__clang__)
+#define CRN_NORETURN __attribute__((noreturn))
+#else
+#define CRN_NORETURN
+#endif
+
+typedef union Val {
+ uint64_t u; /* unsigned, address (as integer), bool, char */
+ int64_t i; /* signed integers, integer literals */
+ double d; /* float32 (widened) and float64 */
+ void* p; /* focus target / data pointer */
+} Val;
+
+/* A transformation reference: function pointer + parameter count.
+ Indirect calls and spawns are rejected at runtime unless np == 0. */
+typedef struct CrFnRef {
+ Val (*fn)(void);
+ int np;
+} CrFnRef;
+
+#define CR_FH_MAX 1024
+#define CR_SPAWN_MAX 256
+#define CR_FILES_MAX 256
+
+typedef struct CrCtx {
+ Val* stk; /* value stack */
+ uint64_t sp, cap;
+ void* fh[CR_FH_MAX]; /* focus history */
+ uint64_t fh_sp;
+ FILE* in; /* current input stream (default stdin) */
+ FILE* out; /* current output stream (default stdout) */
+ FILE* files[CR_FILES_MAX]; /* open files; slot i has handle i+1 */
+ unsigned char fmodes[CR_FILES_MAX]; /* 0 read, 1 write, 2 both, 3 append */
+ pthread_t spawned[CR_SPAWN_MAX];
+ int nspawned;
+} CrCtx;
+
+extern _Thread_local CrCtx cr_ctx;
+
+void crn_init(void);
+void crn_die(const char* msg) CRN_NORETURN;
+void crn_die_range(uint64_t i, uint64_t n) CRN_NORETURN;
+
+void crn_push(Val v);
+Val crn_pop(void);
+
+void crn_focus_push(void* f);
+void* crn_focus_pop(void);
+
+void* crn_alloc(uint64_t nbytes);
+void crn_free(void* p);
+uint64_t crn_block_n(void* data, uint64_t elemsz);
+void crn_check_block(void* data, uint64_t i, uint64_t elemsz);
+void crn_check_range(uint64_t i, uint64_t n);
+
+/* Relaxed atomic scalar access. Cryn defines a data race on an aligned
+ scalar cell (<= 8 bytes) as: the access yields an unspecified value of
+ the cell's type and never tears. To keep that promise (and avoid C data
+ races, which are undefined behavior), the backend performs every scalar
+ load/store through these helpers. Racily accessed transform-reference
+ cells (16 bytes) are undefined. No ordering is provided beyond wait. */
+uint8_t crn_ld_u8(const uint8_t* p);
+uint16_t crn_ld_u16(const uint16_t* p);
+uint32_t crn_ld_u32(const uint32_t* p);
+uint64_t crn_ld_u64(const uint64_t* p);
+float crn_ld_f32(const float* p);
+double crn_ld_f64(const double* p);
+void crn_st_u8(uint8_t* p, uint8_t v);
+void crn_st_u16(uint16_t* p, uint16_t v);
+void crn_st_u32(uint32_t* p, uint32_t v);
+void crn_st_u64(uint64_t* p, uint64_t v);
+void crn_st_f32(float* p, float v);
+void crn_st_f64(double* p, double v);
+
+/* Atomic exchange (acquire-release). `exchange` swaps the working value
+ with the focused cell in one indivisible step and is the only ordering
+ primitive besides wait: an exchange that stores a value releases the
+ accesses that preceded it in program order, and one that loads a value
+ acquires for the accesses that follow it. It never tears. This is
+ enough to build locks (test-and-set). */
+uint8_t crn_xch_u8(uint8_t* p, uint8_t v);
+uint16_t crn_xch_u16(uint16_t* p, uint16_t v);
+uint32_t crn_xch_u32(uint32_t* p, uint32_t v);
+uint64_t crn_xch_u64(uint64_t* p, uint64_t v);
+float crn_xch_f32(float* p, float v);
+double crn_xch_f64(double* p, double v);
+
+int64_t crn_d2i64(double d);
+uint64_t crn_d2u64(double d);
+int64_t crn_d2i8(double d);
+int64_t crn_d2i16(double d);
+int64_t crn_d2i32(double d);
+uint64_t crn_d2u8(double d);
+uint64_t crn_d2u16(double d);
+uint64_t crn_d2u32(double d);
+
+void crn_out_i64(int64_t v);
+void crn_out_u64(uint64_t v);
+void crn_out_f64(double v);
+void crn_out_f32(float v);
+void crn_out_bool(int b);
+void crn_out_char(unsigned c);
+void crn_out_addr(uint64_t a);
+void crn_out_text(uint8_t* p);
+
+int64_t crn_input(void);
+/* open modes: 0 read, 1 write, 2 both, 3 append.
+ Returns a handle >= 1, or -1 on failure. A handle is an index into the
+ context's file table; closing a handle frees its number for reuse. */
+int64_t crn_open(uint8_t* path, int mode);
+/* Route this context's streams through a handle: the readable side of the
+ file becomes the input stream, the writable side the output stream;
+ sides the mode does not support keep their current stream. Handle 0 is
+ the standard streams (input: stdin, output: stdout). */
+void crn_use(int64_t h);
+void crn_flush(void);
+/* Position the context's file streams at a byte offset from the start.
+ Applies fseek(SEEK_SET) to every current stream that belongs to an
+ open handle (both sides of an 'open both' stream are the same file and
+ are positioned once). Standard streams are not positionable; if no
+ current stream belongs to a handle, it is a runtime error. Returns 0
+ on success and -1 if the position could not be set. */
+int64_t crn_seek(int64_t off);
+int64_t crn_close(int64_t h);
+int64_t crn_system(uint8_t* cmd);
+void crn_stop(int64_t code) CRN_NORETURN;
+
+void crn_spawn(CrFnRef* f);
+void crn_wait(void);
+
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+
+#include <errno.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+_Thread_local CrCtx cr_ctx = {0};
+
+void crn_init(void) {
+ memset(&cr_ctx, 0, sizeof cr_ctx);
+ cr_ctx.in = stdin;
+ cr_ctx.out = stdout;
+}
+
+void crn_die(const char* msg) {
+ fprintf(stderr, "cryn: runtime error: %s\n", msg);
+ exit(1);
+}
+
+void crn_die_range(uint64_t i, uint64_t n) {
+ fprintf(stderr, "cryn: runtime error: index %llu out of bounds (size %llu)\n",
+ (unsigned long long)i, (unsigned long long)n);
+ exit(1);
+}
+
+/* ---- value stack ---- */
+
+void crn_push(Val v) {
+ if (cr_ctx.sp == cr_ctx.cap) {
+ cr_ctx.cap = cr_ctx.cap ? cr_ctx.cap * 2 : 64;
+ cr_ctx.stk = realloc(cr_ctx.stk, cr_ctx.cap * sizeof(Val));
+ if (!cr_ctx.stk) crn_die("out of memory");
+ }
+ cr_ctx.stk[cr_ctx.sp++] = v;
+}
+
+Val crn_pop(void) { return cr_ctx.stk[--cr_ctx.sp]; }
+
+/* ---- focus ---- */
+
+void crn_focus_push(void* f) {
+ if (cr_ctx.fh_sp >= CR_FH_MAX) {
+ char msg[64];
+ snprintf(msg, sizeof msg, "focus history overflow (limit %d)", CR_FH_MAX);
+ crn_die(msg);
+ }
+ cr_ctx.fh[cr_ctx.fh_sp++] = f;
+}
+
+void* crn_focus_pop(void) {
+ if (cr_ctx.fh_sp == 0) crn_die("focus history underflow");
+ return cr_ctx.fh[--cr_ctx.fh_sp];
+}
+
+/* ---- heap ---- */
+
+typedef struct CrHdr {
+ uint64_t magic;
+ uint64_t size; /* data bytes */
+} CrHdr;
+
+#define CR_MAGIC 0x4352594E424C4BULL /* "CRYNBLK" */
+
+/* Registry of live block addresses. Validation must not read the header
+ of a block that free() may already have returned to the allocator:
+ reading freed memory is undefined in C, so stale-address detection
+ (double free, indexing through a freed or shifted address) goes
+ through this table instead. One global table, mutex protected,
+ because blocks can be shared across spawned contexts. */
+typedef struct CrReg {
+ void** keys; /* NULL = empty, CR_TOMB = deleted */
+ size_t cap; /* power of two */
+ size_t used; /* live + tombstones */
+ pthread_mutex_t mu;
+} CrReg;
+
+#define CR_TOMB ((void*)1)
+
+static CrReg cr_reg = {0, 0, 0, PTHREAD_MUTEX_INITIALIZER};
+static pthread_once_t cr_reg_once = PTHREAD_ONCE_INIT;
+
+static size_t reg_slot(void* p, size_t cap) {
+ uintptr_t x = (uintptr_t)p >> 4; /* malloc blocks are 16-aligned */
+ x ^= x >> 17;
+ x *= 0x9E3779B97F4A7C15ULL;
+ x ^= x >> 29;
+ return (size_t)x & (cap - 1);
+}
+
+static void reg_init(void) {
+ cr_reg.cap = 256;
+ cr_reg.keys = malloc(cr_reg.cap * sizeof(void*));
+ if (!cr_reg.keys) crn_die("out of memory");
+ memset(cr_reg.keys, 0, cr_reg.cap * sizeof(void*));
+ cr_reg.used = 0;
+}
+
+static void reg_grow_locked(void) {
+ size_t ncap = cr_reg.cap * 2;
+ void** nk = malloc(ncap * sizeof(void*));
+ if (!nk) crn_die("out of memory");
+ memset(nk, 0, ncap * sizeof(void*));
+ for (size_t i = 0; i < cr_reg.cap; i++) {
+ void* k = cr_reg.keys[i];
+ if (!k || k == CR_TOMB) continue;
+ size_t j = reg_slot(k, ncap);
+ while (nk[j]) j = (j + 1) & (ncap - 1);
+ nk[j] = k;
+ }
+ free(cr_reg.keys);
+ cr_reg.keys = nk;
+ cr_reg.cap = ncap;
+}
+
+/* returns the slot of p, or cap when absent (probing stops at NULL) */
+static size_t reg_find_locked(void* p) {
+ size_t j = reg_slot(p, cr_reg.cap);
+ for (;;) {
+ void* k = cr_reg.keys[j];
+ if (!k) return cr_reg.cap;
+ if (k == p) return j;
+ j = (j + 1) & (cr_reg.cap - 1);
+ }
+}
+
+static void reg_add(void* p) {
+ pthread_once(&cr_reg_once, reg_init);
+ pthread_mutex_lock(&cr_reg.mu);
+ if ((cr_reg.used + 1) * 10 >= cr_reg.cap * 7) {
+ reg_grow_locked();
+ /* collapse tombstones into the new table */
+ cr_reg.used = 0;
+ for (size_t i = 0; i < cr_reg.cap; i++)
+ if (cr_reg.keys[i]) cr_reg.used++;
+ }
+ size_t j = reg_slot(p, cr_reg.cap);
+ while (cr_reg.keys[j] && cr_reg.keys[j] != CR_TOMB) j = (j + 1) & (cr_reg.cap - 1);
+ cr_reg.keys[j] = p;
+ cr_reg.used++;
+ pthread_mutex_unlock(&cr_reg.mu);
+}
+
+static int reg_has(void* p) {
+ pthread_once(&cr_reg_once, reg_init);
+ pthread_mutex_lock(&cr_reg.mu);
+ int found = reg_find_locked(p) != cr_reg.cap;
+ pthread_mutex_unlock(&cr_reg.mu);
+ return found;
+}
+
+static int reg_take(void* p) {
+ pthread_once(&cr_reg_once, reg_init);
+ pthread_mutex_lock(&cr_reg.mu);
+ size_t j = reg_find_locked(p);
+ if (j == cr_reg.cap) {
+ pthread_mutex_unlock(&cr_reg.mu);
+ return 0;
+ }
+ cr_reg.keys[j] = CR_TOMB;
+ pthread_mutex_unlock(&cr_reg.mu);
+ return 1;
+}
+
+void* crn_alloc(uint64_t nbytes) {
+ /* the header and padding additions must not wrap */
+ if (nbytes > (uint64_t)-32) crn_die("allocation too large");
+ CrHdr* h = malloc(sizeof(CrHdr) + (size_t)nbytes + 16);
+ if (!h) crn_die("out of memory");
+ h->magic = CR_MAGIC;
+ h->size = nbytes;
+ unsigned char* p = (unsigned char*)h;
+ p += sizeof(CrHdr);
+ /* keep data 16-aligned: sizeof(CrHdr)==16 on 64-bit */
+ memset(p, 0, (size_t)nbytes);
+ reg_add(p);
+ return p;
+}
+
+void crn_free(void* p) {
+ if (!p) crn_die("free of the null address");
+ if (!reg_take(p)) {
+ crn_die("free of an address that is not a heap block "
+ "(only live blocks made with 'make <size> of <type>' or anonymous "
+ "'make' can be freed)");
+ }
+ CrHdr* h = (CrHdr*)((unsigned char*)p - sizeof(CrHdr));
+ h->magic = 0;
+ free(h);
+}
+
+uint64_t crn_block_n(void* data, uint64_t elemsz) {
+ if (elemsz == 0) elemsz = 1;
+ if (!reg_has(data)) {
+ crn_die("index into memory that is not a heap block "
+ "(only the base address of a live block made with "
+ "'make <size> of <type>' is bounds checked)");
+ }
+ CrHdr* h = (CrHdr*)((unsigned char*)data - sizeof(CrHdr));
+ return h->size / elemsz;
+}
+
+void crn_check_block(void* data, uint64_t i, uint64_t elemsz) {
+ uint64_t n = crn_block_n(data, elemsz);
+ if (i >= n) crn_die_range(i, n);
+}
+
+void crn_check_range(uint64_t i, uint64_t n) {
+ if (i >= n) crn_die_range(i, n);
+}
+
+/* ---- relaxed atomic scalar access ---- */
+
+#if defined(__GNUC__) || defined(__clang__)
+#define CRN_LD(P) __atomic_load_n((P), __ATOMIC_RELAXED)
+#define CRN_ST(P, V) __atomic_store_n((P), (V), __ATOMIC_RELAXED)
+#else
+/* fallback for hosts without the builtins: plain access (single aligned
+ scalar loads/stores do not tear on such hosts in practice) */
+#define CRN_LD(P) (*(P))
+#define CRN_ST(P, V) (*(P) = (V))
+#endif
+
+uint8_t crn_ld_u8(const uint8_t* p) { return CRN_LD((uint8_t*)p); }
+uint16_t crn_ld_u16(const uint16_t* p) { return CRN_LD((uint16_t*)p); }
+uint32_t crn_ld_u32(const uint32_t* p) { return CRN_LD((uint32_t*)p); }
+uint64_t crn_ld_u64(const uint64_t* p) { return CRN_LD((uint64_t*)p); }
+float crn_ld_f32(const float* p) {
+ uint32_t b = crn_ld_u32((const uint32_t*)(const void*)p);
+ float v;
+ memcpy(&v, &b, sizeof v);
+ return v;
+}
+double crn_ld_f64(const double* p) {
+ uint64_t b = crn_ld_u64((const uint64_t*)(const void*)p);
+ double v;
+ memcpy(&v, &b, sizeof v);
+ return v;
+}
+void crn_st_u8(uint8_t* p, uint8_t v) { CRN_ST(p, v); }
+void crn_st_u16(uint16_t* p, uint16_t v) { CRN_ST(p, v); }
+void crn_st_u32(uint32_t* p, uint32_t v) { CRN_ST(p, v); }
+void crn_st_u64(uint64_t* p, uint64_t v) { CRN_ST(p, v); }
+void crn_st_f32(float* p, float v) {
+ uint32_t b;
+ memcpy(&b, &v, sizeof b);
+ crn_st_u32((uint32_t*)(void*)p, b);
+}
+void crn_st_f64(double* p, double v) {
+ uint64_t b;
+ memcpy(&b, &v, sizeof b);
+ crn_st_u64((uint64_t*)(void*)p, b);
+}
+
+/* ---- atomic exchange (acquire-release) ---- */
+
+uint8_t crn_xch_u8(uint8_t* p, uint8_t v) {
+#if defined(__GNUC__) || defined(__clang__)
+ return __atomic_exchange_n(p, v, __ATOMIC_ACQ_REL);
+#else
+ /* fallback for hosts without the builtins: a plain swap (correct for
+ a single context; such hosts are not targets for racing programs) */
+ uint8_t t = *p;
+ *p = v;
+ return t;
+#endif
+}
+uint16_t crn_xch_u16(uint16_t* p, uint16_t v) {
+#if defined(__GNUC__) || defined(__clang__)
+ return __atomic_exchange_n(p, v, __ATOMIC_ACQ_REL);
+#else
+ uint16_t t = *p;
+ *p = v;
+ return t;
+#endif
+}
+uint32_t crn_xch_u32(uint32_t* p, uint32_t v) {
+#if defined(__GNUC__) || defined(__clang__)
+ return __atomic_exchange_n(p, v, __ATOMIC_ACQ_REL);
+#else
+ uint32_t t = *p;
+ *p = v;
+ return t;
+#endif
+}
+uint64_t crn_xch_u64(uint64_t* p, uint64_t v) {
+#if defined(__GNUC__) || defined(__clang__)
+ return __atomic_exchange_n(p, v, __ATOMIC_ACQ_REL);
+#else
+ uint64_t t = *p;
+ *p = v;
+ return t;
+#endif
+}
+float crn_xch_f32(float* p, float v) {
+ uint32_t b, nv;
+ memcpy(&nv, &v, sizeof nv);
+ b = crn_xch_u32((uint32_t*)(void*)p, nv);
+ float r;
+ memcpy(&r, &b, sizeof r);
+ return r;
+}
+double crn_xch_f64(double* p, double v) {
+ uint64_t b, nv;
+ memcpy(&nv, &v, sizeof nv);
+ b = crn_xch_u64((uint64_t*)(void*)p, nv);
+ double r;
+ memcpy(&r, &b, sizeof r);
+ return r;
+}
+
+/* ---- conversions ---- */
+
+int64_t crn_d2i64(double d) {
+ if (d >= 9223372036854775808.0) return INT64_MAX;
+ if (d <= -9223372036854775808.0) return INT64_MIN;
+ return (int64_t)d;
+}
+
+uint64_t crn_d2u64(double d) {
+ if (d >= 18446744073709551616.0) return UINT64_MAX;
+ if (d <= 0.0) return 0;
+ return (uint64_t)d;
+}
+
+int64_t crn_d2i8(double d) {
+ if (d >= 127.0) return 127;
+ if (d <= -128.0) return -128;
+ return (int64_t)(int8_t)d;
+}
+
+int64_t crn_d2i16(double d) {
+ if (d >= 32767.0) return 32767;
+ if (d <= -32768.0) return -32768;
+ return (int64_t)(int16_t)d;
+}
+
+int64_t crn_d2i32(double d) {
+ if (d >= 2147483648.0) return 2147483647;
+ if (d <= -2147483649.0) return -2147483648;
+ return (int64_t)(int32_t)d;
+}
+
+uint64_t crn_d2u8(double d) {
+ if (d >= 255.0) return 255;
+ if (d <= 0.0) return 0;
+ return (uint64_t)(uint8_t)d;
+}
+
+uint64_t crn_d2u16(double d) {
+ if (d >= 65535.0) return 65535;
+ if (d <= 0.0) return 0;
+ return (uint64_t)(uint16_t)d;
+}
+
+uint64_t crn_d2u32(double d) {
+ if (d >= 4294967296.0) return 4294967295ULL;
+ if (d <= 0.0) return 0;
+ return (uint64_t)(uint32_t)d;
+}
+
+/* ---- output ---- */
+
+static void crn_out_str(const char* s) { fputs(s, cr_ctx.out); }
+
+void crn_out_i64(int64_t v) { fprintf(cr_ctx.out, "%lld", (long long)v); }
+void crn_out_u64(uint64_t v) { fprintf(cr_ctx.out, "%llu", (unsigned long long)v); }
+
+static void crn_out_f(double v, int maxprec) {
+ char buf[64];
+ if (v != v) { /* NaN: the sign is meaningless; print one spelling */
+ crn_out_str("nan");
+ return;
+ }
+ for (int prec = 1; prec <= maxprec; prec++) {
+ snprintf(buf, sizeof buf, "%.*g", prec, v);
+ if (strtod(buf, NULL) == v) break;
+ }
+ crn_out_str(buf);
+}
+
+void crn_out_f64(double v) { crn_out_f(v, 17); }
+void crn_out_f32(float v) {
+ char buf[64];
+ if (v != v) {
+ crn_out_str("nan");
+ return;
+ }
+ for (int prec = 1; prec <= 9; prec++) {
+ snprintf(buf, sizeof buf, "%.*g", prec, v);
+ if ((float)strtod(buf, NULL) == v) break;
+ }
+ crn_out_str(buf);
+}
+void crn_out_bool(int b) { crn_out_str(b ? "true" : "false"); }
+void crn_out_char(unsigned c) { fputc((int)(c & 0xFF), cr_ctx.out); }
+void crn_out_addr(uint64_t a) { fprintf(cr_ctx.out, "0x%llx", (unsigned long long)a); }
+
+void crn_out_text(uint8_t* p) {
+ FILE* f = cr_ctx.out;
+ while (*p) fputc(*p++, f);
+}
+
+/* ---- input / files / system ---- */
+
+int64_t crn_input(void) {
+ int c = fgetc(cr_ctx.in);
+ if (c == EOF) return -1;
+ return c;
+}
+
+int64_t crn_open(uint8_t* path, int mode) {
+ const char* m;
+ switch (mode) {
+ case 0: m = "rb"; break;
+ case 1: m = "wb"; break;
+ case 2: m = "w+b"; break;
+ case 3: m = "ab"; break;
+ default: return -1;
+ }
+ FILE* f = fopen((const char*)path, m);
+ if (!f) return -1;
+ /* find a free slot; handles are slot+1 */
+ for (int i = 0; i < CR_FILES_MAX; i++) {
+ if (cr_ctx.files[i]) continue;
+ cr_ctx.files[i] = f;
+ cr_ctx.fmodes[i] = (unsigned char)mode;
+ return i + 1;
+ }
+ fclose(f);
+ return -1; /* table full */
+}
+
+void crn_use(int64_t h) {
+ if (h == 0) {
+ cr_ctx.in = stdin;
+ cr_ctx.out = stdout;
+ return;
+ }
+ if (h < 1 || h > CR_FILES_MAX || !cr_ctx.files[h - 1])
+ crn_die("use of a handle that is not open in this context "
+ "(handles come from 'open'; 0 means the standard streams)");
+ FILE* f = cr_ctx.files[h - 1];
+ switch (cr_ctx.fmodes[h - 1]) {
+ case 0: cr_ctx.in = f; break; /* read */
+ case 2: cr_ctx.in = f; cr_ctx.out = f; break; /* both */
+ default: cr_ctx.out = f; break; /* write / append */
+ }
+}
+
+void crn_flush(void) {
+ if (fflush(cr_ctx.out) != 0) crn_die("flush failed");
+}
+
+int64_t crn_seek(int64_t off) {
+ FILE* a = cr_ctx.in;
+ FILE* b = cr_ctx.out;
+ int any = 0;
+ int bad = 0;
+ /* standard streams are not positionable; only handle streams are */
+ if (a && a != stdin) {
+ any = 1;
+ if (fseek(a, (long)off, SEEK_SET) != 0) bad = 1;
+ }
+ if (b && b != a && b != stdout) {
+ any = 1;
+ if (fseek(b, (long)off, SEEK_SET) != 0) bad = 1;
+ }
+ if (!any)
+ crn_die("seek requires a stream routed through an open handle "
+ "('use' a handle from 'open' first)");
+ return bad ? -1 : 0;
+}
+
+int64_t crn_close(int64_t h) {
+ if (h == 0)
+ crn_die("close of handle 0 (the standard streams cannot be closed)");
+ if (h < 1 || h > CR_FILES_MAX || !cr_ctx.files[h - 1])
+ crn_die("close of a handle that is not open in this context "
+ "(handles come from 'open' and are closed once)");
+ FILE* f = cr_ctx.files[h - 1];
+ cr_ctx.files[h - 1] = NULL;
+ if (cr_ctx.in == f) cr_ctx.in = stdin;
+ if (cr_ctx.out == f) cr_ctx.out = stdout;
+ return fclose(f) == 0 ? 0 : -1;
+}
+
+int64_t crn_system(uint8_t* cmd) { return (int64_t)system((const char*)cmd); }
+
+void crn_stop(int64_t code) { exit((int)(code & 0xFF)); }
+
+/* ---- concurrency ---- */
+
+typedef struct CrSpawnArg {
+ CrFnRef* fn;
+} CrSpawnArg;
+
+static void* crn_thread_main(void* argp) {
+ CrSpawnArg* a = argp;
+ CrFnRef* fn = a->fn;
+ free(a);
+ crn_init();
+ fn->fn();
+ return NULL;
+}
+
+void crn_spawn(CrFnRef* f) {
+ if (f->np != 0)
+ crn_die("spawn of a transformation that takes parameters");
+ if (cr_ctx.nspawned >= CR_SPAWN_MAX) {
+ char msg[80];
+ snprintf(msg, sizeof msg,
+ "too many outstanding spawned contexts (limit %d)", CR_SPAWN_MAX);
+ crn_die(msg);
+ }
+ CrSpawnArg* a = malloc(sizeof(CrSpawnArg));
+ if (!a) crn_die("out of memory");
+ a->fn = f;
+ if (pthread_create(&cr_ctx.spawned[cr_ctx.nspawned++], NULL, crn_thread_main, a) != 0)
+ crn_die("thread creation failed");
+}
+
+void crn_wait(void) {
+ for (int i = 0; i < cr_ctx.nspawned; i++)
+ pthread_join(cr_ctx.spawned[i], NULL);
+ cr_ctx.nspawned = 0;
+}
+
+
+static uint8_t g_nl[2] = {0};
+static int8_t g_c8 = 0;
+static uint8_t g_u8 = 0;
+static int16_t g_c16 = 0;
+static uint16_t g_u16 = 0;
+static int32_t g_c32 = 0;
+static uint32_t g_u32 = 0;
+static int64_t g_c64 = 0;
+static uint64_t g_u64 = 0;
+static uint8_t g_b = 0;
+static uint8_t g_ch = 0;
+static float g_f32 = 0;
+static double g_f64 = 0;
+static const uint8_t g_nl_init[] = "\n\000";
+
+static Val cr_fn_newline(void);
+static Val cr_fn_main(void);
+
+static CrFnRef cr_ref_newline = {(Val(*)(void))cr_fn_newline, 0};
+static CrFnRef cr_ref_main = {(Val(*)(void))cr_fn_main, 0};
+
+static void crn_init_world(void) {
+ memcpy(g_nl, g_nl_init, 2);
+}
+
+static Val cr_fn_newline(void) {
+ uint64_t cr_fh0 = cr_ctx.fh_sp;
+ int cr_cmp = 0;
+ Val wv; wv.u = 0;
+ void* F = 0;
+ wv.u = (uint64_t)(uintptr_t)&g_nl;
+ crn_out_text((uint8_t*)(uintptr_t)wv.u);
+ wv.u = (uint64_t)0ULL;
+ cr_ctx.fh_sp = cr_fh0; return wv;
+ cr_ctx.fh_sp = cr_fh0; return wv;
+}
+
+static Val cr_fn_main(void) {
+ uint64_t cr_fh0 = cr_ctx.fh_sp;
+ int cr_cmp = 0;
+ Val wv; wv.u = 0;
+ void* F = 0;
+ wv.u = (uint64_t)18446744073709551607ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_c8;
+ crn_st_u8((uint8_t*)F, (uint8_t)(int64_t)wv.u);
+ wv.u = (uint64_t)47ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_c8;
+ wv.i = (int64_t)(int8_t)wv.u;
+ wv.i = (int64_t)(int8_t)crn_xch_u8((uint8_t*)F, (int8_t)wv.i);
+ wv.i = (int64_t)(int64_t)wv.u;
+ crn_out_i64((int64_t)wv.u);
+ cr_fn_newline();
+ cr_ctx.fh_sp = cr_fh0; F = &g_c8;
+ wv.i = (int64_t)(int8_t)crn_ld_u8((const uint8_t*)F);
+ wv.i = (int64_t)(int64_t)wv.u;
+ crn_out_i64((int64_t)wv.u);
+ cr_fn_newline();
+ wv.u = (uint64_t)200ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_u8;
+ crn_st_u8((uint8_t*)F, (uint8_t)(int64_t)wv.u);
+ wv.u = (uint64_t)1ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_u8;
+ wv.u = (uint64_t)(uint8_t)wv.u;
+ wv.u = (uint64_t)(uint8_t)crn_xch_u8((uint8_t*)F, (uint8_t)wv.u);
+ wv.i = (int64_t)(int64_t)wv.u;
+ crn_out_i64((int64_t)wv.u);
+ cr_fn_newline();
+ cr_ctx.fh_sp = cr_fh0; F = &g_u8;
+ wv.u = (uint64_t)crn_ld_u8((const uint8_t*)F);
+ wv.i = (int64_t)(int64_t)wv.u;
+ crn_out_i64((int64_t)wv.u);
+ cr_fn_newline();
+ wv.u = (uint64_t)18446744073709550616ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_c16;
+ crn_st_u16((uint16_t*)F, (uint16_t)(int64_t)wv.u);
+ wv.u = (uint64_t)1000ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_c16;
+ wv.i = (int64_t)(int16_t)wv.u;
+ wv.i = (int64_t)(int16_t)crn_xch_u16((uint16_t*)F, (int16_t)wv.i);
+ wv.i = (int64_t)(int64_t)wv.u;
+ crn_out_i64((int64_t)wv.u);
+ cr_fn_newline();
+ wv.u = (uint64_t)60000ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_u16;
+ crn_st_u16((uint16_t*)F, (uint16_t)(int64_t)wv.u);
+ wv.u = (uint64_t)60001ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_u16;
+ wv.u = (uint64_t)(uint16_t)wv.u;
+ wv.u = (uint64_t)(uint16_t)crn_xch_u16((uint16_t*)F, (uint16_t)wv.u);
+ wv.i = (int64_t)(int64_t)wv.u;
+ crn_out_i64((int64_t)wv.u);
+ cr_fn_newline();
+ wv.u = (uint64_t)18446744071709551616ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_c32;
+ crn_st_u32((uint32_t*)F, (uint32_t)(int64_t)wv.u);
+ wv.u = (uint64_t)7ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_c32;
+ wv.i = (int64_t)(int32_t)wv.u;
+ wv.i = (int64_t)(int32_t)crn_xch_u32((uint32_t*)F, (int32_t)wv.i);
+ wv.i = (int64_t)(int64_t)wv.u;
+ crn_out_i64((int64_t)wv.u);
+ cr_fn_newline();
+ wv.u = (uint64_t)4000000000ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_u32;
+ crn_st_u32((uint32_t*)F, (uint32_t)(int64_t)wv.u);
+ wv.u = (uint64_t)8ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_u32;
+ wv.u = (uint64_t)(uint32_t)wv.u;
+ wv.u = (uint64_t)(uint32_t)crn_xch_u32((uint32_t*)F, (uint32_t)wv.u);
+ wv.i = (int64_t)(int64_t)wv.u;
+ crn_out_i64((int64_t)wv.u);
+ cr_fn_newline();
+ wv.u = (uint64_t)18446744068709551616ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_c64;
+ crn_st_u64((uint64_t*)F, (uint64_t)(int64_t)wv.u);
+ wv.u = (uint64_t)5000000000ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_c64;
+ wv.i = (int64_t)(int64_t)wv.u;
+ wv.i = (int64_t)(int64_t)crn_xch_u64((uint64_t*)F, (int64_t)wv.i);
+ crn_out_i64((int64_t)wv.u);
+ cr_fn_newline();
+ wv.u = (uint64_t)18000000000000000000ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_u64;
+ crn_st_u64((uint64_t*)F, (uint64_t)(int64_t)wv.u);
+ wv.u = (uint64_t)2ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_u64;
+ wv.u = (uint64_t)(uint64_t)wv.u;
+ wv.u = (uint64_t)(uint64_t)crn_xch_u64((uint64_t*)F, (uint64_t)wv.u);
+ crn_out_u64(wv.u);
+ cr_fn_newline();
+ wv.u = (uint64_t)0ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_b;
+ crn_st_u8((uint8_t*)F, (uint8_t)wv.u);
+ wv.u = (uint64_t)1ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_b;
+ wv.u = (uint64_t)(uint8_t)crn_xch_u8((uint8_t*)F, (uint8_t)wv.u);
+ crn_out_bool((int)wv.u);
+ cr_fn_newline();
+ cr_ctx.fh_sp = cr_fh0; F = &g_b;
+ wv.u = (uint64_t)crn_ld_u8((const uint8_t*)F);
+ crn_out_bool((int)wv.u);
+ cr_fn_newline();
+ wv.u = (uint64_t)81ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_ch;
+ crn_st_u8((uint8_t*)F, (uint8_t)wv.u);
+ wv.u = (uint64_t)90ULL;
+ cr_ctx.fh_sp = cr_fh0; F = &g_ch;
+ wv.u = (uint64_t)(uint8_t)crn_xch_u8((uint8_t*)F, (uint8_t)wv.u);
+ crn_out_char((unsigned)wv.u);
+ cr_fn_newline();
+ wv.d = (double)1.5;
+ cr_ctx.fh_sp = cr_fh0; F = &g_f32;
+ crn_st_f32((float*)F, (float)wv.d);
+ wv.d = (double)2.75;
+ cr_ctx.fh_sp = cr_fh0; F = &g_f32;
+ wv.d = (double)(float)wv.d;
+ wv.d = (double)crn_xch_f32((float*)F, (float)wv.d);
+ crn_out_f32((float)wv.d);
+ cr_fn_newline();
+ cr_ctx.fh_sp = cr_fh0; F = &g_f32;
+ wv.d = (double)crn_ld_f32((const float*)F);
+ crn_out_f32((float)wv.d);
+ cr_fn_newline();
+ wv.d = (double)0.125;
+ cr_ctx.fh_sp = cr_fh0; F = &g_f64;
+ crn_st_f64((double*)F, wv.d);
+ wv.d = (double)6.5;
+ cr_ctx.fh_sp = cr_fh0; F = &g_f64;
+ wv.d = crn_xch_f64((double*)F, wv.d);
+ crn_out_f64(wv.d);
+ cr_fn_newline();
+ wv.u = (uint64_t)0ULL;
+ cr_ctx.fh_sp = cr_fh0; return wv;
+ cr_ctx.fh_sp = cr_fh0; return wv;
+}
+
+int main(void) {
+ crn_init();
+ crn_init_world();
+ cr_fn_main();
+ fflush(NULL);
+ return 0;
+}
diff --git a/tests/34_xchgwidths.cryn b/tests/34_xchgwidths.cryn
new file mode 100644
index 0000000..dd46880
--- /dev/null
+++ b/tests/34_xchgwidths.cryn
@@ -0,0 +1,171 @@
+[# exchange on every scalar width: the old cell value leaves, the new
+ one lands, and the type rules hold for each width #]
+make string "\n" nl
+make int8 c8
+make uint8 u8
+make int16 c16
+make uint16 u16
+make int32 c32
+make uint32 u32
+make int64 c64
+make uint64 u64
+make bool b
+make char ch
+make float32 f32
+make float64 f64
+
+define newline result nothing
+ address nl
+ output text
+ set nothing
+ return
+end
+
+define main
+ [# int8 #]
+ set -9
+ move c8
+ write
+ set 47
+ move c8
+ exchange
+ convert int64
+ output
+ call newline
+ move c8
+ read
+ convert int64
+ output
+ call newline
+
+ [# uint8 #]
+ set 200
+ move u8
+ write
+ set 1
+ move u8
+ exchange
+ convert int64
+ output
+ call newline
+ move u8
+ read
+ convert int64
+ output
+ call newline
+
+ [# int16 #]
+ set -1000
+ move c16
+ write
+ set 1000
+ move c16
+ exchange
+ convert int64
+ output
+ call newline
+
+ [# uint16 #]
+ set 60000
+ move u16
+ write
+ set 60001
+ move u16
+ exchange
+ convert int64
+ output
+ call newline
+
+ [# int32 #]
+ set -2000000000
+ move c32
+ write
+ set 7
+ move c32
+ exchange
+ convert int64
+ output
+ call newline
+
+ [# uint32 #]
+ set 4000000000
+ move u32
+ write
+ set 8
+ move u32
+ exchange
+ convert int64
+ output
+ call newline
+
+ [# int64 #]
+ set -5000000000
+ move c64
+ write
+ set 5000000000
+ move c64
+ exchange
+ output
+ call newline
+
+ [# uint64 #]
+ set 18000000000000000000
+ move u64
+ write
+ set 2
+ move u64
+ exchange
+ output
+ call newline
+
+ [# bool #]
+ set false
+ move b
+ write
+ set true
+ move b
+ exchange
+ output
+ call newline
+ move b
+ read
+ output
+ call newline
+
+ [# char #]
+ set 'Q'
+ move ch
+ write
+ set 'Z'
+ move ch
+ exchange
+ output
+ call newline
+
+ [# float32 #]
+ set 1.5
+ move f32
+ write
+ set 2.75
+ move f32
+ exchange
+ output
+ call newline
+ move f32
+ read
+ output
+ call newline
+
+ [# float64 #]
+ set 0.125
+ move f64
+ write
+ set 6.5
+ move f64
+ exchange
+ output
+ call newline
+
+ set nothing
+ return
+end
diff --git a/tests/35_seekedge.cryn b/tests/35_seekedge.cryn
new file mode 100644
index 0000000..2eb9bb2
--- /dev/null
+++ b/tests/35_seekedge.cryn
@@ -0,0 +1,129 @@
+[# seek edges: offset in the working value and as a literal, seeking
+ before the start fails without moving the position, seeking past the
+ end reads end of file and writes extend the file #]
+define main
+ make string "cryn_test_35.txt" path
+ make int64 h
+ make int64 r1
+ make int64 r2
+ make int64 r3
+
+ address path
+ open both
+ home
+ compare 0
+ jump bad when less
+ move h
+ write
+ push
+ use
+ home
+
+ make string "abcdef" data
+ address data
+ output text
+ flush
+
+ [# literal form: land on byte 3 and read it #]
+ seek 3
+ home
+ compare 0
+ jump ok1 when equal
+ set 1
+ stop
+ mark ok1
+ input
+ move r1
+ write
+
+ [# working value form: land on byte 0 and read it #]
+ set 0
+ seek
+ home
+ compare 0
+ jump ok2 when equal
+ set 1
+ stop
+ mark ok2
+ input
+ move r2
+ write
+
+ [# negative offset fails, reports -1, position stays put at byte 1 #]
+ set -1
+ seek
+ home
+ compare -1
+ jump ok3 when equal
+ set 1
+ stop
+ mark ok3
+ input
+ move r3
+ write
+
+ [# past the end reads end of file #]
+ seek 100
+ home
+ compare 0
+ jump ok4 when equal
+ set 1
+ stop
+ mark ok4
+ input
+ compare -1
+ jump past when equal
+ set 1
+ stop
+ mark past
+
+ [# writing there extends the file: byte 100 becomes G over a gap #]
+ set 71
+ convert uint8
+ output
+ flush
+
+ pop
+ close
+ use 0
+
+ move r1
+ read
+ convert uint8
+ output
+ move r2
+ read
+ convert uint8
+ output
+ move r3
+ read
+ convert uint8
+ output
+
+ [# read the whole file back: six letters, one gap byte, one G #]
+ address path
+ open read
+ move h
+ write
+ push
+ use
+ home
+ set 0
+ mark rd
+ input
+ compare -1
+ jump fin when equal
+ convert uint8
+ output
+ jump rd
+ mark fin
+
+ pop
+ close
+ set 0
+ stop
+
+ mark bad
+ set 1
+ stop
+end
diff --git a/tests/expected/31_seek.txt b/tests/expected/31_seek.txt
new file mode 100644
index 0000000..8042f2b
--- /dev/null
+++ b/tests/expected/31_seek.txt
@@ -0,0 +1 @@
+0ABABCXE
\ No newline at end of file
diff --git a/tests/expected/32_cas.txt b/tests/expected/32_cas.txt
new file mode 100644
index 0000000..e3b5acb
--- /dev/null
+++ b/tests/expected/32_cas.txt
@@ -0,0 +1 @@
+107
\ No newline at end of file
diff --git a/tests/expected/33_conversions.txt b/tests/expected/33_conversions.txt
new file mode 100644
index 0000000..3335444
--- /dev/null
+++ b/tests/expected/33_conversions.txt
@@ -0,0 +1,21 @@
+44
+255
+-126
+65535
+34464
+18446744073709551615
+705032704
+7
+-3
+255
+0
+1000000000000000000
+9223372036854775807
+0
+0.1
+3.1415927
+16777216
+true
+false
+true
+false
diff --git a/tests/expected/34_xchgwidths.txt b/tests/expected/34_xchgwidths.txt
new file mode 100644
index 0000000..6ffb08b
--- /dev/null
+++ b/tests/expected/34_xchgwidths.txt
@@ -0,0 +1,16 @@
+-9
+47
+200
+1
+-1000
+60000
+-2000000000
+4000000000
+-5000000000
+18000000000000000000
+false
+true
+Q
+1.5
+2.75
+0.125
diff --git a/tests/expected/35_seekedge.txt b/tests/expected/35_seekedge.txt
new file mode 100644
index 0000000..7285fb0
Binary files /dev/null and b/tests/expected/35_seekedge.txt differ
diff --git a/tests/run.sh b/tests/run.sh
index d51c3e4..9b19bcb 100644
--- a/tests/run.sh
+++ b/tests/run.sh
@@ -1,9 +1,11 @@
#!/bin/sh
# Cryn test suite: compile and run each tests/*.cryn and compare with
# tests/expected/<name>.txt. Error tests in tests/errors must fail to compile.
+# CRYN_ARGS: extra flags passed to the compiler (e.g. --release).
set -u
cd "$(dirname "$0")"
CRYN=../cryn
+CRYN_ARGS=${CRYN_ARGS:-}
pass=0
fail=0
@@ -12,9 +14,9 @@ for src in *.cryn; do
exp="expected/$name.txt"
[ -f "$exp" ] || continue
if [ -f "input/$name.txt" ]; then
- out=$($CRYN run "$src" < "input/$name.txt" 2>/dev/null)
+ out=$($CRYN run "$src" $CRYN_ARGS < "input/$name.txt" 2>/dev/null)
else
- out=$($CRYN run "$src" 2>/dev/null)
+ out=$($CRYN run "$src" $CRYN_ARGS 2>/dev/null)
fi
if [ "$out" = "$(cat "$exp")" ]; then
pass=$((pass + 1))